@usethink/cf-admin-fe 0.1.1 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +244 -53
  3. package/dist/composables/createAdminFlagSession.d.ts +42 -0
  4. package/dist/composables/createAdminRequest.d.ts +95 -0
  5. package/dist/composables/createAdminTokenSession.d.ts +49 -0
  6. package/dist/composables/index.d.ts +7 -1
  7. package/dist/composables/useAdminListLoader.d.ts +45 -0
  8. package/dist/composables/useAdminSidebar.d.ts +17 -0
  9. package/dist/composables/useClipboard.d.ts +24 -2
  10. package/dist/composables/useConfirmDialog.d.ts +10 -4
  11. package/dist/composables/useNow.d.ts +3 -0
  12. package/dist/composables/useTablePagination.d.ts +6 -0
  13. package/dist/composables/useTableSelection.d.ts +2 -2
  14. package/dist/composables/useToast.d.ts +1 -1
  15. package/dist/types/index.d.ts +1 -0
  16. package/dist/types/system-config.d.ts +34 -0
  17. package/dist/utils/admin-navigation.d.ts +66 -0
  18. package/dist/utils/admin-query.d.ts +29 -0
  19. package/dist/utils/batch-limit.d.ts +38 -0
  20. package/dist/utils/batch-result.d.ts +41 -0
  21. package/dist/utils/body-scroll-lock.d.ts +13 -0
  22. package/dist/utils/csv-export.d.ts +8 -0
  23. package/dist/utils/currency-cents.d.ts +16 -0
  24. package/dist/utils/datetime.d.ts +20 -0
  25. package/dist/utils/error-message.d.ts +7 -0
  26. package/dist/utils/format-metadata.d.ts +5 -0
  27. package/dist/utils/i18n.d.ts +14 -0
  28. package/dist/utils/index.d.ts +13 -0
  29. package/dist/utils/otp-input.d.ts +11 -0
  30. package/dist/utils/system-config-engine.d.ts +65 -0
  31. package/docs/001_cf-admin-fe/346/217/220/345/217/226/345/267/256/350/267/235/345/210/206/346/236/220/346/212/245/345/221/212_2026-08-05.md +225 -0
  32. package/docs/002_cf-admin-fe/344/275/277/347/224/250/350/257/264/346/230/216/344/271/246/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.md +805 -0
  33. package/docs/003_cf-admin-fe/346/236/201/350/207/264/345/214/226/346/226/271/346/241/210/344/270/216/345/217/226/350/210/215_2026-08-05.md +332 -0
  34. package/package.json +35 -6
  35. package/recipes/AdminLayout.vue +73 -0
  36. package/recipes/README.md +131 -0
  37. package/recipes/i18n-merge.snippet.ts +29 -0
  38. package/recipes/list-page.snippet.ts +141 -0
  39. package/recipes/main-styles.snippet.ts +13 -0
  40. package/recipes/reexports/components.ts +19 -0
  41. package/recipes/reexports/composables.ts +29 -0
  42. package/recipes/reexports/utils.ts +31 -0
  43. package/recipes/router-guard.snippet.ts +32 -0
  44. package/recipes/useAdminAuth.cookie.ts +29 -0
  45. package/recipes/useAdminAuth.shop.ts +29 -0
  46. package/recipes/useAdminRequest.cookie.ts +36 -0
  47. package/recipes/useAdminRequest.ts +37 -0
  48. package/src/components/AdminLoginShell.vue +31 -0
  49. package/src/components/AdminMetadataDetail.vue +121 -0
  50. package/src/components/AdminShell.vue +384 -0
  51. package/src/components/ConfigField.vue +387 -0
  52. package/src/components/ConfirmDialog.vue +67 -3
  53. package/src/components/index.ts +6 -0
  54. package/src/composables/createAdminFlagSession.ts +129 -0
  55. package/src/composables/createAdminRequest.ts +322 -0
  56. package/src/composables/createAdminTokenSession.ts +149 -0
  57. package/src/composables/index.ts +51 -0
  58. package/src/composables/useAdminBatchOperation.ts +4 -0
  59. package/src/composables/useAdminListLoader.ts +124 -0
  60. package/src/composables/useAdminSidebar.ts +68 -0
  61. package/src/composables/useClipboard.ts +41 -2
  62. package/src/composables/useConfirmDialog.ts +26 -4
  63. package/src/composables/useNow.ts +38 -0
  64. package/src/composables/useTablePagination.ts +6 -0
  65. package/src/composables/useTableSelection.ts +2 -2
  66. package/src/composables/useToast.ts +4 -1
  67. package/src/env.d.ts +2 -2
  68. package/src/i18n/index.ts +8 -0
  69. package/src/i18n/kit-messages.ts +297 -0
  70. package/src/index.ts +147 -4
  71. package/src/styles/admin-primitives.css +28 -1
  72. package/src/styles/index.css +1 -0
  73. package/src/styles/login-primitives.css +149 -0
  74. package/src/types/index.ts +7 -0
  75. package/src/types/system-config.ts +38 -0
  76. package/src/utils/admin-navigation.ts +138 -0
  77. package/src/utils/admin-query.ts +51 -0
  78. package/src/utils/batch-limit.ts +66 -0
  79. package/src/utils/batch-result.ts +81 -0
  80. package/src/utils/body-scroll-lock.ts +63 -0
  81. package/src/utils/csv-export.ts +25 -0
  82. package/src/utils/currency-cents.ts +30 -0
  83. package/src/utils/datetime.ts +54 -0
  84. package/src/utils/error-message.ts +32 -0
  85. package/src/utils/format-metadata.ts +11 -0
  86. package/src/utils/i18n.ts +63 -0
  87. package/src/utils/index.ts +87 -0
  88. package/src/utils/otp-input.ts +18 -0
  89. package/src/utils/system-config-engine.ts +212 -0
  90. package/dist/components/index.d.ts +0 -4
  91. package/dist/index.d.ts +0 -17
@@ -0,0 +1,332 @@
1
+ # @usethink/cf-admin-fe 极致化方案与取舍
2
+
3
+ > **版本锚点:** 以 `package.json` 为准(**0.2.8** 已落地 §9.1 P0 三件套;起草基线为 0.2.7)
4
+ > **日期:** 2026-08-05(P0 落地修订同日)
5
+ > **性质:** 路线图与决策备忘(**不是**待办全开的 backlog;**不是**对外 API 说明书)
6
+ > **配套:** [`002` 使用说明书](./002_cf-admin-fe使用说明书与注意事项.md) · [`001` 提取差距结案](./001_cf-admin-fe提取差距分析报告_2026-08-05.md) · 根 [`README.md`](../README.md) · [`recipes/README.md`](../recipes/README.md)
7
+
8
+ ---
9
+
10
+ ## 0. 文档怎么用
11
+
12
+ | 读者问题 | 读哪里 |
13
+ |----------|--------|
14
+ | 怎么接套件、有哪些 API | **002** + README + recipes |
15
+ | 当初抽了什么、故意不抽什么 | **001**(结案,勿当未完成清单) |
16
+ | **下一步该不该做、做到什么程度** | **本文 003** |
17
+ | 某条观察项(如 number offset)何时可提 API | 本文 §5.2 + 001 **R5** + 002 §5.8 / 不做清单 |
18
+
19
+ **维护约定:**
20
+
21
+ - 实施完成后,把对应条目改成「已落地(版本号)」,**不要**删决策理由。
22
+ - 新想法先对照 §1 定位与 §2 提取纪律;通不过则记入 §7「冻结 / 触发条件」,勿直接开码。
23
+ - 本文可随迭代修订;**破坏性结论变更**须在文末修订表留一行。
24
+
25
+ ---
26
+
27
+ ## 1. 核心定位(一切极致化的前提)
28
+
29
+ ### 1.1 一句话
30
+
31
+ `@usethink/cf-admin-fe` 是 **CF 产品族 Vue 3 管理端「业务模式」基础套件**,不是通用 UI 组件库,不是业务中台,不是 Worker/API 包。
32
+
33
+ ### 1.2 已验证边界(0.2.x 守住的)
34
+
35
+ | 是 | 不是 |
36
+ |----|------|
37
+ | 壳(AdminShell / LoginShell)、表格 UX、分页/选择/确认/Toast | Element / Ant 式通用控件集 |
38
+ | 双会话工厂(Bearer token / Cookie+flag)+ request 旋钮 | 产品域 `api/admin.ts` 方法全集 |
39
+ | ConfigField、列表 loader、错误/批量助手、offset **string** 查询助手 | 订单/抽奖/OAuth/RBAC 页面 |
40
+ | i18n **种子** + recipes 复制清单 | 「一条命令生成完整业务后台」的产品本身 |
41
+ | 在 **≥2 真实消费方**(或第 3 路径验证出的通用缺口)后提取 | 为对齐单一产品而抹平特殊性 |
42
+
43
+ **线上验证面(起草时):**
44
+
45
+ | 产品 | 会话 | 在套件中的角色 |
46
+ |------|------|----------------|
47
+ | **cf-shop** | Bearer | 主路径参考 |
48
+ | **cf-lottery** | Bearer + 平台/仿冒胶水 | Bearer 扩展;布局可有产品自有部分 |
49
+ | **cf-auth** | Cookie + flag | **检验 cookie 路径**;禁止把 OAuth/业务页抽进 kit |
50
+
51
+ ### 1.3 「极致」在本仓库的定义
52
+
53
+ 对 solo / 小团队维护者,**极致 = 减少摩擦 + 提高改动安全感**,**不是**功能表面积最大化。
54
+
55
+ 优先序:
56
+
57
+ 1. **改组件不踩雷**(契约可测)
58
+ 2. **重复逻辑有单一真相**(如 i18n fallback)
59
+ 3. **体积与依赖不偷偷膨胀**
60
+ 4. **提取纪律可执行**(≥2 同形,写进政策)
61
+ 5. 仅在摩擦真实变大时,再上脚手架 / 文档站 / 更深工厂
62
+
63
+ ---
64
+
65
+ ## 2. 治理纪律(建议升格为公开政策)
66
+
67
+ 以下三条应与 001/002 一致,并在评审新 PR 时当作硬门禁:
68
+
69
+ 1. **不进** Element/Ant 式通用组件库路线。
70
+ 2. **不抽** 仅单产品出现的页面编排(业务列定义、域 CRUD、产品特有批量语义)。
71
+ 3. **提取门槛:**
72
+ - 同形模式出现在 **≥2 个消费方**,或
73
+ - **同一产品 ≥3 处**稳定复制且契约不再摇摆,或
74
+ - **第 3 条路径**(如 cookie)验证出的通用缺口。
75
+
76
+ **禁止:** 为「看起来整齐」而预抽;为「对齐 shop」而抹平 auth。
77
+
78
+ ---
79
+
80
+ ## 3. 现状快照(相对极致化议题)
81
+
82
+ | 维度 | 起草时实况 | 含义 |
83
+ |------|------------|------|
84
+ | 测试 | ~27 个测试文件,**composable / utils 为主**;**8 个 `.vue` 几乎无渲染测** | 模板/插槽回归靠手测,套件信任短板 |
85
+ | 交付形态 | `exports` 指向 **`src/`**;`build` 主要产 **`.d.ts`**;CSS 在 `src/styles` | 体积 KPI 不能按「单一 runtime bundle」幻想来设计 |
86
+ | i18n | `AdminShell.tFallback` 与 `ConfigField.tSafe` **同构重复**;部分组件模板直接 `$t` | 收口 ROI 高、对外行为可兼容 |
87
+ | 请求层 | `createAdminRequest` 已有 `resultMode` / `credentials` 等;产品仍自写 `api/*` | 实例级配置已够用;路径级 registry 证据不足 |
88
+ | 分页 | `buildAdminOffsetParams` **仅 string**(URLSearchParams);number body 手写 | 与 001 **R5** / 002 一致,默认不扩 API |
89
+ | 样式 | `.table-wrap` 已在 **0.2.7** 去掉 `scrollbar-gutter: stable`(消除无溢出右侧缺口) | 表格以视觉对齐优先;弹层/卡片列表仍可 stable |
90
+ | 接入 | recipes **清单式复制**,无官方脚手架 | 3 内部消费方阶段可接受 |
91
+
92
+ ---
93
+
94
+ ## 4. 分层裁决总表
95
+
96
+ 图例:**采纳** = 应排期;**轻量采纳** = 做,但改写目标;**暂缓** = 有条件再开;**冻结** = 默认不做。
97
+
98
+ | 层级 | 议题 | 裁决 | 一句话理由 |
99
+ |------|------|------|------------|
100
+ | **P0** | 组件 SFC 契约测试 | **已落地(0.2.8)** | `tests/AdminShell|ConfigField|AdminMetadataDetail.test.ts` + happy-dom |
101
+ | **P0** | 体积 / 依赖监控 | **已落地(0.2.8)** | `scripts/size-report.mjs` + `size-baseline.json`;`size-report` / `:write` / `:check` |
102
+ | **P0** | 统一 i18n fallback(`tKit` 等) | **已落地(0.2.8)** | `resolveAdminT` / `tKit`;Shell + ConfigField 收口 |
103
+ | **P1** | `createAdminApi` 路径 resultMode 工厂 | **暂缓** | 第二真相源;现主流是一产品一 request 配置 |
104
+ | **P1** | `create-cf-admin` 脚手架 | **后置** | 模板双维护税 > 当前 onboarding 痛 |
105
+ | **P1** | VitePress 组件演示站 | **冻结(现阶段)** | 卖的是模式套件;002 已够内部用 |
106
+ | **P2** | number offset helper | **观察(R5)** | 先审计同形,默认不提 API |
107
+ | **P2** | Light 主题全集 | **冻结** | 产品族暗色;变量可覆盖即可 |
108
+ | **P2** | dist 过期「玄学」强化 | **低优** | prepack clean 对发布已够 |
109
+ | 架构 | AdminShell 注入式侧栏 | **有第二行为再做** | 现硬依赖 `useAdminSidebar` 合理 |
110
+ | 架构 | 源码交付改 dist-only runtime | **需单独 RFC** | 改变调试与消费模型,非默认极致 |
111
+
112
+ ---
113
+
114
+ ## 5. P0 — 高影响、值得做(细则)
115
+
116
+ ### 5.1 组件级测试(SFC / 契约测试)
117
+
118
+ **问题:** composable 测得再满,**插槽条件、cents 编辑、元数据安全回退**仍可能被「只改模板」弄坏。
119
+
120
+ **目标重写(重要):**
121
+
122
+ - 要的是 **行为契约**(渲染条件、emit、边界 props),不是行覆盖率表演。
123
+ - 与产品仓「读源码 contract」互补:
124
+ - **kit 内 SFC 测** = 套件契约
125
+ - **产品 contract** = 接线/业务约定
126
+
127
+ **建议优先级:**
128
+
129
+ | 优先级 | 组件 | 覆盖要点 |
130
+ |--------|------|----------|
131
+ | 必做 | `AdminShell` | `#sidebar-footer` 有/无才渲染;`showUser` / `showLogout`;`#header-actions` 与用户区相对位置;`navigate` / `logout` emit;窄屏名称可藏(若可稳定断言) |
132
+ | 必做 | `ConfigField` | cents 往返;枚举空校验;sensitive 占位;status 文案(saving/saved/error) |
133
+ | 应做 | `AdminMetadataDetail` | 空、非法结构、循环/过深时的安全回退展示 |
134
+ | 可后做 | `AdminPagination` / `ConfirmDialog` / `ToastContainer` | 逻辑多在 composable 已测则 **薄测** 即可 |
135
+ | 低 | `AdminLoginShell` / `AdminModal` | 结构 + 关键 a11y 点;少绑 class 字符串 |
136
+
137
+ **基建建议:** Vitest + `@vue/test-utils` + **happy-dom**(相对 jsdom 更轻)。
138
+ **反模式:** 为冲 80% 组件行覆盖写脆测试;断言大段 HTML 快照无说明。
139
+
140
+ **预期发版形态:** 行为兼容 → **0.2.x patch**(如与 i18n 收口同发 0.2.8)。
141
+
142
+ ---
143
+
144
+ ### 5.2 构建产物与「体积」监控(轻量版)
145
+
146
+ **问题重定义:**
147
+ 本包 **不是**「npm 只下 dist JS、运行全走打包产物」。消费者 Vite 直接编 **`src/*.vue` + CSS`**;`tsc` 的 `dist/` 以声明为主。
148
+ 因此「打一个假入口把全部 subpath 打成一个 app 再 gzip」可以做实验,但 **不宜当主 KPI**,否则会优化错对象。
149
+
150
+ **建议监控(按优先级):**
151
+
152
+ 1. **`src/styles/*.css` raw / gzip 体积**(`admin-primitives.css` 是大头之一)
153
+ 2. **按 exports 子路径的源码规模**(composables / components / utils / i18n:文件数、近似 LOC)——防 utils 黑洞
154
+ 3. **依赖门禁:** 除 `peerDependencies`(`vue`;可选 `vue-i18n`)外,**禁止悄悄增加 runtime dependency**(比多 2KB CSS 更致命)
155
+ 4. 可选:对 **纯 TS、无 SFC** 的子树做 esbuild metafile 抽样
156
+
157
+ **CI / 脚本形态:**
158
+
159
+ - `scripts/size-report.mjs`(或同等)输出可读报告 + 可提交的 baseline JSON
160
+ - **仅在明显膨胀时失败**(例如 CSS gzip +15% 且无说明),避免每次微改必红
161
+
162
+ **明确不做(现阶段):** 把套件改成「唯一预打包 runtime bundle」作为默认交付——除非另开 RFC 并改所有消费方调试习惯。
163
+
164
+ ---
165
+
166
+ ### 5.3 统一 i18n 辅助(P0 中 ROI 最高、风险最低)
167
+
168
+ **事实:** `AdminShell` 的 `tFallback` 与 `ConfigField` 的 `tSafe` 同为:
169
+
170
+ `getCurrentInstance()` → `proxy.$t` → 失败或 key 原样则 **fallback**(ConfigField 另支持 params)。
171
+
172
+ **建议 API 形态(实现时二选一,文档写死):**
173
+
174
+ | 形态 | 适用 | 注意 |
175
+ |------|------|------|
176
+ | `tKit(key, fallback, params?)` 在 **setup 内** 调用 | 组件 script | 依赖 `getCurrentInstance`,勿在模块顶层调用 |
177
+ | 纯函数 `resolveAdminT(proxy, key, fallback, params?)` | 可单测、可非 setup | 组件内一行薄封装 |
178
+
179
+ **策略必须统一(避免半套):**
180
+
181
+ - **A:** 套件组件展示文案 **全部** 走 `tKit` / `resolveAdminT`(无 vue-i18n 也能跑中文 fallback);或
182
+ - **B:** 文档强制消费方 `mergeAdminKitMessages`,模板可用 `$t`,但 **kit 内仍建议统一走 tKit** 以免漏 merge 时白屏/露 key。
183
+
184
+ 推荐 **A 为主**(与「vue-i18n 为 optional peer」一致)。
185
+
186
+ **范围:** 先收 `AdminShell`、`ConfigField`,再扫其他组件是否有第三份拷贝。
187
+ **发版:** 对外行为不变 → **patch**。
188
+
189
+ ---
190
+
191
+ ## 6. P1 — 中等影响,看 ROI
192
+
193
+ ### 6.1 通信协议:`createAdminApi` 路径级工厂 — **暂缓**
194
+
195
+ **设想:** 按 path 注册 `resultMode` / `errorField`,调用时自动匹配。
196
+
197
+ **暂缓理由(硬):**
198
+
199
+ - 现抽象已在 **`createAdminRequest` 实例级**;shop / auth / lottery 主流是 **一产品一配置**,不是「一 path 一 envelope」为主矛盾。
200
+ - 路径表会成为 **第二份路由真相**,与产品 `api/admin.ts` 双维护。
201
+ - 强类型 path→响应要么弱(近 `any`),要么维护税接近 OpenAPI。
202
+
203
+ **触发条件(同时满足再评估):**
204
+
205
+ - ≥2 产品在 **同一 request 实例内** 出现多 envelope,且手写分支重复;
206
+ - 配置表能明显减少重复,而不是只是搬家。
207
+
208
+ **更贴定位的替代:** 强化 recipes 中「如何组织 `api/admin.ts`」;必要时只加 **小纯函数**(如 envelope 断言),不建 path registry。
209
+
210
+ ---
211
+
212
+ ### 6.2 准入脚手架 `create-cf-admin` — **后置**
213
+
214
+ **痛点真实,但养模板仓库的成本更高:** kit 每个 API 变更要 **双改模板**,solo 最易腐烂。
215
+
216
+ **更极致、更便宜的中间态:**
217
+
218
+ 1. recipes 保持权威清单;
219
+ 2. 可选 **`examples/minimal`**(可运行的最小壳,仍非业务后台);
220
+ 3. 可选 **检查脚本**(是否 import styles、是否 merge i18n 种子)——**验证**优于**生成**。
221
+
222
+ **触发条件:** 第 4 个消费方(尤其外部)或公开 onboarding 压力明显,再开脚手架。
223
+
224
+ ---
225
+
226
+ ### 6.3 VitePress 文档站 — **现阶段冻结**
227
+
228
+ - 对内:002 + recipes 已是高水位。
229
+ - 对外:本包卖 **模式与纪律**,不是组件货架;Playground 维护税高。
230
+
231
+ **例外:** 若只需 ConfigField / AdminShell **静态一页 demo**,可放 recipes 或单页,**不上**完整文档站基建。
232
+
233
+ ---
234
+
235
+ ## 7. P2 与架构 — 低优先 / 触发后再做
236
+
237
+ ### 7.1 number 版 offset helper(R5)
238
+
239
+ - 说明书已标明:string 给 `URLSearchParams`;**number body 手写**。
240
+ - shop 全仓大量 `limit/offset` **不等于** 前端构造参数「同形」;服务层分页与 admin API 拼装要分开看。
241
+
242
+ **允许动作:** 审计 `cf-shop/frontend`(及 lottery)里 number body 分页构造;若 **稳定同形 ≥2 处跨产品或 ≥3 处单产品**,再提 `buildAdminOffsetBody` 之类。
243
+ **默认:** 不增 API;结论写回 001 R5 / 002。
244
+
245
+ ### 7.2 CSS 主题(Light)
246
+
247
+ - 产品族管理端 **暗色**;无第二套视觉 QA 面需求。
248
+ - **保持** CSS 变量可被消费方覆盖即可;kit **不维护**官方 light 主题包。
249
+
250
+ ### 7.3 dist 过期防护
251
+
252
+ - `prepack` → clean → build 对 **发布** 足够。
253
+ - IDE 误解析:以 `exports`→`src` 为准;不必为开发体验叠加复杂 outDir 策略。
254
+
255
+ ### 7.4 AdminShell ↔ `useAdminSidebar`
256
+
257
+ - 三家断点一致时,**硬依赖是合理默认**,不是缺陷。
258
+ - **注入整棵侧栏状态机** = 有第二套断点/行为时再做。
259
+ - 现阶段扩展优先靠 **已有插槽**(`#nav` / `#nav-extra` / `#sidebar-footer` / `#header-actions` / `#banner`)。
260
+
261
+ ### 7.5 模板中的 `$t`
262
+
263
+ - 根因是 **optional `vue-i18n`**,不是缺 DI 容器。
264
+ - 用 **统一 tKit + merge 种子契约** 收敛,优于「注入 i18n 实例」大重构。
265
+
266
+ ---
267
+
268
+ ## 8. 摩擦点:愿景 vs 可执行极致
269
+
270
+ | 摩擦点 | 常见「极致」想象 | **本仓库建议的极致** |
271
+ |--------|------------------|----------------------|
272
+ | 新产品接入 | `npx create-cf-admin` | recipes + 可选 minimal example + 可选检查脚本 |
273
+ | 改套件后验证 | 全自动消费方 monorepo CI | **kit SFC 契约测** + 发版前三仓 `vue-tsc`(可先脚本化) |
274
+ | 组件行为确认 | 读源码 / 全量快照 | **高风险 SFC 行为测** |
275
+ | 升级影响 | 类型断崖吓阻 | **0.2 尽量兼容**;破坏性进 0.3;changelog 记行为 |
276
+ | 是否该提取 | 自动模式挖掘工具 | **人工 ≥2 + R 观察列表**(工具 ROI 过低) |
277
+
278
+ ---
279
+
280
+ ## 9. 推荐落地路线图
281
+
282
+ ### 9.1 近端 — **0.2.8 三件套(已完成)**
283
+
284
+ 1. ~~抽取 **`tKit` / `resolveAdminT`**~~ → `src/utils/i18n.ts`;Shell / ConfigField 收口;`tests/i18n-resolve.test.ts`。
285
+ 2. ~~SFC 测试基建 + 契约测~~ → vitest `happy-dom`;AdminShell / ConfigField / AdminMetadataDetail。
286
+ 3. ~~**轻量 size-report**~~ → `npm run size-report` / `size-report:write` / `size-report:check`。
287
+
288
+ **刻意不碰(仍有效):** path 级 `createAdminApi`、脚手架、VitePress、双主题、注入式侧栏。
289
+
290
+ ### 9.2 中端(有余力)
291
+
292
+ 4. 发版辅助脚本:`type-check` + `test` + `size-report` + 提示消费方 bump。
293
+ 5. number offset **审计笔记** 写入 R5(有同形再 API,无则结案「维持手写」)。
294
+ 6. recipes「最小接入」补强(仍非脚手架)。
295
+
296
+ ### 9.3 冻结直到触发条件
297
+
298
+ | 项 | 触发条件(示例) |
299
+ |----|------------------|
300
+ | `createAdminApi` 路径表 | §6.1 条件 |
301
+ | `create-cf-admin` | 第 4 消费方或外部 onboarding 压力 |
302
+ | VitePress | 明确对外品牌/演示需求且有维护带宽 |
303
+ | Light 主题 | 真实产品浅色管理端需求 |
304
+ | 注入式侧栏 | 第二套断点/行为无法用插槽解决 |
305
+ | dist-only runtime | 书面 RFC + 消费方迁移窗 |
306
+
307
+ ---
308
+
309
+ ## 10. 与「极致」容易打架的三条钉死原则
310
+
311
+ 1. **源码交付是优势**(可跳进 SFC 调试);勿为「像标准组件库」默认改成仅 dist 运行时。
312
+ 2. **测试的极致是契约稳定**,不是测试文件数量或行覆盖率。
313
+ 3. **每多一个工厂 / 脚手架 / 主题,就多一条要与 shop·lottery·auth 同步的真理之源**;在 3 仓阶段,**少真相源才是极致**。
314
+
315
+ ---
316
+
317
+ ## 11. 总结(给决策者的一页)
318
+
319
+ - **定位与 ≥2 提取纪律:全盘坚持,并当 PR 门禁。**
320
+ - **P0 三件套已在 0.2.8 落地**(契约测 + i18n 收口 + size-report)。
321
+ - **API 路径工厂、脚手架、文档站、双主题:远见可记,默认冻结。**
322
+ - **R5 number offset:可审计,不可默认加 API。**
323
+ - **下一列车:** 中端(发版辅助脚本 / R5 审计笔记 / recipes 最小接入),仍不扩冻结项。
324
+
325
+ ---
326
+
327
+ ## 12. 修订记录
328
+
329
+ | 日期 | 说明 |
330
+ |------|------|
331
+ | 2026-08-05 | 初版:基于 0.2.7 现状与消费方(shop / lottery / auth)对照;固化 P0/P1/P2 裁决、触发条件与 0.2.8 三件套路线;明确极致定义=减摩擦+安全感而非扩面 |
332
+ | 2026-08-05 | **0.2.8 P0 落地**:`tKit`/`resolveAdminT`、SFC 契约测、size-report;§4/§9.1/§11 状态同步 |
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@usethink/cf-admin-fe",
3
- "version": "0.1.1",
4
- "description": "Vue 3 frontend admin kit for CF product family — composables, shell components, design tokens (FE only; not cf-core)",
3
+ "version": "0.2.8",
4
+ "description": "Vue 3 frontend admin kit for CF product family — composables, shell, ConfigField, list loader, login chrome, request/token/raw factories, error/batch helpers, system-config engine, nav helpers, i18n seeds, recipes (FE only; not cf-core)",
5
5
  "type": "module",
6
6
  "private": false,
7
7
  "files": [
8
8
  "src",
9
9
  "dist",
10
- "README.md"
10
+ "recipes",
11
+ "docs",
12
+ "README.md",
13
+ "LICENSE"
11
14
  ],
12
15
  "main": "./src/index.ts",
13
16
  "module": "./src/index.ts",
@@ -26,14 +29,32 @@
26
29
  "types": "./src/components/index.ts",
27
30
  "import": "./src/components/index.ts"
28
31
  },
32
+ "./utils": {
33
+ "types": "./src/utils/index.ts",
34
+ "import": "./src/utils/index.ts"
35
+ },
36
+ "./types": {
37
+ "types": "./src/types/index.ts",
38
+ "import": "./src/types/index.ts"
39
+ },
40
+ "./i18n": {
41
+ "types": "./src/i18n/index.ts",
42
+ "import": "./src/i18n/index.ts"
43
+ },
29
44
  "./styles": "./src/styles/index.css",
30
45
  "./styles/tokens.css": "./src/styles/tokens.css",
31
46
  "./styles/admin-primitives.css": "./src/styles/admin-primitives.css",
47
+ "./styles/login-primitives.css": "./src/styles/login-primitives.css",
32
48
  "./package.json": "./package.json",
33
49
  "./components/AdminPagination.vue": "./src/components/AdminPagination.vue",
34
50
  "./components/AdminModal.vue": "./src/components/AdminModal.vue",
35
51
  "./components/ConfirmDialog.vue": "./src/components/ConfirmDialog.vue",
36
- "./components/ToastContainer.vue": "./src/components/ToastContainer.vue"
52
+ "./components/ToastContainer.vue": "./src/components/ToastContainer.vue",
53
+ "./components/ConfigField.vue": "./src/components/ConfigField.vue",
54
+ "./components/AdminShell.vue": "./src/components/AdminShell.vue",
55
+ "./components/AdminLoginShell.vue": "./src/components/AdminLoginShell.vue",
56
+ "./recipes/*": "./recipes/*",
57
+ "./components/AdminMetadataDetail.vue": "./src/components/AdminMetadataDetail.vue"
37
58
  },
38
59
  "sideEffects": [
39
60
  "**/*.css"
@@ -47,7 +68,10 @@
47
68
  }
48
69
  },
49
70
  "devDependencies": {
71
+ "@types/node": "^22.10.0",
50
72
  "@vitejs/plugin-vue": "^5.2.1",
73
+ "@vue/test-utils": "^2.4.11",
74
+ "happy-dom": "^20.11.1",
51
75
  "typescript": "^5.7.2",
52
76
  "vite": "^6.0.0",
53
77
  "vitest": "^3.0.0",
@@ -55,10 +79,15 @@
55
79
  "vue-tsc": "^2.1.10"
56
80
  },
57
81
  "scripts": {
58
- "type-check": "tsc --noEmit && vue-tsc --noEmit -p tsconfig.json",
82
+ "type-check": "tsc --noEmit -p tsconfig.build.json && vue-tsc --noEmit -p tsconfig.json",
59
83
  "test": "vitest run",
60
84
  "test:watch": "vitest",
61
- "build": "tsc -p tsconfig.build.json"
85
+ "build": "npm run clean && tsc -p tsconfig.build.json",
86
+ "prepack": "npm run build",
87
+ "clean": "node scripts/clean-dist.mjs",
88
+ "size-report": "node scripts/size-report.mjs",
89
+ "size-report:write": "node scripts/size-report.mjs --write",
90
+ "size-report:check": "node scripts/size-report.mjs --check"
62
91
  },
63
92
  "keywords": [
64
93
  "admin",
@@ -0,0 +1,73 @@
1
+ <template>
2
+ <!--
3
+ RECIPE — 产品 AdminLayout 外壳。
4
+ 将 menuItems + brand 替换为产品的 i18n / 配置。
5
+ 可选插槽:#nav-extra(平台链接)、#banner(仿冒登录)、#header-actions(语言,在顶栏用户信息左侧)。
6
+ 用户头像/名称/退出默认在顶栏右上;侧栏底默认不占位(菜单多时省高)。
7
+ 若需自定义侧栏底:#sidebar-footer(有内容才渲染)。
8
+ -->
9
+ <AdminShell
10
+ :brand="brand"
11
+ :menu-items="shellMenuItems"
12
+ :current-name="currentName ?? null"
13
+ :page-title="pageTitle"
14
+ :user-name="t('adminLayout.admin')"
15
+ @logout="handleLogout"
16
+ @navigate="onNavClick"
17
+ >
18
+ <template #header-actions>
19
+ <!-- 产品:语言选择、环境徽标等(出现在用户信息与退出左侧) -->
20
+ <slot name="header-actions" />
21
+ </template>
22
+
23
+ <RouterView v-slot="{ Component }">
24
+ <transition name="admin-fade" mode="out-in">
25
+ <component :is="Component" />
26
+ </transition>
27
+ </RouterView>
28
+ </AdminShell>
29
+ </template>
30
+
31
+ <script setup lang="ts">
32
+ import { computed } from 'vue'
33
+ import { useRoute, useRouter } from 'vue-router'
34
+ import { useI18n } from 'vue-i18n'
35
+ import { AdminShell, type AdminShellMenuItem } from '@usethink/cf-admin-fe/components'
36
+ import { useAdminAuth } from '@/composables/useAdminAuth'
37
+ import { useToast } from '@/composables/useToast'
38
+
39
+ const route = useRoute()
40
+ const router = useRouter()
41
+ const { t } = useI18n()
42
+ const { clearToken } = useAdminAuth()
43
+ const { showToast } = useToast()
44
+
45
+ /** PRODUCT: 品牌标题 */
46
+ const brand = computed(() => t('adminLayout.title'))
47
+
48
+ /** PRODUCT: 替换为真实菜单 */
49
+ const menuItems = computed(() => [
50
+ { name: 'AdminDashboard', to: '/admin', label: t('adminLayout.title'), icon: '📊' },
51
+ // { name: 'AdminOrders', to: '/admin/orders', label: '…', icon: '📋' },
52
+ ])
53
+
54
+ const shellMenuItems = computed<AdminShellMenuItem[]>(() => menuItems.value)
55
+
56
+ const currentName = computed(() => route.name as string | undefined)
57
+
58
+ const pageTitle = computed(() => {
59
+ const item = menuItems.value.find((m) => m.name === currentName.value)
60
+ return item?.label || t('adminLayout.title')
61
+ })
62
+
63
+ function onNavClick(to: string) {
64
+ if (route.fullPath === to) return
65
+ router.push(to)
66
+ }
67
+
68
+ function handleLogout() {
69
+ clearToken()
70
+ showToast(t('adminLayout.loggedOut'), 'success')
71
+ router.replace('/admin/login')
72
+ }
73
+ </script>
@@ -0,0 +1,131 @@
1
+ # Recipes — 复制到新的 CF 管理后台前端
2
+
3
+ 这些文件是**起点模板**,不是运行时导入。将它们复制(或重新导出)到你的产品 `src/` 目录下,然后补齐产品专属部分(菜单、领域 API 方法、登录 UI)。
4
+
5
+ **完整用法、注意事项、能力榨干清单:** 见 [`docs/002_cf-admin-fe使用说明书与注意事项.md`](../docs/002_cf-admin-fe使用说明书与注意事项.md)。
6
+ **套件下一步该做什么 / 明确不做:** 见 [`docs/003_cf-admin-fe极致化方案与取舍_2026-08-05.md`](../docs/003_cf-admin-fe极致化方案与取舍_2026-08-05.md)(路线图,非本 recipes 步骤替代)。
7
+
8
+ ## 推荐复制顺序
9
+
10
+ | 步骤 | Recipe | 产品路径(示例) |
11
+ |---|---|---|
12
+ | 1 | `reexports/` | `src/composables/*`、`src/components/*`、`src/lib/*` |
13
+ | 2a | **Bearer:** `useAdminAuth.shop.ts` | `src/composables/useAdminAuth.ts` |
14
+ | 2b | **Cookie:** `useAdminAuth.cookie.ts` | 同上 — 使用 `createAdminFlagSession` |
15
+ | 3a | **Bearer:** `useAdminRequest.ts` | `src/composables/useAdminRequest.ts`(或 `api/admin.ts`) |
16
+ | 3b | **Cookie:** `useAdminRequest.cookie.ts` | `credentials: 'include'` + `onSuccess` 标志修复 |
17
+ | 4 | `AdminLayout.vue` | `src/views/AdminLayout.vue` |
18
+ | 5 | `router-guard.snippet.ts` | 粘贴到 `src/router/index.ts`(Bearer 用 `hasToken`;cookie 产品可在**产品内**添加健康探测) |
19
+ | 6 | `i18n-merge.snippet.ts` | 粘贴到 `src/i18n/index.ts` |
20
+ | 7 | 样式 | `import '@usethink/cf-admin-fe/styles'` **或** `@import` 基础样式(+ 登录 CSS) |
21
+ | 8 | 应用外壳 | 在应用根节点挂载一次 `ToastContainer` |
22
+ | 9 | 登录 | 用 `AdminLoginShell` 包裹**产品**表单;用 `resolveAdminLoginRedirect` 处理 `?redirect=` |
23
+ | 10 | 列表页 | 见 `list-page.snippet.ts`:`useAdminListLoader` + 分页/多选 + `toErrorMessage` / `copyWithToast`;**先确认**后端是 string query 还是 number body offset |
24
+
25
+ ### 两种一等会话模型
26
+
27
+ | 模型 | 工具包工厂 | 请求 | 实际参考 |
28
+ |---|---|---|---|
29
+ | Bearer JWT | `createAdminTokenSession` | `getToken` + 可选 Bearer | **cf-shop**、**cf-lottery** |
30
+ | Cookie + 本地标志 | `createAdminFlagSession` | `credentials: 'include'`,可选 `onSuccess` 修复 | **cf-auth** |
31
+
32
+ Cookie 产品**不应**被强制套用 token TTL / 密码表单。它们的特性(OAuth、健康探测、登出 cookie)留在产品侧;工具包只提供标志存储 + 请求工厂 + 外壳 UI。
33
+
34
+ ## 不要放入工具包 / 不要按领域复制
35
+
36
+ - 领域页面(`views/admin/*` 业务 CRUD)
37
+ - 领域 API 方法目录
38
+ - 抽奖仿冒登录 / 平台角色(产品 L3)
39
+ - Better Auth / OAuth / PKCE **流程**和允许列表策略(产品)
40
+ - 多币种 / 订单状态映射
41
+ - 通用组件(日期选择器、级联选择、树、富文本)— 产品自选轻量库
42
+ - 一次性产品辅助函数“以防将来共享”
43
+
44
+ ## 导入风格
45
+
46
+ 产品重新导出时优先使用**子路径**导入,这样 Vitest/node 不会解析根 SFC barrel 文件:
47
+
48
+ ```ts
49
+ from '@usethink/cf-admin-fe/composables'
50
+ from '@usethink/cf-admin-fe/utils'
51
+ from '@usethink/cf-admin-fe/components/ConfigField.vue'
52
+ from '@usethink/cf-admin-fe/i18n'
53
+ ```
54
+
55
+ ## 错误 / 批量纯辅助函数(0.2.6+)
56
+
57
+ ```ts
58
+ import {
59
+ toErrorMessage,
60
+ buildBatchToast,
61
+ resolveBatchToastType,
62
+ checkAdminBatchLimit,
63
+ } from '@usethink/cf-admin-fe/utils'
64
+
65
+ // catch
66
+ showToast(toErrorMessage(e), 'error')
67
+ // 列表 loader
68
+ onError: (err) => toErrorMessage(err, t('…loadFailed'))
69
+
70
+ // 服务端批量响应 { success, failed }
71
+ const toast = buildBatchToast(res, {
72
+ mixed: '完成:成功 {success},失败 {failed}',
73
+ success: '已处理 {count} 条',
74
+ })
75
+ showToast(toast.message, toast.type)
76
+
77
+ // 或保留 vue-i18n 文案,只复用成功/失败色调:
78
+ showToast(t('…', counts), resolveBatchToastType(counts))
79
+
80
+ // POST 前多选上限
81
+ // 默认 20 = auth / docs 风格;后端若 z.array.max(200) 必须传 limit: 200
82
+ const limitCheck = checkAdminBatchLimit(ids, { unit: '人' })
83
+ // shop 示例:checkAdminBatchLimit(ids, { limit: 200, unit: '单' })
84
+ if (!limitCheck.ok) {
85
+ showToast(limitCheck.message, 'error')
86
+ return
87
+ }
88
+ ```
89
+
90
+ 不要把业务成功文案或硬删除策略用语放进套件——只放纯形状。
91
+ **`limit` 必须与后端单次批量 max 一致**,默认 20 不是「所有 CF 产品的真理」。
92
+
93
+ ## 列表 limit / offset(string vs number)
94
+
95
+ | 形态 | 工具 | 参考产品 |
96
+ |---|---|---|
97
+ | `URLSearchParams` / query:`limit`、`offset` 为 **字符串** | `buildAdminOffsetParams(page, limit)` 或 `applyAdminOffsetParams` | cf-lottery 平台审计、租户、webhook、兑换码 |
98
+ | 类型化 filter / body:`limit`、`offset` 为 **数字** | **手写** `(page - 1) * limit`;**不要**套 string helper | cf-shop 余额 / 充值 / 代金券等 |
99
+ | Cursor | 产品自管 | shop 部分订单/日志 |
100
+
101
+ `buildAdminOffsetParams` 的返回值是 `{ limit: string; offset: string }`,塞进需要 `number` 的 TypeScript filter 会类型错误或静默把数字变成字符串——属于误用,不是「再抽一个兼容层就能糊弄过去」。
102
+
103
+ 完整对照与升级清单:`docs/002` **§5.8**。
104
+
105
+ ## 复制 + toast(0.2.6+)
106
+
107
+ ```ts
108
+ import { copyWithToast } from '@usethink/cf-admin-fe/composables'
109
+ // 或产品 reexport:@/composables/useClipboard
110
+
111
+ const ok = await copyWithToast(text, {
112
+ successMessage: t('…copySuccess'),
113
+ errorMessage: t('…copyFailed'),
114
+ })
115
+ if (ok) {
116
+ // 可选:关弹层、清选中
117
+ }
118
+ ```
119
+
120
+ - **`copyWithToast`**:剪贴板 + 全局 toast(管理端列表/详情主路径)
121
+ - **`copyText(text, event)`**:按钮内 ✅/❌(前台交付明细等,不抢 toast)
122
+ - **`writeClipboardText`**:仅写入,由调用方自管反馈(例如创建成功后静默复制链接)
123
+
124
+ ## 列表页骨架
125
+
126
+ 见 [`list-page.snippet.ts`](./list-page.snippet.ts)(含形态 A/B 注释与 batch `limit` 提示)。
127
+
128
+ ## 审计元数据
129
+
130
+ - 纯函数:`formatMetadata`(旧页、自研详情 markup 足够时只用这个)
131
+ - 组件:`AdminMetadataDetail` — **新审计详情页优先**;已有产品详情 DOM 的旧页 **不必** 为对齐而全量迁移(见 docs/001 R4 尾巴、docs/002 步骤 11)
@@ -0,0 +1,29 @@
1
+ /**
2
+ * RECIPE — 合并工具包消息种子,使 ConfigField / Pagination / Confirm 在新产品
3
+ * 上不会显示原始 key。冲突时产品宿主 key 始终优先。
4
+ *
5
+ * 示例(vue-i18n):
6
+ *
7
+ * import { mergeAdminKitMessages } from '@usethink/cf-admin-fe/i18n'
8
+ * import zhHost from './zh-CN'
9
+ * import enHost from './en'
10
+ *
11
+ * const i18n = createI18n({
12
+ * locale: 'zh-CN',
13
+ * messages: {
14
+ * 'zh-CN': mergeAdminKitMessages(zhHost, 'zh-CN'),
15
+ * en: mergeAdminKitMessages(enHost, 'en'),
16
+ * },
17
+ * })
18
+ */
19
+ import { mergeAdminKitMessages } from '@usethink/cf-admin-fe/i18n'
20
+
21
+ export function withAdminKitI18n<T extends Record<string, unknown>>(
22
+ zhHost: T,
23
+ enHost: T,
24
+ ): { 'zh-CN': T; en: T } {
25
+ return {
26
+ 'zh-CN': mergeAdminKitMessages(zhHost, 'zh-CN'),
27
+ en: mergeAdminKitMessages(enHost, 'en'),
28
+ }
29
+ }