基于 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 加密前缀保留(已加密凭证不受影响)。
12 KiB
12 KiB
02 — 项目架构与目录结构
参考:vue-vben-admin(三层包划分)、soybean-admin(文件路由 + store 纯函数分离)、ant-design-pro(页面 co-location)、vue-pure-admin(页面内聚五件套);补充:gin-vue-admin(分域 api/view)
一、组织目录的三种流派
1.1 流派对照
| 流派 | 代表 | 结构 | 优点 | 代价 |
|---|---|---|---|---|
| 按层分(layer-first) | 早期中台 | components/ views/ api/ stores/ 每层内平铺全部业务 |
上手简单 | 业务散在 5 个目录,删模块要全局找 |
| 按域分(domain-first) | 本标准(Vue 单包)/ gva | 层目录保留,但 api/user/、views/user/、stores/ 域内聚合 |
删域=删目录,心智清晰 | 需要约定维持层内域目录一致 |
| 按功能分包(feature-first) | adp 页面级、vben 包级 | 页面目录自带 service/mock/类型/样式;框架能力独立成包 | 高内聚,可整体搬移 | 需要晋升机制防止复制泛滥 |
结论:中小工程用「按域分 + 页面 co-location」混合式——全局按域(api/views 域目录对齐),页面私有代码留在页面目录内。
1.2 四标杆的目录精髓
vue-vben-admin(依赖方向三层,分层标杆)
apps/* ──▶ packages/effects/* ──▶ packages/@core/*
(app 是唯一允许直接依赖具体 UI 库的层;
@core 零 UI 依赖零业务;effects 通过 adapter 适配 UI 生态)
ant-design-pro(页面五件套 co-location)
src/pages/table-list/
├── index.tsx # 页面(ProTable 声明式列配置)
├── service.ts # 本页接口(手写层)
├── data.d.ts # 本页类型
├── _mock.ts # 本页 mock(config 显式 include)
└── components/
├── CreateForm.tsx # 新建弹窗(trigger 模式)
└── UpdateForm.tsx # 编辑弹窗(StepsForm 进 Modal)
soybean-admin(生成物 + store 纯函数分离,★★★★☆)
src/
├── router/elegant/ # 文件路由生成物(routes/transform/imports + RouteKey 类型 d.ts)
├── service/{api,request}/ # api=纯请求函数、request=实例拦截器,两层分离
├── store/modules/<mod>/ # 每模块 index.ts(状态编排) + shared.ts(可单测纯函数)
├── typings/{api,...} # 全局 namespace 类型(Api/App/Storage/Env)
└── views/_builtin/ # 403/404/500/login 内置页统一收纳,业务页平级
vue-pure-admin(页面内聚五件套,★★★★☆)
src/views/system/user/
├── index.vue # 纯模板(零逻辑,只解构 hook)
├── form/index.vue # 弹窗表单
├── tree.vue # 左树
└── utils/{hook.tsx,rule.ts,types.ts} # 逻辑/校验/类型全在本域
gin-vue-admin(补充:全局分域 + 指令/钩子独立)
src/
├── api/ # 一域一文件:user.js menu.js ...
├── pinia/modules/ # 一域一 store
├── view/superAdmin/ view/example/ # 按业务分组
├── directive/auth.js # v-auth 指令独立目录
├── hooks/responsive.js # 组合式函数独立目录
└── plugin/<name>/ # 业务插件 = 自带 view/api/assets 的小应用
二、标准目录结构(Vue 工程)
2.1 目标结构
src/
├── main.js # 入口:仅装配(EP按需、pinia、router、指令、全局组件)
├── App.vue
├── api/ # ★ 按功能域分目录,与 views 域目录同名
│ ├── auth/ # captcha.js mfa.js
│ ├── user/ # users.js roles.js permissions.js
│ ├── rbac/ # 域小可单文件,大了再拆
│ ├── system/ # menu.js modules.js logs.js ...
│ └── <domain>/ # 业务域
├── views/ # ★ 域目录与 api/ 一一对应
│ ├── user/ # Users.vue + components/XxxFormDialog.vue ...
│ ├── <domain>/
│ └── _builtin/ # 403/404/500/login 内置页统一收纳(soybean 范式)
├── stores/ # Pinia:一域一文件(auth.js menu.js site.js ...)
├── composables/ # useXxx(依赖业务上下文的钩子)
├── components/ # ★ 全局组件(需晋升,见第四章)
├── layout/ # 布局骨架(业务页只挂内容区,禁止重写布局)
├── router/ # index.js(含守卫) modules/(路由分文件)
├── config/ # theme.js modules.js(纯配置,无副作用导入)
├── typings/ # 全局契约类型(TS 渐进路线,soybean 四区)
├── utils/ # request.js permission.js(无状态工具)
└── styles/ # 全部文件必须有入口引用链
React 工程同构映射:views→pages、composables→hooks、stores→store(zustand slices)、components→components(含 barrel 出口)。
2.2 层内规则(必须)
- api 域目录与 views 域目录强制同名(
api/cron/↔views/cron/);禁止在 api 根级别放散文件 - store 文件名 = 导出名(
stores/auth.js→useAuthStore);单 store ≤300 行;推荐 soybean 的「index + shared」目录式(状态编排与纯函数分离) - layout/ 是禁改区:业务页通过页面容器组件的 header/slot 定制,不碰布局骨架(可裁剪底座的前提)
- components/ 只放「晋升组件」(见第四章晋升机制),新页面私有组件一律
views/<域>/components/
2.3 可裁剪分层与模块开关(底座/脚手架推荐)
┌─────────────────────────────────────────────────────┐
│ 壳层 shell 登录/MFA/403/500 —— 不挂主布局 │
├─────────────────────────────────────────────────────┤
│ 系统层 system 用户/RBAC/审计/设置 —— 底座自带 │
├─────────────────────────────────────────────────────┤
│ 示例层 examples 演示模块 —— fork 后可整层删除 │
└─────────────────────────────────────────────────────┘
- 路由
meta.layer: 'shell' | 'system' | 'examples'声明归属 - 运行时开关:源真相在后端(如
GET/PUT /api/system/modules),localStorage 仅作缓存——禁止把 localStorage 当源真相 - 构建期裁剪:环境变量(如
VITE_ENABLE_EXAMPLES=false)+ 路由中字面量比较,保证打包器 tree-shake 掉示例 chunk - 示例层代码不得 import 系统层内聚单元,保证整层可摘除;示例域附删除指引文档(EXAMPLES.md 惯例)
分层是目录结构之外的第二根轴:每个新模块必须先回答「我在哪一层」。
2.4 命名规范与类型目录
| 对象 | 规范 | 示例 |
|---|---|---|
| 组件文件 | PascalCase | UserFormDialog.vue |
| 工具 / composable | camelCase,composable 带 use 前缀 | request.js、usePagedQuery.js |
| 目录 | kebab-case(views 域目录用业务域名) | user-management/、cron/ |
| store 文件 | 文件名与导出同名 | stores/auth.js → useAuthStore |
| API 文件 | 资源名复数 | api/user/users.js |
| 常量 | UPPER_SNAKE_CASE | SYSTEM_ORDER |
类型目录(TS 渐进路线,soybean 范式):全局契约类型集中 typings/(Api / App / Storage / Env 四区 namespace,storage 与 env 均键级类型);页面私有类型随页面(data.d.ts / utils/types.ts);禁止同一类型定义在多个组件内重复出现。
三、页面 co-location(adp 五件套)
3.1 什么时候页面自带 service
全局 api/<域>/xxx.js # 多页面复用、域级语义(users.js 的 list/create/delete)
页面内 service.ts # 只服务本页的接口(dashboard 聚合、报表导出)
判定:接口第二个消费者出现时,从页面 service.ts 提升到 api/<域>/。反向不成立——不要预防性提升。
3.2 页面目录模板
views/<domain>/<Page>.vue # 页面骨架:容器 + 列表组件 + 子组件编排(≤400 行)
views/<domain>/components/ # 本页私有组件(表单弹窗、详情抽屉、单元格渲染器)
views/<domain>/composables/ # 本页私有逻辑(可选,超过 2 个页面组件共享时建)
views/<domain>/utils/ # 本页 hook/rule/types(pure 范式,可选)
views/<domain>/EXAMPLES.md # 仅示例层:删除指引
四、公共组件晋升机制
第 1 次使用:写在页面 components/ 里
第 2 次使用:复制一份,页面内注明来源 ← 允许的重复
第 3 次使用:晋升 src/components/,删除两份复制 ← 必须执行
晋升时同步做三件事:
- 去业务化:props 收口(
fetcher/permissions进来,具体 API 调用出去) - 注册出口:Vue 端全局组件在
main.js注册;React 端进components/index.tsbarrel(adp:按「布局组件 / 业务组件」分组导出) - 写使用说明:组件头部注释 3 行(用途/必需 props/已知限制)
必备的两个全局页面组件(详见 05/07 章):PageContainer(页面容器:页头/面包屑/slot)与 ListPage(分页列表:fetcher + 筛选 slot + 表格透传 + 分页)。
五、架构守护:让目录约定不被腐蚀
| 守护手段 | 出处 | 可落地做法 |
|---|---|---|
| 循环依赖扫描 | vben check:circular |
npx madge --circular src/ 进 CI |
| 依赖方向检查 | vben check:dep(@core 禁依赖 UI 库) |
示例层禁 import 系统层:ESLint no-restricted-imports 按路径分组 |
| 死代码检测 | adp coverage 排除生成物 | CI 跑 knip/depcheck,styles 无入口文件直接报错 |
| 生成物隔离 | adp services/ant-design-pro/(OpenAPI 生成,禁手改) |
生成物独立目录 + lint ignore |
| 路由自动收集 | pure router/modules/ + import.meta.glob |
新增路由文件零注册代码 |
六、最佳实践清单
✅ 要做
- api 域目录与 views 域目录同名;新域从脚手架起步(自动生成五处对照:菜单/路由/页面/API/权限码)
- 页面私有组件不出页面目录;晋升走「三次法则」
- 每个新模块先声明
meta.layer,示例层保持可整层摘除 - store 按域拆分、文件名=导出名、单 store ≤300 行
- 目录结构变化(新增域/晋升组件/删示例)在同一 PR 内更新架构文档
❌ 不要
- 不要在 api 根级别放散文件
- 不要把页面私有组件直接丢进
src/components/ - 不要让业务页 import layout 内部组件(只准用 slot 协议)
- 不要建「future/」「temp/」「new/」这类无语义目录
- 不要保留无入口引用的样式文件和空目录
七、AI 检查清单(对比目标工程逐项核对)
P0
- api 与 views 域目录是否同名对齐?有无 api 根级散文件?
- store 文件名与导出名是否一致?有无超 300 行的 store?
- 有无空目录、无引用样式、描述已不存在目录的文档残留?
P1
- 页面骨架是否 ≤400 行?超 400 行的视图列出拆分建议(参照 07 章)
- 私有组件是否越界进全局 components/?全局组件是否都有注册出口与头注释?
- 若为可裁剪底座:meta.layer 是否全覆盖?模块开关源真相是否在后端?
- CI 是否有循环依赖与死代码检查?
P2
- 是否值得引入路由文件自动收集(
router/modules/+ glob)或文件路由(soybean @elegant-router)? typings/全局类型目录是否建立(Api/App/Storage/Env 四区)?
下一篇:03-请求层与数据流 —— 前端工程的血管系统。