This commit is contained in:
Viet An
2026-05-15 11:18:33 +07:00
parent 869138c003
commit 0ef1d29850
18 changed files with 175 additions and 111 deletions

View File

@@ -2,7 +2,7 @@
<div>
<div
class="field has-addons"
:id="docid"
:id="$id()"
>
<div class="control has-icons-left has-icons-right is-expanded">
<div
@@ -17,7 +17,6 @@
:class="[
'input',
{
'is-danger': error,
'has-text-dark': disabled,
},
]"
@@ -171,11 +170,9 @@ const selected = ref();
const showmodal = ref();
const params = ref(props.api && $findapi(props.api).params);
const orgdata = ref();
const error = ref(false);
const focused = ref(false);
const count1 = ref(0);
const count2 = ref(0);
const docid = ref($id());
const pos = ref();
getPos();

View File

@@ -1,6 +1,6 @@
<template>
<div class="field has-addons is-justify-content-center">
<p class="control">
<!-- <p class="control">
<button
class="button is-light is-primary"
@click="checkFilter() ? false : $emit('modalevent', { name: 'dosort', data: 'az' })"
@@ -37,7 +37,7 @@
style="top: 110%; bottom: unset; min-width: max-content; left: 25px"
>Sắp xếp giảm dần</span
>
</p>
</p> -->
<p class="control">
<button
class="button is-light is-primary"

View File

@@ -103,7 +103,6 @@
:row="v"
v-if="field.template"
@clickevent="clickEvent($event, v, field)"
@dynamicCompEvent="onDynamicCompEvent"
/>
<span v-else>{{ v[field.name] }}</span>
</td>
@@ -671,13 +670,6 @@ const doubleClick = function (field, v) {
};
const tableStyle = getSettingStyle("table");
setTimeout(() => updateShow(), 200);
async function onDynamicCompEvent(e) {
console.log("DataTable received dynamicCompEvent", e);
if (e.type === "refresh") {
updateShow(); // doesn't get new data
}
}
</script>
<style scoped>
:deep(.table tbody tr:hover td, .table tbody tr:hover th) {

View File

@@ -151,12 +151,12 @@ const checkDataChanges = async () => {
delete conn1.params.sort;
delete conn1.params.values;
conn1.params.summary = "aggregate";
conn1.params.distinct_values = JSON.stringify({
total_count: { type: "Count", field: "id" },
last_updated: { type: "Max", field: "update_time" },
last_created: { type: "Max", field: "create_time" },
});
// conn1.params.summary = "aggregate";
// conn1.params.distinct_values = JSON.stringify({
// total_count: { type: "Count", field: "id" },
// last_updated: { type: "Max", field: "update_time" },
// last_created: { type: "Max", field: "create_time" },
// });
connlist.push(conn1);
@@ -263,6 +263,8 @@ const refreshData = async () => {
startAutoCheck();
};
provide("refreshData", refreshData);
watch(
() => props.realtime,
(newVal) => {

View File

@@ -1,11 +1,19 @@
<template>
<div>
<div
v-for="(v, i) in arr"
v-for="(v, i) in lines"
:key="i"
:class="i > 0 && 'mt-4'"
>
<p class="font-semibold">Dòng thứ {{ i + 1 }}<span class="has-text-danger"> *</span></p>
<p class="font-semibold">
Dòng thứ {{ i + 1 }}
<span
v-if="i === 0"
class="has-text-danger"
>
*
</span>
</p>
<div class="field has-addons mt-1">
<div class="control is-expanded">
<input
@@ -14,19 +22,6 @@
v-model="v.label"
/>
</div>
<div class="control">
<button
class="button is-success is-light"
@click="add()"
>
<span class="icon">
<Icon
name="material-symbols:add-rounded"
:size="20"
/>
</span>
</button>
</div>
<div
class="control"
@click="remove(i)"
@@ -41,6 +36,19 @@
</span>
</button>
</div>
<div class="control">
<button
class="button is-success is-light"
@click="add"
>
<span class="icon">
<Icon
name="material-symbols:add-rounded"
:size="20"
/>
</span>
</button>
</div>
</div>
<p
class="help has-text-danger"
@@ -52,7 +60,7 @@
<div class="buttons mt-5">
<button
class="button is-primary has-text-white"
@click="update()"
@click="update"
>
Cập nhật
</button>
@@ -70,7 +78,7 @@ export default {
props: ["label"],
data() {
return {
arr: [],
lines: [],
};
},
created() {
@@ -79,37 +87,37 @@ export default {
if (!this.$empty(v)) {
let label = v + "</p>";
label = this.$stripHtml(label);
this.arr.push({ label });
this.lines.push({ label });
}
});
},
methods: {
add() {
this.arr.push({ label: undefined });
this.lines.push({ label: undefined });
},
remove(i) {
this.$remove(this.arr, i);
this.$remove(this.lines, i);
},
checkError() {
let error = false;
this.arr.map((v) => {
this.lines.map((v) => {
if (this.$empty(v.label)) {
v.error = "Nội dung không được bỏ trống";
error = true;
}
});
if (error) this.arr = this.$copy(this.arr);
if (error) this.lines = this.$copy(this.lines);
return error;
},
update() {
if (this.checkError()) return;
let label = "";
if (this.arr.length > 1) {
this.arr.map((v, i) => {
label += `<p${i < this.arr.length - 1 ? ' style="border-bottom: 1px solid white;"' : ""}>${v.label.trim()}</p>`;
if (this.lines.length > 1) {
this.lines.map((v, i) => {
label += `<p${i < this.lines.length - 1 ? ' style="border-bottom: 1px solid white;"' : ""}>${v.label.trim()}</p>`;
});
label = `<div>${label}</div>`;
} else label = this.arr[0].label.trim();
} else label = this.lines[0].label.trim();
this.$emit("modalevent", { name: "label", data: label });
this.$emit("close");
},

View File

@@ -9,7 +9,7 @@
</p>
</div>
<div class="field">
<label class="label">Chọn chế độ lưu <span class="has-text-danger"> * </span></label>
<label class="label">Chọn chế độ lưu</label>
<div class="control is-expanded fs-14">
<button
class="button is-white has-text-inherit"
@@ -54,7 +54,7 @@
<input
class="input"
type="text"
placeholder=""
placeholder="products, variants-table, etc."
v-model="name"
v-on:keyup.enter="saveSetting"
/>
@@ -67,28 +67,16 @@
</div>
</div>
<div class="field">
<label class="label"> tả </label>
<label class="label"> tả</label>
<p class="control is-expanded">
<textarea
class="textarea"
rows="3"
rows="1"
placeholder="Note"
v-model="note"
></textarea>
</p>
</div>
<!--
<div class="field mt-4 px-0 mx-0">
<label class="label fs-14">Loại thiết lập <span class="has-text-danger"> * </span>
</label>
<div class="control is-expanded fs-14">
<span class="mr-4" v-for="(v,i) in $filter(store.settingtype, {code: ['private', 'public']})">
<a class="icon-text" @click="changeOption(v)">
<SvgIcon v-bind="{name: `radio-${radioOption===v.code? '' : 'un'}checked.svg`, type: radioOption===v.code? 'primary' : 'gray', size: 22}"></SvgIcon>
</a>
{{v.name}}
</span>
</div>
</div>-->
</template>
<div class="field mt-5 px-0 mx-0">
<label
@@ -100,6 +88,7 @@
</label>
<p class="control is-expanded">
<button
ref="saveBtn"
:class="['button is-primary', isLoading && 'is-loading']"
@click="saveSetting()"
>
@@ -134,6 +123,10 @@ var currentsetting = undefined;
var pagename = props.pagename;
var pagedata = store[props.pagename];
const isLoading = ref(false);
const saveBtnRef = useTemplateRef("saveBtn");
onMounted(() => {
saveBtnRef.value.focus();
});
async function saveSetting() {
errors.value = [];
@@ -184,7 +177,7 @@ async function saveSetting() {
copy[idx] = result;
store.commit("settings", copy);
}
$snackbar("Lưu thiết lập thành công");
$snackbar("Lưu thiết lập thành công", undefined, "Success");
emit("modalevent", { name: "updatesetting", data: result });
emit("close");
}

View File

@@ -2,7 +2,7 @@
<div class="tabs is-boxed">
<ul>
<li
v-for="v in fieldType"
v-for="v in fieldTypes"
:class="selectType.code === v.code && 'is-active'"
>
<a
@@ -273,16 +273,16 @@ var data = [];
var current = 1;
var filterData = [];
var loading = false;
const fieldType = [
{ code: "formula", name: "Tạo công thức" },
const fieldTypes = [
{ code: "empty", name: "Tạo cột rỗng" },
{ code: "formula", name: "Tạo công thức" },
];
var tags = [];
var formula = undefined;
var name = `f${$id().toLocaleLowerCase()}`;
var label = undefined;
const errors = ref([]);
var selectType = fieldType.find((v) => v.code === "empty");
var selectType = fieldTypes.find((v) => v.code === "empty");
var radioType = ref(datatype.find((v) => v.code === "string"));
var fields = [];
var options = undefined;
@@ -293,7 +293,7 @@ var choices = [
{ code: "function", name: "Dùng hàm số" },
];
const choice = ref("column");
var funcs = [
const funcs = [
{ code: "sum", name: "Sum" },
{ code: "max", name: "Max" },
{ code: "min", name: "Min" },
@@ -436,7 +436,6 @@ function createField() {
emit("close");
}
function createEmptyField() {
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." });
@@ -454,7 +453,6 @@ function createEmptyField() {
message: "Mô tả bị trùng. Hãy đặt mô tả khác.",
});
}
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();
@@ -463,7 +461,6 @@ 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

@@ -59,7 +59,7 @@
:placeholder="lang === 'vi' ? 'Nhập từ khóa...' : 'Enter keyword...'"
class="input is-orange fs-12"
:style="{
maxWidth: '180px',
maxWidth: '150px',
width: this.store.viewport === 1 ? '150px' : 'auto',
}"
/>

View File

@@ -1,10 +0,0 @@
<template>
<span :style="color ? `color:${color}` : ''">{{ $numtoString(value) }}</span>
</template>
<script setup>
const { $numtoString } = useNuxtApp();
const props = defineProps({
value: Number,
color: String,
});
</script>

View File

@@ -24,9 +24,13 @@ function selected(field, data) {
else body.value[field] = data.id;
}
const refreshData = inject("refreshData");
async function createProduct() {
isPending.value = true;
const res = await $insertapi("product", body.value);
if (res !== "error") {
if (refreshData) refreshData();
}
isPending.value = false;
}
</script>

View File

@@ -0,0 +1,49 @@
<script setup>
import Modal from "@/components/Modal.vue";
const props = defineProps({
product: Object,
});
const { $deleteapi } = useNuxtApp();
const showConfirmModal = ref(null);
function displayModal() {
showConfirmModal.value = {
component: "dialog/Confirm",
title: "Xoá sản phẩm",
width: "500px",
height: "auto",
vbind: {
content: `Bạn xác nhận xoá sản phẩm <b>${props.product.name}</b>?`,
onModalevent: deleteProduct,
},
};
}
const refreshData = inject("refreshData");
async function deleteProduct() {
const res = await $deleteapi("product", props.product.id);
if (res !== "error") {
if (refreshData) refreshData();
}
}
</script>
<template>
<a
class="has-text-danger"
@click="displayModal"
>
<span class="icon">
<Icon
name="material-symbols:delete-outline-rounded"
:size="18"
/>
</span>
<Modal
v-bind="showConfirmModal"
@close="showConfirmModal = null"
/>
</a>
</template>

View File

@@ -5,7 +5,6 @@ const props = defineProps({
variant: Object,
});
const emit = defineEmits(["dynamicCompEvent"]);
const { $deleteapi } = useNuxtApp();
const showConfirmModal = ref(null);
@@ -22,11 +21,11 @@ function displayModal() {
};
}
const refreshData = inject("refreshData");
async function deleteVariant() {
const res = await $deleteapi("Product_Variant", props.variant.id);
if (res !== "error") {
// emit to parent, which is DataTable
emit("dynamicCompEvent", { type: "refresh" });
if (refreshData) refreshData();
}
}
</script>

View File

@@ -1,22 +1,22 @@
<script setup>
import AddProductForm from "@/components/imports/AddProductForm.vue";
import AddProductVariant from "@/components/imports/AddProductVariant.vue";
import Products from "@/components/imports/Products.vue";
const menus = [
{
id: "add-product",
name: "Tạo sản phẩm",
id: "product",
name: "Sản phẩm",
},
{
id: "add-product-variant",
name: "Thêm phiên bản",
id: "product-variant",
name: "Phiên bản",
},
];
const activeMenu = ref(menus[1]);
const activeMenu = ref(menus[0]);
</script>
<template>
<div class="fixed-grid has-12-cols">
<div class="grid is-gap-4">
<div class="cell is-col-span-3">
<div class="cell is-col-span-2">
<aside class="menu">
<ul class="menu-list">
<li
@@ -25,27 +25,23 @@ const activeMenu = ref(menus[1]);
>
<a
@click="activeMenu = menu"
:class="[
'fs-13',
{
'is-active': activeMenu.id === menu.id,
},
]"
:class="[{ 'is-active': activeMenu.id === menu.id }]"
>{{ menu.name }}</a
>
</li>
</ul>
</aside>
</div>
<div class="cell is-col-span-9">
<AddProductForm v-if="activeMenu.id === 'add-product'" />
<AddProductVariant v-if="activeMenu.id === 'add-product-variant'" />
<div class="cell is-col-span-10">
<Products v-if="activeMenu.id === 'product'" />
<AddProductVariant v-if="activeMenu.id === 'product-variant'" />
</div>
</div>
</div>
</template>
<style scoped>
.menu-list a {
font-size: 0.95em;
--bulma-menu-list-link-padding: 0.75em 1.25em;
&:not(.is-active) {

View File

@@ -0,0 +1,25 @@
<script setup>
import DataView from "@/components/datatable/DataView.vue";
</script>
<template>
<DataView
v-bind="{
api: 'product',
setting: 'products',
pagename: 'products',
params: {
values:
'id,code,name,manufacturer,manufacturer__name,os,os__name,battery,battery__code,screen,cpu,cpu__name,gpu,gpu__name,camera_system,camera_system__code,sim,sim__code,network_technology,network_technology__name,charging_technology,charging_technology__code,external_storage,external_storage__max_capacity,ip_rating,ip_rating__code,design,create_time,update_time',
sort: 'id',
},
timeopt: { time: 36000 },
modal: {
component: 'imports/AddProductForm',
title: 'Tạo sản phẩm',
width: '75%',
height: 'auto',
},
}"
/>
</template>

View File

@@ -1,6 +1,6 @@
<template>
<div
class="snackbar is-flex is-align-items-center is-gap-3 pl-3 pr-1.5 py-2 rounded-md has-background-grey-35 has-text-white"
class="snackbar is-flex is-align-items-center is-gap-3 pl-3 pr-1.5 py-2 rounded-md has-background-grey-25 has-text-white"
>
<component
:is="resolvedComponent"

View File

@@ -4,8 +4,13 @@ export default defineNuxtPlugin(() => {
//==========Find & filter=================
const find = function (arr, obj, attr) {
if (typeof arr === "object" && !Array.isArray(arr)) {
if (!Array.isArray(arr)) {
if (typeof arr === "object") {
arr = Object.values(arr);
} else {
console.error(`arr "${arr}" is not an array`);
return;
}
}
const keys = Object.keys(obj);
let found = arr.find((v) => {

View File

@@ -570,7 +570,10 @@ export default defineNuxtPlugin((nuxtApp) => {
commit: "product",
url: "data/Product/",
url_detail: "data-detail/Product/",
params: {},
params: {
values:
"id,code,name,manufacturer,manufacturer__name,os,os__name,battery,battery__code,screen,cpu,cpu__name,gpu,gpu__name,camera_system,camera_system__code,sim,sim__code,network_technology,network_technology__name,charging_technology,charging_technology__code,external_storage,external_storage__max_capacity,ip_rating,ip_rating__code,design,create_time,update_time",
},
},
{
name: "Product_Variant",
@@ -1188,7 +1191,7 @@ export default defineNuxtPlugin((nuxtApp) => {
// insert data
const insertapi = async function (name, data, values, notify) {
try {
let found = findapi(name);
const found = findapi(name);
const curpath = found.path ? paths.find((x) => x.name === found.path).url : path;
let rs;
if (!Array.isArray(data)) {
@@ -1464,13 +1467,13 @@ export default defineNuxtPlugin((nuxtApp) => {
});
}
if (found.commit) {
let copy = JSON.parse(JSON.stringify($store[found.commit]));
const copy = $copy($store[found.commit]);
if (!Array.isArray(id)) {
let index = copy.findIndex((v) => v.id === id);
const index = copy.findIndex((v) => v.id === id);
if (index >= 0) $remove(copy, index);
} else {
rs.forEach((element) => {
let index = copy.findIndex((v) => v.id === element.id);
const index = copy.findIndex((v) => v.id === element.id);
if (index >= 0) $remove(copy, index);
});
}
@@ -1478,7 +1481,7 @@ export default defineNuxtPlugin((nuxtApp) => {
}
return id;
} catch (err) {
console.log(err);
console.error(err);
if (err.response) {
let content = `<span>Đã xảy ra lỗi, xóa dữ liệu không thành công</span>`;
if (err.response.data)

View File

@@ -7,6 +7,8 @@ import POS from "@/components/pos/POS.vue";
import CreateReceipts from "@/components/receipts/CreateReceipts.vue";
import Return from "@/components/receipts/Return.vue";
import Imports from "@/components/imports/Imports.vue";
import AddProductForm from "@/components/imports/AddProductForm.vue";
import DeleteProduct from "@/components/imports/DeleteProduct.vue";
import AddOS from "@/components/imports/addons/AddOS.vue";
import AddManufacturer from "@/components/imports/addons/AddManufacturer.vue";
import AddBattery from "@/components/imports/addons/AddBattery.vue";
@@ -168,6 +170,8 @@ const components = {
CreateReceipts,
Return,
Imports,
AddProductForm,
DeleteProduct,
AddOS,
AddManufacturer,
AddBattery,