This commit is contained in:
BianLzhaoMin 2026-02-10 16:12:59 +08:00
parent 400a11fc7e
commit 3bc10cb2de
1 changed files with 319 additions and 136 deletions

View File

@ -26,79 +26,66 @@
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<!-- 搜索栏 --> <!-- 人员tab内容 -->
<div class="search-bar"> <div v-show="activeTab === 'person'" class="tab-content">
<el-input <!-- 搜索栏 -->
v-if="activeTab === 'person'" <div class="search-bar">
v-model.trim="searchKeyword" <el-input
placeholder="输入关键字" v-model.trim="searchKeyword"
clearable placeholder="输入关键字"
prefix-icon="Search" clearable
@input="handleSearch" prefix-icon="Search"
/> @input="handleSearch"
<el-input />
v-else </div>
v-model.trim="groupSearchKeyword"
placeholder="输入关键字" <!-- 人员表格 - 使用虚拟表格 -->
clearable <div class="table-container">
prefix-icon="Search" <el-auto-resizer>
@input="handleGroupSearch" <template #default="{ height, width }">
/> <el-table-v2
:columns="personTableColumns"
:data="filteredPersonList"
:width="width"
:height="height"
row-key="id"
fixed
/>
</template>
</el-auto-resizer>
</div>
</div> </div>
<!-- 人员表格 --> <!-- 分组tab内容 -->
<el-table <div v-show="activeTab === 'group'" class="tab-content">
v-if="activeTab === 'person'" <!-- 搜索栏 -->
border <div class="search-bar">
ref="personTableRef" <el-input
:data="filteredPersonList" v-model.trim="groupSearchKeyword"
height="400" placeholder="输入关键字"
@selection-change="onPersonSelectionChange" clearable
> prefix-icon="Search"
<el-table-column type="selection" width="50" align="center" /> @input="handleGroupSearch"
<el-table-column />
show-overflow-tooltip </div>
prop="orgName"
label="人员所属"
align="center"
/>
<el-table-column
show-overflow-tooltip
prop="workerName"
label="姓名"
width="120"
align="center"
/>
<el-table-column show-overflow-tooltip prop="sex" label="性别" align="center">
<template #default="{ row }">
{{ row.sex == 1 ? '男' : '女' }}
</template>
</el-table-column>
<el-table-column
show-overflow-tooltip
prop="phone"
label="电话"
align="center"
/>
</el-table>
<!-- 分组表格 --> <!-- 分组表格 -->
<el-table <el-table
v-else border
border ref="groupTableRef"
ref="groupTableRef" :data="filteredGroupList"
:data="filteredGroupList" height="400"
height="400" @selection-change="onGroupSelectionChange"
@selection-change="onGroupSelectionChange" >
> <el-table-column type="selection" width="50" align="center" />
<el-table-column type="selection" width="50" align="center" /> <el-table-column
<el-table-column show-overflow-tooltip
show-overflow-tooltip prop="groupName"
prop="groupName" label="分组名称"
label="分组名称" align="center"
align="center" />
/> </el-table>
</el-table> </div>
</el-col> </el-col>
<!-- 右侧已选内容 --> <!-- 右侧已选内容 -->
@ -157,10 +144,12 @@
</template> </template>
<script setup name="PersonPicker"> <script setup name="PersonPicker">
import { ref, computed, watch, nextTick, onMounted } from 'vue' import { ref, computed, watch, nextTick, onMounted, h } from 'vue'
import { Close } from '@element-plus/icons-vue' import { Close } from '@element-plus/icons-vue'
import { ElCheckbox } from 'element-plus'
import { getAllPersonAPI } from '@/api/personManage/person.js' import { getAllPersonAPI } from '@/api/personManage/person.js'
import { getAllGroupAPI } from '@/api/basicManage/groupManage.js' import { getAllGroupAPI } from '@/api/basicManage/groupManage.js'
import { debounce } from '@/utils'
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
@ -178,7 +167,6 @@ const emit = defineEmits(['update:modelValue', 'update:groupList', 'confirm'])
const activeTab = ref('person') const activeTab = ref('person')
const searchKeyword = ref('') const searchKeyword = ref('')
const groupSearchKeyword = ref('') const groupSearchKeyword = ref('')
const personTableRef = ref(null)
const groupTableRef = ref(null) const groupTableRef = ref(null)
// //
@ -197,19 +185,106 @@ const isSyncing = ref(false)
// //
const isInitialized = ref(false) const isInitialized = ref(false)
// // keys
const filteredPersonList = computed(() => { const selectedRowKeys = ref([])
if (!searchKeyword.value) {
return personList.value // 使ref
} const filteredPersonList = ref([])
const keyword = searchKeyword.value.toLowerCase()
return personList.value.filter((item) => { //
const orgName = (item.orgName || '').toLowerCase() const debouncedPersonFilter = ref(null)
const workerName = (item.workerName || '').toLowerCase()
return orgName.includes(keyword) || workerName.includes(keyword) //
}) const personTableColumns = computed(() => {
return [
{
key: 'selection',
width: 55,
cellRenderer: ({ rowData }) => {
const isSelected = selectedRowKeys.value.includes(rowData.id)
return h(ElCheckbox, {
modelValue: isSelected,
'onUpdate:modelValue': (val) => {
handlePersonRowSelect({ row: rowData, checked: val })
},
})
},
headerCellRenderer: () => {
const allSelected =
filteredPersonList.value.length > 0 &&
filteredPersonList.value.every((row) => selectedRowKeys.value.includes(row.id))
const someSelected = filteredPersonList.value.some((row) =>
selectedRowKeys.value.includes(row.id),
)
return h(ElCheckbox, {
modelValue: allSelected,
indeterminate: someSelected && !allSelected,
'onUpdate:modelValue': (val) => {
handlePersonSelectAll({ checked: val })
},
})
},
},
{
key: 'orgName',
dataKey: 'orgName',
title: '人员所属',
width: 150,
},
{
key: 'workerName',
dataKey: 'workerName',
title: '姓名',
width: 120,
},
{
key: 'sex',
dataKey: 'sex',
title: '性别',
width: 80,
cellRenderer: ({ rowData }) => {
return rowData.sex == 1 ? '男' : '女'
},
},
{
key: 'phone',
dataKey: 'phone',
title: '电话',
width: 200,
},
]
}) })
//
const performPersonFilter = () => {
if (personList.value.length === 0) {
filteredPersonList.value = []
return
}
if (!searchKeyword.value) {
filteredPersonList.value = personList.value
} else {
const keyword = searchKeyword.value.toLowerCase()
filteredPersonList.value = personList.value.filter((item) => {
const orgName = (item.orgName || '').toLowerCase()
const workerName = (item.workerName || '').toLowerCase()
return orgName.includes(keyword) || workerName.includes(keyword)
})
}
//
nextTick(() => {
syncPersonTableSelection()
})
}
//
const createDebouncedPersonFilter = () => {
debouncedPersonFilter.value = debounce(performPersonFilter, 300)
}
// //
const filteredGroupList = computed(() => { const filteredGroupList = computed(() => {
if (!groupSearchKeyword.value) { if (!groupSearchKeyword.value) {
@ -286,11 +361,13 @@ const handleTabChange = (tabName) => {
}) })
} }
// // 使
const handleSearch = () => { const handleSearch = () => {
nextTick(() => { if (debouncedPersonFilter.value) {
syncPersonTableSelection() debouncedPersonFilter.value()
}) } else {
performPersonFilter()
}
} }
const handleGroupSearch = () => { const handleGroupSearch = () => {
@ -299,50 +376,133 @@ const handleGroupSearch = () => {
}) })
} }
// //
const onPersonSelectionChange = (selection) => { const handlePersonRowSelect = ({ row, checked }) => {
if (isSyncing.value) { if (isSyncing.value) {
return return
} }
// ID isSyncing.value = true
const visiblePersonIds = new Set(filteredPersonList.value.map((item) => item.id))
// ID const rowId = Number(row.id)
const selectedVisibleIds = new Set(selection.map((item) => item.id)) const allSelectedIds = new Set(directSelectedPersonIds.value.map((id) => Number(id)))
// if (checked) {
const preservedIds = directSelectedPersonIds.value.filter((id) => !visiblePersonIds.has(id)) allSelectedIds.add(rowId)
if (!selectedRowKeys.value.includes(row.id)) {
selectedRowKeys.value.push(row.id)
}
} else {
allSelectedIds.delete(rowId)
const index = selectedRowKeys.value.indexOf(row.id)
if (index > -1) {
selectedRowKeys.value.splice(index, 1)
}
}
// IDID directSelectedPersonIds.value = Array.from(allSelectedIds)
directSelectedPersonIds.value = [...preservedIds, ...selectedVisibleIds]
// //
updateSelectedPersons() updateSelectedPersons()
nextTick(() => {
isSyncing.value = false
})
} }
// // /使
const handlePersonSelectAll = ({ checked }) => {
if (isSyncing.value) {
return
}
isSyncing.value = true
// UI
if (checked) {
// UIkeys
const filteredLength = filteredPersonList.value.length
if (filteredLength > 0) {
// 使
const newSelectedKeys = new Array(filteredLength)
for (let i = 0; i < filteredLength; i++) {
newSelectedKeys[i] = filteredPersonList.value[i].id
}
selectedRowKeys.value = newSelectedKeys
}
} else {
// keys
selectedRowKeys.value = []
}
// 使 requestAnimationFrame UI
requestAnimationFrame(() => {
const allSelectedIds = new Set(directSelectedPersonIds.value.map((id) => Number(id)))
const filteredList = filteredPersonList.value
const filteredLength = filteredList.length
if (checked) {
//
// filteredList
for (let i = 0; i < filteredLength; i++) {
allSelectedIds.add(Number(filteredList[i].id))
}
} else {
//
for (let i = 0; i < filteredLength; i++) {
allSelectedIds.delete(Number(filteredList[i].id))
}
}
// ID使
const selectedIdsArray = new Array(allSelectedIds.size)
let index = 0
for (const id of allSelectedIds) {
selectedIdsArray[index++] = id
}
directSelectedPersonIds.value = selectedIdsArray
// UI
requestAnimationFrame(() => {
updateSelectedPersons()
isSyncing.value = false
})
})
}
// 使Map
const updateSelectedPersons = () => { const updateSelectedPersons = () => {
// watch // watch
isInitialized.value = true isInitialized.value = true
// // IDMap
const persons = directSelectedPersonIds.value const personMap = new Map()
.map((personId) => { personList.value.forEach((person) => {
const person = personList.value.find((p) => p.id === personId) personMap.set(Number(person.id), person)
if (person) { })
return {
id: person.id, // 使Map
workerName: person.workerName, const selectedIds = directSelectedPersonIds.value
orgName: person.orgName, const persons = new Array(selectedIds.length)
sex: person.sex, let validCount = 0
phone: person.phone,
} for (let i = 0; i < selectedIds.length; i++) {
const personId = Number(selectedIds[i])
const person = personMap.get(personId)
if (person) {
persons[validCount++] = {
id: person.id,
workerName: person.workerName,
orgName: person.orgName,
sex: person.sex,
phone: person.phone,
} }
return null }
}) }
.filter(Boolean)
emit('update:modelValue', persons) //
const validPersons = validCount === persons.length ? persons : persons.slice(0, validCount)
emit('update:modelValue', validPersons)
// //
const groups = selectedGroupIds.value const groups = selectedGroupIds.value
@ -376,33 +536,38 @@ const onGroupSelectionChange = (selection) => {
updateSelectedPersons() updateSelectedPersons()
} }
// // 使Set
const syncPersonTableSelection = () => { const syncPersonTableSelection = () => {
if (!personTableRef.value || directSelectedPersonIds.value.length === 0) { //
if (personTableRef.value) { if (isSyncing.value) {
isSyncing.value = true
personTableRef.value.clearSelection()
nextTick(() => {
isSyncing.value = false
})
}
return return
} }
// watch
isSyncing.value = true isSyncing.value = true
const selectedIds = new Set(directSelectedPersonIds.value.map((id) => Number(id)))
nextTick(() => { if (directSelectedPersonIds.value.length === 0) {
personTableRef.value.clearSelection() //
filteredPersonList.value.forEach((row) => { selectedRowKeys.value = []
const rowId = Number(row.id)
if (selectedIds.has(rowId)) {
personTableRef.value.toggleRowSelection(row, true)
}
})
nextTick(() => { nextTick(() => {
isSyncing.value = false isSyncing.value = false
}) })
return
}
// 使Set
const selectedIds = new Set(directSelectedPersonIds.value.map((id) => Number(id)))
//
const visibleSelectedKeys = filteredPersonList.value
.filter((row) => selectedIds.has(Number(row.id)))
.map((row) => row.id)
selectedRowKeys.value = visibleSelectedKeys
//
nextTick(() => {
isSyncing.value = false
}) })
} }
@ -439,6 +604,11 @@ const syncGroupTableSelection = () => {
// tab // tab
const handleRemovePerson = (personId) => { const handleRemovePerson = (personId) => {
directSelectedPersonIds.value = directSelectedPersonIds.value.filter((id) => id !== personId) directSelectedPersonIds.value = directSelectedPersonIds.value.filter((id) => id !== personId)
// keys
const index = selectedRowKeys.value.indexOf(personId)
if (index > -1) {
selectedRowKeys.value.splice(index, 1)
}
updateSelectedPersons() updateSelectedPersons()
// //
nextTick(() => { nextTick(() => {
@ -451,9 +621,7 @@ const handleClearAll = () => {
isSyncing.value = true isSyncing.value = true
if (activeTab.value === 'person') { if (activeTab.value === 'person') {
directSelectedPersonIds.value = [] directSelectedPersonIds.value = []
if (personTableRef.value) { selectedRowKeys.value = []
personTableRef.value.clearSelection()
}
} else { } else {
selectedGroupIds.value = [] selectedGroupIds.value = []
if (groupTableRef.value) { if (groupTableRef.value) {
@ -472,6 +640,11 @@ const getPersonList = async () => {
const result = await getAllPersonAPI() const result = await getAllPersonAPI()
if (result.code === 200 && result.rows) { if (result.code === 200 && result.rows) {
personList.value = result.rows || [] personList.value = result.rows || []
//
performPersonFilter()
//
createDebouncedPersonFilter()
//
nextTick(() => { nextTick(() => {
syncPersonTableSelection() syncPersonTableSelection()
}) })
@ -600,10 +773,20 @@ onMounted(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.person-picker { .person-picker {
.tab-content {
min-height: 450px;
}
.search-bar { .search-bar {
margin-bottom: 10px; margin-bottom: 10px;
} }
.table-container {
height: 400px;
border: 1px solid #dcdfe6;
border-radius: 4px;
}
.selected-persons { .selected-persons {
border: 1px solid #dcdfe6; border: 1px solid #dcdfe6;
border-radius: 4px; border-radius: 4px;