From e8743752763785363034b6bae1d03e79c6026ea4 Mon Sep 17 00:00:00 2001 From: provira Date: Mon, 24 Mar 2025 11:05:36 +0100 Subject: [PATCH] feat: refs #8406 upgraded CrudModel --- src/components/CrudModel.vue | 104 ++++++++++++++++--- src/components/VnTable/VnTable.vue | 1 + src/pages/InvoiceIn/Card/InvoiceInDueDay.vue | 1 + src/pages/Item/Card/ItemTags.vue | 13 ++- src/pages/Item/Card/ItemTax.vue | 1 + src/pages/Ticket/Card/TicketPackage.vue | 6 +- src/pages/Ticket/Card/TicketSale.vue | 1 + 7 files changed, 109 insertions(+), 18 deletions(-) diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index 6303f48ae..5b9cac7f1 100644 --- a/src/components/CrudModel.vue +++ b/src/components/CrudModel.vue @@ -42,7 +42,15 @@ const $props = defineProps({ }, dataRequired: { type: Object, - default: () => {}, + default: () => ({}), + }, + dataDefault: { + type: Object, + default: () => ({}), + }, + insertOnLoad: { + type: Boolean, + default: true, }, defaultSave: { type: Boolean, @@ -86,6 +94,9 @@ const vnPaginateRef = ref(); const formData = ref([]); const saveButtonRef = ref(null); const watchChanges = ref(); +let isNotEqual = ref(false); +let isLastRowEmpty = ref(false); +const isFirstFetch = ref(true); const formUrl = computed(() => $props.url); const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']); @@ -122,9 +133,14 @@ async function fetch(data) { const rows = keyData ? data[keyData] : data; resetData(rows); emit('onFetch', rows); + if (isFirstFetch.value && $props.insertOnLoad) { + await insert(); + } + isFirstFetch.value = false; return rows; } + function resetData(data) { if (!data) return; if (data && Array.isArray(data)) { @@ -132,12 +148,27 @@ function resetData(data) { data.map((d) => (d.$index = $index++)); } originalData.value = JSON.parse(JSON.stringify(data)); + formData.value = JSON.parse(JSON.stringify(data)); if (watchChanges.value) watchChanges.value(); //destroy watcher - watchChanges.value = watch(formData, () => (hasChanges.value = true), { deep: true }); + watchChanges.value = watch(formData, (nVal) => { + hasChanges.value = false; + for(let index = 0; index < nVal.length; index++) { + const curRow = nVal[index]; + const originalRow = originalData.value[index]; + if(originalRow && JSON.stringify(removeIndexField(curRow)) == JSON.stringify(removeIndexField(originalRow))) continue; + for(const key in curRow) { + if(key == '$index') continue; + if(Object.hasOwn($props.dataRequired || {}, key)) continue; + if(curRow[key]) { + hasChanges.value = true; + break; + } + } + } + }, { deep: true }); } - async function reset() { await fetch(originalData.value); hasChanges.value = false; @@ -165,7 +196,9 @@ async function onSubmit() { }); } isLoading.value = true; + await saveChanges($props.saveFn ? formData.value : null); + } async function onSubmitAndGo() { @@ -203,14 +236,36 @@ async function saveChanges(data) { }); } -async function insert(pushData = $props.dataRequired) { - const $index = formData.value.length - ? formData.value[formData.value.length - 1].$index + 1 - : 0; - formData.value.push(Object.assign({ $index }, pushData)); - hasChanges.value = true; +async function insert(pushData = { ...$props.dataRequired, ...$props.dataDefault }) { + const lastRow = formData.value.at(-1); + const $index = lastRow?.$index >= 0 ? lastRow.$index + 1 : 0; + isNotEqual = JSON.stringify(removeIndexField(formData.value)) !== JSON.stringify(removeIndexField(originalData.value)); + isLastRowEmpty = checkLastRow(lastRow); + if (isNotEqual && isLastRowEmpty) return; + + const nRow = Object.assign({ $index }, pushData); + formData.value.push(nRow); + for (const key in nRow) { + if (isChange(nRow, key)) continue; + else { + hasChanges.value = true; + break; + } + } } + +function isChange(row,key){ + return !row[key] || key == '$index' || Object.hasOwn($props.dataRequired || {}, key); +} + +function checkLastRow(lastRow) { + for (const key in lastRow) { + if (isChange(lastRow, key)) continue; + return false; + } + return true; +} async function remove(data) { if (!data.length) return quasar.notify({ @@ -220,7 +275,7 @@ async function remove(data) { const pk = $props.primaryKey; let ids = data.map((d) => d[pk]).filter(Boolean); - let preRemove = data.map((d) => (d[pk] ? null : d.$index)).filter(Boolean); + let preRemove = data.map((d) => (d[pk] ? null : d.$index)).filter(index => index !== null && index !== undefined); let newData = formData.value; if (preRemove.length) { @@ -249,8 +304,23 @@ async function remove(data) { fetch(newData); }); } else { - reset(); + await fetch(formData.value); + hasChanges.value = false; + + const lastRow = formData.value.at(-1); + + const limitedFormData = formData.value.slice(0, originalData.value.length); + isNotEqual = JSON.stringify(removeIndexField(limitedFormData)) !== JSON.stringify(removeIndexField(originalData.value)); + + const additionalRows = formData.value.slice(originalData.value.length); + const hasNonEmptyAdditionalRows = additionalRows.some(row => !checkLastRow(row)); + + + if (isNotEqual || hasNonEmptyAdditionalRows) { + hasChanges.value = true; } +} + emit('update:selected', []); } @@ -261,7 +331,7 @@ function getChanges() { const pk = $props.primaryKey; for (const [i, row] of formData.value.entries()) { if (!row[pk]) { - creates.push(row); + creates.push(Object.assign(row, { ...$props.dataRequired })); } else if (originalData.value[i]) { const data = getDifferences(originalData.value[i], row); if (!isEmpty(data)) { @@ -287,6 +357,16 @@ function isEmpty(obj) { return !Object.keys(obj).length; } +function removeIndexField(data) { + if (Array.isArray(data)) { + return data.map(({ $index, ...rest }) => rest); + } else if (typeof data === 'object' && data !== null) { + const { $index, ...rest } = data; + return rest; + } +} + + async function reload(params) { const data = await vnPaginateRef.value.fetch(params); fetch(data); diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index c64217198..c712cf2f5 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -649,6 +649,7 @@ const rowCtrlClickFunction = computed(() => { :class="$attrs['class'] ?? 'q-px-md'" :limit="$attrs['limit'] ?? 100" ref="CrudModelRef" + :insert-on-load="crudModel.insertOnLoad" @on-fetch="(...args) => emit('onFetch', ...args)" :search-url="searchUrl" :disable-infinite-scroll="isTableMode" diff --git a/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue b/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue index 20cc1cc71..2839c299e 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue @@ -85,6 +85,7 @@ onBeforeMount(async () => { data-key="InvoiceInDueDays" url="InvoiceInDueDays" :filter="filter" + :insert-on-load="false" auto-load :data-required="{ invoiceInFk: invoiceId }" v-model:selected="rowsSelected" diff --git a/src/pages/Item/Card/ItemTags.vue b/src/pages/Item/Card/ItemTags.vue index ab26b9cae..3522c8363 100644 --- a/src/pages/Item/Card/ItemTags.vue +++ b/src/pages/Item/Card/ItemTags.vue @@ -76,15 +76,22 @@ const insertTag = (rows) => { model="ItemTags" url="ItemTags" :data-required="{ - $index: undefined, itemFk: route.params.id, - priority: undefined, tag: { - isFree: undefined, + isFree: true, + value: undefined, + name: undefined, + }, + + }" + :data-default="{ + tag: { + isFree: true, value: undefined, name: undefined, }, tagFk: undefined, + priority: undefined, }" :default-remove="false" :user-filter="{ diff --git a/src/pages/Item/Card/ItemTax.vue b/src/pages/Item/Card/ItemTax.vue index 8060481f0..dffab6da5 100644 --- a/src/pages/Item/Card/ItemTax.vue +++ b/src/pages/Item/Card/ItemTax.vue @@ -58,6 +58,7 @@ const submitTaxes = async (data) => { :save-fn="submitTaxes" :filter="taxesFilter" :default-remove="false" + :insert-on-load="false" data-key="ItemTax" model="ItemTax" ref="ItemTaxRef" diff --git a/src/pages/Ticket/Card/TicketPackage.vue b/src/pages/Ticket/Card/TicketPackage.vue index 5fbf4c800..3ff3df6ed 100644 --- a/src/pages/Ticket/Card/TicketPackage.vue +++ b/src/pages/Ticket/Card/TicketPackage.vue @@ -24,10 +24,10 @@ const crudModelFilter = reactive({ where: { ticketFk: route.params.id }, }); -const crudModelRequiredData = computed(() => ({ +const crudModelDefaultData = computed(() => ({ + created: Date.vnNew(), packagingFk: null, quantity: 0, - created: Date.vnNew(), ticketFk: route.params.id, })); @@ -59,7 +59,7 @@ watch( url="TicketPackagings" model="TicketPackagings" :filter="crudModelFilter" - :data-required="crudModelRequiredData" + :data-default="crudModelDefaultData" :default-remove="false" auto-load style="max-width: 800px" diff --git a/src/pages/Ticket/Card/TicketSale.vue b/src/pages/Ticket/Card/TicketSale.vue index 2fb305cc3..61c6b861a 100644 --- a/src/pages/Ticket/Card/TicketSale.vue +++ b/src/pages/Ticket/Card/TicketSale.vue @@ -690,6 +690,7 @@ watch( :create-as-dialog="false" :crud-model="{ disableInfiniteScroll: true, + insertOnLoad: false, }" :default-remove="false" :default-reset="false"