diff --git a/uniapp/youjiala/.editorconfig b/uniapp/youjiala/.editorconfig
new file mode 100644
index 0000000..5cadaca
--- /dev/null
+++ b/uniapp/youjiala/.editorconfig
@@ -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
\ No newline at end of file
diff --git a/uniapp/youjiala/src/common/http.interceptor.ts b/uniapp/youjiala/src/common/http.interceptor.ts
index 943a61b..273bfa2 100644
--- a/uniapp/youjiala/src/common/http.interceptor.ts
+++ b/uniapp/youjiala/src/common/http.interceptor.ts
@@ -69,6 +69,15 @@ export function clearLoginState() {
uni.removeStorageSync(JWT_STORAGE_KEY);
}
+/**
+ * 是否已有登录会话:只认请求层的 AccessToken(令牌唯一来源,与 nav-guard 口径一致)。
+ * 不在此判过期——AccessToken 过期时请求层会拿 RefreshToken 自动续期,续期失败再 goLogin 退回登录页,
+ * 所以"登录过"就足以决定冷启动是否直接进 dashboard。
+ */
+export function hasLoginSession(): boolean {
+ return !!getJwtInfo()?.AccessToken;
+}
+
function getToken(): string {
return getJwtInfo()?.AccessToken ?? "";
}
diff --git a/uniapp/youjiala/src/components/admin-list/admin-list.vue b/uniapp/youjiala/src/components/admin-list/admin-list.vue
index 6b70c93..6682c16 100644
--- a/uniapp/youjiala/src/components/admin-list/admin-list.vue
+++ b/uniapp/youjiala/src/components/admin-list/admin-list.vue
@@ -188,9 +188,8 @@ defineExpose({ refresh, clearSelection, selectedRows });
{{ filterCount }}
-
-
@@ -302,17 +301,6 @@ defineExpose({ refresh, clearSelection, selectedRows });
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 {
display: flex;
flex-direction: row;
diff --git a/uniapp/youjiala/src/components/app-tabbar/app-tabbar.vue b/uniapp/youjiala/src/components/app-tabbar/app-tabbar.vue
index 2726bfe..3ba2db7 100644
--- a/uniapp/youjiala/src/components/app-tabbar/app-tabbar.vue
+++ b/uniapp/youjiala/src/components/app-tabbar/app-tabbar.vue
@@ -2,8 +2,9 @@
-
+
diff --git a/uniapp/youjiala/src/locale/lang/en-US.json b/uniapp/youjiala/src/locale/lang/en-US.json
index 7297178..a1354e4 100644
--- a/uniapp/youjiala/src/locale/lang/en-US.json
+++ b/uniapp/youjiala/src/locale/lang/en-US.json
@@ -7,6 +7,13 @@
"cancelBut": "Cancel",
"cultureLabel": "Language:"
},
+ "TabBar": {
+ "home": "Home",
+ "file": "Files",
+ "media": "Media",
+ "share": "Share",
+ "mine": "Me"
+ },
"AdminPage": {
"filterBut": "Filter",
"filterTitle": "Filter Conditions",
diff --git a/uniapp/youjiala/src/locale/lang/zh-CN.json b/uniapp/youjiala/src/locale/lang/zh-CN.json
index a7f3f72..4f1cecd 100644
--- a/uniapp/youjiala/src/locale/lang/zh-CN.json
+++ b/uniapp/youjiala/src/locale/lang/zh-CN.json
@@ -7,6 +7,13 @@
"cancelBut": "取消",
"cultureLabel": "语言:"
},
+ "TabBar": {
+ "home": "首页",
+ "file": "文件",
+ "media": "多媒体",
+ "share": "共享",
+ "mine": "我的"
+ },
"AdminPage": {
"filterBut": "筛选",
"filterTitle": "筛选条件",
diff --git a/uniapp/youjiala/src/pages.json b/uniapp/youjiala/src/pages.json
index fa9346e..f0ac1a4 100644
--- a/uniapp/youjiala/src/pages.json
+++ b/uniapp/youjiala/src/pages.json
@@ -28,6 +28,19 @@
},
{
"path": "pages/security/actor-enquiry"
+ },
+ // 底部导航测试页(首页复用上面的 dashboard):文件/多媒体/共享/我的
+ {
+ "path": "pages/tab/file"
+ },
+ {
+ "path": "pages/tab/media"
+ },
+ {
+ "path": "pages/tab/share"
+ },
+ {
+ "path": "pages/tab/mine"
}
],
// 全局配置
diff --git a/uniapp/youjiala/src/pages/dashboard/dashboard.vue b/uniapp/youjiala/src/pages/dashboard/dashboard.vue
index 53b9ea5..b7acf5b 100644
--- a/uniapp/youjiala/src/pages/dashboard/dashboard.vue
+++ b/uniapp/youjiala/src/pages/dashboard/dashboard.vue
@@ -32,7 +32,7 @@ useAuthGuard(() => {
-
+
{{ t("AdminPage.menuLoading") }}
diff --git a/uniapp/youjiala/src/pages/login/login.vue b/uniapp/youjiala/src/pages/login/login.vue
index 88107f7..0b438d7 100644
--- a/uniapp/youjiala/src/pages/login/login.vue
+++ b/uniapp/youjiala/src/pages/login/login.vue
@@ -4,10 +4,10 @@
// 加密已抽到 @/common/pwd-crypto(wxmp-rsa 纯 JS 实现,H5/小程序/App 全端可用)
import type { LoginData } from "@/data/auto/loginData";
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 { 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 { LoginData as LoginDataClass } from "@/data/auto/loginData";
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
const { t, setLocale, currentLocale } = useLocale();
const isZh = computed(() => (currentLocale.value?.name ?? "zh-CN") !== "en-US");
diff --git a/uniapp/youjiala/src/pages/security/user-enquiry.vue b/uniapp/youjiala/src/pages/security/user-enquiry.vue
index cdb5d54..cca7337 100644
--- a/uniapp/youjiala/src/pages/security/user-enquiry.vue
+++ b/uniapp/youjiala/src/pages/security/user-enquiry.vue
@@ -164,12 +164,9 @@ useAuthGuard(() => {
:filter-items="draft" :search-token="searchToken" @search="onSearch" @open-filter="state.openFilter()"
@close-filter="state.closeFilter()" @apply-filter="state.applyFilter($event)"
@clear-filter="state.clearFiltersAndSearch()" @row-tap="onRowTap">
-
+
-
-
- {{ t("AdminPage.createBut") }}
-
+
@@ -188,21 +185,17 @@ useAuthGuard(() => {
background-color: $u-bg-color;
}
-/* 新增:翠绿实心胶囊,靠右落在次级工具行里 */
+/* 新增:与筛选按钮同一行的方形图标钮,只留一个加号 */
.create-entry {
display: flex;
flex-direction: row;
align-items: center;
- padding: 12rpx 28rpx;
- color: #ffffff;
- background-color: $u-type-primary;
- border-radius: 999rpx;
- box-shadow: 0 6rpx 16rpx rgba(5, 150, 105, 0.24);
-}
-
-.create-text {
- margin-left: 8rpx;
- font-size: 26rpx;
- font-weight: 500;
+ justify-content: center;
+ width: 64rpx;
+ height: 64rpx;
+ margin-left: 12rpx;
+ color: $u-type-primary;
+ background-color: $u-bg-color;
+ border-radius: 16rpx;
}
diff --git a/uniapp/youjiala/src/pages/tab/file.vue b/uniapp/youjiala/src/pages/tab/file.vue
new file mode 100644
index 0000000..dfbb9bf
--- /dev/null
+++ b/uniapp/youjiala/src/pages/tab/file.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ 文件
+
+
+ 测试页,功能待接入
+
+
+
+
+
+
diff --git a/uniapp/youjiala/src/pages/tab/media.vue b/uniapp/youjiala/src/pages/tab/media.vue
new file mode 100644
index 0000000..0278246
--- /dev/null
+++ b/uniapp/youjiala/src/pages/tab/media.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+ 多媒体
+
+
+ 测试页,功能待接入
+
+
+
+
+
+
diff --git a/uniapp/youjiala/src/pages/tab/mine.vue b/uniapp/youjiala/src/pages/tab/mine.vue
new file mode 100644
index 0000000..308d4e6
--- /dev/null
+++ b/uniapp/youjiala/src/pages/tab/mine.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+ 我的
+
+
+ 测试页,功能待接入
+
+
+
+
+
+
diff --git a/uniapp/youjiala/src/pages/tab/share.vue b/uniapp/youjiala/src/pages/tab/share.vue
new file mode 100644
index 0000000..a9bdab7
--- /dev/null
+++ b/uniapp/youjiala/src/pages/tab/share.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+ 共享
+
+
+ 测试页,功能待接入
+
+
+
+
+
+
diff --git a/uniapp/youjiala/src/router/nav-guard.ts b/uniapp/youjiala/src/router/nav-guard.ts
index 61f6556..b3958e5 100644
--- a/uniapp/youjiala/src/router/nav-guard.ts
+++ b/uniapp/youjiala/src/router/nav-guard.ts
@@ -11,7 +11,7 @@
* 拦截器拦不到,必须由页面自己调用 composables/useAuthGuard(页面级 onShow 校验)。
*/
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 {
errorPageUrl,
@@ -126,8 +126,13 @@ export function setupUnknownPathFallback(): void {
/**
* 供页面把用户送回登录页(带 redirect,登录成功后回到原地址;缺省回 dashboard)
* redirect 与 from 一样不做预编码,理由见 routes.ts 的 errorPageUrl
+ *
+ * 进登录页前先清登录态:走到这里都是"有意去登录/换号"(401 去登录、403 重新登录),
+ * 旧会话整体作废——否则登录页的冷启动自动放行(hasLoginSession)会把这个还带着令牌的
+ * 用户直接弹回原页,403"重新登录"根本够不到登录表单。
*/
export function goLoginWithRedirect(from?: string): void {
+ clearLoginState();
const target = from && resolvePageName(from) ? from : pageUrl("dashboard");
uni.reLaunch({
url: `${pageUrl("login")}?redirect=${normalizePath(target)}`,
diff --git a/uniapp/youjiala/src/router/routes.ts b/uniapp/youjiala/src/router/routes.ts
index 67a11ca..17f0f62 100644
--- a/uniapp/youjiala/src/router/routes.ts
+++ b/uniapp/youjiala/src/router/routes.ts
@@ -19,6 +19,11 @@ export const PAGE_PATHS = {
userEnquiry: "pages/security/user-enquiry",
userOperation: "pages/security/user-operation",
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;
export type PageName = keyof typeof PAGE_PATHS;
@@ -63,8 +68,28 @@ export const PAGE_PERMISSIONS: Partial> = {
userEnquiry: "UserEnquiry",
userOperation: "UserOperation",
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 之外的路径比较、日志使用 */
export function toRealPath(name: PageName): string {
return `/${PAGE_PATHS[name]}`;
diff --git a/uniapp/youjiala/src/static/tabbar/media.png b/uniapp/youjiala/src/static/tabbar/media.png
new file mode 100644
index 0000000..a4a1d10
Binary files /dev/null and b/uniapp/youjiala/src/static/tabbar/media.png differ
diff --git a/uniapp/youjiala/src/stores/tabbar.ts b/uniapp/youjiala/src/stores/tabbar.ts
index 4f0275c..aedaebe 100644
--- a/uniapp/youjiala/src/stores/tabbar.ts
+++ b/uniapp/youjiala/src/stores/tabbar.ts
@@ -1,37 +1,88 @@
-import type { TabbarItem } from "uview-pro/types/global";
import { defineStore } from "pinia";
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(
"tabbar",
() => {
const activeIndex = ref(0);
- // 默认空列表:脚手架演示 tab(首页/关于)已随演示页一并移除。
- // 后续底部导航项由旧项目移植的菜单数据(/SecurityFunction/FindMenus)驱动,
- // 在此按端需要填充,或整体改造成 pages.json 原生 tabBar。
- const tabbarList = ref([]);
+ // 顺序必须与 routes.ts 的 TAB_PAGES 逐项对齐(首页/文件/多媒体/共享/我的)。
+ // 刻意不写 pagePath:u-tabbar 一旦拿到 pagePath 就会调 uni.switchTab,而本项目没有原生
+ // tabBar,switchTab 会 fail。跳转统一由 app-tabbar 的 @change 用 reLaunch 完成,
+ // 高亮由当前页面路由反推下标(见 app-tabbar 的 onMounted)。
+ const tabs = ref([
+ {
+ langKey: "TabBar.home",
+ iconPath: "home",
+ selectedIconPath: "home-fill",
+ },
+ {
+ langKey: "TabBar.file",
+ iconPath: "file-text",
+ selectedIconPath: "file-text-fill",
+ },
+ {
+ langKey: "TabBar.media",
+ // 中心凸起用自定义圆标图片(含 "/" → u-icon 走 渲染),非字体图标
+ 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>({ 3: 0 });
const setActiveIndex = (index: number) => {
activeIndex.value = index;
};
- const updateBadge = (index: number, count: number) => {
- tabbarList.value[index].count = count;
+ const setBadge = (index: number, count: number) => {
+ // 整体替换以触发响应式(改对象属性 Vue3 也能追踪,但替换更稳、便于持久化裁剪)
+ badges.value = { ...badges.value, [index]: count };
};
- const updateIsDot = (index: number, isDot: boolean) => {
- tabbarList.value[index].isDot = isDot;
+ const clearBadge = (index: number) => {
+ const next = { ...badges.value };
+ delete next[index];
+ badges.value = next;
};
return {
activeIndex,
- tabbarList,
+ tabs,
+ badges,
setActiveIndex,
- updateBadge,
- updateIsDot,
+ setBadge,
+ clearBadge,
};
},
{
- persist: true,
+ // 只持久化 activeIndex:tabs/badges 是配置与运行期数据,持久化会让改默认值后仍读到旧缓存
+ persist: {
+ paths: ["activeIndex"],
+ },
},
);