ow
This commit is contained in:
1 parent
32f237c4bf
commit
8a2adb0a92
18 files changed
+418
-53
No files matched your search
@@ -0,0 +1,12 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
@@ -69,6 +69,15 @@ export function clearLoginState() {
|
|||||||
uni.removeStorageSync(JWT_STORAGE_KEY);
|
uni.removeStorageSync(JWT_STORAGE_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已有登录会话:只认请求层的 AccessToken(令牌唯一来源,与 nav-guard 口径一致)。
|
||||||
|
* 不在此判过期——AccessToken 过期时请求层会拿 RefreshToken 自动续期,续期失败再 goLogin 退回登录页,
|
||||||
|
* 所以"登录过"就足以决定冷启动是否直接进 dashboard。
|
||||||
|
*/
|
||||||
|
export function hasLoginSession(): boolean {
|
||||||
|
return !!getJwtInfo()?.AccessToken;
|
||||||
|
}
|
||||||
|
|
||||||
function getToken(): string {
|
function getToken(): string {
|
||||||
return getJwtInfo()?.AccessToken ?? "";
|
return getJwtInfo()?.AccessToken ?? "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,9 +188,8 @@ defineExpose({ refresh, clearSelection, selectedRows });
|
|||||||
{{ filterCount }}
|
{{ filterCount }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
<!-- 页面可在此放次级动作按钮(如"新增"),排在筛选右侧、只留图标 -->
|
||||||
<view v-if="$slots.toolbar" class="toolbar-extra">
|
<slot name="toolbar-actions" />
|
||||||
<slot name="toolbar" />
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -302,17 +301,6 @@ defineExpose({ refresh, clearSelection, selectedRows });
|
|||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 页面自定义的次级工具(如"新增")走白底一行,与搜索栏区分 */
|
|
||||||
.toolbar-extra {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
padding: 4rpx 24rpx 16rpx;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-bottom: 1rpx solid $u-border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { $u } from "uview-pro";
|
import { $u, useLocale } from "uview-pro";
|
||||||
import { ref } from "vue";
|
import { computed, onMounted } from "vue";
|
||||||
|
import { PAGE_PATHS, pageUrl, TAB_PAGES } from "@/router/routes";
|
||||||
import { useTabbarStore } from "@/stores";
|
import { useTabbarStore } from "@/stores";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@@ -16,14 +17,82 @@ defineOptions({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { tabbarList } = useTabbarStore();
|
const { t } = useLocale();
|
||||||
|
const store = useTabbarStore();
|
||||||
|
|
||||||
const current = ref<number>(0);
|
/**
|
||||||
|
* 把 store 里的"结构配置"翻译成 u-tabbar 需要的 item:文字按当前语言 t(langKey) 取,
|
||||||
|
* 因此切换语言时 tab 文案会自动跟着变(t 依赖 currentLocale,computed 会重新求值)。
|
||||||
|
*
|
||||||
|
* props 收进一个对象再 v-bind:既避开长标签被格式化器折行后触发 html-indent 对撞,
|
||||||
|
* 也集中覆盖 u-tabbar 几个默认值不合口味的 prop:
|
||||||
|
* - hideTabBar 必须 false:其 onMounted 默认调 uni.hideTabBar(),本项目是自定义 tabbar
|
||||||
|
* (无原生 pages.json tabBar),该调用必 reject 成 "hideTabBar:fail not TabBar page" 并被
|
||||||
|
* 全局错误边界弹成错误框。
|
||||||
|
* - midButton 必须 true:否则 item.midButton 的凸起样式不生效(中心「多媒体」大按钮)。
|
||||||
|
*/
|
||||||
|
const tabbarProps = computed(() => ({
|
||||||
|
modelValue: store.activeIndex,
|
||||||
|
list: store.tabs.map((tab, index) => ({
|
||||||
|
text: t(tab.langKey),
|
||||||
|
iconPath: tab.iconPath,
|
||||||
|
selectedIconPath: tab.selectedIconPath,
|
||||||
|
midButton: tab.midButton,
|
||||||
|
count: store.badges[index] ?? 0,
|
||||||
|
})),
|
||||||
|
activeColor: $u.color.primary,
|
||||||
|
midButton: true,
|
||||||
|
// 中心凸起的圆标图片尺寸:显式带单位,撑满 u-tabbar 写死的 100rpx 凸起圆(留一点白边当描边)
|
||||||
|
midButtonSize: "100rpx",
|
||||||
|
iconSize: 40,
|
||||||
|
textSize: 26,
|
||||||
|
hideTabBar: false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 由当前页面真实路径反推 tab 下标。
|
||||||
|
* 不依赖 activeIndex 的持久值:冷启动直达 / 403 回首页时持久值可能是上一次会话的旧下标,
|
||||||
|
* 按路由算才能保证高亮一定落在真正显示的这一页上。getCurrentPages 给的是不带前导斜杠的真实路径。
|
||||||
|
*/
|
||||||
|
function indexOfCurrentRoute(): number {
|
||||||
|
const pages = getCurrentPages();
|
||||||
|
const route = pages[pages.length - 1]?.route ?? "";
|
||||||
|
return TAB_PAGES.findIndex(name => PAGE_PATHS[name] === route);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const index = indexOfCurrentRoute();
|
||||||
|
if (index >= 0) {
|
||||||
|
store.setActiveIndex(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/** item 未配 pagePath,u-tabbar 只会 emit change 而不自行 switchTab;跳转在这里做 */
|
||||||
|
function onChange(index: number): void {
|
||||||
|
if (index === store.activeIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
store.setActiveIndex(index);
|
||||||
|
// reLaunch 清空页面栈,符合"切 tab 不叠加历史"的直觉,也会被 nav-guard 正常拦截鉴权
|
||||||
|
uni.reLaunch({ url: pageUrl(TAB_PAGES[index]) });
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<u-tabbar v-model="current" :list="tabbarList" :active-color="$u.color.primary" />
|
<u-tabbar v-bind="tabbarProps" @change="onChange" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
/*
|
||||||
|
* 给底部导航加"上分隔线 + 向上投影",让它与页面内容之间有明显层次。
|
||||||
|
* 作用在 .u-tabbar__content(真正带背景色的那条)上:
|
||||||
|
* - border-top:比 u-border-top 自带的 1px 淡线更清晰一点;
|
||||||
|
* - box-shadow:负 y 偏移让阴影朝上投到页面上,形成浮起感。
|
||||||
|
* 注意:borderTop prop 仍保持默认 true —— 中心凸起按钮的缺口白圆(circle__border)
|
||||||
|
* 依赖它才渲染,关掉会让熊猫圆标和导航条衔接处出现割裂。
|
||||||
|
*/
|
||||||
|
:deep(.u-tabbar__content) {
|
||||||
|
border-top: 1rpx solid rgba(0, 0, 0, 0.06);
|
||||||
|
box-shadow: 0 -6rpx 28rpx rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -7,6 +7,13 @@
|
|||||||
"cancelBut": "Cancel",
|
"cancelBut": "Cancel",
|
||||||
"cultureLabel": "Language:"
|
"cultureLabel": "Language:"
|
||||||
},
|
},
|
||||||
|
"TabBar": {
|
||||||
|
"home": "Home",
|
||||||
|
"file": "Files",
|
||||||
|
"media": "Media",
|
||||||
|
"share": "Share",
|
||||||
|
"mine": "Me"
|
||||||
|
},
|
||||||
"AdminPage": {
|
"AdminPage": {
|
||||||
"filterBut": "Filter",
|
"filterBut": "Filter",
|
||||||
"filterTitle": "Filter Conditions",
|
"filterTitle": "Filter Conditions",
|
||||||
|
|||||||
@@ -7,6 +7,13 @@
|
|||||||
"cancelBut": "取消",
|
"cancelBut": "取消",
|
||||||
"cultureLabel": "语言:"
|
"cultureLabel": "语言:"
|
||||||
},
|
},
|
||||||
|
"TabBar": {
|
||||||
|
"home": "首页",
|
||||||
|
"file": "文件",
|
||||||
|
"media": "多媒体",
|
||||||
|
"share": "共享",
|
||||||
|
"mine": "我的"
|
||||||
|
},
|
||||||
"AdminPage": {
|
"AdminPage": {
|
||||||
"filterBut": "筛选",
|
"filterBut": "筛选",
|
||||||
"filterTitle": "筛选条件",
|
"filterTitle": "筛选条件",
|
||||||
|
|||||||
@@ -28,6 +28,19 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/security/actor-enquiry"
|
"path": "pages/security/actor-enquiry"
|
||||||
|
},
|
||||||
|
// 底部导航测试页(首页复用上面的 dashboard):文件/多媒体/共享/我的
|
||||||
|
{
|
||||||
|
"path": "pages/tab/file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/tab/media"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/tab/share"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/tab/mine"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
// 全局配置
|
// 全局配置
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ useAuthGuard(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<app-page :nav-title="WebSitePolicy.Title" :show-nav-back="false" :hide-nav="true">
|
<app-page :nav-title="WebSitePolicy.Title" :show-nav-back="false" :hide-nav="true" :show-tabbar="true">
|
||||||
<view class="dashboard">
|
<view class="dashboard">
|
||||||
<view v-if="menu.loading.value" class="dashboard__tip">
|
<view v-if="menu.loading.value" class="dashboard__tip">
|
||||||
<text>{{ t("AdminPage.menuLoading") }}</text>
|
<text>{{ t("AdminPage.menuLoading") }}</text>
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
// 加密已抽到 @/common/pwd-crypto(wxmp-rsa 纯 JS 实现,H5/小程序/App 全端可用)
|
// 加密已抽到 @/common/pwd-crypto(wxmp-rsa 纯 JS 实现,H5/小程序/App 全端可用)
|
||||||
import type { LoginData } from "@/data/auto/loginData";
|
import type { LoginData } from "@/data/auto/loginData";
|
||||||
import type { LoginProfile } from "@/data/auto/loginProfile";
|
import type { LoginProfile } from "@/data/auto/loginProfile";
|
||||||
import { onLoad } from "@dcloudio/uni-app";
|
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||||
import { useLocale } from "uview-pro";
|
import { useLocale } from "uview-pro";
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import { backendLocale, baseUrl } from "@/common/http.interceptor";
|
import { backendLocale, baseUrl, hasLoginSession } from "@/common/http.interceptor";
|
||||||
import { encryptLoginPassword } from "@/common/pwd-crypto";
|
import { encryptLoginPassword } from "@/common/pwd-crypto";
|
||||||
import { LoginData as LoginDataClass } from "@/data/auto/loginData";
|
import { LoginData as LoginDataClass } from "@/data/auto/loginData";
|
||||||
import { reloadLocales } from "@/locale";
|
import { reloadLocales } from "@/locale";
|
||||||
@@ -35,6 +35,19 @@ onLoad((options) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 冷启动直接落在登录入口且本地已有登录会话 → 自动进 dashboard,
|
||||||
|
* 省掉"明明登录过还闪一下登录页"。令牌若已过期,dashboard 首个请求会触发请求层用
|
||||||
|
* RefreshToken 自动续期;续期失败再 goLogin 退回本页,不会卡在受保护页里。
|
||||||
|
* 被"有意送回登录页"的场景(401 去登录 / 403 重新登录)不必再设豁免:
|
||||||
|
* goLoginWithRedirect 已先清登录态,此刻 hasLoginSession 必为假,表单正常展示。
|
||||||
|
*/
|
||||||
|
onShow(() => {
|
||||||
|
if (hasLoginSession()) {
|
||||||
|
uni.reLaunch({ url: redirectUrl.value });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 登录页中英文切换:走 uView Pro 语言包(t / setLocale),语言包 name 为 zh-CN / en-US
|
// 登录页中英文切换:走 uView Pro 语言包(t / setLocale),语言包 name 为 zh-CN / en-US
|
||||||
const { t, setLocale, currentLocale } = useLocale();
|
const { t, setLocale, currentLocale } = useLocale();
|
||||||
const isZh = computed(() => (currentLocale.value?.name ?? "zh-CN") !== "en-US");
|
const isZh = computed(() => (currentLocale.value?.name ?? "zh-CN") !== "en-US");
|
||||||
|
|||||||
@@ -164,12 +164,9 @@ useAuthGuard(() => {
|
|||||||
:filter-items="draft" :search-token="searchToken" @search="onSearch" @open-filter="state.openFilter()"
|
:filter-items="draft" :search-token="searchToken" @search="onSearch" @open-filter="state.openFilter()"
|
||||||
@close-filter="state.closeFilter()" @apply-filter="state.applyFilter($event)"
|
@close-filter="state.closeFilter()" @apply-filter="state.applyFilter($event)"
|
||||||
@clear-filter="state.clearFiltersAndSearch()" @row-tap="onRowTap">
|
@clear-filter="state.clearFiltersAndSearch()" @row-tap="onRowTap">
|
||||||
<template #toolbar>
|
<template #toolbar-actions>
|
||||||
<view v-if="perm.canCreate.value" class="create-entry" @click="onCreate">
|
<view v-if="perm.canCreate.value" class="create-entry" @click="onCreate">
|
||||||
<u-icon name="plus" size="24" color="#ffffff" />
|
<u-icon name="plus" size="40" />
|
||||||
<text class="create-text">
|
|
||||||
{{ t("AdminPage.createBut") }}
|
|
||||||
</text>
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -188,21 +185,17 @@ useAuthGuard(() => {
|
|||||||
background-color: $u-bg-color;
|
background-color: $u-bg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 新增:翠绿实心胶囊,靠右落在次级工具行里 */
|
/* 新增:与筛选按钮同一行的方形图标钮,只留一个加号 */
|
||||||
.create-entry {
|
.create-entry {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 12rpx 28rpx;
|
justify-content: center;
|
||||||
color: #ffffff;
|
width: 64rpx;
|
||||||
background-color: $u-type-primary;
|
height: 64rpx;
|
||||||
border-radius: 999rpx;
|
margin-left: 12rpx;
|
||||||
box-shadow: 0 6rpx 16rpx rgba(5, 150, 105, 0.24);
|
color: $u-type-primary;
|
||||||
}
|
background-color: $u-bg-color;
|
||||||
|
border-radius: 16rpx;
|
||||||
.create-text {
|
|
||||||
margin-left: 8rpx;
|
|
||||||
font-size: 26rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* 底部导航「文件」测试页:先占位,后续接相册/文件功能。
|
||||||
|
* 权限暂用 UserOperation(见 routes.ts 的 PAGE_PERMISSIONS);nav-guard 在 reLaunch 时做路由级校验,
|
||||||
|
* 这里再挂一次 useAuthGuard 兜冷启动直达(地址栏 / file、分享链接不经过导航 API)。
|
||||||
|
*/
|
||||||
|
import { useAuthGuard } from "@/composables";
|
||||||
|
|
||||||
|
useAuthGuard();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<app-page nav-title="文件" :show-nav-back="false" :show-tabbar="true">
|
||||||
|
<view class="tab-page">
|
||||||
|
<text class="tab-page__title">
|
||||||
|
文件
|
||||||
|
</text>
|
||||||
|
<text class="tab-page__hint">
|
||||||
|
测试页,功能待接入
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</app-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tab-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 120rpx 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-page__title {
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $u-main-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-page__hint {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: $u-tips-color;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* 底部导航「多媒体」测试页:先占位,后续接照片/视频/音频。
|
||||||
|
* 权限暂用 UserOperation(见 routes.ts 的 PAGE_PERMISSIONS);useAuthGuard 兜冷启动直达。
|
||||||
|
*/
|
||||||
|
import { useAuthGuard } from "@/composables";
|
||||||
|
|
||||||
|
useAuthGuard();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<app-page nav-title="多媒体" :show-nav-back="false" :show-tabbar="true">
|
||||||
|
<view class="tab-page">
|
||||||
|
<text class="tab-page__title">
|
||||||
|
多媒体
|
||||||
|
</text>
|
||||||
|
<text class="tab-page__hint">
|
||||||
|
测试页,功能待接入
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</app-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tab-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 120rpx 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-page__title {
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $u-main-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-page__hint {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: $u-tips-color;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* 底部导航「我的」测试页:先占位,后续接个人资料/设置/关于入口。
|
||||||
|
* 权限暂用 UserOperation(见 routes.ts 的 PAGE_PERMISSIONS);useAuthGuard 兜冷启动直达。
|
||||||
|
*/
|
||||||
|
import { useAuthGuard } from "@/composables";
|
||||||
|
|
||||||
|
useAuthGuard();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<app-page nav-title="我的" :show-nav-back="false" :show-tabbar="true">
|
||||||
|
<view class="tab-page">
|
||||||
|
<text class="tab-page__title">
|
||||||
|
我的
|
||||||
|
</text>
|
||||||
|
<text class="tab-page__hint">
|
||||||
|
测试页,功能待接入
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</app-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tab-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 120rpx 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-page__title {
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $u-main-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-page__hint {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: $u-tips-color;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* 底部导航「共享」测试页:先占位,后续接家庭组/共享管理。
|
||||||
|
* 权限暂用 UserOperation(见 routes.ts 的 PAGE_PERMISSIONS);useAuthGuard 兜冷启动直达。
|
||||||
|
*/
|
||||||
|
import { useAuthGuard } from "@/composables";
|
||||||
|
|
||||||
|
useAuthGuard();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<app-page nav-title="共享" :show-nav-back="false" :show-tabbar="true">
|
||||||
|
<view class="tab-page">
|
||||||
|
<text class="tab-page__title">
|
||||||
|
共享
|
||||||
|
</text>
|
||||||
|
<text class="tab-page__hint">
|
||||||
|
测试页,功能待接入
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</app-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tab-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 120rpx 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-page__title {
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $u-main-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-page__hint {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: $u-tips-color;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
* 拦截器拦不到,必须由页面自己调用 composables/useAuthGuard(页面级 onShow 校验)。
|
* 拦截器拦不到,必须由页面自己调用 composables/useAuthGuard(页面级 onShow 校验)。
|
||||||
*/
|
*/
|
||||||
import type { ErrorPageName, PageName } from "@/router/routes";
|
import type { ErrorPageName, PageName } from "@/router/routes";
|
||||||
import { getJwtInfo } from "@/common/http.interceptor";
|
import { clearLoginState, getJwtInfo } from "@/common/http.interceptor";
|
||||||
import { canVisitPage } from "@/permission/permission-context";
|
import { canVisitPage } from "@/permission/permission-context";
|
||||||
import {
|
import {
|
||||||
errorPageUrl,
|
errorPageUrl,
|
||||||
@@ -126,8 +126,13 @@ export function setupUnknownPathFallback(): void {
|
|||||||
/**
|
/**
|
||||||
* 供页面把用户送回登录页(带 redirect,登录成功后回到原地址;缺省回 dashboard)
|
* 供页面把用户送回登录页(带 redirect,登录成功后回到原地址;缺省回 dashboard)
|
||||||
* redirect 与 from 一样不做预编码,理由见 routes.ts 的 errorPageUrl
|
* redirect 与 from 一样不做预编码,理由见 routes.ts 的 errorPageUrl
|
||||||
|
*
|
||||||
|
* 进登录页前先清登录态:走到这里都是"有意去登录/换号"(401 去登录、403 重新登录),
|
||||||
|
* 旧会话整体作废——否则登录页的冷启动自动放行(hasLoginSession)会把这个还带着令牌的
|
||||||
|
* 用户直接弹回原页,403"重新登录"根本够不到登录表单。
|
||||||
*/
|
*/
|
||||||
export function goLoginWithRedirect(from?: string): void {
|
export function goLoginWithRedirect(from?: string): void {
|
||||||
|
clearLoginState();
|
||||||
const target = from && resolvePageName(from) ? from : pageUrl("dashboard");
|
const target = from && resolvePageName(from) ? from : pageUrl("dashboard");
|
||||||
uni.reLaunch({
|
uni.reLaunch({
|
||||||
url: `${pageUrl("login")}?redirect=${normalizePath(target)}`,
|
url: `${pageUrl("login")}?redirect=${normalizePath(target)}`,
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ export const PAGE_PATHS = {
|
|||||||
userEnquiry: "pages/security/user-enquiry",
|
userEnquiry: "pages/security/user-enquiry",
|
||||||
userOperation: "pages/security/user-operation",
|
userOperation: "pages/security/user-operation",
|
||||||
actorEnquiry: "pages/security/actor-enquiry",
|
actorEnquiry: "pages/security/actor-enquiry",
|
||||||
|
// 底部导航 tab 页(首页复用 dashboard):先各建一个简单测试页占位
|
||||||
|
fileTab: "pages/tab/file",
|
||||||
|
mediaTab: "pages/tab/media",
|
||||||
|
shareTab: "pages/tab/share",
|
||||||
|
mineTab: "pages/tab/mine",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type PageName = keyof typeof PAGE_PATHS;
|
export type PageName = keyof typeof PAGE_PATHS;
|
||||||
@@ -63,8 +68,28 @@ export const PAGE_PERMISSIONS: Partial<Record<PageName, PermissionNames>> = {
|
|||||||
userEnquiry: "UserEnquiry",
|
userEnquiry: "UserEnquiry",
|
||||||
userOperation: "UserOperation",
|
userOperation: "UserOperation",
|
||||||
actorEnquiry: "SecurityActor",
|
actorEnquiry: "SecurityActor",
|
||||||
|
// 底部导航 5 个 tab:真实权限尚未定义,按当前要求统一用 UserOperation 占位。
|
||||||
|
// 注意 dashboard 也被登记后,登录落地页(首页)会开始按 UserOperation 做路由级校验;
|
||||||
|
// 路由级是 fail-open(档案缺失/为空时放行),真正的边界仍在后端。
|
||||||
|
dashboard: "UserOperation",
|
||||||
|
fileTab: "UserOperation",
|
||||||
|
mediaTab: "UserOperation",
|
||||||
|
shareTab: "UserOperation",
|
||||||
|
mineTab: "UserOperation",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 底部导航的 tab 顺序,与 stores/tabbar 的 tabbarList 逐项按角标对齐。
|
||||||
|
* 首页复用 dashboard;app-tabbar 靠它把「点击下标 -> 目标页」与「当前路由 -> 高亮下标」对上。
|
||||||
|
*/
|
||||||
|
export const TAB_PAGES: PageName[] = [
|
||||||
|
"dashboard",
|
||||||
|
"fileTab",
|
||||||
|
"mediaTab",
|
||||||
|
"shareTab",
|
||||||
|
"mineTab",
|
||||||
|
];
|
||||||
|
|
||||||
/** 带前导斜杠的真实路径,供 uni.* API 之外的路径比较、日志使用 */
|
/** 带前导斜杠的真实路径,供 uni.* API 之外的路径比较、日志使用 */
|
||||||
export function toRealPath(name: PageName): string {
|
export function toRealPath(name: PageName): string {
|
||||||
return `/${PAGE_PATHS[name]}`;
|
return `/${PAGE_PATHS[name]}`;
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
@@ -1,37 +1,88 @@
|
|||||||
import type { TabbarItem } from "uview-pro/types/global";
|
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 底部导航的静态配置:只放"结构"(图标 + i18n 键 + 是否凸起),
|
||||||
|
* 文字不在这里写死——由 components/app-tabbar 按当前语言 t(langKey) 翻译,
|
||||||
|
* 这样切换语言时 tab 文案会跟着变(store 本身对 locale 无感知)。
|
||||||
|
*/
|
||||||
|
export interface TabbarSeed {
|
||||||
|
langKey: string;
|
||||||
|
iconPath: string;
|
||||||
|
selectedIconPath: string;
|
||||||
|
/** 中心凸起大按钮;还需 u-tabbar 父级 midButton 也为 true 才生效 */
|
||||||
|
midButton?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export const useTabbarStore = defineStore(
|
export const useTabbarStore = defineStore(
|
||||||
"tabbar",
|
"tabbar",
|
||||||
() => {
|
() => {
|
||||||
const activeIndex = ref(0);
|
const activeIndex = ref(0);
|
||||||
// 默认空列表:脚手架演示 tab(首页/关于)已随演示页一并移除。
|
// 顺序必须与 routes.ts 的 TAB_PAGES 逐项对齐(首页/文件/多媒体/共享/我的)。
|
||||||
// 后续底部导航项由旧项目移植的菜单数据(/SecurityFunction/FindMenus)驱动,
|
// 刻意不写 pagePath:u-tabbar 一旦拿到 pagePath 就会调 uni.switchTab,而本项目没有原生
|
||||||
// 在此按端需要填充,或整体改造成 pages.json 原生 tabBar。
|
// tabBar,switchTab 会 fail。跳转统一由 app-tabbar 的 @change 用 reLaunch 完成,
|
||||||
const tabbarList = ref<TabbarItem[]>([]);
|
// 高亮由当前页面路由反推下标(见 app-tabbar 的 onMounted)。
|
||||||
|
const tabs = ref<TabbarSeed[]>([
|
||||||
|
{
|
||||||
|
langKey: "TabBar.home",
|
||||||
|
iconPath: "home",
|
||||||
|
selectedIconPath: "home-fill",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
langKey: "TabBar.file",
|
||||||
|
iconPath: "file-text",
|
||||||
|
selectedIconPath: "file-text-fill",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
langKey: "TabBar.media",
|
||||||
|
// 中心凸起用自定义圆标图片(含 "/" → u-icon 走 <image> 渲染),非字体图标
|
||||||
|
iconPath: "/static/tabbar/media.png",
|
||||||
|
selectedIconPath: "/static/tabbar/media.png",
|
||||||
|
midButton: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
langKey: "TabBar.share",
|
||||||
|
iconPath: "share",
|
||||||
|
selectedIconPath: "share-fill",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
langKey: "TabBar.mine",
|
||||||
|
iconPath: "account",
|
||||||
|
selectedIconPath: "account-fill",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
// 各 tab 角标数字(下标 -> count),0/缺省表示不显示。
|
||||||
|
// 共享先挂占位值 34 以验证角标渲染,接入真实待办数量后由 setBadge 覆盖(下标 3 = 共享)。
|
||||||
|
const badges = ref<Record<number, number>>({ 3: 0 });
|
||||||
|
|
||||||
const setActiveIndex = (index: number) => {
|
const setActiveIndex = (index: number) => {
|
||||||
activeIndex.value = index;
|
activeIndex.value = index;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateBadge = (index: number, count: number) => {
|
const setBadge = (index: number, count: number) => {
|
||||||
tabbarList.value[index].count = count;
|
// 整体替换以触发响应式(改对象属性 Vue3 也能追踪,但替换更稳、便于持久化裁剪)
|
||||||
|
badges.value = { ...badges.value, [index]: count };
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateIsDot = (index: number, isDot: boolean) => {
|
const clearBadge = (index: number) => {
|
||||||
tabbarList.value[index].isDot = isDot;
|
const next = { ...badges.value };
|
||||||
|
delete next[index];
|
||||||
|
badges.value = next;
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activeIndex,
|
activeIndex,
|
||||||
tabbarList,
|
tabs,
|
||||||
|
badges,
|
||||||
setActiveIndex,
|
setActiveIndex,
|
||||||
updateBadge,
|
setBadge,
|
||||||
updateIsDot,
|
clearBadge,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
persist: true,
|
// 只持久化 activeIndex:tabs/badges 是配置与运行期数据,持久化会让改默认值后仍读到旧缓存
|
||||||
|
persist: {
|
||||||
|
paths: ["activeIndex"],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Reference in new issue
Block a user