refactor file tree

This commit is contained in:
Timi
2026-08-11 15:24:36 +08:00
parent 575d41b2e2
commit 819c1623eb
42 changed files with 86 additions and 86 deletions
+325
View File
@@ -0,0 +1,325 @@
<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>
</template>
<template #leftIcon>
<t-image
class="avatar"
:src="currentUserAvatar"
:alt="currentUser.name"
fit="cover"
/>
</template>
</t-cell>
<t-cell
v-if="canViewStore"
class="cell"
title="当前门店"
:note="currentStoreStore.currentStoreName"
:arrow="canSelectStore"
:hover="canSelectStore"
@click="openStorePicker"
>
<template #leftIcon>
<t-icon name="shop" size="1.75rem" />
</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>版本 1.0.0</p>
<p>技术支持:夜雨</p>
<p>Copyright © 2026 imyeyu.com</p>
</footer>
<t-popup v-model="storePickerVisible" placement="bottom">
<t-picker
v-model="storePickerDraft"
:columns="storePickerColumns"
title="当前门店"
@cancel="storePickerVisible = false"
@confirm="confirmStorePicker"
/>
</t-popup>
</div>
</template>
<script setup lang="ts">
import { UserAPI, useUserStore } from "timi-web";
import { Toast } from "tdesign-mobile-vue";
import { useCurrentStoreStore } from "@/store/currentStoreStore";
import { buildFallbackAvatar } from "@/utils/avatar";
import { corePermissionCodes, gaoRoleCodes, gaoStoreScopePermissionCodes, permissionCodes } from "@/utils/permission";
type UserAvatarSource = Parameters<typeof UserAPI.getAvatarURL>[0];
type PickerValue = string | number;
type MenuItem = {
icon: string;
path: string;
title: string;
visible: boolean;
action?: "store";
};
type MenuGroup = {
items: MenuItem[];
title: string;
};
defineOptions({
name: "SettingTab"
});
const router = useRouter();
const userStore = useUserStore();
const currentStoreStore = useCurrentStoreStore();
const currentUser = computed(() => userStore.loginUser.user);
const currentUserAvatar = computed(() => buildUserAvatar(currentUser.value));
const canSelectStore = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
const canLoadLoginStore = computed(() => userStore.hasPermissions(gaoStoreScopePermissionCodes, "or"));
const canViewStore = computed(() => canSelectStore.value || canLoadLoginStore.value);
const canManageStore = computed(() => userStore.hasRoles([gaoRoleCodes.globalManager, gaoRoleCodes.storeManager], "or"));
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 storePickerVisible = ref(false);
const storePickerDraft = ref<PickerValue[]>([]);
const storePickerColumns = computed(() => [
currentStoreStore.storeList.map(item => ({
label: item.name || item.code || "未命名门店",
value: item.id || ""
})).filter(item => item.value)
]);
function openStorePicker(): void {
if (!canSelectStore.value) {
return;
}
if (!currentStoreStore.storeList.length) {
Toast.warning("暂无可选门店");
return;
}
storePickerDraft.value = [currentStoreStore.currentStoreId];
storePickerVisible.value = true;
}
function confirmStorePicker(): void {
const storeId = String(storePickerDraft.value[0] || "");
if (!storeId) {
return;
}
currentStoreStore.selectStore(storeId);
storePickerVisible.value = false;
Toast.success("当前门店已切换");
}
const menuGroupList = computed<MenuGroup[]>(() => [
{
title: "客户",
items: [
{
icon: "shop",
path: "/management/stores",
title: "门店管理",
action: "store",
visible: canManageStore.value && userStore.hasPermissions(permissionCodes.store.read)
},
{
icon: "usergroup",
path: "/management/customers",
title: "客户管理",
visible: userStore.hasPermissions(permissionCodes.customer.read)
},
{
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: "usergroup",
path: "/management/customer-data-charts",
title: "客户统计数据",
visible: userStore.hasPermissions(permissionCodes.stat.read)
},
{
icon: "chart",
path: "/management/data-charts",
title: "全局统计数据",
visible: userStore.hasPermissions(permissionCodes.stat.read)
}
]
},
{
title: "系统",
items: [
{
icon: "setting",
path: "/management/settings",
title: "应用设置",
visible: true
},
{
icon: "secured",
path: "/management/permissions",
title: "权限设置",
visible: canManagePermission.value
},
{
icon: "root-list",
path: "/management/users",
title: "用户管理",
visible: canManageUser.value && userStore.hasPermissions(permissionCodes.user.read)
},
{
icon: "qrcode",
path: "/management/qr-code",
title: "生成客户二维码",
visible: userStore.hasPermissions(permissionCodes.qrCode.create)
}
]
}
]);
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;
}
if (item.action === "store" && !canSelectStore.value) {
const store = await currentStoreStore.loadLoginStore();
if (!store?.id) {
Toast.warning("未绑定所属门店");
return;
}
await router.push(`/management/stores/${store.id}`);
return;
}
await router.push(item.path);
}
onMounted(async () => {
if (canSelectStore.value) {
await currentStoreStore.loadStoreList();
} else if (canLoadLoginStore.value) {
await currentStoreStore.loadLoginStore();
}
});
</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;
}
}
}
.store-card {
.cell {
padding: 10px;
}
}
.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>