@veltra/desktop 1.7.16 → 1.7.18
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/dist/components/batch-edit/batch-edit-form.js +6 -1
- package/dist/components/batch-edit/batch-edit-form.js.map +1 -1
- package/dist/components/batch-edit/batch-edit-list.js +37 -31
- package/dist/components/batch-edit/batch-edit-list.js.map +1 -1
- package/dist/components/batch-edit/batch-edit.js +2 -2
- package/dist/components/batch-edit/batch-edit.js.map +1 -1
- package/dist/components/batch-edit/di.js.map +1 -1
- package/dist/components/batch-edit/use-edit-state.js +14 -10
- package/dist/components/batch-edit/use-edit-state.js.map +1 -1
- package/dist/components/batch-edit/use-handlers.js +1 -0
- package/dist/components/batch-edit/use-handlers.js.map +1 -1
- package/dist/components/multi-tree-select/multi-tree-select.js +8 -2
- package/dist/components/multi-tree-select/multi-tree-select.js.map +1 -1
- package/dist/components/tree/tree-node.js +1 -1
- package/dist/components/tree/tree-node.js.map +1 -1
- package/dist/components/tree/tree.js +3 -6
- package/dist/components/tree/tree.js.map +1 -1
- package/dist/components/tree/use-check.js +32 -9
- package/dist/components/tree/use-check.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/types/batch-edit.d.ts +0 -3
- package/dist/types/index.d.ts +2 -2
- package/package.json +1 -1
- package/src/components/batch-edit/__test__/batch-edit.test.ts +164 -38
- package/src/components/batch-edit/batch-edit-form.vue +7 -1
- package/src/components/batch-edit/batch-edit-list.vue +38 -24
- package/src/components/batch-edit/batch-edit.vue +2 -1
- package/src/components/batch-edit/di.ts +2 -0
- package/src/components/batch-edit/use-edit-state.ts +25 -32
- package/src/components/batch-edit/use-handlers.ts +2 -0
- package/src/components/multi-tree-select/__test__/multi-tree-select.test.ts +222 -0
- package/src/components/multi-tree-select/multi-tree-select.vue +3 -1
- package/src/components/tree/tree-node.vue +1 -1
- package/src/components/tree/tree.vue +8 -6
- package/src/components/tree/use-check.ts +63 -33
- package/src/types/batch-edit.ts +0 -4
|
@@ -107,6 +107,10 @@ function queryRows(host: HTMLElement) {
|
|
|
107
107
|
return [...host.querySelectorAll<HTMLElement>('tr.u-table__row')]
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
function queryEditButton(host: HTMLElement, index: number) {
|
|
111
|
+
return queryRows(host)[index]?.querySelector<HTMLElement>('button[title="编辑"]')
|
|
112
|
+
}
|
|
113
|
+
|
|
110
114
|
async function clickRow(host: HTMLElement, index: number) {
|
|
111
115
|
const row = queryRows(host)[index]
|
|
112
116
|
expect(row, `第 ${index} 行应存在`).toBeTruthy()
|
|
@@ -115,19 +119,27 @@ async function clickRow(host: HTMLElement, index: number) {
|
|
|
115
119
|
await nextTick()
|
|
116
120
|
}
|
|
117
121
|
|
|
122
|
+
async function clickEditRow(host: HTMLElement, index: number) {
|
|
123
|
+
const btn = queryEditButton(host, index)
|
|
124
|
+
expect(btn, `第 ${index} 行的编辑按钮应存在`).toBeTruthy()
|
|
125
|
+
btn!.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
126
|
+
await nextTick()
|
|
127
|
+
await nextTick()
|
|
128
|
+
}
|
|
129
|
+
|
|
118
130
|
describe('UBatchEdit quick-edit 切换编辑行', () => {
|
|
119
131
|
it('切换行时先重置表单再回显,不污染行数据', async () => {
|
|
120
132
|
const { host, data, model, unmount } = mountBatchEdit()
|
|
121
133
|
|
|
122
|
-
// 第一次点击第 1
|
|
123
|
-
await
|
|
134
|
+
// 第一次点击第 1 行的编辑按钮:正常回显
|
|
135
|
+
await clickEditRow(host, 0)
|
|
124
136
|
expect(model.label).toBe('存草稿')
|
|
125
137
|
expect(model.behavior).toBe('common-resource')
|
|
126
138
|
expect(model.submitType).toBe('DRAFT')
|
|
127
139
|
expect(model.apiMethod).toBeUndefined()
|
|
128
140
|
|
|
129
|
-
// 再点击第 2
|
|
130
|
-
await
|
|
141
|
+
// 再点击第 2 行的编辑按钮:先重置回 model 初始快照,再回显第 2 行数据
|
|
142
|
+
await clickEditRow(host, 1)
|
|
131
143
|
expect(model.label).toBe('调接口')
|
|
132
144
|
expect(model.behavior).toBe('api')
|
|
133
145
|
expect(model.submitType).toBe('SUBMIT')
|
|
@@ -142,14 +154,32 @@ describe('UBatchEdit quick-edit 切换编辑行', () => {
|
|
|
142
154
|
unmount()
|
|
143
155
|
})
|
|
144
156
|
|
|
145
|
-
it('
|
|
157
|
+
it('点击行不触发编辑,仅编辑按钮打开表单', async () => {
|
|
158
|
+
const { host, model, unmount } = mountBatchEdit()
|
|
159
|
+
|
|
160
|
+
await clickRow(host, 0)
|
|
161
|
+
|
|
162
|
+
const aside = host.querySelector<HTMLElement>('.u-batch-edit__form')!
|
|
163
|
+
expect(aside.style.display).toBe('none')
|
|
164
|
+
expect(model.label).toBe('')
|
|
165
|
+
|
|
166
|
+
await clickEditRow(host, 0)
|
|
167
|
+
expect(aside.style.display).not.toBe('none')
|
|
168
|
+
expect(model.label).toBe('存草稿')
|
|
169
|
+
|
|
170
|
+
unmount()
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('关闭表单后表单数据重置为 model 初始值', async () => {
|
|
146
174
|
const { host, data, model, unmount } = mountBatchEdit()
|
|
147
175
|
|
|
148
|
-
await
|
|
176
|
+
await clickEditRow(host, 1)
|
|
149
177
|
expect(model.label).toBe('调接口')
|
|
150
178
|
|
|
151
|
-
//
|
|
152
|
-
|
|
179
|
+
// 点击「取消」关闭表单,model 恢复到初始快照
|
|
180
|
+
clickEl(queryCancelButton(host)!)
|
|
181
|
+
await nextTick()
|
|
182
|
+
await nextTick()
|
|
153
183
|
expect(model.label).toBe('')
|
|
154
184
|
expect(model.behavior).toBe('event')
|
|
155
185
|
expect(model.submitType).toBeUndefined()
|
|
@@ -169,16 +199,16 @@ describe('UBatchEdit quick-edit 切换编辑行', () => {
|
|
|
169
199
|
it('表单常驻挂载(v-show 控制显隐),切换行不重建表单', async () => {
|
|
170
200
|
const { host, unmount } = mountBatchEdit()
|
|
171
201
|
|
|
172
|
-
// model
|
|
202
|
+
// model 存在即挂载表单,未编辑行时仅隐藏(快照只在首次挂载时拍一次)
|
|
173
203
|
const aside = host.querySelector<HTMLElement>('.u-batch-edit__form')
|
|
174
204
|
expect(aside).toBeTruthy()
|
|
175
205
|
expect(aside!.style.display).toBe('none')
|
|
176
206
|
|
|
177
|
-
await
|
|
207
|
+
await clickEditRow(host, 0)
|
|
178
208
|
const formBody = host.querySelector('.u-batch-edit__form-body')
|
|
179
209
|
expect(aside!.style.display).not.toBe('none')
|
|
180
210
|
|
|
181
|
-
await
|
|
211
|
+
await clickEditRow(host, 1)
|
|
182
212
|
// 同一 DOM 节点:表单未因切行销毁重建,初始快照不会被重新生成
|
|
183
213
|
expect(host.querySelector('.u-batch-edit__form-body')).toBe(formBody)
|
|
184
214
|
|
|
@@ -188,7 +218,7 @@ describe('UBatchEdit quick-edit 切换编辑行', () => {
|
|
|
188
218
|
it('quick-edit 下用户输入仍实时写回行数据', async () => {
|
|
189
219
|
const { host, data, unmount } = mountBatchEdit()
|
|
190
220
|
|
|
191
|
-
await
|
|
221
|
+
await clickEditRow(host, 0)
|
|
192
222
|
|
|
193
223
|
const input = host.querySelector<HTMLInputElement>('.u-input input')
|
|
194
224
|
expect(input).toBeTruthy()
|
|
@@ -205,18 +235,71 @@ describe('UBatchEdit quick-edit 切换编辑行', () => {
|
|
|
205
235
|
})
|
|
206
236
|
})
|
|
207
237
|
|
|
238
|
+
describe('UBatchEdit 操作列', () => {
|
|
239
|
+
it('按钮顺序:编辑在最前,上/下插入在最后', async () => {
|
|
240
|
+
const { host, unmount } = mountBatchEdit({ props: { tree: true } })
|
|
241
|
+
await nextTick()
|
|
242
|
+
|
|
243
|
+
const titles = [...queryRows(host)[0]!.querySelectorAll<HTMLElement>('button[title]')].map(
|
|
244
|
+
(btn) => btn.title
|
|
245
|
+
)
|
|
246
|
+
expect(titles).toEqual(['编辑', '添加子级', '删除', '在上方插入', '在下方插入'])
|
|
247
|
+
|
|
248
|
+
unmount()
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('readonly 模式操作列只保留「查看」按钮,点击打开只读表单', async () => {
|
|
252
|
+
const { host, unmount } = mountBatchEdit({ props: { readonly: true } })
|
|
253
|
+
await nextTick()
|
|
254
|
+
|
|
255
|
+
const row = queryRows(host)[0]!
|
|
256
|
+
const viewBtn = row.querySelector<HTMLElement>('button[title="查看"]')
|
|
257
|
+
expect(viewBtn).toBeTruthy()
|
|
258
|
+
expect(row.querySelector('button[title="删除"]')).toBeNull()
|
|
259
|
+
expect(row.querySelector('button[title="在上方插入"]')).toBeNull()
|
|
260
|
+
expect(host.querySelector('.u-batch-edit__add-btn')).toBeNull()
|
|
261
|
+
|
|
262
|
+
viewBtn!.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
263
|
+
await nextTick()
|
|
264
|
+
await nextTick()
|
|
265
|
+
|
|
266
|
+
expect(host.querySelector('.u-batch-edit__form-title-text')!.textContent).toBe('查看详情')
|
|
267
|
+
|
|
268
|
+
unmount()
|
|
269
|
+
})
|
|
270
|
+
})
|
|
271
|
+
|
|
208
272
|
function queryDialog() {
|
|
209
273
|
return document.body.querySelector<HTMLElement>('.u-batch-edit__form-dialog')
|
|
210
274
|
}
|
|
211
275
|
|
|
212
|
-
/** 等弹框(teleport 到 body
|
|
213
|
-
async function
|
|
276
|
+
/** 等弹框(teleport 到 body)打开:进入过渡在 CI 高负载下可能超过固定时长,轮询等待 */
|
|
277
|
+
async function waitDialogOpen() {
|
|
278
|
+
await vi.waitFor(() => {
|
|
279
|
+
expect(queryDialog()).toBeTruthy()
|
|
280
|
+
})
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** 等弹框关闭卸载:离开过渡在 CI 高负载下可能超过固定时长,轮询等待 */
|
|
284
|
+
async function waitDialogClosed() {
|
|
285
|
+
await vi.waitFor(() => {
|
|
286
|
+
expect(queryDialog()).toBeNull()
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** 等一次校验 / 保存等异步处理落地(固定短等,避免两次点击的 handleSave 重叠) */
|
|
291
|
+
async function waitDialogSettled() {
|
|
214
292
|
await nextTick()
|
|
215
293
|
await nextTick()
|
|
216
294
|
await new Promise((resolve) => setTimeout(resolve, 20))
|
|
217
295
|
await nextTick()
|
|
218
296
|
}
|
|
219
297
|
|
|
298
|
+
/** 当前用例弹框的遮罩(body 上可能残留前序用例未卸载的弹框,必须按当前弹框定位) */
|
|
299
|
+
function currentOverlay() {
|
|
300
|
+
return queryDialog()!.closest<HTMLElement>('.u-dialog__overlay')!
|
|
301
|
+
}
|
|
302
|
+
|
|
220
303
|
function clickEl(el: HTMLElement) {
|
|
221
304
|
el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
222
305
|
}
|
|
@@ -232,8 +315,8 @@ function querySaveButton(dialog: HTMLElement) {
|
|
|
232
315
|
return dialog.querySelector<HTMLElement>('.u-batch-edit__form-actions button[title="保存"]')
|
|
233
316
|
}
|
|
234
317
|
|
|
235
|
-
function queryCancelButton(
|
|
236
|
-
return [...
|
|
318
|
+
function queryCancelButton(container: HTMLElement) {
|
|
319
|
+
return [...container.querySelectorAll<HTMLElement>('.u-batch-edit__form-actions button')].find(
|
|
237
320
|
(btn) => btn.textContent?.includes('取消')
|
|
238
321
|
)
|
|
239
322
|
}
|
|
@@ -242,7 +325,7 @@ describe('UBatchEdit formMode', () => {
|
|
|
242
325
|
it('默认(不传 formMode)仍渲染右侧面板,不出现弹框', async () => {
|
|
243
326
|
const { host, unmount } = mountBatchEdit()
|
|
244
327
|
|
|
245
|
-
await
|
|
328
|
+
await clickEditRow(host, 0)
|
|
246
329
|
const aside = host.querySelector<HTMLElement>('.u-batch-edit__form')
|
|
247
330
|
expect(aside).toBeTruthy()
|
|
248
331
|
expect(aside!.style.display).not.toBe('none')
|
|
@@ -259,8 +342,8 @@ describe('UBatchEdit formMode', () => {
|
|
|
259
342
|
expect(host.querySelector('.u-batch-edit__form')).toBeNull()
|
|
260
343
|
expect(queryDialog()).toBeNull()
|
|
261
344
|
|
|
262
|
-
await
|
|
263
|
-
await
|
|
345
|
+
await clickEditRow(host, 0)
|
|
346
|
+
await waitDialogOpen()
|
|
264
347
|
|
|
265
348
|
const dialog = queryDialog()
|
|
266
349
|
expect(dialog).toBeTruthy()
|
|
@@ -285,12 +368,11 @@ describe('UBatchEdit formMode', () => {
|
|
|
285
368
|
|
|
286
369
|
// 打开新增弹框
|
|
287
370
|
clickEl(host.querySelector<HTMLElement>('.u-batch-edit__add-btn')!)
|
|
288
|
-
await
|
|
289
|
-
expect(queryDialog()).toBeTruthy()
|
|
371
|
+
await waitDialogOpen()
|
|
290
372
|
|
|
291
373
|
// 必填校验失败:不调用 saveMethod,弹框保持打开
|
|
292
374
|
clickEl(querySaveButton(queryDialog()!)!)
|
|
293
|
-
await
|
|
375
|
+
await waitDialogSettled()
|
|
294
376
|
expect(saveMethod).not.toHaveBeenCalled()
|
|
295
377
|
expect(queryDialog()).toBeTruthy()
|
|
296
378
|
|
|
@@ -299,14 +381,13 @@ describe('UBatchEdit formMode', () => {
|
|
|
299
381
|
await nextTick()
|
|
300
382
|
await nextTick()
|
|
301
383
|
clickEl(querySaveButton(queryDialog()!)!)
|
|
302
|
-
await
|
|
384
|
+
await waitDialogClosed()
|
|
303
385
|
|
|
304
386
|
expect(saveMethod).toHaveBeenCalledTimes(1)
|
|
305
387
|
expect(saveMethod.mock.calls[0]![0]).toMatchObject({ label: '新名称' })
|
|
306
388
|
expect(saveMethod.mock.calls[0]![1]).toBe('create')
|
|
307
389
|
expect(data.value).toHaveLength(3)
|
|
308
390
|
expect(data.value[2]).toMatchObject({ label: '新名称' })
|
|
309
|
-
expect(queryDialog()).toBeNull()
|
|
310
391
|
|
|
311
392
|
unmount()
|
|
312
393
|
})
|
|
@@ -318,24 +399,23 @@ describe('UBatchEdit formMode', () => {
|
|
|
318
399
|
})
|
|
319
400
|
|
|
320
401
|
// 编辑第 1 行后取消
|
|
321
|
-
await
|
|
322
|
-
await
|
|
402
|
+
await clickEditRow(host, 0)
|
|
403
|
+
await waitDialogOpen()
|
|
323
404
|
setInputValue(queryDialog()!.querySelector<HTMLInputElement>('.u-input input')!, '改过的名称')
|
|
324
405
|
await nextTick()
|
|
325
406
|
await nextTick()
|
|
326
407
|
clickEl(queryCancelButton(queryDialog()!)!)
|
|
327
|
-
await
|
|
408
|
+
await waitDialogClosed()
|
|
328
409
|
|
|
329
410
|
expect(saveMethod).not.toHaveBeenCalled()
|
|
330
411
|
expect(data.value[0]).toMatchObject({ label: '存草稿' })
|
|
331
412
|
// model 恢复初始快照,而不是残留上一行数据
|
|
332
413
|
expect(model.label).toBe('')
|
|
333
414
|
expect(model.submitType).toBeUndefined()
|
|
334
|
-
expect(queryDialog()).toBeNull()
|
|
335
415
|
|
|
336
416
|
// 取消后再新增:表单应为初始值(快照不被上一行污染)
|
|
337
417
|
clickEl(host.querySelector<HTMLElement>('.u-batch-edit__add-btn')!)
|
|
338
|
-
await
|
|
418
|
+
await waitDialogOpen()
|
|
339
419
|
expect(queryDialog()!.querySelector<HTMLInputElement>('.u-input input')!.value).toBe('')
|
|
340
420
|
|
|
341
421
|
// 表单头部的关闭按钮同样不保存
|
|
@@ -343,27 +423,73 @@ describe('UBatchEdit formMode', () => {
|
|
|
343
423
|
await nextTick()
|
|
344
424
|
await nextTick()
|
|
345
425
|
clickEl(queryDialog()!.querySelector<HTMLElement>('.u-batch-edit__form-close')!)
|
|
346
|
-
await
|
|
426
|
+
await waitDialogClosed()
|
|
347
427
|
expect(saveMethod).not.toHaveBeenCalled()
|
|
348
428
|
expect(model.label).toBe('')
|
|
349
429
|
expect(data.value).toHaveLength(2)
|
|
350
|
-
expect(queryDialog()).toBeNull()
|
|
351
430
|
|
|
352
431
|
// 弹框自身关闭交互(点击遮罩)也不保存
|
|
353
|
-
await
|
|
354
|
-
await
|
|
432
|
+
await clickEditRow(host, 1)
|
|
433
|
+
await waitDialogOpen()
|
|
355
434
|
setInputValue(queryDialog()!.querySelector<HTMLInputElement>('.u-input input')!, '改过的名称')
|
|
356
435
|
await nextTick()
|
|
357
436
|
await nextTick()
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }))
|
|
361
|
-
await flushDialog()
|
|
437
|
+
currentOverlay().dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }))
|
|
438
|
+
await waitDialogClosed()
|
|
362
439
|
|
|
363
440
|
expect(saveMethod).not.toHaveBeenCalled()
|
|
364
441
|
expect(data.value[1]).toMatchObject({ label: '调接口' })
|
|
365
442
|
expect(model.label).toBe('')
|
|
366
|
-
|
|
443
|
+
|
|
444
|
+
unmount()
|
|
445
|
+
})
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
describe('UBatchEdit 重入保护', () => {
|
|
449
|
+
it('保存进行中重复触发(快捷键/连点)被忽略:saveMethod 只调一次、只插一行、弹框正常关闭', async () => {
|
|
450
|
+
const saveMethod = vi.fn()
|
|
451
|
+
const { host, data, unmount } = mountBatchEdit({
|
|
452
|
+
props: { formMode: 'dialog', quickEdit: false, saveMethod },
|
|
453
|
+
labelRules: { required: true }
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
clickEl(host.querySelector<HTMLElement>('.u-batch-edit__add-btn')!)
|
|
457
|
+
await waitDialogOpen()
|
|
458
|
+
|
|
459
|
+
setInputValue(queryDialog()!.querySelector<HTMLInputElement>('.u-input input')!, '新名称')
|
|
460
|
+
await nextTick()
|
|
461
|
+
await nextTick()
|
|
462
|
+
|
|
463
|
+
// 同一 tick 内连续两次触发保存(模拟保存进行中再按 Ctrl/Cmd+S),中间故意不等待
|
|
464
|
+
const saveBtn = querySaveButton(queryDialog()!)!
|
|
465
|
+
clickEl(saveBtn)
|
|
466
|
+
clickEl(saveBtn)
|
|
467
|
+
|
|
468
|
+
await waitDialogClosed()
|
|
469
|
+
|
|
470
|
+
expect(saveMethod).toHaveBeenCalledTimes(1)
|
|
471
|
+
expect(data.value).toHaveLength(3)
|
|
472
|
+
expect(data.value[2]).toMatchObject({ label: '新名称' })
|
|
473
|
+
|
|
474
|
+
unmount()
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
it('删除进行中重复触发被忽略:deleteMethod 只调一次、只删一行', async () => {
|
|
478
|
+
const deleteMethod = vi.fn()
|
|
479
|
+
const { host, data, unmount } = mountBatchEdit({ props: { deleteMethod } })
|
|
480
|
+
await nextTick()
|
|
481
|
+
|
|
482
|
+
// 同一 tick 内连续两次点击删除,中间故意不等待
|
|
483
|
+
const deleteBtn = queryRows(host)[0]!.querySelector<HTMLElement>('button[title="删除"]')!
|
|
484
|
+
clickEl(deleteBtn)
|
|
485
|
+
clickEl(deleteBtn)
|
|
486
|
+
|
|
487
|
+
await waitDialogSettled()
|
|
488
|
+
|
|
489
|
+
expect(deleteMethod).toHaveBeenCalledTimes(1)
|
|
490
|
+
expect(deleteMethod.mock.calls[0]![0]).toHaveLength(1)
|
|
491
|
+
expect(data.value).toHaveLength(1)
|
|
492
|
+
expect(data.value[0]).toMatchObject({ id: '2' })
|
|
367
493
|
|
|
368
494
|
unmount()
|
|
369
495
|
})
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
v-show="state.formVisible"
|
|
8
8
|
>
|
|
9
9
|
<header :class="cls.e('form-header')">
|
|
10
|
-
<span :class="[cls.e('form-icon'),
|
|
10
|
+
<span :class="[cls.e('form-icon'), iconToneCls]">
|
|
11
11
|
<u-icon>
|
|
12
12
|
<component :is="headerInfo.icon" />
|
|
13
13
|
</u-icon>
|
|
@@ -152,4 +152,10 @@ const showSaveBtn = computed(() => {
|
|
|
152
152
|
})
|
|
153
153
|
|
|
154
154
|
const headerInfo = computed(() => FORM_ACTION_HEADER_MAP[state.formActionType])
|
|
155
|
+
|
|
156
|
+
/** 头部图标色调修饰类:样式中 createChild 对应的修饰类为 is-child */
|
|
157
|
+
const iconToneCls = computed(() => {
|
|
158
|
+
const type = state.formActionType
|
|
159
|
+
return bem.is(type === 'createChild' ? 'child' : type)
|
|
160
|
+
})
|
|
155
161
|
</script>
|
|
@@ -4,28 +4,21 @@
|
|
|
4
4
|
:slots="slots"
|
|
5
5
|
:class="[cls.e('table')]"
|
|
6
6
|
:columns="columns"
|
|
7
|
-
highlight-current
|
|
8
7
|
:current="state.row"
|
|
9
|
-
@update:current="handleUpdateCurrentRow"
|
|
10
8
|
@update:checked="emit('update:checked', $event)"
|
|
11
9
|
@update:selected="emit('update:selected', $event)"
|
|
12
10
|
>
|
|
13
11
|
<template #column:__action__="{ row }">
|
|
14
|
-
<u-action-group :
|
|
12
|
+
<u-action-group :max="5" circle>
|
|
15
13
|
<u-action
|
|
16
|
-
v-if="allowed('
|
|
17
|
-
:icon="
|
|
18
|
-
title="
|
|
19
|
-
@run="
|
|
20
|
-
|
|
21
|
-
<u-action
|
|
22
|
-
v-if="allowed('create', row)"
|
|
23
|
-
:icon="InsertToNext"
|
|
24
|
-
title="在下方插入"
|
|
25
|
-
@run="handleInsertToNext(row)"
|
|
14
|
+
v-if="allowed('update', row)"
|
|
15
|
+
:icon="props.readonly ? View : EditPen"
|
|
16
|
+
:title="props.readonly ? '查看' : '编辑'"
|
|
17
|
+
@run="startEdit(row)"
|
|
18
|
+
v-bind="props.actionsProps?.update"
|
|
26
19
|
/>
|
|
27
20
|
<u-action
|
|
28
|
-
v-if="!!props.tree && allowed('createChild', row)"
|
|
21
|
+
v-if="!props.readonly && !!props.tree && allowed('createChild', row)"
|
|
29
22
|
:icon="AddChild"
|
|
30
23
|
title="添加子级"
|
|
31
24
|
@run="handleInsertChild(row)"
|
|
@@ -38,6 +31,18 @@
|
|
|
38
31
|
@run="handleDelete(row)"
|
|
39
32
|
v-bind="props.actionsProps?.delete"
|
|
40
33
|
/>
|
|
34
|
+
<u-action
|
|
35
|
+
v-if="!props.readonly && allowed('create', row)"
|
|
36
|
+
:icon="InsertToPrev"
|
|
37
|
+
title="在上方插入"
|
|
38
|
+
@run="handleInsertToPrev(row)"
|
|
39
|
+
/>
|
|
40
|
+
<u-action
|
|
41
|
+
v-if="!props.readonly && allowed('create', row)"
|
|
42
|
+
:icon="InsertToNext"
|
|
43
|
+
title="在下方插入"
|
|
44
|
+
@run="handleInsertToNext(row)"
|
|
45
|
+
/>
|
|
41
46
|
</u-action-group>
|
|
42
47
|
</template>
|
|
43
48
|
|
|
@@ -61,7 +66,15 @@
|
|
|
61
66
|
|
|
62
67
|
<script setup lang="ts">
|
|
63
68
|
import { o } from '@cat-kit/core'
|
|
64
|
-
import {
|
|
69
|
+
import {
|
|
70
|
+
AddChild,
|
|
71
|
+
Delete,
|
|
72
|
+
EditPen,
|
|
73
|
+
InsertToNext,
|
|
74
|
+
InsertToPrev,
|
|
75
|
+
Plus,
|
|
76
|
+
View
|
|
77
|
+
} from '@veltra/icons/normal'
|
|
65
78
|
import { computed, inject, type Slots } from 'vue'
|
|
66
79
|
|
|
67
80
|
import type { BatchEditFeature, TableRow } from '../../types'
|
|
@@ -81,6 +94,7 @@ const {
|
|
|
81
94
|
emit,
|
|
82
95
|
staticFeatures,
|
|
83
96
|
dynamicFeatures,
|
|
97
|
+
startEdit,
|
|
84
98
|
handleCreate,
|
|
85
99
|
handleDelete,
|
|
86
100
|
handleInsertToNext,
|
|
@@ -97,9 +111,11 @@ const tableProps = computed(() => {
|
|
|
97
111
|
'deleteMethod',
|
|
98
112
|
'saveMethod',
|
|
99
113
|
'features',
|
|
100
|
-
'mode',
|
|
101
114
|
'beforeCreate',
|
|
102
|
-
'formMode'
|
|
115
|
+
'formMode',
|
|
116
|
+
'quickEdit',
|
|
117
|
+
'labelWidth',
|
|
118
|
+
'actionsProps'
|
|
103
119
|
])
|
|
104
120
|
})
|
|
105
121
|
|
|
@@ -110,7 +126,11 @@ const allowed = (feature: BatchEditFeature, row?: TableRow) => {
|
|
|
110
126
|
}
|
|
111
127
|
|
|
112
128
|
const columns = computed(() => {
|
|
113
|
-
|
|
129
|
+
// 只读模式的操作列只保留「查看」入口
|
|
130
|
+
const related: BatchEditFeature[] = props.readonly
|
|
131
|
+
? ['update']
|
|
132
|
+
: ['create', 'delete', 'createChild', 'update']
|
|
133
|
+
if (hasNot(related)) return props.columns
|
|
114
134
|
|
|
115
135
|
return (props.columns ?? []).concat({
|
|
116
136
|
name: '操作',
|
|
@@ -122,12 +142,6 @@ const columns = computed(() => {
|
|
|
122
142
|
})
|
|
123
143
|
})
|
|
124
144
|
|
|
125
|
-
function handleUpdateCurrentRow(row?: TableRow) {
|
|
126
|
-
if (allowed('update', row)) {
|
|
127
|
-
state.row = row
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
145
|
function canDelete(row: TableRow) {
|
|
132
146
|
return !props.readonly && allowed('delete', row)
|
|
133
147
|
}
|
|
@@ -64,7 +64,7 @@ const cls = bem('batch-edit')
|
|
|
64
64
|
|
|
65
65
|
const formRef = useTemplateRef<FormExposed>('form')
|
|
66
66
|
|
|
67
|
-
const { state, resetState, syncing } = useEditState({ props, formRef })
|
|
67
|
+
const { state, resetState, startEdit, syncing } = useEditState({ props, formRef })
|
|
68
68
|
|
|
69
69
|
const { staticFeatures, dynamicFeatures } = useFeatures({ props })
|
|
70
70
|
|
|
@@ -91,6 +91,7 @@ provide(BatchEditDIKey, {
|
|
|
91
91
|
dynamicFeatures,
|
|
92
92
|
focused,
|
|
93
93
|
syncing,
|
|
94
|
+
startEdit,
|
|
94
95
|
...handlers
|
|
95
96
|
})
|
|
96
97
|
</script>
|
|
@@ -24,5 +24,7 @@ export const BatchEditDIKey: InjectionKey<
|
|
|
24
24
|
focused: ShallowRef<boolean>
|
|
25
25
|
/** 是否正在以编程方式重置/回显表单,此期间禁止 quick-edit 回写行数据 */
|
|
26
26
|
syncing: ShallowRef<boolean>
|
|
27
|
+
/** 开始编辑/查看一行(操作列「编辑」按钮触发) */
|
|
28
|
+
startEdit: (row: TableRow) => void
|
|
27
29
|
} & EditReturned
|
|
28
30
|
> = Symbol('BatchEditDIKey')
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { copy, o } from '@cat-kit/core'
|
|
2
2
|
import { nextTick, shallowReactive, shallowRef, watch, type ShallowRef } from 'vue'
|
|
3
3
|
|
|
4
|
-
import type { BatchEditProps, BatchEditStates, FormExposed } from '../../types'
|
|
4
|
+
import type { BatchEditProps, BatchEditStates, FormExposed, TableRow } from '../../types'
|
|
5
5
|
|
|
6
6
|
interface Options {
|
|
7
7
|
props: BatchEditProps
|
|
@@ -56,10 +56,6 @@ export function useEditState(options: Options) {
|
|
|
56
56
|
function resetState() {
|
|
57
57
|
resetModel()
|
|
58
58
|
|
|
59
|
-
if (state.row) {
|
|
60
|
-
state.row.isCurrent = false
|
|
61
|
-
}
|
|
62
|
-
|
|
63
59
|
Object.keys(state).forEach((key) => {
|
|
64
60
|
delete state[key]
|
|
65
61
|
})
|
|
@@ -67,33 +63,30 @@ export function useEditState(options: Options) {
|
|
|
67
63
|
Object.assign(state, defaultState)
|
|
68
64
|
}
|
|
69
65
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
o(props.model).deepExtend(row.data)
|
|
83
|
-
}
|
|
84
|
-
state.formActionType = props.readonly ? 'view' : 'update'
|
|
85
|
-
state.formVisible = true
|
|
86
|
-
state.depth = row.depth
|
|
87
|
-
state.indexPath = [...row.indexes]
|
|
88
|
-
} else {
|
|
89
|
-
resetState()
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
nextTick(() => {
|
|
93
|
-
syncing.value = false
|
|
94
|
-
})
|
|
66
|
+
/**
|
|
67
|
+
* 开始编辑/查看一行:先重置回 model 初始快照,再回显行数据。
|
|
68
|
+
* 同一 tick 内的连续写入会被表单字段 watcher 合并,
|
|
69
|
+
* 重置产生的默认值不会触发 field:update 回写行数据
|
|
70
|
+
*/
|
|
71
|
+
function startEdit(row: TableRow) {
|
|
72
|
+
syncing.value = true
|
|
73
|
+
|
|
74
|
+
resetModel()
|
|
75
|
+
|
|
76
|
+
if (props.model) {
|
|
77
|
+
o(props.model).deepExtend(row.data)
|
|
95
78
|
}
|
|
96
|
-
)
|
|
97
79
|
|
|
98
|
-
|
|
80
|
+
state.row = row
|
|
81
|
+
state.formActionType = props.readonly ? 'view' : 'update'
|
|
82
|
+
state.formVisible = true
|
|
83
|
+
state.depth = row.depth
|
|
84
|
+
state.indexPath = [...row.indexes]
|
|
85
|
+
|
|
86
|
+
nextTick(() => {
|
|
87
|
+
syncing.value = false
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { state, resetState, startEdit, syncing }
|
|
99
92
|
}
|
|
@@ -135,6 +135,8 @@ export function useHandlers(options: Options): EditReturned {
|
|
|
135
135
|
|
|
136
136
|
function runWithLoading<Arg extends any[]>(fn: (...args: Arg) => Promise<void> | void) {
|
|
137
137
|
return async (...args: Arg) => {
|
|
138
|
+
// 重入保护:进行中(快捷键/按钮并发触发)直接忽略
|
|
139
|
+
if (state.loading) return
|
|
138
140
|
state.loading = true
|
|
139
141
|
if (state.row) {
|
|
140
142
|
state.row.operating = true
|