@veltra/desktop 1.2.7 → 1.2.9
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/action/action-group.vue.d.ts +2 -2
- package/dist/components/action/action.vue.d.ts +1 -1
- package/dist/components/auto-complete/auto-complete.vue.d.ts +2 -2
- package/dist/components/batch-edit/batch-edit-form.js +41 -79
- package/dist/components/batch-edit/batch-edit-form.js.map +1 -1
- package/dist/components/batch-edit/batch-edit-list.js +17 -29
- package/dist/components/batch-edit/batch-edit-list.js.map +1 -1
- package/dist/components/batch-edit/batch-edit.js +34 -60
- package/dist/components/batch-edit/batch-edit.js.map +1 -1
- package/dist/components/batch-edit/batch-edit.vue.d.ts +3 -14
- package/dist/components/batch-edit/di.js.map +1 -1
- package/dist/components/batch-edit/form-action-header.js +25 -0
- package/dist/components/batch-edit/form-action-header.js.map +1 -0
- package/dist/components/batch-edit/style2.css +0 -13
- package/dist/components/batch-edit/use-edit-state.js +40 -0
- package/dist/components/batch-edit/use-edit-state.js.map +1 -0
- package/dist/components/batch-edit/use-handlers.js +146 -0
- package/dist/components/batch-edit/use-handlers.js.map +1 -0
- package/dist/components/batch-edit/use-shortcut-key.js +44 -0
- package/dist/components/batch-edit/use-shortcut-key.js.map +1 -0
- package/dist/components/cascade/cascade.vue.d.ts +4 -4
- package/dist/components/checkbox/checkbox-button.vue.d.ts +2 -2
- package/dist/components/checkbox/checkbox.vue.d.ts +1 -1
- package/dist/components/checkbox-group/checkbox-group.vue.d.ts +2 -2
- package/dist/components/code-editor/code-editor.vue.d.ts +1 -1
- package/dist/components/date-panel/date-panel.vue.d.ts +1 -1
- package/dist/components/date-picker/date-picker.vue.d.ts +2 -2
- package/dist/components/date-range-picker/date-range-picker.vue.d.ts +2 -2
- package/dist/components/empty/empty.vue.d.ts +1 -1
- package/dist/components/file-picker/file-picker.vue.d.ts +1 -1
- package/dist/components/form/form.js.map +1 -1
- package/dist/components/form/form.vue.d.ts +5 -1
- package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js +8 -2
- package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/form-item/form-item.js.map +1 -1
- package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js +3 -2
- package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/input/input.vue.d.ts +8 -8
- package/dist/components/multi-select/multi-select.vue.d.ts +3 -3
- package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +6 -6
- package/dist/components/number-input/number-input.vue.d.ts +4 -4
- package/dist/components/number-range-input/number-range-input.vue.d.ts +2 -2
- package/dist/components/palette/palette.vue.d.ts +1 -1
- package/dist/components/password-input/password-input.vue.d.ts +2 -2
- package/dist/components/pop-confirm/pop-confirm.vue.d.ts +1 -1
- package/dist/components/progress-nodes/progress-nodes.vue.d.ts +1 -1
- package/dist/components/radio-group/radio-group.vue.d.ts +2 -2
- package/dist/components/rich-text-editor/rich-text-editor.vue.d.ts +2 -2
- package/dist/components/scroll/scroll.vue.d.ts +2 -2
- package/dist/components/select/select.vue.d.ts +2 -2
- package/dist/components/steps/steps.vue.d.ts +3 -3
- package/dist/components/switch/switch.vue.d.ts +1 -1
- package/dist/components/tabs/tabs-horizontal.vue.d.ts +2 -2
- package/dist/components/tabs/tabs-vertical.vue.d.ts +2 -2
- package/dist/components/tabs/tabs.vue.d.ts +2 -2
- package/dist/components/textarea/textarea.vue.d.ts +4 -4
- package/dist/components/tip/tip.vue.d.ts +1 -1
- package/dist/components/tree/tree.vue.d.ts +1 -1
- package/dist/components/tree-select/tree-select.vue.d.ts +6 -6
- package/dist/index.d.ts +4 -4
- package/dist/types/batch-edit.d.ts +38 -12
- package/dist/types/form.d.ts +4 -1
- package/dist/types/index.d.ts +3 -3
- package/package.json +6 -6
- package/src/components/batch-edit/batch-edit-form.vue +40 -86
- package/src/components/batch-edit/batch-edit-list.vue +13 -29
- package/src/components/batch-edit/batch-edit.vue +27 -92
- package/src/components/batch-edit/di.ts +6 -5
- package/src/components/batch-edit/form-action-header.ts +21 -0
- package/src/components/batch-edit/style.scss +0 -14
- package/src/components/batch-edit/use-dialog-effect.ts +21 -0
- package/src/components/batch-edit/use-edit-state.ts +55 -0
- package/src/components/batch-edit/use-handlers.ts +226 -0
- package/src/components/batch-edit/use-shortcut-key.ts +58 -0
- package/src/components/form/form.vue +10 -3
- package/src/components/form-item/form-item.vue +4 -2
- package/src/types/batch-edit.ts +43 -22
- package/src/types/form.ts +4 -0
- package/dist/components/batch-edit/use-edit.js +0 -215
- package/dist/components/batch-edit/use-edit.js.map +0 -1
- package/src/components/batch-edit/use-edit.ts +0 -343
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
import type { BEM } from '@veltra/utils'
|
|
2
|
-
import type { ComputedRef, InjectionKey, ShallowRef
|
|
2
|
+
import type { ComputedRef, InjectionKey, ShallowRef } from 'vue'
|
|
3
3
|
|
|
4
4
|
import type {
|
|
5
5
|
BatchEditEmits,
|
|
6
6
|
BatchEditFeature,
|
|
7
7
|
BatchEditProps,
|
|
8
|
-
|
|
8
|
+
BatchEditStates,
|
|
9
9
|
TableRow
|
|
10
10
|
} from '../../types'
|
|
11
|
-
import type { EditReturned } from './use-
|
|
11
|
+
import type { EditReturned } from './use-handlers'
|
|
12
|
+
|
|
12
13
|
export const BatchEditDIKey: InjectionKey<
|
|
13
14
|
{
|
|
14
15
|
cls: BEM<'batch-edit', 'u-'>
|
|
15
16
|
props: BatchEditProps
|
|
16
17
|
emit: BatchEditEmits
|
|
17
|
-
|
|
18
|
+
state: BatchEditStates
|
|
18
19
|
staticFeatures: ComputedRef<Set<BatchEditFeature>>
|
|
19
20
|
dynamicFeatures: ComputedRef<
|
|
20
21
|
Record<BatchEditFeature, ((row?: TableRow) => boolean) | undefined>
|
|
21
22
|
>
|
|
22
23
|
/** 组件或其子节点是否处于聚焦状态 */
|
|
23
|
-
focused:
|
|
24
|
+
focused: ShallowRef<boolean>
|
|
24
25
|
} & EditReturned
|
|
25
26
|
> = Symbol('BatchEditDIKey')
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AddChild, EditPen, Plus, View } from '@veltra/icons/normal'
|
|
2
|
+
import type { Component } from 'vue'
|
|
3
|
+
|
|
4
|
+
import type { BatchEditStates } from '../../types'
|
|
5
|
+
|
|
6
|
+
/** 表单操作类型对应的头部展示信息 */
|
|
7
|
+
export interface FormActionHeaderInfo {
|
|
8
|
+
icon: Component
|
|
9
|
+
title: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** 按 formActionType 映射的表单头部信息 */
|
|
13
|
+
export const FORM_ACTION_HEADER_MAP: Record<
|
|
14
|
+
BatchEditStates['formActionType'],
|
|
15
|
+
FormActionHeaderInfo
|
|
16
|
+
> = {
|
|
17
|
+
create: { icon: Plus, title: '新增' },
|
|
18
|
+
update: { icon: EditPen, title: '编辑' },
|
|
19
|
+
view: { icon: View, title: '查看详情' },
|
|
20
|
+
createChild: { icon: AddChild, title: '新增子级' }
|
|
21
|
+
}
|
|
@@ -121,20 +121,6 @@ $root-name: batch-edit;
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
@include m.e(form-chip) {
|
|
125
|
-
display: inline-block;
|
|
126
|
-
padding: 1px 8px;
|
|
127
|
-
font-size: 12px;
|
|
128
|
-
line-height: 18px;
|
|
129
|
-
border-radius: fn.use-var(radius, default);
|
|
130
|
-
background-color: fn.use-var(bg-color, default);
|
|
131
|
-
color: fn.use-var(text-color, second);
|
|
132
|
-
white-space: nowrap;
|
|
133
|
-
overflow: hidden;
|
|
134
|
-
text-overflow: ellipsis;
|
|
135
|
-
max-width: 60%;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
124
|
@include m.e(form-close) {
|
|
139
125
|
flex-shrink: 0;
|
|
140
126
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { inject, watch } from 'vue'
|
|
2
|
+
|
|
3
|
+
import { DialogDIKey } from '../dialog/di'
|
|
4
|
+
|
|
5
|
+
interface Options {
|
|
6
|
+
/** 关闭表单 */
|
|
7
|
+
closeForm: () => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 在表单上的副作用。
|
|
12
|
+
* 包括:关闭表单
|
|
13
|
+
*/
|
|
14
|
+
export function useDialogEffect(options: Options) {
|
|
15
|
+
const dialogCtx = inject(DialogDIKey, undefined)
|
|
16
|
+
if (!dialogCtx) return
|
|
17
|
+
|
|
18
|
+
watch(dialogCtx.visible, (visible) => {
|
|
19
|
+
!visible && options.closeForm?.()
|
|
20
|
+
})
|
|
21
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { o } from '@cat-kit/core'
|
|
2
|
+
import { shallowReactive, watch } from 'vue'
|
|
3
|
+
|
|
4
|
+
import type { BatchEditProps, BatchEditStates } from '../../types'
|
|
5
|
+
|
|
6
|
+
interface Options {
|
|
7
|
+
props: BatchEditProps
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function useEditState(options: Options) {
|
|
11
|
+
const { props } = options
|
|
12
|
+
|
|
13
|
+
const defaultState: BatchEditStates = {
|
|
14
|
+
depth: -1,
|
|
15
|
+
formVisible: false,
|
|
16
|
+
formActionType: 'create',
|
|
17
|
+
loading: false,
|
|
18
|
+
indexPath: []
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const state = shallowReactive({ ...defaultState })
|
|
22
|
+
|
|
23
|
+
function resetState() {
|
|
24
|
+
if (state.row) {
|
|
25
|
+
state.row.isCurrent = false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
Object.keys(state).forEach((key) => {
|
|
29
|
+
delete state[key]
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
Object.assign(state, defaultState)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
watch(
|
|
36
|
+
() => state.row,
|
|
37
|
+
(row) => {
|
|
38
|
+
if (row) {
|
|
39
|
+
if (props.model) {
|
|
40
|
+
o(props.model).deepExtend(row.data)
|
|
41
|
+
}
|
|
42
|
+
state.formActionType = props.readonly ? 'view' : 'update'
|
|
43
|
+
state.formVisible = true
|
|
44
|
+
state.depth = row.depth
|
|
45
|
+
state.indexPath = [...row.indexes]
|
|
46
|
+
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
resetState()
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return { state, resetState }
|
|
55
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { last, o } from '@cat-kit/core'
|
|
2
|
+
import { computed, nextTick, type ShallowRef } from 'vue'
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
TableRow,
|
|
6
|
+
BatchEditEmits,
|
|
7
|
+
BatchEditProps,
|
|
8
|
+
BatchEditStates,
|
|
9
|
+
FormExposed
|
|
10
|
+
} from '../../types'
|
|
11
|
+
|
|
12
|
+
/** 按索引路径在原始数据树中定位节点(等价于历史 Forest.visit 路径访问) */
|
|
13
|
+
function visitDataByIndexPath<T extends Record<string, unknown>>(
|
|
14
|
+
roots: T[],
|
|
15
|
+
indexPath: number[],
|
|
16
|
+
childrenKey: string
|
|
17
|
+
): T | undefined {
|
|
18
|
+
if (!indexPath.length) return undefined
|
|
19
|
+
let currentList: T[] | undefined = roots
|
|
20
|
+
let node: T | undefined
|
|
21
|
+
for (let i = 0; i < indexPath.length; i++) {
|
|
22
|
+
const idx = indexPath[i]!
|
|
23
|
+
if (!currentList) return undefined
|
|
24
|
+
node = currentList[idx]
|
|
25
|
+
if (node === undefined) return undefined
|
|
26
|
+
if (i < indexPath.length - 1) {
|
|
27
|
+
const next = node[childrenKey]
|
|
28
|
+
currentList = Array.isArray(next) ? (next as T[]) : undefined
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return node
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface Options {
|
|
35
|
+
props: BatchEditProps
|
|
36
|
+
emit: BatchEditEmits
|
|
37
|
+
state: BatchEditStates
|
|
38
|
+
formRef: ShallowRef<FormExposed | null>
|
|
39
|
+
resetState: () => void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface EditReturned {
|
|
43
|
+
handleSave: () => Promise<void>
|
|
44
|
+
handleClose: () => void
|
|
45
|
+
handleCreate: () => void
|
|
46
|
+
handleDelete: (row: TableRow) => Promise<void>
|
|
47
|
+
handleInsertToNext: (row: TableRow) => void
|
|
48
|
+
handleInsertToPrev: (row: TableRow) => void
|
|
49
|
+
handleInsertChild: (row: TableRow) => void
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function useHandlers(options: Options): EditReturned {
|
|
53
|
+
const { props, emit, state, resetState, formRef } = options
|
|
54
|
+
|
|
55
|
+
const childrenKey = computed(() => {
|
|
56
|
+
return typeof props.tree === 'string' ? props.tree : 'children'
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
function getInsertData(): Record<string, any> {
|
|
60
|
+
return o(props.model ?? {}).copy()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 插入数据
|
|
65
|
+
* @param item 数据
|
|
66
|
+
*/
|
|
67
|
+
function insert(item: Record<string, any>) {
|
|
68
|
+
const data = [...(props.data ?? [])]
|
|
69
|
+
|
|
70
|
+
const parent = visitDataByIndexPath(data ?? [], state.indexPath.slice(0, -1), childrenKey.value)
|
|
71
|
+
|
|
72
|
+
if (parent) {
|
|
73
|
+
const children = parent[childrenKey.value]
|
|
74
|
+
if (!children) {
|
|
75
|
+
parent[childrenKey.value] = [item]
|
|
76
|
+
} else {
|
|
77
|
+
children.splice(last(state.indexPath), 0, item)
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
data.splice(last(state.indexPath), 0, item)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
emit('update:data', data)
|
|
84
|
+
|
|
85
|
+
return item
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 点击新增按钮
|
|
90
|
+
*/
|
|
91
|
+
function handleCreate() {
|
|
92
|
+
const { data } = props
|
|
93
|
+
|
|
94
|
+
resetState()
|
|
95
|
+
|
|
96
|
+
nextTick(() => {
|
|
97
|
+
state.formActionType = 'create'
|
|
98
|
+
state.formVisible = true
|
|
99
|
+
state.depth = 1
|
|
100
|
+
state.indexPath = [data?.length ?? 0]
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function handleInsertToPrev(row: TableRow) {
|
|
105
|
+
resetState()
|
|
106
|
+
state.formActionType = 'create'
|
|
107
|
+
state.formVisible = true
|
|
108
|
+
state.depth = row.depth
|
|
109
|
+
state.indexPath = [...row.indexes]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function handleInsertToNext(row: TableRow) {
|
|
113
|
+
resetState()
|
|
114
|
+
state.formActionType = 'create'
|
|
115
|
+
state.formVisible = true
|
|
116
|
+
state.depth = row.depth
|
|
117
|
+
state.indexPath = [...row.indexes.slice(0, -1), row.index + 1]
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function handleInsertChild(row: TableRow) {
|
|
121
|
+
resetState()
|
|
122
|
+
state.formActionType = 'createChild'
|
|
123
|
+
state.formVisible = true
|
|
124
|
+
state.parentRow = row
|
|
125
|
+
state.depth = row.depth + 1
|
|
126
|
+
row.expanded = true
|
|
127
|
+
state.indexPath = [...row.indexes, row.children?.length ?? 0]
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function runWithLoading<Arg extends any[]>(fn: (...args: Arg) => Promise<void> | void) {
|
|
131
|
+
return async (...args: Arg) => {
|
|
132
|
+
state.loading = true
|
|
133
|
+
if (state.row) {
|
|
134
|
+
state.row.operating = true
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
await fn.apply(null, args)
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error(error)
|
|
140
|
+
}
|
|
141
|
+
state.loading = false
|
|
142
|
+
if (state.row) {
|
|
143
|
+
state.row.operating = false
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** 保存 */
|
|
149
|
+
const handleSave = runWithLoading(async () => {
|
|
150
|
+
const { model, saveMethod } = props
|
|
151
|
+
const valid = await formRef.value?.validate()
|
|
152
|
+
if (!model || !valid) return
|
|
153
|
+
|
|
154
|
+
let item = getInsertData()
|
|
155
|
+
|
|
156
|
+
// 新增
|
|
157
|
+
if (state.formActionType === 'create' || state.formActionType === 'createChild') {
|
|
158
|
+
await props.beforeCreate?.(item, state.parentRow?.data)
|
|
159
|
+
if (saveMethod) {
|
|
160
|
+
const result = await saveMethod(item, state.formActionType, state.parentRow?.data)
|
|
161
|
+
if (result) {
|
|
162
|
+
item = result
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
insert(item)
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 普通模式下的编辑
|
|
170
|
+
if (saveMethod) {
|
|
171
|
+
const result = await saveMethod(item, state.formActionType, state.parentRow?.data)
|
|
172
|
+
if (result) {
|
|
173
|
+
item = result
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const { row } = state
|
|
178
|
+
row &&
|
|
179
|
+
Object.keys(item).forEach((key) => {
|
|
180
|
+
o(row.data).set(key, o(item).get(key))
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
const handleDelete = runWithLoading(async (row: TableRow) => {
|
|
185
|
+
const { deleteMethod } = props
|
|
186
|
+
|
|
187
|
+
if (deleteMethod) {
|
|
188
|
+
await deleteMethod([row.data])
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const data = [...(props.data ?? [])]
|
|
192
|
+
|
|
193
|
+
const parent = visitDataByIndexPath(data, row.indexes.slice(0, -1), childrenKey.value)
|
|
194
|
+
|
|
195
|
+
if (parent) {
|
|
196
|
+
const children = parent[childrenKey.value]
|
|
197
|
+
if (children) {
|
|
198
|
+
children.splice(row.index, 1)
|
|
199
|
+
} else {
|
|
200
|
+
console.error('行路径不正确')
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
data.splice(row.index, 1)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (state.row === row) {
|
|
207
|
+
resetState()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
emit('update:data', data)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
function handleClose() {
|
|
214
|
+
resetState()
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
handleDelete,
|
|
219
|
+
handleInsertChild,
|
|
220
|
+
handleInsertToNext,
|
|
221
|
+
handleInsertToPrev,
|
|
222
|
+
handleCreate,
|
|
223
|
+
handleClose,
|
|
224
|
+
handleSave
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { shallowRef } from 'vue'
|
|
2
|
+
|
|
3
|
+
import type { BatchEditProps, BatchEditStates } from '../../types'
|
|
4
|
+
|
|
5
|
+
interface Options {
|
|
6
|
+
props: BatchEditProps
|
|
7
|
+
onSave: () => void
|
|
8
|
+
onClose: () => void
|
|
9
|
+
state: BatchEditStates
|
|
10
|
+
}
|
|
11
|
+
export function useShortcutKey(options: Options) {
|
|
12
|
+
const { props, onClose, onSave, state } = options
|
|
13
|
+
const focused = shallowRef(false)
|
|
14
|
+
|
|
15
|
+
function handleFocusIn() {
|
|
16
|
+
focused.value = true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function handleFocusOut(e: FocusEvent) {
|
|
20
|
+
const root = e.currentTarget as HTMLElement
|
|
21
|
+
if (!root.contains(e.relatedTarget as Node | null)) {
|
|
22
|
+
focused.value = false
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function runEscape(e: KeyboardEvent) {
|
|
27
|
+
e.preventDefault()
|
|
28
|
+
onClose()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function runSave(e: KeyboardEvent) {
|
|
32
|
+
if (!state.formVisible) return
|
|
33
|
+
if (props.quickEdit && state.formActionType === 'update') return
|
|
34
|
+
e.preventDefault()
|
|
35
|
+
onSave()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 键盘快捷键(仅组件获焦时生效):
|
|
40
|
+
* - Esc 关闭表单
|
|
41
|
+
* - ⌘/Ctrl + S 保存(快速编辑模式下编辑行时除外)
|
|
42
|
+
*/
|
|
43
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
44
|
+
if (!focused.value) return
|
|
45
|
+
if (props.readonly && e.key !== 'Escape') return
|
|
46
|
+
|
|
47
|
+
if (e.key === 'Escape') return runEscape(e)
|
|
48
|
+
|
|
49
|
+
const meta = e.metaKey || e.ctrlKey
|
|
50
|
+
if (!meta) return
|
|
51
|
+
|
|
52
|
+
const key = e.key.toLowerCase()
|
|
53
|
+
|
|
54
|
+
if (key === 's') return runSave(e)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { focused, handleFocusIn, handleFocusOut, handleKeydown }
|
|
58
|
+
}
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
import { o } from '@cat-kit/core'
|
|
28
28
|
import { bem } from '@veltra/utils'
|
|
29
29
|
import { provideFormContext } from '@veltra/utils'
|
|
30
|
-
import { nextTick,
|
|
30
|
+
import { nextTick, toRef, useTemplateRef } from 'vue'
|
|
31
31
|
|
|
32
|
-
import type { BreakCols,
|
|
32
|
+
import type { BreakCols, FormProps, _FormExposed, FormEmits } from '../../types'
|
|
33
33
|
import { UFormItem } from '../form-item'
|
|
34
34
|
import { UGrid } from '../grid'
|
|
35
35
|
import { useFormFields } from './use-form-fields'
|
|
@@ -39,6 +39,8 @@ defineOptions({ name: 'Form' })
|
|
|
39
39
|
|
|
40
40
|
const props = defineProps<FormProps>()
|
|
41
41
|
|
|
42
|
+
const emit = defineEmits<FormEmits>()
|
|
43
|
+
|
|
42
44
|
defineSlots<{ default(props?: { data: Record<string, any> | undefined }): any }>()
|
|
43
45
|
|
|
44
46
|
const cls = bem('form')
|
|
@@ -68,12 +70,17 @@ async function validate(keys?: string[]) {
|
|
|
68
70
|
return valid
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
function handleFieldChange(field: string, value: any) {
|
|
74
|
+
emit('field:change', field, value)
|
|
75
|
+
}
|
|
76
|
+
|
|
71
77
|
provideFormContext({
|
|
72
78
|
formProps: props,
|
|
73
79
|
registerField,
|
|
74
80
|
unregisterField,
|
|
75
81
|
validateFields: validate,
|
|
76
|
-
shouldValidate
|
|
82
|
+
shouldValidate,
|
|
83
|
+
handleFieldChange
|
|
77
84
|
})
|
|
78
85
|
|
|
79
86
|
const { getSlotsNodes } = useNodeInterceptor()
|
|
@@ -56,7 +56,8 @@ defineSlots<{
|
|
|
56
56
|
}>()
|
|
57
57
|
|
|
58
58
|
/** 表单组件上下文 */
|
|
59
|
-
const { formProps, registerField, unregisterField, shouldValidate } =
|
|
59
|
+
const { formProps, registerField, unregisterField, shouldValidate, handleFieldChange } =
|
|
60
|
+
injectFormContext()
|
|
60
61
|
|
|
61
62
|
const { config } = useConfig()
|
|
62
63
|
|
|
@@ -127,7 +128,8 @@ watch(
|
|
|
127
128
|
|
|
128
129
|
stopWatchFieldValue = watch(
|
|
129
130
|
() => o(model).get(field),
|
|
130
|
-
() => {
|
|
131
|
+
(value) => {
|
|
132
|
+
handleFieldChange?.(field, value)
|
|
131
133
|
fieldItem.validate()
|
|
132
134
|
}
|
|
133
135
|
)
|
package/src/types/batch-edit.ts
CHANGED
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
import type { DeconstructValue } from '@veltra/utils'
|
|
2
2
|
|
|
3
3
|
import type { ActionProps } from './action'
|
|
4
|
-
import type { TableColumn, TableEmits, TableProps, TableRow } from './table'
|
|
4
|
+
import type { TableColumn, TableColumnSlotsScope, TableEmits, TableProps, TableRow } from './table'
|
|
5
5
|
|
|
6
6
|
/** 批量编辑列 */
|
|
7
|
-
export interface BatchEditColumn extends TableColumn {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// * @default true
|
|
11
|
-
// */
|
|
12
|
-
// visible?: boolean
|
|
13
|
-
// /** 校验规则 */
|
|
14
|
-
// rules?: ValidateRule
|
|
15
|
-
// /** 默认值 */
|
|
16
|
-
// defaultValue?: any | (() => any)
|
|
17
|
-
}
|
|
7
|
+
export interface BatchEditColumn extends TableColumn {}
|
|
8
|
+
|
|
9
|
+
export type BatchEditFeature = 'create' | 'update' | 'delete' | 'view' | 'createChild'
|
|
18
10
|
|
|
19
|
-
export type
|
|
11
|
+
export type BatchEditFormStatus = 'hidden'
|
|
20
12
|
|
|
21
|
-
/**
|
|
22
|
-
export
|
|
13
|
+
/** 批量编辑状态 */
|
|
14
|
+
export interface BatchEditStates {
|
|
15
|
+
/** 层级 */
|
|
16
|
+
depth: number
|
|
17
|
+
/** 表单可见性 */
|
|
18
|
+
formVisible: boolean
|
|
19
|
+
/** 表单操作类型 */
|
|
20
|
+
formActionType: 'create' | 'update' | 'view' | 'createChild'
|
|
21
|
+
/** 加载状态 */
|
|
22
|
+
loading: boolean
|
|
23
|
+
/** 当前编辑行 */
|
|
24
|
+
row?: TableRow
|
|
25
|
+
/** 当前编辑或者新增的父级行 */
|
|
26
|
+
parentRow?: TableRow
|
|
27
|
+
/** 行索引路径, */
|
|
28
|
+
indexPath: number[]
|
|
29
|
+
}
|
|
23
30
|
|
|
24
31
|
/** 批量编辑组件属性 */
|
|
25
32
|
export interface BatchEditProps extends TableProps {
|
|
@@ -37,14 +44,13 @@ export interface BatchEditProps extends TableProps {
|
|
|
37
44
|
/** 只读模式 */
|
|
38
45
|
readonly?: boolean
|
|
39
46
|
/**
|
|
40
|
-
*
|
|
41
|
-
* @
|
|
42
|
-
* @description `quick` 模式下编辑行时表单直接绑定 `row.data` 实时更新,且不调用 `saveMethod`
|
|
47
|
+
* 开启快速编辑
|
|
48
|
+
* @description 开启后,编辑行时表单实时写回 `row.data`(经 `model` 中转),且不调用 `saveMethod`
|
|
43
49
|
*/
|
|
44
|
-
|
|
50
|
+
quickEdit?: boolean
|
|
45
51
|
/**
|
|
46
52
|
* 新增前的钩子
|
|
47
|
-
* @description 仅作用于 create
|
|
53
|
+
* @description 仅作用于 create 类操作,在保存时调用。可直接修改传入的 draft 对象
|
|
48
54
|
*/
|
|
49
55
|
beforeCreate?: (
|
|
50
56
|
data: Record<string, any>,
|
|
@@ -56,14 +62,14 @@ export interface BatchEditProps extends TableProps {
|
|
|
56
62
|
deleteMethod?: (data: Record<string, any>[]) => any
|
|
57
63
|
/**
|
|
58
64
|
* 保存方法
|
|
59
|
-
* @description
|
|
65
|
+
* @description 保存时调用。`quick` 模式下编辑行时实时写回,不调用此方法;新增时与 `normal` 模式一致
|
|
60
66
|
* @returns 如果返回一个值,那么这个值会被插入,否则插入的为表单值
|
|
61
67
|
*/
|
|
62
68
|
saveMethod?: (
|
|
63
69
|
/** 表单数据 */
|
|
64
70
|
data: Record<string, any>,
|
|
65
71
|
/** 操作类型 */
|
|
66
|
-
actionType: '
|
|
72
|
+
actionType: BatchEditStates['formActionType'],
|
|
67
73
|
/** 父级数据 */
|
|
68
74
|
parentData?: Record<string, any>
|
|
69
75
|
) => any
|
|
@@ -109,6 +115,21 @@ export interface BatchEditEmits extends TableEmits {
|
|
|
109
115
|
(e: 'update:data', value: Record<string, any>[]): void
|
|
110
116
|
}
|
|
111
117
|
|
|
118
|
+
export type BatchEditSlots = {
|
|
119
|
+
form?: (props: {
|
|
120
|
+
/** 当前编辑的层级 */
|
|
121
|
+
depth?: number
|
|
122
|
+
/** 当前编辑的行 */
|
|
123
|
+
row?: TableRow
|
|
124
|
+
/** 当前编辑的行索引 */
|
|
125
|
+
index?: number
|
|
126
|
+
/** 操作的目标行索引路径 */
|
|
127
|
+
indexes?: number[]
|
|
128
|
+
}) => any
|
|
129
|
+
|
|
130
|
+
header?: () => any
|
|
131
|
+
} & Partial<{ [key: `column:${string}`]: (props: TableColumnSlotsScope) => any }>
|
|
132
|
+
|
|
112
133
|
/** 批量编辑组件暴露的属性和方法(组件内部使用) */
|
|
113
134
|
export interface _BatchEditExposed {}
|
|
114
135
|
|
package/src/types/form.ts
CHANGED
|
@@ -21,6 +21,10 @@ export interface FormProps extends ComponentProps {
|
|
|
21
21
|
disabled?: boolean
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export interface FormEmits {
|
|
25
|
+
(e: 'field:change', field: string, value: any): void
|
|
26
|
+
}
|
|
27
|
+
|
|
24
28
|
export interface _FormExposed {
|
|
25
29
|
el: ShallowRef<HTMLElement | null | undefined>
|
|
26
30
|
validate: (keys?: string[]) => Promise<boolean>
|