346 lines
8.0 KiB
Vue
346 lines
8.0 KiB
Vue
<template>
|
||
<div class="management-tab">
|
||
<t-cell-group class="user-card" theme="card">
|
||
<t-cell
|
||
v-if="currentUser"
|
||
class="info"
|
||
arrow
|
||
hover
|
||
@click="go('/management/profile')"
|
||
>
|
||
<template #description>
|
||
<div v-if="currentUser?.phoneNo" v-text="currentUser.phoneNo" />
|
||
</template>
|
||
<template #title>
|
||
<div class="header">
|
||
<div class="name" v-text="currentUser.name" />
|
||
<gender-icon :value="currentUser.gender" />
|
||
<div class="roles">
|
||
<t-tag
|
||
v-for="role in currentUserRoleList"
|
||
:key="getRoleKey(role)"
|
||
size="small"
|
||
theme="success"
|
||
variant="outline"
|
||
:content="getRoleName(role)"
|
||
/>
|
||
<span v-if="!currentUserRoleList.length && !isRoleLoading" class="muted" v-text="'未配置角色'" />
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<template #leftIcon>
|
||
<t-image
|
||
class="avatar"
|
||
:src="currentUserAvatar"
|
||
:alt="currentUser.name"
|
||
fit="cover"
|
||
/>
|
||
</template>
|
||
</t-cell>
|
||
</t-cell-group>
|
||
<t-cell-group v-for="group in visibleMenuGroupList" :key="group.title" :title="group.title" theme="card">
|
||
<t-cell
|
||
v-for="item in group.items"
|
||
:key="item.path"
|
||
class="item"
|
||
:title="item.title"
|
||
arrow
|
||
hover
|
||
@click="go(item)"
|
||
>
|
||
<template #leftIcon>
|
||
<t-icon :name="item.icon" size="1.75rem" />
|
||
</template>
|
||
</t-cell>
|
||
</t-cell-group>
|
||
<footer class="footer">
|
||
<p>技术支持:夜雨</p>
|
||
<p>Copyright © 2026 imyeyu.com</p>
|
||
</footer>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
let settingTabScrollTop: number | undefined;
|
||
</script>
|
||
|
||
<script setup lang="ts">
|
||
import { onBeforeRouteLeave } from "vue-router";
|
||
import { type Role as UserRole, RoleAPI, UserAPI, useUserStore } from "timi-web";
|
||
import { buildFallbackAvatar } from "@/utils/avatar";
|
||
import { moduleCode } from "@/store/permissionSettingsStore";
|
||
import { corePermissionCodes, gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||
|
||
type UserAvatarSource = Parameters<typeof UserAPI.getAvatarURL>[0];
|
||
type MenuItem = {
|
||
icon: string;
|
||
path: string;
|
||
title: string;
|
||
visible: boolean;
|
||
};
|
||
|
||
type MenuGroup = {
|
||
items: MenuItem[];
|
||
title: string;
|
||
};
|
||
|
||
defineOptions({
|
||
name: "SettingTab"
|
||
});
|
||
|
||
const router = useRouter();
|
||
const route = useRoute();
|
||
const userStore = useUserStore();
|
||
|
||
function getSettingTabPage(): HTMLElement | undefined {
|
||
return Array.from(document.querySelectorAll<HTMLElement>(".page-transition .pages"))
|
||
.find(element => !!element.querySelector(".management-tab"));
|
||
}
|
||
|
||
const currentUser = computed(() => userStore.loginUser.user);
|
||
|
||
const currentUserAvatar = computed(() => buildUserAvatar(currentUser.value));
|
||
const currentUserRoleList = ref<UserRole[]>([]);
|
||
const isRoleLoading = ref(false);
|
||
const canManagePermission = computed(() => userStore.hasPermissions([
|
||
corePermissionCodes.role.read,
|
||
corePermissionCodes.permission.read
|
||
], "and"));
|
||
const canManageUser = computed(() => userStore.hasRoles([gaoRoleCodes.globalManager, gaoRoleCodes.storeManager], "or"));
|
||
const visibleMenuGroupList = computed<MenuGroup[]>(() => menuGroupList.value
|
||
.map(group => ({
|
||
...group,
|
||
items: group.items.filter(item => item.visible)
|
||
}))
|
||
.filter(group => group.items.length));
|
||
|
||
const menuGroupList = computed<MenuGroup[]>(() => [
|
||
{
|
||
title: "客户",
|
||
items: [
|
||
{
|
||
icon: "usergroup",
|
||
path: "/management/customers",
|
||
title: "客户管理",
|
||
visible: userStore.hasPermissions(permissionCodes.customer.read)
|
||
},
|
||
{
|
||
icon: "chart-combo",
|
||
path: "/management/customer-data-charts",
|
||
title: "客户统计数据",
|
||
visible: userStore.hasPermissions(permissionCodes.stat.read)
|
||
},
|
||
{
|
||
icon: "tag",
|
||
path: "/management/tags",
|
||
title: "标签管理",
|
||
visible: userStore.hasPermissions(corePermissionCodes.tag.read)
|
||
}
|
||
]
|
||
},
|
||
{
|
||
title: "登记",
|
||
items: [
|
||
{
|
||
icon: "task",
|
||
path: "/management/registration-events",
|
||
title: "登记事件",
|
||
visible: userStore.hasPermissions([permissionCodes.event.read, permissionCodes.user.read], "and")
|
||
},
|
||
{
|
||
icon: "history",
|
||
path: "/management/registration-event-records",
|
||
title: "登记事件记录",
|
||
visible: userStore.hasPermissions(permissionCodes.eventRecord.read)
|
||
},
|
||
{
|
||
icon: "order-list",
|
||
path: "/management/registration-event-record-rank",
|
||
title: "登记记录排行",
|
||
visible: userStore.hasPermissions(permissionCodes.stat.read)
|
||
}
|
||
]
|
||
},
|
||
{
|
||
title: "积分",
|
||
items: [
|
||
{
|
||
icon: "git-merge",
|
||
path: "/management/points/rules",
|
||
title: "积分规则",
|
||
visible: userStore.hasPermissions(permissionCodes.pointRule.read)
|
||
},
|
||
{
|
||
icon: "wallet",
|
||
path: "/management/points/account",
|
||
title: "积分账户",
|
||
visible: userStore.hasPermissions(permissionCodes.point.accountRead)
|
||
},
|
||
{
|
||
icon: "bill",
|
||
path: "/management/points/ledger",
|
||
title: "积分流水",
|
||
visible: userStore.hasPermissions(permissionCodes.point.ledgerRead)
|
||
},
|
||
{
|
||
icon: "user-marked",
|
||
path: "/management/points/adjust",
|
||
title: "人工调整积分",
|
||
visible: userStore.hasPermissions(permissionCodes.point.adjust)
|
||
}
|
||
]
|
||
},
|
||
{
|
||
title: "系统",
|
||
items: [
|
||
{
|
||
icon: "setting",
|
||
path: "/management/settings",
|
||
title: "应用设置",
|
||
visible: true
|
||
},
|
||
{
|
||
icon: "secured",
|
||
path: "/management/permissions",
|
||
title: "权限设置",
|
||
visible: canManagePermission.value
|
||
},
|
||
{
|
||
icon: "usergroup",
|
||
path: "/management/users",
|
||
title: "用户管理",
|
||
visible: canManageUser.value && userStore.hasPermissions(permissionCodes.user.read)
|
||
},
|
||
{
|
||
icon: "notification",
|
||
path: "/management/notifications",
|
||
title: "通知管理",
|
||
visible: true
|
||
},
|
||
{
|
||
icon: "qrcode",
|
||
path: "/management/qr-code",
|
||
title: "生成客户二维码",
|
||
visible: userStore.hasPermissions(permissionCodes.qrCode.create)
|
||
}
|
||
]
|
||
}
|
||
]);
|
||
|
||
function getRoleKey(role: UserRole): string {
|
||
return role.id || `${role.moduleCode}:${role.code}`;
|
||
}
|
||
|
||
function getRoleName(role: UserRole): string {
|
||
return role.name || role.code || "未配置角色";
|
||
}
|
||
|
||
async function loadCurrentRoleList(): Promise<void> {
|
||
if (!currentUser.value) {
|
||
return;
|
||
}
|
||
|
||
isRoleLoading.value = true;
|
||
try {
|
||
currentUserRoleList.value = await RoleAPI.currentDetailList(moduleCode);
|
||
} finally {
|
||
isRoleLoading.value = false;
|
||
}
|
||
}
|
||
|
||
function buildUserAvatar(user?: UserAvatarSource): string {
|
||
return UserAPI.getAvatarURL(user) || buildFallbackAvatar(currentUser.value?.name, "#0f766e");
|
||
}
|
||
|
||
async function go(item: MenuItem | string): Promise<void> {
|
||
if (typeof item === "string") {
|
||
await router.push(item);
|
||
return;
|
||
}
|
||
|
||
await router.push(item.path);
|
||
}
|
||
|
||
function restoreScrollPosition(): void {
|
||
if (settingTabScrollTop === undefined || route.path !== "/management") {
|
||
return;
|
||
}
|
||
const scrollTop = settingTabScrollTop;
|
||
const settingTabPage = getSettingTabPage();
|
||
if (!settingTabPage) {
|
||
return;
|
||
}
|
||
settingTabPage.scrollTop = scrollTop;
|
||
settingTabScrollTop = undefined;
|
||
}
|
||
|
||
onMounted(async () => {
|
||
restoreScrollPosition();
|
||
await loadCurrentRoleList();
|
||
});
|
||
onBeforeRouteLeave(() => {
|
||
const settingTabPage = getSettingTabPage();
|
||
const scrollElement = document.scrollingElement;
|
||
settingTabScrollTop = settingTabPage?.scrollTop ?? scrollElement?.scrollTop ?? window.scrollY;
|
||
});
|
||
</script>
|
||
|
||
<style scoped lang="less">
|
||
.management-tab {
|
||
gap: .5rem;
|
||
display: flex;
|
||
padding: calc(var(--app-nav-height) + 1rem) 0 5rem 0;
|
||
flex-direction: column;
|
||
|
||
.user-card {
|
||
|
||
.info {
|
||
padding: 10px;
|
||
|
||
.avatar {
|
||
width: 4rem;
|
||
height: 4rem;
|
||
border-radius: .875rem;
|
||
}
|
||
|
||
.header {
|
||
gap: .5rem;
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.roles {
|
||
gap: .25rem;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.footer {
|
||
padding: 1.25rem 1rem calc(1rem + env(safe-area-inset-bottom, 0));
|
||
text-align: center;
|
||
|
||
p {
|
||
margin: 0;
|
||
color: #52607a;
|
||
font-size: .8125rem;
|
||
line-height: 1.8;
|
||
}
|
||
}
|
||
}
|
||
|
||
:global(html.theme-dark) {
|
||
.management-tab {
|
||
.office {
|
||
color: #94a3b8;
|
||
}
|
||
|
||
.footer p {
|
||
color: #94a3b8;
|
||
}
|
||
|
||
}
|
||
}
|
||
</style>
|