feat: 接入用户与文档权限管理
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user