基于 WEB-UI-Development-Guide 标准的整体升级,并更名 LokiDeploy → opsx-loki。 前端(Gin+React+shadcn/ui): - 按 02/07 章重构:页面域化拆分(无超 400 行文件)、PageContainer/ListQuery、 反馈分级(成功静默/失败 toast/危险确认)、401 单飞刷新+队列重放 - 暗色模式(next-themes)+ 语义 Token(--primary 品牌色/success/warning/info) - 部署向导(状态推导四步:环境→存储→Loki→采集,内嵌表单闭环) - 日志查询页:LogQL 标签构建器/拖拽直方图/日志上下文/Live Tail/查询历史 - 按用户+标签策略的查询授权(服务端强制合并策略) 后端(Go+Gin+JSON DB): - 简单用户管理(仅 admin,无 RBAC)+ TokenVersion 会话吊销 + 登录 IP 限流 - LDAP 接入:配置(AES 加密存储/hasPassword 脱敏)/测试连接/批量导入/登录 - 存储配置测试连接(SigV4 零依赖)+ 桶不存在自动创建 - Loki 三模式部署修复:SSH 密钥认证、3.x 配置模板、ruler 对象存储嵌套块、 凭证解密、孤儿 deploying 状态恢复、Grafana 部署(可选默认关)+面板导入 - DaemonSet 采集纯 YAML(去 helm 依赖)+ DNS 自愈回退 ClusterIP + 集群名标签 - 日志查询代理:cluster-svc 经 kube-apiserver proxy/url 直连 + cursor 翻页 - 统一 log 命名空间;部署就绪探活;Grafana 访问地址自动记录 修复:run.sh 增量编译检测、Sanitized 脱敏补全、LokiDeployment Update 白名单、 健康检查 CreatedAt 零值等 10+ 存量 bug。 数据兼容:DB 沿用旧 lokideploy.json(新名优先);localStorage 一次性迁移; HKDF 加密前缀保留(已加密凭证不受影响)。
218 lines
14 KiB
Markdown
218 lines
14 KiB
Markdown
# 01 — 框架选型与技术栈
|
||
|
||
> 参考:vue-vben-admin(★★★★★ Vue 标准标杆)、ant-design-pro(★★★★★ React 标准标杆)、soybean-admin(★★★★☆ 强烈推荐)、vue-pure-admin(★★★★☆ 推荐);对照:gin-vue-admin(★★☆☆☆ Gin 全栈)
|
||
|
||
---
|
||
|
||
## 一、选型的第一性问题:单栈还是双栈
|
||
|
||
### 1.1 标杆项目的单栈现实
|
||
|
||
| 项目 | 框架 | 为什么不双栈 |
|
||
|------|------|--------------|
|
||
| ant-design-pro | React 19 | 生态深度绑定 Umi Max 运行时(约定式路由、运行时配置、插件体系) |
|
||
| gin-vue-admin | Vue 3 | Element Plus + 国内管理端生态,Go 后端配套脚手架默认 Vue(★★☆ 仅对照) |
|
||
| vue-vben-admin | Vue 3 | 用 monorepo + adapter 层支持 5 个 UI 库,但**框架始终只有 Vue** |
|
||
| soybean-admin | Vue 3 | 文件路由 + 生成物策略,规范优先(★★★★☆ 强烈推荐) |
|
||
| vue-pure-admin | Vue 3 | 单包直出、轻量实用,官方精简版 thin(★★★★☆ 推荐) |
|
||
|
||
> **结论**:标杆项目全部单栈。多 UI 库共存靠的是「适配器层」(vben 的 `effects/*` adapter),而不是多框架。**框架级双栈的成本(请求层、状态、权限、构建、测试全部 ×2)只有迁移期才值得付。**
|
||
>
|
||
> 参考优先级:**Vue 架构看 vben、规范写法看 soybean、同栈 EP 实操看 pure、React 看 adp**;gva 仅作 Gin 全栈视角补充,不作前端代码质量的首选参考。
|
||
|
||
### 1.2 双栈只在迁移期
|
||
|
||
```
|
||
┌─────────────────────────────────────┐
|
||
│ 后端 REST API(Gin / FastAPI ...) │
|
||
│ 单一契约:/api/* │
|
||
└───────────┬─────────────┬───────────┘
|
||
│ │
|
||
┌───────────▼────┐ ┌─────▼──────────────┐
|
||
│ 主端(功能源) │ │ 副端(迁移参照) │
|
||
│ 承接全部新功能 │ │ 只对齐、不抢先 │
|
||
│ 进入产物 │ │ 迁移完成即退役 │
|
||
└────────────────┘ └────────────────────┘
|
||
```
|
||
|
||
迁移期三条纪律(**必须**):
|
||
|
||
1. **功能以主端为准**:副端只做「对齐」同步,不抢先实现。
|
||
2. **契约以两端共同名为准**:token key、主题 key、模块开关键、权限码、响应格式——迁移开始前就统一命名(推荐命名空间 `<app>.auth.token` / `<app>.theme.mode`),而不是迁移后再改。
|
||
3. **不共享代码,只共享约定**:两端各自实现同一份「契约文档」(见 08 章),不引入 monorepo 这种为双栈兜底的重型方案。
|
||
|
||
### 1.3 何时值得上 monorepo(vben 模式)
|
||
|
||
vben 用 pnpm workspace + Turbo 支撑 5 个 UI 库 app + 30+ 框架包,代价是:
|
||
|
||
- `pnpm-workspace.yaml` 维护 300+ 依赖 catalog,`overrides` 强制统一版本
|
||
- 循环依赖检查(`check:circular`)、依赖合规检查(`check:dep`)成为必跑任务
|
||
- 构建配置收敛为内部包 `internal/vite-config`,app 配置才能薄到 15 行
|
||
|
||
**判断标准**:当「框架层代码」(布局引擎、请求客户端、权限系统)需要被 ≥3 个应用复用时,才值得付 monorepo 成本。普通单产品工程(1-2 个前端)——**不值得**,独立 Vite 工程 + 统一 Makefile 编排即可。soybean 的「轻 workspace」(单应用 + 8 个工具包)是中间形态,适合工具链需要沉淀的团队。
|
||
|
||
### 1.4 何时允许偏离默认栈
|
||
|
||
**必须**:新项目 / 新模块使用本章默认栈(见 00 章「默认技术栈」);存量代码**不因「不合推荐」而大规模重写**——渐进迁移:新文件执行新标准,旧文件随功能迭代就近升级。
|
||
|
||
**允许偏离**的场景(需在 PR / ADR 中说明理由):组件库确缺关键能力(虚拟表格、复杂甘特等);性能热点需要更轻方案;与后端生态强绑定(如脚手架生成的默认产物)。
|
||
|
||
**禁止**:同一工程内并存两套 UI 库、两套状态库、两种 CSS 方案——偏离只允许发生在「整工程」粒度。
|
||
|
||
---
|
||
|
||
## 二、UI 组件库选型
|
||
|
||
### 2.1 三种流派对照
|
||
|
||
| 维度 | Element Plus(本标准 Vue 主推;gva 补充) | Ant Design + ProComponents(adp) | shadcn/ui(React 品牌差异化) |
|
||
|------|--------------------------|-----------------------------------|-----------------------------|
|
||
| 形态 | 传统全量组件库 | 组件库 + 中后台增强层 | 源码复制型(Radix + Tailwind) |
|
||
| 中后台生产力 | 高(表格/表单/弹层齐全) | **极高**(ProTable/ProForm 声明式 CRUD) | 中(原子件全,业务件自建) |
|
||
| 主题定制 | CSS 变量 + SCSS 双通道 | Design Token(configProvider) | **完全可控**(Token 就是你的 CSS 变量) |
|
||
| 暗色模式 | `dark/css-vars.css` 一行接入 | `data-theme="dark"` + token | `.dark` class 天然支持 |
|
||
| TS 支持 | 一般(类型宽松) | 好 | 极好(TSX 源码进仓库) |
|
||
| 体积 | 全量较大,靠 unplugin 按需 | 较大 | 用多少复制多少 |
|
||
| 适合谁 | Vue 主栈、快速交付 | React 中后台重 CRUD | React、要品牌差异化 |
|
||
|
||
### 2.2 选型决策树
|
||
|
||
```
|
||
需要管理端全家桶、快速交付?
|
||
├─ Vue 栈 ──────────▶ Element Plus(+ unplugin 按需引入)
|
||
├─ React 栈、CRUD 为主 ▶ Ant Design + ProComponents
|
||
└─ React 栈、要品牌感 ─▶ shadcn/ui + Tailwind
|
||
|
||
双栈迁移期?
|
||
└─▶ 主端选全量组件库保效率(EP),
|
||
副端选 shadcn 保可读性 —— 常见组合
|
||
```
|
||
|
||
两个 ★★★★☆ 参考的选型启示:soybean-admin(Naive UI + UnoCSS)证明「hook 优先 + 组件薄壳」也能撑起完整中后台;vue-pure-admin 证明了 **Tailwind v4 `@theme` 把 `--el-*` 变量桥接为原子色 token** 是 EP 工程引入原子类的最短路径(详见 06 章)。
|
||
|
||
### 2.3 图标体系(易被忽视的选型)
|
||
|
||
- **glob 注册模式(gva,补充)**:`import.meta.glob` 自动收集 `assets/icons/**/*.svg` 注册为全局组件,菜单存 icon 字符串即可 `<component :is>` 渲染;插件图标加 `插件名-` 前缀防冲突。适合「菜单图标由后端配置下发」的场景。
|
||
- **路由声明模式(adp)**:路由 `icon: 'crown'` 字符串 + 图标映射表,与路由集中声明天然契合。
|
||
- **按需编译模式(pure/soybean)**:unplugin-icons 虚拟模块(`import Upload from '~icons/ri/upload-line'`)+ Iconify 离线/在线双模式。
|
||
- **通用规则**:**图标名是数据(存在菜单表里),渲染是前端映射**——不要把图标组件硬编码进菜单模板。
|
||
|
||
---
|
||
|
||
## 三、构建工具与运行时
|
||
|
||
### 3.1 Vite 是唯一现实选择(2026)
|
||
|
||
| 能力 | 说明 | 标杆用法 |
|
||
|------|------|----------|
|
||
| 按需引入 | `unplugin-auto-import` + `unplugin-vue-components` | EP 组件零 import(vben/soybean 系均如此) |
|
||
| `import.meta.glob` | 动态路由的组件映射基石 | gva/vben/pure 的 backend 菜单模式 |
|
||
| env 分层 | `.env.development` / `.env.production` + `import.meta.env` | 全部项目;vben 另分 `VITE_GLOB_*` 运行时可改(见 08 章) |
|
||
| proxy | dev 反代 `/api` 到后端端口,避免 CORS 配置漂移 | `'/api' → 后端端口`(含 `ws: true` 供 WebSocket) |
|
||
| 构建指纹 | `define` 注入版本/commit hash | adp:`__APP_VERSION__` + git rev-parse 兜底 |
|
||
|
||
**必须**按需引入,**禁止**全量 `app.use(组件库)` + 全量 CSS(gva 全量引入是反面教材,首屏体积大)。
|
||
|
||
### 3.2 路由库
|
||
|
||
- Vue:`vue-router@4`,`createWebHistory`(hash 模式是历史包袱,新工程用 history)
|
||
- React:`react-router@7`,用**路由配置数组 + `<Route>` 声明式**或 `createBrowserRouter`;**禁止**用 `Object.entries().map` + 三元链选页面渲染
|
||
|
||
### 3.3 HTTP 客户端
|
||
|
||
| 库 | 使用方 | 要点 |
|
||
|----|--------|------|
|
||
| axios | vben / soybean / pure / gva | 拦截器、取消(AbortController)生态成熟 |
|
||
| umi-request / axios | adp | 错误契约挂载在 request config 上(skipErrorHandler 等) |
|
||
| fetch | — | 零依赖,但拦截器/超时/取消需自建,**不推荐**作为管理端基础 |
|
||
|
||
> **必须**统一 axios:拦截器结构、错误对象形态在全员脑中保持一致,排障不用切换心智模型。
|
||
|
||
---
|
||
|
||
## 四、TypeScript 采用策略
|
||
|
||
### 4.1 三档策略
|
||
|
||
| 档位 | 适用 | 做法 | 标杆 |
|
||
|------|------|------|------|
|
||
| **纯 JS + JSDoc** | 快速交付的存量 Vue 主端 | 关键模块(request/permission/stores)写 JSDoc 类型注释 | gva |
|
||
| **渐进 TS** | 存量 JS 工程演进 | 新文件 `.ts`,老文件不动;`allowJs: true`;先建 `typings/` 写 `.d.ts` | soybean 路线 |
|
||
| **TS strict** | 新工程(**默认**) | `strict: true`,类型即文档 | adp、vben、soybean、pure |
|
||
|
||
**新项目必须 TS strict**;存量工程按上表渐进,但接口函数**不允许**「既无类型注释也无类型定义」。
|
||
|
||
### 4.2 类型放哪里(adp/soybean 范式)
|
||
|
||
- 全局契约类型集中声明:`declare namespace API { interface RuleListItem {...} }`,服务层函数签名 `request<API.RuleList>(...)` 收口
|
||
- 页面私有类型随页面 co-location(`data.d.ts` / `utils/types.ts`)
|
||
- 路由 meta 用 `vue-router.d.ts` 模块扩充强类型化(vben 30+ 字段全 JSDoc;soybean 17 字段同款)
|
||
- soybean 补充:全局类型按 namespace 组织(`typings/` 下 Api/App/Storage/Env 四区,storage 与 env 均有**键级类型**,i18n key 递归派生)——**JS 工程渐进 TS 的最低成本路径**:先建 `typings/` 写 `.d.ts`,不动业务代码
|
||
|
||
---
|
||
|
||
## 五、状态管理选型
|
||
|
||
### 5.1 先分类再选库
|
||
|
||
```
|
||
状态三类:
|
||
├─ 客户端全局状态(token、用户信息、菜单、UI 偏好)→ Pinia / Zustand
|
||
├─ 服务端状态(列表、详情、缓存失效) → TanStack Query / ProTable request / 自研 usePagedQuery
|
||
└─ URL 状态(筛选、分页、Tab) → 路由 query,永远不要复制进 store
|
||
```
|
||
|
||
### 5.2 三种落法
|
||
|
||
| 落法 | 出处 | 说明 |
|
||
|------|------|------|
|
||
| adp 三层 | initialState(useModel)+ React Query + ProTable request | 服务端状态交给 Query:`queryKey` 按资源命名,mutation 成功后 `invalidateQueries` |
|
||
| vben 分域 store | Pinia setup 风格,按域拆 modules(gva 同构,补充参考) | token/user/router/app/dictionary 各一,持久化手动分层 |
|
||
| pure StoreHook | `useXxxStoreHook()` 包装 store + pinia 实例 | 在 setup 外(拦截器、守卫、工具函数)也能安全取 store |
|
||
| soybean 模块化 | 每模块一目录 `index.ts`(状态编排)+ `shared.ts`(可单测纯函数) | 纯逻辑与响应式状态强制分离 |
|
||
|
||
**经验值**:列表页优先「组件内管理 + 请求取消」(AbortController + 版本号方案,见 03 章),只有跨页面共享的数据才进 store。**不要为了「规范」把每个列表都塞进全局 store。**
|
||
|
||
---
|
||
|
||
## 六、技术栈对照总表
|
||
|
||
| 维度 | ant-design-pro | vue-vben-admin | soybean-admin | vue-pure-admin | gin-vue-admin | 本标准推荐值 |
|
||
| ---- | -------------- | -------------- | ------------- | -------------- | ------------- | ------------------ |
|
||
| 框架 | React 19 | Vue 3 | Vue 3.5 | Vue 3.5 | Vue 3 | Vue 3 或 React(单栈) |
|
||
| 语言 | TS strict | TS strict | TS strict(namespace 组织) | TS(strict 关闭) | JS | 新项目 TS strict;存量渐进 |
|
||
| UI 库 | antd v6 + Pro | antd-vue/EP/naive 适配 | Naive UI + UnoCSS | Element Plus + TW v4 | Element Plus | Vue: EP;React: antd 或 shadcn |
|
||
| 构建 | Umi Max(utoopack) | Vite + Turbo monorepo | Vite(轻 workspace) | Vite 8/rolldown 单包 | Vite | Vite + pnpm |
|
||
| 状态 | useModel + RQ | Pinia + 持久化插件 | Pinia(index+shared) | Pinia(StoreHook 模式) | Pinia 分域 | Pinia(Vue)/ Zustand+RQ(React) |
|
||
| 请求 | umi-request + 错误契约 | RequestClient 类 | 五钩子工厂 + flat 返回 | PureHttp 类(196 行) | axios 单例 | axios 封装(见 03 章) |
|
||
| 样式 | Tailwind v4 + antd-style | HSL Token + TW v4 | ThemeSetting 三端同步 | EP↔TW v4 变量桥接 | SCSS + UnoCSS + EP 变量 | 语义 Token 单源(见 06 章) |
|
||
| 规范 | Biome + commitlint | oxlint + eslint + lefthook | soybeanjs/eslint-config + sa CLI | eslint + stylelint + husky | ESLint flat + Prettier | ESLint flat + Prettier + commitlint |
|
||
| 测试 | Vitest + Playwright | Vitest(内核组件全覆盖) | —(未见体系化) | —(未见体系化) | 无(短板) | Vitest + Playwright(见 08 章) |
|
||
|
||
---
|
||
|
||
## 七、AI 检查清单(对比目标工程逐项核对)
|
||
|
||
**P0(结构性的,立即改)**
|
||
|
||
1. 工程是否单栈?若双栈并存,是否有明确的「主端/副端 + 退役计划」与统一契约命名?
|
||
2. 是否存在同工程两套 UI 库 / 两套状态库 / 两种 CSS 方案?
|
||
3. 组件库是否按需引入(unplugin 或手动)?全量 `app.use()` 即不合格
|
||
4. 包管理器是否 pnpm?`preinstall: only-allow pnpm` 是否配置?
|
||
|
||
**P1(质量项,排期改)**
|
||
|
||
5. 新文件是否 TS / 存量是否补 JSDoc?接口函数是否存在「无类型也无注释」
|
||
6. 路由渲染是否声明式(禁止 entries+三元链)?
|
||
7. HTTP 客户端是否统一 axios?
|
||
8. 状态三分法是否被遵守(URL 状态不进 store、列表不进全局 store)?
|
||
|
||
**P2(演进项)**
|
||
|
||
9. 是否需要 `typings/` 目录承接全局契约类型(soybean 四区范式)?
|
||
10. 构建指纹(版本/commit 注入)是否配置?
|
||
|
||
---
|
||
|
||
> 下一篇:[02-项目架构与目录结构](02-项目架构与目录结构.md) —— 目录是架构的最后陈述。
|