@chuchen3d/wallpaper-plugin-sdk 0.0.0-stage → 1.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.
@@ -0,0 +1,76 @@
1
+ /**
2
+ * validate 命令 — 校验 manifest.json 格式
3
+ */
4
+
5
+ import fs from 'fs'
6
+ import path from 'path'
7
+ import { colors, printSeparator } from '../utils/colors.js'
8
+ import { validateManifest } from '../utils/validator.js'
9
+
10
+ /** 校验 manifest.json 格式 */
11
+ export async function cmdValidate() {
12
+ console.log('')
13
+ console.log(colors.title('✅ 校验 manifest.json'))
14
+ printSeparator()
15
+
16
+ const projectDir = process.cwd()
17
+
18
+ // 1. 读取 manifest.json
19
+ const manifestPath = path.join(projectDir, 'manifest.json')
20
+ if (!fs.existsSync(manifestPath)) {
21
+ console.log(colors.error('未找到 manifest.json'))
22
+ console.log(' 请在壁纸项目根目录下执行此命令')
23
+ process.exit(1)
24
+ }
25
+
26
+ let manifest
27
+ try {
28
+ const content = fs.readFileSync(manifestPath, 'utf-8')
29
+ manifest = JSON.parse(content)
30
+ } catch (err) {
31
+ console.log(colors.error(`manifest.json 解析失败: ${err.message}`))
32
+ process.exit(1)
33
+ }
34
+
35
+ // 2. 校验
36
+ const validation = validateManifest(manifest, projectDir)
37
+
38
+ // 3. 输出结果
39
+ console.log('')
40
+ console.log('📋 基本信息:')
41
+ console.log(` ID: ${manifest.id || '(未设置)'}`)
42
+ console.log(` 名称: ${manifest.name || '(未设置)'}`)
43
+ console.log(` 版本: ${manifest.version || '(未设置)'}`)
44
+ console.log(` 作者: ${manifest.author?.name || '(未设置)'}`)
45
+ console.log(` 入口: ${manifest.entry || '(未设置)'}`)
46
+ console.log('')
47
+
48
+ if (validation.valid) {
49
+ console.log(colors.success('✅ manifest.json 校验通过'))
50
+ } else {
51
+ console.log(colors.error('❌ manifest.json 校验失败'))
52
+ }
53
+
54
+ if (validation.errors.length > 0) {
55
+ console.log('')
56
+ console.log('❌ 错误:')
57
+ for (const error of validation.errors) {
58
+ console.log(` ${error}`)
59
+ }
60
+ }
61
+
62
+ if (validation.warnings.length > 0) {
63
+ console.log('')
64
+ console.log('⚠️ 建议:')
65
+ for (const warning of validation.warnings) {
66
+ console.log(` ${warning}`)
67
+ }
68
+ }
69
+
70
+ console.log('')
71
+
72
+ // 4. 退出码
73
+ if (!validation.valid) {
74
+ process.exit(1)
75
+ }
76
+ }
package/src/index.js ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * ════════════════════════════════════════════════════════════
3
+ * 🧩 初尘3D壁纸 — 插件 SDK 主入口
4
+ * ════════════════════════════════════════════════════════════
5
+ *
6
+ * 支持两种使用方式:
7
+ * 1. CLI 命令行:chuchen-plugin init/build/validate/example
8
+ * 2. 程序化调用:import { ... } from '@chuchen3d/wallpaper-plugin-sdk'
9
+ *
10
+ * @author Chuchen Team
11
+ * @version 1.0.0
12
+ */
13
+
14
+ import { createRequire } from 'module'
15
+
16
+ // ===== 命令函数(供程序化调用)=====
17
+ export { cmdInit } from './commands/init.js'
18
+ export { cmdBuild } from './commands/build.js'
19
+ export { cmdValidate } from './commands/validate.js'
20
+ export { cmdExample } from './commands/example.js'
21
+
22
+ // ===== 模板生成器 =====
23
+ export { generateManifest } from './templates/manifest.js'
24
+ export { generateVueComponentTemplate } from './templates/vue-component.js'
25
+ export { generateDevProjectFiles } from './templates/dev-project.js'
26
+
27
+ // ===== 工具函数 =====
28
+ export { validateManifest } from './utils/validator.js'
29
+ export { packProject, packFromDist } from './utils/packager.js'
30
+ export { convertParamsToAppFormat } from './utils/paramsConverter.js'
31
+ export { buildPlugin } from './utils/viteBuilder.js'
32
+
33
+ // ===== 构建模板 =====
34
+ export { generatePluginEntry } from './templates/plugin-entry.js'
35
+
36
+ // ===== 版本信息 =====
37
+ const require = createRequire(import.meta.url)
38
+ const pkg = require('../../package.json')
39
+ export const VERSION = pkg.version
40
+ export const PACKAGE_NAME = pkg.name
@@ -0,0 +1,357 @@
1
+ /**
2
+ * 开发环境项目模板生成器
3
+ *
4
+ * 生成以下文件:
5
+ * - package.json(vite + vue + three 依赖)
6
+ * - vite.config.js(Vue 插件配置)
7
+ * - index.html(开发预览入口)
8
+ * - dev-preview.js(Mock props 注入脚本)
9
+ */
10
+
11
+ /** 生成开发环境文件集合 */
12
+ export function generateDevProjectFiles(componentName, manifest) {
13
+ const entry = manifest.entry || `${componentName}.vue`;
14
+ const params = manifest.params || {};
15
+ const hasThree =
16
+ manifest.category === "tech" ||
17
+ Object.keys(params).some((k) =>
18
+ ["particleCount", "rotationSpeed"].includes(k),
19
+ );
20
+
21
+ return {
22
+ "package.json": generatePackageJson(componentName, hasThree),
23
+ "vite.config.js": generateViteConfig(),
24
+ "index.html": generateIndexHtml(componentName, manifest),
25
+ "dev-preview.js": generateDevPreviewJs(componentName, manifest),
26
+ };
27
+ }
28
+
29
+ /** 生成 package.json */
30
+ function generatePackageJson(projectName, hasThree) {
31
+ const deps = {
32
+ vue: "^3.4.0",
33
+ vite: "^5.2.0",
34
+ "@vitejs/plugin-vue": "^5.0.0",
35
+ };
36
+
37
+ if (hasThree) {
38
+ deps.three = "^0.162.0";
39
+ }
40
+
41
+ return JSON.stringify(
42
+ {
43
+ name: projectName,
44
+ version: "1.0.0",
45
+ private: true,
46
+ type: "module",
47
+ scripts: {
48
+ dev: "vite",
49
+ build: "vite build",
50
+ preview: "vite preview",
51
+ },
52
+ dependencies: deps,
53
+ },
54
+ null,
55
+ 2,
56
+ );
57
+ }
58
+
59
+ /** 生成 vite.config.js */
60
+ function generateViteConfig() {
61
+ return `import { defineConfig } from 'vite'
62
+ import vue from '@vitejs/plugin-vue'
63
+ import { fileURLToPath, URL } from 'node:url'
64
+
65
+ export default defineConfig({
66
+ plugins: [vue()],
67
+ resolve: {
68
+ alias: {
69
+ // ★ 必须指向含运行时编译器的完整构建
70
+ // 因为 dev-preview.js 使用了 createApp({ template: ... }) 运行时模板,
71
+ // 默认的 vue.runtime-esm-bundler 不包含模板编译器,会报错:
72
+ // "Component provided template option but runtime compilation is not supported"
73
+ 'vue': 'vue/dist/vue.esm-bundler.js',
74
+ '@': fileURLToPath(new URL('./src', import.meta.url)),
75
+ },
76
+ },
77
+ server: {
78
+ port: 3000,
79
+ open: true,
80
+ },
81
+ })
82
+ `;
83
+ }
84
+
85
+ /** 生成 index.html */
86
+ function generateIndexHtml(componentName, manifest) {
87
+ const entry = manifest.entry || `${componentName}.vue`;
88
+ const name = manifest.name || componentName;
89
+ const params = manifest.params || {};
90
+
91
+ return `<!DOCTYPE html>
92
+ <html lang="zh-CN">
93
+ <head>
94
+ <meta charset="UTF-8">
95
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
96
+ <title>🎨 ${name} — 初尘3D壁纸插件预览</title>
97
+ <style>
98
+ * { margin: 0; padding: 0; box-sizing: border-box; }
99
+ body {
100
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
101
+ background: #0a0a0a;
102
+ color: #fff;
103
+ overflow: hidden;
104
+ }
105
+ #preview-container {
106
+ width: 100vw;
107
+ height: 100vh;
108
+ position: relative;
109
+ }
110
+ #param-panel {
111
+ position: fixed;
112
+ top: 16px;
113
+ right: 16px;
114
+ background: rgba(0, 0, 0, 0.75);
115
+ backdrop-filter: blur(12px);
116
+ border: 1px solid rgba(255, 255, 255, 0.15);
117
+ border-radius: 12px;
118
+ padding: 16px;
119
+ min-width: 220px;
120
+ z-index: 100;
121
+ font-size: 13px;
122
+ }
123
+ #param-panel h3 {
124
+ margin-bottom: 12px;
125
+ font-size: 14px;
126
+ color: #a78bfa;
127
+ }
128
+ #param-panel label {
129
+ display: flex;
130
+ align-items: center;
131
+ justify-content: space-between;
132
+ margin-bottom: 8px;
133
+ gap: 8px;
134
+ }
135
+ #param-panel input[type="range"] {
136
+ width: 100px;
137
+ }
138
+ #param-panel input[type="color"] {
139
+ width: 40px;
140
+ height: 24px;
141
+ border: none;
142
+ border-radius: 4px;
143
+ cursor: pointer;
144
+ }
145
+ #param-panel input[type="checkbox"] {
146
+ width: 18px;
147
+ height: 18px;
148
+ cursor: pointer;
149
+ }
150
+ #param-panel select {
151
+ background: #1a1a2e;
152
+ color: #fff;
153
+ border: 1px solid #333;
154
+ border-radius: 4px;
155
+ padding: 4px 8px;
156
+ font-size: 12px;
157
+ }
158
+ #param-panel span {
159
+ min-width: 36px;
160
+ text-align: right;
161
+ color: #888;
162
+ font-family: monospace;
163
+ }
164
+ #toggle-btn {
165
+ position: fixed;
166
+ top: 16px;
167
+ right: 16px;
168
+ z-index: 101;
169
+ background: rgba(0, 0, 0, 0.6);
170
+ border: 1px solid rgba(255, 255, 255, 0.2);
171
+ color: #fff;
172
+ width: 32px;
173
+ height: 32px;
174
+ border-radius: 8px;
175
+ cursor: pointer;
176
+ font-size: 16px;
177
+ display: none;
178
+ }
179
+ #info-bar {
180
+ position: fixed;
181
+ bottom: 16px;
182
+ left: 50%;
183
+ transform: translateX(-50%);
184
+ background: rgba(0, 0, 0, 0.6);
185
+ backdrop-filter: blur(8px);
186
+ border: 1px solid rgba(255, 255, 255, 0.1);
187
+ border-radius: 8px;
188
+ padding: 8px 16px;
189
+ font-size: 12px;
190
+ color: #888;
191
+ z-index: 50;
192
+ }
193
+ </style>
194
+ </head>
195
+ <body>
196
+ <div id="preview-container">
197
+ <!-- 壁纸组件将挂载到这里 -->
198
+ </div>
199
+
200
+ <!-- 参数面板 -->
201
+ <div id="param-panel">
202
+ <h3>🎛️ 参数调节</h3>
203
+ ${generateParamPanelHTML(params)}
204
+ </div>
205
+
206
+ <!-- 底部信息栏 -->
207
+ <div id="info-bar">
208
+ 🎨 ${name} v${manifest.version || "1.0.0"} — 初尘3D壁纸插件预览模式
209
+ </div>
210
+
211
+ <script type="module" src="/dev-preview.js"></script>
212
+ </body>
213
+ </html>
214
+ `;
215
+ }
216
+
217
+ /** 生成参数面板 HTML */
218
+ function generateParamPanelHTML(params) {
219
+ return Object.entries(params)
220
+ .map(([key, def]) => {
221
+ const label = def.label || key;
222
+ if (def.type === "number") {
223
+ return `<label>${label}: <input type="range" id="param-${key}" min="${def.min ?? 0}" max="${def.max ?? 100}" step="${def.step ?? 1}" value="${def.default}"> <span id="val-${key}">${def.default}</span></label>`;
224
+ }
225
+ if (def.type === "toggle") {
226
+ return `<label>${label}: <input type="checkbox" id="param-${key}" ${def.default ? "checked" : ""}></label>`;
227
+ }
228
+ if (def.type === "color") {
229
+ return `<label>${label}: <input type="color" id="param-${key}" value="${def.default}"></label>`;
230
+ }
231
+ if (def.type === "select") {
232
+ const options = (def.options || [])
233
+ .map((opt) => `<option value="${opt.value}">${opt.label}</option>`)
234
+ .join("");
235
+ return `<label>${label}: <select id="param-${key}">${options}</select></label>`;
236
+ }
237
+ return "";
238
+ })
239
+ .filter(Boolean)
240
+ .join("\n ");
241
+ }
242
+
243
+ /** 生成 dev-preview.js(Mock props 注入脚本) */
244
+ function generateDevPreviewJs(componentName, manifest) {
245
+ const entry = manifest.entry || `${componentName}.vue`;
246
+ const params = manifest.params || {};
247
+
248
+ return `/**
249
+ * dev-preview.js — Mock props 注入脚本
250
+ *
251
+ * 模拟初尘3D壁纸的 props 注入,让开发者可以独立预览和调试壁纸组件。
252
+ * 打包时会自动排除此文件。
253
+ */
254
+
255
+ import { createApp, ref, onMounted, onUnmounted, watch } from 'vue'
256
+ import WallpaperComponent from './${entry}'
257
+
258
+ // ===== 创建 Vue 应用 =====
259
+ const app = createApp({
260
+ setup() {
261
+ // ★ 所有在模板中使用的 ref 必须在 setup() 中定义并通过 return 暴露
262
+ const containerRef = ref(null)
263
+
264
+ // Mock 参数值
265
+ const paramValues = ref({})
266
+ // 初始化参数默认值
267
+ ${Object.entries(params)
268
+ .map(([key, def]) => {
269
+ return `paramValues.value['${key}'] = ${JSON.stringify(def.default)}`;
270
+ })
271
+ .join("\n ")}
272
+
273
+ // Mock 性能策略
274
+ const performancePolicy = ref({
275
+ fpsLimit: 60,
276
+ pixelRatioCap: 2.0,
277
+ mouseInteraction: true,
278
+ })
279
+
280
+ onMounted(() => {
281
+ containerRef.value = document.getElementById('preview-container')
282
+ })
283
+
284
+ // ★ 必须返回所有模板中使用的响应式变量
285
+ return { containerRef, paramValues, performancePolicy }
286
+ },
287
+ template: \`
288
+ <div id="preview-container" ref="containerRef">
289
+ <WallpaperComponent
290
+ :containerRef="containerRef"
291
+ :paramValues="paramValues"
292
+ :performancePolicy="performancePolicy"
293
+ />
294
+ </div>
295
+ \`,
296
+ components: { WallpaperComponent },
297
+ })
298
+
299
+ app.mount('#preview-container')
300
+
301
+ // ===== 参数面板事件绑定 =====
302
+ ${Object.entries(params)
303
+ .map(([key, def]) => {
304
+ if (def.type === "number") {
305
+ return `
306
+ // ${def.label || key} 滑块
307
+ const ${key}Slider = document.getElementById('param-${key}')
308
+ const ${key}Val = document.getElementById('val-${key}')
309
+ if (${key}Slider) {
310
+ ${key}Slider.addEventListener('input', (e) => {
311
+ const val = parseFloat(e.target.value)
312
+ // ★ 通过 app._instance.setupState 访问 setup 中的 ref
313
+ app._instance.setupState.paramValues['${key}'] = val
314
+ if (${key}Val) ${key}Val.textContent = val.toFixed(2)
315
+ })
316
+ }`;
317
+ }
318
+ if (def.type === "toggle") {
319
+ return `
320
+ // ${def.label || key} 开关
321
+ const ${key}Checkbox = document.getElementById('param-${key}')
322
+ if (${key}Checkbox) {
323
+ ${key}Checkbox.addEventListener('change', (e) => {
324
+ app._instance.setupState.paramValues['${key}'] = e.target.checked
325
+ })
326
+ }`;
327
+ }
328
+ if (def.type === "color") {
329
+ return `
330
+ // ${def.label || key} 颜色选择器
331
+ const ${key}Color = document.getElementById('param-${key}')
332
+ if (${key}Color) {
333
+ ${key}Color.addEventListener('input', (e) => {
334
+ app._instance.setupState.paramValues['${key}'] = e.target.value
335
+ })
336
+ }`;
337
+ }
338
+ if (def.type === "select") {
339
+ return `
340
+ // ${def.label || key} 下拉选择
341
+ const ${key}Select = document.getElementById('param-${key}')
342
+ if (${key}Select) {
343
+ ${key}Select.addEventListener('change', (e) => {
344
+ app._instance.setupState.paramValues['${key}'] = e.target.value
345
+ })
346
+ }`;
347
+ }
348
+ return "";
349
+ })
350
+ .filter(Boolean)
351
+ .join("\n")}
352
+
353
+ console.log('🎨 初尘3D壁纸插件预览模式已启动')
354
+ console.log('📦 组件:', '${entry}')
355
+ console.log('🎛️ 参数:', Object.keys(app._instance.setupState.paramValues).length, '个')
356
+ `;
357
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * manifest.json 模板生成器
3
+ */
4
+
5
+ /** 生成 manifest.json 对象 */
6
+ export function generateManifest(projectName, options = {}) {
7
+ const id = projectName || 'my-wallpaper'
8
+ const name = options.name || toTitleCase(id)
9
+ const description = options.description || '一款精美的初尘3D壁纸插件'
10
+ const author = options.author || '开发者'
11
+ const category = options.category || 'abstract'
12
+ const three = options.three || false
13
+
14
+ return {
15
+ schemaVersion: '2.0',
16
+ id: id,
17
+ name: name,
18
+ version: '1.0.0',
19
+ author: {
20
+ name: author,
21
+ },
22
+ description: description,
23
+ tags: [category],
24
+ category: category,
25
+ entry: `${toPascalCase(id)}.vue`,
26
+ files: [`${toPascalCase(id)}.vue`],
27
+ params: generateDefaultParams(category, three),
28
+ previewBg: getDefaultPreviewBg(category),
29
+ emoji: getDefaultEmoji(category),
30
+ minAppVersion: '1.0.0',
31
+
32
+ // ★ 真隔离重构(2026-10-08):沙箱运行时字段。
33
+ // network.domains 是沙箱 CSP connect-src/img-src 的唯一放行依据(默认全禁网),
34
+ // 上架审核时对审核员可见;访问未声明域名会被浏览器层拦截。
35
+ // 示例:network: { domains: ['https://devapi.qweather.com'] },
36
+ apiVersion: 1,
37
+ network: { domains: [] },
38
+
39
+ // ★ 预览视频/GIF(可选)。支持 mp4 / webm / mov / gif,video ≤10MB,gif ≤5MB。
40
+ // 路径相对于项目根;构建时由 SDK 打入 .yizirwp 的 preview/ 子目录。
41
+ // 服务端首次上传会自动抽取到独立存储;后续可由 admin 或 dev 通过预览管理 UI 替换。
42
+ // 示例:preview: 'preview/preview.mp4',
43
+
44
+ // 商业字段(默认免费,开发者可覆盖)
45
+ isPremium: false,
46
+ price: 0,
47
+ tier: 'free',
48
+ }
49
+ }
50
+
51
+ /** 生成默认参数 */
52
+ function generateDefaultParams(category, three) {
53
+ const base = {
54
+ opacity: {
55
+ type: 'number',
56
+ default: 1.0,
57
+ min: 0,
58
+ max: 1,
59
+ step: 0.05,
60
+ label: '透明度',
61
+ },
62
+ speed: {
63
+ type: 'number',
64
+ default: 1.0,
65
+ min: 0.1,
66
+ max: 3,
67
+ step: 0.1,
68
+ label: '动画速度',
69
+ },
70
+ }
71
+
72
+ if (category === 'natural') {
73
+ return {
74
+ ...base,
75
+ intensity: {
76
+ type: 'number',
77
+ default: 0.5,
78
+ min: 0,
79
+ max: 1,
80
+ step: 0.05,
81
+ label: '效果强度',
82
+ },
83
+ }
84
+ }
85
+
86
+ if (category === 'tech' || three) {
87
+ return {
88
+ ...base,
89
+ particleCount: {
90
+ type: 'number',
91
+ default: 1000,
92
+ min: 100,
93
+ max: 10000,
94
+ step: 100,
95
+ label: '粒子数量',
96
+ },
97
+ rotationSpeed: {
98
+ type: 'number',
99
+ default: 0.5,
100
+ min: 0,
101
+ max: 2,
102
+ step: 0.1,
103
+ label: '旋转速度',
104
+ },
105
+ }
106
+ }
107
+
108
+ return base
109
+ }
110
+
111
+ /** 获取默认预览背景 */
112
+ function getDefaultPreviewBg(category) {
113
+ const presets = {
114
+ natural: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
115
+ tech: 'linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%)',
116
+ abstract: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
117
+ festival: 'linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%)',
118
+ utility: 'linear-gradient(135deg, #a1c4fd 0%, #c2e9fb 100%)',
119
+ }
120
+ return presets[category] || presets.abstract
121
+ }
122
+
123
+ /** 获取默认 Emoji */
124
+ function getDefaultEmoji(category) {
125
+ const presets = {
126
+ natural: '🌿',
127
+ tech: '⚡',
128
+ abstract: '🎨',
129
+ festival: '🎉',
130
+ utility: '📊',
131
+ }
132
+ return presets[category] || presets.abstract
133
+ }
134
+
135
+ /** 工具函数:kebab-case → Title Case */
136
+ function toTitleCase(str) {
137
+ return str.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase())
138
+ }
139
+
140
+ /** 工具函数:kebab-case → PascalCase */
141
+ function toPascalCase(str) {
142
+ return str.replace(/(?:^|-)(\w)/g, (_, c) => c.toUpperCase())
143
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * 插件入口模板(生成器)
3
+ *
4
+ * ★ 真隔离重构(2026-10-08):适配沙箱运行时(opaque origin iframe)。
5
+ * 设计文档:docs/superpowers/specs/2026-10-08-plugin-sandbox-isolation-design.md
6
+ *
7
+ * 此模板在 build 阶段生成一个临时入口 .js 文件,作为 Vite 的 build.lib.entry。
8
+ * 它负责:
9
+ * 1. 导入开发者编写的 Vue SFC 组件(连同 Vue/Three 依赖全部打进自包含 IIFE)
10
+ * 2. 从沙箱 Shim(window.__YIZIRAN_PLUGIN_API__,srcdoc 内联)获取宿主 API
11
+ * 3. 创建 Vue 应用,传入 containerRef/paramValues/performancePolicy props
12
+ * (★ 组件 props 契约与旧版完全一致 —— 包作者组件代码零改动)
13
+ * 4. 监听参数和性能策略变化,响应式更新组件
14
+ * 5. 通知宿主插件已就绪
15
+ *
16
+ * 与旧版(宿主注入时代)的差异:
17
+ * - Vue 由包自带(不再 window.__YIZIRAN_VUE__)
18
+ * - 挂载点是沙箱 iframe 自身的 document.body(iframe 即受约束的渲染根)
19
+ * - 无 api._bindApp(宿主不捕获 app;iframe 移除即整体销毁)
20
+ * - beforeunload 时 app.unmount() 触发包组件的 onUnmounted 清理(软清理兜底)
21
+ */
22
+
23
+ /**
24
+ * 生成插件入口代码
25
+ *
26
+ * @param {string} componentImportPath — Vue SFC 组件的相对路径(如 './CodePlanet.vue')
27
+ * @returns {string} 入口文件源码字符串
28
+ */
29
+ export function generatePluginEntry(componentImportPath) {
30
+ return `// ⚠️ 此文件由 chuchen-plugin build 自动生成,请勿手动编辑
31
+ import PluginComponent from '${componentImportPath}'
32
+ import { createApp, reactive } from 'vue'
33
+
34
+ const api = window.__YIZIRAN_PLUGIN_API__
35
+
36
+ if (!api) {
37
+ console.error('[Chuchen Plugin] __YIZIRAN_PLUGIN_API__ 不可用:请在初尘3D壁纸(真隔离沙箱运行时)或 chuchen-plugin 环境中运行')
38
+ throw new Error('Sandbox Plugin API not available')
39
+ }
40
+
41
+ // 创建响应式参数和性能策略对象
42
+ // ★ 初值已由宿主烘焙进沙箱(__YIZIRAN_INIT__),此处同步读取,无竞态
43
+ const paramValues = reactive(api.getAllParams())
44
+ const performancePolicy = reactive(api.getPerformancePolicy())
45
+
46
+ // 挂载容器:沙箱 iframe 自身的 document.body 就是受约束的渲染根
47
+ // (iframe 即"铺满即壁纸",不存在旧版越界挂到宿主 body 的问题)
48
+ const appRoot = document.createElement('div')
49
+ appRoot.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;'
50
+ document.body.appendChild(appRoot)
51
+
52
+ // 创建并挂载 Vue 应用
53
+ const app = createApp(PluginComponent, {
54
+ containerRef: appRoot,
55
+ paramValues: paramValues,
56
+ performancePolicy: performancePolicy,
57
+ })
58
+
59
+ app.mount(appRoot)
60
+
61
+ // 沙箱销毁(宿主移除 iframe)前触发组件 onUnmounted 清理
62
+ // (cancelAnimationFrame / renderer.dispose 等;上下文销毁本身已是兜底)
63
+ window.addEventListener('beforeunload', function () {
64
+ try { app.unmount() } catch (_) {}
65
+ })
66
+
67
+ // 监听宿主参数变化,同步到响应式对象
68
+ api.onParamChange(function (key, value) {
69
+ paramValues[key] = value
70
+ })
71
+
72
+ // 监听宿主性能策略变化
73
+ api.onPolicyChange(function (newPolicy) {
74
+ Object.assign(performancePolicy, newPolicy)
75
+ })
76
+
77
+ // 通知宿主插件已就绪
78
+ api._notifyReady()
79
+
80
+ console.log('[Chuchen Plugin] 插件已就绪(沙箱运行时)')
81
+ `
82
+ }