重复名称可选

This commit is contained in:
jhd
2026-05-21 18:33:44 +08:00
parent a189c3904d
commit ae6878c00b

View File

@@ -16,23 +16,76 @@
<el-button slot="append" icon="el-icon-search" @click="handleSearch" />
</el-input>
<!-- 多选模式穿梭框 -->
<el-transfer
v-loading="loading"
v-if="multiple"
v-model="selectedIds"
:data="transferData"
:titles="['可选员工', '已选员工']"
:button-texts="['移除', '添加']"
:props="{
key: keyField,
label: 'label',
disabled: 'disabled'
}"
filterable
:filter-method="transferFilterMethod"
filter-placeholder="根据员工部门搜索"
/>
<!-- 多选模式自定义穿梭框 -->
<div v-if="multiple" class="custom-transfer" v-loading="loading">
<!-- 左侧可选列表 -->
<div class="transfer-panel">
<div class="panel-header">可选员工</div>
<div class="panel-body">
<div
v-for="item in availableList"
:key="`left-${item.infoId}`"
:class="['transfer-item', { disabled: item.disabled, selected: isLeftSelected(item), 'already-selected': item.isSelected }]"
@click="addToSelected(item)"
>
<el-checkbox
:checked="item.isSelected"
:disabled="item.disabled"
@click.stop="toggleItem(item)"
/>
<span v-if="item.isDuplicate" class="duplicate-icon" title="重名">
<i class="el-icon-user" />
</span>
<span class="item-label">{{ item.dept }} - {{ item.name }} ({{ item.jobType }})</span>
<span class="id-badge" :title="'ID: ' + item.infoId">{{ item.infoId }}</span>
<span v-if="item.isSelected" class="selected-tag">已选</span>
</div>
<div v-if="availableList.length === 0" class="empty-tip">暂无数据</div>
</div>
</div>
<!-- 中间按钮组 -->
<div class="transfer-buttons">
<el-button
type="primary"
icon="el-icon-arrow-right"
@click="addAll"
:disabled="availableList.filter(i => !i.disabled).length === 0"
>
添加
</el-button>
<el-button
icon="el-icon-arrow-left"
@click="removeAll"
:disabled="selectedList.length === 0"
>
移除
</el-button>
</div>
<!-- 右侧已选列表 -->
<div class="transfer-panel">
<div class="panel-header">已选员工</div>
<div class="panel-body">
<div
v-for="item in selectedList"
:key="`right-${item.infoId}`"
:class="['transfer-item', 'selected-item', { selected: isRightSelected(item) }]"
@click="removeFromSelected(item)"
>
<el-checkbox
:checked="isRightSelected(item)"
@click.stop="toggleSelectedItem(item)"
/>
<span v-if="item.isDuplicate" class="duplicate-icon" title="重名">
<i class="el-icon-user" />
</span>
<span class="item-label">{{ item.dept }} - {{ item.name }} ({{ item.jobType }})</span>
</div>
<div v-if="selectedList.length === 0" class="empty-tip">暂无数据</div>
</div>
</div>
</div>
<!-- 单选模式表格 -->
<div v-if="!multiple" class="table-container">
@@ -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)
}
</style>