@shwfed/nuxt 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,21 @@ export const StringFieldC = z.object({
11
11
  type: z.literal("string").describe("\u5355\u884C\u6587\u672C\u8F93\u5165\u5B57\u6BB5\uFF0C\u7ED1\u5B9A string \u7C7B\u578B\u7684\u503C"),
12
12
  path: dotPropC,
13
13
  title: localeC.describe("\u5B57\u6BB5\u6807\u7B7E\u7684\u672C\u5730\u5316\u663E\u793A\u6587\u672C"),
14
+ required: z.boolean().optional().describe("\u4E3A true \u65F6\uFF0C\u4EC5\u5728\u5B57\u6BB5\u6807\u7B7E\u540E\u663E\u793A\u5FC5\u586B\u63D0\u793A\u661F\u53F7\uFF0C\u4E0D\u5F71\u54CD\u5B9E\u9645\u6821\u9A8C\u903B\u8F91"),
15
+ icon: z.string().optional().describe("Iconify \u56FE\u6807\u6807\u8BC6\u7B26\uFF0C\u663E\u793A\u5728\u8F93\u5165\u6846\u5185\u4FA7"),
16
+ style: z.string().optional().describe("CSS \u6837\u5F0F\u5BF9\u8C61\u8868\u8FBE\u5F0F\uFF0C\u63A7\u5236\u5B57\u6BB5\u7684\u5E03\u5C40\u4E0E\u5916\u89C2"),
17
+ discardEmptyString: z.boolean().optional().describe("\u4E3A true \u65F6\uFF0C\u7A7A\u5B57\u7B26\u4E32\u4F5C\u4E3A\u6709\u6548\u503C\u5B58\u50A8\uFF1B\u5426\u5219\u6E05\u7A7A\u8F93\u5165\u5C06\u5220\u9664\u5BF9\u5E94\u5C5E\u6027"),
18
+ maxLength: expressionC("int").optional().describe('\u8FD4\u56DE\u6574\u6570\u7684 CEL \u8868\u8FBE\u5F0F\uFF0C\u9650\u5236\u8F93\u5165\u7684\u6700\u5927\u5B57\u7B26\u6570\uFF0C\u5982 "100"'),
19
+ hidden: expressionC("bool", { form: "map<string, dyn>" }).optional().describe("\u4E3A true \u65F6\uFF0C\u9690\u85CF\u8FD9\u4E2A\u5B57\u6BB5"),
20
+ disabled: expressionC("bool", { form: "map<string, dyn>" }).optional().describe("\u4E3A true \u65F6\uFF0C\u7981\u7528\u8FD9\u4E2A\u5B57\u6BB5"),
21
+ validation: validationC
22
+ });
23
+ export const TextareaFieldC = z.object({
24
+ id: fieldIdC,
25
+ type: z.literal("textarea").describe("\u591A\u884C\u6587\u672C\u8F93\u5165\u5B57\u6BB5\uFF0C\u7ED1\u5B9A string \u7C7B\u578B\u7684\u503C"),
26
+ path: dotPropC,
27
+ title: localeC.describe("\u5B57\u6BB5\u6807\u7B7E\u7684\u672C\u5730\u5316\u663E\u793A\u6587\u672C"),
28
+ required: z.boolean().optional().describe("\u4E3A true \u65F6\uFF0C\u4EC5\u5728\u5B57\u6BB5\u6807\u7B7E\u540E\u663E\u793A\u5FC5\u586B\u63D0\u793A\u661F\u53F7\uFF0C\u4E0D\u5F71\u54CD\u5B9E\u9645\u6821\u9A8C\u903B\u8F91"),
14
29
  icon: z.string().optional().describe("Iconify \u56FE\u6807\u6807\u8BC6\u7B26\uFF0C\u663E\u793A\u5728\u8F93\u5165\u6846\u5185\u4FA7"),
15
30
  style: z.string().optional().describe("CSS \u6837\u5F0F\u5BF9\u8C61\u8868\u8FBE\u5F0F\uFF0C\u63A7\u5236\u5B57\u6BB5\u7684\u5E03\u5C40\u4E0E\u5916\u89C2"),
16
31
  discardEmptyString: z.boolean().optional().describe("\u4E3A true \u65F6\uFF0C\u7A7A\u5B57\u7B26\u4E32\u4F5C\u4E3A\u6709\u6548\u503C\u5B58\u50A8\uFF1B\u5426\u5219\u6E05\u7A7A\u8F93\u5165\u5C06\u5220\u9664\u5BF9\u5E94\u5C5E\u6027"),
@@ -24,6 +39,7 @@ export const NumberFieldC = z.object({
24
39
  type: z.literal("number").describe("\u6570\u5B57\u8F93\u5165\u5B57\u6BB5\uFF0C\u7ED1\u5B9A number \u7C7B\u578B\u7684\u503C"),
25
40
  path: dotPropC,
26
41
  title: localeC.describe("\u5B57\u6BB5\u6807\u7B7E\u7684\u672C\u5730\u5316\u663E\u793A\u6587\u672C"),
42
+ required: z.boolean().optional().describe("\u4E3A true \u65F6\uFF0C\u4EC5\u5728\u5B57\u6BB5\u6807\u7B7E\u540E\u663E\u793A\u5FC5\u586B\u63D0\u793A\u661F\u53F7\uFF0C\u4E0D\u5F71\u54CD\u5B9E\u9645\u6821\u9A8C\u903B\u8F91"),
27
43
  icon: z.string().optional().describe("Iconify \u56FE\u6807\u6807\u8BC6\u7B26\uFF0C\u663E\u793A\u5728\u8F93\u5165\u6846\u5185\u4FA7"),
28
44
  style: z.string().optional().describe("CSS \u6837\u5F0F\u5BF9\u8C61\u8868\u8FBE\u5F0F\uFF0C\u63A7\u5236\u5B57\u6BB5\u7684\u5E03\u5C40\u4E0E\u5916\u89C2"),
29
45
  min: expressionC("double").optional().describe('\u8FD4\u56DE\u6D6E\u70B9\u6570\u7684 CEL \u8868\u8FBE\u5F0F\uFF0C\u9650\u5236\u5141\u8BB8\u7684\u6700\u5C0F\u503C\uFF0C\u5982 "0.0"'),
@@ -38,6 +54,7 @@ export const SelectFieldC = z.object({
38
54
  type: z.literal("select").describe("\u4E0B\u62C9\u9009\u62E9\u5B57\u6BB5\uFF0C\u4ECE\u9884\u5B9A\u4E49\u9009\u9879\u4E2D\u9009\u53D6\u4E00\u4E2A\u503C"),
39
55
  path: dotPropC,
40
56
  title: localeC.describe("\u5B57\u6BB5\u6807\u7B7E\u7684\u672C\u5730\u5316\u663E\u793A\u6587\u672C"),
57
+ required: z.boolean().optional().describe("\u4E3A true \u65F6\uFF0C\u4EC5\u5728\u5B57\u6BB5\u6807\u7B7E\u540E\u663E\u793A\u5FC5\u586B\u63D0\u793A\u661F\u53F7\uFF0C\u4E0D\u5F71\u54CD\u5B9E\u9645\u6821\u9A8C\u903B\u8F91"),
41
58
  icon: z.string().optional().describe("Iconify \u56FE\u6807\u6807\u8BC6\u7B26\uFF0C\u663E\u793A\u5728\u8F93\u5165\u6846\u5185\u4FA7"),
42
59
  options: expressionC(/^list/).describe("\u5168\u90E8\u5019\u9009\u9879"),
43
60
  label: expressionC("string", { option: "dyn" }).describe("\u8FD4\u56DE\u5B57\u7B26\u4E32\u7684 CEL \u8868\u8FBE\u5F0F\uFF0C\u63D0\u4F9B option \u53D8\u91CF\uFF0C\u751F\u6210\u8BE5\u9009\u9879\u7684\u663E\u793A\u6587\u672C"),
@@ -53,6 +70,7 @@ export const CalendarFieldC = z.object({
53
70
  type: z.literal("calendar").describe("\u65E5\u671F\u9009\u62E9\u5B57\u6BB5\uFF0C\u901A\u8FC7\u5F39\u51FA\u65E5\u5386\u9762\u677F\u9009\u62E9\u65E5\u671F"),
54
71
  path: dotPropC,
55
72
  title: localeC.describe("\u5B57\u6BB5\u6807\u7B7E\u7684\u672C\u5730\u5316\u663E\u793A\u6587\u672C"),
73
+ required: z.boolean().optional().describe("\u4E3A true \u65F6\uFF0C\u4EC5\u5728\u5B57\u6BB5\u6807\u7B7E\u540E\u663E\u793A\u5FC5\u586B\u63D0\u793A\u661F\u53F7\uFF0C\u4E0D\u5F71\u54CD\u5B9E\u9645\u6821\u9A8C\u903B\u8F91"),
56
74
  icon: z.string().optional().describe("Iconify \u56FE\u6807\u6807\u8BC6\u7B26\uFF0C\u663E\u793A\u5728\u8F93\u5165\u6846\u5185\u4FA7"),
57
75
  style: z.string().optional().describe("CSS \u6837\u5F0F\u5BF9\u8C61\u8868\u8FBE\u5F0F\uFF0C\u63A7\u5236\u5B57\u6BB5\u7684\u5E03\u5C40\u4E0E\u5916\u89C2"),
58
76
  mode: z.enum(["year", "month", "date"]).describe("\u65E5\u5386\u9009\u62E9\u7CBE\u5EA6\uFF1Ayear \u4EC5\u9009\u5E74\uFF0Cmonth \u9009\u5E74\u6708\uFF0Cdate \u9009\u5E74\u6708\u65E5"),
@@ -68,12 +86,19 @@ export const SlotFieldC = z.strictObject({
68
86
  type: z.literal("slot").describe("\u5177\u540D\u63D2\u69FD\u5B57\u6BB5\uFF0C\u901A\u8FC7\u5B57\u6BB5 id \u5BF9\u5E94\u7684\u5177\u540D\u63D2\u69FD\u6E32\u67D3\u5185\u5BB9"),
69
87
  style: z.string().optional().describe("CSS \u6837\u5F0F\u5BF9\u8C61\u8868\u8FBE\u5F0F\uFF0C\u6C42\u503C\u540E\u901A\u8FC7 scoped slot \u7684 style prop \u4F20\u7ED9\u6D88\u8D39\u8005")
70
88
  });
89
+ export const EmptyFieldC = z.strictObject({
90
+ id: fieldIdC,
91
+ type: z.literal("empty").describe("\u7A7A\u767D\u5B57\u6BB5\uFF0C\u6309\u987A\u5E8F\u6E32\u67D3\u4E00\u4E2A\u7A7A div\uFF0C\u53EA\u80FD\u901A\u8FC7 style \u63A7\u5236\u5E03\u5C40"),
92
+ style: z.string().optional().describe("CSS \u6837\u5F0F\u5BF9\u8C61\u8868\u8FBE\u5F0F\uFF0C\u6C42\u503C\u540E\u4F5C\u4E3A\u7A7A div \u7684\u5185\u8054\u6837\u5F0F")
93
+ });
71
94
  export const FieldC = z.discriminatedUnion("type", [
72
95
  StringFieldC,
96
+ TextareaFieldC,
73
97
  NumberFieldC,
74
98
  SelectFieldC,
75
99
  CalendarFieldC,
76
- SlotFieldC
100
+ SlotFieldC,
101
+ EmptyFieldC
77
102
  ]);
78
103
  export const FieldsOrientationC = z.enum(["horizontal", "vertical", "floating"]);
79
104
  export const FieldsStyleC = expressionC(/^map/, {
@@ -17,6 +17,26 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
17
17
  locale: "zh" | "ja" | "en" | "ko";
18
18
  message: string;
19
19
  }[];
20
+ required?: boolean | undefined;
21
+ icon?: string | undefined;
22
+ style?: string | undefined;
23
+ discardEmptyString?: boolean | undefined;
24
+ maxLength?: string | undefined;
25
+ hidden?: string | undefined;
26
+ disabled?: string | undefined;
27
+ validation?: readonly Readonly<{
28
+ expression: string;
29
+ message: string;
30
+ }>[] | undefined;
31
+ } | {
32
+ id: string;
33
+ type: "textarea";
34
+ path: string;
35
+ title: readonly {
36
+ locale: "zh" | "ja" | "en" | "ko";
37
+ message: string;
38
+ }[];
39
+ required?: boolean | undefined;
20
40
  icon?: string | undefined;
21
41
  style?: string | undefined;
22
42
  discardEmptyString?: boolean | undefined;
@@ -35,6 +55,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
35
55
  locale: "zh" | "ja" | "en" | "ko";
36
56
  message: string;
37
57
  }[];
58
+ required?: boolean | undefined;
38
59
  icon?: string | undefined;
39
60
  style?: string | undefined;
40
61
  min?: string | undefined;
@@ -58,6 +79,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
58
79
  label: string;
59
80
  value: string;
60
81
  key: string;
82
+ required?: boolean | undefined;
61
83
  icon?: string | undefined;
62
84
  style?: string | undefined;
63
85
  hidden?: string | undefined;
@@ -76,6 +98,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
76
98
  }[];
77
99
  mode: "date" | "month" | "year";
78
100
  value: string;
101
+ required?: boolean | undefined;
79
102
  icon?: string | undefined;
80
103
  style?: string | undefined;
81
104
  display?: string | undefined;
@@ -90,6 +113,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
90
113
  id: string;
91
114
  type: "slot";
92
115
  style?: string | undefined;
116
+ } | {
117
+ id: string;
118
+ type: "empty";
119
+ style?: string | undefined;
93
120
  })[];
94
121
  orientation?: "vertical" | "horizontal" | "floating" | undefined;
95
122
  style?: string | undefined;
@@ -105,6 +132,26 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
105
132
  locale: "zh" | "ja" | "en" | "ko";
106
133
  message: string;
107
134
  }[];
135
+ required?: boolean | undefined;
136
+ icon?: string | undefined;
137
+ style?: string | undefined;
138
+ discardEmptyString?: boolean | undefined;
139
+ maxLength?: string | undefined;
140
+ hidden?: string | undefined;
141
+ disabled?: string | undefined;
142
+ validation?: readonly Readonly<{
143
+ expression: string;
144
+ message: string;
145
+ }>[] | undefined;
146
+ } | {
147
+ id: string;
148
+ type: "textarea";
149
+ path: string;
150
+ title: readonly {
151
+ locale: "zh" | "ja" | "en" | "ko";
152
+ message: string;
153
+ }[];
154
+ required?: boolean | undefined;
108
155
  icon?: string | undefined;
109
156
  style?: string | undefined;
110
157
  discardEmptyString?: boolean | undefined;
@@ -123,6 +170,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
123
170
  locale: "zh" | "ja" | "en" | "ko";
124
171
  message: string;
125
172
  }[];
173
+ required?: boolean | undefined;
126
174
  icon?: string | undefined;
127
175
  style?: string | undefined;
128
176
  min?: string | undefined;
@@ -146,6 +194,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
146
194
  label: string;
147
195
  value: string;
148
196
  key: string;
197
+ required?: boolean | undefined;
149
198
  icon?: string | undefined;
150
199
  style?: string | undefined;
151
200
  hidden?: string | undefined;
@@ -164,6 +213,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
164
213
  }[];
165
214
  mode: "date" | "month" | "year";
166
215
  value: string;
216
+ required?: boolean | undefined;
167
217
  icon?: string | undefined;
168
218
  style?: string | undefined;
169
219
  display?: string | undefined;
@@ -178,6 +228,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
178
228
  id: string;
179
229
  type: "slot";
180
230
  style?: string | undefined;
231
+ } | {
232
+ id: string;
233
+ type: "empty";
234
+ style?: string | undefined;
181
235
  })[];
182
236
  orientation?: "vertical" | "horizontal" | "floating" | undefined;
183
237
  style?: string | undefined;
@@ -7,12 +7,14 @@ import { computed, nextTick, ref, toRaw, watch } from "vue";
7
7
  import { useI18n } from "vue-i18n";
8
8
  import {
9
9
  CalendarFieldC,
10
+ EmptyFieldC,
10
11
  FieldsConfigC,
12
+ FieldsStyleC,
11
13
  NumberFieldC,
12
14
  SelectFieldC,
13
15
  SlotFieldC,
14
16
  StringFieldC,
15
- FieldsStyleC
17
+ TextareaFieldC
16
18
  } from "../fields/schema";
17
19
  import { cn } from "../../../utils/cn";
18
20
  import { Button } from "../button";
@@ -54,9 +56,11 @@ const sortableItemIds = ref([]);
54
56
  const validationErrors = ref({});
55
57
  const fieldTypeOptions = computed(() => [
56
58
  { type: "string", label: t("field-type-string") },
59
+ { type: "textarea", label: t("field-type-textarea") },
57
60
  { type: "number", label: t("field-type-number") },
58
61
  { type: "select", label: t("field-type-select") },
59
62
  { type: "calendar", label: t("field-type-calendar") },
63
+ { type: "empty", label: t("field-type-empty") },
60
64
  { type: "slot", label: t("field-type-slot") }
61
65
  ]);
62
66
  const generalItem = computed(() => ({
@@ -67,7 +71,7 @@ const normalizedSearch = computed(() => search.value.trim().toLocaleLowerCase())
67
71
  const selectedField = computed(() => draftFields.value.find((field) => field.draftId === selectedItemId.value));
68
72
  const selectedFieldValidationRules = computed(() => {
69
73
  const field = selectedField.value?.field;
70
- if (!field || field.type === "slot") {
74
+ if (!field || field.type === "slot" || field.type === "empty") {
71
75
  return [];
72
76
  }
73
77
  return field.validation ?? [];
@@ -99,10 +103,14 @@ function normalizeOrientation(value) {
99
103
  function getFieldTypeLabel(type) {
100
104
  return t(`field-type-${type}`);
101
105
  }
102
- function getSlotFieldLabel(field) {
103
- return t("slot-field", {
104
- id: field.id.slice(0, 8)
105
- });
106
+ function isPassiveField(field) {
107
+ return field.type === "slot" || field.type === "empty";
108
+ }
109
+ function getSlotFieldLabel(_) {
110
+ return getFieldTypeLabel("slot");
111
+ }
112
+ function getEmptyFieldLabel(_) {
113
+ return getFieldTypeLabel("empty");
106
114
  }
107
115
  function getUnnamedFieldLabel(field) {
108
116
  return t("unnamed-field", {
@@ -110,7 +118,7 @@ function getUnnamedFieldLabel(field) {
110
118
  });
111
119
  }
112
120
  function getFieldChineseTitle(field) {
113
- if (field.type === "slot") {
121
+ if (isPassiveField(field)) {
114
122
  return void 0;
115
123
  }
116
124
  const zhTitle = field.title.find((item) => item.locale === "zh");
@@ -124,14 +132,17 @@ function getFieldListLabel(field) {
124
132
  if (field.type === "slot") {
125
133
  return getSlotFieldLabel(field);
126
134
  }
135
+ if (field.type === "empty") {
136
+ return getEmptyFieldLabel(field);
137
+ }
127
138
  return getFieldChineseTitle(field) ?? getUnnamedFieldLabel(field);
128
139
  }
129
140
  const fieldItems = computed(() => draftFields.value.map((item) => ({
130
141
  itemId: item.draftId,
131
142
  fieldId: item.field.id,
132
143
  label: getFieldListLabel(item.field),
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(" "),
144
+ path: isPassiveField(item.field) ? void 0 : item.field.path,
145
+ searchMeta: isPassiveField(item.field) ? item.field.id : [item.field.path, item.field.id].filter(Boolean).join(" "),
135
146
  type: item.field.type
136
147
  })));
137
148
  const filteredFieldItems = computed(() => {
@@ -201,9 +212,11 @@ function normalizeValidationRules(validation) {
201
212
  function normalizeField(field) {
202
213
  switch (field.type) {
203
214
  case "string":
215
+ case "textarea":
204
216
  return {
205
217
  ...field,
206
218
  path: field.path.trim(),
219
+ required: field.required ? true : void 0,
207
220
  icon: normalizeOptionalString(field.icon ?? ""),
208
221
  style: normalizeOptionalString(field.style ?? ""),
209
222
  maxLength: normalizeOptionalString(field.maxLength ?? ""),
@@ -216,6 +229,7 @@ function normalizeField(field) {
216
229
  return {
217
230
  ...field,
218
231
  path: field.path.trim(),
232
+ required: field.required ? true : void 0,
219
233
  icon: normalizeOptionalString(field.icon ?? ""),
220
234
  style: normalizeOptionalString(field.style ?? ""),
221
235
  min: normalizeOptionalString(field.min ?? ""),
@@ -229,6 +243,7 @@ function normalizeField(field) {
229
243
  return {
230
244
  ...field,
231
245
  path: field.path.trim(),
246
+ required: field.required ? true : void 0,
232
247
  icon: normalizeOptionalString(field.icon ?? ""),
233
248
  style: normalizeOptionalString(field.style ?? ""),
234
249
  options: field.options.trim(),
@@ -243,6 +258,7 @@ function normalizeField(field) {
243
258
  return {
244
259
  ...field,
245
260
  path: field.path.trim(),
261
+ required: field.required ? true : void 0,
246
262
  icon: normalizeOptionalString(field.icon ?? ""),
247
263
  style: normalizeOptionalString(field.style ?? ""),
248
264
  display: normalizeOptionalString(field.display ?? ""),
@@ -257,6 +273,11 @@ function normalizeField(field) {
257
273
  ...field,
258
274
  style: normalizeOptionalString(field.style ?? "")
259
275
  };
276
+ case "empty":
277
+ return {
278
+ ...field,
279
+ style: normalizeOptionalString(field.style ?? "")
280
+ };
260
281
  }
261
282
  }
262
283
  function createField(type) {
@@ -264,6 +285,7 @@ function createField(type) {
264
285
  const id = createFieldId();
265
286
  switch (type) {
266
287
  case "string":
288
+ case "textarea":
267
289
  return {
268
290
  id,
269
291
  type,
@@ -297,6 +319,11 @@ function createField(type) {
297
319
  mode: "date",
298
320
  value: "yyyy-MM-dd"
299
321
  };
322
+ case "empty":
323
+ return {
324
+ id,
325
+ type
326
+ };
300
327
  case "slot":
301
328
  return {
302
329
  id,
@@ -469,7 +496,7 @@ async function copyFieldId(fieldId) {
469
496
  }
470
497
  function updateSelectedFieldPath(value) {
471
498
  const selected = selectedField.value;
472
- if (!selected || selected.field.type === "slot") {
499
+ if (!selected || isPassiveField(selected.field)) {
473
500
  return;
474
501
  }
475
502
  clearFieldError(selected.draftId, "path");
@@ -511,13 +538,28 @@ function updateSelectedFieldDisabled(value) {
511
538
  disabled: normalizeOptionalString(String(value))
512
539
  }));
513
540
  }
541
+ function updateSelectedFieldRequired(value) {
542
+ const selected = selectedField.value;
543
+ if (!selected || isPassiveField(selected.field)) {
544
+ return;
545
+ }
546
+ updateDraftField(selected.draftId, (field) => {
547
+ if (isPassiveField(field)) {
548
+ return field;
549
+ }
550
+ return {
551
+ ...field,
552
+ required: value ? true : void 0
553
+ };
554
+ });
555
+ }
514
556
  function updateSelectedStringDiscardEmpty(value) {
515
557
  const selected = selectedField.value;
516
- if (!selected || selected.field.type !== "string") {
558
+ if (!selected || selected.field.type !== "string" && selected.field.type !== "textarea") {
517
559
  return;
518
560
  }
519
561
  updateDraftField(selected.draftId, (field) => {
520
- if (field.type !== "string") {
562
+ if (field.type !== "string" && field.type !== "textarea") {
521
563
  return field;
522
564
  }
523
565
  return {
@@ -528,12 +570,12 @@ function updateSelectedStringDiscardEmpty(value) {
528
570
  }
529
571
  function updateSelectedStringMaxLength(value) {
530
572
  const selected = selectedField.value;
531
- if (!selected || selected.field.type !== "string") {
573
+ if (!selected || selected.field.type !== "string" && selected.field.type !== "textarea") {
532
574
  return;
533
575
  }
534
576
  clearFieldError(selected.draftId, "maxLength");
535
577
  updateDraftField(selected.draftId, (field) => {
536
- if (field.type !== "string") {
578
+ if (field.type !== "string" && field.type !== "textarea") {
537
579
  return field;
538
580
  }
539
581
  return {
@@ -724,11 +766,11 @@ function updateSelectedCalendarDisableDate(value) {
724
766
  }
725
767
  function addValidationRule() {
726
768
  const selected = selectedField.value;
727
- if (!selected || selected.field.type === "slot") {
769
+ if (!selected || isPassiveField(selected.field)) {
728
770
  return;
729
771
  }
730
772
  updateDraftField(selected.draftId, (field) => {
731
- if (field.type === "slot") {
773
+ if (isPassiveField(field)) {
732
774
  return field;
733
775
  }
734
776
  return {
@@ -745,7 +787,7 @@ function addValidationRule() {
745
787
  }
746
788
  function updateValidationRule(index, updater) {
747
789
  const selected = selectedField.value;
748
- if (!selected || selected.field.type === "slot") {
790
+ if (!selected || isPassiveField(selected.field)) {
749
791
  return;
750
792
  }
751
793
  const validation = selected.field.validation ?? [];
@@ -755,7 +797,7 @@ function updateValidationRule(index, updater) {
755
797
  }
756
798
  const nextValidation = validation.map((rule, ruleIndex) => ruleIndex === index ? updater(rule) : rule);
757
799
  updateDraftField(selected.draftId, (field) => {
758
- if (field.type === "slot") {
800
+ if (isPassiveField(field)) {
759
801
  return field;
760
802
  }
761
803
  return {
@@ -788,7 +830,7 @@ function updateSelectedValidationMessage(index, value) {
788
830
  }
789
831
  function moveValidationRule(index, offset) {
790
832
  const selected = selectedField.value;
791
- if (!selected || selected.field.type === "slot") {
833
+ if (!selected || isPassiveField(selected.field)) {
792
834
  return;
793
835
  }
794
836
  const validation = selected.field.validation ?? [];
@@ -805,7 +847,7 @@ function moveValidationRule(index, offset) {
805
847
  nextValidation[index] = targetRule;
806
848
  nextValidation[nextIndex] = currentRule;
807
849
  updateDraftField(selected.draftId, (field) => {
808
- if (field.type === "slot") {
850
+ if (isPassiveField(field)) {
809
851
  return field;
810
852
  }
811
853
  return {
@@ -817,7 +859,7 @@ function moveValidationRule(index, offset) {
817
859
  }
818
860
  function deleteValidationRule(index) {
819
861
  const selected = selectedField.value;
820
- if (!selected || selected.field.type === "slot") {
862
+ if (!selected || isPassiveField(selected.field)) {
821
863
  return;
822
864
  }
823
865
  const validation = selected.field.validation ?? [];
@@ -827,7 +869,7 @@ function deleteValidationRule(index) {
827
869
  clearValidationRuleError(selected.draftId, index, "expression");
828
870
  clearValidationRuleError(selected.draftId, index, "message");
829
871
  updateDraftField(selected.draftId, (field) => {
830
- if (field.type === "slot") {
872
+ if (isPassiveField(field)) {
831
873
  return field;
832
874
  }
833
875
  const validation2 = field.validation ?? [];
@@ -848,6 +890,10 @@ function getSchemaIssues(field) {
848
890
  const result = StringFieldC.safeParse(field);
849
891
  return result.success ? [] : result.error.issues;
850
892
  }
893
+ case "textarea": {
894
+ const result = TextareaFieldC.safeParse(field);
895
+ return result.success ? [] : result.error.issues;
896
+ }
851
897
  case "number": {
852
898
  const result = NumberFieldC.safeParse(field);
853
899
  return result.success ? [] : result.error.issues;
@@ -864,6 +910,10 @@ function getSchemaIssues(field) {
864
910
  const result = SlotFieldC.safeParse(field);
865
911
  return result.success ? [] : result.error.issues;
866
912
  }
913
+ case "empty": {
914
+ const result = EmptyFieldC.safeParse(field);
915
+ return result.success ? [] : result.error.issues;
916
+ }
867
917
  }
868
918
  }
869
919
  function normalizeIssuePath(path) {
@@ -903,7 +953,7 @@ function validateDraftFields() {
903
953
  } else {
904
954
  idOwners[item.field.id] = item.draftId;
905
955
  }
906
- if (item.field.type !== "slot") {
956
+ if (!isPassiveField(item.field)) {
907
957
  const existingPathOwner = pathOwners[item.field.path];
908
958
  if (item.field.path.length === 0) {
909
959
  setError(errors, getFieldErrorKey(item.draftId, "path"), t("field-path-required"));
@@ -1020,16 +1070,16 @@ function buildDslGuideMarkdown() {
1020
1070
  "",
1021
1071
  "### 3. \u5B57\u6BB5\u7C7B\u578B\u7EA6\u675F",
1022
1072
  "- \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"
1073
+ "- \u53EA\u6709\u975E `slot` / `empty` \u5B57\u6BB5\u53EF\u4EE5\u914D\u7F6E `path`\uFF0C\u5E76\u53C2\u4E0E\u8868\u5355\u503C\u8BFB\u5199\u3002",
1074
+ "- `slot` \u548C `empty` \u5B57\u6BB5\u90FD\u4E0D\u4F1A\u7ED1\u5B9A\u6A21\u578B\u503C\uFF0C\u53EA\u5141\u8BB8 `id`\u3001`type` \u548C\u53EF\u9009\u7684 `style`\u3002"
1025
1075
  ].join("\n");
1026
1076
  }
1027
1077
  function buildMarkdownNotes() {
1028
1078
  return [
1029
1079
  "## \u6CE8\u610F\u4E8B\u9879",
1030
1080
  "- \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",
1081
+ "- `slot` \u4E0E `empty` \u5B57\u6BB5\u53EA\u80FD\u4F7F\u7528 `id`\u3001`type` \u548C\u53EF\u9009\u7684 `style`\u3002",
1082
+ "- \u975E `slot` / `empty` \u5B57\u6BB5\u7684 `path` \u5FC5\u987B\u552F\u4E00\u4E14\u4E0D\u80FD\u4E3A\u7A7A\u3002",
1033
1083
  "- \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
1084
  ].join("\n");
1035
1085
  }
@@ -1454,7 +1504,7 @@ function confirmChanges() {
1454
1504
  </p>
1455
1505
 
1456
1506
  <section
1457
- v-if="selectedField.field.type !== 'slot'"
1507
+ v-if="!isPassiveField(selectedField.field)"
1458
1508
  data-slot="fields-configurator-field-path-section"
1459
1509
  class="flex flex-col gap-2"
1460
1510
  >
@@ -1483,7 +1533,7 @@ function confirmChanges() {
1483
1533
  </section>
1484
1534
 
1485
1535
  <section
1486
- v-if="selectedField.field.type !== 'slot'"
1536
+ v-if="!isPassiveField(selectedField.field)"
1487
1537
  data-slot="fields-configurator-field-label-section"
1488
1538
  class="flex flex-col gap-2"
1489
1539
  >
@@ -1507,8 +1557,8 @@ function confirmChanges() {
1507
1557
  </section>
1508
1558
 
1509
1559
  <section
1510
- v-if="selectedField.field.type === 'slot'"
1511
- data-slot="fields-configurator-slot-options"
1560
+ v-if="isPassiveField(selectedField.field)"
1561
+ data-slot="fields-configurator-passive-options"
1512
1562
  class="grid gap-4 md:grid-cols-2"
1513
1563
  >
1514
1564
  <label class="flex flex-col gap-2 md:col-span-2">
@@ -1537,6 +1587,18 @@ function confirmChanges() {
1537
1587
  data-slot="fields-configurator-field-general-options"
1538
1588
  class="grid gap-4 md:grid-cols-2"
1539
1589
  >
1590
+ <label class="flex items-center justify-between gap-3 rounded-md border border-zinc-200 px-3 py-2 md:col-span-2">
1591
+ <div class="flex flex-col gap-1">
1592
+ <span class="text-sm font-medium text-zinc-800">{{ t("field-required") }}</span>
1593
+ <span class="text-xs text-zinc-500">{{ t("field-required-description") }}</span>
1594
+ </div>
1595
+ <Switch
1596
+ data-slot="fields-configurator-field-required-switch"
1597
+ :model-value="selectedField.field.required ?? false"
1598
+ @update:model-value="updateSelectedFieldRequired"
1599
+ />
1600
+ </label>
1601
+
1540
1602
  <label class="flex flex-col gap-2">
1541
1603
  <span class="text-xs font-medium text-zinc-500">
1542
1604
  {{ t("field-style") }}
@@ -1599,7 +1661,7 @@ function confirmChanges() {
1599
1661
  </section>
1600
1662
 
1601
1663
  <section
1602
- v-if="selectedField.field.type === 'string'"
1664
+ v-if="selectedField.field.type === 'string' || selectedField.field.type === 'textarea'"
1603
1665
  data-slot="fields-configurator-string-options"
1604
1666
  class="grid gap-4 md:grid-cols-2"
1605
1667
  >
@@ -1867,7 +1929,7 @@ function confirmChanges() {
1867
1929
  </section>
1868
1930
 
1869
1931
  <section
1870
- v-if="selectedField.field.type !== 'slot'"
1932
+ v-if="!isPassiveField(selectedField.field)"
1871
1933
  data-slot="fields-configurator-validation"
1872
1934
  class="flex flex-col gap-4"
1873
1935
  >
@@ -2077,9 +2139,11 @@ function confirmChanges() {
2077
2139
  "add-field": "新增字段",
2078
2140
  "field-type": "字段类型",
2079
2141
  "field-type-string": "文本",
2142
+ "field-type-textarea": "多行文本",
2080
2143
  "field-type-number": "数字",
2081
2144
  "field-type-select": "选择",
2082
2145
  "field-type-calendar": "日期",
2146
+ "field-type-empty": "空白",
2083
2147
  "field-type-slot": "插槽",
2084
2148
  "field-id": "字段 ID",
2085
2149
  "field-id-duplicate": "字段 ID 不能重复",
@@ -2091,6 +2155,8 @@ function confirmChanges() {
2091
2155
  "copy-field-id-short": "复制 ID",
2092
2156
  "copy-field-id-failed": "复制字段 ID 失败,请检查剪贴板权限。",
2093
2157
  "field-label": "字段标题",
2158
+ "field-required": "显示必填提示",
2159
+ "field-required-description": "开启后仅在标签后显示红色星号,不会自动添加校验规则。",
2094
2160
  "field-icon": "图标",
2095
2161
  "field-icon-placeholder": "例如 fluent:person-20-regular",
2096
2162
  "field-style": "样式表达式",
@@ -2139,6 +2205,7 @@ function confirmChanges() {
2139
2205
  "no-matches": "没有匹配的字段。",
2140
2206
  "no-validation-rules": "暂未配置校验规则。",
2141
2207
  "unnamed-field": "未命名{type}字段",
2208
+ "empty-field": "空白 {id}",
2142
2209
  "slot-field": "插槽 {id}",
2143
2210
  "no-fields": "还没有字段。",
2144
2211
  "drag-field": "拖拽调整字段顺序:{field}",
@@ -2173,9 +2240,11 @@ function confirmChanges() {
2173
2240
  "add-field": "フィールドを追加",
2174
2241
  "field-type": "フィールド種別",
2175
2242
  "field-type-string": "テキスト",
2243
+ "field-type-textarea": "複数行テキスト",
2176
2244
  "field-type-number": "数値",
2177
2245
  "field-type-select": "選択",
2178
2246
  "field-type-calendar": "日付",
2247
+ "field-type-empty": "空白",
2179
2248
  "field-type-slot": "スロット",
2180
2249
  "field-id": "フィールド ID",
2181
2250
  "field-id-duplicate": "フィールド ID は重複できません",
@@ -2187,6 +2256,8 @@ function confirmChanges() {
2187
2256
  "copy-field-id-short": "ID をコピー",
2188
2257
  "copy-field-id-failed": "フィールド ID のコピーに失敗しました。クリップボード権限を確認してください。",
2189
2258
  "field-label": "フィールドラベル",
2259
+ "field-required": "必須ヒントを表示",
2260
+ "field-required-description": "有効にするとラベルの後ろに赤い星印だけを表示し、検証ルールは自動追加しません。",
2190
2261
  "field-icon": "アイコン",
2191
2262
  "field-icon-placeholder": "例: fluent:person-20-regular",
2192
2263
  "field-style": "スタイル式",
@@ -2235,6 +2306,7 @@ function confirmChanges() {
2235
2306
  "no-matches": "一致するフィールドがありません。",
2236
2307
  "no-validation-rules": "検証ルールはまだありません。",
2237
2308
  "unnamed-field": "未命名の{type}フィールド",
2309
+ "empty-field": "空白 {id}",
2238
2310
  "slot-field": "スロット {id}",
2239
2311
  "no-fields": "フィールドがありません。",
2240
2312
  "drag-field": "{field} の順序をドラッグで変更",
@@ -2269,9 +2341,11 @@ function confirmChanges() {
2269
2341
  "add-field": "Add field",
2270
2342
  "field-type": "Field type",
2271
2343
  "field-type-string": "Text",
2344
+ "field-type-textarea": "Textarea",
2272
2345
  "field-type-number": "Number",
2273
2346
  "field-type-select": "Select",
2274
2347
  "field-type-calendar": "Date",
2348
+ "field-type-empty": "Empty",
2275
2349
  "field-type-slot": "Slot",
2276
2350
  "field-id": "Field ID",
2277
2351
  "field-id-duplicate": "Field ID must be unique",
@@ -2283,6 +2357,8 @@ function confirmChanges() {
2283
2357
  "copy-field-id-short": "Copy ID",
2284
2358
  "copy-field-id-failed": "Failed to copy the field ID. Check clipboard permissions.",
2285
2359
  "field-label": "Field label",
2360
+ "field-required": "Show required hint",
2361
+ "field-required-description": "When enabled, only a red asterisk is shown after the label. No validation rule is added automatically.",
2286
2362
  "field-icon": "Icon",
2287
2363
  "field-icon-placeholder": "For example fluent:person-20-regular",
2288
2364
  "field-style": "Style expression",
@@ -2331,6 +2407,7 @@ function confirmChanges() {
2331
2407
  "no-matches": "No matching fields.",
2332
2408
  "no-validation-rules": "No validation rules yet.",
2333
2409
  "unnamed-field": "Untitled {type} field",
2410
+ "empty-field": "Empty {id}",
2334
2411
  "slot-field": "Slot {id}",
2335
2412
  "no-fields": "No fields yet.",
2336
2413
  "drag-field": "Drag to reorder field {field}",