feat: 接入用户与文档权限管理

This commit is contained in:
xy
2026-09-03 18:43:42 +08:00
parent 6d56bc0075
commit f45864e817
24 changed files with 2139 additions and 775 deletions
+143 -19
View File
@@ -34,6 +34,7 @@ import type {
UserRecord as ApiUserRecord,
} from '@/api/management/types'
import { useAppStore } from '@/stores/app'
import { useLoginStore } from '@/stores/login'
import RoleSummaryCards from './components/RoleSummaryCards.vue'
import UserTable from './components/UserTable.vue'
@@ -45,6 +46,7 @@ import { roleColors, roleSummaries, users } from './mock'
import type { RoleSummary, UserEditorForm, UserTableRow } from './types'
const appStore = useAppStore()
const loginStore = useLoginStore()
const loading = ref(true)
const keyword = ref('')
const roleRows = ref<RoleSummary[]>([])
@@ -74,8 +76,33 @@ const rolePermissionVersion = ref<number | undefined>()
const apiPermissionLoading = ref(false)
const apiPermissionSaving = ref(false)
const apiPermissionError = ref('')
const rolePermissionDrawerVisible = ref(false)
let userRequestId = 0
const canEditUsers = computed(
() => isUserMockEnabled || loginStore.hasPermission('system:user:update'),
)
const canCreateUsers = computed(
() => isUserMockEnabled || loginStore.hasPermission('system:user:create'),
)
const canDeactivateUsers = computed(
() => isUserMockEnabled || loginStore.hasPermission('system:user:disable'),
)
const canResetPasswords = computed(
() => isUserMockEnabled || loginStore.hasPermission('system:user:password:reset'),
)
const canImportUsers = computed(
() => isUserMockEnabled || loginStore.hasPermission('system:user:import'),
)
const canViewRoleApiPermissions = computed(
() =>
!isUserMockEnabled &&
loginStore.hasPermission(['system:permission:query', 'system:role:permission:query']),
)
const canEditRoleApiPermissions = computed(
() => !isUserMockEnabled && loginStore.hasPermission('system:role:permission:update'),
)
const mockCampuses: CampusRecord[] = [
{ id: 'mock-main', code: 'MAIN', name: '本部院区', status: 'ENABLED', sortNo: 1 },
{ id: 'mock-east', code: 'EAST', name: '东院区', status: 'ENABLED', sortNo: 2 },
@@ -229,7 +256,7 @@ async function loadSelectedRolePermissions() {
}
async function loadApiPermissionCatalog() {
if (isUserMockEnabled || apiPermissionLoading.value) {
if (isUserMockEnabled || !canViewRoleApiPermissions.value || apiPermissionLoading.value) {
return
}
@@ -255,6 +282,10 @@ async function loadApiPermissionCatalog() {
}
async function selectRoleForApiPermissions(roleId: string) {
if (!canViewRoleApiPermissions.value) {
return
}
selectedRoleId.value = roleId
apiPermissionLoading.value = true
apiPermissionError.value = ''
@@ -282,7 +313,7 @@ function toggleApiPermission(permissionId: string) {
}
async function saveRoleApiPermissions() {
if (!selectedRoleId.value || apiPermissionSaving.value) {
if (!canEditRoleApiPermissions.value || !selectedRoleId.value || apiPermissionSaving.value) {
return
}
@@ -363,11 +394,19 @@ function mapRoleSummary(role: RoleRecord): RoleSummary {
}
function showAddUserMessage() {
if (!canCreateUsers.value) {
return
}
editorInitial.value = null
editorVisible.value = true
}
function showImportMessage() {
if (!canImportUsers.value) {
return
}
if (isUserMockEnabled) {
ElMessage.info('批量导入需要切换到真实接口模式(VITE_USE_MOCK=false)')
return
@@ -379,6 +418,10 @@ function showImportMessage() {
}
async function editUser(row: UserTableRow) {
if (!canEditUsers.value) {
return
}
try {
const user = isUserMockEnabled
? userRows.value.find((item) => item.id === row.id)
@@ -419,6 +462,10 @@ async function editUser(row: UserTableRow) {
}
function resetPassword(user: UserTableRow) {
if (!canResetPasswords.value) {
return
}
if (isUserMockEnabled) {
ElMessage.info('密码重置需要切换到真实接口模式(VITE_USE_MOCK=false)')
return
@@ -515,7 +562,11 @@ function changeUserPage(nextPage: number) {
}
async function deactivateUser(row: UserTableRow) {
if (row.status !== 'enabled' || !window.confirm(`确定停用用户“${row.name}”吗?`)) {
if (
!canDeactivateUsers.value ||
row.status !== 'enabled' ||
!window.confirm(`确定停用用户“${row.name}”吗?`)
) {
return
}
@@ -663,6 +714,15 @@ watch(
)
onMounted(loadUsers)
function openRolePermissionDrawer(role: RoleSummary) {
if (!canViewRoleApiPermissions.value) {
return
}
rolePermissionDrawerVisible.value = true
void selectRoleForApiPermissions(role.id)
}
</script>
<template>
@@ -672,7 +732,12 @@ onMounted(loadUsers)
</div>
<template v-else>
<RoleSummaryCards :rows="roleRows" />
<RoleSummaryCards
:rows="roleRows"
:interactive="canViewRoleApiPermissions"
:selected-role-id="selectedRoleId"
@select="openRolePermissionDrawer"
/>
<section class="page-card">
<div class="card-heading">
@@ -687,13 +752,30 @@ onMounted(loadUsers)
@keydown.enter.prevent="searchUsers"
/>
<button type="button" class="table-button" @click="searchUsers">查询</button>
<button type="button" class="table-button" @click="showAddUserMessage">+新增用户</button>
<button type="button" class="table-button" @click="showImportMessage">批量导入</button>
<button
v-if="canCreateUsers"
type="button"
class="table-button"
@click="showAddUserMessage"
>
+新增用户
</button>
<button
v-if="canImportUsers"
type="button"
class="table-button"
@click="showImportMessage"
>
批量导入
</button>
</div>
<UserTable
:rows="visibleUsers"
:role-colors="roleColorMap"
:disabling-id="disablingUserId"
:can-edit="canEditUsers"
:can-reset-password="canResetPasswords"
:can-deactivate="canDeactivateUsers"
@edit="editUser"
@reset-password="resetPassword"
@deactivate="deactivateUser"
@@ -720,19 +802,31 @@ onMounted(loadUsers)
</div>
</section>
<RoleApiPermissionPanel
v-if="!isUserMockEnabled"
:roles="roleOptions"
:permissions="apiPermissions"
:selected-role-id="selectedRoleId"
:selected-permission-ids="selectedRolePermissionIds"
:loading="apiPermissionLoading"
:saving="apiPermissionSaving"
:error="apiPermissionError"
@select-role="selectRoleForApiPermissions"
@toggle-permission="toggleApiPermission"
@save="saveRoleApiPermissions"
/>
<Teleport to="body">
<div
v-if="rolePermissionDrawerVisible && canViewRoleApiPermissions"
class="role-permission-mask"
@click.self="rolePermissionDrawerVisible = false"
>
<aside class="role-permission-drawer" role="dialog" aria-modal="true">
<RoleApiPermissionPanel
:roles="roleOptions"
:permissions="apiPermissions"
:selected-role-id="selectedRoleId"
:selected-permission-ids="selectedRolePermissionIds"
:loading="apiPermissionLoading"
:saving="apiPermissionSaving"
:error="apiPermissionError"
:can-edit="canEditRoleApiPermissions"
drawer
@select-role="selectRoleForApiPermissions"
@toggle-permission="toggleApiPermission"
@save="saveRoleApiPermissions"
@close="rolePermissionDrawerVisible = false"
/>
</aside>
</div>
</Teleport>
</template>
<UserEditorDialog
@@ -841,6 +935,24 @@ onMounted(loadUsers)
opacity: 0.55;
}
.role-permission-mask {
position: fixed;
z-index: 50;
inset: 0;
display: flex;
justify-content: flex-end;
background: rgb(9 40 52 / 45%);
}
.role-permission-drawer {
width: min(760px, 96vw);
height: 100%;
overflow: hidden;
background: var(--card);
box-shadow: var(--sh-modal);
animation: slide-in 0.18s ease-out;
}
.loading-card {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
@@ -867,6 +979,18 @@ onMounted(loadUsers)
}
}
@keyframes slide-in {
from {
transform: translateX(12px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@media (max-width: 900px) {
.card-heading {
align-items: flex-start;