chat-pc/src/components/x-naive-ui/x-search-form/index.vue

272 lines
6.8 KiB
Vue
Raw Normal View History

<script setup>
import { cloneDeep, debounce } from "lodash-es";
import { computed, onBeforeUnmount, ref } from "vue";
import { NButton, NForm, NFormItemGi, NGrid, NInput, NSelect, NDatePicker } from 'naive-ui'
const props = defineProps({
title: {
type: String,
default: '',
},
cols: {
type: [Number, String],
default: 4,
},
searchConfig: {
type: Array,
default: () => ([]),
},
xGap: {
type: [Number, String],
default: 81,
},
yGap: {
type: [Number, String],
default: 20,
},
autoSearch: {
type: Boolean,
default: true,
},
debounceWait: {
type: Number,
default: 300,
},
loading: {
type: Boolean,
default: false,
}
})
const emit = defineEmits(['change', 'init'])
const resetForm = ref({})
const formData = ref({})
const indexRef = ref(0)
const datePickerRef = ref(null)
const formRef = ref(null)
// 防抖搜索函数
const debouncedSearch = debounce(() => {
emit('change', formData.value)
}, props.debounceWait)
// 清理防抖函数
onBeforeUnmount(() => {
debouncedSearch.cancel()
})
// 优化计算属性
const calculateEmptySpans = computed(() => {
const num = props.searchConfig?.reduce((acc, cur) =>
acc + (cur.col || 1), 0
)
const remainder = num % props.cols
return remainder === 0 ? props.cols - 1 : props.cols - remainder - 1
})
// 优化表单值变更处理
const manualChange = () => {
if (props.autoSearch) {
debouncedSearch()
}
}
const handleInputChange = (value, item) => {
// 如果是pair类型的输入
if (item.props?.pair && Array.isArray(item.key)) {
// 确保value是数组如果不是则转换为数组
const valueArray = Array.isArray(value) ? value : [value, value];
// 获取默认值来判断数据类型
const defaultValues = Array.isArray(item.default) ? item.default : [];
// 分别赋值给对应的key并根据default的类型进行转换
item.key.forEach((key, index) => {
const defaultValue = defaultValues[index];
const currentValue = valueArray[index] || '';
// 如果默认值是数字类型,则转换为数字
if (typeof defaultValue === 'number') {
formData.value[key] = currentValue === '' ? defaultValue : Number(currentValue);
} else {
formData.value[key] = currentValue;
}
});
} else {
// 处理普通输入
formData.value[item.key] = value;
}
manualChange();
};
const handleSelectChange = (value, item) => {
formData.value[item.key] = value
console.log('formData.value[item.key]',formData.value[item.key]);
manualChange()
}
const handleDateChange = (value, key) => {
if (Array.isArray(key)) {
key.forEach((k, i) => {
formData.value[k] = value?.[i]
})
} else {
formData.value[key] = value
}
manualChange()
}
const search = async () => {
if (!formRef.value) {
emit('change', formData.value)
return
}
try {
await formRef.value?.validate()
emit('change', formData.value)
} catch (errors) {
console.error('表单验证失败:', errors)
}
}
// 优化重置函数
const reset = () => {
formData.value = cloneDeep(resetForm.value)
emit('change', formData.value)
indexRef.value++
if (formRef.value) {
formRef.value?.restoreValidation()
}
}
// 优化表单对象生成
const generateFormObject = (config) => {
return config.reduce((formObject, field) => {
const defaultValue = field.default ?? undefined;
if (Array.isArray(field.key)) {
if (Array.isArray(defaultValue)) {
field.key.forEach((subKey, index) => {
// 使用原始默认值,保持类型一致
formObject[subKey] = defaultValue[index];
});
} else {
field.key.forEach(subKey => {
formObject[subKey] = defaultValue;
});
}
} else {
formObject[field.key] = defaultValue;
}
return formObject;
}, {});
};
// 初始化函数
const initData = () => {
const initialData = generateFormObject(props.searchConfig)
formData.value = initialData
resetForm.value = cloneDeep(initialData)
emit('init', initialData)
}
// 初始化
initData()
</script>
<template>
<div class="search-form w-[100%] pb-[20px] bg-[#fff] rounded-[3px] overflow-hidden">
<div
v-if="title"
class="search-form__header h-[59px] border-b-[2px] border-[#EAEAEA]"
>
<div class="text-[#1F2225] text-[18px] font-[600] h-[100%] flex justify-center align-center w-fit border-b-[4px] border-[#46299D]">
{{ title }}
</div>
</div>
<n-form
ref="formRef"
class="mt-[20px]"
:show-feedback="false"
label-placement="left"
label-align="left"
label-width="110"
:key="indexRef"
>
<n-grid :cols="cols" :x-gap="xGap" :y-gap="yGap">
<n-form-item-gi
v-for="item in searchConfig"
:key="item.key"
:span="item.col || 1"
:label="item.label"
:rule="item.rule"
>
<!-- Input 类型 -->
<template v-if="item.type === 'input'">
<n-input
:value="formData[item.key]"
placeholder="请输入"
@input="value => handleInputChange(value, item)"
clearable
v-bind="item.props"
/>
</template>
<!-- Select 类型 -->
<template v-else-if="item.type === 'select'">
<n-select
:value="formData[item.key]"
placeholder="请选择"
@update:value="value => handleSelectChange(value, item)"
clearable
v-bind="item.props"
/>
</template>
<!-- DatePicker 类型 -->
<template v-else-if="item.type === 'date-picker'">
<n-date-picker
ref="datePickerRef"
class="w-[100%]"
clearable
:value="formData[item.key]"
@update-formatted-value="value => handleDateChange(value, item.key)"
v-bind="item.props"
/>
</template>
</n-form-item-gi>
<!-- 空白占位 -->
<n-form-item-gi :span="calculateEmptySpans" />
<!-- 操作按钮 -->
<n-form-item-gi :span="1">
<div class="flex justify-end w-full">
<n-button
class="w-[145px] h-[34px] mr-[20px]"
@click="search"
type="primary"
:loading="loading"
>
查询
</n-button>
<n-button
class="w-[145px] h-[34px]"
color="#EEE9F8"
text-color="#46299D"
@click="reset"
>
重置
</n-button>
</div>
</n-form-item-gi>
</n-grid>
</n-form>
</div>
</template>
<style scoped lang="scss">
.search-form {
&__header {
margin-bottom: 16px;
}
}
</style>