@arronqzy/vue-view 0.1.19 → 0.1.21
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/README.md +25 -21
- package/package.json +8 -8
- package/src/index.ts +2 -0
- package/src/panel/VueViewPanel.vue +109 -3
- package/src/panel/components/PanelConfigSidebar.vue +45 -11
- package/src/panel/components/config/ConfigFieldGroup.vue +27 -3
- package/src/panel/components/config/PanelConfigAudioSection.vue +1 -0
- package/src/panel/components/config/PanelConfigChartSection.vue +3 -0
- package/src/panel/components/config/PanelConfigGeometrySection.vue +2 -0
- package/src/panel/components/config/PanelConfigImageSection.vue +1 -0
- package/src/panel/components/config/PanelConfigNodeInfo.vue +1 -0
- package/src/panel/components/config/PanelConfigReferenceSection.vue +1 -0
- package/src/panel/components/config/PanelConfigStyleSections.vue +1 -0
- package/src/panel/components/config/PanelConfigTextSection.vue +1 -0
- package/src/panel/components/config/PanelConfigViewportSection.vue +1 -0
- package/src/panel/components/scope-config/ScopeTemplateAutocompleteHost.vue +6 -9
- package/src/panel/components/scope-config/ScopeTemplateUsageHint.vue +1 -0
- package/src/panel/library/event-subscription.ts +20 -0
- package/src/panel/utils/scope-template.ts +20 -8
package/README.md
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
# @arronqzy/vue-view
|
|
2
2
|
|
|
3
|
-
Vue 3
|
|
3
|
+
Vue 3 视图编辑器,功能对齐 `@arronqzy/react-view`,组件用 Ant Design Vue。大多数 Vue 宿主应直接用 [`@arronqzy/abuilder-vue`](../abuilder-vue/README.md)。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+

|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
7
|
+
## 能力
|
|
8
|
+
|
|
9
|
+
- 无限平移 / 缩放(Infinite Viewer)、Moveable、Selecto 框选
|
|
10
|
+
- 图表、文本、表格、图片、音视频、几何、网格、三维、引用节点
|
|
11
|
+
- 多图层、映射图层、主图层、锁定与合并
|
|
12
|
+
- 按物料类型的配置侧栏,支持 Scope 模版
|
|
13
|
+
- IndexedDB 多项目、导入导出、跨标签同步
|
|
14
|
+
- 分屏蓝图(`@arronqzy/vue-blueprint`)和调试时的 Scope 回写
|
|
15
|
+
- `VueViewOnlinePreview` 只读预览
|
|
16
|
+
|
|
17
|
+
打开蓝图后的分屏和 React 版一样:上面视图,下面节点:
|
|
18
|
+
|
|
19
|
+

|
|
14
20
|
|
|
15
21
|
## 使用
|
|
16
22
|
|
|
@@ -29,7 +35,7 @@ createApp(VueViewPanel).use(Antd).mount("#app");
|
|
|
29
35
|
import { VueViewOnlinePreview, parseOnlinePreviewSearchParams } from "@arronqzy/vue-view";
|
|
30
36
|
```
|
|
31
37
|
|
|
32
|
-
URL
|
|
38
|
+
URL:`?preview=online&projectId=<id>&pid=<instanceId>`。传入 `workspace` 时优先于 `projectId`。
|
|
33
39
|
|
|
34
40
|
### 宿主集成
|
|
35
41
|
|
|
@@ -43,21 +49,19 @@ import {
|
|
|
43
49
|
} from "@arronqzy/vue-view";
|
|
44
50
|
```
|
|
45
51
|
|
|
46
|
-
创建 /
|
|
52
|
+
创建 / 同步发出 `workspace:add` / `workspace:sync`。`parseWorkspaceData` 的 `value` 可作 `initialWorkspace`。
|
|
47
53
|
|
|
48
|
-
|
|
54
|
+
IndexedDB CRUD 与 `getPreviewSnapshot` **不导出**。缩略图只在 React 包实现。
|
|
55
|
+
|
|
56
|
+
## 和 React 版的差别
|
|
49
57
|
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
- `@arronqzy/
|
|
53
|
-
- `@arronqzy/blueprint-dsl` — 蓝图 DSL
|
|
54
|
-
- `ant-design-vue`、`echarts`、`moveable`、`selecto`、`infinite-viewer`
|
|
58
|
+
- UI 是 Ant Design Vue
|
|
59
|
+
- 标尺是 canvas 实现(可换成 `@scena/ruler`)
|
|
60
|
+
- 画布状态仍是共享的 `@arronqzy/rx-store`
|
|
55
61
|
|
|
56
|
-
##
|
|
62
|
+
## 依赖
|
|
57
63
|
|
|
58
|
-
-
|
|
59
|
-
- 标尺使用 canvas 兼容实现(可替换为 `@scena/ruler`)
|
|
60
|
-
- 共享 `@arronqzy/rx-store` 与 `panel/utils` 数据逻辑
|
|
64
|
+
`@arronqzy/rx-store`、`@arronqzy/vue-rx-store`、`@arronqzy/vue-blueprint`、`@arronqzy/blueprint-dsl`、`ant-design-vue`、`echarts`、`moveable`、`selecto`、`infinite-viewer`。
|
|
61
65
|
|
|
62
66
|
## 许可证
|
|
63
67
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arronqzy/vue-view",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"description": "Vue 3 visual view editor for Abuilder (Ant Design Vue)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,13 +23,13 @@
|
|
|
23
23
|
"selecto": "^1.26.3",
|
|
24
24
|
"uuid": "^13.0.0",
|
|
25
25
|
"vue": "^3.5.13",
|
|
26
|
-
"@arronqzy/
|
|
27
|
-
"@arronqzy/blueprint-dsl": "1.0.15",
|
|
26
|
+
"@arronqzy/blueprint-dsl": "1.0.16",
|
|
28
27
|
"@arronqzy/view-scene3d": "0.1.5",
|
|
29
28
|
"@arronqzy/view-table": "0.1.8",
|
|
30
|
-
"@arronqzy/
|
|
31
|
-
"@arronqzy/vue-
|
|
32
|
-
"@arronqzy/vue-
|
|
29
|
+
"@arronqzy/i18n": "0.1.16",
|
|
30
|
+
"@arronqzy/vue-rx-store": "0.1.6",
|
|
31
|
+
"@arronqzy/vue-blueprint": "0.1.16",
|
|
32
|
+
"@arronqzy/rx-store": "1.0.8"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"vue": "^3.4.0"
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"eslint": "^8.57.0",
|
|
40
40
|
"typescript": "^5.5.4",
|
|
41
41
|
"vue-tsc": "^2.2.0",
|
|
42
|
-
"@arronqzy/
|
|
43
|
-
"@arronqzy/
|
|
42
|
+
"@arronqzy/typescript-config": "1.0.0",
|
|
43
|
+
"@arronqzy/eslint-config": "0.0.0"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,8 @@ export type {
|
|
|
22
22
|
AbuilderEventPayloadMap,
|
|
23
23
|
WorkspaceAddEventPayload,
|
|
24
24
|
WorkspaceSyncEventPayload,
|
|
25
|
+
ApiCollectionUploadStartEventPayload,
|
|
26
|
+
ApiCollectionUploadSuccessEventPayload,
|
|
25
27
|
WorkspaceData,
|
|
26
28
|
} from "./panel/library/event-subscription";
|
|
27
29
|
export type {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { computed, onMounted, onUnmounted, ref, shallowRef, watch } from "vue";
|
|
3
3
|
import type { State } from "@arronqzy/rx-store";
|
|
4
4
|
import {
|
|
5
|
+
ApiCollectionManagerDialog,
|
|
5
6
|
BlueprintGraph,
|
|
6
7
|
abortClockNode,
|
|
7
8
|
blueprintDocumentsEqual,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
documentToRunnableGraph,
|
|
13
14
|
downloadBlueprintExport,
|
|
14
15
|
getBlueprintLibraryRecord,
|
|
16
|
+
hydrateApiCollections,
|
|
15
17
|
libraryRecordFromImport,
|
|
16
18
|
listBlueprintLibrary,
|
|
17
19
|
parseBlueprintImportFile,
|
|
@@ -27,7 +29,7 @@ import {
|
|
|
27
29
|
resolveRunnableNodeType,
|
|
28
30
|
resolveViewElementIds,
|
|
29
31
|
} from "@arronqzy/vue-blueprint";
|
|
30
|
-
import { collectArmedViewEventBindings, EVENT_NODE_TYPE, LIFECYCLE_NODE_TYPE, type LibraryBlueprintResolver } from "@arronqzy/blueprint-dsl";
|
|
32
|
+
import { collectArmedViewEventBindings, EVENT_NODE_TYPE, LIFECYCLE_NODE_TYPE, buildApiCollectionSyncPatches, toApiCollectionSyncResult, type LibraryBlueprintResolver } from "@arronqzy/blueprint-dsl";
|
|
31
33
|
import {
|
|
32
34
|
Button,
|
|
33
35
|
Checkbox,
|
|
@@ -49,6 +51,7 @@ import {
|
|
|
49
51
|
useWorkspaceProjects,
|
|
50
52
|
} from "./hooks/useWorkspaceProjects";
|
|
51
53
|
import type { WorkspaceProjectRecord } from "./library/workspace-project-db";
|
|
54
|
+
import { AbuilderEvents, emitAbuilderEvent } from "./library/event-subscription";
|
|
52
55
|
import { type ViewportZoom } from "./viewportZoom";
|
|
53
56
|
import PanelCanvas from "./components/PanelCanvas.vue";
|
|
54
57
|
import ElementsLayer from "./components/ElementsLayer.vue";
|
|
@@ -83,10 +86,26 @@ const props = withDefaults(
|
|
|
83
86
|
initialZoom?: number;
|
|
84
87
|
nameSpace?: string | null;
|
|
85
88
|
initialWorkspace?: WorkspaceProjectRecord | null;
|
|
89
|
+
/** 顶栏「设置 → 关于」展示的 npm 包名 */
|
|
90
|
+
packageName?: string | null;
|
|
91
|
+
/** 顶栏「设置 → 关于」展示的包版本号 */
|
|
92
|
+
packageVersion?: string | null;
|
|
86
93
|
}>(),
|
|
87
|
-
{
|
|
94
|
+
{
|
|
95
|
+
initialZoom: 1,
|
|
96
|
+
nameSpace: null,
|
|
97
|
+
initialWorkspace: null,
|
|
98
|
+
packageName: null,
|
|
99
|
+
packageVersion: null,
|
|
100
|
+
}
|
|
88
101
|
);
|
|
89
102
|
|
|
103
|
+
const packageVersionText = computed(() => {
|
|
104
|
+
const version = (props.packageVersion ?? "").trim();
|
|
105
|
+
if (!version) return t("panel.menubar.versionUnavailable");
|
|
106
|
+
return t("panel.menubar.versionValue", { version });
|
|
107
|
+
});
|
|
108
|
+
|
|
90
109
|
const {
|
|
91
110
|
elements,
|
|
92
111
|
allElements,
|
|
@@ -142,6 +161,7 @@ const leftWidth = ref(240);
|
|
|
142
161
|
const rightWidth = ref(300);
|
|
143
162
|
const importInputRef = ref<HTMLInputElement | null>(null);
|
|
144
163
|
const blueprintImportInputRef = ref<HTMLInputElement | null>(null);
|
|
164
|
+
const apiCollectionManagerOpen = ref(false);
|
|
145
165
|
|
|
146
166
|
const viewportEl = ref<HTMLDivElement | null>(null);
|
|
147
167
|
const canvasEl = ref<HTMLDivElement | null>(null);
|
|
@@ -375,6 +395,16 @@ async function refreshBlueprintLibrary() {
|
|
|
375
395
|
blueprintLibraryItems.value = await listBlueprintLibrary(props.nameSpace);
|
|
376
396
|
}
|
|
377
397
|
|
|
398
|
+
watch(
|
|
399
|
+
() => props.nameSpace,
|
|
400
|
+
(nameSpace) => {
|
|
401
|
+
void hydrateApiCollections(nameSpace).catch((error) => {
|
|
402
|
+
console.error("[abuilder] hydrate api collections failed", error);
|
|
403
|
+
});
|
|
404
|
+
},
|
|
405
|
+
{ immediate: true }
|
|
406
|
+
);
|
|
407
|
+
|
|
378
408
|
onMounted(() => {
|
|
379
409
|
void refreshBlueprintLibrary();
|
|
380
410
|
});
|
|
@@ -588,6 +618,50 @@ function openBlueprintExport() {
|
|
|
588
618
|
});
|
|
589
619
|
}
|
|
590
620
|
|
|
621
|
+
function handleApiCollectionUploadStart(info: {
|
|
622
|
+
fileNames: string[];
|
|
623
|
+
count: number;
|
|
624
|
+
}) {
|
|
625
|
+
emitAbuilderEvent(AbuilderEvents.apiCollectionUploadStart, info);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function handleApiCollectionUploadSuccess(info: {
|
|
629
|
+
collections: Array<{
|
|
630
|
+
id: string;
|
|
631
|
+
name: string;
|
|
632
|
+
apiCount: number;
|
|
633
|
+
persistLocal: boolean;
|
|
634
|
+
}>;
|
|
635
|
+
}) {
|
|
636
|
+
emitAbuilderEvent(AbuilderEvents.apiCollectionUploadSuccess, info);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function handleApiCollectionSync(record: {
|
|
640
|
+
id: string;
|
|
641
|
+
document: import("@arronqzy/blueprint-dsl").ApiCollectionDocument;
|
|
642
|
+
}) {
|
|
643
|
+
const patches = buildApiCollectionSyncPatches({
|
|
644
|
+
collectionId: record.id,
|
|
645
|
+
document: record.document,
|
|
646
|
+
nodes: blueprintGraph.value.document.nodes
|
|
647
|
+
.filter((node) => node.role === "fetch" || Boolean(node.fetchConfig))
|
|
648
|
+
.map((node) => ({
|
|
649
|
+
id: node.id,
|
|
650
|
+
label: node.label,
|
|
651
|
+
fetchConfig: node.fetchConfig,
|
|
652
|
+
})),
|
|
653
|
+
});
|
|
654
|
+
if (patches.length === 0) return toApiCollectionSyncResult([]);
|
|
655
|
+
let next = blueprintGraph.value;
|
|
656
|
+
for (const patch of patches) {
|
|
657
|
+
next = next.updateNode(patch.nodeId, {
|
|
658
|
+
fetchConfig: patch.fetchConfigPatch as import("@arronqzy/blueprint-dsl").FetchRequestConfig,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
blueprintGraph.value = next;
|
|
662
|
+
return toApiCollectionSyncResult(patches);
|
|
663
|
+
}
|
|
664
|
+
|
|
591
665
|
async function handleBlueprintImportFile(file: File) {
|
|
592
666
|
try {
|
|
593
667
|
await runBusyTask(t("common.importingBlueprint"), async () => {
|
|
@@ -741,6 +815,8 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
|
|
|
741
815
|
<Menu>
|
|
742
816
|
<Menu.Item @click="openBlueprintExport">{{ t("panel.menubar.exportBlueprint") }}</Menu.Item>
|
|
743
817
|
<Menu.Item @click="blueprintImportInputRef?.click()">{{ t("panel.menubar.importBlueprint") }}</Menu.Item>
|
|
818
|
+
<Menu.Divider />
|
|
819
|
+
<Menu.Item @click="apiCollectionManagerOpen = true">{{ t("panel.menubar.apiCollections") }}</Menu.Item>
|
|
744
820
|
</Menu>
|
|
745
821
|
</template>
|
|
746
822
|
</Dropdown>
|
|
@@ -756,7 +832,29 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
|
|
|
756
832
|
@delete-project="handleWorkspaceDeleteProject"
|
|
757
833
|
@preview-project="openOnlinePreviewForProject"
|
|
758
834
|
/>
|
|
759
|
-
|
|
835
|
+
<Dropdown :trigger="['click']">
|
|
836
|
+
<Button type="text" class="!text-white">{{ t("panel.menubar.settings") }}</Button>
|
|
837
|
+
<template #overlay>
|
|
838
|
+
<Menu>
|
|
839
|
+
<Menu.ItemGroup :title="t('panel.theme.language')">
|
|
840
|
+
<Menu.Item :class="{ 'ant-menu-item-selected': locale === 'zh-CN' }" @click="setLocale('zh-CN')">{{ t("panel.theme.zhCN") }}</Menu.Item>
|
|
841
|
+
<Menu.Item :class="{ 'ant-menu-item-selected': locale === 'en-US' }" @click="setLocale('en-US')">{{ t("panel.theme.enUS") }}</Menu.Item>
|
|
842
|
+
</Menu.ItemGroup>
|
|
843
|
+
<Menu.Divider />
|
|
844
|
+
<Menu.SubMenu :title="t('panel.menubar.about')" key="about">
|
|
845
|
+
<Menu.Item
|
|
846
|
+
key="version"
|
|
847
|
+
disabled
|
|
848
|
+
class="!cursor-default !opacity-100"
|
|
849
|
+
:data-abuilder-version="props.packageVersion || undefined"
|
|
850
|
+
>
|
|
851
|
+
{{ packageVersionText }}
|
|
852
|
+
</Menu.Item>
|
|
853
|
+
</Menu.SubMenu>
|
|
854
|
+
</Menu>
|
|
855
|
+
</template>
|
|
856
|
+
</Dropdown>
|
|
857
|
+
<Dropdown :trigger="['click']">
|
|
760
858
|
<Button type="text" class="!text-white">{{ t("panel.theme.language") }}</Button>
|
|
761
859
|
<template #overlay>
|
|
762
860
|
<Menu>
|
|
@@ -983,6 +1081,14 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
|
|
|
983
1081
|
<Input v-model:value="mergeLayerName" :placeholder="t('panel.layers.mergedNamePlaceholderOptional')" />
|
|
984
1082
|
<div class="mt-2 text-xs text-gray-500">{{ t("panel.layers.mergeSelectedCount", { count: mergeSelectedCount }) }}</div>
|
|
985
1083
|
</Modal>
|
|
1084
|
+
|
|
1085
|
+
<ApiCollectionManagerDialog
|
|
1086
|
+
:open="apiCollectionManagerOpen"
|
|
1087
|
+
:on-open-change="(open) => (apiCollectionManagerOpen = open)"
|
|
1088
|
+
:on-upload-start="handleApiCollectionUploadStart"
|
|
1089
|
+
:on-upload-success="handleApiCollectionUploadSuccess"
|
|
1090
|
+
:on-sync-collection="handleApiCollectionSync"
|
|
1091
|
+
/>
|
|
986
1092
|
</Layout>
|
|
987
1093
|
</template>
|
|
988
1094
|
|
|
@@ -62,8 +62,10 @@ const isSearchCollapsed = ref(false);
|
|
|
62
62
|
const sidebarScrollRef = ref<HTMLElement | null>(null);
|
|
63
63
|
const configScrollPosById = new Map<string, number>();
|
|
64
64
|
const activeConfigScrollKey = ref<string | null>(null);
|
|
65
|
+
const expandedSectionsById = new Map<string, Record<string, boolean>>();
|
|
66
|
+
const activeExpandedKey = ref<string | null>(null);
|
|
65
67
|
|
|
66
|
-
const
|
|
68
|
+
const DEFAULT_EXPANDED_SECTIONS: Record<string, boolean> = {
|
|
67
69
|
nodeInfo: true,
|
|
68
70
|
styleBackground: false,
|
|
69
71
|
styleBorder: false,
|
|
@@ -78,6 +80,10 @@ const expandedSections = ref<Record<string, boolean>>({
|
|
|
78
80
|
reference: true,
|
|
79
81
|
imageConfig: true,
|
|
80
82
|
tableConfig: true,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const expandedSections = ref<Record<string, boolean>>({
|
|
86
|
+
...DEFAULT_EXPANDED_SECTIONS,
|
|
81
87
|
});
|
|
82
88
|
|
|
83
89
|
const normalizedSearch = computed(() => configSearch.value.trim().toLowerCase());
|
|
@@ -118,12 +124,39 @@ watch(
|
|
|
118
124
|
}
|
|
119
125
|
activeConfigScrollKey.value = nextKey;
|
|
120
126
|
void nextTick(() => restoreConfigScroll(nextKey));
|
|
127
|
+
|
|
128
|
+
if (prevKey && prevKey !== nextKey) {
|
|
129
|
+
expandedSectionsById.set(prevKey, { ...expandedSections.value });
|
|
130
|
+
}
|
|
131
|
+
activeExpandedKey.value = nextKey;
|
|
132
|
+
if (!nextKey) {
|
|
133
|
+
expandedSections.value = { ...DEFAULT_EXPANDED_SECTIONS };
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const saved = expandedSectionsById.get(nextKey);
|
|
137
|
+
if (saved) {
|
|
138
|
+
expandedSections.value = { ...saved };
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const initial = { ...DEFAULT_EXPANDED_SECTIONS };
|
|
142
|
+
expandedSectionsById.set(nextKey, initial);
|
|
143
|
+
expandedSections.value = initial;
|
|
121
144
|
},
|
|
122
145
|
{ flush: "post" }
|
|
123
146
|
);
|
|
124
147
|
|
|
125
148
|
onMounted(() => {
|
|
126
149
|
activeConfigScrollKey.value = configScrollKey.value;
|
|
150
|
+
activeExpandedKey.value = configScrollKey.value;
|
|
151
|
+
if (configScrollKey.value) {
|
|
152
|
+
const saved = expandedSectionsById.get(configScrollKey.value);
|
|
153
|
+
if (saved) expandedSections.value = { ...saved };
|
|
154
|
+
else {
|
|
155
|
+
const initial = { ...DEFAULT_EXPANDED_SECTIONS };
|
|
156
|
+
expandedSectionsById.set(configScrollKey.value, initial);
|
|
157
|
+
expandedSections.value = initial;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
127
160
|
const el = sidebarScrollRef.value;
|
|
128
161
|
el?.addEventListener("scroll", onConfigSidebarScroll, { passive: true });
|
|
129
162
|
restoreConfigScroll(configScrollKey.value);
|
|
@@ -171,13 +204,23 @@ const materialType = computed(() => props.selectedElement?.materialType ?? "");
|
|
|
171
204
|
|
|
172
205
|
const forceOpenSections = computed(() => hasSearch.value);
|
|
173
206
|
provide("configHasSearch", hasSearch);
|
|
207
|
+
provide("configExpandState", {
|
|
208
|
+
isExpanded: (key: string, defaultValue = true) =>
|
|
209
|
+
expandedSections.value[key] ?? defaultValue,
|
|
210
|
+
setExpanded: (key: string, next: boolean) => {
|
|
211
|
+
setSectionExpanded(key, next);
|
|
212
|
+
},
|
|
213
|
+
});
|
|
174
214
|
|
|
175
215
|
function isSectionExpanded(key: string, defaultValue = true) {
|
|
176
216
|
return expandedSections.value[key] ?? defaultValue;
|
|
177
217
|
}
|
|
178
218
|
|
|
179
219
|
function setSectionExpanded(key: string, next: boolean) {
|
|
180
|
-
|
|
220
|
+
const updated = { ...expandedSections.value, [key]: next };
|
|
221
|
+
expandedSections.value = updated;
|
|
222
|
+
const id = activeExpandedKey.value;
|
|
223
|
+
if (id) expandedSectionsById.set(id, updated);
|
|
181
224
|
}
|
|
182
225
|
|
|
183
226
|
function shouldShowSection(key: string, title: string, searchTerms: string[] = []) {
|
|
@@ -223,15 +266,6 @@ onMounted(() => {
|
|
|
223
266
|
watch(isSearchCollapsed, (next) => {
|
|
224
267
|
window.localStorage.setItem(SEARCH_COLLAPSE_STORAGE_KEY, next ? "1" : "0");
|
|
225
268
|
});
|
|
226
|
-
|
|
227
|
-
watch(
|
|
228
|
-
() => [props.selectedElement?.id, props.selectedElement?.materialType],
|
|
229
|
-
() => {
|
|
230
|
-
if (isChartElement.value) {
|
|
231
|
-
setSectionExpanded("chartBasic", true);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
);
|
|
235
269
|
</script>
|
|
236
270
|
|
|
237
271
|
<template>
|
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
import { computed, inject, ref, type ComputedRef } from "vue";
|
|
3
3
|
import ConfigHintIcon from "../ConfigHintIcon.vue";
|
|
4
4
|
|
|
5
|
+
export type ConfigExpandState = {
|
|
6
|
+
isExpanded: (key: string, defaultValue?: boolean) => boolean;
|
|
7
|
+
setExpanded: (key: string, next: boolean) => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
5
10
|
const props = withDefaults(
|
|
6
11
|
defineProps<{
|
|
7
12
|
title: string;
|
|
8
13
|
hint?: boolean;
|
|
9
14
|
collapsible?: boolean;
|
|
10
15
|
defaultOpen?: boolean;
|
|
16
|
+
/** Stable key for remembering expand state per view node */
|
|
17
|
+
groupKey?: string;
|
|
11
18
|
}>(),
|
|
12
19
|
{ defaultOpen: true, collapsible: false }
|
|
13
20
|
);
|
|
@@ -16,13 +23,30 @@ const hasSearch = inject<ComputedRef<boolean>>(
|
|
|
16
23
|
"configHasSearch",
|
|
17
24
|
computed(() => false)
|
|
18
25
|
);
|
|
26
|
+
const expandState = inject<ConfigExpandState | null>("configExpandState", null);
|
|
27
|
+
|
|
28
|
+
const localOpen = ref(props.defaultOpen);
|
|
29
|
+
const storageKey = computed(
|
|
30
|
+
() => props.groupKey?.trim() || `fg:${props.title}`
|
|
31
|
+
);
|
|
19
32
|
|
|
20
|
-
const
|
|
21
|
-
|
|
33
|
+
const isOpen = computed(() => {
|
|
34
|
+
if (!props.collapsible) return true;
|
|
35
|
+
if (hasSearch.value) return true;
|
|
36
|
+
if (expandState) {
|
|
37
|
+
return expandState.isExpanded(storageKey.value, props.defaultOpen);
|
|
38
|
+
}
|
|
39
|
+
return localOpen.value;
|
|
40
|
+
});
|
|
22
41
|
|
|
23
42
|
function toggle() {
|
|
24
43
|
if (!props.collapsible || hasSearch.value) return;
|
|
25
|
-
|
|
44
|
+
const next = !isOpen.value;
|
|
45
|
+
if (expandState) {
|
|
46
|
+
expandState.setExpanded(storageKey.value, next);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
localOpen.value = next;
|
|
26
50
|
}
|
|
27
51
|
</script>
|
|
28
52
|
|
|
@@ -238,6 +238,7 @@ function onOptionJsonChange(v: string) {
|
|
|
238
238
|
</ConfigFieldGroup>
|
|
239
239
|
<ConfigFieldGroup
|
|
240
240
|
:title="t('panel.config.groupChartDisplayMore')"
|
|
241
|
+
group-key="chartDisplayMore"
|
|
241
242
|
collapsible
|
|
242
243
|
:default-open="false"
|
|
243
244
|
>
|
|
@@ -308,6 +309,7 @@ function onOptionJsonChange(v: string) {
|
|
|
308
309
|
|
|
309
310
|
<ConfigFieldGroup
|
|
310
311
|
:title="t('panel.config.groupTooltip')"
|
|
312
|
+
group-key="chartTooltip"
|
|
311
313
|
collapsible
|
|
312
314
|
:default-open="false"
|
|
313
315
|
>
|
|
@@ -369,6 +371,7 @@ function onOptionJsonChange(v: string) {
|
|
|
369
371
|
<ConfigFieldGroup
|
|
370
372
|
v-if="['bar', 'line', 'area', 'scatter'].includes(selectedChartType)"
|
|
371
373
|
:title="t('panel.config.groupAxes')"
|
|
374
|
+
group-key="chartAxes"
|
|
372
375
|
collapsible
|
|
373
376
|
:default-open="false"
|
|
374
377
|
>
|
|
@@ -140,6 +140,7 @@ function clearSketch() {
|
|
|
140
140
|
</ConfigFieldGroup>
|
|
141
141
|
<ConfigFieldGroup
|
|
142
142
|
:title="t('panel.config.groupCanvasScript')"
|
|
143
|
+
group-key="geometryCanvasScript"
|
|
143
144
|
collapsible
|
|
144
145
|
:default-open="false"
|
|
145
146
|
>
|
|
@@ -158,6 +159,7 @@ function clearSketch() {
|
|
|
158
159
|
</ConfigFieldGroup>
|
|
159
160
|
<ConfigFieldGroup
|
|
160
161
|
:title="t('panel.config.groupSketchOverlay')"
|
|
162
|
+
group-key="geometrySketch"
|
|
161
163
|
collapsible
|
|
162
164
|
:default-open="false"
|
|
163
165
|
>
|
|
@@ -69,17 +69,13 @@ function closeDropdown() {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
function refreshForInput(input: HTMLInputElement | HTMLTextAreaElement) {
|
|
72
|
-
if (props.scope === undefined) {
|
|
73
|
-
closeDropdown();
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
72
|
const cursor = input.selectionStart ?? input.value.length;
|
|
77
|
-
const parsed = parseScopeAutocomplete(input.value, cursor, props.scope);
|
|
73
|
+
const parsed = parseScopeAutocomplete(input.value, cursor, props.scope ?? {});
|
|
78
74
|
if (!parsed) {
|
|
79
75
|
closeDropdown();
|
|
80
76
|
return;
|
|
81
77
|
}
|
|
82
|
-
const suggestions = getScopeAutocompleteSuggestions(props.scope, parsed);
|
|
78
|
+
const suggestions = getScopeAutocompleteSuggestions(props.scope ?? {}, parsed);
|
|
83
79
|
if (suggestions.length === 0) {
|
|
84
80
|
closeDropdown();
|
|
85
81
|
return;
|
|
@@ -173,7 +169,7 @@ function onPointerDown(event: MouseEvent) {
|
|
|
173
169
|
watch(
|
|
174
170
|
() => toValue(props.containerRef),
|
|
175
171
|
(container, _prev, onCleanup) => {
|
|
176
|
-
if (!container
|
|
172
|
+
if (!container) return;
|
|
177
173
|
|
|
178
174
|
container.addEventListener("focusin", onFocusIn);
|
|
179
175
|
container.addEventListener("input", onInput, true);
|
|
@@ -197,13 +193,14 @@ watch(
|
|
|
197
193
|
watch(
|
|
198
194
|
() => props.scope,
|
|
199
195
|
() => {
|
|
200
|
-
|
|
196
|
+
const current = dropdown.value;
|
|
197
|
+
if (current) refreshForInput(current.input);
|
|
201
198
|
}
|
|
202
199
|
);
|
|
203
200
|
</script>
|
|
204
201
|
|
|
205
202
|
<template>
|
|
206
|
-
<Teleport v-if="dropdown
|
|
203
|
+
<Teleport v-if="dropdown" to="body">
|
|
207
204
|
<div
|
|
208
205
|
data-scope-ac-list
|
|
209
206
|
class="fixed z-[10120] max-h-44 min-w-[140px] overflow-auto rounded-md border border-border bg-popover py-1 text-popover-foreground shadow-md"
|
|
@@ -5,6 +5,10 @@ export const AbuilderEvents = {
|
|
|
5
5
|
workspaceAdd: "workspace:add",
|
|
6
6
|
/** 同步工作区 */
|
|
7
7
|
workspaceSync: "workspace:sync",
|
|
8
|
+
/** 开始上传接口集合 */
|
|
9
|
+
apiCollectionUploadStart: "api-collection:upload-start",
|
|
10
|
+
/** 上传接口集合成功 */
|
|
11
|
+
apiCollectionUploadSuccess: "api-collection:upload-success",
|
|
8
12
|
} as const;
|
|
9
13
|
|
|
10
14
|
export type AbuilderEventName = (typeof AbuilderEvents)[keyof typeof AbuilderEvents];
|
|
@@ -15,9 +19,25 @@ export type WorkspaceData = WorkspaceProjectRecord;
|
|
|
15
19
|
export type WorkspaceAddEventPayload = WorkspaceData;
|
|
16
20
|
export type WorkspaceSyncEventPayload = WorkspaceData;
|
|
17
21
|
|
|
22
|
+
export type ApiCollectionUploadStartEventPayload = {
|
|
23
|
+
fileNames: string[];
|
|
24
|
+
count: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type ApiCollectionUploadSuccessEventPayload = {
|
|
28
|
+
collections: Array<{
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
apiCount: number;
|
|
32
|
+
persistLocal: boolean;
|
|
33
|
+
}>;
|
|
34
|
+
};
|
|
35
|
+
|
|
18
36
|
export type AbuilderEventPayloadMap = {
|
|
19
37
|
[AbuilderEvents.workspaceAdd]: WorkspaceAddEventPayload;
|
|
20
38
|
[AbuilderEvents.workspaceSync]: WorkspaceSyncEventPayload;
|
|
39
|
+
[AbuilderEvents.apiCollectionUploadStart]: ApiCollectionUploadStartEventPayload;
|
|
40
|
+
[AbuilderEvents.apiCollectionUploadSuccess]: ApiCollectionUploadSuccessEventPayload;
|
|
21
41
|
};
|
|
22
42
|
|
|
23
43
|
type EventCallback = (payload: unknown) => void;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { PanelElement } from "../types";
|
|
2
2
|
import type { PanelTableConfig } from "@arronqzy/view-table";
|
|
3
3
|
import { isUsableTableRawData } from "@arronqzy/view-table";
|
|
4
|
+
import {
|
|
5
|
+
createSystemRoot,
|
|
6
|
+
isDayjsValue,
|
|
7
|
+
} from "@arronqzy/blueprint-dsl";
|
|
4
8
|
import { materializeChartLabelsFromScope, materializeChartValuesFromScope } from "./scope-template-chart";
|
|
5
9
|
import {
|
|
6
10
|
SCOPE_SPREAD_TEMPLATE_RE,
|
|
@@ -11,12 +15,12 @@ import {
|
|
|
11
15
|
resolveSpreadScopePath,
|
|
12
16
|
} from "./scope-template-spread";
|
|
13
17
|
|
|
14
|
-
const
|
|
18
|
+
const TEMPLATE_TOKEN_RE = /\{((?:scope|system)[^}]*)\}/g;
|
|
15
19
|
|
|
16
20
|
export function hasScopeTemplate(value: string): boolean {
|
|
17
|
-
|
|
21
|
+
TEMPLATE_TOKEN_RE.lastIndex = 0;
|
|
18
22
|
SCOPE_SPREAD_TEMPLATE_RE.lastIndex = 0;
|
|
19
|
-
return
|
|
23
|
+
return TEMPLATE_TOKEN_RE.test(value) || SCOPE_SPREAD_TEMPLATE_RE.test(value);
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
export function evaluateScopeExpression(
|
|
@@ -26,8 +30,13 @@ export function evaluateScopeExpression(
|
|
|
26
30
|
const trimmed = expression.trim();
|
|
27
31
|
if (!trimmed) return undefined;
|
|
28
32
|
try {
|
|
29
|
-
const
|
|
30
|
-
|
|
33
|
+
const system = createSystemRoot();
|
|
34
|
+
const fn = new Function(
|
|
35
|
+
"scope",
|
|
36
|
+
"system",
|
|
37
|
+
`"use strict"; return (${trimmed});`
|
|
38
|
+
);
|
|
39
|
+
return fn(scope, system);
|
|
31
40
|
} catch {
|
|
32
41
|
return undefined;
|
|
33
42
|
}
|
|
@@ -39,6 +48,9 @@ function stringifyScopeValue(value: unknown): string {
|
|
|
39
48
|
if (typeof value === "number" || typeof value === "boolean") {
|
|
40
49
|
return String(value);
|
|
41
50
|
}
|
|
51
|
+
if (isDayjsValue(value)) {
|
|
52
|
+
return value.toISOString();
|
|
53
|
+
}
|
|
42
54
|
try {
|
|
43
55
|
return JSON.stringify(value);
|
|
44
56
|
} catch {
|
|
@@ -60,8 +72,8 @@ export function evaluateScopeTemplate(
|
|
|
60
72
|
}
|
|
61
73
|
);
|
|
62
74
|
|
|
63
|
-
// Only `{scope...}`
|
|
64
|
-
return withSpread.replace(
|
|
75
|
+
// Only `{scope...}` / `{system...}` — keep ECharts / CSS braces like `{b}` intact.
|
|
76
|
+
return withSpread.replace(TEMPLATE_TOKEN_RE, (_: string, rawExpr: string) =>
|
|
65
77
|
stringifyScopeValue(evaluateScopeExpression(rawExpr, scope))
|
|
66
78
|
);
|
|
67
79
|
}
|
|
@@ -91,7 +103,7 @@ function materializeTemplateValue(template: string, scope: unknown): unknown {
|
|
|
91
103
|
);
|
|
92
104
|
}
|
|
93
105
|
|
|
94
|
-
const singleMatch = /^\{(scope[^}]*)\}$/.exec(trimmed);
|
|
106
|
+
const singleMatch = /^\{((?:scope|system)[^}]*)\}$/.exec(trimmed);
|
|
95
107
|
if (singleMatch) {
|
|
96
108
|
return evaluateScopeExpression(singleMatch[1]!, scope);
|
|
97
109
|
}
|