<template>
<el-dialog
:append-to-body="true"
title="配置人员"
v-if="hasLoaded"
:close-on-click-modal="false"
v-model="dialogVisible"
width="800px"
center
class="dialog"
@close="handleDialogVisible"
>
<div class="line"></div>
<el-form ref="form" :model="form" label-width="140px" size="default" :rules="rules">
<el-form-item label="有效期" :style="{ width: '30%' }" prop="signatureTime">
<el-date-picker
v-model="form.signatureTime"
clearable
type="daterange"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
align="right"
class="time-picker"
/>
</el-form-item>
<!-- 树状选择区域 -->
<div class="merchant-select-area">
<div style="text-align: center; position: relative">
<h3>选择人员</h3>
<div class="selectablebox" style="margin-right: -2px">
<el-input
placeholder="请输入名称/ID"
v-model="merchantSearch"
style="margin-bottom: 10px; width: 220px"
class="merchant-search-input"
clearable
/>
<Search class="small-search-icon" />
<el-tree
ref="merchantTree"
:data="filteredTreeData"
show-checkbox
node-key="id"
:props="treeProps"
:filter-node-method="filterNode"
@check-change="handleTreeCheck"
default-expand-all
></el-tree>
</div>
</div>
<!-- 中间:穿梭箭头 -->
<div class="arrow-column">
<el-button class="btn" @click="moveToSelected" :disabled="selectedTreeNodes.length === 0">
<el-icon class="icons">
<ArrowRight />
<ArrowRight />
</el-icon>
</el-button>
<el-button class="btn" @click="moveToSelectable" :disabled="selectedMerchantsToRemove.length === 0">
<el-icon class="icons">
<ArrowLeft />
<ArrowLeft />
</el-icon>
</el-button>
</div>
<!-- 右侧:已选列表 -->
<div style="text-align: center" class="boxes">
<h3>已选中人员</h3>
<div class="selectablebox">
<div class="selected-column">
<el-checkbox-group v-model="selectedMerchantsToRemove">
<el-checkbox
v-for="merchant in selectedMerchants"
:key="merchant.id"
:label="merchant"
style="display: block; margin-bottom: 5px"
class="merchant-checkbox"
>
{{ merchant.name }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</div>
</div>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSave">保存</el-button>
</div>
</el-form>
</el-dialog>
</template>
<script>
import _ from 'lodash';
import { ElMessageBox, ElMessage } from 'element-plus';
import { Validator } from '/@/config/utils';
import { ArrowRight, ArrowLeft } from '@element-plus/icons-vue';
export default {
components: { ArrowRight, ArrowLeft },
props: {
id: {
type: String,
default: '',
},
},
data() {
return {
form: {
signatureTime: [],
templateName: '5',
},
dialogVisible: true,
hasLoaded: false,
merchantSearch: '',
treeData: [], // 原始树数据
filteredTreeData: [], // 过滤后显示的树数据
treeProps: { children: 'children', label: 'name' },
selectedMerchants: [], // 已选中的人员(右侧列表)
selectedMerchantsToRemove: [], // 右侧待移除的人员
selectedTreeNodes: [], // 左侧树中勾选的节点(待右移)
};
},
created() {
this.getDetail();
},
watch: {
merchantSearch(val) {
this.$refs.merchantTree.filter(val);
},
},
methods: {
async getDetail() {
this.$http
.get(this.$api.settlement.userConfigDetail, {
params: { rateId: this.id },
})
.then(({ data }) => {
this.form.signatureTime = [data.validStartTime, data.validEndTime];
this.treeData = [...data.allUserList];
this.selectedMerchants = [...(data.mappingUserList || [])];
this.filterTreeData();
this.hasLoaded = true;
})
.catch((err) => {
console.error('数据加载错误:', err);
});
},
// 递归提取一个节点及其所有子节点(扁平化为数组)
flattenNodeWithChildren(node) {
const result = [];
const traverse = (n) => {
result.push({ ...n }); // 浅拷贝避免引用污染
if (n.children && Array.isArray(n.children)) {
n.children.forEach(traverse);
}
};
traverse(node);
return result;
},
// 扁平化整棵树的所有节点(用于比对)
flattenTree(tree) {
let flat = [];
tree.forEach((node) => {
flat.push(...this.flattenNodeWithChildren(node));
});
return flat;
},
// 过滤树数据(隐藏已选节点及其子节点)
filterTreeData() {
const selectedIds = new Set(this.selectedMerchants.map((m) => m.id));
this.filteredTreeData = this.deepFilterTree(JSON.parse(JSON.stringify(this.treeData)), selectedIds);
},
deepFilterTree(tree, excludedIds) {
return tree
.map((node) => {
if (excludedIds.has(node.id)) return null;
if (node.children && node.children.length > 0) {
const filteredChildren = this.deepFilterTree(node.children, excludedIds).filter((child) => child !== null);
if (filteredChildren.length > 0) {
return { ...node, children: filteredChildren };
}
return null;
}
return node;
})
.filter((node) => node !== null);
},
// 树节点搜索过滤
filterNode(value, data) {
if (!value) return true;
return data.name.includes(value) || (data.id && data.id.toString().includes(value));
},
// 处理勾选变化:获取所有被勾选的节点(包含父节点和子节点)
handleTreeCheck(data, checked) {
this.$nextTick(() => {
// getCheckedNodes(true) 返回所有被勾选的节点(不含半选)
const checkedNodes = this.$refs.merchantTree.getCheckedNodes(true);
const allSelectedNodes = [];
// 遍历每个勾选的节点,并将其自身+所有子节点扁平化加入
checkedNodes.forEach((node) => {
allSelectedNodes.push(...this.flattenNodeWithChildren(node));
});
// 去重(按 id)
this.selectedTreeNodes = _.uniqBy(allSelectedNodes, 'id');
});
},
// 右移:将勾选的节点(含其所有子节点)移到右侧
moveToSelected() {
if (this.selectedTreeNodes.length === 0) return;
// 合并新选中的节点(去重)
this.selectedMerchants = _.unionBy(this.selectedMerchants, this.selectedTreeNodes, 'id');
// 清空当前选择 & 更新树显示
this.selectedTreeNodes = [];
this.$refs.merchantTree.setCheckedKeys([]); // 清除 UI 上的勾选
this.filterTreeData(); // 刷新左侧树(隐藏已选)
},
// 左移:从右侧移除选中的项
moveToSelectable() {
if (this.selectedMerchantsToRemove.length === 0) return;
// 移除指定元素
this.selectedMerchants = this.selectedMerchants.filter((m) => !this.selectedMerchantsToRemove.some((rm) => rm.id === m.id));
this.selectedMerchantsToRemove = [];
this.filterTreeData(); // 重新显示被移回的节点
},
handleSave() {
let vm = this;
let { form } = this;
var validator = new Validator();
// validator.add(form.signatureTime, [{ strategy: 'isNonEmpty', errorMsg: '请选择有效期' }]);
if (!Array.isArray(form.signatureTime) || form.signatureTime.length !== 2 || !form.signatureTime[0] || !form.signatureTime[1]) {
return ElMessage.error('请选择完整的有效期范围');
}
// validator.add(vm.selectedMerchants, [{ strategy: 'isNonEmpty', errorMsg: '请至少选择一个人员' }]);
// 手动验证商户选择
if (vm.selectedMerchants.length === 0) {
return ElMessage.error('请至少选择一个人员');
}
const errorMsg = validator.check();
if (errorMsg) return ElMessage.error(errorMsg);
console.log('最终选中的人员:', this.selectedMerchants);
const submitData = {
// // 有效期:拆分为开始日期和结束日期(根据接口要求调整)
rateId: this.id,
validStartTime: form.signatureTime[0] + ' 00:00:00',
validEndTime: form.signatureTime[1] + ' 23:59:59',
userIds: this.selectedMerchants.map((item) => item.userId), // 已选中的人员数组
};
console.log('88888888888', submitData);
this.$http
.post(this.$api.settlement.saveUserRateConfig, submitData)
.then((data) => {
console.log('配置已保存:', data);
if (data.code == 0) {
this.$message.success('配置已保存');
this.$emit('onSuccess', submitData);
this.dialogVisible = false;
} else {
this.$message.error(data.msg || '保存失败');
}
})
.catch((err) => {
//this.$message.error('保存失败,请重试')
console.error('保存错误:', err);
});
},
handleDialogVisible() {
this.$emit('onClose');
},
},
};
</script>
<style lang="scss" scoped>
::v-deep .el-input {
width: 350px;
}
.dialog-footer {
display: flex;
justify-content: center;
margin-top: 20px;
}
.line {
margin-top: -20px;
margin-bottom: 23px;
border-bottom: 1px solid #3333 !important;
}
::v-deep .el-dialog__title {
text-align: center;
font-size: 18px;
}
::v-deep .el-date-editor {
min-width: 350px !important;
}
.merchant-select-area {
display: flex;
justify-content: space-between;
margin-top: 43px;
max-height: 400px;
overflow: hidden;
overflow-x: auto;
}
.boxes {
margin-bottom: 10px;
display: flex;
flex-direction: column;
align-items: center;
width: 26-px;
}
.selectablebox {
margin-top: 10px;
//width: 316px;
width: 266px;
border: 1px solid #3333;
border-radius: 5px;
padding: 10px;
height: 350px;
}
.selectablebox > div {
width: 100%;
}
.arrow-column {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.arrow-column .el-button {
margin: 5px 0;
}
::v-deep .el-tree {
width: 100%;
max-height: 260px;
overflow-y: auto;
overflow-x: auto;
padding: 10px;
}
.selected-column {
flex: 1;
width: 90%;
max-height: 350px;
overflow-y: auto;
overflow-x: auto;
border-radius: 5px;
margin-top: 10px;
}
::v-deep .el-tree::-webkit-scrollbar,
.selected-column::-webkit-scrollbar {
width: 6px;
}
::v-deep .el-tree::-webkit-scrollbar-thumb,
.selected-column::-webkit-scrollbar-thumb {
background-color: #ddd;
border-radius: 3px;
}
.merchant-search-input ::v-deep .el-input__wrapper {
border-radius: 30px;
}
.merchant-checkbox ::v-deep.el-checkbox {
width: 100%;
height: 37px;
line-height: 37px;
padding-left: 10px;
margin-top: 10px;
}
.btn {
color: var(--el-color-primary);
border: 1px solid var(--el-color-primary);
}
:deep(.small-search-icon) {
font-size: 12px !important;
color: #666 !important;
cursor: pointer;
position: absolute;
top: 52px;
right: 69px;
width: 12px !important;
height: 12px !important;
}
:deep(.el-checkbox-group) {
display: flex !important;
flex-wrap: wrap;
height: 300px;
}
:deep(.el-checkbox-group .el-checkbox) {
width: 30% !important;
box-sizing: border-box;
}
:deep(.el-button:not(.is-circle) i.el-icon),
:deep(.el-button i.fa),
:deep(.el-button--default i.iconfont),
:deep(.el-button--default i.fa) {
font-size: 32px !important;
}
</style>
选择人员下面数据框的数据,超出宽度,为什么没有出现滚动条
最新发布