feat: 接入文档权限批量接口
This commit is contained in:
@@ -1,13 +1,22 @@
|
||||
import { unwrapApiResponse, unwrapNullableApiResponse } from '@/utils/api-response'
|
||||
import { request } from '@/utils/request'
|
||||
import type { ApiResponse } from '@/types/common'
|
||||
import type { ApiResponse, PageResult } from '@/types/common'
|
||||
|
||||
import type {
|
||||
BackendCollection,
|
||||
BackendPage,
|
||||
CreateTemplatePermissionRequest,
|
||||
PermissionListResponse,
|
||||
PermissionQuery,
|
||||
PermissionRecord,
|
||||
TemplatePermissionBatchRequest,
|
||||
TemplatePermissionBatchResponse,
|
||||
TemplatePermissionBatchResponseDto,
|
||||
TemplatePermissionBatchResult,
|
||||
TemplatePermissionBatchResultDto,
|
||||
TemplatePermissionMatrixItemDto,
|
||||
TemplatePermissionMatrixQuery,
|
||||
TemplatePermissionMatrixResponse,
|
||||
TemplatePermissionRecord,
|
||||
TemplatePermissionResponseDto,
|
||||
} from './types'
|
||||
@@ -85,17 +94,96 @@ function formatDateTime(value: string | null | undefined) {
|
||||
}
|
||||
|
||||
function mapTemplatePermission(dto: TemplatePermissionResponseDto): TemplatePermissionRecord {
|
||||
const subjectId = dto.subjectId ?? null
|
||||
|
||||
return {
|
||||
id: dto.id,
|
||||
id: dto.id ?? undefined,
|
||||
templateId: dto.templateId,
|
||||
subjectType: dto.subjectType,
|
||||
subjectId: dto.subjectId,
|
||||
subjectName: dto.subjectId,
|
||||
subjectId,
|
||||
subjectName: subjectId ?? '系统默认继承规则',
|
||||
permissionLevel: dto.permissionLevel,
|
||||
effect: dto.effect,
|
||||
inherited: dto.inherited,
|
||||
createdAt: formatDateTime(dto.createdAt),
|
||||
createdBy: dto.createdBy,
|
||||
createdBy: dto.createdBy ?? undefined,
|
||||
}
|
||||
}
|
||||
|
||||
function mapMatrixPermission(dto: TemplatePermissionMatrixItemDto): TemplatePermissionRecord {
|
||||
const subjectId = dto.subjectId ?? null
|
||||
|
||||
return {
|
||||
id: dto.permissionId ?? undefined,
|
||||
templateId: dto.templateId,
|
||||
subjectType: dto.subjectType,
|
||||
subjectId,
|
||||
subjectName: subjectId ?? '系统默认继承规则',
|
||||
permissionLevel: dto.permissionLevel,
|
||||
effect: dto.effect,
|
||||
inherited: dto.inherited,
|
||||
createdAt: formatDateTime(dto.createdAt),
|
||||
createdBy: dto.createdBy ?? undefined,
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePage<T>(
|
||||
data: BackendPage<T> | T[],
|
||||
fallbackPage: number,
|
||||
fallbackPageSize: number,
|
||||
): PageResult<T> {
|
||||
const records = Array.isArray(data) ? data : (data.records ?? data.items ?? data.content ?? [])
|
||||
|
||||
return {
|
||||
records,
|
||||
total: Array.isArray(data) ? records.length : (data.total ?? records.length),
|
||||
page: Array.isArray(data) ? fallbackPage : (data.page ?? fallbackPage),
|
||||
pageSize: Array.isArray(data) ? fallbackPageSize : (data.size ?? fallbackPageSize),
|
||||
}
|
||||
}
|
||||
|
||||
function toMatrixQuery(query: TemplatePermissionMatrixQuery) {
|
||||
if (query.templateIds && query.templateIds.length > 100) {
|
||||
throw new Error('跨模板权限矩阵最多同时查询 100 个模板')
|
||||
}
|
||||
|
||||
const params: Record<string, string | number | string[]> = {
|
||||
page: Math.max(query.page, 1),
|
||||
size: Math.min(Math.max(query.pageSize, 1), 200),
|
||||
}
|
||||
|
||||
const optionalParams: Array<[string, string | undefined]> = [
|
||||
['keyword', query.keyword?.trim() || undefined],
|
||||
['templateId', query.templateId],
|
||||
['roleId', query.roleId],
|
||||
['subjectDepartmentId', query.subjectDepartmentId],
|
||||
['userId', query.userId],
|
||||
['subjectType', query.subjectType],
|
||||
['campusId', query.campusId],
|
||||
['templateDepartmentId', query.templateDepartmentId],
|
||||
]
|
||||
|
||||
for (const [key, value] of optionalParams) {
|
||||
if (value) {
|
||||
params[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (query.templateIds?.length) {
|
||||
params.templateIds = query.templateIds
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
function mapBatchResult(dto: TemplatePermissionBatchResultDto): TemplatePermissionBatchResult {
|
||||
return {
|
||||
index: dto.index,
|
||||
templateId: dto.templateId,
|
||||
action: dto.action,
|
||||
result: dto.result,
|
||||
permissionId: dto.permissionId ?? undefined,
|
||||
permission: dto.permission ? mapMatrixPermission(dto.permission) : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +204,52 @@ export async function getTemplatePermissions(
|
||||
return records.map(mapTemplatePermission)
|
||||
}
|
||||
|
||||
export async function getTemplatePermissionMatrix(
|
||||
query: TemplatePermissionMatrixQuery,
|
||||
): Promise<TemplatePermissionMatrixResponse> {
|
||||
const page = Math.max(query.page, 1)
|
||||
const pageSize = Math.min(Math.max(query.pageSize, 1), 200)
|
||||
|
||||
if (useMockData) {
|
||||
return { records: [], total: 0, page, pageSize }
|
||||
}
|
||||
|
||||
const response = await request.get<
|
||||
ApiResponse<BackendCollection<TemplatePermissionMatrixItemDto>>
|
||||
>('/v1/templates/permissions/matrix', {
|
||||
params: toMatrixQuery(query),
|
||||
paramsSerializer: { indexes: null },
|
||||
})
|
||||
const data = normalizePage(unwrapApiResponse(response), page, pageSize)
|
||||
|
||||
return {
|
||||
...data,
|
||||
records: data.records.map(mapMatrixPermission),
|
||||
}
|
||||
}
|
||||
|
||||
export async function batchSaveTemplatePermissions(
|
||||
payload: TemplatePermissionBatchRequest,
|
||||
): Promise<TemplatePermissionBatchResponse> {
|
||||
if (useMockData) {
|
||||
throw new Error('Mock 模式不执行权限批量写操作')
|
||||
}
|
||||
|
||||
if (payload.operations.length > 200) {
|
||||
throw new Error('模板权限批量保存最多支持 200 项操作')
|
||||
}
|
||||
|
||||
const response = await request.post<ApiResponse<TemplatePermissionBatchResponseDto>>(
|
||||
'/v1/templates/permissions/batch',
|
||||
payload,
|
||||
)
|
||||
const data = unwrapApiResponse(response)
|
||||
|
||||
return {
|
||||
results: (data.results ?? []).map(mapBatchResult),
|
||||
}
|
||||
}
|
||||
|
||||
export async function createTemplatePermission(
|
||||
templateId: string,
|
||||
payload: CreateTemplatePermissionRequest,
|
||||
|
||||
@@ -138,23 +138,35 @@ function toBackendQuery(query: DocumentQuery) {
|
||||
params.status = statusMap[query.status]
|
||||
}
|
||||
|
||||
if (query.campusId) {
|
||||
params.campusId = query.campusId
|
||||
}
|
||||
|
||||
if (query.departmentId) {
|
||||
params.departmentId = query.departmentId
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
function getRemoteDocuments(path: string, query: DocumentQuery): Promise<DocumentListResponse> {
|
||||
return request
|
||||
.get<ApiResponse<BackendCollection<TemplateResponseDto>>>(path, {
|
||||
params: toBackendQuery(query),
|
||||
})
|
||||
.then((response) => {
|
||||
const page = normalizePage(unwrapApiResponse(response), query.page, query.pageSize)
|
||||
|
||||
return {
|
||||
...page,
|
||||
records: page.records.map(mapDocument),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function getDocuments(query: DocumentQuery): Promise<DocumentListResponse> {
|
||||
if (!useMockData) {
|
||||
return request
|
||||
.get<ApiResponse<BackendCollection<TemplateResponseDto>>>('/v1/templates', {
|
||||
params: toBackendQuery(query),
|
||||
})
|
||||
.then((response) => {
|
||||
const page = normalizePage(unwrapApiResponse(response), query.page, query.pageSize)
|
||||
|
||||
return {
|
||||
...page,
|
||||
records: page.records.map(mapDocument),
|
||||
}
|
||||
})
|
||||
return getRemoteDocuments('/v1/templates', query)
|
||||
}
|
||||
|
||||
const keyword = query.keyword?.trim().toLowerCase()
|
||||
@@ -176,6 +188,14 @@ export function getDocuments(query: DocumentQuery): Promise<DocumentListResponse
|
||||
})
|
||||
}
|
||||
|
||||
export function getTemplatePermissionConfig(query: DocumentQuery): Promise<DocumentListResponse> {
|
||||
if (useMockData) {
|
||||
return getDocuments(query)
|
||||
}
|
||||
|
||||
return getRemoteDocuments('/v1/templates/permission-config', query)
|
||||
}
|
||||
|
||||
export async function getDocumentDetail(id: string): Promise<DocumentRecord | null> {
|
||||
if (useMockData) {
|
||||
const record = mockRecords.find((item) => item.id === id)
|
||||
|
||||
@@ -5,6 +5,8 @@ export type DocumentStatus = 'draft' | 'published' | 'archived' | 'review'
|
||||
export interface DocumentQuery extends PageQuery {
|
||||
keyword?: string
|
||||
status?: DocumentStatus | 'all'
|
||||
campusId?: string
|
||||
departmentId?: string
|
||||
}
|
||||
|
||||
export interface DocumentRecord {
|
||||
@@ -625,22 +627,22 @@ export type ApiPermissionLevel = 'VIEW' | 'USE' | 'MAINTAIN'
|
||||
export type ApiPermissionEffect = 'ALLOW' | 'DENY'
|
||||
|
||||
export interface TemplatePermissionResponseDto {
|
||||
id?: string
|
||||
id?: string | null
|
||||
templateId: string
|
||||
subjectType: ApiPermissionSubjectType
|
||||
subjectId: string
|
||||
subjectId: string | null
|
||||
permissionLevel: ApiPermissionLevel
|
||||
effect: ApiPermissionEffect
|
||||
inherited: boolean
|
||||
createdAt: string
|
||||
createdBy?: string
|
||||
createdAt?: string | null
|
||||
createdBy?: string | null
|
||||
}
|
||||
|
||||
export interface TemplatePermissionRecord {
|
||||
id?: string
|
||||
templateId: string
|
||||
subjectType: ApiPermissionSubjectType
|
||||
subjectId: string
|
||||
subjectId: string | null
|
||||
subjectName: string
|
||||
permissionLevel: ApiPermissionLevel
|
||||
effect: ApiPermissionEffect
|
||||
@@ -656,6 +658,74 @@ export interface CreateTemplatePermissionRequest {
|
||||
effect: ApiPermissionEffect
|
||||
}
|
||||
|
||||
export interface TemplatePermissionMatrixQuery extends PageQuery {
|
||||
keyword?: string
|
||||
templateId?: string
|
||||
templateIds?: string[]
|
||||
roleId?: string
|
||||
subjectDepartmentId?: string
|
||||
userId?: string
|
||||
subjectType?: ApiPermissionSubjectType
|
||||
campusId?: string
|
||||
templateDepartmentId?: string
|
||||
}
|
||||
|
||||
export interface TemplatePermissionMatrixItemDto {
|
||||
templateId: string
|
||||
permissionId?: string | null
|
||||
subjectType: ApiPermissionSubjectType
|
||||
subjectId?: string | null
|
||||
permissionLevel: ApiPermissionLevel
|
||||
effect: ApiPermissionEffect
|
||||
inherited: boolean
|
||||
createdAt?: string | null
|
||||
createdBy?: string | null
|
||||
}
|
||||
|
||||
export type TemplatePermissionBatchAction = 'ADD' | 'REVOKE'
|
||||
|
||||
export interface TemplatePermissionBatchItemRequest {
|
||||
templateId: string
|
||||
action: TemplatePermissionBatchAction
|
||||
permissionId?: string
|
||||
subjectType?: ApiPermissionSubjectType
|
||||
subjectId?: string
|
||||
permissionLevel?: ApiPermissionLevel
|
||||
effect?: ApiPermissionEffect
|
||||
}
|
||||
|
||||
export interface TemplatePermissionBatchRequest {
|
||||
operations: TemplatePermissionBatchItemRequest[]
|
||||
}
|
||||
|
||||
export interface TemplatePermissionBatchResultDto {
|
||||
index: number
|
||||
templateId: string
|
||||
action: TemplatePermissionBatchAction
|
||||
result: string
|
||||
permissionId?: string | null
|
||||
permission?: TemplatePermissionMatrixItemDto | null
|
||||
}
|
||||
|
||||
export interface TemplatePermissionBatchResponseDto {
|
||||
results?: TemplatePermissionBatchResultDto[] | null
|
||||
}
|
||||
|
||||
export interface TemplatePermissionBatchResult {
|
||||
index: number
|
||||
templateId: string
|
||||
action: TemplatePermissionBatchAction
|
||||
result: string
|
||||
permissionId?: string
|
||||
permission?: TemplatePermissionRecord
|
||||
}
|
||||
|
||||
export interface TemplatePermissionBatchResponse {
|
||||
results: TemplatePermissionBatchResult[]
|
||||
}
|
||||
|
||||
export type TemplatePermissionMatrixResponse = PageResult<TemplatePermissionRecord>
|
||||
|
||||
export interface AuditLogQuery extends PageQuery {
|
||||
keyword?: string
|
||||
}
|
||||
|
||||
+5
-1
@@ -84,7 +84,11 @@ function deletePermission(permissionId: string | undefined) {
|
||||
}
|
||||
}
|
||||
|
||||
function formatSubject(subjectType: PermissionSubjectOption['type'], subjectId: string) {
|
||||
function formatSubject(subjectType: PermissionSubjectOption['type'], subjectId: string | null) {
|
||||
if (!subjectId) {
|
||||
return '系统默认继承规则'
|
||||
}
|
||||
|
||||
const subject = props.subjects.find((item) => item.type === subjectType && item.id === subjectId)
|
||||
return subject?.label ?? subjectId
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ import { computed, onMounted, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
import { getAllDepartments } from '@/api/management/organization'
|
||||
import { getDocuments } from '@/api/management/documents'
|
||||
import { getTemplatePermissionConfig } from '@/api/management/documents'
|
||||
import {
|
||||
createTemplatePermission,
|
||||
deleteTemplatePermission,
|
||||
batchSaveTemplatePermissions,
|
||||
getTemplatePermissions,
|
||||
getTemplatePermissionMatrix,
|
||||
isPermissionMockEnabled,
|
||||
} from '@/api/management/document-permissions'
|
||||
import { getUsers } from '@/api/management/users'
|
||||
@@ -16,6 +16,7 @@ import type {
|
||||
DepartmentRecord,
|
||||
DocumentRecord,
|
||||
RoleRecord,
|
||||
TemplatePermissionBatchItemRequest,
|
||||
TemplatePermissionRecord,
|
||||
UserRecord,
|
||||
} from '@/api/management/types'
|
||||
@@ -146,7 +147,7 @@ function syncPermissionRows() {
|
||||
}
|
||||
|
||||
async function getAllTemplatesForPermission() {
|
||||
const firstPage = await getDocuments({ page: 1, pageSize: 200, status: 'all' })
|
||||
const firstPage = await getTemplatePermissionConfig({ page: 1, pageSize: 200, status: 'all' })
|
||||
const pageCount = Math.ceil(firstPage.total / firstPage.pageSize)
|
||||
|
||||
if (pageCount <= 1) {
|
||||
@@ -155,13 +156,69 @@ async function getAllTemplatesForPermission() {
|
||||
|
||||
const remainingPages = await Promise.all(
|
||||
Array.from({ length: pageCount - 1 }, (_, index) =>
|
||||
getDocuments({ page: index + 2, pageSize: firstPage.pageSize, status: 'all' }),
|
||||
getTemplatePermissionConfig({
|
||||
page: index + 2,
|
||||
pageSize: firstPage.pageSize,
|
||||
status: 'all',
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
return [firstPage, ...remainingPages].flatMap((page) => page.records)
|
||||
}
|
||||
|
||||
async function getAllTemplatePermissionMatrix(templateId: string) {
|
||||
const firstPage = await getTemplatePermissionMatrix({
|
||||
page: 1,
|
||||
pageSize: 200,
|
||||
templateId,
|
||||
})
|
||||
const pageCount = Math.ceil(firstPage.total / firstPage.pageSize)
|
||||
|
||||
if (pageCount <= 1) {
|
||||
return firstPage.records
|
||||
}
|
||||
|
||||
const remainingPages = await Promise.all(
|
||||
Array.from({ length: pageCount - 1 }, (_, index) =>
|
||||
getTemplatePermissionMatrix({
|
||||
page: index + 2,
|
||||
pageSize: firstPage.pageSize,
|
||||
templateId,
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
return [firstPage, ...remainingPages].flatMap((page) => page.records)
|
||||
}
|
||||
|
||||
function mergeTemplatePermissions(
|
||||
matrixRecords: TemplatePermissionRecord[],
|
||||
effectiveRecords: TemplatePermissionRecord[],
|
||||
) {
|
||||
const seen = new Set<string>()
|
||||
|
||||
return [...matrixRecords, ...effectiveRecords.filter((record) => record.inherited)].filter(
|
||||
(record) => {
|
||||
const key = [
|
||||
record.templateId,
|
||||
record.subjectType,
|
||||
record.subjectId ?? '',
|
||||
record.permissionLevel,
|
||||
record.effect,
|
||||
record.inherited,
|
||||
].join('|')
|
||||
|
||||
if (seen.has(key)) {
|
||||
return false
|
||||
}
|
||||
|
||||
seen.add(key)
|
||||
return true
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
async function getRolesForPermission(): Promise<{
|
||||
records: RoleRecord[]
|
||||
usedFallback: boolean
|
||||
@@ -281,17 +338,28 @@ async function loadTemplatePermissions() {
|
||||
permissionError.value = false
|
||||
|
||||
try {
|
||||
const records = await getTemplatePermissions(
|
||||
templateId,
|
||||
permissionPreviewUserId.value || undefined,
|
||||
)
|
||||
const [matrixResult, effectiveResult] = await Promise.allSettled([
|
||||
getAllTemplatePermissionMatrix(templateId),
|
||||
getTemplatePermissions(templateId, permissionPreviewUserId.value || undefined),
|
||||
])
|
||||
|
||||
if (matrixResult.status === 'rejected') {
|
||||
throw matrixResult.reason
|
||||
}
|
||||
|
||||
if (requestId !== permissionRequestId || templateId !== selectedTemplateId.value) {
|
||||
return
|
||||
}
|
||||
|
||||
templatePermissions.value = records
|
||||
templatePermissions.value = mergeTemplatePermissions(
|
||||
matrixResult.value,
|
||||
effectiveResult.status === 'fulfilled' ? effectiveResult.value : [],
|
||||
)
|
||||
syncPermissionRows()
|
||||
|
||||
if (effectiveResult.status === 'rejected') {
|
||||
ElMessage.warning('用户继承权限预览加载失败,当前仅展示矩阵权限数据')
|
||||
}
|
||||
} catch {
|
||||
if (requestId !== permissionRequestId) {
|
||||
return
|
||||
@@ -341,6 +409,7 @@ async function applyTemplatePermission(
|
||||
permission: PermissionKey,
|
||||
enabled: boolean,
|
||||
) {
|
||||
const operations: TemplatePermissionBatchItemRequest[] = []
|
||||
const matchingRecords = templatePermissions.value.filter(
|
||||
(record) =>
|
||||
record.subjectType === subjectType &&
|
||||
@@ -362,34 +431,49 @@ async function applyTemplatePermission(
|
||||
if (!binding.id) {
|
||||
throw new Error('该直接拒绝权限缺少绑定 ID,无法撤销')
|
||||
}
|
||||
await deleteTemplatePermission(templateId, binding.id)
|
||||
operations.push({
|
||||
templateId,
|
||||
action: 'REVOKE',
|
||||
permissionId: binding.id,
|
||||
})
|
||||
}
|
||||
|
||||
if (!directAllows.length && !inheritedAllows) {
|
||||
await createTemplatePermission(templateId, {
|
||||
operations.push({
|
||||
templateId,
|
||||
action: 'ADD',
|
||||
subjectType,
|
||||
subjectId,
|
||||
permissionLevel: permission,
|
||||
effect: 'ALLOW',
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for (const binding of directAllows) {
|
||||
if (!binding.id) {
|
||||
throw new Error('该直接允许权限缺少绑定 ID,无法撤销')
|
||||
} else {
|
||||
for (const binding of directAllows) {
|
||||
if (!binding.id) {
|
||||
throw new Error('该直接允许权限缺少绑定 ID,无法撤销')
|
||||
}
|
||||
operations.push({
|
||||
templateId,
|
||||
action: 'REVOKE',
|
||||
permissionId: binding.id,
|
||||
})
|
||||
}
|
||||
|
||||
if (!directDenies.length && inheritedAllows) {
|
||||
operations.push({
|
||||
templateId,
|
||||
action: 'ADD',
|
||||
subjectType,
|
||||
subjectId,
|
||||
permissionLevel: permission,
|
||||
effect: 'DENY',
|
||||
})
|
||||
}
|
||||
await deleteTemplatePermission(templateId, binding.id)
|
||||
}
|
||||
|
||||
if (!directDenies.length && inheritedAllows) {
|
||||
await createTemplatePermission(templateId, {
|
||||
subjectType,
|
||||
subjectId,
|
||||
permissionLevel: permission,
|
||||
effect: 'DENY',
|
||||
})
|
||||
if (operations.length) {
|
||||
await batchSaveTemplatePermissions({ operations })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,7 +521,15 @@ async function addTemplatePermission(form: TemplatePermissionForm) {
|
||||
permissionSaving.value = true
|
||||
|
||||
try {
|
||||
await createTemplatePermission(selectedTemplateId.value, form)
|
||||
await batchSaveTemplatePermissions({
|
||||
operations: [
|
||||
{
|
||||
templateId: selectedTemplateId.value,
|
||||
action: 'ADD',
|
||||
...form,
|
||||
},
|
||||
],
|
||||
})
|
||||
await loadTemplatePermissions()
|
||||
ElMessage.success('模板权限已添加')
|
||||
} catch (error) {
|
||||
@@ -455,7 +547,15 @@ async function removeTemplatePermission(permissionId: string) {
|
||||
permissionSaving.value = true
|
||||
|
||||
try {
|
||||
await deleteTemplatePermission(selectedTemplateId.value, permissionId)
|
||||
await batchSaveTemplatePermissions({
|
||||
operations: [
|
||||
{
|
||||
templateId: selectedTemplateId.value,
|
||||
action: 'REVOKE',
|
||||
permissionId,
|
||||
},
|
||||
],
|
||||
})
|
||||
await loadTemplatePermissions()
|
||||
ElMessage.success('模板权限已删除')
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user