@arronqzy/vue-view 0.1.0 → 0.1.3

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 +10 -7
  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
@@ -1,9 +1,10 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "@arronqzy/i18n/vue";
2
3
  import { computed, onMounted, ref, watch } from "vue";
3
4
  import { Empty, Input } from "ant-design-vue";
4
5
  import type { PanelElement, PanelLayer, ReferenceCopyMode } from "../types";
5
6
  import { CHART_TYPES } from "../utils/chartOptionBuilder";
6
- import { PANEL_MESSAGES } from "../constants/messages";
7
+ import { getPanelMessages } from "../constants/messages";
7
8
  import { hasViewElementScope } from "../scope/view-scope-store";
8
9
  import { collectElementScopeWarnings } from "../utils/scope-template-warnings";
9
10
  import { sectionMatchesSearch } from "./config/shared";
@@ -22,7 +23,10 @@ import PanelConfigGeometrySection from "./config/PanelConfigGeometrySection.vue"
22
23
  import PanelConfigGridSection from "./config/PanelConfigGridSection.vue";
23
24
  import PanelConfigGridChildSpan from "./config/PanelConfigGridChildSpan.vue";
24
25
  import PanelConfigReferenceSection from "./config/PanelConfigReferenceSection.vue";
26
+ import PanelConfigTableSection from "./table/PanelConfigTableSection.vue";
25
27
 
28
+ const { t, locale } = useI18n();
29
+ const msgs = () => getPanelMessages(t);
26
30
  const SEARCH_COLLAPSE_STORAGE_KEY = "panel:config-search-collapsed";
27
31
 
28
32
  const props = withDefaults(
@@ -43,9 +47,11 @@ const props = withDefaults(
43
47
  action: "bringForward" | "sendBackward" | "bringToFront" | "sendToBack"
44
48
  ) => void;
45
49
  viewElementScope?: unknown;
50
+ blueprintNodeOptions?: { id: string; label: string }[];
46
51
  }>(),
47
52
  {
48
53
  selectedElements: () => [],
54
+ blueprintNodeOptions: () => [],
49
55
  }
50
56
  );
51
57
 
@@ -67,6 +73,7 @@ const expandedSections = ref<Record<string, boolean>>({
67
73
  gridChildSpan: true,
68
74
  reference: true,
69
75
  imageConfig: true,
76
+ tableConfig: true,
70
77
  });
71
78
 
72
79
  const normalizedSearch = computed(() => configSearch.value.trim().toLowerCase());
@@ -110,7 +117,7 @@ const showScopePanel = computed(
110
117
 
111
118
  const scopeWarnings = computed(() => {
112
119
  if (!props.selectedElement || props.viewElementScope === undefined) return [];
113
- return collectElementScopeWarnings(props.selectedElement, props.viewElementScope);
120
+ return collectElementScopeWarnings(props.selectedElement, props.viewElementScope, t);
114
121
  });
115
122
 
116
123
  const materialType = computed(() => props.selectedElement?.materialType ?? "");
@@ -133,24 +140,24 @@ const visibleSectionCount = computed(() => {
133
140
  if (!props.selectedElement || isMultiSelectMode.value) return 0;
134
141
  let count = 0;
135
142
  const checks: Array<[string, string, string[]]> = [
136
- ["nodeInfo", "节点信息", ["名称", "id", "类型"]],
137
- ["styleBackground", "通用样式 / 背景", ["背景"]],
138
- ["styleBorder", "通用样式 / 边框", ["边框"]],
143
+ ["nodeInfo", t("panel.config.sectionNodeInfo"), [t("panel.config.name"), "id", t("panel.config.type")]],
144
+ ["styleBackground", t("panel.config.sectionStyleBackground"), [t("panel.config.backgroundColor"), "background"]],
145
+ ["styleBorder", t("panel.config.sectionStyleBorder"), [t("panel.config.searchKwBorder")]],
139
146
  ];
140
147
  if (isChartElement.value) {
141
- checks.push(["chartBasic", "图表配置 / 基础", ["图表"]]);
142
- checks.push(["chartAdvanced", "图表配置 / 高级", ["json", "高级"]]);
148
+ checks.push(["chartBasic", t("panel.config.sectionChartBasic"), [t("panel.material.charts")]]);
149
+ checks.push(["chartAdvanced", t("panel.config.sectionChartAdvanced"), ["json", t("panel.config.searchKwAdvanced")]]);
143
150
  }
144
- if (materialType.value === "text") checks.push(["textConfig", "文本配置", ["文本"]]);
145
- if (materialType.value === "audio") checks.push(["audioConfig", "音频配置", ["音频"]]);
146
- if (materialType.value === "video") checks.push(["videoConfig", "视频配置", ["视频"]]);
147
- if (materialType.value === "image") checks.push(["imageConfig", "图片配置", ["图片"]]);
148
- if (materialType.value === "geometry") checks.push(["geometryConfig", "几何配置", ["几何"]]);
149
- if (materialType.value === "grid") checks.push(["gridConfig", "网格布局配置", ["网格"]]);
151
+ if (materialType.value === "text") checks.push(["textConfig", t("panel.config.sectionText"), [t("panel.defaults.text")]]);
152
+ if (materialType.value === "audio") checks.push(["audioConfig", t("panel.config.sectionAudio"), [t("panel.defaults.audio")]]);
153
+ if (materialType.value === "video") checks.push(["videoConfig", t("panel.config.sectionVideo"), [t("panel.defaults.video")]]);
154
+ if (materialType.value === "image") checks.push(["imageConfig", t("panel.config.sectionImage"), [t("panel.defaults.image")]]);
155
+ if (materialType.value === "geometry") checks.push(["geometryConfig", t("panel.config.sectionGeometry"), [t("panel.defaults.geometry")]]);
156
+ if (materialType.value === "grid") checks.push(["gridConfig", t("panel.config.sectionGrid"), [t("panel.config.searchKwGrid")]]);
150
157
  if (props.selectedElement?.parentGridId) {
151
- checks.push(["gridChildSpan", "网格子节点占位", ["跨列"]]);
158
+ checks.push(["gridChildSpan", t("panel.config.sectionGridChildSpan"), [t("panel.config.searchKwCrossCol")]]);
152
159
  }
153
- if (materialType.value === "reference") checks.push(["reference", "引用组件配置", ["引用"]]);
160
+ if (materialType.value === "reference") checks.push(["reference", t("panel.config.sectionReference"), [t("panel.material.reference")]]);
154
161
  for (const [, title, terms] of checks) {
155
162
  if (shouldShowSection("", title, terms)) count += 1;
156
163
  }
@@ -188,7 +195,7 @@ watch(
188
195
  class="scope-config-sidebar h-full overflow-auto border-l border-gray-200 bg-gray-50/30 px-3 py-3 text-gray-900 [&_.scope-field--highlight]:rounded-md [&_.scope-field--highlight]:ring-2 [&_.scope-field--highlight]:ring-amber-400/80"
189
196
  >
190
197
  <div class="sticky top-0 z-20 mb-3 rounded-lg border border-gray-200/80 bg-white px-2.5 py-2 shadow-sm">
191
- <div class="text-xs font-semibold tracking-wide">配置面板</div>
198
+ <div class="text-xs font-semibold tracking-wide">{{ t("panel.config.panelTitle") }}</div>
192
199
  <ViewElementScopePanel v-if="showScopePanel" :scope="viewElementScope!" />
193
200
  <ScopeTemplateWarningsPanel />
194
201
  <div
@@ -200,18 +207,18 @@ watch(
200
207
  class="rounded border border-gray-200 px-1.5 py-0.5 text-[11px] text-gray-500 hover:bg-gray-50"
201
208
  @click="isSearchCollapsed = !isSearchCollapsed"
202
209
  >
203
- {{ isSearchCollapsed ? "展开搜索" : "收起搜索" }}
210
+ {{ isSearchCollapsed ? t("panel.config.expandSearch") : t("panel.config.collapseSearch") }}
204
211
  </button>
205
212
  </div>
206
213
  <div v-if="!isSearchCollapsed" class="mt-2">
207
214
  <Input
208
215
  v-model:value="configSearch"
209
216
  size="small"
210
- placeholder="搜索配置,如:边框、tooltip、音频、网格..."
217
+ :placeholder="t('panel.config.searchPlaceholder')"
211
218
  data-scope-autocomplete="off"
212
219
  />
213
220
  <div v-if="hasSearch" class="mt-1 text-[11px] text-gray-500">
214
- 搜索中:{{ configSearch }}
221
+ {{ t("panel.config.searching", { query: configSearch }) }}
215
222
  </div>
216
223
  </div>
217
224
  </div>
@@ -232,14 +239,14 @@ watch(
232
239
  <Empty
233
240
  v-else-if="!selectedElement"
234
241
  class="py-7"
235
- description="请先在画布中选中一个节点,再到这里进行配置。"
242
+ :description="t('panel.config.emptyNoNodeDesc')"
236
243
  />
237
244
 
238
245
  <div v-else class="space-y-3">
239
246
  <fieldset :disabled="!isNodeEditable" :class="!isNodeEditable ? 'opacity-60' : ''">
240
247
  <div class="space-y-3.5 text-xs">
241
248
  <PanelConfigNodeInfo
242
- v-if="shouldShowSection('nodeInfo', '节点信息', ['名称', 'id', '类型', '锁定'])"
249
+ v-if="shouldShowSection('nodeInfo', t('panel.config.sectionNodeInfo'), [t('panel.config.name'), 'id', t('panel.config.type'), t('panel.layers.lockShort')])"
243
250
  :element="selectedElement"
244
251
  :layers="layers"
245
252
  :is-editable="isNodeEditable"
@@ -254,13 +261,13 @@ watch(
254
261
 
255
262
  <PanelConfigStyleSections
256
263
  v-if="
257
- shouldShowSection('styleBackground', '通用样式 / 背景', ['背景', 'background']) ||
258
- shouldShowSection('styleBorder', '通用样式 / 边框', ['边框', 'border'])
264
+ shouldShowSection('styleBackground', t('panel.config.sectionStyleBackground'), [t('panel.config.backgroundColor'), 'background']) ||
265
+ shouldShowSection('styleBorder', t('panel.config.sectionStyleBorder'), [t('panel.config.searchKwBorder'), 'border'])
259
266
  "
260
267
  :element="selectedElement"
261
268
  :is-editable="isNodeEditable"
262
- :show-background="shouldShowSection('styleBackground', '通用样式 / 背景', ['背景', 'background'])"
263
- :show-border="shouldShowSection('styleBorder', '通用样式 / 边框', ['边框', 'border'])"
269
+ :show-background="shouldShowSection('styleBackground', t('panel.config.sectionStyleBackground'), [t('panel.config.backgroundColor'), 'background'])"
270
+ :show-border="shouldShowSection('styleBorder', t('panel.config.sectionStyleBorder'), [t('panel.config.searchKwBorder'), 'border'])"
264
271
  :background-open="isSectionExpanded('styleBackground')"
265
272
  :border-open="isSectionExpanded('styleBorder')"
266
273
  :force-open="forceOpenSections"
@@ -270,7 +277,7 @@ watch(
270
277
  />
271
278
 
272
279
  <PanelConfigChartSection
273
- v-if="isChartElement && shouldShowSection('chartBasic', '图表配置 / 基础', ['图表', 'tooltip'])"
280
+ v-if="isChartElement && shouldShowSection('chartBasic', t('panel.config.sectionChartBasic'), [t('panel.material.charts'), 'tooltip'])"
274
281
  :element="selectedElement"
275
282
  :is-editable="isNodeEditable"
276
283
  :basic-open="isSectionExpanded('chartBasic')"
@@ -282,7 +289,7 @@ watch(
282
289
  />
283
290
 
284
291
  <PanelConfigTextSection
285
- v-if="materialType === 'text' && shouldShowSection('textConfig', '文本配置', ['文本', '字体', '颜色'])"
292
+ v-if="materialType === 'text' && shouldShowSection('textConfig', t('panel.config.sectionText'), [t('panel.defaults.text'), t('panel.config.fontFamily'), t('panel.config.color')])"
286
293
  :element="selectedElement"
287
294
  :is-editable="isNodeEditable"
288
295
  :open="isSectionExpanded('textConfig')"
@@ -291,8 +298,19 @@ watch(
291
298
  @update:open="(v) => setSectionExpanded('textConfig', v)"
292
299
  />
293
300
 
301
+ <PanelConfigTableSection
302
+ v-if="materialType === 'table' && shouldShowSection('tableConfig', t('panel.config.sectionTable'), [t('panel.material.table'), 'table', t('panel.config.tableSource'), t('panel.config.tableColumns'), t('panel.config.tableRowsText')])"
303
+ :element="selectedElement"
304
+ :is-editable="isNodeEditable"
305
+ :open="isSectionExpanded('tableConfig')"
306
+ :force-open="forceOpenSections"
307
+ :update-element="updateElement"
308
+ :blueprint-node-options="blueprintNodeOptions"
309
+ @update:open="(v) => setSectionExpanded('tableConfig', v)"
310
+ />
311
+
294
312
  <PanelConfigAudioSection
295
- v-if="materialType === 'audio' && shouldShowSection('audioConfig', '音频配置', ['音频', 'url', '录音'])"
313
+ v-if="materialType === 'audio' && shouldShowSection('audioConfig', t('panel.config.sectionAudio'), [t('panel.defaults.audio'), 'url', t('panel.config.searchKwRecord')])"
296
314
  :element="selectedElement"
297
315
  :is-editable="isNodeEditable"
298
316
  :open="isSectionExpanded('audioConfig')"
@@ -302,7 +320,7 @@ watch(
302
320
  />
303
321
 
304
322
  <PanelConfigVideoSection
305
- v-if="materialType === 'video' && shouldShowSection('videoConfig', '视频配置', ['视频', 'url'])"
323
+ v-if="materialType === 'video' && shouldShowSection('videoConfig', t('panel.config.sectionVideo'), [t('panel.defaults.video'), 'url'])"
306
324
  :element="selectedElement"
307
325
  :is-editable="isNodeEditable"
308
326
  :open="isSectionExpanded('videoConfig')"
@@ -312,7 +330,7 @@ watch(
312
330
  />
313
331
 
314
332
  <PanelConfigImageSection
315
- v-if="materialType === 'image' && shouldShowSection('imageConfig', '图片配置', ['图片', 'src', 'fit'])"
333
+ v-if="materialType === 'image' && shouldShowSection('imageConfig', t('panel.config.sectionImage'), [t('panel.defaults.image'), 'src', 'fit'])"
316
334
  :element="selectedElement"
317
335
  :is-editable="isNodeEditable"
318
336
  :open="isSectionExpanded('imageConfig')"
@@ -322,7 +340,7 @@ watch(
322
340
  />
323
341
 
324
342
  <PanelConfigGeometrySection
325
- v-if="materialType === 'geometry' && shouldShowSection('geometryConfig', '几何配置', ['几何', '形状'])"
343
+ v-if="materialType === 'geometry' && shouldShowSection('geometryConfig', t('panel.config.sectionGeometry'), [t('panel.defaults.geometry'), t('panel.config.shape')])"
326
344
  :element="selectedElement"
327
345
  :is-editable="isNodeEditable"
328
346
  :open="isSectionExpanded('geometryConfig')"
@@ -332,7 +350,7 @@ watch(
332
350
  />
333
351
 
334
352
  <PanelConfigGridSection
335
- v-if="materialType === 'grid' && shouldShowSection('gridConfig', '网格布局配置', ['网格', '行', '列', '间距'])"
353
+ v-if="materialType === 'grid' && shouldShowSection('gridConfig', t('panel.config.sectionGrid'), [t('panel.config.searchKwGrid'), t('panel.config.rowsShort'), t('panel.config.colsShort'), t('panel.config.searchKwGap')])"
336
354
  :element="selectedElement"
337
355
  :is-editable="isNodeEditable"
338
356
  :open="isSectionExpanded('gridConfig')"
@@ -342,7 +360,7 @@ watch(
342
360
  />
343
361
 
344
362
  <PanelConfigGridChildSpan
345
- v-if="selectedElement.parentGridId && shouldShowSection('gridChildSpan', '网格子节点占位', ['跨列', '跨行'])"
363
+ v-if="selectedElement.parentGridId && shouldShowSection('gridChildSpan', t('panel.config.sectionGridChildSpan'), [t('panel.config.searchKwCrossCol'), t('panel.config.searchKwCrossRow')])"
346
364
  :element="selectedElement"
347
365
  :is-editable="isNodeEditable"
348
366
  :open="isSectionExpanded('gridChildSpan')"
@@ -352,7 +370,7 @@ watch(
352
370
  />
353
371
 
354
372
  <PanelConfigReferenceSection
355
- v-if="materialType === 'reference' && shouldShowSection('reference', '引用组件配置', ['引用', '浅拷贝', '深拷贝'])"
373
+ v-if="materialType === 'reference' && shouldShowSection('reference', t('panel.config.sectionReference'), [t('panel.material.reference'), t('panel.material.shallowCopy'), t('panel.material.deepCopy')])"
356
374
  :element="selectedElement"
357
375
  :layers="layers"
358
376
  :is-editable="isNodeEditable"
@@ -364,17 +382,17 @@ watch(
364
382
  />
365
383
 
366
384
  <div
367
- v-if="!isChartElement && materialType && !['text', 'audio', 'video', 'image', 'geometry', 'grid', 'reference'].includes(materialType)"
385
+ v-if="!isChartElement && materialType && !['text', 'table', 'audio', 'video', 'image', 'geometry', 'grid', 'reference'].includes(materialType)"
368
386
  class="text-xs leading-6 text-gray-500"
369
387
  >
370
- 当前节点不是图表类型,暂无图表配置项。
388
+ {{ t("panel.config.notChartType") }}
371
389
  </div>
372
390
 
373
391
  <div
374
392
  v-if="hasSearch && visibleSectionCount === 0"
375
393
  class="rounded border border-gray-200/60 bg-white px-2 py-1.5 text-[11px] text-gray-500"
376
394
  >
377
- 未找到匹配项,请尝试更换关键词。
395
+ {{ t("panel.config.noMatch") }}
378
396
  </div>
379
397
  </div>
380
398
  </fieldset>
@@ -383,13 +401,13 @@ watch(
383
401
  v-if="selectedElement.locked"
384
402
  class="rounded border border-amber-500/40 bg-amber-500/10 px-2 py-1.5 text-[11px] text-amber-700"
385
403
  >
386
- {{ PANEL_MESSAGES.nodeConfigLocked }}
404
+ {{ msgs().nodeConfigLocked }}
387
405
  </div>
388
406
  <div
389
407
  v-else-if="selectedLayer?.locked"
390
408
  class="rounded border border-amber-500/40 bg-amber-500/10 px-2 py-1.5 text-[11px] text-amber-700"
391
409
  >
392
- {{ PANEL_MESSAGES.nodeConfigLayerLocked }}
410
+ {{ msgs().nodeConfigLayerLocked }}
393
411
  </div>
394
412
  </div>
395
413
  </aside>
@@ -1,9 +1,11 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "@arronqzy/i18n/vue";
2
3
  import { formatViewElementScope } from "../utils/scope-template";
3
4
  import ConfigHintIcon from "./ConfigHintIcon.vue";
4
5
  import ScopeTemplateUsageHint from "./scope-config/ScopeTemplateUsageHint.vue";
5
6
  import { onMounted, ref, watch } from "vue";
6
7
 
8
+ const { t, locale } = useI18n();
7
9
  const SCOPE_COLLAPSE_STORAGE_KEY = "panel:config-scope-collapsed";
8
10
 
9
11
  const props = defineProps<{
@@ -49,15 +51,15 @@ watch(
49
51
  <div class="mt-2 border-t border-border/50 pt-2">
50
52
  <div class="flex items-center justify-between gap-2">
51
53
  <div class="flex min-w-0 items-center gap-1.5">
52
- <div class="text-[11px] font-medium text-foreground">Scope 数据</div>
53
- <ConfigHintIcon label="Scope 模版" content-class="max-w-[380px]">
54
+ <div class="text-[11px] font-medium text-foreground">{{ t("panel.scope.dataTitle") }}</div>
55
+ <ConfigHintIcon :label="t('panel.scope.templateHintLabel')" content-class="max-w-[380px]">
54
56
  <ScopeTemplateUsageHint />
55
57
  </ConfigHintIcon>
56
58
  <span
57
59
  v-if="scopeUpdated"
58
60
  class="shrink-0 rounded bg-sky-500/15 px-1.5 py-0.5 text-[10px] font-medium text-sky-700 dark:text-sky-300"
59
61
  >
60
- 已更新
62
+ {{ t("panel.scope.updated") }}
61
63
  </span>
62
64
  </div>
63
65
  <button
@@ -65,7 +67,7 @@ watch(
65
67
  class="rounded border border-border px-1.5 py-0.5 text-[11px] text-muted-foreground hover:bg-accent"
66
68
  @click="isCollapsed = !isCollapsed"
67
69
  >
68
- {{ isCollapsed ? "展开 Scope" : "收起 Scope" }}
70
+ {{ isCollapsed ? t("panel.scope.expand") : t("panel.scope.collapse") }}
69
71
  </button>
70
72
  </div>
71
73
  <pre
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "@arronqzy/i18n/vue";
2
3
  import { computed } from "vue";
3
4
  import { Empty } from "ant-design-vue";
4
5
  import {
@@ -11,6 +12,8 @@ import {
11
12
  import type { PanelElement, PanelLayer, ReferenceCopyMode } from "../types";
12
13
  import PanelConfigSidebar from "./PanelConfigSidebar.vue";
13
14
 
15
+ const { t, locale } = useI18n();
16
+
14
17
  export type WorkspaceConfigFocus = "view" | "blueprint" | "blueprint-log";
15
18
 
16
19
  export type PanelConfigSidebarProps = {
@@ -30,6 +33,7 @@ export type PanelConfigSidebarProps = {
30
33
  action: "bringForward" | "sendBackward" | "bringToFront" | "sendToBack"
31
34
  ) => void;
32
35
  viewElementScope?: unknown;
36
+ blueprintNodeOptions?: { id: string; label: string }[];
33
37
  };
34
38
 
35
39
  export type BlueprintExecutionLogViewProps = {
@@ -86,6 +90,7 @@ const props = withDefaults(
86
90
  {
87
91
  allowFalseSignalPropagation: false,
88
92
  blueprintLibraryOptions: () => [],
93
+ blueprintNodeOptions: () => [],
89
94
  selectedElements: () => [],
90
95
  }
91
96
  );
@@ -107,6 +112,7 @@ const viewElementOptions = computed(() =>
107
112
  :layers="layers"
108
113
  :update-element="updateElement"
109
114
  :view-element-scope="viewElementScope"
115
+ :blueprint-node-options="blueprintNodeOptions"
110
116
  :set-reference-copy-mode="setReferenceCopyMode"
111
117
  :node-z-order-label="nodeZOrderLabel"
112
118
  :on-exclude-selected-node="onExcludeSelectedNode"
@@ -131,7 +137,7 @@ const viewElementOptions = computed(() =>
131
137
  v-else-if="!selectedBlueprintNode"
132
138
  class="flex h-full flex-col overflow-hidden bg-background text-foreground"
133
139
  >
134
- <Empty class="py-10" description="在蓝图面板中选中一个节点后,这里会显示对应的配置。" />
140
+ <Empty class="py-10" :description="t('panel.workspace.configEmptyDesc')" />
135
141
  </div>
136
142
 
137
143
  <BlueprintNodeConfigSidebar
@@ -1,8 +1,11 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "@arronqzy/i18n/vue";
2
3
  import { Button, Dropdown, Menu, Modal, Tooltip } from "ant-design-vue";
3
4
  import { computed, h, ref } from "vue";
4
5
  import type { WorkspaceProjectListItem } from "../library/workspace-project-db";
5
6
 
7
+ const { t, locale } = useI18n();
8
+
6
9
  function formatUpdatedAt(ts: number): string {
7
10
  try {
8
11
  return new Date(ts).toLocaleString();
@@ -26,6 +29,14 @@ const emit = defineEmits<{
26
29
  previewProject: [id: string, options?: { syncFirst?: boolean }];
27
30
  }>();
28
31
 
32
+ const syncTooltip = computed(() => {
33
+ void locale.value;
34
+ if (!props.dirty) return t("panel.workspace.syncUpToDate");
35
+ return t("panel.workspace.syncUpdate", {
36
+ name: props.activeProjectName ?? t("panel.workspace.currentWorkspaceFallback"),
37
+ });
38
+ });
39
+
29
40
  const pendingDeleteId = ref<string | null>(null);
30
41
  const busy = ref(false);
31
42
 
@@ -64,7 +75,7 @@ const projectMenu = computed(() =>
64
75
  {
65
76
  default: () =>
66
77
  props.projects.length === 0
67
- ? [h(Menu.Item, { key: "empty", disabled: true }, () => "暂无已保存工作区")]
78
+ ? [h(Menu.Item, { key: "empty", disabled: true }, () => t("panel.workspace.noSavedWorkspaces"))]
68
79
  : props.projects.map((project) =>
69
80
  h(
70
81
  "div",
@@ -105,7 +116,7 @@ const projectMenu = computed(() =>
105
116
  })
106
117
  ),
107
118
  },
108
- () => "预览"
119
+ () => t("panel.workspace.previewDocTitle")
109
120
  ),
110
121
  h(
111
122
  Button,
@@ -118,7 +129,7 @@ const projectMenu = computed(() =>
118
129
  pendingDeleteId.value = project.id;
119
130
  },
120
131
  },
121
- () => "删除"
132
+ () => t("common.delete")
122
133
  ),
123
134
  ])
124
135
  )
@@ -131,7 +142,7 @@ const projectMenu = computed(() =>
131
142
  <template>
132
143
  <div class="flex items-center gap-1.5 border-l border-border pl-2">
133
144
  <Tooltip
134
- title="以当前产物名称新建一条 IndexedDB 工作区记录,不会覆盖已有工作区"
145
+ :title="t('panel.workspace.createHint')"
135
146
  :overlay-style="{ zIndex: 10100 }"
136
147
  :mouse-enter-delay="0.15"
137
148
  >
@@ -141,17 +152,13 @@ const projectMenu = computed(() =>
141
152
  :disabled="busy"
142
153
  @click="runAction(() => emit('createProject'))"
143
154
  >
144
- 创建工作区
155
+ {{ t("panel.workspace.create") }}
145
156
  </Button>
146
157
  </Tooltip>
147
158
 
148
159
  <Tooltip
149
160
  v-if="activeProjectId"
150
- :title="
151
- dirty
152
- ? `同步更新「${activeProjectName ?? '当前工作区'}」到 IndexedDB`
153
- : '当前工作区已与 IndexedDB 同步'
154
- "
161
+ :title="syncTooltip"
155
162
  :overlay-style="{ zIndex: 10100 }"
156
163
  :mouse-enter-delay="0.15"
157
164
  >
@@ -162,13 +169,13 @@ const projectMenu = computed(() =>
162
169
  :disabled="busy || !dirty"
163
170
  @click="runAction(() => emit('syncProject'))"
164
171
  >
165
- 同步{{ dirty ? " *" : "" }}
172
+ {{ dirty ? t("panel.workspace.syncDirty") : t("common.sync") }}
166
173
  </Button>
167
174
  </Tooltip>
168
175
 
169
176
  <Dropdown :trigger="['click']" :disabled="busy" :overlay-style="{ zIndex: 10100 }">
170
177
  <Button size="small" class="h-7 max-w-[200px] truncate px-2 text-xs">
171
- {{ activeProjectName ? `工作区:${activeProjectName}` : "已保存工作区" }}
178
+ {{ activeProjectName ? t("panel.workspace.workspaceNamed", { name: activeProjectName }) : t("panel.workspace.savedWorkspaces") }}
172
179
  </Button>
173
180
  <template #overlay>
174
181
  <component :is="projectMenu" />
@@ -178,15 +185,15 @@ const projectMenu = computed(() =>
178
185
 
179
186
  <Modal
180
187
  v-model:open="deleteModalOpen"
181
- title="删除工作区?"
182
- ok-text="删除"
183
- cancel-text="取消"
188
+ :title="t('panel.workspace.deleteConfirmTitle')"
189
+ :ok-text="t('common.delete')"
190
+ :cancel-text="t('common.cancel')"
184
191
  ok-type="danger"
185
192
  :z-index="10150"
186
193
  @ok="confirmDelete"
187
194
  >
188
195
  <p class="text-sm text-muted-foreground">
189
- 将永久删除 IndexedDB 中的「{{ pendingDeleteProject?.name ?? "" }}」,此操作不可恢复。
196
+ {{ t("panel.workspace.deleteConfirmBody", { name: pendingDeleteProject?.name ?? "" }) }}
190
197
  </p>
191
198
  </Modal>
192
199
  </template>
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "@arronqzy/i18n/vue";
2
3
  import {
3
4
  computed,
4
5
  onMounted,
@@ -13,6 +14,8 @@ import {
13
14
  type BlueprintExecutionOverlay,
14
15
  } from "@arronqzy/vue-blueprint";
15
16
 
17
+ const { t, locale } = useI18n();
18
+
16
19
  export type BlueprintLibraryListItem = {
17
20
  id: string;
18
21
  name: string;
@@ -200,7 +203,7 @@ watch(blueprintWrapRef, (el) => {
200
203
  class="min-w-[140px]"
201
204
  :value="activeBlueprintLibraryId ?? undefined"
202
205
  :options="libraryOptions"
203
- placeholder="蓝图库"
206
+ :placeholder="t('panel.workspace.blueprintLibrary')"
204
207
  @change="(id) => id && onSelectBlueprintLibraryItem?.(String(id))"
205
208
  />
206
209
  <span
@@ -209,17 +212,17 @@ watch(blueprintWrapRef, (el) => {
209
212
  >
210
213
  {{ currentBlueprintLabel }}
211
214
  </span>
212
- <Tooltip title="保存蓝图到库">
213
- <Button size="small" @click="onSaveBlueprint?.()">保存</Button>
215
+ <Tooltip :title="t('panel.workspace.saveBlueprintToLibrary')">
216
+ <Button size="small" @click="onSaveBlueprint?.()">{{ t("common.save") }}</Button>
214
217
  </Tooltip>
215
- <Tooltip :title="canSyncBlueprint ? '同步蓝图' : '无可同步变更'">
218
+ <Tooltip :title="canSyncBlueprint ? t('panel.workspace.syncBlueprint') : t('panel.workspace.noSyncChanges')">
216
219
  <Button
217
220
  size="small"
218
221
  type="primary"
219
222
  :disabled="!canSyncBlueprint"
220
223
  @click="onSyncBlueprint?.()"
221
224
  >
222
- 同步
225
+ {{ t("common.sync") }}
223
226
  </Button>
224
227
  </Tooltip>
225
228
  <Select
@@ -228,7 +231,7 @@ watch(blueprintWrapRef, (el) => {
228
231
  class="min-w-[120px]"
229
232
  :value="blueprintDebug.lifecyclePhase"
230
233
  :options="blueprintDebug.lifecycleOptions"
231
- placeholder="生命周期"
234
+ :placeholder="t('panel.workspace.lifecycle')"
232
235
  @change="(v) => v && blueprintDebug?.onLifecyclePhaseChange?.(String(v))"
233
236
  />
234
237
  </Space>
@@ -250,7 +253,7 @@ watch(blueprintWrapRef, (el) => {
250
253
  v-else
251
254
  class="flex h-full items-center justify-center text-[11px] text-muted-foreground"
252
255
  >
253
- {{ blueprintOpen ? "画布加载中…" : "" }}
256
+ {{ blueprintOpen ? t("panel.workspace.canvasLoading") : "" }}
254
257
  </div>
255
258
  </div>
256
259
  </div>
@@ -1,7 +1,9 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "@arronqzy/i18n/vue";
2
3
  import { computed } from "vue";
3
4
  import { Input } from "ant-design-vue";
4
5
 
6
+ const { t, locale } = useI18n();
5
7
  const props = defineProps<{
6
8
  label: string;
7
9
  value: string;
@@ -44,7 +46,7 @@ function onColorInput(e: Event) {
44
46
  class="h-7 w-10 shrink-0 cursor-pointer rounded border border-gray-200 p-0.5"
45
47
  :value="pickerValue"
46
48
  :disabled="disabled"
47
- :aria-label="`${label}调色盘`"
49
+ :aria-label="t('common.colorPickerAria', { label })"
48
50
  @input="onColorInput"
49
51
  />
50
52
  </div>