create-dwy 0.8.0 → 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.
- package/package.json +1 -1
- package/src/create.js +50 -3
- package/src/utils.js +4 -0
- package/templates/claude-global/CLAUDE.md +81 -6
- package/templates/claude-global/rules/dwy-postgres.md +89 -0
- package/templates/claude-global/rules/dwy-python-backend.md +492 -0
- package/templates/claude-global/rules/dwy-python-core.md +94 -634
- package/templates/claude-global/rules/dwy-python-testing.md +115 -13
- package/templates/claude-global/rules/dwy-redis.md +88 -0
- package/templates/claude-global/rules/dwy-vue-core.md +341 -645
- package/templates/claude-global/rules/dwy-vue-pinia.md +47 -34
- package/templates/claude-global/rules/dwy-vue-testing.md +120 -55
- package/templates/claude-global/skills/dwy-deploy-audit/references/checks-network.md +1 -1
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_nginx.sh +1 -1
- package/templates/claude-global/skills/dwy-dolphindb/SKILL.md +22 -15
- package/templates/claude-global/skills/dwy-dolphindb/evals/evals.json +23 -0
- package/templates/claude-global/skills/dwy-dolphindb/references/dolphindb-rules.md +100 -1
- package/templates/claude-global/skills/dwy-ekit/SKILL.md +142 -276
- package/templates/claude-global/skills/dwy-eui/SKILL.md +264 -182
- package/templates/claude-global/skills/dwy-fullstack-scaffold/SKILL.md +117 -129
- package/templates/claude-global/skills/dwy-git-commit/SKILL.md +125 -0
- package/templates/claude-global/skills/dwy-publish/SKILL.md +1 -1
- package/templates/claude-global/{rules/dwy-tech-stack.md → skills/dwy-tech-stack/SKILL.md} +4 -3
- package/templates/project/fullstack-monorepo/CLAUDE.md.hbs +81 -0
- package/templates/project/fullstack-monorepo/backend/.python-version +1 -0
- package/templates/project/fullstack-monorepo/backend/CLAUDE.md.hbs +68 -0
- package/templates/project/fullstack-monorepo/backend/Dockerfile.dev +23 -0
- package/templates/project/fullstack-monorepo/backend/alembic/env.py.hbs +68 -0
- package/templates/project/fullstack-monorepo/backend/alembic/script.py.mako +26 -0
- package/templates/project/fullstack-monorepo/backend/alembic.ini.hbs +43 -0
- package/templates/project/fullstack-monorepo/backend/packages/dwy-shared/pyproject.toml +16 -0
- package/templates/project/fullstack-monorepo/backend/packages/dwy-shared/src/dwy_shared/__init__.py +12 -0
- package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/pyproject.toml.hbs +20 -0
- package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/__init__.py.hbs +40 -0
- package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/config.py +9 -0
- package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/models.py.hbs +17 -0
- package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/protocols.py +14 -0
- package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/router.py.hbs +22 -0
- package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/schemas.py +21 -0
- package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/service.py +11 -0
- package/templates/project/fullstack-monorepo/backend/pyproject.toml.hbs +84 -0
- package/templates/project/fullstack-monorepo/backend/src/app/__init__.py +3 -0
- package/templates/project/fullstack-monorepo/backend/src/app/config.py.hbs +21 -0
- package/templates/project/fullstack-monorepo/backend/src/app/exceptions.py +26 -0
- package/templates/project/fullstack-monorepo/backend/src/app/factories.py +16 -0
- package/templates/project/fullstack-monorepo/backend/src/app/infra/__init__.py +1 -0
- package/templates/project/fullstack-monorepo/backend/src/app/main.py.hbs +65 -0
- package/templates/project/fullstack-monorepo/backend/src/app/middleware/__init__.py +1 -0
- package/templates/project/fullstack-monorepo/backend/tests/__init__.py +0 -0
- package/templates/project/fullstack-monorepo/backend/tests/conftest.py.hbs +42 -0
- package/templates/project/fullstack-monorepo/dev.sh.hbs +83 -0
- package/templates/project/fullstack-monorepo/docker-compose.dev.yml.hbs +36 -0
- package/templates/project/fullstack-monorepo/frontend/CLAUDE.md.hbs +35 -0
- package/templates/project/fullstack-monorepo/frontend/Dockerfile +18 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/index.html.hbs +13 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/package.json.hbs +31 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/src/App.vue.hbs +10 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/src/api/client.ts.hbs +11 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/src/features/{{initialDomain}}/.gitkeep +0 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/src/index.css +12 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/src/main.ts +17 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/src/router/index.ts.hbs +24 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/src/stores/.gitkeep +0 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/src/types/.gitkeep +0 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/src/views/HomeView.vue.hbs +36 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/tsconfig.json +7 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/vite.config.ts.hbs +27 -0
- package/templates/project/fullstack-monorepo/frontend/nginx.conf +11 -0
- package/templates/project/fullstack-monorepo/frontend/package.json.hbs +13 -0
- package/templates/project/fullstack-monorepo/frontend/packages/core/package.json.hbs +15 -0
- package/templates/project/fullstack-monorepo/frontend/packages/core/src/index.ts +3 -0
- package/templates/project/fullstack-monorepo/frontend/packages/features/{{initialDomain}}/package.json.hbs +14 -0
- package/templates/project/fullstack-monorepo/frontend/packages/features/{{initialDomain}}/src/index.ts.hbs +16 -0
- package/templates/project/fullstack-monorepo/frontend/packages/features/{{initialDomain}}/src/pages/IndexPage.vue.hbs +9 -0
- package/templates/project/fullstack-monorepo/frontend/packages/ui/package.json.hbs +15 -0
- package/templates/project/fullstack-monorepo/frontend/packages/ui/src/index.ts +3 -0
- package/templates/project/fullstack-monorepo/frontend/pnpm-workspace.yaml.hbs +23 -0
- package/templates/project/fullstack-monorepo/frontend/tsconfig.base.json.hbs +23 -0
- package/templates/claude-global/rules/dwy-api-design.md +0 -110
- package/templates/claude-global/rules/dwy-git-commit-convention.md +0 -82
- package/templates/claude-global/rules/dwy-git-security.md +0 -53
- package/templates/claude-global/rules/dwy-payload-limits.md +0 -186
- package/templates/claude-global/rules/dwy-prefer-open-source.md +0 -32
- package/templates/claude-global/rules/dwy-project-structure.md +0 -181
- package/templates/claude-global/rules/dwy-python-fastapi.md +0 -272
- package/templates/claude-global/rules/dwy-python-orm.md +0 -61
- package/templates/claude-global/rules/dwy-python-security.md +0 -636
- package/templates/claude-global/rules/dwy-server-security.md +0 -518
- package/templates/claude-global/rules/dwy-vue-api.md +0 -146
- package/templates/claude-global/skills/dwy-ekit/references/data.md +0 -135
- package/templates/claude-global/skills/dwy-ekit/references/form.md +0 -68
- package/templates/claude-global/skills/dwy-ekit/references/request.md +0 -183
- package/templates/claude-global/skills/dwy-ekit/references/ui.md +0 -109
- package/templates/claude-global/skills/dwy-vue-core/SKILL.md +0 -890
- /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: "
|
|
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
|
|
7
|
+
# @dwydev/ekit 工具库使用指南
|
|
7
8
|
|
|
8
|
-
Vue 3
|
|
9
|
+
Vue 3 项目通用工具库。**薄封装层**:底层用 axios / dayjs / zod / js-cookie / qs / file-saver / @vueuse/core,对外**只暴露 ekit 自有契约**,不泄露底层库类型。
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
10 个模块:`request` / `storage` / `cookie` / `date` / `validators` / `copy` / `qs` / `file` / `hooks` / `masking`。
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
本 skill 索引基于 ekit **0.8.0**。消费方版本可能不同 — 见 [版本兼容规则](#版本兼容规则)。
|
|
13
14
|
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
+
### 第一步:定位 ekit 真实版本
|
|
30
22
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
读消费方项目 `package.json`,找 `dependencies["@dwydev/ekit"]` 或 `peerDependencies["@dwydev/ekit"]`。这才是写代码的依据。
|
|
24
|
+
|
|
25
|
+
### 第二步:读 module-manifest.json(一次拿导出索引)
|
|
26
|
+
|
|
27
|
+
从 ekit 0.8.0 起,npm 包带一份导出清单:
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
69
|
+
b. 次选:<dwy-shared-root>/frontend/ekit/src/{module}/{file}.ts
|
|
70
|
+
—— 仅当在 dwy-shared monorepo 内或并列 clone 时可用
|
|
68
71
|
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
87
|
+
---
|
|
116
88
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
89
|
+
## 防腐层硬性约束(写代码前必读)
|
|
90
|
+
|
|
91
|
+
这是 ekit **设计核心**,与版本无关。即使读了 .d.ts,也要遵守以下原则:
|
|
120
92
|
|
|
121
|
-
|
|
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
|
-
|
|
131
|
-
|
|
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
|
-
###
|
|
105
|
+
### 业务代码禁止直接调底层 API
|
|
135
106
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
151
|
-
cookie.set('user', { id: 1 }, { expires: 7, secure: true })
|
|
152
|
-
cookie.remove('user', { path: '/' })
|
|
119
|
+
---
|
|
153
120
|
|
|
154
|
-
|
|
155
|
-
lang.value = 'en-US' // 自动写入
|
|
156
|
-
lang.value = undefined // 自动删除
|
|
157
|
-
```
|
|
121
|
+
## 重要约束与已知陷阱
|
|
158
122
|
|
|
159
|
-
|
|
123
|
+
源码读不出来的"踩坑经验",每次写 ekit 代码都遵守:
|
|
160
124
|
|
|
161
|
-
|
|
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
|
-
|
|
171
|
-
|
|
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
|
-
###
|
|
132
|
+
### date
|
|
180
133
|
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
```
|
|
139
|
+
- 双层 API:布尔函数(`isPhone / isEmail / ...`)用于简单校验;**zod schemas**(`phoneSchema / emailSchema / ...`)用于 vee-validate 集成(配合 `toTypedSchema()`)
|
|
140
|
+
- `minLengthSchema(n)` / `maxLengthSchema(n)` 是**工厂函数**,调用后才返回 schema
|
|
194
141
|
|
|
195
|
-
###
|
|
142
|
+
### hooks
|
|
196
143
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
153
|
+
### storage / cookie
|
|
217
154
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
224
|
-
const { defineField, errors } = useForm({ validationSchema: schema })
|
|
225
|
-
```
|
|
159
|
+
### file
|
|
226
160
|
|
|
227
|
-
|
|
161
|
+
- `downloadFile(url, options?)` 默认 GET,自动从 Content-Disposition 解析文件名
|
|
162
|
+
- `requester` 选项接受任何符合 `FileRequester` 契约的对象(axios 实例天然满足)
|
|
228
163
|
|
|
229
|
-
|
|
164
|
+
---
|
|
230
165
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
180
|
+
`CommonBusinessCode` 各业务码的 HTTP 状态 + 语义 + 典型处理已写进 `request/api.d.ts` 的 JSDoc,需要时读 .d.ts。
|
|
253
181
|
|
|
254
|
-
|
|
255
|
-
import { copyText, useClipboard } from '@dwydev/ekit'
|
|
256
|
-
```
|
|
182
|
+
### storage — localStorage 封装
|
|
257
183
|
|
|
258
|
-
|
|
259
|
-
await copyText('hello')
|
|
184
|
+
`useStorage` (VueUse 再导出) + `storage` 静态对象(`get / set / remove / clear`)。
|
|
260
185
|
|
|
261
|
-
|
|
262
|
-
copy('hello')
|
|
263
|
-
// copied: ComputedRef<boolean>,1500ms 后自动复位
|
|
264
|
-
// isSupported: ComputedRef<boolean>
|
|
265
|
-
```
|
|
186
|
+
### cookie — Cookie 读写
|
|
266
187
|
|
|
267
|
-
|
|
188
|
+
`useCookie` + `cookie` 静态对象(`get / set / remove`)+ `CookieOptions` 类型。
|
|
268
189
|
|
|
269
|
-
|
|
270
|
-
import { downloadFile, saveBlob, formatFileSize } from '@dwydev/ekit'
|
|
271
|
-
import type { DownloadOptions, FileRequester } from '@dwydev/ekit'
|
|
272
|
-
```
|
|
190
|
+
### date — 日期/时间/时区
|
|
273
191
|
|
|
274
|
-
|
|
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
|
-
|
|
281
|
-
await downloadFile('/api/export', { requester: myHttpClient })
|
|
194
|
+
### validators — 表单校验
|
|
282
195
|
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
###
|
|
199
|
+
### copy — 剪贴板
|
|
289
200
|
|
|
290
|
-
|
|
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
|
-
|
|
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
|
-
|
|
205
|
+
`stringify / parse` + `StringifyOptions / ParseOptions`。
|
|
311
206
|
|
|
312
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
```
|
|
209
|
+
`downloadFile / saveBlob / formatFileSize` + `DownloadOptions / FileRequester`。
|
|
210
|
+
|
|
211
|
+
### hooks — Vue Composables
|
|
328
212
|
|
|
329
|
-
|
|
213
|
+
- ekit 自有:`useFormPersist / DEFAULT_SENSITIVE_FIELDS / UseFormPersistOptions / UseFormPersistReturn / useDebounce / useClickOutside / useEventListener`
|
|
214
|
+
- VueUse 再导出:`useThrottle / useWindowSize / useMediaQuery / useIntersectionObserver / useResizeObserver`
|
|
330
215
|
|
|
331
|
-
|
|
216
|
+
### masking — PII 脱敏
|
|
332
217
|
|
|
333
|
-
|
|
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。
|