更新记录

1.1.5(2026-05-08)

优化了

1.1.4(2026-05-08)

  • 重构 readme.md 使用说明:拍平第 5 步嵌套结构,改为「方案 B(推荐) / 方案 A」并列,各含启动页 + 首页完整可粘贴示例
  • 新增「两种 WGT 集成方案决策表」,在进入第 5 步前明确取舍(生效时机、启动耗时、必填 Props 差异)
  • 新增「常见问题」小节,覆盖首页没弹窗、方案 A 需两次启动、iOS 支持度、后台版本号怎么填、manualCheck() 用法、升级覆盖 config.js 等 6 个高频问题
  • config.jsAPP_UPDATE_SERVER_URL 还原为占位符 https://api.example.com,避免示例值泄露
  • package.json:补全 dcloudext.contact.qq;价格定为 普通授权 ¥1.00 / 源码授权 ¥5.00

1.1.3(2026-03-25)

  • 提供插件内 config.js,集中配置应用 ID 与更新服务地址
  • 文档补充页面集成步骤与启动页两种 WGT 方案说明
查看更多

平台兼容性

uni-app(4.0)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
- - - - - - - - -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - - -

uni-app x(4.0)

Chrome Safari Android iOS 鸿蒙 微信小程序
- - - - - -

其他

多语言 暗黑模式 宽屏模式
× ×

isir-version-update

App 热更新组件,支持 APK 全包更新(弹窗提示 + 下载安装)和 WGT 热更新(后台静默下载下次安装,或在启动页内检查→下载→安装→重启),含 ThinkPHP8 完整后端 + 企业级管理后台。

功能特性

  • APK 全包更新:弹窗提示用户,显示更新日志、下载进度、自动安装
  • WGT 热更新:① 后台静默下载,下次启动安装(wgt-installer mixin);② 启动页一条龙检查/下载/安装(launch-wgt-flow.js
  • 强制更新模式:隐藏关闭按钮,用户必须更新
  • 下载失败自动重试(最多 3 次)
  • WGT 版本码本地管理,无需依赖 appWgtVersion
  • 含 ThinkPHP8 后端(API + 管理后台 + 建表 SQL)
  • 零第三方依赖,不依赖 uview-plus 等 UI 库

快速开始(共 5 步)

第 1 ~ 4 步是通用准备;第 5 步需要在方案 A / 方案 B 之间二选一(见下方决策表)。

第 1 步:导入插件

在 HBuilderX 插件市场导入,或手动将 uni_modules/isir-version-update 目录复制到你的项目 src/uni_modules/ 下。

第 2 步:部署后端

  1. server/database_init.sql 导入你的 MySQL 数据库
  2. server/thinkphp8/AppUpdate.php 复制到 ThinkPHP8 项目的 app/controller/ 目录
  3. server/thinkphp8/CrossDomain.php 复制到 app/middleware/ 目录
  4. server/thinkphp8/route.php 中的路由配置合并到你的 route/app.php
  5. config/middleware.php 中注册跨域中间件:
return [
    'alias' => [
        'cross' => \app\middleware\CrossDomain::class,
    ],
];
  1. server/admin/appUpdate.html 复制到 public/admin/ 目录,访问 http://你的域名/admin/appUpdate.html 即可打开管理后台

第 3 步:修改插件内 config.js(必做,不改不可用)

打开 uni_modules/isir-version-update/config.js只需改前两个常量

// 1) 改成你后台「应用管理」中的 app_id(建议用包名格式,如 com.yourcompany.app)
export const APP_UPDATE_APP_ID = "com.xxxx.app";

// 2) 改成你的后端站点根地址(结尾不带 /,也不要带 /appUpdate/check 这种子路径)
export const APP_UPDATE_SERVER_URL = "https://api.example.com";

// 3) 一般保持默认;首次安装时写入本地作为 WGT 基线版本码
export const APP_UPDATE_WGT_INIT_CODE = 10001;

业务页面统一这样引用:

import {
  APP_UPDATE_APP_ID,
  APP_UPDATE_SERVER_URL,
  APP_UPDATE_WGT_INIT_CODE,
} from "@/uni_modules/isir-version-update/config.js";

升级插件风险:HBuilderX 升级时可能覆盖 uni_modules 内文件,升级前请备份 config.js,或把这两个常量挪到业务侧管理。

第 4 步:pages.json 配置

两条关键要求:

  1. 启动页放 pages 数组第一项(冷启动先进入启动页,便于执行 WGT 流程)
  2. 主界面需能常驻或尽早挂载 <isir-version-update />(APK 全包更新检查时机依赖此组件)

最小示例:

{
  "pages": [
    {
      "path": "pages/Launchpage/index",
      "style": { "navigationStyle": "custom" }
    },
    {
      "path": "pages/home/index",
      "style": { "navigationBarTitleText": "首页" }
    }
  ]
}

本插件符合 easycom 规范,不需要手动 import,也不需要在 pages.json 的 easycom 里额外配置,页面模板里直接写 <isir-version-update ... /> 即可使用。

第 5 步:选择 WGT 集成方案(二选一)

在真机 APP 上才会生效(H5 / 小程序会自动跳过更新检查)。本插件的 APK 全包更新 总是由首页组件 <isir-version-update /> 负责,你只需决定 WGT 热更新走哪种:

维度 方案 B:runLaunchWgtFlow推荐 方案 A:wgt-installer mixin
启动页职责 当次启动:检查 → 下载 → 安装 → 重启 仅安装上次已下载好的 WGT
WGT 下载时机 当次启动,启动页同步下载 上次运行中,由首页组件静默后台下载
WGT 生效时机 当次启动(立即生效) 下次启动(用户需开两次 App)
启动耗时 每次启动多一次 wgt 检查请求 几乎无感(无额外网络请求)
首页组件必填属性 :skip-silent-wgt-check="true" 不要skipSilentWgtCheck
适合场景 紧急修复、要求立即生效 体验优先,版本变化不紧急

拿不准就选方案 B:每次启动都能拿到最新 WGT,用户侧最一致。


方案 B:runLaunchWgtFlow(推荐)

需要改 2 个页面启动页 + 首页

B1. 启动页(完整示例)

<template>
  <view class="splash">
    <text class="status">{{ wgtStatusText }}</text>
    <view class="progress-bar">
      <view class="progress-fill" :style="{ width: wgtProgress + '%' }" />
    </view>
  </view>
</template>

<script>
import { runLaunchWgtFlow } from "@/uni_modules/isir-version-update/js_sdk/launch-wgt-flow.js";
import {
  APP_UPDATE_APP_ID,
  APP_UPDATE_SERVER_URL,
  APP_UPDATE_WGT_INIT_CODE,
} from "@/uni_modules/isir-version-update/config.js";

export default {
  data() {
    return { wgtStatusText: "正在检查更新...", wgtProgress: 0 };
  },
  async mounted() {
    const result = await runLaunchWgtFlow({
      appId: APP_UPDATE_APP_ID,
      serverUrl: APP_UPDATE_SERVER_URL,
      wgtInitCode: APP_UPDATE_WGT_INIT_CODE,
      onStatus: (t) => {
        this.wgtStatusText = t;
      },
      : (n) => {
        this.wgtProgress = n;
      },
    });
    // 即将重启:禁止再跳转,交给 plus.runtime.restart()
    if (result.waitingRestart) return;
    // 无更新 / 下载失败 / 非 APP:进入首页
    uni.reLaunch({ url: "/pages/home/index" });
  },
};
</script>

B2. 首页(完整示例,必须skipSilentWgtCheck

<template>
  <view>
    <isir-version-update
      :app-id="appId"
      :server-url="serverUrl"
      :wgt-init-code="wgtInitCode"
      :skip-silent-wgt-check="true"
    />
    <!-- 你的首页内容 -->
  </view>
</template>

<script>
import {
  APP_UPDATE_APP_ID,
  APP_UPDATE_SERVER_URL,
  APP_UPDATE_WGT_INIT_CODE,
} from "@/uni_modules/isir-version-update/config.js";

export default {
  data() {
    return {
      appId: APP_UPDATE_APP_ID,
      serverUrl: APP_UPDATE_SERVER_URL,
      wgtInitCode: APP_UPDATE_WGT_INIT_CODE,
    };
  },
};
</script>

为什么首页还要挂组件:启动页的 runLaunchWgtFlow 只管 WGT;APK 全包更新弹窗 + 下载安装<isir-version-update /> 负责,所以首页必须挂。:skip-silent-wgt-check="true" 让组件不再重复请求 WGT 检查,只处理 APK。


方案 A:wgt-installer mixin

同样需要改 2 个页面启动页 + 首页

A1. 启动页(完整示例)

<template>
  <view class="splash" v-if="wgtInstalling">
    <text>{{ wgtStatusText }}</text>
    <view class="progress-bar">
      <view class="progress-fill" :style="{ width: wgtProgress + '%' }" />
    </view>
  </view>
</template>

<script>
import wgtInstaller from "@/uni_modules/isir-version-update/js_sdk/wgt-installer.js";

export default {
  mixins: [wgtInstaller],
  methods: {
    // 必填:无待安装 WGT 或安装失败兜底时,跳去首页
    onWgtSkip() {
      uni.reLaunch({ url: "/pages/home/index" });
    },
    // 可选:安装失败回调,之后仍会走 onWgtSkip
    onWgtFail(err) {
      console.error("[isir] WGT 安装失败", err);
    },
  },
};
</script>

mixin 已经在 mounted 内部执行检测,你不需要自己调用任何方法

A2. 首页(完整示例,不要skipSilentWgtCheck

<template>
  <view>
    <isir-version-update
      :app-id="appId"
      :server-url="serverUrl"
      :wgt-init-code="wgtInitCode"
    />
    <!-- 你的首页内容 -->
  </view>
</template>

<script>
import {
  APP_UPDATE_APP_ID,
  APP_UPDATE_SERVER_URL,
  APP_UPDATE_WGT_INIT_CODE,
} from "@/uni_modules/isir-version-update/config.js";

export default {
  data() {
    return {
      appId: APP_UPDATE_APP_ID,
      serverUrl: APP_UPDATE_SERVER_URL,
      wgtInitCode: APP_UPDATE_WGT_INIT_CODE,
    };
  },
};
</script>

关键点:方案 A 的 WGT 由首页组件静默下载到本地(落在 pending_wgt_*),下次冷启动才被启动页 mixin 安装。所以用户至少需要打开两次 App 才能用上新 WGT,这是方案 A 的固有特性。


进阶参考

runLaunchWgtFlow(options) 返回值

Promise<{ waitingRestart?: boolean; skipped?: boolean }>;
字段 何时出现 你应做的
waitingRestart 已安装 WGT 并即将 plus.runtime.restart() 禁止uni.reLaunch,直接 return
skipped 非 APP / 无更新 / 下载失败 / 安装失败 继续你的正常路由(如 uni.reLaunch 首页)

执行顺序(仅 APP-PLUS):

  1. 若本地有 pending_wgt_*(来自上一次首页组件的静默下载):先安装它 → 重启
  2. 否则请求 /appUpdate/check?type=wgt → 有更新则下载 → 安装 → 重启
  3. 任一步失败或无更新:返回 { skipped: true }

onStatus(text) / onProgress(num) 仅用于驱动启动页 UI;onProgress 范围 0–100,下载阶段 10–90,安装阶段 95,完成 100

wgt-installer mixin 注入的数据与钩子

mixin 会在你的启动页 data 中注入:

字段 类型 说明
wgtInstalling Boolean 是否正在安装 WGT(用来切换启动页 UI)
wgtProgress Number 安装进度 0–100
wgtStatusText String 状态文字(如 正在安装更新...

你需要实现的方法:

方法 是否必填 何时调用
onWgtSkip() 必填 无待安装包、或安装失败兜底
onWgtFail(err) 可选 安装失败(之后仍会调 onWgtSkip

Props 属性

属性 类型 必填 默认值 说明
appId String - 应用唯一标识,区分不同 APP 项目
serverUrl String - 后端服务器地址,如 https://update.example.com
autoCheck Boolean true 组件挂载后是否自动检查更新
skipSilentWgtCheck Boolean false true在组件内请求 WGT 检查/静默下载,仍会检查 APK;与启动页 launch-wgt-flow 联用时可避免重复
wgtInitCode Number 10001 WGT 初始版本码,第一次安装 APP 时的默认值

Events 事件

type / updateType 取值均为 'apk''wgt',用于区分事件来源。

事件名 参数 说明
has-update { version, updateInfo, updateType } 有更新可用;updateType = 'apk' | 'wgt'
no-update { currentVersion } APK / WGT 均无更新
check-error { error } 检查更新请求失败
download-progress { type, progress, totalBytesWritten, totalBytesExpectedToWrite } 下载进度;type 区分 APK 或 WGT
download-error { error, retryCount } 下载失败(APK 会自动重试,最多 3 次)
install-success { type } 安装成功
install-error { error, type } 安装失败
wgt-ready { version, filePath } WGT 静默下载完成,已写入 pending_wgt_*,下次启动生效

插件内文件一览

路径 说明
config.js 全局配置APP_UPDATE_*),导入插件后修改
js_sdk/wgt-installer.js 启动页 mixin:仅安装待安装 WGT
js_sdk/launch-wgt-flow.js runLaunchWgtFlow:启动页 WGT 检查、下载、安装、重启
components/isir-version-update/isir-version-update.vue 页面组件(APK 弹窗 + WGT 静默逻辑)

Methods 方法

方法名 说明
manualCheck() 手动触发检查更新(通过 ref 调用)
<isir-version-update
  ref="updater"
  appId="xxx"
  serverUrl="xxx"
  :autoCheck="false"
/>
<button @click="$refs.updater.manualCheck()">检查更新</button>

WGT 版本码机制

由于 uni-app 没有原生的 WGT 版本号 API,本插件采用本地存储数字版本码的方案:

  1. APP 首次安装时,本地存储默认版本码(默认 10001,可通过 wgtInitCode 自定义)
  2. 后台发布 WGT 时,填写比当前版本码更大的数字(如 10002
  3. 组件检查更新时,将本地版本码发送给后端比较
  4. WGT 安装成功后,自动更新本地版本码

后端 API 接口

方法 路径 说明
GET /appUpdate/check 检查更新(前端组件调用)
POST /appUpdate/upload 上传安装包(管理后台调用)
GET /appUpdate/list 版本列表
POST /appUpdate/del 删除版本
POST /appUpdate/edit 编辑版本信息
GET /appUpdate/detail 版本详情
POST /appUpdate/toggleStatus 切换启用/禁用
GET /appUpdate/appList 应用列表
GET /appUpdate/clientReport 客户端上报版本

管理后台

server/admin/appUpdate.html 部署到后端 public/admin/ 目录,访问即可使用企业级管理界面:

  • 数据统计卡片(应用数、版本数、APK/WGT 数量)
  • 客户端上报版本查看(便于确定下次发版填多大的版本号)
  • 版本发布(上传 APK/WGT + 填写版本号和更新日志)
  • 版本编辑(修改强制更新、更新日志等)
  • 版本启用/禁用/删除

静态资源

组件使用了一张弹窗背景图 static/versionUpdate.png,请将你的更新弹窗背景图放入插件的 static/ 目录中。

如果没有自定义背景图,可以在网上搜索 "app更新弹窗背景" 找一张合适的。建议尺寸:宽 600rpx,高 700rpx 左右。

常见问题

Q1. 首页没弹出更新提示?

按此顺序排查:

  1. 是否在 真机 APP 运行(H5 / 小程序会直接跳过)
  2. config.js 里的 APP_UPDATE_APP_IDAPP_UPDATE_SERVER_URL 是否已改为你自己的值
  3. 后台对应 app_id 下是否有已启用的 APK 版本,且版本号大于当前 App
  4. 首页是否真的挂载了 <isir-version-update />(组件卸载的页面不会检查)
  5. 浏览器 DevTools / HBuilderX 控制台是否有 [iSir-update] 打印或网络错误

Q2. 方案 A 装完 WGT 没生效?

方案 A 的 WGT 下次启动才生效。首次发版时用户没有 pending_wgt_*,需要:

  1. 打开 App(首页组件静默下载 WGT,落到本地)
  2. 完全杀掉 App,再次冷启动(启动页 mixin 安装 WGT 并重启)

如果要当次立即生效,请改用方案 B

Q3. iOS 能用吗?

  • APK 全包更新:仅 Android,iOS 必须走 App Store
  • WGT 热更新:iOS 可用,但只能更新前端资源(HTML/JS/CSS/图片),不能引入新的原生能力;请遵守 App Store 审核政策

Q4. 发布新版本时,后台那个「版本号」应该填多少?

  • APK:填语义化版本号(如 1.0.1),比当前大即可
  • WGT:填纯数字(如 10002),必须大于客户端当前的本地版本码;当前值可在管理后台的「客户端上报版本」里看

Q5. manualCheck() 怎么用?

一般用于「设置 → 检查更新」按钮,此时应关闭自动检查:

<isir-version-update
  ref="updater"
  :app-id="appId"
  :server-url="serverUrl"
  :auto-check="false"
/>
<button @click="$refs.updater.manualCheck()">检查更新</button>

Q6. 升级插件后我改过的 appId / serverUrl 丢了?

HBuilderX 升级插件时可能覆盖 uni_modules 下的文件。两种做法:

  • 升级前手动备份 config.js,升级后恢复;或
  • 彻底不用 config.js,把常量写在你自己的业务代码里(Props 直接传字面量)

注意事项

  • 本插件仅支持 APP 端(Android/iOS),H5 和小程序环境会自动跳过更新检查
  • APK 版本号使用语义化格式(如 1.0.0),WGT 版本码使用纯数字递增(如 10001
  • 上传的安装包文件存储在后端 public/uploads/app_update/{app_id}/ 目录下
  • 后端需要 PHP 8.0+ 和 ThinkPHP 8.x

发布到 DCloud 插件市场(避免常见报错)

重要: 插件 package.jsonid、磁盘目录名、components 下子目录名、主 vue 文件名须 完全一致(建议 全小写 + 连字符,如本插件为 isir-version-update),以便通过 easycom 校验并支持普通授权定价。在插件市场创建插件时,插件 ID 请填 isir-version-update(或 你的作者id-version-update,但与包内 id、解压目录名须一致)。

1. components/isir-version-update 不存在 / 插件包格式不正确

多为 zip 根目录多包了一层。正确做法是:解压后 第一层 就应能看到本插件的 package.jsoncomponents/ 目录(即与 uni_modules 插件目录结构 一致)。

  • 错误:zip 里是 uni_modules/isir-version-update/...src/uni_modules/...,或再多一层空文件夹
  • 正确:在资源管理器中进入 uni_modules/isir-version-update选中该文件夹内全部内容(或选中该文件夹本身用「压缩到 xxx.zip」,保证解压第一层即 package.json + components),压缩为 标准 zip,文件名建议英文(如 isir-version-update.zip

更稳妥:在 HBuilderX 中对本插件目录下的 package.json 右键 → 发布到插件市场,由工具打包,避免手工 zip 结构错误。

2. 「仅 easycom 规范的前端组件插件可设置价格」

插件市场当前对 付费 + 自动加密 的前端组件,优先支持符合 easycom 的 uni_modules 组件传统 uni-app(Vue2)+ 付费加密 可能与 uni-app x + 云端编译加密 规则不完全一致。若持续无法设价:

  • 可先 免费发布 验证 zip 与文档;或
  • 向 DCloud 插件市场工单 / 官方文档确认 「component-vue + uni-app Vue2」 是否允许与普通授权同价上架;或
  • 长期方案:将主组件按官方要求迁移至 uni-app x + easycom(见 付费前端插件说明

本插件已采用 components/isir-version-update/isir-version-update.vue,安装后可直接 不写 import、在页面使用 <isir-version-update />(与 easycom 一致);示例工程里若仍 import 组件,仅为便于说明,不影响 easycom 能力。

3. 付费插件与 uni_modules.encrypt

package.json 内已配置 uni_modules.encrypt,列出可参与加密的 JS 文件路径(相对于插件根目录)。config.js 未列入加密,便于购买「普通授权」的用户修改自己的 appId / serverUrl;若市场审核要求调整列表,以审核意见为准。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件会向用户配置的serverUrl服务器发送版本检查请求,包含app_id和当前版本号

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率: