新增 有家相册 uni-app 前端(uniapp/youjiala,H5 / 微信小程序 / App 三端)

从旧 WebVue 移植的地基部分:
- 登录页(RSA+MD5 密码加密、redirect 回跳、中英文切换)与登录后落地页
- 401 / 403 / 404 错误页 + error-panel 通用外壳(文案走 ErrorPage 语言包)
- 登录守卫双链路:uni.addInterceptor 拦站内跳转,页面级 onShow 复核冷启动深链
- 单一请求链路 common/http.interceptor:JWT 唯一来源、401 并发刷新单例、
  业务异常识别、HTTP 403 跳无权限页
- H5 干净短地址:initRouter 前向 __uniRoutes 追加克隆记录(router/h5-clean-url)
- 路由登记表 router/routes.ts(PAGE_PATHS / pageUrl / PAGE_PERMISSIONS)
  与新增页面四步登记约定(src/md/page-routing.md)
- i18n:uView Pro 语言包 + 后端词条合并,双取值(后端 zh-cn/en,包名 zh-CN/en-US)
- 后端接口层 service/ 与数据模型 data/auto/ 为生成器产物,不手改

不入库的内容:
- 摘除脚手架残留的 Harmony 签名密码(manifest.json signingConfigs 与
  harmony-configs/build-profile.json5 的 keyPassword / storePassword 已置空)
- 忽略 node_modules、dist、unpackage、.hbuilderx、.agents、pnpm-lock.yaml
  及根目录临时截图
This commit is contained in:
owenchen committed 2026-10-13 08:27:34 +08:00
1 parent 74757f9eb4
commit 6a8258ac4b
274 files changed
+30291

No files matched your search

+131
View File
@@ -0,0 +1,131 @@
/**
* 全局导航守卫(对应旧 WebVue 的 setupLoginGuard + catch-all 404)
*
* uni-app 没有 router.beforeEach,导航拦截靠 uni.addInterceptor:
* invoke 钩子返回 false 时本次导航被静默丢弃(uni-h5 的 queue 见 res === false 直接返回空 thenable,
* 既不执行原调用也不产生 promise rejection),所以取消导航不会给调用方留下未捕获异常。
*
* 覆盖范围(两条链路都要,缺一漏一半):
* 1. 站内跳转:本文件的 addInterceptor,拦住 navigateTo/redirectTo/reLaunch/switchTab;
* 2. 冷启动直达:H5 地址栏输入 / 分享链接 / 小程序编译进某个页面,不经过任何导航 API,
* 拦截器拦不到,必须由页面自己调用 composables/useAuthGuard(页面级 onShow 校验)。
*/
import type { ErrorPageName, PageName } from "@/router/routes";
import { getJwtInfo } from "@/common/http.interceptor";
import {
errorPageUrl,
isAuthFree,
normalizePath,
pageUrl,
resolvePageName,
} from "@/router/routes";
/** 需要拦的导航 API(switchTab 目前没有 tabBar 页,留着以免将来漏) */
const NAV_APIS = ["navigateTo", "redirectTo", "reLaunch", "switchTab"];
/** 是否已登录:只认请求层的 AccessToken(令牌唯一来源,与 store 口径一致) */
function isLoggedIn(): boolean {
return !!getJwtInfo()?.AccessToken;
}
/**
* 判定目标地址该被拦成哪个错误页;返回 null 表示放行
*
* 顺序即语义:地址不存在(404) -> 没登录(401) -> 没权限(403)
* 403 这层要等 PAGE_PERMISSIONS 与 PermissionContext 移植后接在这里
*/
function evaluate(url: string): ErrorPageName | null {
const name: PageName | null = resolvePageName(url);
if (!name) {
return "error404";
}
if (isAuthFree(name)) {
return null;
}
if (!isLoggedIn()) {
return "error401";
}
return null;
}
/** 跳错误页;redirectTo 保住返回栈("返回上一页"还有内容),页面栈为空时退化成 reLaunch */
function jumpToError(blocked: ErrorPageName, from: string): void {
const url = errorPageUrl(blocked, from);
uni.redirectTo({
url,
fail: () => {
uni.reLaunch({ url });
},
});
}
/**
* 冷启动/页面级校验:当前页面对象已由 uni 解析好,按真实路径判定
* 返回 false 表示已被拦走,调用方(useAuthGuard)不必再做后续初始化
*/
export function ensureCurrentPageAuthorized(): boolean {
const pages = getCurrentPages();
const current = pages[pages.length - 1];
if (!current) {
// 页面栈还没建立(启动极早期),交给页面自己的 useAuthGuard 再判一次
return true;
}
const path = `/${current.route}`;
const blocked = evaluate(path);
if (!blocked) {
return true;
}
// getCurrentPages 只会给出真实路径,而 ?from= 是给用户看的:换成当前端的地址形态
const name = resolvePageName(path);
jumpToError(blocked, name ? pageUrl(name) : path);
return false;
}
/** 注册导航拦截器;在 App.vue 的 onLaunch 调用,确保首次站内跳转就被管住 */
export function setupNavGuard(): void {
NAV_APIS.forEach((api) => {
uni.addInterceptor(api, {
invoke(args: { url?: string }) {
const url = args?.url ?? "";
const blocked = evaluate(url);
if (!blocked) {
return true;
}
jumpToError(blocked, url);
// 取消本次导航
return false;
},
});
});
}
/**
* H5 专属:地址栏直接访问一个不存在的地址时,uni 路由表里没有对应页面,
* 结果是白屏 + 一条 vue-router "No match found",且这条路径不经过导航 API,拦截器也管不到,
* 只能在启动时按 location.pathname 兜底跳 404。
*
* 前提:manifest 的 h5.router.base 为 "/";改成子路径部署时这里要先剥掉 base 前缀。
*/
export function setupUnknownPathFallback(): void {
// #ifdef H5
if (resolvePageName(window.location.pathname)) {
return;
}
const from = `${window.location.pathname}${window.location.search}`;
// 延到下一个任务:此时 uni 已完成首屏路由解析,reLaunch 才作用在真实页面栈上
setTimeout(() => {
uni.reLaunch({ url: errorPageUrl("error404", from) });
}, 0);
// #endif
}
/**
* 供页面把用户送回登录页(带 redirect,登录成功后回到原地址;缺省回 dashboard)
* redirect 与 from 一样不做预编码,理由见 routes.ts 的 errorPageUrl
*/
export function goLoginWithRedirect(from?: string): void {
const target = from && resolvePageName(from) ? from : pageUrl("dashboard");
uni.reLaunch({
url: `${pageUrl("login")}?redirect=${normalizePath(target)}`,
});
}