多选框组件重写

This commit is contained in:
jhd
2026-05-21 19:08:51 +08:00
parent ae6878c00b
commit b9d8c17953

View File

@@ -21,26 +21,34 @@
<!-- 左侧可选列表 -->
<div class="transfer-panel">
<div class="panel-header">可选员工</div>
<div class="panel-search">
<input
type="text"
v-model="leftSearchQuery"
placeholder="搜索姓名/部门"
class="search-input"
/>
</div>
<div class="panel-body">
<div
v-for="item in availableList"
v-for="item in filteredAvailableList"
:key="`left-${item.infoId}`"
:class="['transfer-item', { disabled: item.disabled, selected: isLeftSelected(item), 'already-selected': item.isSelected }]"
@click="addToSelected(item)"
>
<el-checkbox
<input
type="checkbox"
:checked="item.isSelected"
:disabled="item.disabled"
@click.stop="toggleItem(item)"
class="custom-checkbox"
/>
<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 v-if="filteredAvailableList.length === 0" class="empty-tip">暂无数据</div>
</div>
</div>
@@ -49,15 +57,15 @@
<el-button
type="primary"
icon="el-icon-arrow-right"
@click="addAll"
:disabled="availableList.filter(i => !i.disabled).length === 0"
@click="addSelected"
:disabled="leftSelectedKeys.length === 0"
>
添加
</el-button>
<el-button
icon="el-icon-arrow-left"
@click="removeAll"
:disabled="selectedList.length === 0"
@click="removeSelected"
:disabled="rightSelectedKeys.length === 0"
>
移除
</el-button>
@@ -66,23 +74,32 @@
<!-- 右侧已选列表 -->
<div class="transfer-panel">
<div class="panel-header">已选员工</div>
<div class="panel-search">
<input
type="text"
v-model="rightSearchQuery"
placeholder="搜索姓名/部门"
class="search-input"
/>
</div>
<div class="panel-body">
<div
v-for="item in selectedList"
v-for="item in filteredSelectedList"
:key="`right-${item.infoId}`"
:class="['transfer-item', 'selected-item', { selected: isRightSelected(item) }]"
@click="removeFromSelected(item)"
>
<el-checkbox
<input
type="checkbox"
:checked="isRightSelected(item)"
@click.stop="toggleSelectedItem(item)"
class="custom-checkbox"
/>
<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 v-if="filteredSelectedList.length === 0" class="empty-tip">暂无数据</div>
</div>
</div>
</div>
@@ -154,6 +171,8 @@ export default {
open: false,
loading: false,
searchQuery: '',
leftSearchQuery: '',
rightSearchQuery: '',
selectedIds: [],
selectedId: '',
selectedEmployee: {},
@@ -231,6 +250,30 @@ export default {
return null
})
.filter(item => item !== null)
},
// 左侧过滤后的列表
filteredAvailableList() {
if (!this.leftSearchQuery) {
return this.availableList
}
const query = this.leftSearchQuery.toLowerCase()
return this.availableList.filter(item => {
const name = (item.name || '').toLowerCase()
const dept = (item.dept || '').toLowerCase()
return name.includes(query) || dept.includes(query)
})
},
// 右侧过滤后的列表
filteredSelectedList() {
if (!this.rightSearchQuery) {
return this.selectedList
}
const query = this.rightSearchQuery.toLowerCase()
return this.selectedList.filter(item => {
const name = (item.name || '').toLowerCase()
const dept = (item.dept || '').toLowerCase()
return name.includes(query) || dept.includes(query)
})
}
},
watch: {
@@ -280,7 +323,10 @@ export default {
return new Promise((resolve) => {
listEmployeeInfo(params).then(response => {
// 处理重名情况:确保每个员工有唯一标识
this.rawEmployeeList = response.rows || []
// 过滤掉已离职的员工
this.rawEmployeeList = (response.rows || []).filter(employee => {
return !this.isEmployeeResigned(employee)
})
this.loading = false
resolve()
}).catch(() => {
@@ -289,6 +335,11 @@ export default {
})
})
},
// 判断员工是否已离职
isEmployeeResigned(employee) {
// 支持多种可能的离职状态字段
return employee.status === 1 || employee.isLeave === true || employee.resignStatus === 1 || employee.leaveStatus === 1
},
handleSearch() {
},
@@ -311,6 +362,12 @@ export default {
return isDisabled
},
// 判断员工是否已离职
isEmployeeResigned(employee) {
// isLeave: 1 表示已离职0 表示在职
return employee.isLeave === 1 || employee.isLeave === '1'
},
rowClassName({ row }) {
if (this.isSelected(row)) {
return 'selected-row'
@@ -400,13 +457,9 @@ export default {
}
},
// 添加所有左侧选中的
addAll() {
const toAdd = this.leftSelectedKeys.length > 0
? this.leftSelectedKeys
: this.availableList.filter(item => !item.disabled).map(item => item.infoId)
toAdd.forEach(key => {
// 添加左侧勾选的员工
addSelected() {
this.leftSelectedKeys.forEach(key => {
const keyStr = String(key)
if (!this.selectedIds.some(id => String(id) === keyStr)) {
this.selectedIds.push(key)
@@ -415,13 +468,9 @@ export default {
this.leftSelectedKeys = []
},
// 移除所有右侧选中的
removeAll() {
const toRemove = this.rightSelectedKeys.length > 0
? this.rightSelectedKeys
: this.selectedIds.slice()
toRemove.forEach(key => {
// 移除右侧勾选的员工
removeSelected() {
this.rightSelectedKeys.forEach(key => {
const keyStr = String(key)
const index = this.selectedIds.findIndex(id => String(id) === keyStr)
if (index > -1) {
@@ -431,6 +480,24 @@ export default {
this.rightSelectedKeys = []
},
// 添加所有可选员工(未禁用且未选中的)
addAll() {
this.availableList.forEach(item => {
if (!item.disabled && !item.isSelected) {
const keyStr = String(item.infoId)
if (!this.selectedIds.some(id => String(id) === keyStr)) {
this.selectedIds.push(item.infoId)
}
}
})
},
// 移除所有已选员工
removeAll() {
this.selectedIds = []
this.rightSelectedKeys = []
},
confirmSelection() {
if (this.multiple) {
// 使用ID作为唯一标识避免重名问题
@@ -520,6 +587,25 @@ export default {
background: #f5f7fa;
}
.panel-search {
padding: 10px;
border-bottom: 1px solid #f2f6fc;
}
.search-input {
width: 100%;
padding: 6px 10px;
border: 1px solid #dcdfe6;
border-radius: 4px;
font-size: 13px;
box-sizing: border-box;
}
.search-input:focus {
outline: none;
border-color: #409eff;
}
.panel-body {
flex: 1;
overflow-y: auto;
@@ -587,6 +673,15 @@ export default {
margin-left: 4px;
}
.resigned-tag {
font-size: 11px;
color: #f56c6c;
background-color: #fef0f0;
padding: 1px 4px;
border-radius: 2px;
margin-left: 4px;
}
.id-badge {
font-size: 10px;
color: #909399;
@@ -597,6 +692,19 @@ export default {
font-family: monospace;
}
/* 自定义 checkbox 样式 */
.custom-checkbox {
width: 16px;
height: 16px;
cursor: pointer;
accent-color: #409eff;
}
.custom-checkbox:disabled {
cursor: not-allowed;
opacity: 0.5;
}
.transfer-buttons {
display: flex;
flex-direction: column;