336 lines
8.9 KiB
Vue
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>
|
|
|