refactor(pc): 从 Gi Admin Pro + Arco 重搭为纯 Vue 3 + Naive UI
旧模板的 193 个文件整体删除,换成纯 Vue 3.5 + Naive UI 2.42 的树。
- 去掉 auto-import(组件与 API 全显式 import)、v-hasPerm(改 user.has('码'),全库 8 处)、
Stylelint;n-tag 的 type 统一过 utils/tag.ts 转换(Arco 色词直接透传会静默变灰)
- 图标改为构建期抽取:scripts/extract-pc-icons.py -> icons/icon-data.ts(37 个),
内网离线部署不会白屏
- 重搭后修掉 6 类"编译 / lint / typecheck 全绿、界面上就是坏的"故障:
① NGrid 的 cols 写成像素值(不是断点名 -> 永远只 1 列,工作台因此错乱)
② n-tab-pane 用 tab 属性(只渲染空注释节点,整排 tab 一个字都不显示)
③ 漏 import 被当未知自定义元素(样式整块丢) ④ .n-card__content 类名不存在
(高度链第一环失效,表格行在 DOM 里但屏幕上没有) ⑤ 填报单状态映射错位一格
⑥ 有标题的卡片没有上内边距(naive 的 padding-top 只给"内容区是长子"的卡片)
- 卡片式版面提成全局基线:styles/index.scss 的 xt-* 令牌 + .page-card / .toolbar /
.section-title,页面只引用、不再各写一套
- 品牌:改名「深学在线」,文字标换成几何标(方 = 卷宗 / 环 = 在线,不用文字字形);
index.html 的页签图标从 404 的 /favicon.ico 改指真实存在的 /favicon.svg
This commit is contained in:
@@ -1,20 +1,22 @@
|
||||
/**
|
||||
* 登录密码加密(RSA)
|
||||
* 登录密码加密(RSA)。
|
||||
*
|
||||
* 公钥从后端 `GET /auth/public-key` 拉取并缓存——这样**轮换密钥不需要重新构建前端**。
|
||||
* 否则"后端换了密钥、前端还是旧的"会表现为"所有人密码都错",而且极难定位。
|
||||
* 公钥从后端 `GET /auth/public-key` 拉取并缓存 —— 这样**轮换密钥不需要重新构建前端**。
|
||||
* 否则"后端换了密钥、前端还是旧的"会表现为"所有人密码都错",且极难定位。
|
||||
*
|
||||
* ⚠️ 算法必须与服务端 RsaCipher 一致:**RSA/ECB/PKCS1Padding**,
|
||||
* 与 jsencrypt 的默认行为相同。换成 OAEP 会表现为"密码永远错",两边都改才能换。
|
||||
* ⚠️ 算法必须与服务端 `RsaCipher` 一致:**RSA/ECB/PKCS1Padding**(jsencrypt 的默认行为)。
|
||||
* 换成 OAEP 会表现为"密码永远错"。
|
||||
*
|
||||
* ⚠️ 这不是用来替代 HTTPS 的。它的唯一目的是**防止明文密码进日志**——
|
||||
* 网关、Nginx、APM、链路追踪都会记录请求体,明文密码一旦落进日志就是永久泄露。
|
||||
* ⚠️ 它不是用来替代 HTTPS 的:唯一目的是**防止明文密码进日志** ——
|
||||
* 网关、Nginx、APM 都会记录请求体,明文一旦落进日志就是永久泄露。
|
||||
*/
|
||||
import JSEncrypt from 'jsencrypt'
|
||||
// 直接用 http 实例,不经 @/apis/xt —— 否则会形成
|
||||
// xt/index → http → router → stores → xt/index 的循环引用
|
||||
// 直接用 http,不经 @/apis/xt —— 否则形成 xt → http → … → xt 的环
|
||||
import http from '@/utils/http'
|
||||
|
||||
/** 折叠空白用(提到模块级:每次调用都新建正则纯属浪费,lint 也会拦) */
|
||||
const WHITESPACE_RE = /\s+/g
|
||||
|
||||
let cachedKey: string | null = null
|
||||
let inflight: Promise<string> | null = null
|
||||
|
||||
@@ -28,9 +30,9 @@ export async function fetchPublicKey(): Promise<string> {
|
||||
}
|
||||
inflight = http
|
||||
.get<string>('/auth/public-key')
|
||||
.then((res) => {
|
||||
cachedKey = res.data
|
||||
return res.data
|
||||
.then((pem) => {
|
||||
cachedKey = pem
|
||||
return pem
|
||||
})
|
||||
.finally(() => {
|
||||
inflight = null
|
||||
@@ -41,6 +43,16 @@ export async function fetchPublicKey(): Promise<string> {
|
||||
/** 加密密码,返回 Base64 密文 */
|
||||
export async function encryptPassword(plain: string): Promise<string> {
|
||||
const pem = await fetchPublicKey()
|
||||
if (typeof pem !== 'string' || !pem.includes('BEGIN PUBLIC KEY')) {
|
||||
// ⚠️ 这一条是**踩过的坑**:`/auth/public-key` 若没走到后端(例如 baseURL 少了
|
||||
// `/api/v1`,请求落到 SPA 兜底上),拿到的是 **index.html** 而不是公钥。
|
||||
// 此时 jsencrypt 只会返回 false,界面显示"密码加密失败" —— 排查方向会完全跑偏
|
||||
// (去查 jsencrypt、去查密钥轮换,而真正的原因是请求 URL)。
|
||||
// 所以这里把"到底拿到了什么"直接说出来。
|
||||
throw new Error(`公钥格式不对(拿到 ${typeof pem === 'string' ? `${pem.length} 字节` : typeof pem}:`
|
||||
+ `${String(pem).slice(0, 30).replace(WHITESPACE_RE, ' ')}…)`)
|
||||
}
|
||||
|
||||
const engine = new JSEncrypt()
|
||||
engine.setPublicKey(pem)
|
||||
const cipher = engine.encrypt(plain)
|
||||
|
||||
Reference in New Issue
Block a user