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-tabs>
<!-- 搜索栏 -->
<div class="search-bar">
<el-input
v-if="activeTab === 'person'"
v-model.trim="searchKeyword"
placeholder="输入关键字"
clearable
prefix-icon="Search"
@input="handleSearch"
/>
<el-input
v-else
v-model.trim="groupSearchKeyword"
placeholder="输入关键字"
clearable
prefix-icon="Search"
@input="handleGroupSearch"
/>
<!-- 人员tab内容 -->
<div v-show="activeTab === 'person'" class="tab-content">
<!-- 搜索栏 -->
<div class="search-bar">
<el-input
v-model.trim="searchKeyword"
placeholder="输入关键字"
clearable
prefix-icon="Search"
@input="handleSearch"
/>
</div>
<!-- 人员表格 - 使用虚拟表格 -->
<div class="table-container">
<el-auto-resizer>
<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>
<!-- 人员表格 -->
<el-table
v-if="activeTab === 'person'"
border
ref="personTableRef"
:data="filteredPersonList"
height="400"
@selection-change="onPersonSelectionChange"
>
<el-table-column type="selection" width="50" align="center" />
<el-table-column
show-overflow-tooltip
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>
<!-- 分组tab内容 -->
<div v-show="activeTab === 'group'" class="tab-content">
<!-- 搜索栏 -->
<div class="search-bar">
<el-input
v-model.trim="groupSearchKeyword"
placeholder="输入关键字"
clearable
prefix-icon="Search"
@input="handleGroupSearch"
/>
</div>
<!-- 分组表格 -->
<el-table
v-else
border
ref="groupTableRef"
:data="filteredGroupList"
height="400"
@selection-change="onGroupSelectionChange"
>
<el-table-column type="selection" width="50" align="center" />
<el-table-column
show-overflow-tooltip
prop="groupName"
label="分组名称"
align="center"
/>
</el-table>
<!-- 分组表格 -->
<el-table
border
ref="groupTableRef"
:data="filteredGroupList"
height="400"
@selection-change="onGroupSelectionChange"
>
<el-table-column type="selection" width="50" align="center" />
<el-table-column
show-overflow-tooltip
prop="groupName"
label="分组名称"
align="center"
/>
</el-table>
</div>
</el-col>
<!-- 右侧已选内容 -->
@ -157,10 +144,12 @@
</template>
<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 { ElCheckbox } from 'element-plus'
import { getAllPersonAPI } from '@/api/personManage/person.js'
import { getAllGroupAPI } from '@/api/basicManage/groupManage.js'
import { debounce } from '@/utils'
const props = defineProps({
modelValue: {
@ -178,7 +167,6 @@ const emit = defineEmits(['update:modelValue', 'update:groupList', 'confirm'])
const activeTab = ref('person')
const searchKeyword = ref('')
const groupSearchKeyword = ref('')
const personTableRef = ref(null)
const groupTableRef = ref(null)
//
@ -197,19 +185,106 @@ const isSyncing = ref(false)
//
const isInitialized = ref(false)
//
const filteredPersonList = computed(() => {
if (!searchKeyword.value) {
return personList.value
}
const keyword = searchKeyword.value.toLowerCase()
return personList.value.filter((item) => {
const orgName = (item.orgName || '').toLowerCase()
const workerName = (item.workerName || '').toLowerCase()
return orgName.includes(keyword) || workerName.includes(keyword)
})
// keys
const selectedRowKeys = ref([])
// 使ref
const filteredPersonList = ref([])
//
const debouncedPersonFilter = ref(null)
//
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(() => {
if (!groupSearchKeyword.value) {
@ -286,11 +361,13 @@ const handleTabChange = (tabName) => {
})
}
//
// 使
const handleSearch = () => {
nextTick(() => {
syncPersonTableSelection()
})
if (debouncedPersonFilter.value) {
debouncedPersonFilter.value()
} else {
performPersonFilter()
}
}
const handleGroupSearch = () => {
@ -299,50 +376,133 @@ const handleGroupSearch = () => {
})
}
//
const onPersonSelectionChange = (selection) => {
//
const handlePersonRowSelect = ({ row, checked }) => {
if (isSyncing.value) {
return
}
// ID
const visiblePersonIds = new Set(filteredPersonList.value.map((item) => item.id))
isSyncing.value = true
// ID
const selectedVisibleIds = new Set(selection.map((item) => item.id))
const rowId = Number(row.id)
const allSelectedIds = new Set(directSelectedPersonIds.value.map((id) => Number(id)))
//
const preservedIds = directSelectedPersonIds.value.filter((id) => !visiblePersonIds.has(id))
if (checked) {
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 = [...preservedIds, ...selectedVisibleIds]
directSelectedPersonIds.value = Array.from(allSelectedIds)
//
//
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 = () => {
// watch
isInitialized.value = true
//
const persons = directSelectedPersonIds.value
.map((personId) => {
const person = personList.value.find((p) => p.id === personId)
if (person) {
return {
id: person.id,
workerName: person.workerName,
orgName: person.orgName,
sex: person.sex,
phone: person.phone,
}
// IDMap
const personMap = new Map()
personList.value.forEach((person) => {
personMap.set(Number(person.id), person)
})
// 使Map
const selectedIds = directSelectedPersonIds.value
const persons = new Array(selectedIds.length)
let validCount = 0
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
@ -376,33 +536,38 @@ const onGroupSelectionChange = (selection) => {
updateSelectedPersons()
}
//
// 使Set
const syncPersonTableSelection = () => {
if (!personTableRef.value || directSelectedPersonIds.value.length === 0) {
if (personTableRef.value) {
isSyncing.value = true
personTableRef.value.clearSelection()
nextTick(() => {
isSyncing.value = false
})
}
//
if (isSyncing.value) {
return
}
// watch
isSyncing.value = true
const selectedIds = new Set(directSelectedPersonIds.value.map((id) => Number(id)))
nextTick(() => {
personTableRef.value.clearSelection()
filteredPersonList.value.forEach((row) => {
const rowId = Number(row.id)
if (selectedIds.has(rowId)) {
personTableRef.value.toggleRowSelection(row, true)
}
})
if (directSelectedPersonIds.value.length === 0) {
//
selectedRowKeys.value = []
nextTick(() => {
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
const handleRemovePerson = (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()
//
nextTick(() => {
@ -451,9 +621,7 @@ const handleClearAll = () => {
isSyncing.value = true
if (activeTab.value === 'person') {
directSelectedPersonIds.value = []
if (personTableRef.value) {
personTableRef.value.clearSelection()
}
selectedRowKeys.value = []
} else {
selectedGroupIds.value = []
if (groupTableRef.value) {
@ -472,6 +640,11 @@ const getPersonList = async () => {
const result = await getAllPersonAPI()
if (result.code === 200 && result.rows) {
personList.value = result.rows || []
//
performPersonFilter()
//
createDebouncedPersonFilter()
//
nextTick(() => {
syncPersonTableSelection()
})
@ -600,10 +773,20 @@ onMounted(() => {
<style lang="scss" scoped>
.person-picker {
.tab-content {
min-height: 450px;
}
.search-bar {
margin-bottom: 10px;
}
.table-container {
height: 400px;
border: 1px solid #dcdfe6;
border-radius: 4px;
}
.selected-persons {
border: 1px solid #dcdfe6;
border-radius: 4px;