Files
Docker/uniapp/youjiala/src/components/error-panel/error-panel.vue
T
owenchen 6a8258ac4b 新增 有家相册 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
  及根目录临时截图
2026-10-13 08:27:34 +08:00

317 lines
7.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>