Skip to content

应用入口与外壳 ​

本文引用的文件

目录 ​

  1. 简介
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 性能注意事项
  8. 故障排查指南
  9. 结论

简介 ​

本文聚焦前端应用的启动过程与应用外壳职责,围绕以下目标展开:

  • 说明 web/src/main.js 如何创建并挂载 Vue 3 应用。
  • 解释 index.html 与 manifest.webmanifest 在站点元信息和渐进式 Web 应用中的作用。
  • 详细说明 App.vue 作为应用外壳的职责:加载会话、注入国际化、渲染路由视图、组合管理面板、插件 iframe 宿主、弹窗宿主、全局提示和页面加载中容器。
  • 描述服务端渲染期间语言包、站点 URL、主题 token、登录态和诊断数据如何传入单页应用。
  • 明确客户端与服务端之间的数据契约、初始 JSON、错误边界和回退行为。
  • 提供新增全局样式、全局中间件逻辑以及扩展 App.vue 布局的示例路径。
  • 指出根组件相关的性能注意事项,避免首屏重计算阻塞渲染。

项目结构 ​

本项目的 SPA 前端位于 web 目录,关键文件包括:

  • web/index.html:浏览器加载的 HTML 骨架,定义应用挂载点、PWA manifest、主题色和启动占位。
  • web/public/manifest.webmanifest:PWA 清单,声明应用名称、启动地址、显示模式和图标。
  • web/src/main.js:Vue 3 应用入口,创建应用实例、注册国际化、引入全局样式并挂载到 DOM。
  • web/src/App.vue:应用外壳,负责会话引导、路由视图切换、全局 UI 容器组合和首屏引导状态。
  • web/src/i18n.js:vue-i18n 初始化、语言解析策略和本地化设置。
  • web/src/style.css:全站基础样式,导入服务器提供的主题 token 文件。
  • web/src/api.js:统一 API 请求封装、会话守卫回调钩子和业务接口集合。
  • web/src/session.js:401 未授权事件通知与守卫。
  • web/src/dialog.js:替代原生对话框的全局弹窗状态机。
  • web/src/components/*:管理面板、弹窗宿主、页面加载中、全局提示等全局 UI 容器。
  • server/cmd/skin-nexus/skinlib_ssr.go:服务端为皮肤库列表注入可爬取内容,并将内容写入 #app 标记区域。

图表来源

章节来源

核心组件 ​

应用入口 main.js ​

web/src/main.js 是 Vue 3 应用的唯一入口,职责非常集中:

  • 从 Vue 创建应用实例。
  • 导入根组件 App.vue。
  • 导入并注册国际化实例 i18n。
  • 引入全局样式 style.css。
  • 将应用挂载到 index.html 中的 #app 节点。

这意味着所有后续初始化都发生在 App.vue 中,main.js 只承担最小化的装配职责。

章节来源

HTML 骨架与 PWA 清单 ​

index.html 定义了以下内容:

  • 文档语言设为中文。
  • 引入 favicon、Apple touch icon 和 PWA manifest。
  • 设置 theme-color 用于移动端浏览器状态栏。
  • 保留服务端可替换标记,供服务端注入皮肤库列表等 SSR 内容。
  • 在 #app 内提供静态启动占位动画,使 SPA bundle 尚未到达时用户也能看到反馈。
  • 以模块方式加载 /src/main.js。

manifest.webmanifest 则定义:

  • 应用名称与短名称。
  • 应用描述。
  • 启动地址。
  • 显示模式为独立窗口。
  • 背景色与主题色。
  • SVG 与 PNG 图标集。

这两个文件共同构成站点元信息、SEO 可见性和 PWA 安装能力的基础。

章节来源

国际化 i18n ​

web/src/i18n.js 使用 vue-i18n 初始化多语言支持,语言优先级如下:

  1. skin_lang Cookie。
  2. localStorage.skin_lang。
  3. navigator.language。
  4. 默认中文。

它还会:

  • 同步 document.documentElement.lang。
  • 持久化 Cookie 与 localStorage。
  • 暴露 setLocale 供顶部语言选择器调用。
  • 将中英文消息对象直接注册为 messages。

该模块与 App.vue 的语言下拉框协作,保证跨刷新后的语言偏好一致。

章节来源

API 封装与中间件钩子 ​

web/src/api.js 提供统一的 req 方法,负责:

  • 发起同域请求。
  • 处理非 JSON 响应。
  • 包装错误对象,附带 HTTP 状态码和后端数据。
  • 当后端返回邮箱验证必需错误时,通过 gateWatch.onVerifyRequired 通知外壳重新读取会话。

它还导出:

  • api 对象,包含登录、注册、资料、衣柜、材质、通知、皮肤库、OIDC、Union 等接口。
  • gateWatch,作为客户端与后端校验门控之间的轻量中间件通道。

章节来源

会话与 401 守卫 ​

web/src/session.js 暴露:

  • setUnauthorizedHandler:由 App.vue 注册“已登出”处理器。
  • notifyUnauthorized:任意页面捕获 401 后统一触发。
  • guard401:判断错误是否属于会话失效,帮助页面决定是否阻止重复提示。

App.vue 在启动阶段将该处理器设置为清空用户、跳转登录页。

章节来源

架构总览 ​

下面的架构图展示了浏览器、HTML、Vue 应用、API、服务端渲染和全局 UI 容器之间的关系:

图表来源

详细组件分析 ​

App.vue:应用外壳 ​

App.vue 是整个 SPA 的控制中心,职责远不止“渲染一个页面”。其核心职责包括:

1. 初始化与引导 ​

  • 维护 booting、bootErr 表示引导状态和引导失败信息。
  • 并行发起四个关键请求:
    • /api/v1/setup/status:检测是否需要安装引导。
    • api.siteConfig():站点配置,控制邀请码、邮件开关、Union 开关、上传限制和底部链接。
    • loadPluginUI():加载插件界面与导航。
    • api.me():当前用户会话。
  • 如果安装未完成,直接跳转到安装视图。
  • 如果 me 请求出现网络或超时错误,不视为注销,而是展示可重试的错误边界。
  • 如果是真正的 401,则将用户置空并进入登录流程。

2. 会话状态与权限 ​

  • 保存当前用户对象 user。
  • 根据 permission 判断是否为管理员。
  • 向子组件提供 sessionUser 与 siteConfig。
  • 通过 gotoView 实现无刷新跳转,避免整页刷新丢失引导状态。

3. 路由与视图切换 ​

  • 使用内部 view 字符串驱动不同视图:登录、注册、找回密码、重置密码、邮箱验证、设备页、仪表板、衣柜、玩家、资料、应用、Union、管理面板、插件页面、帮助页等。
  • 将内部视图映射到 URL,例如 /user/dashboard、/user/closet、/admin、/skinlib、/help。
  • 监听 popstate,实现浏览器前进后退与视图同步。
  • 支持待登录后恢复的目标视图,避免用户被卡在登录卡之后看不到原始页面。

4. 国际化注入 ​

  • 使用 useI18n 获取翻译函数和当前语言。
  • 顶部语言选择器调用 setLocale,更新语言、Cookie 和 html lang。
  • 所有文案使用 t('...'),包括错误提示、按钮文本、导航标签和页面标题。

5. 全局 UI 容器组合 ​

  • AdminPanel:异步加载的管理面板外壳,按权限和控制台配置决定可见项。
  • PluginSlot / PluginFields:插件 iframe 宿主和表单字段扩展点。
  • ModalHost:全局弹窗宿主,承载 dialog.js 定义的 prompt、confirm、alert。
  • ToastHub:全局提示容器,订阅 window.skin-toast 事件,自动消失。
  • PageLoading:异步视图加载时的占位组件。

6. 错误边界与回退 ​

  • 引导阶段失败时展示带重试按钮的错误界面。
  • 401 错误通过 session.js 统一回到登录页。
  • 邮箱验证门控关闭时,通过 gateWatch.onVerifyRequired 重新读取会话。
  • 登录失败可能携带锁定期,客户端用倒计时防止用户反复提交。

图表来源

章节来源

AdminPanel:管理面板外壳 ​

AdminPanel.vue 是管理侧的二级外壳,职责包括:

  • 接收来自 App.vue 的 user、siteConf、confLoaded。
  • 根据用户权限决定是否需要机器令牌。
  • 渲染左侧导航和右侧管理视图。
  • 支持管理面板内的深链,如 /admin/users、/admin/plugin/<id>。
  • 将已启用的站点配置过滤进导航项。
  • 复用插件系统,允许管理员扩展管理面板。

该组件强调“不要重复拉取 App.vue 已经加载过的数据”,以减少串行往返延迟。

章节来源

ModalHost:弹窗宿主 ​

ModalHost.vue 是全局弹窗的唯一宿主,负责:

  • 消费 dialog.js 中的 dialogState。
  • 区分 alert、confirm、prompt 三种弹窗形态。
  • 调用 resolveDialog 完成 Promise 结果。
  • 对 prompt 类型自动聚焦输入框。

它与 dialog.js 形成“状态源 + 宿主视图”的解耦结构,避免每个页面重复实现确认框。

章节来源

ToastHub:全局提示容器 ​

ToastHub.vue 订阅 window.skin-toast 事件,负责:

  • 接收插件或宿主代码发送的 toast。
  • 归一化消息内容和级别。
  • 最多保留最近三条。
  • 每条约 4.2 秒自动消失。
  • 支持手动关闭。

它是插件系统与宿主 UI 之间的重要桥接点。

章节来源

PageLoading:页面加载中容器 ​

PageLoading.vue 是一个极简占位组件,用于:

  • 异步路由懒加载期间的视觉反馈。
  • 配合 defineAsyncComponent 的 loadingComponent。
  • 使用国际化文案显示加载中。

章节来源

服务端渲染与 SPA 接管 ​

server/cmd/skin-nexus/skinlib_ssr.go 说明:

  • 皮肤库是公开页面,但纯 SPA 无法让爬虫抓取正文。
  • 服务端首次渲染皮肤库列表,并将其插入 #app 内部的标记区间。
  • Vue 挂载后替换 #app 内容,因此第一次绘制的是可爬取的静态内容,而不是空白。
  • 爬虫可见内容与后续客户端交互仍由前端 SPA 负责。

这构成一种“SSR 提供 SEO 与首屏内容,SPA 接管交互”的混合模式。

章节来源

依赖关系分析 ​

图表来源

客户端与服务端数据契约 ​

数据类别传输方式客户端用途失败或缺失时的行为
语言偏好Cookie、localStorage、navigator.languagei18n 初始化语言回退到中文
站点配置/api/v1/site-config控制邀请码、邮件、Union、底部链接保持默认值,不阻断引导
用户会话/api/v1/me显示用户名、权限、通知数、邮箱验证状态401 视为未登录;网络错误保留已有界面并提示重试
安装状态/api/v1/setup/status判断是否进入安装流程失败不影响其他数据继续加载
插件界面loadPluginUI()扩展导航和表单字段不应影响核心登录流程
SSR 内容HTML 中的 #app 标记区间给爬虫提供皮肤库列表Vue 挂载后替换为动态内容
主题 token服务器 CSS 文件通过 style.css 导入全局颜色、字体、间距、阴影缺少时应降级到基础可读样式
诊断数据诊断相关逻辑主要在服务端和管理面板不在本入口直接注入 SPA诊断问题应通过管理面板或日志定位

章节来源

性能注意事项 ​

根组件应避免的重计算 ​

App.vue 在引导阶段执行大量副作用:

  • 并发请求安装状态、站点配置、插件界面和用户信息。
  • 解析 URL、历史状态和登录回调。
  • 计算管理员权限、导航分组、站点名称和底部链接。
  • 处理锁定期倒计时、语言切换、插件导航和错误边界。

新增逻辑时应遵循以下原则:

  • 不要在 computed 中发起网络请求。
  • 不要把昂贵计算放在模板表达式中。
  • 不要无条件增加新的顶层状态,除非它确实参与渲染。
  • 不要在每次渲染中创建新数组或新对象,除非必要。
  • 对于仅用于调试或管理面板的数据,放在局部状态或懒加载模块中。
  • 对长列表、图片预览、材质解析等重任务,使用异步组件、懒加载或离屏处理。

首屏优化现状 ​

代码已经做了若干优化:

  • 使用 Promise.allSettled 并行加载多个独立资源。
  • 为重要路由使用 defineAsyncComponent 和 PageLoading。
  • 管理员视图按需加载。
  • 语言切换不刷新页面。
  • 引导失败提供重试按钮,避免误判为会话丢失。

进一步建议 ​

  • 若新增全局中间件逻辑,优先放在 api.js 的请求层或 session.js 的事件层,而不是 App.vue。
  • 若新增全局样式,优先放入 web/src/style.css,并通过主题 token 覆盖。
  • 若新增全局 UI 容器,优先以独立组件形式挂载在 App.vue 中,并保持低耦合。
  • 对插件 iframe 宿主和弹窗宿主,尽量复用现有 PluginSlot、PluginFields、ModalHost、dialog.js。

[本节为通用性能指导,不直接分析具体代码行]

故障排查指南 ​

应用白屏或一直显示启动旋转 ​

检查:

  • index.html 中是否存在 #app。
  • main.js 是否正确挂载到 #app。
  • App.vue 是否在引导阶段抛出未捕获异常。
  • 网络请求是否被拦截或跨域失败。

参考位置:

登录后仍停留在登录页 ​

检查:

  • finishAuth 是否被 ?next=、设备页、插件外链或管理面板深链占用。
  • pendingView 是否在登录后正确清空。
  • 登录成功后是否尝试补充 /me 以获取完整会话。

参考位置:

401 未授权后没有回到登录页 ​

检查:

  • 页面是否调用 notifyUnauthorized。
  • App.vue 是否已通过 setUnauthorizedHandler 注册处理器。
  • 错误对象是否包含 status === 401。

参考位置:

语言切换无效 ​

检查:

  • setLocale 是否更新了 i18n.global.locale。
  • Cookie 是否被写入。
  • document.documentElement.lang 是否同步。
  • 浏览器是否拒绝 Cookie,导致回落到 localStorage 或 navigator。

参考位置:

弹窗不显示或被原生对话框干扰 ​

检查:

  • 是否使用 dialog.js 的 promptDialog、confirmDialog、alertDialog。
  • ModalHost 是否挂载在应用外壳中。
  • 是否在沙箱插件 iframe 中误用原生 window.confirm。

参考位置:

提示不显示或一直堆积 ​

检查:

  • 是否派发 window.skin-toast 事件。
  • ToastHub 是否已挂载。
  • 消息是否为空字符串。
  • 定时器是否在组件卸载时清理。

参考位置:

爬虫看不到皮肤库内容 ​

检查:

  • 服务端是否启用皮肤库 SSR。
  • HTML 中 #app 内部是否包含 SSR 生成的皮肤列表。
  • Vue 挂载后是否覆盖了 SSR 内容。

参考位置:

结论 ​

web/src/main.js 承担最小化的 Vue 应用装配职责,真正的应用状态、路由、会话、国际化、权限和全局 UI 集中在 App.vue。index.html 提供 PWA 与站点元信息,manifest.webmanifest 提供安装能力,而服务端 SSR 则为爬虫提供可索引的皮肤库内容。

App.vue 的核心价值在于:

  • 在引导阶段协调安装状态、站点配置、插件界面和用户会话。
  • 把会话、权限、站点能力和导航状态提供给子组件。
  • 统一管理登录、注册、找回密码、邮箱验证、设备页、仪表板和插件入口。
  • 组合管理面板、弹窗宿主、提示容器和加载中占位。
  • 处理 401、网络超时、邮箱验证门控和锁定期等边界情况。

新增功能时,推荐:

  • 全局样式放入 style.css。
  • 全局中间件逻辑放入 api.js 或 session.js。
  • 全局 UI 容器以独立组件形式挂载到 App.vue。
  • 避免在根组件中进行阻塞性重计算。
  • 保持与服务端的数据契约稳定,特别是会话、站点配置和语言偏好。

AGPL-3.0 · SkinNexus