refactor file tree
This commit is contained in:
@@ -0,0 +1,335 @@
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user