@shwfed/config 2.12.18 → 3.0.1
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/module.json +1 -1
- package/dist/runtime/components/actions/buttons/2026-06-25/com.shwfed.actions.button.state.write/config.vue +17 -11
- package/dist/runtime/components/config/footer.vue +14 -8
- package/dist/runtime/components/config/index.d.vue.ts +1 -0
- package/dist/runtime/components/config/index.vue +30 -2
- package/dist/runtime/components/config/index.vue.d.ts +1 -0
- package/dist/runtime/components/config/use-editor.d.ts +12 -1
- package/dist/runtime/components/config/use-editor.js +47 -24
- package/dist/runtime/components/config/use-unsaved-guard.d.ts +1 -0
- package/dist/runtime/components/config/use-unsaved-guard.js +8 -0
- package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/config.d.vue.ts +6 -6
- package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/config.vue.d.ts +6 -6
- package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/config.d.vue.ts +8 -8
- package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/config.vue.d.ts +8 -8
- package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.timerange/config.d.vue.ts +2 -2
- package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.timerange/config.vue.d.ts +2 -2
- package/dist/runtime/components/form/fields/2026-05-24/com.shwfed.form.field.monthrange/config.d.vue.ts +6 -6
- package/dist/runtime/components/form/fields/2026-05-24/com.shwfed.form.field.monthrange/config.vue.d.ts +6 -6
- package/dist/runtime/components/modal.d.vue.ts +8 -8
- package/dist/runtime/components/modal.vue +9 -1
- package/dist/runtime/components/modal.vue.d.ts +8 -8
- package/dist/runtime/components/sidebar/footer.vue +13 -7
- package/dist/runtime/components/sidebar/index.d.vue.ts +1 -0
- package/dist/runtime/components/sidebar/index.vue +30 -2
- package/dist/runtime/components/sidebar/index.vue.d.ts +1 -0
- package/dist/runtime/components/sidebar/use-editor.d.ts +11 -1
- package/dist/runtime/components/sidebar/use-editor.js +38 -17
- package/dist/runtime/components/table/columns/2026-07-12/com.shwfed.table.column.dynamic/config.d.vue.ts +10 -0
- package/dist/runtime/components/table/columns/2026-07-12/com.shwfed.table.column.dynamic/config.vue +73 -0
- package/dist/runtime/components/table/columns/2026-07-12/com.shwfed.table.column.dynamic/config.vue.d.ts +10 -0
- package/dist/runtime/components/table/columns/2026-07-12/com.shwfed.table.column.dynamic/schema.d.ts +63 -0
- package/dist/runtime/components/table/columns/2026-07-12/com.shwfed.table.column.dynamic/schema.js +157 -0
- package/dist/runtime/components/table/index.vue +86 -12
- package/dist/runtime/components/table/utils/resolve.d.ts +42 -0
- package/dist/runtime/components/table/utils/resolve.js +11 -3
- package/dist/runtime/components/ui/date-picker/DatePickerInput.d.vue.ts +1 -1
- package/dist/runtime/components/ui/date-picker/DatePickerInput.vue.d.ts +1 -1
- package/dist/runtime/components/ui/date-picker/DatePickerTimeInput.d.vue.ts +1 -1
- package/dist/runtime/components/ui/date-picker/DatePickerTimeInput.vue.d.ts +1 -1
- package/dist/runtime/components/ui/date-range-picker/DateRangePickerInput.d.vue.ts +1 -1
- package/dist/runtime/components/ui/date-range-picker/DateRangePickerInput.vue.d.ts +1 -1
- package/dist/runtime/components/ui/date-range-picker/DateRangePickerTimeInput.d.vue.ts +2 -2
- package/dist/runtime/components/ui/date-range-picker/DateRangePickerTimeInput.vue.d.ts +2 -2
- package/dist/runtime/components/ui/expression-editor/ExpressionEditor.vue +4 -1
- package/dist/runtime/share/define-registry.d.ts +11 -0
- package/dist/runtime/share/define-registry.js +10 -3
- package/package.json +1 -1
|
@@ -32,10 +32,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
32
32
|
readonly mode: "formula" | "prefill";
|
|
33
33
|
readonly expression: string;
|
|
34
34
|
} | undefined;
|
|
35
|
-
readonly format?: string | undefined;
|
|
36
|
-
readonly valueFormat?: string | undefined;
|
|
37
|
-
readonly disabledDate?: string | undefined;
|
|
38
35
|
readonly rangeSeparatorIcon?: string | undefined;
|
|
36
|
+
readonly format?: string | undefined;
|
|
39
37
|
readonly startPlaceholder?: readonly [{
|
|
40
38
|
readonly locale: "zh";
|
|
41
39
|
readonly message: string;
|
|
@@ -50,6 +48,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
50
48
|
readonly locale: "en" | "ja" | "ko";
|
|
51
49
|
readonly message: string;
|
|
52
50
|
}[]] | undefined;
|
|
51
|
+
readonly valueFormat?: string | undefined;
|
|
52
|
+
readonly disabledDate?: string | undefined;
|
|
53
53
|
readonly validations?: readonly {
|
|
54
54
|
readonly message: readonly [{
|
|
55
55
|
readonly locale: "zh";
|
|
@@ -104,10 +104,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
104
104
|
readonly mode: "formula" | "prefill";
|
|
105
105
|
readonly expression: string;
|
|
106
106
|
} | undefined;
|
|
107
|
-
readonly format?: string | undefined;
|
|
108
|
-
readonly valueFormat?: string | undefined;
|
|
109
|
-
readonly disabledDate?: string | undefined;
|
|
110
107
|
readonly rangeSeparatorIcon?: string | undefined;
|
|
108
|
+
readonly format?: string | undefined;
|
|
111
109
|
readonly startPlaceholder?: readonly [{
|
|
112
110
|
readonly locale: "zh";
|
|
113
111
|
readonly message: string;
|
|
@@ -122,6 +120,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
122
120
|
readonly locale: "en" | "ja" | "ko";
|
|
123
121
|
readonly message: string;
|
|
124
122
|
}[]] | undefined;
|
|
123
|
+
readonly valueFormat?: string | undefined;
|
|
124
|
+
readonly disabledDate?: string | undefined;
|
|
125
125
|
readonly validations?: readonly {
|
|
126
126
|
readonly message: readonly [{
|
|
127
127
|
readonly locale: "zh";
|
|
@@ -10,29 +10,29 @@ declare function close(): void;
|
|
|
10
10
|
declare var __VLS_20: {
|
|
11
11
|
close: typeof close;
|
|
12
12
|
isDesktop: boolean;
|
|
13
|
-
},
|
|
13
|
+
}, __VLS_45: {
|
|
14
14
|
close: typeof close;
|
|
15
15
|
isDesktop: boolean;
|
|
16
|
-
},
|
|
16
|
+
}, __VLS_53: {
|
|
17
17
|
close: typeof close;
|
|
18
18
|
isDesktop: boolean;
|
|
19
|
-
},
|
|
19
|
+
}, __VLS_60: {
|
|
20
20
|
close: typeof close;
|
|
21
21
|
isDesktop: boolean;
|
|
22
|
-
},
|
|
22
|
+
}, __VLS_68: {
|
|
23
23
|
close: typeof close;
|
|
24
24
|
isDesktop: boolean;
|
|
25
25
|
};
|
|
26
26
|
type __VLS_Slots = {} & {
|
|
27
27
|
trigger?: (props: typeof __VLS_20) => any;
|
|
28
28
|
} & {
|
|
29
|
-
title?: (props: typeof
|
|
29
|
+
title?: (props: typeof __VLS_45) => any;
|
|
30
30
|
} & {
|
|
31
|
-
description?: (props: typeof
|
|
31
|
+
description?: (props: typeof __VLS_53) => any;
|
|
32
32
|
} & {
|
|
33
|
-
default?: (props: typeof
|
|
33
|
+
default?: (props: typeof __VLS_60) => any;
|
|
34
34
|
} & {
|
|
35
|
-
footer?: (props: typeof
|
|
35
|
+
footer?: (props: typeof __VLS_68) => any;
|
|
36
36
|
};
|
|
37
37
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
38
38
|
"update:open": (open: boolean) => any;
|
|
@@ -78,6 +78,9 @@ const open = computed({
|
|
|
78
78
|
function close() {
|
|
79
79
|
open.value = false;
|
|
80
80
|
}
|
|
81
|
+
function onDismissAttempt(event) {
|
|
82
|
+
if (!props.dismissible) event.preventDefault();
|
|
83
|
+
}
|
|
81
84
|
</script>
|
|
82
85
|
|
|
83
86
|
<template>
|
|
@@ -98,12 +101,17 @@ function close() {
|
|
|
98
101
|
/>
|
|
99
102
|
</component>
|
|
100
103
|
|
|
104
|
+
<!-- The × button is a dismiss affordance too — it hides alongside the
|
|
105
|
+
other dismissal paths while the modal is non-dismissible. -->
|
|
101
106
|
<component
|
|
102
107
|
:is="modalComponents.Content"
|
|
103
108
|
v-bind="attrs"
|
|
104
|
-
:show-close-button="props.showCloseButton"
|
|
109
|
+
:show-close-button="props.showCloseButton && props.dismissible"
|
|
105
110
|
:content-width="props.contentWidth"
|
|
106
111
|
@after-close="emit('after-close')"
|
|
112
|
+
@escape-key-down="onDismissAttempt"
|
|
113
|
+
@pointer-down-outside="onDismissAttempt"
|
|
114
|
+
@interact-outside="onDismissAttempt"
|
|
107
115
|
>
|
|
108
116
|
<component
|
|
109
117
|
:is="modalComponents.Header"
|
|
@@ -10,29 +10,29 @@ declare function close(): void;
|
|
|
10
10
|
declare var __VLS_20: {
|
|
11
11
|
close: typeof close;
|
|
12
12
|
isDesktop: boolean;
|
|
13
|
-
},
|
|
13
|
+
}, __VLS_45: {
|
|
14
14
|
close: typeof close;
|
|
15
15
|
isDesktop: boolean;
|
|
16
|
-
},
|
|
16
|
+
}, __VLS_53: {
|
|
17
17
|
close: typeof close;
|
|
18
18
|
isDesktop: boolean;
|
|
19
|
-
},
|
|
19
|
+
}, __VLS_60: {
|
|
20
20
|
close: typeof close;
|
|
21
21
|
isDesktop: boolean;
|
|
22
|
-
},
|
|
22
|
+
}, __VLS_68: {
|
|
23
23
|
close: typeof close;
|
|
24
24
|
isDesktop: boolean;
|
|
25
25
|
};
|
|
26
26
|
type __VLS_Slots = {} & {
|
|
27
27
|
trigger?: (props: typeof __VLS_20) => any;
|
|
28
28
|
} & {
|
|
29
|
-
title?: (props: typeof
|
|
29
|
+
title?: (props: typeof __VLS_45) => any;
|
|
30
30
|
} & {
|
|
31
|
-
description?: (props: typeof
|
|
31
|
+
description?: (props: typeof __VLS_53) => any;
|
|
32
32
|
} & {
|
|
33
|
-
default?: (props: typeof
|
|
33
|
+
default?: (props: typeof __VLS_60) => any;
|
|
34
34
|
} & {
|
|
35
|
-
footer?: (props: typeof
|
|
35
|
+
footer?: (props: typeof __VLS_68) => any;
|
|
36
36
|
};
|
|
37
37
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
38
38
|
"update:open": (open: boolean) => any;
|
|
@@ -7,15 +7,12 @@ const props = defineProps({
|
|
|
7
7
|
});
|
|
8
8
|
const emit = defineEmits(["close"]);
|
|
9
9
|
function onDismiss() {
|
|
10
|
+
if (props.state.saving.value) return;
|
|
10
11
|
props.state.dismiss();
|
|
11
12
|
emit("close");
|
|
12
13
|
}
|
|
13
|
-
function onConfirm() {
|
|
14
|
-
|
|
15
|
-
props.state.confirm();
|
|
16
|
-
const errorAfter = props.state.validationError.value;
|
|
17
|
-
if (errorAfter && errorAfter !== errorBefore) return;
|
|
18
|
-
if (!errorAfter) emit("close");
|
|
14
|
+
async function onConfirm() {
|
|
15
|
+
if (await props.state.confirm()) emit("close");
|
|
19
16
|
}
|
|
20
17
|
</script>
|
|
21
18
|
|
|
@@ -26,11 +23,16 @@ function onConfirm() {
|
|
|
26
23
|
v-if="props.state.validationError.value"
|
|
27
24
|
class="text-red-500 break-all"
|
|
28
25
|
>{{ props.state.validationError.value }}</span>
|
|
26
|
+
<span
|
|
27
|
+
v-if="props.state.saveError.value"
|
|
28
|
+
class="text-red-500 break-all"
|
|
29
|
+
>{{ props.state.saveError.value }}</span>
|
|
29
30
|
</div>
|
|
30
31
|
<Button
|
|
31
32
|
type="button"
|
|
32
33
|
variant="default"
|
|
33
34
|
size="sm"
|
|
35
|
+
:disabled="props.state.saving.value"
|
|
34
36
|
@click="onDismiss"
|
|
35
37
|
>
|
|
36
38
|
<Icon icon="fluent:dismiss-20-regular" />
|
|
@@ -40,9 +42,13 @@ function onConfirm() {
|
|
|
40
42
|
type="button"
|
|
41
43
|
variant="primary"
|
|
42
44
|
size="sm"
|
|
45
|
+
:disabled="props.state.saving.value"
|
|
43
46
|
@click="onConfirm"
|
|
44
47
|
>
|
|
45
|
-
<Icon
|
|
48
|
+
<Icon
|
|
49
|
+
:icon="props.state.saving.value ? 'fluent:arrow-sync-20-regular' : 'fluent:checkmark-20-regular'"
|
|
50
|
+
:class="props.state.saving.value ? 'animate-spin' : ''"
|
|
51
|
+
/>
|
|
46
52
|
确认
|
|
47
53
|
</Button>
|
|
48
54
|
</div>
|
|
@@ -3,6 +3,7 @@ import { type SidebarConfigValue } from './schema.js';
|
|
|
3
3
|
type __VLS_Props = {
|
|
4
4
|
configure?: (env: Environment) => void;
|
|
5
5
|
editable?: boolean;
|
|
6
|
+
save?: (config: SidebarConfigValue) => void | Promise<void>;
|
|
6
7
|
storageKey?: string;
|
|
7
8
|
collapsible?: 'offcanvas' | 'icon' | 'none';
|
|
8
9
|
width?: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, ref, watch } from "vue";
|
|
3
|
+
import { Icon } from "@iconify/vue";
|
|
3
4
|
import { useLocalStorage } from "@vueuse/core";
|
|
4
5
|
import { useRuntimeConfig } from "#app";
|
|
5
6
|
import { injectCELContext } from "../../utils/cel-context";
|
|
@@ -9,6 +10,7 @@ import SidebarTree from "./tree.vue";
|
|
|
9
10
|
import EditorBody from "./config.vue";
|
|
10
11
|
import EditorFooter from "./footer.vue";
|
|
11
12
|
import { useSidebarEditor } from "./use-editor";
|
|
13
|
+
import { useUnsavedChangesGuard } from "../config/use-unsaved-guard";
|
|
12
14
|
import { createSidebarConfig } from "./schema";
|
|
13
15
|
defineOptions({ name: "ShwfedSidebar" });
|
|
14
16
|
const configModel = defineModel("config", { type: null });
|
|
@@ -21,6 +23,7 @@ const config = computed({
|
|
|
21
23
|
const props = defineProps({
|
|
22
24
|
configure: { type: Function, required: false },
|
|
23
25
|
editable: { type: Boolean, required: false },
|
|
26
|
+
save: { type: Function, required: false },
|
|
24
27
|
storageKey: { type: String, required: false },
|
|
25
28
|
collapsible: { type: String, required: false, default: "none" },
|
|
26
29
|
width: { type: String, required: false, default: "13rem" }
|
|
@@ -83,10 +86,16 @@ const mergedConfigure = (env) => {
|
|
|
83
86
|
const editorOpen = ref(false);
|
|
84
87
|
const editorState = useSidebarEditor(config, {
|
|
85
88
|
configure: mergedConfigure,
|
|
89
|
+
// Presence decided once at setup; the wrapper reads the current prop per
|
|
90
|
+
// call so a rebound handler identity stays honored (mirrors config/index.vue).
|
|
91
|
+
save: props.save ? (cfg) => props.save(cfg) : void 0,
|
|
86
92
|
onClose: () => {
|
|
87
93
|
editorOpen.value = false;
|
|
88
94
|
}
|
|
89
95
|
});
|
|
96
|
+
useUnsavedChangesGuard(
|
|
97
|
+
() => props.editable === true && (editorState.isDirty.value || editorState.saving.value)
|
|
98
|
+
);
|
|
90
99
|
watch(editorOpen, (open, prev) => {
|
|
91
100
|
if (open && !prev) editorState.reset();
|
|
92
101
|
});
|
|
@@ -137,11 +146,16 @@ function onRootClick(event) {
|
|
|
137
146
|
</SidebarProvider>
|
|
138
147
|
</div>
|
|
139
148
|
|
|
149
|
+
<!-- While the draft is dirty (or a save is in flight) the modal refuses
|
|
150
|
+
casual dismissal — Esc, outside-click, and the × button all drop the
|
|
151
|
+
session on the reopen re-anchor above. The explicit footer exits
|
|
152
|
+
(取消 discards, 确认 saves) remain the only ways out of a dirty
|
|
153
|
+
session. Mirrors `config/index.vue`. -->
|
|
140
154
|
<ShwfedModal
|
|
141
155
|
v-if="editable"
|
|
142
156
|
v-model:open="editorOpen"
|
|
143
157
|
content-width="min(52rem, 92vw)"
|
|
144
|
-
:dismissible="
|
|
158
|
+
:dismissible="!editorState.isDirty.value && !editorState.saving.value"
|
|
145
159
|
>
|
|
146
160
|
<template #title>
|
|
147
161
|
导航侧栏
|
|
@@ -149,7 +163,21 @@ function onRootClick(event) {
|
|
|
149
163
|
<template #description>
|
|
150
164
|
<span class="sr-only">编辑导航侧栏配置</span>
|
|
151
165
|
</template>
|
|
152
|
-
|
|
166
|
+
<!-- The overlay freezes the form while the host save runs: an edit
|
|
167
|
+
landing mid-save would be silently clobbered by the post-commit
|
|
168
|
+
draft re-anchor. -->
|
|
169
|
+
<div class="relative">
|
|
170
|
+
<EditorBody :state="editorState" />
|
|
171
|
+
<div
|
|
172
|
+
v-if="editorState.saving.value"
|
|
173
|
+
class="absolute inset-0 z-20 flex items-center justify-center bg-white/60"
|
|
174
|
+
>
|
|
175
|
+
<Icon
|
|
176
|
+
icon="fluent:arrow-sync-20-regular"
|
|
177
|
+
class="animate-spin text-2xl text-zinc-500"
|
|
178
|
+
/>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
153
181
|
<template #footer>
|
|
154
182
|
<EditorFooter
|
|
155
183
|
:state="editorState"
|
|
@@ -3,6 +3,7 @@ import { type SidebarConfigValue } from './schema.js';
|
|
|
3
3
|
type __VLS_Props = {
|
|
4
4
|
configure?: (env: Environment) => void;
|
|
5
5
|
editable?: boolean;
|
|
6
|
+
save?: (config: SidebarConfigValue) => void | Promise<void>;
|
|
6
7
|
storageKey?: string;
|
|
7
8
|
collapsible?: 'offcanvas' | 'icon' | 'none';
|
|
8
9
|
width?: string;
|
|
@@ -4,14 +4,24 @@ import { type SidebarConfigValue } from './schema.js';
|
|
|
4
4
|
export interface SidebarEditorState {
|
|
5
5
|
draft: Ref<SidebarConfigValue>;
|
|
6
6
|
validationError: Ref<string | null>;
|
|
7
|
+
saveError: Ref<string | null>;
|
|
8
|
+
saving: Ref<boolean>;
|
|
7
9
|
isDirty: ComputedRef<boolean>;
|
|
8
10
|
configure: (env: Environment) => void;
|
|
9
|
-
confirm: () =>
|
|
11
|
+
confirm: () => Promise<boolean>;
|
|
10
12
|
dismiss: () => void;
|
|
11
13
|
reset: () => void;
|
|
12
14
|
}
|
|
13
15
|
export interface UseSidebarEditorOptions {
|
|
14
16
|
configure?: (env: Environment) => void;
|
|
17
|
+
/**
|
|
18
|
+
* Delegated persistence. When provided, a successful 确认 first awaits this
|
|
19
|
+
* with the validated config; only after it resolves does the draft commit
|
|
20
|
+
* to the outer model and the editor close. A rejection keeps the editor
|
|
21
|
+
* open with the draft intact and surfaces the failure via `saveError` (see
|
|
22
|
+
* config/use-editor.ts — same contract).
|
|
23
|
+
*/
|
|
24
|
+
save?: (config: SidebarConfigValue) => void | Promise<void>;
|
|
15
25
|
/** Called after a successful 确认 commits the draft to the outer model. */
|
|
16
26
|
onClose?: () => void;
|
|
17
27
|
}
|
|
@@ -16,41 +16,62 @@ export function useSidebarEditor(model, options = {}) {
|
|
|
16
16
|
() => JSON.stringify(draft.value) !== JSON.stringify(baseline.value)
|
|
17
17
|
);
|
|
18
18
|
const validationError = ref(null);
|
|
19
|
+
const saveError = ref(null);
|
|
20
|
+
const saving = ref(false);
|
|
19
21
|
const validityRegistry = createExpressionValidityRegistry();
|
|
20
22
|
provideExpressionValidity(validityRegistry);
|
|
21
|
-
function confirm() {
|
|
23
|
+
async function confirm() {
|
|
24
|
+
if (saving.value) return false;
|
|
22
25
|
const schema = SidebarConfig(configure);
|
|
26
|
+
let decoded;
|
|
23
27
|
try {
|
|
24
|
-
|
|
25
|
-
if (!validityRegistry.allValid()) {
|
|
26
|
-
validationError.value = "\u5B58\u5728\u65E0\u6548\u7684\u8DE8\u5C42\u4F5C\u7528\u57DF\u5F15\u7528\uFF0C\u8BF7\u4FEE\u6B63\u6807\u7EA2\u7684\u8868\u8FBE\u5F0F\u540E\u518D\u4FDD\u5B58";
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
const dangling = findDanglingLinkages(decoded);
|
|
30
|
-
if (dangling.length > 0) {
|
|
31
|
-
validationError.value = formatDanglingLinkages(dangling, decoded, () => void 0);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
validationError.value = null;
|
|
35
|
-
model.value = decoded;
|
|
36
|
-
baseline.value = deepClone(decoded);
|
|
37
|
-
draft.value = deepClone(decoded);
|
|
38
|
-
options.onClose?.();
|
|
28
|
+
decoded = Schema.decodeUnknownSync(schema)(draft.value);
|
|
39
29
|
} catch (err) {
|
|
40
30
|
validationError.value = formatValidationError(err, () => void 0, draft.value);
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (!validityRegistry.allValid()) {
|
|
34
|
+
validationError.value = "\u5B58\u5728\u65E0\u6548\u7684\u8DE8\u5C42\u4F5C\u7528\u57DF\u5F15\u7528\uFF0C\u8BF7\u4FEE\u6B63\u6807\u7EA2\u7684\u8868\u8FBE\u5F0F\u540E\u518D\u4FDD\u5B58";
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const dangling = findDanglingLinkages(decoded);
|
|
38
|
+
if (dangling.length > 0) {
|
|
39
|
+
validationError.value = formatDanglingLinkages(dangling, decoded, () => void 0);
|
|
40
|
+
return false;
|
|
41
41
|
}
|
|
42
|
+
validationError.value = null;
|
|
43
|
+
if (options.save) {
|
|
44
|
+
saving.value = true;
|
|
45
|
+
saveError.value = null;
|
|
46
|
+
try {
|
|
47
|
+
await options.save(deepClone(decoded));
|
|
48
|
+
} catch (err) {
|
|
49
|
+
saveError.value = `\u4FDD\u5B58\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`;
|
|
50
|
+
return false;
|
|
51
|
+
} finally {
|
|
52
|
+
saving.value = false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
model.value = decoded;
|
|
56
|
+
baseline.value = deepClone(decoded);
|
|
57
|
+
draft.value = deepClone(decoded);
|
|
58
|
+
options.onClose?.();
|
|
59
|
+
return true;
|
|
42
60
|
}
|
|
43
61
|
function reset() {
|
|
44
62
|
baseline.value = deepClone(model.value);
|
|
45
63
|
draft.value = deepClone(model.value);
|
|
46
64
|
validationError.value = null;
|
|
65
|
+
saveError.value = null;
|
|
47
66
|
}
|
|
48
67
|
function dismiss() {
|
|
68
|
+
if (saving.value) return;
|
|
49
69
|
reset();
|
|
50
70
|
options.onClose?.();
|
|
51
71
|
}
|
|
52
72
|
watch(draft, () => {
|
|
53
73
|
if (validationError.value) validationError.value = null;
|
|
74
|
+
if (saveError.value) saveError.value = null;
|
|
54
75
|
}, { deep: true });
|
|
55
|
-
return { draft, validationError, isDirty, configure, confirm, dismiss, reset };
|
|
76
|
+
return { draft, validationError, saveError, saving, isDirty, configure, confirm, dismiss, reset };
|
|
56
77
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type __VLS_ModelProps = {
|
|
2
|
+
modelValue: Record<string, any>;
|
|
3
|
+
};
|
|
4
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
5
|
+
"update:modelValue": (value: Record<string, any>) => any;
|
|
6
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
7
|
+
"onUpdate:modelValue"?: ((value: Record<string, any>) => any) | undefined;
|
|
8
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
9
|
+
declare const _default: typeof __VLS_export;
|
|
10
|
+
export default _default;
|
package/dist/runtime/components/table/columns/2026-07-12/com.shwfed.table.column.dynamic/config.vue
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ExpressionEditor } from "../../../../ui/expression-editor";
|
|
3
|
+
import { Field, FieldLabel } from "../../../../ui/field";
|
|
4
|
+
import { Markdown } from "../../../../ui/markdown";
|
|
5
|
+
import { getStructFieldDescription, getStructFieldTitle } from "../../../utils/schema-meta";
|
|
6
|
+
import { schema } from "./schema";
|
|
7
|
+
defineOptions({ name: "ShwfedTableDynamicColumnsConfig" });
|
|
8
|
+
const value = defineModel({ type: Object, ...{ required: true } });
|
|
9
|
+
const fieldSchema = schema(() => {
|
|
10
|
+
});
|
|
11
|
+
const fieldTitle = (field) => getStructFieldTitle(fieldSchema, field) ?? field;
|
|
12
|
+
const fieldDescription = (field) => getStructFieldDescription(fieldSchema, field);
|
|
13
|
+
const NON_ROW_EXCLUDE = ["row", "index", "selections", "options"];
|
|
14
|
+
const TRANSFORM_VARS = {
|
|
15
|
+
json: { type: "optional<dyn>", label: "HTTP \u54CD\u5E94\u4F53" },
|
|
16
|
+
query: { type: "dyn", label: "\u641C\u7D22\u6761\u4EF6" }
|
|
17
|
+
};
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<div class="space-y-5">
|
|
22
|
+
<Field orientation="vertical">
|
|
23
|
+
<FieldLabel class="text-xs text-zinc-500">
|
|
24
|
+
<template
|
|
25
|
+
v-if="fieldDescription('request')"
|
|
26
|
+
#tooltip
|
|
27
|
+
>
|
|
28
|
+
<Markdown
|
|
29
|
+
:source="fieldDescription('request')"
|
|
30
|
+
block
|
|
31
|
+
class="prose prose-sm prose-zinc"
|
|
32
|
+
/>
|
|
33
|
+
</template>
|
|
34
|
+
{{ fieldTitle("request") }}
|
|
35
|
+
</FieldLabel>
|
|
36
|
+
<ExpressionEditor
|
|
37
|
+
:model-value="value.request ?? ''"
|
|
38
|
+
placeholder="如 http.get('/api/columns').query('channel', 'invoice')"
|
|
39
|
+
result-type="HttpRequest"
|
|
40
|
+
multiline
|
|
41
|
+
class="min-h-20"
|
|
42
|
+
:exclude-vars="NON_ROW_EXCLUDE"
|
|
43
|
+
@update:model-value="(v) => value = { ...value, request: v }"
|
|
44
|
+
/>
|
|
45
|
+
</Field>
|
|
46
|
+
|
|
47
|
+
<Field orientation="vertical">
|
|
48
|
+
<FieldLabel class="text-xs text-zinc-500">
|
|
49
|
+
<template
|
|
50
|
+
v-if="fieldDescription('transform')"
|
|
51
|
+
#tooltip
|
|
52
|
+
>
|
|
53
|
+
<Markdown
|
|
54
|
+
:source="fieldDescription('transform')"
|
|
55
|
+
block
|
|
56
|
+
class="prose prose-sm prose-zinc"
|
|
57
|
+
/>
|
|
58
|
+
</template>
|
|
59
|
+
{{ fieldTitle("transform") }}
|
|
60
|
+
</FieldLabel>
|
|
61
|
+
<ExpressionEditor
|
|
62
|
+
:model-value="value.transform ?? ''"
|
|
63
|
+
placeholder="如 json.orValue([]).map(c, { 'key': c.parameter_name, 'header': c.name, 'kind': 'text', 'accessor': "row.custom_property.filter(p, p.name == '" + c.parameter_name + "')[0].?value" })"
|
|
64
|
+
result-type="list"
|
|
65
|
+
multiline
|
|
66
|
+
class="min-h-32"
|
|
67
|
+
:extra-vars="TRANSFORM_VARS"
|
|
68
|
+
:exclude-vars="NON_ROW_EXCLUDE"
|
|
69
|
+
@update:model-value="(v) => value = { ...value, transform: v }"
|
|
70
|
+
/>
|
|
71
|
+
</Field>
|
|
72
|
+
</div>
|
|
73
|
+
</template>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type __VLS_ModelProps = {
|
|
2
|
+
modelValue: Record<string, any>;
|
|
3
|
+
};
|
|
4
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
5
|
+
"update:modelValue": (value: Record<string, any>) => any;
|
|
6
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
7
|
+
"onUpdate:modelValue"?: ((value: Record<string, any>) => any) | undefined;
|
|
8
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
9
|
+
declare const _default: typeof __VLS_export;
|
|
10
|
+
export default _default;
|
package/dist/runtime/components/table/columns/2026-07-12/com.shwfed.table.column.dynamic/schema.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Schema } from 'effect';
|
|
2
|
+
import type { Environment } from '../../../../../vendor/cel-js/lib/index.js';
|
|
3
|
+
import type { ColumnExpand } from '../../../utils/resolve.js';
|
|
4
|
+
export declare const type: "com.shwfed.table.column.dynamic";
|
|
5
|
+
export declare const compatibilityDate: "2026-07-12";
|
|
6
|
+
export declare const metadata: {
|
|
7
|
+
readonly name: "动态列";
|
|
8
|
+
readonly icon: "fluent:table-lightning-20-regular";
|
|
9
|
+
readonly description: import("../../../../../share/markdown.js").Markdown;
|
|
10
|
+
};
|
|
11
|
+
declare const DynColumnKind: Schema.Literal<["text", "number", "date"]>;
|
|
12
|
+
export type DynColumnKindValue = Schema.Schema.Type<typeof DynColumnKind>;
|
|
13
|
+
/**
|
|
14
|
+
* The narrow, module-owned DTO the `transform` must emit per column. Keeping
|
|
15
|
+
* this contract closed (vs. exposing the internal column union) means internal
|
|
16
|
+
* schema evolution never breaks app config, the transform stays terse, and dyn
|
|
17
|
+
* cols auto-upgrade to the current renderer. Each element is decoded against
|
|
18
|
+
* this at runtime (partial-drop, loud) — the strongest static guarantee is
|
|
19
|
+
* only that `transform` returns `list<dyn>`.
|
|
20
|
+
*/
|
|
21
|
+
export declare const DynColumnDescriptor: Schema.Struct<{
|
|
22
|
+
key: Schema.filter<typeof Schema.String>;
|
|
23
|
+
header: typeof Schema.String;
|
|
24
|
+
kind: Schema.Literal<["text", "number", "date"]>;
|
|
25
|
+
accessor: Schema.filter<typeof Schema.String>;
|
|
26
|
+
/** px column width; defaults to 150. */
|
|
27
|
+
width: Schema.optional<typeof Schema.Number>;
|
|
28
|
+
/** Client-side sort by accessed value. */
|
|
29
|
+
sortable: Schema.optional<typeof Schema.Boolean>;
|
|
30
|
+
/** Backend sort field; only needed for server-side sort. */
|
|
31
|
+
sortKey: Schema.optional<typeof Schema.String>;
|
|
32
|
+
/** Default-hidden; the user can re-show it. */
|
|
33
|
+
hidden: Schema.optional<typeof Schema.Boolean>;
|
|
34
|
+
/** Header hover text; wrapped into a Locale by `expand`. */
|
|
35
|
+
tooltip: Schema.optional<typeof Schema.String>;
|
|
36
|
+
/** Text / number alignment. */
|
|
37
|
+
align: Schema.optional<Schema.Literal<["left", "center", "right"]>>;
|
|
38
|
+
}>;
|
|
39
|
+
export type DynColumnDescriptorValue = Schema.Schema.Type<typeof DynColumnDescriptor>;
|
|
40
|
+
export declare function schema(configure: (env: Environment) => void): Schema.Struct<{
|
|
41
|
+
request: Schema.Schema<string, string, never>;
|
|
42
|
+
transform: Schema.Schema<string, string, never>;
|
|
43
|
+
id: Schema.refine<string, typeof Schema.String>;
|
|
44
|
+
groupId: Schema.optional<typeof Schema.UUID>;
|
|
45
|
+
type: Schema.Literal<["com.shwfed.table.column.dynamic"]>;
|
|
46
|
+
compatibilityDate: Schema.Literal<["2026-07-12"]>;
|
|
47
|
+
}>;
|
|
48
|
+
export type Value = Schema.Schema.Type<ReturnType<typeof schema>>;
|
|
49
|
+
export declare function defaults(): Partial<Value>;
|
|
50
|
+
/**
|
|
51
|
+
* Runtime generator. Evaluates the block's `transform` against the fetched
|
|
52
|
+
* `json`, then decodes each element against `DynColumnDescriptor` — keeping the
|
|
53
|
+
* valid, dropping the invalid / duplicate / colliding (partial-drop, loud).
|
|
54
|
+
* Three failure layers (see the design doc):
|
|
55
|
+
* 1. transform throws / returns a non-list → the whole block yields 0 columns
|
|
56
|
+
* (other columns render normally).
|
|
57
|
+
* 2. individual elements malformed / duplicate `key` / `key` colliding with a
|
|
58
|
+
* static id → drop that element alone, first-wins on keys.
|
|
59
|
+
* 3. per-row accessor eval throws → handled downstream (`translateColumn`'s
|
|
60
|
+
* `accessorFn` try/catches per row) — not here.
|
|
61
|
+
*/
|
|
62
|
+
export declare const expand: ColumnExpand;
|
|
63
|
+
export {};
|