WholeProcessPlatform/frontend/src/components/BasicSearch/index.vue

465 lines
15 KiB
Vue
Raw Normal View History

2026-04-20 16:57:54 +08:00
<template>
<div class="basic-search-container">
<a-form
ref="formRef"
:model="formData"
:rules="rules"
layout="inline"
class="basic-search-form"
@reset="handleReset"
@finish="handleFinish"
@values-change="handleValuesChange"
>
2026-04-20 16:57:54 +08:00
<div class="form-content-wrapper">
<!-- 1. 搜索项 + 查询/重置按钮 (栅格布局) -->
<a-row :gutter="[16, 0]" class="search-row" wrap>
<a-col v-for="item in validSearchList" :key="item.name">
<a-form-item
:label="item.label"
:name="item.name"
style="width: 100%; margin-bottom: 0"
>
2026-04-24 15:31:32 +08:00
<!-- 1. 优先检查是否有具名插槽或者 type custom -->
<slot
v-if="$slots[item.name] || item.type === 'custom'"
:name="item.name"
:value="formData[item.name]"
2026-05-07 08:35:20 +08:00
:onChange="(val: any) => {
2026-04-24 15:31:32 +08:00
formData[item.name] = val;
triggerManualValuesChange(item.name, val);
}"
:formModel="formData"
/>
2026-04-24 15:31:32 +08:00
2026-04-20 16:57:54 +08:00
<!-- 普通日期选择器 -->
<a-date-picker
v-else-if="item.type === 'DataPicker'"
v-model:value="formData[item.name]"
:picker="item.picker"
:format="item.fieldProps?.format"
:value-format="item.fieldProps?.valueFormat"
:disabled-date="item.fieldProps?.disabledDate"
:show-time="item.fieldProps?.showTime"
:allow-clear="item.fieldProps?.allowClear"
:presets="item.presets"
style="width: 100%"
@change="val => triggerManualValuesChange(item.name, val)"
/>
2026-04-24 15:31:32 +08:00
2026-04-20 16:57:54 +08:00
<!-- 日期范围选择器 -->
<a-range-picker
v-else-if="item.type === 'RangePicker'"
v-model:value="formData[item.name]"
:picker="item.picker"
:format="item.fieldProps?.format"
:value-format="item.fieldProps?.valueFormat"
:disabled-date="item.fieldProps?.disabledDate"
:show-time="item.fieldProps?.showTime"
:allow-clear="item.fieldProps?.allowClear"
:presets="item.presets"
style="width: 100%"
@change="val => triggerManualValuesChange(item.name, val)"
/>
2026-04-20 16:57:54 +08:00
<!-- 普通输入框 -->
<a-input
v-else-if="!item.type || item.type === 'Input'"
v-model:value="formData[item.name]"
:placeholder="item.placeholder || '请输入'"
:allow-clear="item.fieldProps?.allowClear"
2026-04-20 16:57:54 +08:00
:style="{ width: item.width ? item.width + 'px' : '200px' }"
@change="
e => triggerManualValuesChange(item.name, e.target.value)
"
/>
2026-04-24 15:31:32 +08:00
2026-04-20 16:57:54 +08:00
<!-- 电站下拉框 -->
<div
class="flex gap-[10px]"
v-else-if="item.type === 'waterStation'"
>
2026-04-20 16:57:54 +08:00
<a-form-item-rest>
<!-- 基地下拉框 -->
<!-- <a-select
2026-04-24 15:31:32 +08:00
:value="formData.baseId"
placeholder="请选择"
@change="dataDimensionDataChange"
show-search
allow-clear
:loading="shuJuTianBaoStore.baseLoading"
:filter-option="filterOption"
2026-04-24 15:31:32 +08:00
style="width: 135px"
2026-04-20 16:57:54 +08:00
>
2026-04-24 15:31:32 +08:00
<a-select-option
v-for="opt in shuJuTianBaoStore.baseOption"
:key="opt.baseid"
:value="opt.baseid"
:label="opt.basename"
2026-04-24 15:31:32 +08:00
>
{{ opt.basename }}
</a-select-option>
</a-select> -->
<!-- 流域下拉框 -->
<a-select
:value="formData.hbrvcd"
placeholder="请选择"
@change="lyChange"
show-search
:loading="shuJuTianBaoStore.lyLoading"
:filter-option="filterOption"
style="width: 135px"
>
<a-select-option
v-for="opt in shuJuTianBaoStore.lyOption"
:key="opt.hbrvcd"
:value="opt.hbrvcd"
:label="opt.hbrvnm"
>
{{ opt.hbrvnm }}
</a-select-option>
2026-04-24 15:31:32 +08:00
</a-select>
<!-- 电站下拉框 -->
<a-select
v-if="props.zhujianfujian == 'fu'"
:value="formData.rstcd"
placeholder="请选择电站"
@change="stcdIdChange"
show-search
allow-clear
:loading="shuJuTianBaoStore.engLoading"
:filter-option="filterOption"
style="width: 135px"
>
<a-select-option
v-for="opt in shuJuTianBaoStore.engOption"
:key="opt.stcd"
:value="opt.stcd"
:label="opt.ennm"
>
2026-05-07 08:35:20 +08:00
{{ opt.ennm }}
</a-select-option>
</a-select>
<!-- 电站下拉框 -->
<a-select
v-if="props.zhujianfujian == 'zhu'"
:value="formData.stcd"
placeholder="请选择电站"
@change="stcdIdChange"
show-search
allow-clear
:loading="shuJuTianBaoStore.engLoading"
:filter-option="filterOption"
style="width: 135px"
>
<a-select-option
v-for="opt in shuJuTianBaoStore.engOption"
:key="opt.stcd"
:value="opt.stcd"
:label="opt.ennm"
>
2026-04-24 15:31:32 +08:00
{{ opt.ennm }}
</a-select-option>
</a-select>
2026-04-20 16:57:54 +08:00
</a-form-item-rest>
</div>
<!-- 下拉选择 -->
<!-- <div v-else-if="item.type === 'Select'">
<div v-for="i in item.options"> {{ i[item.values?.name] }} {{ i[item.values?.value] }}</div>
</div> -->
<a-select
v-else-if="item.type === 'Select'"
v-model:value="formData[item.name]"
:placeholder="item.placeholder || '请选择'"
:allow-clear="item.fieldProps?.allowClear"
2026-04-20 16:57:54 +08:00
:style="{ width: item.width ? item.width + 'px' : '200px' }"
@change="val => triggerManualValuesChange(item.name, val)"
show-search
:filter-option="filterOption"
>
<a-select-option
v-for="opt in item.options"
:key="opt[item.values?.value] || opt.value || opt.itemCode"
2026-04-24 15:31:32 +08:00
:value="opt[item.values?.value] || opt.value || opt.itemCode"
:label="opt[item.values?.name] || opt.label || opt.dictName"
>
2026-04-24 15:31:32 +08:00
{{ opt[item.values?.name] || opt.label || opt.dictName }}
2026-04-20 16:57:54 +08:00
</a-select-option>
</a-select>
2026-04-22 17:53:20 +08:00
<!-- 树形选择 -->
<a-tree-select
v-else-if="item.type === 'TreeSelect'"
v-model:value="formData[item.name]"
:tree-data="item.options"
:placeholder="item.placeholder || '请选择'"
:allow-clear="item.fieldProps?.allowClear"
:style="{ width: item.width ? item.width + 'px' : '200px' }"
:field-names="
item.fieldNames || {
label: 'label',
value: 'value',
children: 'children'
}
"
:show-search="item.showSearch !== false"
:multiple="item.multiple"
:tree-checkable="item.treeCheckable"
:check-strictly="item.checkStrictly"
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
@change="val => triggerManualValuesChange(item.name, val)"
/>
2026-04-24 15:31:32 +08:00
<!-- 单选框 -->
<a-radio-group
v-else-if="item.type === 'Radio'"
v-model:value="formData[item.name]"
2026-04-22 17:53:20 +08:00
:style="{ width: item.width ? item.width + 'px' : '200px' }"
@change="
e => triggerManualValuesChange(item.name, e.target.value)
"
>
<a-radio
v-for="opt in item.options"
:key="opt.value"
:value="opt.value"
>
2026-04-22 17:53:20 +08:00
{{ opt.label }}
</a-radio>
</a-radio-group>
2026-04-20 16:57:54 +08:00
</a-form-item>
</a-col>
<!-- 2. 查询/重置按钮列 (固定占据一部分宽度例如 6) -->
<a-col class="custom-action-col">
<a-space>
<a-button type="primary" html-type="submit" v-if="showSearch">
查询
</a-button>
<a-button v-if="showReset" @click="handleReset"> 重置 </a-button>
2026-05-07 08:35:20 +08:00
</a-space>
</a-col>
<a-col class="custom-action-col !pl-0">
2026-05-07 08:35:20 +08:00
<a-space>
2026-04-20 16:57:54 +08:00
<slot name="actions" :form="formRef" :values="formData">
<!-- 默认无内容 -->
</slot>
</a-space>
</a-col>
</a-row>
2026-05-07 08:35:20 +08:00
<!-- <div class="custom-action-col">
</div> -->
2026-04-20 16:57:54 +08:00
</div>
</a-form>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, reactive, watch, onMounted, nextTick } from 'vue';
import { useShuJuTianBaoStore } from '@/store/modules/shuJuTianBao';
2026-04-24 15:31:32 +08:00
const shuJuTianBaoStore = useShuJuTianBaoStore();
2026-04-20 16:57:54 +08:00
// --- 类型定义 ---
export interface SearchItem {
type?: 'Input' | 'Select' | 'TreeSelect' | 'DataPicker' | string;
2026-04-20 16:57:54 +08:00
name: string;
label: string;
picker?: 'year' | 'month' | 'date' | 'week';
2026-04-20 16:57:54 +08:00
fieldProps?: any;
placeholder?: string;
span?: number;
xlSpan?: number;
width?: number;
presets?: any[];
2026-04-24 15:31:32 +08:00
values?: any;
options?: {
itemCode?: string;
dictName?: string;
label: string;
value: any;
}[];
treeData?: any[];
fieldNames?: {
label?: string;
value?: string;
children?: string;
};
showSearch?: boolean;
multiple?: boolean;
treeCheckable?: boolean;
checkStrictly?: boolean;
2026-04-20 16:57:54 +08:00
component?: any;
}
interface Props {
searchList: SearchItem[];
initialValues?: any;
showSearch?: boolean;
showReset?: boolean;
2026-05-07 15:40:18 +08:00
zhujianfujian?: string;
2026-04-20 16:57:54 +08:00
}
const props = withDefaults(defineProps<Props>(), {
initialValues: () => ({}),
showSearch: true,
showReset: true
2026-04-20 16:57:54 +08:00
});
const emit = defineEmits<{
(e: 'finish', values: any): void;
(e: 'valuesChange', changedValues: any, allValues: any): void;
(e: 'reset'): void;
2026-04-20 16:57:54 +08:00
}>();
2026-04-22 17:53:20 +08:00
const formRef = ref<any>();
2026-04-20 16:57:54 +08:00
const formData = reactive<any>({});
const rules = reactive<Record<string, any>>({});
const filterOption = (inputValue: string, option: any) => {
if (!option.label) return false;
return option.label.indexOf(inputValue) !== -1;
};
2026-04-20 16:57:54 +08:00
// 2. 创建计算属性,自动过滤掉 false/null/undefined 的项
const validSearchList = computed(() => {
return props.searchList.filter(item => item);
2026-04-20 16:57:54 +08:00
});
2026-04-24 15:31:32 +08:00
2026-04-20 16:57:54 +08:00
// --- 初始化逻辑 ---
const initForm = () => {
const initial = JSON.parse(JSON.stringify(props.initialValues || {}));
2026-04-24 15:31:32 +08:00
2026-04-20 16:57:54 +08:00
// 1. 清空 formData
Object.keys(formData).forEach(key => delete formData[key]);
2026-04-24 15:31:32 +08:00
2026-04-20 16:57:54 +08:00
// 2. 赋值初始数据
Object.assign(formData, initial);
// 3. 过滤掉 searchList 中为 false/null/undefined 的项,并生成规则
validSearchList.value.forEach(item => {
if (item.type == 'waterStation') {
2026-04-24 15:31:32 +08:00
// 下拉菜单
// shuJuTianBaoStore.getBaseOption();
shuJuTianBaoStore.getSelectForOption();
shuJuTianBaoStore.getEngOption(formData.hbrvcd);
2026-04-24 15:31:32 +08:00
}
2026-04-20 16:57:54 +08:00
if (item.fieldProps?.required) {
rules[item.name] = [
{ required: true, message: `${item.label}不能为空`, trigger: 'blur' }
2026-04-20 16:57:54 +08:00
];
}
});
};
2026-04-24 15:31:32 +08:00
/**
* 手动触发 valuesChange 事件
* 用于处理那些没有被 a-form 直接管理的字段 waterStation 内部逻辑
* 或者作为标准控件的备份触发机制
*/
const triggerManualValuesChange = (changedKey: string, newValue: any) => {
// 构造变更对象
const changedValues = { [changedKey]: newValue };
// 发射事件,传递变更值和当前所有值
// 注意:这里使用 {...formData} 是为了传递当前的最新状态
emit('valuesChange', changedValues, { ...formData });
2026-04-24 15:31:32 +08:00
};
// const dataDimensionDataChange = (value: any) => {
// formData.baseId = value;
// formData.rstcd = "";
// shuJuTianBaoStore.getEngOption(formData.baseId);
// // 【关键修改】手动触发 valuesChange因为 a-form-item-rest 阻断了自动监听
// triggerManualValuesChange("baseId", formData.baseId);
// };
const lyChange = (value: any) => {
formData.hbrvcd = value;
formData.rstcd = '';
shuJuTianBaoStore.getEngOption(formData.hbrvcd);
2026-04-24 15:31:32 +08:00
// 【关键修改】手动触发 valuesChange因为 a-form-item-rest 阻断了自动监听
triggerManualValuesChange('hbrvcd', formData.hbrvcd);
2026-04-20 16:57:54 +08:00
};
2026-04-24 15:31:32 +08:00
2026-04-20 16:57:54 +08:00
const stcdIdChange = (value: any) => {
if (props.zhujianfujian == 'fu') {
2026-05-07 08:35:20 +08:00
formData.rstcd = value;
shuJuTianBaoStore.getFpssOption(formData.hbrvcd, value);
// 【关键修改】手动触发 valuesChange
triggerManualValuesChange('rstcd', formData.rstcd);
2026-05-07 08:35:20 +08:00
} else {
formData.stcd = value;
shuJuTianBaoStore.getFpssOption(formData.hbrvcd, value);
// 【关键修改】手动触发 valuesChange
triggerManualValuesChange('stcd', formData.stcd);
2026-05-07 08:35:20 +08:00
}
2026-04-20 16:57:54 +08:00
};
2026-04-24 15:31:32 +08:00
2026-04-20 16:57:54 +08:00
onMounted(() => {
initForm();
});
watch(
() => props.initialValues,
newVal => {
2026-04-20 16:57:54 +08:00
if (newVal && formRef.value) {
formRef.value.setFieldsValue(newVal);
Object.assign(formData, newVal);
}
},
{ deep: true }
);
// --- 事件处理 ---
const handleFinish = (values: any) => {
2026-04-24 15:31:32 +08:00
const finalValues = { ...formData, ...values };
emit('finish', finalValues);
2026-04-20 16:57:54 +08:00
};
const handleValuesChange = (changedValues: any, allValues: any) => {
emit('valuesChange', changedValues, allValues);
2026-04-20 16:57:54 +08:00
};
const handleReset = () => {
if (formRef.value) {
formRef.value.resetFields();
2026-04-24 15:31:32 +08:00
nextTick(() => {
initForm();
});
emit('reset');
2026-04-20 16:57:54 +08:00
}
};
defineExpose({
form: formRef,
2026-04-24 15:31:32 +08:00
formData,
2026-04-20 16:57:54 +08:00
reset: handleReset,
submit: () => formRef.value?.submit()
2026-04-20 16:57:54 +08:00
});
</script>
<style scoped lang="scss">
.basic-search-container {
width: 100%;
margin-bottom: 10px;
}
2026-05-07 08:35:20 +08:00
2026-04-20 16:57:54 +08:00
:deep(.ant-form-item) {
margin-bottom: 24px;
margin-inline-end: 0px;
}
2026-05-07 08:35:20 +08:00
2026-04-20 16:57:54 +08:00
/* 关键代码:让自定义按钮列自动占据剩余空间并靠右 */
.custom-action-col {
2026-05-07 08:35:20 +08:00
// margin-left: auto;
2026-04-20 16:57:54 +08:00
display: flex;
align-items: flex-start;
justify-content: flex-start;
/* 防止在小屏幕下挤压变形 */
white-space: nowrap;
}
</style>