Initial commit
This commit is contained in:
275
app/components/SearchBox.vue
Normal file
275
app/components/SearchBox.vue
Normal file
@@ -0,0 +1,275 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="field has-addons" :id="docid">
|
||||
<div class="control has-icons-left is-expanded">
|
||||
<div :class="`dropdown ${ pos || ''} ${focused? 'is-active' : ''}`" style="width: 100%;">
|
||||
<div class="dropdown-trigger" style="width: 100%;">
|
||||
<input
|
||||
:disabled="disabled"
|
||||
:class="`input ${error? 'is-danger' : ''} ${disabled? 'has-text-dark' : ''}`"
|
||||
type="text"
|
||||
@focus="setFocus"
|
||||
@blur="lostFocus"
|
||||
@keyup.enter="pressEnter"
|
||||
@keyup="beginSearch"
|
||||
v-model="value"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
</div>
|
||||
<div class="dropdown-menu" style="min-width: 100%" role="menu" @click="doClick()">
|
||||
<div class="dropdown-content px-3" style="min-width: 100%;">
|
||||
<p class="has-text-warning" v-if="data.length===0">{{ isVietnamese ? 'Không có giá trị thỏa mãn' : 'No matching values' }}</p>
|
||||
<ScrollBox v-bind="{data: data, name: field, fontsize: 14, maxheight: '200px', notick: true}" @selected="choose" v-else></ScrollBox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="icon is-left">
|
||||
<SvgIcon v-bind="{name: 'magnify.svg', type: 'gray', size: 22}"></SvgIcon>
|
||||
</span>
|
||||
</div>
|
||||
<div class="control" v-if="clearable && value">
|
||||
<button class="button is-primary px-2" @click="clearValue" style="height: 100%" type="button">
|
||||
<SvgIcon v-bind="{name: 'close.svg', type: 'white', size: 24}"></SvgIcon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control" v-if="viewaddon">
|
||||
<button class="button is-dark px-2" @click="viewInfo()" style="height: 100%" type="button">
|
||||
<SvgIcon v-bind="{name: 'view.svg', type: 'white', size: 24}"></SvgIcon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control" v-if="addon">
|
||||
<button class="button is-primary px-2" @click="addNew()" style="height: 100%" type="button">
|
||||
<SvgIcon v-bind="{name: 'add1.png', type: 'white', size: 24}"></SvgIcon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Modal @dataevent="dataevent" @close="showmodal=undefined" v-bind="showmodal" v-if="showmodal"></Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useStore } from "@/stores/index";
|
||||
|
||||
export default {
|
||||
props: ['api', 'field', 'column', 'first', 'optionid', 'filter', 'addon', 'viewaddon', 'position', 'disabled', 'vdata', 'clearable', 'placeholder', 'searchfield'],
|
||||
setup() {
|
||||
const store = useStore();
|
||||
return { store };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
search: undefined,
|
||||
data: [],
|
||||
timer: undefined,
|
||||
value: undefined,
|
||||
selected: undefined,
|
||||
showmodal: undefined,
|
||||
params: this.api? this.$findapi(this.api)['params'] : undefined,
|
||||
orgdata: undefined,
|
||||
error: false,
|
||||
focused: false,
|
||||
count1: 0,
|
||||
count2: 0,
|
||||
docid: this.$id(),
|
||||
pos: undefined,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isVietnamese() {
|
||||
return this.store.lang === "vi";
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.getPos()
|
||||
if(this.vdata) {
|
||||
this.orgdata = this.$copy(this.vdata)
|
||||
this.orgdata.map(v=>v.search = this.$nonAccent(v[this.field]))
|
||||
}
|
||||
if(this.first) {
|
||||
this.data = this.orgdata? this.$copy(this.orgdata) : await this.getData()
|
||||
if(this.optionid) {
|
||||
let f = {}
|
||||
f[this.field] = this.optionid
|
||||
if(this.optionid>0) f = {id: this.optionid}
|
||||
this.selected = this.$find(this.data, f)
|
||||
if(this.selected && this.vdata) {
|
||||
return this.value = this.selected[this.field]
|
||||
}
|
||||
}
|
||||
} else if(this.optionid) {
|
||||
this.selected = await this.$getdata(this.api, {id: this.optionid}, undefined, true)
|
||||
}
|
||||
if(this.selected) this.doSelect(this.selected)
|
||||
},
|
||||
watch: {
|
||||
optionid: function(newVal) {
|
||||
if(this.optionid) this.selected = this.$find(this.data, {id: this.optionid})
|
||||
if(this.selected) this.doSelect(this.selected)
|
||||
else this.value = undefined
|
||||
},
|
||||
filter: async function(newVal) {
|
||||
this.data = await this.getData()
|
||||
},
|
||||
vdata: function(newval) {
|
||||
if(newval) {
|
||||
this.orgdata = this.$copy(this.vdata)
|
||||
this.orgdata.map(v=>v.search = this.$nonAccent(v[this.field]))
|
||||
this.data = this.$copy(this.orgdata)
|
||||
this.selected = undefined
|
||||
this.value = undefined
|
||||
if(this.optionid) this.selected = this.$find(this.data, {id: this.optionid})
|
||||
if(this.selected) this.doSelect(this.selected)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
choose(v) {
|
||||
this.focused = false
|
||||
this.count1 = 0
|
||||
this.count2 = 0
|
||||
this.doSelect(v)
|
||||
},
|
||||
setFocus() {
|
||||
this.focused = true
|
||||
this.count1 = 0
|
||||
this.count2 = 0
|
||||
},
|
||||
lostFocus() {
|
||||
let self = this
|
||||
setTimeout(()=>{
|
||||
if(self.focused && self.count1===0) self.focused = false
|
||||
}, 200)
|
||||
},
|
||||
pressEnter() {
|
||||
if(this.data.length===0) return
|
||||
this.choose(this.data[0])
|
||||
},
|
||||
doClick() {
|
||||
this.count1 += 1
|
||||
},
|
||||
doSelect(option) {
|
||||
if(this.$empty(option)) return
|
||||
this.$emit('option', option)
|
||||
this.$emit('modalevent', {name: 'option', data: option})
|
||||
this.selected = option
|
||||
this.value = this.selected[this.field]
|
||||
},
|
||||
clearValue() {
|
||||
this.value = undefined
|
||||
this.selected = undefined
|
||||
this.$emit('option', null)
|
||||
this.$emit('modalevent', {name: 'option', data: null})
|
||||
},
|
||||
findObject(val) {
|
||||
let rows = this.$copy(this.orgdata)
|
||||
if(this.$empty(val)) this.data = rows
|
||||
else {
|
||||
let text = this.$nonAccent(val)
|
||||
this.data = rows.filter(v=>v.search.toLowerCase().indexOf(text.toLowerCase())>=0)
|
||||
}
|
||||
},
|
||||
async getData() {
|
||||
this.params.filter = this.filter
|
||||
let data = await this.$getdata(this.api, undefined, this.params)
|
||||
return data
|
||||
},
|
||||
async getApi(val) {
|
||||
if(this.vdata) return this.findObject(val)
|
||||
let text = val? val.toLowerCase() : ''
|
||||
let f = {}
|
||||
|
||||
// Sử dụng searchfield nếu có, nếu không thì dùng column
|
||||
const fieldsToSearch = this.searchfield || this.column;
|
||||
|
||||
fieldsToSearch.map(v=>{
|
||||
f[`${v}__icontains`] = text
|
||||
})
|
||||
this.params.filter_or = f
|
||||
if(this.filter) this.params.filter = this.$copy(this.filter)
|
||||
let arr = await this.$getdata(this.api, undefined, this.params)
|
||||
this.data = this.$copy(arr)
|
||||
},
|
||||
beginSearch(e) {
|
||||
let val = e.target.value
|
||||
this.search = val
|
||||
if (this.timer) clearTimeout(this.timer)
|
||||
this.timer = setTimeout(() => this.getApi(val), 150)
|
||||
},
|
||||
addNew() {
|
||||
this.showmodal = this.$copy(this.addon)
|
||||
},
|
||||
dataevent(v) {
|
||||
console.log("SearchBox received dataevent:", v); // Debug log
|
||||
|
||||
if (!v || !v.id) {
|
||||
console.error("Invalid data received in SearchBox:", v);
|
||||
return;
|
||||
}
|
||||
|
||||
// Tìm và cập nhật trong danh sách
|
||||
let idx = this.$findIndex(this.data, {id: v.id})
|
||||
if (idx < 0) {
|
||||
// Nếu chưa có trong danh sách, thêm vào đầu
|
||||
this.data.unshift(v);
|
||||
console.log("Added new item to data:", v);
|
||||
} else {
|
||||
// Nếu đã có, cập nhật
|
||||
this.data[idx] = v;
|
||||
console.log("Updated existing item in data:", v);
|
||||
}
|
||||
|
||||
// Cập nhật orgdata nếu có
|
||||
if (this.orgdata) {
|
||||
let orgIdx = this.$findIndex(this.orgdata, {id: v.id});
|
||||
if (orgIdx < 0) {
|
||||
this.orgdata.unshift(v);
|
||||
// Thêm search field cho orgdata
|
||||
if (this.field && v[this.field]) {
|
||||
v.search = this.$nonAccent(v[this.field]);
|
||||
}
|
||||
} else {
|
||||
this.orgdata[orgIdx] = v;
|
||||
if (this.field && v[this.field]) {
|
||||
this.orgdata[orgIdx].search = this.$nonAccent(v[this.field]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **Tự động select item vừa tạo/cập nhật**
|
||||
this.doSelect(v);
|
||||
|
||||
// Đóng modal
|
||||
this.showmodal = undefined;
|
||||
|
||||
console.log("SearchBox data after update:", this.data);
|
||||
},
|
||||
viewInfo() {
|
||||
if(!this.selected) return this.$dialog(this.isVietnamese ? 'Vui lòng lựa chọn trước khi xem thông tin.' : 'Please select before viewing', this.isVietnamese ? 'Thông báo' : 'Notice')
|
||||
let copy = this.$copy(this.viewaddon)
|
||||
copy.vbind = {row: this.selected}
|
||||
this.showmodal = copy
|
||||
},
|
||||
getPos() {
|
||||
switch(this.position) {
|
||||
case 'is-top-left':
|
||||
this.pos = 'is-up is-left'
|
||||
break;
|
||||
case 'is-top-right':
|
||||
this.pos = 'is-up is-right'
|
||||
break;
|
||||
case 'is-bottom-left':
|
||||
this.pos = 'is-right'
|
||||
break;
|
||||
case 'is-bottom-right':
|
||||
this.pos = 'is-right'
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.field:not(:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user