feat(rbac): 批量删除联调 + 角色数据权限部门分配 UI
- 用户/组织/资源页批量删除改批量接口 + 失败汇总提示 - 角色页新增「分配部门」(dataScope=custom 时显示,部门树勾选) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
eff4f8b314
commit
d37af1b73e
@ -37,3 +37,8 @@ export function updateOrg(
|
||||
export function deleteOrg(id: string): Promise<unknown> {
|
||||
return http.delete(`/orgs/${id}`)
|
||||
}
|
||||
|
||||
/** 批量删除组织。 */
|
||||
export function batchDeleteOrgs(ids: string[]): Promise<{ total: number; success: number; failed: string[] }> {
|
||||
return http.post('/orgs/batch-delete', { ids })
|
||||
}
|
||||
|
||||
@ -49,3 +49,8 @@ export function updateResource(id: string, data: ResourceUpsertData): Promise<Re
|
||||
export function deleteResource(id: string): Promise<unknown> {
|
||||
return http.delete(`/resources/${id}`)
|
||||
}
|
||||
|
||||
/** 批量删除资源。 */
|
||||
export function batchDeleteResources(ids: string[]): Promise<{ total: number; success: number; failed: string[] }> {
|
||||
return http.post('/resources/batch-delete', { ids })
|
||||
}
|
||||
|
||||
@ -60,3 +60,13 @@ export function listAssignedResources(roleCode: string, signal?: AbortSignal): P
|
||||
export function assignResources(roleCode: string, resourceCodes: string[]): Promise<unknown> {
|
||||
return http.put(`/roles/${roleCode}/resources`, { resourceCodes })
|
||||
}
|
||||
|
||||
/** 角色已分配的数据权限部门 code 列表(data_scope=custom)。 */
|
||||
export function listAssignedOrgs(roleCode: string, signal?: AbortSignal): Promise<string[]> {
|
||||
return http.get<string[]>(`/roles/${roleCode}/orgs`, { signal })
|
||||
}
|
||||
|
||||
/** 分配数据权限部门。 */
|
||||
export function assignOrgs(roleCode: string, orgCodes: string[]): Promise<unknown> {
|
||||
return http.put(`/roles/${roleCode}/orgs`, { orgCodes })
|
||||
}
|
||||
|
||||
@ -67,6 +67,11 @@ export function deleteUser(id: string): Promise<unknown> {
|
||||
return http.delete(`/users/${id}`)
|
||||
}
|
||||
|
||||
/** 批量删除用户。 */
|
||||
export function batchDeleteUsers(ids: string[]): Promise<{ total: number; success: number; failed: string[] }> {
|
||||
return http.post('/users/batch-delete', { ids })
|
||||
}
|
||||
|
||||
/** 重置密码。 */
|
||||
export function resetPassword(id: string, password: string): Promise<unknown> {
|
||||
return http.patch(`/users/${id}/password`, { password })
|
||||
|
||||
@ -46,7 +46,7 @@ import TyForm from '@/components/ty-form.vue'
|
||||
import { showDialog } from '@/utils/dialog'
|
||||
import { useRequest } from '@/composables/useRequest'
|
||||
import { buildTree, collectDescendants, filterTree, type TreeNode } from '@/utils/tree'
|
||||
import { createOrg, deleteOrg, listOrgs, updateOrg, type Org } from '@/api/org'
|
||||
import { batchDeleteOrgs, createOrg, deleteOrg, listOrgs, updateOrg, type Org } from '@/api/org'
|
||||
|
||||
type OrgNode = TreeNode<Org>
|
||||
|
||||
@ -238,10 +238,14 @@ async function batchDelete(rows: Record<string, any>[]): Promise<void> {
|
||||
codes.add(String(r.code))
|
||||
for (const c of collectDescendants(orgs.value, String(r.code))) codes.add(c)
|
||||
}
|
||||
const targets = orgs.value.filter((o) => codes.has(o.code))
|
||||
for (const t of targets) await deleteOrg(t.id)
|
||||
const ids = orgs.value.filter((o) => codes.has(o.code)).map((o) => o.id)
|
||||
const result = await batchDeleteOrgs(ids)
|
||||
if (result.failed.length > 0) {
|
||||
ElMessage.warning(`删除完成:成功 ${result.success} 个,失败 ${result.failed.length} 个`)
|
||||
} else {
|
||||
ElMessage.success('删除成功')
|
||||
}
|
||||
selectedRows.value = []
|
||||
ElMessage.success('删除成功')
|
||||
await refreshOrgs()
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -46,7 +46,7 @@ import TyForm from '@/components/ty-form.vue'
|
||||
import { showDialog } from '@/utils/dialog'
|
||||
import { useRequest } from '@/composables/useRequest'
|
||||
import { buildTree, collectDescendants, filterTree, type TreeNode } from '@/utils/tree'
|
||||
import { createResource, deleteResource, listResources, updateResource, type Resource, type ResourceUpsertData } from '@/api/resource'
|
||||
import { batchDeleteResources, createResource, deleteResource, listResources, updateResource, type Resource, type ResourceUpsertData } from '@/api/resource'
|
||||
|
||||
type ResourceNode = TreeNode<Resource>
|
||||
|
||||
@ -288,10 +288,14 @@ async function batchDelete(rows: Record<string, any>[]): Promise<void> {
|
||||
codes.add(String(r.code))
|
||||
for (const c of collectDescendants(resources.value, String(r.code))) codes.add(c)
|
||||
}
|
||||
const targets = resources.value.filter((o) => codes.has(o.code))
|
||||
for (const t of targets) await deleteResource(t.id)
|
||||
const ids = resources.value.filter((o) => codes.has(o.code)).map((o) => o.id)
|
||||
const result = await batchDeleteResources(ids)
|
||||
if (result.failed.length > 0) {
|
||||
ElMessage.warning(`删除完成:成功 ${result.success} 个,失败 ${result.failed.length} 个`)
|
||||
} else {
|
||||
ElMessage.success('删除成功')
|
||||
}
|
||||
selectedRows.value = []
|
||||
ElMessage.success('删除成功')
|
||||
await refreshResources()
|
||||
}
|
||||
</script>
|
||||
|
||||
57
src/pages/system/role/assign-orgs-form.vue
Normal file
57
src/pages/system/role/assign-orgs-form.vue
Normal file
@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<el-tree
|
||||
v-if="ready"
|
||||
ref="treeRef"
|
||||
class="assign-tree"
|
||||
:data="tree"
|
||||
node-key="code"
|
||||
show-checkbox
|
||||
:props="{ label: 'name', children: 'children' }"
|
||||
default-expand-all
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref, watch } from 'vue'
|
||||
import { useRequest } from '@/composables/useRequest'
|
||||
import { listOrgs } from '@/api/org'
|
||||
import { listAssignedOrgs } from '@/api/role'
|
||||
import { buildTree, type TreeNode } from '@/utils/tree'
|
||||
import type { Org } from '@/api/org'
|
||||
|
||||
defineOptions({ name: 'AssignOrgsForm' })
|
||||
|
||||
const props = defineProps<{ roleCode: string }>()
|
||||
|
||||
interface TreeApi {
|
||||
getCheckedKeys: (leafOnly?: boolean) => (string | number)[]
|
||||
setCheckedKeys: (keys: (string | number)[], leafOnly?: boolean) => void
|
||||
}
|
||||
const treeRef = ref<TreeApi>()
|
||||
|
||||
const { data: orgList } = useRequest((signal: AbortSignal) => listOrgs(signal))
|
||||
const { data: assigned } = useRequest((signal: AbortSignal) => listAssignedOrgs(props.roleCode, signal))
|
||||
|
||||
const tree = computed(() => buildTree(orgList.value ?? []) as TreeNode<Org>[])
|
||||
const ready = computed(() => orgList.value !== undefined && assigned.value !== undefined)
|
||||
|
||||
watch(ready, async (r) => {
|
||||
if (r) {
|
||||
await nextTick()
|
||||
treeRef.value?.setCheckedKeys(assigned.value ?? [], true)
|
||||
}
|
||||
})
|
||||
|
||||
function submit(): { orgCodes: string[] } {
|
||||
const checked = treeRef.value?.getCheckedKeys(true) ?? []
|
||||
return { orgCodes: [...new Set(checked.map(String))] }
|
||||
}
|
||||
|
||||
defineExpose({ submit })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.assign-tree {
|
||||
@apply max-h-[420px] overflow-y-auto;
|
||||
}
|
||||
</style>
|
||||
@ -19,8 +19,9 @@
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { CrudAction, JsonSchema, PageQuery, TableColumn } from '@/types/crud'
|
||||
import { showDialog } from '@/utils/dialog'
|
||||
import { assignResources, createRole, deleteRole, listRoles, updateRole, type RoleListQuery } from '@/api/role'
|
||||
import { assignOrgs, assignResources, createRole, deleteRole, listRoles, updateRole, type RoleListQuery } from '@/api/role'
|
||||
import AssignResourcesForm from './assign-resources-form.vue'
|
||||
import AssignOrgsForm from './assign-orgs-form.vue'
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{ prop: 'name', label: '角色名称', minWidth: 140 },
|
||||
@ -72,6 +73,7 @@ const actions: CrudAction[] = [
|
||||
'edit',
|
||||
'delete',
|
||||
{ label: '分配权限', type: 'warning', onClick: (row) => assignAction(row) },
|
||||
{ label: '分配部门', type: 'primary', show: (row) => row.dataScope === 'custom', onClick: (row) => assignOrgsAction(row) },
|
||||
]
|
||||
|
||||
function toListQuery(query: PageQuery): RoleListQuery {
|
||||
@ -103,4 +105,16 @@ async function assignAction(row: Record<string, any>): Promise<void> {
|
||||
await assignResources(String(row.code), result.resourceCodes)
|
||||
ElMessage.success('权限已保存')
|
||||
}
|
||||
|
||||
async function assignOrgsAction(row: Record<string, any>): Promise<void> {
|
||||
const result = await showDialog({
|
||||
component: AssignOrgsForm,
|
||||
props: { roleCode: String(row.code) },
|
||||
title: `分配数据权限部门(${row.name})`,
|
||||
width: '520px',
|
||||
})
|
||||
if (result == null) return
|
||||
await assignOrgs(String(row.code), result.orgCodes)
|
||||
ElMessage.success('部门已保存')
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -39,6 +39,7 @@ import { showDialog } from '@/utils/dialog'
|
||||
import { useRequest } from '@/composables/useRequest'
|
||||
import {
|
||||
assignUserRoles,
|
||||
batchDeleteUsers,
|
||||
createUser,
|
||||
deleteUser,
|
||||
listUsers,
|
||||
@ -167,10 +168,12 @@ const crudRef = ref()
|
||||
|
||||
async function batchDelete(rows: Record<string, any>[]): Promise<void> {
|
||||
await ElMessageBox.confirm(`确认删除选中的 ${rows.length} 个用户?`, '提示', { type: 'warning' })
|
||||
for (const r of rows) {
|
||||
await deleteUser(String(r.id))
|
||||
const result = await batchDeleteUsers(rows.map((r) => String(r.id)))
|
||||
if (result.failed.length > 0) {
|
||||
ElMessage.warning(`删除完成:成功 ${result.success} 个,失败 ${result.failed.length} 个`)
|
||||
} else {
|
||||
ElMessage.success('删除成功')
|
||||
}
|
||||
ElMessage.success('删除成功')
|
||||
crudRef.value?.reload()
|
||||
}
|
||||
|
||||
|
||||
13
typed-router.d.ts
vendored
13
typed-router.d.ts
vendored
@ -121,6 +121,13 @@ declare module 'vue-router/auto-routes' {
|
||||
Record<never, never>,
|
||||
| never
|
||||
>,
|
||||
'/system/role/assign-orgs-form': RouteRecordInfo<
|
||||
'/system/role/assign-orgs-form',
|
||||
'/system/role/assign-orgs-form',
|
||||
Record<never, never>,
|
||||
Record<never, never>,
|
||||
| never
|
||||
>,
|
||||
'/system/role/assign-resources-form': RouteRecordInfo<
|
||||
'/system/role/assign-resources-form',
|
||||
'/system/role/assign-resources-form',
|
||||
@ -253,6 +260,12 @@ declare module 'vue-router/auto-routes' {
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'src/pages/system/role/assign-orgs-form.vue': {
|
||||
routes:
|
||||
| '/system/role/assign-orgs-form'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'src/pages/system/role/assign-resources-form.vue': {
|
||||
routes:
|
||||
| '/system/role/assign-resources-form'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user