update RolePermissionAssign style

This commit is contained in:
Timi
2026-08-11 17:34:43 +08:00
parent 1bf53c9d1c
commit c045fbb49b
7 changed files with 76 additions and 145 deletions
@@ -50,6 +50,7 @@ import type { DropdownOption, DropdownValue } from "tdesign-mobile-vue";
import { Toast } from "tdesign-mobile-vue"; import { Toast } from "tdesign-mobile-vue";
import { type Permission, useUserStore } from "timi-web"; import { type Permission, useUserStore } from "timi-web";
import { type Role, usePermissionSettingsStore } from "@/store/permissionSettingsStore"; import { type Role, usePermissionSettingsStore } from "@/store/permissionSettingsStore";
import { getErrorMessage } from "@/utils/error";
import { corePermissionCodes, coreRoleCodes } from "@/utils/permission"; import { corePermissionCodes, coreRoleCodes } from "@/utils/permission";
defineOptions({ defineOptions({
@@ -188,16 +189,6 @@ async function loadAuthorizationScope(): Promise<void> {
candidateRoleList.value = roleList; candidateRoleList.value = roleList;
} }
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;
}
onMounted(async () => { onMounted(async () => {
await permissionStore.loadAll(); await permissionStore.loadAll();
await loadAuthorizationScope(); await loadAuthorizationScope();
+1 -10
View File
@@ -112,6 +112,7 @@ import {
type RoleDraft, type RoleDraft,
usePermissionSettingsStore usePermissionSettingsStore
} from "@/store/permissionSettingsStore"; } from "@/store/permissionSettingsStore";
import { getErrorMessage } from "@/utils/error";
import { corePermissionCodes, coreRoleCodes } from "@/utils/permission"; import { corePermissionCodes, coreRoleCodes } from "@/utils/permission";
import PermissionCell from "@/pages/permission/component/PermissionCell.vue"; import PermissionCell from "@/pages/permission/component/PermissionCell.vue";
import PermissionLegend from "@/pages/permission/component/PermissionLegend.vue"; import PermissionLegend from "@/pages/permission/component/PermissionLegend.vue";
@@ -453,16 +454,6 @@ async function getCurrentStoreId(): Promise<string> {
return storeId; return storeId;
} }
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;
}
onMounted(fillForm); onMounted(fillForm);
function updateNavBar(): void { function updateNavBar(): void {
+1 -10
View File
@@ -39,6 +39,7 @@ import { useUserStore } from "timi-web";
import type { Role } from "@/store/permissionSettingsStore"; import type { Role } from "@/store/permissionSettingsStore";
import { useNavBarStore } from "@/store/navBarStore"; import { useNavBarStore } from "@/store/navBarStore";
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore"; import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
import { getErrorMessage } from "@/utils/error";
import { corePermissionCodes, coreRoleCodes } from "@/utils/permission"; import { corePermissionCodes, coreRoleCodes } from "@/utils/permission";
defineOptions({ defineOptions({
@@ -161,15 +162,6 @@ async function refreshAuthorizationScope(): Promise<void> {
} }
} }
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> </script>
<style scoped lang="less"> <style scoped lang="less">
@@ -198,4 +190,3 @@ function getErrorMessage(error: unknown, fallback: string): string {
} }
</style> </style>
+55 -103
View File
@@ -13,21 +13,23 @@
@change="handleRoleChange" @change="handleRoleChange"
/> />
</t-dropdown-menu> </t-dropdown-menu>
<div class="show-child-permission">
<span>显示继承权限</span>
<t-switch v-model="showChildRolePermissions" />
</div>
</div> </div>
<div class="inherit"> <t-cell-group
<span class="label">显示继承权限</span> v-if="activeRole && grantablePermissionList.length"
<t-switch class="switch" v-model="showChildRolePermissions" /> class="list"
</div> theme="card"
<t-cell-group v-if="activeRole && grantablePermissionList.length" title="可配置的直接权限 P" theme="card"> >
<t-cell <t-cell
:class="{ inherited: isPermissionInherited(item.id) }"
v-for="item in grantablePermissionList" v-for="item in grantablePermissionList"
:key="item.id" :key="item.id"
:title="item.name || item.code" :title="item.name"
:class="{ inherited: isPermissionInherited(item.id) }" :description="item.code"
> >
<template #description>
<span class="code" v-text="item.code"></span>
</template>
<template #right-icon> <template #right-icon>
<t-switch <t-switch
:disabled="!canUpdateRolePermission || !canEditActiveRole || isPermissionInherited(item.id)" :disabled="!canUpdateRolePermission || !canEditActiveRole || isPermissionInherited(item.id)"
@@ -38,15 +40,6 @@
</t-cell> </t-cell>
</t-cell-group> </t-cell-group>
<t-empty v-else-if="!permissionStore.loading" description="暂无可配置的直接权限 P" /> <t-empty v-else-if="!permissionStore.loading" description="暂无可配置的直接权限 P" />
<t-button
v-if="canEditActiveRole"
v-can="rolePermissionWriteAccess"
class="save"
theme="primary"
@click="savePermissionList"
>
保存 P
</t-button>
</div> </div>
</template> </template>
@@ -54,7 +47,9 @@
import type { DropdownOption, DropdownValue } from "tdesign-mobile-vue"; import type { DropdownOption, DropdownValue } from "tdesign-mobile-vue";
import { Toast } from "tdesign-mobile-vue"; import { Toast } from "tdesign-mobile-vue";
import { type Permission, useUserStore } from "timi-web"; import { type Permission, useUserStore } from "timi-web";
import { useNavBarStore } from "@/store/navBarStore";
import { type Role, usePermissionSettingsStore } from "@/store/permissionSettingsStore"; import { type Role, usePermissionSettingsStore } from "@/store/permissionSettingsStore";
import { getErrorMessage } from "@/utils/error";
import { corePermissionCodes, coreRoleCodes } from "@/utils/permission"; import { corePermissionCodes, coreRoleCodes } from "@/utils/permission";
defineOptions({ defineOptions({
@@ -62,16 +57,13 @@ defineOptions({
}); });
const permissionStore = usePermissionSettingsStore(); const permissionStore = usePermissionSettingsStore();
const navBarStore = useNavBarStore();
const userStore = useUserStore(); const userStore = useUserStore();
const route = useRoute(); const route = useRoute();
// ---------- 角色选择 ---------- // ---------- 角色选择 ----------
const activeRoleId = ref(String(route.query.roleId || route.params.id || "")); const activeRoleId = ref(String(route.query.roleId || route.params.id || ""));
const checkedPermissionIds = ref<string[]>([]);
const unmanagedPermissionIds = ref<string[]>([]);
const inheritedPermissionIds = ref<string[]>([]);
const grantablePermissionList = ref<Permission[]>([]);
const candidateRoleList = ref<Role[]>([]); const candidateRoleList = ref<Role[]>([]);
const showChildRolePermissions = ref(false); const showChildRolePermissions = ref(false);
let permissionSyncSeq = 0; let permissionSyncSeq = 0;
@@ -94,16 +86,12 @@ const roleOptions = computed<DropdownOption[]>(() => candidateRoleList.value
disabled: false disabled: false
}))); })));
const rolePermissionWriteAccess = {
value: [
corePermissionCodes.rolePermission.create,
corePermissionCodes.rolePermission.delete
],
mode: "and"
} as const;
// ---------- 权限状态 ---------- // ---------- 权限状态 ----------
const checkedPermissionIds = ref<string[]>([]);
const unmanagedPermissionIds = ref<string[]>([]);
const inheritedPermissionIds = ref<string[]>([]);
const grantablePermissionList = ref<Permission[]>([]);
const checkedPermissionIdSet = computed(() => new Set(checkedPermissionIds.value)); const checkedPermissionIdSet = computed(() => new Set(checkedPermissionIds.value));
const inheritedPermissionIdSet = computed(() => new Set(inheritedPermissionIds.value)); const inheritedPermissionIdSet = computed(() => new Set(inheritedPermissionIds.value));
@@ -152,7 +140,7 @@ async function savePermissionList(): Promise<void> {
...checkedPermissionIds.value, ...checkedPermissionIds.value,
...unmanagedPermissionIds.value ...unmanagedPermissionIds.value
]))); ])));
Toast.success("P 已保存"); Toast.success("已保存");
await loadActiveRolePermissions(); await loadActiveRolePermissions();
} catch (error) { } catch (error) {
clearPermissionState(); clearPermissionState();
@@ -160,11 +148,25 @@ async function savePermissionList(): Promise<void> {
await loadAuthorizationScope(); await loadAuthorizationScope();
await loadActiveRolePermissions(); await loadActiveRolePermissions();
} finally { } finally {
Toast.error(getErrorMessage(error, "P 保存失败")); Toast.error(getErrorMessage(error, "保存失败"));
} }
} }
} }
function updateNavBar(): void {
navBarStore.clearRight();
if (canEditActiveRole.value && canUpdateRolePermission.value) {
navBarStore.setRightText("保存", "var(--td-brand-color)", savePermissionList);
}
}
watch([canEditActiveRole, canUpdateRolePermission], updateNavBar, { immediate: true });
onMounted(updateNavBar);
onUnmounted(() => {
navBarStore.clearRight();
});
// ---------- 权限勾选 ---------- // ---------- 权限勾选 ----------
function isPermissionChecked(permissionId: string): boolean { function isPermissionChecked(permissionId: string): boolean {
@@ -204,18 +206,7 @@ function clearPermissionState(): void {
// ---------- 授权范围 ---------- // ---------- 授权范围 ----------
async function loadAuthorizationScope(): Promise<void> { async function loadAuthorizationScope(): Promise<void> {
const roleList = isSystemAdmin.value ? permissionStore.roleList : await permissionStore.loadManageableRoles(); candidateRoleList.value = isSystemAdmin.value ? permissionStore.roleList : await permissionStore.loadManageableRoles();
candidateRoleList.value = roleList;
}
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;
} }
onMounted(async () => { onMounted(async () => {
@@ -232,7 +223,7 @@ onMounted(async () => {
.role-permission-assign { .role-permission-assign {
gap: 1rem; gap: 1rem;
display: flex; display: flex;
padding: calc(var(--app-nav-height) + 4.5rem) 0 6rem 0; padding: calc(var(--app-nav-height) + 4rem) 0 1rem 0;
flex-direction: column; flex-direction: column;
.header { .header {
@@ -241,81 +232,42 @@ onMounted(async () => {
right: 0; right: 0;
z-index: 20; z-index: 20;
display: flex; display: flex;
padding: 0 1rem;
position: fixed; position: fixed;
background: #fff; background: #fff;
box-shadow: var(--td-shadow-1); box-shadow: var(--td-shadow-1);
align-items: center; align-items: center;
justify-content: center; justify-content: space-between;
.role-selector::after { .role-selector::after {
display: none; display: none;
} }
}
.inherit { .show-child-permission {
gap: .75rem;
display: flex;
margin: 0 1rem;
padding: .75rem 1rem;
background: #fff;
align-items: center;
border-radius: .75rem;
justify-content: space-between;
.label {
color: #334155;
font-size: .875rem;
line-height: 1.25rem;
}
.switch {
--td-switch-checked-color: #14b8a6; --td-switch-checked-color: #14b8a6;
gap: .5rem;
display: flex;
align-items: center;
} }
} }
:deep(.t-cell__title) { .list {
min-width: 0;
}
:deep(.t-cell__title-text) { :deep(.t-cell) {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
:deep(.t-cell__description) { &.inherited {
min-width: 0;
}
:deep(.t-cell__right) { .t-cell__title-text {
flex: 0 0 auto; color: #0f766e;
margin-left: .75rem; }
}
.code { .t-switch--checked.t-switch--disabled {
width: 100%; background: #14b8a6;
display: block; opacity: 1;
overflow: hidden; }
white-space: nowrap; }
text-overflow: ellipsis;
}
.inherited {
:deep(.t-cell__title) {
color: #0f766e;
} }
:deep(.t-switch--checked.t-switch--disabled) {
background: #14b8a6;
opacity: 1;
}
}
.save {
left: 1rem;
right: 1rem;
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
position: fixed;
} }
} }
+1 -10
View File
@@ -54,6 +54,7 @@ import ManagedUserAPI from "@/api/ManagedUserAPI";
import { useCurrentStoreStore } from "@/store/currentStoreStore"; import { useCurrentStoreStore } from "@/store/currentStoreStore";
import type { Role } from "@/store/permissionSettingsStore"; import type { Role } from "@/store/permissionSettingsStore";
import type { ManagedUser } from "@/types/ManagedUser"; import type { ManagedUser } from "@/types/ManagedUser";
import { getErrorMessage } from "@/utils/error";
import { corePermissionCodes, gaoRoleCodes } from "@/utils/permission"; import { corePermissionCodes, gaoRoleCodes } from "@/utils/permission";
defineOptions({ defineOptions({
@@ -270,16 +271,6 @@ function getRoleId(role: UserRole | string): string {
return fullRole?.id || ""; return fullRole?.id || "";
} }
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;
}
function setRoleChecked(roleId: string, value: boolean): void { function setRoleChecked(roleId: string, value: boolean): void {
if (value && !checkedRoleIds.value.includes(roleId)) { if (value && !checkedRoleIds.value.includes(roleId)) {
checkedRoleIds.value.push(roleId); checkedRoleIds.value.push(roleId);
+2 -2
View File
@@ -73,7 +73,7 @@ const permissionRoutes: RouteRecordRaw[] = [
depth: 6, depth: 6,
navBarVisible: true, navBarVisible: true,
navBarCanBack: true, navBarCanBack: true,
navBarTitle: "角色直接权限 P", navBarTitle: "角色直接授权权限 P",
tabBarVisible: false, tabBarVisible: false,
bodyBackground: "#F5F5F8", bodyBackground: "#F5F5F8",
darkBodyBackground: "#10161f" darkBodyBackground: "#10161f"
@@ -147,7 +147,7 @@ const permissionRoutes: RouteRecordRaw[] = [
depth: 4, depth: 4,
navBarVisible: true, navBarVisible: true,
navBarCanBack: true, navBarCanBack: true,
navBarTitle: "角色直接权限 P", navBarTitle: "角色直接授权权限 P",
tabBarVisible: false, tabBarVisible: false,
bodyBackground: "#F5F5F8", bodyBackground: "#F5F5F8",
darkBodyBackground: "#10161f" darkBodyBackground: "#10161f"
+15
View File
@@ -0,0 +1,15 @@
/**
* 从未知错误值中提取可展示的错误文案。
*
* @param error 未知错误值
* @param fallback 无法提取错误文案时的备用文案
*/
export 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;
}