@qilitt-mickey/vue3-temp-skill 1.0.12 → 1.1.2

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 (42) hide show
  1. package/README.md +66 -214
  2. package/SKILL.md +165 -527
  3. package/package.json +2 -2
  4. package/references/api-check.md +187 -128
  5. package/references/base-code-dict.md +49 -48
  6. package/references/chart-echarts.md +61 -0
  7. package/references/code-quality.md +44 -15
  8. package/references/core-kernel.md +305 -0
  9. package/references/crud-pages.md +87 -261
  10. package/references/data-compare.md +502 -501
  11. package/references/data-mapping.md +218 -213
  12. package/references/data-screen.md +94 -79
  13. package/references/data-writeback.md +104 -104
  14. package/references/detail-page.md +100 -99
  15. package/references/directives-advanced.md +27 -4
  16. package/references/download-export.md +70 -68
  17. package/references/feedback-loading.md +62 -60
  18. package/references/feedback-ui.md +125 -111
  19. package/references/file-management.md +28 -7
  20. package/references/flowchart-g6.md +249 -244
  21. package/references/form-advanced.md +264 -26
  22. package/references/graph-relation.md +12 -7
  23. package/references/http-api.md +138 -103
  24. package/references/icons.md +326 -0
  25. package/references/layout-theme.md +1 -1
  26. package/references/mobile-h5.md +20 -0
  27. package/references/particles.md +143 -0
  28. package/references/permission-auth.md +8 -31
  29. package/references/project-inventory.md +175 -120
  30. package/references/qrcode-barcode.md +107 -92
  31. package/references/rich-text.md +85 -73
  32. package/references/seamless-scroll.md +40 -38
  33. package/references/table-vxe.md +114 -0
  34. package/references/tree-table.md +2 -1
  35. package/references/ui-components.md +5 -1
  36. package/references/verify-captcha.md +110 -96
  37. package/references/websocket-realtime.md +2 -1
  38. package/references/wechat-js.md +58 -0
  39. package/references/workflow-bpmn.md +207 -206
  40. package/references/advanced-ui.md +0 -302
  41. package/references/build-optim.md +0 -282
  42. package/references/vue-core.md +0 -209
@@ -1,213 +1,218 @@
1
- # JsPlumb 数据映射
2
-
3
- ## 依赖
4
-
5
- `jsplumb` — 拖拽连线、字段映射组件。
6
-
7
- ## 页面结构
8
-
9
- ```vue
10
- <script setup lang="ts">
11
- import { jsPlumb } from 'jsplumb'
12
- import { message } from '@/utils/message'
13
-
14
- defineOptions({ name: 'FieldMapping' })
15
-
16
- interface Field {
17
- id: string
18
- name: string
19
- type: string
20
- value?: string
21
- }
22
-
23
- const sourceFields = ref<Field[]>([
24
- { id: 'src_1', name: 'id', type: 'int', value: '1001' },
25
- { id: 'src_2', name: 'organizationid', type: 'int', value: 'ORG_01' },
26
- { id: 'src_3', name: 'brand_id', type: 'int' },
27
- { id: 'src_4', name: 'source', type: 'string' },
28
- ])
29
-
30
- const targetFields = ref<Field[]>([
31
- { id: 'target_1', name: 'id', type: 'BIGINT', value: '' },
32
- { id: 'target_2', name: 'pid', type: 'BIGINT', value: '' },
33
- { id: 'target_3', name: 'tn_id', type: 'BIGINT' },
34
- { id: 'target_4', name: 'vpdm', type: 'STRING' },
35
- ])
36
-
37
- let jsPlumbInstance: any = null
38
- </script>
39
- ```
40
-
41
- ## 初始化 jsPlumb
42
-
43
- ```typescript
44
- const initJsPlumb = () => {
45
- jsPlumbInstance = jsPlumb.getInstance({
46
- // 连接线:Straight(直线) / Bezier(贝塞尔) / Flowchart(折线) / StateMachine(状态机)
47
- Connector: ['Straight', { stub: 0, gap: 1 }],
48
- PaintStyle: { stroke: '#409eff', strokeWidth: 2 },
49
- HoverPaintStyle: { stroke: '#66b1ff', strokeWidth: 3 },
50
- Endpoint: ['Dot', { radius: 6 }],
51
- EndpointStyle: { fill: 'transparent', stroke: 'transparent' },
52
- EndpointHoverStyle: { fill: '#fff', stroke: '#66b1ff' },
53
- Container: 'mapping-container', // 必须指定容器 ID
54
- ConnectionOverlays: [
55
- ['Arrow', {
56
- width: 10, length: 10, location: 1,
57
- id: 'arrow', foldback: 0.8,
58
- }],
59
- ],
60
- ConnectionsDetachable: true, // 允许拖动重连
61
- ReattachConnections: false,
62
- })
63
-
64
- // 点击连线删除
65
- jsPlumbInstance.bind('click', (conn: any) => {
66
- jsPlumbInstance.deleteConnection(conn)
67
- })
68
-
69
- // 注册源和目标
70
- nextTick(() => {
71
- sourceFields.value.forEach((item) => {
72
- jsPlumbInstance.makeSource(item.id, {
73
- anchor: 'Right',
74
- allowLoopback: false,
75
- maxConnections: 1,
76
- filter: 'input, select, textarea, .el-input, .el-select',
77
- filterExclude: true,
78
- })
79
- })
80
- targetFields.value.forEach((item) => {
81
- jsPlumbInstance.makeTarget(item.id, {
82
- anchor: 'Left',
83
- allowLoopback: false,
84
- maxConnections: 2,
85
- })
86
- })
87
- })
88
- }
89
- ```
90
-
91
- ## 常用操作
92
-
93
- ```typescript
94
- // 同行映射(按顺序一一对应)
95
- const handleSameLineMapping = () => {
96
- jsPlumbInstance.deleteEveryConnection()
97
- const len = Math.min(sourceFields.value.length, targetFields.value.length)
98
- for (let i = 0; i < len; i++) {
99
- jsPlumbInstance.connect({
100
- source: sourceFields.value[i].id,
101
- target: targetFields.value[i].id,
102
- })
103
- }
104
- }
105
-
106
- // 清空所有连线
107
- const clearAll = () => {
108
- jsPlumbInstance.deleteEveryConnection()
109
- }
110
-
111
- // 获取映射结果
112
- const getMappingResult = () => {
113
- const connections = jsPlumbInstance.getAllConnections()
114
- return connections.map((conn: any) => ({
115
- source: sourceFields.value.find(f => f.id === conn.sourceId),
116
- target: targetFields.value.find(f => f.id === conn.targetId),
117
- }))
118
- }
119
- ```
120
-
121
- ## 模板结构
122
-
123
- ```vue
124
- <template>
125
- <div class="mapping-page p-4 flex flex-col">
126
- <!-- 操作栏 -->
127
- <el-card shadow="never" class="mb-4">
128
- <div class="flex justify-center gap-4 mt-4">
129
- <el-button @click="handleSameLineMapping">同行映射</el-button>
130
- <el-button type="danger" plain @click="clearAll">清空所有</el-button>
131
- <el-button type="primary" @click="getMappingResult">保存映射</el-button>
132
- </div>
133
- </el-card>
134
-
135
- <!-- 映射区域 -->
136
- <div id="mapping-container" class="flex-1 relative flex justify-between bg-white p-8">
137
- <!-- 左侧源字段 -->
138
- <div class="w-[40%] flex flex-col border rounded">
139
- <div v-for="item in sourceFields" :id="item.id" :key="item.id"
140
- class="field-item flex border-b hover:bg-gray-50 cursor-pointer relative">
141
- <div class="flex-1 p-2">{{ item.name }}</div>
142
- <div class="w-[80px] p-2 text-center">{{ item.type }}</div>
143
- <!-- 连接点 -->
144
- <div :id="`handle-${item.id}`"
145
- class="connector-handle-right absolute right-0 top-1/2 -mt-[6px] translate-x-1/2" />
146
- </div>
147
- </div>
148
-
149
- <!-- 中间留白给连线 -->
150
- <div class="w-[20%]" />
151
-
152
- <!-- 右侧目标字段 -->
153
- <div class="w-[40%] flex flex-col border rounded">
154
- <div v-for="item in targetFields" :id="item.id" :key="item.id"
155
- class="field-item flex border-b hover:bg-gray-50 cursor-pointer relative">
156
- <div :id="`handle-${item.id}`"
157
- class="connector-handle-left absolute left-0 top-1/2 -mt-[6px] -translate-x-1/2" />
158
- <div class="flex-1 p-2">{{ item.name }}</div>
159
- <div class="w-[80px] p-2 text-center">{{ item.type }}</div>
160
- </div>
161
- </div>
162
- </div>
163
- </div>
164
- </template>
165
- ```
166
-
167
- ## 样式
168
-
169
- ```scss
170
- .connector-handle-right,
171
- .connector-handle-left {
172
- z-index: 10;
173
- width: 12px;
174
- height: 12px;
175
- background-color: #fff;
176
- border: 1px solid #409eff;
177
- border-radius: 50%;
178
- }
179
- .connector-handle-right:hover,
180
- .connector-handle-left:hover {
181
- background-color: #e6f7ff;
182
- border-color: #1890ff;
183
- }
184
- :deep(.jtk-endpoint) { z-index: 20; }
185
- :deep(.jtk-connector) { z-index: 10; }
186
- :deep(.jtk-connector.jtk-hover) { z-index: 30; }
187
- ```
188
-
189
- ## 生命周期
190
-
191
- ```typescript
192
- onMounted(() => {
193
- nextTick(() => initJsPlumb())
194
- })
195
-
196
- onUnmounted(() => {
197
- jsPlumbInstance?.reset()
198
- })
199
- ```
200
-
201
- ## 关键约定
202
-
203
- 1. `Container` 必须指定为映射区域的 DOM ID,否则连线定位错误。
204
- 2. 源端点用 `makeSource`,目标端点用 `makeTarget`,锚点分别在 `Right` 和 `Left`。
205
- 3. `filter` 排除表单元素,避免点击输入框时触发连线拖动。
206
- 4. 连线删除通过 `bind('click')` 事件实现,点击连线即删除。
207
- 5. `onUnmounted` 中必须调用 `jsPlumbInstance.reset()` 清理。
208
- 6. 字段行必须有唯一 `id`,jsPlumb 通过 ID 绑定源/目标。
209
- 7. 中间留白区域(`w-[20%]`)给连线绘制空间。
210
-
211
- ## 关联文件
212
-
213
- - [src/views/workflow/jsplumb/index.vue](file:///e:/work/vue3-web-temp/src/views/workflow/jsplumb/index.vue)
1
+ # JsPlumb 数据映射
2
+
3
+ > 按本文件示例编写;必须使用选型表中的 `jsplumb`(见 project-inventory),勿改用同类其他连线库;新依赖须待确认。
4
+
5
+ ## 依赖(缺则先装)
6
+
7
+ | 包 | 版本 |
8
+ |---|---|
9
+ | `jsplumb` | `2.15.6` |
10
+
11
+ ```bash
12
+ pnpm add jsplumb@2.15.6
13
+ ```
14
+
15
+ ## 页面结构
16
+
17
+ ```vue
18
+ <script setup lang="ts">
19
+ import { jsPlumb } from 'jsplumb'
20
+ import { message } from '@/utils/message'
21
+
22
+ defineOptions({ name: 'FieldMapping' })
23
+
24
+ interface Field {
25
+ id: string
26
+ name: string
27
+ type: string
28
+ value?: string
29
+ }
30
+
31
+ const sourceFields = ref<Field[]>([
32
+ { id: 'src_1', name: 'id', type: 'int', value: '1001' },
33
+ { id: 'src_2', name: 'organizationid', type: 'int', value: 'ORG_01' },
34
+ { id: 'src_3', name: 'brand_id', type: 'int' },
35
+ { id: 'src_4', name: 'source', type: 'string' },
36
+ ])
37
+
38
+ const targetFields = ref<Field[]>([
39
+ { id: 'target_1', name: 'id', type: 'BIGINT', value: '' },
40
+ { id: 'target_2', name: 'pid', type: 'BIGINT', value: '' },
41
+ { id: 'target_3', name: 'tn_id', type: 'BIGINT' },
42
+ { id: 'target_4', name: 'vpdm', type: 'STRING' },
43
+ ])
44
+
45
+ let jsPlumbInstance: any = null
46
+ </script>
47
+ ```
48
+
49
+ ## 初始化 jsPlumb
50
+
51
+ ```typescript
52
+ const initJsPlumb = () => {
53
+ jsPlumbInstance = jsPlumb.getInstance({
54
+ // 连接线:Straight(直线) / Bezier(贝塞尔) / Flowchart(折线) / StateMachine(状态机)
55
+ Connector: ['Straight', { stub: 0, gap: 1 }],
56
+ PaintStyle: { stroke: '#409eff', strokeWidth: 2 },
57
+ HoverPaintStyle: { stroke: '#66b1ff', strokeWidth: 3 },
58
+ Endpoint: ['Dot', { radius: 6 }],
59
+ EndpointStyle: { fill: 'transparent', stroke: 'transparent' },
60
+ EndpointHoverStyle: { fill: '#fff', stroke: '#66b1ff' },
61
+ Container: 'mapping-container', // 必须指定容器 ID
62
+ ConnectionOverlays: [
63
+ ['Arrow', {
64
+ width: 10, length: 10, location: 1,
65
+ id: 'arrow', foldback: 0.8,
66
+ }],
67
+ ],
68
+ ConnectionsDetachable: true, // 允许拖动重连
69
+ ReattachConnections: false,
70
+ })
71
+
72
+ // 点击连线删除
73
+ jsPlumbInstance.bind('click', (conn: any) => {
74
+ jsPlumbInstance.deleteConnection(conn)
75
+ })
76
+
77
+ // 注册源和目标
78
+ nextTick(() => {
79
+ sourceFields.value.forEach((item) => {
80
+ jsPlumbInstance.makeSource(item.id, {
81
+ anchor: 'Right',
82
+ allowLoopback: false,
83
+ maxConnections: 1,
84
+ filter: 'input, select, textarea, .el-input, .el-select',
85
+ filterExclude: true,
86
+ })
87
+ })
88
+ targetFields.value.forEach((item) => {
89
+ jsPlumbInstance.makeTarget(item.id, {
90
+ anchor: 'Left',
91
+ allowLoopback: false,
92
+ maxConnections: 2,
93
+ })
94
+ })
95
+ })
96
+ }
97
+ ```
98
+
99
+ ## 常用操作
100
+
101
+ ```typescript
102
+ // 同行映射(按顺序一一对应)
103
+ const handleSameLineMapping = () => {
104
+ jsPlumbInstance.deleteEveryConnection()
105
+ const len = Math.min(sourceFields.value.length, targetFields.value.length)
106
+ for (let i = 0; i < len; i++) {
107
+ jsPlumbInstance.connect({
108
+ source: sourceFields.value[i].id,
109
+ target: targetFields.value[i].id,
110
+ })
111
+ }
112
+ }
113
+
114
+ // 清空所有连线
115
+ const clearAll = () => {
116
+ jsPlumbInstance.deleteEveryConnection()
117
+ }
118
+
119
+ // 获取映射结果
120
+ const getMappingResult = () => {
121
+ const connections = jsPlumbInstance.getAllConnections()
122
+ return connections.map((conn: any) => ({
123
+ source: sourceFields.value.find(f => f.id === conn.sourceId),
124
+ target: targetFields.value.find(f => f.id === conn.targetId),
125
+ }))
126
+ }
127
+ ```
128
+
129
+ ## 模板结构
130
+
131
+ ```vue
132
+ <template>
133
+ <div class="mapping-page p-4 flex flex-col">
134
+ <!-- 操作栏 -->
135
+ <el-card shadow="never" class="mb-4">
136
+ <div class="flex justify-center gap-4 mt-4">
137
+ <el-button @click="handleSameLineMapping">同行映射</el-button>
138
+ <el-button type="danger" plain @click="clearAll">清空所有</el-button>
139
+ <el-button type="primary" @click="getMappingResult">保存映射</el-button>
140
+ </div>
141
+ </el-card>
142
+
143
+ <!-- 映射区域 -->
144
+ <div id="mapping-container" class="flex-1 relative flex justify-between bg-white p-8">
145
+ <!-- 左侧源字段 -->
146
+ <div class="w-[40%] flex flex-col border rounded">
147
+ <div v-for="item in sourceFields" :id="item.id" :key="item.id"
148
+ class="field-item flex border-b hover:bg-gray-50 cursor-pointer relative">
149
+ <div class="flex-1 p-2">{{ item.name }}</div>
150
+ <div class="w-[80px] p-2 text-center">{{ item.type }}</div>
151
+ <!-- 连接点 -->
152
+ <div :id="`handle-${item.id}`"
153
+ class="connector-handle-right absolute right-0 top-1/2 -mt-[6px] translate-x-1/2" />
154
+ </div>
155
+ </div>
156
+
157
+ <!-- 中间留白给连线 -->
158
+ <div class="w-[20%]" />
159
+
160
+ <!-- 右侧目标字段 -->
161
+ <div class="w-[40%] flex flex-col border rounded">
162
+ <div v-for="item in targetFields" :id="item.id" :key="item.id"
163
+ class="field-item flex border-b hover:bg-gray-50 cursor-pointer relative">
164
+ <div :id="`handle-${item.id}`"
165
+ class="connector-handle-left absolute left-0 top-1/2 -mt-[6px] -translate-x-1/2" />
166
+ <div class="flex-1 p-2">{{ item.name }}</div>
167
+ <div class="w-[80px] p-2 text-center">{{ item.type }}</div>
168
+ </div>
169
+ </div>
170
+ </div>
171
+ </div>
172
+ </template>
173
+ ```
174
+
175
+ ## 样式
176
+
177
+ ```scss
178
+ .connector-handle-right,
179
+ .connector-handle-left {
180
+ z-index: 10;
181
+ width: 12px;
182
+ height: 12px;
183
+ background-color: #fff;
184
+ border: 1px solid #409eff;
185
+ border-radius: 50%;
186
+ }
187
+ .connector-handle-right:hover,
188
+ .connector-handle-left:hover {
189
+ background-color: #e6f7ff;
190
+ border-color: #1890ff;
191
+ }
192
+ :deep(.jtk-endpoint) { z-index: 20; }
193
+ :deep(.jtk-connector) { z-index: 10; }
194
+ :deep(.jtk-connector.jtk-hover) { z-index: 30; }
195
+ ```
196
+
197
+ ## 生命周期
198
+
199
+ ```typescript
200
+ onMounted(() => {
201
+ nextTick(() => initJsPlumb())
202
+ })
203
+
204
+ onUnmounted(() => {
205
+ jsPlumbInstance?.reset()
206
+ })
207
+ ```
208
+
209
+ ## 关键约定
210
+
211
+ 1. `Container` 必须指定为映射区域的 DOM ID,否则连线定位错误。
212
+ 2. 源端点用 `makeSource`,目标端点用 `makeTarget`,锚点分别在 `Right` 和 `Left`。
213
+ 3. `filter` 排除表单元素,避免点击输入框时触发连线拖动。
214
+ 4. 连线删除通过 `bind('click')` 事件实现,点击连线即删除。
215
+ 5. `onUnmounted` 中必须调用 `jsPlumbInstance.reset()` 清理。
216
+ 6. 字段行必须有唯一 `id`,jsPlumb 通过 ID 绑定源/目标。
217
+ 7. 中间留白区域(`w-[20%]`)给连线绘制空间。
218
+
@@ -1,79 +1,94 @@
1
- # 数据大屏
2
-
3
- 规范数据大屏/可视化大屏的等比缩放、响应式适配与布局结构。在开发大屏展示页面时参照。
4
-
5
- ## 布局结构
6
-
7
- 大屏页面使用固定设计稿尺寸(默认 `1920x1080`),通过 CSS `transform: scale()` 实现等比缩放。
8
-
9
- ```vue
10
- <script setup lang="ts">
11
- defineOptions({ name: 'DataScreen' })
12
-
13
- const dataScreenRef = ref<HTMLElement | null>(null)
14
-
15
- onMounted(() => {
16
- resize()
17
- window.addEventListener('resize', resize)
18
- })
19
-
20
- onBeforeUnmount(() => {
21
- window.removeEventListener('resize', resize)
22
- })
23
-
24
- function resize() {
25
- if (!dataScreenRef.value) return
26
- dataScreenRef.value.style.transform = `scale(${getScale()}) translate(-50%, -50%)`
27
- }
28
-
29
- function getScale(width = 1920, height = 1080) {
30
- const ww = window.innerWidth / width
31
- const wh = window.innerHeight / height
32
- return ww < wh ? ww : wh
33
- }
34
- </script>
35
-
36
- <template>
37
- <div class="s-container">
38
- <div ref="dataScreenRef" class="s-content">
39
- <!-- 头部 -->
40
- <div class="s-header">...</div>
41
- <!-- 主体:左/中/右三栏 -->
42
- <div class="s-main">
43
- <div class="main-lf">...</div>
44
- <div class="main-ct">...</div>
45
- <div class="main-ri">...</div>
46
- </div>
47
- </div>
48
- </div>
49
- </template>
50
- ```
51
-
52
- ## 样式约定
53
-
54
- ```scss
55
- .s-container {
56
- position: relative;
57
- width: 100vw;
58
- height: 100vh;
59
- overflow: hidden;
60
- background: #000;
61
- }
62
-
63
- .s-content {
64
- position: absolute;
65
- top: 50%;
66
- left: 50%;
67
- width: 1920px;
68
- height: 1080px;
69
- transform-origin: 0 0;
70
- }
71
- ```
72
-
73
- ## 关键约定
74
-
75
- 1. 设计稿宽度、高度与 `getScale` 参数保持一致。
76
- 2. 内部元素全部按设计稿像素开发,不要写响应式单位。
77
- 3. 图表组件在 `resize` 时调用 `chart.resize()` 重新自适应(如使用 ECharts)。
78
- 4. 定时器在 `onBeforeUnmount` 中清理。
79
- 5. 大屏通常不需要展示菜单/页头,使用独立布局或全屏路由。
1
+ # 数据大屏
2
+
3
+ > 按本文件示例编写,复用项目已有布局与图表封装;缩放/适配按本规范实现,勿另起一套大屏方案。
4
+
5
+ 规范数据大屏/可视化大屏的等比缩放、响应式适配与布局结构。在开发大屏展示页面时参照。
6
+
7
+ ## 依赖(缺则先装)
8
+
9
+ | 包 | 版本 | 说明 |
10
+ |---|---|---|
11
+ | `grid-layout-plus` | `1.1.1` | 拖拽网格 widget(按需) |
12
+ | `echarts` | `6.1.0` | 图表时叠加 `chart-echarts` |
13
+
14
+ ```bash
15
+ pnpm add grid-layout-plus@1.1.1
16
+ # 需要图表时:
17
+ pnpm add echarts@6.1.0
18
+ ```
19
+
20
+ ## 布局结构
21
+
22
+ 大屏页面使用固定设计稿尺寸(默认 `1920x1080`),通过 CSS `transform: scale()` 实现等比缩放。
23
+
24
+ ```vue
25
+ <script setup lang="ts">
26
+ defineOptions({ name: 'DataScreen' })
27
+
28
+ const dataScreenRef = ref<HTMLElement | null>(null)
29
+
30
+ onMounted(() => {
31
+ resize()
32
+ window.addEventListener('resize', resize)
33
+ })
34
+
35
+ onBeforeUnmount(() => {
36
+ window.removeEventListener('resize', resize)
37
+ })
38
+
39
+ function resize() {
40
+ if (!dataScreenRef.value) return
41
+ dataScreenRef.value.style.transform = `scale(${getScale()}) translate(-50%, -50%)`
42
+ }
43
+
44
+ function getScale(width = 1920, height = 1080) {
45
+ const ww = window.innerWidth / width
46
+ const wh = window.innerHeight / height
47
+ return ww < wh ? ww : wh
48
+ }
49
+ </script>
50
+
51
+ <template>
52
+ <div class="s-container">
53
+ <div ref="dataScreenRef" class="s-content">
54
+ <!-- 头部 -->
55
+ <div class="s-header">...</div>
56
+ <!-- 主体:左/中/右三栏 -->
57
+ <div class="s-main">
58
+ <div class="main-lf">...</div>
59
+ <div class="main-ct">...</div>
60
+ <div class="main-ri">...</div>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </template>
65
+ ```
66
+
67
+ ## 样式约定
68
+
69
+ ```scss
70
+ .s-container {
71
+ position: relative;
72
+ width: 100vw;
73
+ height: 100vh;
74
+ overflow: hidden;
75
+ background: #000;
76
+ }
77
+
78
+ .s-content {
79
+ position: absolute;
80
+ top: 50%;
81
+ left: 50%;
82
+ width: 1920px;
83
+ height: 1080px;
84
+ transform-origin: 0 0;
85
+ }
86
+ ```
87
+
88
+ ## 关键约定
89
+
90
+ 1. 设计稿宽度、高度与 `getScale` 参数保持一致。
91
+ 2. 内部元素全部按设计稿像素开发,不要写响应式单位。
92
+ 3. 图表组件在 `resize` 时调用 `chart.resize()` 重新自适应(如使用 ECharts)。
93
+ 4. 定时器在 `onBeforeUnmount` 中清理。
94
+ 5. 大屏通常不需要展示菜单/页头,使用独立布局或全屏路由。