Files
sgxt_web/src/components/aboutTable/Search.vue
2026-04-20 20:36:59 +08:00

658 lines
18 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 v-loading="loadingPage" class="query-wrap">
<div class="query-title">查询条件</div>
<div class="query-grid">
<div v-for="(item, index) in getArr" :key="index" class="query-cell">
<div class="cell-label">{{ item.label }}</div>
<div class="cell-control">
<!-- select -->
<el-select v-if="item.showType === 'select'" v-model="searchObj[item.prop]" :multiple="item.multiple"
:clearable="item.clearable" :filterable="item.filterable" :placeholder="item.placeholder" collapse-tags
collapse-tags-tooltip class="control-select">
<el-option v-for="obj in (getOptions[item.prop] || [])" :key="obj.value" :label="obj.label || obj.lable"
:value="obj.value" />
</el-select>
<!-- input -->
<el-input v-else-if="item.showType === 'input'" class="control-input" v-model="searchObj[item.prop]"
:clearable="item.clearable" :placeholder="item.placeholder" />
<!-- input -->
<el-input v-else-if="item.showType === 'number'" class="control-input" v-model="searchObj[item.prop]"
:clearable="item.clearable" :placeholder="item.placeholder" type="number" />
<!-- 日期段选择器 -->
<el-date-picker v-else-if="item.showType === 'daterange'" v-model="searchObj[item.prop]" type="daterange"
unlink-panels :range-separator="item.rangeSeparator" :start-placeholder="item.startPlaceholder"
:end-placeholder="item.endPlaceholder" :shortcuts="item.shortcuts" :disabledDate="disabledDate"
value-format="YYYY-MM-DD" class="control-date" />
<el-date-picker v-else-if="item.showType === 'datetimerange'" v-model="searchObj[item.prop]"
type="datetimerange" unlink-panels :range-separator="item.rangeSeparator || ''"
:start-placeholder="item.startPlaceholder || '开始日期'" :end-placeholder="item.endPlaceholder || '结束日期'"
:shortcuts="item.shortcuts" value-format="YYYY-MM-DD HH:mm:ss" class="control-date" />
<el-date-picker v-else-if="item.showType === 'date'" v-model="searchObj[item.prop]" type="date"
:placeholder="item.placeholder" :disabled-date="disabledDate" :shortcuts="item.shortcuts"
value-format="YYYY-MM-DD" class="control-date">
</el-date-picker>
<el-date-picker v-else-if="item.showType === 'datetime'" v-model="searchObj[item.prop]" type="datetime"
:placeholder="item.placeholder" value-format="YYYY-MM-DD HH:mm:ss" class="control-date">
</el-date-picker>
<!-- checkbox -->
<template v-else-if="item.showType === 'department'">
<MOSTY.Department clearable v-model="searchObj[item.prop]" class="control-select" />
</template>
<!-- checkbox -->
<template v-else-if="item.showType === 'checkbox'">
<div class="checkbox-wrap">
<el-checkbox v-if="item.showSelectAll" v-model="item.checkAll" :indeterminate="item.isIndeterminate"
@change="
(val) => {
handleCheckAllChange(val, item);
}
">全选</el-checkbox>
<el-checkbox-group v-model="searchObj[item.prop]" @change="
(val) => {
handleCheckedCitiesChange(val, item);
}
" class="checkbox-group">
<el-checkbox v-for="obj in item.options" :key="obj.value" :label="obj.value">{{ obj.label }}</el-checkbox>
</el-checkbox-group>
</div>
</template>
<!-- radio -->
<el-radio-group v-else-if="item.showType === 'radio'" v-model="searchObj[item.prop]" @change="
(val) => {
handleRadioChange(val, item);
}
" class="radio-group">
<el-radio v-for="obj in item.options" :key="obj.value" :label="obj.value">{{ obj.label }}</el-radio>
</el-radio-group>
<!-- 级联选择 -->
<el-cascader v-else-if="item.showType === 'cascader'" v-model="searchObj[item.prop]" :props="item.props"
:show-all-levels="item.showAllLevels" :clearable="item.clearable" :options="getOptions[item.prop]"
:placeholder="item.placeholder" class="control-select" />
<div v-if="item.showType === 'Slot'">
<slot :name="item.prop"></slot>
</div>
<div v-if="item.showType === 'defaultSlot'">
<slot name="defaultSlot"></slot>
</div>
<div v-if="item.showType === 'nameSlot'">
<slot name="nameSlot"></slot>
</div>
</div>
</div>
</div>
<div class="query-action">
<div>
<slot> </slot>
</div>
<div>
<el-button type="primary" @click="submit" size="small">查询</el-button>
<el-button type="default" @click="reset" size="small">重置</el-button>
</div>
</div>
</div>
</template>
<script setup>
import {
ref,
reactive,
watchEffect,
getCurrentInstance,
watch,
computed
} from "vue";
import * as MOSTY from "@/components/MyComponents/index";
const { proxy } = getCurrentInstance();
const props = defineProps({
searchArr: {
type: Array,
default: () => {
return [
{
showType: "select",
prop: "selectKey",
options: [
{
value: 1,
label: "选择1"
}
],
defaultVal: "",
label: "选择",
dict: "" // 字典编码
},
{
showType: "input",
prop: "inputKey",
defaultVal: "",
label: "输入"
},
{
showType: "department",
prop: "deptKey",
defaultVal: "",
label: "输入"
},
{
showType: "daterange",
prop: "daterangeKey",
defaultVal: "",
label: "输入"
},
{
showType: "date",
prop: "date",
defaultVal: ""
},
{
showType: "checkbox",
prop: "checkboxKey1",
options: [
{
value: 1,
label: "选择1"
}
],
defaultVal: ""
},
{
showType: "cascader",
prop: "cascaderKey",
label: "级联选择",
checkStrictly: false, //点击任意选中
defaultVal: ""
},
{
showType: "radio",
defaultVal: ""
},
{
showType: "defaultTime",
prop: "timeField",
options: []
}
];
}
},
marginBottom: {
type: Number,
default: 0
},
backgroundColor: {
type: String,
default: "rgb(255, 255, 255, 1)"
},
bool: {
type: Boolean,
default: true
}
});
let loadingPage = ref(false);
const emit = defineEmits(["submit", "reset"]);
let searchObj = reactive({});
// select 的一些默认配置
const selectDefault = {
clearable: true, // 是否可以清空
filterable: true, // 是否可以筛选
multiple: false, // 是否多选
placeholder: "请选择"
};
// 重新定义下拉框的选项
let getOptions = reactive({});
// input 的一些默认配置
const inputDefault = {
clearable: true, // 是否可以清空
placeholder: "请输入"
};
const shortcuts = [
{
text: "今天",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 0);
return [start, end];
}
},
{
text: "昨天",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 1);
end.setTime(end.getTime() - 3600 * 1000 * 24 * 1);
return [start, end];
}
},
{
text: "最近7天",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
return [start, end];
}
},
{
text: "最近30天",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
return [start, end];
}
},
{
text: "最近90天",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
return [start, end];
}
}
];
// daterange 的一些默认配置
const daterangeDefault = {
rangeSeparator: "",
startPlaceholder: "开始日期",
endPlaceholder: "结束日期",
shortcuts: [], // 快捷选择
defaultShortcuts: true // 是否显示快捷选择 如果要自定义快捷选择传入一个shortcuts就可以了
};
// date 的一些默认配置
const defaultDate = {
clearable: true, // 是否可以清空
placeholder: "请输入",
shortcuts: [], // 快捷选择
defaultShortcuts: true // 是否显示快捷选择 如果要自定义快捷选择传入一个shortcuts就可以了
};
const dateShortcuts = [
{
text: "今天",
value: new Date()
},
{
text: "昨天",
value: () => {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24);
return date;
}
},
{
text: "7天前",
value: () => {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24 * 7);
return date;
}
},
{
text: "30天前",
value: () => {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24 * 30);
return date;
}
},
{
text: "90天前",
value: () => {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24 * 90);
return date;
}
}
];
// 设置不可选的日期
const disabledDate = (time) => {
return time.getTime() > Date.now();
};
// checkbox 的一些默认配置
const defaultCheckbox = reactive({
defaultVal: [],
checkAll: false, // 全选的值
isIndeterminate: false, // 控制全选按钮样式
showSelectAll: true // 是否显示全选
});
// 全选复选框的选中与不选中
const handleCheckAllChange = (val, obj) => {
searchObj[obj.prop] = val ? obj.checkboxValueArr : [];
obj.isIndeterminate = false;
};
// 单个复选框的选中与不选中
const handleCheckedCitiesChange = (value, obj) => {
const checkedCount = value.length;
obj.checkAll = checkedCount === obj.checkboxValueArr.length;
obj.isIndeterminate =
checkedCount > 0 && checkedCount < obj.checkboxValueArr.length;
};
//单选
const handleRadioChange = (val, obj) => {
console.log(val, obj);
};
// cascader 的一些默认配置
let defaultCascader = {
filterable: true, // 是否可以搜索
clearable: true, // 是否可以清空
placeholder: "请选择",
checkStrictly: true, // 控制是否父子联动(是否可以选择任意节点)
showAllLevels: false, // 是否显示完整路径
lazy: false, // 是否懒加载 当设置为false时就要传入options
portUrl: "", // 这里必须写 接口地址
props: {
label: "label",
value: "value",
children: "children"
},
options: []
};
// 在懒加载状态下cascader 的props的一些配置
const cascaderLazyProps = reactive({
value: "value",
label: "label",
lazy: false,
lazyLoad(node, resolve) {
// 这里要根据实际情况修改
const { level } = node;
let options = [];
switch (level) {
case 0:
options = [
{
value: 1,
label: "选择1",
leaf: false // 表示有下一级 必须有这个表示 不然获取不到值
},
{
value: 2,
label: "选择2",
leaf: false // 表示有下一级
},
{
value: 3,
label: "选择3",
leaf: false // 表示有下一级
},
{
value: 4,
label: "选择4",
leaf: true // 表示没有下一级了
}
];
break;
case 1:
options = [
{
value: 11,
label: "选择1_1",
leaf: true // 表示没有下一级了
},
{
value: 21,
label: "选择2_1",
leaf: true // 表示没有下一级了
},
{
value: 31,
label: "选择3_1",
leaf: true // 表示没有下一级了
},
{
value: 41,
label: "选择4_1",
leaf: true // 表示没有下一级了
}
];
}
resolve(options);
}
});
// 获取到传过来的参数
let getArr = ref([]);
const submit = () => {
emit("submit", searchObj);
};
const reset = () => {
getArr.value.forEach((item) => {
searchObj[item.prop] = item.defaultVal;
});
emit("reset", true);
emit("submit", searchObj);
};
// 暴露searchObj给父组件
defineExpose({
searchObj,
submit,
reset
});
// 监听 searchArr 变化,同时监听内部 options 的异步更新
watch(() => props.searchArr, (newArr) => {
loadingPage.value = true;
try {
// 不再深拷贝,保留响应式引用
getArr.value = newArr.map((item) => {
const itemCopy = { ...item }; // 浅拷贝即可
switch (itemCopy.showType) {
case "select":
Object.assign(itemCopy, { ...selectDefault, ...itemCopy });
// 直接引用原 options保持响应式
getOptions[itemCopy.prop] = itemCopy.options || [];
break;
case "input":
Object.assign(itemCopy, { ...inputDefault, ...itemCopy });
break;
case "daterange":
Object.assign(itemCopy, { ...daterangeDefault, ...itemCopy });
if (itemCopy.defaultShortcuts) itemCopy.shortcuts = shortcuts;
break;
case "date":
Object.assign(itemCopy, { ...defaultDate, ...itemCopy });
if (itemCopy.defaultShortcuts) {
itemCopy.shortcuts = dateShortcuts;
}
break;
case "checkbox":
Object.assign(itemCopy, { ...defaultCheckbox, ...itemCopy });
itemCopy.checkboxValueArr = (itemCopy.options || []).map((obj) => {
return obj.value;
});
break;
case "cascader":
Object.assign(itemCopy, { ...defaultCascader, ...itemCopy });
if (itemCopy.lazy) {
cascaderLazyProps.checkStrictly = itemCopy.checkStrictly;
itemCopy.props = { ...cascaderLazyProps, ...(itemCopy.props || {}) };
delete itemCopy.options;
} else {
itemCopy.props = {
...defaultCascader.props,
...(itemCopy.props || {}),
...{ checkStrictly: itemCopy.checkStrictly }
};
getOptions[itemCopy.prop] = itemCopy.options || [];
}
break;
}
searchObj[itemCopy.prop] = itemCopy.defaultVal;
return itemCopy;
});
} catch (e) {
console.error('Search组件解析searchArr失败:', e);
} finally {
loadingPage.value = false;
}
}, { immediate: true, deep: true }); // 开启深度监听,检测 options 变化
</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%;
}
.checkbox-group {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.radio-group {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
: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: space-between;
align-items: center;
padding: 0 8px;
}
:deep(.el-button--primary) {
background-color: #0f5bbd;
border-color: #0f5bbd;
}
:deep(.el-button--primary:hover) {
background-color: #1a73e8;
border-color: #1a73e8;
}
:deep(.el-button--default) {
background-color: #ffffff;
border-color: #dcdfe6;
color: #606266;
}
:deep(.el-button--default:hover) {
color: #409eff;
border-color: #c6e2ff;
background-color: #ecf5ff;
}
</style>