Files
gao-customer-h5/src/pages/user/UserRoleAssign.vue
T
2026-08-11 15:24:36 +08:00

336 lines
8.9 KiB
Vue

<template>
<div v-can="corePermissionCodes.userRole.read" class="user-role-assign">
<div class="user-menu">
<t-dropdown-menu>
<t-dropdown-item
v-if="isGlobalManager"
v-model="selectedStoreId"
:label="storeFilterLabel"
:options="storeOptions"
@change="handleStoreChange"
/>
<t-dropdown-item
:label="activeUserLabel"
:options="userOptions"
:value="activeManagedUserId"
@change="handleUserChange"
/>
</t-dropdown-menu>
</div>
<t-cell-group v-if="activeUser && roleList.length" theme="card">
<t-cell
v-for="item in roleList"
:key="item.id"
:title="item.name"
:description="item.code"
>
<template #right-icon>
<t-switch
v-can="userRoleWriteAccess"
:model-value="isRoleChecked(item.id)"
@change="value => setRoleChecked(item.id, value === true)"
/>
</template>
</t-cell>
</t-cell-group>
<t-empty v-else-if="!isLoading" description="暂无可授权角色" />
<t-button
v-can="userRoleWriteAccess"
class="save"
theme="primary"
:loading="isSaving"
@click="saveRoleList"
>
保存授权
</t-button>
</div>
</template>
<script setup lang="ts">
import type { DropdownOption, DropdownValue } from "tdesign-mobile-vue";
import { Toast } from "tdesign-mobile-vue";
import { RoleAPI, type ModuleCode, type Role as UserRole, useUserStore } from "timi-web";
import ManagedUserAPI from "@/api/ManagedUserAPI";
import { useCurrentStoreStore } from "@/store/currentStoreStore";
import type { Role } from "@/store/permissionSettingsStore";
import type { ManagedUser } from "@/types/ManagedUser";
import { corePermissionCodes, gaoRoleCodes } from "@/utils/permission";
defineOptions({
name: "UserRoleAssign"
});
const route = useRoute();
const moduleCode: ModuleCode = "GAO";
const allStoreFilterValue = "__ALL_STORES__";
// ---------- 用户选择 ----------
const userStore = useUserStore();
const currentStoreStore = useCurrentStoreStore();
const userList = ref<ManagedUser[]>([]);
const roleList = ref<Role[]>([]);
const routeManagedUserId = computed(() => String(route.params.id || route.query.managedUserId || ""));
const activeManagedUserId = ref(routeManagedUserId.value);
const selectedStoreId = ref(allStoreFilterValue);
const loginStoreId = ref("");
const checkedRoleIds = ref<string[]>([]);
const isLoading = ref(false);
const isSaving = ref(false);
let roleSyncSeq = 0;
const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
const storeOptions = computed(() => [
{ label: "全部门店", value: allStoreFilterValue },
...currentStoreStore.storeList
.map(item => ({
label: item.name || item.code || "未命名门店",
value: item.id || ""
}))
.filter(item => item.value)
]);
const storeFilterLabel = computed(() => storeOptions.value.find(item => item.value === selectedStoreId.value)?.label || "全部门店");
const userListOptions = computed(() => {
if (isGlobalManager.value) {
return selectedStoreId.value === allStoreFilterValue
? { allStores: true }
: { storeId: selectedStoreId.value };
}
return { storeId: loginStoreId.value };
});
// ---------- 权限 ----------
const userRoleWriteAccess = {
value: [
corePermissionCodes.userRole.create,
corePermissionCodes.userRole.delete
],
mode: "and"
} as const;
// ---------- 用户状态 ----------
const activeUser = computed(() => userList.value.find(item => item.id === activeManagedUserId.value));
const activeUserLabel = computed(() => activeUser.value ? `当前用户:${getUserName(activeUser.value)}` : "选择用户");
const userOptions = computed<DropdownOption[]>(() => userList.value
.filter(item => !!item.id)
.map(item => ({
label: getUserName(item),
value: item.id as string,
disabled: false
})));
// ---------- 用户切换 ----------
async function handleUserChange(value: DropdownValue): Promise<void> {
activeManagedUserId.value = Array.isArray(value) ? String(value[0] || "") : String(value);
await loadActiveUserRoles();
}
async function handleStoreChange(value: DropdownValue): Promise<void> {
if (Array.isArray(value)) {
return;
}
selectedStoreId.value = String(value);
await loadUserList();
}
// ---------- 页面数据 ----------
async function loadStoreScope(): Promise<void> {
if (isGlobalManager.value) {
await currentStoreStore.loadStoreList();
const queryStoreId = String(route.query.storeId || "");
selectedStoreId.value = currentStoreStore.storeList.some(item => item.id === queryStoreId)
? queryStoreId
: allStoreFilterValue;
return;
}
const store = await currentStoreStore.loadLoginStore();
loginStoreId.value = store?.id || "";
}
async function loadData(): Promise<void> {
isLoading.value = true;
try {
await loadStoreScope();
roleList.value = await loadRoleList();
await loadUserList();
} finally {
isLoading.value = false;
}
}
async function loadUserList(): Promise<void> {
const result = await ManagedUserAPI.list({
index: 0,
size: 100
}, userListOptions.value);
userList.value = result.list || [];
if (!userList.value.some(item => item.id === activeManagedUserId.value)) {
activeManagedUserId.value = userList.value[0]?.id || "";
}
await loadActiveUserRoles();
}
// ---------- 角色同步 ----------
async function loadActiveUserRoles(): Promise<void> {
const managedUserId = activeManagedUserId.value;
const managedUser = activeUser.value;
if (!managedUserId || !managedUser?.userId) {
checkedRoleIds.value = [];
return;
}
const currentSeq = roleSyncSeq + 1;
roleSyncSeq = currentSeq;
const roles = await RoleAPI.authorizedList(managedUser.userId, moduleCode);
if (currentSeq !== roleSyncSeq || activeManagedUserId.value !== managedUserId) {
return;
}
const availableRoleIdSet = new Set(roleList.value.map(item => item.id));
checkedRoleIds.value = roles
.map(getRoleId)
.filter((item): item is string => !!item && availableRoleIdSet.has(item));
}
// ---------- 保存 ----------
async function saveRoleList(): Promise<void> {
const current = activeUser.value;
if (!current?.id || !current.userId) {
Toast.warning("请选择用户");
return;
}
isSaving.value = true;
try {
await RoleAPI.authorize({
userId: current.userId,
moduleCode: moduleCode,
roleIdList: [...checkedRoleIds.value]
});
await loadActiveUserRoles();
Toast.success("用户角色已保存");
} catch (error) {
checkedRoleIds.value = [];
try {
await reloadAuthorizationScope();
await loadActiveUserRoles();
} finally {
Toast.error(getErrorMessage(error, "用户角色授权失败"));
}
} finally {
isSaving.value = false;
}
}
// ---------- 展示文案 ----------
function getUserName(item: ManagedUser): string {
return item.user?.nick || item.user?.name || item.user?.phoneNo || "未命名用户";
}
// ---------- 角色列表 ----------
async function loadRoleList(): Promise<Role[]> {
const result = await RoleAPI.grantableList(moduleCode);
return (result || [])
.filter((role): role is UserRole & { id: string } => Boolean(role.id))
.map(role => ({
...role,
id: role.id,
permissionIdList: role.permissionIdList || role.permissionList?.map(item => item.id) || [],
childRoleIdList: role.childRoleIdList || role.childRoleList?.map(item => item.id).filter((id): id is string => Boolean(id)) || [],
delegationPermissionIdList: role.delegationPermissionIdList || role.delegationPermissionList?.map(item => item.id) || []
}));
}
async function reloadAuthorizationScope(): Promise<void> {
roleList.value = await loadRoleList();
}
// ---------- 角色勾选 ----------
function isRoleChecked(roleId: string): boolean {
return checkedRoleIds.value.includes(roleId);
}
function getRoleId(role: UserRole | string): string {
if (typeof role !== "string") {
return role.id || "";
}
const fullRole = roleList.value.find(item => item.code === role || `${item.moduleCode}:${item.code}` === role);
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 {
if (value && !checkedRoleIds.value.includes(roleId)) {
checkedRoleIds.value.push(roleId);
return;
}
if (!value) {
checkedRoleIds.value = checkedRoleIds.value.filter(item => item !== roleId);
}
}
onMounted(async () => {
try {
await loadData();
} catch {
Toast.error("用户角色加载失败");
}
});
</script>
<style scoped lang="less">
.user-role-assign {
gap: 1rem;
display: flex;
padding: calc(var(--app-nav-height) + 4.5rem) 0 6rem 0;
flex-direction: column;
.user-menu {
top: var(--app-nav-height);
left: 0;
right: 0;
z-index: 20;
position: fixed;
background: #fff;
}
.save {
left: 1rem;
right: 1rem;
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
position: fixed;
}
}
:global(html.theme-dark) {
.user-role-assign {
.user-menu {
background: #0f172a;
}
}
}
</style>