重复名称可选
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user