@arronqzy/vue-view 0.1.6 → 0.1.7
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.
- package/package.json +7 -6
- package/src/panel/VueViewOnlinePreview.vue +28 -2
- package/src/panel/VueViewPanel.vue +120 -66
- package/src/panel/components/BusyOverlay.vue +24 -0
- package/src/panel/components/ConfigHintIcon.vue +10 -10
- package/src/panel/components/ElementsLayer.vue +51 -8
- package/src/panel/components/MaterialPreview.vue +8 -0
- package/src/panel/components/PanelConfigSidebar.vue +11 -0
- package/src/panel/components/WorkspaceConfigSidebar.vue +1 -0
- package/src/panel/components/WorkspaceProjectNav.vue +36 -8
- package/src/panel/components/config/ConfigColorField.vue +33 -9
- package/src/panel/components/config/PanelConfigAudioSection.vue +32 -18
- package/src/panel/components/config/PanelConfigImageSection.vue +13 -9
- package/src/panel/components/config/PanelConfigScene3dSection.vue +505 -0
- package/src/panel/components/config/PanelConfigStyleSections.vue +13 -9
- package/src/panel/components/config/PanelConfigTextSection.vue +20 -6
- package/src/panel/components/config/PanelConfigVideoSection.vue +13 -9
- package/src/panel/components/config/shared.ts +16 -21
- package/src/panel/components/elementsLayerNodes.ts +64 -59
- package/src/panel/components/materialSidebarData.ts +2 -0
- package/src/panel/components/scope-config/ScopeTemplateAutocompleteHost.vue +1 -1
- package/src/panel/components/table/PanelConfigTableSection.vue +39 -25
- package/src/panel/components/table/TableNodeContent.vue +36 -15
- package/src/panel/hooks/usePanelElements.ts +13 -1
- package/src/panel/hooks/useWorkspaceProjects.ts +128 -57
- package/src/panel/library/workspace-project-cache.ts +7 -2
- package/src/panel/library/workspace-project-db.ts +29 -11
- package/src/panel/library/workspace-snapshot.ts +21 -4
- package/src/panel/scope/view-scope-store.ts +8 -0
- package/src/panel/types.ts +4 -2
- package/src/panel/utils/async-work.ts +153 -0
- package/src/panel/utils/gridPlacement.ts +29 -0
- package/src/panel/utils/panelElementDefaults.ts +37 -1
- package/src/panel/utils/scope-autocomplete.ts +0 -114
|
@@ -41,15 +41,19 @@ function patchStyle(patch: Partial<PanelElementStyle>) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
async function handleUploadBackgroundImage(file: File) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
44
|
+
try {
|
|
45
|
+
const base64 = await readFileAsDataUrl(file, msgs().readImageFailed);
|
|
46
|
+
patchStyle({ backgroundImage: `url("${base64}")` });
|
|
47
|
+
uploadStatus.value = t("panel.config.uploadWrittenBase64");
|
|
48
|
+
const url = await uploadFileToRemote(file);
|
|
49
|
+
if (url) {
|
|
50
|
+
patchStyle({ backgroundImageRemoteUrl: url });
|
|
51
|
+
uploadStatus.value = t("panel.config.uploadServerAndBase64");
|
|
52
|
+
} else {
|
|
53
|
+
uploadStatus.value = t("panel.config.uploadServerFailedKeepBase64");
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
uploadStatus.value = error instanceof Error ? error.message : msgs().readImageFailed;
|
|
53
57
|
}
|
|
54
58
|
}
|
|
55
59
|
|
|
@@ -3,6 +3,7 @@ import { useI18n } from "@arronqzy/i18n/vue";
|
|
|
3
3
|
import { onMounted, ref, watch } from "vue";
|
|
4
4
|
import { Checkbox, Input, InputNumber, Select } from "ant-design-vue";
|
|
5
5
|
import type { PanelElement } from "../../types";
|
|
6
|
+
import { cssTextLineHeight, cssTextAlignStyle } from "../../utils/panelElementDefaults";
|
|
6
7
|
import ConfigColorField from "./ConfigColorField.vue";
|
|
7
8
|
import ConfigFieldGroup from "./ConfigFieldGroup.vue";
|
|
8
9
|
import ConfigSection from "./ConfigSection.vue";
|
|
@@ -87,15 +88,16 @@ watch(() => [props.element.id, props.element.textHtml], syncEditorHtml);
|
|
|
87
88
|
</div>
|
|
88
89
|
<div
|
|
89
90
|
ref="textEditorRef"
|
|
90
|
-
|
|
91
|
+
data-panel-user-text=""
|
|
92
|
+
class="min-h-[120px] rounded border border-gray-200 bg-white px-2 py-1.5 leading-6 outline-none"
|
|
91
93
|
:contenteditable="isEditable"
|
|
92
94
|
:style="{
|
|
93
95
|
fontFamily: element.textFontFamily || undefined,
|
|
94
96
|
fontSize: element.textFontSize ? `${element.textFontSize}px` : undefined,
|
|
95
97
|
fontWeight: element.textFontWeight || undefined,
|
|
96
98
|
color: element.textColor || undefined,
|
|
97
|
-
lineHeight:
|
|
98
|
-
|
|
99
|
+
lineHeight: cssTextLineHeight(element.textLineHeight),
|
|
100
|
+
...cssTextAlignStyle(element.textAlign),
|
|
99
101
|
}"
|
|
100
102
|
@input="(e) => {
|
|
101
103
|
const nextHtml = (e.target as HTMLDivElement).innerHTML;
|
|
@@ -156,20 +158,32 @@ watch(() => [props.element.id, props.element.textHtml], syncEditorHtml);
|
|
|
156
158
|
>
|
|
157
159
|
<Select.Option value="left">{{ t("panel.config.alignLeft") }}</Select.Option>
|
|
158
160
|
<Select.Option value="center">{{ t("panel.config.alignCenter") }}</Select.Option>
|
|
161
|
+
<Select.Option value="middle">{{ t("panel.config.alignMiddle") }}</Select.Option>
|
|
159
162
|
<Select.Option value="right">{{ t("panel.config.alignRight") }}</Select.Option>
|
|
160
163
|
<Select.Option value="justify">{{ t("panel.config.alignJustify") }}</Select.Option>
|
|
161
164
|
</Select>
|
|
162
165
|
</label>
|
|
163
166
|
<label class="block space-y-1">
|
|
164
|
-
<div
|
|
167
|
+
<div class="flex items-center justify-between gap-2">
|
|
168
|
+
<span>{{ t("panel.config.lineHeight") }}</span>
|
|
169
|
+
<label class="flex items-center gap-1 font-normal">
|
|
170
|
+
<Checkbox
|
|
171
|
+
:checked="element.textLineHeight === 'auto'"
|
|
172
|
+
:disabled="!isEditable"
|
|
173
|
+
@update:checked="(v) => patch({ textLineHeight: v ? 'auto' : 1.6 })"
|
|
174
|
+
/>
|
|
175
|
+
<span>{{ t("panel.config.lineHeightAuto") }}</span>
|
|
176
|
+
</label>
|
|
177
|
+
</div>
|
|
165
178
|
<InputNumber
|
|
166
179
|
size="small"
|
|
167
180
|
class="w-full"
|
|
168
181
|
:min="1"
|
|
169
182
|
:max="3"
|
|
170
183
|
:step="0.1"
|
|
171
|
-
:value="element.textLineHeight ?? 1.6"
|
|
172
|
-
:disabled="!isEditable"
|
|
184
|
+
:value="element.textLineHeight === 'auto' ? undefined : (element.textLineHeight ?? 1.6)"
|
|
185
|
+
:disabled="!isEditable || element.textLineHeight === 'auto'"
|
|
186
|
+
:placeholder="t('panel.config.lineHeightAuto')"
|
|
173
187
|
@update:value="(v) => {
|
|
174
188
|
const n = Number(v);
|
|
175
189
|
if (!Number.isNaN(n)) patch({ textLineHeight: Math.min(3, Math.max(1, n)) });
|
|
@@ -35,15 +35,19 @@ function patch(patch: Partial<PanelElement>) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
async function handleUploadVideoFile(file: File) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
try {
|
|
39
|
+
const base64 = await readFileAsDataUrl(file, msgs().readVideoFailed, "video");
|
|
40
|
+
patch({ videoSrc: base64 });
|
|
41
|
+
videoStatus.value = msgs().videoLocalSaved;
|
|
42
|
+
const url = await uploadFileToRemote(file);
|
|
43
|
+
if (url) {
|
|
44
|
+
patch({ videoRemoteUrl: url });
|
|
45
|
+
videoStatus.value = msgs().videoRemoteUploaded;
|
|
46
|
+
} else {
|
|
47
|
+
videoStatus.value = msgs().videoServerUploadFailed;
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
videoStatus.value = error instanceof Error ? error.message : msgs().readVideoFailed;
|
|
47
51
|
}
|
|
48
52
|
}
|
|
49
53
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { PanelChartConfig } from "../../types";
|
|
2
|
+
import { resolveLocale, tForLocale } from "@arronqzy/i18n";
|
|
3
|
+
import { runBusyTask } from "../../utils/async-work";
|
|
2
4
|
|
|
3
5
|
export function mergeOptionPatch(
|
|
4
6
|
base: Record<string, unknown> | undefined,
|
|
@@ -38,29 +40,22 @@ export function sectionMatchesSearch(
|
|
|
38
40
|
);
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
export
|
|
42
|
-
file: File,
|
|
43
|
-
errorMessage: string
|
|
44
|
-
): Promise<string> {
|
|
45
|
-
return new Promise((resolve, reject) => {
|
|
46
|
-
const reader = new FileReader();
|
|
47
|
-
reader.onload = () => resolve(String(reader.result ?? ""));
|
|
48
|
-
reader.onerror = () => reject(new Error(errorMessage));
|
|
49
|
-
reader.readAsDataURL(file);
|
|
50
|
-
});
|
|
51
|
-
}
|
|
43
|
+
export { readFileAsDataUrl } from "../../utils/async-work";
|
|
52
44
|
|
|
53
45
|
export async function uploadFileToRemote(file: File): Promise<string | undefined> {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
46
|
+
const t = tForLocale(resolveLocale());
|
|
47
|
+
return runBusyTask(t("common.uploadingFile"), async () => {
|
|
48
|
+
try {
|
|
49
|
+
const form = new FormData();
|
|
50
|
+
form.append("file", file);
|
|
51
|
+
const resp = await fetch("/api/upload", { method: "POST", body: form });
|
|
52
|
+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
53
|
+
const data = (await resp.json()) as { url?: string };
|
|
54
|
+
return data.url;
|
|
55
|
+
} catch {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
64
59
|
}
|
|
65
60
|
|
|
66
61
|
export function patchChart(
|
|
@@ -15,7 +15,9 @@ import * as echarts from "echarts";
|
|
|
15
15
|
import type { PanelElement } from "../types";
|
|
16
16
|
import { buildChartOption, CHART_TYPES } from "../utils/chartOptionBuilder";
|
|
17
17
|
import { PREVIEW_LAYOUT_EVENT } from "../utils/panelStateIO";
|
|
18
|
+
import { cssTextLineHeight, cssTextAlignStyle } from "../utils/panelElementDefaults";
|
|
18
19
|
import TableNodeContent from "./table/TableNodeContent.vue";
|
|
20
|
+
import Scene3dNodeContent from "@arronqzy/view-scene3d/vue";
|
|
19
21
|
|
|
20
22
|
export { CHART_TYPES };
|
|
21
23
|
|
|
@@ -74,8 +76,8 @@ export const TextNodeContent = defineComponent({
|
|
|
74
76
|
fontSize: p.element.textFontSize ? `${p.element.textFontSize}px` : undefined,
|
|
75
77
|
fontWeight: p.element.textFontWeight || undefined,
|
|
76
78
|
color: p.element.textColor || undefined,
|
|
77
|
-
lineHeight:
|
|
78
|
-
|
|
79
|
+
lineHeight: cssTextLineHeight(p.element.textLineHeight),
|
|
80
|
+
...cssTextAlignStyle(p.element.textAlign),
|
|
79
81
|
}));
|
|
80
82
|
watch(
|
|
81
83
|
html,
|
|
@@ -89,7 +91,8 @@ export const TextNodeContent = defineComponent({
|
|
|
89
91
|
return () =>
|
|
90
92
|
h("div", {
|
|
91
93
|
ref: nodeRef,
|
|
92
|
-
|
|
94
|
+
"data-panel-user-text": "",
|
|
95
|
+
class: "h-full w-full overflow-auto break-words p-2 outline-none",
|
|
93
96
|
style: textStyle.value,
|
|
94
97
|
contentEditable: p.editable,
|
|
95
98
|
onInput: (e: Event) => emit("change", (e.currentTarget as HTMLDivElement).innerHTML),
|
|
@@ -127,31 +130,10 @@ export const GridNodeContent = defineComponent({
|
|
|
127
130
|
});
|
|
128
131
|
return set;
|
|
129
132
|
});
|
|
130
|
-
const occupiedBlocks = computed(() =>
|
|
131
|
-
p.allElements
|
|
132
|
-
.filter(
|
|
133
|
-
(el) =>
|
|
134
|
-
el.parentGridId === p.element.id &&
|
|
135
|
-
el.layerId === p.element.layerId &&
|
|
136
|
-
el.gridSlotIndex !== undefined
|
|
137
|
-
)
|
|
138
|
-
.map((el) => {
|
|
139
|
-
const start = Math.max(0, Math.floor(el.gridSlotIndex ?? 0));
|
|
140
|
-
const baseRow = Math.floor(start / cols.value);
|
|
141
|
-
const baseCol = start % cols.value;
|
|
142
|
-
const rowSpan = Math.max(1, Math.min(rows.value - baseRow, Math.floor(el.gridRowSpan ?? 1)));
|
|
143
|
-
const colSpan = Math.max(1, Math.min(cols.value - baseCol, Math.floor(el.gridColSpan ?? 1)));
|
|
144
|
-
return {
|
|
145
|
-
id: el.id,
|
|
146
|
-
rowStart: baseRow + 1,
|
|
147
|
-
rowEnd: baseRow + rowSpan + 1,
|
|
148
|
-
colStart: baseCol + 1,
|
|
149
|
-
colEnd: baseCol + colSpan + 1,
|
|
150
|
-
};
|
|
151
|
-
})
|
|
152
|
-
);
|
|
153
133
|
return () => {
|
|
154
134
|
if (p.previewMode) return h("div", { class: "relative h-full w-full" });
|
|
135
|
+
const total = rows.value * cols.value;
|
|
136
|
+
const emptyCount = total - occupied.value.size;
|
|
155
137
|
const gridStyle = {
|
|
156
138
|
display: "grid",
|
|
157
139
|
gridTemplateColumns: `repeat(${cols.value}, minmax(0, 1fr))`,
|
|
@@ -160,40 +142,58 @@ export const GridNodeContent = defineComponent({
|
|
|
160
142
|
padding: `${padding.value}px`,
|
|
161
143
|
boxSizing: "border-box" as const,
|
|
162
144
|
};
|
|
163
|
-
return h(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
145
|
+
return h(
|
|
146
|
+
"div",
|
|
147
|
+
{
|
|
148
|
+
class: "pointer-events-none relative h-full w-full overflow-hidden rounded-md",
|
|
149
|
+
style:
|
|
150
|
+
emptyCount > 0
|
|
151
|
+
? {
|
|
152
|
+
border: "2px dashed rgba(14, 165, 233, 0.7)",
|
|
153
|
+
background: "rgba(14, 165, 233, 0.08)",
|
|
154
|
+
}
|
|
155
|
+
: undefined,
|
|
156
|
+
},
|
|
157
|
+
[
|
|
158
|
+
h(
|
|
159
|
+
"div",
|
|
160
|
+
{ class: "h-full w-full", style: gridStyle },
|
|
161
|
+
Array.from({ length: total }).map((_, idx) => {
|
|
162
|
+
const filled = occupied.value.has(idx);
|
|
163
|
+
return h(
|
|
164
|
+
"div",
|
|
165
|
+
{
|
|
166
|
+
key: idx,
|
|
167
|
+
class: filled
|
|
168
|
+
? "pointer-events-none"
|
|
169
|
+
: "pointer-events-auto flex flex-col items-center justify-center gap-0.5 rounded-md text-[11px] font-semibold",
|
|
170
|
+
style: filled
|
|
171
|
+
? undefined
|
|
172
|
+
: {
|
|
173
|
+
border: "2px dashed rgba(2, 132, 199, 0.85)",
|
|
174
|
+
background: "rgba(186, 230, 253, 0.72)",
|
|
175
|
+
color: "rgba(3, 105, 161, 0.95)",
|
|
176
|
+
},
|
|
177
|
+
title: filled
|
|
178
|
+
? t("panel.config.slotOccupied", { n: idx + 1 })
|
|
179
|
+
: t("panel.config.slotEmpty", { n: idx + 1 }),
|
|
180
|
+
},
|
|
181
|
+
filled ? undefined : h("span", String(idx + 1))
|
|
182
|
+
);
|
|
193
183
|
})
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
|
|
184
|
+
),
|
|
185
|
+
occupied.value.size === 0
|
|
186
|
+
? h(
|
|
187
|
+
"div",
|
|
188
|
+
{
|
|
189
|
+
class:
|
|
190
|
+
"pointer-events-none absolute bottom-1.5 left-0 right-0 text-center text-[11px] font-medium text-sky-800/90",
|
|
191
|
+
},
|
|
192
|
+
t("panel.config.gridDropHint")
|
|
193
|
+
)
|
|
194
|
+
: null,
|
|
195
|
+
]
|
|
196
|
+
);
|
|
197
197
|
};
|
|
198
198
|
},
|
|
199
199
|
});
|
|
@@ -788,6 +788,11 @@ export const ReferenceNodeContent = defineComponent({
|
|
|
788
788
|
})
|
|
789
789
|
: node.materialType === "geometry"
|
|
790
790
|
? h(GeometryNodeContent, { element: node })
|
|
791
|
+
: node.materialType === "scene3d"
|
|
792
|
+
? h(Scene3dNodeContent, {
|
|
793
|
+
config: node.scene3d,
|
|
794
|
+
previewMode: p.previewMode,
|
|
795
|
+
})
|
|
791
796
|
: node.materialType === "table"
|
|
792
797
|
? h(TableNodeContent, { element: node })
|
|
793
798
|
: h("div", { class: "h-full w-full" }),
|
|
@@ -31,6 +31,7 @@ const MATERIAL_LABEL_KEYS: Record<string, string> = {
|
|
|
31
31
|
audio: "panel.material.audio",
|
|
32
32
|
reference: "panel.material.reference",
|
|
33
33
|
geometry: "panel.material.geometry",
|
|
34
|
+
scene3d: "panel.material.scene3d",
|
|
34
35
|
table: "panel.material.table",
|
|
35
36
|
};
|
|
36
37
|
|
|
@@ -65,6 +66,7 @@ export function getDefaultCategories(t: TranslateFn): MaterialCategory[] {
|
|
|
65
66
|
{ id: "text", title: t("panel.material.text") },
|
|
66
67
|
{ id: "table", title: t("panel.material.table") },
|
|
67
68
|
{ id: "geometry", title: t("panel.material.geometry") },
|
|
69
|
+
{ id: "scene3d", title: t("panel.material.scene3d") },
|
|
68
70
|
{ id: "grid", title: t("panel.material.grid") },
|
|
69
71
|
{ id: "image", title: t("panel.material.image") },
|
|
70
72
|
{ id: "reference", title: t("panel.material.reference") },
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
getScopeAutocompleteSuggestions,
|
|
15
15
|
parseScopeAutocomplete,
|
|
16
16
|
type ScopeAutocompleteState,
|
|
17
|
-
} from "
|
|
17
|
+
} from "@arronqzy/blueprint-dsl";
|
|
18
18
|
|
|
19
19
|
type DropdownState = {
|
|
20
20
|
input: HTMLInputElement | HTMLTextAreaElement;
|
|
@@ -27,6 +27,7 @@ import ConfigSection from "../config/ConfigSection.vue";
|
|
|
27
27
|
import ConfigHintIcon from "../ConfigHintIcon.vue";
|
|
28
28
|
import ConditionEditor from "./ConditionEditor.vue";
|
|
29
29
|
import JsonCodeEditor from "./JsonCodeEditor.vue";
|
|
30
|
+
import { readFileAsDataUrl, runBusyTask } from "../../utils/async-work";
|
|
30
31
|
|
|
31
32
|
const { t } = useI18n();
|
|
32
33
|
|
|
@@ -256,29 +257,27 @@ const imageUploadHint = ref("");
|
|
|
256
257
|
|
|
257
258
|
async function uploadImageForColumn(index: number, file: File) {
|
|
258
259
|
try {
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
imageUploadHint.value = t("panel.config.uploadServerAndBase64");
|
|
260
|
+
await runBusyTask(t("common.uploadingFile"), async () => {
|
|
261
|
+
const base64 = await readFileAsDataUrl(file, t("panel.messages.readImageFailed"), "image");
|
|
262
|
+
patchWidgetProps(index, { imageUrlMode: "static", imageUrl: base64 });
|
|
263
|
+
imageUploadHint.value = t("panel.config.uploadWrittenBase64");
|
|
264
|
+
try {
|
|
265
|
+
const form = new FormData();
|
|
266
|
+
form.append("file", file);
|
|
267
|
+
const resp = await fetch("/api/upload", { method: "POST", body: form });
|
|
268
|
+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
269
|
+
const data = (await resp.json()) as { url?: string };
|
|
270
|
+
if (data.url) {
|
|
271
|
+
patchWidgetProps(index, { imageUrlMode: "static", imageUrl: data.url });
|
|
272
|
+
imageUploadHint.value = t("panel.config.uploadServerAndBase64");
|
|
273
|
+
}
|
|
274
|
+
} catch {
|
|
275
|
+
imageUploadHint.value = t("panel.config.uploadServerFailedKeepBase64");
|
|
276
276
|
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
imageUploadHint.value = t("panel.messages.readImageFailed");
|
|
277
|
+
});
|
|
278
|
+
} catch (error) {
|
|
279
|
+
imageUploadHint.value =
|
|
280
|
+
error instanceof Error ? error.message : t("panel.messages.readImageFailed");
|
|
282
281
|
}
|
|
283
282
|
}
|
|
284
283
|
|
|
@@ -785,7 +784,7 @@ function patchProgressRuleWhen(ruleIndex: number, when: Condition) {
|
|
|
785
784
|
</ConfigHintIcon>
|
|
786
785
|
</div>
|
|
787
786
|
|
|
788
|
-
<template v-if="(selectedColumn.column.widget ?? 'text')
|
|
787
|
+
<template v-if="(selectedColumn.column.widget ?? 'text') !== 'image'">
|
|
789
788
|
<div class="grid grid-cols-2 gap-2">
|
|
790
789
|
<label class="block space-y-1">
|
|
791
790
|
<div>{{ t("panel.config.tableTextFontSize") }}</div>
|
|
@@ -934,6 +933,9 @@ function patchProgressRuleWhen(ruleIndex: number, when: Condition) {
|
|
|
934
933
|
</Select>
|
|
935
934
|
</label>
|
|
936
935
|
</div>
|
|
936
|
+
</template>
|
|
937
|
+
|
|
938
|
+
<template v-if="(selectedColumn.column.widget ?? 'text') === 'text'">
|
|
937
939
|
<label class="block space-y-1">
|
|
938
940
|
<div class="inline-flex items-center gap-1">
|
|
939
941
|
{{ t("panel.config.tableActionOnClick") }}
|
|
@@ -1867,11 +1869,21 @@ function patchProgressRuleWhen(ruleIndex: number, when: Condition) {
|
|
|
1867
1869
|
/>
|
|
1868
1870
|
</label>
|
|
1869
1871
|
<div class="grid grid-cols-2 gap-2">
|
|
1872
|
+
<ConfigColorField
|
|
1873
|
+
:label="t('panel.config.tableBodyBg')"
|
|
1874
|
+
:value="(table.tableStyle?.backgroundColor as string) ?? ''"
|
|
1875
|
+
:disabled="!isEditable"
|
|
1876
|
+
allow-transparent
|
|
1877
|
+
fallback-hex="#ffffff"
|
|
1878
|
+
@update:value="(v) => updateTableStyle({ backgroundColor: v || undefined })"
|
|
1879
|
+
/>
|
|
1870
1880
|
<ConfigColorField
|
|
1871
1881
|
:label="t('panel.config.tableHeaderBg')"
|
|
1872
|
-
:value="(table.headerStyle?.backgroundColor as string) ?? '
|
|
1882
|
+
:value="(table.headerStyle?.backgroundColor as string) ?? ''"
|
|
1873
1883
|
:disabled="!isEditable"
|
|
1874
|
-
|
|
1884
|
+
allow-transparent
|
|
1885
|
+
fallback-hex="#f3f4f6"
|
|
1886
|
+
@update:value="(v) => updateHeaderStyle({ backgroundColor: v || undefined })"
|
|
1875
1887
|
/>
|
|
1876
1888
|
<label class="block space-y-1.5">
|
|
1877
1889
|
<div>{{ t("panel.config.tableHeaderFontSize") }}</div>
|
|
@@ -1939,6 +1951,8 @@ function patchProgressRuleWhen(ruleIndex: number, when: Condition) {
|
|
|
1939
1951
|
:label="t('panel.config.tableBgColor')"
|
|
1940
1952
|
:value="(rule.style.backgroundColor as string) ?? '#eff6ff'"
|
|
1941
1953
|
:disabled="!isEditable"
|
|
1954
|
+
allow-transparent
|
|
1955
|
+
fallback-hex="#eff6ff"
|
|
1942
1956
|
@update:value="(v) => patchRowStyleRule(ruleIndex, { backgroundColor: v || '#eff6ff' })"
|
|
1943
1957
|
/>
|
|
1944
1958
|
</div>
|
|
@@ -8,8 +8,11 @@ import {
|
|
|
8
8
|
resolveCellDisplay,
|
|
9
9
|
resolveRowDisplay,
|
|
10
10
|
stylePropsToCss,
|
|
11
|
+
tableTextStyleToCss,
|
|
11
12
|
resolveRawTableInput,
|
|
12
13
|
resolveProgressDisplay,
|
|
14
|
+
resolveTableBodyBackground,
|
|
15
|
+
resolveTableHeaderBackground,
|
|
13
16
|
type PanelTableConfig,
|
|
14
17
|
type NormalizedColumn,
|
|
15
18
|
type NormalizedRow,
|
|
@@ -102,7 +105,7 @@ const headerTextColor = computed(
|
|
|
102
105
|
);
|
|
103
106
|
|
|
104
107
|
const rootStyle = computed(() => ({
|
|
105
|
-
background: (config.value.tableStyle?.backgroundColor
|
|
108
|
+
background: resolveTableBodyBackground(config.value.tableStyle?.backgroundColor),
|
|
106
109
|
border:
|
|
107
110
|
config.value.tableStyle?.borderWidth != null
|
|
108
111
|
? `${config.value.tableStyle.borderWidth}px solid ${config.value.tableStyle.borderColor ?? "rgba(0,0,0,0.12)"}`
|
|
@@ -126,7 +129,7 @@ const headerRowStyle = computed(() => ({
|
|
|
126
129
|
: ("sticky" as const),
|
|
127
130
|
top: 0,
|
|
128
131
|
zIndex: 2,
|
|
129
|
-
background: (config.value.headerStyle?.backgroundColor
|
|
132
|
+
background: resolveTableHeaderBackground(config.value.headerStyle?.backgroundColor),
|
|
130
133
|
borderBottom: "1px solid rgba(0,0,0,0.1)",
|
|
131
134
|
...headerStyle.value,
|
|
132
135
|
color: headerTextColor.value,
|
|
@@ -145,7 +148,8 @@ function headerJustify(align: NormalizedColumn["align"]) {
|
|
|
145
148
|
function cellCommonStyle(cell: CellDisplay): Record<string, string | number> {
|
|
146
149
|
const style = stylePropsToCss(cell.style);
|
|
147
150
|
const textOverflow = cell.widgetProps?.textStyle?.overflow ?? "ellipsis";
|
|
148
|
-
const isWrap =
|
|
151
|
+
const isWrap =
|
|
152
|
+
textOverflow === "wrap" && cell.widget !== "image" && cell.widget !== "progress";
|
|
149
153
|
return {
|
|
150
154
|
...style,
|
|
151
155
|
overflow: "hidden",
|
|
@@ -189,6 +193,7 @@ const TableCellView = defineComponent({
|
|
|
189
193
|
return (): VNode => {
|
|
190
194
|
const cell = p.cell;
|
|
191
195
|
const common = cellCommonStyle(cell);
|
|
196
|
+
const textCss = tableTextStyleToCss(cell.widgetProps?.textStyle);
|
|
192
197
|
let node: VNode;
|
|
193
198
|
|
|
194
199
|
if (cell.widget === "tag" || cell.widget === "badge") {
|
|
@@ -208,6 +213,7 @@ const TableCellView = defineComponent({
|
|
|
208
213
|
lineHeight: "20px",
|
|
209
214
|
color: "#fff",
|
|
210
215
|
background: bg,
|
|
216
|
+
...textCss,
|
|
211
217
|
},
|
|
212
218
|
},
|
|
213
219
|
[
|
|
@@ -243,6 +249,7 @@ const TableCellView = defineComponent({
|
|
|
243
249
|
textDecoration: "underline",
|
|
244
250
|
overflow: "hidden",
|
|
245
251
|
textOverflow: "ellipsis",
|
|
252
|
+
...textCss,
|
|
246
253
|
},
|
|
247
254
|
},
|
|
248
255
|
cell.text || cell.href
|
|
@@ -254,7 +261,14 @@ const TableCellView = defineComponent({
|
|
|
254
261
|
const trackColor = cell.color ?? "#3b82f6";
|
|
255
262
|
const label = h(
|
|
256
263
|
"span",
|
|
257
|
-
{
|
|
264
|
+
{
|
|
265
|
+
style: {
|
|
266
|
+
fontSize: 11,
|
|
267
|
+
color: "rgba(0,0,0,0.55)",
|
|
268
|
+
flex: "0 0 auto",
|
|
269
|
+
...textCss,
|
|
270
|
+
},
|
|
271
|
+
},
|
|
258
272
|
cell.text
|
|
259
273
|
);
|
|
260
274
|
|
|
@@ -295,8 +309,16 @@ const TableCellView = defineComponent({
|
|
|
295
309
|
y: "50%",
|
|
296
310
|
"dominant-baseline": "central",
|
|
297
311
|
"text-anchor": "middle",
|
|
298
|
-
"font-size":
|
|
299
|
-
|
|
312
|
+
"font-size":
|
|
313
|
+
typeof textCss.fontSize === "number"
|
|
314
|
+
? textCss.fontSize
|
|
315
|
+
: Math.max(8, Math.round(size * 0.32)),
|
|
316
|
+
fill: typeof textCss.color === "string" ? textCss.color : "rgba(0,0,0,0.65)",
|
|
317
|
+
"font-family":
|
|
318
|
+
typeof textCss.fontFamily === "string" ? textCss.fontFamily : undefined,
|
|
319
|
+
"font-weight": textCss.fontWeight,
|
|
320
|
+
"font-style":
|
|
321
|
+
typeof textCss.fontStyle === "string" ? textCss.fontStyle : undefined,
|
|
300
322
|
},
|
|
301
323
|
String(Math.round(pct))
|
|
302
324
|
),
|
|
@@ -343,7 +365,7 @@ const TableCellView = defineComponent({
|
|
|
343
365
|
},
|
|
344
366
|
draggable: false,
|
|
345
367
|
})
|
|
346
|
-
: h("span", { style: { fontSize: 11, opacity: 0.5 } }, "—"),
|
|
368
|
+
: h("span", { style: { fontSize: 11, opacity: 0.5, ...textCss } }, "—"),
|
|
347
369
|
]);
|
|
348
370
|
} else if (cell.widget === "boolean") {
|
|
349
371
|
const on = Boolean(cell.booleanValue);
|
|
@@ -392,22 +414,21 @@ const TableCellView = defineComponent({
|
|
|
392
414
|
}),
|
|
393
415
|
]
|
|
394
416
|
),
|
|
395
|
-
h("span", { style: { marginLeft: 6, fontSize: 12 } }, cell.text),
|
|
417
|
+
h("span", { style: { marginLeft: 6, fontSize: 12, ...textCss } }, cell.text),
|
|
396
418
|
]);
|
|
397
419
|
} else {
|
|
398
|
-
const textStyle = cell.widgetProps?.textStyle;
|
|
399
420
|
const canClickText = Boolean(cell.widgetProps?.actions?.onClickBlueprintNodeId);
|
|
400
421
|
node = h(
|
|
401
422
|
"div",
|
|
402
423
|
{
|
|
403
424
|
style: {
|
|
404
425
|
...common,
|
|
405
|
-
|
|
406
|
-
fontStyle: textStyle?.fontStyle,
|
|
407
|
-
textDecoration:
|
|
408
|
-
textStyle?.textDecoration === "none" ? undefined : textStyle?.textDecoration,
|
|
426
|
+
...textCss,
|
|
409
427
|
cursor: canClickText ? "pointer" : undefined,
|
|
410
|
-
color:
|
|
428
|
+
color:
|
|
429
|
+
canClickText && !(common as { color?: string }).color && !textCss.color
|
|
430
|
+
? "#2563eb"
|
|
431
|
+
: (textCss.color as string | undefined) ?? (common as { color?: string }).color,
|
|
411
432
|
},
|
|
412
433
|
onClick: (e: Event) => {
|
|
413
434
|
if (!canClickText) return;
|
|
@@ -504,7 +525,7 @@ const TableRowView = defineComponent({
|
|
|
504
525
|
</div>
|
|
505
526
|
</div>
|
|
506
527
|
|
|
507
|
-
<div ref="parentRef" class="min-h-0 flex-1 overflow-auto">
|
|
528
|
+
<div ref="parentRef" class="min-h-0 flex-1 overflow-auto" style="background: transparent">
|
|
508
529
|
<div
|
|
509
530
|
v-if="model.rows.length === 0"
|
|
510
531
|
class="flex h-full flex-col items-center justify-center gap-2 px-3 text-center text-xs text-gray-400"
|