✨ 支持账单客户名称和编码远程搜索
This commit is contained in:
@@ -10,12 +10,15 @@
|
|||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:size="size"
|
:size="size"
|
||||||
|
:remote="remoteSearch"
|
||||||
|
:remote-method="remoteSearch ? handleRemoteSearch : undefined"
|
||||||
|
:reserve-keyword="remoteSearch"
|
||||||
style="width: 100%;"
|
style="width: 100%;"
|
||||||
@change="handleChange"
|
@change="handleChange"
|
||||||
@visible-change="handleVisibleChange"
|
@visible-change="handleVisibleChange"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in options"
|
v-for="item in displayOptions"
|
||||||
:key="item[realValueKey]"
|
:key="item[realValueKey]"
|
||||||
:label="getLabel(item)"
|
:label="getLabel(item)"
|
||||||
:value="item[realValueKey]"
|
:value="item[realValueKey]"
|
||||||
@@ -87,6 +90,11 @@ const props = defineProps({
|
|||||||
pageSize: {
|
pageSize: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 1000
|
default: 1000
|
||||||
|
},
|
||||||
|
// 远程搜索时按客户名称/代码查询,避免本地只加载固定数量选项。
|
||||||
|
remoteSearch: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -94,8 +102,11 @@ const emit = defineEmits(['update:modelValue', 'change'])
|
|||||||
|
|
||||||
const innerValue = ref(props.modelValue)
|
const innerValue = ref(props.modelValue)
|
||||||
const options = ref([])
|
const options = ref([])
|
||||||
|
const selectedOptions = ref([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const loaded = ref(false)
|
const loaded = ref(false)
|
||||||
|
const remoteTimer = ref(null)
|
||||||
|
let requestSerial = 0
|
||||||
|
|
||||||
const isCustomer = computed(() => props.type === 'customer')
|
const isCustomer = computed(() => props.type === 'customer')
|
||||||
const realValueKey = computed(() => props.valueKey || (isCustomer.value ? 'customerId' : 'supplierId'))
|
const realValueKey = computed(() => props.valueKey || (isCustomer.value ? 'customerId' : 'supplierId'))
|
||||||
@@ -105,36 +116,68 @@ const realCodeKey = computed(() => props.codeKey || (isCustomer.value ? 'custome
|
|||||||
watch(() => props.modelValue, (val) => {
|
watch(() => props.modelValue, (val) => {
|
||||||
// 多选模式下空值归一化为数组,避免 el-select 告警
|
// 多选模式下空值归一化为数组,避免 el-select 告警
|
||||||
innerValue.value = props.multiple && (val === undefined || val === null) ? [] : val
|
innerValue.value = props.multiple && (val === undefined || val === null) ? [] : val
|
||||||
|
const empty = props.multiple ? !Array.isArray(val) || val.length === 0 : val === undefined || val === null || val === ''
|
||||||
|
if (empty) selectedOptions.value = []
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
function getLabel(item) {
|
function getLabel(item) {
|
||||||
return item[realLabelKey.value] || ''
|
return item[realLabelKey.value] || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchList() {
|
const displayOptions = computed(() => {
|
||||||
if (loaded.value) return Promise.resolve(options.value)
|
const merged = [...selectedOptions.value, ...options.value]
|
||||||
|
const seen = new Set()
|
||||||
|
return merged.filter(item => {
|
||||||
|
const key = String(item[realValueKey.value])
|
||||||
|
if (seen.has(key)) return false
|
||||||
|
seen.add(key)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function fetchList(keyword = '') {
|
||||||
|
if (!props.remoteSearch && loaded.value) return Promise.resolve(options.value)
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const api = isCustomer.value ? listCustomer : listSupplier
|
const api = isCustomer.value ? listCustomer : listSupplier
|
||||||
return api({ pageNum: 1, pageSize: props.pageSize, ...props.params })
|
const currentRequest = ++requestSerial
|
||||||
|
const query = String(keyword || '').trim()
|
||||||
|
return api({
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: props.pageSize,
|
||||||
|
...props.params,
|
||||||
|
...(props.remoteSearch ? { keywords: query || undefined } : {})
|
||||||
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
|
if (currentRequest !== requestSerial) return options.value
|
||||||
options.value = res.rows || []
|
options.value = res.rows || []
|
||||||
loaded.value = true
|
loaded.value = true
|
||||||
return options.value
|
return displayOptions.value
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
loading.value = false
|
if (currentRequest === requestSerial) loading.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleRemoteSearch(keyword) {
|
||||||
|
if (!props.remoteSearch) return
|
||||||
|
if (remoteTimer.value) clearTimeout(remoteTimer.value)
|
||||||
|
remoteTimer.value = setTimeout(() => {
|
||||||
|
remoteTimer.value = null
|
||||||
|
fetchList(keyword)
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
function handleChange(val) {
|
function handleChange(val) {
|
||||||
emit('update:modelValue', val)
|
emit('update:modelValue', val)
|
||||||
if (props.multiple) {
|
if (props.multiple) {
|
||||||
const selected = options.value.filter(item =>
|
const selected = displayOptions.value.filter(item =>
|
||||||
Array.isArray(val) && val.some(v => String(v) === String(item[realValueKey.value]))
|
Array.isArray(val) && val.some(v => String(v) === String(item[realValueKey.value]))
|
||||||
)
|
)
|
||||||
|
selectedOptions.value = selected
|
||||||
emit('change', val, selected)
|
emit('change', val, selected)
|
||||||
} else {
|
} else {
|
||||||
const selected = options.value.find(item => String(item[realValueKey.value]) === String(val))
|
const selected = displayOptions.value.find(item => String(item[realValueKey.value]) === String(val))
|
||||||
|
selectedOptions.value = selected ? [selected] : []
|
||||||
emit('change', val, selected)
|
emit('change', val, selected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,12 +192,18 @@ onMounted(() => {
|
|||||||
fetchList()
|
fetchList()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (remoteTimer.value) clearTimeout(remoteTimer.value)
|
||||||
|
requestSerial++
|
||||||
|
})
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
reload: () => {
|
reload: () => {
|
||||||
loaded.value = false
|
loaded.value = false
|
||||||
|
options.value = []
|
||||||
return fetchList()
|
return fetchList()
|
||||||
},
|
},
|
||||||
getOptions: () => options.value
|
getOptions: () => displayOptions.value
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<el-dialog v-model="dialogOpen" class="generate-dialog" title="生成应收账单" width="1200px" append-to-body destroy-on-close>
|
<el-dialog v-model="dialogOpen" class="generate-dialog" title="生成应收账单" width="1200px" append-to-body destroy-on-close>
|
||||||
<el-form ref="generateFormRef" :model="generateForm" :rules="generateRules" label-width="110px" class="generate-form">
|
<el-form ref="generateFormRef" :model="generateForm" :rules="generateRules" label-width="110px" class="generate-form">
|
||||||
<el-row :gutter="16">
|
<el-row :gutter="16">
|
||||||
<el-col :span="8"><el-form-item label="客户" prop="customerId"><customer-select v-model="generateForm.customerId" placeholder="请选择客户" @change="handleCustomerChange" /></el-form-item></el-col>
|
<el-col :span="8"><el-form-item label="客户" prop="customerId"><customer-select v-model="generateForm.customerId" placeholder="请输入客户名称或代码搜索" :remote-search="true" :page-size="20" @change="handleCustomerChange" /></el-form-item></el-col>
|
||||||
<el-col :span="8"><el-form-item label="币种" prop="currency"><el-select v-model="generateForm.currency" placeholder="请选择币种" style="width: 100%;" @change="handleCurrencyChange"><el-option label="人民币 CNY" value="CNY" /><el-option label="美元 USD" value="USD" /></el-select></el-form-item></el-col>
|
<el-col :span="8"><el-form-item label="币种" prop="currency"><el-select v-model="generateForm.currency" placeholder="请选择币种" style="width: 100%;" @change="handleCurrencyChange"><el-option label="人民币 CNY" value="CNY" /><el-option label="美元 USD" value="USD" /></el-select></el-form-item></el-col>
|
||||||
<el-col :span="8"><el-form-item label="账单截止日" prop="cutoffDate"><el-date-picker v-model="generateForm.cutoffDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择账单截止日" style="width: 100%;" @change="resetPreview" /></el-form-item></el-col>
|
<el-col :span="8"><el-form-item label="账单截止日" prop="cutoffDate"><el-date-picker v-model="generateForm.cutoffDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择账单截止日" style="width: 100%;" @change="resetPreview" /></el-form-item></el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<el-col v-if="customerScoped" :span="1.5"><el-button type="info" plain icon="Close" @click="clearCustomerFilter">清除客户条件</el-button></el-col>
|
<el-col v-if="customerScoped" :span="1.5"><el-button type="info" plain icon="Close" @click="clearCustomerFilter">清除客户条件</el-button></el-col>
|
||||||
</template>
|
</template>
|
||||||
<template #search-customerId="{ model }">
|
<template #search-customerId="{ model }">
|
||||||
<customer-select v-model="model.customerId" placeholder="请选择客户" :page-size="1000" />
|
<customer-select v-model="model.customerId" placeholder="请输入客户名称或代码搜索" :remote-search="true" :page-size="20" />
|
||||||
</template>
|
</template>
|
||||||
<template #col-status="{ row }"><el-tag :type="statusType(row.status)">{{ statusText(row.status) }}</el-tag></template>
|
<template #col-status="{ row }"><el-tag :type="statusType(row.status)">{{ statusText(row.status) }}</el-tag></template>
|
||||||
<template #col-totalAmount="{ row }"><span class="amount">{{ money(row.totalAmount) }}</span></template>
|
<template #col-totalAmount="{ row }"><span class="amount">{{ money(row.totalAmount) }}</span></template>
|
||||||
@@ -153,7 +153,7 @@ const columns = [
|
|||||||
{ label: '账单日期', prop: 'billDate', width: 120, formatter: row => date(row.billDate) }, { label: '操作', prop: 'action', width: 330, fixed: 'right', slot: 'action', showInToolbar: false }
|
{ label: '账单日期', prop: 'billDate', width: 120, formatter: row => date(row.billDate) }, { label: '操作', prop: 'action', width: 330, fixed: 'right', slot: 'action', showInToolbar: false }
|
||||||
]
|
]
|
||||||
const searchColumns = [
|
const searchColumns = [
|
||||||
{ label: '账单号', prop: 'billNo', component: 'input', placeholder: '账单号', width: '180px' }, { label: '账单名称', prop: 'billName', component: 'input', placeholder: '账单名称', width: '180px' }, { label: '客户名称', prop: 'customerId', component: 'input', placeholder: '请选择客户', width: '220px' }, { label: '客户编码', prop: 'customerCode', component: 'input', placeholder: '客户编码', width: '140px' },
|
{ label: '账单号', prop: 'billNo', component: 'input', placeholder: '账单号', width: '180px' }, { label: '账单名称', prop: 'billName', component: 'input', placeholder: '账单名称', width: '180px' }, { label: '客户名称', prop: 'customerId', component: 'input', placeholder: '请选择客户', width: '220px' },
|
||||||
{ label: '状态', prop: 'status', component: 'select', options: statusOptions, placeholder: '全部状态', width: '150px' }, { label: '账单日期', prop: 'billDateRange', component: 'daterange', width: '300px' }, { label: '账单截止日', prop: 'billPeriodEnd', component: 'date', width: '160px' }
|
{ label: '状态', prop: 'status', component: 'select', options: statusOptions, placeholder: '全部状态', width: '150px' }, { label: '账单日期', prop: 'billDateRange', component: 'daterange', width: '300px' }, { label: '账单截止日', prop: 'billPeriodEnd', component: 'date', width: '160px' }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user