This commit is contained in:
Viet An
2026-05-12 15:13:43 +07:00
parent f1ecd5c7ff
commit 336c8c9036
25 changed files with 1195 additions and 852 deletions

View File

@@ -22,22 +22,15 @@
v-if="title"
class="modal-card-head px-4 py-3"
>
<div class="w-full">
<div class="field is-grouped is-align-items-center">
<div class="control is-expanded has-text-left">
<p
class="fs-17 font-semibold has-text-primary"
class="fs-17 font-semibold has-text-primary control is-expanded has-text-left modal-card-title"
v-html="title"
></p>
</div>
<div class="control has-text-right">
<button
class="delete is-medium"
aria-label="close"
@click="closeModal()"
></button>
</div>
</div>
</div>
</header>
<section
class="modal-card-body p-4"
@@ -104,7 +97,7 @@ const docid = $id();
let count = 0;
const lock = false;
const closeModal = function () {
const closeModal = () => {
if (!lock) emit("close");
};
@@ -129,7 +122,8 @@ const doClick = function (e) {
};
onMounted(() => {
if (Object.values(props).some((x) => isNotNil(x))) {
const modalHasProps = Object.values(props).some((x) => isNotNil(x));
if (modalHasProps) {
document.documentElement.classList.add("is-clipped");
window.addEventListener("keydown", (e) => {
if (e.key === "Escape") closeModal();

View File

@@ -34,13 +34,13 @@
<span class="icon is-left">
<Icon
name="material-symbols:search"
:size="22"
:size="20"
/>
</span>
<span class="icon is-right">
<Icon
name="material-symbols:keyboard-arrow-down-rounded"
:size="22"
:size="20"
/>
</span>
</div>
@@ -54,14 +54,8 @@
class="dropdown-content px-3"
style="min-width: 100%"
>
<p
v-if="data.length === 0"
class="has-text-grey px-2 py-1"
>
{{ isVietnamese ? "Không có giá trị thỏa mãn" : "No matching values" }}
</p>
<ScrollBox
v-else
v-if="data.length > 0"
v-bind="{
data: data,
name: field,
@@ -71,34 +65,55 @@
}"
@selected="choose"
/>
<p
v-else
class="has-text-grey px-2 py-1"
>
<Icon
v-if="isLoading"
name="svg-spinners:90-ring"
:size="22"
/>
<span v-else>Không có giá trị thoả mãn</span>
</p>
</div>
</div>
</div>
</div>
<div
class="control"
v-if="clearable && value"
class="control"
>
<button
class="button is-primary px-2"
class="button is-light"
@click="clearValue"
style="height: 100%"
type="button"
>
<SvgIcon v-bind="{ name: 'close.svg', type: 'white', size: 24 }"></SvgIcon>
<span class="icon">
<Icon
name="material-symbols:close-rounded"
:size="16"
/>
</span>
</button>
</div>
<div
class="control"
v-if="viewaddon"
class="control"
>
<button
class="button is-dark px-2"
class="button is-primary is-light"
@click="viewInfo()"
style="height: 100%"
type="button"
>
<SvgIcon v-bind="{ name: 'view.svg', type: 'white', size: 24 }"></SvgIcon>
<span class="icon">
<Icon
name="material-symbols:visibility-rounded"
:size="20"
/>
</span>
</button>
</div>
<div
@@ -106,15 +121,15 @@
v-if="addon"
>
<button
class="button is-primary"
class="button is-success is-light"
@click="addNew()"
style="height: 100%"
type="button"
>
<span class="icon">
<Icon
name="material-symbols:add-2-rounded"
:size="22"
name="material-symbols:add-rounded"
:size="18"
/>
</span>
</button>
@@ -124,8 +139,7 @@
@dataevent="dataevent"
@close="showmodal = undefined"
v-bind="showmodal"
v-if="showmodal"
></Modal>
/>
</div>
</template>
@@ -157,6 +171,7 @@ export default {
return {
search: undefined,
data: [],
isLoading: false,
timer: undefined,
value: undefined,
selected: undefined,
@@ -266,8 +281,10 @@ export default {
}
},
async getData() {
this.isLoading = false;
this.params.filter = this.filter;
let data = await this.$getdata(this.api, undefined, this.params);
this.isLoading = true;
return data;
},
async getApi(val) {
@@ -373,4 +390,11 @@ export default {
.field:not(:last-child) {
margin-bottom: 0;
}
.button.is-light {
--bulma-button-background-l: 89%;
}
.button:hover,
.button.is-hovered {
--bulma-button-background-l-delta: -10%;
}
</style>

View File

@@ -1,11 +1,12 @@
<template>
<div class="control has-icons-left">
<div class="field has-addons">
<p class="control has-icons-left has-icons-right is-expanded">
<input
type="text"
v-model="value"
@keyup="doCheck"
:class="['input', disabled && 'has-text-black']"
:placeholder="placeholder || ''"
@keyup="doCheck"
:disabled="disabled"
/>
<span class="icon is-left">
@@ -14,11 +15,20 @@
:size="22"
/>
</span>
</p>
<p
v-if="unit"
class="control"
>
<a class="button is-static fs-12 h-full">
{{ unit }}
</a>
</p>
</div>
</template>
<script>
export default {
props: ["record", "attr", "placeholder", "disabled", "defaultValue"],
props: ["record", "attr", "placeholder", "disabled", "defaultValue", "unit"],
data() {
return {
value: this.getInitialValue(),

View File

@@ -192,7 +192,7 @@
>
<span class="icon">
<Icon
name="material-symbols:add-2-rounded"
name="material-symbols:add-rounded"
:size="22"
/>
</span>
@@ -210,7 +210,7 @@
>
<span class="icon">
<Icon
name="material-symbols:settings-outline-rounded"
name="material-symbols:table-edit-outline"
:size="22"
/>
</span>
@@ -240,8 +240,8 @@
>
</p>
</div>
<div class="tabs is-toggle">
<ul class="is-flex-grow-0 mx-auto">
<div class="tabs is-toggle is-centered">
<ul>
<li
v-for="(v, i) in getMenu().filter((x) =>
currentField.format === 'number'
@@ -254,32 +254,31 @@
:class="selectTab.code === v.code ? 'is-active' : 'has-text-primary'"
@click="changeTab(v)"
>
<a class="px-4 py-1.5">{{ v.name }}</a>
<a class="px-8 py-1.5 fs-14">{{ v.name }}</a>
</li>
</ul>
</div>
<div v-if="currentTab === 'detail'">
<p class="fs-14 mt-3">
<strong> Tên trường: </strong> {{ currentField.name }}
<a @click="copyContent(currentField.name)">
<span class="tooltip">
<SvgIcon
class="ml-1"
v-bind="{ name: 'copy.svg', type: 'primary', size: 16 }"
></SvgIcon>
<span
class="tooltiptext"
style="top: 110%; bottom: unset; min-width: max-content; left: 25px"
>Copy</span
<div class="fs-14 mt-3 is-flex is-gap-0.5 is-align-items-center">
<p class="font-semibold">Tên trường:</p>
<p>{{ currentField.name }}</p>
<button
class="button is-white is-small"
@click="copyContent(currentField.name)"
>
<span class="icon">
<Icon
name="material-symbols:content-copy-outline-rounded"
:size="16"
/>
</span>
</a>
</p>
</button>
</div>
<label class="label fs-14 mt-3">Mô tả<span class="has-text-danger"> *</span></label>
<div class="field has-addons">
<div class="control is-expanded">
<input
class="input fs-14"
class="input fs-14 h-full"
type="text"
@change="changeLabel($event.target.value)"
v-model="label"
@@ -287,39 +286,48 @@
</div>
<div class="control">
<button
class="button"
class="button h-full"
@click="editLabel()"
>
<SvgIcon v-bind="{ name: 'pen.svg', type: 'dark', size: 19 }"></SvgIcon>
<span class="icon">
<Icon
name="material-symbols:edit-outline-rounded"
:size="18"
/>
</span>
</button>
</div>
</div>
<p
class="help is-danger"
v-if="errors.find((v) => v.name === 'label')"
class="help is-danger"
>
{{ errors.find((v) => v.name === "label").msg }}
</p>
<div class="field mt-3">
<label class="label fs-14">Kiểu dữ liệu<span class="has-text-danger"> * </span></label>
<div class="control fs-14">
<span
class="mr-4"
v-for="(v, i) in datatype"
<button
v-for="v in datatype"
class="button is-white fs-14"
>
<span
class="icon-text"
v-if="radioType === v"
>
<SvgIcon v-bind="{ name: 'radio-checked.svg', type: 'gray', size: 22 }"></SvgIcon>
</span>
{{ v.name }}
<span class="icon">
<Icon
:name="
radioType === v
? 'material-symbols:radio-button-checked-outline'
: 'material-symbols:radio-button-unchecked'
"
:size="22"
/>
</span>
<span>{{ v.name }}</span>
</button>
</div>
</div>
<div
class="field is-horizontal"
v-if="currentField.format === 'number'"
class="field is-horizontal"
>
<div class="field-body">
<div class="field">
@@ -334,7 +342,7 @@
position: 'is-top-left',
}"
@option="selected('_account', $event)"
></SearchBox>
/>
</div>
<p
class="help has-text-danger"
@@ -361,27 +369,25 @@
<div class="field-body">
<div class="field">
<label class="label fs-14">Định dạng nâng cao</label>
<p class="control fs-14">
<span
class="mr-4"
v-for="(v, i) in colorchoice.filter((v) => v.code !== 'condition')"
>
<a
class="icon-text"
<div class="control">
<button
v-for="v in colorchoice.filter((v) => v.code !== 'condition')"
@click="changeTemplate(v)"
class="button is-white fs-14"
>
<SvgIcon
v-bind="{
name: `radio-${radioTemplate === v.code ? '' : 'un'}checked.svg`,
type: 'gray',
size: 22,
}"
>
</SvgIcon>
</a>
{{ v.name }}
<span class="icon">
<Icon
:name="
radioTemplate === v.code
? 'material-symbols:radio-button-checked-outline'
: 'material-symbols:radio-button-unchecked'
"
:size="22"
/>
</span>
</p>
<span>{{ v.name }}</span>
</button>
</div>
</div>
</div>
</div>
@@ -390,10 +396,10 @@
v-if="radioTemplate === 'option'"
>
<button
class="button is-primary is-small has-text-white"
class="button is-primary is-light fs-14"
@click="showSidebar()"
>
<span class="fs-14">{{ `${currentField.template ? "Sửa" : "Tạo"} định dạng` }}</span>
{{ `${currentField.template ? "Sửa" : "Tạo"} định dạng` }}
</button>
</p>
</div>
@@ -414,7 +420,7 @@
@label="changeLabel"
@updatefields="updateFields"
@close="close"
></Modal>
/>
</template>
<script setup>
import { useStore } from "@/stores/index";
@@ -522,7 +528,6 @@ function doRemove() {
copy.fields.splice(idx, 1);
copy.update = { fields: copy.fields };
store.commit(props.pagename, copy);
emit("close");
}
function fieldList() {
showmodal.value = {
@@ -538,7 +543,7 @@ function tableOption() {
component: "datatable/TableSetting",
vbind: { pagename: props.pagename },
title: "Tùy chọn bảng",
width: "40%",
width: "50%",
height: "400px",
};
}
@@ -556,9 +561,10 @@ const changeLabel = function (text) {
function editLabel() {
showmodal.value = {
component: "datatable/EditLabel",
title: "Điều chỉnh tiêu đề",
width: "500px",
height: "300px",
vbind: { label: label },
vbind: { label },
};
}
const changeTemplate = function (v) {
@@ -598,7 +604,7 @@ const saveSetting = function () {
vbind: { pagename: props.pagename, classify: 4 },
title: "Lưu thiết lập",
width: "500px",
height: "400px",
height: "auto",
};
};
const showSidebar = function () {

View File

@@ -4,33 +4,41 @@
<div class="field-body">
<div class="field">
<label class="label">Đối tượng</label>
<p class="control fs-14">
<b-radio
<div class="control fs-14 is-flex is-gap-2">
<label
v-for="(v, i) in types"
:key="i"
v-model="type"
:native-value="v"
@input="changeType(v)"
class="radio"
>
<input
v-model="type"
@input="changeType(v)"
type="radio"
name="type"
/>
{{ v.name }}
</b-radio>
</p>
</label>
</div>
</div>
<div class="field">
<label class="label">Kích cỡ</label>
<p class="control fs-14">
<b-radio
<div class="control fs-14 is-flex is-gap-2">
<label
v-for="(v, i) in sizes.filter((v) =>
type ? (type.code === 'tag' ? v.code !== 'is-small' : 1 > 0) : true,
)"
:key="i"
v-model="size"
:native-value="v"
@input="changeType(v)"
class="radio"
>
<input
v-model="size"
@input="changeType(v)"
type="radio"
name="size"
/>
{{ v.name }}
</b-radio>
</p>
</label>
</div>
</div>
<div class="field">
<label
@@ -38,20 +46,21 @@
v-if="['tag'].find((v) => v === type.code)"
>Hình khối</label
>
<p
class="control fs-14"
v-if="['tag'].find((v) => v === type.code)"
>
<b-radio
<div class="control fs-14 is-flex is-gap-2">
<label
v-for="(v, i) in shapes"
:key="i"
v-model="shape"
:native-value="v"
@input="changeType(v)"
class="radio"
>
<input
v-model="shape"
@input="changeType(v)"
type="radio"
name="shape"
/>
{{ v.name }}
</b-radio>
</p>
</label>
</div>
</div>
</div>
</div>
@@ -116,7 +125,7 @@
:key="i"
@click="changeTab(v)"
>
<a class="fs-15">{{ v.name }}</a>
<a class="">{{ v.name }}</a>
</li>
</ul>
</div>
@@ -346,68 +355,77 @@
</div>
</template>
<template v-else-if="tab.code === 'template'">
<p class="mb-3">
<a
<div class="buttons mb-3">
<button
@click="copyContent()"
class="mr-6"
class="button is-primary is-light fs-14"
>
<span class="icon-text">
<SvgIcon
class="mr-2"
v-bind="{ name: 'copy.svg', type: 'primary', siz: 18 }"
></SvgIcon>
<span class="fs-16">Copy</span>
<span class="icon">
<Icon
name="material-symbols:content-copy-outline-rounded"
:size="18"
/>
</span>
</a>
<a
<span>Copy</span>
</button>
<button
@click="paste()"
class="mr-6"
class="button is-primary is-light fs-14"
>
<span class="icon-text">
<SvgIcon
class="mr-2"
v-bind="{ name: 'pen1.svg', type: 'primary', siz: 18 }"
></SvgIcon>
<span class="fs-16">Paste</span>
<span class="icon">
<Icon
name="material-symbols:content-paste-rounded"
:size="18"
/>
</span>
</a>
</p>
<span>Paste</span>
</button>
</div>
<div>
<textarea
class="textarea fs-14"
rows="8"
class="textarea fs-13"
style="font-family: monospace"
rows="4"
v-model="text"
@dblclick="doCheck"
></textarea>
</div>
<p class="mt-5">
<span class="icon-text fsb-18">
Replace
<SvgIcon v-bind="{ name: 'right.svg', type: 'dark', size: 22 }"></SvgIcon>
<span class="icon-text">
<span class="fs-17 font-semibold">Replace</span>
<span class="icon">
<Icon
name="material-symbols:arrow-forward-ios-rounded"
:size="18"
/>
</span>
</span>
</p>
<div class="field is-grouped mt-4">
<div class="control">
<p class="fsb-14 mb-1">Đoạn text</p>
<label class="label">Đoạn text</label>
<input
class="input"
type="text"
placeholder=""
v-model="source"
/>
</div>
<div class="control">
<p class="fsb-14 mb-1">Thay bằng</p>
<label class="label">Thay bằng</label>
<input
class="input"
type="text"
placeholder=""
v-model="target"
/>
</div>
<div class="control pl-5">
<div class="control">
<label
class="label"
style="opacity: 0"
>Hidden</label
>
<button
class="button is-primary is-outlined mt-5"
class="button is-primary is-light"
@click="replace()"
>
Replace

View File

@@ -1,20 +1,26 @@
<template>
<div
class="field is-grouped is-grouped-multiline pl-2"
class="field is-grouped is-grouped-multiline is-gap-4 px-2"
v-if="filters ? filters.length > 0 : false"
>
<div class="control mr-5">
<div class="control">
<a
class="button is-primary is-small has-text-white has-text-weight-bold"
class="button is-primary is-light"
@click="updateData({ filters: [] })"
>
<span class="fs-14">Xóa lọc</span>
</a>
</div>
<div class="control pr-2 mr-5">
<div class="control">
<span class="icon-text">
<SvgIcon v-bind="{ name: 'sigma.svg', type: 'primary', size: 20 }"></SvgIcon>
<span class="fsb-18 has-text-primary">{{ totalRows }}</span>
<span class="icon">
<Icon
name="mdi:sigma"
:size="20"
class="has-text-primary"
/>
</span>
<span class="fs-16 font-semibold has-text-primary">{{ totalRows }}</span>
</span>
</div>
<div
@@ -24,16 +30,16 @@
>
<div class="tags has-addons is-marginless">
<a
class="tag is-primary has-text-white is-marginless"
class="tag is-primary is-light is-marginless"
@click="showCondition(v)"
>{{ v.label.indexOf(">") >= 0 ? $stripHtml(v.label, 30) : v.label }}</a
>
<a
class="tag is-delete is-marginless has-text-black-bis"
class="tag is-delete is-marginless"
@click="removeFilter(i)"
></a>
</div>
<span class="help has-text-black-bis">
<span class="help">
{{
v.sort
? v.sort
@@ -59,14 +65,13 @@
v-for="(field, i) in displayFields"
:key="i"
:style="field.headerStyle"
>
<div
class="is-clickable header"
@click="showField(field)"
:style="field.dropStyle"
>
<div :style="field.dropStyle">
<a
v-if="field.label.indexOf('<') < 0"
class="has-text-white font-semibold"
class="font-semibold"
>{{ field.label }}</a
>
<a v-else>
@@ -160,13 +165,12 @@ var currentPage = 1;
var displayFields = ref([]);
var displayData = [];
var pagedata = store[props.pagename];
console.log("props.pagename", props.pagename);
console.log("store", toRaw(store));
console.log("store[props.pagename]", toRaw(store[props.pagename]));
console.log("pagedata.tablesetting", toRaw(pagedata.tablesetting)); // table-font-size is 13
console.log("store.tablesetting", toRaw(store.tablesetting)); // table-font-size is 12
// var tablesetting = $copy(pagedata.tablesetting || store.tablesetting);
var tablesetting = $copy(store.tablesetting);
var tablesetting = $copy(pagedata.tablesetting || store.tablesetting);
if (!Array.isArray(tablesetting)) {
tablesetting = Object.values(tablesetting);
}
// var tablesetting = $copy(store.tablesetting);
// const tablesettingObj = Object.fromEntries(tablesetting.map((v) => [v.code, v]));
var perPage = Number($find(tablesetting, { code: "per-page" }, "detail")) || 20;
var filters = $copy(pagedata.filters || []);
var currentField;
@@ -551,7 +555,10 @@ setTimeout(() => updateShow(), 200);
</script>
<style scoped>
:deep(.table tbody tr:hover td, .table tbody tr:hover th) {
background-color: hsl(0, 0%, 78%);
color: rgb(0, 0, 0);
--bulma-table-row-hover-background-color: var(--bulma-scheme-main-ter);
background-color: var(--bulma-table-row-hover-background-color);
}
.header:hover a {
opacity: 0.75;
}
</style>

View File

@@ -1,12 +1,11 @@
<template>
<div>
<p class="fsb-20 mb-5">Điều chỉnh tiêu đề</p>
<div
v-for="(v, i) in arr"
:key="i"
:class="i > 0 ? 'mt-4' : null"
:class="i > 0 && 'mt-4'"
>
<p class="fsb-14">Dòng thứ {{ i + 1 }}<span class="has-text-danger"> *</span></p>
<p class="font-semibold">Dòng thứ {{ i + 1 }}<span class="has-text-danger"> *</span></p>
<div class="field has-addons mt-1">
<div class="control is-expanded">
<input
@@ -16,23 +15,31 @@
/>
</div>
<div class="control">
<a
class="button px-2 is-primary"
<button
class="button is-success is-light"
@click="add()"
>
<span> <SvgIcon v-bind="{ name: 'add1.png', type: 'white', size: 17 }"></SvgIcon></span>
</a>
<span class="icon">
<Icon
name="material-symbols:add-rounded"
:size="20"
/>
</span>
</button>
</div>
<div
class="control"
@click="remove(i)"
v-if="i > 0"
>
<a class="button px-2 is-dark">
<span>
<SvgIcon v-bind="{ name: 'bin.svg', type: 'white', size: 17 }"></SvgIcon>
<button class="button is-danger is-light">
<span class="icon">
<Icon
name="material-symbols:delete-outline-rounded"
:size="20"
/>
</span>
</a>
</button>
</div>
</div>
<p
@@ -50,10 +57,10 @@
Cập nhật
</button>
<button
class="button is-dark"
class="button is-white"
@click="$emit('close')"
>
Hủy bỏ
Hủy
</button>
</div>
</div>
@@ -72,7 +79,7 @@ export default {
if (!this.$empty(v)) {
let label = v + "</p>";
label = this.$stripHtml(label);
this.arr.push({ label: label });
this.arr.push({ label });
}
});
},

View File

@@ -27,45 +27,56 @@
/>
</div>
</div>
<div class="field is-narrow">
<div class="field is-narrow has-addons">
<p class="control">
<a @click="addAttr()">
<SvgIcon v-bind="{ name: 'add1.png', type: 'gray', size: 18 }"></SvgIcon>
</a>
<a
class="ml-2"
@click="remove(i)"
>
<SvgIcon v-bind="{ name: 'bin1.svg', type: 'gray', size: 18 }"></SvgIcon>
</a>
<a
class="ml-2"
<button
class="button is-primary is-light"
@click="jsonData(v, i)"
>
<SvgIcon v-bind="{ name: 'apps.svg', type: 'gray', size: 18 }"></SvgIcon>
</a>
<span class="icon">
<Icon
name="material-symbols:app-registration-rounded"
:size="20"
/>
</span>
</button>
</p>
<p class="control">
<button
class="button is-primary is-light"
@click="remove(i)"
>
<span class="icon">
<Icon
name="material-symbols:delete-outline-rounded"
:size="20"
/>
</span>
</button>
</p>
</div>
</div>
</div>
</template>
<div
class="mb-6"
v-else
>
<button
class="button is-primary has-text-white"
class="button is-success is-light"
@click="addAttr()"
>
Thêm thuộc tính
<span class="icon">
<Icon
name="material-symbols:add-rounded"
:size="20"
/>
</span>
<span>Thêm thuộc tính</span>
</button>
</div>
<div class="buttons mt-5">
<a
class="button is-primary has-text-white"
<div class="mt-2">
<button
class="button is-primary"
@click="update()"
>Cập nhật</a
>
Cập nhật
</button>
</div>
<Modal
@close="comp = undefined"

View File

@@ -1,55 +1,61 @@
<template>
<!-- v-if="currentsetting ? currentsetting.user === login.id : false" -->
<div
class="mb-4"
v-if="currentsetting ? currentsetting.user === login.id : false"
v-if="currentsetting"
>
<p class="fs-16 has-text-findata">
<p>
Đang mở: <b>{{ $stripHtml(currentsetting.name, 40) }}</b>
</p>
</div>
<div class="field">
<label class="label fs-14">Chọn chế độ lưu <span class="has-text-danger"> * </span></label>
<label class="label">Chọn chế độ lưu <span class="has-text-danger"> * </span></label>
<div class="control is-expanded fs-14">
<a
class="mr-5"
<button
class="button is-white has-text-inherit"
v-if="isOverwrite()"
@click="changeType('overwrite')"
>
<span class="icon-text">
<SvgIcon
v-bind="{
name: radioSave === 'overwrite' ? 'radio-checked.svg' : 'radio-unchecked.svg',
type: 'gray',
size: 22,
}"
></SvgIcon>
Ghi đè
<span class="icon">
<Icon
:name="
radioSave === 'overwrite'
? 'material-symbols:radio-button-checked-outline'
: 'material-symbols:radio-button-unchecked'
"
:size="22"
/>
</span>
</a>
<a @click="changeType('new')">
<span class="icon-text">
<SvgIcon
v-bind="{
name: radioSave === 'new' ? 'radio-checked.svg' : 'radio-unchecked.svg',
type: 'gray',
size: 22,
}"
></SvgIcon>
Tạo mới
<span>Ghi đè</span>
</button>
<button
class="button is-white"
@click="changeType('new')"
>
<span class="icon">
<Icon
:name="
radioSave === 'new'
? 'material-symbols:radio-button-checked-outline'
: 'material-symbols:radio-button-unchecked'
"
:size="22"
/>
</span>
</a>
<span>Tạo mới</span>
</button>
</div>
</div>
<template v-if="radioSave === 'new'">
<div class="field mt-4 px-0 mx-0">
<label class="label fs-14">Tên thiết lập <span class="has-text-danger"> * </span></label>
<div class="field">
<label class="label">Tên thiết lập <span class="has-text-danger"> * </span></label>
<div class="control">
<!-- ref="name" -->
<input
class="input"
type="text"
placeholder=""
v-model="name"
ref="name"
v-on:keyup.enter="saveSetting"
/>
</div>
@@ -60,12 +66,12 @@
{{ errors.find((v) => v.name === "name").msg }}
</div>
</div>
<div class="field mt-4 px-0 mx-0">
<label class="label fs-14"> Mô tả </label>
<div class="field">
<label class="label"> tả </label>
<p class="control is-expanded">
<textarea
class="textarea"
rows="4"
rows="3"
v-model="note"
></textarea>
</p>
@@ -93,11 +99,12 @@
{{ status ? "Lưu thiết lập thành công." : "Lỗi. Lưu thiết lập thất bại." }}
</label>
<p class="control is-expanded">
<a
class="button is-primary has-text-white"
<button
:class="['button is-primary', isLoading && 'is-loading']"
@click="saveSetting()"
>Lưu lại</a
>
Lưu lại
</button>
</p>
</div>
</template>
@@ -106,28 +113,30 @@ import { ref } from "vue";
import { useStore } from "@/stores/index";
const emit = defineEmits([]);
const store = useStore();
var props = defineProps({
const props = defineProps({
pagename: String,
classify: String,
option: String,
data: Object,
focus: Boolean,
});
const { $empty, $copy, $filter, $stripHtml, $updateapi, $insertapi, $findIndex, $snackbar } = useNuxtApp();
var pagename = props.pagename;
var radioOption = ref();
const { $empty, $copy, $stripHtml, $updateapi, $insertapi, $findIndex, $snackbar } = useNuxtApp();
const radioOption = ref();
var login = { id: 1 };
var errors = [];
var radioType = undefined;
var radioDefault = 0;
var radioSave = ref("new");
var note = undefined;
var status = undefined;
var name = undefined;
const errors = ref([]);
const radioType = ref();
const radioDefault = 0;
const radioSave = ref("overwrite");
const name = ref();
const note = ref();
const status = undefined;
var currentsetting = undefined;
var pagename = props.pagename;
var pagedata = store[props.pagename];
const isLoading = ref(false);
async function saveSetting() {
errors = [];
errors.value = [];
let detail = pagename ? { fields: pagedata.fields } : {};
if (pagename) {
let element = pagedata.tablesetting || {};
@@ -139,19 +148,21 @@ async function saveSetting() {
if (props.option) detail.option = props.option;
if (props.data) detail.data = props.data;
let data = {
user: login.id,
name: name,
detail: detail,
note: note,
type: radioType.id,
// user: login.id,
user: undefined,
name: name.value,
detail,
note,
type: radioType.value.id,
classify: props.classify ? props.classify : store.settingclass.find((v) => v.code === "data-field").id,
default: radioDefault,
update_time: new Date(),
};
let result;
isLoading.value = true;
if (radioSave.value === "new") {
if ($empty(name)) {
return errors.push({
if ($empty(name.value)) {
return errors.value.push({
name: "name",
msg: "Tên thiết lập không được bỏ trống",
});
@@ -163,6 +174,7 @@ async function saveSetting() {
copy.update_time = new Date();
result = await $updateapi("usersetting", copy);
}
isLoading.value = false;
if (radioSave.value === "new") {
emit("modalevent", { name: "opensetting", data: result });
} else {
@@ -187,11 +199,12 @@ function changeOption(v) {
radioOption.value = v.code;
}
function initData() {
radioType = store.settingtype.find((v) => v.code === "private");
radioType.value = store.settingtype.find((v) => v.code === "private");
if (props.pagename) currentsetting = $copy(pagedata.setting ? pagedata.setting : undefined);
if (!currentsetting) radioSave.value = "new";
// disable temp: for now, radioSave is always 'overwrite'
/* if (!currentsetting) radioSave.value = "new";
else if (currentsetting.user !== login.id) radioSave.value = "new";
else radioSave.value = "overwrite";
else radioSave.value = "overwrite"; */
}
initData();
</script>

View File

@@ -2,10 +2,12 @@
<div class="tabs is-boxed">
<ul>
<li
:class="selectType.code === v.code ? 'is-active fs-16' : 'fs-16'"
v-for="v in fieldType"
:class="selectType.code === v.code && 'is-active'"
>
<a @click="selectType = v"
<a
class="has-text-inherit"
@click="selectType = v"
><span>{{ v.name }}</span></a
>
</li>
@@ -148,7 +150,7 @@
</div>
</div>
</template>
<div class="field px-0 mx-0">
<div class="field">
<label class="label">Tên trường <span class="has-text-danger"> * </span> </label>
<p class="control">
<input
@@ -166,13 +168,13 @@
{{ errors.find((v) => v.name === "name").message }}
</p>
<p
class="help has-text-primary"
class="help has-text-grey"
v-else
>
Tên trường do hệ thống tự sinh.
</p>
</div>
<div class="mt-5">
<div class="field">
<label class="label"> tả<span class="has-text-danger"> *</span></label>
<div class="field has-addons">
<div class="control is-expanded">
@@ -187,7 +189,12 @@
class="button"
@click="editLabel()"
>
<span><SvgIcon v-bind="{ name: 'pen.svg', type: 'dark', size: 17 }"></SvgIcon></span>
<span class="icon">
<Icon
name="material-symbols:edit-outline-rounded"
:size="18"
/>
</span>
</button>
</div>
</div>
@@ -199,53 +206,47 @@
</p>
</div>
<div
class="field mt-5"
class="field"
v-if="selectType.code === 'empty'"
>
<label class="label"
>Kiểu dữ liệu
<span class="has-text-danger"> * </span>
</label>
<div class="control fs-14">
<span
class="mr-4"
<div class="control">
<button
v-for="(v, i) in datatype"
>
<a
class="icon-text"
class="button is-white fs-14"
@click="changeType(v)"
>
<SvgIcon
v-bind="{
name: `radio-${radioType.code === v.code ? '' : 'un'}checked.svg`,
type: 'gray',
size: 22,
}"
></SvgIcon>
</a>
{{ v.name }}
<span class="icon">
<Icon
:name="
radioType.code === v.code
? 'material-symbols:radio-button-checked-outline'
: 'material-symbols:radio-button-unchecked'
"
:size="22"
/>
</span>
<span>{{ v.name }}</span>
</button>
</div>
</div>
<div class="field mt-5">
<p class="control">
<a
class="button is-primary has-text-white"
<button
class="button is-primary"
@click="selectType.code === 'formula' ? createField() : createEmptyField()"
>Tạo cột</a
>
</p>
</div>
Tạo cột
</button>
<Modal
v-bind="showmodal"
v-if="showmodal"
@label="changeLabel"
@close="close"
></Modal>
/>
</template>
<script setup>
import { useStore } from "@/stores/index";
import ScrollBox from "~/components/datatable/ScrollBox";
const emit = defineEmits(["modalevent"]);
const store = useStore();
const { $id, $copy, $clone, $empty, $stripHtml, $createField, $calc, $isNumber } = useNuxtApp();
@@ -256,7 +257,7 @@ var props = defineProps({
filterData: Object,
width: String,
});
const moneyunit = store.moneyunit;
const moneyunit = store.moneyunit || [];
const datatype = store.datatype;
var showmodal = ref();
var pagedata = store[props.pagename];
@@ -265,16 +266,15 @@ var data = [];
var current = 1;
var filterData = [];
var loading = false;
var fieldType = [
const fieldType = [
{ code: "formula", name: "Tạo công thức" },
{ code: "empty", name: "Tạo cột rỗng" },
];
var errors = [];
var tags = [];
var formula = undefined;
var name = `f${$id().toLocaleLowerCase()}`;
var label = undefined;
var errors = [];
const errors = ref([]);
var selectType = fieldType.find((v) => v.code === "empty");
var radioType = ref(datatype.find((v) => v.code === "string"));
var fields = [];
@@ -362,25 +362,25 @@ function checkFunc() {
return error ? "error" : content;
}
function checkValid() {
errors = [];
errors.value = [];
if (tags.length === 0 && choice === "column") {
errors.push({
errors.value.push({
name: "tags",
message: "Chưa chọn trường xây dựng công thức.",
});
}
if (!$empty(formula) ? $empty(formula.trim()) : true) {
errors.push({ name: "formula", message: "Công thức không được bỏ trống." });
errors.value.push({ name: "formula", message: "Công thức không được bỏ trống." });
}
if (!$empty(label) ? $empty(label.trim()) : true)
errors.push({ name: "label", message: "Mô tả không được bỏ trống." });
errors.value.push({ name: "label", message: "Mô tả không được bỏ trống." });
else if (pagedata.fields.find((v) => v.label.toLowerCase() === label.toLowerCase())) {
errors.push({
errors.value.push({
name: "label",
message: "Mô tả bị trùng. Hãy đặt mô tả khác.",
});
}
if (errors.length > 0) return false;
if (errors.value.length > 0) return false;
//check formula in case use column
if (choice === "column") {
let val = $copy(formula);
@@ -391,20 +391,20 @@ function checkValid() {
try {
let value = $calc(val);
if (isNaN(value) || value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY) {
errors.push({ name: "formula", message: "Công thức không hợp lệ" });
errors.value.push({ name: "formula", message: "Công thức không hợp lệ" });
}
} catch (err) {
console.log(err);
errors.push({ name: "formula", message: "Công thức không hợp lệ" });
errors.value.push({ name: "formula", message: "Công thức không hợp lệ" });
}
} else {
if (checkFunc() === "error")
errors.push({
errors.value.push({
name: "formula",
message: `Hàm ${func.toUpperCase()} không hợp lệ`,
});
}
return errors.length > 0 ? false : true;
return errors.value.length > 0 ? false : true;
}
function createField() {
if (!checkValid()) return;
@@ -429,23 +429,26 @@ function createField() {
emit("close");
}
function createEmptyField() {
errors = [];
if (!$empty(name) ? $empty(name.trim()) : true) errors.push({ name: "name", message: "Tên không được bỏ trống." });
console.log("createEmptyField");
errors.value = [];
if (!$empty(name) ? $empty(name.trim()) : true)
errors.value.push({ name: "name", message: "Tên không được bỏ trống." });
else if (pagedata.fields.find((v) => v.name.toLowerCase() === name.toLowerCase())) {
errors.push({
errors.value.push({
name: "name",
message: "Tên trường bị trùng. Hãy đặt tên khác.",
});
}
if (!$empty(label) ? $empty(label.trim()) : true)
errors.push({ name: "label", message: "Mô tả không được bỏ trống." });
errors.value.push({ name: "label", message: "Mô tả không được bỏ trống." });
else if (pagedata.fields.find((v) => v.label.toLowerCase() === label.toLowerCase())) {
errors.push({
errors.value.push({
name: "label",
message: "Mô tả bị trùng. Hãy đặt mô tả khác.",
});
}
if (errors.length > 0) return;
console.log("errors", errors);
if (errors.value.length > 0) return;
let field = $createField(name.trim(), label.trim(), radioType.value.code, true);
if (selectType.code === "chart") field = createChartField();
let copy = $clone(pagedata);
@@ -453,6 +456,7 @@ function createEmptyField() {
copy.update = { fields: copy.fields };
store.commit(props.pagename, copy);
//pagedata = copy
console.log("field", field);
emit("newfield", field);
label = undefined;
name = `f${$id()}`;

View File

@@ -1,11 +1,11 @@
<template>
<table class="table">
<table class="table is-fullwidth">
<thead>
<tr class="fs-14">
<th>#</th>
<th>Tên trường</th>
<th>Tên cột</th>
<th>...</th>
<th class="is-narrow">...</th>
</tr>
</thead>
<tbody>
@@ -23,21 +23,47 @@
</td>
<td>{{ $stripHtml(v.label, 50) }}</td>
<td>
<a
class="mr-4"
<div class="field has-addons">
<p class="control">
<button
class="button is-primary is-light"
@click="moveDown(v, i)"
>
<SvgIcon v-bind="{ name: 'down1.png', type: 'dark', size: 18 }"></SvgIcon>
</a>
<a
class="mr-4"
<span class="icon">
<Icon
name="material-symbols:arrow-downward-rounded"
:size="19"
/>
</span>
</button>
</p>
<p class="control">
<button
class="button is-primary is-light"
@click="moveUp(v, i)"
>
<SvgIcon v-bind="{ name: 'up.png', type: 'dark', size: 18 }"></SvgIcon>
</a>
<a @click="askConfirm(v, i)">
<SvgIcon v-bind="{ name: 'bin1.svg', type: 'dark', size: 18 }"></SvgIcon>
</a>
<span class="icon">
<Icon
name="material-symbols:arrow-upward-rounded"
:size="19"
/>
</span>
</button>
</p>
<p class="control">
<button
class="button is-primary is-light"
@click="askConfirm(v, i)"
>
<span class="icon">
<Icon
name="material-symbols:delete-outline-rounded"
:size="19"
/>
</span>
</button>
</p>
</div>
</td>
</tr>
</tbody>

View File

@@ -1,33 +1,38 @@
<template>
<div class="field is-horizontal">
<div class="field-body">
<div class="field">
<label class="label fs-14"> Cỡ chữ của bảng <span class="has-text-danger"> * </span></label>
<div class="fixed-grid has-12-cols">
<div class="grid">
<div class="cell is-col-span-4">
<div class="box">
<label class="label fs-14">Cỡ chữ của bảng</label>
<p class="control fs-14">
<input
class="input is-small"
class="input fs-13"
type="text"
:value="tablesetting.find((v) => v.code === 'table-font-size').detail"
@change="changeSetting($event.target.value, 'table-font-size')"
/>
</p>
</div>
<div class="field">
<label class="label fs-14"> Cỡ chữ tiêu đề <span class="has-text-danger"> * </span></label>
<p class="control fs-14">
</div>
<div class="cell is-col-span-4">
<div class="box">
<label class="label fs-14">Cỡ chữ tiêu đề</label>
<p class="control">
<input
class="input is-small"
class="input fs-13"
type="text"
:value="tablesetting.find((v) => v.code === 'header-font-size').detail"
@change="changeSetting($event.target.value, 'header-font-size')"
/>
</p>
</div>
<div class="field">
<label class="label fs-14"> Số dòng trên 1 trang <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
</div>
<div class="cell is-col-span-4">
<div class="box">
<label class="label fs-14">Số dòng trên 1 trang</label>
<p class="control">
<input
class="input is-small"
class="input fs-13"
type="text"
:value="tablesetting.find((v) => v.code === 'per-page').detail"
@change="changeSetting($event.target.value, 'per-page')"
@@ -35,72 +40,115 @@
</p>
</div>
</div>
</div>
<div class="field is-horizontal mt-5">
<div class="field-body">
<div class="field">
<label class="label fs-14"> Màu nền bảng <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<div class="cell is-col-span-6">
<div class="box">
<label class="label fs-14">Màu nền bảng</label>
<p class="control is-flex is-gap-1">
<input
type="color"
:value="tablesetting.find((v) => v.code === 'table-background').detail"
@change="changeSetting($event.target.value, 'table-background')"
/>
<input
class="input fs-13"
type="text"
placeholder="#f29384, var(--bulma-blue)"
:value="tablesetting.find((v) => v.code === 'table-background').detail"
@change="changeSetting($event.target.value, 'table-background')"
/>
</p>
</div>
<div class="field">
<label class="label fs-14"> Màu chữ <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
</div>
<div class="cell is-col-span-6">
<div class="box">
<label class="label fs-14">Màu chữ</label>
<p class="control is-flex is-gap-1">
<input
type="color"
:value="tablesetting.find((v) => v.code === 'table-font-color').detail"
@change="changeSetting($event.target.value, 'table-font-color')"
/>
<input
class="input fs-13"
type="text"
placeholder="#f29384, var(--bulma-blue)"
:value="tablesetting.find((v) => v.code === 'table-font-color').detail"
@change="changeSetting($event.target.value, 'table-font-color')"
/>
</p>
</div>
</div>
</div>
<div class="field is-horizontal mt-5">
<div class="field-body">
<div class="field">
<label class="label fs-14"> Màu chữ tiêu đề <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<div class="cell is-col-span-6">
<div class="box">
<label class="label fs-14">Màu chữ tiêu đề</label>
<p class="control is-flex is-gap-1">
<input
type="color"
:value="tablesetting.find((v) => v.code === 'header-font-color').detail"
@change="changeSetting($event.target.value, 'header-font-color')"
/>
</p>
</div>
<div class="field">
<label class="label fs-14"> Màu nền tiêu đề <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input
type="color"
:value="tablesetting.find((v) => v.code === 'header-background').detail"
@change="changeSetting($event.target.value, 'header-background')"
class="input fs-13"
type="text"
placeholder="#f29384, var(--bulma-blue)"
:value="tablesetting.find((v) => v.code === 'header-font-color').detail"
@change="changeSetting($event.target.value, 'header-font-color')"
/>
</p>
</div>
<div class="field">
<label class="label fs-14"> Màu chữ khi filter<span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
</div>
<div class="cell is-col-span-6">
<div class="box">
<label class="label fs-14">Màu nền tiêu đề</label>
<div class="control is-flex is-gap-1 is-align-items-stretch">
<input
type="color"
:value="rgbToHex(headerBg)"
@change="changeSetting($event.target.value, 'header-background')"
/>
<input
class="input fs-13 h-full"
type="text"
placeholder="#f29384, var(--bulma-blue)"
:value="tablesetting.find((v) => v.code === 'header-background').detail"
@change="changeSetting($event.target.value, 'header-background')"
/>
<div
ref="renderedHeaderBg"
class="is-hidden"
:style="{
backgroundColor: tablesetting.find((v) => v.code === 'header-background').detail,
}"
></div>
</div>
</div>
</div>
<div class="cell is-col-span-6">
<div class="box">
<label class="label fs-14">Màu chữ khi filter</label>
<p class="control is-flex is-gap-1">
<input
type="color"
:value="tablesetting.find((v) => v.code === 'header-filter-color').detail"
@change="changeSetting($event.target.value, 'header-filter-color')"
/>
<input
class="input fs-13"
type="text"
placeholder="#f29384, var(--bulma-blue)"
:value="tablesetting.find((v) => v.code === 'header-filter-color').detail"
@change="changeSetting($event.target.value, 'header-filter-color')"
/>
</p>
</div>
</div>
</div>
<div class="field is-horizontal mt-5">
<div class="field-body">
<div class="field">
<label class="label fs-14"> Đường viền <span class="has-text-danger"> * </span> </label>
<div class="cell is-col-span-6">
<div class="box">
<label class="label fs-14">Đường viền</label>
<p class="control fs-14">
<input
class="input is-small"
class="input fs-13"
type="text"
:value="
tablesetting.find((v) => v.code === 'td-border')
@@ -113,20 +161,30 @@
</div>
</div>
</div>
</div>
<button
class="button is-primary"
@click="emit('close')"
>
Cập nhật
</button>
</template>
<script setup>
import { useStore } from "@/stores/index";
const store = useStore();
var props = defineProps({
const props = defineProps({
pagename: String,
});
const { $copy, $clone, $empty } = useNuxtApp();
var pagedata = $clone(store[props.pagename]);
var errors = [];
var radioNote = "no";
const emit = defineEmits(["close"]);
const { $copy, $clone, $empty, $store } = useNuxtApp();
var pagedata = $clone($store[props.pagename]);
if (!Array.isArray(pagedata.tablesetting)) {
pagedata.tablesetting = Object.values(pagedata.tablesetting);
}
const errors = ref([]);
let radioNote = "no";
var tablesetting = pagedata.tablesetting;
let found = tablesetting.find((v) => v.code === "note");
if (found ? found.detail !== "@" : false) radioNote = "yes";
function changeSetting(value, code) {
if (code === "note" && $empty(value)) return;
let copy = $copy(tablesetting);
@@ -139,6 +197,45 @@ function changeSetting(value, code) {
}
tablesetting = copy;
pagedata.tablesetting = tablesetting;
store.commit(props.pagename, pagedata);
$store.commit(props.pagename, pagedata);
}
const renderedHeaderBg = useTemplateRef("renderedHeaderBg");
const headerBg = ref("#000000");
onMounted(() => {
headerBg.value = window.getComputedStyle(renderedHeaderBg.value).backgroundColor;
});
watch();
function rgbToHex(rgb) {
const [r, g, b] = rgb.match(/\d+/g);
return "#" + [r, g, b].map((x) => Number(x).toString(16).padStart(2, "0")).join("");
}
</script>
<style scoped>
input[type="color"] {
width: var(--bulma-control-height);
min-width: var(--bulma-control-height);
max-width: var(--bulma-control-height);
margin: 0 !important;
padding: 0 !important;
overflow: hidden;
}
input[type="color"]::-webkit-color-swatch-wrapper {
margin: 0 !important;
padding: 0 !important;
}
input[type="color"]::-webkit-color-swatch {
margin: 0 !important;
padding: 0 !important;
border: none;
}
.box {
box-shadow: none;
border: 1px solid var(--bulma-grey-90);
background-color: var(--bulma-grey-95);
height: 100%;
}
</style>

View File

@@ -20,7 +20,8 @@ const body = ref({
});
function selected(field, data) {
body.value[field] = data.id;
if (data === null) body.value[field] = data;
else body.value[field] = data.id;
}
async function createProduct() {
@@ -31,8 +32,6 @@ async function createProduct() {
</script>
<template>
<div>
<h1 class="subtitle is-4">AddProductForm</h1>
<form class="fixed-grid has-12-cols">
<div class="grid">
<div class="cell is-col-span-8">
@@ -57,6 +56,7 @@ async function createProduct() {
field: 'name',
column: ['name'],
first: true,
clearable: true,
placeholder: 'OS',
addon: {
component: 'imports/addons/AddOS',
@@ -78,6 +78,7 @@ async function createProduct() {
field: 'name',
column: ['name'],
first: true,
clearable: true,
placeholder: 'Hãng',
addon: {
component: 'imports/addons/AddManufacturer',
@@ -99,6 +100,7 @@ async function createProduct() {
field: 'code',
column: ['code'],
first: true,
clearable: true,
placeholder: 'Pin',
addon: {
component: 'imports/addons/AddBattery',
@@ -117,9 +119,10 @@ async function createProduct() {
<SearchBox
v-bind="{
api: 'Screen',
field: 'code',
column: ['code'],
field: 'label',
column: ['resolution', 'standard', 'technology'],
first: true,
clearable: true,
placeholder: 'Màn hình',
addon: {
component: 'imports/addons/AddScreen',
@@ -141,6 +144,7 @@ async function createProduct() {
field: 'name',
column: ['name'],
first: true,
clearable: true,
placeholder: 'CPU',
addon: {
component: 'imports/addons/AddCPU',
@@ -162,6 +166,7 @@ async function createProduct() {
field: 'name',
column: ['name'],
first: true,
clearable: true,
placeholder: 'GPU',
addon: {
component: 'imports/addons/AddGPU',
@@ -183,6 +188,7 @@ async function createProduct() {
field: 'code',
column: ['code'],
first: true,
clearable: true,
placeholder: 'Camera',
addon: {
component: 'imports/addons/AddCamera',
@@ -204,6 +210,7 @@ async function createProduct() {
field: 'code',
column: ['code'],
first: true,
clearable: true,
placeholder: 'SIM',
addon: {
component: 'imports/addons/AddSIM',
@@ -225,6 +232,7 @@ async function createProduct() {
field: 'name',
column: ['name'],
first: true,
clearable: true,
placeholder: 'Kết nối',
addon: {
component: 'imports/addons/AddNetworkTechnology',
@@ -246,6 +254,7 @@ async function createProduct() {
field: 'code',
column: ['code'],
first: true,
clearable: true,
placeholder: 'Công nghệ sạc',
addon: {
component: 'imports/addons/AddChargingTechnology',
@@ -267,6 +276,7 @@ async function createProduct() {
field: 'code',
column: ['code'],
first: true,
clearable: true,
placeholder: 'Bộ nhớ ngoài',
addon: {
component: 'imports/addons/AddExternalStorage',
@@ -288,6 +298,7 @@ async function createProduct() {
field: 'name',
column: ['name'],
first: true,
clearable: true,
placeholder: 'Chỉ số IP',
addon: {
component: 'imports/addons/AddIPRating',
@@ -309,6 +320,7 @@ async function createProduct() {
field: 'label',
column: ['frame_material', 'back_material'],
first: true,
clearable: true,
placeholder: 'Chất liệu',
addon: {
component: 'imports/addons/AddDesign',
@@ -328,12 +340,11 @@ async function createProduct() {
@click.prevent="createProduct"
>
<span class="icon">
<Icon name="material-symbols:add-2-rounded" />
<Icon name="material-symbols:add-rounded" />
</span>
<span>Tạo sản phẩm</span>
</button>
</div>
</div>
</form>
</div>
</template>

View File

@@ -0,0 +1,61 @@
<script setup>
import DataView from "@/components/datatable/DataView.vue";
import AddProductVariantForm from "@/components/imports/AddProductVariantForm.vue";
const product = ref();
const key = ref(0);
watch(product, () => {
key.value++;
});
</script>
<template>
<div class="fixed-grid has-12-cols">
<div class="grid">
<div class="cell is-col-span-6 is-col-start-4">
<div class="field">
<label class="label">Sản phẩm</label>
<SearchBox
v-bind="{
api: 'product',
field: 'name',
column: ['name'],
first: true,
clearable: true,
placeholder: 'Tìm sản phẩm',
addon: {
component: 'imports/AddProductForm',
width: '90%',
height: 'auto',
title: 'Thêm sản phẩm',
},
}"
@option="product = $event"
/>
</div>
</div>
<div
v-if="product"
class="cell is-col-span-12"
>
<DataView
:key="key"
v-bind="{
api: 'Product_Variant',
setting: 'product-variants',
pagename: 'product-variants',
params: {
values:
'id,code,product,product__name,color,color__code,color__name,color__hex_code,ram,ram__code,ram__capacity,internal_storage,internal_storage__code,internal_storage__capacity,image,price,note,create_time,update_time',
filter: { product: product.id },
},
}"
/>
</div>
</div>
</div>
<AddProductVariantForm
v-if="product"
:productId="product.id"
@created="key++"
/>
</template>

View File

@@ -1,20 +1,33 @@
<script setup>
import InputNumber from "@/components/common/InputNumber.vue";
import { omitBy } from "es-toolkit";
const { $insertapi } = useNuxtApp();
const props = defineProps({
productId: Number,
});
const emit = defineEmits(["created"]);
const { $empty, $insertapi } = useNuxtApp();
const isPending = ref(false);
const body = ref({
product: null,
product: props.productId,
price: null,
internal_storage: null,
ram: null,
color: null,
note: null,
note: "",
});
watch(
() => props.productId,
(newVal) => {
body.value.product = props.productId;
},
);
function selected(field, data) {
if (field === "price") {
if (data === null || field === "price") {
body.value[field] = data;
} else {
body.value[field] = data.id;
@@ -23,38 +36,21 @@ function selected(field, data) {
async function createProductVariant() {
isPending.value = true;
const res = await $insertapi("Product_Variant", body.value);
const trimmedBody = omitBy(body.value, $empty);
const res = await $insertapi("Product_Variant", trimmedBody);
isPending.value = false;
if (res !== "error") {
emit("created");
}
}
</script>
<template>
<div>
<h1 class="subtitle is-4">AddProductVariantForm</h1>
<h1 class="subtitle">Thêm phiên bản</h1>
<form class="fixed-grid has-12-cols">
<div class="grid">
<div class="cell is-col-span-8">
<div class="field">
<label class="label">Sản phẩm</label>
<SearchBox
v-bind="{
api: 'product',
field: 'name',
column: ['name'],
first: true,
placeholder: 'Sản phẩm',
addon: {
component: 'imports/AddProductForm',
width: '90%',
height: 'auto',
title: 'Thêm sản phẩm',
},
}"
@option="selected('product', $event)"
/>
</div>
</div>
<div class="cell is-col-span-4">
<div class="cell is-col-span-3">
<div class="field">
<label class="label">Đơn giá</label>
<div class="control">
@@ -70,15 +66,16 @@ async function createProductVariant() {
</div>
</div>
</div>
<div class="cell is-col-span-4">
<div class="cell is-col-span-3">
<div class="field">
<label class="label">Màu sắc</label>
<SearchBox
v-bind="{
api: 'color',
api: 'Color',
field: 'name',
column: ['name'],
first: true,
clearable: true,
placeholder: 'Màu sắc',
addon: {
component: 'imports/addons/AddColor',
@@ -91,7 +88,7 @@ async function createProductVariant() {
/>
</div>
</div>
<div class="cell is-col-span-4">
<div class="cell is-col-span-3">
<div class="field">
<label class="label">RAM</label>
<SearchBox
@@ -100,6 +97,7 @@ async function createProductVariant() {
field: 'code',
column: ['code'],
first: true,
clearable: true,
placeholder: 'RAM',
addon: {
component: 'imports/addons/AddRAM',
@@ -112,7 +110,7 @@ async function createProductVariant() {
/>
</div>
</div>
<div class="cell is-col-span-4">
<div class="cell is-col-span-3">
<div class="field">
<label class="label">Bộ nhớ trong</label>
<SearchBox
@@ -121,6 +119,7 @@ async function createProductVariant() {
field: 'code',
column: ['code'],
first: true,
clearable: true,
placeholder: 'Bộ nhớ trong',
addon: {
component: 'imports/addons/AddInternalStorage',
@@ -148,11 +147,14 @@ async function createProductVariant() {
<div class="cell is-col-span-12">
<button
:class="['button is-primary', { 'is-loading': isPending }]"
:disabled="Object.values(body).every((v) => v === null)"
:disabled="Object.values(body).every($empty)"
@click.prevent="createProductVariant"
>
<span class="icon">
<Icon name="material-symbols:add-2-rounded" />
<Icon
name="material-symbols:add-rounded"
:size="18"
/>
</span>
<span>Thêm phiên bản</span>
</button>

View File

@@ -0,0 +1,12 @@
<script setup>
const props = defineProps({
color: String,
});
</script>
<template>
<div
class="w-20 h-4"
:style="{ backgroundColor: color, outline: '1px solid var(--bulma-grey-85)' }"
></div>
</template>

View File

@@ -1,22 +1,43 @@
<script setup>
import AddProductForm from "@/components/imports/AddProductForm.vue";
import AddProductVariantForm from "@/components/imports/AddProductVariantForm.vue";
import FileUpload from "@/components/media/FileUpload.vue";
import AddProductVariant from "@/components/imports/AddProductVariant.vue";
const menus = [
{
id: "add-product",
name: "Tạo sản phẩm",
},
{
id: "add-product-variant",
name: "Thêm phiên bản",
},
];
const activeMenu = ref(menus[1]);
</script>
<template>
<FileUpload
:type="['file']"
class="mb-2"
@files="onFiles"
<div class="fixed-grid has-12-cols">
<div class="grid is-gap-4">
<div class="cell is-col-span-2">
<aside class="menu">
<ul class="menu-list">
<li
v-for="menu in menus"
:key="menu.id"
>
<template #icon>
<Icon
name="material-symbols:upload-rounded"
:size="20"
/>
</template>
<span class="font-medium">Import</span>
</FileUpload>
<AddProductForm />
<AddProductVariantForm />
<a
@click="activeMenu = menu"
:class="{
'is-active': activeMenu.id === menu.id,
}"
>{{ menu.name }}</a
>
</li>
</ul>
</aside>
</div>
<div class="cell is-col-span-10">
<AddProductForm v-if="activeMenu.id === 'add-product'" />
<AddProductVariant v-if="activeMenu.id === 'add-product-variant'" />
</div>
</div>
</div>
</template>

View File

@@ -71,7 +71,7 @@ const inventoryHighlights = [
<span class="icon">
<Icon
:size="18"
name="material-symbols:add-2-rounded"
name="material-symbols:add-rounded"
/>
</span>
<span>Điều chỉnh</span>

View File

@@ -75,7 +75,7 @@ const viewMode = ref("list");
<span class="icon">
<Icon
:size="18"
name="material-symbols:add-2-rounded"
name="material-symbols:add-rounded"
/>
</span>
<span>Tạo đơn hàng</span>

View File

@@ -151,7 +151,7 @@ export default {
component: "menu/MenuSave",
title: "Lưu thiết lập",
width: "600px",
height: "300px",
height: "auto",
vbind: { pagename: this.pagename3, classify: 3 },
};
},

View File

@@ -1,7 +1,5 @@
<template>
<div
style="min-height: 100vh"
class="has-background-blue-100"
data-theme="light"
lang="vi"
>
@@ -28,6 +26,7 @@ import { onMounted } from "vue";
import { useRoute } from "vue-router";
import SnackBar from "@/components/snackbar/SnackBar.vue";
import Modal from "@/components/Modal.vue";
import { throttle } from "es-toolkit";
const route = useRoute();
const { $getdata, $requestLogin, $store } = useNuxtApp();
@@ -36,16 +35,16 @@ const snackbar = ref(undefined);
const showmodal = ref(undefined);
function getViewport() {
let viewport;
var width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
const width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
if (width <= 768)
viewport = 1; //'mobile'
viewport = 1; // 'mobile'
else if (width >= 769 && width <= 1023)
viewport = 2; //'tablet'
viewport = 2; // 'tablet'
else if (width >= 1024 && width <= 1215)
viewport = 3; //'desktop'
viewport = 3; // 'desktop'
else if (width >= 1216 && width <= 1407)
viewport = 4; //'widescreen'
else if (width >= 1408) viewport = 5; //'fullhd'
viewport = 4; // 'widescreen'
else if (width >= 1408) viewport = 5; // 'fullhd'
$store.commit("viewport", viewport);
}
async function checkRedirect() {
@@ -91,6 +90,8 @@ async function checkLogin() {
onMounted(() => {
checkRedirect();
getViewport();
const throttledGetViewport = throttle(getViewport, 400);
window.addEventListener("resize", throttledGetViewport);
});
watch(
() => $store.snackbar,

View File

@@ -59,6 +59,10 @@
<script setup>
useHead({
link: [{ rel: "icon", type: "image/x-icon", href: "/favicon.svg" }],
htmlAttrs: {
class: "has-background-blue-100",
style: "min-height: 100vh",
},
});
const { $createMeta, $store, $copy, $id } = useNuxtApp();

View File

@@ -4,6 +4,9 @@ export default defineNuxtPlugin(() => {
//==========Find & filter=================
const find = function (arr, obj, attr) {
if (typeof arr === "object" && !Array.isArray(arr)) {
arr = Object.values(arr);
}
const keys = Object.keys(obj);
let found = arr.find((v) => {
let valid = true;

View File

@@ -1012,7 +1012,16 @@ export default defineNuxtPlugin((nuxtApp) => {
name: "Screen",
url: "data/Screen/",
url_detail: "data-detail/Screen/",
params: {},
params: {
values: "id,code,resolution,standard,technology,size,create_time",
distinct_values: {
label: {
type: "Concat",
field: ["resolution", "standard", "technology"],
},
},
summary: "annotate",
},
},
{
name: "CPU",
@@ -1078,7 +1087,7 @@ export default defineNuxtPlugin((nuxtApp) => {
},
},
{
name: "color",
name: "Color",
url: "data/Color/",
url_detail: "data-detail/Color/",
params: {},
@@ -1170,7 +1179,7 @@ export default defineNuxtPlugin((nuxtApp) => {
try {
let found = findapi(name);
let curpath = found.path ? paths.find((x) => x.name === found.path).url : path;
var rs;
let rs;
if (!Array.isArray(data)) {
rs = await $fetch(`${curpath}${found.url}`, {
method: "POST",
@@ -1241,7 +1250,7 @@ export default defineNuxtPlugin((nuxtApp) => {
? $snackbar("Data has been successfully saved to the system.", "Success", "Success")
: $snackbar("Dữ liệu đã được lưu vào hệ thống", "Thành công", "Success");
}
return rs.data;
return rs;
} catch (err) {
console.log(err);
return "error";
@@ -1450,7 +1459,7 @@ export default defineNuxtPlugin((nuxtApp) => {
let index = copy.findIndex((v) => v.id === id);
if (index >= 0) $remove(copy, index);
} else {
rs.data.forEach((element) => {
rs.forEach((element) => {
let index = copy.findIndex((v) => v.id === element.id);
if (index >= 0) $remove(copy, index);
});

View File

@@ -23,6 +23,7 @@ import AddDesign from "@/components/imports/addons/AddDesign.vue";
import AddColor from "@/components/imports/addons/AddColor.vue";
import AddRAM from "@/components/imports/addons/AddRAM.vue";
import AddInternalStorage from "@/components/imports/addons/AddInternalStorage.vue";
import Color from "@/components/imports/Color.vue";
import Returns from "@/components/imports/Returns.vue";
import Exports from "@/components/exports/Exports.vue";
import ExportsDamaged from "@/components/exports/ExportsDamaged.vue";
@@ -177,6 +178,7 @@ const components = {
AddColor,
AddRAM,
AddInternalStorage,
Color,
Returns,
Exports,
ExportsDamaged,