@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 usethink / cf-admin-fe contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,104 +1,295 @@
1
1
  # @usethink/cf-admin-fe
2
2
 
3
- Vue 3 **admin foundation** for the Cloudflare product family (`cf-shop`, `cf-lottery`, future CF admin apps).
3
+ 面向 Cloudflare 产品家族(`cf-shop`、`cf-lottery`、`cf-auth` 以及未来的 CF 管理端应用)的 Vue 3 **管理端基础套件(admin foundation)**。
4
4
 
5
- This package is the **frontend** counterpart to backend `@usethink/cf-core`:
5
+ **状态:** **0.2.8** P0 三件套:`resolveAdminT` / `tKit`、AdminShell/ConfigField/MetadataDetail 契约测、`size-report`。完整用法见 **[`docs/002_cf-admin-fe使用说明书与注意事项.md`](./docs/002_cf-admin-fe使用说明书与注意事项.md)**;起步清单见 `recipes/README.md`;路线图见 **[`docs/003_cf-admin-fe极致化方案与取舍_2026-08-05.md`](./docs/003_cf-admin-fe极致化方案与取舍_2026-08-05.md)**。
6
6
 
7
- | Layer | Package | Contents |
7
+ **核心目标:** 最大化共享 **管理端前端基础套件**,让 CF 产品快速搭建 **不**通过克隆业务页面实现,也 **不** 强迫每个产品套用 shop 的 Bearer 模式。
8
+
9
+ | 会话模型 | 套件 | 线上消费方 |
10
+ |---|---|---|
11
+ | Bearer JWT + localStorage TTL | `createAdminTokenSession` | cf-shop, cf-lottery |
12
+ | Cookie HTTP 会话 + 本地守卫标志 | `createAdminFlagSession` + `credentials: 'include'` + 可选 `onSuccess` | **cf-auth**(验证方,非克隆目标) |
13
+
14
+ | 分层 | 包 | 内容 |
8
15
  |---|---|---|
9
- | Workers / infra | `@usethink/cf-core` | HTTP, crypto, media, secrets, |
10
- | Admin UI kit | **`@usethink/cf-admin-fe`** | Composables, shell components, admin CSS tokens |
16
+ | Workers / 基础设施 | `@usethink/cf-core` | HTTPcryptomediasecrets |
17
+ | 管理端 UI 套件 | **`@usethink/cf-admin-fe`** | 表格 UX、shell、ConfigField、request/token 工厂、导航助手、i18n 种子、recipes、CSS |
18
+
19
+ ## 为什么叫 `cf-admin-fe`(而不是 `cf-admin`)
20
+
21
+ **仅前端。** `-fe` 后缀使 UI 套件代码与 Worker/API 基础设施保持区分。
22
+
23
+ **不是**业务中间层:不包含订单、抽奖、RBAC 产品页面或支付 UI。
24
+
25
+ ## 边界(新增 API 前请先阅读)
11
26
 
27
+ `cf-admin-fe` 是 **管理端基础套件**,不是通用 Vue 组件库,也不是 Element Plus。
12
28
 
13
- ## Why `cf-admin-fe` (not `cf-admin`)
29
+ | 应做 | 不应做 |
30
+ |---|---|
31
+ | AdminPagination、AdminModal、ConfirmDialog、Toast、AdminShell、ConfigField | 日期选择器、级联、树、富文本、数据网格框架 |
32
+ | 已被 **≥2 个线上消费方** 验证的纯函数助手(或经验证的第三条路径,如 cookie 认证) | “以后可能有用”的抽象 |
33
+ | 轻量 request/session 工厂 + 列表加载器 + 导航助手 | 业务页面、OAuth 流程、抽奖 L3、商城结算 |
14
34
 
15
- This package is **frontend-only**. The `-fe` suffix makes that explicit next to backend `@usethink/cf-core`, so operators never confuse UI kit code with Worker/API infrastructure.
35
+ **心智成本才是真正的成本。** 每次套件变更都必须让 cf-shop、cf-lottery、cf-auth 保持绿色(测试 + 类型检查)。有 3 个管理端时该成本可以摊销;只有 1 个管理端时就不值得。
16
36
 
17
- **Not** a business mid-tier: no orders, lottery draws, RBAC pages, or payment UIs.
37
+ **提取气味的检验标准**
18
38
 
19
- ## Install
39
+ - 好:两个产品已经复制粘贴了同样的 10 行 → 提取。
40
+ - 坏:只有 cf-lottery 需要 → 留在产品内。
41
+ - 坏:为了“一致性”而包装第三方日期选择器 → 在产品里用轻量专用库。
42
+
43
+ ## 安装
20
44
 
21
45
  ```bash
22
- # monorepo / sibling template (recommended during development)
46
+ # monorepo / 兄弟模板(开发期推荐)
23
47
  npm install file:../cf-admin-fe
24
48
 
25
- # or once published
49
+ # 或发布后
26
50
  npm install @usethink/cf-admin-fe
27
51
  ```
28
52
 
29
- Peer: `vue` ^3.4.
53
+ Peer 依赖:`vue` ^3.4。可选 peer 依赖:`vue-i18n`(组件在存在时会调用 `$t`)。
54
+
55
+ ---
56
+
57
+ ## 批量复制剧本(几分钟搭建新管理端)
58
+
59
+ ### 快速路径:复制 recipes
60
+
61
+ 包在 `recipes/` 下提供 **复制即用的起步代码**(monorepo 中也可通过 `@usethink/cf-admin-fe/recipes/*` import 路径阅读):
62
+
63
+ | Recipe | 用途 |
64
+ |---|---|
65
+ | `recipes/README.md` | 有序检查清单 |
66
+ | `recipes/reexports/*` | 轻量 composable/组件/utils 再导出 |
67
+ | `recipes/useAdminAuth.shop.ts` | 单键 Bearer 会话 |
68
+ | `recipes/useAdminRequest.ts` | `createAdminRequest` + 401 处理器 |
69
+ | `recipes/AdminLayout.vue` | `AdminShell` + 产品菜单 hooks |
70
+ | `recipes/router-guard.snippet.ts` | `decideAdminRouteAccess` 守卫 |
71
+ | `recipes/i18n-merge.snippet.ts` | `mergeAdminKitMessages` |
72
+ | `recipes/main-styles.snippet.ts` | CSS 入口选项 |
73
+
74
+ 参考线上消费方:**cf-shop**(Bearer)、**cf-lottery**(Bearer + L3 胶水)、**cf-auth**(cookie + flag — 用于 **验证** 套件 cookie 路径,不要复制其 OAuth/业务页面)。
30
75
 
31
- ## Quick start (new admin app)
76
+ ### 1. 接入包 + 样式 + 全局 toast
32
77
 
33
78
  ```ts
34
79
  // main.ts
35
80
  import { createApp } from 'vue'
36
81
  import App from './App.vue'
37
- import '@usethink/cf-admin-fe/styles' // tokens + admin primitives
38
-
39
- createApp(App).mount('#app')
82
+ import '@usethink/cf-admin-fe/styles' // 或仅 primitives,见 assets/admin.css
83
+ createApp(App).use(router).use(i18n).mount('#app')
40
84
  ```
41
85
 
42
86
  ```vue
43
- <script setup lang="ts">
44
- import {
45
- useTablePagination,
46
- useTableSelection,
47
- useConfirmDialog,
48
- useToast,
49
- useAdminBatchOperation,
50
- } from '@usethink/cf-admin-fe'
51
- import {
52
- AdminPagination,
53
- ConfirmDialog,
54
- ToastContainer,
55
- AdminModal,
56
- } from '@usethink/cf-admin-fe/components'
57
- </script>
87
+ <!-- App.vue:根节点挂一次 -->
88
+ <ToastContainer />
58
89
  ```
59
90
 
60
- Products may keep thin re-exports under `@/composables/*` so existing views do not mass-rewrite.
91
+ ### 2. 导入清单(最大化复用套件——不要重新实现)
92
+
93
+ | 需求 | 从哪导入 | 备注 |
94
+ |---|---|---|
95
+ | 表格分页/每页条数 | `useTablePagination` | `/composables` |
96
+ | 多选 + shift 连选 | `useTableSelection` | |
97
+ | 确认 + 可选复选框 | `useConfirmDialog` + `ConfirmDialog` | |
98
+ | Toast 队列 | `useToast` + `ToastContainer` | |
99
+ | 顺序批量操作 | `useAdminBatchOperation` | |
100
+ | 剪贴板 | `copyWithToast`(管理端主路径)/ `copyText`(按钮反馈)/ `writeClipboardText`(静默或降级) | |
101
+ | 弹窗 / 分页外壳 | `AdminModal` / `AdminPagination` | |
102
+ | 系统配置卡片 | `ConfigField` | 枚举 + 分单位金额的超集 |
103
+ | 侧边栏外壳 | `AdminShell` + `useAdminSidebar` | 菜单数据留在产品中 |
104
+ | Bearer token 会话 | `createAdminTokenSession` | 多 key / 伪装 = 产品侧 |
105
+ | Cookie 本地守卫标志 | `createAdminFlagSession` | HTTP cookie 是产品侧;flag 属于套件 |
106
+ | 管理端 `fetch` | `createAdminRequest` | `http-ok` \| `ok-envelope`;cookie:`credentials` + `onSuccess` |
107
+ | **401 → 登录页** | `createAdminUnauthorizedHandler` | 只注入 `clearSession` |
108
+ | **路由守卫** | `decideAdminRouteAccess` | 之后再加产品专属规则 |
109
+ | **登录重定向** | `resolveAdminLoginRedirect` | 防开放重定向 |
110
+ | **i18n 种子** | `mergeAdminKitMessages` | `/i18n` — 宿主覆盖优先 |
111
+ | 日期 | `formatDate` 等 | `/utils` |
112
+ | 异常转字符串 | `toErrorMessage` | toast / loader `onError` |
113
+ | 批量 toast 色调 | `resolveBatchToastType` / `buildBatchToast` | 纯函数;产品保留 i18n 键 |
114
+ | 批量数量上限守卫 | `checkAdminBatchLimit` | **默认 20 = auth 风格**;对接 `max(N)` 的 API 须传 `{ limit: N }`(如 shop 批量删 `200`) |
115
+ | 列表 limit/offset(**字符串**) | `buildAdminOffsetParams` / `applyAdminOffsetParams` | **仅** URLSearchParams / query string;产出 `{ limit: string, offset: string }` |
116
+ | CSV 导出 | `downloadCsv`, `safeCsvCell` | 防公式注入 |
117
+ | 遮罩滚动锁 | `lockBodyScroll` / `unlockBodyScroll` | 引用计数 |
118
+ | 分 ↔ 元 | `formatCents`, `parseYuanToCents` | 多币种仍属产品侧 |
119
+
120
+ **分页形态(必读,防误用):**
121
+
122
+ | 后端契约 | 做法 | 参考 |
123
+ |---|---|---|
124
+ | Query:`limit`/`offset` **字符串**(`URLSearchParams`) | `buildAdminOffsetParams(page, limit)` 或 `applyAdminOffsetParams` | **cf-lottery** 平台审计 / 租户 / webhook / 兑换码 |
125
+ | Body 或类型化 filter:`limit`/`offset` **数字** | 产品手写 `offset: (page - 1) * limit`(数字) | **cf-shop** Balance / Recharges / Vouchers 等 |
126
+ | Cursor | 产品自管;勿套 offset helper | shop 部分订单/日志列表 |
127
+
128
+ **不要**为了「用上 helper」把 number body 改成 string query。number 形态 helper 仅当 **≥2 产品** 同形复制时再提案(观察期,见 `docs/002` §5.8)。
61
129
 
62
- ## Public surface
130
+ 优先使用 **子路径** 导入(`/composables`、`/utils`、`/components/*.vue`、`/i18n`),避免 Vitest 解析根 SFC 桶文件。
63
131
 
64
- ### Composables (`@usethink/cf-admin-fe` or `/composables`)
132
+ ### 3. 最小产品胶水层(保持精简)
65
133
 
66
- - `useTablePagination` — page/limit/total/offset
67
- - `useTableSelection`multi-select + shift range
68
- - `useConfirmDialog` confirm + optional checkboxes
69
- - `useToast` / `MAX_TOASTS` global toast queue
70
- - `useAdminBatchOperation` sequential batch with progress
71
- - `writeClipboardText` / `copyText` — clipboard helpers
134
+ ```ts
135
+ // useAdminAuthshop 简化版(见 recipes/useAdminAuth.shop.ts)
136
+ import { createAdminTokenSession } from '@usethink/cf-admin-fe/composables'
137
+ const session = createAdminTokenSession({ storageKey: 'admin_token' })
138
+ export const readAdminToken = () => session.readToken()
139
+ export function useAdminAuth() {
140
+ return { token: session.token, isLoggedIn: session.isLoggedIn, setToken: session.setToken, clearToken: session.clearToken }
141
+ }
142
+ ```
72
143
 
73
- ### Components (`@usethink/cf-admin-fe/components`)
144
+ ```ts
145
+ // useAdminRequest — 尽量复用套件工厂
146
+ import { createAdminRequest } from '@usethink/cf-admin-fe/composables'
147
+ import { createAdminUnauthorizedHandler } from '@usethink/cf-admin-fe/utils'
148
+ import { readAdminToken, useAdminAuth } from './useAdminAuth'
74
149
 
75
- - `AdminPagination`, `AdminModal`, `ConfirmDialog`, `ToastContainer`
150
+ const onUnauthorized = createAdminUnauthorizedHandler({
151
+ clearSession: () => useAdminAuth().clearToken(),
152
+ })
76
153
 
77
- Components use `$t(...)` for labels when `vue-i18n` is present; provide the usual keys (`pagination.*`, `confirm.*`, `adminModal.*`) in the host app.
154
+ export const adminRequest = createAdminRequest({
155
+ getToken: () => readAdminToken(),
156
+ resultMode: 'ok-envelope', // shop 风格 body 可用 'http-ok'
157
+ errorField: 'message',
158
+ onUnauthorized,
159
+ })
160
+ ```
78
161
 
79
- ### Styles
162
+ ```ts
163
+ // i18n — 填补套件键;菜单/品牌文案留产品
164
+ import { mergeAdminKitMessages } from '@usethink/cf-admin-fe/i18n'
165
+ messages: {
166
+ 'zh-CN': mergeAdminKitMessages(zhHost, 'zh-CN'),
167
+ en: mergeAdminKitMessages(enHost, 'en'),
168
+ }
169
+ ```
170
+
171
+ ```ts
172
+ // router — 先走共享门禁
173
+ import { decideAdminRouteAccess } from '@usethink/cf-admin-fe/utils'
174
+ const d = decideAdminRouteAccess({ hasToken: !!readAdminToken(), requiresAuth: to.meta.requiresAuth, requiresGuest: to.meta.requiresGuest, fullPath: to.fullPath })
175
+ if (d.type === 'login') return { name: 'AdminLogin', query: { redirect: d.redirect }, replace: true }
176
+ if (d.type === 'home') return { name: 'AdminDashboard', replace: true }
177
+ // 产品专属:引导 / 平台角色 / …
178
+ ```
179
+
180
+ 产品可将路径别名(`@/composables/*`)保留为 **再导出**,这样视图无需大规模改写。
181
+
182
+ ### 4. 必须留在产品侧的内容(不要强行塞进套件)
183
+
184
+ - 业务管理端**页面**(`views/admin/*`、抽奖 `views/platform/*` 页面主体)
185
+ - 菜单项列表、页面标题、品牌名(传入 `AdminShell`)
186
+ - 业务 API 模块(`api/admin.ts` 的**方法面** / 业务 DTO)— 只有传输层使用 `createAdminRequest`
187
+ - 多租户伪装 / 平台角色键(抽奖 L3);使用 `AdminShell` 上的 `#banner` / `#nav-extra` 插槽
188
+ - Cookie-flag 管理端(cf-auth)— 认证模型不同;仍可使用分页/toast/弹窗/utils
189
+ - 订单状态 / 多币种金额库(`@shared/money`、订单状态映射)
190
+ - 抽奖列表 UX 启发式(`admin-list-ux`)
191
+ - 产品错误类型(`AdminApiError`)与业务下载方法签名(围绕 `createAdminRequestRaw` 的 token 优先包装)
192
+ - 套件原语之外的业务专属 CSS(伪装横幅、语言选择、平台导航强调色)
193
+
194
+ ### 5. i18n
195
+
196
+ **推荐:** `mergeAdminKitMessages(host, locale)`,这样新产品永远不会遗漏套件键。
197
+
198
+ 宿主仍应提供产品菜单(`adminLayout.menu*`)与业务字符串。字段级配置文案:`adminConfig.fields.<key>.{label,description,effect}` — 缺失时 ConfigField 通过 `$te` 回退到后端文本。
199
+
200
+ ---
201
+
202
+ ## 公开 API 面
203
+
204
+ ### 根 / composables
205
+
206
+ - `useTablePagination`、`useTableSelection`、`useConfirmDialog`、`useToast`、`MAX_TOASTS`
207
+ - `useAdminBatchOperation`、`writeClipboardText`、`copyText`、`copyWithToast`(剪贴板 + 成功/失败 toast;与按钮内反馈的 `copyText` 互补)
208
+ - `createAdminTokenSession`、`useAdminTokenSession`、`parseStoredAdminToken`、`readAdminTokenFromStorage`、`DEFAULT_ADMIN_TOKEN_TTL_MS`
209
+ - `createAdminRequest`、`createAdminRequestRaw`、`AdminRequestError`
210
+ - `useAdminSidebar`
211
+ - `useAdminListLoader` — 列表页的竞态安全 `loading` / `loadError` / 序列
212
+
213
+ ### Utils(`@usethink/cf-admin-fe/utils`)
214
+
215
+ - 日期时间:`formatDate`、`toDateTimeLocalValue`、`dateTimeLocalToIso`、`isoToDateTimeLocal`、`formatIpFingerprint`
216
+ - CSV:`downloadCsv`、`safeCsvCell`
217
+ - 滚动:`lockBodyScroll`、`unlockBodyScroll`、`isBodyScrollLocked`
218
+ - 金额最小单位:`normalizeCents`、`formatCents`、`parseYuanToCents`
219
+ - 导航:`createAdminUnauthorizedHandler`、`decideAdminRouteAccess`、`resolveAdminLoginRedirect`
220
+ - 错误:`toErrorMessage`
221
+ - 批量 UX 纯函数:`buildBatchToast`、`resolveBatchToastType`、`formatBatchCountsMessage`、`formatDefaultBatchMixedMessage`、`hasBatchFailures`、`checkAdminBatchLimit`、`DEFAULT_ADMIN_BATCH_LIMIT`(**默认 20**;产品 `max(N)` 须显式 `limit`)
222
+ - 列表查询:`buildAdminOffsetParams`、`applyAdminOffsetParams`(1-based 页码 → **`limit`/`offset` 字符串**,面向 URLSearchParams;**不是** number body filter)
223
+ - 日志元数据:`formatMetadata`(安全 `JSON.stringify`,抗循环引用;旧页可只用纯函数)
224
+ - 系统配置引擎(纯函数):`groupDefinitionsByName`、`countSectionItems`、`buildActiveGroupedDefinitions`、`countAdvancedItems`、`resolveSystemConfigSectionId`、`getSystemConfigSection`、`listedSystemConfigGroups`、`auditSystemConfigSectionCoverage` — 分节 id 联合、分组列表、危险区、API 由产品侧负责
225
+
226
+ ### i18n(`@usethink/cf-admin-fe/i18n`)
227
+
228
+ - `adminKitMessages`、`adminKitMessagesZhCN`、`adminKitMessagesEn`、`mergeAdminKitMessages`
229
+
230
+ ### 组件(`@usethink/cf-admin-fe/components`)
231
+
232
+ - `AdminPagination`、`AdminModal`、`ConfirmDialog`、`ToastContainer`
233
+ - `ConfigField`(boolean / 枚举多选 / integer / cents / sensitive / textarea)
234
+ - `AdminShell`(响应式侧边栏 + header / nav-extra / banner 插槽)
235
+ - `AdminLoginShell`(展示型登录卡片外壳;认证表单由产品侧实现)
236
+ - `AdminMetadataDetail`(审计日志详情弹层;依赖宿主 `adminLogs.*` i18n 键;**新页优先**;已有自研详情 markup 的旧页不必强迁)
237
+
238
+ ### 类型(`@usethink/cf-admin-fe/types`)
239
+
240
+ - `AdminSystemConfigFieldDefinition` / 别名 `AdminSystemConfigDefinition`、`ConfigFieldStatus` 等
241
+
242
+ ### 样式
80
243
 
81
244
  ```ts
82
245
  import '@usethink/cf-admin-fe/styles'
83
- // or granular:
246
+ // 或按需:
84
247
  import '@usethink/cf-admin-fe/styles/tokens.css'
85
- import '@usethink/cf-admin-fe/styles/admin-primitives.css'
248
+ import '@usethink/cf-admin-fe/styles/admin-primitives.css' // 含 .lang-select
249
+ import '@usethink/cf-admin-fe/styles/login-primitives.css' // .admin-login / .login-card
86
250
  ```
87
251
 
88
- ## What stays in the product
252
+ ### Recipes
89
253
 
90
- - Domain admin **pages** (`views/admin/*`)
91
- - Auth/session composables, API clients, ConfigField registries
92
- - Product-only CSS beyond the shared primitives
254
+ 参见 [`recipes/README.md`](./recipes/README.md)。列表骨架见 `recipes/list-page.snippet.ts`(含 **string vs number offset**、批量 `limit` 注释)。
93
255
 
94
- ## Develop
256
+ ### 说明书与接线对照
257
+
258
+ - **完整用法 / 注意事项:** [`docs/002_cf-admin-fe使用说明书与注意事项.md`](./docs/002_cf-admin-fe使用说明书与注意事项.md)
259
+ - **cf-shop 已接线什么、故意不接什么:** 同文档 **§5.8**
260
+ - **提取决策与结案残留:** [`docs/001_…差距分析`](./docs/001_cf-admin-fe提取差距分析报告_2026-08-05.md)(勿当待办路线图)
261
+
262
+ ## 开发与封包
95
263
 
96
264
  ```bash
97
265
  npm install
98
266
  npm test
99
267
  npm run type-check
268
+ npm run clean # 清空 dist/,避免过期声明混入
269
+ npm run build # clean + 仅生成 composables/utils/types 的 .d.ts
270
+ npm pack # 触发 prepack→build;入口仍以 src/ 为准
100
271
  ```
101
272
 
102
- ## License
273
+ **封包注意:**
274
+
275
+ - `files` 含 `src`、`dist`、`recipes`、`README.md`、`LICENSE`
276
+ - `dist/` 在 `.gitignore` 中,由 `prepack` 现场生成;**不要**手写或提交旧版根 `dist/index.d.ts`
277
+ - 运行时 `exports` 指向 **`src/`**(Vue SFC + TS 源);`dist` 仅为声明辅助
278
+
279
+ ## 版本记录
280
+
281
+ - **0.1.x** — 表格/toast/弹窗/分页原语
282
+ - **0.2.0** — 深度提取:ConfigField、AdminShell、token/request 工厂、日期时间/CSV/滚动/分位金额工具
283
+ - **0.2.1** — 导航助手、i18n 合并种子、recipes
284
+ - **0.2.2** — 系统配置纯函数引擎、`createAdminRequestRaw`、`AdminSystemConfigDefinition` 别名
285
+ - **0.2.3** — `useAdminListLoader`、`AdminLoginShell` + login-primitives CSS、原语中的 `.lang-select`
286
+ - **0.2.4** — OTP 助手、更深化的 recipes
287
+ - **0.2.5** — `createAdminFlagSession`、request `onSuccess` / credentials、cookie recipes(cf-auth 验证)
288
+ - **0.2.6** — `toErrorMessage`、批量 toast/数量上限;`formatMetadata` + `AdminMetadataDetail`;`buildAdminOffsetParams` / `applyAdminOffsetParams`;`copyWithToast`;`recipes/list-page.snippet.ts`;封包 clean/prepack/LICENSE;完整说明书 `docs/002_…`
289
+ - **0.2.6 文档收口(无 API 变更)** — README/recipes/001/002 对齐:string vs number offset、batch `limit` 须跟后端 max、shop §5.8 接线对照、MetadataDetail「新页优先」
290
+ - **0.2.7** — `.table-wrap` 去掉 `scrollbar-gutter: stable`,消除无溢出时表格右侧缺口
291
+ - **0.2.8** — `resolveAdminT` / `tKit`;AdminShell / ConfigField / AdminMetadataDetail 契约测;`npm run size-report`(CSS/源码规模 + runtime dep 门禁)
292
+
293
+ ## 许可证
103
294
 
104
- MIT
295
+ MIT(见仓库根目录 `LICENSE`)
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Cookie 会话产品用的本地布尔管理端守卫 flag。
3
+ *
4
+ * 真正的会话在 HTTP cookie(Better Auth 等);此处只存 localStorage 快路径标记,
5
+ * 供路由守卫使用——与 Bearer 产品的 {@link createAdminTokenSession} 对称。
6
+ *
7
+ * 产品负责:健康探测、登录/登出、OAuth/PKCE、ADMIN_* 白名单。
8
+ * 套件负责:单 key 读写 + 一个 Vue ref 句柄(在模块作用域创建一次)。
9
+ */
10
+ import { type ComputedRef, type Ref } from 'vue';
11
+ export declare const DEFAULT_ADMIN_FLAG_TRUTHY = "1";
12
+ export type CreateAdminFlagSessionOptions = {
13
+ /** localStorage 键名(如 `cf_auth_admin_ok`) */
14
+ storageKey: string;
15
+ /** 已登录时写入的值;默认 `'1'` */
16
+ truthyValue?: string;
17
+ /** 覆盖存储实现(测试用);默认在可用时用 `localStorage` */
18
+ storage?: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'> | null;
19
+ };
20
+ export declare function readAdminFlagFromStorage(storageKey: string, storage?: CreateAdminFlagSessionOptions['storage'], truthyValue?: string): boolean;
21
+ export declare function writeAdminFlagToStorage(storageKey: string, ok: boolean, options?: {
22
+ storage?: CreateAdminFlagSessionOptions['storage'];
23
+ truthyValue?: string;
24
+ }): void;
25
+ export type AdminFlagSession = {
26
+ /** 与 storage flag 同步的响应式镜像 */
27
+ loggedIn: Ref<boolean>;
28
+ isLoggedIn: ComputedRef<boolean>;
29
+ /** 读 storage(作为 session.readFlag 调用时也可刷新 ref) */
30
+ readFlag: () => boolean;
31
+ /** 唯一写入入口:true 写入 truthy,false 删除键 */
32
+ writeFlag: (ok: boolean) => void;
33
+ markLoggedIn: () => void;
34
+ clearSession: () => void;
35
+ storageKey: string;
36
+ truthyValue: string;
37
+ };
38
+ /**
39
+ * Cookie 管理端本地守卫 flag 的 Vue 会话句柄。
40
+ * 在产品模块作用域 **创建一次**(与 createAdminTokenSession 同模式)。
41
+ */
42
+ export declare function createAdminFlagSession(options: CreateAdminFlagSessionOptions): AdminFlagSession;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * 产品管理端 HTTP 客户端工厂。
3
+ *
4
+ * CF 各产品差异点:
5
+ * - 认证:Bearer JWT(shop/lottery)vs cookie 会话(cf-auth)
6
+ * - 成功判定:仅 `res.ok` vs `res.ok && data.ok`
7
+ * - 错误字段:`error` vs `message`
8
+ * - 401 副作用:清 token / 整会话清空 / 跳转登录
9
+ * - Token 来源:闭包 `getToken` vs 每次调用传 `token`(shop 的 api/admin.ts)
10
+ *
11
+ * 业务 API 方法仍在产品侧;此处只统一 fetch、请求头与 401 处理。
12
+ */
13
+ export type AdminRequestResultMode = 'http-ok' | 'ok-envelope';
14
+ /** 结构化错误,便于产品映射到 AdminApiError,无需再解析 message 字符串。 */
15
+ export declare class AdminRequestError extends Error {
16
+ readonly status: number;
17
+ readonly code?: string;
18
+ readonly data: Record<string, unknown>;
19
+ readonly response?: Response;
20
+ constructor(message: string, status: number, options?: {
21
+ code?: string;
22
+ data?: Record<string, unknown>;
23
+ response?: Response;
24
+ });
25
+ }
26
+ export type CreateAdminRequestOptions = {
27
+ /** 可选 API 前缀(如 `''` 或 origin)。path 仍可为绝对地址。 */
28
+ baseUrl?: string;
29
+ /** 返回当前 Bearer token;省略/空 → 不带 Authorization */
30
+ getToken?: () => string | null | undefined;
31
+ /** fetch credentials;cookie 管理端用 `include` */
32
+ credentials?: RequestCredentials;
33
+ /**
34
+ * - `http-ok`:`res.ok` 即成功(cf-shop admin.ts 风格;body 可无 `ok`)
35
+ * - `ok-envelope`:`res.ok && data.ok`(lottery / 多数列表接口)
36
+ */
37
+ resultMode?: AdminRequestResultMode;
38
+ /** 构造 Error 时优先取 `error`(shop)或 `message`(lottery) */
39
+ errorField?: 'error' | 'message' | 'auto';
40
+ /** HTTP 401 且抛错前调用;产品负责清会话并跳转登录 */
41
+ onUnauthorized?: (ctx: {
42
+ path: string;
43
+ data: Record<string, unknown>;
44
+ response: Response;
45
+ }) => void;
46
+ /**
47
+ * 成功响应被接受后调用(http-ok / ok-envelope)。
48
+ * Cookie 管理端用来「治愈」缺失的本地守卫 flag,无需每个调用点包一层(cf-auth 模式)。
49
+ * Bearer 产品通常省略。
50
+ */
51
+ onSuccess?: (ctx: {
52
+ path: string;
53
+ data: Record<string, unknown>;
54
+ response: Response;
55
+ }) => void;
56
+ /**
57
+ * 为 false 时:401 仍抛错,但跳过 onUnauthorized
58
+ * (登录探测 token 时不得清掉已有会话)。
59
+ */
60
+ redirectOnUnauthorized?: boolean;
61
+ /** 默认请求头,会被单次调用的 headers 覆盖合并 */
62
+ defaultHeaders?: HeadersInit;
63
+ /**
64
+ * 跳过跳转时(token 探测)401 文案覆盖。
65
+ * Shop 使用固定「管理令牌无效」与 code INVALID_ADMIN_TOKEN。
66
+ */
67
+ unauthorizedProbeMessage?: string;
68
+ unauthorizedProbeCode?: string;
69
+ };
70
+ export type AdminRequestCallOptions = RequestInit & {
71
+ /** 查询参数;undefined / '' 会跳过 */
72
+ params?: Record<string, string | number | boolean | Array<string | number> | undefined | null>;
73
+ /** 覆盖工厂对本调用的 redirectOnUnauthorized 默认 */
74
+ redirectOnUnauthorized?: boolean;
75
+ /**
76
+ * 单次调用的 Bearer token(shop 每个域 API 方法都会传入)。
77
+ * 已设置时(含空字符串)覆盖工厂 `getToken`。
78
+ */
79
+ token?: string | null;
80
+ };
81
+ export type AdminRequestFn = <T = unknown>(path: string, options?: AdminRequestCallOptions) => Promise<T>;
82
+ /**
83
+ * 非 JSON 管理端请求(blob 模板 / 导出下载)。
84
+ * 不解析成功 body;只检查 `res.ok` 并处理 401。
85
+ */
86
+ export type AdminRequestRawFn = (path: string, options?: AdminRequestCallOptions) => Promise<Response>;
87
+ /**
88
+ * 构建可复用的 `adminRequest(path, init)`。
89
+ */
90
+ export declare function createAdminRequest(factory?: CreateAdminRequestOptions): AdminRequestFn;
91
+ /**
92
+ * 构建 raw `adminRequestRaw(path, init)`,用于二进制 / 非 JSON 响应。
93
+ * 与 {@link createAdminRequest} 共享 token、params、headers 与 401 行为。
94
+ */
95
+ export declare function createAdminRequestRaw(factory?: CreateAdminRequestOptions): AdminRequestRawFn;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * 通用管理端 Bearer token 会话(localStorage + TTL)。
3
+ *
4
+ * 产品在其上叠加产品专属 key / 伪装 / 平台角色。
5
+ * Cookie 会话产品改用 {@link createAdminFlagSession}(无 Bearer)。
6
+ */
7
+ import { type ComputedRef, type Ref } from 'vue';
8
+ export type StoredAdminToken = {
9
+ token: string;
10
+ expiry: number;
11
+ };
12
+ export declare const DEFAULT_ADMIN_TOKEN_TTL_MS: number;
13
+ export type CreateAdminTokenSessionOptions = {
14
+ /** localStorage 键名(如 `admin_token`) */
15
+ storageKey: string;
16
+ /** 会话有效期;默认 8 小时,对齐常见 verify-jwt 会话 JWT */
17
+ ttlMs?: number;
18
+ /** 覆盖存储实现(测试用);默认在可用时用 `localStorage` */
19
+ storage?: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'> | null;
20
+ };
21
+ /**
22
+ * 解析已存储的 token 记录。
23
+ * - 含 token+expiry 的对象:遵守 TTL;过期则清除键
24
+ * - 旧版裸字符串:保留一次迁移(不自动清除)
25
+ */
26
+ export declare function parseStoredAdminToken(raw: string | null, options?: {
27
+ storageKey?: string;
28
+ storage?: CreateAdminTokenSessionOptions['storage'];
29
+ }): string;
30
+ export declare function readAdminTokenFromStorage(storageKey: string, storage?: CreateAdminTokenSessionOptions['storage']): string;
31
+ export type AdminTokenSession = {
32
+ token: Ref<string>;
33
+ isLoggedIn: ComputedRef<boolean>;
34
+ setToken: (value: string) => void;
35
+ clearToken: () => void;
36
+ readToken: () => string;
37
+ storageKey: string;
38
+ ttlMs: number;
39
+ };
40
+ /**
41
+ * 单个管理端 token 键的 Vue 会话句柄。
42
+ * 在 setup 外的路由守卫中调用 `readToken`。
43
+ */
44
+ export declare function createAdminTokenSession(options: CreateAdminTokenSessionOptions): AdminTokenSession;
45
+ /**
46
+ * 便捷组合式:默认键 `admin_token`、8 小时 TTL(cf-shop 形态)。
47
+ * 多租户 / 伪装登录产品应直接调用 {@link createAdminTokenSession}。
48
+ */
49
+ export declare function useAdminTokenSession(options?: Partial<CreateAdminTokenSessionOptions>): AdminTokenSession;
@@ -3,4 +3,10 @@ export { useTableSelection, type UseTableSelectionOptions, } from './useTableSel
3
3
  export { useConfirmDialog, type ConfirmOptionDef, type ConfirmAskOptions, type ConfirmResult, } from './useConfirmDialog';
4
4
  export { useToast, MAX_TOASTS, type ToastType, type ToastItem, } from './useToast';
5
5
  export { useAdminBatchOperation, type AdminBatchOperationResult, } from './useAdminBatchOperation';
6
- export { writeClipboardText, copyText, } from './useClipboard';
6
+ export { writeClipboardText, copyText, copyWithToast, type CopyWithToastOptions, } from './useClipboard';
7
+ export { createAdminTokenSession, useAdminTokenSession, parseStoredAdminToken, readAdminTokenFromStorage, DEFAULT_ADMIN_TOKEN_TTL_MS, type StoredAdminToken, type CreateAdminTokenSessionOptions, type AdminTokenSession, } from './createAdminTokenSession';
8
+ export { createAdminFlagSession, readAdminFlagFromStorage, writeAdminFlagToStorage, DEFAULT_ADMIN_FLAG_TRUTHY, type CreateAdminFlagSessionOptions, type AdminFlagSession, } from './createAdminFlagSession';
9
+ export { createAdminRequest, createAdminRequestRaw, AdminRequestError, type CreateAdminRequestOptions, type AdminRequestCallOptions, type AdminRequestFn, type AdminRequestRawFn, type AdminRequestResultMode, } from './createAdminRequest';
10
+ export { useAdminSidebar, type UseAdminSidebarOptions, type UseAdminSidebarReturn, } from './useAdminSidebar';
11
+ export { useAdminListLoader, type UseAdminListLoaderOptions, type UseAdminListLoaderReturn, type AdminListLoadContext, type AdminListRunLoadOptions, } from './useAdminListLoader';
12
+ export { useNow, } from './useNow';
@@ -0,0 +1,45 @@
1
+ /**
2
+ * 后台列表通用的加载生命周期:loading / loadError / 竞态安全的序号。
3
+ * 业务请求、列定义、筛选逻辑保留在产品页面中。
4
+ */
5
+ import { type Ref } from 'vue';
6
+ export type UseAdminListLoaderOptions = {
7
+ /** 初始加载状态(默认 false)。 */
8
+ initialLoading?: boolean;
9
+ };
10
+ export type AdminListLoadContext = {
11
+ /** 本次尝试的单调递增序号。 */
12
+ sequence: number;
13
+ /** 若有更新的加载已开始(或调用了 invalidate),返回 true。 */
14
+ isStale: () => boolean;
15
+ };
16
+ export type AdminListRunLoadOptions = {
17
+ /**
18
+ * 将抛出的错误映射为面向用户的 loadError 字符串。
19
+ * 默认:Error.message 或 String(err)。
20
+ */
21
+ onError?: (err: unknown) => string;
22
+ };
23
+ export type UseAdminListLoaderReturn = {
24
+ loading: Ref<boolean>;
25
+ loadError: Ref<string>;
26
+ /** 递增序号、置 loading 为 true、清空 loadError;返回本次尝试的序号。 */
27
+ beginLoad: () => number;
28
+ isStale: (sequence: number) => boolean;
29
+ /** 仅当序号仍为最新时清除 loading。 */
30
+ finishLoad: (sequence: number) => void;
31
+ /**
32
+ * 若序号仍为最新则记录错误信息。
33
+ * @returns 错误是否已生效(未被判定为过期)
34
+ */
35
+ failLoad: (sequence: number, message: string) => boolean;
36
+ /**
37
+ * 完整且竞态安全的加载流程:begin → work → catch → finish。
38
+ * work 内部在 await 之后应通过 `ctx.isStale()` 提前返回。
39
+ * @returns 若尝试未抛错且 try 结束时未过期,返回 true
40
+ */
41
+ runLoad: (work: (ctx: AdminListLoadContext) => void | Promise<void>, options?: AdminListRunLoadOptions) => Promise<boolean>;
42
+ /** 使进行中的加载失效(例如组件卸载时)。 */
43
+ invalidate: () => void;
44
+ };
45
+ export declare function useAdminListLoader(options?: UseAdminListLoaderOptions): UseAdminListLoaderReturn;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * 响应式后台侧边栏状态(1024px 以下显示汉堡菜单)。
3
+ * 布局骨架由产品方维护;这里只负责媒体查询与打开状态。
4
+ */
5
+ import { type Ref } from 'vue';
6
+ export type UseAdminSidebarOptions = {
7
+ /** matchMedia 查询条件;默认 `(max-width: 1023px)` */
8
+ query?: string;
9
+ };
10
+ export type UseAdminSidebarReturn = {
11
+ isMobile: Ref<boolean>;
12
+ sidebarOpen: Ref<boolean>;
13
+ toggleSidebar: () => void;
14
+ closeSidebar: () => void;
15
+ openSidebar: () => void;
16
+ };
17
+ export declare function useAdminSidebar(options?: UseAdminSidebarOptions): UseAdminSidebarReturn;