WholeProcessPlatform/frontend/src/components/BasicSearch/index.vue
2026-05-07 08:35:20 +08:00

338 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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