create-dwy 0.7.1 → 0.9.0

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 (96) hide show
  1. package/package.json +1 -1
  2. package/src/create.js +50 -3
  3. package/src/sync.js +228 -130
  4. package/src/utils.js +4 -0
  5. package/templates/claude-global/CLAUDE.md +81 -6
  6. package/templates/claude-global/rules/dwy-postgres.md +89 -0
  7. package/templates/claude-global/rules/dwy-python-backend.md +492 -0
  8. package/templates/claude-global/rules/dwy-python-core.md +94 -634
  9. package/templates/claude-global/rules/dwy-python-testing.md +115 -13
  10. package/templates/claude-global/rules/dwy-redis.md +88 -0
  11. package/templates/claude-global/rules/dwy-vue-core.md +341 -645
  12. package/templates/claude-global/rules/dwy-vue-pinia.md +47 -34
  13. package/templates/claude-global/rules/dwy-vue-testing.md +120 -55
  14. package/templates/claude-global/skills/dwy-deploy-audit/references/checks-network.md +1 -1
  15. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_nginx.sh +1 -1
  16. package/templates/claude-global/skills/dwy-dolphindb/SKILL.md +22 -15
  17. package/templates/claude-global/skills/dwy-dolphindb/evals/evals.json +23 -0
  18. package/templates/claude-global/skills/dwy-dolphindb/references/dolphindb-rules.md +100 -1
  19. package/templates/claude-global/skills/dwy-ekit/SKILL.md +142 -276
  20. package/templates/claude-global/skills/dwy-eui/SKILL.md +264 -182
  21. package/templates/claude-global/skills/dwy-fullstack-scaffold/SKILL.md +117 -129
  22. package/templates/claude-global/skills/dwy-git-commit/SKILL.md +125 -0
  23. package/templates/claude-global/skills/dwy-publish/SKILL.md +1 -1
  24. package/templates/claude-global/{rules/dwy-tech-stack.md → skills/dwy-tech-stack/SKILL.md} +4 -3
  25. package/templates/project/fullstack-monorepo/CLAUDE.md.hbs +81 -0
  26. package/templates/project/fullstack-monorepo/backend/.python-version +1 -0
  27. package/templates/project/fullstack-monorepo/backend/CLAUDE.md.hbs +68 -0
  28. package/templates/project/fullstack-monorepo/backend/Dockerfile.dev +23 -0
  29. package/templates/project/fullstack-monorepo/backend/alembic/env.py.hbs +68 -0
  30. package/templates/project/fullstack-monorepo/backend/alembic/script.py.mako +26 -0
  31. package/templates/project/fullstack-monorepo/backend/alembic.ini.hbs +43 -0
  32. package/templates/project/fullstack-monorepo/backend/packages/dwy-shared/pyproject.toml +16 -0
  33. package/templates/project/fullstack-monorepo/backend/packages/dwy-shared/src/dwy_shared/__init__.py +12 -0
  34. package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/pyproject.toml.hbs +20 -0
  35. package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/__init__.py.hbs +40 -0
  36. package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/config.py +9 -0
  37. package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/models.py.hbs +17 -0
  38. package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/protocols.py +14 -0
  39. package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/router.py.hbs +22 -0
  40. package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/schemas.py +21 -0
  41. package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/service.py +11 -0
  42. package/templates/project/fullstack-monorepo/backend/pyproject.toml.hbs +84 -0
  43. package/templates/project/fullstack-monorepo/backend/src/app/__init__.py +3 -0
  44. package/templates/project/fullstack-monorepo/backend/src/app/config.py.hbs +21 -0
  45. package/templates/project/fullstack-monorepo/backend/src/app/exceptions.py +26 -0
  46. package/templates/project/fullstack-monorepo/backend/src/app/factories.py +16 -0
  47. package/templates/project/fullstack-monorepo/backend/src/app/infra/__init__.py +1 -0
  48. package/templates/project/fullstack-monorepo/backend/src/app/main.py.hbs +65 -0
  49. package/templates/project/fullstack-monorepo/backend/src/app/middleware/__init__.py +1 -0
  50. package/templates/project/fullstack-monorepo/backend/tests/__init__.py +0 -0
  51. package/templates/project/fullstack-monorepo/backend/tests/conftest.py.hbs +42 -0
  52. package/templates/project/fullstack-monorepo/dev.sh.hbs +83 -0
  53. package/templates/project/fullstack-monorepo/docker-compose.dev.yml.hbs +36 -0
  54. package/templates/project/fullstack-monorepo/frontend/CLAUDE.md.hbs +35 -0
  55. package/templates/project/fullstack-monorepo/frontend/Dockerfile +18 -0
  56. package/templates/project/fullstack-monorepo/frontend/apps/web/index.html.hbs +13 -0
  57. package/templates/project/fullstack-monorepo/frontend/apps/web/package.json.hbs +31 -0
  58. package/templates/project/fullstack-monorepo/frontend/apps/web/src/App.vue.hbs +10 -0
  59. package/templates/project/fullstack-monorepo/frontend/apps/web/src/api/client.ts.hbs +11 -0
  60. package/templates/project/fullstack-monorepo/frontend/apps/web/src/features/{{initialDomain}}/.gitkeep +0 -0
  61. package/templates/project/fullstack-monorepo/frontend/apps/web/src/index.css +12 -0
  62. package/templates/project/fullstack-monorepo/frontend/apps/web/src/main.ts +17 -0
  63. package/templates/project/fullstack-monorepo/frontend/apps/web/src/router/index.ts.hbs +24 -0
  64. package/templates/project/fullstack-monorepo/frontend/apps/web/src/stores/.gitkeep +0 -0
  65. package/templates/project/fullstack-monorepo/frontend/apps/web/src/types/.gitkeep +0 -0
  66. package/templates/project/fullstack-monorepo/frontend/apps/web/src/views/HomeView.vue.hbs +36 -0
  67. package/templates/project/fullstack-monorepo/frontend/apps/web/tsconfig.json +7 -0
  68. package/templates/project/fullstack-monorepo/frontend/apps/web/vite.config.ts.hbs +27 -0
  69. package/templates/project/fullstack-monorepo/frontend/nginx.conf +11 -0
  70. package/templates/project/fullstack-monorepo/frontend/package.json.hbs +13 -0
  71. package/templates/project/fullstack-monorepo/frontend/packages/core/package.json.hbs +15 -0
  72. package/templates/project/fullstack-monorepo/frontend/packages/core/src/index.ts +3 -0
  73. package/templates/project/fullstack-monorepo/frontend/packages/features/{{initialDomain}}/package.json.hbs +14 -0
  74. package/templates/project/fullstack-monorepo/frontend/packages/features/{{initialDomain}}/src/index.ts.hbs +16 -0
  75. package/templates/project/fullstack-monorepo/frontend/packages/features/{{initialDomain}}/src/pages/IndexPage.vue.hbs +9 -0
  76. package/templates/project/fullstack-monorepo/frontend/packages/ui/package.json.hbs +15 -0
  77. package/templates/project/fullstack-monorepo/frontend/packages/ui/src/index.ts +3 -0
  78. package/templates/project/fullstack-monorepo/frontend/pnpm-workspace.yaml.hbs +23 -0
  79. package/templates/project/fullstack-monorepo/frontend/tsconfig.base.json.hbs +23 -0
  80. package/templates/claude-global/rules/dwy-api-design.md +0 -110
  81. package/templates/claude-global/rules/dwy-git-commit-convention.md +0 -82
  82. package/templates/claude-global/rules/dwy-git-security.md +0 -53
  83. package/templates/claude-global/rules/dwy-payload-limits.md +0 -186
  84. package/templates/claude-global/rules/dwy-prefer-open-source.md +0 -32
  85. package/templates/claude-global/rules/dwy-project-structure.md +0 -181
  86. package/templates/claude-global/rules/dwy-python-fastapi.md +0 -272
  87. package/templates/claude-global/rules/dwy-python-orm.md +0 -61
  88. package/templates/claude-global/rules/dwy-python-security.md +0 -636
  89. package/templates/claude-global/rules/dwy-server-security.md +0 -518
  90. package/templates/claude-global/rules/dwy-vue-api.md +0 -146
  91. package/templates/claude-global/skills/dwy-ekit/references/data.md +0 -135
  92. package/templates/claude-global/skills/dwy-ekit/references/form.md +0 -68
  93. package/templates/claude-global/skills/dwy-ekit/references/request.md +0 -183
  94. package/templates/claude-global/skills/dwy-ekit/references/ui.md +0 -109
  95. package/templates/claude-global/skills/dwy-vue-core/SKILL.md +0 -890
  96. /package/templates/{claude-global/commands → project/fullstack-monorepo/backend/alembic/versions}/.gitkeep +0 -0
@@ -1,354 +1,220 @@
1
1
  ---
2
2
  name: dwy-ekit
3
- description: "【强制速查】只要涉及以下任何主题,必须调用本 skill,无论用户是否说ekit:HTTP 请求 / axios 封装 / token 刷新 / dwyeapi 响应契约对接 / localStorage / Cookie / useStorage / useCookie / 日期格式化 / 时区处理 / dayjs / 表单校验 / zod schema / vee-validate / PII 脱敏 / 剪贴板 / useClipboard / 文件下载 / Blob / 查询字符串 / qs / Vue composables(debounce / throttle / click-outside / event-listener / window-size / media-query / intersection-observer / resize-observer / useFormPersist)。本 skill 是 @dwydev/ekit(v0.7.0)的唯一权威来源,禁止绕过 ekit 直接手写底层 API。"
3
+ description: "@dwydev/ekit 工具库使用指南。涉及以下任何主题,**必须**使用此 skill(即使用户没有说 'ekit'):HTTP 请求 / axios 封装 / token 刷新 / dwyeapi 响应契约对接 / localStorage / Cookie / useStorage / useCookie / 日期格式化 / 时区处理 / dayjs / 表单校验 / zod schema / vee-validate / PII 脱敏 / 剪贴板 / useClipboard / 文件下载 / Blob / 查询字符串 / qs / Vue composables(debounce / throttle / click-outside / event-listener / window-size / media-query / intersection-observer / resize-observer / useFormPersist)。本 skill 是 @dwydev/ekit 的唯一权威导航来源,禁止绕过 ekit 直接手写底层 API。"
4
+ ekit_baseline_version: "0.8.0"
4
5
  ---
5
6
 
6
- # @dwydev/ekit 工具库速查(v0.7.0)
7
+ # @dwydev/ekit 工具库使用指南
7
8
 
8
- Vue 3 项目通用工具库,**薄封装层**:底层用 axios / dayjs / zod / js-cookie / qs / file-saver / @vueuse/core,**对外只暴露 ekit 自有契约**,不泄露底层库类型。
9
+ Vue 3 项目通用工具库。**薄封装层**:底层用 axios / dayjs / zod / js-cookie / qs / file-saver / @vueuse/core,对外**只暴露 ekit 自有契约**,不泄露底层库类型。
9
10
 
10
- 包含 10 个模块:`request` / `storage` / `cookie` / `date` / `validators` / `copy` / `qs` / `file` / `hooks` / `masking`。
11
+ 10 个模块:`request` / `storage` / `cookie` / `date` / `validators` / `copy` / `qs` / `file` / `hooks` / `masking`。
11
12
 
12
- ## 安装
13
+ 本 skill 索引基于 ekit **0.8.0**。消费方版本可能不同 — 见 [版本兼容规则](#版本兼容规则)。
13
14
 
14
- ```bash
15
- pnpm add @dwydev/ekit
16
- # peerDependencies: vue ^3.5.0, axios ^1.0.0, dayjs ^1.11.0, zod ^3.25.0, @vueuse/core ^14.2.1
17
- ```
15
+ ---
18
16
 
19
- ```ts
20
- import {
21
- createRequest, useStorage, useCookie,
22
- formatDateTime, isPhone, copyText, stringify, downloadFile,
23
- useDebounce, maskPhone,
24
- } from '@dwydev/ekit'
25
- ```
17
+ ## 查 API 标准动作(核心:每次写 ekit 代码前都做)
26
18
 
27
- ## 防腐层硬性约束(写代码前必读)
19
+ ekit 共 82 个对外导出。**本文档不再镜像函数签名** —— 因为会随版本漂移。需要某个 API 的精确签名时,按下面顺序拿:
28
20
 
29
- ekit 对外类型 **不允许** 出现底层库类名。下面这些导入 **会失败** —— 因为 ekit 不再 re-export:
21
+ ### 第一步:定位 ekit 真实版本
30
22
 
31
- ```ts
32
- // 错误:ekit 不导出底层类型
33
- import type { AxiosInstance, IStringifyOptions, CookieAttributes } from '@dwydev/ekit'
34
- import { dayjs } from '@dwydev/ekit'
23
+ 读消费方项目 `package.json`,找 `dependencies["@dwydev/ekit"]` 或 `peerDependencies["@dwydev/ekit"]`。这才是写代码的依据。
24
+
25
+ ### 第二步:读 module-manifest.json(一次拿导出索引)
26
+
27
+ 从 ekit 0.8.0 起,npm 包带一份导出清单:
35
28
 
36
- // 正确:用 ekit 自有契约
37
- import type { HttpClient, StringifyOptions, CookieOptions } from '@dwydev/ekit'
38
- import { now, formatTimestamp } from '@dwydev/ekit' // 不需要 dayjs 实例
29
+ ```
30
+ <project-root>/node_modules/@dwydev/ekit/dist/module-manifest.json
39
31
  ```
40
32
 
41
- 业务代码也 **禁止** 直接调底层 API:
33
+ 结构(精简示例):
34
+ ```json
35
+ {
36
+ "version": "0.8.0",
37
+ "exportToModule": {
38
+ "createRequest": "request",
39
+ "HttpClient": "request",
40
+ "useStorage": "storage",
41
+ "now": "date"
42
+ },
43
+ "modules": {
44
+ "request": {
45
+ "entry": "request/index.d.ts",
46
+ "files": ["request/types.d.ts", "request/client.d.ts", "request/plugins.d.ts", "..."],
47
+ "values": ["createRequest", "tokenPlugin", "..."],
48
+ "types": ["HttpClient", "HttpConfig", "PageData", "..."]
49
+ }
50
+ }
51
+ }
52
+ ```
42
53
 
43
- | 禁止 | 改用 |
44
- |------|------|
45
- | `new Date()` / `toLocaleString()` | `now()` / `formatTimestamp()` / `formatDateTime()` |
46
- | `localStorage.setItem(...)` | `storage.set()` 或 `useStorage()` |
47
- | `navigator.clipboard.writeText(...)` | `copyText()` 或 `useClipboard()` |
48
- | `document.cookie = ...` | `cookie.set()` 或 `useCookie()` |
49
- | `document.addEventListener('click', ...)` | `useEventListener()` |
50
- | 手写 axios 实例 | `createRequest()` |
54
+ 用法:
55
+ - 知道 API 名(如 `createRequest`、`HttpClient`) → 查 `exportToModule[name]` 拿到模块名
56
+ - 看 `modules[name].files` 找到要读的 .d.ts 文件
57
+ - 类型定义通常在 `{module}/types.d.ts`,函数实现签名在 `{module}/{client,plugins,helpers,api}.d.ts`
51
58
 
52
- ## 模块速查
59
+ manifest 不存在的回退:消费方装的是 ekit < 0.8.0 → 直接走第三步用约定路径。
53
60
 
54
- ### request — HTTP 客户端 + 4 内置插件 + dwyeapi 契约
61
+ ### 第三步:读模块 .d.ts(精确签名 + JSDoc)
62
+
63
+ 按存在性回退:
55
64
 
56
- ```ts
57
- import {
58
- createRequest, tokenPlugin, headerPlugin, unwrapPlugin, refreshTokenPlugin,
59
- SUCCESS_CODE, isApiBusinessError, extractValidationErrors,
60
- } from '@dwydev/ekit'
61
- import type {
62
- HttpClient, HttpConfig, HttpResponse, HttpError, HttpPlugin,
63
- CreateRequestOptions, ApiResponse, PageData,
64
- } from '@dwydev/ekit'
65
65
  ```
66
+ a. 优先:<project-root>/node_modules/@dwydev/ekit/dist/{module}/{file}.d.ts
67
+ —— 下游消费方默认场景,含完整签名 + JSDoc 注释
66
68
 
67
- **createRequest**
69
+ b. 次选:<dwy-shared-root>/frontend/ekit/src/{module}/{file}.ts
70
+ —— 仅当在 dwy-shared monorepo 内或并列 clone 时可用
68
71
 
69
- ```ts
70
- const http: HttpClient = createRequest({
71
- baseURL: '/api',
72
- timeout: 30000,
73
- headers: { 'X-Client': 'web' },
74
- plugins: [tokenPlugin({ getToken: () => localStorage.getItem('token') }), unwrapPlugin()],
75
- })
76
-
77
- const res = await http.get<User>('/users/1') // res: HttpResponse<User>
78
- const list = await http.get<PageData<User>>('/users')
79
- list.data.items // User[]
80
- list.data.total // number
81
- list.data.page // number
82
- list.data.page_size // number
72
+ c. 都拿不到 → 退到本文档下方的 [模块用途索引](#模块用途索引) + 总入口 dist/index.d.ts
83
73
  ```
84
74
 
85
- **内置插件**
75
+ ### 第四步:冲突时永远信 node_modules
86
76
 
87
- | 插件 | 参数 | 说明 |
88
- |------|------|------|
89
- | `tokenPlugin` | `{ getToken: () => string | null }` | 注入 `Authorization: Bearer <token>` |
90
- | `headerPlugin` | `{ name, getValue }` | 动态自定义 header |
91
- | `unwrapPlugin` | 无 | 解包 dwyeapi `{ code, message, data, timestamp }`,业务失败时 reject |
92
- | `refreshTokenPlugin` | `{ getRefreshToken, refreshFn, onRefreshFail, retry, isLoginUrl? }` | 401 自动刷新并重放;`retry: (cfg) => http.request(cfg)` 必填 |
77
+ 本 SKILL.md 是导航 + 心智模型,**不是 API 真相**。当文档描述与消费方 node_modules 中 `.d.ts` 不一致:无条件以 node_modules 为准。
93
78
 
94
- **响应契约 & 错误捕获**
79
+ ---
95
80
 
96
- ```ts
97
- // unwrap 后 data 是纯业务数据(单体)或 PageData<T>(分页)
98
- const res = await http.get<User>('/users/1') // res.data: User
99
-
100
- // 捕获业务错误
101
- try {
102
- await http.post('/orders', body)
103
- } catch (err) {
104
- if (!isApiBusinessError(err)) { message.error('网络异常'); return }
105
- switch (err.businessCode) {
106
- case 'INSUFFICIENT_BALANCE': showRecharge(); break
107
- case 'VALIDATION_ERROR': form.setErrors(extractValidationErrors(err)); break
108
- default: message.error(err.message)
109
- }
110
- }
111
- ```
81
+ ## 版本兼容规则
112
82
 
113
- `extractValidationErrors` 剥离 `body. / query. / path.` 前缀,返回 `{ field: message }` 扁平对象,与 vee-validate 的 `form.setErrors` 直接兼容。
83
+ - 本 skill `ekit_baseline_version: 0.8.0`,模块索引、防腐层约束、踩坑提示都基于这个版本
84
+ - ekit < 0.8.0 没有 `module-manifest.json`:直接走第三步的 .d.ts 约定路径
85
+ - 主版本号变更(0.x → 1.x)时,必须先读 `node_modules/@dwydev/ekit/dist/index.d.ts` 重建认知
114
86
 
115
- ### storage — localStorage
87
+ ---
116
88
 
117
- ```ts
118
- import { useStorage, storage } from '@dwydev/ekit'
119
- ```
89
+ ## 防腐层硬性约束(写代码前必读)
90
+
91
+ 这是 ekit **设计核心**,与版本无关。即使读了 .d.ts,也要遵守以下原则:
120
92
 
121
- | API | 签名 / 说明 |
122
- |-----|------------|
123
- | `useStorage(key, defaultValue, storage?, opts?)` | 再导出 @vueuse/core;跨标签页同步、SSR-safe、自动 JSON 序列化 |
124
- | `storage.get<T>(key, defaultValue?)` | 返回 `T | undefined`;JSON 解析失败按字符串返回 |
125
- | `storage.set(key, value)` | 自动 `JSON.stringify` |
126
- | `storage.remove(key)` | 删除 |
127
- | `storage.clear()` | 清空全部(慎用) |
93
+ ### 对外类型不允许出现底层库类名
128
94
 
129
95
  ```ts
130
- const token = useStorage<string>('token', '')
131
- const config = useStorage('config', { theme: 'light' }, localStorage, { mergeDefaults: true })
96
+ // 错误:ekit 不导出底层类型
97
+ import type { AxiosInstance, IStringifyOptions, CookieAttributes } from '@dwydev/ekit'
98
+ import { dayjs } from '@dwydev/ekit'
99
+
100
+ // 正确:用 ekit 自有契约
101
+ import type { HttpClient, StringifyOptions, CookieOptions } from '@dwydev/ekit'
102
+ import { now, formatTimestamp } from '@dwydev/ekit'
132
103
  ```
133
104
 
134
- ### cookie — Cookie 读写
105
+ ### 业务代码禁止直接调底层 API
135
106
 
136
- ```ts
137
- import { useCookie, cookie } from '@dwydev/ekit'
138
- import type { CookieOptions } from '@dwydev/ekit'
139
- ```
107
+ | 禁止 | 改用 |
108
+ |------|------|
109
+ | `new Date()` / `toLocaleString()` | `now()` / `formatTimestamp()` / `formatDateTime()` |
110
+ | `localStorage.setItem(...)` | `storage.set()` 或 `useStorage()` |
111
+ | `navigator.clipboard.writeText(...)` | `copyText()` 或 `useClipboard()` |
112
+ | `document.cookie = ...` | `cookie.set()` 或 `useCookie()` |
113
+ | `document.addEventListener('click', ...)` | `useEventListener()` |
114
+ | 手写 axios 实例 | `createRequest()` |
115
+ | 自写 debounce / throttle / clickOutside | `useDebounce()` / `useThrottle()` / `useClickOutside()` |
140
116
 
141
- ```ts
142
- interface CookieOptions {
143
- expires?: number | Date // 天数或 Date;不传 = 会话级
144
- path?: string // 默认 '/'
145
- domain?: string
146
- secure?: boolean
147
- sameSite?: 'strict' | 'lax' | 'none'
148
- }
117
+ 理由:替换底层实现(如 axios → fetch、dayjs → date-fns)时业务代码不用改,且统一封装能保证 SSR-safe / 跨标签页同步 / 边界处理等开源库已经做好的事。
149
118
 
150
- cookie.get<User>('user')
151
- cookie.set('user', { id: 1 }, { expires: 7, secure: true })
152
- cookie.remove('user', { path: '/' })
119
+ ---
153
120
 
154
- const lang = useCookie('lang', 'zh-CN')
155
- lang.value = 'en-US' // 自动写入
156
- lang.value = undefined // 自动删除
157
- ```
121
+ ## 重要约束与已知陷阱
158
122
 
159
- ### date — 日期 / 时间 / 时区
123
+ 源码读不出来的"踩坑经验",每次写 ekit 代码都遵守:
160
124
 
161
- ```ts
162
- import {
163
- now, formatTimestamp, formatInTimezone,
164
- formatDate, formatDateTime, formatTime, formatRelativeTime, formatBy,
165
- } from '@dwydev/ekit'
166
- ```
125
+ ### request(HTTP 客户端)
167
126
 
168
- | 函数 | 签名 | 输出示例 |
169
- |------|------|---------|
170
- | `now()` | `(): number` | `1745318445000`(UTC 毫秒戳) |
171
- | `formatTimestamp(ts, fmt?)` | `(number, string?) => string` | `'2026-04-22 18:40:45'`(默认) |
172
- | `formatInTimezone(tz, ts?, fmt?)` | `(string, number?, string?) => string` | `formatInTimezone('America/New_York', ts)` |
173
- | `formatDate(input)` | `(string | number | Date) => string` | `'2026-04-22'` |
174
- | `formatDateTime(input)` | 同上 | `'2026-04-22 18:40:45'` |
175
- | `formatTime(input)` | 同上 | `'18:40'` |
176
- | `formatRelativeTime(input)` | 同上 | `'刚刚' / '5 分钟前' / '3 小时前' / '2 天前'`;> 30 天退化为 `YYYY-MM-DD` |
177
- | `formatBy(input, template)` | 同上 | `formatBy(ts, 'YYYY年MM月DD日')` -> `'2026年04月22日'` |
127
+ - **dwyeapi 响应契约**:用 `unwrapPlugin()` 自动解包 `{ code, message, data, timestamp }`,业务失败自动 reject。`HttpResponse<T>.data` 才是纯业务数据
128
+ - **分页**:返回类型用 `PageData<T>`(字段:`items / total / page / page_size`)
129
+ - **token 刷新**:`refreshTokenPlugin` 的 `retry: (cfg) => http.request(cfg)` **必填**,否则刷新成功后无法重放失败请求
130
+ - **业务错误捕获**:用 `isApiBusinessError(err)` 类型守卫 + `err.businessCode` 判断;表单字段错误用 `extractValidationErrors(err)` 剥离 `body./query./path.` 前缀后直接 `form.setErrors()`
178
131
 
179
- ### qs — 查询字符串
132
+ ### date
180
133
 
181
- ```ts
182
- import { stringify, parse } from '@dwydev/ekit'
183
- import type { StringifyOptions, ParseOptions } from '@dwydev/ekit'
184
- ```
134
+ - ekit **不暴露** `dayjs` 实例和类型,所有日期运算走函数(`now / formatTimestamp / formatInTimezone / formatDate / formatDateTime / formatTime / formatRelativeTime / formatBy`)
135
+ - 入参 `DateInput = string | number | Date`,返回原生 `string / number / Date`
185
136
 
186
- ```ts
187
- stringify({ ids: [1, 2], name: 'a' }) // 'ids=1&ids=2&name=a'
188
- stringify({ ids: [1, 2] }, { arrayFormat: 'brackets' }) // 'ids[]=1&ids[]=2'
189
- stringify({ a: 1 }, { addQueryPrefix: true }) // '?a=1'
137
+ ### validators
190
138
 
191
- parse('?a=1&b=2') // { a: '1', b: '2' }(默认 ignoreQueryPrefix: true)
192
- parse('a.b=1', { allowDots: true }) // { a: { b: '1' } }
193
- ```
139
+ - 双层 API:布尔函数(`isPhone / isEmail / ...`)用于简单校验;**zod schemas**(`phoneSchema / emailSchema / ...`)用于 vee-validate 集成(配合 `toTypedSchema()`)
140
+ - `minLengthSchema(n)` / `maxLengthSchema(n)` 是**工厂函数**,调用后才返回 schema
194
141
 
195
- ### validators — 表单校验
142
+ ### hooks
196
143
 
197
- ```ts
198
- import {
199
- isPhone, isEmail, isIdCard, isUrl, isRequired, minLength, maxLength,
200
- phoneSchema, emailSchema, idCardSchema, urlSchema,
201
- requiredSchema, minLengthSchema, maxLengthSchema,
202
- } from '@dwydev/ekit'
203
- ```
144
+ - `useFormPersist(key, initialValue, options?)` 表单刷新自动回填,**默认排除 19 项敏感字段**(password / token / code / otp 等)。需要追加:`options.exclude`;需要完全自定义:`options.disableDefaultExclude: true`
145
+ - VueUse 再导出:`useThrottle / useWindowSize / useMediaQuery / useIntersectionObserver / useResizeObserver`(直接用 VueUse 名字,不要找 ekit 自己实现的版本 — 已经废弃)
146
+ - `useDebounce` 返回 `Readonly<Ref<T>>`(不是可写 ref,过去版本是可写的,迁移时注意)
204
147
 
205
- **布尔函数**
148
+ ### masking
206
149
 
207
- | 函数 | 规则 |
208
- |------|------|
209
- | `isPhone(value)` | 中国手机号 1[3-9] 开头 11 位 |
210
- | `isEmail(value)` | 基础邮箱格式 |
211
- | `isIdCard(value)` | 18 位身份证(末位允许 X/x) |
212
- | `isUrl(value)` | 合法 URL |
213
- | `isRequired(value)` | 非空(0 / false 视为有效) |
214
- | `minLength(value, n)` / `maxLength(value, n)` | 长度校验 |
150
+ - 纯函数,无依赖
151
+ - **输入不符合格式时原样返回,不抛异常**(设计意图:不影响显示流程)
215
152
 
216
- **zod schema(配合 vee-validate)**
153
+ ### storage / cookie
217
154
 
218
- ```ts
219
- import { useForm } from 'vee-validate'
220
- import { toTypedSchema } from '@vee-validate/zod'
221
- import { phoneSchema, emailSchema } from '@dwydev/ekit'
155
+ - `useStorage()` 是 VueUse 再导出,跨标签页同步 + SSR-safe + 自动 JSON 序列化
156
+ - `useCookie(key, default)` 返回 ref:`ref.value = undefined` 自动删除 cookie
157
+ - `storage.set()` / `cookie.set()` 自动 JSON.stringify,`get()` 自动 parse
222
158
 
223
- const schema = toTypedSchema(z.object({ phone: phoneSchema, email: emailSchema }))
224
- const { defineField, errors } = useForm({ validationSchema: schema })
225
- ```
159
+ ### file
226
160
 
227
- > `minLengthSchema(n)` / `maxLengthSchema(n)` 是工厂函数,调用后返回 schema。
161
+ - `downloadFile(url, options?)` 默认 GET,自动从 Content-Disposition 解析文件名
162
+ - `requester` 选项接受任何符合 `FileRequester` 契约的对象(axios 实例天然满足)
228
163
 
229
- ### masking — PII 脱敏
164
+ ---
230
165
 
231
- ```ts
232
- import {
233
- maskPhone, maskEmail, maskIdCard, maskBankCard,
234
- maskName, maskAddress, maskIp, maskLicensePlate, maskText,
235
- } from '@dwydev/ekit'
236
- ```
166
+ ## 模块用途索引
167
+
168
+ 按模块导航。**只提供模块名 + 用途 + 关键导出**,精确签名一律按 [查 API 标准动作](#查-api-标准动作核心每次写-ekit-代码前都做) 读 `.d.ts`。
237
169
 
238
- 纯函数,无外部依赖。**输入不符合格式时原样返回,不抛异常**。
170
+ ### request — HTTP 客户端 + dwyeapi 契约
239
171
 
240
- | 函数 | 示例输出 |
172
+ | 类别 | 关键导出 |
241
173
  |------|---------|
242
- | | |
243
- | | |
244
- | | |
245
- | | |
246
- | / | / |
247
- | | |
248
- | | |
249
- | | |
250
- | | |
174
+ | 工厂 | `createRequest` |
175
+ | 内置插件 | `tokenPlugin / headerPlugin / unwrapPlugin / refreshTokenPlugin` |
176
+ | 业务常量/守卫 | `SUCCESS_CODE / isApiBusinessError / extractValidationErrors` |
177
+ | 核心类型 | `HttpClient / HttpConfig / HttpResponse<T> / HttpError / HttpPlugin / HttpMethod / HttpResponseType / CreateRequestOptions` |
178
+ | dwyeapi 契约类型 | `ApiResponse<T> / PageData<T> / ValidationFieldError / ValidationErrorData / CommonBusinessCode / BusinessCode / ApiBusinessError` |
251
179
 
252
- ### copy — 剪贴板
180
+ `CommonBusinessCode` 各业务码的 HTTP 状态 + 语义 + 典型处理已写进 `request/api.d.ts` 的 JSDoc,需要时读 .d.ts。
253
181
 
254
- ```ts
255
- import { copyText, useClipboard } from '@dwydev/ekit'
256
- ```
182
+ ### storage — localStorage 封装
257
183
 
258
- ```ts
259
- await copyText('hello')
184
+ `useStorage` (VueUse 再导出) + `storage` 静态对象(`get / set / remove / clear`)。
260
185
 
261
- const { text, copy, copied, isSupported } = useClipboard({ source: '', copiedDuring: 1500 })
262
- copy('hello')
263
- // copied: ComputedRef<boolean>,1500ms 后自动复位
264
- // isSupported: ComputedRef<boolean>
265
- ```
186
+ ### cookie — Cookie 读写
266
187
 
267
- ### file — 文件下载 / Blob / 文件大小
188
+ `useCookie` + `cookie` 静态对象(`get / set / remove`)+ `CookieOptions` 类型。
268
189
 
269
- ```ts
270
- import { downloadFile, saveBlob, formatFileSize } from '@dwydev/ekit'
271
- import type { DownloadOptions, FileRequester } from '@dwydev/ekit'
272
- ```
190
+ ### date — 日期/时间/时区
273
191
 
274
- ```ts
275
- await downloadFile('/api/export.xlsx') // GET,自动解析文件名
276
- await downloadFile('/api/export', {
277
- filename: '报表.xlsx', method: 'POST', data: { dateRange },
278
- })
192
+ `now / formatTimestamp / formatInTimezone / formatRelativeTime / formatDate / formatDateTime / formatTime / formatBy`。
279
193
 
280
- // 自定义 requester(axios 实例天然满足 FileRequester)
281
- await downloadFile('/api/export', { requester: myHttpClient })
194
+ ### validators — 表单校验
282
195
 
283
- saveBlob(blob, 'data.json')
284
- formatFileSize(1234567) // '1.18 MB'
285
- formatFileSize(1234567, 0) // '1 MB'
286
- ```
196
+ - 布尔:`isPhone / isEmail / isIdCard / isUrl / isRequired / minLength / maxLength`
197
+ - zod schemas:`phoneSchema / emailSchema / idCardSchema / urlSchema / requiredSchema / minLengthSchema / maxLengthSchema`
287
198
 
288
- ### hooks — Vue Composables
199
+ ### copy — 剪贴板
289
200
 
290
- ```ts
291
- import {
292
- useDebounce, useClickOutside, useEventListener, useThrottle,
293
- useWindowSize, useMediaQuery, useIntersectionObserver, useResizeObserver,
294
- useFormPersist, DEFAULT_SENSITIVE_FIELDS,
295
- } from '@dwydev/ekit'
296
- ```
201
+ `copyText` 快捷函数 + `useClipboard` composable(VueUse 再导出)。
297
202
 
298
- | Composable | 说明 |
299
- |-----------|------|
300
- | `useDebounce(value, delay=300)` | 防抖 ref;返回 `Readonly<Ref<T>>` |
301
- | `useClickOutside(target, handler)` | 点击元素外部触发 |
302
- | `useEventListener(target, event, handler, options?)` | 自动绑/解绑 |
303
- | `useThrottle(fn, ms)` | 节流函数 |
304
- | `useWindowSize()` | `{ width, height }` |
305
- | `useMediaQuery(query)` | `useMediaQuery('(max-width: 768px)')` |
306
- | `useIntersectionObserver(target, cb)` | 监听元素出现/消失 |
307
- | `useResizeObserver(target, cb)` | 监听元素尺寸变化 |
308
- | `useFormPersist(key, initialValue, options?)` | 表单刷新自动回填;默认排除敏感字段 |
203
+ ### qs — 查询字符串
309
204
 
310
- **useFormPersist**
205
+ `stringify / parse` + `StringifyOptions / ParseOptions`。
311
206
 
312
- ```ts
313
- const { form, reset, clear } = useFormPersist('register-form', {
314
- email: '', code: '', remark: '',
315
- })
316
-
317
- // 选项
318
- interface UseFormPersistOptions<T> {
319
- exclude?: (keyof T)[] // 追加敏感字段
320
- storage?: 'session' | 'local' // 默认 'session'
321
- disableDefaultExclude?: boolean // 默认 false(开启内置排除)
322
- }
207
+ ### file — 文件下载
323
208
 
324
- // 提交成功后清空残留
325
- await api.register(form.value)
326
- reset()
327
- ```
209
+ `downloadFile / saveBlob / formatFileSize` + `DownloadOptions / FileRequester`。
210
+
211
+ ### hooks — Vue Composables
328
212
 
329
- 内置 `DEFAULT_SENSITIVE_FIELDS` 包含 password / confirmPassword / oldPassword / newPassword / pwd / code / captcha / verifyCode / verificationCode / smsCode / emailCode / phoneCode / otp / token / accessToken / refreshToken / signSecret / secret / apiKey 共 19 项,不区分大小写。
213
+ - ekit 自有:`useFormPersist / DEFAULT_SENSITIVE_FIELDS / UseFormPersistOptions / UseFormPersistReturn / useDebounce / useClickOutside / useEventListener`
214
+ - VueUse 再导出:`useThrottle / useWindowSize / useMediaQuery / useIntersectionObserver / useResizeObserver`
330
215
 
331
- ## 何时读取 references
216
+ ### masking — PII 脱敏
332
217
 
333
- 本 SKILL.md 正文已包含 10 个模块的核心 API、签名和典型用法,满足 90% 编码场景。`references/` 目录保存边缘细节,仅在需要时读取:
218
+ `maskPhone / maskEmail / maskIdCard / maskBankCard / maskName / maskAddress / maskIp / maskLicensePlate / maskText`。
334
219
 
335
- | 文件 | 内容 |
336
- |------|------|
337
- | `references/request.md` | `HttpPlugin` 完整接口、`HttpError` 字段详情、refreshTokenPlugin 完整参数、`CommonBusinessCode` 枚举、非标准响应兜底行为 |
338
- | `references/data.md` | `StringifyOptions` / `ParseOptions` 完整字段、`CookieOptions` 完整字段、useStorage 全部选项 |
339
- | `references/form.md` | validators 底层正则规则、masking 边界处理说明 |
340
- | `references/ui.md` | `FileRequester` 完整契约、`DownloadOptions` 全部字段、旧版迁移陷阱 |
341
-
342
- ## v0.6.0 迁移要点
343
-
344
- | 旧 | 新 |
345
- |----|----|
346
- | `createRequest(): AxiosInstance` | `createRequest(): HttpClient` |
347
- | `RequestPlugin` | `HttpPlugin` |
348
- | `import { dayjs } from '@dwydev/ekit'` | 用 `now / formatTimestamp / formatInTimezone / formatDateTime` 等函数 |
349
- | `CookieAttributes` | `CookieOptions` |
350
- | `IStringifyOptions / IParseOptions` | `StringifyOptions / ParseOptions` |
351
- | `DownloadOptions.requestInstance: AxiosInstance` | `DownloadOptions.requester: FileRequester` |
352
- | 自写 `useDebounce` 返回可写 `Ref` | VueUse `refDebounced` 返回 `Readonly<Ref>` |
353
- | 自写 `useClipboard` 返回 `{ text, copy, copied, isSupported }`(isSupported 是 boolean) | VueUse 版(isSupported 是 `ComputedRef<boolean>`) |
354
- | `refreshTokenPlugin` 自动重放 | 必须传 `retry: (cfg) => client.request(cfg)` |
220
+ 每个函数的脱敏规则 + `@example` 已写进 `masking/index.d.ts` 的 JSDoc(如 `maskPhone('13812345678') → '138****5678'`),需要时读 .d.ts。