更新记录
1.6.0(2026-06-02) 下载此版本
优化文档
1.5.9(2026-06-02) 下载此版本
新增pc后台管理模板代码,优化文档
1.5.8(2026-06-02) 下载此版本
新增pc后台管理模板代码
查看更多平台兼容性
uni-app(3.7.8)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | √ | - | - | √ | - | √ | √ | - |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - | - |
整包更新和热更新组件 支持vue3 支持打开纯血鸿蒙、安卓、苹果应用市场,支持wgt静默更新
- ui图是采用uniapp官方更新组件的ui,如不满足需要,可自行替换
- 一键式检查更新,同时支持整包升级与wgt资源包更新 支持打开纯血鸿蒙、安卓自带的应用市场和苹果appstore
- 好看、实用、可自定义的客户端提示框
- 支持强制更新,无法退出
- 支持静默更新,下次启动后更新的内容自动生效
- 支持覆盖原生tabar,原生导航栏
uniappx版本插件在下面这个地址
安装指引
-
在插件市场打开本插件页面,在右侧点击
使用 HBuilderX 导入插件,选择要导入的项目点击确定(建议使用uni_modules版本 非uni_modules版本不在维护,有需要自行修改) -
在
pages.json中添加页面路径。注意:一定不要设置为pages.json中第一项
"pages": [
// ……其他页面配置
{
"path": "uni_modules/rt-uni-update/components/rt-uni-update/rt-uni-update",
"style": {
"disableScroll": true,
"app-plus": {
"backgroundColorTop": "transparent",
"background": "transparent",
"titleNView": false,
"scrollIndicator": false,
"popGesture": "none",
"animationType": "fade-in",
"animationDuration": 200
}
}
}
]
- 查看显示效果 (注意:这里只是查看显示效果,具体代码需要按照下面的项目使用说明编写)
// App.vue的onShow中查看效果 如果无法跳转 请在`pages.json`中添加页面路径,参照第二步
uni.navigateTo({
url: '/uni_modules/rt-uni-update/components/rt-uni-update/rt-uni-update'
});
PC管理后台都需要有一个app的版本管理系统(可参考下代码模板,根据自己需求和字段修改)
pc列表页代码 index.vue
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索区域 -->
<el-row>
<el-form :model="queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="应用名称" prop="appName">
<el-input clearable placeholder="请输入应用名称" v-model="queryForm.appName" />
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮区 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button icon="folder-add" type="primary" class="ml10" @click="handleAdd">发布新版</el-button>
</div>
</el-row>
<!-- 版本列表表格 -->
<el-table
:data="dataList"
v-loading="loading"
border
@selection-change="selectionChangHandle"
>
<el-table-column prop="appName" label="应用名称" show-overflow-tooltip width="120" />
<el-table-column prop="appId" label="AppID" show-overflow-tooltip width="150" />
<el-table-column prop="editionNumber" label="版本号" width="100" />
<el-table-column prop="editionName" label="版本名称" width="120" />
<el-table-column prop="platform" label="系统类型" width="100">
<template #default="scope">
<el-tag :type="scope.row.platform === 'android' ? 'success' : scope.row.platform === 'ios' ? 'warning' : 'danger'">
{{ scope.row.platform === 'android' ? 'Android' : scope.row.platform === 'ios' ? 'iOS' : '鸿蒙' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="packageType" label="安装包类型" width="100">
<template #default="scope">
<el-tag :type="scope.row.packageType === 0 ? 'success' : 'warning'">
{{ scope.row.packageType === 0 ? '整包更新' : 'wgt热更新' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="editionIssue" label="是否发行" width="90">
<template #default="scope">
<el-tag :type="scope.row.editionIssue === 1 ? 'success' : 'info'">
{{ scope.row.editionIssue === 1 ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="editionSilence" label="静默更新" width="90">
<template #default="scope">
{{ scope.row.editionSilence === 1 ? '是' : '否' }}
</template>
</el-table-column>
<el-table-column prop="editionForce" label="强制更新" width="100">
<template #default="scope">
{{ scope.row.editionForce === 1 ? '是' : '否' }}
</template>
</el-table-column>
<el-table-column prop="editionDesc" label="更新内容" min-width="200" show-overflow-tooltip>
<template #default="scope">
<div v-html="scope.row.editionDesc"></div>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="180">
<template #default="scope">
<el-button icon="edit-pen" text type="primary" @click="handleEdit(scope.row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
v-model:current-page="pagination.current"
v-model:page-size="pagination.size"
:page-sizes="[10, 20, 30, 50,100]"
:size="pagination.size"
layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
<!-- 版本编辑/新增弹窗 -->
<VersionDialog ref="versionDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="appVersionManager">
import { reactive, ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { useMessage, useMessageBox } from '/@/hooks/message'
import { fetchList, delObjs } from '/@/api/app/version'
import VersionDialog from './form.vue'
// 搜索表单
const queryForm = reactive({
appName: '',
})
// 列表数据
const dataList = ref<any[]>([])
const loading = ref(false)
const pagination = reactive({
current: 1,
size: 20,
total: 0
})
// 多选
const selectObjs = ref<string[]>([])
const multiple = ref(true)
// 搜索相关
const queryRef = ref()
// 弹窗引用
const versionDialogRef = ref()
// 获取列表数据
const getDataList = async () => {
loading.value = true
try {
const params = {
...queryForm,
current: pagination.current,
size: pagination.size
}
const res = await fetchList(params)
dataList.value = res.data.list || []
pagination.total = res.data.total || 0
} catch (error: any) {
useMessage().error(error.message || '获取列表失败')
} finally {
loading.value = false
}
}
// 重置搜索
const resetQuery = () => {
queryRef.value?.resetFields()
// 重置搜索条件
queryForm.appName = ''
queryForm.platform = ''
queryForm.editionIssue = undefined
pagination.current = 1
getDataList()
}
// 分页改变
const handleSizeChange = (size: number) => {
pagination.size = size
pagination.current = 1
getDataList()
}
const handleCurrentChange = (current: number) => {
pagination.current = current
getDataList()
}
// 多选事件
const selectionChangHandle = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id)
multiple.value = !objs.length
}
// 新增
const handleAdd = () => {
versionDialogRef.value?.openDialog()
}
// 编辑
const handleEdit = (row: any) => {
versionDialogRef.value?.openDialog(row.id)
}
// 单个删除
const handleDelete = async (id: string) => {
try {
await useMessageBox().confirm('此操作将永久删除该版本')
} catch {
return
}
try {
await delObjs(id)
ElMessage.success('删除成功')
getDataList()
} catch (err: any) {
useMessage().error(err.msg)
}
}
onMounted(() => {
getDataList()
})
</script>
<style scoped lang="scss">
.layout-padding {
padding: 20px;
}
.layout-padding-auto {
background: #fff;
border-radius: 8px;
padding: 20px;
}
.mb8 {
margin-bottom: 8px;
}
.ml10 {
margin-left: 10px;
}
.mr20 {
margin-right: 20px;
}
:deep(.el-table) {
font-size: 14px;
}
:deep(.el-table th.el-table__cell) {
background-color: #f5f7fa;
}
</style>
pc更新弹窗组件代码 form.vue
<template>
<el-dialog
:title="form.id ? '编辑版本' : '发布新版'"
v-model="visible"
:close-on-click-modal="false"
draggable
width="70%"
>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="130px"
v-loading="loading"
>
<!-- 基础信息 -->
<div class="bt">基础信息</div>
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="AppID" prop="appId">
<el-input v-model="form.appId" placeholder="例如:__UNI__xxxxx" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="应用名称" prop="appName">
<el-input v-model="form.appName" placeholder="请输入应用名称" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="版本号(数字)" prop="editionNumber">
<el-input-number v-model="form.editionNumber" :min="1" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="版本名称" prop="editionName">
<el-input v-model="form.editionName" placeholder="例如:1.0.0" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="系统类型" prop="platform">
<el-radio-group v-model="form.platform">
<el-radio label="android">Android</el-radio>
<el-radio label="ios">iOS</el-radio>
<el-radio label="harmonyos">鸿蒙</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="安装包类型" prop="packageType">
<el-radio-group v-model="form.packageType">
<el-radio :label="0">整包更新(APK/应用市场)</el-radio>
<el-radio :label="1">WGT热更新</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="是否发行" prop="editionIssue">
<el-switch v-model="form.editionIssue" :active-value="1" :inactive-value="0" />
<span class="form-tip">(上架市场审核时请设为“否”)</span>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="静默更新" prop="editionSilence">
<el-switch v-model="form.editionSilence" :active-value="1" :inactive-value="0" />
<span class="form-tip">(静默下载,下次启动生效)</span>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="强制更新" prop="editionForce">
<el-switch v-model="form.editionForce" :active-value="1" :inactive-value="0" />
<span class="form-tip">(强制弹窗,不更新无法使用)</span>
</el-form-item>
</el-col>
</el-row>
<!-- 下载地址 -->
<div class="bt">下载地址</div>
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="下载地址" prop="editionUrl">
<div>
<el-input
v-model="form.editionUrl"
placeholder="http:// 或 market:// 或 itms-apps:// 等"
style="flex: 1;margin-bottom: 10px;"
/>
<el-upload
action="#"
class="avatar-uploader"
:limit="1"
drag
ref="upload"
:on-change="beforeAvatarUpload"
:file-list="File.fileList"
:auto-upload="false"
accept=".apk,.wgt"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">
上传安装包(.apk/.wgt),上传成功后会自动填充下载地址
</div>
</el-upload>
</div>
</el-form-item>
</el-col>
</el-row>
<!-- 更新内容 -->
<div class="bt">更新内容</div>
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="更新内容" prop="editionDesc">
<!-- 富文本组件 -->
<editor v-model:get-html="form.editionDesc" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="onSubmit" :loading="loading">确认</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import { addObj, putObj, getObj,uploadApi } from '/@/api/app/version'
import { useMessage } from '/@/hooks/message';
const emit = defineEmits(['refresh'])
const visible = ref(false)
const loading = ref(false)
const uploadLoading = ref(false)
const dataFormRef = ref(null)
const form = reactive({
id: undefined,
appId: '',
appName: '',
editionNumber: 100,
editionName: '1.0.0',
platform: 'android',
packageType: 1,
editionIssue: 1,
editionSilence: 1,
editionForce: 0,
editionUrl: '',
editionDesc: ''
})
const dataRules = {
appId: [{ required: true, message: '请输入 AppID', trigger: 'blur' }],
appName: [{ required: true, message: '请输入应用名称', trigger: 'blur' }],
editionNumber: [{ required: true, message: '请输入版本号', trigger: 'blur' }],
editionName: [{ required: true, message: '请输入版本名称', trigger: 'blur' }],
platform: [{ required: true, message: '请选择系统类型', trigger: 'change' }],
packageType: [{ required: true, message: '请选择安装包类型', trigger: 'change' }],
editionUrl: [{ required: true, message: '请输入下载地址', trigger: 'blur' }],
editionDesc: [{ required: true, message: '请输入更新内容', trigger: 'blur' }]
}
const File = reactive({
fileList: [],
});
const beforeAvatarUpload = (file) => {
file = file.raw;
uploadLoading.value = true
let fromData = new FormData();
fromData.append('file', file);
uploadApi(fromData).then((res) => {
uploadLoading.value = false
if (res.status == 200) {
useMessage().success('上传成功');
form.editionUrl = 'http://你的ip' + res.data.objectName;
} else {
useMessage().error(res.message);
}
}).catch(()=>{
uploadLoading.value = false
useMessage().error('上传失败');
});
};
const openDialog = (id) => {
visible.value = true
Object.assign(form, {
id: undefined,
appId: '',
appName: '',
editionNumber: 100,
editionName: '1.0.0',
platform: 'android',
packageType: 1,
editionIssue: 1,
editionSilence: 0,
editionForce: 1,
editionUrl: '',
editionDesc: ''
})
nextTick(() => {
dataFormRef.value?.clearValidate()
File.fileList = []
})
if (id) {
getVersionDetail(id)
}
}
const getVersionDetail = async (id) => {
loading.value = true
try {
const res = await getObj(id)
if (res.code === 0 || res.status === 200) {
const detail = res.data
Object.assign(form, {
id: detail.id,
appId: detail.appId,
appName: detail.appName,
editionNumber: detail.editionNumber,
editionName: detail.editionName,
platform: detail.platform,
packageType: detail.packageType,
editionIssue: detail.editionIssue,
editionSilence: detail.editionSilence,
editionForce: detail.editionForce,
editionUrl: detail.editionUrl,
editionDesc: detail.editionDesc?.replace(/<br>/g, '\n') || ''
})
} else {
ElMessage.error(res.message || '获取版本详情失败')
}
} catch (error) {
ElMessage.error('获取版本详情失败')
console.error(error)
} finally {
loading.value = false
}
}
const onSubmit = async () => {
try {
await dataFormRef.value?.validate()
} catch {
return
}
loading.value = true
try {
const submitData = {
...form,
editionDesc: form.editionDesc?.replace(/\n/g, '<br>') || ''
}
let res
if (form.id) {
res = await putObj(submitData)
} else {
res = await addObj(submitData)
}
if (res.code === 0 || res.status === 200) {
ElMessage.success(form.id ? '编辑成功' : '发布成功')
visible.value = false
emit('refresh')
} else {
ElMessage.error(res.message || '操作失败')
}
} catch (error) {
ElMessage.error('操作失败')
console.error(error)
} finally {
loading.value = false
}
}
defineExpose({ openDialog })
</script>
<style lang="scss" scoped>
.tip {
padding: 8px 16px;
background-color: #ecf8ff;
border-radius: 4px;
border-left: 5px solid #50bfff;
margin: 20px 0;
font-size: 13px;
color: #31708f;
}
.bt {
margin-left: 30px;
margin-bottom: 20px;
font-size: 18px;
font-weight: bold;
border-left: 4px solid #409eff;
padding-left: 12px;
}
.mb20 {
margin-bottom: 20px;
}
.form-tip {
font-size: 12px;
color: #909399;
margin-left: 10px;
}
.dialog-footer {
text-align: right;
}
</style>
项目使用说明 最重要!!!
- 注意!!!后端返回数据要求 字段如下 (如果后端字段不一样,请在跳转更新页时手动赋值,示例见下面代码)
data:{
// 版本更新内容 支持<br>自动换行
describe: '1. 修复已知问题<br>
2. 优化用户体验',
edition_url: '', //apk、wgt包下载地址或者应用市场地址 安卓应用市场: market://details?id=xxxx 苹果store: itms-apps://itunes.apple.com/cn/app/xxxxxx 鸿蒙: store//appgallery.huawei.com/app/detail?id=包名 或者 https://appgallery.huawei.com/app/detail?id=包名
edition_force: 0, //是否强制更新 0代表否 1代表是
package_type: 1, //0是整包升级(apk或者appstore或者安卓应用市场) 1是wgt升级
edition_issue:1, //是否发行 0否 1是 为了控制上架应用市场审核时不能弹出热更新框
edition_number:100, //版本号 最重要的manifest里的版本号 (检查更新主要以服务器返回的edition_number版本号是否大于当前app的版本号来实现是否更新)
edition_name:'1.0.0',// 版本名称 manifest里的版本名称
edition_silence:0, // 是否静默更新 0代表否 1代表是
}
// 如果后端返回的字段和上面不一致,请在前端手动赋值(示例)
let data = res.data.data;
data.edition_url = res.data.data.editionUrl;
data.describe = res.data.data.editionDesc;
data.edition_force = res.data.data.editionForce;
data.package_type = res.data.data.packageType;
data.edition_issue = res.data.data.editionIssue;
data.edition_number = res.data.data.editionNumber;
data.edition_name = res.data.data.editionName;
data.edition_silence = res.data.data.editionSilence;
//跳转更新页面 (注意!!!如果pages.json第一页的代码里有一打开就跳转其他页面的操作,下面这行代码最好写在setTimeout里面设置延时3到5秒再执行)
uni.navigateTo({
url: '/uni_modules/rt-uni-update/components/rt-uni-update/rt-uni-update?obj=' + JSON.stringify(data)
});
后端注意!!!
后端根据前端的edition_number这个字段,先查询大于edition_number的整包地址,如果没有,那就返回大于当前edition_number的最新一条记录就行
edition_number传这个参数是为了解决部分用户app长期不使用,第一次打开服务器查到的版本是最新的是wgt包,但是之前app有过整包更新,如果直接更新最新wgt的话,会出现以前的整包添加的原生模块或者安卓权限无法使用,
- 前端示例代码 或者根据实际业务修改 如果需要自动检测新版本,建议写在App.vue的onShow中
import silenceUpdate from '@/uni_modules/rt-uni-update/js_sdk/silence-update.js' //引入静默更新
//#ifdef APP-PLUS
// 获取本地应用资源版本号
plus.runtime.getProperty(plus.runtime.appid, (inf) => {
//获取服务器的版本号
uni.request({
url: 'http://127.0.0.1:8088/edition_manage/get_edition', //示例接口
data: {
edition_type: plus.runtime.appid,
version_type: uni.getSystemInfoSync().platform, //android或者ios或者harmonyos
edition_number: inf.versionCode // 打包时manifest设置的版本号
},
success: (res) => {
//res.data.xxx根据后台返回的数据决定(我这里后端返回的是data),所以是res.data.data
//判断后台返回版本号是否大于当前应用版本号 && 是否发行 (上架应用市场时一定不能弹出更新提示)
if (Number(res.data.data.edition_number) > Number(inf.versionCode) && res
.data.data.edition_issue == 1) {
//如果是wgt升级,并且是静默更新 (注意!!! 如果是手动检查新版本,就不用判断静默更新,请直接跳转更新页,不然点击检查新版本后会没反应)
if (res.data.data.package_type == 1 && res.data.data.edition_silence == 1) {
//调用静默更新方法 传入下载地址
silenceUpdate(res.data.data.edition_url)
} else {
// 如果后端返回的字段和上面不一致,请在前端手动赋值(示例)
let data = res.data.data;
data.edition_url = res.data.data.editionUrl;
data.describe = res.data.data.editionDesc;
data.edition_force = res.data.data.editionForce;
data.package_type = res.data.data.packageType;
data.edition_issue = res.data.data.editionIssue;
data.edition_number = res.data.data.editionNumber;
data.edition_name = res.data.data.editionName;
data.edition_silence = res.data.data.editionSilence;
//跳转更新页面 (注意!!!如果pages.json第一页的代码里有一打开就跳转其他页面的操作,下面这行代码最好写在setTimeout里面设置延时3到5秒再执行)
uni.navigateTo({
url: '/uni_modules/rt-uni-update/components/rt-uni-update/rt-uni-update?obj=' + JSON.stringify(data)
});
}
} else {
// 如果是手动检查新版本 需开启以下注释
/* uni.showModal({
title: '提示',
content: '已是最新版本',
showCancel: false
}) */
}
}
})
});
//#endif
应用商店常见地址
鸿蒙5.0以下及安卓手机自带应用市场:market://details?id=包名
苹果AppStore:itms-apps://itunes.apple.com/cn/app/id<应用ID>
鸿蒙5.0及以上:store//appgallery.huawei.com/app/detail?id=包名 或者 https://appgallery.huawei.com/app/detail?id=包名
常见问题汇总!!!
热更新制作wgt包的方法:1、修改manifest.json版本名称和版本号,必须大于当前版本。2、点击菜单的发行——原生App-制作应用wgt包
app上传地址:个人建议开通unicloud的阿里云按量付费,方便、便宜,apk或者wgt包直接上传到云存储就行。
1、调试请打包自定义基座测试,否则uni.getSystemInfoSync().platform获取到的可能不是android或者ios,会导致无法跳转更新页
2、进度条不显示,先把下载路径在浏览器打开,测试一下是否能正常下载,排查原因:99%的情况是因为下载链接为内网链接,内网链接无法监听下载进度,请更换为外网链接,或者是检查后端是否设置content-length,如果用的是支付宝云的话,支付宝云下载文件时是流式返回,没有进度
3、进度条显示,下载apk完成后,安卓不会自动弹出安装页面,原因:可能是离线打包未添加安卓安装权限,请添加以下权限,除了加权限,还要去sdk里把install-apk-release.aar放自己基座的lib文件夹里。或者使用云打包
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

收藏人数:
下载插件并导入HBuilderX
赞赏(11)
下载 10624
赞赏 11
下载 12174050
赞赏 1918
赞赏
京公网安备:11010802035340号