feat(permission): replace OA roles with post-bound roles and assignee posts

Unify effective roles via post-role bindings, remove dedicated OA role UI, and update OA assignee resolution plus related SQL/docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-08-04 17:41:04 +08:00
1 parent a3c73d76bb
commit 06db3bc966
66 files changed
+55337 -38647

No files matched your search

+124 -5
View File
@@ -103,17 +103,21 @@
</el-table-column>
<el-table-column
label="操作"
min-width="160"
min-width="320"
fixed="right"
align="center"
header-align="center"
>
<template #default="{ row }">
<div style="display: flex; justify-content: center; gap: 8px">
<div style="display: flex; justify-content: center; gap: 8px; flex-wrap: wrap">
<el-button class="btn-op-item primary" @click="handlePostBindUsers(row)">
<el-icon><User /></el-icon>
关联用户
</el-button>
<el-button class="btn-op-item" @click="handlePostBindRoles(row)">
<el-icon><Key /></el-icon>
角色配置
</el-button>
<el-dropdown trigger="hover" @command="(cmd: string) => handleDropdownCommand(cmd, row)">
<el-button class="btn-op-item">
<el-icon><More /></el-icon>
@@ -308,6 +312,58 @@
</template>
</el-dialog>
<!-- 角色配置 -->
<el-dialog
v-model="postRoleDialogVisible"
title="角色配置"
width="560px"
class="dialog-form-layout"
align-center
append-to-body
modal-class="protocol-dialog-mask"
destroy-on-close
:close-on-click-modal="false"
>
<DialogFormBody :loading="postRoleLoading">
<p v-if="postRolePostName" class="post-bind-subtitle">
职位:<strong>{{ postRolePostName }}</strong>
</p>
<el-form label-position="top">
<el-form-item label="关联角色">
<el-select
v-model="postRoleCheckedIds"
multiple
filterable
placeholder="请选择角色"
style="width: 100%"
>
<el-option
v-for="item in postRoleOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
</DialogFormBody>
<template #footer>
<el-button class="btn-ghost" @click="postRoleDialogVisible = false">
<el-icon><Close /></el-icon>
取消
</el-button>
<el-button
class="btn-main"
type="primary"
@click="handlePostRoleSubmit"
:loading="postRoleSubmitLoading"
>
<el-icon><Check /></el-icon>
确定
</el-button>
</template>
</el-dialog>
<SystemViewDialog
v-model:visible="viewVisible"
:title="viewRow?.postName || '职位详情'"
@@ -328,6 +384,7 @@
Close,
Check,
User,
Key,
View,
Edit,
Delete,
@@ -349,7 +406,10 @@
fetchDeptTree,
fetchUserList,
fetchPostBoundUserIds,
fetchUpdatePostBoundUsers
fetchUpdatePostBoundUsers,
fetchPostBoundRoleIds,
fetchUpdatePostBoundRoles,
fetchRoleList
} from '@/api/admin'
defineOptions({ name: 'SystemPost' })
@@ -385,6 +445,7 @@
const viewVisible = ref(false)
const viewRow = ref<Api.Post.PostItem | null>(null)
const viewBoundUserNames = ref<string[]>([])
const viewBoundRoleNames = ref<string[]>([])
const viewSections = computed(() => {
const row = viewRow.value
if (!row) return []
@@ -404,6 +465,12 @@
]
}
]
if (viewBoundRoleNames.value.length > 0) {
sections.push({
title: '关联角色',
fields: [{ label: '已关联角色', value: viewBoundRoleNames.value.join('、'), span: true }]
})
}
if (viewBoundUserNames.value.length > 0) {
sections.push({
title: '关联用户',
@@ -415,16 +482,68 @@
async function handleView(row: Api.Post.PostItem) {
viewRow.value = row
viewBoundUserNames.value = []
viewBoundRoleNames.value = []
viewVisible.value = true
try {
const [boundRes] = await Promise.all([fetchPostBoundUserIds(row.id), fetchDeptTree()])
const [boundRes, roleRes] = await Promise.all([
fetchPostBoundUserIds(row.id),
fetchPostBoundRoleIds(row.id)
])
const roleIds = roleRes?.roleIds || []
if (roleIds.length > 0) {
const roleListRes = await fetchRoleList({ page: 1, pageSize: 100 })
const idToName = new Map(
(roleListRes?.list || []).map((r: any) => [r.id, r.roleName || r.id])
)
viewBoundRoleNames.value = roleIds.map((id: string) => idToName.get(id) || id)
}
const boundIds = boundRes?.userIds || []
if (boundIds.length === 0) return
const users = await fetchAllUsersForPostBind()
const idToName = new Map(users.map((u) => [u.id, u.nickName || u.userNo || u.id]))
viewBoundUserNames.value = boundIds.map((id: string) => idToName.get(id) || id)
} catch (e) {
console.error('加载关联用户失败:', e)
console.error('加载职位关联信息失败:', e)
}
}
// ---- 角色配置弹窗 ----
const postRoleDialogVisible = ref(false)
const postRoleLoading = ref(false)
const postRoleSubmitLoading = ref(false)
const postRolePostId = ref('')
const postRolePostName = ref('')
const postRoleCheckedIds = ref<string[]>([])
const postRoleOptions = ref<Array<{ label: string; value: string }>>([])
async function handlePostBindRoles(row: Api.Post.PostItem) {
postRolePostId.value = row.id
postRolePostName.value = row.postName || row.postCode || ''
postRoleCheckedIds.value = []
postRoleDialogVisible.value = true
postRoleLoading.value = true
try {
const [boundRes, roleListRes] = await Promise.all([
fetchPostBoundRoleIds(row.id),
fetchRoleList({ page: 1, pageSize: 100 })
])
postRoleCheckedIds.value = [...(boundRes?.roleIds || [])]
postRoleOptions.value = (roleListRes?.list || []).map((r: any) => ({
label: r.roleName || r.id,
value: r.id
}))
} finally {
postRoleLoading.value = false
}
}
async function handlePostRoleSubmit() {
postRoleSubmitLoading.value = true
try {
await fetchUpdatePostBoundRoles(postRolePostId.value, [...postRoleCheckedIds.value])
postRoleDialogVisible.value = false
} finally {
postRoleSubmitLoading.value = false
}
}
+51 -4
View File
@@ -361,6 +361,7 @@
<el-select
v-model="formData.roleIds"
multiple
filterable
placeholder="请选择角色"
style="width: 100%"
>
@@ -372,6 +373,22 @@
/>
</el-select>
</el-form-item>
<el-form-item label="职位" style="grid-column: 1 / -1">
<el-select
v-model="formData.postIds"
multiple
filterable
placeholder="请选择职位"
style="width: 100%"
>
<el-option
v-for="item in postOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="备注" style="grid-column: 1 / -1">
<el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="可选" />
</el-form-item>
@@ -426,12 +443,14 @@
fetchDeleteUser,
fetchResetPassword,
fetchDeptTree,
fetchRoleOptions
fetchRoleOptions,
fetchPostList
} from '@/api/admin'
import { buildPathLabelMapFromTreeRoots } from '@/utils/tree-path'
import { resolveUserAvatarFromInfo } from '@/utils/user-avatar'
const roleOptions = ref<Array<{ label: string; value: string }>>([])
const postOptions = ref<Array<{ label: string; value: string }>>([])
async function loadRoleOptions() {
try {
@@ -441,6 +460,18 @@
}
}
async function loadPostOptions() {
try {
const res = await fetchPostList({ page: 1, pageSize: 100 })
postOptions.value = (res?.list || []).map((p) => ({
label: p.postName || p.postCode || p.id,
value: p.id
}))
} catch {
postOptions.value = []
}
}
defineOptions({ name: 'SystemUser' })
const deptKeyword = ref('')
@@ -489,6 +520,15 @@
return labels.length ? labels.join('、') : '—'
}
function formatUserPostDisplay(row: Api.User.UserItem) {
const ids = row.postIds || []
if (!ids.length) return '—'
const labels = ids
.map((id) => postOptions.value.find((o) => o.value === id)?.label)
.filter((name): name is string => Boolean(name))
return labels.length ? labels.join('、') : '—'
}
const searchForm = reactive<{ keyword: string; status: string }>({ keyword: '', status: '' })
const tableData = ref<Api.User.UserItem[]>([])
const selectedRows = ref<Api.User.UserItem[]>([])
@@ -518,7 +558,8 @@
status: '1',
orderNum: 0,
remark: '',
roleIds: [] as string[]
roleIds: [] as string[],
postIds: [] as string[]
})
const supervisorUserOptions = ref<Api.User.UserItem[]>([])
@@ -627,6 +668,7 @@
formData.orderNum = 0
formData.remark = ''
formData.roleIds = []
formData.postIds = []
supervisorUserOptions.value = []
editingId.value = null
}
@@ -667,6 +709,7 @@
formData.orderNum = detail.orderNum ?? 0
formData.remark = detail.remark || ''
formData.roleIds = detail.roleIds ? [...detail.roleIds] : []
formData.postIds = detail.postIds ? [...detail.postIds] : []
if (formData.supervisorUserId) {
await remoteSearchSupervisorUsers(detail.nickName || '')
if (!supervisorUserOptions.value.some((u) => u.id === formData.supervisorUserId)) {
@@ -701,7 +744,8 @@
status: formData.status,
orderNum: formData.orderNum,
remark: formData.remark?.trim() || undefined,
roleIds: [...formData.roleIds]
roleIds: [...formData.roleIds],
postIds: [...formData.postIds]
})
} else {
await fetchCreateUser({
@@ -719,7 +763,8 @@
status: formData.status,
orderNum: formData.orderNum,
remark: formData.remark?.trim() || undefined,
roleIds: formData.roleIds.length ? formData.roleIds : undefined
roleIds: formData.roleIds.length ? formData.roleIds : undefined,
postIds: formData.postIds.length ? formData.postIds : undefined
})
}
dialogVisible.value = false
@@ -838,6 +883,7 @@
tagType: row.status === '1' ? 'success' : 'info'
},
{ label: '角色', value: formatUserRoleDisplay(row), span: true },
{ label: '职位', value: formatUserPostDisplay(row), span: true },
{ label: '创建时间', value: row.createTime },
{ label: '备注', value: row.remark, span: true }
]
@@ -868,6 +914,7 @@
loadDeptTree()
loadData()
loadRoleOptions()
loadPostOptions()
})
</script>