create-dwy 0.6.3 → 0.6.4
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/bin/index.js
CHANGED
package/package.json
CHANGED
package/src/sync.js
CHANGED
|
@@ -260,11 +260,11 @@ export async function syncClaude() {
|
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
/**
|
|
263
|
-
* 单独同步 CLAUDE.md
|
|
263
|
+
* 单独同步 CLAUDE.md 到全局 ~/.claude/
|
|
264
264
|
*/
|
|
265
265
|
export async function syncProjectClaudeMd() {
|
|
266
266
|
const sourceDir = await resolveSourceDir()
|
|
267
|
-
const targetDir = path.join(
|
|
267
|
+
const targetDir = path.join(os.homedir(), '.claude')
|
|
268
268
|
|
|
269
269
|
if (!await fs.pathExists(sourceDir)) {
|
|
270
270
|
console.error(chalk.red('Error: claude-global templates not found in repo'))
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
- **服务地址**:必须输出完整的 URL(如 `http://localhost:8000/api/users`),禁止省略协议、端口或路径
|
|
7
7
|
- **数据库连接串**:必须输出完整连接字符串(如 `postgresql+asyncpg://user:pass@localhost:5432/mydb`),禁止只写主机名或库名
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## 团队执行任务
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
任务执行优先使用 agent team 模式执行。
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## 减少常见 LLM 编码错误的行为指南 (Behavioral Guidelines)
|
|
14
14
|
|
|
15
15
|
与项目特定指令合并使用。
|
|
16
16
|
|
|
@@ -111,3 +111,244 @@ try {
|
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
`extractValidationErrors` 剥离 `body. / query. / path.` 前缀,返回 `{ field: message }` 扁平对象,与 vee-validate 的 `form.setErrors` 直接兼容。
|
|
114
|
+
|
|
115
|
+
### storage — localStorage
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { useStorage, storage } from '@dwydev/ekit'
|
|
119
|
+
```
|
|
120
|
+
|
|
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()` | 清空全部(慎用) |
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
const token = useStorage<string>('token', '')
|
|
131
|
+
const config = useStorage('config', { theme: 'light' }, localStorage, { mergeDefaults: true })
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### cookie — Cookie 读写
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { useCookie, cookie } from '@dwydev/ekit'
|
|
138
|
+
import type { CookieOptions } from '@dwydev/ekit'
|
|
139
|
+
```
|
|
140
|
+
|
|
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
|
+
}
|
|
149
|
+
|
|
150
|
+
cookie.get<User>('user')
|
|
151
|
+
cookie.set('user', { id: 1 }, { expires: 7, secure: true })
|
|
152
|
+
cookie.remove('user', { path: '/' })
|
|
153
|
+
|
|
154
|
+
const lang = useCookie('lang', 'zh-CN')
|
|
155
|
+
lang.value = 'en-US' // 自动写入
|
|
156
|
+
lang.value = undefined // 自动删除
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### date — 日期 / 时间 / 时区
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import {
|
|
163
|
+
now, formatTimestamp, formatInTimezone,
|
|
164
|
+
formatDate, formatDateTime, formatTime, formatRelativeTime, formatBy,
|
|
165
|
+
} from '@dwydev/ekit'
|
|
166
|
+
```
|
|
167
|
+
|
|
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日'` |
|
|
178
|
+
|
|
179
|
+
### qs — 查询字符串
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import { stringify, parse } from '@dwydev/ekit'
|
|
183
|
+
import type { StringifyOptions, ParseOptions } from '@dwydev/ekit'
|
|
184
|
+
```
|
|
185
|
+
|
|
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'
|
|
190
|
+
|
|
191
|
+
parse('?a=1&b=2') // { a: '1', b: '2' }(默认 ignoreQueryPrefix: true)
|
|
192
|
+
parse('a.b=1', { allowDots: true }) // { a: { b: '1' } }
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### validators — 表单校验
|
|
196
|
+
|
|
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
|
+
```
|
|
204
|
+
|
|
205
|
+
**布尔函数**
|
|
206
|
+
|
|
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)` | 长度校验 |
|
|
215
|
+
|
|
216
|
+
**zod schema(配合 vee-validate)**
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { useForm } from 'vee-validate'
|
|
220
|
+
import { toTypedSchema } from '@vee-validate/zod'
|
|
221
|
+
import { phoneSchema, emailSchema } from '@dwydev/ekit'
|
|
222
|
+
|
|
223
|
+
const schema = toTypedSchema(z.object({ phone: phoneSchema, email: emailSchema }))
|
|
224
|
+
const { defineField, errors } = useForm({ validationSchema: schema })
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
> `minLengthSchema(n)` / `maxLengthSchema(n)` 是工厂函数,调用后返回 schema。
|
|
228
|
+
|
|
229
|
+
### masking — PII 脱敏
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
import {
|
|
233
|
+
maskPhone, maskEmail, maskIdCard, maskBankCard,
|
|
234
|
+
maskName, maskAddress, maskIp, maskLicensePlate, maskText,
|
|
235
|
+
} from '@dwydev/ekit'
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
纯函数,无外部依赖。**输入不符合格式时原样返回,不抛异常**。
|
|
239
|
+
|
|
240
|
+
| 函数 | 示例输出 |
|
|
241
|
+
|------|---------|
|
|
242
|
+
| | |
|
|
243
|
+
| | |
|
|
244
|
+
| | |
|
|
245
|
+
| | |
|
|
246
|
+
| / | / |
|
|
247
|
+
| | |
|
|
248
|
+
| | |
|
|
249
|
+
| | |
|
|
250
|
+
| | |
|
|
251
|
+
|
|
252
|
+
### copy — 剪贴板
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
import { copyText, useClipboard } from '@dwydev/ekit'
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
await copyText('hello')
|
|
260
|
+
|
|
261
|
+
const { text, copy, copied, isSupported } = useClipboard({ source: '', copiedDuring: 1500 })
|
|
262
|
+
copy('hello')
|
|
263
|
+
// copied: ComputedRef<boolean>,1500ms 后自动复位
|
|
264
|
+
// isSupported: ComputedRef<boolean>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### file — 文件下载 / Blob / 文件大小
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
import { downloadFile, saveBlob, formatFileSize } from '@dwydev/ekit'
|
|
271
|
+
import type { DownloadOptions, FileRequester } from '@dwydev/ekit'
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
await downloadFile('/api/export.xlsx') // GET,自动解析文件名
|
|
276
|
+
await downloadFile('/api/export', {
|
|
277
|
+
filename: '报表.xlsx', method: 'POST', data: { dateRange },
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
// 自定义 requester(axios 实例天然满足 FileRequester)
|
|
281
|
+
await downloadFile('/api/export', { requester: myHttpClient })
|
|
282
|
+
|
|
283
|
+
saveBlob(blob, 'data.json')
|
|
284
|
+
formatFileSize(1234567) // '1.18 MB'
|
|
285
|
+
formatFileSize(1234567, 0) // '1 MB'
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### hooks — Vue Composables
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
import {
|
|
292
|
+
useDebounce, useClickOutside, useEventListener, useThrottle,
|
|
293
|
+
useWindowSize, useMediaQuery, useIntersectionObserver, useResizeObserver,
|
|
294
|
+
useFormPersist, DEFAULT_SENSITIVE_FIELDS,
|
|
295
|
+
} from '@dwydev/ekit'
|
|
296
|
+
```
|
|
297
|
+
|
|
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?)` | 表单刷新自动回填;默认排除敏感字段 |
|
|
309
|
+
|
|
310
|
+
**useFormPersist**
|
|
311
|
+
|
|
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
|
+
}
|
|
323
|
+
|
|
324
|
+
// 提交成功后清空残留
|
|
325
|
+
await api.register(form.value)
|
|
326
|
+
reset()
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
内置 `DEFAULT_SENSITIVE_FIELDS` 包含 password / confirmPassword / oldPassword / newPassword / pwd / code / captcha / verifyCode / verificationCode / smsCode / emailCode / phoneCode / otp / token / accessToken / refreshToken / signSecret / secret / apiKey 共 19 项,不区分大小写。
|
|
330
|
+
|
|
331
|
+
## 何时读取 references
|
|
332
|
+
|
|
333
|
+
本 SKILL.md 正文已包含 10 个模块的核心 API、签名和典型用法,满足 90% 编码场景。`references/` 目录保存边缘细节,仅在需要时读取:
|
|
334
|
+
|
|
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)` |
|