@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,34 @@
1
+ <script setup lang="ts">
2
+ import { Tooltip } from "ant-design-vue";
3
+
4
+ withDefaults(
5
+ defineProps<{
6
+ label?: string;
7
+ contentClass?: string;
8
+ buttonClass?: string;
9
+ }>(),
10
+ { label: "说明" }
11
+ );
12
+ </script>
13
+
14
+ <template>
15
+ <Tooltip
16
+ placement="top"
17
+ :overlay-class-name="`z-[10120] max-w-[360px] text-[11px] leading-5 ${contentClass ?? ''}`"
18
+ :mouse-enter-delay="0.12"
19
+ >
20
+ <template #title>
21
+ <slot />
22
+ </template>
23
+ <button
24
+ type="button"
25
+ :class="
26
+ buttonClass ??
27
+ 'inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full border border-border text-[10px] leading-none text-muted-foreground hover:bg-accent/50'
28
+ "
29
+ :aria-label="`${label}说明`"
30
+ >
31
+ ?
32
+ </button>
33
+ </Tooltip>
34
+ </template>
@@ -0,0 +1,165 @@
1
+ <script setup lang="ts">
2
+ import { computed, defineComponent, h, type PropType } from "vue";
3
+ import type { PanelElement } from "../types";
4
+ import {
5
+ AudioNodeContent,
6
+ ChartNodeContent,
7
+ CHART_TYPES,
8
+ EmptyNodePlaceholder,
9
+ GeometryNodeContent,
10
+ getNodeVisualStyle,
11
+ GridNodeContent,
12
+ ImageNodeContent,
13
+ ReferenceNodeContent,
14
+ TextNodeContent,
15
+ VideoNodeContent,
16
+ } from "./elementsLayerNodes";
17
+
18
+ const props = withDefaults(
19
+ defineProps<{
20
+ elements: PanelElement[];
21
+ allElements: PanelElement[];
22
+ selectedIds: string[];
23
+ updateElement: (
24
+ id: string,
25
+ patch: Partial<PanelElement>,
26
+ options?: { batchId?: string; meta?: Record<string, unknown> }
27
+ ) => void;
28
+ layerLocked?: boolean;
29
+ previewMode?: boolean;
30
+ previewLayoutKey?: number;
31
+ }>(),
32
+ { layerLocked: false, previewMode: false }
33
+ );
34
+
35
+ const emit = defineEmits<{
36
+ selectIds: [ids: string[]];
37
+ }>();
38
+
39
+ const NodeContent = defineComponent({
40
+ name: "NodeContent",
41
+ props: {
42
+ element: { type: Object as PropType<PanelElement>, required: true },
43
+ allElements: { type: Array as PropType<PanelElement[]>, required: true },
44
+ selected: { type: Boolean, default: false },
45
+ layerLocked: { type: Boolean, default: false },
46
+ previewMode: { type: Boolean, default: false },
47
+ previewLayoutKey: { type: Number, default: undefined },
48
+ updateElement: {
49
+ type: Function as PropType<
50
+ (
51
+ id: string,
52
+ patch: Partial<PanelElement>,
53
+ options?: { batchId?: string; meta?: Record<string, unknown> }
54
+ ) => void
55
+ >,
56
+ required: true,
57
+ },
58
+ },
59
+ setup(p) {
60
+ return () => {
61
+ const el = p.element;
62
+ if (CHART_TYPES.has(el.materialType ?? "")) {
63
+ return h(ChartNodeContent, {
64
+ element: el,
65
+ previewLayoutKey: p.previewLayoutKey,
66
+ previewMode: p.previewMode,
67
+ });
68
+ }
69
+ if (el.materialType === "reference") {
70
+ return h(ReferenceNodeContent, {
71
+ element: el,
72
+ allElements: p.allElements,
73
+ previewLayoutKey: p.previewLayoutKey,
74
+ previewMode: p.previewMode,
75
+ });
76
+ }
77
+ if (el.materialType === "grid") {
78
+ return h(GridNodeContent, {
79
+ element: el,
80
+ allElements: p.allElements,
81
+ previewMode: p.previewMode,
82
+ });
83
+ }
84
+ if (el.materialType === "text") {
85
+ return h(TextNodeContent, {
86
+ element: el,
87
+ editable: (el.textAllowInput ?? true) && !el.locked && !p.layerLocked,
88
+ onChange: (nextHtml: string) => p.updateElement(el.id, { textHtml: nextHtml }),
89
+ });
90
+ }
91
+ if (el.materialType === "audio") {
92
+ return h(AudioNodeContent, { element: el, selected: p.selected });
93
+ }
94
+ if (el.materialType === "video") {
95
+ return h(VideoNodeContent, { element: el, selected: p.selected });
96
+ }
97
+ if (el.materialType === "geometry") {
98
+ return h(GeometryNodeContent, { element: el });
99
+ }
100
+ if (el.materialType === "image") {
101
+ return h(ImageNodeContent, { element: el });
102
+ }
103
+ return h(EmptyNodePlaceholder, { element: el });
104
+ };
105
+ },
106
+ });
107
+
108
+ const sortedElements = computed(() =>
109
+ [...props.elements].sort((a, b) => {
110
+ const za = a.zIndex ?? 1;
111
+ const zb = b.zIndex ?? 1;
112
+ if (za !== zb) return za - zb;
113
+ return a.id.localeCompare(b.id);
114
+ })
115
+ );
116
+
117
+ function onSelect(id: string, event: MouseEvent) {
118
+ if (props.previewMode) return;
119
+ if (event.button !== 0) return;
120
+ const isSelected = props.selectedIds.includes(id);
121
+ if (event.shiftKey) {
122
+ emit(
123
+ "selectIds",
124
+ isSelected ? props.selectedIds.filter((sid) => sid !== id) : [...props.selectedIds, id]
125
+ );
126
+ return;
127
+ }
128
+ emit("selectIds", [id]);
129
+ }
130
+ </script>
131
+
132
+ <template>
133
+ <div
134
+ v-for="el in sortedElements"
135
+ :key="el.id"
136
+ :class="[
137
+ 'absolute select-none',
138
+ previewMode ? '' : 'rv-selectable',
139
+ !previewMode && selectedIds.includes(el.id) ? 'ring-2 ring-blue-500/90 ring-offset-0' : '',
140
+ ]"
141
+ :data-element-id="el.id"
142
+ :style="{
143
+ left: `${el.x}px`,
144
+ top: `${el.y}px`,
145
+ width: `${Math.max(1, el.width)}px`,
146
+ height: `${Math.max(1, el.height)}px`,
147
+ zIndex: el.zIndex ?? 1,
148
+ transform: `rotate(${el.rotate ?? 0}deg)`,
149
+ transformOrigin: 'center center',
150
+ boxSizing: 'border-box',
151
+ ...getNodeVisualStyle(el),
152
+ }"
153
+ @mousedown="onSelect(el.id, $event)"
154
+ >
155
+ <NodeContent
156
+ :element="el"
157
+ :all-elements="allElements"
158
+ :selected="selectedIds.includes(el.id)"
159
+ :layer-locked="layerLocked"
160
+ :preview-mode="previewMode"
161
+ :preview-layout-key="previewLayoutKey"
162
+ :update-element="updateElement"
163
+ />
164
+ </div>
165
+ </template>
@@ -0,0 +1,135 @@
1
+ <script setup lang="ts">
2
+ defineProps<{ id: string }>();
3
+
4
+ const common =
5
+ "relative aspect-[4/3] w-full shrink-0 overflow-hidden rounded-md border border-border bg-gradient-to-br from-card to-muted/40";
6
+ </script>
7
+
8
+ <template>
9
+ <div v-if="id === 'bar'" :class="common">
10
+ <div class="absolute inset-0 grid grid-cols-5 items-end gap-1 px-2 pb-2 pt-1.5">
11
+ <div class="h-2 rounded-sm bg-primary/55" />
12
+ <div class="h-4 rounded-sm bg-primary/65" />
13
+ <div class="h-7 rounded-sm bg-primary/80" />
14
+ <div class="h-5 rounded-sm bg-primary/70" />
15
+ <div class="h-3 rounded-sm bg-primary/60" />
16
+ </div>
17
+ </div>
18
+ <div v-else-if="id === 'line' || id === 'area'" :class="common">
19
+ <svg viewBox="0 0 80 60" class="h-full w-full">
20
+ <polygon
21
+ v-if="id === 'area'"
22
+ points="6,42 18,28 34,34 50,20 66,24 74,18 74,56 6,56"
23
+ class="fill-primary/30"
24
+ />
25
+ <polyline
26
+ points="6,42 18,28 34,34 50,20 66,24 74,18"
27
+ fill="none"
28
+ stroke="hsl(var(--primary))"
29
+ stroke-width="3"
30
+ stroke-linecap="round"
31
+ stroke-linejoin="round"
32
+ />
33
+ </svg>
34
+ </div>
35
+ <div v-else-if="id === 'pie'" :class="common">
36
+ <svg viewBox="0 0 80 60" class="h-full w-full">
37
+ <circle cx="40" cy="30" r="16" class="fill-primary/20" />
38
+ <path d="M40 30 L40 14 A16 16 0 0 1 55 39 Z" class="fill-primary/75" />
39
+ <path d="M40 30 L55 39 A16 16 0 0 1 27 43 Z" class="fill-primary/45" />
40
+ </svg>
41
+ </div>
42
+ <div v-else-if="id === 'scatter'" :class="common">
43
+ <svg viewBox="0 0 80 60" class="h-full w-full">
44
+ <circle cx="14" cy="42" r="2.5" class="fill-primary/80" />
45
+ <circle cx="24" cy="34" r="2.5" class="fill-primary/70" />
46
+ <circle cx="36" cy="28" r="2.5" class="fill-primary/75" />
47
+ <circle cx="48" cy="20" r="2.5" class="fill-primary/65" />
48
+ <circle cx="62" cy="14" r="2.5" class="fill-primary/85" />
49
+ </svg>
50
+ </div>
51
+ <div v-else-if="id === 'radar'" :class="common">
52
+ <svg viewBox="0 0 80 60" class="h-full w-full">
53
+ <polygon points="40,10 58,20 54,40 26,40 22,20" class="fill-primary/20 stroke-primary/60" />
54
+ <polygon points="40,16 52,23 49,36 31,36 28,24" class="fill-primary/45 stroke-primary/80" />
55
+ </svg>
56
+ </div>
57
+ <div v-else-if="id === 'gauge'" :class="common">
58
+ <svg viewBox="0 0 80 60" class="h-full w-full">
59
+ <path d="M12 44a28 28 0 0 1 56 0" class="fill-none stroke-primary/35" stroke-width="6" />
60
+ <path d="M12 44a28 28 0 0 1 40-24" class="fill-none stroke-primary/80" stroke-width="6" />
61
+ <line x1="40" y1="44" x2="54" y2="28" class="stroke-primary" stroke-width="2" />
62
+ </svg>
63
+ </div>
64
+ <div v-else-if="id === 'funnel'" :class="common">
65
+ <svg viewBox="0 0 80 60" class="h-full w-full">
66
+ <polygon points="10,10 70,10 58,22 22,22" class="fill-primary/80" />
67
+ <polygon points="22,24 58,24 50,34 30,34" class="fill-primary/65" />
68
+ <polygon points="30,36 50,36 44,44 36,44" class="fill-primary/50" />
69
+ </svg>
70
+ </div>
71
+ <div v-else-if="id === 'text'" :class="common">
72
+ <div class="space-y-1.5 px-2 py-2">
73
+ <div class="h-2 w-10 rounded bg-primary/70" />
74
+ <div class="h-1.5 w-full rounded bg-muted-foreground/45" />
75
+ <div class="h-1.5 w-11/12 rounded bg-muted-foreground/45" />
76
+ <div class="h-1.5 w-8/12 rounded bg-muted-foreground/45" />
77
+ </div>
78
+ </div>
79
+ <div v-else-if="id === 'rect'" :class="common">
80
+ <div class="absolute inset-2 rounded-md border-2 border-primary/80 bg-primary/15" />
81
+ </div>
82
+ <div v-else-if="id === 'geometry'" :class="common">
83
+ <svg viewBox="0 0 80 60" class="h-full w-full">
84
+ <circle cx="22" cy="18" r="8" class="fill-primary/75" />
85
+ <rect x="36" y="10" width="16" height="16" rx="3" class="fill-primary/55" />
86
+ <polygon points="62,10 70,26 54,26" class="fill-primary/65" />
87
+ <polygon points="24,36 34,42 24,48 14,42" class="fill-primary/50" />
88
+ <polygon points="50,36 54,42 50,48 42,48 38,42 42,36" class="fill-primary/70" />
89
+ </svg>
90
+ </div>
91
+ <div v-else-if="id === 'reference'" :class="common">
92
+ <div class="absolute inset-2 rounded-md border border-dashed border-primary/70 bg-primary/10" />
93
+ <div class="absolute inset-0 flex items-center justify-center text-[10px] text-primary/80">
94
+ 引用组件
95
+ </div>
96
+ </div>
97
+ <div v-else-if="id === 'image'" :class="common">
98
+ <div class="absolute inset-0 bg-gradient-to-br from-primary/25 via-muted/60 to-card" />
99
+ <svg viewBox="0 0 80 60" class="absolute inset-0 h-full w-full">
100
+ <circle cx="16" cy="14" r="5" class="fill-primary/70" />
101
+ <polygon points="0,60 24,34 38,50 52,30 80,60" class="fill-primary/35" />
102
+ <polygon points="0,60 18,42 30,54 44,36 62,60" class="fill-primary/55" />
103
+ </svg>
104
+ </div>
105
+ <div v-else-if="id === 'video'" :class="common">
106
+ <div class="absolute inset-0 bg-primary/12" />
107
+ <div class="absolute left-2 right-2 top-2 h-1.5 rounded bg-primary/35" />
108
+ <div class="absolute inset-0 flex items-center justify-center">
109
+ <div class="rounded-full bg-background/80 p-1">
110
+ <div
111
+ class="h-0 w-0 border-b-[7px] border-l-[11px] border-t-[7px] border-b-transparent border-l-primary/85 border-t-transparent"
112
+ />
113
+ </div>
114
+ </div>
115
+ </div>
116
+ <div v-else-if="id === 'audio'" :class="common">
117
+ <div class="absolute inset-0 flex items-center justify-center gap-1">
118
+ <div class="h-3 w-1.5 rounded bg-primary/50" />
119
+ <div class="h-6 w-1.5 rounded bg-primary/75" />
120
+ <div class="h-8 w-1.5 rounded bg-primary/90" />
121
+ <div class="h-5 w-1.5 rounded bg-primary/70" />
122
+ <div class="h-4 w-1.5 rounded bg-primary/55" />
123
+ </div>
124
+ </div>
125
+ <div v-else-if="id === 'grid'" :class="common">
126
+ <div class="absolute inset-0 grid grid-cols-3 grid-rows-2 gap-1.5 p-2">
127
+ <div
128
+ v-for="idx in 6"
129
+ :key="idx"
130
+ class="rounded-sm border border-dashed border-primary/55 bg-primary/10"
131
+ />
132
+ </div>
133
+ </div>
134
+ <div v-else :class="common" />
135
+ </template>