272 lines
6.8 KiB
Vue
272 lines
6.8 KiB
Vue
|
<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>
|