@shwfed/nuxt 0.9.2 → 0.10.0
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/fields.d.vue.ts +21 -3
- package/dist/runtime/components/fields.vue +1 -0
- package/dist/runtime/components/fields.vue.d.ts +21 -3
- package/dist/runtime/components/ui/field/FieldError.vue +1 -1
- package/dist/runtime/components/ui/field/index.js +1 -1
- package/dist/runtime/components/ui/fields/Fields.d.vue.ts +39 -2
- package/dist/runtime/components/ui/fields/Fields.vue +29 -4
- package/dist/runtime/components/ui/fields/Fields.vue.d.ts +39 -2
- package/dist/runtime/components/ui/fields/schema.d.ts +28 -2
- package/dist/runtime/components/ui/fields/schema.js +12 -1
- package/dist/runtime/components/ui/fields-configurator/FieldsConfiguratorDialog.d.vue.ts +16 -0
- package/dist/runtime/components/ui/fields-configurator/FieldsConfiguratorDialog.vue +609 -145
- package/dist/runtime/components/ui/fields-configurator/FieldsConfiguratorDialog.vue.d.ts +16 -0
- package/package.json +1 -1
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { useNuxtApp } from "#app";
|
|
3
|
+
import z, {} from "zod";
|
|
2
4
|
import { useSortable } from "@vueuse/integrations/useSortable";
|
|
3
5
|
import { Icon } from "@iconify/vue";
|
|
4
6
|
import { computed, nextTick, ref, toRaw, watch } from "vue";
|
|
5
7
|
import { useI18n } from "vue-i18n";
|
|
6
8
|
import {
|
|
7
9
|
CalendarFieldC,
|
|
10
|
+
FieldsConfigC,
|
|
8
11
|
NumberFieldC,
|
|
9
12
|
SelectFieldC,
|
|
13
|
+
SlotFieldC,
|
|
10
14
|
StringFieldC,
|
|
11
15
|
FieldsStyleC
|
|
12
16
|
} from "../fields/schema";
|
|
@@ -26,7 +30,6 @@ import {
|
|
|
26
30
|
DropdownMenuItem,
|
|
27
31
|
DropdownMenuTrigger
|
|
28
32
|
} from "../dropdown-menu";
|
|
29
|
-
import { IconPicker } from "../icon-picker";
|
|
30
33
|
import { Input } from "../input";
|
|
31
34
|
import Locale from "../locale/Locale.vue";
|
|
32
35
|
import { NativeSelect, NativeSelectOption } from "../native-select";
|
|
@@ -39,9 +42,11 @@ const emit = defineEmits(["confirm"]);
|
|
|
39
42
|
const open = defineModel("open", { type: Boolean, ...{
|
|
40
43
|
default: false
|
|
41
44
|
} });
|
|
45
|
+
const { $toast } = useNuxtApp();
|
|
42
46
|
const { t } = useI18n();
|
|
43
47
|
const draftOrientation = ref("horizontal");
|
|
44
48
|
const draftStyle = ref();
|
|
49
|
+
const search = ref("");
|
|
45
50
|
const selectedItemId = ref("general");
|
|
46
51
|
const draftFields = ref([]);
|
|
47
52
|
const sortableListRef = ref(null);
|
|
@@ -51,17 +56,28 @@ const fieldTypeOptions = computed(() => [
|
|
|
51
56
|
{ type: "string", label: t("field-type-string") },
|
|
52
57
|
{ type: "number", label: t("field-type-number") },
|
|
53
58
|
{ type: "select", label: t("field-type-select") },
|
|
54
|
-
{ type: "calendar", label: t("field-type-calendar") }
|
|
59
|
+
{ type: "calendar", label: t("field-type-calendar") },
|
|
60
|
+
{ type: "slot", label: t("field-type-slot") }
|
|
55
61
|
]);
|
|
56
62
|
const generalItem = computed(() => ({
|
|
57
63
|
id: "general",
|
|
58
64
|
label: t("general")
|
|
59
65
|
}));
|
|
66
|
+
const normalizedSearch = computed(() => search.value.trim().toLocaleLowerCase());
|
|
60
67
|
const selectedField = computed(() => draftFields.value.find((field) => field.draftId === selectedItemId.value));
|
|
61
|
-
const selectedFieldValidationRules = computed(() =>
|
|
68
|
+
const selectedFieldValidationRules = computed(() => {
|
|
69
|
+
const field = selectedField.value?.field;
|
|
70
|
+
if (!field || field.type === "slot") {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
return field.validation ?? [];
|
|
74
|
+
});
|
|
62
75
|
function createDraftId() {
|
|
63
76
|
return crypto.randomUUID();
|
|
64
77
|
}
|
|
78
|
+
function createFieldId() {
|
|
79
|
+
return crypto.randomUUID();
|
|
80
|
+
}
|
|
65
81
|
function createDefaultLocaleValue() {
|
|
66
82
|
return [{ locale: "zh", message: "" }];
|
|
67
83
|
}
|
|
@@ -83,12 +99,20 @@ function normalizeOrientation(value) {
|
|
|
83
99
|
function getFieldTypeLabel(type) {
|
|
84
100
|
return t(`field-type-${type}`);
|
|
85
101
|
}
|
|
102
|
+
function getSlotFieldLabel(field) {
|
|
103
|
+
return t("slot-field", {
|
|
104
|
+
id: field.id.slice(0, 8)
|
|
105
|
+
});
|
|
106
|
+
}
|
|
86
107
|
function getUnnamedFieldLabel(field) {
|
|
87
108
|
return t("unnamed-field", {
|
|
88
109
|
type: getFieldTypeLabel(field.type)
|
|
89
110
|
});
|
|
90
111
|
}
|
|
91
112
|
function getFieldChineseTitle(field) {
|
|
113
|
+
if (field.type === "slot") {
|
|
114
|
+
return void 0;
|
|
115
|
+
}
|
|
92
116
|
const zhTitle = field.title.find((item) => item.locale === "zh");
|
|
93
117
|
if (!zhTitle) {
|
|
94
118
|
return void 0;
|
|
@@ -97,14 +121,28 @@ function getFieldChineseTitle(field) {
|
|
|
97
121
|
return message.length > 0 ? message : void 0;
|
|
98
122
|
}
|
|
99
123
|
function getFieldListLabel(field) {
|
|
124
|
+
if (field.type === "slot") {
|
|
125
|
+
return getSlotFieldLabel(field);
|
|
126
|
+
}
|
|
100
127
|
return getFieldChineseTitle(field) ?? getUnnamedFieldLabel(field);
|
|
101
128
|
}
|
|
102
129
|
const fieldItems = computed(() => draftFields.value.map((item) => ({
|
|
103
130
|
itemId: item.draftId,
|
|
131
|
+
fieldId: item.field.id,
|
|
104
132
|
label: getFieldListLabel(item.field),
|
|
105
|
-
path: item.field.path,
|
|
133
|
+
path: item.field.type === "slot" ? void 0 : item.field.path,
|
|
134
|
+
searchMeta: item.field.type === "slot" ? item.field.id : [item.field.path, item.field.id].filter(Boolean).join(" "),
|
|
106
135
|
type: item.field.type
|
|
107
136
|
})));
|
|
137
|
+
const filteredFieldItems = computed(() => {
|
|
138
|
+
if (!normalizedSearch.value) {
|
|
139
|
+
return fieldItems.value;
|
|
140
|
+
}
|
|
141
|
+
return fieldItems.value.filter((item) => {
|
|
142
|
+
const haystack = [item.label, item.searchMeta].filter(Boolean).join(" ").toLocaleLowerCase();
|
|
143
|
+
return haystack.includes(normalizedSearch.value);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
108
146
|
const selectedItemLabel = computed(() => selectedField.value ? getFieldListLabel(selectedField.value.field) : generalItem.value.label);
|
|
109
147
|
const sortable = useSortable(sortableListRef, sortableItemIds);
|
|
110
148
|
function getFieldErrorKey(draftId, fieldKey) {
|
|
@@ -214,25 +252,34 @@ function normalizeField(field) {
|
|
|
214
252
|
disabled: normalizeOptionalString(field.disabled ?? ""),
|
|
215
253
|
validation: normalizeValidationRules(field.validation)
|
|
216
254
|
};
|
|
255
|
+
case "slot":
|
|
256
|
+
return {
|
|
257
|
+
...field,
|
|
258
|
+
style: normalizeOptionalString(field.style ?? "")
|
|
259
|
+
};
|
|
217
260
|
}
|
|
218
261
|
}
|
|
219
262
|
function createField(type) {
|
|
220
263
|
const title = createDefaultLocaleValue();
|
|
264
|
+
const id = createFieldId();
|
|
221
265
|
switch (type) {
|
|
222
266
|
case "string":
|
|
223
267
|
return {
|
|
268
|
+
id,
|
|
224
269
|
type,
|
|
225
270
|
path: "",
|
|
226
271
|
title
|
|
227
272
|
};
|
|
228
273
|
case "number":
|
|
229
274
|
return {
|
|
275
|
+
id,
|
|
230
276
|
type,
|
|
231
277
|
path: "",
|
|
232
278
|
title
|
|
233
279
|
};
|
|
234
280
|
case "select":
|
|
235
281
|
return {
|
|
282
|
+
id,
|
|
236
283
|
type,
|
|
237
284
|
path: "",
|
|
238
285
|
title,
|
|
@@ -243,18 +290,28 @@ function createField(type) {
|
|
|
243
290
|
};
|
|
244
291
|
case "calendar":
|
|
245
292
|
return {
|
|
293
|
+
id,
|
|
246
294
|
type,
|
|
247
295
|
path: "",
|
|
248
296
|
title,
|
|
249
297
|
mode: "date",
|
|
250
298
|
value: "yyyy-MM-dd"
|
|
251
299
|
};
|
|
300
|
+
case "slot":
|
|
301
|
+
return {
|
|
302
|
+
id,
|
|
303
|
+
type
|
|
304
|
+
};
|
|
252
305
|
}
|
|
253
306
|
}
|
|
254
307
|
function resetDraftConfig() {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
308
|
+
applyDraftConfig(props.config);
|
|
309
|
+
}
|
|
310
|
+
function applyDraftConfig(config) {
|
|
311
|
+
draftOrientation.value = config.orientation ?? "horizontal";
|
|
312
|
+
draftStyle.value = normalizeOptionalString(config.style ?? "");
|
|
313
|
+
search.value = "";
|
|
314
|
+
draftFields.value = cloneFields(config.fields);
|
|
258
315
|
selectedItemId.value = "general";
|
|
259
316
|
validationErrors.value = {};
|
|
260
317
|
}
|
|
@@ -289,7 +346,7 @@ function configureSortable() {
|
|
|
289
346
|
}
|
|
290
347
|
async function refreshSortable() {
|
|
291
348
|
sortable.stop();
|
|
292
|
-
if (!open.value || draftFields.value.length === 0) {
|
|
349
|
+
if (!open.value || draftFields.value.length === 0 || normalizedSearch.value) {
|
|
293
350
|
return;
|
|
294
351
|
}
|
|
295
352
|
await nextTick();
|
|
@@ -324,6 +381,21 @@ watch(fieldItems, async (items) => {
|
|
|
324
381
|
await refreshSortable();
|
|
325
382
|
}
|
|
326
383
|
}, { immediate: true });
|
|
384
|
+
watch(filteredFieldItems, (items) => {
|
|
385
|
+
if (!normalizedSearch.value || selectedItemId.value === "general") {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (items.some((item) => item.itemId === selectedItemId.value)) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
selectedItemId.value = items[0]?.itemId ?? "general";
|
|
392
|
+
}, { immediate: true });
|
|
393
|
+
watch(normalizedSearch, async () => {
|
|
394
|
+
if (!open.value) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
await refreshSortable();
|
|
398
|
+
});
|
|
327
399
|
function discardChanges() {
|
|
328
400
|
resetDraftConfig();
|
|
329
401
|
open.value = false;
|
|
@@ -385,26 +457,25 @@ function updateSelectedFieldTitle(value) {
|
|
|
385
457
|
title: value
|
|
386
458
|
}));
|
|
387
459
|
}
|
|
388
|
-
function
|
|
460
|
+
async function copySelectedFieldId() {
|
|
389
461
|
const selected = selectedField.value;
|
|
390
462
|
if (!selected) {
|
|
391
463
|
return;
|
|
392
464
|
}
|
|
393
|
-
|
|
394
|
-
updateDraftField(selected.draftId, (field) => ({
|
|
395
|
-
...field,
|
|
396
|
-
path: String(value).trim()
|
|
397
|
-
}));
|
|
465
|
+
await copyFieldId(selected.field.id);
|
|
398
466
|
}
|
|
399
|
-
function
|
|
467
|
+
async function copyFieldId(fieldId) {
|
|
468
|
+
await writeClipboardText(fieldId, t("copy-field-id-failed"));
|
|
469
|
+
}
|
|
470
|
+
function updateSelectedFieldPath(value) {
|
|
400
471
|
const selected = selectedField.value;
|
|
401
|
-
if (!selected) {
|
|
472
|
+
if (!selected || selected.field.type === "slot") {
|
|
402
473
|
return;
|
|
403
474
|
}
|
|
404
|
-
clearFieldError(selected.draftId, "
|
|
475
|
+
clearFieldError(selected.draftId, "path");
|
|
405
476
|
updateDraftField(selected.draftId, (field) => ({
|
|
406
477
|
...field,
|
|
407
|
-
|
|
478
|
+
path: String(value).trim()
|
|
408
479
|
}));
|
|
409
480
|
}
|
|
410
481
|
function updateSelectedFieldStyle(value) {
|
|
@@ -653,23 +724,28 @@ function updateSelectedCalendarDisableDate(value) {
|
|
|
653
724
|
}
|
|
654
725
|
function addValidationRule() {
|
|
655
726
|
const selected = selectedField.value;
|
|
656
|
-
if (!selected) {
|
|
727
|
+
if (!selected || selected.field.type === "slot") {
|
|
657
728
|
return;
|
|
658
729
|
}
|
|
659
|
-
updateDraftField(selected.draftId, (field) =>
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
730
|
+
updateDraftField(selected.draftId, (field) => {
|
|
731
|
+
if (field.type === "slot") {
|
|
732
|
+
return field;
|
|
733
|
+
}
|
|
734
|
+
return {
|
|
735
|
+
...field,
|
|
736
|
+
validation: [
|
|
737
|
+
...field.validation ?? [],
|
|
738
|
+
{
|
|
739
|
+
expression: "",
|
|
740
|
+
message: ""
|
|
741
|
+
}
|
|
742
|
+
]
|
|
743
|
+
};
|
|
744
|
+
});
|
|
669
745
|
}
|
|
670
746
|
function updateValidationRule(index, updater) {
|
|
671
747
|
const selected = selectedField.value;
|
|
672
|
-
if (!selected) {
|
|
748
|
+
if (!selected || selected.field.type === "slot") {
|
|
673
749
|
return;
|
|
674
750
|
}
|
|
675
751
|
const validation = selected.field.validation ?? [];
|
|
@@ -678,10 +754,15 @@ function updateValidationRule(index, updater) {
|
|
|
678
754
|
return;
|
|
679
755
|
}
|
|
680
756
|
const nextValidation = validation.map((rule, ruleIndex) => ruleIndex === index ? updater(rule) : rule);
|
|
681
|
-
updateDraftField(selected.draftId, (field) =>
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
757
|
+
updateDraftField(selected.draftId, (field) => {
|
|
758
|
+
if (field.type === "slot") {
|
|
759
|
+
return field;
|
|
760
|
+
}
|
|
761
|
+
return {
|
|
762
|
+
...field,
|
|
763
|
+
validation: nextValidation
|
|
764
|
+
};
|
|
765
|
+
});
|
|
685
766
|
}
|
|
686
767
|
function updateSelectedValidationExpression(index, value) {
|
|
687
768
|
const selected = selectedField.value;
|
|
@@ -707,7 +788,7 @@ function updateSelectedValidationMessage(index, value) {
|
|
|
707
788
|
}
|
|
708
789
|
function moveValidationRule(index, offset) {
|
|
709
790
|
const selected = selectedField.value;
|
|
710
|
-
if (!selected) {
|
|
791
|
+
if (!selected || selected.field.type === "slot") {
|
|
711
792
|
return;
|
|
712
793
|
}
|
|
713
794
|
const validation = selected.field.validation ?? [];
|
|
@@ -723,15 +804,20 @@ function moveValidationRule(index, offset) {
|
|
|
723
804
|
}
|
|
724
805
|
nextValidation[index] = targetRule;
|
|
725
806
|
nextValidation[nextIndex] = currentRule;
|
|
726
|
-
updateDraftField(selected.draftId, (field) =>
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
807
|
+
updateDraftField(selected.draftId, (field) => {
|
|
808
|
+
if (field.type === "slot") {
|
|
809
|
+
return field;
|
|
810
|
+
}
|
|
811
|
+
return {
|
|
812
|
+
...field,
|
|
813
|
+
validation: nextValidation
|
|
814
|
+
};
|
|
815
|
+
});
|
|
730
816
|
clearValidationRuleErrors(selected.draftId);
|
|
731
817
|
}
|
|
732
818
|
function deleteValidationRule(index) {
|
|
733
819
|
const selected = selectedField.value;
|
|
734
|
-
if (!selected) {
|
|
820
|
+
if (!selected || selected.field.type === "slot") {
|
|
735
821
|
return;
|
|
736
822
|
}
|
|
737
823
|
const validation = selected.field.validation ?? [];
|
|
@@ -741,7 +827,14 @@ function deleteValidationRule(index) {
|
|
|
741
827
|
clearValidationRuleError(selected.draftId, index, "expression");
|
|
742
828
|
clearValidationRuleError(selected.draftId, index, "message");
|
|
743
829
|
updateDraftField(selected.draftId, (field) => {
|
|
744
|
-
|
|
830
|
+
if (field.type === "slot") {
|
|
831
|
+
return field;
|
|
832
|
+
}
|
|
833
|
+
const validation2 = field.validation ?? [];
|
|
834
|
+
const nextValidation = [
|
|
835
|
+
...validation2.slice(0, index),
|
|
836
|
+
...validation2.slice(index + 1)
|
|
837
|
+
];
|
|
745
838
|
return {
|
|
746
839
|
...field,
|
|
747
840
|
validation: nextValidation.length > 0 ? nextValidation : void 0
|
|
@@ -767,6 +860,10 @@ function getSchemaIssues(field) {
|
|
|
767
860
|
const result = CalendarFieldC.safeParse(field);
|
|
768
861
|
return result.success ? [] : result.error.issues;
|
|
769
862
|
}
|
|
863
|
+
case "slot": {
|
|
864
|
+
const result = SlotFieldC.safeParse(field);
|
|
865
|
+
return result.success ? [] : result.error.issues;
|
|
866
|
+
}
|
|
770
867
|
}
|
|
771
868
|
}
|
|
772
869
|
function normalizeIssuePath(path) {
|
|
@@ -794,19 +891,30 @@ function validateDraftFields() {
|
|
|
794
891
|
draftId: item.draftId,
|
|
795
892
|
field: normalizeField(item.field)
|
|
796
893
|
}));
|
|
894
|
+
const idOwners = {};
|
|
797
895
|
const pathOwners = {};
|
|
798
896
|
let firstInvalidItemId;
|
|
799
897
|
for (const item of normalizedFields) {
|
|
800
|
-
const
|
|
801
|
-
if (
|
|
802
|
-
setError(errors, getFieldErrorKey(item.draftId, "
|
|
803
|
-
|
|
804
|
-
} else if (existingOwner !== void 0) {
|
|
805
|
-
setError(errors, getFieldErrorKey(item.draftId, "path"), t("field-path-duplicate"));
|
|
806
|
-
setError(errors, getFieldErrorKey(existingOwner, "path"), t("field-path-duplicate"));
|
|
898
|
+
const existingIdOwner = idOwners[item.field.id];
|
|
899
|
+
if (existingIdOwner !== void 0) {
|
|
900
|
+
setError(errors, getFieldErrorKey(item.draftId, "id"), t("field-id-duplicate"));
|
|
901
|
+
setError(errors, getFieldErrorKey(existingIdOwner, "id"), t("field-id-duplicate"));
|
|
807
902
|
firstInvalidItemId = firstInvalidItemId ?? item.draftId;
|
|
808
903
|
} else {
|
|
809
|
-
|
|
904
|
+
idOwners[item.field.id] = item.draftId;
|
|
905
|
+
}
|
|
906
|
+
if (item.field.type !== "slot") {
|
|
907
|
+
const existingPathOwner = pathOwners[item.field.path];
|
|
908
|
+
if (item.field.path.length === 0) {
|
|
909
|
+
setError(errors, getFieldErrorKey(item.draftId, "path"), t("field-path-required"));
|
|
910
|
+
firstInvalidItemId = firstInvalidItemId ?? item.draftId;
|
|
911
|
+
} else if (existingPathOwner !== void 0) {
|
|
912
|
+
setError(errors, getFieldErrorKey(item.draftId, "path"), t("field-path-duplicate"));
|
|
913
|
+
setError(errors, getFieldErrorKey(existingPathOwner, "path"), t("field-path-duplicate"));
|
|
914
|
+
firstInvalidItemId = firstInvalidItemId ?? item.draftId;
|
|
915
|
+
} else {
|
|
916
|
+
pathOwners[item.field.path] = item.draftId;
|
|
917
|
+
}
|
|
810
918
|
}
|
|
811
919
|
if (item.field.type === "calendar" && item.field.value.length === 0) {
|
|
812
920
|
setError(errors, getFieldErrorKey(item.draftId, "value"), t("calendar-value-required"));
|
|
@@ -828,10 +936,10 @@ function validateDraftFields() {
|
|
|
828
936
|
}
|
|
829
937
|
return normalizedFields;
|
|
830
938
|
}
|
|
831
|
-
function
|
|
939
|
+
function buildDraftConfig() {
|
|
832
940
|
const normalizedFields = validateDraftFields();
|
|
833
941
|
if (!normalizedFields) {
|
|
834
|
-
return;
|
|
942
|
+
return void 0;
|
|
835
943
|
}
|
|
836
944
|
const generalStyleResult = FieldsStyleC.safeParse(draftStyle.value);
|
|
837
945
|
if (!generalStyleResult.success) {
|
|
@@ -840,9 +948,8 @@ function confirmChanges() {
|
|
|
840
948
|
[getGeneralErrorKey("style")]: generalStyleResult.error.issues[0]?.message ?? t("general-style-invalid")
|
|
841
949
|
};
|
|
842
950
|
selectedItemId.value = "general";
|
|
843
|
-
return;
|
|
951
|
+
return void 0;
|
|
844
952
|
}
|
|
845
|
-
draftFields.value = normalizedFields.map((item) => createDraftField(item.field));
|
|
846
953
|
const nextConfig = {
|
|
847
954
|
fields: normalizedFields.map((item) => item.field)
|
|
848
955
|
};
|
|
@@ -852,7 +959,235 @@ function confirmChanges() {
|
|
|
852
959
|
if (generalStyleResult.data) {
|
|
853
960
|
nextConfig.style = generalStyleResult.data;
|
|
854
961
|
}
|
|
855
|
-
|
|
962
|
+
return {
|
|
963
|
+
config: nextConfig,
|
|
964
|
+
normalizedFields
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
function showImportError(message) {
|
|
968
|
+
$toast.error(message);
|
|
969
|
+
}
|
|
970
|
+
function showCopyError(message) {
|
|
971
|
+
$toast.error(message);
|
|
972
|
+
}
|
|
973
|
+
function showImportErrorWithCopyAction(message, onClick) {
|
|
974
|
+
$toast.error(message, {
|
|
975
|
+
action: {
|
|
976
|
+
label: t("copy-paste-error"),
|
|
977
|
+
onClick
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
function getValidDraftConfig(errorMessage) {
|
|
982
|
+
const result = buildDraftConfig();
|
|
983
|
+
if (!result) {
|
|
984
|
+
showCopyError(errorMessage);
|
|
985
|
+
return void 0;
|
|
986
|
+
}
|
|
987
|
+
return result.config;
|
|
988
|
+
}
|
|
989
|
+
function buildFieldsConfigJsonSchema() {
|
|
990
|
+
return z.toJSONSchema(FieldsConfigC, {
|
|
991
|
+
io: "input",
|
|
992
|
+
unrepresentable: "any"
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
function buildAiPromptHeaderMarkdown() {
|
|
996
|
+
return [
|
|
997
|
+
"# \u5B57\u6BB5\u914D\u7F6E AI \u4E0A\u4E0B\u6587",
|
|
998
|
+
"\u4F60\u662F\u4E00\u4E2A\u5E2E\u52A9\u7528\u6237\u914D\u7F6E\u5B57\u6BB5\u7EC4\u4EF6\u7684 AI \u52A9\u624B\uFF0C\u8D1F\u8D23\u89E3\u91CA\u3001\u4FEE\u6539\u5E76\u751F\u6210\u53EF\u7C98\u8D34\u7684\u5B57\u6BB5\u914D\u7F6E\u3002",
|
|
999
|
+
"\u5982\u679C\u4F60\u4E0D\u786E\u5B9A\u67D0\u4E2A\u5B57\u6BB5\u3001\u8868\u8FBE\u5F0F\u53D8\u91CF\u3001\u8FD0\u884C\u65F6\u4E0A\u4E0B\u6587\u6216\u4E1A\u52A1\u542B\u4E49\uFF0C\u5FC5\u987B\u660E\u786E\u8BF4\u660E\u4E0D\u786E\u5B9A\u70B9\uFF0C\u4E0D\u8981\u731C\u6D4B\u3002",
|
|
1000
|
+
"\u53EA\u6709\u5F53\u7528\u6237\u660E\u786E\u8981\u6C42\u751F\u6210\u914D\u7F6E\u65F6\uFF0C\u624D\u8FD4\u56DE\u5B8C\u6574\u914D\u7F6E\u6216\u914D\u7F6E\u7247\u6BB5\uFF1B\u5982\u679C\u8FD4\u56DE\u914D\u7F6E\uFF0C\u5FC5\u987B\u653E\u5728 Markdown code block \u4E2D\u3002"
|
|
1001
|
+
].join("\n");
|
|
1002
|
+
}
|
|
1003
|
+
function buildDslGuideMarkdown() {
|
|
1004
|
+
return [
|
|
1005
|
+
"## DSL / CEL \u7F16\u5199\u8BF4\u660E",
|
|
1006
|
+
"\u672C\u914D\u7F6E\u4E2D\u7684\u8868\u8FBE\u5F0F\u5B57\u6BB5\u5FC5\u987B\u586B\u5199 CEL \u5B57\u7B26\u4E32\uFF0C\u4E0D\u8981\u751F\u6210 JavaScript\u3001TypeScript\u3001\u7BAD\u5934\u51FD\u6570\u6216\u4F2A\u4EE3\u7801\u3002",
|
|
1007
|
+
"",
|
|
1008
|
+
"### 1. \u57FA\u7840\u8BED\u6CD5",
|
|
1009
|
+
'- \u5B57\u9762\u91CF\uFF1A`"text"`\u3001`123`\u3001`true`\u3001`false`\u3001`null`\u3002',
|
|
1010
|
+
'- \u5217\u8868 / \u5BF9\u8C61\uFF1A`[1, 2]`\u3001`{"display": "grid"}`\u3002',
|
|
1011
|
+
"- \u8BBF\u95EE\uFF1A`.`\u3001`[]`\u3001`.?`\u3001`[?]`\uFF0C\u4F8B\u5982 `form.name`\u3001`ctx.?user.orValue(null)`\u3002",
|
|
1012
|
+
"- \u6761\u4EF6\u4E0E\u903B\u8F91\uFF1A`&&`\u3001`||`\u3001`!`\u3001`condition ? a : b`\u3002",
|
|
1013
|
+
'- \u65B9\u6CD5\u8C03\u7528\uFF1A`value.method(args)`\uFF0C\u4F8B\u5982 `now.format("yyyy-MM-dd")`\u3002',
|
|
1014
|
+
"",
|
|
1015
|
+
"### 2. \u5E38\u89C1\u5B57\u6BB5\u4E0E\u7528\u9014",
|
|
1016
|
+
'- `style` \u76F8\u5173\u5B57\u6BB5\u9700\u8981\u8FD4\u56DE style map\uFF0C\u4F8B\u5982 `{"display": "grid"}`\u3002',
|
|
1017
|
+
"- `hidden` / `disabled` / `disableDate` \u9700\u8981\u8FD4\u56DE `bool`\u3002",
|
|
1018
|
+
"- `validation[].expression` \u9700\u8981\u8FD4\u56DE `bool`\uFF1B\u8FD4\u56DE `false` \u65F6\u5C55\u793A\u5BF9\u5E94\u6D88\u606F\u3002",
|
|
1019
|
+
"- `select.options` \u901A\u5E38\u8FD4\u56DE\u5217\u8868\uFF1B`label` / `value` / `key` \u7528\u6765\u63CF\u8FF0\u5355\u4E2A\u9009\u9879\u5982\u4F55\u6620\u5C04\u3002",
|
|
1020
|
+
"",
|
|
1021
|
+
"### 3. \u5B57\u6BB5\u7C7B\u578B\u7EA6\u675F",
|
|
1022
|
+
"- \u6240\u6709\u5B57\u6BB5\u90FD\u5FC5\u987B\u5305\u542B\u7A33\u5B9A\u7684 UUID `id`\u3002",
|
|
1023
|
+
"- \u53EA\u6709\u975E `slot` \u5B57\u6BB5\u53EF\u4EE5\u914D\u7F6E `path`\uFF0C\u5E76\u53C2\u4E0E\u8868\u5355\u503C\u8BFB\u5199\u3002",
|
|
1024
|
+
"- `slot` \u5B57\u6BB5\u4E0D\u4F1A\u7ED1\u5B9A\u6A21\u578B\u503C\uFF0C\u53EA\u901A\u8FC7 `field.id` \u5BF9\u5E94\u7684\u5177\u540D\u63D2\u69FD\u6E32\u67D3\u3002"
|
|
1025
|
+
].join("\n");
|
|
1026
|
+
}
|
|
1027
|
+
function buildMarkdownNotes() {
|
|
1028
|
+
return [
|
|
1029
|
+
"## \u6CE8\u610F\u4E8B\u9879",
|
|
1030
|
+
"- \u6240\u6709\u5B57\u6BB5\u90FD\u5FC5\u987B\u4FDD\u7559\u73B0\u6709 `id`\uFF0C\u4E0D\u8981\u751F\u6210\u65B0\u7684 `id` \u66FF\u6362\u5DF2\u6709\u5B57\u6BB5\u3002",
|
|
1031
|
+
"- `slot` \u5B57\u6BB5\u53EA\u80FD\u4F7F\u7528 `id`\u3001`type` \u548C\u53EF\u9009\u7684 `style`\u3002",
|
|
1032
|
+
"- \u975E `slot` \u5B57\u6BB5\u7684 `path` \u5FC5\u987B\u552F\u4E00\u4E14\u4E0D\u80FD\u4E3A\u7A7A\u3002",
|
|
1033
|
+
"- \u8868\u8FBE\u5F0F\u5B57\u6BB5\u5FC5\u987B\u4E25\u683C\u9075\u5B88 schema \u7EA6\u675F\uFF1B\u5982\u679C schema \u4E0D\u652F\u6301\uFF0C\u5C31\u76F4\u63A5\u8BF4\u660E\u9650\u5236\u3002"
|
|
1034
|
+
].join("\n");
|
|
1035
|
+
}
|
|
1036
|
+
function buildMarkdownCopyContent(config) {
|
|
1037
|
+
return [
|
|
1038
|
+
buildAiPromptHeaderMarkdown(),
|
|
1039
|
+
"",
|
|
1040
|
+
"## \u5F53\u524D\u914D\u7F6E",
|
|
1041
|
+
"```json",
|
|
1042
|
+
JSON.stringify(config, null, 2),
|
|
1043
|
+
"```",
|
|
1044
|
+
"",
|
|
1045
|
+
buildDslGuideMarkdown(),
|
|
1046
|
+
"",
|
|
1047
|
+
buildMarkdownNotes(),
|
|
1048
|
+
"",
|
|
1049
|
+
"## FieldsConfig JSON Schema",
|
|
1050
|
+
"```json",
|
|
1051
|
+
JSON.stringify(buildFieldsConfigJsonSchema(), null, 2),
|
|
1052
|
+
"```"
|
|
1053
|
+
].join("\n");
|
|
1054
|
+
}
|
|
1055
|
+
function formatIssuePath(path) {
|
|
1056
|
+
if (path.length === 0) {
|
|
1057
|
+
return "(root)";
|
|
1058
|
+
}
|
|
1059
|
+
return path.map((segment) => {
|
|
1060
|
+
if (typeof segment === "number") {
|
|
1061
|
+
return `${segment}`;
|
|
1062
|
+
}
|
|
1063
|
+
if (typeof segment === "string") {
|
|
1064
|
+
return segment;
|
|
1065
|
+
}
|
|
1066
|
+
return String(segment);
|
|
1067
|
+
}).join(".");
|
|
1068
|
+
}
|
|
1069
|
+
function formatIssueExtraFields(issue) {
|
|
1070
|
+
const lines = [];
|
|
1071
|
+
for (const [key, value] of Object.entries(issue)) {
|
|
1072
|
+
if (key === "code" || key === "message" || key === "path") {
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
lines.push(` - ${key}: ${JSON.stringify(value)}`);
|
|
1076
|
+
}
|
|
1077
|
+
return lines;
|
|
1078
|
+
}
|
|
1079
|
+
function buildPasteConfigErrorDetails(source, error) {
|
|
1080
|
+
if (error instanceof SyntaxError) {
|
|
1081
|
+
return [
|
|
1082
|
+
"## \u7C98\u8D34\u5931\u8D25\u539F\u56E0",
|
|
1083
|
+
"- \u7C7B\u578B\uFF1AJSON \u89E3\u6790\u5931\u8D25",
|
|
1084
|
+
`- message: ${error.message}`,
|
|
1085
|
+
"",
|
|
1086
|
+
"## \u539F\u59CB\u7C98\u8D34\u5185\u5BB9",
|
|
1087
|
+
"```text",
|
|
1088
|
+
source,
|
|
1089
|
+
"```"
|
|
1090
|
+
].join("\n");
|
|
1091
|
+
}
|
|
1092
|
+
const issueLines = error.issues.flatMap((issue, index) => [
|
|
1093
|
+
`### Issue ${index + 1}`,
|
|
1094
|
+
`- path: ${formatIssuePath(issue.path)}`,
|
|
1095
|
+
`- code: ${issue.code}`,
|
|
1096
|
+
`- message: ${issue.message}`,
|
|
1097
|
+
...formatIssueExtraFields(issue)
|
|
1098
|
+
]);
|
|
1099
|
+
return [
|
|
1100
|
+
"## \u7C98\u8D34\u5931\u8D25\u539F\u56E0",
|
|
1101
|
+
"- \u7C7B\u578B\uFF1ASchema \u6821\u9A8C\u5931\u8D25",
|
|
1102
|
+
"",
|
|
1103
|
+
"## \u539F\u59CB\u7C98\u8D34\u5185\u5BB9",
|
|
1104
|
+
"```json",
|
|
1105
|
+
source,
|
|
1106
|
+
"```",
|
|
1107
|
+
"",
|
|
1108
|
+
"## Schema \u62A5\u9519",
|
|
1109
|
+
...issueLines
|
|
1110
|
+
].join("\n");
|
|
1111
|
+
}
|
|
1112
|
+
function buildPasteConfigErrorMarkdown(source, error) {
|
|
1113
|
+
return [
|
|
1114
|
+
buildAiPromptHeaderMarkdown(),
|
|
1115
|
+
"",
|
|
1116
|
+
"## \u5F53\u524D\u4EFB\u52A1",
|
|
1117
|
+
"\u7528\u6237\u628A\u4E00\u6BB5\u914D\u7F6E\u7C98\u8D34\u56DE\u5B57\u6BB5\u914D\u7F6E\u5668\u65F6\u5931\u8D25\u4E86\u3002\u8BF7\u57FA\u4E8E\u4E0B\u9762\u7684\u539F\u59CB\u5185\u5BB9\u548C\u62A5\u9519\u4FEE\u590D\u5F53\u524D\u914D\u7F6E\u3002",
|
|
1118
|
+
"\u8BF7\u4F18\u5148\u4FEE\u590D\u6700\u5C0F\u5FC5\u8981\u8303\u56F4\uFF0C\u4E0D\u8981\u53D1\u660E schema \u4E2D\u4E0D\u5B58\u5728\u7684\u5B57\u6BB5\uFF0C\u4E5F\u4E0D\u8981\u66FF\u73B0\u6709\u5B57\u6BB5\u751F\u6210\u65B0\u7684 ID\u3002",
|
|
1119
|
+
"\u53EA\u6709\u5F53\u7528\u6237\u660E\u786E\u8981\u6C42\u8F93\u51FA\u914D\u7F6E\u65F6\uFF0C\u624D\u8FD4\u56DE\u5B8C\u6574\u914D\u7F6E\uFF1B\u5982\u679C\u8FD4\u56DE\u914D\u7F6E\uFF0C\u5FC5\u987B\u653E\u5728 Markdown code block \u4E2D\u3002",
|
|
1120
|
+
"",
|
|
1121
|
+
buildPasteConfigErrorDetails(source, error),
|
|
1122
|
+
"",
|
|
1123
|
+
buildMarkdownNotes()
|
|
1124
|
+
].join("\n");
|
|
1125
|
+
}
|
|
1126
|
+
async function writeClipboardText(value, errorMessage) {
|
|
1127
|
+
try {
|
|
1128
|
+
await navigator.clipboard.writeText(value);
|
|
1129
|
+
} catch {
|
|
1130
|
+
showCopyError(errorMessage);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
async function copyPasteConfigError(source, error) {
|
|
1134
|
+
await writeClipboardText(buildPasteConfigErrorMarkdown(source, error), t("copy-paste-error-failed"));
|
|
1135
|
+
}
|
|
1136
|
+
async function pasteConfigFromClipboard() {
|
|
1137
|
+
let source = "";
|
|
1138
|
+
try {
|
|
1139
|
+
source = await navigator.clipboard.readText();
|
|
1140
|
+
} catch {
|
|
1141
|
+
showImportError(t("paste-config-read-failed"));
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
let parsedValue;
|
|
1145
|
+
try {
|
|
1146
|
+
parsedValue = JSON.parse(source);
|
|
1147
|
+
} catch (error) {
|
|
1148
|
+
if (error instanceof SyntaxError) {
|
|
1149
|
+
showImportErrorWithCopyAction(
|
|
1150
|
+
t("paste-config-invalid-json"),
|
|
1151
|
+
async () => copyPasteConfigError(source, error)
|
|
1152
|
+
);
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
showImportError(t("paste-config-invalid-json"));
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
const result = FieldsConfigC.safeParse(parsedValue);
|
|
1159
|
+
if (!result.success) {
|
|
1160
|
+
showImportErrorWithCopyAction(
|
|
1161
|
+
t("paste-config-invalid-schema"),
|
|
1162
|
+
async () => copyPasteConfigError(source, result.error)
|
|
1163
|
+
);
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
applyDraftConfig(result.data);
|
|
1167
|
+
await nextTick();
|
|
1168
|
+
await refreshSortable();
|
|
1169
|
+
}
|
|
1170
|
+
async function copyConfig() {
|
|
1171
|
+
const config = getValidDraftConfig(t("copy-config-failed"));
|
|
1172
|
+
if (!config) {
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
await writeClipboardText(JSON.stringify(config, null, 2), t("copy-config-failed"));
|
|
1176
|
+
}
|
|
1177
|
+
async function copyMarkdown() {
|
|
1178
|
+
const config = getValidDraftConfig(t("copy-markdown-failed"));
|
|
1179
|
+
if (!config) {
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
await writeClipboardText(buildMarkdownCopyContent(config), t("copy-markdown-failed"));
|
|
1183
|
+
}
|
|
1184
|
+
function confirmChanges() {
|
|
1185
|
+
const result = buildDraftConfig();
|
|
1186
|
+
if (!result) {
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
draftFields.value = result.normalizedFields.map((item) => createDraftField(item.field));
|
|
1190
|
+
emit("confirm", result.config);
|
|
856
1191
|
open.value = false;
|
|
857
1192
|
}
|
|
858
1193
|
</script>
|
|
@@ -867,9 +1202,22 @@ function confirmChanges() {
|
|
|
867
1202
|
:show-close-button="true"
|
|
868
1203
|
>
|
|
869
1204
|
<DialogHeader class="gap-1 border-b border-zinc-200 px-6 py-5">
|
|
870
|
-
<
|
|
871
|
-
|
|
872
|
-
|
|
1205
|
+
<div class="flex items-center gap-3">
|
|
1206
|
+
<DialogTitle class="text-xl font-semibold text-zinc-800">
|
|
1207
|
+
{{ t("configure-fields") }}
|
|
1208
|
+
</DialogTitle>
|
|
1209
|
+
<Button
|
|
1210
|
+
type="button"
|
|
1211
|
+
variant="ghost"
|
|
1212
|
+
size="sm"
|
|
1213
|
+
data-slot="fields-configurator-paste"
|
|
1214
|
+
class="shrink-0"
|
|
1215
|
+
@click="pasteConfigFromClipboard"
|
|
1216
|
+
>
|
|
1217
|
+
<Icon icon="fluent:clipboard-paste-20-regular" />
|
|
1218
|
+
{{ t("paste-config") }}
|
|
1219
|
+
</Button>
|
|
1220
|
+
</div>
|
|
873
1221
|
<DialogDescription class="text-sm text-zinc-500">
|
|
874
1222
|
{{ t("configure-fields-description") }}
|
|
875
1223
|
</DialogDescription>
|
|
@@ -877,7 +1225,42 @@ function confirmChanges() {
|
|
|
877
1225
|
|
|
878
1226
|
<div class="grid min-h-0 flex-1 grid-cols-[19rem_minmax(0,1fr)]">
|
|
879
1227
|
<section class="flex min-h-0 flex-col border-r border-zinc-200 px-4 py-4">
|
|
880
|
-
<
|
|
1228
|
+
<Input
|
|
1229
|
+
v-model="search"
|
|
1230
|
+
data-slot="fields-configurator-search"
|
|
1231
|
+
:placeholder="t('search-fields')"
|
|
1232
|
+
/>
|
|
1233
|
+
|
|
1234
|
+
<DropdownMenu>
|
|
1235
|
+
<DropdownMenuTrigger as-child>
|
|
1236
|
+
<Button
|
|
1237
|
+
type="button"
|
|
1238
|
+
data-slot="fields-configurator-add"
|
|
1239
|
+
class="mt-3 w-full justify-center"
|
|
1240
|
+
>
|
|
1241
|
+
<Icon icon="fluent:add-20-regular" />
|
|
1242
|
+
{{ t("add-field") }}
|
|
1243
|
+
</Button>
|
|
1244
|
+
</DropdownMenuTrigger>
|
|
1245
|
+
|
|
1246
|
+
<DropdownMenuContent
|
|
1247
|
+
align="start"
|
|
1248
|
+
:style="{
|
|
1249
|
+
width: 'var(--reka-dropdown-menu-trigger-width)'
|
|
1250
|
+
}"
|
|
1251
|
+
>
|
|
1252
|
+
<DropdownMenuItem
|
|
1253
|
+
v-for="option in fieldTypeOptions"
|
|
1254
|
+
:key="option.type"
|
|
1255
|
+
:data-slot="`fields-configurator-add-item-${option.type}`"
|
|
1256
|
+
@select="addField(option.type)"
|
|
1257
|
+
>
|
|
1258
|
+
{{ option.label }}
|
|
1259
|
+
</DropdownMenuItem>
|
|
1260
|
+
</DropdownMenuContent>
|
|
1261
|
+
</DropdownMenu>
|
|
1262
|
+
|
|
1263
|
+
<div class="mt-4 flex min-h-0 flex-1 flex-col overflow-hidden">
|
|
881
1264
|
<div class="flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto pr-1">
|
|
882
1265
|
<button
|
|
883
1266
|
type="button"
|
|
@@ -899,16 +1282,17 @@ function confirmChanges() {
|
|
|
899
1282
|
</button>
|
|
900
1283
|
|
|
901
1284
|
<div
|
|
902
|
-
v-if="
|
|
1285
|
+
v-if="filteredFieldItems.length > 0"
|
|
903
1286
|
ref="sortableListRef"
|
|
904
1287
|
data-slot="fields-configurator-list"
|
|
905
1288
|
class="flex flex-col gap-1"
|
|
906
1289
|
>
|
|
907
1290
|
<div
|
|
908
|
-
v-for="item in
|
|
1291
|
+
v-for="item in filteredFieldItems"
|
|
909
1292
|
:key="item.itemId"
|
|
910
1293
|
data-slot="fields-configurator-field-item"
|
|
911
1294
|
:data-item-id="item.itemId"
|
|
1295
|
+
:data-field-id="item.fieldId"
|
|
912
1296
|
:data-field-path="item.path"
|
|
913
1297
|
:data-selected="selectedItemId === item.itemId ? 'true' : 'false'"
|
|
914
1298
|
:class="cn(
|
|
@@ -952,6 +1336,14 @@ function confirmChanges() {
|
|
|
952
1336
|
</div>
|
|
953
1337
|
</div>
|
|
954
1338
|
|
|
1339
|
+
<p
|
|
1340
|
+
v-else-if="normalizedSearch"
|
|
1341
|
+
data-slot="fields-configurator-empty"
|
|
1342
|
+
class="px-1 pt-2 text-xs text-zinc-400"
|
|
1343
|
+
>
|
|
1344
|
+
{{ t("no-matches") }}
|
|
1345
|
+
</p>
|
|
1346
|
+
|
|
955
1347
|
<p
|
|
956
1348
|
v-else
|
|
957
1349
|
data-slot="fields-configurator-empty"
|
|
@@ -961,46 +1353,30 @@ function confirmChanges() {
|
|
|
961
1353
|
</p>
|
|
962
1354
|
</div>
|
|
963
1355
|
</div>
|
|
964
|
-
|
|
965
|
-
<div
|
|
966
|
-
data-slot="fields-configurator-add-container"
|
|
967
|
-
class="mt-4"
|
|
968
|
-
>
|
|
969
|
-
<DropdownMenu>
|
|
970
|
-
<DropdownMenuTrigger as-child>
|
|
971
|
-
<Button
|
|
972
|
-
type="button"
|
|
973
|
-
data-slot="fields-configurator-add"
|
|
974
|
-
size="sm"
|
|
975
|
-
variant="default"
|
|
976
|
-
class="w-full justify-center"
|
|
977
|
-
>
|
|
978
|
-
<Icon icon="fluent:add-20-regular" />
|
|
979
|
-
{{ t("add-field") }}
|
|
980
|
-
</Button>
|
|
981
|
-
</DropdownMenuTrigger>
|
|
982
|
-
|
|
983
|
-
<DropdownMenuContent align="start">
|
|
984
|
-
<DropdownMenuItem
|
|
985
|
-
v-for="option in fieldTypeOptions"
|
|
986
|
-
:key="option.type"
|
|
987
|
-
:data-slot="`fields-configurator-add-item-${option.type}`"
|
|
988
|
-
@select="addField(option.type)"
|
|
989
|
-
>
|
|
990
|
-
{{ option.label }}
|
|
991
|
-
</DropdownMenuItem>
|
|
992
|
-
</DropdownMenuContent>
|
|
993
|
-
</DropdownMenu>
|
|
994
|
-
</div>
|
|
995
1356
|
</section>
|
|
996
1357
|
|
|
997
1358
|
<section class="flex min-h-0 flex-col overflow-y-auto px-6 py-6">
|
|
998
|
-
<
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1359
|
+
<div class="flex items-center gap-2">
|
|
1360
|
+
<h3
|
|
1361
|
+
data-slot="fields-configurator-detail-title"
|
|
1362
|
+
class="text-lg font-semibold text-zinc-800"
|
|
1363
|
+
>
|
|
1364
|
+
{{ selectedItemLabel }}
|
|
1365
|
+
</h3>
|
|
1366
|
+
<Button
|
|
1367
|
+
v-if="selectedField"
|
|
1368
|
+
type="button"
|
|
1369
|
+
variant="ghost"
|
|
1370
|
+
size="sm"
|
|
1371
|
+
data-slot="fields-configurator-copy-id"
|
|
1372
|
+
class="size-7 p-0 text-zinc-400 hover:text-zinc-700"
|
|
1373
|
+
:aria-label="t('copy-field-id', { field: selectedItemLabel })"
|
|
1374
|
+
:title="selectedField.field.id"
|
|
1375
|
+
@click="copySelectedFieldId"
|
|
1376
|
+
>
|
|
1377
|
+
<Icon icon="fluent:copy-20-regular" />
|
|
1378
|
+
</Button>
|
|
1379
|
+
</div>
|
|
1004
1380
|
|
|
1005
1381
|
<p
|
|
1006
1382
|
v-if="selectedItemId === 'general'"
|
|
@@ -1069,24 +1445,20 @@ function confirmChanges() {
|
|
|
1069
1445
|
data-slot="fields-configurator-field-main"
|
|
1070
1446
|
class="mt-6 flex flex-col gap-6"
|
|
1071
1447
|
>
|
|
1072
|
-
<
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
</span>
|
|
1080
|
-
<div
|
|
1081
|
-
data-slot="fields-configurator-field-type"
|
|
1082
|
-
class="flex h-9 items-center rounded-md border border-zinc-200 bg-zinc-50 px-3 text-sm text-zinc-600"
|
|
1083
|
-
>
|
|
1084
|
-
{{ getFieldTypeLabel(selectedField.field.type) }}
|
|
1085
|
-
</div>
|
|
1086
|
-
</label>
|
|
1448
|
+
<p
|
|
1449
|
+
v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'id')]"
|
|
1450
|
+
data-slot="fields-configurator-field-id-error"
|
|
1451
|
+
class="-mt-4 text-xs text-red-500"
|
|
1452
|
+
>
|
|
1453
|
+
{{ validationErrors[getFieldErrorKey(selectedField.draftId, "id")] }}
|
|
1454
|
+
</p>
|
|
1087
1455
|
|
|
1456
|
+
<section
|
|
1457
|
+
v-if="selectedField.field.type !== 'slot'"
|
|
1458
|
+
data-slot="fields-configurator-field-path-section"
|
|
1459
|
+
class="flex flex-col gap-2"
|
|
1460
|
+
>
|
|
1088
1461
|
<label
|
|
1089
|
-
data-slot="fields-configurator-field-path-section"
|
|
1090
1462
|
class="flex flex-col gap-2"
|
|
1091
1463
|
>
|
|
1092
1464
|
<span class="text-xs font-medium text-zinc-500">
|
|
@@ -1111,6 +1483,7 @@ function confirmChanges() {
|
|
|
1111
1483
|
</section>
|
|
1112
1484
|
|
|
1113
1485
|
<section
|
|
1486
|
+
v-if="selectedField.field.type !== 'slot'"
|
|
1114
1487
|
data-slot="fields-configurator-field-label-section"
|
|
1115
1488
|
class="flex flex-col gap-2"
|
|
1116
1489
|
>
|
|
@@ -1134,28 +1507,36 @@ function confirmChanges() {
|
|
|
1134
1507
|
</section>
|
|
1135
1508
|
|
|
1136
1509
|
<section
|
|
1137
|
-
|
|
1510
|
+
v-if="selectedField.field.type === 'slot'"
|
|
1511
|
+
data-slot="fields-configurator-slot-options"
|
|
1138
1512
|
class="grid gap-4 md:grid-cols-2"
|
|
1139
1513
|
>
|
|
1140
|
-
<label class="flex flex-col gap-2">
|
|
1514
|
+
<label class="flex flex-col gap-2 md:col-span-2">
|
|
1141
1515
|
<span class="text-xs font-medium text-zinc-500">
|
|
1142
|
-
{{ t("field-
|
|
1516
|
+
{{ t("field-style") }}
|
|
1143
1517
|
</span>
|
|
1144
|
-
<
|
|
1145
|
-
data-slot="fields-configurator-field-
|
|
1146
|
-
:model-value="selectedField.field.
|
|
1147
|
-
:invalid="validationErrors[getFieldErrorKey(selectedField.draftId, '
|
|
1148
|
-
:placeholder="t('field-
|
|
1149
|
-
|
|
1518
|
+
<Textarea
|
|
1519
|
+
data-slot="fields-configurator-field-style-input"
|
|
1520
|
+
:model-value="selectedField.field.style ?? ''"
|
|
1521
|
+
:aria-invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'style')] ? 'true' : void 0"
|
|
1522
|
+
:placeholder="t('field-style-placeholder')"
|
|
1523
|
+
class="min-h-20 font-mono text-sm"
|
|
1524
|
+
@update:model-value="updateSelectedFieldStyle"
|
|
1150
1525
|
/>
|
|
1151
1526
|
<p
|
|
1152
|
-
v-if="validationErrors[getFieldErrorKey(selectedField.draftId, '
|
|
1527
|
+
v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'style')]"
|
|
1153
1528
|
class="text-xs text-red-500"
|
|
1154
1529
|
>
|
|
1155
|
-
{{ validationErrors[getFieldErrorKey(selectedField.draftId, "
|
|
1530
|
+
{{ validationErrors[getFieldErrorKey(selectedField.draftId, "style")] }}
|
|
1156
1531
|
</p>
|
|
1157
1532
|
</label>
|
|
1533
|
+
</section>
|
|
1158
1534
|
|
|
1535
|
+
<section
|
|
1536
|
+
v-else
|
|
1537
|
+
data-slot="fields-configurator-field-general-options"
|
|
1538
|
+
class="grid gap-4 md:grid-cols-2"
|
|
1539
|
+
>
|
|
1159
1540
|
<label class="flex flex-col gap-2">
|
|
1160
1541
|
<span class="text-xs font-medium text-zinc-500">
|
|
1161
1542
|
{{ t("field-style") }}
|
|
@@ -1486,6 +1867,7 @@ function confirmChanges() {
|
|
|
1486
1867
|
</section>
|
|
1487
1868
|
|
|
1488
1869
|
<section
|
|
1870
|
+
v-if="selectedField.field.type !== 'slot'"
|
|
1489
1871
|
data-slot="fields-configurator-validation"
|
|
1490
1872
|
class="flex flex-col gap-4"
|
|
1491
1873
|
>
|
|
@@ -1617,25 +1999,50 @@ function confirmChanges() {
|
|
|
1617
1999
|
</section>
|
|
1618
2000
|
</div>
|
|
1619
2001
|
|
|
1620
|
-
<DialogFooter class="border-t border-zinc-200 px-6 py-4">
|
|
1621
|
-
<
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
variant="default"
|
|
1625
|
-
@click="discardChanges"
|
|
2002
|
+
<DialogFooter class="border-t border-zinc-200 px-6 py-4 sm:justify-between">
|
|
2003
|
+
<div
|
|
2004
|
+
data-slot="fields-configurator-copy-actions"
|
|
2005
|
+
class="flex items-center gap-2"
|
|
1626
2006
|
>
|
|
1627
|
-
<
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
<
|
|
1637
|
-
|
|
1638
|
-
|
|
2007
|
+
<Button
|
|
2008
|
+
type="button"
|
|
2009
|
+
data-slot="fields-configurator-copy-markdown"
|
|
2010
|
+
variant="ghost"
|
|
2011
|
+
@click="copyMarkdown"
|
|
2012
|
+
>
|
|
2013
|
+
<Icon icon="simple-icons:markdown" />
|
|
2014
|
+
{{ t("copy-markdown") }}
|
|
2015
|
+
</Button>
|
|
2016
|
+
<Button
|
|
2017
|
+
type="button"
|
|
2018
|
+
data-slot="fields-configurator-copy-config"
|
|
2019
|
+
variant="ghost"
|
|
2020
|
+
@click="copyConfig"
|
|
2021
|
+
>
|
|
2022
|
+
<Icon icon="fluent:copy-20-regular" />
|
|
2023
|
+
{{ t("copy-config") }}
|
|
2024
|
+
</Button>
|
|
2025
|
+
</div>
|
|
2026
|
+
<div class="flex items-center gap-2">
|
|
2027
|
+
<Button
|
|
2028
|
+
type="button"
|
|
2029
|
+
data-slot="fields-configurator-cancel"
|
|
2030
|
+
variant="default"
|
|
2031
|
+
@click="discardChanges"
|
|
2032
|
+
>
|
|
2033
|
+
<Icon icon="fluent:dismiss-20-regular" />
|
|
2034
|
+
{{ t("cancel") }}
|
|
2035
|
+
</Button>
|
|
2036
|
+
<Button
|
|
2037
|
+
type="button"
|
|
2038
|
+
data-slot="fields-configurator-confirm"
|
|
2039
|
+
variant="primary"
|
|
2040
|
+
@click="confirmChanges"
|
|
2041
|
+
>
|
|
2042
|
+
<Icon icon="fluent:checkmark-20-regular" />
|
|
2043
|
+
{{ t("confirm") }}
|
|
2044
|
+
</Button>
|
|
2045
|
+
</div>
|
|
1639
2046
|
</DialogFooter>
|
|
1640
2047
|
</DialogContent>
|
|
1641
2048
|
</Dialog>
|
|
@@ -1645,7 +2052,7 @@ function confirmChanges() {
|
|
|
1645
2052
|
{
|
|
1646
2053
|
"zh": {
|
|
1647
2054
|
"configure-fields": "配置字段",
|
|
1648
|
-
"configure-fields-description": "
|
|
2055
|
+
"configure-fields-description": "在这里管理字段列表,并编辑当前选中的通用项或字段配置。",
|
|
1649
2056
|
"field-list": "字段列表",
|
|
1650
2057
|
"general": "通用",
|
|
1651
2058
|
"general-description": "在这里配置字段集合级别的公共选项。",
|
|
@@ -1656,16 +2063,33 @@ function confirmChanges() {
|
|
|
1656
2063
|
"fields-orientation-horizontal": "水平",
|
|
1657
2064
|
"fields-orientation-vertical": "垂直",
|
|
1658
2065
|
"fields-orientation-floating": "浮动标签",
|
|
2066
|
+
"paste-config": "粘贴配置",
|
|
2067
|
+
"paste-config-invalid-json": "粘贴失败,剪贴板不是有效的 JSON。",
|
|
2068
|
+
"paste-config-invalid-schema": "粘贴失败,配置格式无效。",
|
|
2069
|
+
"paste-config-read-failed": "读取剪贴板失败,请检查剪贴板权限。",
|
|
2070
|
+
"copy-paste-error": "复制错误",
|
|
2071
|
+
"copy-paste-error-failed": "复制错误详情失败,请检查剪贴板权限。",
|
|
2072
|
+
"copy-markdown": "复制为 Markdown",
|
|
2073
|
+
"copy-markdown-failed": "复制 Markdown 失败,请先修正当前配置中的错误。",
|
|
2074
|
+
"copy-config": "仅复制配置",
|
|
2075
|
+
"copy-config-failed": "复制配置失败,请先修正当前配置中的错误。",
|
|
2076
|
+
"search-fields": "搜索字段名称……",
|
|
1659
2077
|
"add-field": "新增字段",
|
|
1660
2078
|
"field-type": "字段类型",
|
|
1661
2079
|
"field-type-string": "文本",
|
|
1662
2080
|
"field-type-number": "数字",
|
|
1663
2081
|
"field-type-select": "选择",
|
|
1664
2082
|
"field-type-calendar": "日期",
|
|
2083
|
+
"field-type-slot": "插槽",
|
|
2084
|
+
"field-id": "字段 ID",
|
|
2085
|
+
"field-id-duplicate": "字段 ID 不能重复",
|
|
1665
2086
|
"field-path": "字段路径",
|
|
1666
2087
|
"field-path-placeholder": "例如 profile.name",
|
|
1667
2088
|
"field-path-required": "字段路径不能为空",
|
|
1668
2089
|
"field-path-duplicate": "字段路径不能重复",
|
|
2090
|
+
"copy-field-id": "复制字段 ID:{field}",
|
|
2091
|
+
"copy-field-id-short": "复制 ID",
|
|
2092
|
+
"copy-field-id-failed": "复制字段 ID 失败,请检查剪贴板权限。",
|
|
1669
2093
|
"field-label": "字段标题",
|
|
1670
2094
|
"field-icon": "图标",
|
|
1671
2095
|
"field-icon-placeholder": "例如 fluent:person-20-regular",
|
|
@@ -1712,8 +2136,10 @@ function confirmChanges() {
|
|
|
1712
2136
|
"validation-expression-placeholder": "返回 false 时展示下面的消息",
|
|
1713
2137
|
"validation-message": "失败消息",
|
|
1714
2138
|
"validation-message-placeholder": "支持 Markdown 与双花括号表达式",
|
|
2139
|
+
"no-matches": "没有匹配的字段。",
|
|
1715
2140
|
"no-validation-rules": "暂未配置校验规则。",
|
|
1716
2141
|
"unnamed-field": "未命名{type}字段",
|
|
2142
|
+
"slot-field": "插槽 {id}",
|
|
1717
2143
|
"no-fields": "还没有字段。",
|
|
1718
2144
|
"drag-field": "拖拽调整字段顺序:{field}",
|
|
1719
2145
|
"delete-field": "删除字段:{field}",
|
|
@@ -1722,7 +2148,7 @@ function confirmChanges() {
|
|
|
1722
2148
|
},
|
|
1723
2149
|
"ja": {
|
|
1724
2150
|
"configure-fields": "フィールドを設定",
|
|
1725
|
-
"configure-fields-description": "
|
|
2151
|
+
"configure-fields-description": "ここではフィールド一覧を管理し、選択中の共通設定またはフィールド設定を編集できます。",
|
|
1726
2152
|
"field-list": "フィールド一覧",
|
|
1727
2153
|
"general": "共通",
|
|
1728
2154
|
"general-description": "ここではフィールド群全体に適用される共通設定を編集できます。",
|
|
@@ -1733,16 +2159,33 @@ function confirmChanges() {
|
|
|
1733
2159
|
"fields-orientation-horizontal": "横並び",
|
|
1734
2160
|
"fields-orientation-vertical": "縦並び",
|
|
1735
2161
|
"fields-orientation-floating": "フローティングラベル",
|
|
2162
|
+
"paste-config": "設定を貼り付け",
|
|
2163
|
+
"paste-config-invalid-json": "貼り付けに失敗しました。クリップボードの内容が有効な JSON ではありません。",
|
|
2164
|
+
"paste-config-invalid-schema": "貼り付けに失敗しました。設定形式が無効です。",
|
|
2165
|
+
"paste-config-read-failed": "クリップボードの読み取りに失敗しました。権限を確認してください。",
|
|
2166
|
+
"copy-paste-error": "エラーをコピー",
|
|
2167
|
+
"copy-paste-error-failed": "エラー詳細のコピーに失敗しました。クリップボード権限を確認してください。",
|
|
2168
|
+
"copy-markdown": "Markdown としてコピー",
|
|
2169
|
+
"copy-markdown-failed": "Markdown のコピーに失敗しました。現在の設定エラーを先に修正してください。",
|
|
2170
|
+
"copy-config": "設定のみコピー",
|
|
2171
|
+
"copy-config-failed": "設定のコピーに失敗しました。現在の設定エラーを先に修正してください。",
|
|
2172
|
+
"search-fields": "フィールド名を検索…",
|
|
1736
2173
|
"add-field": "フィールドを追加",
|
|
1737
2174
|
"field-type": "フィールド種別",
|
|
1738
2175
|
"field-type-string": "テキスト",
|
|
1739
2176
|
"field-type-number": "数値",
|
|
1740
2177
|
"field-type-select": "選択",
|
|
1741
2178
|
"field-type-calendar": "日付",
|
|
2179
|
+
"field-type-slot": "スロット",
|
|
2180
|
+
"field-id": "フィールド ID",
|
|
2181
|
+
"field-id-duplicate": "フィールド ID は重複できません",
|
|
1742
2182
|
"field-path": "フィールドパス",
|
|
1743
2183
|
"field-path-placeholder": "例: profile.name",
|
|
1744
2184
|
"field-path-required": "フィールドパスは必須です",
|
|
1745
2185
|
"field-path-duplicate": "フィールドパスは重複できません",
|
|
2186
|
+
"copy-field-id": "{field} のフィールド ID をコピー",
|
|
2187
|
+
"copy-field-id-short": "ID をコピー",
|
|
2188
|
+
"copy-field-id-failed": "フィールド ID のコピーに失敗しました。クリップボード権限を確認してください。",
|
|
1746
2189
|
"field-label": "フィールドラベル",
|
|
1747
2190
|
"field-icon": "アイコン",
|
|
1748
2191
|
"field-icon-placeholder": "例: fluent:person-20-regular",
|
|
@@ -1789,8 +2232,10 @@ function confirmChanges() {
|
|
|
1789
2232
|
"validation-expression-placeholder": "false を返すと下のメッセージを表示します",
|
|
1790
2233
|
"validation-message": "失敗メッセージ",
|
|
1791
2234
|
"validation-message-placeholder": "Markdown と二重波括弧式を利用できます",
|
|
2235
|
+
"no-matches": "一致するフィールドがありません。",
|
|
1792
2236
|
"no-validation-rules": "検証ルールはまだありません。",
|
|
1793
2237
|
"unnamed-field": "未命名の{type}フィールド",
|
|
2238
|
+
"slot-field": "スロット {id}",
|
|
1794
2239
|
"no-fields": "フィールドがありません。",
|
|
1795
2240
|
"drag-field": "{field} の順序をドラッグで変更",
|
|
1796
2241
|
"delete-field": "{field} を削除",
|
|
@@ -1799,7 +2244,7 @@ function confirmChanges() {
|
|
|
1799
2244
|
},
|
|
1800
2245
|
"en": {
|
|
1801
2246
|
"configure-fields": "Configure Fields",
|
|
1802
|
-
"configure-fields-description": "
|
|
2247
|
+
"configure-fields-description": "Manage the field list and edit the selected general or field settings here.",
|
|
1803
2248
|
"field-list": "Field list",
|
|
1804
2249
|
"general": "General",
|
|
1805
2250
|
"general-description": "Edit the shared settings that apply to the whole field group here.",
|
|
@@ -1810,16 +2255,33 @@ function confirmChanges() {
|
|
|
1810
2255
|
"fields-orientation-horizontal": "Horizontal",
|
|
1811
2256
|
"fields-orientation-vertical": "Vertical",
|
|
1812
2257
|
"fields-orientation-floating": "Floating label",
|
|
2258
|
+
"paste-config": "Paste Config",
|
|
2259
|
+
"paste-config-invalid-json": "Paste failed because the clipboard does not contain valid JSON.",
|
|
2260
|
+
"paste-config-invalid-schema": "Paste failed because the config shape is invalid.",
|
|
2261
|
+
"paste-config-read-failed": "Failed to read from the clipboard. Check clipboard permissions.",
|
|
2262
|
+
"copy-paste-error": "Copy Error",
|
|
2263
|
+
"copy-paste-error-failed": "Failed to copy the error details. Check clipboard permissions.",
|
|
2264
|
+
"copy-markdown": "Copy as Markdown",
|
|
2265
|
+
"copy-markdown-failed": "Failed to copy Markdown. Fix the current config errors first.",
|
|
2266
|
+
"copy-config": "Copy Config Only",
|
|
2267
|
+
"copy-config-failed": "Failed to copy the config. Fix the current config errors first.",
|
|
2268
|
+
"search-fields": "Search fields…",
|
|
1813
2269
|
"add-field": "Add field",
|
|
1814
2270
|
"field-type": "Field type",
|
|
1815
2271
|
"field-type-string": "Text",
|
|
1816
2272
|
"field-type-number": "Number",
|
|
1817
2273
|
"field-type-select": "Select",
|
|
1818
2274
|
"field-type-calendar": "Date",
|
|
2275
|
+
"field-type-slot": "Slot",
|
|
2276
|
+
"field-id": "Field ID",
|
|
2277
|
+
"field-id-duplicate": "Field ID must be unique",
|
|
1819
2278
|
"field-path": "Field path",
|
|
1820
2279
|
"field-path-placeholder": "For example profile.name",
|
|
1821
2280
|
"field-path-required": "Field path is required",
|
|
1822
2281
|
"field-path-duplicate": "Field path must be unique",
|
|
2282
|
+
"copy-field-id": "Copy field ID: {field}",
|
|
2283
|
+
"copy-field-id-short": "Copy ID",
|
|
2284
|
+
"copy-field-id-failed": "Failed to copy the field ID. Check clipboard permissions.",
|
|
1823
2285
|
"field-label": "Field label",
|
|
1824
2286
|
"field-icon": "Icon",
|
|
1825
2287
|
"field-icon-placeholder": "For example fluent:person-20-regular",
|
|
@@ -1866,8 +2328,10 @@ function confirmChanges() {
|
|
|
1866
2328
|
"validation-expression-placeholder": "Return false to show the message below",
|
|
1867
2329
|
"validation-message": "Failure message",
|
|
1868
2330
|
"validation-message-placeholder": "Supports Markdown and double-curly expressions",
|
|
2331
|
+
"no-matches": "No matching fields.",
|
|
1869
2332
|
"no-validation-rules": "No validation rules yet.",
|
|
1870
2333
|
"unnamed-field": "Untitled {type} field",
|
|
2334
|
+
"slot-field": "Slot {id}",
|
|
1871
2335
|
"no-fields": "No fields yet.",
|
|
1872
2336
|
"drag-field": "Drag to reorder field {field}",
|
|
1873
2337
|
"delete-field": "Delete field {field}",
|