重复名称可选
This commit is contained in:
@@ -16,23 +16,76 @@
|
|||||||
<el-button slot="append" icon="el-icon-search" @click="handleSearch" />
|
<el-button slot="append" icon="el-icon-search" @click="handleSearch" />
|
||||||
</el-input>
|
</el-input>
|
||||||
|
|
||||||
<!-- 多选模式:穿梭框 -->
|
<!-- 多选模式:自定义穿梭框 -->
|
||||||
<el-transfer
|
<div v-if="multiple" class="custom-transfer" v-loading="loading">
|
||||||
v-loading="loading"
|
<!-- 左侧:可选列表 -->
|
||||||
v-if="multiple"
|
<div class="transfer-panel">
|
||||||
v-model="selectedIds"
|
<div class="panel-header">可选员工</div>
|
||||||
:data="transferData"
|
<div class="panel-body">
|
||||||
:titles="['可选员工', '已选员工']"
|
<div
|
||||||
:button-texts="['移除', '添加']"
|
v-for="item in availableList"
|
||||||
:props="{
|
:key="`left-${item.infoId}`"
|
||||||
key: keyField,
|
:class="['transfer-item', { disabled: item.disabled, selected: isLeftSelected(item), 'already-selected': item.isSelected }]"
|
||||||
label: 'label',
|
@click="addToSelected(item)"
|
||||||
disabled: 'disabled'
|
>
|
||||||
}"
|
<el-checkbox
|
||||||
filterable
|
:checked="item.isSelected"
|
||||||
:filter-method="transferFilterMethod"
|
:disabled="item.disabled"
|
||||||
filter-placeholder="根据员工部门搜索"
|
@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">
|
<div v-if="!multiple" class="table-container">
|
||||||
@@ -103,7 +156,10 @@ export default {
|
|||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
selectedIds: [],
|
selectedIds: [],
|
||||||
selectedId: '',
|
selectedId: '',
|
||||||
selectedEmployee: {}
|
selectedEmployee: {},
|
||||||
|
// 用于记录选中状态(解决重名问题)
|
||||||
|
leftSelectedKeys: [],
|
||||||
|
rightSelectedKeys: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -132,12 +188,49 @@ export default {
|
|||||||
disabled: this.disabledIdList.includes(employee[this.keyField].toString())
|
disabled: this.disabledIdList.includes(employee[this.keyField].toString())
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
transferData() {
|
// 获取重名的姓名列表
|
||||||
return this.employeeList.map(employee => ({
|
duplicateNames() {
|
||||||
[this.keyField]: employee[this.keyField],
|
const nameCount = {}
|
||||||
label: `${employee.dept} - ${employee.name} (${employee.jobType})`,
|
this.rawEmployeeList.forEach(employee => {
|
||||||
disabled: employee.disabled
|
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: {
|
watch: {
|
||||||
@@ -145,7 +238,13 @@ export default {
|
|||||||
handler(newVal) {
|
handler(newVal) {
|
||||||
if (this.multiple) {
|
if (this.multiple) {
|
||||||
if (newVal) {
|
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 {
|
} else {
|
||||||
this.selectedIds = []
|
this.selectedIds = []
|
||||||
}
|
}
|
||||||
@@ -164,6 +263,9 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
toggleDialog() {
|
toggleDialog() {
|
||||||
if (!this.open) {
|
if (!this.open) {
|
||||||
|
// 打开时清空选中状态
|
||||||
|
this.leftSelectedKeys = []
|
||||||
|
this.rightSelectedKeys = []
|
||||||
this.getEmployeeList()
|
this.getEmployeeList()
|
||||||
}
|
}
|
||||||
this.open = !this.open
|
this.open = !this.open
|
||||||
@@ -177,7 +279,8 @@ export default {
|
|||||||
}
|
}
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
listEmployeeInfo(params).then(response => {
|
listEmployeeInfo(params).then(response => {
|
||||||
this.rawEmployeeList = response.rows;
|
// 处理重名情况:确保每个员工有唯一标识
|
||||||
|
this.rawEmployeeList = response.rows || []
|
||||||
this.loading = false
|
this.loading = false
|
||||||
resolve()
|
resolve()
|
||||||
}).catch(() => {
|
}).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() {
|
confirmSelection() {
|
||||||
if (this.multiple) {
|
if (this.multiple) {
|
||||||
|
// 使用ID作为唯一标识,避免重名问题
|
||||||
this.$emit('input', this.selectedIds.join(','))
|
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.$emit('change', selectedEmployees)
|
||||||
}
|
}
|
||||||
this.open = false
|
this.open = false
|
||||||
@@ -251,10 +444,17 @@ export default {
|
|||||||
cancelSelection() {
|
cancelSelection() {
|
||||||
if (this.multiple) {
|
if (this.multiple) {
|
||||||
if (this.value) {
|
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 {
|
} else {
|
||||||
this.selectedIds = []
|
this.selectedIds = []
|
||||||
}
|
}
|
||||||
|
this.leftSelectedKeys = []
|
||||||
|
this.rightSelectedKeys = []
|
||||||
} else {
|
} else {
|
||||||
if (this.value) {
|
if (this.value) {
|
||||||
this.findSelectedEmployee(this.value)
|
this.findSelectedEmployee(this.value)
|
||||||
@@ -295,29 +495,120 @@ export default {
|
|||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .el-transfer {
|
/* 自定义穿梭框样式 */
|
||||||
|
.custom-transfer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
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;
|
align-items: center;
|
||||||
|
padding: 10px 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 1px solid #f2f6fc;
|
||||||
|
transition: background-color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .el-transfer-panel {
|
.transfer-item:hover:not(.disabled) {
|
||||||
width: 380px;
|
background-color: #ecf5ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .el-transfer-panel__body {
|
.transfer-item.disabled {
|
||||||
height: 450px;
|
color: #c0c4cc;
|
||||||
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .el-transfer__buttons {
|
.transfer-item.selected {
|
||||||
padding: 0 15px;
|
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;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .el-transfer__button {
|
.empty-tip {
|
||||||
margin: 10px 0;
|
text-align: center;
|
||||||
|
color: #c0c4cc;
|
||||||
|
padding: 40px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .el-table .selected-row {
|
::v-deep .el-table .selected-row {
|
||||||
@@ -327,8 +618,4 @@ export default {
|
|||||||
::v-deep .el-table .selected-row:hover {
|
::v-deep .el-table .selected-row:hover {
|
||||||
background-color: #ecf5ff !important;
|
background-color: #ecf5ff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .el-transfer-panel__list.is-filterable {
|
|
||||||
height: calc(100% - 60px)
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user