changes
This commit is contained in:
378
components/datatable/TimeOption.vue
Normal file
378
components/datatable/TimeOption.vue
Normal file
@@ -0,0 +1,378 @@
|
||||
<template>
|
||||
<div class="pb-1" style="border-bottom: 2px solid #3c5b63" v-if="array || !enableTime">
|
||||
<div class="columns mx-0 mb-0">
|
||||
<div class="column is-8 px-0 pb-0" v-if="enableTime">
|
||||
<div class="field is-grouped is-grouped-multiline mb-0">
|
||||
<div class="control mb-0">
|
||||
<Caption v-bind="{ title: lang === 'vi' ? 'Thời gian' : 'Time', type: 'has-text-warning' }" />
|
||||
</div>
|
||||
<div class="control mb-0" v-for="v in array" :key="v.code">
|
||||
<span class="icon-text fsb-16 has-text-warning px-1" v-if="v.code === current">
|
||||
<SvgIcon v-bind="{ name: 'tick.png', size: 20 }"></SvgIcon>
|
||||
<span>{{ v.name }}</span>
|
||||
</span>
|
||||
<span class="icon-text has-text-grey hyperlink px-1 fsb-16" @click="changeOption(v)" v-else>{{
|
||||
v.name
|
||||
}}</span>
|
||||
</div>
|
||||
<span v-if="newDataAvailable" class="has-text-danger is-italic is-size-6 ml-2">Có dữ liệu mới, vui lòng làm
|
||||
mới.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4 px-0">
|
||||
<div class="field is-grouped is-grouped-multiline mb-0">
|
||||
<div class="control mb-0">
|
||||
<Caption v-bind="{
|
||||
type: 'has-text-warning',
|
||||
title: lang === 'vi' ? `Tìm ${viewport === 1 ? '' : 'kiếm'}` : 'Search',
|
||||
}" />
|
||||
</div>
|
||||
<div class="control mb-0">
|
||||
<input class="input is-small" type="text" v-model="text"
|
||||
:style="`${viewport === 1 ? 'width:150px;' : ''} border: 1px solid #BEBEBE;`" @keyup="startSearch"
|
||||
id="input" :placeholder="lang === 'vi' ? 'Nhập từ khóa...' : 'Enter keyword...'" />
|
||||
</div>
|
||||
<div class="control mb-0">
|
||||
<span class="tooltip" v-if="importdata">
|
||||
<a class="mr-2" @click="openImport()">
|
||||
<SvgIcon v-bind="{
|
||||
name: 'upload.svg',
|
||||
type: 'findata',
|
||||
size: 22
|
||||
}"></SvgIcon>
|
||||
</a>
|
||||
<span class="tooltiptext" style="min-width: max-content">
|
||||
{{ lang === "vi" ? "Nhập dữ liệu" : "Import data" }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="tooltip" v-if="enableAdd">
|
||||
<a class="mr-2" @click="$emit('add')">
|
||||
<SvgIcon v-bind="{ name: 'add1.png', type: 'findata', size: 22 }"></SvgIcon>
|
||||
</a>
|
||||
<span class="tooltiptext" style="min-width: max-content">{{
|
||||
lang === "vi" ? "Thêm mới" : "Add new"
|
||||
}}</span>
|
||||
</span>
|
||||
<span class="tooltip">
|
||||
<a class="mr-2" @click="$emit('excel')">
|
||||
<SvgIcon v-bind="{ name: 'excel.png', type: 'findata', size: 22 }"></SvgIcon>
|
||||
</a>
|
||||
<span class="tooltiptext" style="min-width: max-content">{{
|
||||
lang === "vi" ? "Xuất excel" : "Export excel"
|
||||
}}</span>
|
||||
</span>
|
||||
|
||||
<span class="tooltip">
|
||||
<a @click="$emit('refresh-data')">
|
||||
<SvgIcon v-bind="{ name: 'refresh.svg', type: 'findata', size: 22 }"></SvgIcon>
|
||||
</a>
|
||||
<span class="tooltiptext" style="min-width: max-content">{{
|
||||
lang === "vi" ? "Làm mới" : "Refresh"
|
||||
}}</span>
|
||||
|
||||
</span>
|
||||
<a class="button is-primary is-loading is-small ml-3" v-if="loading"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Modal @close="showmodal = undefined" v-bind="showmodal" v-if="showmodal"></Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useStore } from "@/stores/index"
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const store = useStore()
|
||||
return { store }
|
||||
},
|
||||
|
||||
props: ["pagename", "api", "timeopt", "filter", "realtime", "newDataAvailable", "params", "importdata"],
|
||||
|
||||
data() {
|
||||
return {
|
||||
options: [
|
||||
{ code: 0, name: this.lang === "vi" ? "Hôm nay" : "Today" },
|
||||
{ code: 1, name: "1D" },
|
||||
{ code: 7, name: "7D" },
|
||||
{ code: 30, name: "1M" },
|
||||
{ code: 90, name: "3M" },
|
||||
{ code: 180, name: "6M" },
|
||||
{ code: 360, name: "1Y" },
|
||||
{ code: 36000, name: this.lang === "vi" ? "Tất cả" : "All" },
|
||||
],
|
||||
showmodal: undefined,
|
||||
current: 7,
|
||||
search: undefined,
|
||||
timer: undefined,
|
||||
text: undefined,
|
||||
status: [{ code: 100, name: "Chọn" }],
|
||||
array: undefined,
|
||||
enableAdd: true,
|
||||
enableTime: true,
|
||||
choices: [], // Sẽ được cập nhật động từ pagedata
|
||||
viewport: this.store.viewport,
|
||||
pagedata: undefined,
|
||||
loading: false,
|
||||
pollingInterval: null,
|
||||
searchableFields: [] // Lưu thông tin các field có thể tìm kiếm
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
pagename(newVal) {
|
||||
this.updateSearchableFields()
|
||||
}
|
||||
},
|
||||
|
||||
async created() {
|
||||
console.log("TimeOption created")
|
||||
|
||||
// Cập nhật searchable fields ngay từ đầu
|
||||
this.updateSearchableFields()
|
||||
|
||||
if (this.viewport < 5) {
|
||||
this.options = [
|
||||
{ code: 0, name: "Hôm nay" },
|
||||
{ code: 1, name: "1D" },
|
||||
{ code: 7, name: "7D" },
|
||||
{ code: 30, name: "1M" },
|
||||
{ code: 90, name: "3M" },
|
||||
{ code: 36000, name: "Tất cả" },
|
||||
]
|
||||
}
|
||||
|
||||
this.checkTimeopt()
|
||||
if (!this.enableTime) return this.$emit("option")
|
||||
|
||||
let found = this.$findapi(this.api)
|
||||
found.commit = undefined
|
||||
|
||||
let filter = this.$copy(this.filter)
|
||||
if (filter) {
|
||||
//dynamic parameter
|
||||
for (const [key, value] of Object.entries(filter)) {
|
||||
if (value.toString().indexOf("$") >= 0) {
|
||||
filter[key] = this.store[value.replace("$", "")].id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found.params.filter) {
|
||||
if (!filter) filter = {}
|
||||
for (const [key, value] of Object.entries(found.params.filter)) {
|
||||
filter[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
this.options.map((v) => {
|
||||
let f = filter ? this.$copy(filter) : {}
|
||||
f["create_time__date__gte"] = this.$dayjs()
|
||||
.subtract(v.code, "day")
|
||||
.format("YYYY-MM-DD")
|
||||
v.filter = f
|
||||
})
|
||||
|
||||
this.$emit("option", this.$find(this.options, { code: this.current }))
|
||||
|
||||
let f = {}
|
||||
this.options.map((v) => {
|
||||
f[`${v.code}`] = {
|
||||
type: "Count",
|
||||
field: "create_time__date",
|
||||
filter: v.filter
|
||||
}
|
||||
})
|
||||
|
||||
let params = { summary: "aggregate", distinct_values: f }
|
||||
found.params = params
|
||||
|
||||
try {
|
||||
let rs = await this.$getapi([found])
|
||||
for (const [key, value] of Object.entries(rs[0].data.rows)) {
|
||||
let found = this.$find(this.options, { code: Number(key) })
|
||||
if (found) {
|
||||
found.name = `${found.name} (${value})`
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error)
|
||||
}
|
||||
|
||||
this.array = this.$copy(this.options)
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.realtime) {
|
||||
const interval = typeof this.realtime === "number" ? this.realtime * 1000 : 5000
|
||||
this.pollingInterval = setInterval(this.refresh, interval)
|
||||
}
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
if (this.pollingInterval) {
|
||||
clearInterval(this.pollingInterval)
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
lang: function () {
|
||||
return this.store.lang
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
// Lấy các field có thể tìm kiếm từ API params.values
|
||||
updateSearchableFields() {
|
||||
try {
|
||||
// Lấy API config
|
||||
const found = this.$findapi(this.api)
|
||||
|
||||
if (!found) {
|
||||
console.warn('Không tìm thấy API config')
|
||||
this.choices = []
|
||||
this.searchableFields = []
|
||||
return
|
||||
}
|
||||
|
||||
// Ưu tiên lấy values từ props.params, nếu không có thì lấy từ API config
|
||||
let valuesString = ''
|
||||
|
||||
if (this.params && this.params.values) {
|
||||
// Lấy từ props.params (ưu tiên cao nhất)
|
||||
valuesString = this.params.values
|
||||
console.log('Using values from props.params')
|
||||
} else if (found.params && found.params.values) {
|
||||
// Lấy từ API config mặc định
|
||||
valuesString = found.params.values
|
||||
console.log('Using values from API default params')
|
||||
} else {
|
||||
console.warn('Không tìm thấy API values trong props hoặc config')
|
||||
this.choices = []
|
||||
this.searchableFields = []
|
||||
return
|
||||
}
|
||||
|
||||
// Parse values string từ API
|
||||
let fieldNames = valuesString.split(',').map(v => v.trim())
|
||||
|
||||
console.log('Raw fieldNames:', fieldNames)
|
||||
console.log('Total fields:', fieldNames.length)
|
||||
|
||||
// Lấy pagedata để lấy label
|
||||
this.pagedata = this.store[this.pagename]
|
||||
|
||||
// Lấy tất cả các field để search (không lọc format)
|
||||
const searchable = fieldNames.filter(fieldName => {
|
||||
// Loại bỏ các field kỹ thuật
|
||||
if (fieldName === 'id' ||
|
||||
fieldName === 'create_time' ||
|
||||
fieldName === 'update_time' ||
|
||||
fieldName === 'created_at' ||
|
||||
fieldName === 'updated_at') {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// Lấy tên và label các field
|
||||
this.choices = searchable
|
||||
this.searchableFields = searchable.map(fieldName => {
|
||||
// Lấy field base name (trước dấu __)
|
||||
const baseFieldName = fieldName.split('__')[0]
|
||||
const fieldInfo = this.pagedata && this.pagedata.fields
|
||||
? this.pagedata.fields.find(f => f.name === baseFieldName)
|
||||
: null
|
||||
|
||||
return {
|
||||
name: fieldName,
|
||||
label: fieldInfo ? fieldInfo.label : fieldName
|
||||
}
|
||||
})
|
||||
|
||||
console.log('Searchable fields:', this.searchableFields)
|
||||
console.log('Choices:', this.choices)
|
||||
console.log('Total searchable fields:', this.choices.length)
|
||||
} catch (error) {
|
||||
console.error('Error updating searchable fields:', error)
|
||||
this.choices = []
|
||||
this.searchableFields = []
|
||||
}
|
||||
},
|
||||
|
||||
refresh() {
|
||||
let found = this.$find(this.options, { code: this.current })
|
||||
this.changeOption(found)
|
||||
},
|
||||
|
||||
changeOption(v) {
|
||||
this.current = v.code
|
||||
if (this.search) {
|
||||
this.text = undefined
|
||||
this.search = undefined
|
||||
}
|
||||
this.$emit("option", this.$find(this.array, { code: this.current }))
|
||||
},
|
||||
|
||||
doSearch() {
|
||||
// Cập nhật choices trước khi search
|
||||
this.updateSearchableFields()
|
||||
|
||||
this.pagedata = this.store[this.pagename]
|
||||
|
||||
if (!this.pagedata || !this.pagedata.fields) {
|
||||
console.warn('Không có pagedata hoặc fields')
|
||||
return
|
||||
}
|
||||
|
||||
let fields = this.pagedata.fields.filter(
|
||||
(v) => this.choices.findIndex((x) => x === v.name) >= 0
|
||||
)
|
||||
|
||||
if (fields.length === 0) {
|
||||
console.warn('Không tìm thấy field để tìm kiếm')
|
||||
return
|
||||
}
|
||||
|
||||
let f = {}
|
||||
fields.map((v) => {
|
||||
f[`${v.name}__icontains`] = this.search
|
||||
})
|
||||
|
||||
console.log('Search filter:', f)
|
||||
this.$emit("option", { filter_or: f })
|
||||
},
|
||||
|
||||
openImport() {
|
||||
if (!this.importdata) return
|
||||
// Emit event lên parent (DataView)
|
||||
this.$emit('import', this.importdata)
|
||||
},
|
||||
|
||||
startSearch(val) {
|
||||
this.search = this.$empty(val.target.value)
|
||||
? ""
|
||||
: val.target.value.trim()
|
||||
|
||||
if (this.timer) clearTimeout(this.timer)
|
||||
this.timer = setTimeout(() => this.doSearch(), 300)
|
||||
},
|
||||
|
||||
checkTimeopt() {
|
||||
if (this.timeopt > 0) {
|
||||
let obj = this.$find(this.options, { code: this.$formatNumber(this.timeopt) })
|
||||
if (obj) this.current = obj.code
|
||||
}
|
||||
if (this.timeopt ? this.$empty(this.timeopt.disable) : true) return
|
||||
if (this.timeopt.disable.indexOf("add") >= 0) this.enableAdd = false
|
||||
if (this.timeopt.disable.indexOf("time") >= 0) this.enableTime = false
|
||||
if (this.timeopt.time) {
|
||||
let obj = this.$find(this.options, { code: this.$formatNumber(this.timeopt.time) })
|
||||
if (obj) this.current = obj.code
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user