658 lines
18 KiB
Vue
658 lines
18 KiB
Vue
<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>
|