This commit is contained in:
parent
2c1d9d1eb8
commit
9c6b1670f1
|
|
@ -22,42 +22,41 @@
|
|||
<!-- 左侧:可选人员列表 -->
|
||||
<div class="member-list-left">
|
||||
<div class="search-bar">
|
||||
<el-row :gutter="10">
|
||||
<el-row style="width: 100%">
|
||||
<el-col :span="12">
|
||||
<el-input
|
||||
v-model="searchDept"
|
||||
placeholder="输入部门名称"
|
||||
clearable
|
||||
prefix-icon="Search"
|
||||
style="width: 99%"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :span="12" style="text-align: right">
|
||||
<el-input
|
||||
v-model="searchName"
|
||||
placeholder="输入人员姓名"
|
||||
clearable
|
||||
prefix-icon="Search"
|
||||
style="width: 99%"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<el-table
|
||||
ref="personTableRef"
|
||||
:data="filteredPersonList"
|
||||
height="300"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="orgName" label="人员所属" width="150" />
|
||||
<el-table-column prop="workerName" label="姓名" width="120" />
|
||||
<el-table-column prop="sex" label="性别" width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.sex == 1 ? '男' : '女' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="phone" label="电话" />
|
||||
</el-table>
|
||||
<el-auto-resizer>
|
||||
<template #default="{ height, width }">
|
||||
<el-table-v2
|
||||
ref="personTableRef"
|
||||
:columns="tableColumns"
|
||||
:data="filteredPersonList"
|
||||
:width="width"
|
||||
:height="height"
|
||||
row-key="id"
|
||||
fixed
|
||||
/>
|
||||
</template>
|
||||
</el-auto-resizer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -107,9 +106,11 @@
|
|||
</template>
|
||||
|
||||
<script setup name="AddAndEditForm">
|
||||
import { ref, computed, watch, onMounted, nextTick } from 'vue'
|
||||
import { ref, computed, watch, onMounted, nextTick, h } from 'vue'
|
||||
import { ElCheckbox } from 'element-plus'
|
||||
import { getAllPersonAPI } from '@/api/personManage/person.js'
|
||||
import { Close } from '@element-plus/icons-vue'
|
||||
import { debounce } from '@/utils'
|
||||
|
||||
const props = defineProps({
|
||||
formData: {
|
||||
|
|
@ -126,6 +127,81 @@ const personList = ref([])
|
|||
const selectedPersonList = ref([])
|
||||
// 标志位:是否正在同步选中状态,防止循环触发
|
||||
const isSyncing = ref(false)
|
||||
// 已选人员Map缓存(优化性能)
|
||||
const selectedMembersMap = ref(new Map())
|
||||
// 防抖后的过滤函数
|
||||
const debouncedFilter = ref(null)
|
||||
// 已选中行的keys(用于虚拟化表格)
|
||||
const selectedRowKeys = ref([])
|
||||
// 已选中行的Set(优化查找性能)
|
||||
const selectedIdsSet = computed(() => {
|
||||
if (!props.formData.workerList || props.formData.workerList.length === 0) {
|
||||
return new Set()
|
||||
}
|
||||
return new Set(props.formData.workerList.map((id) => Number(id)))
|
||||
})
|
||||
|
||||
// 虚拟化表格列配置
|
||||
const tableColumns = computed(() => {
|
||||
return [
|
||||
{
|
||||
key: 'selection',
|
||||
width: 55,
|
||||
cellRenderer: ({ rowData }) => {
|
||||
const isSelected = selectedRowKeys.value.includes(rowData.id)
|
||||
return h(ElCheckbox, {
|
||||
modelValue: isSelected,
|
||||
'onUpdate:modelValue': (val) => {
|
||||
handleRowSelect({ 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) => {
|
||||
handleSelectAll({ 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 rules = {
|
||||
|
|
@ -146,8 +222,16 @@ const rules = {
|
|||
],
|
||||
}
|
||||
|
||||
// 过滤后的人员列表
|
||||
const filteredPersonList = computed(() => {
|
||||
// 过滤后的人员列表(使用ref避免频繁计算)
|
||||
const filteredPersonList = ref([])
|
||||
|
||||
// 执行过滤操作(优化后的版本)
|
||||
const performFilter = () => {
|
||||
if (personList.value.length === 0) {
|
||||
filteredPersonList.value = []
|
||||
return
|
||||
}
|
||||
|
||||
let filtered = personList.value
|
||||
|
||||
// 部门过滤
|
||||
|
|
@ -168,15 +252,52 @@ const filteredPersonList = computed(() => {
|
|||
})
|
||||
}
|
||||
|
||||
return filtered
|
||||
})
|
||||
filteredPersonList.value = filtered
|
||||
|
||||
// 已选人员列表
|
||||
// 过滤后同步选中状态
|
||||
nextTick(() => {
|
||||
syncTableSelection()
|
||||
})
|
||||
}
|
||||
|
||||
// 创建防抖过滤函数
|
||||
const createDebouncedFilter = () => {
|
||||
debouncedFilter.value = debounce(performFilter, 300)
|
||||
}
|
||||
|
||||
// 已选人员列表(优化:使用Map缓存,避免频繁遍历)
|
||||
const selectedMembers = computed(() => {
|
||||
if (!props.formData.workerList || props.formData.workerList.length === 0) {
|
||||
selectedMembersMap.value.clear()
|
||||
return []
|
||||
}
|
||||
return personList.value.filter((item) => props.formData.workerList.includes(item.id))
|
||||
|
||||
// 如果Map已存在且大小匹配,直接使用缓存
|
||||
if (selectedMembersMap.value.size === props.formData.workerList.length) {
|
||||
const cachedIds = Array.from(selectedMembersMap.value.keys())
|
||||
const currentIds = props.formData.workerList.map((id) => Number(id))
|
||||
const idsMatch =
|
||||
cachedIds.every((id) => currentIds.includes(id)) &&
|
||||
currentIds.every((id) => cachedIds.includes(id))
|
||||
if (idsMatch) {
|
||||
return Array.from(selectedMembersMap.value.values())
|
||||
}
|
||||
}
|
||||
|
||||
// 重新构建Map和数组
|
||||
selectedMembersMap.value.clear()
|
||||
const result = []
|
||||
const selectedIdsSet = new Set(props.formData.workerList.map((id) => Number(id)))
|
||||
|
||||
// 只遍历一次,同时构建Map和数组
|
||||
for (const item of personList.value) {
|
||||
if (selectedIdsSet.has(Number(item.id))) {
|
||||
selectedMembersMap.value.set(Number(item.id), item)
|
||||
result.push(item)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
// 获取人员列表
|
||||
|
|
@ -194,6 +315,10 @@ const getPersonList = async () => {
|
|||
sex: item.sex,
|
||||
phone: item.phone,
|
||||
}))
|
||||
|
||||
// 初始化过滤列表
|
||||
performFilter()
|
||||
|
||||
// 人员列表加载完成后,watch 监听器会自动处理选中状态同步
|
||||
// 不需要在这里手动调用 syncTableSelection,避免循环触发
|
||||
}
|
||||
|
|
@ -212,10 +337,10 @@ watch(
|
|||
if (isSyncing.value) {
|
||||
return
|
||||
}
|
||||
// 只有当人员列表已加载且workerList有值时才同步
|
||||
if (personList.value.length > 0 && newVal && newVal.length > 0) {
|
||||
// 只有当人员列表已加载时才同步(包括空数组的情况)
|
||||
if (personList.value.length > 0) {
|
||||
// 检查值是否真的变化了(避免不必要的同步)
|
||||
const newValStr = JSON.stringify(newVal.sort())
|
||||
const newValStr = JSON.stringify((newVal || []).sort())
|
||||
const oldValStr = oldVal ? JSON.stringify(oldVal.sort()) : ''
|
||||
if (newValStr !== oldValStr) {
|
||||
nextTick(() => {
|
||||
|
|
@ -249,139 +374,164 @@ watch(
|
|||
},
|
||||
)
|
||||
|
||||
// 同步表格选中状态
|
||||
// 同步表格选中状态(优化:使用Set提高查找性能,批量操作)
|
||||
const syncTableSelection = () => {
|
||||
if (
|
||||
!personTableRef.value ||
|
||||
!props.formData.workerList ||
|
||||
props.formData.workerList.length === 0
|
||||
) {
|
||||
// 如果没有选中项,清空所有选中状态
|
||||
if (personTableRef.value) {
|
||||
isSyncing.value = true
|
||||
personTableRef.value.clearSelection()
|
||||
// 延迟重置标志位,确保 clearSelection 触发的事件被忽略
|
||||
nextTick(() => {
|
||||
isSyncing.value = false
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 设置同步标志,防止触发 watch 和 handleSelectionChange
|
||||
isSyncing.value = true
|
||||
|
||||
// 确保 workerList 是数字数组
|
||||
const selectedIds = props.formData.workerList.map((id) => Number(id))
|
||||
|
||||
nextTick(() => {
|
||||
// 先清空所有选中状态(这会触发 selection-change,但由于 isSyncing=true,会被忽略)
|
||||
personTableRef.value.clearSelection()
|
||||
|
||||
// 遍历当前过滤后的人员列表,只选中在当前过滤结果中的已选人员
|
||||
// 这样可以确保即使搜索条件变化,已选中的行也能正确显示
|
||||
filteredPersonList.value.forEach((row) => {
|
||||
const rowId = Number(row.id)
|
||||
if (selectedIds.includes(rowId)) {
|
||||
personTableRef.value.toggleRowSelection(row, true)
|
||||
}
|
||||
})
|
||||
|
||||
// 同步完成后,延迟重置标志位,确保所有事件处理完成
|
||||
nextTick(() => {
|
||||
nextTick(() => {
|
||||
isSyncing.value = false
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 表格选择变化
|
||||
const handleSelectionChange = (selection) => {
|
||||
// 如果正在同步中,跳过,防止循环触发和误删已选项
|
||||
// 如果正在同步中,跳过,避免重复同步
|
||||
if (isSyncing.value) {
|
||||
return
|
||||
}
|
||||
|
||||
selectedPersonList.value = selection
|
||||
// 更新表单数据中的workerList
|
||||
// 保留所有已选中的ID(包括不在当前过滤列表中的)
|
||||
const allSelectedIds = new Set(props.formData.workerList || [])
|
||||
|
||||
// 获取当前过滤列表中所有人员的ID
|
||||
const currentFilteredIds = new Set(filteredPersonList.value.map((item) => item.id))
|
||||
|
||||
// 移除当前过滤列表中未选中的项(只处理当前可见的过滤结果)
|
||||
// 注意:不在当前过滤结果中的已选项不会被删除,因为它们不在 currentFilteredIds 中
|
||||
currentFilteredIds.forEach((id) => {
|
||||
if (!selection.find((s) => s.id === id)) {
|
||||
allSelectedIds.delete(id)
|
||||
}
|
||||
})
|
||||
|
||||
// 添加当前过滤列表中选中的项
|
||||
selection.forEach((item) => {
|
||||
allSelectedIds.add(item.id)
|
||||
})
|
||||
|
||||
// 设置同步标志,防止触发 watch
|
||||
isSyncing.value = true
|
||||
props.formData.workerList = Array.from(allSelectedIds)
|
||||
|
||||
if (!props.formData.workerList || props.formData.workerList.length === 0) {
|
||||
// 如果没有选中项,清空所有选中状态
|
||||
selectedRowKeys.value = []
|
||||
nextTick(() => {
|
||||
isSyncing.value = false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 使用computed的Set提高查找性能
|
||||
const idsSet = selectedIdsSet.value
|
||||
|
||||
// 只同步当前过滤列表中可见的选中项
|
||||
const visibleSelectedKeys = filteredPersonList.value
|
||||
.filter((row) => idsSet.has(Number(row.id)))
|
||||
.map((row) => row.id)
|
||||
|
||||
selectedRowKeys.value = visibleSelectedKeys
|
||||
|
||||
// 同步完成后,延迟重置标志位
|
||||
nextTick(() => {
|
||||
isSyncing.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 移除单个已选人员
|
||||
// 虚拟化表格:单行选择变化
|
||||
const handleRowSelect = ({ row, checked }) => {
|
||||
if (isSyncing.value) {
|
||||
return
|
||||
}
|
||||
|
||||
isSyncing.value = true
|
||||
|
||||
const rowId = Number(row.id)
|
||||
const allSelectedIds = new Set(props.formData.workerList || [])
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
props.formData.workerList = Array.from(allSelectedIds)
|
||||
|
||||
nextTick(() => {
|
||||
isSyncing.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 虚拟化表格:全选/取消全选(优化:批量处理,避免逐个操作)
|
||||
const handleSelectAll = ({ checked }) => {
|
||||
if (isSyncing.value) {
|
||||
return
|
||||
}
|
||||
|
||||
isSyncing.value = true
|
||||
|
||||
const allSelectedIds = new Set(props.formData.workerList || [])
|
||||
const currentFilteredIds = filteredPersonList.value.map((item) => Number(item.id))
|
||||
|
||||
if (checked) {
|
||||
// 全选:添加当前过滤列表中的所有项
|
||||
currentFilteredIds.forEach((id) => {
|
||||
allSelectedIds.add(id)
|
||||
})
|
||||
selectedRowKeys.value = filteredPersonList.value.map((row) => row.id)
|
||||
} else {
|
||||
// 取消全选:只移除当前过滤列表中的项
|
||||
currentFilteredIds.forEach((id) => {
|
||||
allSelectedIds.delete(id)
|
||||
})
|
||||
// 清空当前可见的选中keys
|
||||
selectedRowKeys.value = []
|
||||
}
|
||||
|
||||
props.formData.workerList = Array.from(allSelectedIds)
|
||||
|
||||
nextTick(() => {
|
||||
isSyncing.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 移除单个已选人员(优化:直接过滤,避免查找,并同步更新选中状态)
|
||||
const handleRemoveMember = (memberId) => {
|
||||
if (!props.formData.workerList) {
|
||||
props.formData.workerList = []
|
||||
selectedRowKeys.value = []
|
||||
return
|
||||
}
|
||||
// 设置同步标志,防止触发 watch
|
||||
|
||||
const memberIdNum = Number(memberId)
|
||||
|
||||
// 设置同步标志,防止 watch 触发时重复处理
|
||||
isSyncing.value = true
|
||||
props.formData.workerList = props.formData.workerList.filter((id) => id !== memberId)
|
||||
|
||||
// 更新 workerList
|
||||
props.formData.workerList = props.formData.workerList.filter((id) => Number(id) !== memberIdNum)
|
||||
|
||||
// 立即更新 selectedRowKeys,确保左侧表格选中状态同步
|
||||
// 从 selectedRowKeys 中移除该人员(无论是否在当前过滤列表中)
|
||||
const index = selectedRowKeys.value.findIndex((id) => Number(id) === memberIdNum)
|
||||
if (index > -1) {
|
||||
selectedRowKeys.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// 重置同步标志,并确保选中状态完全同步
|
||||
// 使用 nextTick 确保 DOM 更新完成后再同步
|
||||
nextTick(() => {
|
||||
// 同步更新表格选中状态
|
||||
isSyncing.value = false
|
||||
// 再次同步,确保状态一致(处理该人员不在当前过滤列表中的情况)
|
||||
syncTableSelection()
|
||||
})
|
||||
}
|
||||
|
||||
// 清空所有已选人员
|
||||
// 清空所有已选人员(优化:直接清空,避免遍历)
|
||||
const handleClearAll = () => {
|
||||
// 设置同步标志,防止触发 watch
|
||||
isSyncing.value = true
|
||||
props.formData.workerList = []
|
||||
if (personTableRef.value) {
|
||||
personTableRef.value.clearSelection()
|
||||
}
|
||||
selectedRowKeys.value = []
|
||||
nextTick(() => {
|
||||
isSyncing.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 监听搜索关键词变化,重新同步选中状态
|
||||
// 监听搜索关键词变化,使用防抖优化性能
|
||||
watch([() => searchDept.value, () => searchName.value], () => {
|
||||
// 搜索关键词变化时,需要重新同步选中状态
|
||||
// 因为表格数据源变化可能导致选中状态丢失
|
||||
if (
|
||||
personList.value.length > 0 &&
|
||||
props.formData.workerList &&
|
||||
props.formData.workerList.length > 0
|
||||
) {
|
||||
// 设置同步标志,防止 handleSelectionChange 误删已选项
|
||||
isSyncing.value = true
|
||||
|
||||
// 延迟一下,确保表格数据更新完成
|
||||
nextTick(() => {
|
||||
nextTick(() => {
|
||||
syncTableSelection()
|
||||
})
|
||||
})
|
||||
if (personList.value.length > 0) {
|
||||
// 使用防抖函数,避免频繁过滤
|
||||
if (debouncedFilter.value) {
|
||||
debouncedFilter.value()
|
||||
} else {
|
||||
performFilter()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 创建防抖过滤函数
|
||||
createDebouncedFilter()
|
||||
// 获取人员列表
|
||||
getPersonList()
|
||||
})
|
||||
|
||||
|
|
@ -436,6 +586,8 @@ defineExpose({
|
|||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
height: 300px;
|
||||
min-height: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -140,6 +140,10 @@ const actionColumns = [
|
|||
link: true,
|
||||
|
||||
handler: (row) => {
|
||||
if (row.taskStatus === '0') {
|
||||
proxy.$modal.msgError('该任务已启用,无法删除')
|
||||
return
|
||||
}
|
||||
proxy.$modal
|
||||
.confirm('是否确认删除该循环发送任务?')
|
||||
.then(async () => {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
style="width: 100%"
|
||||
max-height="500px"
|
||||
>
|
||||
<el-table-column type="index" label="序号" align="center" width="80" />
|
||||
<el-table-column prop="workerName" label="姓名" align="center" width="120" />
|
||||
<el-table-column
|
||||
prop="orgName"
|
||||
|
|
@ -156,6 +157,11 @@ const tableColumns = [
|
|||
return typeMap[row.msgType] || '-'
|
||||
},
|
||||
},
|
||||
{
|
||||
prop: 'messageContent',
|
||||
label: '短信内容',
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
prop: 'remark',
|
||||
label: '备注',
|
||||
|
|
|
|||
Loading…
Reference in New Issue