This commit is contained in:
Xuan Loi
2026-01-09 17:25:23 +07:00
commit ae1ea57130
315 changed files with 57694 additions and 0 deletions

202
components/SearchBox.vue Normal file
View File

@@ -0,0 +1,202 @@
<template>
<div>
<div class="field has-addons">
<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" />
</div>
<div class="dropdown-menu" role="menu">
<div class="dropdown-content px-3" style="min-width: 250px;">
<p class="has-text-findata" v-if="data.length===0">Không giá trị thỏa mãn</p>
<ScrollBox v-bind="{data: data, name: field, fontsize: 14, maxheight: '200px'}" @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="viewaddon">
<button class="button is-dark px-2" @click="viewInfo()">
<SvgIcon v-bind="{name: 'view.svg', type: 'white', size: 22}"></SvgIcon>
</button>
</div>
<div class="control" v-if="addon">
<button class="button is-primary px-2" @click="addNew()">
<SvgIcon v-bind="{name: 'add1.png', type: 'white', size: 22}"></SvgIcon>
</button>
</div>
</div>
<Modal @dataevent="dataevent" @close="showmodal=undefined" v-bind="showmodal" v-if="showmodal"></Modal>
</div>
</template>
<script setup>
const emit = defineEmits(['option', 'modalevent'])
import ScrollBox from '~/components/datatable/ScrollBox'
const { $nonAccent, $find, $getdata, $copy, $empty, $findapi, $findIndex} = useNuxtApp()
var props = defineProps({
api: String,
field: String,
column: Array,
first: Boolean,
optionid: Number,
filter: Object,
addon: Object,
viewaddon: Object,
position: String,
disabled: Boolean,
vdata: Object
})
var search = undefined
var data = ref([])
var timer = undefined
var value = ref()
var selected = undefined
var showmodal = ref()
var params = props.api? $findapi(props.api)['params'] : {}
var orgdata = undefined
var error = false
var focused = ref(false)
var count1 = 0
var count2 = 0
var pos = undefined
async function initData() {
getPos()
if(props.vdata) {
orgdata = $copy(props.vdata)
orgdata.map(v=>v.search = $nonAccent(v[props.field]))
}
if(props.first) {
data.value = orgdata? $copy(orgdata) : await getData()
if(props.optionid) {
let f = {}
f[props.field] = props.optionid
if(props.optionid>0) f = {id: props.optionid}
selected = $find(data.value, f)
if(selected && props.vdata) {
return value.value = selected[props.field]
}
}
} else if(props.optionid) {
selected = await $getdata(props.api, {id: props.optionid}, undefined, true)
}
if(selected) doSelect(selected)
}
/*watch(()=> {
optionid: function(newVal) {
if(optionid) selected = $find(data, {id: optionid})
if(selected) doSelect(selected)
else value = undefined
}
filter: async function(newVal) {
data = await getData()
},
vdata: function(newval) {
if(newval) {
orgdata = $copy(props.vdata)
orgdata.map(v=>v.search = $nonAccent(v[field]))
data = $copy(orgdata)
selected = undefined
value = undefined
if(optionid) selected = $find(data, {id: optionid})
if(selected) doSelect(selected)
}
}
}}*/
function choose(v) {
focused.value = false
count1 = 0
count2 = 0
doSelect(v)
}
function setFocus() {
focused.value = true
count1 = 0
count2 = 0
}
function lostFocus() {
setTimeout(()=>{
focused.value = false
if($empty(value.value)) emit('option', null)
}, 200)
}
function pressEnter() {
if(data.length===0) return
choose(data[0])
}
function doClick() {
count1 += 1
}
function doSelect(option) {
if($empty(option)) return
selected = option
value.value = selected[props.field]
emit('option', option)
emit('modalevent', {name: 'option', data: option})
}
function findObject(val) {
let rows = $copy(orgdata)
if($empty(val)) data.value = rows
else {
let text = $nonAccent(val)
data.value = rows.filter(v=>v.search.toLowerCase().indexOf(text.toLowerCase())>=0)
}
}
async function getData() {
params.filter = props.filter
params.sort = '-id'
let data = await $getdata(props.api, undefined, params)
return data
}
async function getApi(val) {
if(props.vdata) return findObject(val)
let text = val? val.toLowerCase() : ''
let f = {}
props.column.map(v=>{
f[`${v}__icontains`] = text
})
params.filter_or = f
if(props.filter) params.filter = $copy(props.filter)
data.value = await $getdata(props.api, undefined, params)
}
function beginSearch(e) {
let val = e.target.value
search = val
if (timer) clearTimeout(timer)
timer = setTimeout(() => getApi(val), 150)
if($empty(val)) emit('option', null)
}
function addNew() {
showmodal.value = $copy(props.addon)
}
function dataevent(v) {
let idx = $findIndex(data.value, {id: v.id})
idx<0? data.value.push(v) : data.value[idx] = v
doSelect(v)
}
function viewInfo() {
if(!selected) return $dialog('Vui lòng lựa chọn trước khi xem thông tin.', 'Thông báo')
let copy = $copy(props.viewaddon)
copy.vbind = {row: selected}
showmodal.value = copy
}
function getPos() {
switch(props.position) {
case 'is-top-left':
pos = 'is-up is-left'
break;
case 'is-top-right':
pos = 'is-up is-right'
break;
case 'is-bottom-left':
pos = 'is-right'
break;
case 'is-bottom-right':
pos = 'is-right'
break;
}
}
initData()
</script>