三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

微信小程序版本更新全攻略:从UpdateManager到企业级更新策略

微信小程序版本更新全攻略:从UpdateManager到企业级更新策略

1. 项目概述:为什么小程序版本更新提示如此重要

做微信小程序开发的朋友,尤其是负责线上运营的,肯定都遇到过这样的场景:你熬夜加班修复了一个严重的线上Bug,或者上线了一个用户期待已久的新功能,满怀期待地发布了新版本。结果几天过去,一看后台数据,老版本的用户占比依然居高不下,新功能触达率低得可怜,那个致命的Bug还在影响着大量用户的体验。问题出在哪?很多时候,就是因为缺少一套有效的“版本更新监测与用户提示”机制。

这绝不仅仅是一个“有比没有好”的锦上添花功能,而是直接影响用户体验、产品迭代效率和商业价值的核心环节。想象一下,一个电商小程序因为老版本存在支付漏洞,而用户没有更新,导致订单失败或资金风险;一个工具类小程序的新版本优化了性能,节省了用户50%的等待时间,但用户无从知晓。这些沉默的成本,每天都在发生。

核心需求很明确:当我们在微信小程序管理后台发布新版本后,需要一种可靠的方式,让正在使用老版本的用户感知到更新,并引导他们顺利完成升级。这涉及到两个关键动作:监测(判断用户当前运行的版本是否为最新)和提示(以友好的方式告知用户并引导更新)。微信官方提供的基础能力是UpdateManagerAPI,但如何用好它,如何设计更新策略,如何处理用户的不同反应,这里面有大量的细节和“坑”需要我们去填平。今天,我就结合自己多次迭代项目的实战经验,把这个看似简单实则暗藏玄机的功能,从原理到实践,从代码到策略,彻底讲透。

2. 核心机制与API深度解析

2.1 微信官方更新机制:冷启动与热启动

在动手写代码之前,必须彻底理解微信小程序的运行和更新机制,否则很容易做出错误的设计。微信小程序并非像网页一样每次访问都从服务器拉取最新资源。它的运行包(代码包)是下载到用户本地微信客户端的。

这里有两个关键概念:冷启动热启动

  • 冷启动:用户首次打开小程序,或微信主动销毁了小程序后台(如长时间未使用、手机内存不足),再次打开时,需要重新加载代码包,这个过程叫冷启动。
  • 热启动:用户打开过小程序,然后在一定时间内(默认5分钟,但非绝对)再次打开,小程序并未被销毁,直接从后台切换到前台,这个过程叫热启动。

版本检查的时机就在冷启动时。当小程序冷启动,微信客户端会异步检查是否有新版本可用。如果有,它会在后台静默下载新版本的代码包。注意,是“后台下载”,用户此时无感知。下载完成后,并不会立即应用。新版本代码包会等待下一次冷启动时才会被启用。

这就引出了我们功能的核心:UpdateManagerAPI。它的作用,就是在检测到新版本已经下载完成后,向用户发出提示,让用户可以选择立即重启小程序以应用新版本。如果用户不选择重启,则继续使用老版本,直到下一次冷启动时自动切换。

2.2 UpdateManager API 全解与实战代码

微信小程序提供了wx.getUpdateManager()接口来管理更新。它返回一个UpdateManager对象,提供了监听更新事件的能力。下面我们逐行解析一个健壮的实现方案。

首先,我们需要在应用启动时(通常在app.jsonLaunchonShow生命周期中)初始化更新管理器。

// app.js App({ onLaunch: function(options) { // 其他初始化逻辑... this.checkMiniProgramUpdate(); }, checkMiniProgramUpdate: function() { // 判断当前环境,通常在开发工具或体验版中我们不需要弹出更新提示 if (wx.canIUse('getUpdateManager')) { const updateManager = wx.getUpdateManager(); // 监听检查更新结果事件 updateManager.onCheckForUpdate(function(res) { // res.hasUpdate 布尔值,表示是否有新版本 console.log('检查更新结果:', res.hasUpdate); if (res.hasUpdate) { // 有更新,开始后台下载新版本 console.log('发现新版本,开始后台下载...'); } }); // 监听更新包下载完成事件 updateManager.onUpdateReady(function() { // 新版本下载完成 wx.showModal({ title: '更新提示', content: '新版本已经准备好,是否重启应用?', showCancel: false, // 隐藏取消按钮,强制用户选择“确定”来更新 confirmText: '马上重启', success: function(res) { if (res.confirm) { // 调用 applyUpdate 应用新版本并重启 updateManager.applyUpdate(); } // 如果用户点击取消(如果showCancel为true),则什么都不做,下次冷启动自动更新 } }); }); // 监听更新包下载失败事件 updateManager.onUpdateFailed(function() { // 网络问题或其它原因导致下载失败 wx.showToast({ title: '更新下载失败', icon: 'none', duration: 2000 }); // 可以在这里记录日志,或提供手动重试的入口 }); } else { // 如果客户端版本太低,不支持 getUpdateManager,需要做降级处理 wx.showModal({ title: '提示', content: '当前微信版本过低,无法使用更新功能,请升级到最新微信版本后重试。', showCancel: false }); } } });

这段代码构成了最基础的更新提示流程。但直接用到生产环境,还远远不够。

注意:onCheckForUpdate回调只在真正执行了版本检查时才会触发。如果本次启动命中了热启动,或者版本检查太快(比如本地缓存),这个回调可能不会执行。因此,不能依赖这个回调来做“无更新”的逻辑。

2.3 版本监测的原理与局限性

很多开发者会问:我能获取到用户当前的具体版本号吗?比如1.2.3,然后和我后台配置的最新版本号做对比,实现更灵活的提示策略?

答案是:不能直接通过API获取。微信出于安全和管理一致性考虑,没有向小程序代码开放查询当前运行版本号的API。你无法在小程序内部通过wx.getSystemInfo或其他接口拿到类似versionCodeversionName的信息。

那么,UpdateManager是如何判断是否有更新的呢?它依赖的是微信客户端底层机制。微信客户端在冷启动时,会向微信服务器查询该小程序appid对应的最新线上版本号,并与本地已下载的版本号进行比对。这个过程对小程序代码是黑盒。

这就意味着,我们无法实现诸如“仅当版本号低于2.0.0时才弹窗提示”、“A/B测试不同版本的更新文案”这种需要精确版本判断的精细化策略。我们只能依赖微信官方的“有”或“没有”二值判断。

但是,这并非绝路。我们可以通过一些间接手段来“感知”版本。例如,在app.js的全局数据globalData中定义一个configVersion字段。每次发布新版本时,如果更新提示逻辑有变化,就修改这个configVersion。小程序启动时,从本地存储中读取上次记录的configVersion,与当前的globalData.configVersion对比。如果不一致,可以执行一些特殊的逻辑(比如展示新版本特性弹窗),然后再更新本地存储的版本。这常用于更新提示本身的迭代。

3. 高级更新策略与用户体验设计

如果只用最基本的模态对话框,用户体验是粗糙的,转化率也不会高。我们需要设计更友好的更新策略。

3.1 分层提示策略:强制更新、温和提醒与静默更新

不是所有更新都值得用强弹窗打断用户。根据更新内容的重要性,我们可以设计分层策略。

1. 强制更新(阻断式)适用于修复重大安全漏洞、核心流程错误、或底层协议变更导致老版本完全无法使用的情况。

  • 实现方式:在onUpdateReady回调中,使用wx.showModal并设置showCancel: false,用户只能点击“确定”重启,无法取消。
  • 设计要点:标题和内容要清晰说明更新的必要性,如“修复了可能导致支付失败的严重问题,请立即更新以继续使用”。
updateManager.onUpdateReady(() => { wx.showModal({ title: '重要更新', content: '本次更新修复了关键问题,请立即应用新版本以保证正常使用。', showCancel: false, confirmText: '立即重启', success: (res) => { if (res.confirm) { updateManager.applyUpdate(); } } }); });

2. 温和提醒(推荐式)适用于发布新功能、体验优化、性能提升等增量更新。目标是鼓励更新,但不强制。

  • 实现方式:同样是wx.showModal,但showCancel: true,允许用户点击“取消”。可以优化取消按钮的文案,如“稍后再说”。
  • 体验优化:用户点击“取消”后,可以将本次提示记录在本地(如wx.setStorageSync('skipUpdateVersion', latestVersion)),在接下来的一段时间内(如24小时内)或本次会话内不再重复提示,避免骚扰。

3. 静默更新适用于一些微小的、不影响功能的改动(如文案调整、埋点更新)。我们不主动提示用户,依赖微信的后台下载和下次冷启动自动生效机制即可。

  • 实现方式:在onUpdateReady回调中,不进行任何UI提示,或者只在一个不显眼的位置(如个人中心页面底部)添加一个“新版本已就绪,点击重启”的小文字链接。

3.2 设计优雅的更新提示界面

原生的模态弹窗样式固定,体验生硬。我们可以自定义更新提示界面,提升品牌感和转化率。

步骤:

  1. 创建自定义更新组件:在项目根目录或组件目录下创建update-popup组件。
  2. 设计UI:使用position: fixed的遮罩层,居中展示一个自定义的弹窗。可以加入应用Logo、新版本特性列表(图文)、进度条(模拟)、美观的按钮。
  3. 控制显示逻辑:在app.js中监听onUpdateReady,通过全局事件总线或修改全局状态,触发自定义组件的显示。
  4. 处理用户操作:组件内“立即更新”按钮绑定updateManager.applyUpdate(),“稍后再说”按钮则隐藏组件并记录状态。
<!-- components/update-popup/update-popup.wxml --> <view class="update-mask" wx:if="{{show}}"> <view class="update-dialog"> <image class="update-logo" src="/images/logo.png"></image> <view class="update-title">发现新版本 V{{version}}</view> <scroll-view class="feature-list" scroll-y> <view class="feature-item" wx:for="{{features}}" wx:key="*this"> <text class="icon">✓</text> {{item}} </view> </scroll-view> <view class="progress-area" wx:if="{{downloading}}"> <progress percent="{{downloadPercent}}" show-info stroke-width="6"/> <text class="progress-text">正在下载更新包...</text> </view> <view class="button-group"> <button class="btn-secondary" bindtap="onCancel" wx:if="{{!forceUpdate}}">稍后再说</button> <button class="btn-primary" bindtap="onConfirm">{{confirmText}}</button> </view> </view> </view>
// components/update-popup/update-popup.js Component({ properties: { show: Boolean, version: String, features: Array, forceUpdate: Boolean, downloading: Boolean, downloadPercent: Number }, data: { confirmText: '立即更新' }, methods: { onConfirm() { const updateManager = wx.getUpdateManager(); updateManager.applyUpdate(); // 可以在这里触发加载态 }, onCancel() { this.triggerEvent('cancel'); // 记录跳过此版本 wx.setStorageSync('skipUpdateVersion', this.data.version); } } })

app.js中,我们可以这样触发:

// app.js const updateManager = wx.getUpdateManager(); updateManager.onUpdateReady(() => { // 通过获取全局实例来显示组件,这里假设通过getApp()能访问到页面实例 // 更推荐使用全局状态管理(如MobX-miniprogram)或事件总线 const pages = getCurrentPages(); const currentPage = pages[pages.length - 1]; if (currentPage && currentPage.selectComponent) { const updatePopup = currentPage.selectComponent('#update-popup'); if (updatePopup) { updatePopup.setData({ show: true, version: '2.1.0', // 这个版本号需要你通过其他方式同步,比如从服务器接口获取 features: ['- 全新首页设计,浏览更便捷', '- 优化搜索算法,结果更精准', '- 修复了已知的若干问题'] }); } } });

3.3 更新时机与频率控制

无节制的弹窗是用户体验的灾难。必须控制提示的时机和频率。

  • 启动后延迟提示:不要在onLaunch里立即弹窗,这会打断用户进入小程序的初始流程。可以设置一个延时,比如在onShow后 1-2 秒,或者等首页主要内容加载完毕后再检查并提示。
    onShow() { setTimeout(() => { this.checkUpdate(); }, 1500); }
  • 频率控制:用户点击“稍后再说”后,应该在一段时间内不再提示。可以将当前线上版本号(可通过请求服务器接口获得)和当前时间戳存入本地缓存。
    const skipInfo = wx.getStorageSync('skipUpdateInfo') || {}; const latestVersion = await getLatestVersionFromServer(); // 假设的接口 const now = Date.now(); const oneDay = 24 * 60 * 60 * 1000; if (skipInfo.version === latestVersion && (now - skipInfo.timestamp) < oneDay) { // 24小时内已跳过此版本,不再提示 return; }
  • 场景化提示:不要在用户进行关键操作时(如支付填写表单、观看视频)弹出更新提示。可以在用户跳转到“我的”页面,或者从某个次要页面返回时再触发检查,这样干扰最小。

4. 异常处理、兼容性与性能优化

4.1 全面覆盖的异常处理

网络世界充满不确定性,更新流程的每一步都可能出错。

  1. onUpdateFailed处理:这是下载失败。除了给用户一个Toast提示,我们应该提供重试机制。例如,在提示失败后,可以在页面角落放置一个“更新下载失败,点击重试”的入口,点击后重新调用updateManager.onCheckForUpdate()(注意:需要一定的触发条件,不能无限循环)。
  2. applyUpdate失败处理:虽然极少发生,但应用更新也可能失败。我们可以监听小程序错误事件App.onError,或者在一个setTimeout后检查版本是否真的更新了(通过之前提到的configVersion间接判断),如果失败,则引导用户彻底关闭小程序后重新打开。
  3. 兼容性处理:如前所述,用wx.canIUse('getUpdateManager')判断基础库版本。对于不支持的老版本微信客户端,给出明确的升级引导,而不是让功能静默失败。

4.2 多端与框架兼容性考量

现在很多团队使用uni-appTaro等跨端框架开发小程序。这些框架对更新API的封装可能有所不同。

  • uni-app:它提供了统一的uni.getUpdateManager()API,其用法与微信原生极其相似,但需要注意编译到微信小程序平台时的细微差别。务必在真机上测试更新流程。
  • Taro:需要使用Taro.getUpdateManager(),同样需要关注Taro版本与微信基础库版本的对应关系。
  • 通用建议:在跨端项目中,关于更新的代码最好放在条件编译中,或者确保框架提供的API在目标平台上有完整的实现和测试。

4.3 性能与体验优化点

  1. 减少主包体积:更新提示组件、相关的工具函数可以放到小程序分包中,避免增加主包大小,影响初始下载速度。
  2. 预加载更新资源:对于已知即将进行的大版本更新,可以在用户使用小程序时,提前在后台发起一个预检查请求(不弹窗),让微信客户端提前开始后台下载,缩短用户收到提示后的等待时间。但这需要服务端配合,提供一个标志位。
  3. 后台下载状态感知:虽然不能精确控制,但我们可以通过onUpdateReadyonUpdateFailed来感知下载结果,并据此更新UI状态(如从“下载中”变为“下载完成”)。
  4. 避免内存泄漏:在页面或组件销毁时,如果注册了全局的更新监听器,要注意及时清理。不过UpdateManager的事件监听通常是应用级别的,问题不大,但在复杂单页应用(SPA)模式下需要注意。

5. 实战:构建一个企业级更新管理方案

让我们整合以上所有知识点,设计一个较为完整的企业级方案。这个方案将包含服务端协作,实现更灵活的更新控制。

5.1 系统架构设计

用户小程序端 (Client) 微信客户端 (WeChat Client) 管理后台/服务端 (Server) | | | | 1. 启动小程序,冷启动 | | |------------------------------>| | | | 2. 检查微信服务器版本 | | |------------------------------>| | | | | | 3. 返回最新版本信息 | | |<------------------------------| | | | | 4. (可选) 上报当前版本特征 | | |-------------------------------------------------------------->| | | | | 5. 后台下载更新包 | | |<-------------------------------| | | | | | 6. 轮询或接收服务端更新策略 | | |-------------------------------------------------------------->| | | | | 7. 返回策略:{force: false, tips: '...', version: '2.0.0'} | |<--------------------------------------------------------------| | | | | 8. 根据策略显示自定义更新弹窗 | | | | | | 9. 用户确认,调用applyUpdate | | |------------------------------>| | | | 10. 重启并应用新版本 |

核心思路:不完全依赖微信客户端的检查,而是引入服务端作为控制中心。小程序启动后,除了微信自身的检查,还向自己的服务器请求一份“更新策略”。这份策略可以包含:是否强制更新、提示文案、新版本特性列表、最低支持版本、更新包下载地址(用于特殊分发渠道)等。

5.2 服务端接口设计与实现

服务端需要提供一个简单的接口,例如GET /api/miniprogram/update-check

请求参数

  • appId: 小程序标识
  • clientVersion: 客户端版本(这里指我们自己在app.js中定义的configVersion,用于判断提示逻辑的版本)
  • platform: 平台(微信)
  • channel: 渠道(可选,用于A/B测试)

响应数据

{ "code": 0, "data": { "hasUpdate": true, "latestVersion": "2.1.0", "minRequiredVersion": "2.0.0", // 最低必需版本,低于此版本强制更新 "updateType": "recommend", // "force", "recommend", "silent" "title": "版本更新", "content": "优化了使用体验,修复了已知问题。", "features": ["新增个性化推荐", "优化页面加载速度"], "downloadUrl": "", // 一般留空,用微信自更新。特殊情况下可用于应用商店引流。 "promptInterval": 86400 // 提示间隔,秒(24小时) } }

服务端逻辑

  1. 根据appId获取配置的最新版本信息。
  2. clientVersionlatestVersionminRequiredVersion比对。
  3. 如果clientVersion < minRequiredVersion,则updateType设为"force"
  4. 如果clientVersion < latestVersion,则hasUpdatetrue,根据业务规则决定updateType"recommend"还是"silent"
  5. 返回对应的策略数据。

5.3 客户端完整实现代码示例

app.js中,我们整合微信官方更新和服务端策略。

// app.js App({ globalData: { configVersion: '2.0.1' // 本次编译的配置版本 }, onLaunch: function() { this.checkWxUpdate(); // 微信官方更新检查 setTimeout(() => { this.checkServerUpdatePolicy(); // 服务端策略检查,延迟执行 }, 3000); }, // 微信官方更新检查 checkWxUpdate: function() { if (!wx.canIUse('getUpdateManager')) { this.showUnsupportToast(); return; } const updateManager = wx.getUpdateManager(); updateManager.onCheckForUpdate((res) => {}); updateManager.onUpdateReady(() => { // 默认使用微信原生弹窗,后续可能被服务端策略覆盖 this.showWxUpdateDialog(updateManager); }); updateManager.onUpdateFailed(this.showUpdateFailedToast); }, // 服务端策略检查 async checkServerUpdatePolicy() { try { const { data: policy } = await wx.request({ url: 'https://your-api.com/api/miniprogram/update-check', method: 'GET', data: { appId: 'your-appid', clientVersion: this.globalData.configVersion, platform: 'wechat' } }); if (policy.code === 0 && policy.data.hasUpdate) { // 根据服务端策略,决定如何提示 this.handleUpdatePolicy(policy.data); } } catch (err) { console.error('检查更新策略失败:', err); // 网络失败时,降级为仅依赖微信官方更新 } }, // 处理服务端更新策略 handleUpdatePolicy(policy) { const { updateType, latestVersion, title, content, features, forceUpdate } = policy; const skipVersion = wx.getStorageSync('skipUpdateVersion'); // 检查是否已跳过此版本 if (skipVersion === latestVersion) { return; } // 根据策略类型展示不同UI switch(updateType) { case 'force': this.showCustomForceUpdateDialog(policy); break; case 'recommend': this.showCustomRecommendUpdateDialog(policy); break; case 'silent': // 静默更新,只记录日志或做其他处理 this.logSilentUpdate(policy); break; default: break; } }, // 显示自定义强制更新弹窗 showCustomForceUpdateDialog(policy) { // 通过全局事件或状态管理,触发显示一个全屏、不可关闭的更新弹窗 wx.eventCenter.emit('SHOW_FORCE_UPDATE', policy); // 假设有事件中心 }, // 显示自定义推荐更新弹窗 showCustomRecommendUpdateDialog(policy) { // 触发显示一个可关闭的、美观的推荐更新弹窗 wx.eventCenter.emit('SHOW_RECOMMEND_UPDATE', policy); }, // 微信原生更新弹窗(作为服务端不可用时的降级方案) showWxUpdateDialog(updateManager) { // 这里可以加一个判断,如果服务端策略已经弹窗,则不再弹出微信原生弹窗 if (this.hasShownCustomUpdate) { return; } wx.showModal({ title: '更新提示', content: '新版本已经准备好,是否重启应用?', showCancel: true, success: (res) => { if (res.confirm) { updateManager.applyUpdate(); } } }); }, // 其他辅助方法... showUpdateFailedToast() { wx.showToast({ title: '更新失败', icon: 'none' }); }, showUnsupportToast() { wx.showModal({ title: '提示', content: '微信版本过低,请升级', showCancel: false }); } });

5.4 数据监控与效果分析

上线了更新提示功能,我们还需要知道它效果如何。

  • 关键指标

    1. 更新提示曝光率:有多少用户收到了更新提示?(通过服务端接口调用量或前端埋点计算)
    2. 更新确认率(转化率):收到提示的用户中,有多少人点击了“立即更新”?(确认弹窗事件数 / 曝光数)
    3. 版本渗透率:新版本发布后,经过N天(如7天),使用新版本的用户占比达到多少?(需要服务端通过接口上报的clientVersion来统计)
    4. 更新失败率onUpdateFailed事件触发的比例。
  • 实现方式

    • 在显示自定义更新弹窗时,上报曝光事件。
    • 在用户点击“立即更新”时,上报确认事件。
    • 在小程序启动时,向服务端上报当前的configVersion(或其他版本标识)。
    • onUpdateFailed回调中,上报失败事件,并附带可能的错误信息(如网络错误类型)。

通过分析这些数据,我们可以持续优化更新文案、提示时机和策略。例如,如果发现更新确认率很低,可能需要优化新版本特性描述;如果更新失败率高,可能需要检查网络环境或包体积是否过大。

6. 常见问题排查与避坑指南

在实际开发和线上运维中,我遇到了不少典型问题,这里汇总一下,希望能帮你省去不少调试时间。

Q1:为什么在开发者工具上测试时,onCheckForUpdate总是返回false(没有更新)?A:开发者工具模拟的是本地编译的版本,它不会去检查线上版本。测试更新功能必须在体验版开发版(设置中勾选“编译时自动预览”)上进行。最可靠的测试方法是:上传一个体验版,然后在手机上打开前一个版本的体验版,再打开新上传的版本进行测试。

Q2:用户点击“更新”后,小程序闪了一下,但看起来没变化,版本号还是旧的?A:这通常是因为applyUpdate()调用后,小程序重启加载的依然是本地已有的代码包(可能因为热启动)。确保用户是完全退出小程序(从微信后台划掉)再重新进入,这时才会触发冷启动加载新包。可以在更新弹窗文案中明确提示用户“重启后生效”。

Q3:如何实现“跳过本次版本”的功能?A:如前面所述,当用户点击“稍后再说”时,将服务端返回的latestVersion存入本地缓存(如wx.setStorageSync)。下次检查更新时,先从服务端获取最新版本号,如果与本地存储的“跳过版本”一致,且在约定的时间间隔内(如24小时),则不再提示。注意,对于强制更新(force),不应该提供跳过选项。

Q4:小程序分包后,更新机制有什么不同?A:微信小程序的更新是以整个小程序代码包为单位的。当你更新了某个分包的内容并上传后,用户更新时也会下载整个主包+所有分包的最新版本。无法实现只更新某个分包。因此,要谨慎规划分包内容,经常变动的模块可以考虑放在同一个分包里。

Q5:在uni-appTaro里,更新提示不生效怎么办?A:首先确认编译到微信小程序平台后,生成的代码是否包含了getUpdateManager的调用。其次,检查框架的API文档,看是否有特殊用法。最后,务必在真机上进行测试,因为开发者工具和真机环境在更新逻辑上可能存在差异。

Q6:有没有办法让用户无感更新?A:完全的“无感”更新(像Web一样)在微信小程序中是无法实现的,因为代码包需要本地加载。我们能做的是“静默”更新:后台下载,下次冷启动自动切换,期间不给用户任何提示。这适用于绝大多数不紧急的修复。可以通过服务端策略将updateType设置为silent来实现。

Q7:更新弹窗出现时,页面上的按钮点击失效了?A:原生的wx.showModal是一个模态弹窗,它会阻止页面其他交互。如果你使用了自定义的全屏遮罩弹窗,需要确保弹窗的catchtouchmove属性绑定一个空函数来防止穿透,并且弹窗的z-index足够高。同时,在弹窗显示时,可以考虑暂时禁用页面上的某些交互元素。

← 返回列表