Files
system/components/datatable/DataView.vue
Xuan Loi ae1ea57130 changes
2026-01-09 17:25:23 +07:00

402 lines
11 KiB
Vue

<template>
<TimeOption
v-bind="{ pagename: vpagename, api: api, timeopt: timeopt, filter: optfilter, importdata: props.importdata, newDataAvailable: newDataAvailable, params: vparams }"
ref="timeopt" @option="timeOption" @excel="exportExcel" @add="insert" @manual-refresh="manualRefresh" @refresh-data="refreshData"
@import="openImportModal" class="mb-3" v-if="timeopt"></TimeOption>
<DataTable v-bind="{ pagename: vpagename }" @edit="edit" @insert="insert" @dataevent="dataEvent" v-if="pagedata" />
<Modal @close="showmodal = undefined" v-bind="showmodal" v-if="showmodal" />
</template>
<script setup>
import TimeOption from '~/components/datatable/TimeOption'
import { useStore } from '~/stores/index'
import { ref, watch, onBeforeUnmount } from 'vue'
const emit = defineEmits(['modalevent', 'dataevent', 'dataUpdated'])
const store = useStore()
const props = defineProps({
pagename: String,
api: String,
setting: String,
filter: Object,
params: Object,
data: Object,
modal: Object,
timeopt: Object,
realtime: Object,
importdata: Object
})
const { $copy, $find, $findapi, $getapi, $setpage, $clone, $stripHtml, $snackbar, $dayjs } = useNuxtApp()
const showmodal = ref()
const pagedata = ref()
const newDataAvailable = ref(false)
const pendingNewData = ref(null)
const lastDataHash = ref(null)
const pollingInterval = ref(null)
let vpagename = props.pagename
let vfilter = props.filter ? $copy(props.filter) : undefined
let vparams = props.params ? $copy(props.params) : undefined
let connection = undefined
let optfilter = props.filter || (props.params ? props.params.filter : undefined)
const realtimeConfig = ref({ time: 0, update: "true" })
if (props.realtime) {
realtimeConfig.value = { time: props.realtime.time || 0, update: props.realtime.update }
}
if (vparams?.filter) {
for (const [key, value] of Object.entries(vparams.filter)) {
if (value.toString().indexOf('$') >= 0) {
vparams.filter[key] = store[value.replace('$', '')].id
}
}
}
const generateDataHash = (data) => {
if (!data) return null
try {
// Use a replacer with JSON.stringify to create a stable string representation
// by sorting the keys of any object. This ensures the hash is consistent
// even if the API returns objects with keys in a different order.
const replacer = (key, value) =>
value && typeof value === 'object' && !Array.isArray(value)
? Object.keys(value)
.sort()
.reduce((sorted, key) => {
sorted[key] = value[key];
return sorted;
}, {})
: value;
const stringToHash = JSON.stringify(data, replacer);
return stringToHash.split('').reduce((a, b) => {
a = ((a << 5) - a) + b.charCodeAt(0)
return a & a
}, 0)
} catch (e) {
console.error('Error generating data hash:', e);
return null
}
}
const startAutoCheck = () => {
if (pollingInterval.value) clearInterval(pollingInterval.value)
if (realtimeConfig.value.time && realtimeConfig.value.time > 0) {
pollingInterval.value = setInterval(() => checkDataChanges(), realtimeConfig.value.time * 1000)
}
}
const checkDataChanges = async () => {
try {
const connlist = []
const conn1 = $findapi(props.api)
if (vfilter) {
const filter = $copy(conn1.params.filter) || {}
for (const [key, value] of Object.entries(vfilter)) {
filter[key] = value
}
for (const [key, value] of Object.entries(filter)) {
if (value.toString().indexOf('$') >= 0) {
filter[key] = store[value.replace('$', '')].id
}
}
conn1.params.filter = filter
}
if (vparams) conn1.params = $copy(vparams)
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' }
})
connlist.push(conn1)
const rs = await $getapi(connlist)
const obj = $find(rs, { name: props.api })
const newMetadata = obj ? obj.data.rows : {}
const newHash = generateDataHash(newMetadata)
if (lastDataHash.value === null) {
lastDataHash.value = newHash
return
}
if (newHash !== lastDataHash.value) {
lastDataHash.value = newHash
if (realtimeConfig.value.update === "true") {
await loadFullDataAsync()
emit('dataUpdated', { newData: store[vpagename].data, autoUpdate: true, hasChanges: true })
} else {
newDataAvailable.value = true
emit('dataUpdated', { newData: null, autoUpdate: false, hasChanges: true })
}
}
} catch (error) {
console.error('Error checking data:', error)
}
}
const loadFullDataAsync = async () => {
try {
const connlist = []
const conn1 = $findapi(props.api)
if (vfilter) {
const filter = $copy(conn1.params.filter) || {}
for (const [key, value] of Object.entries(vfilter)) {
filter[key] = value
}
for (const [key, value] of Object.entries(filter)) {
if (value.toString().indexOf('$') >= 0) {
filter[key] = store[value.replace('$', '')].id
}
}
conn1.params.filter = filter
}
if (vparams) conn1.params = $copy(vparams)
delete conn1.params.summary
delete conn1.params.distinct_values
connlist.push(conn1)
const rs = await $getapi(connlist)
const obj = $find(rs, { name: props.api })
const newData = obj ? $copy(obj.data.rows) : []
updateDataDisplay(newData)
} catch (error) {
console.error('Error loading full data:', error)
}
}
const openImportModal = () => {
const copy = $copy(props.importdata)
showmodal.value = copy
}
const updateDataDisplay = (newData) => {
const copy = $clone(store[vpagename])
copy.data = newData
copy.update = { data: newData }
store.commit(vpagename, copy)
newDataAvailable.value = false
pendingNewData.value = null
}
const manualRefresh = () => {
if (pendingNewData.value) {
updateDataDisplay(pendingNewData.value)
}
}
const refreshData = async () => {
if (pollingInterval.value) clearInterval(pollingInterval.value);
newDataAvailable.value = false;
pendingNewData.value = null;
await getApi();
// After a manual refresh, force a metadata check to get the correct new hash.
lastDataHash.value = null;
await checkDataChanges();
newDataAvailable.value = false;
startAutoCheck();
}
watch(() => props.realtime, (newVal) => {
if (newVal) {
realtimeConfig.value.time = newVal.time || 0
realtimeConfig.value.update = newVal.update === true
startAutoCheck()
}
}, { deep: true })
onBeforeUnmount(() => {
if (pollingInterval.value) clearInterval(pollingInterval.value)
})
const timeOption = (v) => {
if (!v) return getApi()
if (v.filter_or) {
if (vfilter) vfilter = undefined
if (vparams) {
vparams.filter_or = v.filter_or
} else {
const found = $copy($findapi(props.api))
found.params.filter_or = v.filter_or
if (props.filter) {
const filter = $copy(props.filter)
for (const [key, value] of Object.entries(filter)) {
if (value.toString().indexOf('$') >= 0) {
filter[key] = store[value.replace('$', '')].id
}
}
found.params.filter = filter
}
vparams = found.params
}
return getApi()
}
let filter = vfilter ? vfilter : (props.params ? props.params.filter || {} : {})
for (const [key, value] of Object.entries(v.filter)) {
filter[key] = value
}
for (const [key, value] of Object.entries(filter)) {
if (value.toString().indexOf('$') >= 0) {
filter[key] = store[value.replace('$', '')].id
}
}
if (vfilter) {
vfilter = filter
vparams = undefined
} else if (vparams) {
vparams.filter = filter
vparams.filter_or = undefined
}
if (!vfilter && !vparams) vfilter = filter
getApi()
}
const edit = (v) => {
const copy = props.modal ? $copy(props.modal) : {}
const f = copy.vbind ? copy.vbind : { pagename: vpagename, api: props.api, row: v }
f.pagename = vpagename
f.row = v
copy.vbind = f
showmodal.value = copy
}
const insert = () => {
const copy = props.modal ? $copy(props.modal) : {}
const f = copy.vbind ? copy.vbind : { pagename: vpagename, api: props.api }
f.pagename = vpagename
copy.vbind = f
showmodal.value = copy
}
const getApi = async () => {
const connlist = []
let row = props.setting?.id ? $copy(props.setting) : undefined
if (!row) {
const found = $find(store.settings.filter(v => v), props.setting > 0 ? { id: props.setting } : { name: props.setting })
if (found) row = $copy(found)
}
if (!row) {
const conn = $findapi('usersetting')
conn.params.filter = props.setting > 0 ? { id: props.setting } : { name: props.setting }
connlist.push(conn)
}
let data = props.data ? $copy(props.data) : undefined
if (!data) {
const conn1 = $findapi(props.api)
if (vfilter) {
const filter = conn1.params.filter || {}
for (const [key, value] of Object.entries(vfilter)) {
filter[key] = value
}
for (const [key, value] of Object.entries(filter)) {
if (value.toString().indexOf('$') >= 0) {
filter[key] = store[value.replace('$', '')].id
}
}
conn1.params.filter = filter
}
if (vparams) conn1.params = vparams
connection = conn1
connlist.push(conn1)
}
let obj = undefined
if (connlist.length > 0) {
const rs = await $getapi(connlist)
const ele = $find(rs, { name: 'usersetting' })
if (ele) {
row = $find(ele.data.rows, { name: props.setting.name || props.setting })
const copy = $copy(store.settings)
copy.push(row)
store.commit('settings', copy)
}
obj = $find(rs, { name: props.api })
if (obj) data = $copy(obj.data.rows)
}
pagedata.value = $setpage(vpagename, row, obj)
const copy = $clone(pagedata.value)
copy.data = data
copy.update = { data: data }
store.commit(vpagename, copy)
// lastDataHash.value = generateDataHash(data); // This is now handled by checkDataChanges after a refresh.
}
const dataEvent = (v, field, data) => {
if (data?.modal) {
const copy = $copy(data.modal)
const f = copy.vbind ? copy.vbind : {}
if (!f.api) f.api = props.api
f.pagename = vpagename
if (!f.row) f.row = v
copy.vbind = f
copy.field = field
showmodal.value = copy
}
emit('modalevent', { name: 'dataevent', data: { row: v, field: field } })
emit('dataevent', v, field)
}
const exportExcel = async () => {
if (!props.api) return
const found = $findapi('exportcsv')
found.params = connection.params
const fields = pagedata.value.fields
.filter(v => (v.show && v.export !== 'no') || v.export === 'yes')
.map(x => ({ name: x.name, label: $stripHtml(x.label) }))
found.params.fields = JSON.stringify(fields)
found.url = connection.url.replace('data/', 'exportcsv/')
const rs = await $getapi([found])
if (rs === 'error') {
$snackbar('Đã xảy ra lỗi. Vui lòng thử lại.')
return
}
const url = window.URL.createObjectURL(new Blob([rs[0].data]))
const link = document.createElement('a')
const fileName = `${$dayjs(new Date()).format('YYYYMMDDhhmmss')}-data.csv`
link.href = url
link.setAttribute('download', fileName)
document.body.appendChild(link)
link.click()
link.remove()
}
if (!props.timeopt) await getApi()
startAutoCheck()
</script>