diff --git a/klp-ui/src/components/EmployeeSelector/index.vue b/klp-ui/src/components/EmployeeSelector/index.vue index 8d46322a..65ac6f68 100644 --- a/klp-ui/src/components/EmployeeSelector/index.vue +++ b/klp-ui/src/components/EmployeeSelector/index.vue @@ -16,23 +16,76 @@ - - + +
+ +
+
可选员工
+
+
+ + + + + {{ item.dept }} - {{ item.name }} ({{ item.jobType }}) + {{ item.infoId }} + 已选 +
+
暂无数据
+
+
+ + +
+ + 添加 + + + 移除 + +
+ + +
+
已选员工
+
+
+ + + + + {{ item.dept }} - {{ item.name }} ({{ item.jobType }}) +
+
暂无数据
+
+
+
@@ -103,7 +156,10 @@ export default { searchQuery: '', selectedIds: [], selectedId: '', - selectedEmployee: {} + selectedEmployee: {}, + // 用于记录选中状态(解决重名问题) + leftSelectedKeys: [], + rightSelectedKeys: [] } }, computed: { @@ -132,12 +188,49 @@ export default { disabled: this.disabledIdList.includes(employee[this.keyField].toString()) })) }, - transferData() { - return this.employeeList.map(employee => ({ - [this.keyField]: employee[this.keyField], - label: `${employee.dept} - ${employee.name} (${employee.jobType})`, - disabled: employee.disabled - })) + // 获取重名的姓名列表 + duplicateNames() { + const nameCount = {} + this.rawEmployeeList.forEach(employee => { + const name = employee.name || '' + nameCount[name] = (nameCount[name] || 0) + 1 + }) + return Object.keys(nameCount).filter(name => nameCount[name] > 1) + }, + // 可选列表(显示所有员工,包括已选的) + availableList() { + const duplicates = this.duplicateNames + return this.rawEmployeeList.map(employee => { + // 直接使用 infoId 作为唯一标识 + const employeeId = employee.infoId + const idStr = String(employeeId) + return { + ...employee, + disabled: this.disabledIdList.includes(idStr), + isDuplicate: duplicates.includes(employee.name || ''), + isSelected: this.selectedIds.some(id => String(id) === idStr) + } + }) + }, + // 已选列表 + selectedList() { + const duplicates = this.duplicateNames + // 使用 infoId 作为唯一标识,支持同名不同ID的员工 + return this.selectedIds + .map(id => { + const idStr = String(id) + const matched = this.rawEmployeeList.filter(item => String(item.infoId) === idStr) + const employee = matched.length > 0 ? matched[0] : null + if (employee) { + // 创建新对象,避免修改原始数据 + return { + ...employee, + isDuplicate: duplicates.includes(employee.name || '') + } + } + return null + }) + .filter(item => item !== null) } }, watch: { @@ -145,7 +238,13 @@ export default { handler(newVal) { if (this.multiple) { if (newVal) { - this.selectedIds = newVal.split(',').map(id => id.trim()).filter(id => id).map(id => isNaN(Number(id)) ? id : Number(id)) + // 处理重名问题:确保ID是唯一标识,转换为正确类型 + this.selectedIds = newVal.split(',').map(id => { + const trimmedId = id.trim() + if (!trimmedId) return null + const numId = Number(trimmedId) + return isNaN(numId) ? trimmedId : numId + }).filter(id => id !== null) } else { this.selectedIds = [] } @@ -164,6 +263,9 @@ export default { methods: { toggleDialog() { if (!this.open) { + // 打开时清空选中状态 + this.leftSelectedKeys = [] + this.rightSelectedKeys = [] this.getEmployeeList() } this.open = !this.open @@ -177,7 +279,8 @@ export default { } return new Promise((resolve) => { listEmployeeInfo(params).then(response => { - this.rawEmployeeList = response.rows; + // 处理重名情况:确保每个员工有唯一标识 + this.rawEmployeeList = response.rows || [] this.loading = false resolve() }).catch(() => { @@ -235,14 +338,104 @@ export default { } }, - transferFilterMethod(query, item) { - return item.label.toLowerCase().includes(query.toLowerCase()) + // ========== 多选模式方法 ========== + + // 判断左侧是否选中 + isLeftSelected(item) { + return this.leftSelectedKeys.includes(item.infoId) + }, + + // 判断右侧是否选中 + isRightSelected(item) { + return this.rightSelectedKeys.includes(item.infoId) + }, + + // 切换左侧选中状态 + toggleItem(item) { + if (item.disabled) return + const key = item.infoId + const index = this.leftSelectedKeys.indexOf(key) + if (index > -1) { + this.leftSelectedKeys.splice(index, 1) + } else { + this.leftSelectedKeys.push(key) + } + }, + + // 切换右侧选中状态 + toggleSelectedItem(item) { + const key = item.infoId + const index = this.rightSelectedKeys.indexOf(key) + if (index > -1) { + this.rightSelectedKeys.splice(index, 1) + } else { + this.rightSelectedKeys.push(key) + } + }, + + // 添加单个到已选 + addToSelected(item) { + if (item.disabled) return + // 直接使用 infoId 作为唯一标识 + const key = item.infoId + const keyStr = String(key) + // 检查是否已存在(字符串比较) + const exists = this.selectedIds.some(id => String(id) === keyStr) + if (!exists) { + this.selectedIds.push(key) + console.log('添加员工:', item.name, 'ID:', key, 'selectedIds:', this.selectedIds) + } else { + console.log('员工已存在:', item.name, 'ID:', key) + } + }, + + // 从已选移除单个 + removeFromSelected(item) { + // 直接使用 infoId 作为唯一标识 + const key = item.infoId + const keyStr = String(key) + const index = this.selectedIds.findIndex(id => String(id) === keyStr) + if (index > -1) { + this.selectedIds.splice(index, 1) + } + }, + + // 添加所有左侧选中的 + addAll() { + const toAdd = this.leftSelectedKeys.length > 0 + ? this.leftSelectedKeys + : this.availableList.filter(item => !item.disabled).map(item => item.infoId) + + toAdd.forEach(key => { + const keyStr = String(key) + if (!this.selectedIds.some(id => String(id) === keyStr)) { + this.selectedIds.push(key) + } + }) + this.leftSelectedKeys = [] + }, + + // 移除所有右侧选中的 + removeAll() { + const toRemove = this.rightSelectedKeys.length > 0 + ? this.rightSelectedKeys + : this.selectedIds.slice() + + toRemove.forEach(key => { + const keyStr = String(key) + const index = this.selectedIds.findIndex(id => String(id) === keyStr) + if (index > -1) { + this.selectedIds.splice(index, 1) + } + }) + this.rightSelectedKeys = [] }, confirmSelection() { if (this.multiple) { + // 使用ID作为唯一标识,避免重名问题 this.$emit('input', this.selectedIds.join(',')) - const selectedEmployees = this.rawEmployeeList.filter(item => this.selectedIds.includes(item[this.keyField])) + const selectedEmployees = this.selectedList this.$emit('change', selectedEmployees) } this.open = false @@ -251,10 +444,17 @@ export default { cancelSelection() { if (this.multiple) { if (this.value) { - this.selectedIds = this.value.split(',').map(id => id.trim()).filter(id => id).map(id => isNaN(Number(id)) ? id : Number(id)) + this.selectedIds = this.value.split(',').map(id => { + const trimmedId = id.trim() + if (!trimmedId) return null + const numId = Number(trimmedId) + return isNaN(numId) ? trimmedId : numId + }).filter(id => id !== null) } else { this.selectedIds = [] } + this.leftSelectedKeys = [] + this.rightSelectedKeys = [] } else { if (this.value) { this.findSelectedEmployee(this.value) @@ -295,29 +495,120 @@ export default { margin-top: 15px; } -::v-deep .el-transfer { +/* 自定义穿梭框样式 */ +.custom-transfer { display: flex; justify-content: center; + align-items: flex-start; + gap: 20px; + padding: 10px; +} + +.transfer-panel { + width: 350px; + border: 1px solid #dcdfe6; + border-radius: 4px; + background: #fff; + display: flex; + flex-direction: column; +} + +.panel-header { + padding: 12px 15px; + font-weight: 600; + border-bottom: 1px solid #dcdfe6; + background: #f5f7fa; +} + +.panel-body { + flex: 1; + overflow-y: auto; + max-height: 400px; +} + +.transfer-item { + display: flex; align-items: center; + padding: 10px 15px; + cursor: pointer; + border-bottom: 1px solid #f2f6fc; + transition: background-color 0.2s; } -::v-deep .el-transfer-panel { - width: 380px; +.transfer-item:hover:not(.disabled) { + background-color: #ecf5ff; } -::v-deep .el-transfer-panel__body { - height: 450px; +.transfer-item.disabled { + color: #c0c4cc; + cursor: not-allowed; } -::v-deep .el-transfer__buttons { - padding: 0 15px; +.transfer-item.selected { + background-color: #e8f4fd; +} + +.item-label { + flex: 1; + margin-left: 4px; + font-size: 13px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.duplicate-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + background-color: #ffb800; + color: #fff; + border-radius: 50%; + font-size: 12px; + margin-left: 2px; +} + +.duplicate-icon i { + font-size: 10px; +} + +.already-selected { + opacity: 0.6; +} + +.selected-tag { + font-size: 11px; + color: #67c23a; + background-color: #f0f9eb; + padding: 1px 4px; + border-radius: 2px; + margin-left: 4px; +} + +.id-badge { + font-size: 10px; + color: #909399; + background-color: #f5f7fa; + padding: 1px 3px; + border-radius: 2px; + margin-left: 4px; + font-family: monospace; +} + +.transfer-buttons { display: flex; flex-direction: column; justify-content: center; + gap: 10px; + padding: 10px; } -::v-deep .el-transfer__button { - margin: 10px 0; +.empty-tip { + text-align: center; + color: #c0c4cc; + padding: 40px 0; } ::v-deep .el-table .selected-row { @@ -327,8 +618,4 @@ export default { ::v-deep .el-table .selected-row:hover { background-color: #ecf5ff !important; } - -::v-deep .el-transfer-panel__list.is-filterable { - height: calc(100% - 60px) -}