Files
gao-customer-h5/src/layout/MainLayout.vue
T
2026-09-02 12:56:22 +08:00

181 lines
4.1 KiB
Vue

<template>
<div class="main-layout">
<app-nav-bar @height-change="onChangeNavBarHeight" />
<div class="router-view">
<page-transition />
</div>
<t-tab-bar
v-model="tabVal"
:class="{ 'is-hidden': !tabBarStore.isShowing }"
class="tab-bar glass-white"
shape="round"
theme="tag"
:split="false"
:z-index="999"
:disabled="!tabBarStore.isShowing"
@change="onChangeTab"
>
<t-tab-bar-item v-for="item in list" :key="item.value" class="bg-transparent" :value="item.value">
<span v-text="item.text"></span>
<template #icon>
<t-icon :name="item.icon" />
</template>
</t-tab-bar-item>
</t-tab-bar>
</div>
</template>
<script setup lang="ts">
import AppNavBar from "@/components/navbar/index.vue";
import PageTransition from "@/components/PageTransition.vue";
import { useTabBarStore } from "@/store/tabBarStore";
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
import { useUserStore } from "timi-web-api";
const route = useRoute();
const router = useRouter();
const tabBarStore = useTabBarStore();
const userStore = useUserStore();
// ---------- 导航边距 ----------
const navBarHeight = ref("0px");
function onChangeNavBarHeight(value: string): void {
navBarHeight.value = value;
}
// ---------- Tab 切换 ----------
type TabItem = {
value: string;
icon: string;
text: string;
permission?: string | string[];
permissionMode?: "and" | "or";
role?: string | string[];
roleMode?: "and" | "or";
};
const tabVal = ref(route.path);
const tabList: TabItem[] = [
{
value: "/customers",
icon: "usergroup",
text: "客户",
permission: [permissionCodes.customer.create, permissionCodes.eventRecord.create]
},
{ value: "/product", icon: "layers", text: "产品", role: gaoRoleCodes.storeManager },
{ value: "/store", icon: "shop-3", text: "门店", permission: permissionCodes.stat.read },
{ value: "/management", icon: "setting-1", text: "管理" }
];
const list = computed(() => tabList.filter(item => (
(!item.permission || userStore.hasPermissions(item.permission, item.permissionMode))
&& (!item.role || userStore.hasRoles(item.role, item.roleMode))
)));
watch(() => route.path, (newPath: string) => {
tabVal.value = resolveTabValue(newPath);
}, { immediate: true });
async function syncVisibleTab(): Promise<void> {
const visiblePath = resolveVisibleTabValue(route.path);
if (tabVal.value !== visiblePath) {
tabVal.value = visiblePath;
}
if (route.path === "/customers" && visiblePath !== "/customers") {
await router.replace(visiblePath);
}
}
watch(list, syncVisibleTab, { immediate: true });
function resolveTabValue(path: string): string {
if (path.startsWith("/customers")) {
return "/customers";
}
if (path.startsWith("/store")) {
return "/store";
}
if (path.startsWith("/product")) {
return "/product";
}
if (path.startsWith("/management")) {
return "/management";
}
return path;
}
function resolveVisibleTabValue(path: string): string {
const tabPath = resolveTabValue(path);
if (list.value.some(item => item.value === tabPath)) {
return tabPath;
}
return list.value[0]?.value || tabPath;
}
function onChangeTab(value: string | number): void {
const path = String(value);
if (resolveTabValue(route.path) === path) {
return;
}
router.push(path);
}
// ---------- Tab 边距 ----------
const isTabBarPaddingNeeded = computed(() => {
if (!tabBarStore.isShowing) {
return false;
}
return route.meta.tabBarPadding !== false;
});
const tabBarHeight = computed(() => {
if (isTabBarPaddingNeeded.value) {
return "6rem";
}
return "0rem";
});
</script>
<style scoped lang="less">
.main-layout {
--app-nav-height: v-bind(navBarHeight);
--app-tab-height: v-bind(tabBarHeight);
height: 100%;
transition: padding-bottom .24s ease;
.router-view {
height: 100%;
}
.tab-bar {
--td-tab-bar-height: 52px;
bottom: 1rem;
opacity: 1;
transform: translateY(0);
backdrop-filter: blur(10px);
pointer-events: auto;
transition: opacity 520ms, transform 520ms var(--tui-bezier);
&.is-hidden {
opacity: 0;
pointer-events: none;
transform: translateY(calc(100% + 1rem));
}
}
}
</style>