refactor file tree
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<div v-can="{ value: [corePermissionCodes.role.read, corePermissionCodes.permission.read], mode: 'and' }" class="role-list">
|
||||
<t-pull-down-refresh
|
||||
class="list-wrapper"
|
||||
v-model="isRefreshing"
|
||||
:refresh-timeout="30000"
|
||||
@refresh="refresh"
|
||||
>
|
||||
<t-cell-group v-if="sortedRoles.length" theme="card">
|
||||
<t-swipe-cell
|
||||
v-for="item in sortedRoles"
|
||||
:key="item.id"
|
||||
class="item"
|
||||
>
|
||||
<t-cell
|
||||
:title="item.name"
|
||||
:note="item.code"
|
||||
arrow
|
||||
hover
|
||||
@click="router.push(`/management/permissions/roles/${item.id}`)"
|
||||
>
|
||||
<template #title>
|
||||
<div class="title" v-text="item.name" :style="{ paddingLeft: `${item.level * 1.25}rem` }" />
|
||||
</template>
|
||||
</t-cell>
|
||||
<template #right>
|
||||
<t-button v-if="canDeleteRole(item)" v-can="corePermissionCodes.role.delete" class="delete" theme="danger" @click="removeRole(item.id)">删除</t-button>
|
||||
</template>
|
||||
</t-swipe-cell>
|
||||
</t-cell-group>
|
||||
<t-empty v-else-if="!permissionStore.loading" description="暂无角色" />
|
||||
</t-pull-down-refresh>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Toast } from "tdesign-mobile-vue";
|
||||
import { useUserStore } from "timi-web";
|
||||
import type { Role } from "@/store/permissionSettingsStore";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
||||
import { corePermissionCodes, coreRoleCodes } from "@/utils/permission";
|
||||
|
||||
defineOptions({
|
||||
name: "RoleList"
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
const navBarStore = useNavBarStore();
|
||||
const permissionStore = usePermissionSettingsStore();
|
||||
const userStore = useUserStore();
|
||||
const isRefreshing = ref(false);
|
||||
const deletableRoleIdSet = ref(new Set<string>());
|
||||
|
||||
// ---------- 顶部导航 ----------
|
||||
|
||||
const canCreateRole = computed(() => userStore.hasPermissions(corePermissionCodes.role.create));
|
||||
|
||||
async function toCreate(): Promise<void> {
|
||||
if (!canCreateRole.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
await router.push("/management/permissions/roles/create");
|
||||
}
|
||||
|
||||
// ---------- 列表数据 ----------
|
||||
|
||||
const displayRoleList = ref<Role[]>([]);
|
||||
const sortedRoles = computed<Array<Role & { level: number }>>(() => {
|
||||
if (userStore.hasRoles(coreRoleCodes.admin)) {
|
||||
return permissionStore.roleTree;
|
||||
}
|
||||
return buildManageableRoleTree();
|
||||
});
|
||||
|
||||
async function removeRole(id: string): Promise<void> {
|
||||
try {
|
||||
await permissionStore.removeRole(id);
|
||||
} catch (error) {
|
||||
await refreshAuthorizationScope();
|
||||
await permissionStore.loadAll(true);
|
||||
Toast.error(getErrorMessage(error, "角色删除失败"));
|
||||
}
|
||||
}
|
||||
|
||||
function canDeleteRole(role: Role): boolean {
|
||||
if (userStore.hasRoles(coreRoleCodes.admin)) {
|
||||
return true;
|
||||
}
|
||||
return role.builtin !== true && role.protectedRole !== true && deletableRoleIdSet.value.has(role.id);
|
||||
}
|
||||
|
||||
// ---------- 页面数据 ----------
|
||||
|
||||
async function refresh(): Promise<void> {
|
||||
isRefreshing.value = true;
|
||||
try {
|
||||
await permissionStore.loadAll(true);
|
||||
displayRoleList.value = getDisplayRoleList();
|
||||
await refreshAuthorizationScope();
|
||||
} finally {
|
||||
isRefreshing.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
navBarStore.clearRight();
|
||||
if (canCreateRole.value) {
|
||||
navBarStore.setRightText("添加", "var(--td-brand-color)", toCreate);
|
||||
}
|
||||
if (userStore.hasRoles(coreRoleCodes.admin)) {
|
||||
await permissionStore.loadAll();
|
||||
}
|
||||
await refreshAuthorizationScope();
|
||||
if (!displayRoleList.value.length) {
|
||||
displayRoleList.value = getDisplayRoleList();
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
navBarStore.clearRight();
|
||||
});
|
||||
|
||||
function getDisplayRoleList(): Role[] {
|
||||
return userStore.hasRoles(coreRoleCodes.admin) ? permissionStore.roleList : permissionStore.manageableRoleList;
|
||||
}
|
||||
|
||||
function buildManageableRoleTree(): Array<Role & { level: number }> {
|
||||
const roleIdSet = new Set(displayRoleList.value.map(item => item.id));
|
||||
const roleMap = new Map(displayRoleList.value.map(item => [item.id, item]));
|
||||
const roots = displayRoleList.value.filter(item => !item.parentRoleId || !roleIdSet.has(item.parentRoleId));
|
||||
const buildTree = (roleId: string, level: number, visitedRoleIds: Set<string>): Array<Role & { level: number }> => {
|
||||
const role = roleMap.get(roleId);
|
||||
if (!role || visitedRoleIds.has(roleId)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const nextVisitedRoleIds = new Set(visitedRoleIds);
|
||||
nextVisitedRoleIds.add(roleId);
|
||||
const childRoleIdList = displayRoleList.value
|
||||
.filter(item => item.parentRoleId === roleId)
|
||||
.map(item => item.id);
|
||||
return [{ ...role, level }, ...childRoleIdList.flatMap(childRoleId => buildTree(childRoleId, level + 1, nextVisitedRoleIds))];
|
||||
};
|
||||
return roots.flatMap(item => buildTree(item.id, 0, new Set<string>()));
|
||||
}
|
||||
|
||||
async function refreshAuthorizationScope(): Promise<void> {
|
||||
if (userStore.hasRoles(coreRoleCodes.admin)) {
|
||||
deletableRoleIdSet.value = new Set(permissionStore.roleList.map(item => item.id));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const roleList = await permissionStore.loadManageableRoles();
|
||||
displayRoleList.value = roleList;
|
||||
deletableRoleIdSet.value = new Set(roleList.map(item => item.id).filter((id): id is string => Boolean(id)));
|
||||
} catch {
|
||||
deletableRoleIdSet.value = new Set<string>();
|
||||
}
|
||||
}
|
||||
|
||||
function getErrorMessage(error: unknown, fallback: string): string {
|
||||
if (error instanceof Error && error.message) {
|
||||
return error.message;
|
||||
}
|
||||
if (typeof error === "string" && error) {
|
||||
return error;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.role-list {
|
||||
padding: calc(var(--app-nav-height) + 1rem) 0 1rem 0;
|
||||
|
||||
.list-wrapper {
|
||||
padding-bottom: 6rem;
|
||||
|
||||
.item {
|
||||
|
||||
&:last-child {
|
||||
|
||||
:deep(.t-cell:after) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.delete {
|
||||
height: 100%;
|
||||
padding: 0 1rem;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user