@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,12 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "@arronqzy/i18n/vue";
2
3
  import { Checkbox, Input, InputNumber, Select } from "ant-design-vue";
3
4
  import type { PanelElement, PanelLayer } from "../../types";
4
- import { PANEL_MESSAGES } from "../../constants/messages";
5
+ import { getPanelMessages } from "../../constants/messages";
5
6
  import ConfigSection from "./ConfigSection.vue";
6
7
 
8
+ const { t, locale } = useI18n();
9
+ const msgs = () => getPanelMessages(t);
7
10
  const props = defineProps<{
8
11
  element: PanelElement;
9
12
  layers: PanelLayer[];
@@ -34,18 +37,18 @@ function patch(patch: Partial<PanelElement>) {
34
37
 
35
38
  <template>
36
39
  <ConfigSection
37
- title="节点信息"
40
+ :title="t('panel.config.sectionNodeInfo')"
38
41
  :open="open"
39
42
  :force-open="forceOpen"
40
43
  @update:open="emit('update:open', $event)"
41
44
  >
42
45
  <label class="block space-y-1">
43
- <div>节点名称</div>
46
+ <div>{{ t("panel.config.nodeName") }}</div>
44
47
  <Input
45
48
  size="small"
46
49
  :value="element.name ?? ''"
47
50
  :disabled="!isEditable"
48
- placeholder="自定义节点名称(显示在节点树)"
51
+ :placeholder="t('panel.config.nodeNamePlaceholder')"
49
52
  @update:value="(v: string) => patch({ name: v || undefined })"
50
53
  />
51
54
  </label>
@@ -73,7 +76,7 @@ function patch(patch: Partial<PanelElement>) {
73
76
  </div>
74
77
  <div class="grid grid-cols-3 gap-2">
75
78
  <label class="block space-y-1">
76
- <div>旋转角度</div>
79
+ <div>{{ t("panel.config.rotate") }}</div>
77
80
  <InputNumber
78
81
  size="small"
79
82
  class="w-full"
@@ -83,7 +86,7 @@ function patch(patch: Partial<PanelElement>) {
83
86
  />
84
87
  </label>
85
88
  <label class="block space-y-1">
86
- <div>宽</div>
89
+ <div>{{ t("panel.config.width") }}</div>
87
90
  <InputNumber
88
91
  size="small"
89
92
  class="w-full"
@@ -94,7 +97,7 @@ function patch(patch: Partial<PanelElement>) {
94
97
  />
95
98
  </label>
96
99
  <label class="block space-y-1">
97
- <div>高</div>
100
+ <div>{{ t("panel.config.height") }}</div>
98
101
  <InputNumber
99
102
  size="small"
100
103
  class="w-full"
@@ -106,8 +109,8 @@ function patch(patch: Partial<PanelElement>) {
106
109
  </label>
107
110
  </div>
108
111
  <div class="space-y-1.5">
109
- <div class="text-[11px] text-gray-500">节点层级</div>
110
- <div class="text-[11px] text-gray-400">当前 zIndex:{{ nodeZOrderLabel ?? "-" }}</div>
112
+ <div class="text-[11px] text-gray-500">{{ t("panel.config.nodeZOrder") }}</div>
113
+ <div class="text-[11px] text-gray-400">{{ t("panel.config.currentZIndex", { value: nodeZOrderLabel ?? "-" }) }}</div>
111
114
  <div v-if="onAdjustNodeZOrder" class="grid grid-cols-2 gap-2">
112
115
  <button
113
116
  type="button"
@@ -115,7 +118,7 @@ function patch(patch: Partial<PanelElement>) {
115
118
  :disabled="!isEditable"
116
119
  @click="onAdjustNodeZOrder(element.id, 'bringForward')"
117
120
  >
118
- 上移一层
121
+ {{ t("panel.menubar.bringForward") }}
119
122
  </button>
120
123
  <button
121
124
  type="button"
@@ -123,7 +126,7 @@ function patch(patch: Partial<PanelElement>) {
123
126
  :disabled="!isEditable"
124
127
  @click="onAdjustNodeZOrder(element.id, 'sendBackward')"
125
128
  >
126
- 下移一层
129
+ {{ t("panel.menubar.sendBackward") }}
127
130
  </button>
128
131
  <button
129
132
  type="button"
@@ -131,7 +134,7 @@ function patch(patch: Partial<PanelElement>) {
131
134
  :disabled="!isEditable"
132
135
  @click="onAdjustNodeZOrder(element.id, 'bringToFront')"
133
136
  >
134
- 置顶
137
+ {{ t("panel.menubar.bringToFront") }}
135
138
  </button>
136
139
  <button
137
140
  type="button"
@@ -139,12 +142,12 @@ function patch(patch: Partial<PanelElement>) {
139
142
  :disabled="!isEditable"
140
143
  @click="onAdjustNodeZOrder(element.id, 'sendToBack')"
141
144
  >
142
- 置底
145
+ {{ t("panel.menubar.sendToBack") }}
143
146
  </button>
144
147
  </div>
145
148
  </div>
146
149
  <div class="truncate text-gray-400">ID: {{ element.id }}</div>
147
- <div class="text-gray-400">类型: {{ element.materialType ?? element.id }}</div>
150
+ <div class="text-gray-400">{{ t("panel.config.type") }}: {{ element.materialType ?? element.id }}</div>
148
151
  <div class="rounded-lg border border-gray-200/60 bg-white/80 px-2.5 py-2">
149
152
  <label class="flex items-center gap-2">
150
153
  <Checkbox
@@ -152,14 +155,14 @@ function patch(patch: Partial<PanelElement>) {
152
155
  :disabled="!canToggleNodeLock"
153
156
  @update:checked="(v) => patch({ locked: v === true })"
154
157
  />
155
- <span>锁定节点(禁止层级/位置/大小/旋转)</span>
158
+ <span>{{ t("panel.config.lockNode") }}</span>
156
159
  </label>
157
160
  </div>
158
161
  <div
159
162
  v-if="!isEditable"
160
163
  class="rounded border border-amber-500/40 bg-amber-500/10 px-2 py-1.5 text-[11px] text-amber-700"
161
164
  >
162
- {{ element.locked ? PANEL_MESSAGES.nodeConfigLocked : PANEL_MESSAGES.nodeConfigLayerLocked }}
165
+ {{ element.locked ? msgs().nodeConfigLocked : msgs().nodeConfigLayerLocked }}
163
166
  </div>
164
167
  </ConfigSection>
165
168
  </template>
@@ -1,9 +1,11 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "@arronqzy/i18n/vue";
2
3
  import { Select } from "ant-design-vue";
3
4
  import type { PanelElement, PanelLayer, ReferenceCopyMode } 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
  layers: PanelLayer[];
@@ -29,14 +31,14 @@ function patch(patch: Partial<PanelElement>) {
29
31
 
30
32
  <template>
31
33
  <ConfigSection
32
- title="引用组件配置"
34
+ :title="t('panel.config.sectionReference')"
33
35
  :open="open"
34
36
  :force-open="forceOpen"
35
37
  @update:open="emit('update:open', $event)"
36
38
  >
37
- <ConfigFieldGroup title="引用源">
39
+ <ConfigFieldGroup :title="t('panel.config.groupRefSource')">
38
40
  <label class="block space-y-1">
39
- <div>引用图层</div>
41
+ <div>{{ t("panel.config.refLayer") }}</div>
40
42
  <Select
41
43
  size="small"
42
44
  class="w-full"
@@ -44,7 +46,7 @@ function patch(patch: Partial<PanelElement>) {
44
46
  :disabled="!isEditable"
45
47
  @update:value="(v) => patch({ refLayerId: v === '__none__' ? undefined : String(v) })"
46
48
  >
47
- <Select.Option value="__none__">无(不引用)</Select.Option>
49
+ <Select.Option value="__none__">{{ t("panel.config.noneNoRef") }}</Select.Option>
48
50
  <Select.Option
49
51
  v-for="layer in layers.filter((l) => l.id !== element.layerId)"
50
52
  :key="layer.id"
@@ -55,12 +57,12 @@ function patch(patch: Partial<PanelElement>) {
55
57
  </Select>
56
58
  </label>
57
59
  </ConfigFieldGroup>
58
- <ConfigFieldGroup title="拷贝策略">
60
+ <ConfigFieldGroup :title="t('panel.config.groupCopyStrategy')">
59
61
  <template #hint>
60
- 浅拷贝会实时同步被引用图层;深拷贝会固定当前快照,不再随源变化。
62
+ {{ t("panel.config.copyStrategyHint") }}
61
63
  </template>
62
64
  <label class="block space-y-1">
63
- <div>拷贝模式</div>
65
+ <div>{{ t("panel.config.copyMode") }}</div>
64
66
  <Select
65
67
  size="small"
66
68
  class="w-full"
@@ -68,8 +70,8 @@ function patch(patch: Partial<PanelElement>) {
68
70
  :disabled="!isEditable"
69
71
  @update:value="(v) => setReferenceCopyMode?.(element.id, v as ReferenceCopyMode)"
70
72
  >
71
- <Select.Option value="shallow">浅拷贝(跟随源图层变化)</Select.Option>
72
- <Select.Option value="deep">深拷贝(冻结当前引用快照)</Select.Option>
73
+ <Select.Option value="shallow">{{ t("panel.config.shallowFollow") }}</Select.Option>
74
+ <Select.Option value="deep">{{ t("panel.config.deepFreeze") }}</Select.Option>
73
75
  </Select>
74
76
  </label>
75
77
  </ConfigFieldGroup>
@@ -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, PanelElementStyle } 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 ConfigColorField from "./ConfigColorField.vue";
11
9
  import ConfigFieldGroup from "./ConfigFieldGroup.vue";
12
10
  import ConfigSection from "./ConfigSection.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;
@@ -40,15 +41,15 @@ function patchStyle(patch: Partial<PanelElementStyle>) {
40
41
  }
41
42
 
42
43
  async function handleUploadBackgroundImage(file: File) {
43
- const base64 = await readFileAsDataUrl(file, PANEL_MESSAGES.readImageFailed);
44
+ const base64 = await readFileAsDataUrl(file, msgs().readImageFailed);
44
45
  patchStyle({ backgroundImage: `url("${base64}")` });
45
- uploadStatus.value = "已写入 base64";
46
+ uploadStatus.value = t("panel.config.uploadWrittenBase64");
46
47
  const url = await uploadFileToRemote(file);
47
48
  if (url) {
48
49
  patchStyle({ backgroundImageRemoteUrl: url });
49
- uploadStatus.value = "已上传服务器并写入 base64";
50
+ uploadStatus.value = t("panel.config.uploadServerAndBase64");
50
51
  } else {
51
- uploadStatus.value = "服务器上传失败,仅保留 base64";
52
+ uploadStatus.value = t("panel.config.uploadServerFailedKeepBase64");
52
53
  }
53
54
  }
54
55
 
@@ -63,20 +64,20 @@ function onFileChange(e: Event) {
63
64
  <template>
64
65
  <ConfigSection
65
66
  v-if="showBackground !== false"
66
- title="通用样式 / 背景"
67
+ :title="t('panel.config.sectionStyleBackground')"
67
68
  :open="backgroundOpen"
68
69
  :force-open="forceOpen"
69
70
  @update:open="emit('update:backgroundOpen', $event)"
70
71
  >
71
- <ConfigFieldGroup title="背景填充">
72
+ <ConfigFieldGroup :title="t('panel.config.groupBgFill')">
72
73
  <ConfigColorField
73
- label="背景色"
74
+ :label="t('panel.scope.fieldStyleBackgroundColor')"
74
75
  :value="element.style?.backgroundColor ?? ''"
75
76
  :disabled="!isEditable"
76
77
  @update:value="(v) => patchStyle({ backgroundColor: v || undefined })"
77
78
  />
78
79
  <label class="block space-y-1">
79
- <div>背景图</div>
80
+ <div>{{ t("panel.scope.fieldStyleBackgroundImage") }}</div>
80
81
  <Input
81
82
  size="small"
82
83
  :value="element.style?.backgroundImage ?? ''"
@@ -90,7 +91,7 @@ function onFileChange(e: Event) {
90
91
  class="inline-flex cursor-pointer items-center rounded border border-gray-200 px-2 py-1 text-[11px] hover:bg-gray-50"
91
92
  :class="{ 'pointer-events-none opacity-50': !isEditable }"
92
93
  >
93
- 上传图片
94
+ {{ t("panel.config.uploadImage") }}
94
95
  <input
95
96
  type="file"
96
97
  accept="image/*"
@@ -102,10 +103,10 @@ function onFileChange(e: Event) {
102
103
  <span v-if="uploadStatus" class="text-[11px] text-gray-500">{{ uploadStatus }}</span>
103
104
  </div>
104
105
  </ConfigFieldGroup>
105
- <ConfigFieldGroup title="背景布局">
106
+ <ConfigFieldGroup :title="t('panel.config.groupBgLayout')">
106
107
  <div class="grid grid-cols-2 gap-2">
107
108
  <label class="block space-y-1">
108
- <div>背景尺寸</div>
109
+ <div>{{ t("panel.scope.fieldStyleBackgroundSize") }}</div>
109
110
  <Select
110
111
  size="small"
111
112
  class="w-full"
@@ -113,7 +114,7 @@ function onFileChange(e: Event) {
113
114
  :disabled="!isEditable"
114
115
  @update:value="(v) => patchStyle({ backgroundSize: v === '__none__' ? undefined : String(v) })"
115
116
  >
116
- <Select.Option value="__none__">默认</Select.Option>
117
+ <Select.Option value="__none__">{{ t("common.default") }}</Select.Option>
117
118
  <Select.Option value="cover">cover</Select.Option>
118
119
  <Select.Option value="contain">contain</Select.Option>
119
120
  <Select.Option value="100% 100%">100% 100%</Select.Option>
@@ -121,7 +122,7 @@ function onFileChange(e: Event) {
121
122
  </Select>
122
123
  </label>
123
124
  <label class="block space-y-1">
124
- <div>背景位置</div>
125
+ <div>{{ t("panel.scope.fieldStyleBackgroundPosition") }}</div>
125
126
  <Select
126
127
  size="small"
127
128
  class="w-full"
@@ -129,7 +130,7 @@ function onFileChange(e: Event) {
129
130
  :disabled="!isEditable"
130
131
  @update:value="(v) => patchStyle({ backgroundPosition: v === '__none__' ? undefined : String(v) })"
131
132
  >
132
- <Select.Option value="__none__">默认</Select.Option>
133
+ <Select.Option value="__none__">{{ t("common.default") }}</Select.Option>
133
134
  <Select.Option value="center">center</Select.Option>
134
135
  <Select.Option value="top">top</Select.Option>
135
136
  <Select.Option value="bottom">bottom</Select.Option>
@@ -147,15 +148,15 @@ function onFileChange(e: Event) {
147
148
 
148
149
  <ConfigSection
149
150
  v-if="showBorder !== false"
150
- title="通用样式 / 边框"
151
+ :title="t('panel.config.sectionStyleBorder')"
151
152
  :open="borderOpen"
152
153
  :force-open="forceOpen"
153
154
  @update:open="emit('update:borderOpen', $event)"
154
155
  >
155
- <ConfigFieldGroup title="边框几何">
156
+ <ConfigFieldGroup :title="t('panel.config.groupBorderGeometry')">
156
157
  <div class="grid grid-cols-2 gap-2">
157
158
  <label class="block space-y-1">
158
- <div>边框宽度(px)</div>
159
+ <div>{{ t("panel.config.borderWidth") }}</div>
159
160
  <Input
160
161
  size="small"
161
162
  type="number"
@@ -166,7 +167,7 @@ function onFileChange(e: Event) {
166
167
  />
167
168
  </label>
168
169
  <label class="block space-y-1">
169
- <div>边框圆角(px)</div>
170
+ <div>{{ t("panel.config.borderRadius") }}</div>
170
171
  <Input
171
172
  size="small"
172
173
  type="number"
@@ -178,10 +179,10 @@ function onFileChange(e: Event) {
178
179
  </label>
179
180
  </div>
180
181
  </ConfigFieldGroup>
181
- <ConfigFieldGroup title="边框视觉">
182
+ <ConfigFieldGroup :title="t('panel.config.groupBorderVisual')">
182
183
  <div class="grid grid-cols-2 gap-2">
183
184
  <label class="block space-y-1">
184
- <div>边框样式</div>
185
+ <div>{{ t("panel.config.borderStyle") }}</div>
185
186
  <Select
186
187
  size="small"
187
188
  class="w-full"
@@ -197,7 +198,7 @@ function onFileChange(e: Event) {
197
198
  </Select>
198
199
  </label>
199
200
  <ConfigColorField
200
- label="边框颜色"
201
+ :label="t('panel.scope.fieldStyleBorderColor')"
201
202
  :value="element.style?.borderColor ?? ''"
202
203
  :disabled="!isEditable"
203
204
  @update:value="(v) => patchStyle({ borderColor: v || undefined })"
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "@arronqzy/i18n/vue";
2
3
  import { onMounted, ref, watch } from "vue";
3
4
  import { Checkbox, Input, InputNumber, Select } from "ant-design-vue";
4
5
  import type { PanelElement } from "../../types";
@@ -6,6 +7,7 @@ import ConfigColorField from "./ConfigColorField.vue";
6
7
  import ConfigFieldGroup from "./ConfigFieldGroup.vue";
7
8
  import ConfigSection from "./ConfigSection.vue";
8
9
 
10
+ const { t, locale } = useI18n();
9
11
  const props = defineProps<{
10
12
  element: PanelElement;
11
13
  isEditable: boolean;
@@ -38,7 +40,8 @@ function execTextCommand(cmd: "bold" | "italic" | "underline") {
38
40
 
39
41
  function syncEditorHtml() {
40
42
  if (props.element.materialType !== "text") return;
41
- const nextHtml = props.element.textHtml ?? "<p>双击输入文本</p>";
43
+ const nextHtml =
44
+ props.element.textHtml ?? `<p>${t("panel.defaults.doubleClickTextHtml")}</p>`;
42
45
  if (textEditorRef.value && textEditorRef.value.innerHTML !== nextHtml) {
43
46
  textEditorRef.value.innerHTML = nextHtml;
44
47
  }
@@ -50,12 +53,12 @@ watch(() => [props.element.id, props.element.textHtml], syncEditorHtml);
50
53
 
51
54
  <template>
52
55
  <ConfigSection
53
- title="文本配置"
56
+ :title="t('panel.config.sectionText')"
54
57
  :open="open"
55
58
  :force-open="forceOpen"
56
59
  @update:open="emit('update:open', $event)"
57
60
  >
58
- <ConfigFieldGroup title="文本内容">
61
+ <ConfigFieldGroup :title="t('panel.config.groupTextContent')">
59
62
  <div class="flex items-center gap-1">
60
63
  <button
61
64
  type="button"
@@ -100,20 +103,20 @@ watch(() => [props.element.id, props.element.textHtml], syncEditorHtml);
100
103
  }"
101
104
  />
102
105
  </ConfigFieldGroup>
103
- <ConfigFieldGroup title="文字样式">
106
+ <ConfigFieldGroup :title="t('panel.config.groupTextStyle')">
104
107
  <label class="block space-y-1">
105
- <div>字体</div>
108
+ <div>{{ t("panel.config.fontFamily") }}</div>
106
109
  <Input
107
110
  size="small"
108
111
  :value="element.textFontFamily ?? ''"
109
112
  :disabled="!isEditable"
110
- placeholder="如:Inter, PingFang SC, Microsoft YaHei"
113
+ :placeholder="t('panel.config.fontFamilyPlaceholder')"
111
114
  @update:value="(v: string) => patch({ textFontFamily: v || undefined })"
112
115
  />
113
116
  </label>
114
117
  <div class="grid grid-cols-2 gap-2">
115
118
  <label class="block space-y-1">
116
- <div>字号(px)</div>
119
+ <div>{{ t("panel.config.fontSizePx") }}</div>
117
120
  <InputNumber
118
121
  size="small"
119
122
  class="w-full"
@@ -125,7 +128,7 @@ watch(() => [props.element.id, props.element.textHtml], syncEditorHtml);
125
128
  />
126
129
  </label>
127
130
  <label class="block space-y-1">
128
- <div>字重</div>
131
+ <div>{{ t("panel.config.fontWeight") }}</div>
129
132
  <Select
130
133
  size="small"
131
134
  class="w-full"
@@ -143,7 +146,7 @@ watch(() => [props.element.id, props.element.textHtml], syncEditorHtml);
143
146
  </div>
144
147
  <div class="grid grid-cols-2 gap-2">
145
148
  <label class="block space-y-1">
146
- <div>对齐</div>
149
+ <div>{{ t("panel.config.textAlign") }}</div>
147
150
  <Select
148
151
  size="small"
149
152
  class="w-full"
@@ -151,14 +154,14 @@ watch(() => [props.element.id, props.element.textHtml], syncEditorHtml);
151
154
  :disabled="!isEditable"
152
155
  @update:value="(v) => patch({ textAlign: v as PanelElement['textAlign'] })"
153
156
  >
154
- <Select.Option value="left">左对齐</Select.Option>
155
- <Select.Option value="center">居中</Select.Option>
156
- <Select.Option value="right">右对齐</Select.Option>
157
- <Select.Option value="justify">两端对齐</Select.Option>
157
+ <Select.Option value="left">{{ t("panel.config.alignLeft") }}</Select.Option>
158
+ <Select.Option value="center">{{ t("panel.config.alignCenter") }}</Select.Option>
159
+ <Select.Option value="right">{{ t("panel.config.alignRight") }}</Select.Option>
160
+ <Select.Option value="justify">{{ t("panel.config.alignJustify") }}</Select.Option>
158
161
  </Select>
159
162
  </label>
160
163
  <label class="block space-y-1">
161
- <div>行高</div>
164
+ <div>{{ t("panel.config.lineHeight") }}</div>
162
165
  <InputNumber
163
166
  size="small"
164
167
  class="w-full"
@@ -175,20 +178,20 @@ watch(() => [props.element.id, props.element.textHtml], syncEditorHtml);
175
178
  </label>
176
179
  </div>
177
180
  <ConfigColorField
178
- label="文字颜色"
181
+ :label="t('panel.config.textColor')"
179
182
  :value="element.textColor ?? ''"
180
183
  :disabled="!isEditable"
181
184
  @update:value="(v) => patch({ textColor: v || undefined })"
182
185
  />
183
186
  </ConfigFieldGroup>
184
- <ConfigFieldGroup title="输入能力">
187
+ <ConfigFieldGroup :title="t('panel.config.groupInputAbility')">
185
188
  <label class="flex items-center gap-2">
186
189
  <Checkbox
187
190
  :checked="element.textAllowInput ?? true"
188
191
  :disabled="!isEditable"
189
192
  @update:checked="(v) => patch({ textAllowInput: v !== false })"
190
193
  />
191
- <span>允许在画布内直接输入(默认开启)</span>
194
+ <span>{{ t("panel.config.allowCanvasInput") }}</span>
192
195
  </label>
193
196
  </ConfigFieldGroup>
194
197
  </ConfigSection>
@@ -1,15 +1,16 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "@arronqzy/i18n/vue";
2
3
  import { ref } from "vue";
3
4
  import { Checkbox, Input } 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
 
11
+ const { t, locale } = useI18n();
12
+ const msgs = () => getPanelMessages(t);
13
+
13
14
  const props = defineProps<{
14
15
  element: PanelElement;
15
16
  isEditable: boolean;
@@ -34,15 +35,15 @@ function patch(patch: Partial<PanelElement>) {
34
35
  }
35
36
 
36
37
  async function handleUploadVideoFile(file: File) {
37
- const base64 = await readFileAsDataUrl(file, PANEL_MESSAGES.readVideoFailed);
38
+ const base64 = await readFileAsDataUrl(file, msgs().readVideoFailed);
38
39
  patch({ videoSrc: base64 });
39
- videoStatus.value = PANEL_MESSAGES.videoLocalSaved;
40
+ videoStatus.value = msgs().videoLocalSaved;
40
41
  const url = await uploadFileToRemote(file);
41
42
  if (url) {
42
43
  patch({ videoRemoteUrl: url });
43
- videoStatus.value = PANEL_MESSAGES.videoRemoteUploaded;
44
+ videoStatus.value = msgs().videoRemoteUploaded;
44
45
  } else {
45
- videoStatus.value = PANEL_MESSAGES.videoServerUploadFailed;
46
+ videoStatus.value = msgs().videoServerUploadFailed;
46
47
  }
47
48
  }
48
49
 
@@ -55,14 +56,14 @@ function onVideoFileChange(e: Event) {
55
56
 
56
57
  <template>
57
58
  <ConfigSection
58
- title="视频配置"
59
+ :title="t('panel.config.sectionVideo')"
59
60
  :open="open"
60
61
  :force-open="forceOpen"
61
62
  @update:open="emit('update:open', $event)"
62
63
  >
63
- <ConfigFieldGroup title="视频来源">
64
+ <ConfigFieldGroup :title="t('panel.config.groupVideoSource')">
64
65
  <label class="block space-y-1">
65
- <div>视频 URL</div>
66
+ <div>{{ t("panel.config.videoUrl") }}</div>
66
67
  <Input
67
68
  size="small"
68
69
  :value="element.videoRemoteUrl ?? ''"
@@ -79,7 +80,7 @@ function onVideoFileChange(e: Event) {
79
80
  class="inline-flex cursor-pointer items-center rounded border border-gray-200 px-2 py-1 text-[11px] hover:bg-gray-50"
80
81
  :class="{ 'pointer-events-none opacity-50': !isEditable }"
81
82
  >
82
- 上传视频
83
+ {{ t("panel.config.uploadVideo") }}
83
84
  <input type="file" accept="video/*" class="hidden" :disabled="!isEditable" @change="onVideoFileChange" />
84
85
  </label>
85
86
  </div>
@@ -100,7 +101,7 @@ function onVideoFileChange(e: Event) {
100
101
  :disabled="!isEditable"
101
102
  @update:checked="(v) => patch({ mediaAutoPauseOnEdit: v !== false })"
102
103
  />
103
- <span>编辑时自动暂停媒体</span>
104
+ <span>{{ t("panel.config.autoPauseMedia") }}</span>
104
105
  </label>
105
106
  </ConfigFieldGroup>
106
107
  </ConfigSection>
@@ -1,5 +1,4 @@
1
1
  import type { PanelChartConfig } from "../../types";
2
- import { PANEL_MESSAGES } from "../../constants/messages";
3
2
 
4
3
  export function mergeOptionPatch(
5
4
  base: Record<string, unknown> | undefined,
@@ -70,5 +69,3 @@ export function patchChart(
70
69
  ): PanelChartConfig {
71
70
  return { ...(chart ?? {}), ...patch };
72
71
  }
73
-
74
- export { PANEL_MESSAGES };