Files
front/src/components/search-form/index.vue
2026-03-27 19:26:10 +08:00

153 lines
3.8 KiB
Vue

<template>
<a-row>
<a-col :flex="1">
<a-form
:model="localModel"
:label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 18 }"
label-align="left"
>
<a-row :gutter="16">
<a-col v-for="item in formItems" :key="item.field" :span="item.span || 8">
<a-form-item :field="item.field" :label="item.label">
<!-- 输入框 -->
<a-input
v-if="item.type === 'input'"
v-model="localModel[item.field]"
:placeholder="item.placeholder"
allow-clear
/>
<!-- 选择框 -->
<a-select
v-else-if="item.type === 'select'"
v-model="localModel[item.field]"
:options="item.options"
:placeholder="item.placeholder || '请选择'"
:disabled="item.disabled"
:allow-search="item.allowSearch"
@search="handleSelectSearch(item, $event)"
allow-clear
/>
<!-- 日期范围选择器 -->
<a-range-picker
v-else-if="item.type === 'dateRange'"
v-model="localModel[item.field]"
style="width: 100%"
/>
</a-form-item>
</a-col>
<!-- 自定义表单项插槽 -->
<slot name="form-items" />
</a-row>
</a-form>
</a-col>
<a-divider v-if="showButtons" style="height: 84px" direction="vertical" />
<a-col v-if="showButtons" :flex="'86px'" style="text-align: right">
<a-space direction="vertical" :size="18">
<a-button type="primary" @click="handleSearch">
<template #icon>
<icon-search />
</template>
{{ searchButtonText }}
</a-button>
<a-button @click="handleReset">
<template #icon>
<icon-refresh />
</template>
{{ resetButtonText }}
</a-button>
</a-space>
</a-col>
</a-row>
</template>
<script lang="ts" setup>
import { PropType, reactive, watch, ref } from 'vue'
import type { FormItem } from './types'
const props = defineProps({
modelValue: {
type: Object as PropType<Record<string, any>>,
required: true,
},
formItems: {
type: Array as PropType<FormItem[]>,
default: () => [],
},
showButtons: {
type: Boolean,
default: true,
},
searchButtonText: {
type: String,
default: '查询',
},
resetButtonText: {
type: String,
default: '重置',
},
})
const emit = defineEmits<{
(e: 'update:modelValue', value: Record<string, any>): void
(e: 'search'): void
(e: 'reset'): void
}>()
// 使用本地响应式副本,避免直接修改 props
const localModel = reactive<Record<string, any>>({})
const isUpdating = ref(false)
// 初始化本地模型
const initLocalModel = () => {
Object.keys(localModel).forEach(key => delete localModel[key])
Object.assign(localModel, props.modelValue)
}
// 监听外部值变化(只在非本地更新时响应)
watch(
() => props.modelValue,
(val) => {
if (isUpdating.value) return
Object.keys(localModel).forEach(key => delete localModel[key])
Object.assign(localModel, val)
},
{ immediate: true, deep: true }
)
// 监听本地值变化,同步到外部
watch(
localModel,
(val) => {
isUpdating.value = true
emit('update:modelValue', { ...val })
// 使用 nextTick 避免循环更新
setTimeout(() => {
isUpdating.value = false
}, 0)
},
{ deep: true }
)
const handleSearch = () => {
emit('search')
}
const handleReset = () => {
emit('reset')
}
const handleSelectSearch = (item: FormItem, keyword: string) => {
item.onSearch?.(keyword)
}
</script>
<script lang="ts">
export default {
name: 'SearchForm',
}
</script>
<style scoped lang="less">
</style>