@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,10 +1,9 @@
|
|
|
1
1
|
import { TableColumn, TableColumnSlotsScope, TableRow } from "../../types/table.js";
|
|
2
|
-
import { BatchEditProps } from "../../types/batch-edit.js";
|
|
2
|
+
import { BatchEditMode, BatchEditProps } from "../../types/batch-edit.js";
|
|
3
3
|
//#region src/components/batch-edit/batch-edit.vue.d.ts
|
|
4
4
|
type __VLS_Slots = {
|
|
5
5
|
form?: (props: {
|
|
6
|
-
/**
|
|
7
|
-
depth?: number; /** 当前编辑的行 */
|
|
6
|
+
/** 当前编辑的层级 */depth?: number; /** 当前编辑的行 */
|
|
8
7
|
row?: TableRow; /** 当前编辑的行索引 */
|
|
9
8
|
index?: number; /** 操作的目标行索引路径 */
|
|
10
9
|
indexes?: number[];
|
|
@@ -33,6 +32,7 @@ declare const __VLS_base: import("vue").DefineComponent<BatchEditProps, {}, {},
|
|
|
33
32
|
"onRow-click"?: ((row: TableRow, ev: MouseEvent) => any) | undefined;
|
|
34
33
|
}>, {
|
|
35
34
|
cols: string | [string, string];
|
|
35
|
+
mode: BatchEditMode;
|
|
36
36
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
37
37
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
38
38
|
declare const _default: typeof __VLS_export;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"di.js","names":[],"sources":["../../../src/components/batch-edit/di.ts"],"sourcesContent":["import type { BEM } from '@veltra/utils'\nimport type { ComputedRef, InjectionKey, ShallowRef } from 'vue'\n\nimport type {\n BatchEditEmits,\n BatchEditFeature,\n BatchEditProps,\n TableExposed,\n TableRow\n} from '../../types'\nimport type { EditReturned } from './use-edit'\nexport const BatchEditDIKey: InjectionKey<\n {\n cls: BEM<'batch-edit', 'u-'>\n props: BatchEditProps\n emit: BatchEditEmits\n tableRef: ShallowRef<TableExposed | undefined>\n staticFeatures: ComputedRef<Set<BatchEditFeature>>\n dynamicFeatures: ComputedRef<\n Record<BatchEditFeature, ((row?: TableRow) => boolean) | undefined>\n >\n } & EditReturned\n> = Symbol('BatchEditDIKey')\n"],"mappings":";AAWA,MAAa,
|
|
1
|
+
{"version":3,"file":"di.js","names":[],"sources":["../../../src/components/batch-edit/di.ts"],"sourcesContent":["import type { BEM } from '@veltra/utils'\nimport type { ComputedRef, InjectionKey, ShallowRef, Ref } from 'vue'\n\nimport type {\n BatchEditEmits,\n BatchEditFeature,\n BatchEditProps,\n TableExposed,\n TableRow\n} from '../../types'\nimport type { EditReturned } from './use-edit'\nexport const BatchEditDIKey: InjectionKey<\n {\n cls: BEM<'batch-edit', 'u-'>\n props: BatchEditProps\n emit: BatchEditEmits\n tableRef: ShallowRef<TableExposed | undefined>\n staticFeatures: ComputedRef<Set<BatchEditFeature>>\n dynamicFeatures: ComputedRef<\n Record<BatchEditFeature, ((row?: TableRow) => boolean) | undefined>\n >\n /** 组件或其子节点是否处于聚焦状态 */\n focused: Ref<boolean>\n } & EditReturned\n> = Symbol('BatchEditDIKey')\n"],"mappings":";AAWA,MAAa,iBAaT,OAAO,gBAAgB"}
|
|
@@ -26,67 +26,33 @@ function replaceModelData(model, data) {
|
|
|
26
26
|
Object.assign(model, data);
|
|
27
27
|
}
|
|
28
28
|
function useEdit(options) {
|
|
29
|
-
const { props, emit,
|
|
29
|
+
const { props, emit, formRef } = options;
|
|
30
30
|
/** 组件状态 */
|
|
31
31
|
const state = shallowReactive({
|
|
32
32
|
depth: void 0,
|
|
33
33
|
visible: false,
|
|
34
34
|
type: "create",
|
|
35
|
-
loading: false
|
|
36
|
-
dataUpdated: false
|
|
35
|
+
loading: false
|
|
37
36
|
});
|
|
38
37
|
const insertIndexes = shallowRef([]);
|
|
39
|
-
watch(() => state.visible, (v) => {
|
|
40
|
-
if (!v) state.dataUpdated = false;
|
|
41
|
-
});
|
|
42
|
-
/**
|
|
43
|
-
* 「打开行 / 切换 type」时记录的基线快照
|
|
44
|
-
* @description
|
|
45
|
-
* 用于 changeCb 内部比对 — 若新值与基线相等(深比较)视为无改动,避免某些组件在 mount
|
|
46
|
-
* 阶段把规范化后的值再写回模型时被误判为「用户已修改」
|
|
47
|
-
*/
|
|
48
|
-
let baselineSnapshot;
|
|
49
|
-
let emptySnapshot;
|
|
50
38
|
function snapshotModelData() {
|
|
51
39
|
return snapshotData(props.model);
|
|
52
40
|
}
|
|
53
|
-
function resetFormData(data) {
|
|
54
|
-
if (!props.model) return;
|
|
55
|
-
replaceModelData(props.model, snapshotData(data ?? emptySnapshot));
|
|
56
|
-
formRef.value?.clearValidate();
|
|
57
|
-
}
|
|
58
41
|
function setFormData(formData) {
|
|
59
42
|
if (!props.model) return;
|
|
60
43
|
replaceModelData(props.model, snapshotData(formData));
|
|
61
44
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
45
|
+
const isQuickMode = () => props.mode === "quick";
|
|
46
|
+
async function runBeforeCreate(data) {
|
|
47
|
+
await props.beforeCreate?.(data, state.parentRow?.data);
|
|
48
|
+
}
|
|
65
49
|
watch(() => state.row, (row) => {
|
|
66
|
-
resetFormData();
|
|
67
50
|
if (row) {
|
|
68
51
|
state.type = "update";
|
|
69
52
|
state.visible = true;
|
|
70
|
-
setFormData(row.data);
|
|
53
|
+
if (!isQuickMode()) setFormData(row.data);
|
|
71
54
|
} else state.visible = false;
|
|
72
|
-
baselineSnapshot = snapshotModelData();
|
|
73
|
-
state.dataUpdated = false;
|
|
74
55
|
}, { flush: "sync" });
|
|
75
|
-
watch(() => props.model, () => {
|
|
76
|
-
if (!baselineSnapshot || !state.visible) return;
|
|
77
|
-
state.dataUpdated = !isSameValue(baselineSnapshot, snapshotModelData());
|
|
78
|
-
}, { deep: true });
|
|
79
|
-
function isSameValue(a, b) {
|
|
80
|
-
if (a === b) return true;
|
|
81
|
-
if (a == null && b == null) return true;
|
|
82
|
-
if (typeof a !== typeof b) return false;
|
|
83
|
-
if (typeof a === "object") try {
|
|
84
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
85
|
-
} catch {
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
56
|
const childrenKey = computed(() => {
|
|
91
57
|
return typeof props.tree === "string" ? props.tree : "children";
|
|
92
58
|
});
|
|
@@ -108,42 +74,41 @@ function useEdit(options) {
|
|
|
108
74
|
function getInsertData() {
|
|
109
75
|
return snapshotModelData();
|
|
110
76
|
}
|
|
111
|
-
async function runCreate(cb) {
|
|
77
|
+
async function runCreate(cb, initialData) {
|
|
112
78
|
state.parentRow = void 0;
|
|
113
79
|
if (state.row) state.row.isCurrent = false;
|
|
114
80
|
state.row = void 0;
|
|
115
81
|
state.type = "create";
|
|
116
|
-
resetFormData();
|
|
117
82
|
cb();
|
|
118
83
|
await nextTick();
|
|
84
|
+
setFormData(initialData);
|
|
85
|
+
if (isQuickMode() && props.model) await runBeforeCreate(props.model);
|
|
119
86
|
state.visible = true;
|
|
120
|
-
baselineSnapshot = snapshotModelData();
|
|
121
|
-
state.dataUpdated = false;
|
|
122
87
|
}
|
|
123
88
|
/**
|
|
124
89
|
* 点击新增按钮
|
|
125
90
|
*/
|
|
126
|
-
function handleCreate() {
|
|
91
|
+
async function handleCreate() {
|
|
127
92
|
const { data } = props;
|
|
128
|
-
runCreate(() => {
|
|
93
|
+
await runCreate(() => {
|
|
129
94
|
state.depth = 1;
|
|
130
95
|
insertIndexes.value = [data?.length ?? 0];
|
|
131
96
|
});
|
|
132
97
|
}
|
|
133
|
-
function handleInsertToPrev(row) {
|
|
134
|
-
runCreate(() => {
|
|
98
|
+
async function handleInsertToPrev(row) {
|
|
99
|
+
await runCreate(() => {
|
|
135
100
|
state.depth = row.depth;
|
|
136
101
|
insertIndexes.value = [...row.indexes];
|
|
137
102
|
});
|
|
138
103
|
}
|
|
139
|
-
function handleInsertToNext(row) {
|
|
140
|
-
runCreate(() => {
|
|
104
|
+
async function handleInsertToNext(row) {
|
|
105
|
+
await runCreate(() => {
|
|
141
106
|
state.depth = row.depth;
|
|
142
107
|
insertIndexes.value = [...row.indexes.slice(0, -1), row.index + 1];
|
|
143
108
|
});
|
|
144
109
|
}
|
|
145
|
-
function handleInsertChild(row) {
|
|
146
|
-
runCreate(() => {
|
|
110
|
+
async function handleInsertChild(row) {
|
|
111
|
+
await runCreate(() => {
|
|
147
112
|
state.parentRow = row;
|
|
148
113
|
state.depth = row.depth + 1;
|
|
149
114
|
row.expanded = true;
|
|
@@ -152,18 +117,16 @@ function useEdit(options) {
|
|
|
152
117
|
}
|
|
153
118
|
/** 编辑指定行(等同于点击行) */
|
|
154
119
|
function handleEdit(row) {
|
|
120
|
+
state.type = "update";
|
|
155
121
|
state.row = row;
|
|
156
122
|
state.depth = row.depth;
|
|
157
123
|
}
|
|
158
124
|
/** 复制指定行:在其下方以 create 模式打开表单,并预填副本数据 */
|
|
159
|
-
function handleCopy(row) {
|
|
160
|
-
runCreate(() => {
|
|
125
|
+
async function handleCopy(row) {
|
|
126
|
+
await runCreate(() => {
|
|
161
127
|
state.depth = row.depth;
|
|
162
128
|
insertIndexes.value = [...row.indexes.slice(0, -1), row.index + 1];
|
|
163
|
-
|
|
164
|
-
baselineSnapshot = snapshotModelData();
|
|
165
|
-
state.dataUpdated = false;
|
|
166
|
-
});
|
|
129
|
+
}, row.data);
|
|
167
130
|
}
|
|
168
131
|
function runWithLoading(fn) {
|
|
169
132
|
return async (...args) => {
|
|
@@ -182,25 +145,31 @@ function useEdit(options) {
|
|
|
182
145
|
const handleSave = runWithLoading(async () => {
|
|
183
146
|
const { model, saveMethod } = props;
|
|
184
147
|
if (!model) return;
|
|
148
|
+
if (isQuickMode() && state.type === "update") return;
|
|
185
149
|
formRef.value?.clearValidate();
|
|
186
150
|
if (!await formRef.value?.validate()) return;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (result) item = result;
|
|
151
|
+
if (isQuickMode() && state.type === "create") {
|
|
152
|
+
insert(getInsertData());
|
|
153
|
+
return;
|
|
191
154
|
}
|
|
192
|
-
|
|
155
|
+
let item = getInsertData();
|
|
193
156
|
if (state.type === "create") {
|
|
157
|
+
await runBeforeCreate(item);
|
|
158
|
+
if (saveMethod) {
|
|
159
|
+
const result = await saveMethod(item, state.type, state.parentRow?.data);
|
|
160
|
+
if (result) item = result;
|
|
161
|
+
}
|
|
194
162
|
insert(item);
|
|
195
|
-
resetFormData();
|
|
196
163
|
return;
|
|
197
164
|
}
|
|
198
|
-
if (
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
o(row.data).set(key, o(item).get(key));
|
|
202
|
-
});
|
|
165
|
+
if (saveMethod) {
|
|
166
|
+
const result = await saveMethod(item, state.type, state.parentRow?.data);
|
|
167
|
+
if (result) item = result;
|
|
203
168
|
}
|
|
169
|
+
const { row } = state;
|
|
170
|
+
row && Object.keys(item).forEach((key) => {
|
|
171
|
+
o(row.data).set(key, o(item).get(key));
|
|
172
|
+
});
|
|
204
173
|
});
|
|
205
174
|
const handleDelete = runWithLoading(async (row) => {
|
|
206
175
|
const { deleteMethod } = props;
|
|
@@ -224,7 +193,6 @@ function useEdit(options) {
|
|
|
224
193
|
state.row = void 0;
|
|
225
194
|
state.parentRow = void 0;
|
|
226
195
|
state.depth = void 0;
|
|
227
|
-
resetFormData();
|
|
228
196
|
insertIndexes.value = [];
|
|
229
197
|
}
|
|
230
198
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-edit.js","names":[],"sources":["../../../src/components/batch-edit/use-edit.ts"],"sourcesContent":["import { last, o, safeRun } from '@cat-kit/core'\nimport { computed, nextTick, shallowReactive, shallowRef, watch, type ShallowRef } from 'vue'\n\nimport type { TableRow, BatchEditEmits, BatchEditProps, TableExposed } from '../../types'\nimport type { FormExposed } from '../../types/form'\n\n/** 按索引路径在原始数据树中定位节点(等价于历史 Forest.visit 路径访问) */\nfunction visitDataByIndexPath<T extends Record<string, unknown>>(\n roots: T[],\n indexPath: number[],\n childrenKey: string\n): T | undefined {\n if (!indexPath.length) return undefined\n let currentList: T[] | undefined = roots\n let node: T | undefined\n for (let i = 0; i < indexPath.length; i++) {\n const idx = indexPath[i]!\n if (!currentList) return undefined\n node = currentList[idx]\n if (node === undefined) return undefined\n if (i < indexPath.length - 1) {\n const next = node[childrenKey]\n currentList = Array.isArray(next) ? (next as T[]) : undefined\n }\n }\n return node\n}\n\ninterface Options {\n props: BatchEditProps\n emit: BatchEditEmits\n tableRef: ShallowRef<TableExposed | undefined>\n formRef: ShallowRef<FormExposed | undefined>\n}\n\nexport interface BatchEditStates {\n /** 层级 */\n depth?: number\n visible: boolean\n type: 'create' | 'update'\n loading: boolean\n row?: TableRow\n /** 父级,只在添加子级时会有值 */\n parentRow?: TableRow\n /** 数据是否已更新 */\n dataUpdated: boolean\n}\n\nexport interface EditReturned {\n state: BatchEditStates\n insertIndexes: ShallowRef<number[]>\n handleSave: () => Promise<void>\n handleClose: () => void\n handleCreate: () => void\n handleEdit: (row: TableRow) => void\n handleCopy: (row: TableRow) => void\n handleDelete: (row: TableRow) => Promise<void>\n handleInsertToNext: (row: TableRow) => void\n handleInsertToPrev: (row: TableRow) => void\n handleInsertChild: (row: TableRow) => void\n}\n\nfunction snapshotData(data?: Record<string, any>) {\n return safeRun(() => JSON.parse(JSON.stringify(data ?? {})), {})\n}\n\nfunction replaceModelData(model: Record<string, any>, data: Record<string, any>) {\n for (const key of Object.keys(model)) {\n delete model[key]\n }\n Object.assign(model, data)\n}\n\nexport function useEdit(options: Options): EditReturned {\n const { props, emit, tableRef, formRef } = options\n\n /** 组件状态 */\n const state = shallowReactive<BatchEditStates>({\n depth: undefined,\n visible: false,\n type: 'create',\n loading: false,\n dataUpdated: false\n })\n\n const insertIndexes = shallowRef<number[]>([])\n\n watch(\n () => state.visible,\n (v) => {\n if (!v) {\n state.dataUpdated = false\n }\n }\n )\n\n /**\n * 「打开行 / 切换 type」时记录的基线快照\n * @description\n * 用于 changeCb 内部比对 — 若新值与基线相等(深比较)视为无改动,避免某些组件在 mount\n * 阶段把规范化后的值再写回模型时被误判为「用户已修改」\n */\n let baselineSnapshot: Record<string, any> | undefined\n let emptySnapshot: Record<string, any> | undefined\n\n function snapshotModelData() {\n return snapshotData(props.model)\n }\n\n function resetFormData(data?: Record<string, any>) {\n if (!props.model) return\n replaceModelData(props.model, snapshotData(data ?? emptySnapshot))\n formRef.value?.clearValidate()\n }\n\n function setFormData(formData: Record<string, any>) {\n if (!props.model) return\n replaceModelData(props.model, snapshotData(formData))\n }\n\n watch(\n () => props.model,\n (model) => {\n emptySnapshot = snapshotData(model)\n },\n { immediate: true }\n )\n\n watch(\n () => state.row,\n (row) => {\n resetFormData()\n if (row) {\n state.type = 'update'\n state.visible = true\n setFormData(row.data)\n } else {\n state.visible = false\n }\n baselineSnapshot = snapshotModelData()\n state.dataUpdated = false\n },\n // sync 保证 setData 完成后立刻取快照,避免 mount 期间组件回写值时丢基线\n { flush: 'sync' }\n )\n\n watch(\n () => props.model,\n () => {\n if (!baselineSnapshot || !state.visible) return\n state.dataUpdated = !isSameValue(baselineSnapshot, snapshotModelData())\n },\n { deep: true }\n )\n\n function isSameValue(a: any, b: any) {\n if (a === b) return true\n if (a == null && b == null) return true\n if (typeof a !== typeof b) return false\n if (typeof a === 'object') {\n try {\n return JSON.stringify(a) === JSON.stringify(b)\n } catch {\n return false\n }\n }\n return false\n }\n\n const childrenKey = computed(() => {\n return typeof props.tree === 'string' ? props.tree : 'children'\n })\n\n /**\n * 插入数据\n * @param item 数据\n */\n function insert(item: Record<string, any>) {\n const data = [...(props.data ?? [])]\n\n const parent = visitDataByIndexPath(\n data ?? [],\n insertIndexes.value.slice(0, -1),\n childrenKey.value\n )\n\n if (parent) {\n const children = parent[childrenKey.value]\n if (!children) {\n parent[childrenKey.value] = [item]\n } else {\n children.splice(last(insertIndexes.value), 0, item)\n }\n } else {\n data.splice(last(insertIndexes.value), 0, item)\n }\n\n emit('update:data', data)\n\n return item\n }\n\n function getInsertData(): Record<string, any> {\n return snapshotModelData()\n }\n\n async function runCreate(cb: () => void) {\n state.parentRow = undefined\n if (state.row) {\n state.row.isCurrent = false\n }\n state.row = undefined\n state.type = 'create'\n resetFormData()\n cb()\n\n let item: Record<string, any> | undefined = undefined\n\n await nextTick()\n\n state.visible = true\n baselineSnapshot = snapshotModelData()\n state.dataUpdated = false\n\n if (item) {\n const row = tableRef.value?.getRowByData(item)\n if (row) {\n state.row = row\n }\n }\n }\n\n /**\n * 点击新增按钮\n */\n function handleCreate() {\n const { data } = props\n runCreate(() => {\n state.depth = 1\n insertIndexes.value = [data?.length ?? 0]\n })\n }\n\n function handleInsertToPrev(row: TableRow) {\n runCreate(() => {\n state.depth = row.depth\n insertIndexes.value = [...row.indexes]\n })\n }\n\n function handleInsertToNext(row: TableRow) {\n runCreate(() => {\n state.depth = row.depth\n insertIndexes.value = [...row.indexes.slice(0, -1), row.index + 1]\n })\n }\n\n function handleInsertChild(row: TableRow) {\n runCreate(() => {\n state.parentRow = row\n state.depth = row.depth + 1\n row.expanded = true\n\n insertIndexes.value = [...row.indexes, row.children?.length ?? 0]\n })\n }\n\n /** 编辑指定行(等同于点击行) */\n function handleEdit(row: TableRow) {\n state.row = row\n state.depth = row.depth\n }\n\n /** 复制指定行:在其下方以 create 模式打开表单,并预填副本数据 */\n function handleCopy(row: TableRow) {\n runCreate(() => {\n state.depth = row.depth\n insertIndexes.value = [...row.indexes.slice(0, -1), row.index + 1]\n setFormData(row.data)\n baselineSnapshot = snapshotModelData()\n state.dataUpdated = false\n })\n }\n\n function runWithLoading<Arg extends any[]>(fn: (...args: Arg) => Promise<void> | void) {\n return async (...args: Arg) => {\n state.loading = true\n if (state.row) {\n state.row.operating = true\n }\n try {\n await fn.apply(null, args)\n } catch (error) {\n console.error(error)\n }\n state.loading = false\n if (state.row) {\n state.row.operating = false\n }\n }\n }\n\n /** 保存 */\n const handleSave = runWithLoading(async () => {\n const { model, saveMethod } = props\n\n if (!model) return\n\n formRef.value?.clearValidate()\n const valid = await formRef.value?.validate()\n\n if (!valid) return\n\n let item = getInsertData()\n if (saveMethod) {\n const result = await saveMethod(item, state.type, state.parentRow)\n if (result) {\n item = result\n }\n }\n\n state.dataUpdated = false\n\n // 新增\n if (state.type === 'create') {\n insert(item)\n\n resetFormData()\n return\n }\n // 更新\n if (state.type === 'update') {\n const { row } = state\n row &&\n Object.keys(item).forEach((key) => {\n o(row.data).set(key, o(item).get(key))\n })\n }\n })\n\n const handleDelete = runWithLoading(async (row: TableRow) => {\n const { deleteMethod } = props\n\n if (deleteMethod) {\n await deleteMethod([row.data])\n }\n\n const data = [...(props.data ?? [])]\n\n const parent = visitDataByIndexPath(data, row.indexes.slice(0, -1), childrenKey.value)\n\n if (parent) {\n const children = parent[childrenKey.value]\n if (children) {\n children.splice(row.index, 1)\n } else {\n console.error('行路径不正确')\n }\n } else {\n data.splice(row.index, 1)\n }\n\n if (state.row === row) {\n state.row = undefined\n state.depth = undefined\n }\n\n emit('update:data', data)\n })\n\n function handleClose() {\n state.visible = false\n if (state.row) {\n state.row.isCurrent = false\n }\n state.row = undefined\n state.parentRow = undefined\n state.depth = undefined\n resetFormData()\n insertIndexes.value = []\n }\n\n return {\n state,\n insertIndexes,\n handleDelete,\n handleInsertChild,\n handleInsertToNext,\n handleInsertToPrev,\n handleCreate,\n handleEdit,\n handleCopy,\n handleClose,\n handleSave\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-edit.js","names":[],"sources":["../../../src/components/batch-edit/use-edit.ts"],"sourcesContent":["import { last, o, safeRun } from '@cat-kit/core'\nimport { computed, nextTick, shallowReactive, shallowRef, watch, type ShallowRef } from 'vue'\n\nimport type { TableRow, BatchEditEmits, BatchEditProps } from '../../types'\nimport type { FormExposed } from '../../types/form'\n\n/** 按索引路径在原始数据树中定位节点(等价于历史 Forest.visit 路径访问) */\nfunction visitDataByIndexPath<T extends Record<string, unknown>>(\n roots: T[],\n indexPath: number[],\n childrenKey: string\n): T | undefined {\n if (!indexPath.length) return undefined\n let currentList: T[] | undefined = roots\n let node: T | undefined\n for (let i = 0; i < indexPath.length; i++) {\n const idx = indexPath[i]!\n if (!currentList) return undefined\n node = currentList[idx]\n if (node === undefined) return undefined\n if (i < indexPath.length - 1) {\n const next = node[childrenKey]\n currentList = Array.isArray(next) ? (next as T[]) : undefined\n }\n }\n return node\n}\n\ninterface Options {\n props: BatchEditProps\n emit: BatchEditEmits\n formRef: ShallowRef<FormExposed | undefined>\n}\n\nexport interface BatchEditStates {\n /** 层级 */\n depth?: number\n visible: boolean\n type: 'create' | 'update'\n loading: boolean\n row?: TableRow\n /** 父级,只在添加子级时会有值 */\n parentRow?: TableRow\n}\n\nexport interface EditReturned {\n state: BatchEditStates\n insertIndexes: ShallowRef<number[]>\n handleSave: () => Promise<void>\n handleClose: () => void\n handleCreate: () => Promise<void>\n handleEdit: (row: TableRow) => void\n handleCopy: (row: TableRow) => Promise<void>\n handleDelete: (row: TableRow) => Promise<void>\n handleInsertToNext: (row: TableRow) => Promise<void>\n handleInsertToPrev: (row: TableRow) => Promise<void>\n handleInsertChild: (row: TableRow) => Promise<void>\n}\n\nfunction snapshotData(data?: Record<string, any>) {\n return safeRun(() => JSON.parse(JSON.stringify(data ?? {})), {})\n}\n\nfunction replaceModelData(model: Record<string, any>, data: Record<string, any>) {\n for (const key of Object.keys(model)) {\n delete model[key]\n }\n Object.assign(model, data)\n}\n\nexport function useEdit(options: Options): EditReturned {\n const { props, emit, formRef } = options\n\n /** 组件状态 */\n const state = shallowReactive<BatchEditStates>({\n depth: undefined,\n visible: false,\n type: 'create',\n loading: false\n })\n\n const insertIndexes = shallowRef<number[]>([])\n\n function snapshotModelData() {\n return snapshotData(props.model)\n }\n\n function setFormData(formData?: Record<string, any>) {\n if (!props.model) return\n replaceModelData(props.model, snapshotData(formData))\n }\n\n const isQuickMode = () => props.mode === 'quick'\n\n async function runBeforeCreate(data: Record<string, any>) {\n await props.beforeCreate?.(data, state.parentRow?.data)\n }\n\n watch(\n () => state.row,\n (row) => {\n if (row) {\n state.type = 'update'\n state.visible = true\n if (!isQuickMode()) {\n setFormData(row.data)\n }\n } else {\n state.visible = false\n }\n },\n { flush: 'sync' }\n )\n\n const childrenKey = computed(() => {\n return typeof props.tree === 'string' ? props.tree : 'children'\n })\n\n /**\n * 插入数据\n * @param item 数据\n */\n function insert(item: Record<string, any>) {\n const data = [...(props.data ?? [])]\n\n const parent = visitDataByIndexPath(\n data ?? [],\n insertIndexes.value.slice(0, -1),\n childrenKey.value\n )\n\n if (parent) {\n const children = parent[childrenKey.value]\n if (!children) {\n parent[childrenKey.value] = [item]\n } else {\n children.splice(last(insertIndexes.value), 0, item)\n }\n } else {\n data.splice(last(insertIndexes.value), 0, item)\n }\n\n emit('update:data', data)\n\n return item\n }\n\n function getInsertData(): Record<string, any> {\n return snapshotModelData()\n }\n\n async function runCreate(cb: () => void, initialData?: Record<string, any>) {\n state.parentRow = undefined\n if (state.row) {\n state.row.isCurrent = false\n }\n state.row = undefined\n state.type = 'create'\n cb()\n\n await nextTick()\n\n setFormData(initialData)\n\n if (isQuickMode() && props.model) {\n await runBeforeCreate(props.model)\n }\n\n state.visible = true\n }\n\n /**\n * 点击新增按钮\n */\n async function handleCreate() {\n const { data } = props\n await runCreate(() => {\n state.depth = 1\n insertIndexes.value = [data?.length ?? 0]\n })\n }\n\n async function handleInsertToPrev(row: TableRow) {\n await runCreate(() => {\n state.depth = row.depth\n insertIndexes.value = [...row.indexes]\n })\n }\n\n async function handleInsertToNext(row: TableRow) {\n await runCreate(() => {\n state.depth = row.depth\n insertIndexes.value = [...row.indexes.slice(0, -1), row.index + 1]\n })\n }\n\n async function handleInsertChild(row: TableRow) {\n await runCreate(() => {\n state.parentRow = row\n state.depth = row.depth + 1\n row.expanded = true\n\n insertIndexes.value = [...row.indexes, row.children?.length ?? 0]\n })\n }\n\n /** 编辑指定行(等同于点击行) */\n function handleEdit(row: TableRow) {\n state.type = 'update'\n state.row = row\n state.depth = row.depth\n }\n\n /** 复制指定行:在其下方以 create 模式打开表单,并预填副本数据 */\n async function handleCopy(row: TableRow) {\n await runCreate(() => {\n state.depth = row.depth\n insertIndexes.value = [...row.indexes.slice(0, -1), row.index + 1]\n }, row.data)\n }\n\n function runWithLoading<Arg extends any[]>(fn: (...args: Arg) => Promise<void> | void) {\n return async (...args: Arg) => {\n state.loading = true\n if (state.row) {\n state.row.operating = true\n }\n try {\n await fn.apply(null, args)\n } catch (error) {\n console.error(error)\n }\n state.loading = false\n if (state.row) {\n state.row.operating = false\n }\n }\n }\n\n /** 保存 */\n const handleSave = runWithLoading(async () => {\n const { model, saveMethod } = props\n\n if (!model) return\n\n if (isQuickMode() && state.type === 'update') return\n\n formRef.value?.clearValidate()\n const valid = await formRef.value?.validate()\n\n if (!valid) return\n\n // quick + create:仅校验 + insert\n if (isQuickMode() && state.type === 'create') {\n insert(getInsertData())\n return\n }\n\n let item = getInsertData()\n\n // normal + create:beforeCreate → saveMethod → insert\n if (state.type === 'create') {\n await runBeforeCreate(item)\n if (saveMethod) {\n const result = await saveMethod(item, state.type, state.parentRow?.data)\n if (result) {\n item = result\n }\n }\n insert(item)\n return\n }\n\n // normal + update\n if (saveMethod) {\n const result = await saveMethod(item, state.type, state.parentRow?.data)\n if (result) {\n item = result\n }\n }\n\n const { row } = state\n row &&\n Object.keys(item).forEach((key) => {\n o(row.data).set(key, o(item).get(key))\n })\n })\n\n const handleDelete = runWithLoading(async (row: TableRow) => {\n const { deleteMethod } = props\n\n if (deleteMethod) {\n await deleteMethod([row.data])\n }\n\n const data = [...(props.data ?? [])]\n\n const parent = visitDataByIndexPath(data, row.indexes.slice(0, -1), childrenKey.value)\n\n if (parent) {\n const children = parent[childrenKey.value]\n if (children) {\n children.splice(row.index, 1)\n } else {\n console.error('行路径不正确')\n }\n } else {\n data.splice(row.index, 1)\n }\n\n if (state.row === row) {\n state.row = undefined\n state.depth = undefined\n }\n\n emit('update:data', data)\n })\n\n function handleClose() {\n state.visible = false\n if (state.row) {\n state.row.isCurrent = false\n }\n state.row = undefined\n state.parentRow = undefined\n state.depth = undefined\n insertIndexes.value = []\n }\n\n return {\n state,\n insertIndexes,\n handleDelete,\n handleInsertChild,\n handleInsertToNext,\n handleInsertToPrev,\n handleCreate,\n handleEdit,\n handleCopy,\n handleClose,\n handleSave\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { computed } from "vue";
|
|
2
|
+
//#region src/components/batch-edit/use-features.ts
|
|
3
|
+
function useFeatures(options) {
|
|
4
|
+
const { props } = options;
|
|
5
|
+
return {
|
|
6
|
+
staticFeatures: computed(() => {
|
|
7
|
+
const { features } = props;
|
|
8
|
+
if (Array.isArray(features)) return new Set(features);
|
|
9
|
+
const defaultFeatures = new Set([
|
|
10
|
+
"create",
|
|
11
|
+
"delete",
|
|
12
|
+
"update",
|
|
13
|
+
"createChild"
|
|
14
|
+
]);
|
|
15
|
+
if (!features) return defaultFeatures;
|
|
16
|
+
Object.entries(features).forEach(([key, value]) => {
|
|
17
|
+
const k = key;
|
|
18
|
+
if (typeof value === "function" || value === false) defaultFeatures.delete(k);
|
|
19
|
+
else if (value === true) defaultFeatures.add(k);
|
|
20
|
+
});
|
|
21
|
+
return defaultFeatures;
|
|
22
|
+
}),
|
|
23
|
+
dynamicFeatures: computed(() => {
|
|
24
|
+
const { features } = props;
|
|
25
|
+
if (!Array.isArray(features) && typeof features === "object") return Object.entries(features).filter(([_, value]) => {
|
|
26
|
+
return typeof value === "function";
|
|
27
|
+
}).reduce((acc, [key, value]) => {
|
|
28
|
+
acc[key] = value;
|
|
29
|
+
return acc;
|
|
30
|
+
}, {});
|
|
31
|
+
return {};
|
|
32
|
+
})
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { useFeatures };
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=use-features.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-features.js","names":[],"sources":["../../../src/components/batch-edit/use-features.ts"],"sourcesContent":["import { computed } from 'vue'\n\nimport type { BatchEditFeature, BatchEditProps, TableRow } from '../../types'\n\ninterface Options {\n props: BatchEditProps\n}\nexport function useFeatures(options: Options) {\n const { props } = options\n const staticFeatures = computed(() => {\n const { features } = props\n\n if (Array.isArray(features)) {\n return new Set(features)\n }\n\n const defaultFeatures = new Set<BatchEditFeature>(['create', 'delete', 'update', 'createChild'])\n\n if (!features) {\n return defaultFeatures\n }\n\n // 函数与 false 的特性视为关闭;显式 true 视为开启(即便不在默认集合内)\n Object.entries(features).forEach(([key, value]) => {\n const k = key as BatchEditFeature\n if (typeof value === 'function' || value === false) {\n defaultFeatures.delete(k)\n } else if (value === true) {\n defaultFeatures.add(k)\n }\n })\n return defaultFeatures\n })\n\n const dynamicFeatures = computed<\n Record<BatchEditFeature, ((row?: TableRow) => boolean) | undefined>\n >(() => {\n const { features } = props\n if (!Array.isArray(features) && typeof features === 'object') {\n const ret = Object.entries(features)\n .filter(([_, value]) => {\n return typeof value === 'function'\n })\n .reduce(\n (acc, [key, value]) => {\n acc[key] = value\n return acc\n },\n {} as Record<BatchEditFeature, (row?: TableRow) => boolean>\n )\n return ret\n }\n\n return {} as Record<BatchEditFeature, ((row?: TableRow) => boolean) | undefined>\n })\n\n return { staticFeatures, dynamicFeatures }\n}\n"],"mappings":";;AAOA,SAAgB,YAAY,SAAkB;CAC5C,MAAM,EAAE,UAAU;CAgDlB,OAAO;EAAE,gBA/Cc,eAAe;GACpC,MAAM,EAAE,aAAa;GAErB,IAAI,MAAM,QAAQ,QAAQ,GACxB,OAAO,IAAI,IAAI,QAAQ;GAGzB,MAAM,kBAAkB,IAAI,IAAsB;IAAC;IAAU;IAAU;IAAU;GAAa,CAAC;GAE/F,IAAI,CAAC,UACH,OAAO;GAIT,OAAO,QAAQ,QAAQ,EAAE,SAAS,CAAC,KAAK,WAAW;IACjD,MAAM,IAAI;IACV,IAAI,OAAO,UAAU,cAAc,UAAU,OAC3C,gBAAgB,OAAO,CAAC;SACnB,IAAI,UAAU,MACnB,gBAAgB,IAAI,CAAC;GAEzB,CAAC;GACD,OAAO;EACT,CAwBsB;EAAG,iBAtBD,eAEhB;GACN,MAAM,EAAE,aAAa;GACrB,IAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,OAAO,aAAa,UAYlD,OAXY,OAAO,QAAQ,QAAQ,EAChC,QAAQ,CAAC,GAAG,WAAW;IACtB,OAAO,OAAO,UAAU;GAC1B,CAAC,EACA,QACE,KAAK,CAAC,KAAK,WAAW;IACrB,IAAI,OAAO;IACX,OAAO;GACT,GACA,CAAC,CAEI;GAGX,OAAO,CAAC;EACV,CAEuC;CAAE;AAC3C"}
|
|
@@ -17,9 +17,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
|
|
|
17
17
|
"onUpdate:modelValue"?: ((checked: boolean) => any) | undefined;
|
|
18
18
|
onChange?: ((checked: boolean) => any) | undefined;
|
|
19
19
|
}>, {
|
|
20
|
+
type: import("@veltra/utils").ColorType;
|
|
20
21
|
disabled: boolean;
|
|
21
22
|
readonly: boolean;
|
|
22
|
-
type: import("@veltra/utils").ColorType;
|
|
23
23
|
round: boolean;
|
|
24
24
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
25
25
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -79,12 +79,12 @@
|
|
|
79
79
|
flex-wrap: nowrap;
|
|
80
80
|
}
|
|
81
81
|
.u-contextmenu__item:not(.is-disabled):not(.is-loading) > .u-contextmenu__item-content:hover {
|
|
82
|
-
background-color: var(--u-
|
|
83
|
-
color: var(--u-
|
|
82
|
+
background-color: var(--u-nav-hover-bg);
|
|
83
|
+
color: var(--u-nav-hover-color);
|
|
84
84
|
}
|
|
85
85
|
.u-contextmenu .u-contextmenu__item.is-open:not(.is-disabled) > .u-contextmenu__item-content {
|
|
86
|
-
background-color: var(--u-
|
|
87
|
-
color: var(--u-
|
|
86
|
+
background-color: var(--u-nav-hover-bg);
|
|
87
|
+
color: var(--u-nav-hover-color);
|
|
88
88
|
}
|
|
89
89
|
.u-contextmenu__icon-place {
|
|
90
90
|
width: 1em;
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
}
|
|
134
134
|
.u-contextmenu--small__item-content {
|
|
135
135
|
padding: 0 var(--u-gap-small);
|
|
136
|
-
min-height: calc(var(--u-
|
|
136
|
+
min-height: calc(var(--u-nav-height-small) - 8px);
|
|
137
137
|
border-radius: calc(var(--u-radius-small) + 1px);
|
|
138
138
|
}
|
|
139
139
|
.u-contextmenu--default {
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
}
|
|
142
142
|
.u-contextmenu--default__item-content {
|
|
143
143
|
padding: 0 var(--u-gap-default);
|
|
144
|
-
min-height: calc(var(--u-
|
|
144
|
+
min-height: calc(var(--u-nav-height-default) - 8px);
|
|
145
145
|
border-radius: calc(var(--u-radius-default) + 1px);
|
|
146
146
|
}
|
|
147
147
|
.u-contextmenu--large {
|
|
@@ -149,6 +149,6 @@
|
|
|
149
149
|
}
|
|
150
150
|
.u-contextmenu--large__item-content {
|
|
151
151
|
padding: 0 var(--u-gap-large);
|
|
152
|
-
min-height: calc(var(--u-
|
|
152
|
+
min-height: calc(var(--u-nav-height-large) - 8px);
|
|
153
153
|
border-radius: calc(var(--u-radius-large) + 1px);
|
|
154
154
|
}
|
|
@@ -7,8 +7,8 @@ declare const __VLS_export: import("vue").DefineComponent<DatePanelProps, {}, {}
|
|
|
7
7
|
"onSelect:range-date"?: ((rangeDate?: [import("@cat-kit/core").Dater, import("@cat-kit/core").Dater] | undefined) => any) | undefined;
|
|
8
8
|
"onSelect:date"?: ((date: import("@cat-kit/core").Dater) => any) | undefined;
|
|
9
9
|
}>, {
|
|
10
|
-
size: import("@veltra/utils").ComponentSize;
|
|
11
10
|
type: "date" | "month" | "year";
|
|
11
|
+
size: import("@veltra/utils").ComponentSize;
|
|
12
12
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
13
|
declare const _default: typeof __VLS_export;
|
|
14
14
|
//#endregion
|
|
@@ -5,11 +5,11 @@ declare const __VLS_export: import("vue").DefineComponent<DatePickerProps, {}, {
|
|
|
5
5
|
}, string, import("vue").PublicProps, Readonly<DatePickerProps> & Readonly<{
|
|
6
6
|
"onUpdate:modelValue"?: ((value?: string | number | Date | undefined) => any) | undefined;
|
|
7
7
|
}>, {
|
|
8
|
+
type: "date" | "month" | "year";
|
|
8
9
|
placeholder: string;
|
|
9
10
|
clearable: boolean;
|
|
10
11
|
disabled: boolean;
|
|
11
12
|
readonly: boolean;
|
|
12
|
-
type: "date" | "month" | "year";
|
|
13
13
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
14
14
|
declare const _default: typeof __VLS_export;
|
|
15
15
|
//#endregion
|
|
@@ -5,11 +5,11 @@ declare const __VLS_export: import("vue").DefineComponent<DateRangePickerProps,
|
|
|
5
5
|
}, string, import("vue").PublicProps, Readonly<DateRangePickerProps> & Readonly<{
|
|
6
6
|
"onUpdate:modelValue"?: ((value?: [string, string] | undefined) => any) | undefined;
|
|
7
7
|
}>, {
|
|
8
|
+
type: "date" | "month" | "year";
|
|
8
9
|
placeholder: [string, string];
|
|
9
10
|
clearable: boolean;
|
|
10
11
|
disabled: boolean;
|
|
11
12
|
readonly: boolean;
|
|
12
|
-
type: "date" | "month" | "year";
|
|
13
13
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
14
14
|
declare const _default: typeof __VLS_export;
|
|
15
15
|
//#endregion
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import _sfc_main$1 from "../tip/tip.js";
|
|
2
|
+
import _sfc_main$2 from "../nav/nav-icon.js";
|
|
3
|
+
import { createBlock, createElementBlock, createElementVNode, createTextVNode, defineComponent, normalizeClass, openBlock, toDisplayString, unref, withCtx } from "vue";
|
|
4
|
+
import { bem } from "@veltra/utils";
|
|
5
|
+
//#region src/components/dual-nav/dual-nav-app.vue
|
|
6
|
+
const _hoisted_1 = ["disabled"];
|
|
7
|
+
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
8
|
+
name: "DualNavApp",
|
|
9
|
+
__name: "dual-nav-app",
|
|
10
|
+
props: {
|
|
11
|
+
app: {},
|
|
12
|
+
active: { type: Boolean }
|
|
13
|
+
},
|
|
14
|
+
emits: ["click"],
|
|
15
|
+
setup(__props, { emit: __emit }) {
|
|
16
|
+
const cls = bem("dual-nav");
|
|
17
|
+
const emit = __emit;
|
|
18
|
+
return (_ctx, _cache) => {
|
|
19
|
+
return openBlock(), createBlock(unref(_sfc_main$1), {
|
|
20
|
+
direction: "right",
|
|
21
|
+
"hide-arrow": ""
|
|
22
|
+
}, {
|
|
23
|
+
content: withCtx(() => [createTextVNode(toDisplayString(__props.app.title), 1)]),
|
|
24
|
+
default: withCtx(() => [createElementVNode("button", {
|
|
25
|
+
type: "button",
|
|
26
|
+
class: normalizeClass([
|
|
27
|
+
unref(cls).e("app"),
|
|
28
|
+
unref(bem).is("active", __props.active),
|
|
29
|
+
unref(bem).is("disabled", __props.app.disabled ?? false)
|
|
30
|
+
]),
|
|
31
|
+
disabled: __props.app.disabled,
|
|
32
|
+
onClick: _cache[0] || (_cache[0] = ($event) => emit("click"))
|
|
33
|
+
}, [__props.app.icon ? (openBlock(), createBlock(_sfc_main$2, {
|
|
34
|
+
key: 0,
|
|
35
|
+
icon: __props.app.icon,
|
|
36
|
+
class: normalizeClass(unref(cls).e("app-icon"))
|
|
37
|
+
}, null, 8, ["icon", "class"])) : (openBlock(), createElementBlock("span", {
|
|
38
|
+
key: 1,
|
|
39
|
+
class: normalizeClass(unref(cls).e("app-fallback"))
|
|
40
|
+
}, toDisplayString(__props.app.title[0]), 3))], 10, _hoisted_1)]),
|
|
41
|
+
_: 1
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
//#endregion
|
|
47
|
+
export { _sfc_main as default };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=dual-nav-app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dual-nav-app.js","names":[],"sources":["../../../src/components/dual-nav/dual-nav-app.vue"],"sourcesContent":["<template>\n <u-tip direction=\"right\" hide-arrow>\n <button\n type=\"button\"\n :class=\"[cls.e('app'), bem.is('active', active), bem.is('disabled', app.disabled ?? false)]\"\n :disabled=\"app.disabled\"\n @click=\"emit('click')\"\n >\n <UNavIcon v-if=\"app.icon\" :icon=\"app.icon\" :class=\"cls.e('app-icon')\" />\n <span v-else :class=\"cls.e('app-fallback')\">{{ app.title[0] }}</span>\n </button>\n\n <template #content>\n {{ app.title }}\n </template>\n </u-tip>\n</template>\n\n<script setup lang=\"ts\">\nimport { bem } from '@veltra/utils'\n\nimport type { NavItem } from '../../types'\nimport UNavIcon from '../nav/nav-icon.vue'\nimport { UTip } from '../tip'\n\ndefineOptions({ name: 'DualNavApp' })\n\nconst cls = bem('dual-nav')\n\ndefineProps<{ app: NavItem; active: boolean }>()\n\nconst emit = defineEmits<{ click: [] }>()\n</script>\n"],"mappings":";;;;;;;;;;;;;;;EA2BA,MAAM,MAAM,IAAI,UAAU;EAI1B,MAAM,OAAO;;uBA9BX,YAcQ,MAAA,WAAA,GAAA;IAdD,WAAU;IAAQ,cAAA;;IAWZ,SAAO,cACD,CAAA,gBAAA,gBAAZ,QAAA,IAAI,KAAK,GAAA,CAAA,CAAA,CAAA;2BAHL,CART,mBAQS,UAAA;KAPP,MAAK;KACJ,OAAK,eAAA;MAAG,MAAA,GAAA,EAAI,EAAC,KAAA;MAAS,MAAA,GAAA,EAAI,GAAE,UAAW,QAAA,MAAM;MAAG,MAAA,GAAA,EAAI,GAAE,YAAa,QAAA,IAAI,YAAQ,KAAA;KAAA,CAAA;KAC/E,UAAU,QAAA,IAAI;KACd,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,OAAA;QAEI,QAAA,IAAI,QAAA,UAAA,GAApB,YAAwE,aAAA;;KAA7C,MAAM,QAAA,IAAI;KAAO,OAAK,eAAE,MAAA,GAAA,EAAI,EAAC,UAAA,CAAA;oDACxD,mBAAqE,QAAA;;KAAvD,OAAK,eAAE,MAAA,GAAA,EAAI,EAAC,cAAA,CAAA;uBAAqB,QAAA,IAAI,MAAK,EAAA,GAAA,CAAA,EAAA,GAAA,IAAA,UAAA,CAAA,CAAA"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import _sfc_main$1 from "../icon/icon.js";
|
|
2
|
+
import _sfc_main$2 from "../tip/tip.js";
|
|
3
|
+
import { collectNavBranchPaths } from "../nav/helper.js";
|
|
4
|
+
import _sfc_main$3 from "../nav/nav.js";
|
|
5
|
+
import _sfc_main$4 from "./dual-nav-app.js";
|
|
6
|
+
import { findRootApp } from "./helper.js";
|
|
7
|
+
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, normalizeClass, openBlock, ref, renderList, toDisplayString, unref, useTemplateRef, watch, withCtx } from "vue";
|
|
8
|
+
import { bem } from "@veltra/utils";
|
|
9
|
+
import { Folder, FolderOpened } from "@veltra/icons/normal";
|
|
10
|
+
//#region src/components/dual-nav/dual-nav.vue
|
|
11
|
+
const _hoisted_1 = ["aria-label"];
|
|
12
|
+
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
13
|
+
name: "DualNav",
|
|
14
|
+
__name: "dual-nav",
|
|
15
|
+
props: {
|
|
16
|
+
currentPath: {},
|
|
17
|
+
menus: {}
|
|
18
|
+
},
|
|
19
|
+
emits: ["item-click"],
|
|
20
|
+
setup(__props, { emit: __emit }) {
|
|
21
|
+
const props = __props;
|
|
22
|
+
const emit = __emit;
|
|
23
|
+
const cls = bem("dual-nav");
|
|
24
|
+
const navRef = useTemplateRef("navRef");
|
|
25
|
+
/** 右栏当前展示的应用(点击左轨或 currentPath 同步) */
|
|
26
|
+
const panelAppPath = ref();
|
|
27
|
+
const allExpanded = ref(false);
|
|
28
|
+
function syncPanelAppFromCurrentPath(currentPath) {
|
|
29
|
+
const rootApp = findRootApp(props.menus, currentPath);
|
|
30
|
+
if (rootApp) {
|
|
31
|
+
panelAppPath.value = rootApp.path;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (!panelAppPath.value && props.menus?.length) panelAppPath.value = props.menus[0].path;
|
|
35
|
+
}
|
|
36
|
+
watch(() => [props.currentPath, props.menus], ([currentPath]) => {
|
|
37
|
+
syncPanelAppFromCurrentPath(currentPath);
|
|
38
|
+
}, { immediate: true });
|
|
39
|
+
const panelApp = computed(() => {
|
|
40
|
+
if (!panelAppPath.value || !props.menus?.length) return void 0;
|
|
41
|
+
return props.menus.find((app) => app.path === panelAppPath.value);
|
|
42
|
+
});
|
|
43
|
+
/** 右栏菜单:有子级时展示子级,单层时展示根项自身 */
|
|
44
|
+
const panelMenus = computed(() => {
|
|
45
|
+
const app = panelApp.value;
|
|
46
|
+
if (!app) return void 0;
|
|
47
|
+
if (app.children?.length) return app.children;
|
|
48
|
+
return [app];
|
|
49
|
+
});
|
|
50
|
+
const hasExpandableMenus = computed(() => {
|
|
51
|
+
return collectNavBranchPaths(panelMenus.value).length > 0;
|
|
52
|
+
});
|
|
53
|
+
watch(panelAppPath, () => {
|
|
54
|
+
allExpanded.value = false;
|
|
55
|
+
});
|
|
56
|
+
/** 左轨高亮:仅当 currentPath 落在该应用下时激活 */
|
|
57
|
+
function isAppActive(app) {
|
|
58
|
+
return findRootApp(props.menus, props.currentPath)?.path === app.path;
|
|
59
|
+
}
|
|
60
|
+
function handleAppClick(app) {
|
|
61
|
+
if (app.disabled) return;
|
|
62
|
+
panelAppPath.value = app.path;
|
|
63
|
+
if (!app.children?.length) emit("item-click", app);
|
|
64
|
+
}
|
|
65
|
+
function handleToggleExpandAll() {
|
|
66
|
+
if (allExpanded.value) {
|
|
67
|
+
navRef.value?.collapseAll();
|
|
68
|
+
allExpanded.value = false;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
navRef.value?.expandAll();
|
|
72
|
+
allExpanded.value = true;
|
|
73
|
+
}
|
|
74
|
+
return (_ctx, _cache) => {
|
|
75
|
+
return openBlock(), createElementBlock("div", { class: normalizeClass(unref(cls).b) }, [
|
|
76
|
+
createElementVNode("div", { class: normalizeClass(unref(cls).e("rail")) }, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.menus, (app) => {
|
|
77
|
+
return openBlock(), createBlock(_sfc_main$4, {
|
|
78
|
+
key: app.path,
|
|
79
|
+
app,
|
|
80
|
+
active: isAppActive(app),
|
|
81
|
+
onClick: ($event) => handleAppClick(app)
|
|
82
|
+
}, null, 8, [
|
|
83
|
+
"app",
|
|
84
|
+
"active",
|
|
85
|
+
"onClick"
|
|
86
|
+
]);
|
|
87
|
+
}), 128))], 2),
|
|
88
|
+
createElementVNode("div", {
|
|
89
|
+
class: normalizeClass(unref(cls).e("divider")),
|
|
90
|
+
"aria-hidden": "true"
|
|
91
|
+
}, null, 2),
|
|
92
|
+
createElementVNode("div", { class: normalizeClass(unref(cls).e("panel")) }, [panelApp.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("div", { class: normalizeClass(unref(cls).e("panel-header")) }, [createElementVNode("div", { class: normalizeClass(unref(cls).e("panel-header-main")) }, [createElementVNode("h3", { class: normalizeClass(unref(cls).e("panel-title")) }, toDisplayString(panelApp.value.title), 3), panelApp.value.description ? (openBlock(), createElementBlock("p", {
|
|
93
|
+
key: 0,
|
|
94
|
+
class: normalizeClass(unref(cls).e("description"))
|
|
95
|
+
}, toDisplayString(panelApp.value.description), 3)) : createCommentVNode("v-if", true)], 2), hasExpandableMenus.value ? (openBlock(), createBlock(unref(_sfc_main$2), {
|
|
96
|
+
key: 0,
|
|
97
|
+
content: allExpanded.value ? "折叠全部" : "展开全部",
|
|
98
|
+
"hide-arrow": ""
|
|
99
|
+
}, {
|
|
100
|
+
default: withCtx(() => [createElementVNode("button", {
|
|
101
|
+
type: "button",
|
|
102
|
+
class: normalizeClass(unref(cls).e("toggle-expand")),
|
|
103
|
+
"aria-label": allExpanded.value ? "折叠全部" : "展开全部",
|
|
104
|
+
onClick: handleToggleExpandAll
|
|
105
|
+
}, [createVNode(unref(_sfc_main$1), null, {
|
|
106
|
+
default: withCtx(() => [!allExpanded.value ? (openBlock(), createBlock(unref(FolderOpened), { key: 0 })) : (openBlock(), createBlock(unref(Folder), { key: 1 }))]),
|
|
107
|
+
_: 1
|
|
108
|
+
})], 10, _hoisted_1)]),
|
|
109
|
+
_: 1
|
|
110
|
+
}, 8, ["content"])) : createCommentVNode("v-if", true)], 2), createElementVNode("div", { class: normalizeClass(unref(cls).e("panel-body")) }, [createVNode(unref(_sfc_main$3), {
|
|
111
|
+
ref_key: "navRef",
|
|
112
|
+
ref: navRef,
|
|
113
|
+
menus: panelMenus.value,
|
|
114
|
+
"current-path": __props.currentPath,
|
|
115
|
+
collapsed: false,
|
|
116
|
+
onItemClick: _cache[0] || (_cache[0] = ($event) => emit("item-click", $event))
|
|
117
|
+
}, null, 8, ["menus", "current-path"])], 2)], 64)) : createCommentVNode("v-if", true)], 2)
|
|
118
|
+
], 2);
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
//#endregion
|
|
123
|
+
export { _sfc_main as default };
|
|
124
|
+
|
|
125
|
+
//# sourceMappingURL=dual-nav.js.map
|