|
|
|
@ -1,8 +1,8 @@ |
|
|
|
<script setup lang="ts"> |
|
|
|
import { ref, computed, onMounted, watch, reactive, nextTick } from 'vue'; |
|
|
|
import { Search } from '@element-plus/icons-vue'; |
|
|
|
import { Search, Edit } from '@element-plus/icons-vue'; |
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'; |
|
|
|
import { getBusinessTreeList, getUnitUsers, addUnitUser, updateUnitUser, deleteUnitUser } from '@/api/efcode/virtualUnit'; |
|
|
|
import { getBusinessTreeList, getUnitUsers, addUnitUser, updateUnitUser, deleteUnitUser, getReferencedUsers, addReferencedUsers, deleteReferencedUsers, updateReferencedUserRemark } from '@/api/efcode/virtualUnit'; |
|
|
|
import { getAuthRole, updateAuthRole, resetUserPwd } from '@/api/system/user'; |
|
|
|
import type { VirtualUnitVo, UnitUserVo } from '@/api/efcode/virtualUnit/types'; |
|
|
|
import type { RoleVO } from '@/api/system/role/types'; |
|
|
|
@ -224,6 +224,191 @@ onMounted(() => { |
|
|
|
loadTree(); |
|
|
|
}); |
|
|
|
|
|
|
|
/** ============ 右侧Tab切换 ============ */ |
|
|
|
const activeTab = ref('staff'); |
|
|
|
|
|
|
|
/** ============ 引用人员列表(Tab2) ============ */ |
|
|
|
const refUserList = ref<UnitUserVo[]>([]); |
|
|
|
const refUserLoading = ref(false); |
|
|
|
const refUserSearchKeyword = ref(''); |
|
|
|
const refUserTotal = ref(0); |
|
|
|
const refUserPageNum = ref(1); |
|
|
|
const refUserPageSize = ref(16); |
|
|
|
|
|
|
|
/** 加载引用人员列表 */ |
|
|
|
const loadRefUsers = async () => { |
|
|
|
if (!selectedNode.value?.virId) { |
|
|
|
refUserList.value = []; |
|
|
|
refUserTotal.value = 0; |
|
|
|
return; |
|
|
|
} |
|
|
|
refUserLoading.value = true; |
|
|
|
try { |
|
|
|
const res = await getReferencedUsers({ |
|
|
|
virUnitId: selectedNode.value.virId, |
|
|
|
keyword: refUserSearchKeyword.value.trim() || undefined, |
|
|
|
pageNum: refUserPageNum.value, |
|
|
|
pageSize: refUserPageSize.value |
|
|
|
}); |
|
|
|
refUserList.value = res.rows; |
|
|
|
refUserTotal.value = res.total; |
|
|
|
} finally { |
|
|
|
refUserLoading.value = false; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** 引用人员搜索 */ |
|
|
|
const handleRefSearch = () => { |
|
|
|
refUserPageNum.value = 1; |
|
|
|
loadRefUsers(); |
|
|
|
}; |
|
|
|
|
|
|
|
const handleRefPageChange = (page: number) => { |
|
|
|
refUserPageNum.value = page; |
|
|
|
loadRefUsers(); |
|
|
|
}; |
|
|
|
|
|
|
|
const handleRefSizeChange = (size: number) => { |
|
|
|
refUserPageSize.value = size; |
|
|
|
refUserPageNum.value = 1; |
|
|
|
loadRefUsers(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** 移除引用人员 */ |
|
|
|
const handleRemoveRef = async (row: UnitUserVo) => { |
|
|
|
try { |
|
|
|
await ElMessageBox.confirm(`确认移除引用人员“${row.fullname}”吗?`, '提示', { |
|
|
|
confirmButtonText: '确定', |
|
|
|
cancelButtonText: '取消', |
|
|
|
type: 'warning' |
|
|
|
}); |
|
|
|
await deleteReferencedUsers({ virUnitId: selectedNode.value!.virId, sysUserIds: [row.userId] }); |
|
|
|
ElMessage.success('移除成功'); |
|
|
|
loadRefUsers(); |
|
|
|
} catch { |
|
|
|
// cancelled |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** Tab切换或选中节点变化时加载引用人员 */ |
|
|
|
watch(activeTab, () => { |
|
|
|
if (activeTab.value === 'ref') { |
|
|
|
refUserPageNum.value = 1; |
|
|
|
loadRefUsers(); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
watch(selectedNode, () => { |
|
|
|
if (activeTab.value === 'ref') { |
|
|
|
refUserPageNum.value = 1; |
|
|
|
loadRefUsers(); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
/** ============ 引用人员备注行内编辑 ============ */ |
|
|
|
/** 当前正在编辑备注的行userId */ |
|
|
|
const editingRemarkId = ref<number | null>(null); |
|
|
|
/** 编辑中的备注内容 */ |
|
|
|
const editingRemarkValue = ref(''); |
|
|
|
|
|
|
|
/** 进入备注编辑态 */ |
|
|
|
const startEditRemark = (row: UnitUserVo) => { |
|
|
|
editingRemarkId.value = row.userId; |
|
|
|
editingRemarkValue.value = row.remark || ''; |
|
|
|
}; |
|
|
|
|
|
|
|
/** 取消备注编辑 */ |
|
|
|
const cancelEditRemark = () => { |
|
|
|
editingRemarkId.value = null; |
|
|
|
editingRemarkValue.value = ''; |
|
|
|
}; |
|
|
|
|
|
|
|
/** 保存备注 */ |
|
|
|
const saveRemark = async (row: UnitUserVo) => { |
|
|
|
const value = editingRemarkValue.value.trim(); |
|
|
|
try { |
|
|
|
await updateReferencedUserRemark({ |
|
|
|
virUnitId: selectedNode.value!.virId, |
|
|
|
sysUserId: row.userId, |
|
|
|
remark: value |
|
|
|
}); |
|
|
|
row.remark = value; |
|
|
|
ElMessage.success('备注保存成功'); |
|
|
|
cancelEditRemark(); |
|
|
|
} catch { |
|
|
|
// 服务端报错由全局拦截器处理 |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** ============ 人员选择对话框(引用) ============ */ |
|
|
|
const selectDialogVisible = ref(false); |
|
|
|
const selectLoading = ref(false); |
|
|
|
const selectUserList = ref<UnitUserVo[]>([]); |
|
|
|
const selectTableRef = ref<InstanceType<typeof import('element-plus')['ElTable']>>(); |
|
|
|
const selectKeyword = ref(''); |
|
|
|
const selectTotal = ref(0); |
|
|
|
const selectPageNum = ref(1); |
|
|
|
const selectPageSize = ref(10); |
|
|
|
|
|
|
|
/** 打开人员选择对话框 */ |
|
|
|
const openSelectDialog = () => { |
|
|
|
if (!selectedNode.value?.virId) { |
|
|
|
ElMessage.warning('请先选择左侧的执法单位'); |
|
|
|
return; |
|
|
|
} |
|
|
|
selectKeyword.value = ''; |
|
|
|
selectPageNum.value = 1; |
|
|
|
selectDialogVisible.value = true; |
|
|
|
loadSelectUsers(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** 加载可选人员(全局搜索) */ |
|
|
|
const loadSelectUsers = async () => { |
|
|
|
selectLoading.value = true; |
|
|
|
try { |
|
|
|
const res = await getUnitUsers({ |
|
|
|
globalSearch: true, |
|
|
|
keyword: selectKeyword.value.trim() || undefined, |
|
|
|
pageNum: selectPageNum.value, |
|
|
|
pageSize: selectPageSize.value |
|
|
|
}); |
|
|
|
selectUserList.value = res.rows; |
|
|
|
selectTotal.value = res.total; |
|
|
|
} finally { |
|
|
|
selectLoading.value = false; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
const handleSelectSearch = () => { |
|
|
|
selectPageNum.value = 1; |
|
|
|
loadSelectUsers(); |
|
|
|
}; |
|
|
|
|
|
|
|
const handleSelectPageChange = (page: number) => { |
|
|
|
selectPageNum.value = page; |
|
|
|
loadSelectUsers(); |
|
|
|
}; |
|
|
|
|
|
|
|
const handleSelectSizeChange = (size: number) => { |
|
|
|
selectPageSize.value = size; |
|
|
|
selectPageNum.value = 1; |
|
|
|
loadSelectUsers(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** 确认引用所选人员 */ |
|
|
|
const submitSelect = async () => { |
|
|
|
const rows: UnitUserVo[] = selectTableRef.value?.getSelectionRows() || []; |
|
|
|
const ids = rows.map((r) => r.userId); |
|
|
|
if (!ids.length) { |
|
|
|
ElMessage.warning('请至少选择一名人员'); |
|
|
|
return; |
|
|
|
} |
|
|
|
await addReferencedUsers({ virUnitId: selectedNode.value!.virId, sysUserIds: ids }); |
|
|
|
ElMessage.success('引用成功'); |
|
|
|
selectDialogVisible.value = false; |
|
|
|
loadRefUsers(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** ============ 人员新增/编辑对话框 ============ */ |
|
|
|
const userFormRef = ref(); |
|
|
|
const dialogVisible = ref(false); |
|
|
|
@ -492,14 +677,15 @@ const submitResetPwd = async () => { |
|
|
|
<!-- 右侧:人员列表 --> |
|
|
|
<div class="right-panel"> |
|
|
|
<el-card shadow="hover"> |
|
|
|
<template #header> |
|
|
|
<el-tabs v-model="activeTab" class="user-tabs"> |
|
|
|
<!-- Tab1:执法人员 --> |
|
|
|
<el-tab-pane label="执法人员" name="staff"> |
|
|
|
<div class="right-header"> |
|
|
|
<span class="panel-title"> |
|
|
|
执法人员 |
|
|
|
<el-tag v-if="selectedNode && !globalSearch" type="info" size="small" style="margin-left: 8px;"> |
|
|
|
<el-tag v-if="selectedNode && !globalSearch" type="info" size="small"> |
|
|
|
{{ selectedNode.virName }} |
|
|
|
</el-tag> |
|
|
|
<el-tag v-if="globalSearch" type="warning" size="small" style="margin-left: 8px;"> |
|
|
|
<el-tag v-if="globalSearch" type="warning" size="small"> |
|
|
|
全局搜索 |
|
|
|
</el-tag> |
|
|
|
</span> |
|
|
|
@ -517,13 +703,12 @@ const submitResetPwd = async () => { |
|
|
|
<el-button type="primary" plain style="margin-left: 8px;" @click="handleAddUser">新增</el-button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
<el-table |
|
|
|
:data="userList" |
|
|
|
border |
|
|
|
v-loading="userLoading" |
|
|
|
:row-class-name="userRowClassName" |
|
|
|
max-height="calc(100vh - 240px)" |
|
|
|
max-height="calc(100vh - 280px)" |
|
|
|
> |
|
|
|
<el-table-column type="index" label="#" width="50" align="center" :index="(i: number) => (userPageNum - 1) * userPageSize + i + 1" /> |
|
|
|
<el-table-column prop="fullname" label="姓名" min-width="100" show-overflow-tooltip /> |
|
|
|
@ -549,9 +734,126 @@ const submitResetPwd = async () => { |
|
|
|
@current-change="handlePageChange" |
|
|
|
@size-change="handleSizeChange" |
|
|
|
/> |
|
|
|
</el-tab-pane> |
|
|
|
|
|
|
|
<!-- Tab2:引用人员 --> |
|
|
|
<el-tab-pane label="引用人员" name="ref"> |
|
|
|
<div class="right-header"> |
|
|
|
<span class="panel-title"> |
|
|
|
<el-tag v-if="selectedNode" type="info" size="small"> |
|
|
|
{{ selectedNode.virName }} |
|
|
|
</el-tag> |
|
|
|
</span> |
|
|
|
<div class="search-bar"> |
|
|
|
<el-input |
|
|
|
v-model="refUserSearchKeyword" |
|
|
|
placeholder="姓名/账号" |
|
|
|
:prefix-icon="Search" |
|
|
|
clearable |
|
|
|
style="width: 200px;" |
|
|
|
@keyup.enter="handleRefSearch" |
|
|
|
/> |
|
|
|
<el-button type="primary" style="margin-left: 8px;" @click="handleRefSearch">查询</el-button> |
|
|
|
<el-button type="primary" plain style="margin-left: 8px;" @click="openSelectDialog">引用人员</el-button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<el-table |
|
|
|
:data="refUserList" |
|
|
|
border |
|
|
|
v-loading="refUserLoading" |
|
|
|
max-height="calc(100vh - 280px)" |
|
|
|
> |
|
|
|
<el-table-column type="index" label="#" width="50" align="center" :index="(i: number) => (refUserPageNum - 1) * refUserPageSize + i + 1" /> |
|
|
|
<el-table-column prop="fullname" label="姓名" min-width="100" show-overflow-tooltip /> |
|
|
|
<el-table-column prop="loginName" label="账号" min-width="120" show-overflow-tooltip /> |
|
|
|
<el-table-column prop="zzDisplayPath" label="所属部门" min-width="280" show-overflow-tooltip /> |
|
|
|
<el-table-column label="备注" min-width="220"> |
|
|
|
<template #default="{ row }"> |
|
|
|
<div v-if="editingRemarkId === row.userId" class="remark-edit"> |
|
|
|
<el-input |
|
|
|
v-model="editingRemarkValue" |
|
|
|
size="small" |
|
|
|
placeholder="请输入备注" |
|
|
|
maxlength="200" |
|
|
|
@keyup.enter="saveRemark(row)" |
|
|
|
/> |
|
|
|
<el-button type="primary" link size="small" @click="saveRemark(row)">保存</el-button> |
|
|
|
<el-button type="info" link size="small" @click="cancelEditRemark">取消</el-button> |
|
|
|
</div> |
|
|
|
<div v-else class="remark-view" @click="startEditRemark(row)"> |
|
|
|
<span :class="{ 'remark-placeholder': !row.remark }">{{ row.remark || '点击添加备注' }}</span> |
|
|
|
<el-icon class="remark-edit-icon"><Edit /></el-icon> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
</el-table-column> |
|
|
|
<el-table-column label="操作" width="120" align="center" fixed="right"> |
|
|
|
<template #default="{ row }"> |
|
|
|
<el-button type="danger" link size="small" @click="handleRemoveRef(row)">移除</el-button> |
|
|
|
</template> |
|
|
|
</el-table-column> |
|
|
|
</el-table> |
|
|
|
<el-pagination |
|
|
|
v-if="refUserTotal > 0" |
|
|
|
style="margin-top: 12px; justify-content: flex-end;" |
|
|
|
:current-page="refUserPageNum" |
|
|
|
:page-size="refUserPageSize" |
|
|
|
:page-sizes="[10, 20, 50]" |
|
|
|
:total="refUserTotal" |
|
|
|
layout="total, sizes, prev, pager, next, jumper" |
|
|
|
@current-change="handleRefPageChange" |
|
|
|
@size-change="handleRefSizeChange" |
|
|
|
/> |
|
|
|
</el-tab-pane> |
|
|
|
</el-tabs> |
|
|
|
</el-card> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 人员选择对话框(引用) --> |
|
|
|
<el-dialog v-model="selectDialogVisible" title="选择引用人员" width="860px" append-to-body> |
|
|
|
<div style="margin-bottom: 12px; display: flex; align-items: center; justify-content: space-between;"> |
|
|
|
<span style="color: #909399; font-size: 13px;">支持全局搜索,可跨页勾选多人</span> |
|
|
|
<div> |
|
|
|
<el-input |
|
|
|
v-model="selectKeyword" |
|
|
|
placeholder="姓名/账号" |
|
|
|
:prefix-icon="Search" |
|
|
|
clearable |
|
|
|
style="width: 220px;" |
|
|
|
@keyup.enter="handleSelectSearch" |
|
|
|
/> |
|
|
|
<el-button type="primary" style="margin-left: 8px;" @click="handleSelectSearch">查询</el-button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<el-table |
|
|
|
ref="selectTableRef" |
|
|
|
v-loading="selectLoading" |
|
|
|
:data="selectUserList" |
|
|
|
border |
|
|
|
row-key="userId" |
|
|
|
max-height="420px" |
|
|
|
> |
|
|
|
<el-table-column type="selection" width="55" reserve-selection /> |
|
|
|
<el-table-column prop="fullname" label="姓名" min-width="100" show-overflow-tooltip /> |
|
|
|
<el-table-column prop="loginName" label="账号" min-width="120" show-overflow-tooltip /> |
|
|
|
<el-table-column prop="zzDisplayPath" label="所属部门" min-width="300" show-overflow-tooltip /> |
|
|
|
</el-table> |
|
|
|
<el-pagination |
|
|
|
v-if="selectTotal > 0" |
|
|
|
style="margin-top: 12px; justify-content: flex-end;" |
|
|
|
:current-page="selectPageNum" |
|
|
|
:page-size="selectPageSize" |
|
|
|
:page-sizes="[10, 20, 50]" |
|
|
|
:total="selectTotal" |
|
|
|
layout="total, sizes, prev, pager, next, jumper" |
|
|
|
@current-change="handleSelectPageChange" |
|
|
|
@size-change="handleSelectSizeChange" |
|
|
|
/> |
|
|
|
<template #footer> |
|
|
|
<el-button @click="selectDialogVisible = false">取 消</el-button> |
|
|
|
<el-button type="primary" @click="submitSelect">确 定</el-button> |
|
|
|
</template> |
|
|
|
</el-dialog> |
|
|
|
|
|
|
|
<!-- 分配角色对话框 --> |
|
|
|
<el-dialog v-model="roleDialogVisible" title="分配角色" width="860px" append-to-body @close="roleSearchKeyword = ''"> |
|
|
|
<div style="margin-bottom: 12px; display: flex; align-items: center; justify-content: space-between;"> |
|
|
|
@ -698,4 +1000,32 @@ const submitResetPwd = async () => { |
|
|
|
color: #f56c6c; |
|
|
|
font-weight: bold; |
|
|
|
} |
|
|
|
|
|
|
|
.remark-edit { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
gap: 4px; |
|
|
|
} |
|
|
|
|
|
|
|
.remark-view { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
gap: 6px; |
|
|
|
cursor: pointer; |
|
|
|
min-height: 24px; |
|
|
|
|
|
|
|
.remark-edit-icon { |
|
|
|
opacity: 0; |
|
|
|
color: var(--el-color-primary); |
|
|
|
transition: opacity 0.2s; |
|
|
|
} |
|
|
|
|
|
|
|
&:hover .remark-edit-icon { |
|
|
|
opacity: 1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.remark-placeholder { |
|
|
|
color: #c0c4cc; |
|
|
|
} |
|
|
|
</style> |
|
|
|
|