2026-03-14 19:46:21 +08:00
|
|
|
<template>
|
|
|
|
|
<section class="query-wrap">
|
|
|
|
|
<div class="query-title">{{ title }}</div>
|
|
|
|
|
<div class="query-grid">
|
|
|
|
|
<div v-for="field in renderFields" :key="field.key" class="query-cell">
|
|
|
|
|
<div class="cell-label">{{ field.label }}</div>
|
2026-03-29 19:46:50 +08:00
|
|
|
<div
|
|
|
|
|
class="cell-control"
|
|
|
|
|
:class="{ 'is-checkbox': field.type === 'checkbox' }"
|
|
|
|
|
>
|
|
|
|
|
<el-input
|
|
|
|
|
clearable
|
|
|
|
|
v-if="field.type === 'input'"
|
|
|
|
|
v-model="formState[field.key]"
|
|
|
|
|
class="control-input"
|
|
|
|
|
:placeholder="field.placeholder || ''"
|
|
|
|
|
/>
|
|
|
|
|
<el-input
|
|
|
|
|
clearable
|
|
|
|
|
v-else-if="field.type === 'number'"
|
|
|
|
|
v-model="formState[field.key]"
|
|
|
|
|
class="control-input"
|
|
|
|
|
type="number"
|
|
|
|
|
:placeholder="field.placeholder || ''"
|
|
|
|
|
/>
|
|
|
|
|
<el-select
|
|
|
|
|
clearable
|
|
|
|
|
v-else-if="field.type === 'select'"
|
|
|
|
|
v-model="formState[field.key]"
|
|
|
|
|
class="control-select"
|
|
|
|
|
:placeholder="field.placeholder || '请选择'"
|
|
|
|
|
:multiple="field.multiple || false"
|
|
|
|
|
collapse-tags
|
|
|
|
|
collapse-tags-tooltip
|
|
|
|
|
>
|
|
|
|
|
<el-option
|
|
|
|
|
v-for="item in field.options || []"
|
|
|
|
|
:key="item.value ?? item"
|
|
|
|
|
:label="item.label ?? item"
|
|
|
|
|
:value="item.value ?? item"
|
|
|
|
|
/>
|
2026-03-14 19:46:21 +08:00
|
|
|
</el-select>
|
2026-03-29 19:46:50 +08:00
|
|
|
<el-date-picker
|
|
|
|
|
clearable
|
|
|
|
|
v-else-if="field.type === 'date'"
|
|
|
|
|
v-model="formState[field.key]"
|
|
|
|
|
class="control-date"
|
|
|
|
|
type="date"
|
|
|
|
|
:placeholder="field.placeholder || '请选择日期'"
|
|
|
|
|
value-format="YYYY-MM-DD"
|
|
|
|
|
/>
|
|
|
|
|
<el-date-picker
|
|
|
|
|
clearable
|
|
|
|
|
v-else-if="field.type === 'datetime'"
|
|
|
|
|
v-model="formState[field.key]"
|
|
|
|
|
class="control-date"
|
|
|
|
|
type="datetime"
|
|
|
|
|
:placeholder="field.placeholder || '请选择时间'"
|
|
|
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
/>
|
|
|
|
|
<el-date-picker
|
|
|
|
|
clearable
|
|
|
|
|
v-else-if="field.type === 'daterange'"
|
|
|
|
|
v-model="formState[field.key]"
|
|
|
|
|
class="control-date"
|
|
|
|
|
type="daterange"
|
|
|
|
|
range-separator="至"
|
|
|
|
|
start-placeholder="开始日期"
|
|
|
|
|
end-placeholder="结束日期"
|
|
|
|
|
value-format="YYYY-MM-DD"
|
|
|
|
|
/>
|
|
|
|
|
<el-date-picker
|
|
|
|
|
clearable
|
|
|
|
|
v-else-if="field.type === 'datetimerange'"
|
|
|
|
|
v-model="formState[field.key]"
|
|
|
|
|
class="control-date"
|
|
|
|
|
type="datetimerange"
|
|
|
|
|
range-separator="至"
|
|
|
|
|
start-placeholder="开始时间"
|
|
|
|
|
end-placeholder="结束时间"
|
|
|
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
/>
|
2026-03-14 19:46:21 +08:00
|
|
|
<template v-else-if="field.type === 'department'">
|
2026-03-29 19:46:50 +08:00
|
|
|
<MOSTY.Department
|
|
|
|
|
clearable
|
|
|
|
|
v-model="formState[field.key]"
|
|
|
|
|
class="control-select"
|
|
|
|
|
/>
|
2026-03-14 19:46:21 +08:00
|
|
|
</template>
|
2026-03-29 19:46:50 +08:00
|
|
|
<div v-else-if="field.type === 'checkbox'" class="checkbox-wrap">
|
2026-03-14 19:46:21 +08:00
|
|
|
<el-checkbox v-model="formState[field.key]" />
|
|
|
|
|
</div>
|
2026-03-29 19:46:50 +08:00
|
|
|
<div v-else-if="field.type === 'slot'" class="checkbox-wrap">
|
|
|
|
|
<slot :name="field.key" />
|
|
|
|
|
</div>
|
2026-03-14 19:46:21 +08:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="query-action">
|
|
|
|
|
<div>
|
|
|
|
|
<slot name="but"></slot>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
2026-03-29 19:46:50 +08:00
|
|
|
<el-button size="small" type="primary" @click="handleSearch">{{
|
|
|
|
|
searchText
|
|
|
|
|
}}</el-button>
|
|
|
|
|
<el-button size="small" type="button" @click="handleReset"
|
|
|
|
|
>重置
|
|
|
|
|
</el-button>
|
2026-03-14 19:46:21 +08:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2026-03-29 19:46:50 +08:00
|
|
|
import { computed, reactive, watch } from "vue";
|
2026-03-14 19:46:21 +08:00
|
|
|
import * as MOSTY from "@/components/MyComponents/index";
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
title: {
|
|
|
|
|
type: String,
|
2026-03-29 19:46:50 +08:00
|
|
|
default: "查询条件"
|
2026-03-14 19:46:21 +08:00
|
|
|
},
|
|
|
|
|
searchText: {
|
|
|
|
|
type: String,
|
2026-03-29 19:46:50 +08:00
|
|
|
default: "查询"
|
2026-03-14 19:46:21 +08:00
|
|
|
},
|
|
|
|
|
fields: {
|
|
|
|
|
type: Array,
|
|
|
|
|
default: () => []
|
|
|
|
|
},
|
|
|
|
|
searchArr: {
|
|
|
|
|
type: Array,
|
|
|
|
|
default: () => []
|
2026-04-07 17:18:19 +08:00
|
|
|
},
|
2026-03-29 19:46:50 +08:00
|
|
|
});
|
2026-03-14 19:46:21 +08:00
|
|
|
|
2026-04-07 17:18:19 +08:00
|
|
|
|
|
|
|
|
const emit = defineEmits(['update:modelValue',"search", "submit", "reset"]);
|
2026-03-29 19:46:50 +08:00
|
|
|
const formState = reactive({});
|
2026-03-14 19:46:21 +08:00
|
|
|
|
2026-04-07 17:18:19 +08:00
|
|
|
watch(()=>formState,val=>{
|
|
|
|
|
emit('update:modelValue',val)
|
|
|
|
|
},{immediate:true})
|
|
|
|
|
|
|
|
|
|
|
2026-03-14 19:46:21 +08:00
|
|
|
const renderFields = computed(() => {
|
2026-03-29 19:46:50 +08:00
|
|
|
const source = props.searchArr.length ? props.searchArr : props.fields;
|
|
|
|
|
return source
|
|
|
|
|
.map((field) => ({
|
|
|
|
|
...field,
|
|
|
|
|
key: field.key || field.prop,
|
|
|
|
|
type: field.type || field.showType || "input"
|
|
|
|
|
}))
|
|
|
|
|
.filter((field) => field.key);
|
|
|
|
|
});
|
2026-03-14 19:46:21 +08:00
|
|
|
|
|
|
|
|
const getResetValue = (field) => {
|
|
|
|
|
if (field.defaultVal !== undefined) {
|
2026-03-29 19:46:50 +08:00
|
|
|
return Array.isArray(field.defaultVal)
|
|
|
|
|
? [...field.defaultVal]
|
|
|
|
|
: field.defaultVal;
|
2026-03-14 19:46:21 +08:00
|
|
|
}
|
2026-03-29 19:46:50 +08:00
|
|
|
if (field.type === "checkbox") {
|
|
|
|
|
return false;
|
2026-03-14 19:46:21 +08:00
|
|
|
}
|
2026-03-29 19:46:50 +08:00
|
|
|
if (field.type === "daterange" || field.type === "datetimerange") {
|
|
|
|
|
return [];
|
2026-03-14 19:46:21 +08:00
|
|
|
}
|
2026-03-29 19:46:50 +08:00
|
|
|
if (field.type === "select") {
|
2026-03-14 19:46:21 +08:00
|
|
|
if (field.multiple) {
|
2026-03-29 19:46:50 +08:00
|
|
|
return [];
|
2026-03-14 19:46:21 +08:00
|
|
|
}
|
2026-03-29 19:46:50 +08:00
|
|
|
return null;
|
2026-03-14 19:46:21 +08:00
|
|
|
}
|
2026-03-29 19:46:50 +08:00
|
|
|
return "";
|
|
|
|
|
};
|
2026-03-14 19:46:21 +08:00
|
|
|
|
|
|
|
|
const buildResetPayload = () => {
|
2026-03-29 19:46:50 +08:00
|
|
|
const payload = {};
|
2026-03-14 19:46:21 +08:00
|
|
|
renderFields.value.forEach((field) => {
|
2026-03-29 19:46:50 +08:00
|
|
|
payload[field.key] = getResetValue(field);
|
|
|
|
|
});
|
|
|
|
|
return payload;
|
|
|
|
|
};
|
2026-03-14 19:46:21 +08:00
|
|
|
|
|
|
|
|
const setFormState = (value = {}) => {
|
|
|
|
|
Object.keys(formState).forEach((key) => {
|
2026-03-29 19:46:50 +08:00
|
|
|
delete formState[key];
|
|
|
|
|
});
|
2026-03-14 19:46:21 +08:00
|
|
|
renderFields.value.forEach((field) => {
|
|
|
|
|
if (value[field.key] !== undefined) {
|
2026-03-29 19:46:50 +08:00
|
|
|
formState[field.key] = Array.isArray(value[field.key])
|
|
|
|
|
? [...value[field.key]]
|
|
|
|
|
: value[field.key];
|
|
|
|
|
return;
|
2026-03-14 19:46:21 +08:00
|
|
|
}
|
2026-03-29 19:46:50 +08:00
|
|
|
formState[field.key] = getResetValue(field);
|
|
|
|
|
});
|
|
|
|
|
};
|
2026-03-14 19:46:21 +08:00
|
|
|
|
|
|
|
|
const getFormSnapshot = () => {
|
2026-03-29 19:46:50 +08:00
|
|
|
const snapshot = {};
|
2026-03-14 19:46:21 +08:00
|
|
|
renderFields.value.forEach((field) => {
|
2026-03-29 19:46:50 +08:00
|
|
|
const value = formState[field.key];
|
2026-03-14 19:46:21 +08:00
|
|
|
if (Array.isArray(value)) {
|
2026-03-29 19:46:50 +08:00
|
|
|
snapshot[field.key] = [...value];
|
|
|
|
|
return;
|
2026-03-14 19:46:21 +08:00
|
|
|
}
|
2026-03-29 19:46:50 +08:00
|
|
|
snapshot[field.key] = value;
|
|
|
|
|
});
|
|
|
|
|
return snapshot;
|
|
|
|
|
};
|
2026-03-14 19:46:21 +08:00
|
|
|
|
|
|
|
|
const handleSearch = () => {
|
2026-03-29 19:46:50 +08:00
|
|
|
const payload = getFormSnapshot();
|
|
|
|
|
emit("search", payload);
|
|
|
|
|
emit("submit", payload);
|
|
|
|
|
};
|
2026-03-14 19:46:21 +08:00
|
|
|
|
|
|
|
|
const handleReset = () => {
|
2026-03-29 19:46:50 +08:00
|
|
|
const payload = buildResetPayload();
|
|
|
|
|
setFormState(payload);
|
|
|
|
|
emit("reset", true);
|
|
|
|
|
emit("search", payload);
|
|
|
|
|
emit("submit", payload);
|
|
|
|
|
};
|
2026-03-14 19:46:21 +08:00
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
renderFields,
|
|
|
|
|
() => {
|
2026-03-29 19:46:50 +08:00
|
|
|
setFormState(buildResetPayload());
|
2026-03-14 19:46:21 +08:00
|
|
|
},
|
|
|
|
|
{ immediate: true }
|
2026-03-29 19:46:50 +08:00
|
|
|
);
|
2026-03-14 19:46:21 +08:00
|
|
|
|
|
|
|
|
defineExpose({
|
|
|
|
|
formState,
|
|
|
|
|
handleSearch,
|
|
|
|
|
handleReset
|
2026-03-29 19:46:50 +08:00
|
|
|
});
|
2026-03-14 19:46:21 +08:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
.query-wrap {
|
|
|
|
|
border: 1px solid #b8d3ff;
|
|
|
|
|
background: #fff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.query-title {
|
|
|
|
|
height: 32px;
|
|
|
|
|
background: linear-gradient(to right, #9ed7ff, #e6f0f8);
|
|
|
|
|
line-height: 32px;
|
|
|
|
|
padding-left: 10px;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
color: #0d2148;
|
|
|
|
|
border-bottom: 1px solid #b8d3ff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.query-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.query-cell {
|
|
|
|
|
display: flex;
|
|
|
|
|
min-height: 40px;
|
|
|
|
|
border-right: 1px solid #b8d3ff;
|
|
|
|
|
border-bottom: 1px solid #b8d3ff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.query-cell:nth-child(4n) {
|
|
|
|
|
border-right: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.cell-label {
|
|
|
|
|
width: 130px;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
border-right: 1px solid #b8d3ff;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #0d2148;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.cell-control {
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.cell-control.is-checkbox {
|
|
|
|
|
justify-content: center;
|
|
|
|
|
padding: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.control-input,
|
|
|
|
|
.control-select,
|
|
|
|
|
.control-date {
|
|
|
|
|
width: 100%;
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.control-input .el-input__wrapper),
|
|
|
|
|
:deep(.control-select .el-select__wrapper),
|
|
|
|
|
:deep(.control-date .el-input__wrapper) {
|
|
|
|
|
width: 100%;
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
min-height: 28px;
|
|
|
|
|
border-radius: 0;
|
|
|
|
|
box-shadow: 0 0 0 1px #b8d3ff inset;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.control-date.el-date-editor) {
|
|
|
|
|
width: 100%;
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.control-date.el-date-editor .el-range-input) {
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.control-input .el-input__inner),
|
|
|
|
|
:deep(.control-select .el-select__placeholder),
|
|
|
|
|
:deep(.control-date .el-input__inner) {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #0d2148;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.control-date .el-range-input) {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #0d2148;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.control-date .el-range__icon),
|
|
|
|
|
:deep(.control-date .el-range__close-icon) {
|
|
|
|
|
line-height: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.checkbox-wrap {
|
|
|
|
|
width: 100%;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
height: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.checkbox-wrap .el-checkbox) {
|
|
|
|
|
margin-right: 0;
|
|
|
|
|
height: 100%;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.checkbox-wrap .el-checkbox__inner) {
|
|
|
|
|
width: 14px;
|
|
|
|
|
height: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.query-action {
|
|
|
|
|
height: 36px;
|
|
|
|
|
display: flex;
|
|
|
|
|
// justify-content: flex-end;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 0 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-btn {
|
|
|
|
|
height: 26px;
|
|
|
|
|
min-width: 70px;
|
|
|
|
|
border: 1px solid #0f5bbd;
|
|
|
|
|
background: #0f5bbd;
|
|
|
|
|
color: #fff;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
</style>
|