@shwfed/config 3.0.11 → 3.0.13

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.
Files changed (98) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/config/blocks/2026-06-17/com.shwfed.block.tree.single/config.d.vue.ts +18 -18
  3. package/dist/runtime/components/config/blocks/2026-06-17/com.shwfed.block.tree.single/config.vue.d.ts +18 -18
  4. package/dist/runtime/components/config/blocks/2026-06-17/com.shwfed.block.tree.single/runtime.d.vue.ts +18 -18
  5. package/dist/runtime/components/config/blocks/2026-06-17/com.shwfed.block.tree.single/runtime.vue.d.ts +18 -18
  6. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/config.d.vue.ts +86 -0
  7. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/config.vue +376 -0
  8. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/config.vue.d.ts +86 -0
  9. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/items-colgroup.d.vue.ts +3 -0
  10. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/items-colgroup.vue +20 -0
  11. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/items-colgroup.vue.d.ts +3 -0
  12. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/runtime.d.vue.ts +86 -0
  13. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/runtime.vue +146 -0
  14. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/runtime.vue.d.ts +86 -0
  15. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/schema.d.ts +84 -0
  16. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/schema.js +165 -0
  17. package/dist/runtime/components/config/index.d.vue.ts +2 -2
  18. package/dist/runtime/components/config/index.vue.d.ts +2 -2
  19. package/dist/runtime/components/form/fields/2026-04-24/com.shwfed.form.field.combobox.single/config.d.vue.ts +16 -16
  20. package/dist/runtime/components/form/fields/2026-04-24/com.shwfed.form.field.combobox.single/config.vue.d.ts +16 -16
  21. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/config.d.vue.ts +6 -6
  22. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/config.vue.d.ts +6 -6
  23. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/config.d.vue.ts +8 -8
  24. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/config.vue.d.ts +8 -8
  25. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.timerange/config.d.vue.ts +2 -2
  26. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.timerange/config.vue.d.ts +2 -2
  27. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.combobox.single.remote/config.d.vue.ts +16 -16
  28. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.combobox.single.remote/config.vue.d.ts +16 -16
  29. package/dist/runtime/components/form/fields/2026-05-23/com.shwfed.form.field.tree.multi/config.d.vue.ts +26 -26
  30. package/dist/runtime/components/form/fields/2026-05-23/com.shwfed.form.field.tree.multi/config.vue.d.ts +26 -26
  31. package/dist/runtime/components/form/fields/2026-05-24/com.shwfed.form.field.monthrange/config.d.vue.ts +6 -6
  32. package/dist/runtime/components/form/fields/2026-05-24/com.shwfed.form.field.monthrange/config.vue.d.ts +6 -6
  33. package/dist/runtime/components/form/fields/2026-05-25/com.shwfed.form.field.combobox.multi/config.d.vue.ts +16 -16
  34. package/dist/runtime/components/form/fields/2026-05-25/com.shwfed.form.field.combobox.multi/config.vue.d.ts +16 -16
  35. package/dist/runtime/components/form/fields/2026-05-26/com.shwfed.form.field.tree.combobox.multi/config.d.vue.ts +26 -26
  36. package/dist/runtime/components/form/fields/2026-05-26/com.shwfed.form.field.tree.combobox.multi/config.vue.d.ts +26 -26
  37. package/dist/runtime/components/form/fields/2026-05-26/com.shwfed.form.field.tree.combobox.single/config.d.vue.ts +26 -26
  38. package/dist/runtime/components/form/fields/2026-05-26/com.shwfed.form.field.tree.combobox.single/config.vue.d.ts +26 -26
  39. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.combobox.multi/config.d.vue.ts +26 -26
  40. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.combobox.multi/config.vue.d.ts +26 -26
  41. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.combobox.single/config.d.vue.ts +26 -26
  42. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.combobox.single/config.vue.d.ts +26 -26
  43. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.multi/config.d.vue.ts +26 -26
  44. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.multi/config.vue.d.ts +26 -26
  45. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.single/config.d.vue.ts +26 -26
  46. package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.single/config.vue.d.ts +26 -26
  47. package/dist/runtime/components/form/utils/validation.js +9 -7
  48. package/dist/runtime/components/table/columns/2026-05-27/com.shwfed.table.column.tree-combobox-multi/config.d.vue.ts +8 -8
  49. package/dist/runtime/components/table/columns/2026-05-27/com.shwfed.table.column.tree-combobox-multi/config.vue.d.ts +8 -8
  50. package/dist/runtime/components/table/columns/2026-05-27/com.shwfed.table.column.tree-combobox-single/config.d.vue.ts +8 -8
  51. package/dist/runtime/components/table/columns/2026-05-27/com.shwfed.table.column.tree-combobox-single/config.vue.d.ts +8 -8
  52. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-multi/config.d.vue.ts +8 -8
  53. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-multi/config.vue.d.ts +8 -8
  54. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-single/config.d.vue.ts +8 -8
  55. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-single/config.vue.d.ts +8 -8
  56. package/dist/runtime/components/table/columns/2026-06-14/com.shwfed.table.column.tree-combobox-multi/config.d.vue.ts +8 -8
  57. package/dist/runtime/components/table/columns/2026-06-14/com.shwfed.table.column.tree-combobox-multi/config.vue.d.ts +8 -8
  58. package/dist/runtime/components/table/columns/2026-06-14/com.shwfed.table.column.tree-combobox-single/config.d.vue.ts +8 -8
  59. package/dist/runtime/components/table/columns/2026-06-14/com.shwfed.table.column.tree-combobox-single/config.vue.d.ts +8 -8
  60. package/dist/runtime/components/table/columns/2026-07-16/com.shwfed.table.column.upload/config.vue +57 -247
  61. package/dist/runtime/components/table/columns/2026-07-16/com.shwfed.table.column.upload/prompt.md +2 -2
  62. package/dist/runtime/components/table/columns/2026-07-16/com.shwfed.table.column.upload/runtime.vue +21 -124
  63. package/dist/runtime/components/table/columns/2026-07-16/com.shwfed.table.column.upload/schema.d.ts +5 -27
  64. package/dist/runtime/components/table/columns/2026-07-16/com.shwfed.table.column.upload/schema.js +10 -37
  65. package/dist/runtime/components/ui/date-picker/DatePickerInput.d.vue.ts +1 -1
  66. package/dist/runtime/components/ui/date-picker/DatePickerInput.vue.d.ts +1 -1
  67. package/dist/runtime/components/ui/date-picker/DatePickerTimeInput.d.vue.ts +1 -1
  68. package/dist/runtime/components/ui/date-picker/DatePickerTimeInput.vue.d.ts +1 -1
  69. package/dist/runtime/components/ui/date-range-picker/DateRangePickerInput.d.vue.ts +1 -1
  70. package/dist/runtime/components/ui/date-range-picker/DateRangePickerInput.vue.d.ts +1 -1
  71. package/dist/runtime/components/ui/date-range-picker/DateRangePickerTimeInput.d.vue.ts +2 -2
  72. package/dist/runtime/components/ui/date-range-picker/DateRangePickerTimeInput.vue.d.ts +2 -2
  73. package/dist/runtime/components/ui/invoice/InvoiceSheet.d.vue.ts +66 -0
  74. package/dist/runtime/components/ui/invoice/InvoiceSheet.vue +182 -0
  75. package/dist/runtime/components/ui/invoice/InvoiceSheet.vue.d.ts +66 -0
  76. package/dist/runtime/components/ui/invoice/index.d.ts +1 -0
  77. package/dist/runtime/components/ui/invoice/index.js +1 -0
  78. package/dist/runtime/components/ui/invoice-config/InvoiceConfigShell.d.vue.ts +36 -0
  79. package/dist/runtime/components/ui/invoice-config/InvoiceConfigShell.vue +41 -0
  80. package/dist/runtime/components/ui/invoice-config/InvoiceConfigShell.vue.d.ts +36 -0
  81. package/dist/runtime/components/ui/invoice-config/InvoiceExprField.d.vue.ts +14 -0
  82. package/dist/runtime/components/ui/invoice-config/InvoiceExprField.vue +39 -0
  83. package/dist/runtime/components/ui/invoice-config/InvoiceExprField.vue.d.ts +14 -0
  84. package/dist/runtime/components/ui/invoice-config/InvoiceJsonScope.d.vue.ts +13 -0
  85. package/dist/runtime/components/ui/invoice-config/InvoiceJsonScope.vue +19 -0
  86. package/dist/runtime/components/ui/invoice-config/InvoiceJsonScope.vue.d.ts +13 -0
  87. package/dist/runtime/components/ui/invoice-config/InvoiceRowScope.d.vue.ts +13 -0
  88. package/dist/runtime/components/ui/invoice-config/InvoiceRowScope.vue +22 -0
  89. package/dist/runtime/components/ui/invoice-config/InvoiceRowScope.vue.d.ts +13 -0
  90. package/dist/runtime/components/ui/invoice-config/index.d.ts +4 -0
  91. package/dist/runtime/components/ui/invoice-config/index.js +4 -0
  92. package/dist/runtime/share/invoice-eval.d.ts +13 -0
  93. package/dist/runtime/share/invoice-eval.js +88 -0
  94. package/dist/runtime/share/invoice.d.ts +68 -0
  95. package/dist/runtime/share/invoice.js +35 -0
  96. package/dist/runtime/share/rmb.d.ts +11 -0
  97. package/dist/runtime/share/rmb.js +58 -0
  98. package/package.json +1 -1
@@ -1,15 +1,11 @@
1
1
  <script setup>
2
- import { computed, defineComponent, ref, watch } from "vue";
2
+ import { computed, defineComponent, ref } from "vue";
3
3
  import { Icon } from "@iconify/vue";
4
- import {
5
- useTreeDnd
6
- } from "../../../../../composables/useTreeDnd";
7
4
  import { provideCELContext } from "../../../../../utils/cel-context";
8
5
  import TriggersField from "../../../../actions/components/triggers-field.vue";
9
6
  import { ExpressionEditor } from "../../../../ui/expression-editor";
10
7
  import { Separator } from "../../../../ui/separator";
11
8
  import { Switch } from "../../../../ui/switch";
12
- import { Button } from "../../../../ui/button";
13
9
  import { Field, FieldLabel } from "../../../../ui/field";
14
10
  import { IconPicker } from "../../../../ui/icon-picker";
15
11
  import { Locale } from "../../../../ui/locale";
@@ -35,7 +31,7 @@ import {
35
31
  SelectValue
36
32
  } from "../../../../ui/select";
37
33
  import { getStructFieldDescription, getStructFieldTitle } from "../../../utils/schema-meta";
38
- import { FILE_VAR, FILES_VAR, JSON_VAR, UPLOAD_JSON_VAR, schema } from "./schema";
34
+ import { FILE_VAR, FILES_VAR, UPLOAD_JSON_VAR, schema } from "./schema";
39
35
  defineOptions({ name: "ShwfedTableUploadRendererConfig" });
40
36
  const value = defineModel({ type: Object, ...{ required: true } });
41
37
  const fieldSchema = schema(() => {
@@ -49,7 +45,6 @@ const ROW_VARS = {
49
45
  const UPLOAD_REQUEST_VARS = { ...ROW_VARS, files: FILES_VAR };
50
46
  const UPLOAD_HANDLE_VARS = { ...ROW_VARS, json: UPLOAD_JSON_VAR };
51
47
  const UPLOAD_FILENAME_VARS = { ...ROW_VARS, file: FILE_VAR };
52
- const TEMPLATE_DOWNLOAD_VARS = { ...ROW_VARS, json: JSON_VAR };
53
48
  const bindingText = computed({
54
49
  get: () => value.value.binding ?? "",
55
50
  set: (next) => {
@@ -131,110 +126,22 @@ const uploadFilenameText = computed({
131
126
  }
132
127
  }
133
128
  });
134
- const templates = computed(() => value.value.templates ?? []);
135
- function setTemplates(next) {
136
- if (next.length === 0) {
137
- const { templates: _omit, ...rest } = value.value;
129
+ function updateAddIcon(icon) {
130
+ const trimmed = icon.trim();
131
+ if (trimmed.length === 0) {
132
+ const { addIcon: _omit, ...rest } = value.value;
138
133
  value.value = rest;
139
134
  } else {
140
- value.value = { ...value.value, templates: next };
135
+ value.value = { ...value.value, addIcon: trimmed };
141
136
  }
142
137
  }
143
- function makeId() {
144
- if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
145
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
146
- const r = Math.random() * 16 | 0;
147
- return (c === "x" ? r : r & 3 | 8).toString(16);
148
- });
149
- }
150
- const rowKeys = ref([]);
151
- watch(
152
- () => templates.value.length,
153
- (len) => {
154
- if (len === rowKeys.value.length) return;
155
- rowKeys.value = Array.from({ length: len }, () => makeId());
156
- },
157
- { immediate: true }
158
- );
159
- function addTemplate() {
160
- setTemplates([...templates.value, { request: "" }]);
161
- rowKeys.value = [...rowKeys.value, makeId()];
162
- }
163
- function removeTemplate(index) {
164
- setTemplates(templates.value.filter((_, i) => i !== index));
165
- rowKeys.value = rowKeys.value.filter((_, i) => i !== index);
166
- }
167
- function move(from, to) {
168
- if (from === to) return;
169
- if (from < 0 || to < 0 || from >= templates.value.length || to >= templates.value.length) return;
170
- const next = [...templates.value];
171
- const [entry] = next.splice(from, 1);
172
- if (!entry) return;
173
- next.splice(to, 0, entry);
174
- setTemplates(next);
175
- const keys = [...rowKeys.value];
176
- const [key] = keys.splice(from, 1);
177
- if (!key) return;
178
- keys.splice(to, 0, key);
179
- rowKeys.value = keys;
180
- }
181
- function patchTemplate(index, patch) {
182
- setTemplates(templates.value.map((entry, i) => i === index ? patch(entry) : entry));
183
- }
184
- const TEMPLATE_ROW_KIND = "shwfed-table-upload-template";
185
- const rowId = (i) => rowKeys.value[i] ?? `idx-${i}`;
186
- function onTemplateRowDrop(e) {
187
- if (e.source.kind !== TEMPLATE_ROW_KIND || e.target.kind !== TEMPLATE_ROW_KIND) return;
188
- const from = rowKeys.value.findIndex((k) => k === e.source.id);
189
- const to = rowKeys.value.findIndex((k) => k === e.target.id);
190
- if (from < 0 || to < 0) return;
191
- if (e.instruction === "reorder-above") move(from, from < to ? to - 1 : to);
192
- else if (e.instruction === "reorder-below") move(from, from <= to ? to : to + 1);
193
- }
194
- const templateDnd = useTreeDnd({ onRowDrop: onTemplateRowDrop });
195
- const pickDragHandle = (el) => el.querySelector(".drag-handle");
196
- function templateRowConfig(i) {
197
- return {
198
- kind: TEMPLATE_ROW_KIND,
199
- canDrop: (src) => src.kind === TEMPLATE_ROW_KIND,
200
- blockInstructions: (src) => {
201
- const block = ["make-child", "reparent"];
202
- if (src.id === rowId(i)) block.push("reorder-above", "reorder-below");
203
- return block;
204
- },
205
- dragHandle: pickDragHandle
206
- };
207
- }
208
- function updateTemplateRequest(index, request) {
209
- patchTemplate(index, (entry) => ({ ...entry, request }));
210
- }
211
- function updateTemplateDownload(index, download) {
212
- patchTemplate(index, (entry) => {
213
- if (download.length === 0) {
214
- const { download: _omit, ...rest } = entry;
215
- return rest;
216
- }
217
- return { ...entry, download };
218
- });
219
- }
220
- function updateTemplateIcon(index, icon) {
221
- patchTemplate(index, (entry) => {
222
- const trimmed = icon.trim();
223
- if (trimmed.length === 0) {
224
- const { icon: _omit, ...rest } = entry;
225
- return rest;
226
- }
227
- return { ...entry, icon: trimmed };
228
- });
229
- }
230
- function updateTemplateLabel(index, label) {
231
- patchTemplate(index, (entry) => {
232
- if (label == null || label.length === 0) {
233
- const { label: _omit, ...rest } = entry;
234
- return rest;
235
- }
236
- return { ...entry, label };
237
- });
138
+ function updateAddLabel(label) {
139
+ if (label == null || label.length === 0) {
140
+ const { addLabel: _omit, ...rest } = value.value;
141
+ value.value = rest;
142
+ } else {
143
+ value.value = { ...value.value, addLabel: label };
144
+ }
238
145
  }
239
146
  const MIME_GROUPS = [
240
147
  { id: "pdf", label: "PDF", mimes: ["application/pdf"] },
@@ -424,25 +331,6 @@ const DownloadScope = defineComponent({
424
331
  @update:model-value="(v) => value = { ...value, tooltip: v }"
425
332
  />
426
333
  </Field>
427
- <Field orientation="vertical">
428
- <FieldLabel class="text-xs text-zinc-500">
429
- <template
430
- v-if="fieldDescription('placeholder')"
431
- #tooltip
432
- >
433
- <Markdown
434
- :source="fieldDescription('placeholder')"
435
- block
436
- class="prose prose-sm prose-zinc"
437
- />
438
- </template>
439
- {{ fieldTitle("placeholder") }}
440
- </FieldLabel>
441
- <Locale
442
- :model-value="value.placeholder"
443
- @update:model-value="(v) => value = { ...value, placeholder: v }"
444
- />
445
- </Field>
446
334
  </div>
447
335
 
448
336
  <div class="grid grid-cols-3 gap-x-6 gap-y-4">
@@ -622,6 +510,49 @@ const DownloadScope = defineComponent({
622
510
  </Field>
623
511
  </div>
624
512
 
513
+ <!-- Add button appearance -->
514
+ <div class="grid grid-cols-2 gap-x-6 gap-y-4">
515
+ <Field orientation="vertical">
516
+ <FieldLabel class="text-xs text-zinc-500">
517
+ <template
518
+ v-if="fieldDescription('addIcon')"
519
+ #tooltip
520
+ >
521
+ <Markdown
522
+ :source="fieldDescription('addIcon')"
523
+ block
524
+ class="prose prose-sm prose-zinc"
525
+ />
526
+ </template>
527
+ {{ fieldTitle("addIcon") }}
528
+ </FieldLabel>
529
+ <IconPicker
530
+ :model-value="value.addIcon ?? ''"
531
+ @update:model-value="updateAddIcon"
532
+ />
533
+ </Field>
534
+
535
+ <Field orientation="vertical">
536
+ <FieldLabel class="text-xs text-zinc-500">
537
+ <template
538
+ v-if="fieldDescription('addLabel')"
539
+ #tooltip
540
+ >
541
+ <Markdown
542
+ :source="fieldDescription('addLabel')"
543
+ block
544
+ class="prose prose-sm prose-zinc"
545
+ />
546
+ </template>
547
+ {{ fieldTitle("addLabel") }}
548
+ </FieldLabel>
549
+ <Locale
550
+ :model-value="value.addLabel"
551
+ @update:model-value="updateAddLabel"
552
+ />
553
+ </Field>
554
+ </div>
555
+
625
556
  <Separator />
626
557
 
627
558
  <!-- Immediate upload -->
@@ -772,127 +703,6 @@ const DownloadScope = defineComponent({
772
703
 
773
704
  <Separator />
774
705
 
775
- <!-- Download templates -->
776
- <Field orientation="vertical">
777
- <FieldLabel class="text-xs text-zinc-500">
778
- <template #tooltip>
779
- <Markdown
780
- :source="fieldDescription('templates') ?? '\u4E0B\u8F7D\u6A21\u677F\u5217\u8868\uFF1B\u6BCF\u4E00\u9879\u6E32\u67D3\u4E00\u4E2A\u4E0B\u8F7D\u6309\u94AE'"
781
- block
782
- class="prose prose-sm prose-zinc"
783
- />
784
- </template>
785
- {{ fieldTitle("templates") }}
786
- </FieldLabel>
787
-
788
- <div class="flex flex-col gap-2">
789
- <div
790
- v-for="(template, index) in templates"
791
- :key="rowKeys[index]"
792
- :ref="templateDnd.rowRef(rowId(index), templateRowConfig(index))"
793
- data-slot="upload-template"
794
- class="relative flex items-start gap-2 rounded border border-zinc-200 bg-zinc-50/40 p-2"
795
- :data-instruction="templateDnd.instructionFor(rowId(index)) ?? void 0"
796
- >
797
- <Icon
798
- icon="fluent:re-order-dots-vertical-20-regular"
799
- class="drag-handle mt-2.5 size-4 shrink-0 cursor-grab text-zinc-400"
800
- data-slot="upload-template-drag-handle"
801
- />
802
-
803
- <div class="flex min-w-0 flex-1 flex-col gap-2">
804
- <div class="grid grid-cols-2 gap-2">
805
- <Field orientation="vertical">
806
- <FieldLabel class="text-xs text-zinc-500">
807
- <template #tooltip>
808
- <Markdown
809
- source="返回 `HttpRequest` 的 CEL 表达式。未配置「下载请求」时直接发起并下载其响应"
810
- block
811
- class="prose prose-sm prose-zinc"
812
- />
813
- </template>
814
- 请求
815
- </FieldLabel>
816
- <ExpressionEditor
817
- :model-value="template.request"
818
- placeholder="例:http.get('https://api.example.com/template.xlsx')"
819
- result-type="HttpRequest"
820
- :extra-vars="ROW_VARS"
821
- class="min-h-10"
822
- @update:model-value="(v) => updateTemplateRequest(index, v)"
823
- />
824
- </Field>
825
-
826
- <Field orientation="vertical">
827
- <FieldLabel class="text-xs text-zinc-500">
828
- <template #tooltip>
829
- <Markdown
830
- source="可选的第二步:用第一步响应 `json` 中的凭据构造真正的下载请求,例如 `json.data.key`"
831
- block
832
- class="prose prose-sm prose-zinc"
833
- />
834
- </template>
835
- 下载请求
836
- </FieldLabel>
837
- <ExpressionEditor
838
- :model-value="template.download ?? ''"
839
- placeholder="例:http.get('https://api.example.com/download').query('key', json.data.key)"
840
- result-type="HttpRequest"
841
- :extra-vars="TEMPLATE_DOWNLOAD_VARS"
842
- class="min-h-10"
843
- @update:model-value="(v) => updateTemplateDownload(index, v)"
844
- />
845
- </Field>
846
- </div>
847
-
848
- <div class="grid grid-cols-2 gap-2">
849
- <Field orientation="vertical">
850
- <FieldLabel class="text-xs text-zinc-500">
851
- 模板按钮图标
852
- </FieldLabel>
853
- <IconPicker
854
- :model-value="template.icon ?? ''"
855
- @update:model-value="(v) => updateTemplateIcon(index, v)"
856
- />
857
- </Field>
858
-
859
- <Field orientation="vertical">
860
- <FieldLabel class="text-xs text-zinc-500">
861
- 模板按钮文本
862
- </FieldLabel>
863
- <Locale
864
- :model-value="template.label"
865
- @update:model-value="(v) => updateTemplateLabel(index, v)"
866
- />
867
- </Field>
868
- </div>
869
- </div>
870
-
871
- <Button
872
- variant="ghost"
873
- size="sm"
874
- class="absolute top-1 right-1 size-6 p-0 text-red-600 hover:text-red-700"
875
- aria-label="移除模板"
876
- @click="removeTemplate(index)"
877
- >
878
- <Icon icon="fluent:delete-20-regular" />
879
- </Button>
880
- </div>
881
-
882
- <Button
883
- type="button"
884
- data-slot="upload-template-add"
885
- class="w-full justify-center"
886
- @click="addTemplate"
887
- >
888
- <Icon icon="fluent:add-20-regular" />
889
- <span>增加</span>
890
- </Button>
891
- </div>
892
- </Field>
893
-
894
- <Separator />
895
-
896
706
  <div class="grid grid-cols-2 gap-x-6 gap-y-4">
897
707
  <Field orientation="vertical">
898
708
  <FieldLabel class="text-xs text-zinc-500">
@@ -9,11 +9,11 @@
9
9
 
10
10
  三个表达式各有各的变量:`request` 见 `files`(本次选择)、`handle` 见 `json`(上传响应)、`filename` 见 `file`(单个存量项)——都在行作用域(`row`/`index`)之上,不要混用。
11
11
 
12
- `templates` 在上传区旁渲染「下载模板」按钮:只配 `request` 则直接下载其响应;再配 `download` 则是两步式——第二步经 `json` 引用第一步响应(如用 `json.data.key` 拼真正的下载地址)。
13
-
14
12
  `download`(文件下载)给每个**文件项**渲染一个下载按钮,点击时执行一条「触发操作」列表(复用 actions 的操作子系统),列表里的表达式可用 `file` 引用被点击的那一项。分两种情况:
15
13
 
16
14
  - **已上传/回填的对象项**:走操作列表,如放一个 `http.download` 操作、请求写 `http.get('/api/file').query('key', file.cacheKey)`;或两步式——先一个 `发送请求` 行取凭据、再一个 `下载文件` 行经 `steps[?"<步骤标识>"]` 引用其响应。凡下载要用到的字段,都必须已由 `upload.handle` 落进文件项。
17
15
  - **尚未上传的本地文件**(延迟模式刚选中的原始 `File`):内存里没有服务端标识,故绕过列表、直接从 blob 下载。
18
16
 
19
17
  下载按钮在只读表格/表单里仍显示(查看已提交记录并下载附件是主要场景)。约束类字段(`accept` / `maxFileSize` / `maxTotalSize` / `maxFiles` / `multiple`)按需设置即可。`disabled` / `readonly` 是行作用域 CEL(返回 `bool`)。
18
+
19
+ 单元格只有一个「添加文件」按钮(无拖放区,单元格高度只有一行):`addLabel` / `addIcon` 可改其文案与图标。数量达上限即隐藏该按钮——不开 `multiple` 时上限就是 1,要换文件得先删除已有的那个。
@@ -48,24 +48,6 @@ const EXTENSIONS = {
48
48
  "application/msword": [".doc"],
49
49
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document": [".docx"]
50
50
  };
51
- const SHORT_NAMES = {
52
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "XLSX",
53
- "application/vnd.ms-excel": "XLS",
54
- "application/vnd.openxmlformats-officedocument.presentationml.presentation": "PPTX",
55
- "application/vnd.ms-powerpoint": "PPT",
56
- "application/zip": "ZIP",
57
- "application/x-zip-compressed": "ZIP",
58
- "application/vnd.rar": "RAR",
59
- "application/x-rar-compressed": "RAR",
60
- "application/pdf": "PDF",
61
- "application/ofd": "OFD",
62
- "application/xml": "XML",
63
- "image/png": "PNG",
64
- "image/jpg": "JPG",
65
- "image/jpeg": "JPEG",
66
- "application/msword": "DOC",
67
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "DOCX"
68
- };
69
51
  defineOptions({ name: "ShwfedTableUploadRendererRuntime" });
70
52
  const props = defineProps({
71
53
  column: { type: null, required: true },
@@ -76,7 +58,6 @@ const { locale, t } = useI18n({
76
58
  messages: {
77
59
  zh: {
78
60
  "upload-add-file": "\u6DFB\u52A0\u6587\u4EF6",
79
- "upload-accept-hint": "\u4EC5\u63A5\u53D7 {types}",
80
61
  "upload-rejected-mime": "\u300C{name}\u300D\u7C7B\u578B\u4E0D\u88AB\u5141\u8BB8",
81
62
  "upload-rejected-size": "\u300C{name}\u300D\u8D85\u51FA\u5355\u6587\u4EF6\u5927\u5C0F\u9650\u5236",
82
63
  "upload-rejected-total": "\u300C{name}\u300D\u52A0\u5165\u540E\u5C06\u8D85\u51FA\u603B\u5927\u5C0F\u9650\u5236",
@@ -86,7 +67,6 @@ const { locale, t } = useI18n({
86
67
  },
87
68
  en: {
88
69
  "upload-add-file": "Add file",
89
- "upload-accept-hint": "Only {types} are accepted",
90
70
  "upload-rejected-mime": "{name} is not an allowed file type",
91
71
  "upload-rejected-size": "{name} exceeds the maximum file size",
92
72
  "upload-rejected-total": "{name} would exceed the total size limit",
@@ -96,7 +76,6 @@ const { locale, t } = useI18n({
96
76
  },
97
77
  ja: {
98
78
  "upload-add-file": "\u30D5\u30A1\u30A4\u30EB\u3092\u8FFD\u52A0",
99
- "upload-accept-hint": "{types} \u306E\u307F\u53D7\u3051\u4ED8\u3051\u307E\u3059",
100
79
  "upload-rejected-mime": "\u300C{name}\u300D\u306F\u8A31\u53EF\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F\u3067\u306F\u3042\u308A\u307E\u305B\u3093",
101
80
  "upload-rejected-size": "\u300C{name}\u300D\u306F\u30D5\u30A1\u30A4\u30EB\u30B5\u30A4\u30BA\u306E\u4E0A\u9650\u3092\u8D85\u3048\u3066\u3044\u307E\u3059",
102
81
  "upload-rejected-total": "\u300C{name}\u300D\u3092\u8FFD\u52A0\u3059\u308B\u3068\u5408\u8A08\u30B5\u30A4\u30BA\u306E\u4E0A\u9650\u3092\u8D85\u3048\u307E\u3059",
@@ -110,9 +89,6 @@ const formState = useFormState();
110
89
  const inherited = injectCELContext();
111
90
  const $cel = (expression, context) => _rawCel(expression, celScope(inherited, context));
112
91
  const eventChannel = useEventChannel();
113
- const placeholderText = computed(
114
- () => props.column.placeholder ? getLocalizedText(props.column.placeholder, locale.value) : void 0
115
- );
116
92
  function evalBool(expression, label) {
117
93
  if (!expression) return false;
118
94
  try {
@@ -160,19 +136,24 @@ function entryIcon(entry) {
160
136
  if (entry instanceof File) return ICONS[entry.type] ?? "fluent:document-20-regular";
161
137
  return "fluent:document-20-regular";
162
138
  }
139
+ const addIcon = computed(() => props.column.addIcon ?? "fluent:add-20-regular");
140
+ const addLabel = computed(
141
+ () => (props.column.addLabel ? getLocalizedText(props.column.addLabel, locale.value) : void 0) ?? t("upload-add-file")
142
+ );
163
143
  const inputRef = ref();
164
144
  const acceptAttr = computed(() => {
165
145
  const list = props.column.accept;
166
146
  return list && list.length > 0 ? list.join(",") : void 0;
167
147
  });
168
- const acceptHint = computed(() => {
169
- const list = props.column.accept;
170
- if (!list || list.length === 0) return void 0;
171
- return Array.from(new Set(list.map((m) => SHORT_NAMES[m] ?? m))).join("\u3001");
148
+ const effectiveMaxFiles = computed(() => {
149
+ const configured = props.column.maxFiles;
150
+ if (props.column.multiple !== true) return Math.min(1, configured ?? 1);
151
+ return configured;
152
+ });
153
+ const isFull = computed(() => {
154
+ const cap = effectiveMaxFiles.value;
155
+ return cap !== void 0 && entries.value.length >= cap;
172
156
  });
173
- const isFull = computed(
174
- () => props.column.maxFiles !== void 0 && entries.value.length >= props.column.maxFiles
175
- );
176
157
  const canAdd = computed(() => !isDisabled.value && !isReadonly.value && !isFull.value && !uploading.value);
177
158
  function isMimeAllowed(file) {
178
159
  const list = props.column.accept;
@@ -197,7 +178,7 @@ function partition(incoming) {
197
178
  rejected.push({ file, reason: "size" });
198
179
  continue;
199
180
  }
200
- if (props.column.maxFiles !== void 0 && count >= props.column.maxFiles) {
181
+ if (effectiveMaxFiles.value !== void 0 && count >= effectiveMaxFiles.value) {
201
182
  rejected.push({ file, reason: "count" });
202
183
  continue;
203
184
  }
@@ -264,13 +245,6 @@ function onAddClick() {
264
245
  if (!canAdd.value) return;
265
246
  inputRef.value?.click();
266
247
  }
267
- function onKeydown(event) {
268
- if (!canAdd.value) return;
269
- if (event.key === "Enter" || event.key === " ") {
270
- event.preventDefault();
271
- inputRef.value?.click();
272
- }
273
- }
274
248
  function onDelete(index) {
275
249
  if (isDisabled.value || isReadonly.value || uploading.value) return;
276
250
  const next = [...entries.value];
@@ -287,34 +261,6 @@ function saveFile(file) {
287
261
  a.remove();
288
262
  URL.revokeObjectURL(url);
289
263
  }
290
- const templates = computed(() => props.column.templates ?? []);
291
- function templateIcon(template) {
292
- return template.icon ?? "fluent:arrow-download-20-regular";
293
- }
294
- function templateLabelText(template) {
295
- return (template.label ? getLocalizedText(template.label, locale.value) : void 0) ?? "\u4E0B\u8F7D\u6A21\u677F";
296
- }
297
- async function onDownloadTemplate(template) {
298
- const program = Effect.gen(function* () {
299
- const requestBuilder = yield* $cel(template.request, {});
300
- if (!template.download) {
301
- return yield* requestBuilder.file();
302
- }
303
- const json = yield* requestBuilder.json();
304
- const downloadBuilder = yield* $cel(template.download, { json });
305
- return yield* downloadBuilder.file();
306
- });
307
- try {
308
- const file = await Effect.runPromise(Effect.provide(program, Fetch.layer));
309
- if (!(file instanceof File)) {
310
- console.error(`[shwfed-table] template request for ${props.column.id} did not produce a File`);
311
- return;
312
- }
313
- saveFile(file);
314
- } catch (err) {
315
- console.error(`[shwfed-table] failed to download template for ${props.column.id}:`, err);
316
- }
317
- }
318
264
  const downloadEnabled = computed(() => props.column.download != null);
319
265
  const downloadIcon = computed(() => props.column.download?.icon ?? "fluent:arrow-download-20-regular");
320
266
  const downloadLabel = computed(
@@ -418,53 +364,26 @@ async function onDownloadEntry(entry) {
418
364
  </ul>
419
365
 
420
366
  <!--
421
- Empty editable state: a compact dashed add zone. Kept short (min-h-16)
422
- so an empty row does not tower over its neighbours; the non-empty list
423
- grows only as far as the files it holds.
367
+ Add button the only affordance for picking files, empty cell or not.
368
+ A cell is one grid row tall, so the dashed drop zone this replaces made
369
+ empty rows tower over their neighbours. `isFull` hides it (nothing left
370
+ to add); `isDisabled` keeps it rendered but inert, per the schema.
424
371
  -->
425
- <div
426
- v-else
427
- role="button"
428
- :tabindex="canAdd ? 0 : -1"
429
- data-slot="upload-zone"
430
- :data-disabled="isDisabled || void 0"
431
- :aria-disabled="isDisabled || void 0"
432
- class="flex min-h-16 w-full flex-col items-center justify-center gap-1 rounded border border-dashed border-zinc-200 bg-zinc-50/60 px-2 py-2 outline-none transition-colors duration-180 ease-out focus-visible:border-(--primary) data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-60"
433
- :class="canAdd && 'cursor-pointer hover:border-zinc-400 hover:bg-zinc-100'"
434
- @click="onAddClick"
435
- @keydown="onKeydown"
436
- >
437
- <Icon
438
- :icon="uploading ? 'fluent:spinner-ios-20-regular' : 'fluent:cloud-arrow-up-20-regular'"
439
- class="size-5 text-zinc-400"
440
- :class="uploading && 'animate-spin'"
441
- />
442
- <p class="text-[0.7rem] text-zinc-500">
443
- {{ uploading ? t("upload-uploading") : placeholderText ?? t("upload-add-file") }}
444
- </p>
445
- <p
446
- v-if="acceptHint"
447
- class="text-[0.65rem] text-zinc-400"
448
- >
449
- {{ t("upload-accept-hint", { types: acceptHint }) }}
450
- </p>
451
- </div>
452
-
453
- <!-- Add button — only when the list is non-empty and adding is allowed. -->
454
372
  <Button
455
- v-if="entries.length > 0 && canAdd"
373
+ v-if="!isReadonly && !isFull"
456
374
  type="button"
457
375
  variant="default"
458
376
  size="sm"
459
377
  data-slot="upload-zone-add"
460
378
  class="h-6 w-full shrink-0 text-[0.75rem]"
379
+ :disabled="isDisabled || uploading"
461
380
  @click="onAddClick"
462
381
  >
463
382
  <Icon
464
- :icon="uploading ? 'fluent:spinner-ios-20-regular' : 'fluent:add-20-regular'"
383
+ :icon="uploading ? 'fluent:spinner-ios-20-regular' : addIcon"
465
384
  :class="uploading && 'animate-spin'"
466
385
  />
467
- {{ uploading ? t("upload-uploading") : t("upload-add-file") }}
386
+ {{ uploading ? t("upload-uploading") : addLabel }}
468
387
  </Button>
469
388
 
470
389
  <input
@@ -476,28 +395,6 @@ async function onDownloadEntry(entry) {
476
395
  :disabled="isDisabled || isReadonly"
477
396
  @change="onPickerChange"
478
397
  >
479
-
480
- <div
481
- v-if="templates.length > 0 && !isReadonly"
482
- class="flex flex-wrap justify-end gap-1"
483
- >
484
- <Button
485
- v-for="(template, index) in templates"
486
- :key="index"
487
- type="button"
488
- variant="link"
489
- size="sm"
490
- data-slot="upload-zone-template"
491
- class="h-5 px-1 text-[0.7rem]"
492
- @click="onDownloadTemplate(template)"
493
- >
494
- <Icon
495
- :icon="templateIcon(template)"
496
- class="size-3.5"
497
- />
498
- {{ templateLabelText(template) }}
499
- </Button>
500
- </div>
501
398
  </div>
502
399
  </div>
503
400
  </template>
@@ -9,11 +9,6 @@ export declare const metadata: {
9
9
  readonly icon: "fluent:cloud-arrow-up-20-regular";
10
10
  readonly description: "可编辑的文件上传单元格,值为文件描述对象列表,写回行数据";
11
11
  };
12
- export declare const JSON_VAR: {
13
- readonly type: "dyn";
14
- readonly label: "HTTP 响应体";
15
- readonly description: "第一步 `请求` 的响应体(已解析 JSON);用于构造第二步下载请求";
16
- };
17
12
  export declare const FILES_VAR: {
18
13
  readonly type: "list<dyn>";
19
14
  readonly label: "已选文件";
@@ -30,7 +25,11 @@ export declare const FILE_VAR: {
30
25
  readonly description: "行数据中的单个文件项(服务端回填或上传响应映射所得);返回其展示文件名";
31
26
  };
32
27
  export declare function schema(configure: (env: Environment) => void): Schema.Struct<{
33
- placeholder: Schema.optional<Schema.suspend<readonly [{
28
+ disabled: Schema.optional<Schema.Schema<string, string, never>>;
29
+ readonly: Schema.optional<Schema.Schema<string, string, never>>;
30
+ multiple: Schema.optional<Schema.SchemaClass<boolean, boolean, never>>;
31
+ addIcon: Schema.optional<Schema.refine<string, typeof Schema.String>>;
32
+ addLabel: Schema.optional<Schema.suspend<readonly [{
34
33
  readonly locale: "zh";
35
34
  readonly message: string;
36
35
  }, ...{
@@ -43,9 +42,6 @@ export declare function schema(configure: (env: Environment) => void): Schema.St
43
42
  readonly locale: "en" | "ja" | "ko";
44
43
  readonly message: string;
45
44
  }[]], never>>;
46
- disabled: Schema.optional<Schema.Schema<string, string, never>>;
47
- readonly: Schema.optional<Schema.Schema<string, string, never>>;
48
- multiple: Schema.optional<Schema.SchemaClass<boolean, boolean, never>>;
49
45
  accept: Schema.optional<Schema.Array$<typeof Schema.String>>;
50
46
  maxFileSize: Schema.optional<Schema.refine<number, typeof Schema.Number>>;
51
47
  maxTotalSize: Schema.optional<Schema.refine<number, typeof Schema.Number>>;
@@ -55,24 +51,6 @@ export declare function schema(configure: (env: Environment) => void): Schema.St
55
51
  handle: Schema.Schema<string, string, never>;
56
52
  filename: Schema.optional<Schema.Schema<string, string, never>>;
57
53
  }>>;
58
- templates: Schema.optional<Schema.Array$<Schema.Struct<{
59
- request: Schema.Schema<string, string, never>;
60
- download: Schema.optional<Schema.Schema<string, string, never>>;
61
- icon: Schema.optional<Schema.refine<string, typeof Schema.String>>;
62
- label: Schema.optional<Schema.suspend<readonly [{
63
- readonly locale: "zh";
64
- readonly message: string;
65
- }, ...{
66
- readonly locale: "en" | "ja" | "ko";
67
- readonly message: string;
68
- }[]], string | readonly [{
69
- readonly locale: "zh";
70
- readonly message: string;
71
- }, ...{
72
- readonly locale: "en" | "ja" | "ko";
73
- readonly message: string;
74
- }[]], never>>;
75
- }>>>;
76
54
  download: Schema.optional<Schema.Struct<{
77
55
  operations: Schema.Array$<Schema.Struct<{
78
56
  id: Schema.optional<Schema.refine<string, typeof Schema.String>>;