新增 有家相册 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

@@ -0,0 +1,316 @@
<script setup lang="ts">
import type { PropType } from "vue";
import { useLocale } from "uview-pro";
import { WebSitePolicy } from "@/policy/custom/webSitePolicy";
/**
* 错误页通用外壳(401 / 403 / 404 共用),移植自旧 WebVue 的 components/basic/ErrorPanel.vue。
* 只负责排版与配色,具体文案、图标、按钮由各错误页提供。
* 旧版用 ant-design 图标 + <a> 链接,这里换成 uView Pro 的 u-icon 与纯文本,跨端可用。
*/
defineProps({
/** 状态码,如 401 */
code: {
type: String,
required: true,
},
/** 状态码下方的短标签,如「UNAUTHORIZED · 未认证」 */
badge: {
type: String,
default: "",
},
/** 主题色:blue(默认)/ amber(无权限)/ violet(找不到页面) */
tone: {
type: String as PropType<"blue" | "amber" | "violet">,
default: "blue",
},
/** 主标题:一句话说清出了什么问题 */
title: {
type: String,
required: true,
},
/** 副标题:补充说明 */
description: {
type: String,
default: "",
},
/** 可能的原因清单 */
reasons: {
type: Array as PropType<string[]>,
default: () => [],
},
/** 出错的地址(守卫跳页时用 ?from= 带过来) */
from: {
type: String,
default: "",
},
/** 兜底提示 */
hint: {
type: String,
default: "",
},
});
// 「可能的原因」「出错的地址:」是组件自己的固定标签,走语言包;业务文案仍由各错误页传入
const { t } = useLocale();
</script>
<template>
<view class="ep" :class="`ep--${tone}`">
<!-- 顶部渐变头图:品牌 + 大号状态码 -->
<view class="ep-hero">
<view class="ep-brand">
<image class="ep-brand__logo" src="/static/logo.png" mode="aspectFill" />
<text class="ep-brand__name">
{{ WebSitePolicy.Title }}
</text>
</view>
<view class="ep-code">
{{ code }}
</view>
<view class="ep-tag">
{{ badge }}
</view>
</view>
<!-- 悬浮卡片:图标 + 说明 + 可能原因 + 操作 -->
<view class="ep-card">
<view class="ep-icon">
<slot name="icon" />
</view>
<view class="ep-title">
{{ title }}
</view>
<view v-if="description" class="ep-desc">
{{ description }}
</view>
<view v-if="reasons && reasons.length" class="ep-reasons">
<view class="ep-reasons__title">
<text>{{ t('ErrorPage.reasonsTitle') }}</text>
</view>
<view v-for="(item, i) in reasons" :key="i" class="ep-reasons__item">
<text class="ep-reasons__dot">
•
</text>
<text class="ep-reasons__text">
{{ item }}
</text>
</view>
</view>
<view v-if="from" class="ep-from">
<text>{{ t('ErrorPage.fromLabel') }}</text>
<text class="ep-from__code">
{{ from }}
</text>
</view>
<view class="ep-actions">
<slot name="actions" />
</view>
<view v-if="hint" class="ep-hint">
{{ hint }}
</view>
</view>
<view class="ep-footer">
<text class="ep-footer__line">
{{ WebSitePolicy.Copy }}
</text>
<text class="ep-footer__line">
{{ WebSitePolicy.Beian }}
</text>
</view>
</view>
</template>
<style lang="scss" scoped>
.ep {
/* 主题色:默认品牌蓝→青渐变,amber / violet 用于区分不同错误类型 */
--ep-from: #1677ff;
--ep-to: #36cfc9;
--ep-shadow: rgba(22, 119, 255, 0.18);
display: flex;
flex-direction: column;
min-height: 100vh;
background-color: $u-bg-white;
}
.ep--amber {
--ep-from: #fa8c16;
--ep-to: #ffc53d;
--ep-shadow: rgba(250, 140, 22, 0.18);
}
.ep--violet {
--ep-from: #722ed1;
--ep-to: #b37feb;
--ep-shadow: rgba(114, 46, 209, 0.18);
}
/* ---------- 头图 ---------- */
.ep-hero {
/* 状态栏高度:navigationStyle 为 custom,页面顶部会被状态栏压住 */
padding: calc(var(--status-bar-height) + 32rpx) 32rpx 88rpx;
border-radius: 0 0 44rpx 44rpx;
background-image: linear-gradient(135deg, var(--ep-from) 0%, var(--ep-to) 100%);
box-shadow: 0 12rpx 36rpx var(--ep-shadow);
color: $u-bg-white;
}
.ep-brand {
display: flex;
align-items: center;
}
.ep-brand__logo {
width: 52rpx;
height: 52rpx;
border-radius: 10rpx;
background-color: rgba(255, 255, 255, 0.24);
}
.ep-brand__name {
margin-left: 16rpx;
font-size: 30rpx;
font-weight: 600;
opacity: 0.95;
}
.ep-code {
margin-top: 16rpx;
font-size: 104rpx;
font-weight: 800;
line-height: 1;
letter-spacing: 4rpx;
}
.ep-tag {
margin-top: 12rpx;
font-size: 24rpx;
letter-spacing: 2rpx;
opacity: 0.9;
}
/* ---------- 悬浮卡片 ---------- */
.ep-card {
margin: -56rpx 32rpx 36rpx;
padding: 36rpx 32rpx 40rpx;
border-radius: 40rpx;
background-color: #fff;
box-shadow: 0 16rpx 48rpx rgba(15, 35, 60, 0.08);
}
.ep-icon {
display: flex;
align-items: center;
justify-content: center;
width: 96rpx;
height: 96rpx;
margin-bottom: 24rpx;
border-radius: 28rpx;
background-image: linear-gradient(135deg, var(--ep-from) 0%, var(--ep-to) 100%);
box-shadow: 0 4rpx 16rpx var(--ep-shadow);
}
.ep-title {
font-size: 36rpx;
font-weight: 700;
line-height: 1.4;
color: $u-main-color;
}
.ep-desc {
margin-top: 12rpx;
font-size: 27rpx;
line-height: 1.65;
color: $u-content-color;
}
.ep-reasons {
margin-top: 28rpx;
padding: 24rpx;
border: 2rpx solid #eef0f4;
border-radius: 28rpx;
background-color: #f7f9fc;
}
.ep-reasons__title {
margin-bottom: 12rpx;
font-size: 26rpx;
font-weight: 600;
color: $u-main-color;
}
.ep-reasons__item {
display: flex;
align-items: flex-start;
margin-top: 8rpx;
}
.ep-reasons__dot {
margin-right: 12rpx;
font-size: 26rpx;
line-height: 1.6;
color: var(--ep-from);
}
.ep-reasons__text {
flex: 1;
font-size: 26rpx;
line-height: 1.6;
color: $u-tips-color;
}
.ep-from {
margin-top: 24rpx;
font-size: 24rpx;
line-height: 1.6;
color: $u-light-color;
word-break: break-all;
}
.ep-from__code {
margin-left: 8rpx;
padding: 2rpx 12rpx;
border-radius: 12rpx;
background-color: #f2f4f8;
color: $u-main-color;
}
.ep-actions {
display: flex;
flex-direction: column;
/* 按钮来自各页面的插槽,拿不到本组件的 scoped 样式,故间距写在容器上 */
gap: 20rpx;
margin-top: 36rpx;
}
.ep-hint {
margin-top: 28rpx;
font-size: 24rpx;
line-height: 1.6;
color: $u-light-color;
}
/* ---------- 底部信息 ---------- */
.ep-footer {
display: flex;
flex: 1;
flex-direction: column;
align-items: center;
justify-content: flex-end;
padding: 0 32rpx 28rpx;
text-align: center;
}
.ep-footer__line {
font-size: 22rpx;
line-height: 32rpx;
color: $u-light-color;
}
</style>