@veltra/desktop 1.2.3 → 1.2.5
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 +3 -3
- package/dist/components/action/action.vue.d.ts +2 -2
- package/dist/components/auto-complete/auto-complete.js +25 -13
- package/dist/components/auto-complete/auto-complete.js.map +1 -1
- package/dist/components/auto-complete/auto-complete.vue.d.ts +3 -2
- package/dist/components/batch-edit/batch-edit-form.js +33 -26
- package/dist/components/batch-edit/batch-edit-form.js.map +1 -1
- package/dist/components/batch-edit/batch-edit-list.js +4 -1
- package/dist/components/batch-edit/batch-edit-list.js.map +1 -1
- package/dist/components/batch-edit/batch-edit.js +29 -50
- package/dist/components/batch-edit/batch-edit.js.map +1 -1
- package/dist/components/batch-edit/batch-edit.vue.d.ts +3 -3
- package/dist/components/batch-edit/di.js.map +1 -1
- package/dist/components/batch-edit/style2.css +3 -0
- package/dist/components/batch-edit/use-edit.js +39 -71
- package/dist/components/batch-edit/use-edit.js.map +1 -1
- package/dist/components/batch-edit/use-features.js +38 -0
- package/dist/components/batch-edit/use-features.js.map +1 -0
- package/dist/components/checkbox/checkbox-button.vue.d.ts +1 -1
- package/dist/components/contextmenu/style2.css +7 -7
- package/dist/components/date-panel/date-panel.vue.d.ts +1 -1
- package/dist/components/date-picker/date-picker.vue.d.ts +1 -1
- package/dist/components/date-range-picker/date-range-picker.vue.d.ts +1 -1
- package/dist/components/dual-nav/dual-nav-app.js +49 -0
- package/dist/components/dual-nav/dual-nav-app.js.map +1 -0
- package/dist/components/dual-nav/dual-nav.js +125 -0
- package/dist/components/dual-nav/dual-nav.js.map +1 -0
- package/dist/components/dual-nav/dual-nav.vue.d.ts +13 -0
- package/dist/components/dual-nav/helper.js +21 -0
- package/dist/components/dual-nav/helper.js.map +1 -0
- package/dist/components/dual-nav/index.d.ts +1 -0
- package/dist/components/dual-nav/style.js +4 -0
- package/dist/components/dual-nav/style2.css +145 -0
- package/dist/components/empty/empty.vue.d.ts +1 -1
- package/dist/components/index.d.ts +23 -22
- package/dist/components/index.js +28 -26
- package/dist/components/index.js.map +1 -1
- package/dist/components/nav/di.js +7 -0
- package/dist/components/nav/di.js.map +1 -0
- package/dist/components/nav/helper.js +18 -0
- package/dist/components/nav/helper.js.map +1 -0
- package/dist/components/nav/index.d.ts +3 -0
- package/dist/components/{menu/menu-icon.js → nav/nav-icon.js} +4 -4
- package/dist/components/nav/nav-icon.js.map +1 -0
- package/dist/components/{menu/menu-item-collapsed.js → nav/nav-item-collapsed.js} +23 -23
- package/dist/components/nav/nav-item-collapsed.js.map +1 -0
- package/dist/components/{menu/menu-item.js → nav/nav-item.js} +13 -13
- package/dist/components/nav/nav-item.js.map +1 -0
- package/dist/components/{menu/menu-item.vue.d.ts → nav/nav-item.vue.d.ts} +4 -4
- package/dist/components/{menu/menu-sub-collapsed.js → nav/nav-sub-collapsed.js} +19 -19
- package/dist/components/nav/nav-sub-collapsed.js.map +1 -0
- package/dist/components/{menu/menu-sub.js → nav/nav-sub.js} +24 -24
- package/dist/components/nav/nav-sub.js.map +1 -0
- package/dist/components/{menu/menu-sub.vue.d.ts → nav/nav-sub.vue.d.ts} +4 -4
- package/dist/components/{menu/menu.js → nav/nav.js} +45 -36
- package/dist/components/nav/nav.js.map +1 -0
- package/dist/components/nav/nav.vue.d.ts +18 -0
- package/dist/components/nav/style.d.ts +1 -0
- package/dist/components/{menu → nav}/style2.css +105 -105
- package/dist/components/{menu/use-menu-item.js → nav/use-nav-item.js} +11 -11
- package/dist/components/nav/use-nav-item.js.map +1 -0
- package/dist/components/{menu/walk-menu-path.js → nav/walk-nav-path.js} +5 -5
- package/dist/components/nav/walk-nav-path.js.map +1 -0
- package/dist/components/number/number.vue.d.ts +1 -1
- package/dist/components/pop-confirm/pop-confirm.vue.d.ts +1 -1
- package/dist/components/select/style2.css +4 -4
- package/dist/components/table/use-columns.js +14 -12
- package/dist/components/table/use-columns.js.map +1 -1
- package/dist/components/tip/tip.vue.d.ts +1 -1
- package/dist/components/watermark/watermark.vue.d.ts +1 -1
- package/dist/index.d.ts +63 -61
- package/dist/index.js +56 -55
- package/dist/style.js +2 -1
- package/dist/types/auto-complete.d.ts +6 -1
- package/dist/types/batch-edit.d.ts +15 -2
- package/dist/types/dual-nav.d.ts +27 -0
- package/dist/types/index.d.ts +4 -3
- package/dist/types/nav.d.ts +48 -0
- package/package.json +6 -6
- package/src/components/auto-complete/auto-complete.vue +24 -13
- package/src/components/batch-edit/batch-edit-form.vue +29 -18
- package/src/components/batch-edit/batch-edit-list.vue +6 -1
- package/src/components/batch-edit/batch-edit.vue +36 -73
- package/src/components/batch-edit/di.ts +3 -1
- package/src/components/batch-edit/style.scss +5 -0
- package/src/components/batch-edit/use-edit.ts +63 -116
- package/src/components/batch-edit/use-features.ts +58 -0
- package/src/components/contextmenu/style.scss +5 -5
- package/src/components/dual-nav/dual-nav-app.vue +33 -0
- package/src/components/dual-nav/dual-nav.vue +150 -0
- package/src/components/dual-nav/helper.ts +25 -0
- package/src/components/dual-nav/index.ts +1 -0
- package/src/components/dual-nav/style.scss +153 -0
- package/src/components/dual-nav/style.ts +4 -0
- package/src/components/index.ts +3 -1
- package/src/components/nav/di.ts +17 -0
- package/src/components/nav/helper.ts +22 -0
- package/src/components/nav/index.ts +3 -0
- package/src/components/{menu/menu-icon.vue → nav/nav-icon.vue} +1 -1
- package/src/components/nav/nav-item-collapsed.vue +66 -0
- package/src/components/nav/nav-item.vue +36 -0
- package/src/components/{menu/menu-sub-collapsed.vue → nav/nav-sub-collapsed.vue} +15 -15
- package/src/components/{menu/menu-sub.vue → nav/nav-sub.vue} +19 -19
- package/src/components/{menu/menu.vue → nav/nav.vue} +42 -42
- package/src/components/{menu → nav}/style.scss +16 -14
- package/src/components/{menu/use-menu-item.ts → nav/use-nav-item.ts} +14 -14
- package/src/components/{menu/walk-menu-path.ts → nav/walk-nav-path.ts} +2 -2
- package/src/components/select/style.scss +4 -4
- package/src/components/table/use-columns.ts +23 -20
- package/src/style.ts +2 -1
- package/src/types/auto-complete.ts +6 -1
- package/src/types/batch-edit.ts +18 -1
- package/src/types/dual-nav.ts +28 -0
- package/src/types/index.ts +2 -1
- package/src/types/nav.ts +49 -0
- package/dist/components/menu/di.js +0 -7
- package/dist/components/menu/di.js.map +0 -1
- package/dist/components/menu/helper.js +0 -8
- package/dist/components/menu/helper.js.map +0 -1
- package/dist/components/menu/index.d.ts +0 -3
- package/dist/components/menu/menu-icon.js.map +0 -1
- package/dist/components/menu/menu-item-collapsed.js.map +0 -1
- package/dist/components/menu/menu-item.js.map +0 -1
- package/dist/components/menu/menu-sub-collapsed.js.map +0 -1
- package/dist/components/menu/menu-sub.js.map +0 -1
- package/dist/components/menu/menu.js.map +0 -1
- package/dist/components/menu/menu.vue.d.ts +0 -14
- package/dist/components/menu/use-menu-item.js.map +0 -1
- package/dist/components/menu/walk-menu-path.js.map +0 -1
- package/dist/types/menu.d.ts +0 -40
- package/src/components/menu/di.ts +0 -17
- package/src/components/menu/helper.ts +0 -3
- package/src/components/menu/index.ts +0 -3
- package/src/components/menu/menu-item-collapsed.vue +0 -66
- package/src/components/menu/menu-item.vue +0 -36
- package/src/components/menu/menu-node.ts +0 -6
- package/src/types/menu.ts +0 -41
- /package/dist/components/{menu → dual-nav}/style.d.ts +0 -0
- /package/dist/components/{menu → nav}/style.js +0 -0
- /package/src/components/{menu → nav}/style.ts +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { last, o, safeRun } from '@cat-kit/core'
|
|
2
2
|
import { computed, nextTick, shallowReactive, shallowRef, watch, type ShallowRef } from 'vue'
|
|
3
3
|
|
|
4
|
-
import type { TableRow, BatchEditEmits, BatchEditProps
|
|
4
|
+
import type { TableRow, BatchEditEmits, BatchEditProps } from '../../types'
|
|
5
5
|
import type { FormExposed } from '../../types/form'
|
|
6
6
|
|
|
7
7
|
/** 按索引路径在原始数据树中定位节点(等价于历史 Forest.visit 路径访问) */
|
|
@@ -29,7 +29,6 @@ function visitDataByIndexPath<T extends Record<string, unknown>>(
|
|
|
29
29
|
interface Options {
|
|
30
30
|
props: BatchEditProps
|
|
31
31
|
emit: BatchEditEmits
|
|
32
|
-
tableRef: ShallowRef<TableExposed | undefined>
|
|
33
32
|
formRef: ShallowRef<FormExposed | undefined>
|
|
34
33
|
}
|
|
35
34
|
|
|
@@ -42,8 +41,6 @@ export interface BatchEditStates {
|
|
|
42
41
|
row?: TableRow
|
|
43
42
|
/** 父级,只在添加子级时会有值 */
|
|
44
43
|
parentRow?: TableRow
|
|
45
|
-
/** 数据是否已更新 */
|
|
46
|
-
dataUpdated: boolean
|
|
47
44
|
}
|
|
48
45
|
|
|
49
46
|
export interface EditReturned {
|
|
@@ -51,13 +48,13 @@ export interface EditReturned {
|
|
|
51
48
|
insertIndexes: ShallowRef<number[]>
|
|
52
49
|
handleSave: () => Promise<void>
|
|
53
50
|
handleClose: () => void
|
|
54
|
-
handleCreate: () => void
|
|
51
|
+
handleCreate: () => Promise<void>
|
|
55
52
|
handleEdit: (row: TableRow) => void
|
|
56
|
-
handleCopy: (row: TableRow) => void
|
|
53
|
+
handleCopy: (row: TableRow) => Promise<void>
|
|
57
54
|
handleDelete: (row: TableRow) => Promise<void>
|
|
58
|
-
handleInsertToNext: (row: TableRow) => void
|
|
59
|
-
handleInsertToPrev: (row: TableRow) => void
|
|
60
|
-
handleInsertChild: (row: TableRow) => void
|
|
55
|
+
handleInsertToNext: (row: TableRow) => Promise<void>
|
|
56
|
+
handleInsertToPrev: (row: TableRow) => Promise<void>
|
|
57
|
+
handleInsertChild: (row: TableRow) => Promise<void>
|
|
61
58
|
}
|
|
62
59
|
|
|
63
60
|
function snapshotData(data?: Record<string, any>) {
|
|
@@ -72,101 +69,49 @@ function replaceModelData(model: Record<string, any>, data: Record<string, any>)
|
|
|
72
69
|
}
|
|
73
70
|
|
|
74
71
|
export function useEdit(options: Options): EditReturned {
|
|
75
|
-
const { props, emit,
|
|
72
|
+
const { props, emit, formRef } = options
|
|
76
73
|
|
|
77
74
|
/** 组件状态 */
|
|
78
75
|
const state = shallowReactive<BatchEditStates>({
|
|
79
76
|
depth: undefined,
|
|
80
77
|
visible: false,
|
|
81
78
|
type: 'create',
|
|
82
|
-
loading: false
|
|
83
|
-
dataUpdated: false
|
|
79
|
+
loading: false
|
|
84
80
|
})
|
|
85
81
|
|
|
86
82
|
const insertIndexes = shallowRef<number[]>([])
|
|
87
83
|
|
|
88
|
-
watch(
|
|
89
|
-
() => state.visible,
|
|
90
|
-
(v) => {
|
|
91
|
-
if (!v) {
|
|
92
|
-
state.dataUpdated = false
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* 「打开行 / 切换 type」时记录的基线快照
|
|
99
|
-
* @description
|
|
100
|
-
* 用于 changeCb 内部比对 — 若新值与基线相等(深比较)视为无改动,避免某些组件在 mount
|
|
101
|
-
* 阶段把规范化后的值再写回模型时被误判为「用户已修改」
|
|
102
|
-
*/
|
|
103
|
-
let baselineSnapshot: Record<string, any> | undefined
|
|
104
|
-
let emptySnapshot: Record<string, any> | undefined
|
|
105
|
-
|
|
106
84
|
function snapshotModelData() {
|
|
107
85
|
return snapshotData(props.model)
|
|
108
86
|
}
|
|
109
87
|
|
|
110
|
-
function
|
|
111
|
-
if (!props.model) return
|
|
112
|
-
replaceModelData(props.model, snapshotData(data ?? emptySnapshot))
|
|
113
|
-
formRef.value?.clearValidate()
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function setFormData(formData: Record<string, any>) {
|
|
88
|
+
function setFormData(formData?: Record<string, any>) {
|
|
117
89
|
if (!props.model) return
|
|
118
90
|
replaceModelData(props.model, snapshotData(formData))
|
|
119
91
|
}
|
|
120
92
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
{ immediate: true }
|
|
127
|
-
)
|
|
93
|
+
const isQuickMode = () => props.mode === 'quick'
|
|
94
|
+
|
|
95
|
+
async function runBeforeCreate(data: Record<string, any>) {
|
|
96
|
+
await props.beforeCreate?.(data, state.parentRow?.data)
|
|
97
|
+
}
|
|
128
98
|
|
|
129
99
|
watch(
|
|
130
100
|
() => state.row,
|
|
131
101
|
(row) => {
|
|
132
|
-
resetFormData()
|
|
133
102
|
if (row) {
|
|
134
103
|
state.type = 'update'
|
|
135
104
|
state.visible = true
|
|
136
|
-
|
|
105
|
+
if (!isQuickMode()) {
|
|
106
|
+
setFormData(row.data)
|
|
107
|
+
}
|
|
137
108
|
} else {
|
|
138
109
|
state.visible = false
|
|
139
110
|
}
|
|
140
|
-
baselineSnapshot = snapshotModelData()
|
|
141
|
-
state.dataUpdated = false
|
|
142
111
|
},
|
|
143
|
-
// sync 保证 setData 完成后立刻取快照,避免 mount 期间组件回写值时丢基线
|
|
144
112
|
{ flush: 'sync' }
|
|
145
113
|
)
|
|
146
114
|
|
|
147
|
-
watch(
|
|
148
|
-
() => props.model,
|
|
149
|
-
() => {
|
|
150
|
-
if (!baselineSnapshot || !state.visible) return
|
|
151
|
-
state.dataUpdated = !isSameValue(baselineSnapshot, snapshotModelData())
|
|
152
|
-
},
|
|
153
|
-
{ deep: true }
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
function isSameValue(a: any, b: any) {
|
|
157
|
-
if (a === b) return true
|
|
158
|
-
if (a == null && b == null) return true
|
|
159
|
-
if (typeof a !== typeof b) return false
|
|
160
|
-
if (typeof a === 'object') {
|
|
161
|
-
try {
|
|
162
|
-
return JSON.stringify(a) === JSON.stringify(b)
|
|
163
|
-
} catch {
|
|
164
|
-
return false
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
return false
|
|
168
|
-
}
|
|
169
|
-
|
|
170
115
|
const childrenKey = computed(() => {
|
|
171
116
|
return typeof props.tree === 'string' ? props.tree : 'children'
|
|
172
117
|
})
|
|
@@ -204,59 +149,53 @@ export function useEdit(options: Options): EditReturned {
|
|
|
204
149
|
return snapshotModelData()
|
|
205
150
|
}
|
|
206
151
|
|
|
207
|
-
async function runCreate(cb: () => void) {
|
|
152
|
+
async function runCreate(cb: () => void, initialData?: Record<string, any>) {
|
|
208
153
|
state.parentRow = undefined
|
|
209
154
|
if (state.row) {
|
|
210
155
|
state.row.isCurrent = false
|
|
211
156
|
}
|
|
212
157
|
state.row = undefined
|
|
213
158
|
state.type = 'create'
|
|
214
|
-
resetFormData()
|
|
215
159
|
cb()
|
|
216
160
|
|
|
217
|
-
let item: Record<string, any> | undefined = undefined
|
|
218
|
-
|
|
219
161
|
await nextTick()
|
|
220
162
|
|
|
221
|
-
|
|
222
|
-
baselineSnapshot = snapshotModelData()
|
|
223
|
-
state.dataUpdated = false
|
|
163
|
+
setFormData(initialData)
|
|
224
164
|
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
if (row) {
|
|
228
|
-
state.row = row
|
|
229
|
-
}
|
|
165
|
+
if (isQuickMode() && props.model) {
|
|
166
|
+
await runBeforeCreate(props.model)
|
|
230
167
|
}
|
|
168
|
+
|
|
169
|
+
state.visible = true
|
|
231
170
|
}
|
|
232
171
|
|
|
233
172
|
/**
|
|
234
173
|
* 点击新增按钮
|
|
235
174
|
*/
|
|
236
|
-
function handleCreate() {
|
|
175
|
+
async function handleCreate() {
|
|
237
176
|
const { data } = props
|
|
238
|
-
runCreate(() => {
|
|
177
|
+
await runCreate(() => {
|
|
239
178
|
state.depth = 1
|
|
240
179
|
insertIndexes.value = [data?.length ?? 0]
|
|
241
180
|
})
|
|
242
181
|
}
|
|
243
182
|
|
|
244
|
-
function handleInsertToPrev(row: TableRow) {
|
|
245
|
-
runCreate(() => {
|
|
183
|
+
async function handleInsertToPrev(row: TableRow) {
|
|
184
|
+
await runCreate(() => {
|
|
246
185
|
state.depth = row.depth
|
|
247
186
|
insertIndexes.value = [...row.indexes]
|
|
248
187
|
})
|
|
249
188
|
}
|
|
250
189
|
|
|
251
|
-
function handleInsertToNext(row: TableRow) {
|
|
252
|
-
runCreate(() => {
|
|
190
|
+
async function handleInsertToNext(row: TableRow) {
|
|
191
|
+
await runCreate(() => {
|
|
253
192
|
state.depth = row.depth
|
|
254
193
|
insertIndexes.value = [...row.indexes.slice(0, -1), row.index + 1]
|
|
255
194
|
})
|
|
256
195
|
}
|
|
257
196
|
|
|
258
|
-
function handleInsertChild(row: TableRow) {
|
|
259
|
-
runCreate(() => {
|
|
197
|
+
async function handleInsertChild(row: TableRow) {
|
|
198
|
+
await runCreate(() => {
|
|
260
199
|
state.parentRow = row
|
|
261
200
|
state.depth = row.depth + 1
|
|
262
201
|
row.expanded = true
|
|
@@ -267,19 +206,17 @@ export function useEdit(options: Options): EditReturned {
|
|
|
267
206
|
|
|
268
207
|
/** 编辑指定行(等同于点击行) */
|
|
269
208
|
function handleEdit(row: TableRow) {
|
|
209
|
+
state.type = 'update'
|
|
270
210
|
state.row = row
|
|
271
211
|
state.depth = row.depth
|
|
272
212
|
}
|
|
273
213
|
|
|
274
214
|
/** 复制指定行:在其下方以 create 模式打开表单,并预填副本数据 */
|
|
275
|
-
function handleCopy(row: TableRow) {
|
|
276
|
-
runCreate(() => {
|
|
215
|
+
async function handleCopy(row: TableRow) {
|
|
216
|
+
await runCreate(() => {
|
|
277
217
|
state.depth = row.depth
|
|
278
218
|
insertIndexes.value = [...row.indexes.slice(0, -1), row.index + 1]
|
|
279
|
-
|
|
280
|
-
baselineSnapshot = snapshotModelData()
|
|
281
|
-
state.dataUpdated = false
|
|
282
|
-
})
|
|
219
|
+
}, row.data)
|
|
283
220
|
}
|
|
284
221
|
|
|
285
222
|
function runWithLoading<Arg extends any[]>(fn: (...args: Arg) => Promise<void> | void) {
|
|
@@ -306,36 +243,47 @@ export function useEdit(options: Options): EditReturned {
|
|
|
306
243
|
|
|
307
244
|
if (!model) return
|
|
308
245
|
|
|
246
|
+
if (isQuickMode() && state.type === 'update') return
|
|
247
|
+
|
|
309
248
|
formRef.value?.clearValidate()
|
|
310
249
|
const valid = await formRef.value?.validate()
|
|
311
250
|
|
|
312
251
|
if (!valid) return
|
|
313
252
|
|
|
314
|
-
|
|
315
|
-
if (
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
item = result
|
|
319
|
-
}
|
|
253
|
+
// quick + create:仅校验 + insert
|
|
254
|
+
if (isQuickMode() && state.type === 'create') {
|
|
255
|
+
insert(getInsertData())
|
|
256
|
+
return
|
|
320
257
|
}
|
|
321
258
|
|
|
322
|
-
|
|
259
|
+
let item = getInsertData()
|
|
323
260
|
|
|
324
|
-
//
|
|
261
|
+
// normal + create:beforeCreate → saveMethod → insert
|
|
325
262
|
if (state.type === 'create') {
|
|
263
|
+
await runBeforeCreate(item)
|
|
264
|
+
if (saveMethod) {
|
|
265
|
+
const result = await saveMethod(item, state.type, state.parentRow?.data)
|
|
266
|
+
if (result) {
|
|
267
|
+
item = result
|
|
268
|
+
}
|
|
269
|
+
}
|
|
326
270
|
insert(item)
|
|
327
|
-
|
|
328
|
-
resetFormData()
|
|
329
271
|
return
|
|
330
272
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
273
|
+
|
|
274
|
+
// normal + update
|
|
275
|
+
if (saveMethod) {
|
|
276
|
+
const result = await saveMethod(item, state.type, state.parentRow?.data)
|
|
277
|
+
if (result) {
|
|
278
|
+
item = result
|
|
279
|
+
}
|
|
338
280
|
}
|
|
281
|
+
|
|
282
|
+
const { row } = state
|
|
283
|
+
row &&
|
|
284
|
+
Object.keys(item).forEach((key) => {
|
|
285
|
+
o(row.data).set(key, o(item).get(key))
|
|
286
|
+
})
|
|
339
287
|
})
|
|
340
288
|
|
|
341
289
|
const handleDelete = runWithLoading(async (row: TableRow) => {
|
|
@@ -376,7 +324,6 @@ export function useEdit(options: Options): EditReturned {
|
|
|
376
324
|
state.row = undefined
|
|
377
325
|
state.parentRow = undefined
|
|
378
326
|
state.depth = undefined
|
|
379
|
-
resetFormData()
|
|
380
327
|
insertIndexes.value = []
|
|
381
328
|
}
|
|
382
329
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { computed } from 'vue'
|
|
2
|
+
|
|
3
|
+
import type { BatchEditFeature, BatchEditProps, TableRow } from '../../types'
|
|
4
|
+
|
|
5
|
+
interface Options {
|
|
6
|
+
props: BatchEditProps
|
|
7
|
+
}
|
|
8
|
+
export function useFeatures(options: Options) {
|
|
9
|
+
const { props } = options
|
|
10
|
+
const staticFeatures = computed(() => {
|
|
11
|
+
const { features } = props
|
|
12
|
+
|
|
13
|
+
if (Array.isArray(features)) {
|
|
14
|
+
return new Set(features)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const defaultFeatures = new Set<BatchEditFeature>(['create', 'delete', 'update', 'createChild'])
|
|
18
|
+
|
|
19
|
+
if (!features) {
|
|
20
|
+
return defaultFeatures
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 函数与 false 的特性视为关闭;显式 true 视为开启(即便不在默认集合内)
|
|
24
|
+
Object.entries(features).forEach(([key, value]) => {
|
|
25
|
+
const k = key as BatchEditFeature
|
|
26
|
+
if (typeof value === 'function' || value === false) {
|
|
27
|
+
defaultFeatures.delete(k)
|
|
28
|
+
} else if (value === true) {
|
|
29
|
+
defaultFeatures.add(k)
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
return defaultFeatures
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const dynamicFeatures = computed<
|
|
36
|
+
Record<BatchEditFeature, ((row?: TableRow) => boolean) | undefined>
|
|
37
|
+
>(() => {
|
|
38
|
+
const { features } = props
|
|
39
|
+
if (!Array.isArray(features) && typeof features === 'object') {
|
|
40
|
+
const ret = Object.entries(features)
|
|
41
|
+
.filter(([_, value]) => {
|
|
42
|
+
return typeof value === 'function'
|
|
43
|
+
})
|
|
44
|
+
.reduce(
|
|
45
|
+
(acc, [key, value]) => {
|
|
46
|
+
acc[key] = value
|
|
47
|
+
return acc
|
|
48
|
+
},
|
|
49
|
+
{} as Record<BatchEditFeature, (row?: TableRow) => boolean>
|
|
50
|
+
)
|
|
51
|
+
return ret
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {} as Record<BatchEditFeature, ((row?: TableRow) => boolean) | undefined>
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
return { staticFeatures, dynamicFeatures }
|
|
58
|
+
}
|
|
@@ -78,15 +78,15 @@ $arrow-size: 12px;
|
|
|
78
78
|
|
|
79
79
|
#{$b}__item:not(.is-disabled):not(.is-loading) > & {
|
|
80
80
|
&:hover {
|
|
81
|
-
background-color: var(--u-
|
|
82
|
-
color: var(--u-
|
|
81
|
+
background-color: var(--u-nav-hover-bg);
|
|
82
|
+
color: var(--u-nav-hover-color);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
#{$b}__item.is-open:not(.is-disabled) > #{$b}__item-content {
|
|
88
|
-
background-color: var(--u-
|
|
89
|
-
color: var(--u-
|
|
88
|
+
background-color: var(--u-nav-hover-bg);
|
|
89
|
+
color: var(--u-nav-hover-color);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
@include m.e(icon-place) {
|
|
@@ -128,7 +128,7 @@ $arrow-size: 12px;
|
|
|
128
128
|
|
|
129
129
|
@include m.e(item-content) {
|
|
130
130
|
padding: 0 fn.use-var(gap, $size);
|
|
131
|
-
min-height: calc(var(--u-
|
|
131
|
+
min-height: calc(var(--u-nav-height-#{$size}) - 8px);
|
|
132
132
|
border-radius: calc(fn.use-var(radius, $size) + 1px);
|
|
133
133
|
}
|
|
134
134
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<u-tip direction="right" hide-arrow>
|
|
3
|
+
<button
|
|
4
|
+
type="button"
|
|
5
|
+
:class="[cls.e('app'), bem.is('active', active), bem.is('disabled', app.disabled ?? false)]"
|
|
6
|
+
:disabled="app.disabled"
|
|
7
|
+
@click="emit('click')"
|
|
8
|
+
>
|
|
9
|
+
<UNavIcon v-if="app.icon" :icon="app.icon" :class="cls.e('app-icon')" />
|
|
10
|
+
<span v-else :class="cls.e('app-fallback')">{{ app.title[0] }}</span>
|
|
11
|
+
</button>
|
|
12
|
+
|
|
13
|
+
<template #content>
|
|
14
|
+
{{ app.title }}
|
|
15
|
+
</template>
|
|
16
|
+
</u-tip>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script setup lang="ts">
|
|
20
|
+
import { bem } from '@veltra/utils'
|
|
21
|
+
|
|
22
|
+
import type { NavItem } from '../../types'
|
|
23
|
+
import UNavIcon from '../nav/nav-icon.vue'
|
|
24
|
+
import { UTip } from '../tip'
|
|
25
|
+
|
|
26
|
+
defineOptions({ name: 'DualNavApp' })
|
|
27
|
+
|
|
28
|
+
const cls = bem('dual-nav')
|
|
29
|
+
|
|
30
|
+
defineProps<{ app: NavItem; active: boolean }>()
|
|
31
|
+
|
|
32
|
+
const emit = defineEmits<{ click: [] }>()
|
|
33
|
+
</script>
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="cls.b">
|
|
3
|
+
<div :class="cls.e('rail')">
|
|
4
|
+
<DualNavApp
|
|
5
|
+
v-for="app in menus"
|
|
6
|
+
:key="app.path"
|
|
7
|
+
:app="app"
|
|
8
|
+
:active="isAppActive(app)"
|
|
9
|
+
@click="handleAppClick(app)"
|
|
10
|
+
/>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div :class="cls.e('divider')" aria-hidden="true" />
|
|
14
|
+
|
|
15
|
+
<div :class="cls.e('panel')">
|
|
16
|
+
<template v-if="panelApp">
|
|
17
|
+
<div :class="cls.e('panel-header')">
|
|
18
|
+
<div :class="cls.e('panel-header-main')">
|
|
19
|
+
<h3 :class="cls.e('panel-title')">{{ panelApp.title }}</h3>
|
|
20
|
+
<p v-if="panelApp.description" :class="cls.e('description')">
|
|
21
|
+
{{ panelApp.description }}
|
|
22
|
+
</p>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<u-tip
|
|
26
|
+
v-if="hasExpandableMenus"
|
|
27
|
+
:content="allExpanded ? '折叠全部' : '展开全部'"
|
|
28
|
+
hide-arrow
|
|
29
|
+
>
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
:class="cls.e('toggle-expand')"
|
|
33
|
+
:aria-label="allExpanded ? '折叠全部' : '展开全部'"
|
|
34
|
+
@click="handleToggleExpandAll"
|
|
35
|
+
>
|
|
36
|
+
<u-icon>
|
|
37
|
+
<FolderOpened v-if="!allExpanded" />
|
|
38
|
+
<Folder v-else />
|
|
39
|
+
</u-icon>
|
|
40
|
+
</button>
|
|
41
|
+
</u-tip>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div :class="cls.e('panel-body')">
|
|
45
|
+
<UNav
|
|
46
|
+
ref="navRef"
|
|
47
|
+
:menus="panelMenus"
|
|
48
|
+
:current-path="currentPath"
|
|
49
|
+
:collapsed="false"
|
|
50
|
+
@item-click="emit('item-click', $event)"
|
|
51
|
+
/>
|
|
52
|
+
</div>
|
|
53
|
+
</template>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
<script lang="ts" setup>
|
|
59
|
+
import { Folder, FolderOpened } from '@veltra/icons/normal'
|
|
60
|
+
import { bem } from '@veltra/utils'
|
|
61
|
+
import { computed, ref, useTemplateRef, watch } from 'vue'
|
|
62
|
+
|
|
63
|
+
import type { DualNavEmits, DualNavProps, DualNavRootItem } from '../../types/dual-nav'
|
|
64
|
+
import type { NavExposed, NavItem } from '../../types/nav'
|
|
65
|
+
import { UIcon } from '../icon'
|
|
66
|
+
import { UNav } from '../nav'
|
|
67
|
+
import { collectNavBranchPaths } from '../nav/helper'
|
|
68
|
+
import { UTip } from '../tip'
|
|
69
|
+
import DualNavApp from './dual-nav-app.vue'
|
|
70
|
+
import { findRootApp } from './helper'
|
|
71
|
+
|
|
72
|
+
defineOptions({ name: 'DualNav' })
|
|
73
|
+
|
|
74
|
+
const props = defineProps<DualNavProps>()
|
|
75
|
+
const emit = defineEmits<DualNavEmits>()
|
|
76
|
+
|
|
77
|
+
const cls = bem('dual-nav')
|
|
78
|
+
const navRef = useTemplateRef<NavExposed>('navRef')
|
|
79
|
+
|
|
80
|
+
/** 右栏当前展示的应用(点击左轨或 currentPath 同步) */
|
|
81
|
+
const panelAppPath = ref<string>()
|
|
82
|
+
const allExpanded = ref(false)
|
|
83
|
+
|
|
84
|
+
function syncPanelAppFromCurrentPath(currentPath?: string) {
|
|
85
|
+
const rootApp = findRootApp(props.menus, currentPath)
|
|
86
|
+
if (rootApp) {
|
|
87
|
+
panelAppPath.value = rootApp.path
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!panelAppPath.value && props.menus?.length) {
|
|
92
|
+
panelAppPath.value = props.menus[0]!.path
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
watch(
|
|
97
|
+
() => [props.currentPath, props.menus] as const,
|
|
98
|
+
([currentPath]) => {
|
|
99
|
+
syncPanelAppFromCurrentPath(currentPath)
|
|
100
|
+
},
|
|
101
|
+
{ immediate: true }
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
const panelApp = computed<DualNavRootItem | undefined>(() => {
|
|
105
|
+
if (!panelAppPath.value || !props.menus?.length) return undefined
|
|
106
|
+
return props.menus.find((app) => app.path === panelAppPath.value)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
/** 右栏菜单:有子级时展示子级,单层时展示根项自身 */
|
|
110
|
+
const panelMenus = computed<NavItem[] | undefined>(() => {
|
|
111
|
+
const app = panelApp.value
|
|
112
|
+
if (!app) return undefined
|
|
113
|
+
if (app.children?.length) return app.children
|
|
114
|
+
return [app]
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const hasExpandableMenus = computed(() => {
|
|
118
|
+
return collectNavBranchPaths(panelMenus.value).length > 0
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
watch(panelAppPath, () => {
|
|
122
|
+
allExpanded.value = false
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
/** 左轨高亮:仅当 currentPath 落在该应用下时激活 */
|
|
126
|
+
function isAppActive(app: NavItem): boolean {
|
|
127
|
+
return findRootApp(props.menus, props.currentPath)?.path === app.path
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function handleAppClick(app: NavItem) {
|
|
131
|
+
if (app.disabled) return
|
|
132
|
+
|
|
133
|
+
panelAppPath.value = app.path
|
|
134
|
+
|
|
135
|
+
if (!app.children?.length) {
|
|
136
|
+
emit('item-click', app)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function handleToggleExpandAll() {
|
|
141
|
+
if (allExpanded.value) {
|
|
142
|
+
navRef.value?.collapseAll()
|
|
143
|
+
allExpanded.value = false
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
navRef.value?.expandAll()
|
|
148
|
+
allExpanded.value = true
|
|
149
|
+
}
|
|
150
|
+
</script>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { DualNavRootItem } from '../../types'
|
|
2
|
+
import { walkNavWithPath } from '../nav/walk-nav-path'
|
|
3
|
+
|
|
4
|
+
/** 根据当前路径反查所属顶层应用 */
|
|
5
|
+
export function findRootApp(
|
|
6
|
+
menus: DualNavRootItem[] | undefined,
|
|
7
|
+
currentPath?: string
|
|
8
|
+
): DualNavRootItem | undefined {
|
|
9
|
+
if (!currentPath || !menus?.length) return undefined
|
|
10
|
+
|
|
11
|
+
for (const app of menus) {
|
|
12
|
+
if (app.path === currentPath) return app
|
|
13
|
+
|
|
14
|
+
let matched = false
|
|
15
|
+
walkNavWithPath(app, (node) => {
|
|
16
|
+
if (node.path === currentPath) {
|
|
17
|
+
matched = true
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
if (matched) return app
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return undefined
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as UDualNav } from './dual-nav.vue'
|