const { request } = require('../../utils/request.js'); // 许愿树配置 const TREE_CONFIG = { name: '祈福许愿树', desc: '许下美好愿望,祈福平安顺遂', type: 'pine', }; Page({ data: { treeName: TREE_CONFIG.name, treeDesc: TREE_CONFIG.desc, wishes: [], loading: true, showCreateModal: false, wishContent: '', wishType: 'free', maxFreeLength: 20, maxPaidLength: 100, products: [], selectedProduct: null, micEnabled: false, windLevel: 0, }, onLoad() { this.loadWishes(); this.loadProducts(); this.initSensors(); }, onReady() { this.initWebGL(); }, onUnload() { this.stopAnimation(); this.stopSensors(); if (this.renderer) { this.renderer.dispose(); } }, onPullDownRefresh() { this.loadWishes().then(() => { wx.stopPullDownRefresh(); }); }, // 初始化 WebGL initWebGL() { const query = wx.createSelectorQuery().in(this); query.select('#webgl-canvas') .node() .exec((res) => { if (!res || !res[0] || !res[0].node) { console.error('Canvas 初始化失败'); return; } const canvas = res[0].node; this.canvas = canvas; // 获取系统信息 const systemInfo = wx.getSystemInfoSync(); const dpr = systemInfo.pixelRatio; // 设置画布尺寸 canvas.width = systemInfo.windowWidth * dpr; canvas.height = systemInfo.windowHeight * dpr; this.width = canvas.width; this.height = canvas.height; this.dpr = dpr; // 初始化 Three.js 场景 this.initThreeScene(); // 开始动画 this.startAnimation(); }); }, // 初始化 Three.js 场景 initThreeScene() { const canvas = this.canvas; const gl = canvas.getContext('webgl'); if (!gl) { console.error('WebGL 不支持'); return; } this.gl = gl; // 手动创建 Three.js 核心对象 // 由于小程序限制,我们使用原生 WebGL API // 设置视口 gl.viewport(0, 0, this.width, this.height); gl.enable(gl.DEPTH_TEST); gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); // 创建着色器程序 this.createShaderProgram(); // 创建树的几何体 this.createTreeGeometry(); // 创建丝带几何体 this.createRibbonGeometry(); // 初始化相机 this.camera = { position: [0, 2, 8], target: [0, 3, 0], up: [0, 1, 0], fov: 45, aspect: this.width / this.height, near: 0.1, far: 100, }; // 初始化光照 this.lights = { ambient: { color: [0.3, 0.3, 0.4], intensity: 0.5 }, directional: { color: [1, 0.95, 0.8], intensity: 0.8, direction: [0.5, 1, 0.5], }, point: { color: [1, 0.8, 0.4], intensity: 0.5, position: [0, 5, 2], }, }; // 动画状态 this.animationState = { time: 0, windLevel: 0, rotation: 0, }; }, // 创建着色器程序 createShaderProgram() { const gl = this.gl; // 顶点着色器 const vsSource = ` attribute vec3 aPosition; attribute vec3 aNormal; attribute vec2 aTexCoord; attribute vec3 aColor; uniform mat4 uModelMatrix; uniform mat4 uViewMatrix; uniform mat4 uProjectionMatrix; uniform mat3 uNormalMatrix; varying vec3 vNormal; varying vec3 vPosition; varying vec2 vTexCoord; varying vec3 vColor; void main() { vec4 worldPosition = uModelMatrix * vec4(aPosition, 1.0); vPosition = worldPosition.xyz; vNormal = uNormalMatrix * aNormal; vTexCoord = aTexCoord; vColor = aColor; gl_Position = uProjectionMatrix * uViewMatrix * worldPosition; } `; // 片段着色器 const fsSource = ` precision mediump float; varying vec3 vNormal; varying vec3 vPosition; varying vec2 vTexCoord; varying vec3 vColor; uniform vec3 uAmbientLight; uniform vec3 uDirectionalLightColor; uniform vec3 uDirectionalLightDirection; uniform vec3 uPointLightColor; uniform vec3 uPointLightPosition; uniform vec3 uCameraPosition; uniform float uTime; void main() { vec3 normal = normalize(vNormal); vec3 viewDir = normalize(uCameraPosition - vPosition); // 环境光 vec3 ambient = uAmbientLight * vColor; // 漫反射 float diff = max(dot(normal, uDirectionalLightDirection), 0.0); vec3 diffuse = uDirectionalLightColor * diff * vColor; // 点光源 vec3 lightDir = normalize(uPointLightPosition - vPosition); float pointDiff = max(dot(normal, lightDir), 0.0); float distance = length(uPointLightPosition - vPosition); float attenuation = 1.0 / (1.0 + 0.05 * distance + 0.01 * distance * distance); vec3 pointLight = uPointLightColor * pointDiff * attenuation * vColor; // 高光 vec3 reflectDir = reflect(-uDirectionalLightDirection, normal); float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0); vec3 specular = uDirectionalLightColor * spec * 0.3; // 发光效果(许愿树特殊效果) float glow = sin(uTime * 2.0 + vPosition.y * 3.0) * 0.5 + 0.5; vec3 glowColor = vec3(1.0, 0.9, 0.5) * glow * 0.2; vec3 result = ambient + diffuse + pointLight + specular + glowColor; gl_FragColor = vec4(result, 1.0); } `; // 编译着色器 const vertexShader = this.compileShader(gl.VERTEX_SHADER, vsSource); const fragmentShader = this.compileShader(gl.FRAGMENT_SHADER, fsSource); // 创建程序 const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error('着色器程序链接失败:', gl.getProgramInfoLog(program)); return; } this.program = program; gl.useProgram(program); // 获取属性位置 this.attribLocations = { position: gl.getAttribLocation(program, 'aPosition'), normal: gl.getAttribLocation(program, 'aNormal'), texCoord: gl.getAttribLocation(program, 'aTexCoord'), color: gl.getAttribLocation(program, 'aColor'), }; // 获取 uniform 位置 this.uniformLocations = { modelMatrix: gl.getUniformLocation(program, 'uModelMatrix'), viewMatrix: gl.getUniformLocation(program, 'uViewMatrix'), projectionMatrix: gl.getUniformLocation(program, 'uProjectionMatrix'), normalMatrix: gl.getUniformLocation(program, 'uNormalMatrix'), ambientLight: gl.getUniformLocation(program, 'uAmbientLight'), directionalLightColor: gl.getUniformLocation(program, 'uDirectionalLightColor'), directionalLightDirection: gl.getUniformLocation(program, 'uDirectionalLightDirection'), pointLightColor: gl.getUniformLocation(program, 'uPointLightColor'), pointLightPosition: gl.getUniformLocation(program, 'uPointLightPosition'), cameraPosition: gl.getUniformLocation(program, 'uCameraPosition'), time: gl.getUniformLocation(program, 'uTime'), }; }, // 编译着色器 compileShader(type, source) { const gl = this.gl; const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { console.error('着色器编译失败:', gl.getShaderInfoLog(shader)); gl.deleteShader(shader); return null; } return shader; }, // 创建树的几何体 createTreeGeometry() { // 树干 - 使用圆柱体变形 const trunkSegments = 16; const trunkHeight = 4; const trunkRadiusBottom = 0.4; const trunkRadiusTop = 0.15; const trunkVertices = []; const trunkNormals = []; const trunkColors = []; const trunkIndices = []; // 生成树干顶点 for (let i = 0; i <= trunkSegments; i++) { const t = i / trunkSegments; const y = t * trunkHeight; const radius = trunkRadiusBottom + (trunkRadiusTop - trunkRadiusBottom) * t; // 添加扭曲效果 const twist = t * 2; const offsetX = Math.sin(twist) * 0.1 * t; const offsetZ = Math.cos(twist) * 0.1 * t; for (let j = 0; j <= trunkSegments; j++) { const angle = (j / trunkSegments) * Math.PI * 2; const x = Math.cos(angle) * radius + offsetX; const z = Math.sin(angle) * radius + offsetZ; trunkVertices.push(x, y, z); // 法线 const nx = Math.cos(angle); const nz = Math.sin(angle); trunkNormals.push(nx, 0, nz); // 颜色 - 树干棕色 const shade = 0.3 + Math.random() * 0.1; trunkColors.push(shade, shade * 0.6, shade * 0.3); } } // 生成树干索引 for (let i = 0; i < trunkSegments; i++) { for (let j = 0; j < trunkSegments; j++) { const a = i * (trunkSegments + 1) + j; const b = a + trunkSegments + 1; trunkIndices.push(a, b, a + 1); trunkIndices.push(b, b + 1, a + 1); } } this.trunkGeometry = { vertices: new Float32Array(trunkVertices), normals: new Float32Array(trunkNormals), colors: new Float32Array(trunkColors), indices: new Uint16Array(trunkIndices), }; // 树冠 - 使用多个球体变形 this.crownGeometry = this.createCrownGeometry(); // 创建缓冲区 this.createBuffers(); }, // 创建树冠几何体 createCrownGeometry() { const crownLayers = 5; const segments = 16; const vertices = []; const normals = []; const colors = []; const indices = []; let vertexOffset = 0; for (let layer = 0; layer < crownLayers; layer++) { const layerT = layer / crownLayers; const layerY = 3 + layer * 0.8; const layerRadius = 2.5 - layer * 0.4; const layerHeight = 1.2; // 每层树冠是一个变形的球体 for (let i = 0; i <= segments; i++) { const t = i / segments; const phi = t * Math.PI; for (let j = 0; j <= segments; j++) { const theta = (j / segments) * Math.PI * 2; // 基础球体坐标 let x = layerRadius * Math.sin(phi) * Math.cos(theta); let y = layerY + layerHeight * Math.cos(phi); let z = layerRadius * Math.sin(phi) * Math.sin(theta); // 添加噪声使树冠更自然 const noise = Math.sin(theta * 5 + layer) * 0.2 + Math.cos(phi * 3) * 0.15; x += noise * Math.cos(theta); z += noise * Math.sin(theta); y += Math.sin(theta * 3 + layer * 2) * 0.1; vertices.push(x, y, z); // 法线 const nx = Math.sin(phi) * Math.cos(theta); const ny = Math.cos(phi); const nz = Math.sin(phi) * Math.sin(theta); normals.push(nx, ny, nz); // 颜色 - 绿色渐变 const green = 0.4 + layerT * 0.3 + Math.random() * 0.1; colors.push(0.1, green, 0.15); } } // 生成索引 for (let i = 0; i < segments; i++) { for (let j = 0; j < segments; j++) { const a = vertexOffset + i * (segments + 1) + j; const b = a + segments + 1; indices.push(a, b, a + 1); indices.push(b, b + 1, a + 1); } } vertexOffset += (segments + 1) * (segments + 1); } return { vertices: new Float32Array(vertices), normals: new Float32Array(normals), colors: new Float32Array(colors), indices: new Uint16Array(indices), }; }, // 创建丝带几何体 createRibbonGeometry() { // 丝带将在渲染时动态生成 this.ribbons = []; }, // 创建缓冲区 createBuffers() { const gl = this.gl; // 树干缓冲区 this.trunkBuffers = { position: gl.createBuffer(), normal: gl.createBuffer(), color: gl.createBuffer(), index: gl.createBuffer(), }; gl.bindBuffer(gl.ARRAY_BUFFER, this.trunkBuffers.position); gl.bufferData(gl.ARRAY_BUFFER, this.trunkGeometry.vertices, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, this.trunkBuffers.normal); gl.bufferData(gl.ARRAY_BUFFER, this.trunkGeometry.normals, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, this.trunkBuffers.color); gl.bufferData(gl.ARRAY_BUFFER, this.trunkGeometry.colors, gl.STATIC_DRAW); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.trunkBuffers.index); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.trunkGeometry.indices, gl.STATIC_DRAW); // 树冠缓冲区 this.crownBuffers = { position: gl.createBuffer(), normal: gl.createBuffer(), color: gl.createBuffer(), index: gl.createBuffer(), }; gl.bindBuffer(gl.ARRAY_BUFFER, this.crownBuffers.position); gl.bufferData(gl.ARRAY_BUFFER, this.crownGeometry.vertices, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, this.crownBuffers.normal); gl.bufferData(gl.ARRAY_BUFFER, this.crownGeometry.normals, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, this.crownBuffers.color); gl.bufferData(gl.ARRAY_BUFFER, this.crownGeometry.colors, gl.STATIC_DRAW); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.crownBuffers.index); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.crownGeometry.indices, gl.STATIC_DRAW); }, // 开始动画循环 startAnimation() { const animate = () => { this.animationState.time += 0.016; this.animationState.windLevel = this.data.windLevel; this.render(); this.animationId = canvas.requestAnimationFrame(animate); }; this.animationId = this.canvas.requestAnimationFrame(animate); }, // 停止动画 stopAnimation() { if (this.animationId) { this.canvas.cancelAnimationFrame(this.animationId); } }, // 渲染场景 render() { const gl = this.gl; // 清空画布 gl.clearColor(0.05, 0.05, 0.15, 1.0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // 更新相机 this.updateCamera(); // 设置光照 this.setLights(); // 绘制树干 this.drawMesh(this.trunkBuffers, this.trunkGeometry.indices.length); // 绘制树冠 this.drawMesh(this.crownBuffers, this.crownGeometry.indices.length); // 绘制丝带 this.drawRibbons(); }, // 更新相机 updateCamera() { const gl = this.gl; const camera = this.camera; // 相机旋转动画 const rotationSpeed = 0.001; this.animationState.rotation += rotationSpeed; const radius = 8; camera.position[0] = Math.sin(this.animationState.rotation) * radius; camera.position[2] = Math.cos(this.animationState.rotation) * radius; camera.position[1] = 2 + Math.sin(this.animationState.time * 0.5) * 0.5; // 计算视图矩阵 const viewMatrix = this.lookAt(camera.position, camera.target, camera.up); // 计算投影矩阵 const projectionMatrix = this.perspective( camera.fov * Math.PI / 180, camera.aspect, camera.near, camera.far ); // 设置 uniform gl.uniformMatrix4fv(this.uniformLocations.viewMatrix, false, viewMatrix); gl.uniformMatrix4fv(this.uniformLocations.projectionMatrix, false, projectionMatrix); gl.uniform3fv(this.uniformLocations.cameraPosition, camera.position); gl.uniform1f(this.uniformLocations.time, this.animationState.time); }, // 设置光照 setLights() { const gl = this.gl; const lights = this.lights; gl.uniform3fv(this.uniformLocations.ambientLight, lights.ambient.color.map(c => c * lights.ambient.intensity)); gl.uniform3fv(this.uniformLocations.directionalLightColor, lights.directional.color.map(c => c * lights.directional.intensity)); gl.uniform3fv(this.uniformLocations.directionalLightDirection, lights.directional.direction); gl.uniform3fv(this.uniformLocations.pointLightColor, lights.point.color.map(c => c * lights.point.intensity)); gl.uniform3fv(this.uniformLocations.pointLightPosition, lights.point.position); }, // 绘制网格 drawMesh(buffers, indexCount) { const gl = this.gl; // 设置模型矩阵 const modelMatrix = this.identity(); gl.uniformMatrix4fv(this.uniformLocations.modelMatrix, false, modelMatrix); // 设置法线矩阵 const normalMatrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; gl.uniformMatrix3fv(this.uniformLocations.normalMatrix, false, normalMatrix); // 绑定顶点属性 gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position); gl.enableVertexAttribArray(this.attribLocations.position); gl.vertexAttribPointer(this.attribLocations.position, 3, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, buffers.normal); gl.enableVertexAttribArray(this.attribLocations.normal); gl.vertexAttribPointer(this.attribLocations.normal, 3, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, buffers.color); gl.enableVertexAttribArray(this.attribLocations.color); gl.vertexAttribPointer(this.attribLocations.color, 3, gl.FLOAT, false, 0, 0); // 绘制 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.index); gl.drawElements(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0); }, // 绘制丝带 drawRibbons() { // 根据许愿数据动态生成丝带 const wishes = this.data.wishes; if (!wishes || wishes.length === 0) return; // 这里简化处理,使用线条绘制丝带 // 实际项目中可以使用更复杂的几何体 }, // 矩阵工具函数 identity() { return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; }, lookAt(eye, center, up) { const z = this.normalize(this.subtract(eye, center)); const x = this.normalize(this.cross(up, z)); const y = this.cross(z, x); return [ x[0], y[0], z[0], 0, x[1], y[1], z[1], 0, x[2], y[2], z[2], 0, -this.dot(x, eye), -this.dot(y, eye), -this.dot(z, eye), 1 ]; }, perspective(fov, aspect, near, far) { const f = 1.0 / Math.tan(fov / 2); const nf = 1 / (near - far); return [ f / aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, (far + near) * nf, -1, 0, 0, 2 * far * near * nf, 0 ]; }, subtract(a, b) { return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; }, cross(a, b) { return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ]; }, dot(a, b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; }, normalize(v) { const len = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); return len > 0 ? [v[0] / len, v[1] / len, v[2] / len] : [0, 0, 0]; }, // 初始化传感器 initSensors() { // 陀螺仪 wx.startGyroscope({ interval: 'game', success: () => { wx.onGyroscopeChange((res) => { const wind = Math.min(1, Math.abs(res.x) * 0.5 + Math.abs(res.y) * 0.3); this.setData({ windLevel: wind }); }); }, fail: () => { console.log('陀螺仪不可用'); }, }); // 麦克风 this.recorderManager = wx.getRecorderManager(); this.recorderManager.onFrameRecorded((res) => { if (res.frameBuffer) { const volume = this.analyzeVolume(res.frameBuffer); this.setData({ windLevel: Math.min(1, volume * 2) }); } }); }, // 分析音量 analyzeVolume(buffer) { const data = new Int16Array(buffer); let sum = 0; for (let i = 0; i < data.length; i++) { sum += Math.abs(data[i]); } return sum / data.length / 32768; }, // 开启麦克风 enableMicrophone() { wx.authorize({ scope: 'scope.record', success: () => { this.recorderManager.start({ duration: 600000, sampleRate: 16000, numberOfChannels: 1, encodeBitRate: 48000, format: 'PCM', frameSize: 50, }); this.setData({ micEnabled: true }); wx.showToast({ title: '吹气让树摇摆', icon: 'none' }); }, fail: () => { wx.showToast({ title: '需要麦克风权限', icon: 'none' }); }, }); }, // 停止传感器 stopSensors() { wx.stopGyroscope(); if (this.recorderManager) { this.recorderManager.stop(); } }, // 画布触摸事件 onCanvasTouch(e) { // 处理触摸旋转 this.touchStartX = e.touches[0].x; this.touchStartY = e.touches[0].y; }, onCanvasMove(e) { if (this.touchStartX === undefined) return; const deltaX = e.touches[0].x - this.touchStartX; const deltaY = e.touches[0].y - this.touchStartY; // 根据触摸移动调整相机 this.animationState.rotation += deltaX * 0.005; this.touchStartX = e.touches[0].x; this.touchStartY = e.touches[0].y; }, onCanvasEnd() { this.touchStartX = undefined; this.touchStartY = undefined; }, // 加载许愿 async loadWishes() { try { const res = await request({ url: '/api/wish/tree/1/wishes', method: 'GET', }); if (res.code === 0) { this.setData({ wishes: res.data.list || [] }); } } catch (err) { console.error('加载许愿失败:', err); this.setData({ wishes: this.getMockWishes() }); } }, // 模拟许愿数据 getMockWishes() { return [ { id: 1, content: '愿家人平安健康', type: 'paid' }, { id: 2, content: '事业顺利', type: 'free' }, { id: 3, content: '心想事成', type: 'paid' }, { id: 4, content: '考试通过', type: 'free' }, { id: 5, content: '财源广进', type: 'paid' }, ]; }, // 加载许愿商品 async loadProducts() { try { const res = await request({ url: '/api/wish/products', method: 'GET', }); if (res.code === 0) { this.setData({ products: res.data.list || [] }); } } catch (err) { console.error('加载商品失败:', err); this.setData({ products: [ { id: 1, name: '普通许愿条', price: 100, duration: 7 }, { id: 2, name: '精品许愿条', price: 500, duration: 30 }, { id: 3, name: '至尊许愿条', price: 2000, duration: 90 }, ], }); } }, // 打开许愿弹窗 openCreateModal() { this.setData({ showCreateModal: true }); }, // 关闭许愿弹窗 closeCreateModal() { this.setData({ showCreateModal: false, wishContent: '', wishType: 'free', selectedProduct: null, }); }, // 输入许愿内容 onInputContent(e) { this.setData({ wishContent: e.detail.value }); }, // 选择许愿类型 selectType(e) { const type = e.currentTarget.dataset.type; this.setData({ wishType: type }); }, // 选择商品 selectProduct(e) { const id = e.currentTarget.dataset.id; const product = this.data.products.find(p => p.id === id); this.setData({ selectedProduct: product }); }, // 提交许愿 async submitWish() { const { wishContent, wishType, selectedProduct } = this.data; if (!wishContent.trim()) { wx.showToast({ title: '请输入许愿内容', icon: 'none' }); return; } const maxLength = wishType === 'free' ? this.data.maxFreeLength : this.data.maxPaidLength; if (wishContent.length > maxLength) { wx.showToast({ title: `最多输入${maxLength}个字`, icon: 'none' }); return; } if (wishType === 'paid' && !selectedProduct) { wx.showToast({ title: '请选择许愿商品', icon: 'none' }); return; } try { if (wishType === 'paid') { const orderRes = await request({ url: '/api/pay/create', method: 'POST', data: { type: 'wish', productId: selectedProduct.id, content: wishContent, treeId: 1, }, }); if (orderRes.code === 0) { await this.wxPay(orderRes.data); } } else { const res = await request({ url: '/api/wish/create', method: 'POST', data: { content: wishContent, type: 'free', treeId: 1, }, }); if (res.code === 0) { wx.showToast({ title: '许愿成功', icon: 'success' }); this.closeCreateModal(); this.loadWishes(); } } } catch (err) { console.error('许愿失败:', err); wx.showToast({ title: '许愿失败,请重试', icon: 'none' }); } }, // 微信支付 async wxPay(orderData) { return new Promise((resolve, reject) => { wx.requestPayment({ timeStamp: orderData.timeStamp, nonceStr: orderData.nonceStr, package: orderData.package, signType: orderData.signType, paySign: orderData.paySign, success: () => { wx.showToast({ title: '支付成功', icon: 'success' }); this.closeCreateModal(); this.loadWishes(); resolve(); }, fail: (err) => { console.error('支付失败:', err); wx.showToast({ title: '支付失败', icon: 'none' }); reject(err); }, }); }); }, // 分享 onShareAppMessage() { return { title: '快来许愿树许下你的愿望吧!', path: '/pages/wish-tree/wish-tree', }; }, });