@ithinkdt/page 4.0.18 → 4.0.20

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/README.md CHANGED
@@ -1 +1,546 @@
1
1
  # @ithinkdt/page
2
+
3
+ ## 许可证
4
+
5
+ MIT
6
+
7
+ ## 安装
8
+
9
+ ```bash
10
+ npm i @ithinkdt/page
11
+ ```
12
+
13
+ > 需要您自行安装依赖 `vue@3` 与 `vue-router@4`。
14
+
15
+ ## 介绍
16
+
17
+ `@ithinkdt/page` 提供 iThinkDT 页面开发的核心组合式 API,包括:
18
+
19
+ - 数据源管理(远程分页/本地数据)
20
+ - 前端分页
21
+ - 表单管理(查询筛选、编辑表单、表单弹窗)
22
+ - 表格列定义与自定义列
23
+ - CRUD 操作(新增、编辑、查看、删除)
24
+ - 描述详情展示
25
+ - 模态框(对话框/抽屉)
26
+ - 表单校验规则
27
+
28
+ ## 初始化
29
+
30
+ ### 注册插件
31
+
32
+ ```ts
33
+ import { createApp } from 'vue'
34
+ import pagePlugin from '@ithinkdt/page'
35
+
36
+ const app = createApp(App)
37
+
38
+ app.use(pagePlugin, {
39
+ i18n,
40
+ getConfirmRenderer: () => renderConfirm,
41
+ getModalRenderer: () => renderModal,
42
+ getFormRenderer: () => renderForm,
43
+ getFormItemRenderer: {
44
+ text: () => renderTextInput,
45
+ },
46
+ getDescriptionRenderer: {
47
+ text: () => (value) => value,
48
+ },
49
+ getTableActionsRenderer: () => renderTableActions,
50
+ defaultPageSize: 20,
51
+ defaultFilterCached: true,
52
+ })
53
+ ```
54
+
55
+ ## 组合式 API
56
+
57
+ ### `useDs` 数据源
58
+
59
+ 用于管理远程分页数据或本地数据列表,支持增删改操作:
60
+
61
+ ```ts
62
+ import { useDs } from '@ithinkdt/page'
63
+ import { userApi } from '@/api/user'
64
+
65
+ const ds = useDs(
66
+ async ({ currentPage, pageSize, sortField, sortOrder }, filter) => {
67
+ const res = await userApi.page({ currentPage, pageSize, sortField, sortOrder, filter })
68
+ return res
69
+ },
70
+ { pagination: 'remote', defaultPageSize: 20 },
71
+ )
72
+
73
+ // 刷新数据
74
+ ds.pull({ currentPage: 1, pageSize: 20 })
75
+
76
+ // 本地插入一条
77
+ ds.insert({ id: 'new', name: '张三' })
78
+
79
+ // 本地更新一条
80
+ ds.set('id-xxx', { name: '李四' })
81
+
82
+ // 本地删除一条
83
+ ds.remove('id-xxx')
84
+ ```
85
+
86
+ ### `useFilterHelper` 查询筛选表单
87
+
88
+ ```ts
89
+ import { useFilterHelper } from '@ithinkdt/page'
90
+
91
+ const { model, items, reset, validate } = useFilterHelper(
92
+ ({ it, model, validate }) => [
93
+ it('name', '用户名', 'text', { placeholder: '请输入用户名' }),
94
+ it('status', '状态', 'select', { selectProps: { options: statusOptions } }),
95
+ ],
96
+ { cached: true },
97
+ )
98
+ ```
99
+
100
+ ### `useFormHelper` 表单管理
101
+
102
+ ```ts
103
+ import { useFormHelper } from '@ithinkdt/page'
104
+
105
+ const { model, items, validate } = useFormHelper(
106
+ ({ it, model, validate, group, reset }) => [
107
+ it('name', '名称', 'text', { required: true }),
108
+ it('email', '邮箱', 'text', { rule: email('请输入有效邮箱') }),
109
+ group(computed(() => model.country === 'China'), [
110
+ it('phone', '手机号', 'text'),
111
+ ]),
112
+ ],
113
+ {
114
+ initial: { name: '' },
115
+ onChange(name, value) {
116
+ console.log(`${name} changed:`, value)
117
+ },
118
+ },
119
+ )
120
+
121
+ // 校验
122
+ const [valid] = await validate()
123
+ if (valid) {
124
+ await submit(model)
125
+ }
126
+ ```
127
+
128
+ ### `useFormModal` 表单弹窗
129
+
130
+ 将表单放入模态框中展示,常用于编辑场景:
131
+
132
+ ```ts
133
+ import { useFormModal } from '@ithinkdt/page'
134
+
135
+ const { open, close } = useFormModal({
136
+ title: '编辑用户',
137
+ type: 'dialog',
138
+ width: 600,
139
+ items: ({ it, model }) => [
140
+ it('name', '名称', 'text', { required: true }),
141
+ ],
142
+ onSubmit: async (data) => {
143
+ await userApi.save(data)
144
+ },
145
+ initial: { name: '' },
146
+ })
147
+
148
+ // 打开弹窗,传入初始数据
149
+ await open({ id: '1', name: '张三' })
150
+ ```
151
+
152
+ ### `useSimpleCrud` CRUD 操作
153
+
154
+ 封装新增、编辑、查看、删除的标准流程:
155
+
156
+ ```ts
157
+ import { useSimpleCrud } from '@ithinkdt/page'
158
+
159
+ const { onAdd, onEdit, onView, onDel } = useSimpleCrud({
160
+ get: id => userApi.get(id),
161
+ save: data => userApi.save(data),
162
+ delete: id => userApi.delete(id),
163
+ // 增删改查用统一的表单
164
+ items: ({ it }) => [
165
+ it('name', '名称', 'text', { required: true }),
166
+ it('status', '状态', 'select'),
167
+ it('name', '名称', 'text'),
168
+ it('email', '邮箱', 'text'),
169
+ ],
170
+ // 差别较大的时候,可以分开定义
171
+ createItems: ({ it }) => [
172
+ it('name', '名称', 'text', { required: true }),
173
+ ],
174
+ editItems: ({ it, model }) => [
175
+ it('name', '名称', 'text', { required: true }),
176
+ it('status', '状态', 'select'),
177
+ ],
178
+ viewItems: ({ it }) => [
179
+ it('name', '名称', 'text'),
180
+ it('email', '邮箱', 'text'),
181
+ ],
182
+ })
183
+
184
+ // 新增
185
+ await onAdd({ name: '张三' })
186
+
187
+ // 编辑
188
+ await onEdit('id-xxx')
189
+
190
+ // 查看
191
+ await onView('id-xxx')
192
+
193
+ // 删除(带确认弹窗)
194
+ await onDel('id-xxx')
195
+ ```
196
+
197
+ ### `useDeleteHelper` 删除确认
198
+
199
+ ```ts
200
+ import { useDeleteHelper } from '@ithinkdt/page'
201
+
202
+ const { onDel } = useDeleteHelper({
203
+ delete: ids => Array.isArray(ids) ? userApi.deleteBatch(ids) : userApi.delete(id),
204
+ })
205
+
206
+ // 单个删除
207
+ await onDel('id-xxx')
208
+
209
+ // 批量删除
210
+ await onDel(['id-1', 'id-2'])
211
+
212
+ // 自定义提示
213
+ await onDel('id-xxx', '确认删除', '该操作不可撤销')
214
+ ```
215
+
216
+ ### `useTableHelper` 表格列定义
217
+
218
+ ```ts
219
+ import { useTableHelper, calcActionWidth } from '@ithinkdt/page'
220
+
221
+ const { columns, custom } = useTableHelper(
222
+ ({ col, cols, group }) => [
223
+ col('index', '#', (_, __, i) => i + 1, { width: 60 }),
224
+ col('name', '用户名', { width: 120 }),
225
+ col('email', '邮箱', 'text', { ellipsis: true }),
226
+ col('status', '状态', { width: 100, render: v => v ? '启用' : '禁用' }),
227
+ cols('操作', [
228
+ col('$edit', '', (_, record) => h('Button', { onClick: () => onEdit(record) }, '编辑')),
229
+ col('$delete', '', (_, record) => h('Button', { onClick: () => onDel(record) }, '删除')),
230
+ ], { fixed: 'right' }),
231
+ ],
232
+ {
233
+ index: i => i + (params.currentPage - 1) * params.pageSize,
234
+ selectable: canSelect && (record) => record.status === 'selectable',
235
+ customizable: true,
236
+ actions: [
237
+ { preset: 'edit', onClick: (record) => onEdit(record) },
238
+ { preset: 'delete', onClick: (record) => onDel(record) },
239
+ ],
240
+ },
241
+ )
242
+ ```
243
+
244
+ ### `useDescriptionsHelper` 描述详情
245
+
246
+ ```ts
247
+ import { useDescriptionsHelper } from '@ithinkdt/page'
248
+
249
+ const { items, model, reset } = useDescriptionsHelper(
250
+ ({ it, group }) => [
251
+ it('name', '用户名', 'text'),
252
+ it('email', '邮箱'),
253
+ it('status', '状态', (value) => value ? '启用' : '禁用'),
254
+ group('contact', '联系方式', [
255
+ it('phone', '手机号', 'text'),
256
+ it('address', '地址', 'text'),
257
+ ]),
258
+ ],
259
+ )
260
+
261
+ // 设置数据
262
+ reset({ name: '张三', email: 'zhangsan@example.com', phone: '13800000000' })
263
+ ```
264
+
265
+ ### `useModal` 模态框
266
+
267
+ ```ts
268
+ import { useModal, useModalRef } from '@ithinkdt/page'
269
+
270
+ const xxx = ref('123')
271
+ const modal = useModal({
272
+ type: 'drawer',
273
+ title: '编辑',
274
+ width: 600,
275
+ content: () => <MyForm xxx={xxx.value} />,
276
+ onConfirm: async () => {
277
+ await submit()
278
+ },
279
+ })
280
+
281
+ // 打开
282
+ await modal.open()
283
+
284
+ // 关闭
285
+ modal.close()
286
+ ```
287
+
288
+ ### `useDataPagination` 前端分页
289
+
290
+ ```ts
291
+ import { useDataPagination } from '@ithinkdt/page'
292
+ import { ref } from 'vue'
293
+
294
+ const allData = ref([...])
295
+ const { data, pagination, paginate } = useDataPagination(allData, { pageSize: 10 })
296
+
297
+ // data 为当前页数据,pagination 为分页状态
298
+ ```
299
+
300
+ ## 校验规则
301
+
302
+ `@ithinkdt/page/rules` 提供常用的表单校验规则:
303
+
304
+ ```ts
305
+ import { required, min, max, email, phone, url, idNo, chinese, noChinese } from '@ithinkdt/page/rules'
306
+
307
+ const rules = {
308
+ name: required('请输入名称'),
309
+ age: min(0, val => '年龄不能小于0'),
310
+ email: email('请输入有效邮箱'),
311
+ phone: phone('请输入有效手机号'),
312
+ }
313
+ ```
314
+
315
+ ## API 参考
316
+
317
+ ### `useDs(fetch, options?)`
318
+
319
+ | 参数 | 说明 |
320
+ |------|------|
321
+ | `fetch` | `(sortParams, filter) => Promise<{ total, records } \| T[]>`,数据获取函数 |
322
+ | `options.pagination` | `'remote'` 远程分页,`false` 本地数据 |
323
+ | `options.defaultPageSize` | 默认分页大小,默认 `10` |
324
+ | `options.keyField` | 主键字段名,默认 `'key'` |
325
+ | `options.defaultSortField` | 默认排序字段 |
326
+ | `options.defaultSortOrder` | 默认排序方向,`'asc'` / `'desc'` |
327
+ | `options.shallow` | 是否使用 `shallowReactive`,默认 `true` |
328
+ | `options.immediate` | 是否立即执行 fetch,默认 `false` |
329
+ | `options.resetOnFetch` | fetch 时是否先重置为空,默认 `false` |
330
+
331
+ | 属性/方法 | 说明 |
332
+ |-----------|------|
333
+ | `state` | 响应式数据(远程分页模式下含 `total` 与 `records`) |
334
+ | `loading` | 加载状态 |
335
+ | `error` | 错误信息 |
336
+ | `execute(...params)` | 执行 fetch 刷新数据 |
337
+ | `get(key)` | 根据主键获取数据项 |
338
+ | `insert(item, index?)` | 插入数据项 |
339
+ | `set(key, item)` | 更新数据项 |
340
+ | `remove(key)` | 删除数据项 |
341
+
342
+ ### `useFilterHelper(items, options?)`
343
+
344
+ | 参数 | 说明 |
345
+ |------|------|
346
+ | `items` | `(helper) => FormItemOptions[]`,表单项定义函数 |
347
+ | `options.initial` | 表单初始值 |
348
+ | `options.rules` | 校验规则对象或函数 |
349
+ | `options.onChange` | `(name, value) => void`,字段变更回调 |
350
+ | `options.cached` | 是否缓存筛选表单,默认取插件 `defaultFilterCached` |
351
+ | `options.cacheVersion` | 缓存版本号,默认 `1` |
352
+ | `options.customizable` | 是否允许自定义筛选项显隐 |
353
+
354
+ | 属性/方法 | 说明 |
355
+ |-----------|------|
356
+ | `model` | 响应式表单数据 |
357
+ | `items` | 响应式表单项列表 |
358
+ | `reset(initial?)` | 重置表单 |
359
+ | `validate()` | 校验表单 |
360
+ | `invalid` | 是否校验不通过 |
361
+ | `validation` | 校验结果详情 |
362
+ | `custom(modify)` | 自定义筛选项显隐/排序 |
363
+
364
+ ### `useFormHelper(items, options?)`
365
+
366
+ | 参数 | 说明 |
367
+ |------|------|
368
+ | `items` | `(helper) => FormItemOptions[]`,表单项定义函数 |
369
+ | `options.initial` | 表单初始值 |
370
+ | `options.rules` | 校验规则对象或函数 |
371
+ | `options.onChange` | `(name, value) => void`,字段变更回调 |
372
+
373
+ | 属性/方法 | 说明 |
374
+ |-----------|------|
375
+ | `model` | 响应式表单数据 |
376
+ | `items` | 响应式表单项列表 |
377
+ | `reset(initial?)` | 重置表单(`overwrite=true` 覆盖,`reinit=true` 同时重建表单项) |
378
+ | `reinit()` | 重新初始化表单项 |
379
+ | `validate()` | 校验表单 |
380
+ | `invalid` | 是否校验不通过 |
381
+ | `validation` | 校验结果详情 |
382
+ | `beforeSubmit(prop)` | 提交前校验(支持单字段或数组) |
383
+
384
+ ### `useFormModal(options)`
385
+
386
+ | 参数 | 说明 |
387
+ |------|------|
388
+ | `options.title` | 弹窗标题 |
389
+ | `options.type` | `'dialog'` / `'drawer'`,弹窗类型 |
390
+ | `options.width` | 弹窗宽度 |
391
+ | `options.items` | `(helper) => FormItemOptions[]`,表单项定义函数 |
392
+ | `options.initial` | 表单初始值 |
393
+ | `options.rules` | 校验规则 |
394
+ | `options.onSubmit` | `(data) => Promise<void>`,提交回调 |
395
+ | `options.loading` | 加载状态 |
396
+ | `options.readonly` | 是否只读 |
397
+ | `options.maskClosable` | 点击遮罩是否关闭,默认 `false` |
398
+ | `options.closable` | 是否显示关闭按钮,默认 `true` |
399
+
400
+ | 属性/方法 | 说明 |
401
+ |-----------|------|
402
+ | `open(initial?, title?)` | 打开表单弹窗 |
403
+ | `close()` | 关闭弹窗 |
404
+
405
+ ### `useSimpleCrud(options)`
406
+
407
+ | 参数 | 说明 |
408
+ |------|------|
409
+ | `options.get` | `(id) => Promise<Entity>`,获取详情 |
410
+ | `options.post` / `save` | `(data) => Promise<Entity>`,新增 |
411
+ | `options.put` | `(data) => Promise<Entity>`,编辑 |
412
+ | `options.delete` | `(id) => Promise<void>`,删除 |
413
+ | `options.createItems` | 新建表单的表单项定义函数 |
414
+ | `options.editItems` | 编辑表单的表单项定义函数 |
415
+ | `options.viewItems` | 查看表单的表单项定义函数 |
416
+ | `options.items` / `formItems` | 新建和编辑共用的表单项定义函数 |
417
+ | `options.width` | 弹窗宽度,可按 type 返回不同宽度 |
418
+ | `options.cols` | 表单列数,可按 type 返回不同列数 |
419
+ | `options.modalType` | `'dialog'` / `'drawer'`,可按 type 返回不同类型 |
420
+ | `options.deleteButtonText` | 删除按钮文案 |
421
+
422
+ | 方法 | 说明 |
423
+ |------|------|
424
+ | `onAdd(initial?, title?)` | 新增 |
425
+ | `onEdit(dataOrKey, title?)` | 编辑 |
426
+ | `onView(dataOrKey, title?)` | 查看 |
427
+ | `onDel(keyOrKeys, title?, tip?)` | 删除 |
428
+
429
+ ### `useDeleteHelper(options)`
430
+
431
+ | 参数 | 说明 |
432
+ |------|------|
433
+ | `options.delete` | `(ids) => Promise<void>`,删除请求函数 |
434
+ | `options.keyField` | 主键字段名,默认使用插件注入的 `keyField` |
435
+ | `options.deleteButtonText` | 删除按钮文案 |
436
+ | `options.renderDelete` | 自定义删除确认内容渲染函数 |
437
+
438
+ | 方法 | 说明 |
439
+ |------|------|
440
+ | `onDel(keyOrKeys, title?, tip?)` | 删除(带确认弹窗) |
441
+
442
+ ### `useTableHelper(columns, options?)`
443
+
444
+ | 参数 | 说明 |
445
+ |------|------|
446
+ | `columns` | `(helper) => TableColumnOptions[]`,列定义函数 |
447
+ | `options.index` | 是否显示序号列或自定义序号渲染函数 `(i, record) => VNodeChild` |
448
+ | `options.indexTitle` | 序号列标题,默认 `'#'` |
449
+ | `options.selectable` | 是否可选择或自定义可选择判断函数 `(record) => boolean` |
450
+ | `options.selectType` | `'multiple'` / `'single'`,选择类型 |
451
+ | `options.actions` | 操作按钮列表,内置预设 `{ preset: 'edit' \| 'view' \| 'delete' }` |
452
+ | `options.actionTitle` | 操作列标题,默认 `'操作'` |
453
+ | `options.actionWidth` | 操作列宽度,字符串或根据文案计算宽度函数 |
454
+ | `options.actionHidden` | 是否隐藏操作列 |
455
+ | `options.customizable` | 是否允许自定义列显隐/固定/宽度/排序 |
456
+ | `options.expandable` | 是否可展开行或自定义判断函数 `(record) => boolean` |
457
+ | `options.renderExpand` | `(record, i) => VNodeChild`,展开行渲染函数 |
458
+
459
+ | 属性/方法 | 说明 |
460
+ |-----------|------|
461
+ | `columns` | 响应式列定义 |
462
+ | `custom(modify)` | 自定义列显隐/固定/宽度/排序;传 `true` 重置为默认 |
463
+ | `dataMode` | 数据模式(`all` / `selection`) |
464
+ | `reinit()` | 重新初始化列定义 |
465
+
466
+ ### `useDescriptionsHelper(items)`
467
+
468
+ | 参数 | 说明 |
469
+ |------|------|
470
+ | `items` | `(helper) => DescriptionItem[]`,描述项定义函数 |
471
+
472
+ | 属性/方法 | 说明 |
473
+ |-----------|------|
474
+ | `items` | 响应式描述项列表 |
475
+ | `model` | 响应式数据 |
476
+ | `reset(model?, reinit?)` | 重置数据(`reinit=true` 同时重建描述项) |
477
+ | `reinit()` | 重新初始化描述项 |
478
+
479
+ ### `useModal(options)`
480
+
481
+ | 参数 | 说明 |
482
+ |------|------|
483
+ | `options.type` | `'dialog'` / `'drawer'`,类型 |
484
+ | `options.title` | 标题 |
485
+ | `options.content` | 内容 |
486
+ | `options.width` | 宽度 |
487
+ | `options.height` | 高度 |
488
+ | `options.confirmText` | 确认按钮文案 |
489
+ | `options.cancelText` | 取消按钮文案 |
490
+ | `options.confirmLoading` | 确认按钮加载状态 |
491
+ | `options.cancelLoading` | 取消按钮加载状态 |
492
+ | `options.onConfirm` | `() => Promise<boolean \| void>`,确认回调,返回 `false` 阻止关闭 |
493
+ | `options.onCancel` | `() => Promise<boolean \| void>`,取消回调 |
494
+ | `options.onClose` | `() => Promise<boolean \| void>`,关闭回调 |
495
+ | `options.closable` | 是否显示关闭按钮,默认 `true` |
496
+ | `options.maskClosable` | 点击遮罩是否关闭,默认 `false` |
497
+ | `options.footer` | 自定义底部内容,`null` 隐藏底部 |
498
+
499
+ | 方法 | 说明 |
500
+ |------|------|
501
+ | `open(title?)` | 打开模态框 |
502
+ | `close()` | 关闭模态框 |
503
+
504
+ ### `useModalRef()`
505
+
506
+ | 属性/方法 | 说明 |
507
+ |-----------|------|
508
+ | `inModal` | 是否在模态框上下文内 |
509
+ | `visible` | 模态框可见状态 |
510
+ | `close()` | 关闭模态框 |
511
+ | `setTitle(title)` | 设置模态框标题 |
512
+
513
+ ### `useDataPagination(data, options?)`
514
+
515
+ | 参数 | 说明 |
516
+ |------|------|
517
+ | `data` | `MaybeRef<T[]>`,原始数据列表 |
518
+ | `options.pageSize` | 每页条数,默认取插件 `defaultPageSize` |
519
+ | `options.currentPage` | 初始页码,默认 `1` |
520
+ | `options.updateOnChange` | 数据变化时是否重置到第一页,默认 `true` |
521
+
522
+ | 属性/方法 | 说明 |
523
+ |-----------|------|
524
+ | `data` | 当前页数据 |
525
+ | `pagination` | 分页状态(`pageSize`, `currentPage`) |
526
+ | `paginate(params)` | 切换分页 |
527
+
528
+ ### 校验规则(`@ithinkdt/page/rules`)
529
+
530
+ | 方法 | 说明 |
531
+ |------|------|
532
+ | `required(message, options?)` | 必填校验,`options.required` 控制是否必填,`options.trigger` 触发时机 |
533
+ | `min(min, message, options?)` | 最小值/最小长度,`min` 为最小阈值 |
534
+ | `max(max, message, options?)` | 最大值/最大长度,`max` 为最大阈值 |
535
+ | `minmax(min, max, message, options?)` | 同时校验最小值/最大长度与最大值/最大长度 |
536
+ | `email(message, options?)` | 邮箱格式 |
537
+ | `phone(message, options?)` | 手机号格式(`1` 开头 11 位数字) |
538
+ | `url(message, options?)` | URL 格式 |
539
+ | `idNo(message, options?)` | 身份证格式 |
540
+ | `plateNo(message, options?)` | 车牌号格式 |
541
+ | `chinese(message, options?)` | 纯中文 |
542
+ | `noChinese(message, options?)` | 不含中文 |
543
+ | `pattern(pattern, message, options?)` | 自定义正则,`pattern` 为 `RegExp` 或正则字符串 |
544
+ | `isRequiredRule(rule)` | 判断是否为 `required` 规则 |
545
+
546
+ > `message` 参数为 `(value, params) => string`,`options.trigger` 默认 `'blur'`(`required` 默认 `['input', 'blur', 'change']`)。
package/auto-imports.js CHANGED
@@ -1,7 +1,11 @@
1
1
  export const Page = [
2
2
  {
3
3
  from: '@ithinkdt/page',
4
- imports: ['useDs', 'useDataPagination', 'useFilterHelper', 'useFormHelper', 'useFormModal', 'useDeleteHelper', 'useSimpleCrud', 'useModal', 'useTableHelper', 'calcActionWidth', 'useDescriptionsHelper'],
4
+ imports: [
5
+ 'useDs', 'useDataPagination', 'useFilterHelper', 'useFormHelper', 'useFormModal', 'useDeleteHelper',
6
+ 'useSimpleCrud', 'useModal', 'useTableHelper', 'calcActionWidth', 'useDescriptionsHelper',
7
+ 'useListPageCustomization',
8
+ ],
5
9
  },
6
10
  ]
7
11
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ithinkdt/page",
3
- "version": "4.0.18",
3
+ "version": "4.0.20",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "iThinkDT Page",
@@ -39,19 +39,19 @@
39
39
  },
40
40
  "sideEffects": false,
41
41
  "dependencies": {
42
- "@vueuse/core": "^14.2.1",
43
- "nanoid": "^5.1.7",
44
- "@ithinkdt/common": "^4.0.7"
42
+ "@vueuse/core": "^14.3.0",
43
+ "nanoid": "^5.1.11",
44
+ "@ithinkdt/common": "^4.0.9"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "vue": ">=3.5",
48
48
  "vue-router": ">=4.5"
49
49
  },
50
50
  "devDependencies": {
51
- "typescript": "~6.0.2",
52
- "vite": "^8.0.8",
53
- "vue": "^3.5.32",
54
- "vue-router": "^5.0.4"
51
+ "typescript": "~6.0.3",
52
+ "vite": "^8.0.14",
53
+ "vue": "^3.5.34",
54
+ "vue-router": "^5.0.7"
55
55
  },
56
56
  "scripts": {
57
57
  "release": "pnpm publish --no-git-checks"
package/src/crud.js CHANGED
@@ -4,7 +4,7 @@ import { useFormModal } from './form.js'
4
4
  import { IgnoreRejectionError, PAGE_INJECTION, pageInjection } from './plugin.js'
5
5
 
6
6
  export function useDeleteHelper(options) {
7
- const { i18n: useI18n, keyField, getConfirmRenderer } = (hasInjectionContext() ? inject(PAGE_INJECTION, pageInjection) : pageInjection) ?? {}
7
+ const { i18n: useI18n, keyField, getConfirmRenderer } = hasInjectionContext() ? inject(PAGE_INJECTION, pageInjection) : pageInjection
8
8
  const { t } = useI18n()
9
9
 
10
10
  let onDel
@@ -136,7 +136,7 @@ function _useSimpleCrudModal(crudType, getItems, request, options) {
136
136
  }
137
137
 
138
138
  export function useSimpleCrud(options) {
139
- const { i18n: useI18n } = (hasInjectionContext() ? inject(PAGE_INJECTION, pageInjection) : pageInjection) ?? {}
139
+ const { i18n: useI18n } = hasInjectionContext() ? inject(PAGE_INJECTION, pageInjection) : pageInjection
140
140
  const { t } = useI18n()
141
141
 
142
142
  const createModal = _useSimpleCrudModal('create', options.createItems ?? options.formItems ?? options.items, options.post ?? options.save, options)
@@ -1,5 +1,5 @@
1
- import { toReactive } from '@vueuse/core'
2
- import { computed, hasInjectionContext, inject, reactive, ref, shallowReactive, unref } from 'vue'
1
+ import { toReactive, tryOnScopeDispose } from '@vueuse/core'
2
+ import { computed, effectScope, hasInjectionContext, inject, reactive, ref, shallowReactive, unref } from 'vue'
3
3
 
4
4
  import { isNone } from '@ithinkdt/common/object'
5
5
  import { uncapitalize } from '@ithinkdt/common/string'
@@ -103,10 +103,20 @@ export function useDescriptionsHelper(
103
103
  }
104
104
 
105
105
  const items0 = shallowReactive([])
106
+
107
+ let scope
108
+ tryOnScopeDispose(() => {
109
+ scope?.stop()
110
+ })
111
+
106
112
  function reinit() {
113
+ scope?.stop()
114
+ scope = effectScope()
107
115
  items0.length = 0
108
- // eslint-disable-next-line unicorn/no-array-callback-reference
109
- items0.push(...items({ it, group, model }).filter(Boolean).map(reactive))
116
+ scope.run(() => {
117
+ // eslint-disable-next-line unicorn/no-array-callback-reference
118
+ items0.push(...items({ it, group, model }).filter(Boolean).map(reactive))
119
+ })
110
120
  }
111
121
  reinit()
112
122