# Vue 2 项目只读分析 原始分析日期:2026-10-09(Asia/Shanghai) 状态补充日期:2026-10-14(Asia/Shanghai) 工作目录:`D:\project\web\unity\ngfrontend` 分支:`rebuild`;HEAD:`6c38326443f8b967897590fef6cd3d20de4c02e3` 范围:当前工作区的源码、依赖清单、本地 lockfile、已安装依赖、配置与测试。页面明细见 [页面清单](page-inventory.md)。 ## 2026-10-14 状态补充 本文第 1~8 节保留原始分析快照,其中版本、行号、文件数量、lint / Jest 结果和“本轮”均指 2026-10-09。当时的“未构建”“仅新增分析文档”等结论不能直接套用到后续实现;本次没有重新执行全仓库审计、构建或测试。 - **当前目录**:公共布局入口为 [src/ui-refactor/index.vue](../../src/ui-refactor/index.vue),外壳组件在 `layouts/`,注册页和私有业务在 `views/signup/`;旧 `src/ui-refactor/public/` 不再是当前路径。共享 UI 卡片、API、请求工具、路由和资源仍有目录外依赖,尚未拆成独立应用。 - **当前接入**:注册路由在 [src/router/modules/refactor.js](../../src/router/modules/refactor.js),导入 `constantRoutes`;当前懒加载函数返回 `import()` 的 Promise,已不再使用阻塞导航的 `component: () => newLayout` 写法。静态解析得到 13 条 constant、79 条 async,共 92 条路由记录,不等于 92 个独立页面;本次未做浏览器跳转验证。 - **当前登录与资源**:现有 `/login` 页面已直接改造,展示英文并保留认证链路;Logo / 社交 SVG 位于 `src/assets/icons/`,字体位于 `src/assets/custom-theme/fonts/`。早期一期规划已在 [登录说明](../ui-refactor/phase-1-login.md) 中标记为历史。 - **当前页头**:滚动超过 64px 后吸顶,返回阈值以内恢复;平滑动画方案已撤销。注册路由仍隐藏页头,相关行为见 [公共框架说明](public-layout-design.md)。 - **当前游客注册 / 支付**:固定国家和语言选项,调用游客草稿、提交及推荐人接口;站内弹窗通过 Paystack V2 恢复服务端 accessCode,匹配回调后停留注册页等待订单确认。具体契约与未联调项见 [注册说明](signup-welcome-pack.md)。 - **验证口径**:本次只核对源码、文档引用和修改范围;历史测试通过不代表当前版本测试已重跑,真实认证、匿名注册成功和真实支付仍未在本次验证。 ## 1. 结论与证据口径 本项目是基于 vue-element-admin 4.3.1 模板发展而来的会员业务 SPA,使用 Vue 2 / JavaScript / Options API / Vue Router 3 / Vuex 3 / Element UI 2 / Vue CLI 4。真实业务与模板演示共存;静态路由、后端菜单、localStorage 登录缓存和页面内部状态共同影响界面行为。后续 UI 重构需要逐页核对交互、请求字段和样式作用域,不能把模板注释直接视为当前架构事实。 本文使用以下标记: - **[已确认]**:直接读取当前文件或实际执行无修改检查得到的事实;不代表线上行为已经验证。 - **[推断]**:由已确认代码推导的影响或重构风险,尚未用浏览器或后端验证。 - **[待确认]**:需要业务说明、后端契约、授权环境或运行验证才能收敛的事项。 **[已确认] 本轮只新增两份分析文档,没有实施重构。** 开始时已有用户改动:`src/views/login/index.vue`、`src/views/dashboard/admin/index.vue`;以及未跟踪的 `AGENTS.md`、`DESIGN.md`、`docs/`、`src/views/login/assets/`。分析以这些当前文件为准,不将 HEAD 中的旧代码冒充当前实现。 **[已确认] 规则资料存在状态冲突。** `AGENTS.md` 与 `docs/ui-refactor/phase-1-login.md` 仍描述仅文档准备、保留旧登录;`DESIGN.md` 的最新说明及实际源码已经体现直接修改现有登录、英文、私有 Logo/字体和深蓝按钮。长期记忆也记录了后续登录 UI 实施。此次按当前用户明确要求仅分析并新增文档,不修改或同步这些已有资料,也不将历史实施授权延伸到本轮。 ### 1.1 核心发现 | 状态 | 发现 | 影响 / 证据 | |---|---|---| | 已确认 | 90 条静态路由记录:11 条 constant、79 条 async;70 条引用视图的记录,映射 65 个不同 Vue 文件 | 包含父路由、别名入口和通配符,不是 90 个独立页面;见 `src/router/index.js` 与 12 个路由模块 | | 已确认 | `src/views` 共 114 个 Vue 文件;另有 `src/components` 35 个 Vue 文件 | 视图包含内部组件和未静态接入页面;详见页面清单 | | 已确认 | 所有 constant / async 静态路由启动时一起注册 | `createRouter()` 使用 `constantRoutes.concat(asyncRoutes)`,async 名称不等于按角色动态加载 | | 已确认 | 主侧栏来自缓存中的 `baseInfo.menu` | `permission/generateRoutes` 与 `Sidebar/index.vue`;静态路由可匹配不等于菜单会显示 | | 已确认 | Vue 2.7.16 与 vue-template-compiler 2.6.10 不一致 | 直接加载编译器失败;2 个组件测试套件因此不能运行 | | 已确认 | 当前 vue-loader 15.11.1 为 Vue 2.7 选择 `vue/compiler-sfc` | 已检查本地 loader 实现并运行编译器选择探针;不能据上述不匹配断言 Webpack 构建必然失败 | | 已确认 | 只读 ESLint:252 个文件,2,403 个错误、1,280 个警告;无 fatal 解析错误 | 检查使用 fix=false、cache=false;当前全量 lint 门禁不通过 | | 已确认 | Jest:4 个工具套件通过,22 项测试通过;2 个组件套件失败于加载阶段 | 不构成业务、认证、支付或 UI 回归通过 | | 待确认 | 构建、真实菜单、认证、支付、上传、移动端实际表现 | 本轮未构建、未开开发服务器、未访问后端、未执行账户操作 | ## 2. 技术栈、版本与环境 ### 2.1 核心技术 | 层次 | 声明 / 当前本地版本 | 代码证据 | |---|---|---| | 框架 | Vue `^2.6.10` / 2.7.16 | `package.json`、本地 `package-lock.json` 与 `node_modules/vue/package.json`;`new Vue`、Options API | | 语言 | JavaScript、Vue SFC;未发现 TypeScript 工程配置或 TS 源文件 | `src/`、`jsconfig.json`;没有 typecheck 脚本 | | 路由 | vue-router 3.0.2,默认 Hash 模式 | `src/router/index.js`:history 配置被注释 | | 状态 | vuex 3.1.0,模块自动注册 | `src/store/index.js` 的 `require.context` | | UI | element-ui `^2.15.12` / 2.15.14 | `src/main.js` 全量 `Vue.use(Element)` | | 国际化 | vue-i18n 7.3.2 | `src/lang/index.js` | | 请求 | axios 0.18.1 | `src/utils/request.js` | | 构建 | Vue CLI / Babel / ESLint / Jest 插件均为 4.4.4;Webpack 4.47.0;vue-loader 15.11.1 | 根配置及已安装依赖;后两项为传递依赖 | | 样式 | sass 1.26.2、sass-loader 8.0.2、PostCSS / autoprefixer 9.5.1 | `vue.config.js`、`postcss.config.js` | | 图表 / 时间 / 支付 | ECharts 5.6.0、Moment 2.30.1、vue-paystack 2.0.4 | 业务页与公共组件的实际 import | | 更新提醒 | webpack 插件 1.8.1 | `vue.config.js`、`src/App.vue` 的更新事件 | **[已确认] 本机执行环境**:Node v12.22.10、npm 6.14.16;package engines 声明 Node >=8.9、npm >=3.0.0。这些是本地事实,不是经过验证的推荐升级版本或生产环境版本。 **[已确认] 依赖复现边界**:本地 `package-lock.json` 的 lockfileVersion 为 3,但该文件被 `.gitignore` 忽略、未被 Git 跟踪。`yarn.lock` 也在忽略规则中,本地不存在。下表区分声明与当前解析;不能把 README 徽标或 engines 下限当作安装结果。**[待确认]** npm 6 与 lockfile v3 组合的重新安装行为、团队固定 Node/npm 版本、正式发布依赖来源。未运行 install、update、audit,也未查询外部漏洞数据库。 ### 2.2 全部直接依赖 以下为 **[已确认]** 的顶层依赖;“lock”来自本地 lockfile,“安装”来自本地对应 package.json。安装存在不表示全部功能已启用。 #### dependencies | 包 | package.json 声明 | lock | 安装 | |---|---|---|---| | `axios` | `0.18.1` | 0.18.1 | 0.18.1 | | `clipboard` | `2.0.4` | 2.0.4 | 2.0.4 | | `codemirror` | `5.45.0` | 5.45.0 | 5.45.0 | | `core-js` | `3.6.5` | 3.6.5 | 3.6.5 | | `driver.js` | `0.9.5` | 0.9.5 | 0.9.5 | | `dropzone` | `5.5.1` | 5.5.1 | 5.5.1 | | `element-ui` | `^2.15.12` | 2.15.14 | 2.15.14 | | `file-saver` | `2.0.1` | 2.0.1 | 2.0.1 | | `fuse.js` | `3.4.4` | 3.4.4 | 3.4.4 | | `js-cookie` | `2.2.0` | 2.2.0 | 2.2.0 | | `jsonlint` | `1.6.3` | 1.6.3 | 1.6.3 | | `jszip` | `3.2.1` | 3.2.1 | 3.2.1 | | `moment` | `^2.29.4` | 2.30.1 | 2.30.1 | | `normalize.css` | `7.0.0` | 7.0.0 | 7.0.0 | | `nprogress` | `0.2.0` | 0.2.0 | 0.2.0 | | `path-to-regexp` | `2.4.0` | 2.4.0 | 2.4.0 | | `pinyin` | `2.9.0` | 2.9.0 | 2.9.0 | | `screenfull` | `4.2.0` | 4.2.0 | 4.2.0 | | `script-loader` | `0.7.2` | 0.7.2 | 0.7.2 | | `sortablejs` | `1.8.4` | 1.8.4 | 1.8.4 | | `vue` | `^2.6.10` | 2.7.16 | 2.7.16 | | `vue-count-to` | `1.0.13` | 1.0.13 | 1.0.13 | | `vue-i18n` | `7.3.2` | 7.3.2 | 7.3.2 | | `vue-paystack` | `^2.0.4` | 2.0.4 | 2.0.4 | | `vue-router` | `3.0.2` | 3.0.2 | 3.0.2 | | `vue-splitpane` | `1.0.4` | 1.0.4 | 1.0.4 | | `vuedraggable` | `2.20.0` | 2.20.0 | 2.20.0 | | `vuex` | `3.1.0` | 3.1.0 | 3.1.0 | | `xlsx` | `0.14.1` | 0.14.1 | 0.14.1 | #### devDependencies | 包 | package.json 声明 | lock | 安装 | |---|---|---|---| | `@plugin-web-update-notification/webpack` | `^1.6.4` | 1.8.1 | 1.8.1 | | `@vue/cli-plugin-babel` | `4.4.4` | 4.4.4 | 4.4.4 | | `@vue/cli-plugin-eslint` | `4.4.4` | 4.4.4 | 4.4.4 | | `@vue/cli-plugin-unit-jest` | `4.4.4` | 4.4.4 | 4.4.4 | | `@vue/cli-service` | `4.4.4` | 4.4.4 | 4.4.4 | | `@vue/test-utils` | `1.0.0-beta.29` | 1.0.0-beta.29 | 1.0.0-beta.29 | | `autoprefixer` | `9.5.1` | 9.5.1 | 9.5.1 | | `babel-eslint` | `10.1.0` | 10.1.0 | 10.1.0 | | `babel-jest` | `23.6.0` | 23.6.0 | 23.6.0 | | `babel-plugin-dynamic-import-node` | `2.3.3` | 2.3.3 | 2.3.3 | | `chalk` | `2.4.2` | 2.4.2 | 2.4.2 | | `chokidar` | `2.1.5` | 2.1.5 | 2.1.5 | | `connect` | `3.6.6` | 3.6.6 | 3.6.6 | | `echarts` | `^5.4.3` | 5.6.0 | 5.6.0 | | `eslint` | `6.7.2` | 6.7.2 | 6.7.2 | | `eslint-plugin-vue` | `6.2.2` | 6.2.2 | 6.2.2 | | `html-webpack-plugin` | `3.2.0` | 3.2.0 | 3.2.0 | | `husky` | `1.3.1` | 1.3.1 | 1.3.1 | | `lint-staged` | `8.1.5` | 8.1.5 | 8.1.5 | | `mockjs` | `1.0.1-beta3` | 1.0.1-beta3 | 1.0.1-beta3 | | `plop` | `2.3.0` | 2.3.0 | 2.3.0 | | `runjs` | `4.3.2` | 4.3.2 | 4.3.2 | | `sass` | `1.26.2` | 1.26.2 | 1.26.2 | | `sass-loader` | `8.0.2` | 8.0.2 | 8.0.2 | | `script-ext-html-webpack-plugin` | `2.1.3` | 2.1.3 | 2.1.3 | | `serve-static` | `1.13.2` | 1.13.2 | 1.13.2 | | `svg-sprite-loader` | `4.1.3` | 4.1.3 | 4.1.3 | | `svgo` | `1.2.0` | 1.2.0 | 1.2.0 | | `vue-template-compiler` | `2.6.10` | 2.6.10 | 2.6.10 | **[推断]** ECharts 虽列在 devDependencies,但图表视图直接导入它;如果构建环境仅安装生产依赖,需确认构建流程是否仍具备该依赖。此次未改变依赖分类。 ### 2.3 运行命令及副作用 下列命令来自 `package.json`,**并非全部执行过**。 | 命令 | 实际脚本 / 用途 | 只读分析阶段注意 | |---|---|---| | `npm run dev` | `vue-cli-service serve` | 默认端口 9526,自动打开浏览器;加载环境和实际接口 | | `npm run lint` | `eslint --ext .js,.vue src` | 原脚本无 --fix;本轮用等价只读 API 扫描,见第 7 节 | | `npm run build:prod` | `vue-cli-service build` | 默认 production,写入 dist | | `npm run build:dev` | `vue-cli-service build --mode development` | 写入 dist,不等于只读检查 | | `npm run build:stage` | `vue-cli-service build --mode staging` | staging 文件指定 NODE_ENV=production,写入 dist | | `npm run preview` | `node build/index.js --preview` | **先执行构建**,再用 connect / serve-static 启动 9526 预览;不是仅打开已有 dist | | `npm run test:unit` | `jest --clearCache && vue-cli-service test:unit` | 原脚本主动清理 Jest 缓存;本轮直接无缓存运行已有 Jest | | `npm run test:ci` | lint → test:unit | lint 失败会中断后续测试;不包括 build | | `npm run new` | plop | 创建代码,未执行 | | `npm run svgo` | 优化 `src/icons/svg` | 会修改已有资源,未执行 | **[已确认] 构建配置**:生产 publicPath 为 `./`,其他为 `/`;outputDir=dist,assetsDir=static;productionSourceMap=false;仅 development 启用 lintOnSave。别名 `@` 指向 src。SVG sprite 使用 `icon-[name]`;关闭 prefetch;生产分包包括 libs、elementUI、commons,并内联 runtime。Babel 使用 CLI preset,development 启用 dynamic-import-node。browserslist 为 `> 1%`、`last 2 versions`,它不是实际浏览器测试记录。 **[已确认] 环境变量职责**:三个环境文件定义 API、下载、CDN、站点、Paystack 公钥及 Authorization 前缀变量;本文不抄录其值。development / staging 的 BASE_API 为绝对地址;production 的 BASE_API 为空,按相对接口使用。devServer 有以 BASE_API 为键的代理和固定目标。**[待确认]** 当前绝对地址配置如何与 devServer 代理配合、实际跨域策略、部署路径、staging 是否可作为指定测试环境;没有验证请求路径或服务可用性。 ## 3. 入口、路由与布局 ### 3.1 应用入口 **[已确认]** `src/main.js` 顺序加载 normalize、Element SCSS 主题、全局样式、router、store、i18n、图标、权限守卫、错误日志和全局过滤器;Element 尺寸为 Cookie size 或 medium,并桥接 i18n;Moment 挂在 `Vue.prototype.$moment`。除 Element 自带组件外,项目显式全局注册的组件是 `svg-icon`(`src/icons/index.js`)。 `App.vue` 渲染根 router-view,并监听 `plugin_web_update_notice`,显示 Element Dialog,确认后刷新整页。页面替换会处于这个全局提示环境中。公共模板 `public/index.html` 包含禁用用户缩放的 viewport 设置;**[推断]** 移动可访问性需后续验证。 ### 3.2 路由真实结构 **[已确认]** 路由拆为 12 个模块:article、atlas、bonus、config、finance、profile、shop、user,以及模板的 charts、components、nested、table。页面采用动态 import;Layout 静态导入。所有 constantRoutes 和 asyncRoutes 启动时已注册,`resetRouter` 重建相同集合,`selfAddRoutes` 重置后再 addRoutes。 普通登录、后台快捷登录、auth 回调和顶层错误页不套后台 Layout;业务和演示路由多数位于 Layout 下;nested 另有两层 router-view 容器。根 `/` 重定向到 `/dashboard/index`。完整路径与源码关联见页面清单。 **[已确认] 结构风险**: - `Dashboard` name 两次声明,且 `/dashboard/index` 同时作为前一根路由的子页面路径和另一个 Layout 父路径;后者又重定向到自身路径。 - `Profile` name 三次声明,分别对应 `/profile/index`、`/config/profile`、`/user/index`,共用同一个视图。 - bonus 与 finance 父路由 name 同为 `Bonus Management`。 - components、charts、error 的 `redirect: 'noRedirect'` 是实际路由字段,不能仅按注释把它视为“禁用面包屑”。 - 首页轮播出现 `/shop/index` 链接,但静态 shop 模块没有该路径;后端菜单是否补足或需要跳转到其他地址待确认。 **[推断]** 同名路由会影响命名跳转和任何基于 name 的标签/缓存;重复路径、自重定向和上述父路由直接访问结果应在后续浏览器验证。此次没有修改或执行这些路由行为。 ### 3.3 后台布局与实际启用状态 | 状态 | 结构 / 行为 | 证据 | |---|---|---| | 已确认 | Layout = Sidebar + Navbar + AppMain;另有移动侧栏遮罩和可选 RightPanel / Settings | `src/layout/index.vue` | | 已确认 | Navbar 当前显示面包屑、侧栏按钮、语言选择、头像/退出;桌面另显示全屏和尺寸选择 | `src/layout/components/Navbar.vue`;搜索和错误日志控件模板被注释 | | 已确认 | settings 默认 tagsView=true、showSettings=false、fixedHeader=false、sidebarLogo=false | `src/settings.js` | | 已确认 | 当前 Layout 没有 TagsView 节点,也没有 TagsView 组件实现目录 | 仍保留 tagsView store 与 hasTagsView 布局类,不能描述为可用多标签页 | | 已确认 | AppMain 的 keep-alive 注释掉;router-view key 为 route.path | `src/layout/components/AppMain.vue`;cachedViews getter 存在但当前不启用缓存 | | 已确认 | 桌面侧栏宽 210px,收起 54px;响应式 Mixin 用 body 宽度约 992px 阈值 | `variables.scss`、Layout 样式、ResizeHandler | | 已确认 | 多个业务页初始化弹窗宽度使用 window.screen.width,通常以 600px 区分 | `src/utils/index.js`、商品/财务/地址页面 | | 推断 | screen.width 与浏览器实际视口、窗口缩放及 Layout 断点不同;初始计算未必跟随 resize | 后续需检验窄窗口、横屏和短视口,不能仅靠桌面浏览器缩小后得出结论 | ## 4. Vuex、Mixin、公共组件与样式 ### 4.1 状态分布 **[已确认]** `src/store/index.js` 通过 require.context 注册 modules 下全部 JS,root getters 暴露用户、菜单、布局和语言状态。 | 模块 | 已确认职责 | 边界 | |---|---|---| | app | sidebar、device、language、size、languageList | sidebar/language/size 与 Cookie 同步 | | user | 登录/快捷登录、验证码状态、用户与基础信息、退出/重置 | 登录详情主要存在 usersInfo / localStorage,不是纯 Vuex 单一状态 | | permission | 后端 menu → 菜单树;保存 routes/addRoutes | 不做静态 asyncRoutes 的角色过滤;roles 参数被赋为 admin | | settings | 主题、布局开关、站点标题、pageId、国家列表 | theme 来自 SCSS export;pageId action 转交响应 | | dashboard | getIndex API action | 不保存首页数据,数据在视图中维护 | | errorLog | 累积/清理错误日志 | 生产错误处理器写入;Navbar 展示入口已注释 | | tagsView | visitedViews/cachedViews 及增删动作 | UI 和 keep-alive 未启用,不应按模板能力承诺 | | region | 导出普通 regionInfo 对象 | 不是标准 namespaced state/actions 模块;地区工具直接 import 并修改它 | **[已确认]** `usersInfo.js` 管理 accessToken / refreshToken、时效与更新时间、userId/name/email、baseData、baseInfo。页面常直接取缓存并复制到 data。getters 包含 email/mobile/realName/等级等读取,但当前 user state 未声明对应字段;Profile 的实际展示依赖缓存而非这些 getter 全部有效。Cookie 的 `Admin-Token` 工具仍在,登录写入该 Cookie 的调用被注释。 **[推断]** 重构时若只改 Vuex 数据来源、缓存键名、字段大小写或初始化次序,会使页面出现空值、旧值、权限菜单缺失或 token 不一致。localStorage 本身没有 Vue 响应式更新;退出清理、重载和页面初始化属于现有行为契约。 ### 4.2 Mixin、指令、过滤器 **[已确认] 未发现项目代码调用 Vue.mixin 注册全局 Mixin。** 主要局部 Mixin: - `src/layout/mixin/ResizeHandler.js`:resize / route 变化、device、侧栏关闭;销毁时移除监听。 - `src/layout/components/Sidebar/FixiOSBug.js`:重写 Element submenu 的 handleMouseleave,耦合其内部方法。 - `src/components/Charts/mixins/resize.js` 与 `src/views/dashboard/admin/components/mixins/resize.js`:图表 resize、侧栏 transition 监听、生命周期;不是统一业务 Mixin。 指令目录包含 waves、permission、clipboard、el-drag-dialog、el-table 高度自适应及 sticky。通常按页面局部导入/注册;main.js 没有统一安装这批指令。Element 的 loading 等由 Element 自身提供。权限指令通过 roles 决定移除 DOM。 全局过滤器来自 `src/filters/index.js`,包括 parseTime / formatTime、相对时间、数字缩写、千分位、首字母大写及金额格式;业务工具还处理尼日利亚时区、金额、图片/URL和错误提示。重排模板应保留过滤器与业务工具调用,不直接用未经核对的浏览器默认格式替换。 ### 4.3 公共组件能力与耦合 | 类别 | 已确认组件 | 关键契约 / 风险 | |---|---|---| | 导航辅助 | Breadcrumb、Hamburger、LangSelect、SizeSelect、HeaderSearch、Screenfull | 使用 route、store、Cookie;LangSelect 请求语言列表,不是纯静态菜单 | | 数据与文件 | Pagination、Upload/Uploader、SingleImage 系列、UploadExcel | Pagination 使用 update:page/update:limit/pagination;Uploader 独立 Element 上传链路;Excel 使用 FileReader + XLSX | | 图表 | TreeChart、Charts/Keyboard、LineMarker、MixChart | TreeChart props 含业务字段,emit clickNode;图表初始化与容器/生命周期相关 | | 编辑器 | Tinymce、JsonEditor、MDinput | Tinymce 动态载入外部 4.9.3 CDN;JsonEditor 使用 CodeMirror/jsonlint | | 布局和交互 | Sticky、RightPanel、BackToTop、DragSelect、DndList、Kanban、Dropzone、ImageCropper | DOM、事件、拖拽、外部上传和浏览器能力,不能只按外观重写 | | 视觉/模板 | SvgIcon、ThemePicker、PanThumb、GithubCorner、ErrorLog、Share/DropdownMenu、TextHoverEffect/Mallki | 多项来自模板;存在不等于生产启用;ThemePicker 运行时获取 CSS 并替换颜色 | **[已确认]** Uploader 直接设置 action/headers/data 并获取上传授权,未通过统一 Axios service 发起文件上传;before-upload 启动异步请求却没有返回该 Promise,且后续 response.data 的使用需核对。**[推断]** 上传授权时序及返回结构有风险,实际失败条件待测试,不作为此次修复项。 **[已确认]** 地区初始化由 Layout.beforeCreate 调用 `utils/region.js`,动态加载 CDN JS,使用全局 AR_REGION_DATA,并更新导出的 regionInfo,固定 rootCode='234'。**[待确认]** CDN 可用性、地区数据契约及哪些业务页面实际依赖这条旧链路。 ### 4.4 全局与局部样式 **[已确认]** 主样式加载链: `main.js → normalize.css → element-variables.scss(完整 Element theme-chalk)→ styles/index.scss → variables / mixin / transition / element-ui / sidebar / btn`。 全局主色为 #1890ff;成功/警告/危险色为 #13ce66/#ffba00/#ff4949;body 字体为 Helvetica Neue、Helvetica、PingFang SC、Hiragino Sans GB、Microsoft YaHei、Arial、sans-serif。sidebar 色值单独定义,active 色 #409EFF。现有 `src/assets/custom-theme/index.css` 存在,但 main.js 未将它作为默认主题入口。 **[已确认]** 全局覆盖包含 .el-dialog、上传、面包屑、日期控件等;业务 SFC 有大量非 scoped style,例如商品页 .el-table--medium、订单/会员页 .el-table、.el-radio、.el-tabs,多个页面重复 .csClass;快捷登录保留 .white-box 等全局样式。 **[推断]** 路由懒加载后注入的非 scoped 样式可能影响后续页面,CSS 加载顺序可改变结果。抽取共享布局、改全局主题、合并同名类或仅用 scoped 包裹页面,均可能改变 Element append-to-body 弹层表现。 **[已确认] 当前普通登录例外**:现有 index.vue 已用 .ng-login-v2、scoped SCSS、私有 Montserrat/Logo和深蓝按钮;不显示 LangSelect;这不是全站 Element 主色已改。当前文档中的旧登录规范与实际登录不同,后续以再次确认的页面基线为准。 ## 5. API、认证、权限、国际化与表单机制 ### 5.1 API 边界 **[已确认]** `src/api` 有 13 个 JS 文件、86 个命名导出函数。它们通过 `utils/request` 提交 url/method/data/params。真实业务主要使用 /v1;模板端点 /vue-element-admin 与示例 /qiniu 仍存在。以下为 API 出口清单,不代表后端全部可用: | 文件 | 导出数 | 导出函数 | |---|---|---| | `src/api/article.js` | 5 | `fetchArticleList`、`fetchArticleOne`、`fetchCategory`、`fetchList`、`fetchArticle` | | `src/api/atlas.js` | 6 | `fetchMainUserInfo`、`fetchPlacementNetworkList`、`fetchPlacementNetwork`、`fetchSponsorNetworkList`、`fetchSponsorNetwork`、`fetchBrandAmbassadorList` | | `src/api/bonus.js` | 6 | `fetchAccount`、`fetchAccountFlow`、`fetchBonusList`、`fetchBonusDetail`、`fetchHistoricalCumulativeBonus`、`fetchTeamInquiry` | | `src/api/config.js` | 5 | `fetchAddressList`、`createAddress`、`updateAddress`、`deleteAddress`、`getRegionList` | | `src/api/dashboard.js` | 1 | `getIndex` | | `src/api/file.js` | 1 | `fetchToken` | | `src/api/finance.js` | 9 | `fetchTransferList`、`fetchApplyTransfer`、`fetchChkTransferUser`、`fetchBalance`、`fetchTransferCode`、`fetchRechargeList`、`fetchBankList`、`fetchApplyRecharge`、`editApplyRecharge` | | `src/api/qiniu.js` | 1 | `getToken` | | `src/api/remote-search.js` | 2 | `searchUser`、`transactionList` | | `src/api/role.js` | 5 | `getRoutes`、`getRoles`、`addRole`、`updateRole`、`deleteRole` | | `src/api/setting.js` | 4 | `getWebSetting`、`getPageId`、`getLanguages`、`getCountries` | | `src/api/shop.js` | 19 | `fetchProductList`、`fetchProduct`、`fetchDecOrderList`、`downloadDecOrder`、`fetchOrderList`、`fetchShoppingCart`、`downloadOrder`、`fetchBaDecOrderList`、`downloadBaDecOrder`、`createApproachOrder`、`createOrder`、`deleteApproachOrder`、`fetchReconsumeProductList`、`createReconsumeOrder`、`createApproachReconsumeOrder`、`deleteApproachReconsumeOrder`、`fetchAutoMaintenanceProductList`、`createAutoMaintenanceOrder`、`createApproachAutoMaintenanceOrder` | | `src/api/user.js` | 22 | `login`、`backendQuickLogin`、`isLoginVerify`、`getUserInfo`、`getBaseInfo`、`getMessageData`、`updateInfo`、`updateLoginPassword`、`updatePaymentPassword`、`fetchBrandAmbassador`、`createBrandAmbassador`、`fetchFullInfo`、`fetchInspectNetwork`、`fetchUpgrade`、`fetchSendUpgrade`、`fetchUpgradeInfo`、`fetchBrandAmbassadorInfo`、`fetchAutoPlace`、`fetchWelcomePack`、`createWelcomePack`、`fetchOrderList`、`downloadOrder` | ### 5.2 请求封装与异常契约 **[已确认]** service.baseURL=VUE_APP_BASE_API、timeout=12000;withCredentials 未启用。请求拦截器从 usersInfo 获取 token,附加 Authorization 前缀,并使用 language Cookie 设请求语言。 时效判定使用 expiresIn + updateAt - 30 和 tool.getTimestamp(),该时间戳还受 baseInfo.daysDiff 影响。过期时直接用独立 axios.get 刷新 token,**没有 await/return 该刷新 Promise,也没有并发队列**;请求 config 的 Authorization 已提前设置。刷新失败提示、refreshToken 无效时清缓存的路径与普通页面错误处理并不统一。 响应拦截器: 1. success=false 或存在非 200 code 时,reject(responseData.message),可能是字符串。 2. 其他情况若 responseData.data 为 falsy,返回 { data: responseData }。 3. data 为 truthy 时,直接返回 data,不是完整 Axios response。 4. 多数页面又读取 response.data / response.data.list,依赖服务端实际嵌套结构;不能假设这已是统一“解包一次”的结构。 HTTP 错误处理中前半段有保护,后半段仍直接访问 err.response.data.status;网络断开/超时无 response 时存在二次异常路径。401 使用 localStorage.clear() 并重定向登录;400/403 弹 Message;使用的 status 多来自响应 body,而非始终使用 err.response.status。 **[推断]** 错误字符串与 Error 混用、falsy data 包装差异、未等待刷新、缺失 response 和全量 localStorage.clear(),是跨页面重构的高风险边界。此次未发请求复现,不能断言每个接口都存在相同失败。 下载页取得返回的文件 URL,再拼接 VUE_APP_BASE_DO_API,创建 a 标签并打开新窗口;并非统一 blob 下载。验证码是图片地址请求,上传是 Element 独立请求,Paystack 是第三方组件,这些也不完全经过统一 service。 ### 5.3 登录与权限 **[已确认] 当前普通登录链路**: `settings/getPageId → 验证码图片构造 → 用户名 change 查询 user/isLoginVerify → 非空检查 + el-form.validate → user/login → user/getUserInfo → user/getBaseInfo → redirect 或 /(保留其他 query)`。 验证码以 response===1 显示;图片带 page_id 和随机 v;失败清 usersInfo、重查验证码状态、按当前状态刷新图片并收尾 loading。页面传 userName/password/verifyCode/pageId/isLoginVerify,action 在不需验证码时只发送 userName/password/version:'v2';需验证码时带 pageId 并构造 query。登录成功写 localStorage,roles 被写为 ['admin']。 当前模板密码显隐使用 Element show-password;旧 showPwd 方法仍在但模板不绑定它。Caps Lock 提示、密码/验证码 Enter、初始焦点仍存在。Register 和 Shop now 仅展示,Forgot password 只发右下角联系客户服务 Notification。英文状态在 created 中同步 i18n 与 app;验证码页面数据请求从 beforeCreate 发出,因此首次请求语言时序需后续观察。 **[已确认]** LoginRules 当前为空;用户名/密码检查在 handleLogin 内手写。admin/editor 用户名及至少 6 位密码的 validator 函数保留但规则被注释,**未生效**,不能在 UI 重构时误启用它们。 **[已确认] 权限守卫**:白名单只有 /login、/auth-redirect、/login-by-backend;其他页面检查 usersInfo.hasLogin(accessToken 存在且 refreshToken 时效有效)。正常 token 分支的 hasRoles 实际读 localStorage.accessToken,因此不能按变量名理解为真正角色验证。静态 meta.roles 未在当前守卫中过滤;后端菜单生成也不是静态路由授权过滤。 generaMenu 将后端 routePath/name/show/wiki/icon/child 转成菜单树,每一层 component 都设 Layout;Sidebar 初始化时若无菜单则触发生成。user/getBaseInfo 还硬编码改写 ret.menu[3].child[1].routePath 为 atlas/relation-ex。登录后 Sidebar 能依据菜单显示链接,不代表这些生成对象都有真实业务视图映射。 **[推断]** 前端菜单隐藏不能当作安全权限;后端是否逐接口授权待确认。当前 guard 的动态 addRoutes 分支在正常缓存齐全时通常不进入。刷新、缓存缺失、菜单结构变动与直接访问隐藏/演示路由应分别验收。 ### 5.4 国际化 **[已确认]** en/zh/es/ja 四套本地文案与 Element locale 合并;默认 Cookie language 或 en,fallback=en。getLanguage 内浏览器语言推断位于必然返回的默认分支之后,当前正常路径不走该推断。 LangSelect 请求 /v1/site/languages,写 app.languageList;切换同步 i18n、Vuex、Cookie及消息。后端实际提供的语言列表、KEY 与本地四套资源是否一致待确认。侧栏用 menu.wiki 翻译,Breadcrumb/标题使用 route.key,页面用 $t;英文业务标题和中文提示也有硬编码。 当前登录强制英文,Navbar 仍可切换语言;没有全站移除中文。校验信息有在 data 初始化时生成的 $t 字符串,切换语言是否同步更新既有规则待验证。 ### 5.5 表单校验 **[已确认]** 主要方式是 Element Form rules/ref.validate、页面内手写分支、$prompt.inputPattern,以及 `utils/rules.js` / `validate.js`。没有独立、统一的业务表单 schema。 | 位置 | 当前行为 | 重构注意 | |---|---|---| | 登录 | rules 空;手工非空检查;验证码交服务端 | 不误恢复模板账户/密码限制 | | 商品购买 | payForm.validate + 余额/支付方式判断 + 支付密码 prompt | UI 选择与金额/运费计算直接相连 | | 转账 | 禁向自己转账、手续费与最小实到金额检查、密码 prompt、授权码 | 改控件或触发时机不能改变规则 | | 充值 | 页面内 validator + required/float 等规则,依条件决定字段必填 | 提示时序和服务端字段需核对 | | 地址 | Form 与 create/update/delete/default 切换在视图 methods 中 | 是否每个提交分支都前置 validate 需逐项确认 | | 个人信息/密码 | 子组件直接调 API;密码 data 含 type/default 风格对象 | 未见完整统一校验;客户端初始模型与服务端要求待确认 | `rules.js` 含 required、手机号、integer、float、date、email、微信、执照、长度、范围及银行卡规则,多个提示为中文。range validator 在超范围 cb(Error) 后仍调用 cb();这属于已确认的代码路径,用户可见后果尚未复现。不能借 UI 重构修复或重新定义业务校验。 ## 6. UI / 业务耦合与重构风险 风险等级为 **[推断]**,依据来自已确认代码;不代表已执行生产事故复现。 | 优先级 | 耦合点 / 已确认事实 | 重构风险 | 后续最小验证 | |---|---|---|---| | 高 | Login 生命周期、验证码、action 分支、缓存、redirect/query 同在页面和 user action | 更换表单/事件导致重复提交、漏参数、错误收尾或跳转丢失 | 比较字段、调用顺序、Enter、验证码显示/刷新、失败/成功跳转 | | 高 | 商品、复消、维护、BA/升级/报单页面包含价格/税/运费、余额、密码 prompt、Paystack、订单撤销、倒计时 | 只按视觉拆组件可能改变金额、支付时序或订单状态 | 授权测试环境对照支付方式、金额精度、取消、失败、回调和重复操作 | | 高 | Token 刷新未等待,响应解包及错误类型不一致 | 统一封装或改错误提示会影响全部页面 | 过期并发、网络错误、400/401/403、falsy data 与真实 payload | | 高 | 静态路由全注册,侧栏后端驱动且 menu 索引被硬编码 | 视图改名、迁移路径、权限解释错误或菜单结构变化 | 刷新、直达、菜单隐藏、角色与服务端授权验证 | | 高 | 数据源混合 Vuex / Cookie / localStorage / 页面快照 | 登录后、刷新后和多页信息不同步 | 保持缓存键、时效、初始化与退出清理;空缓存测试 | | 高 | 业务 SFC 的非 scoped Element 覆盖与通用 csClass | 样式漏到别页,append-to-body 弹层丢失或覆盖 | 按访问顺序验证表格、弹窗、prompt、radio | | 中高 | Uploader 独立授权/上传,地区 JS 和编辑器 CDN | 拆展示组件时丢失副作用或异步竞态 | 上传授权时序、失败收尾、外链不可用、清理 | | 中高 | TreeChart 业务 props、节点拼装、tooltip HTML、固定 DOM id、反复 init | 树图点击/展开、尺寸和实例生命周期回归 | 大树、连续展开、resize、路由往返及销毁 | | 中高 | dashboard/article 使用 v-html 输出服务端 CONTENT | 模板重构必须保留并明确 HTML 内容的信任边界 | 确认后台是否清洗 HTML;未确认前不声称安全 | | 中 | 支付页 handleCountdown 使用 setInterval,未保存/清理句柄 | 离页后重复回调或再次打开叠加 | 路由往返、关闭重开、重复触发;不能照搬首页已清理的计时器结论 | | 中 | 多页 loading 依赖 then 和人为 timeout,部分异常分支不足 | 调整请求或拆组件后 spinner 卡住、重复交互 | 延迟/失败/取消、空数据和加载反馈 | | 中 | screen.width 布局与 body 断点不同,部分字段静态计算 | 窄窗口、语言长度、横屏/缩放溢出 | 320/375/768/1024/1440、短视口、键盘、长文案 | | 中 | 模板页仍注册,部分 API 调用被注释;未路由页面仍有代码 | 错把演示能力当生产需求、误删仍被导入组件 | 明确保留/下线范围;检查调用链,不以无静态路由直接判定可删除 | **[建议,未实施]** 后续先为获授权页面建立当前交互和数据契约,再只调整布局/展示,保留 Options API、JavaScript、Element UI 和现有服务。共享请求、认证、菜单、校验、全局主题及工具链问题应作为独立需求;本次发现不授权任何修复、迁移、清理或删除。 ## 7. 现有验证能力与本轮结果 ### 7.1 现有能力 - **[已确认]** 六个 unit spec:四个 utils(parseTime、formatTime、param2Obj、validate),两个 components(Hamburger、SvgIcon)。未发现认证、Vuex、业务表单、权限、API、支付和 UI 端到端 spec。 - **[已确认]** jest.config.js 配置 vue-jest、babel-jest、静态资源 stub、@ 映射、Vue snapshot serializer、jsdom testURL。coverage 未默认启用;collectCoverageFrom 主要针对 utils/components,主动排除 auth/request,不含业务 views/store。 - **[已确认]** ESLint 6.7.2 + eslint-plugin-vue 6.2.2 + babel-eslint,plugin:vue/recommended / eslint:recommended,2 空格、单引号、无分号等规则;无独立 Stylelint / TypeScript typecheck 脚本。 - **[已确认]** husky pre-commit 执行 lint-staged,包含 eslint --fix 与 git add,会修改/暂存源码;本轮未触发提交或 hook。 - **[已确认]** .travis.yml 使用 Node 10 并执行 npm run test,但 package.json **没有 test 脚本**。仅证明配置不一致,不能证明 CI 目前实际在线或已失效。 - **[已确认]** .github 目录不存在;未发现 E2E 配置/测试。gitignore 中 E2E 报告规则不是测试存在的证据。 - **[已确认]** mock/ 保留模板 user/article/role/remote-search;devServer.before 和 main.js 的 mockXHR 代码均注释。README“全部模拟数据”不适用于当前业务请求;没有 /v1 业务完整 Mock 契约。 ### 7.2 本轮实际执行 | 检查 | 执行方式 | 结果 / 范围 | |---|---|---| | 环境与版本 | node --version、npm --version;读取 manifest/lock/installed package.json | Node 12.22.10、npm 6.14.16;版本如第 2 节 | | 编译器加载 | node 内直接 require vue-template-compiler | 退出 1:Vue 2.7.16 / compiler 2.6.10 mismatch | | loader 选择 | 调用已安装 vue-loader/lib/compiler.resolveCompiler(process.cwd()) | 退出 0:is27=true,实际使用 vue/compiler-sfc | | ESLint | CLIEngine({fix:false,cache:false,extensions:['.js','.vue']}).executeOnFiles(['src']);发现错误返回 1 | 252 文件;2,403 errors、1,280 warnings、无 fatal;主要 indent/html-indent/quotes/semi,不代表其余逻辑检查通过 | | 已有单元测试 | `node node_modules/jest/bin/jest.js --runInBand --no-cache --coverage=false` | 退出 1;4 套件通过、2 套件加载失败;22 项实际运行测试全部通过;0 snapshots | | 静态清单 | 已安装 @babel/parser 解析路由、import 与 API 导出;枚举 Vue 文件 | 90 路由、114 views、35 公共 Vue 组件、13 API 文件/86 导出;未发现当前本地 API 命名导入缺失 | | 文件保护 | 开始/结束 SHA-256 对照 + Git 状态 + 文档引用/清单检查 | 本轮新增仅两份目标文档;见第 8 节 | 运行 Jest 提示本地 Browserslist/caniuse-lite 与 baseline-browser-mapping 数据陈旧;仅记录提示,没有更新包或数据库。命名导入检查不涵盖服务端契约、第三方运行时或页面渲染正确性。 **[待确认] 构建结果**:本轮未运行 dev/build/preview,因为它们会产生/覆盖构建缓存或 dist,且当前仅允许新增分析文档。Webpack 的 Vue 2.7 编译路径已得到本地探针证据,但不是整个构建成功证据。后续经授权可在指定输出目录执行构建并检查页面,不能先假设“编译器不匹配必然阻塞所有构建”。 **[待确认] 业务验证**:没有真实登录、后端请求、支付、转账、上传、删除、浏览器截图、视口或键盘 smoke。本报告确认代码与工具基线,不声明业务已通过或 UI 已完成。 ## 8. 未决问题、后续门禁与交付范围 | 状态 | 待确认事项 | 用途 | |---|---|---| | 待确认 | 当前 AGENTS / 一期文档与 DESIGN/实际 UI 的最新统一基线 | 后续实施范围、视觉和语言决策;本轮未改旧文档 | | 待确认 | 生产实际菜单、role/permission 规则、后台授权,以及 menu/wiki/routePath 契约 | 避免按模板角色模型重构 | | 待确认 | 标准成功/失败 payload、code/status/data 层级、空结果约定 | 避免调整 service 解包造成业务回归 | | 待确认 | Node/npm 固定版本、锁文件管理、安装/发布流水线和编译器处理方式 | 确定可复现的验证基线,不擅自更新依赖 | | 待确认 | 正式测试环境、可用测试账号及认证/支付 sandbox 授权 | 执行真实认证与高风险交易回归;不写入凭证 | | 待确认 | 哪些模板/隐藏/未接入页面需要保留,哪些未来可下线 | 未路由/未显示不等于可删除 | | 待确认 | 后续页面是否全部英文,以及后端语言列表契约 | 当前登录已英文,其余页仍混合语言 | | 待确认 | 富文本清洗、上传 token 时序、第三方 CDN 和文件下载契约 | 评估共享边界,而非此次直接修复 | | 待确认 | 响应式、长文案、可访问性、空数据、缓存失效的实际表现 | 补充浏览器与业务验收证据 | 后续实现交付应分别报告新增/修改范围、登录/菜单/接口/样式契约对照、定向测试、lint 的增量与历史基线、实际构建结果和运行 smoke。历史 lint 失败与测试加载阻塞不应通过改规则或删除测试掩盖。 **[已确认] 本轮文件保护口径**:开始时对 383 个已有项目文件记录 SHA-256(包括未跟踪及被忽略的 lockfile、环境文件);排除 .git、node_modules、dist、coverage、.cache。未执行安装、构建、提交或资源优化,也未对这些排除目录执行文件编辑;它们没有纳入哈希比较,不据此断言缓存字节不变。结束时再次比较纳入范围的文件,核查新增集合仅为 `docs/refactor/project-analysis.md` 和 `docs/refactor/page-inventory.md`。哈希基线保留在本轮工具会话中,没有另建报告、脚本或日志文件。 本次分析产生的稳定结论可由既有夜间整理任务按需召回这两份文档;本轮没有修改 Obsidian 或其他长期记忆文件。