@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.
@@ -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(() => selectedField.value?.field.validation ?? []);
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
- draftOrientation.value = props.config.orientation ?? "horizontal";
256
- draftStyle.value = normalizeOptionalString(props.config.style ?? "");
257
- draftFields.value = cloneFields(props.config.fields);
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 updateSelectedFieldPath(value) {
460
+ async function copySelectedFieldId() {
389
461
  const selected = selectedField.value;
390
462
  if (!selected) {
391
463
  return;
392
464
  }
393
- clearFieldError(selected.draftId, "path");
394
- updateDraftField(selected.draftId, (field) => ({
395
- ...field,
396
- path: String(value).trim()
397
- }));
465
+ await copyFieldId(selected.field.id);
398
466
  }
399
- function updateSelectedFieldIcon(value) {
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, "icon");
475
+ clearFieldError(selected.draftId, "path");
405
476
  updateDraftField(selected.draftId, (field) => ({
406
477
  ...field,
407
- icon: normalizeOptionalString(String(value ?? ""))
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
- ...field,
661
- validation: [
662
- ...field.validation ?? [],
663
- {
664
- expression: "",
665
- message: ""
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
- ...field,
683
- validation: nextValidation
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
- ...field,
728
- validation: nextValidation
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
- const nextValidation = (field.validation ?? []).filter((_, ruleIndex) => ruleIndex !== index);
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 existingOwner = pathOwners[item.field.path];
801
- if (item.field.path.length === 0) {
802
- setError(errors, getFieldErrorKey(item.draftId, "path"), t("field-path-required"));
803
- firstInvalidItemId = firstInvalidItemId ?? item.draftId;
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
- pathOwners[item.field.path] = item.draftId;
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 confirmChanges() {
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
- emit("confirm", nextConfig);
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
- <DialogTitle class="text-xl font-semibold text-zinc-800">
871
- {{ t("configure-fields") }}
872
- </DialogTitle>
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
- <div class="flex min-h-0 flex-1 flex-col overflow-hidden">
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="fieldItems.length > 0"
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 fieldItems"
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
- <h3
999
- data-slot="fields-configurator-detail-title"
1000
- class="text-lg font-semibold text-zinc-800"
1001
- >
1002
- {{ selectedItemLabel }}
1003
- </h3>
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
- <section class="grid gap-4 md:grid-cols-2">
1073
- <label
1074
- data-slot="fields-configurator-field-type-section"
1075
- class="flex flex-col gap-2"
1076
- >
1077
- <span class="text-xs font-medium text-zinc-500">
1078
- {{ t("field-type") }}
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
- data-slot="fields-configurator-field-general-options"
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-icon") }}
1516
+ {{ t("field-style") }}
1143
1517
  </span>
1144
- <IconPicker
1145
- data-slot="fields-configurator-field-icon-picker"
1146
- :model-value="selectedField.field.icon ?? ''"
1147
- :invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'icon')] !== void 0"
1148
- :placeholder="t('field-icon-placeholder')"
1149
- @update:model-value="updateSelectedFieldIcon"
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, 'icon')]"
1527
+ v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'style')]"
1153
1528
  class="text-xs text-red-500"
1154
1529
  >
1155
- {{ validationErrors[getFieldErrorKey(selectedField.draftId, "icon")] }}
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
- <Button
1622
- type="button"
1623
- data-slot="fields-configurator-cancel"
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
- <Icon icon="fluent:dismiss-20-regular" />
1628
- {{ t("cancel") }}
1629
- </Button>
1630
- <Button
1631
- type="button"
1632
- data-slot="fields-configurator-confirm"
1633
- variant="primary"
1634
- @click="confirmChanges"
1635
- >
1636
- <Icon icon="fluent:checkmark-20-regular" />
1637
- {{ t("confirm") }}
1638
- </Button>
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": "Browse the shared and field-level settings here.",
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}",