153 lines
3.8 KiB
Vue
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>
|