@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
@@ -1,8 +1,9 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "@arronqzy/i18n/vue";
2
3
  import { computed, ref } from "vue";
3
4
  import { Card, Empty, Input, Switch, Tabs } from "ant-design-vue";
4
5
  import type { PanelElement, PanelLayer, ReferenceCopyMode } from "../types";
5
- import { PANEL_MESSAGES } from "../constants/messages";
6
+ import { getPanelMessages } from "../constants/messages";
6
7
  import {
7
8
  concreteGridParentIdForLayer,
8
9
  logicalGridParentIdFromConcrete,
@@ -10,13 +11,16 @@ import {
10
11
  import MaterialPreview from "./MaterialPreview.vue";
11
12
  import MaterialSidebarTreeNode from "./MaterialSidebarTreeNode.vue";
12
13
  import {
13
- defaultCategories,
14
+ getDefaultCategories,
14
15
  getNodeDisplayName,
15
16
  themedScrollbarClass,
16
17
  type MaterialCategoryId,
17
18
  type MaterialItem,
18
19
  } from "./materialSidebarData";
19
20
 
21
+ const { t, locale } = useI18n();
22
+ const msgs = () => getPanelMessages(t);
23
+
20
24
  export type MaterialSidebarProps = {
21
25
  class?: string;
22
26
  onDragMaterialStart?: (material: MaterialItem) => void;
@@ -50,20 +54,20 @@ const draggingTreeNodeId = ref<string | null>(null);
50
54
  const dragOverLayerId = ref<string | null>(null);
51
55
  const expandedKeys = ref<Record<string, boolean>>({ root: true });
52
56
 
53
- const categories = defaultCategories;
57
+ const categories = computed(() => { void locale.value; return getDefaultCategories(t); });
54
58
  const normalizedKeyword = computed(() => keyword.value.trim().toLowerCase());
55
59
  const isSearching = computed(() => normalizedKeyword.value.length > 0);
56
60
  const normalizedTreeKeyword = computed(() => treeKeyword.value.trim().toLowerCase());
57
61
  const isTreeSearching = computed(() => normalizedTreeKeyword.value.length > 0);
58
62
 
59
63
  const activeCategory = computed(
60
- () => categories.find((c) => c.id === activeCategoryId.value) ?? categories[0]
64
+ () => categories.value.find((c) => c.id === activeCategoryId.value) ?? categories.value[0]
61
65
  );
62
66
 
63
67
  const matchedItems = computed(() => {
64
68
  if (!isSearching.value) return [];
65
69
  const result: Array<MaterialItem & { categoryTitle: string }> = [];
66
- categories.forEach((category) => {
70
+ categories.value.forEach((category) => {
67
71
  category.items.forEach((item) => {
68
72
  const haystack = `${item.title} ${item.id} ${category.title}`.toLowerCase();
69
73
  if (haystack.includes(normalizedKeyword.value)) {
@@ -168,7 +172,7 @@ function hasRefInSubtree(node: PanelElement, visited: Set<string>): boolean {
168
172
 
169
173
  function nodeMatchesTreeSearch(node: PanelElement, visited: Set<string>): boolean {
170
174
  if (!isTreeSearching.value) return true;
171
- const selfText = `${getNodeDisplayName(node)} ${node.materialType ?? ""} ${node.id}`.toLowerCase();
175
+ const selfText = `${getNodeDisplayName(node, t)} ${node.materialType ?? ""} ${node.id}`.toLowerCase();
172
176
  if (selfText.includes(normalizedTreeKeyword.value)) return true;
173
177
  if (visited.has(node.id)) return false;
174
178
  const nextVisited = new Set(visited);
@@ -206,13 +210,13 @@ function getLayerDropState(layer: PanelLayer) {
206
210
  const dropBlockReason = !draggingNode
207
211
  ? ""
208
212
  : draggingNode.locked
209
- ? PANEL_MESSAGES.nodeMoveLocked
213
+ ? msgs().nodeMoveLocked
210
214
  : draggingSourceLayer?.locked
211
- ? PANEL_MESSAGES.nodeMoveSourceLayerLocked
215
+ ? msgs().nodeMoveSourceLayerLocked
212
216
  : layer.locked
213
- ? PANEL_MESSAGES.nodeMoveTargetLayerLocked
217
+ ? msgs().nodeMoveTargetLayerLocked
214
218
  : draggingNode.layerId === layer.id
215
- ? PANEL_MESSAGES.nodeMoveSameLayer
219
+ ? msgs().nodeMoveSameLayer
216
220
  : "";
217
221
  const canDropIntoLayer = Boolean(draggingNode) && !dropBlockReason;
218
222
  return { draggingNode, dropBlockReason, canDropIntoLayer };
@@ -278,13 +282,13 @@ const treeSearchEmpty = computed(
278
282
  <Input
279
283
  v-model:value="keyword"
280
284
  size="small"
281
- placeholder="搜索物料(名称 / 分类)"
285
+ :placeholder="t('panel.material.searchPlaceholder')"
282
286
  class="text-xs"
283
287
  />
284
288
  </div>
285
289
 
286
290
  <Tabs v-model:active-key="leftTab" class="flex min-h-0 h-full flex-col">
287
- <Tabs.TabPane key="materials" tab="物料" class="min-h-0 flex-1">
291
+ <Tabs.TabPane key="materials" :tab="t('panel.material.tabMaterials')" class="min-h-0 flex-1">
288
292
  <div class="grid h-full min-h-0 grid-cols-[110px_1fr]">
289
293
  <div :class="`overflow-auto border-r border-border ${themedScrollbarClass}`">
290
294
  <button
@@ -305,7 +309,7 @@ const treeSearchEmpty = computed(
305
309
 
306
310
  <div :class="`overflow-auto ${themedScrollbarClass}`">
307
311
  <div class="px-2.5 py-2.5 text-xs font-semibold">
308
- {{ isSearching ? `搜索结果(${matchedItems.length})` : activeCategory.title }}
312
+ {{ isSearching ? t("panel.material.searchResults", { count: matchedItems.length }) : activeCategory.title }}
309
313
  </div>
310
314
  <div class="grid gap-2 px-2.5 pb-3">
311
315
  <button
@@ -332,12 +336,12 @@ const treeSearchEmpty = computed(
332
336
  <Empty
333
337
  v-if="isSearching && matchedItems.length === 0"
334
338
  class="py-5"
335
- description="未匹配到物料"
339
+ :description="t('panel.material.emptyMaterialsTitle')"
336
340
  >
337
341
  <template #description>
338
- <span class="text-xs">未匹配到物料</span>
342
+ <span class="text-xs">{{ t("panel.material.emptyMaterialsTitle") }}</span>
339
343
  <div class="text-[11px] text-muted-foreground">
340
- 尝试更换关键词,或切换分类后再搜索。
344
+ {{ t("panel.material.emptyMaterialsDesc") }}
341
345
  </div>
342
346
  </template>
343
347
  </Empty>
@@ -346,19 +350,19 @@ const treeSearchEmpty = computed(
346
350
  </div>
347
351
  </Tabs.TabPane>
348
352
 
349
- <Tabs.TabPane key="tree" tab="节点树" class="min-h-0 flex-1">
353
+ <Tabs.TabPane key="tree" :tab="t('panel.material.tabTree')" class="min-h-0 flex-1">
350
354
  <div :class="`h-full overflow-auto px-2 py-2 text-xs ${themedScrollbarClass}`">
351
355
  <div class="mb-2">
352
356
  <Input
353
357
  v-model:value="treeKeyword"
354
358
  size="small"
355
- placeholder="搜索节点(名称 / 类型 / ID)"
359
+ :placeholder="t('panel.material.treeSearchPlaceholder')"
356
360
  class="text-xs"
357
361
  />
358
362
  </div>
359
363
  <div class="mb-2 flex items-center justify-between rounded border border-border bg-card px-2 py-1.5">
360
- <span class="text-[11px] text-muted-foreground">仅看引用子树</span>
361
- <Switch v-model:checked="referenceOnlyTree" size="small" aria-label="仅看引用子树" />
364
+ <span class="text-[11px] text-muted-foreground">{{ t("panel.material.referenceOnlyTree") }}</span>
365
+ <Switch v-model:checked="referenceOnlyTree" size="small" :aria-label="t('panel.material.referenceOnlyTree')" />
362
366
  </div>
363
367
 
364
368
  <div class="rounded border border-border bg-card">
@@ -402,7 +406,7 @@ const treeSearchEmpty = computed(
402
406
  ]"
403
407
  :title="
404
408
  getLayerDropState(layer).canDropIntoLayer
405
- ? '目标图层'
409
+ ? t('panel.material.targetLayer')
406
410
  : getLayerDropState(layer).dropBlockReason
407
411
  "
408
412
  >
@@ -429,7 +433,7 @@ const treeSearchEmpty = computed(
429
433
  v-if="layer.isMapping"
430
434
  class="shrink-0 rounded-md border-2 border-violet-600 bg-violet-500/22 px-1.5 py-0.5 text-[10px] font-semibold text-violet-950 dark:border-violet-400 dark:bg-violet-500/35 dark:text-violet-50"
431
435
  >
432
- 映射图层
436
+ {{ t("panel.material.mappingLayer") }}
433
437
  </span>
434
438
  <span
435
439
  v-if="dragOverLayerId === layer.id"
@@ -442,7 +446,7 @@ const treeSearchEmpty = computed(
442
446
  >
443
447
  {{
444
448
  getLayerDropState(layer).canDropIntoLayer
445
- ? "将移动到该图层"
449
+ ? t("panel.material.willMoveToLayer")
446
450
  : getLayerDropState(layer).dropBlockReason
447
451
  }}
448
452
  </span>
@@ -457,7 +461,7 @@ const treeSearchEmpty = computed(
457
461
  : 'border-destructive/50 bg-destructive/10 text-destructive'
458
462
  : 'border-border/50 bg-muted/20 text-muted-foreground hover:border-border hover:bg-muted/35',
459
463
  ]"
460
- title="拖拽节点到此图层"
464
+ :title="t('panel.material.dragToThisLayer')"
461
465
  @dragover="onLayerDragOver($event, layer)"
462
466
  @dragleave="dragOverLayerId === layer.id && (dragOverLayerId = null)"
463
467
  @drop="onLayerDrop($event, layer)"
@@ -465,9 +469,9 @@ const treeSearchEmpty = computed(
465
469
  {{
466
470
  draggingTreeNodeId
467
471
  ? getLayerDropState(layer).canDropIntoLayer
468
- ? "释放以移动到该图层"
469
- : getLayerDropState(layer).dropBlockReason || "不可移动到该图层"
470
- : "拖拽节点到该图层"
472
+ ? t("panel.material.releaseToMove")
473
+ : getLayerDropState(layer).dropBlockReason || t("panel.material.cannotMoveToLayer")
474
+ : t("panel.material.dragToLayer")
471
475
  }}
472
476
  </div>
473
477
 
@@ -476,7 +480,7 @@ const treeSearchEmpty = computed(
476
480
  v-if="getRootNodes(layer.id).length === 0"
477
481
  class="rounded border border-border/40 bg-muted/15 py-2 pl-3 text-[11px] text-muted-foreground"
478
482
  >
479
- 空图层
483
+ {{ t("panel.material.emptyLayer") }}
480
484
  </div>
481
485
  <MaterialSidebarTreeNode
482
486
  v-for="node in getRootNodes(layer.id)"
@@ -509,11 +513,11 @@ const treeSearchEmpty = computed(
509
513
  </div>
510
514
  </template>
511
515
 
512
- <Empty v-if="treeSearchEmpty" class="mx-2 my-2 py-5" description="未匹配到节点">
516
+ <Empty v-if="treeSearchEmpty" class="mx-2 my-2 py-5" :description="t('panel.material.emptyNodesTitle')">
513
517
  <template #description>
514
- <span class="text-xs">未匹配到节点</span>
518
+ <span class="text-xs">{{ t("panel.material.emptyNodesTitle") }}</span>
515
519
  <div class="text-[11px] text-muted-foreground">
516
- 试试节点名称、类型或 ID 关键词。
520
+ {{ t("panel.material.emptyNodesDesc") }}
517
521
  </div>
518
522
  </template>
519
523
  </Empty>
@@ -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 { Card, Tooltip } from "ant-design-vue";
4
5
  import type { PanelElement, PanelLayer, ReferenceCopyMode } from "../types";
@@ -7,6 +8,8 @@ import {
7
8
  getNodeDisplayName,
8
9
  } from "./materialSidebarData";
9
10
 
11
+ const { t, locale } = useI18n();
12
+
10
13
  const props = defineProps<{
11
14
  node: PanelElement;
12
15
  level: number;
@@ -148,7 +151,7 @@ function onDragStart(e: DragEvent) {
148
151
  @dragstart="onDragStart"
149
152
  @dragend="emit('dragEnd')"
150
153
  >
151
- {{ getNodeDisplayName(node) }}
154
+ {{ getNodeDisplayName(node, t) }}
152
155
  </button>
153
156
 
154
157
  <span
@@ -156,30 +159,28 @@ function onDragStart(e: DragEvent) {
156
159
  class="inline-flex shrink-0 items-center rounded-md border-2 border-violet-600 bg-violet-500/20 px-1.5 py-0.5 text-[10px] font-semibold text-violet-950 shadow-sm dark:border-violet-400 dark:bg-violet-500/35 dark:text-violet-50"
157
160
  :title="
158
161
  nodeHomeLayer.mappingBaseLayerId
159
- ? `映射图层「${nodeHomeLayer.name}」· 基准图层:${
160
- layerById.get(nodeHomeLayer.mappingBaseLayerId)?.name ??
161
- nodeHomeLayer.mappingBaseLayerId
162
- }`
163
- : `映射图层「${nodeHomeLayer.name}」`
162
+ ? t('panel.material.mappingLayerWithBase', {
163
+ name: nodeHomeLayer.name,
164
+ base:
165
+ layerById.get(nodeHomeLayer.mappingBaseLayerId)?.name ??
166
+ nodeHomeLayer.mappingBaseLayerId,
167
+ })
168
+ : t('panel.material.mappingLayerTitle', { name: nodeHomeLayer.name })
164
169
  "
165
170
  >
166
- 映射图层节点
171
+ {{ t("panel.material.mappingLayerNode") }}
167
172
  </span>
168
173
  <span
169
174
  v-if="node.mappingSourceNodeId"
170
175
  class="inline-flex shrink-0 items-center rounded border border-primary/40 bg-primary/10 px-1 text-[10px] text-primary"
171
- :title="`同源节点 ${node.mappingSourceNodeId}${
172
- node.mappingSourceLayerId
173
- ? ` · ${layerById.get(node.mappingSourceLayerId)?.name ?? node.mappingSourceLayerId}`
174
- : ''
175
- }`"
176
+ :title="t('panel.material.sameSourceTitle', { id: node.mappingSourceNodeId })"
176
177
  >
177
- 同源
178
+ {{ t("panel.material.sameSource") }}
178
179
  </span>
179
180
  <span
180
181
  v-if="node.locked"
181
182
  class="inline-flex h-5 w-5 items-center justify-center rounded border border-border/80 bg-background/80 text-muted-foreground"
182
- title="节点已锁定"
183
+ :title="t('panel.config.nodeLockedTitle')"
183
184
  >
184
185
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" class="h-3.5 w-3.5">
185
186
  <rect x="4.5" y="10.5" width="15" height="10" rx="2.5" />
@@ -196,15 +197,15 @@ function onDragStart(e: DragEvent) {
196
197
  ? 'border-violet-500/50 bg-violet-500/15 text-violet-300'
197
198
  : 'border-sky-500/40 bg-sky-500/10 text-sky-300',
198
199
  ]"
199
- :title="isDeepRef ? '深拷贝引用(冻结快照)' : '浅拷贝引用(跟随源变化)'"
200
+ :title="isDeepRef ? t('panel.material.deepCopyTitle') : t('panel.material.shallowCopyTitle')"
200
201
  >
201
- {{ isDeepRef ? "深拷贝" : "浅拷贝" }}
202
+ {{ isDeepRef ? t("panel.material.deepCopy") : t("panel.material.shallowCopy") }}
202
203
  </span>
203
- <Tooltip title="浅拷贝">
204
+ <Tooltip :title="t('panel.material.shallowCopy')">
204
205
  <button
205
206
  type="button"
206
207
  class="inline-flex h-5 w-5 items-center justify-center rounded border border-border hover:bg-accent"
207
- aria-label="浅拷贝"
208
+ :aria-label="t('panel.material.shallowCopy')"
208
209
  @click.stop="emit('copyNode', node.id, 'shallow')"
209
210
  >
210
211
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" class="h-3.5 w-3.5">
@@ -214,11 +215,11 @@ function onDragStart(e: DragEvent) {
214
215
  </svg>
215
216
  </button>
216
217
  </Tooltip>
217
- <Tooltip title="深拷贝">
218
+ <Tooltip :title="t('panel.material.deepCopy')">
218
219
  <button
219
220
  type="button"
220
221
  class="inline-flex h-5 w-5 items-center justify-center rounded border border-border hover:bg-accent"
221
- aria-label="深拷贝"
222
+ :aria-label="t('panel.material.deepCopy')"
222
223
  @click.stop="emit('copyNode', node.id, 'deep')"
223
224
  >
224
225
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" class="h-3.5 w-3.5">
@@ -229,11 +230,11 @@ function onDragStart(e: DragEvent) {
229
230
  </button>
230
231
  </Tooltip>
231
232
  </template>
232
- <Tooltip v-else title="复制节点">
233
+ <Tooltip v-else :title="t('panel.material.duplicateNode')">
233
234
  <button
234
235
  type="button"
235
236
  class="inline-flex h-5 w-5 items-center justify-center rounded border border-border hover:bg-accent"
236
- aria-label="复制节点"
237
+ :aria-label="t('panel.material.duplicateNode')"
237
238
  @click.stop="emit('copyNode', node.id)"
238
239
  >
239
240
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" class="h-3.5 w-3.5">
@@ -242,12 +243,12 @@ function onDragStart(e: DragEvent) {
242
243
  </svg>
243
244
  </button>
244
245
  </Tooltip>
245
- <Tooltip :title="node.locked ? '锁定节点不可删除' : '删除节点'">
246
+ <Tooltip :title="node.locked ? t('panel.material.lockedNodeCannotDelete') : t('panel.material.deleteNode')">
246
247
  <button
247
248
  type="button"
248
249
  :disabled="node.locked"
249
250
  class="inline-flex h-5 w-5 items-center justify-center rounded border border-border hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40"
250
- :aria-label="node.locked ? '锁定节点不可删除' : '删除节点'"
251
+ :aria-label="node.locked ? t('panel.material.lockedNodeCannotDelete') : t('panel.material.deleteNode')"
251
252
  @click.stop="!node.locked && emit('deleteNode', node.id)"
252
253
  >
253
254
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" class="h-3.5 w-3.5">
@@ -266,9 +267,9 @@ function onDragStart(e: DragEvent) {
266
267
  v-if="nextVisited.has(child.id)"
267
268
  class="py-1 text-[10px] text-muted-foreground/80"
268
269
  :style="{ paddingLeft: `${6 + (level + 1) * 14}px` }"
269
- title="检测到循环引用,已停止向下展开"
270
+ :title="t('panel.material.circularRefStopped')"
270
271
  >
271
- {{ getNodeDisplayName(child) }}(循环引用)
272
+ {{ getNodeDisplayName(child, t) }}{{ t("panel.material.circularRefSuffix") }}
272
273
  </div>
273
274
  <MaterialSidebarTreeNode
274
275
  v-else
@@ -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,
@@ -12,6 +13,8 @@ import type { PanelElement } from "../types";
12
13
  import { uniformViewportZoom } from "../viewportZoom";
13
14
  import { notifyPreviewLayoutChanged } from "../utils/panelStateIO";
14
15
 
16
+ const { t, locale } = useI18n();
17
+
15
18
  const props = defineProps<{
16
19
  zoomX: number;
17
20
  zoomY: number;
@@ -814,7 +817,7 @@ const showPartialLockBadge = computed(
814
817
  <div
815
818
  class="pointer-events-none absolute z-[80] inline-flex select-none items-center gap-1 rounded border border-border bg-background/95 px-1.5 py-0.5 text-[11px] text-foreground shadow-sm"
816
819
  :style="{ left: `${lockBadgeScreen!.x + 6}px`, top: `${lockBadgeScreen!.y - 22}px` }"
817
- title="节点已锁定"
820
+ :title="t('panel.config.nodeLockedTitle')"
818
821
  >
819
822
  <svg
820
823
  viewBox="0 0 24 24"
@@ -830,14 +833,14 @@ const showPartialLockBadge = computed(
830
833
  <path d="M8 10.5V8a4 4 0 0 1 8 0v2.5" />
831
834
  <circle cx="12" cy="15.5" r="1.2" />
832
835
  </svg>
833
- <span>已锁定</span>
836
+ <span>{{ t("panel.config.nodeLockedBadge") }}</span>
834
837
  </div>
835
838
  </Teleport>
836
839
  <Teleport v-else-if="pointerRoot && showPartialLockBadge" :to="pointerRoot">
837
840
  <div
838
841
  class="pointer-events-none absolute z-[80] inline-flex select-none items-center gap-1 rounded border border-border bg-background/95 px-1.5 py-0.5 text-[11px] text-foreground shadow-sm"
839
842
  :style="{ left: `${lockBadgeScreen!.x + 6}px`, top: `${lockBadgeScreen!.y - 22}px` }"
840
- title="部分节点已锁定,已自动排除"
843
+ :title="t('panel.config.lockedExcludedTitle')"
841
844
  >
842
845
  <svg
843
846
  viewBox="0 0 24 24"
@@ -853,7 +856,7 @@ const showPartialLockBadge = computed(
853
856
  <path d="M8 10.5V8a4 4 0 0 1 8 0v2.5" />
854
857
  <circle cx="12" cy="15.5" r="1.2" />
855
858
  </svg>
856
- <span>锁定节点已排除</span>
859
+ <span>{{ t("panel.config.lockedExcludedBadge") }}</span>
857
860
  </div>
858
861
  </Teleport>
859
862
  </template>