多选框组件重写
This commit is contained in:
@@ -21,26 +21,34 @@
|
|||||||
<!-- 左侧:可选列表 -->
|
<!-- 左侧:可选列表 -->
|
||||||
<div class="transfer-panel">
|
<div class="transfer-panel">
|
||||||
<div class="panel-header">可选员工</div>
|
<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 class="panel-body">
|
||||||
<div
|
<div
|
||||||
v-for="item in availableList"
|
v-for="item in filteredAvailableList"
|
||||||
:key="`left-${item.infoId}`"
|
:key="`left-${item.infoId}`"
|
||||||
:class="['transfer-item', { disabled: item.disabled, selected: isLeftSelected(item), 'already-selected': item.isSelected }]"
|
:class="['transfer-item', { disabled: item.disabled, selected: isLeftSelected(item), 'already-selected': item.isSelected }]"
|
||||||
@click="addToSelected(item)"
|
|
||||||
>
|
>
|
||||||
<el-checkbox
|
<input
|
||||||
|
type="checkbox"
|
||||||
:checked="item.isSelected"
|
:checked="item.isSelected"
|
||||||
:disabled="item.disabled"
|
:disabled="item.disabled"
|
||||||
@click.stop="toggleItem(item)"
|
@click.stop="toggleItem(item)"
|
||||||
|
class="custom-checkbox"
|
||||||
/>
|
/>
|
||||||
<span v-if="item.isDuplicate" class="duplicate-icon" title="重名">
|
<span v-if="item.isDuplicate" class="duplicate-icon" title="重名">
|
||||||
<i class="el-icon-user" />
|
<i class="el-icon-user" />
|
||||||
</span>
|
</span>
|
||||||
<span class="item-label">{{ item.dept }} - {{ item.name }} ({{ item.jobType }})</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>
|
<span v-if="item.isSelected" class="selected-tag">已选</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="availableList.length === 0" class="empty-tip">暂无数据</div>
|
<div v-if="filteredAvailableList.length === 0" class="empty-tip">暂无数据</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -49,15 +57,15 @@
|
|||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
icon="el-icon-arrow-right"
|
icon="el-icon-arrow-right"
|
||||||
@click="addAll"
|
@click="addSelected"
|
||||||
:disabled="availableList.filter(i => !i.disabled).length === 0"
|
:disabled="leftSelectedKeys.length === 0"
|
||||||
>
|
>
|
||||||
添加
|
添加
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
icon="el-icon-arrow-left"
|
icon="el-icon-arrow-left"
|
||||||
@click="removeAll"
|
@click="removeSelected"
|
||||||
:disabled="selectedList.length === 0"
|
:disabled="rightSelectedKeys.length === 0"
|
||||||
>
|
>
|
||||||
移除
|
移除
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -66,23 +74,32 @@
|
|||||||
<!-- 右侧:已选列表 -->
|
<!-- 右侧:已选列表 -->
|
||||||
<div class="transfer-panel">
|
<div class="transfer-panel">
|
||||||
<div class="panel-header">已选员工</div>
|
<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 class="panel-body">
|
||||||
<div
|
<div
|
||||||
v-for="item in selectedList"
|
v-for="item in filteredSelectedList"
|
||||||
:key="`right-${item.infoId}`"
|
:key="`right-${item.infoId}`"
|
||||||
:class="['transfer-item', 'selected-item', { selected: isRightSelected(item) }]"
|
:class="['transfer-item', 'selected-item', { selected: isRightSelected(item) }]"
|
||||||
@click="removeFromSelected(item)"
|
|
||||||
>
|
>
|
||||||
<el-checkbox
|
<input
|
||||||
|
type="checkbox"
|
||||||
:checked="isRightSelected(item)"
|
:checked="isRightSelected(item)"
|
||||||
@click.stop="toggleSelectedItem(item)"
|
@click.stop="toggleSelectedItem(item)"
|
||||||
|
class="custom-checkbox"
|
||||||
/>
|
/>
|
||||||
<span v-if="item.isDuplicate" class="duplicate-icon" title="重名">
|
<span v-if="item.isDuplicate" class="duplicate-icon" title="重名">
|
||||||
<i class="el-icon-user" />
|
<i class="el-icon-user" />
|
||||||
</span>
|
</span>
|
||||||
<span class="item-label">{{ item.dept }} - {{ item.name }} ({{ item.jobType }})</span>
|
<span class="item-label">{{ item.dept }} - {{ item.name }} ({{ item.jobType }})</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="selectedList.length === 0" class="empty-tip">暂无数据</div>
|
<div v-if="filteredSelectedList.length === 0" class="empty-tip">暂无数据</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -154,6 +171,8 @@ export default {
|
|||||||
open: false,
|
open: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
|
leftSearchQuery: '',
|
||||||
|
rightSearchQuery: '',
|
||||||
selectedIds: [],
|
selectedIds: [],
|
||||||
selectedId: '',
|
selectedId: '',
|
||||||
selectedEmployee: {},
|
selectedEmployee: {},
|
||||||
@@ -231,6 +250,30 @@ export default {
|
|||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
.filter(item => item !== 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: {
|
watch: {
|
||||||
@@ -280,7 +323,10 @@ 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 || []).filter(employee => {
|
||||||
|
return !this.isEmployeeResigned(employee)
|
||||||
|
})
|
||||||
this.loading = false
|
this.loading = false
|
||||||
resolve()
|
resolve()
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
@@ -289,6 +335,11 @@ export default {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
// 判断员工是否已离职
|
||||||
|
isEmployeeResigned(employee) {
|
||||||
|
// 支持多种可能的离职状态字段
|
||||||
|
return employee.status === 1 || employee.isLeave === true || employee.resignStatus === 1 || employee.leaveStatus === 1
|
||||||
|
},
|
||||||
|
|
||||||
handleSearch() {
|
handleSearch() {
|
||||||
},
|
},
|
||||||
@@ -311,6 +362,12 @@ export default {
|
|||||||
return isDisabled
|
return isDisabled
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 判断员工是否已离职
|
||||||
|
isEmployeeResigned(employee) {
|
||||||
|
// isLeave: 1 表示已离职,0 表示在职
|
||||||
|
return employee.isLeave === 1 || employee.isLeave === '1'
|
||||||
|
},
|
||||||
|
|
||||||
rowClassName({ row }) {
|
rowClassName({ row }) {
|
||||||
if (this.isSelected(row)) {
|
if (this.isSelected(row)) {
|
||||||
return 'selected-row'
|
return 'selected-row'
|
||||||
@@ -400,13 +457,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 添加所有左侧选中的
|
// 添加左侧勾选的员工
|
||||||
addAll() {
|
addSelected() {
|
||||||
const toAdd = this.leftSelectedKeys.length > 0
|
this.leftSelectedKeys.forEach(key => {
|
||||||
? this.leftSelectedKeys
|
|
||||||
: this.availableList.filter(item => !item.disabled).map(item => item.infoId)
|
|
||||||
|
|
||||||
toAdd.forEach(key => {
|
|
||||||
const keyStr = String(key)
|
const keyStr = String(key)
|
||||||
if (!this.selectedIds.some(id => String(id) === keyStr)) {
|
if (!this.selectedIds.some(id => String(id) === keyStr)) {
|
||||||
this.selectedIds.push(key)
|
this.selectedIds.push(key)
|
||||||
@@ -415,13 +468,9 @@ export default {
|
|||||||
this.leftSelectedKeys = []
|
this.leftSelectedKeys = []
|
||||||
},
|
},
|
||||||
|
|
||||||
// 移除所有右侧选中的
|
// 移除右侧勾选的员工
|
||||||
removeAll() {
|
removeSelected() {
|
||||||
const toRemove = this.rightSelectedKeys.length > 0
|
this.rightSelectedKeys.forEach(key => {
|
||||||
? this.rightSelectedKeys
|
|
||||||
: this.selectedIds.slice()
|
|
||||||
|
|
||||||
toRemove.forEach(key => {
|
|
||||||
const keyStr = String(key)
|
const keyStr = String(key)
|
||||||
const index = this.selectedIds.findIndex(id => String(id) === keyStr)
|
const index = this.selectedIds.findIndex(id => String(id) === keyStr)
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
@@ -431,6 +480,24 @@ export default {
|
|||||||
this.rightSelectedKeys = []
|
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() {
|
confirmSelection() {
|
||||||
if (this.multiple) {
|
if (this.multiple) {
|
||||||
// 使用ID作为唯一标识,避免重名问题
|
// 使用ID作为唯一标识,避免重名问题
|
||||||
@@ -520,6 +587,25 @@ export default {
|
|||||||
background: #f5f7fa;
|
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 {
|
.panel-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -587,6 +673,15 @@ export default {
|
|||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.resigned-tag {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #f56c6c;
|
||||||
|
background-color: #fef0f0;
|
||||||
|
padding: 1px 4px;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.id-badge {
|
.id-badge {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
@@ -597,6 +692,19 @@ export default {
|
|||||||
font-family: monospace;
|
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 {
|
.transfer-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
Reference in New Issue
Block a user