@chuchen3d/wallpaper-plugin-sdk

初尘3D壁纸插件开发工具包 — 创建、预览、打包 .yizirwp 壁纸插件
349
Install
npm install @chuchen3d/wallpaper-plugin-sdk
Latest Version:1.1.0
Vue:3
License:MIT
Last Updated:Oct 18, 2026
Links: npm
Maintainer: qwe

🧩 @chuchen3d/wallpaper-plugin-sdk

初尘壁纸插件开发工具包 — 创建、预览、打包 .yizirwp 壁纸插件。

  • CLI:chuchen-plugin init / build / validate / example
  • 程序化调用:import { ... } from '@chuchen3d/wallpaper-plugin-sdk'

v1.1.0 起为真隔离沙箱模型:插件运行在宿主创建的跨源 iframe 沙箱内, 构建产物自包含(Vue/Three 全部打进 IIFE bundle,不依赖宿主注入的运行时), manifest 新增 apiVersion 与 network.domains 字段,联网必须在 manifest 声明。 详见下文 沙箱运行时。组件 props 契约不变,存量组件代码零改动, 用新 SDK 重跑 build 即完成迁移。

安装

# 全局安装
npm install -g @chuchen3d/wallpaper-plugin-sdk

# 或从私有仓库安装
npm install -g @chuchen3d/wallpaper-plugin-sdk --registry=https://registry.yiziran.com.cn/

运行时依赖:Node ≥ 16。项目内需 vue ^3.4、vite ^5.2、@vitejs/plugin-vue ^5.0;使用 Three.js 时需 three ^0.162(可选,会随包打进 bundle,zip 体积 +150~250KB 属正常)。

快速开始

chuchen-plugin init my-wallpaper   # 初始化项目(manifest 自动带 apiVersion/network 字段)
cd my-wallpaper
npm install
npm run dev                         # 启动预览,浏览器右侧实时调节参数
# 编辑 MyWallpaper.vue 实现动画
chuchen-plugin build                # 打包为自包含 my-wallpaper-1.0.0.yizirwp

将生成的 .yizirwp 文件在「初尘壁纸 → 功能面板 → 开发者中心」中上传插件即可,之后管理员审核后即可在壁纸商店中下载。


CLI 命令

命令 说明
chuchen-plugin init <项目名> 初始化壁纸插件项目
chuchen-plugin build 编译 Vue SFC 并打包为 .yizirwp
chuchen-plugin validate 校验 manifest.json
chuchen-plugin example [项目名] 生成完整示例项目(粒子之舞)

init 选项

chuchen-plugin init my-wallpaper \
  --name "我的壁纸" \
  --description "一款精美的壁纸" \
  --author "开发者" \
  --category abstract \   # natural/tech/abstract/festival/utility
  --three                 # 启用 Three.js 支持

build 选项

chuchen-plugin build \
  --output my-plugin.yizirwp \   # 自定义输出文件名
  --dry-run                      # 仅校验,不生成文件

预览视频/GIF(可选)

插件可以附带一段预览视频或 GIF,商店详情页会循环播放。

  1. 在项目根放一个预览文件,文件名必须是 preview.{mp4,webm,mov,gif}:

    cp ~/my-preview.mp4 ./preview.mp4
    
  2. 打包(SDK 自动检测并嵌入):

    npx chuchen-plugin build
    # 或自定义文件名/路径:
    npx chuchen-plugin build --preview ./assets/preview.mp4
    
  3. 上传插件包到服务端。服务端首次上传时会自动抽取预览到独立存储。

  4. 替换预览:在 admin 后台 → 审核详情 → "替换预览",或客户端开发者中心 → "预览管理"。 不需要重新上传整个 .yizirwp。

文件约束

格式 扩展名 上限
视频 mp4 / webm / mov 10 MB
动图 gif 5 MB

注意:服务端 mime 推断只用扩展名,请确保文件扩展名与实际类型一致。


项目结构

my-wallpaper/
├── manifest.json        # 插件元数据(必选)
├── MyWallpaper.vue      # 壁纸组件(必选,文件名与 entry 对应)
├── package.json         # 开发依赖(不打入包)
├── vite.config.js       # Vite 配置(不打入包)
├── index.html           # 预览入口(不打入包)
├── dev-preview.js       # Mock props 注入脚本(不打入包)
├── thumbnail.png        # 缩略图(可选,256×256 推荐,自动打入包)
└── README.md            # 开发说明(不打入包)

manifest.json

完整示例

{
  "schemaVersion": "2.0",
  "id": "code-planet",
  "name": "雷电",
  "version": "1.0.2",
  "author": { "name": "忆自然咖啡" },
  "description": "一款精美的初尘壁纸插件-雷电",
  "tags": ["自然", "奇景", "线状闪电"],
  "category": "abstract",
  "entry": "CodePlanet.vue",
  "files": ["CodePlanet.vue"],
  "params": {
    "frequency": {
      "type": "number", "default": 5, "min": 1, "max": 15, "step": 1,
      "label": "闪电频率(秒)"
    },
    "intensity": {
      "type": "number", "default": 1.0, "min": 0.1, "max": 2.0, "step": 0.1,
      "label": "亮度强度"
    },
    "colorTheme": {
      "type": "select", "default": "blue",
      "options": ["blue", "purple", "white", "warm"],
      "label": "颜色主题"
    },
    "cameraHeight": {
      "type": "number", "default": 11, "min": 2, "max": 30, "step": 0.5,
      "label": "相机高度", 
      "tier": "premium"
    }
  },
  "previewBg": "linear-gradient(135deg, #f093fb 0%, #f5576c 100%)",
  "emoji": "🎨",
  "minAppVersion": "1.0.0",
  "apiVersion": 1,
  "network": { "domains": [] },
  "isPremium": false,
  "originalPrice": 19.9,
  "price": 9.9,
  "tier": "yearly"
}

字段说明

元数据(基础)
字段 类型 必需 说明
schemaVersion string ✅ Schema 版本(2.0)
id string ✅ 插件 ID,kebab-case(如 my-wallpaper)
name string ✅ 显示名称
version string ✅ 语义化版本(如 1.0.0)
author object ✅ { name, email? }
entry string ✅ 入口 .vue 文件名
files string[] ✅ 打包文件列表(构建产物由 index.js 替代,此字段仅源码声明)
minAppVersion string ✅ 最低应用版本
展示(推荐)
字段 类型 说明
description string 描述
tags string[] 标签数组
category string 分类:natural / tech / abstract / festival / utility
previewBg string 商店预览背景(CSS 渐变)
emoji string Emoji 图标
商业化(可选)
字段 类型 说明
isPremium boolean 是否付费插件
price number 售价(非负)
originalPrice number 原价(非负,用于划线价)
tier string 订阅档位:free / monthly / yearly / lifetime
沙箱契约(SDK ≥1.1.0,init 模板自动生成)
字段 类型 说明
apiVersion number 宿主插件 API 大版本,当前为 1。宿主按它做版本门禁:包要求版本高于宿主支持时拒绝加载并提示用户升级应用。init 生成的模板自带,勿手改;存量项目手动补 "apiVersion": 1 即可
network object { domains: string[] },≤8 个 https 域名(裸域名自动补 https://,带 path 会被截断)。空数组/缺省 = 默认全禁网:沙箱 CSP 只放行声明域名,fetch/XHR/WebSocket/图片/音频/字体等一切远程请求访问未声明域名都会被浏览器拦截

联网壁纸示例(如拉取天气 API):

"apiVersion": 1,
"network": { "domains": ["https://devapi.qweather.com"] }

validate / build 会校验:apiVersion 为 ≥1 的整数(缺省合法,视为 v1); network 为对象、domains 为合法 https 域名的字符串数组且 ≤8 项。 声明与代码不符的包会在服务端审核的风险报告中标记(未声明域名 / 声明未使用)。

params 字段

params 是「参数 key → 定义」的对象,key 即组件中 paramValues 的读取键。支持四种控件类型:

type 字段 控件
number default / min / max / step / label 滑块
select default / options / label 下拉
toggle default / label 开关
color default / label 颜色选择器
  • 所有类型均可选填 tier(如 "premium"),标记为会员专属参数。
  • select 的 options 支持两种写法:
    • 字符串数组(简写):["blue", "purple", "white"]
    • 对象数组(带显示标签):[{ "value": "rainbow", "label": "彩虹" }]
{
  "speed": { "type": "number", "default": 1.0, "min": 0.1, "max": 3.0, "step": 0.1, "label": "动画速度" },
  "colorPreset": {
    "type": "select", "default": "rainbow",
    "options": [
      { "value": "rainbow", "label": "彩虹" },
      { "value": "ocean", "label": "海洋蓝" }
    ],
    "label": "配色方案"
  },
  "mouseInteraction": { "type": "toggle", "default": true, "label": "鼠标交互" },
  "bgColor": { "type": "color", "default": "#0c0c1d", "label": "背景色" }
}

打包时 SDK 会把 params 自动转换为 { defaults, definitions } 格式,开发者保持扁平写法即可。


沙箱运行时(v1.1.0 起)

宿主把 .yizirwp 解包后,让插件运行在 sandbox="allow-scripts" 的跨源 iframe(opaque origin)内。这是浏览器层面的隔离保证:

能力 状态 替代方案
宿主 localStorage / cookie ❌ 不可达(访问直接抛 SecurityError) __YIZIRAN_PLUGIN_API__.storage(见下)
window.parent / window.top(宿主 DOM/位置/存储) ❌ 跨源拦截 ——(插件不应针对宿主)
Tauri IPC(__TAURI__ / __TAURI_IPC__) ❌ 沙箱内不存在 ——(插件无系统能力)
联网(fetch/XHR/WebSocket/图片/音频/字体) ⚠️ 仅放行 manifest network.domains 声明的域名 未声明域名一律被沙箱 CSP 拦截
window.open 弹窗 ❌ 禁止 ——
与宿主通信 仅 postMessage 协议 SDK 入口已封装,勿直接调 postMessage

插件内持久化(storage KV)

沙箱内 localStorage 不可用。需要持久化(API key 缓存、用户偏好等)时使用宿主代理的 KV 存储:

const api = window.__YIZIRAN_PLUGIN_API__

await api.storage.set('myKey', { a: 1 })
const val = await api.storage.get('myKey')   // null = 不存在

配额:单值 ≤16KB、键数 ≤32、总量 ≤64KB;按插件 ID 命名空间隔离,卸载插件时清空。 key 仅允许字母/数字/_/./-,长度 ≤64。

沙箱内可用的宿主 API(window.__YIZIRAN_PLUGIN_API__)

参数与性能策略由 SDK 入口包装器自动接入组件 props(paramValues / performancePolicy), 通常无需直接调用;以下按需使用:

方法 说明
storage.get(key) / storage.set(key, value) 宿主侧 KV 持久化(Promise)
getMousePosition() {x, y} 或 null
getContainerSize() {width, height}
setCaptureHandler(fn) 覆盖默认截图:fn 返回 { dataUrl, width, height }(多 canvas 场景用)
getPluginId() 当前插件 ID
log(level, ...args) / trackEvent(name, data) 日志转发宿主 / 埋点桥接

Vue 组件规范

必需 Props

宿主通过入口包装器向组件注入 3 个 props:

<script setup>
const props = defineProps({
  /** 容器 DOM 引用(取尺寸、绑事件) */
  containerRef: { type: Object, default: null },
  /** 用户调节的参数值,key 与 manifest.params 一一对应,实时更新 */
  paramValues: { type: Object, default: () => ({}) },
  /** 性能策略:fpsLimit(0=暂停)、pixelRatioCap、mouseInteraction */
  performancePolicy: { type: Object, default: () => ({}) },
})
</script>

生命周期骨架

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const canvasRef = ref(null)
let animationId = null

/** 安全读取参数(带默认值回退) */
function getParam(key, defaultValue) {
  const val = props.paramValues?.[key]
  return val !== undefined ? val : defaultValue
}

onMounted(() => {
  init()   // 建场景、绑事件、启动动画
})

onUnmounted(() => {
  if (animationId) cancelAnimationFrame(animationId)
  // 移除事件监听;释放 GPU 资源(dispose / forceContextLoss)
})

function animate() {
  animationId = requestAnimationFrame(animate)
  const fpsLimit = props.performancePolicy?.fpsLimit ?? 60
  if (fpsLimit === 0) return          // 暂停渲染
  // 读取参数并绘制
}
</script>

<template>
  <canvas ref="canvasRef" class="wallpaper-canvas"
          style="width:100%;height:100%;display:block;"></canvas>
</template>

★ 样式必须内联在模板上(style="..."):构建时 <style scoped> 会输出到 dist/style.css,而打包产物只含 index.js —— 样式表不进包,壁纸尺寸/布局会 整个丢掉。动态样式用 JS 设置 el.style.xxx。

Three.js 壁纸

  • three 随包打进自包含 IIFE bundle(v1.1.0 起不再依赖宿主注入的运行时),组件内 import * as THREE from 'three' 即可。
  • 项目 package.json 中 three 版本对齐 ^0.162,避免本地预览与打包产物 API 不一致。
  • 着色器以内联 JS 模板字面量编写,不需要 vite-plugin-glsl。
  • onUnmounted 必须 dispose 全部 geometry / material / texture,并 renderer.forceContextLoss();沙箱在壁纸移除时会销毁整个 iframe 上下文兜底,但优雅清理仍是组件的责任。

截图兼容性要求

宿主「截图与分享」面板依赖以下契约才能正确截取插件画面。强烈建议遵循 —— 不满足时截图会失败或得到空白图。

要求 必填 说明
canvas 是组件模板内的第一个 canvas ✅ 宿主截图协议取沙箱 iframe 内 querySelector('canvas') 的第一个元素。多 canvas 场景用 __YIZIRAN_PLUGIN_API__.setCaptureHandler(fn) 指定(fn 返回 { dataUrl, width, height })。class wallpaper-canvas 建议保留(兼容旧宿主回退路径)
WebGL renderer 设 preserveDrawingBuffer: true ✅ Three.js: new THREE.WebGLRenderer({ canvas, preserveDrawingBuffer: true })。否则 canvas.toBlob() 拿到的 PNG 是空白/透明帧(不是错误,但图像无效)。
不加载无 CORS 头的跨域图片 ✅ 否则 canvas 被 taint,canvas.toBlob() 抛 SecurityError。使用跨域纹理时需服务器返回 Access-Control-Allow-Origin 头或设置 crossOrigin = 'anonymous',且该图片域名必须已声明进 manifest network.domains(沙箱 CSP 对 img-src 同样管控)。
canvas 写在组件模板内 ✅ 标准流程自动满足——SDK 入口把 Vue 应用挂载到沙箱 iframe 内的铺满 div 上。

Three.js 截图友好写法示例

import * as THREE from 'three'

function init() {
  const canvas = canvasRef.value
  const renderer = new THREE.WebGLRenderer({
    canvas,
    preserveDrawingBuffer: true,   // ★ 让 canvas.toBlob() 能拿到帧
    antialias: true,
    alpha: true,
  })
  // ...
}

Canvas 2D 注意事项

如果用纯 Canvas 2D(无需 WebGL),preserveDrawingBuffer 不适用 —— toBlob 直接可用,但同样不要加载跨域图。

调试技巧

打开 DevTools,在 Console 执行(dev 预览页或宿主沙箱内均可):

const c = document.querySelector('canvas')
console.log(c?.toDataURL('image/png')?.length, 'bytes')  // >1000 表示 canvas 有内容

若长度很小(< 1000),canvas 是空白 —— 检查 preserveDrawingBuffer 是否开启。

若壁纸布局/尺寸整个不对 —— 检查样式是否写在了 <style scoped> 里(不入包,改内联)。 若 fetch/图片报 CSP 拦截错误 —— 域名未声明进 manifest network.domains,声明后重新 build。


打包规则

打包 .yizirwp 时,自动排除以下文件(仅开发用):

  • node_modules/、.git/、.gitignore、.DS_Store、Thumbs.db
  • package.json、package-lock.json、yarn.lock、pnpm-lock.yaml
  • vite.config.js、index.html、dev-preview.js
  • README.md、ICON_GUIDE.md

打包产物:manifest.json(重写 entry=index.js + params 格式转换)、index.js(自包含 IIFE bundle,内含整套 Vue/Three 运行时)、thumbnail.*(如有)、preview/<file>(如有)。


程序化 API

SDK 也支持在脚本中调用:

import {
  cmdInit, cmdBuild, cmdValidate, cmdExample,   // 命令
  generateManifest, generateVueComponentTemplate, generateDevProjectFiles,  // 模板
  generatePluginEntry,                          // 沙箱入口包装器生成
  validateManifest, convertParamsToAppFormat,   // 工具
  buildPlugin, packProject, packFromDist,       // 构建/打包
  VERSION, PACKAGE_NAME,
} from '@chuchen3d/wallpaper-plugin-sdk'

许可证

MIT

相关链接

Related Packages

endset_three_editor

A 3D model editor built with Three.js and Vue 3

22,133
three-bg

基于Threejs的背景组件,目前仅支持8Vue3

1,793
@tresjs/core

Declarative ThreeJS using Vue Components

1,355,627 3,774
vue-3d-loader

vueJS + threeJS 3d viewer component.

27,822 279

Version History

Version Released Vue License
1.1.0 ^3.4.0 MIT
Pre-releases (1)
Version Released License
0.0.0-stage