@arronqzy/vue-view 0.1.0 → 0.1.2

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 (48) hide show
  1. package/package.json +9 -6
  2. package/src/panel/VueViewOnlinePreview.vue +15 -7
  3. package/src/panel/VueViewPanel.vue +81 -55
  4. package/src/panel/components/ConfigHintIcon.vue +4 -2
  5. package/src/panel/components/ElementsLayer.vue +15 -0
  6. package/src/panel/components/MaterialPreview.vue +19 -1
  7. package/src/panel/components/MaterialSidebar.vue +35 -31
  8. package/src/panel/components/MaterialSidebarTreeNode.vue +27 -26
  9. package/src/panel/components/MoveableLayer.vue +7 -4
  10. package/src/panel/components/PanelConfigSidebar.vue +57 -39
  11. package/src/panel/components/ViewElementScopePanel.vue +6 -4
  12. package/src/panel/components/WorkspaceConfigSidebar.vue +7 -1
  13. package/src/panel/components/WorkspaceProjectNav.vue +23 -16
  14. package/src/panel/components/WorkspaceStageSplit.vue +10 -7
  15. package/src/panel/components/config/ConfigColorField.vue +3 -1
  16. package/src/panel/components/config/PanelConfigAudioSection.vue +44 -43
  17. package/src/panel/components/config/PanelConfigChartSection.vue +79 -76
  18. package/src/panel/components/config/PanelConfigGeometrySection.vue +21 -19
  19. package/src/panel/components/config/PanelConfigGridChildSpan.vue +7 -5
  20. package/src/panel/components/config/PanelConfigGridSection.vue +10 -8
  21. package/src/panel/components/config/PanelConfigImageSection.vue +22 -21
  22. package/src/panel/components/config/PanelConfigMultiSelect.vue +42 -40
  23. package/src/panel/components/config/PanelConfigNodeInfo.vue +19 -16
  24. package/src/panel/components/config/PanelConfigReferenceSection.vue +11 -9
  25. package/src/panel/components/config/PanelConfigStyleSections.vue +27 -26
  26. package/src/panel/components/config/PanelConfigTextSection.vue +20 -17
  27. package/src/panel/components/config/PanelConfigVideoSection.vue +15 -14
  28. package/src/panel/components/config/shared.ts +0 -3
  29. package/src/panel/components/elementsLayerNodes.ts +37 -18
  30. package/src/panel/components/materialSidebarData.ts +73 -54
  31. package/src/panel/components/scope-config/ScopeTemplatePreviewHost.vue +7 -2
  32. package/src/panel/components/scope-config/ScopeTemplatePreviewPanel.vue +6 -3
  33. package/src/panel/components/scope-config/ScopeTemplateUsageHint.vue +7 -16
  34. package/src/panel/components/scope-config/ScopeTemplateWarningsPanel.vue +4 -2
  35. package/src/panel/components/table/ConditionEditor.vue +181 -0
  36. package/src/panel/components/table/JsonCodeEditor.vue +148 -0
  37. package/src/panel/components/table/PanelConfigTableSection.vue +2033 -0
  38. package/src/panel/components/table/TableNodeContent.vue +557 -0
  39. package/src/panel/constants/messages.ts +76 -34
  40. package/src/panel/hooks/usePanelElements.ts +54 -41
  41. package/src/panel/hooks/useWorkspaceProjects.ts +8 -5
  42. package/src/panel/types.ts +4 -0
  43. package/src/panel/utils/panelElementDefaults.ts +73 -39
  44. package/src/panel/utils/scope-field-labels.ts +53 -39
  45. package/src/panel/utils/scope-template-spread.ts +57 -24
  46. package/src/panel/utils/scope-template-warnings.ts +23 -22
  47. package/src/panel/utils/scope-template.ts +80 -0
  48. package/src/tailwind.css +18 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arronqzy/vue-view",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Vue 3 visual view editor for Abuilder (Ant Design Vue)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -12,6 +12,7 @@
12
12
  ".": "./src/index.ts"
13
13
  },
14
14
  "dependencies": {
15
+ "@tanstack/vue-virtual": "^3.13.2",
15
16
  "ant-design-vue": "^4.2.6",
16
17
  "dayjs": "^1.11.13",
17
18
  "echarts": "^6.0.0",
@@ -23,9 +24,11 @@
23
24
  "uuid": "^13.0.0",
24
25
  "vue": "^3.5.13",
25
26
  "@arronqzy/blueprint-dsl": "1.0.3",
26
- "@arronqzy/vue-rx-store": "0.1.0",
27
- "@arronqzy/vue-blueprint": "0.1.0",
28
- "@arronqzy/rx-store": "1.0.2"
27
+ "@arronqzy/i18n": "0.1.1",
28
+ "@arronqzy/rx-store": "1.0.2",
29
+ "@arronqzy/view-table": "0.1.1",
30
+ "@arronqzy/vue-blueprint": "0.1.1",
31
+ "@arronqzy/vue-rx-store": "0.1.0"
29
32
  },
30
33
  "peerDependencies": {
31
34
  "vue": "^3.4.0"
@@ -35,8 +38,8 @@
35
38
  "eslint": "^8.57.0",
36
39
  "typescript": "^5.5.4",
37
40
  "vue-tsc": "^2.2.0",
38
- "@arronqzy/eslint-config": "0.0.0",
39
- "@arronqzy/typescript-config": "1.0.0"
41
+ "@arronqzy/typescript-config": "1.0.0",
42
+ "@arronqzy/eslint-config": "0.0.0"
40
43
  },
41
44
  "publishConfig": {
42
45
  "access": "public"
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "@arronqzy/i18n/vue";
2
3
  import { computed, onMounted, onUnmounted, ref, watch } from "vue";
3
4
  import {
4
5
  BlueprintGraph,
@@ -31,6 +32,8 @@ import {
31
32
  resolvePreviewLayerElements,
32
33
  } from "./utils/panelStateIO";
33
34
 
35
+ const { t, locale } = useI18n();
36
+
34
37
  const PREVIEW_BOOT_PHASES: PageLifecyclePhase[] = ["mounted"];
35
38
 
36
39
  const props = defineProps<{
@@ -76,13 +79,13 @@ function applyTitleIcon(titleIconDataUrl?: string) {
76
79
  async function loadProject() {
77
80
  const record = await loadWorkspaceRecord(props.projectId);
78
81
  if (!record) {
79
- loadError.value = "工作区不存在或已被删除";
82
+ loadError.value = t("panel.messages.workspaceNotFound");
80
83
  panelState.value = null;
81
84
  return;
82
85
  }
83
86
  const normalized = normalizeImportedPanelState(record.panelState);
84
87
  if (!normalized) {
85
- loadError.value = "工作区数据格式无效";
88
+ loadError.value = t("panel.messages.workspaceDataInvalid");
86
89
  panelState.value = null;
87
90
  return;
88
91
  }
@@ -90,7 +93,7 @@ async function loadProject() {
90
93
  clearViewElementScopes();
91
94
  panelState.value = normalized;
92
95
  blueprintGraph.value = BlueprintGraph.fromDocument(record.blueprintDocument);
93
- document.title = record.productName.trim() || record.name || "预览";
96
+ document.title = record.productName.trim() || record.name || t("panel.workspace.previewDocTitle");
94
97
  applyTitleIcon(record.titleIconDataUrl);
95
98
  projectRevision.value += 1;
96
99
  }
@@ -192,7 +195,7 @@ const lifecycleReady = computed(
192
195
  () => Boolean(panelState.value && layerElements.value.length > 0 && layoutReady.value)
193
196
  );
194
197
 
195
- useBlueprintPageLifecycle({
198
+ const { triggerBlueprintNode } = useBlueprintPageLifecycle({
196
199
  graph: blueprintGraph,
197
200
  active: ref(true),
198
201
  enabled: lifecycleReady,
@@ -230,15 +233,15 @@ function noopSelect() {}
230
233
  v-else-if="!panelState"
231
234
  class="flex min-h-screen w-full items-center justify-center bg-white text-sm text-gray-600"
232
235
  >
233
- 加载预览中…
236
+ {{ t("panel.workspace.previewLoading") }}
234
237
  </div>
235
238
 
236
239
  <div
237
240
  v-else-if="layerElements.length === 0"
238
241
  class="flex min-h-screen w-full flex-col items-center justify-center gap-2 bg-white px-6 text-center text-sm text-gray-600"
239
242
  >
240
- <div>当前工作区没有可预览的视图节点</div>
241
- <div class="text-xs text-gray-400">请先在编辑器中点击「同步」或「创建工作区」后再预览</div>
243
+ <div>{{ t("panel.workspace.previewNoNodes") }}</div>
244
+ <div class="text-xs text-gray-400">{{ t("panel.workspace.previewHint") }}</div>
242
245
  </div>
243
246
 
244
247
  <div
@@ -268,6 +271,11 @@ function noopSelect() {}
268
271
  :layer-locked="true"
269
272
  :preview-mode="true"
270
273
  :preview-layout-key="layoutRevision"
274
+ :on-table-cell-action="
275
+ (payload) => {
276
+ void triggerBlueprintNode(payload.blueprintNodeId, payload);
277
+ }
278
+ "
271
279
  @select-ids="noopSelect"
272
280
  />
273
281
  </div>
@@ -63,8 +63,13 @@ import {
63
63
  useViewElementScope,
64
64
  } from "./scope/view-scope-store";
65
65
  import { useRafThrottledScroll } from "./hooks/useRafThrottledScroll";
66
+ import { useI18n } from "@arronqzy/i18n/vue";
67
+ import { getPanelMessages } from "./constants/messages";
66
68
  import "../tailwind.css";
67
69
 
70
+ const { t, locale, setLocale } = useI18n();
71
+ const panelMessages = () => getPanelMessages(t);
72
+
68
73
  const props = withDefaults(
69
74
  defineProps<{
70
75
  class?: string;
@@ -107,7 +112,7 @@ const {
107
112
  const selectedIds = ref<string[]>([]);
108
113
  const zoom = ref<ViewportZoom>({ x: props.initialZoom, y: props.initialZoom });
109
114
  const blueprintGraph = ref(BlueprintGraph.empty());
110
- const blueprintMeta = ref<BlueprintMetaDraft>({ name: "未命名蓝图", remark: "" });
115
+ const blueprintMeta = ref<BlueprintMetaDraft>({ name: t("panel.defaults.unnamedBlueprint"), remark: "" });
111
116
  const blueprintOpen = ref(false);
112
117
  const selectedBlueprintNodeId = ref<string | null>(null);
113
118
  const blueprintLibraryItems = ref<BlueprintLibraryListItem[]>([]);
@@ -118,7 +123,7 @@ const workspaceBlueprintRef = shallowRef<{
118
123
  meta: BlueprintMetaDraft;
119
124
  } | null>(null);
120
125
  const configFocus = ref<WorkspaceConfigFocus>("view");
121
- const productName = ref("未命名产物");
126
+ const productName = ref(t("panel.defaults.unnamedProduct"));
122
127
  const titleIconDataUrl = ref("");
123
128
  const editingLayerId = ref<string | null>(null);
124
129
  const editingLayerName = ref("");
@@ -305,7 +310,7 @@ function handleViewScopeUpdate(viewElementIds: string[], scope: unknown) {
305
310
  const blueprintDebugSession = useBlueprintDebugSession({
306
311
  graph: blueprintGraph,
307
312
  blueprintId: activeBlueprintLibraryId,
308
- blueprintName: computed(() => blueprintMeta.value.name || "未命名蓝图"),
313
+ blueprintName: computed(() => { void locale.value; return blueprintMeta.value.name || t("panel.defaults.unnamedBlueprint"); }),
309
314
  resolveLibraryBlueprint,
310
315
  libraryNameById: blueprintLibraryNameById,
311
316
  onExecutionBlocked: handleBlueprintExecutionBlocked,
@@ -361,7 +366,7 @@ function handleWorkspaceProjectApplied(record: WorkspaceProjectRecord) {
361
366
  workspaceBlueprintRef.value = {
362
367
  document: record.blueprintDocument,
363
368
  meta: {
364
- name: record.blueprintMeta?.name ?? "未命名蓝图",
369
+ name: record.blueprintMeta?.name ?? t("panel.defaults.unnamedBlueprint"),
365
370
  remark: record.blueprintMeta?.remark ?? "",
366
371
  },
367
372
  };
@@ -395,7 +400,7 @@ const workspaceProjects = useWorkspaceProjects({
395
400
  onProjectApplied: handleWorkspaceProjectApplied,
396
401
  });
397
402
 
398
- useBlueprintPageLifecycle({
403
+ const { triggerBlueprintNode } = useBlueprintPageLifecycle({
399
404
  graph: blueprintGraph,
400
405
  active: blueprintOpen,
401
406
  bootKey: computed(() => workspaceProjects.activeProjectId.value ?? undefined),
@@ -407,10 +412,16 @@ useBlueprintPageLifecycle({
407
412
  onViewScopeUpdate: handleViewScopeUpdate,
408
413
  });
409
414
 
415
+ const blueprintNodeOptions = computed(() =>
416
+ blueprintGraph.value.document.nodes.map((node) => ({
417
+ id: node.id,
418
+ label: `${node.label || node.id}${node.nodeType ? ` (${node.nodeType})` : ""}`,
419
+ }))
420
+ );
410
421
  async function loadBlueprintFromLibrary(id: string) {
411
422
  const record = await getBlueprintLibraryRecord(id);
412
423
  if (!record) {
413
- message.error("蓝图不存在或已被删除");
424
+ message.error(panelMessages().blueprintNotFound);
414
425
  void refreshBlueprintLibrary();
415
426
  return;
416
427
  }
@@ -461,7 +472,7 @@ async function syncBlueprintToLibrary() {
461
472
  remark: blueprintMeta.value.remark,
462
473
  });
463
474
  blueprintSyncedDocument.value = blueprintGraph.value.document;
464
- message.success("蓝图已同步到库");
475
+ message.success(panelMessages().blueprintSyncedToLibrary);
465
476
  await refreshBlueprintLibrary();
466
477
  }
467
478
 
@@ -478,7 +489,7 @@ async function saveBlueprintToLibrary(meta: BlueprintMetaDraft) {
478
489
  blueprintSyncedDocument.value = blueprintGraph.value.document;
479
490
  blueprintMeta.value = meta;
480
491
  await refreshBlueprintLibrary();
481
- message.success("蓝图已保存到库");
492
+ message.success(panelMessages().blueprintSavedToLibrary);
482
493
  }
483
494
 
484
495
  function handleExport() {
@@ -499,7 +510,7 @@ async function handleImportFile(file: File) {
499
510
  const text = await file.text();
500
511
  const parsed = JSON.parse(text) as State;
501
512
  const ok = importPanelData(parsed);
502
- if (!ok) window.alert("导入失败:文件格式不正确");
513
+ if (!ok) window.alert(panelMessages().importInvalidFormat);
503
514
  else selectedIds.value = [];
504
515
  }
505
516
 
@@ -507,7 +518,7 @@ function openBlueprintExport() {
507
518
  downloadBlueprintExport(
508
519
  buildBlueprintExportPayload(blueprintGraph.value.document, blueprintMeta.value)
509
520
  );
510
- message.success("蓝图已导出");
521
+ message.success(panelMessages().blueprintExported);
511
522
  }
512
523
 
513
524
  async function handleBlueprintImportFile(file: File) {
@@ -518,12 +529,12 @@ async function handleBlueprintImportFile(file: File) {
518
529
  await refreshBlueprintLibrary();
519
530
  if (!activeBlueprintLibraryId.value) snapshotWorkspaceBlueprint();
520
531
  await loadBlueprintFromLibrary(record.id);
521
- message.success("蓝图已导入并加载");
532
+ message.success(panelMessages().blueprintImported);
522
533
  }
523
534
 
524
535
  async function handleWorkspaceCreateProject() {
525
536
  const result = await workspaceProjects.handleCreateProject();
526
- if (result?.name) message.success(`已创建工作区「${result.name}」`);
537
+ if (result?.name) message.success(panelMessages().workspaceCreated(result.name));
527
538
  }
528
539
 
529
540
  async function handleWorkspaceOpenProject(id: string) {
@@ -531,24 +542,24 @@ async function handleWorkspaceOpenProject(id: string) {
531
542
  clearViewElementScopes();
532
543
  await workspaceProjects.handleOpenProject(id);
533
544
  selectedIds.value = [];
534
- message.success("工作区已加载");
545
+ message.success(panelMessages().workspaceLoaded);
535
546
  } catch (error) {
536
- message.error(error instanceof Error ? error.message : "打开工作区失败");
547
+ message.error(error instanceof Error ? error.message : panelMessages().openWorkspaceFailed);
537
548
  }
538
549
  }
539
550
 
540
551
  async function handleWorkspaceSyncProject() {
541
552
  try {
542
553
  const name = await workspaceProjects.handleSyncProject();
543
- if (name) message.success(`已同步「${name}」`);
554
+ if (name) message.success(panelMessages().blueprintSynced(name));
544
555
  } catch (error) {
545
- message.error(error instanceof Error ? error.message : "同步失败");
556
+ message.error(error instanceof Error ? error.message : panelMessages().syncFailed);
546
557
  }
547
558
  }
548
559
 
549
560
  async function handleWorkspaceDeleteProject(id: string) {
550
561
  await workspaceProjects.handleDeleteProject(id);
551
- message.success("工作区已删除");
562
+ message.success(panelMessages().workspaceDeleted);
552
563
  }
553
564
 
554
565
  async function openOnlinePreviewForProject(
@@ -572,7 +583,7 @@ function commitRenameLayer() {
572
583
 
573
584
  function handleMergeLayers() {
574
585
  if (!canMergeLayers.value) {
575
- message.warning("至少勾选 2 个图层后可合并");
586
+ message.warning(panelMessages().mergeNeedTwoLayers);
576
587
  return;
577
588
  }
578
589
  mergeSelectedLayers(mergeLayerName.value.trim() || undefined);
@@ -610,39 +621,39 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
610
621
  </script>
611
622
 
612
623
  <template>
613
- <Layout class="vue-view-panel h-screen overflow-hidden" :class="props.class">
624
+ <Layout class="vue-view-panel h-screen overflow-hidden" :class="props.class" :key="locale">
614
625
  <Layout.Header class="flex h-auto flex-wrap items-center gap-2 bg-[#001529] px-3 py-2">
615
626
  <Dropdown :trigger="['click']">
616
- <Button type="text" class="!text-white">文件</Button>
627
+ <Button type="text" class="!text-white">{{ t("panel.menubar.file") }}</Button>
617
628
  <template #overlay>
618
629
  <Menu>
619
- <Menu.Item @click="handleExport">导出面板 JSON</Menu.Item>
620
- <Menu.Item @click="importInputRef?.click()">导入面板 JSON</Menu.Item>
630
+ <Menu.Item @click="handleExport">{{ t("panel.menubar.exportPanelJson") }}</Menu.Item>
631
+ <Menu.Item @click="importInputRef?.click()">{{ t("panel.menubar.importPanelJson") }}</Menu.Item>
621
632
  </Menu>
622
633
  </template>
623
634
  </Dropdown>
624
635
  <Dropdown :trigger="['click']">
625
- <Button type="text" class="!text-white">编辑</Button>
636
+ <Button type="text" class="!text-white">{{ t("panel.menubar.edit") }}</Button>
626
637
  <template #overlay>
627
638
  <Menu>
628
- <Menu.Item :disabled="!canUndo" @click="undo">撤销</Menu.Item>
629
- <Menu.Item :disabled="!canRedo" @click="redo">重做</Menu.Item>
639
+ <Menu.Item :disabled="!canUndo" @click="undo">{{ t("panel.menubar.undo") }}</Menu.Item>
640
+ <Menu.Item :disabled="!canRedo" @click="redo">{{ t("panel.menubar.redo") }}</Menu.Item>
630
641
  <Menu.Divider />
631
- <Menu.Item :disabled="!hasUnlockedSelection" @click="bringElementsForward(selectedIds)">上移一层</Menu.Item>
632
- <Menu.Item :disabled="!hasUnlockedSelection" @click="sendElementsBackward(selectedIds)">下移一层</Menu.Item>
633
- <Menu.Item :disabled="!hasUnlockedSelection" @click="bringElementsToFront(selectedIds)">置顶</Menu.Item>
634
- <Menu.Item :disabled="!hasUnlockedSelection" @click="sendElementsToBack(selectedIds)">置底</Menu.Item>
642
+ <Menu.Item :disabled="!hasUnlockedSelection" @click="bringElementsForward(selectedIds)">{{ t("panel.menubar.bringForward") }}</Menu.Item>
643
+ <Menu.Item :disabled="!hasUnlockedSelection" @click="sendElementsBackward(selectedIds)">{{ t("panel.menubar.sendBackward") }}</Menu.Item>
644
+ <Menu.Item :disabled="!hasUnlockedSelection" @click="bringElementsToFront(selectedIds)">{{ t("panel.menubar.bringToFront") }}</Menu.Item>
645
+ <Menu.Item :disabled="!hasUnlockedSelection" @click="sendElementsToBack(selectedIds)">{{ t("panel.menubar.sendToBack") }}</Menu.Item>
635
646
  <Menu.Divider />
636
- <Menu.Item :disabled="!hasUnlockedSelection" @click="handleDeleteSelected">删除选中</Menu.Item>
647
+ <Menu.Item :disabled="!hasUnlockedSelection" @click="handleDeleteSelected">{{ t("panel.menubar.deleteSelected") }}</Menu.Item>
637
648
  </Menu>
638
649
  </template>
639
650
  </Dropdown>
640
651
  <Dropdown :trigger="['click']">
641
- <Button type="text" class="!text-white">蓝图</Button>
652
+ <Button type="text" class="!text-white">{{ t("panel.menubar.blueprint") }}</Button>
642
653
  <template #overlay>
643
654
  <Menu>
644
- <Menu.Item @click="openBlueprintExport">导出蓝图</Menu.Item>
645
- <Menu.Item @click="blueprintImportInputRef?.click()">导入蓝图</Menu.Item>
655
+ <Menu.Item @click="openBlueprintExport">{{ t("panel.menubar.exportBlueprint") }}</Menu.Item>
656
+ <Menu.Item @click="blueprintImportInputRef?.click()">{{ t("panel.menubar.importBlueprint") }}</Menu.Item>
646
657
  </Menu>
647
658
  </template>
648
659
  </Dropdown>
@@ -657,17 +668,26 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
657
668
  @delete-project="handleWorkspaceDeleteProject"
658
669
  @preview-project="openOnlinePreviewForProject"
659
670
  />
671
+ <Dropdown :trigger="['click']">
672
+ <Button type="text" class="!text-white">{{ t("panel.theme.language") }}</Button>
673
+ <template #overlay>
674
+ <Menu>
675
+ <Menu.Item :class="{ 'ant-menu-item-selected': locale === 'zh-CN' }" @click="setLocale('zh-CN')">{{ t("panel.theme.zhCN") }}</Menu.Item>
676
+ <Menu.Item :class="{ 'ant-menu-item-selected': locale === 'en-US' }" @click="setLocale('en-US')">{{ t("panel.theme.enUS") }}</Menu.Item>
677
+ </Menu>
678
+ </template>
679
+ </Dropdown>
660
680
  <div class="flex-1" />
661
681
  <Space>
662
- <Button size="small" :disabled="!canUndo" @click="undo">撤销</Button>
663
- <Button size="small" :disabled="!canRedo" @click="redo">重做</Button>
682
+ <Button size="small" :disabled="!canUndo" @click="undo">{{ t("panel.menubar.undo") }}</Button>
683
+ <Button size="small" :disabled="!canRedo" @click="redo">{{ t("panel.menubar.redo") }}</Button>
664
684
  <Button
665
685
  size="small"
666
686
  type="primary"
667
687
  :disabled="!workspaceProjects.activeProjectId.value"
668
688
  @click="workspaceProjects.activeProjectId.value && openOnlinePreviewForProject(workspaceProjects.activeProjectId.value, { syncFirst: workspaceProjects.dirty.value })"
669
689
  >
670
- 预览
690
+ {{ t("common.preview") }}
671
691
  </Button>
672
692
  </Space>
673
693
  </Layout.Header>
@@ -689,9 +709,9 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
689
709
  v-model:value="productName"
690
710
  size="small"
691
711
  class="w-[200px]"
692
- placeholder="产物名称"
712
+ :placeholder="t('panel.menubar.productName')"
693
713
  />
694
- <span class="text-xs text-gray-500">缩放</span>
714
+ <span class="text-xs text-gray-500">{{ t("panel.menubar.zoom") }}</span>
695
715
  <Slider
696
716
  :min="0.25"
697
717
  :max="4"
@@ -701,9 +721,9 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
701
721
  @change="(v: number | [number, number]) => setUniformZoom(Array.isArray(v) ? v[0] : v)"
702
722
  />
703
723
  <Tag v-if="activeLayer">{{ activeLayer.name }}</Tag>
704
- <Tag color="blue">已选 {{ selectedIds.length }}</Tag>
724
+ <Tag color="blue">{{ t("panel.menubar.selectedCount", { count: selectedIds.length }) }}</Tag>
705
725
  <div data-blueprint-toggle class="ml-auto flex items-center gap-2">
706
- <span class="text-xs text-gray-500">蓝图</span>
726
+ <span class="text-xs text-gray-500">{{ t("panel.menubar.blueprint") }}</span>
707
727
  <Switch v-model:checked="blueprintOpen" size="small" />
708
728
  </div>
709
729
  </div>
@@ -738,6 +758,11 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
738
758
  :selected-ids="selectedIds"
739
759
  :update-element="updateElement"
740
760
  :layer-locked="Boolean(activeLayer?.locked)"
761
+ :on-table-cell-action="
762
+ (payload) => {
763
+ void triggerBlueprintNode(payload.blueprintNodeId, payload);
764
+ }
765
+ "
741
766
  @select-ids="selectIds"
742
767
  />
743
768
  <SelectLayer
@@ -765,9 +790,9 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
765
790
 
766
791
  <div class="border-t bg-white px-2 py-2">
767
792
  <div class="mb-2 flex flex-wrap items-center gap-2">
768
- <span class="text-xs font-semibold text-gray-500">图层</span>
769
- <Button size="small" @click="addLayer()">新建</Button>
770
- <Button size="small" :disabled="!canMergeLayers" @click="isMergingLayers = true">合并</Button>
793
+ <span class="text-xs font-semibold text-gray-500">{{ t("panel.layers.title") }}</span>
794
+ <Button size="small" @click="addLayer()">{{ t("panel.layers.addShort") }}</Button>
795
+ <Button size="small" :disabled="!canMergeLayers" @click="isMergingLayers = true">{{ t("panel.layers.mergeShort") }}</Button>
771
796
  </div>
772
797
  <Tabs
773
798
  size="small"
@@ -779,8 +804,8 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
779
804
  <template #tab>
780
805
  <span class="inline-flex items-center gap-1">
781
806
  {{ layer.name }}
782
- <Tag v-if="layer.isMapping" color="blue" class="!m-0">映射</Tag>
783
- <Tag v-if="layer.isPrimary" color="green" class="!m-0">主</Tag>
807
+ <Tag v-if="layer.isMapping" color="blue" class="!m-0">{{ t("panel.layers.mapping") }}</Tag>
808
+ <Tag v-if="layer.isPrimary" color="green" class="!m-0">{{ t("panel.layers.primary") }}</Tag>
784
809
  </span>
785
810
  </template>
786
811
  </Tabs.TabPane>
@@ -794,23 +819,23 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
794
819
  class="w-40"
795
820
  @press-enter="commitRenameLayer"
796
821
  />
797
- <Button size="small" type="primary" @click="commitRenameLayer">确定</Button>
822
+ <Button size="small" type="primary" @click="commitRenameLayer">{{ t("common.ok") }}</Button>
798
823
  </template>
799
824
  <template v-else>
800
825
  <span class="font-medium">{{ activeLayer.name }}</span>
801
- <Button size="small" type="link" @click="startRenameLayer(activeLayer.id, activeLayer.name)">重命名</Button>
826
+ <Button size="small" type="link" @click="startRenameLayer(activeLayer.id, activeLayer.name)">{{ t("panel.layers.renameShort") }}</Button>
802
827
  </template>
803
828
  <Button size="small" @click="toggleLayerLock(activeLayer.id)">
804
- {{ activeLayer.locked ? "解锁" : "锁定" }}
829
+ {{ activeLayer.locked ? t("panel.layers.unlockShort") : t("panel.layers.lockShort") }}
805
830
  </Button>
806
831
  <Checkbox
807
832
  :checked="Boolean(activeLayer.mergeSelected)"
808
833
  @change="() => toggleLayerMergeSelected(activeLayer!.id)"
809
834
  >
810
- 参与合并
835
+ {{ t("panel.layers.joinMerge") }}
811
836
  </Checkbox>
812
837
  <Button size="small" :disabled="activeLayer.isPrimary" @click="setPrimaryLayer(activeLayer.id)">
813
- 设为主图层
838
+ {{ t("panel.layers.setPrimary") }}
814
839
  </Button>
815
840
  <Button
816
841
  size="small"
@@ -818,7 +843,7 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
818
843
  :disabled="!activeLayer.editable"
819
844
  @click="deleteLayer(activeLayer.id)"
820
845
  >
821
- 删除图层
846
+ {{ t("panel.layers.delete") }}
822
847
  </Button>
823
848
  </div>
824
849
  </div>
@@ -840,6 +865,7 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
840
865
  :selected-blueprint-node="selectedBlueprintNode"
841
866
  :allow-false-signal-propagation="blueprintGraph.document.allowFalseSignalPropagation"
842
867
  :blueprint-library-options="blueprintLibraryOptions"
868
+ :blueprint-node-options="blueprintNodeOptions"
843
869
  :all-view-elements="allElements"
844
870
  :on-update-blueprint-node="handleUpdateBlueprintNode"
845
871
  :on-update-allow-false-signal-propagation="handleUpdateAllowFalseSignalPropagation"
@@ -852,12 +878,12 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
852
878
 
853
879
  <Modal
854
880
  v-model:open="isMergingLayers"
855
- title="合并图层"
856
- ok-text="合并"
881
+ :title="t('panel.layers.mergeDialogTitle')"
882
+ :ok-text="t('panel.layers.mergeOk')"
857
883
  @ok="handleMergeLayers"
858
884
  >
859
- <Input v-model:value="mergeLayerName" placeholder="合并后图层名称(可选)" />
860
- <div class="mt-2 text-xs text-gray-500">已勾选 {{ mergeSelectedCount }} 个图层</div>
885
+ <Input v-model:value="mergeLayerName" :placeholder="t('panel.layers.mergedNamePlaceholderOptional')" />
886
+ <div class="mt-2 text-xs text-gray-500">{{ t("panel.layers.mergeSelectedCount", { count: mergeSelectedCount }) }}</div>
861
887
  </Modal>
862
888
  </Layout>
863
889
  </template>
@@ -1,13 +1,15 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "@arronqzy/i18n/vue";
2
3
  import { Tooltip } from "ant-design-vue";
3
4
 
5
+ const { t, locale } = useI18n();
4
6
  withDefaults(
5
7
  defineProps<{
6
8
  label?: string;
7
9
  contentClass?: string;
8
10
  buttonClass?: string;
9
11
  }>(),
10
- { label: "说明" }
12
+ { label: t("common.hint") }
11
13
  );
12
14
  </script>
13
15
 
@@ -26,7 +28,7 @@ withDefaults(
26
28
  buttonClass ??
27
29
  'inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full border border-border text-[10px] leading-none text-muted-foreground hover:bg-accent/50'
28
30
  "
29
- :aria-label="`${label}说明`"
31
+ :aria-label="t('common.hintAria', { label })"
30
32
  >
31
33
  ?
32
34
  </button>
@@ -14,6 +14,8 @@ import {
14
14
  TextNodeContent,
15
15
  VideoNodeContent,
16
16
  } from "./elementsLayerNodes";
17
+ import TableNodeContent from "./table/TableNodeContent.vue";
18
+ import type { TableCellActionHandler } from "./table/TableNodeContent.vue";
17
19
 
18
20
  const props = withDefaults(
19
21
  defineProps<{
@@ -28,6 +30,7 @@ const props = withDefaults(
28
30
  layerLocked?: boolean;
29
31
  previewMode?: boolean;
30
32
  previewLayoutKey?: number;
33
+ onTableCellAction?: TableCellActionHandler;
31
34
  }>(),
32
35
  { layerLocked: false, previewMode: false }
33
36
  );
@@ -45,6 +48,10 @@ const NodeContent = defineComponent({
45
48
  layerLocked: { type: Boolean, default: false },
46
49
  previewMode: { type: Boolean, default: false },
47
50
  previewLayoutKey: { type: Number, default: undefined },
51
+ onTableCellAction: {
52
+ type: Function as PropType<TableCellActionHandler | undefined>,
53
+ default: undefined,
54
+ },
48
55
  updateElement: {
49
56
  type: Function as PropType<
50
57
  (
@@ -100,6 +107,13 @@ const NodeContent = defineComponent({
100
107
  if (el.materialType === "image") {
101
108
  return h(ImageNodeContent, { element: el });
102
109
  }
110
+ if (el.materialType === "table") {
111
+ return h(TableNodeContent, {
112
+ element: el,
113
+ interactive: p.previewMode || Boolean(p.onTableCellAction),
114
+ onCellAction: p.onTableCellAction,
115
+ });
116
+ }
103
117
  return h(EmptyNodePlaceholder, { element: el });
104
118
  };
105
119
  },
@@ -160,6 +174,7 @@ function onSelect(id: string, event: MouseEvent) {
160
174
  :preview-mode="previewMode"
161
175
  :preview-layout-key="previewLayoutKey"
162
176
  :update-element="updateElement"
177
+ :on-table-cell-action="onTableCellAction"
163
178
  />
164
179
  </div>
165
180
  </template>
@@ -1,4 +1,6 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "@arronqzy/i18n/vue";
3
+ const { t, locale } = useI18n();
2
4
  defineProps<{ id: string }>();
3
5
 
4
6
  const common =
@@ -76,6 +78,22 @@ const common =
76
78
  <div class="h-1.5 w-8/12 rounded bg-muted-foreground/45" />
77
79
  </div>
78
80
  </div>
81
+ <div v-else-if="id === 'table'" :class="common">
82
+ <div class="absolute inset-0 flex flex-col gap-1 p-2">
83
+ <div class="grid h-3 flex-none grid-cols-3 gap-1">
84
+ <div class="rounded-sm bg-primary/75" />
85
+ <div class="rounded-sm bg-primary/65" />
86
+ <div class="rounded-sm bg-primary/70" />
87
+ </div>
88
+ <div class="grid min-h-0 flex-1 grid-cols-3 gap-1">
89
+ <div
90
+ v-for="idx in 6"
91
+ :key="idx"
92
+ class="rounded-sm border border-primary/25 bg-primary/10"
93
+ />
94
+ </div>
95
+ </div>
96
+ </div>
79
97
  <div v-else-if="id === 'rect'" :class="common">
80
98
  <div class="absolute inset-2 rounded-md border-2 border-primary/80 bg-primary/15" />
81
99
  </div>
@@ -91,7 +109,7 @@ const common =
91
109
  <div v-else-if="id === 'reference'" :class="common">
92
110
  <div class="absolute inset-2 rounded-md border border-dashed border-primary/70 bg-primary/10" />
93
111
  <div class="absolute inset-0 flex items-center justify-center text-[10px] text-primary/80">
94
- 引用组件
112
+ {{ t("panel.material.reference") }}
95
113
  </div>
96
114
  </div>
97
115
  <div v-else-if="id === 'image'" :class="common">