init project

This commit is contained in:
Timi
2026-08-01 18:01:23 +08:00
parent 286fa1ba56
commit 11c48bf00c
107 changed files with 19350 additions and 1 deletions
+251
View File
@@ -0,0 +1,251 @@
<template>
<div class="main-layout">
<t-navbar
ref="navBarRef"
v-if="navBarStore.isShowing"
class="nav-bar"
:left-arrow="!!navBarStore.canBack"
@left-click="doBack"
>
<template #title>
<div id="app-nav-title" class="nav-title-slot">
<span v-if="!navBarStore.customTitleVisible" class="nav-title-text" v-text="navBarStore.title" />
</div>
</template>
<template v-if="navBarStore.customRightVisible || navBarStore.rightIcon || navBarStore.rightText" #right>
<div v-if="navBarStore.customRightVisible" id="app-nav-right" class="nav-right-slot" />
<button
v-else-if="navBarStore.rightIcon"
class="nav-btn"
type="button"
@click="navBarStore.rightAction?.()"
>
<t-icon :name="navBarStore.rightIcon" size="1.25rem" />
</button>
<span
v-else
v-text="navBarStore.rightText"
class="nav-text"
:style="{ color: navBarStore.rightTextColor ? navBarStore.rightTextColor : '#333' }"
:class="{ clickable: !!navBarStore.rightAction }"
@click="navBarStore.rightAction?.()"
></span>
</template>
</t-navbar>
<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"
: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 PageTransition from "@/components/PageTransition.vue";
import {useNavBarStore} from "@/store/navBarStore";
import {useTabBarStore} from "@/store/tabBarStore";
const route = useRoute();
const router = useRouter();
const navBarStore = useNavBarStore();
const tabBarStore = useTabBarStore();
// ---------- 导航高度 ----------
const navBarRef = ref();
let navBarResizeObs: ResizeObserver | undefined;
onMounted(() => {
if (!navBarRef.value) {
return;
}
navBarResizeObs = new ResizeObserver((entries) => {
for (const entry of entries) {
navBarStore.setHeight(entry.contentRect.height);
return;
}
});
navBarResizeObs.observe(navBarRef.value.$el.querySelector(".t-navbar__content"));
});
onUnmounted(() => {
if (navBarResizeObs) {
navBarResizeObs.disconnect();
}
});
// ---------- 导航返回 ----------
function doBack(): void {
if (navBarStore.backTo) {
router.push(navBarStore.backTo);
navBarStore.setBackTo(undefined);
return;
}
router.back();
}
// ---------- 导航边距 ----------
const navBarHeight = computed(() => {
if (navBarStore.isShowing) {
return "48px";
}
return "0px";
});
// ---------- Tab 切换 ----------
const tabVal = ref(route.path);
const list = [
{ value: "/customers", icon: "usergroup", text: "客户" },
{ value: "/management", icon: "setting-1", text: "管理" }
];
watch(() => route.path, (newPath: string) => {
tabVal.value = resolveTabValue(newPath);
}, { immediate: true });
function resolveTabValue(path: string): string {
if (path.startsWith("/customers")) {
return "/customers";
}
if (path.startsWith("/management")) {
return "/management";
}
return path;
}
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;
.nav-bar {
z-index: 999;
:deep(.t-navbar__content) {
color: var(--tui-black, #000);
overflow: visible;
box-shadow: 0 3px 8px rgba(0, 0, 0, .1);
background: rgba(250, 250, 250, .8);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.nav-btn {
padding: 0;
border: none;
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--td-text-color-primary, #333);
background: transparent;
}
.nav-title-slot {
min-width: 0;
}
.nav-title-text {
overflow: hidden;
display: block;
white-space: nowrap;
text-overflow: ellipsis;
}
.nav-right-slot {
width: 6rem;
height: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
}
.nav-text {
cursor: default;
&.clickable {
cursor: pointer;
}
}
}
:deep(.t-navbar__right),
:deep(.t-navbar__right-content) {
overflow: visible;
}
.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>