@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,9 +1,11 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "@arronqzy/i18n/vue";
2
3
  import { InputNumber } from "ant-design-vue";
3
4
  import type { PanelElement } from "../../types";
4
5
  import ConfigFieldGroup from "./ConfigFieldGroup.vue";
5
6
  import ConfigSection from "./ConfigSection.vue";
6
7
 
8
+ const { t, locale } = useI18n();
7
9
  const props = defineProps<{
8
10
  element: PanelElement;
9
11
  isEditable: boolean;
@@ -28,18 +30,18 @@ function patch(patch: Partial<PanelElement>) {
28
30
  <template>
29
31
  <ConfigSection
30
32
  v-if="element.parentGridId"
31
- title="网格子节点占位"
33
+ :title="t('panel.config.sectionGridChildSpan')"
32
34
  :open="open"
33
35
  :force-open="forceOpen"
34
36
  @update:open="emit('update:open', $event)"
35
37
  >
36
38
  <template #hint>
37
- 网格子节点可跨越多格,占据更大区域,便于复杂布局。
39
+ {{ t("panel.config.gridChildHint") }}
38
40
  </template>
39
- <ConfigFieldGroup title="跨槽位">
41
+ <ConfigFieldGroup :title="t('panel.config.groupCrossSlots')">
40
42
  <div class="grid grid-cols-2 gap-2">
41
43
  <label class="block space-y-1">
42
- <div>跨列(colSpan)</div>
44
+ <div>{{ t("panel.config.colSpan") }}</div>
43
45
  <InputNumber
44
46
  size="small"
45
47
  class="w-full"
@@ -51,7 +53,7 @@ function patch(patch: Partial<PanelElement>) {
51
53
  />
52
54
  </label>
53
55
  <label class="block space-y-1">
54
- <div>跨行(rowSpan)</div>
56
+ <div>{{ t("panel.config.rowSpan") }}</div>
55
57
  <InputNumber
56
58
  size="small"
57
59
  class="w-full"
@@ -1,9 +1,11 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "@arronqzy/i18n/vue";
2
3
  import { InputNumber } from "ant-design-vue";
3
4
  import type { PanelElement } from "../../types";
4
5
  import ConfigFieldGroup from "./ConfigFieldGroup.vue";
5
6
  import ConfigSection from "./ConfigSection.vue";
6
7
 
8
+ const { t, locale } = useI18n();
7
9
  const props = defineProps<{
8
10
  element: PanelElement;
9
11
  isEditable: boolean;
@@ -27,18 +29,18 @@ function patch(patch: Partial<PanelElement>) {
27
29
 
28
30
  <template>
29
31
  <ConfigSection
30
- title="网格布局配置"
32
+ :title="t('panel.config.sectionGrid')"
31
33
  :open="open"
32
34
  :force-open="forceOpen"
33
35
  @update:open="emit('update:open', $event)"
34
36
  >
35
37
  <template #hint>
36
- 其他节点拖拽靠近该网格槽位中心时会自动吸附,并在节点树显示为该网格子节点。
38
+ {{ t("panel.config.gridHint") }}
37
39
  </template>
38
- <ConfigFieldGroup title="网格参数">
40
+ <ConfigFieldGroup :title="t('panel.config.groupGridParams')">
39
41
  <div class="grid grid-cols-2 gap-2">
40
42
  <label class="block space-y-1">
41
- <div>行数</div>
43
+ <div>{{ t("panel.config.rowCount") }}</div>
42
44
  <InputNumber
43
45
  size="small"
44
46
  class="w-full"
@@ -50,7 +52,7 @@ function patch(patch: Partial<PanelElement>) {
50
52
  />
51
53
  </label>
52
54
  <label class="block space-y-1">
53
- <div>列数</div>
55
+ <div>{{ t("panel.config.colCount") }}</div>
54
56
  <InputNumber
55
57
  size="small"
56
58
  class="w-full"
@@ -62,7 +64,7 @@ function patch(patch: Partial<PanelElement>) {
62
64
  />
63
65
  </label>
64
66
  <label class="block space-y-1">
65
- <div>间距(px)</div>
67
+ <div>{{ t("panel.config.gapPx") }}</div>
66
68
  <InputNumber
67
69
  size="small"
68
70
  class="w-full"
@@ -74,7 +76,7 @@ function patch(patch: Partial<PanelElement>) {
74
76
  />
75
77
  </label>
76
78
  <label class="block space-y-1">
77
- <div>内边距(px)</div>
79
+ <div>{{ t("panel.config.paddingPx") }}</div>
78
80
  <InputNumber
79
81
  size="small"
80
82
  class="w-full"
@@ -86,7 +88,7 @@ function patch(patch: Partial<PanelElement>) {
86
88
  />
87
89
  </label>
88
90
  <label class="block space-y-1">
89
- <div>吸附阈值(px)</div>
91
+ <div>{{ t("panel.config.snapThresholdPx") }}</div>
90
92
  <InputNumber
91
93
  size="small"
92
94
  class="w-full"
@@ -1,16 +1,17 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "@arronqzy/i18n/vue";
2
3
  import { ref } from "vue";
3
4
  import { Input, Select } from "ant-design-vue";
4
5
  import type { PanelElement } from "../../types";
5
- import {
6
- PANEL_MESSAGES,
7
- readFileAsDataUrl,
8
- uploadFileToRemote,
9
- } from "./shared";
6
+ import { readFileAsDataUrl, uploadFileToRemote } from "./shared";
7
+ import { getPanelMessages } from "../../constants/messages";
10
8
  import ConfigFieldGroup from "./ConfigFieldGroup.vue";
11
9
  import ConfigSection from "./ConfigSection.vue";
12
10
  import ConfigHintIcon from "../ConfigHintIcon.vue";
13
11
 
12
+ const { t, locale } = useI18n();
13
+ const msgs = () => getPanelMessages(t);
14
+
14
15
  const props = defineProps<{
15
16
  element: PanelElement;
16
17
  isEditable: boolean;
@@ -36,15 +37,15 @@ function patchStyle(patch: Record<string, string | undefined>) {
36
37
  }
37
38
 
38
39
  async function handleUploadImage(file: File) {
39
- const base64 = await readFileAsDataUrl(file, PANEL_MESSAGES.readImageFailed);
40
+ const base64 = await readFileAsDataUrl(file, msgs().readImageFailed);
40
41
  patchStyle({ backgroundImage: `url("${base64}")` });
41
- uploadStatus.value = "已写入 base64";
42
+ uploadStatus.value = t("panel.config.uploadWrittenBase64");
42
43
  const url = await uploadFileToRemote(file);
43
44
  if (url) {
44
45
  patchStyle({ backgroundImageRemoteUrl: url });
45
- uploadStatus.value = "已上传服务器并写入 base64";
46
+ uploadStatus.value = t("panel.config.uploadServerAndBase64");
46
47
  } else {
47
- uploadStatus.value = "服务器上传失败,仅保留 base64";
48
+ uploadStatus.value = t("panel.config.uploadServerFailedKeepBase64");
48
49
  }
49
50
  }
50
51
 
@@ -58,17 +59,17 @@ function onFileChange(e: Event) {
58
59
 
59
60
  <template>
60
61
  <ConfigSection
61
- title="图片配置"
62
+ :title="t('panel.config.sectionImage')"
62
63
  :open="open"
63
64
  :force-open="forceOpen"
64
65
  @update:open="emit('update:open', $event)"
65
66
  >
66
- <ConfigFieldGroup title="图片来源">
67
+ <ConfigFieldGroup :title="t('panel.config.groupImageSource')">
67
68
  <label class="block space-y-1">
68
69
  <div class="flex items-center gap-1">
69
- <span>图片 URL / CSS</span>
70
- <ConfigHintIcon label="图片地址">
71
- 可填写远程 URL 或 url("data:...") 形式;上传后会自动写入 base64。
70
+ <span>{{ t("panel.config.imageUrlCss") }}</span>
71
+ <ConfigHintIcon :label="t('panel.config.imageUrlHintLabel')">
72
+ {{ t("panel.config.imageUrlHint") }}
72
73
  </ConfigHintIcon>
73
74
  </div>
74
75
  <Input
@@ -93,15 +94,15 @@ function onFileChange(e: Event) {
93
94
  class="inline-flex cursor-pointer items-center rounded border border-gray-200 px-2 py-1 text-[11px] hover:bg-gray-50"
94
95
  :class="{ 'pointer-events-none opacity-50': !isEditable }"
95
96
  >
96
- 上传图片
97
+ {{ t("panel.config.uploadImage") }}
97
98
  <input type="file" accept="image/*" class="hidden" :disabled="!isEditable" @change="onFileChange" />
98
99
  </label>
99
100
  <span v-if="uploadStatus" class="text-[11px] text-gray-500">{{ uploadStatus }}</span>
100
101
  </div>
101
102
  </ConfigFieldGroup>
102
- <ConfigFieldGroup title="显示方式">
103
+ <ConfigFieldGroup :title="t('panel.config.groupDisplayMode')">
103
104
  <label class="block space-y-1">
104
- <div>适应方式(object-fit / background-size)</div>
105
+ <div>{{ t("panel.config.objectFitFull") }}</div>
105
106
  <Select
106
107
  size="small"
107
108
  class="w-full"
@@ -109,14 +110,14 @@ function onFileChange(e: Event) {
109
110
  :disabled="!isEditable"
110
111
  @update:value="(v) => patchStyle({ backgroundSize: String(v) })"
111
112
  >
112
- <Select.Option value="cover">cover(裁剪铺满)</Select.Option>
113
- <Select.Option value="contain">contain(完整显示)</Select.Option>
114
- <Select.Option value="100% 100%">fill(拉伸填满)</Select.Option>
113
+ <Select.Option value="cover">{{ t("panel.config.fitCover") }}</Select.Option>
114
+ <Select.Option value="contain">{{ t("panel.config.fitContain") }}</Select.Option>
115
+ <Select.Option value="100% 100%">{{ t("panel.config.fitFill") }}</Select.Option>
115
116
  <Select.Option value="auto">auto</Select.Option>
116
117
  </Select>
117
118
  </label>
118
119
  <label class="block space-y-1">
119
- <div>对齐位置</div>
120
+ <div>{{ t("panel.config.alignPosition") }}</div>
120
121
  <Select
121
122
  size="small"
122
123
  class="w-full"
@@ -1,9 +1,11 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "@arronqzy/i18n/vue";
2
3
  import { computed, ref } from "vue";
3
4
  import { Card, Checkbox, Input, InputNumber, Select } from "ant-design-vue";
4
5
  import type { PanelElement, PanelLayer, ReferenceCopyMode } from "../../types";
5
6
  import { CHART_TYPES } from "../../utils/chartOptionBuilder";
6
7
 
8
+ const { t, locale } = useI18n();
7
9
  const props = defineProps<{
8
10
  elements: PanelElement[];
9
11
  layers: PanelLayer[];
@@ -52,9 +54,9 @@ function setNodeCardExpanded(id: string, open: boolean) {
52
54
 
53
55
  <template>
54
56
  <div class="space-y-3">
55
- <Card size="small" title="批量设置">
57
+ <Card size="small" :title="t('panel.config.batchTitle')">
56
58
  <template #title>
57
- <span class="text-xs">批量设置({{ elements.length }} 个)</span>
59
+ <span class="text-xs">{{ t("panel.config.batchTitleWithCount", { count: elements.length }) }}</span>
58
60
  </template>
59
61
  <div class="grid grid-cols-2 gap-2 text-xs">
60
62
  <button
@@ -62,14 +64,14 @@ function setNodeCardExpanded(id: string, open: boolean) {
62
64
  class="rounded border border-gray-200 bg-white px-2 py-1 hover:bg-gray-50"
63
65
  @click="elements.forEach((el) => updateElement(el.id, { locked: true }))"
64
66
  >
65
- 全部锁定
67
+ {{ t("panel.config.lockAll") }}
66
68
  </button>
67
69
  <button
68
70
  type="button"
69
71
  class="rounded border border-gray-200 bg-white px-2 py-1 hover:bg-gray-50"
70
72
  @click="elements.forEach((el) => updateElement(el.id, { locked: false }))"
71
73
  >
72
- 全部解锁
74
+ {{ t("panel.config.unlockAll") }}
73
75
  </button>
74
76
  <button
75
77
  v-if="onAdjustNodeZOrder"
@@ -77,7 +79,7 @@ function setNodeCardExpanded(id: string, open: boolean) {
77
79
  class="rounded border border-gray-200 bg-white px-2 py-1 hover:bg-gray-50"
78
80
  @click="elements.forEach((el) => onAdjustNodeZOrder?.(el.id, 'bringForward'))"
79
81
  >
80
- 全部上移一层
82
+ {{ t("panel.config.bringAllForward") }}
81
83
  </button>
82
84
  <button
83
85
  v-if="onAdjustNodeZOrder"
@@ -85,14 +87,14 @@ function setNodeCardExpanded(id: string, open: boolean) {
85
87
  class="rounded border border-gray-200 bg-white px-2 py-1 hover:bg-gray-50"
86
88
  @click="elements.forEach((el) => onAdjustNodeZOrder?.(el.id, 'sendBackward'))"
87
89
  >
88
- 全部下移一层
90
+ {{ t("panel.config.sendAllBackward") }}
89
91
  </button>
90
92
  <button
91
93
  type="button"
92
94
  class="rounded border border-gray-200 bg-white px-2 py-1 hover:bg-gray-50"
93
95
  @click="elements.forEach((el) => updateElement(el.id, { zIndex: 1 }))"
94
96
  >
95
- 全部 zIndex 设为 1
97
+ {{ t("panel.config.setAllZIndex1") }}
96
98
  </button>
97
99
  <button
98
100
  type="button"
@@ -105,7 +107,7 @@ function setNodeCardExpanded(id: string, open: boolean) {
105
107
  )
106
108
  "
107
109
  >
108
- 全部背景色设为蓝色
110
+ {{ t("panel.config.setAllBgBlue") }}
109
111
  </button>
110
112
  </div>
111
113
  </Card>
@@ -126,7 +128,7 @@ function setNodeCardExpanded(id: string, open: boolean) {
126
128
  {{ isNodeCardExpanded(el.id) ? "▾" : "▸" }}
127
129
  </button>
128
130
  <span class="min-w-0 flex-1 truncate text-xs">
129
- {{ el.name?.trim() || el.materialType || "节点" }} · {{ el.id }}
131
+ {{ el.name?.trim() || el.materialType || t("common.node") }} · {{ el.id }}
130
132
  </span>
131
133
  <button
132
134
  v-if="onExcludeSelectedNode"
@@ -134,7 +136,7 @@ function setNodeCardExpanded(id: string, open: boolean) {
134
136
  class="inline-flex h-6 items-center rounded border border-gray-200 px-2 text-[11px] text-gray-500 hover:bg-gray-50"
135
137
  @click="onExcludeSelectedNode(el.id)"
136
138
  >
137
- 剔除
139
+ {{ t("panel.config.removeFromSelection") }}
138
140
  </button>
139
141
  </div>
140
142
  </template>
@@ -144,18 +146,18 @@ function setNodeCardExpanded(id: string, open: boolean) {
144
146
  :checked="el.locked === true"
145
147
  @update:checked="(v) => updateElement(el.id, { locked: v === true })"
146
148
  />
147
- <span>锁定节点</span>
149
+ <span>{{ t("panel.config.lockedNode") }}</span>
148
150
  </label>
149
151
  <div
150
152
  v-if="el.locked"
151
153
  class="rounded border border-amber-500/40 bg-amber-500/10 px-2 py-1.5 text-[11px] text-amber-700"
152
154
  >
153
- 当前节点已锁定,仅可操作锁定开关。
155
+ {{ t("panel.config.lockedNodeHint") }}
154
156
  </div>
155
157
  <fieldset :disabled="el.locked" :class="el.locked ? 'opacity-60' : ''">
156
158
  <div class="space-y-2">
157
159
  <label class="block space-y-1">
158
- <div>名称</div>
160
+ <div>{{ t("panel.config.name") }}</div>
159
161
  <Input
160
162
  size="small"
161
163
  :value="el.name ?? ''"
@@ -173,7 +175,7 @@ function setNodeCardExpanded(id: string, open: boolean) {
173
175
  />
174
176
  </label>
175
177
  <label class="block space-y-1">
176
- <div>图层</div>
178
+ <div>{{ t("panel.config.layer") }}</div>
177
179
  <Select
178
180
  size="small"
179
181
  class="w-full"
@@ -196,21 +198,21 @@ function setNodeCardExpanded(id: string, open: boolean) {
196
198
  <InputNumber size="small" class="w-full" :value="el.y" @update:value="(v) => updateElement(el.id, { y: Number(v) || 0 })" />
197
199
  </label>
198
200
  <label class="block space-y-1">
199
- <div>旋转角度</div>
201
+ <div>{{ t("panel.config.rotate") }}</div>
200
202
  <InputNumber size="small" class="w-full" :value="el.rotate ?? 0" @update:value="(v) => updateElement(el.id, { rotate: Number(v) || 0 })" />
201
203
  </label>
202
204
  <label class="block space-y-1">
203
- <div>宽</div>
205
+ <div>{{ t("panel.config.width") }}</div>
204
206
  <InputNumber size="small" class="w-full" :min="1" :value="el.width" @update:value="(v) => updateElement(el.id, { width: Math.max(1, Number(v) || 1) })" />
205
207
  </label>
206
208
  <label class="block space-y-1">
207
- <div>高</div>
209
+ <div>{{ t("panel.config.height") }}</div>
208
210
  <InputNumber size="small" class="w-full" :min="1" :value="el.height" @update:value="(v) => updateElement(el.id, { height: Math.max(1, Number(v) || 1) })" />
209
211
  </label>
210
212
  </div>
211
213
  <div class="grid grid-cols-2 gap-2">
212
214
  <label class="block space-y-1">
213
- <div>背景色</div>
215
+ <div>{{ t("panel.scope.fieldStyleBackgroundColor") }}</div>
214
216
  <Input
215
217
  size="small"
216
218
  :value="el.style?.backgroundColor ?? ''"
@@ -218,7 +220,7 @@ function setNodeCardExpanded(id: string, open: boolean) {
218
220
  />
219
221
  </label>
220
222
  <label class="block space-y-1">
221
- <div>边框色</div>
223
+ <div>{{ t("panel.config.borderColorShort") }}</div>
222
224
  <Input
223
225
  size="small"
224
226
  :value="el.style?.borderColor ?? ''"
@@ -230,7 +232,7 @@ function setNodeCardExpanded(id: string, open: boolean) {
230
232
  <template v-if="CHART_TYPES.has(el.materialType ?? '')">
231
233
  <div class="grid grid-cols-2 gap-2">
232
234
  <label class="col-span-2 block space-y-1">
233
- <div>图表标题</div>
235
+ <div>{{ t("panel.scope.fieldChartTitle") }}</div>
234
236
  <Input
235
237
  size="small"
236
238
  :value="el.chart?.title ?? ''"
@@ -238,7 +240,7 @@ function setNodeCardExpanded(id: string, open: boolean) {
238
240
  />
239
241
  </label>
240
242
  <label class="block space-y-1">
241
- <div>主色</div>
243
+ <div>{{ t("panel.config.primaryColor") }}</div>
242
244
  <Input
243
245
  size="small"
244
246
  :value="el.chart?.color ?? ''"
@@ -251,7 +253,7 @@ function setNodeCardExpanded(id: string, open: boolean) {
251
253
  <template v-if="el.materialType === 'text'">
252
254
  <div class="grid grid-cols-2 gap-2">
253
255
  <label class="col-span-2 block space-y-1">
254
- <div>文本内容(HTML)</div>
256
+ <div>{{ t("panel.config.textHtml") }}</div>
255
257
  <Input.TextArea
256
258
  :value="el.textHtml ?? ''"
257
259
  :rows="3"
@@ -259,7 +261,7 @@ function setNodeCardExpanded(id: string, open: boolean) {
259
261
  />
260
262
  </label>
261
263
  <label class="block space-y-1">
262
- <div>字体大小</div>
264
+ <div>{{ t("panel.config.fontSize") }}</div>
263
265
  <InputNumber
264
266
  size="small"
265
267
  class="w-full"
@@ -269,7 +271,7 @@ function setNodeCardExpanded(id: string, open: boolean) {
269
271
  />
270
272
  </label>
271
273
  <label class="block space-y-1">
272
- <div>文字颜色</div>
274
+ <div>{{ t("panel.config.textColor") }}</div>
273
275
  <Input
274
276
  size="small"
275
277
  :value="el.textColor ?? ''"
@@ -282,7 +284,7 @@ function setNodeCardExpanded(id: string, open: boolean) {
282
284
  <template v-if="el.materialType === 'audio'">
283
285
  <div class="grid grid-cols-2 gap-2">
284
286
  <label class="col-span-2 block space-y-1">
285
- <div>音频 URL</div>
287
+ <div>{{ t("panel.config.audioUrl") }}</div>
286
288
  <Input
287
289
  size="small"
288
290
  :value="el.audioRemoteUrl ?? ''"
@@ -290,7 +292,7 @@ function setNodeCardExpanded(id: string, open: boolean) {
290
292
  />
291
293
  </label>
292
294
  <label class="block space-y-1">
293
- <div>动效</div>
295
+ <div>{{ t("panel.config.effect") }}</div>
294
296
  <Select
295
297
  size="small"
296
298
  class="w-full"
@@ -307,7 +309,7 @@ function setNodeCardExpanded(id: string, open: boolean) {
307
309
 
308
310
  <template v-if="el.materialType === 'video'">
309
311
  <label class="block space-y-1">
310
- <div>视频 URL</div>
312
+ <div>{{ t("panel.config.videoUrl") }}</div>
311
313
  <Input
312
314
  size="small"
313
315
  :value="el.videoRemoteUrl ?? ''"
@@ -319,11 +321,11 @@ function setNodeCardExpanded(id: string, open: boolean) {
319
321
  <template v-if="el.materialType === 'grid'">
320
322
  <div class="grid grid-cols-2 gap-2 rounded-lg border border-gray-200/60 bg-gray-50/50 p-3">
321
323
  <label class="block space-y-1">
322
- <div>行</div>
324
+ <div>{{ t("panel.config.rowsShort") }}</div>
323
325
  <InputNumber size="small" class="w-full" :min="1" :value="el.gridRows ?? 2" @update:value="(v) => updateElement(el.id, { gridRows: Math.max(1, Number(v) || 2) })" />
324
326
  </label>
325
327
  <label class="block space-y-1">
326
- <div>列</div>
328
+ <div>{{ t("panel.config.colsShort") }}</div>
327
329
  <InputNumber size="small" class="w-full" :min="1" :value="el.gridCols ?? 3" @update:value="(v) => updateElement(el.id, { gridCols: Math.max(1, Number(v) || 3) })" />
328
330
  </label>
329
331
  </div>
@@ -332,20 +334,20 @@ function setNodeCardExpanded(id: string, open: boolean) {
332
334
  <template v-if="el.materialType === 'geometry'">
333
335
  <div class="grid grid-cols-2 gap-2 rounded-lg border border-gray-200/60 bg-gray-50/50 p-3">
334
336
  <label class="block space-y-1">
335
- <div>形状</div>
337
+ <div>{{ t("panel.config.shape") }}</div>
336
338
  <Select
337
339
  size="small"
338
340
  class="w-full"
339
341
  :value="el.geometryShape ?? 'rect'"
340
342
  @update:value="(v) => updateElement(el.id, { geometryShape: v as PanelElement['geometryShape'] })"
341
343
  >
342
- <Select.Option value="rect">矩形</Select.Option>
343
- <Select.Option value="circle">圆形</Select.Option>
344
- <Select.Option value="triangle">三角形</Select.Option>
344
+ <Select.Option value="rect">{{ t("panel.config.shapeRect") }}</Select.Option>
345
+ <Select.Option value="circle">{{ t("panel.config.shapeCircle") }}</Select.Option>
346
+ <Select.Option value="triangle">{{ t("panel.config.shapeTriangle") }}</Select.Option>
345
347
  </Select>
346
348
  </label>
347
349
  <label class="block space-y-1">
348
- <div>颜色</div>
350
+ <div>{{ t("panel.config.color") }}</div>
349
351
  <Input
350
352
  size="small"
351
353
  :value="el.geometryColor ?? '#3b82f6'"
@@ -358,14 +360,14 @@ function setNodeCardExpanded(id: string, open: boolean) {
358
360
  <template v-if="el.materialType === 'reference'">
359
361
  <div class="grid grid-cols-2 gap-2">
360
362
  <label class="block space-y-1">
361
- <div>引用图层</div>
363
+ <div>{{ t("panel.config.refLayer") }}</div>
362
364
  <Select
363
365
  size="small"
364
366
  class="w-full"
365
367
  :value="el.refLayerId ?? '__none__'"
366
368
  @update:value="(v) => updateElement(el.id, { refLayerId: v === '__none__' ? undefined : String(v) })"
367
369
  >
368
- <Select.Option value="__none__">无</Select.Option>
370
+ <Select.Option value="__none__">{{ t("common.none") }}</Select.Option>
369
371
  <Select.Option
370
372
  v-for="layer in layers.filter((l) => l.id !== el.layerId)"
371
373
  :key="layer.id"
@@ -376,7 +378,7 @@ function setNodeCardExpanded(id: string, open: boolean) {
376
378
  </Select>
377
379
  </label>
378
380
  <label class="block space-y-1">
379
- <div>拷贝</div>
381
+ <div>{{ t("panel.config.copy") }}</div>
380
382
  <Select
381
383
  size="small"
382
384
  class="w-full"
@@ -392,7 +394,7 @@ function setNodeCardExpanded(id: string, open: boolean) {
392
394
 
393
395
  <template v-if="el.materialType === 'image'">
394
396
  <label class="block space-y-1">
395
- <div>背景图 / URL</div>
397
+ <div>{{ t("panel.config.backgroundImageUrl") }}</div>
396
398
  <Input
397
399
  size="small"
398
400
  :value="el.style?.backgroundImageRemoteUrl ?? el.style?.backgroundImage ?? ''"
@@ -406,7 +408,7 @@ function setNodeCardExpanded(id: string, open: boolean) {
406
408
  />
407
409
  </label>
408
410
  <label class="block space-y-1">
409
- <div>适应方式</div>
411
+ <div>{{ t("panel.config.objectFit") }}</div>
410
412
  <Select
411
413
  size="small"
412
414
  class="w-full"
@@ -428,7 +430,7 @@ function setNodeCardExpanded(id: string, open: boolean) {
428
430
  v-if="noSearchMatch"
429
431
  class="rounded border border-gray-200/60 bg-white px-2 py-1.5 text-[11px] text-gray-500"
430
432
  >
431
- 未匹配到可编辑节点,请更换关键词。
433
+ {{ t("panel.config.noEditableMatch") }}
432
434
  </div>
433
435
  </div>
434
436
  </template>