@arronqzy/vue-view 0.1.0

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 (75) hide show
  1. package/README.md +50 -0
  2. package/package.json +49 -0
  3. package/src/env.d.ts +62 -0
  4. package/src/index.ts +4 -0
  5. package/src/panel/VueViewOnlinePreview.vue +276 -0
  6. package/src/panel/VueViewPanel.vue +871 -0
  7. package/src/panel/components/ConfigHintIcon.vue +34 -0
  8. package/src/panel/components/ElementsLayer.vue +165 -0
  9. package/src/panel/components/MaterialPreview.vue +135 -0
  10. package/src/panel/components/MaterialSidebar.vue +526 -0
  11. package/src/panel/components/MaterialSidebarTreeNode.vue +305 -0
  12. package/src/panel/components/MoveableLayer.vue +859 -0
  13. package/src/panel/components/PanelCanvas.vue +630 -0
  14. package/src/panel/components/PanelConfigSidebar.vue +397 -0
  15. package/src/panel/components/PanelRulers.vue +177 -0
  16. package/src/panel/components/SelectLayer.vue +115 -0
  17. package/src/panel/components/ViewElementScopePanel.vue +76 -0
  18. package/src/panel/components/WorkspaceConfigSidebar.vue +147 -0
  19. package/src/panel/components/WorkspaceProjectNav.vue +192 -0
  20. package/src/panel/components/WorkspaceStageSplit.vue +258 -0
  21. package/src/panel/components/config/ConfigColorField.vue +52 -0
  22. package/src/panel/components/config/ConfigFieldGroup.vue +20 -0
  23. package/src/panel/components/config/ConfigSection.vue +50 -0
  24. package/src/panel/components/config/PanelConfigAudioSection.vue +256 -0
  25. package/src/panel/components/config/PanelConfigChartSection.vue +650 -0
  26. package/src/panel/components/config/PanelConfigGeometrySection.vue +209 -0
  27. package/src/panel/components/config/PanelConfigGridChildSpan.vue +68 -0
  28. package/src/panel/components/config/PanelConfigGridSection.vue +103 -0
  29. package/src/panel/components/config/PanelConfigImageSection.vue +136 -0
  30. package/src/panel/components/config/PanelConfigMultiSelect.vue +434 -0
  31. package/src/panel/components/config/PanelConfigNodeInfo.vue +165 -0
  32. package/src/panel/components/config/PanelConfigReferenceSection.vue +77 -0
  33. package/src/panel/components/config/PanelConfigStyleSections.vue +208 -0
  34. package/src/panel/components/config/PanelConfigTextSection.vue +195 -0
  35. package/src/panel/components/config/PanelConfigVideoSection.vue +107 -0
  36. package/src/panel/components/config/shared.ts +74 -0
  37. package/src/panel/components/elementsLayerNodes.ts +830 -0
  38. package/src/panel/components/materialSidebarData.ts +85 -0
  39. package/src/panel/components/scope-config/ScopeConfigProvider.vue +153 -0
  40. package/src/panel/components/scope-config/ScopeTemplateAutocompleteHost.vue +234 -0
  41. package/src/panel/components/scope-config/ScopeTemplatePreviewHost.vue +192 -0
  42. package/src/panel/components/scope-config/ScopeTemplatePreviewPanel.vue +42 -0
  43. package/src/panel/components/scope-config/ScopeTemplateUsageHint.vue +20 -0
  44. package/src/panel/components/scope-config/ScopeTemplateWarningsPanel.vue +63 -0
  45. package/src/panel/components/scope-config/scopeConfigContext.ts +17 -0
  46. package/src/panel/components/scope-config/useScopeConfig.ts +11 -0
  47. package/src/panel/constants/messages.ts +34 -0
  48. package/src/panel/constants/zIndex.ts +6 -0
  49. package/src/panel/hooks/usePanelElements.ts +1075 -0
  50. package/src/panel/hooks/useRafThrottledScroll.ts +25 -0
  51. package/src/panel/hooks/useWorkspaceProjects.ts +240 -0
  52. package/src/panel/lib/panel-ruler-canvas.ts +139 -0
  53. package/src/panel/library/workspace-project-cache.ts +23 -0
  54. package/src/panel/library/workspace-project-db.ts +111 -0
  55. package/src/panel/library/workspace-project-sync.ts +41 -0
  56. package/src/panel/library/workspace-snapshot.ts +30 -0
  57. package/src/panel/parseOnlinePreviewSearchParams.ts +13 -0
  58. package/src/panel/scope/view-scope-store.ts +82 -0
  59. package/src/panel/types.ts +127 -0
  60. package/src/panel/utils/chartOptionBuilder.ts +327 -0
  61. package/src/panel/utils/gridPlacement.ts +189 -0
  62. package/src/panel/utils/mappingLayerOps.ts +142 -0
  63. package/src/panel/utils/panelElementDefaults.ts +161 -0
  64. package/src/panel/utils/panelElementNodes.ts +35 -0
  65. package/src/panel/utils/panelStateIO.ts +124 -0
  66. package/src/panel/utils/scope-autocomplete.ts +114 -0
  67. package/src/panel/utils/scope-field-labels.ts +46 -0
  68. package/src/panel/utils/scope-template-chart.ts +92 -0
  69. package/src/panel/utils/scope-template-preview.ts +124 -0
  70. package/src/panel/utils/scope-template-spread.ts +229 -0
  71. package/src/panel/utils/scope-template-warnings.ts +243 -0
  72. package/src/panel/utils/scope-template.ts +97 -0
  73. package/src/panel/utils/updateElementDraft.ts +221 -0
  74. package/src/panel/viewportZoom.ts +26 -0
  75. package/src/tailwind.css +43 -0
@@ -0,0 +1,305 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+ import { Card, Tooltip } from "ant-design-vue";
4
+ import type { PanelElement, PanelLayer, ReferenceCopyMode } from "../types";
5
+ import {
6
+ compareGridTreeChildOrder,
7
+ getNodeDisplayName,
8
+ } from "./materialSidebarData";
9
+
10
+ const props = defineProps<{
11
+ node: PanelElement;
12
+ level: number;
13
+ path: string;
14
+ visited: Set<string>;
15
+ sourceOverride?: PanelElement[];
16
+ selectedIds: string[];
17
+ layerById: Map<string, PanelLayer>;
18
+ elementsByLayer: Map<string, PanelElement[]>;
19
+ childrenByGridByLayer: Map<string, Map<string, PanelElement[]>>;
20
+ expandedKeys: Record<string, boolean>;
21
+ normalizedTreeKeyword: string;
22
+ isTreeSearching: boolean;
23
+ draggingTreeNodeId: string | null;
24
+ }>();
25
+
26
+ const emit = defineEmits<{
27
+ toggleExpanded: [key: string, open: boolean];
28
+ selectNode: [nodeId: string, layerId: string];
29
+ nodeContextMenu: [payload: { nodeId: string; layerId: string; x: number; y: number }];
30
+ deleteNode: [nodeId: string];
31
+ copyNode: [nodeId: string, mode?: ReferenceCopyMode];
32
+ dragStart: [nodeId: string];
33
+ dragEnd: [];
34
+ }>();
35
+
36
+ const nodeKey = computed(() => `node:${props.path}`);
37
+ const selected = computed(() => props.selectedIds.includes(props.node.id));
38
+ const isRef = computed(() => props.node.materialType === "reference");
39
+ const refMode = computed(() => props.node.refCopyMode ?? "shallow");
40
+ const isDeepRef = computed(() => isRef.value && refMode.value === "deep");
41
+ const nodeHomeLayer = computed(() => props.layerById.get(props.node.layerId));
42
+ const isExpanded = computed(() => props.expandedKeys[nodeKey.value] ?? true);
43
+
44
+ const children = computed(() => {
45
+ const node = props.node;
46
+ const isGrid = node.materialType === "grid";
47
+ const gridChildren = isGrid
48
+ ? [...(props.childrenByGridByLayer.get(node.layerId)?.get(node.id) ?? [])].sort(
49
+ compareGridTreeChildOrder
50
+ )
51
+ : [];
52
+ if (isRef.value) {
53
+ return refMode.value === "deep"
54
+ ? node.refSnapshot ?? props.sourceOverride ?? []
55
+ : node.refLayerId
56
+ ? props.elementsByLayer.get(node.refLayerId) ?? []
57
+ : [];
58
+ }
59
+ return gridChildren;
60
+ });
61
+
62
+ const hasChildren = computed(() => children.value.length > 0);
63
+
64
+ const nextVisited = computed(() => {
65
+ const set = new Set(props.visited);
66
+ set.add(props.node.id);
67
+ return set;
68
+ });
69
+
70
+ function nodeMatchesTreeSearch(
71
+ node: PanelElement,
72
+ visited: Set<string>,
73
+ sourceOverride?: PanelElement[]
74
+ ): boolean {
75
+ if (!props.isTreeSearching) return true;
76
+ const selfText = `${getNodeDisplayName(node)} ${node.materialType ?? ""} ${node.id}`.toLowerCase();
77
+ if (selfText.includes(props.normalizedTreeKeyword)) return true;
78
+ if (visited.has(node.id)) return false;
79
+ const next = new Set(visited);
80
+ next.add(node.id);
81
+ const childList =
82
+ node.materialType === "reference"
83
+ ? node.refCopyMode === "deep"
84
+ ? node.refSnapshot ?? sourceOverride ?? []
85
+ : node.refLayerId
86
+ ? props.elementsByLayer.get(node.refLayerId) ?? []
87
+ : []
88
+ : node.materialType === "grid"
89
+ ? [...(props.childrenByGridByLayer.get(node.layerId)?.get(node.id) ?? [])].sort(
90
+ compareGridTreeChildOrder
91
+ )
92
+ : [];
93
+ return childList.some((child) => nodeMatchesTreeSearch(child, next, node.refSnapshot));
94
+ }
95
+
96
+ const visible = computed(() =>
97
+ nodeMatchesTreeSearch(props.node, props.visited, props.sourceOverride)
98
+ );
99
+
100
+ function onDragStart(e: DragEvent) {
101
+ e.stopPropagation();
102
+ if (props.node.locked) {
103
+ e.preventDefault();
104
+ return;
105
+ }
106
+ emit("dragStart", props.node.id);
107
+ e.dataTransfer?.setData(
108
+ "application/x-arronqzy-tree-node",
109
+ JSON.stringify({ nodeId: props.node.id, sourceLayerId: props.node.layerId })
110
+ );
111
+ if (e.dataTransfer) e.dataTransfer.effectAllowed = "move";
112
+ }
113
+ </script>
114
+
115
+ <template>
116
+ <template v-if="visible">
117
+ <div
118
+ :class="[
119
+ 'mb-1 flex items-center gap-1 rounded py-1',
120
+ selected ? 'bg-primary/15 text-foreground' : 'text-muted-foreground hover:bg-accent/60',
121
+ ]"
122
+ :style="{ paddingLeft: `${6 + level * 14}px`, paddingRight: '6px' }"
123
+ >
124
+ <button
125
+ v-if="hasChildren"
126
+ type="button"
127
+ class="flex h-7 w-7 items-center justify-center rounded text-sm hover:bg-accent"
128
+ @click="emit('toggleExpanded', nodeKey, !isExpanded)"
129
+ >
130
+ {{ isExpanded ? "▾" : "▸" }}
131
+ </button>
132
+ <span v-else class="inline-flex h-7 w-7 items-center justify-center text-sm opacity-40">•</span>
133
+
134
+ <button
135
+ type="button"
136
+ class="min-w-0 flex-1 truncate text-left"
137
+ :title="getNodeDisplayName(node)"
138
+ :draggable="!node.locked"
139
+ @click="emit('selectNode', node.id, node.layerId)"
140
+ @contextmenu.prevent.stop="
141
+ emit('nodeContextMenu', {
142
+ nodeId: node.id,
143
+ layerId: node.layerId,
144
+ x: $event.clientX,
145
+ y: $event.clientY,
146
+ })
147
+ "
148
+ @dragstart="onDragStart"
149
+ @dragend="emit('dragEnd')"
150
+ >
151
+ {{ getNodeDisplayName(node) }}
152
+ </button>
153
+
154
+ <span
155
+ v-if="nodeHomeLayer?.isMapping"
156
+ class="inline-flex shrink-0 items-center rounded-md border-2 border-violet-600 bg-violet-500/20 px-1.5 py-0.5 text-[10px] font-semibold text-violet-950 shadow-sm dark:border-violet-400 dark:bg-violet-500/35 dark:text-violet-50"
157
+ :title="
158
+ nodeHomeLayer.mappingBaseLayerId
159
+ ? `映射图层「${nodeHomeLayer.name}」· 基准图层:${
160
+ layerById.get(nodeHomeLayer.mappingBaseLayerId)?.name ??
161
+ nodeHomeLayer.mappingBaseLayerId
162
+ }`
163
+ : `映射图层「${nodeHomeLayer.name}」`
164
+ "
165
+ >
166
+ 映射图层节点
167
+ </span>
168
+ <span
169
+ v-if="node.mappingSourceNodeId"
170
+ class="inline-flex shrink-0 items-center rounded border border-primary/40 bg-primary/10 px-1 text-[10px] text-primary"
171
+ :title="`同源节点 ${node.mappingSourceNodeId}${
172
+ node.mappingSourceLayerId
173
+ ? ` · ${layerById.get(node.mappingSourceLayerId)?.name ?? node.mappingSourceLayerId}`
174
+ : ''
175
+ }`"
176
+ >
177
+ 同源
178
+ </span>
179
+ <span
180
+ v-if="node.locked"
181
+ class="inline-flex h-5 w-5 items-center justify-center rounded border border-border/80 bg-background/80 text-muted-foreground"
182
+ title="节点已锁定"
183
+ >
184
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" class="h-3.5 w-3.5">
185
+ <rect x="4.5" y="10.5" width="15" height="10" rx="2.5" />
186
+ <path d="M8 10.5V8a4 4 0 0 1 8 0v2.5" />
187
+ <circle cx="12" cy="15.5" r="1.2" />
188
+ </svg>
189
+ </span>
190
+
191
+ <template v-if="isRef">
192
+ <span
193
+ :class="[
194
+ 'rounded border px-1 text-[10px]',
195
+ isDeepRef
196
+ ? 'border-violet-500/50 bg-violet-500/15 text-violet-300'
197
+ : 'border-sky-500/40 bg-sky-500/10 text-sky-300',
198
+ ]"
199
+ :title="isDeepRef ? '深拷贝引用(冻结快照)' : '浅拷贝引用(跟随源变化)'"
200
+ >
201
+ {{ isDeepRef ? "深拷贝" : "浅拷贝" }}
202
+ </span>
203
+ <Tooltip title="浅拷贝">
204
+ <button
205
+ type="button"
206
+ class="inline-flex h-5 w-5 items-center justify-center rounded border border-border hover:bg-accent"
207
+ aria-label="浅拷贝"
208
+ @click.stop="emit('copyNode', node.id, 'shallow')"
209
+ >
210
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" class="h-3.5 w-3.5">
211
+ <rect x="8.5" y="8.5" width="10" height="10" rx="2" />
212
+ <rect x="5.5" y="5.5" width="10" height="10" rx="2" />
213
+ <path d="M7.5 16.5 16.5 7.5" stroke-dasharray="2 2" />
214
+ </svg>
215
+ </button>
216
+ </Tooltip>
217
+ <Tooltip title="深拷贝">
218
+ <button
219
+ type="button"
220
+ class="inline-flex h-5 w-5 items-center justify-center rounded border border-border hover:bg-accent"
221
+ aria-label="深拷贝"
222
+ @click.stop="emit('copyNode', node.id, 'deep')"
223
+ >
224
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" class="h-3.5 w-3.5">
225
+ <rect x="4.5" y="4.5" width="15" height="4.2" rx="1.6" />
226
+ <rect x="4.5" y="9.9" width="15" height="4.2" rx="1.6" />
227
+ <rect x="4.5" y="15.3" width="15" height="4.2" rx="1.6" />
228
+ </svg>
229
+ </button>
230
+ </Tooltip>
231
+ </template>
232
+ <Tooltip v-else title="复制节点">
233
+ <button
234
+ type="button"
235
+ class="inline-flex h-5 w-5 items-center justify-center rounded border border-border hover:bg-accent"
236
+ aria-label="复制节点"
237
+ @click.stop="emit('copyNode', node.id)"
238
+ >
239
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" class="h-3.5 w-3.5">
240
+ <rect x="9" y="9" width="10" height="10" rx="2" />
241
+ <rect x="5" y="5" width="10" height="10" rx="2" />
242
+ </svg>
243
+ </button>
244
+ </Tooltip>
245
+ <Tooltip :title="node.locked ? '锁定节点不可删除' : '删除节点'">
246
+ <button
247
+ type="button"
248
+ :disabled="node.locked"
249
+ class="inline-flex h-5 w-5 items-center justify-center rounded border border-border hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40"
250
+ :aria-label="node.locked ? '锁定节点不可删除' : '删除节点'"
251
+ @click.stop="!node.locked && emit('deleteNode', node.id)"
252
+ >
253
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" class="h-3.5 w-3.5">
254
+ <path d="M4 7h16" />
255
+ <path d="M9 7V5h6v2" />
256
+ <path d="M7 7l1 12h8l1-12" />
257
+ <path d="M10 11v5M14 11v5" />
258
+ </svg>
259
+ </button>
260
+ </Tooltip>
261
+ </div>
262
+
263
+ <template v-if="hasChildren && isExpanded">
264
+ <template v-for="child in children" :key="`${path}->${child.id}`">
265
+ <div
266
+ v-if="nextVisited.has(child.id)"
267
+ class="py-1 text-[10px] text-muted-foreground/80"
268
+ :style="{ paddingLeft: `${6 + (level + 1) * 14}px` }"
269
+ title="检测到循环引用,已停止向下展开"
270
+ >
271
+ {{ getNodeDisplayName(child) }}(循环引用)
272
+ </div>
273
+ <MaterialSidebarTreeNode
274
+ v-else
275
+ :node="child"
276
+ :level="level + 1"
277
+ :path="`${path}->${child.id}`"
278
+ :visited="nextVisited"
279
+ :source-override="node.refSnapshot"
280
+ :selected-ids="selectedIds"
281
+ :layer-by-id="layerById"
282
+ :elements-by-layer="elementsByLayer"
283
+ :children-by-grid-by-layer="childrenByGridByLayer"
284
+ :expanded-keys="expandedKeys"
285
+ :normalized-tree-keyword="normalizedTreeKeyword"
286
+ :is-tree-searching="isTreeSearching"
287
+ :dragging-tree-node-id="draggingTreeNodeId"
288
+ @toggle-expanded="(k, o) => emit('toggleExpanded', k, o)"
289
+ @select-node="(...args) => emit('selectNode', ...args)"
290
+ @node-context-menu="(p) => emit('nodeContextMenu', p)"
291
+ @delete-node="(id) => emit('deleteNode', id)"
292
+ @copy-node="(id, mode) => emit('copyNode', id, mode)"
293
+ @drag-start="(id) => emit('dragStart', id)"
294
+ @drag-end="emit('dragEnd')"
295
+ />
296
+ </template>
297
+ </template>
298
+ </template>
299
+ </template>
300
+
301
+ <script lang="ts">
302
+ export default {
303
+ name: "MaterialSidebarTreeNode",
304
+ };
305
+ </script>