<template>
|
<div class="search-panel-container">
|
<el-form
|
ref="formRef"
|
:model="modelValue"
|
class="search-form"
|
label-width="0"
|
>
|
<el-row :gutter="10" class="form-row">
|
<!-- 渲染表单项 -->
|
<el-col
|
v-for="(item, index) in visibleSchema"
|
:key="item.prop || index"
|
:xs="24"
|
:sm="12"
|
:md="8"
|
:lg="4"
|
:xl="4"
|
class="search-col"
|
>
|
<el-form-item :prop="item.prop" :rules="item.rules" class="search-form-item">
|
<!-- 自定义插槽 -->
|
<slot v-if="item.slot" :name="item.slot" :item="item"></slot>
|
<!-- 默认渲染类型 -->
|
<template v-else>
|
<!-- 输入框 -->
|
<el-input
|
v-if="item.type === 'input'"
|
v-model="modelValue[item.prop]"
|
:placeholder="item.placeholder || '请输入'"
|
clearable
|
class="full-width"
|
v-bind="item.props"
|
@keyup.enter="handleSearch"
|
/>
|
|
<!-- 下拉框 -->
|
<el-select
|
v-else-if="item.type === 'select'"
|
v-model="modelValue[item.prop]"
|
:placeholder="item.placeholder || '请选择'"
|
clearable
|
class="full-width"
|
v-bind="item.props"
|
>
|
{{ item || '请选择' }}
|
<!-- <el-option
|
v-for="(opt,idx) in getOptions(item)"
|
:key="idx"
|
:label="opt.label"
|
:value="opt.value"
|
/> -->
|
</el-select>
|
|
<!-- 日期选择器 -->
|
<el-date-picker
|
v-else-if="item.type === 'date'"
|
v-model="modelValue[item.prop]"
|
type="date"
|
:placeholder="item.placeholder || '选择日期'"
|
style="width: 100%"
|
value-format="YYYY-MM-DD"
|
class="full-width"
|
v-bind="item.props"
|
/>
|
|
<!-- 日期范围选择器 -->
|
<el-date-picker
|
v-else-if="item.type === 'daterange'"
|
v-model="modelValue[item.prop]"
|
type="daterange"
|
range-separator="至"
|
start-placeholder="开始日期"
|
end-placeholder="结束日期"
|
value-format="YYYY-MM-DD"
|
class="full-width"
|
v-bind="item.props"
|
/>
|
</template>
|
</el-form-item>
|
</el-col>
|
|
<!-- 按钮区域 -->
|
<el-col :xs="24" :sm="12" :md="8" :lg="4" :xl="4" class="search-actions-col">
|
<el-form-item class="search-actions">
|
<el-button style="background: #002FA7; color: white;" icon="Search" @click="handleSearch">搜索</el-button>
|
<el-button icon="Refresh" @click="handleReset">重置</el-button>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<!-- 展开/收起按钮 -->
|
<div v-if="schema.length > 5" class="expand-toggle" @click="toggleExpand">
|
<span>{{ isExpanded ? '收起' : '展开' }}</span>
|
<el-icon :class="{ 'is-reverse': isExpanded }">
|
<ArrowDown />
|
</el-icon>
|
</div>
|
</el-form>
|
</div>
|
</template>
|
|
<script setup name="SearchPanel">
|
import { ref, reactive, computed, getCurrentInstance, onMounted } from 'vue';
|
import { ArrowDown, Search, Refresh } from '@element-plus/icons-vue';
|
|
const { proxy } = getCurrentInstance();
|
|
const props = defineProps({
|
// 表单数据对象
|
modelValue: {
|
type: Object,
|
required: true
|
},
|
// 表单配置项
|
schema: {
|
type: Array,
|
default: () => []
|
}
|
});
|
|
const emit = defineEmits(['update:modelValue', 'search', 'reset']);
|
|
// 是否展开
|
const isExpanded = ref(false);
|
const formRef = ref(null);
|
const dictMap = reactive({});
|
|
// 计算可见的 schema 项
|
const visibleSchema = computed(() => {
|
if (isExpanded.value || props.schema.length <= 5) {
|
return props.schema;
|
}
|
return props.schema.slice(0, 5);
|
});
|
|
// 初始化字典数据
|
onMounted(() => {
|
const dicts = props.schema.filter(item => item.dict).map(item => item.dict);
|
if (dicts.length > 0 && proxy.useDict) {
|
const dictData = proxy.useDict(...dicts);
|
Object.keys(dictData).forEach(key => {
|
dictMap[key] = dictData[key];
|
});
|
}
|
});
|
|
// 获取下拉选项 (支持静态 options 和 字典 dict)
|
function getOptions(item) {
|
if (item.options) return item.options;
|
if (item.dict && dictMap[item.dict]) {
|
return dictMap[item.dict].value || [];
|
}
|
return [];
|
}
|
|
// 搜索
|
function handleSearch() {
|
emit('search', props.modelValue);
|
}
|
|
// 重置
|
function handleReset() {
|
if (formRef.value) {
|
formRef.value.resetFields();
|
}
|
const keys = props.schema.map(item => item.prop).filter(Boolean);
|
keys.forEach(key => {
|
props.modelValue[key] = undefined;
|
});
|
emit('update:modelValue', props.modelValue);
|
emit('reset');
|
}
|
|
// 切换展开/收起
|
function toggleExpand() {
|
isExpanded.value = !isExpanded.value;
|
}
|
</script>
|
|
<style scoped lang="scss">
|
.search-panel-container {
|
background: #fff;
|
padding: 15px 15px 5px;
|
border-radius: 8px;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
margin-bottom: 15px;
|
|
.search-form {
|
.form-row {
|
width: 100%;
|
}
|
|
.search-col {
|
margin-bottom: 10px;
|
}
|
|
.search-form-item {
|
margin-right: 0;
|
margin-bottom: 0;
|
width: 100%;
|
|
:deep(.el-form-item__content) {
|
width: 100%;
|
}
|
}
|
|
.full-width {
|
width: 100% !important;
|
}
|
|
.search-actions-col {
|
margin-left: auto;
|
display: flex;
|
justify-content: flex-end;
|
margin-bottom: 10px;
|
}
|
|
.search-actions {
|
margin-bottom: 0;
|
margin-right: 0;
|
|
:deep(.el-button--primary) {
|
background-color: #409eff;
|
border-color: #409eff;
|
}
|
}
|
|
.expand-toggle {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
gap: 4px;
|
font-size: 13px;
|
color: #909399;
|
cursor: pointer;
|
padding: 5px 0;
|
user-select: none;
|
width: 100%;
|
border-top: 1px solid #f0f2f5;
|
margin-top: 5px;
|
|
&:hover {
|
color: #409eff;
|
}
|
|
.el-icon {
|
transition: transform 0.3s;
|
&.is-reverse {
|
transform: rotate(180deg);
|
}
|
}
|
}
|
}
|
}
|
</style>
|