效果图
1、提示下载安装包

2、热更新app


实践阶段
1、创建PC端APP版本管理界面

管理端页面代码
js
<template>
<div class="p-2">
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" v-hasPermi="['manager:news:add']" plain icon="Plus"
@click="handleAdd">新增</el-button>
</el-col>
</el-row>
</template>
<el-table v-loading="loading" :data="listData">
<el-table-column label="版本号" prop="versionNum" width="100" :show-overflow-tooltip="true">
</el-table-column>
<!-- <el-table-column label="创建者" align="center" prop="createByName" width="100" /> -->
<el-table-column label="版本名称" align="center" prop="versionName" width="100">
</el-table-column>
<el-table-column label="是否热更新" align="center" prop="isHotUpdate" width="100">
<template #default="scope">
<el-tag v-if="scope.row.isHotUpdate ==1" type="success">是</el-tag>
<el-tag v-else type="danger">否</el-tag>
</template>
</el-table-column>
<el-table-column label="apk链接" align="center" prop="apkUrl" width="200">
</el-table-column>
<el-table-column label="wgt链接" align="center" prop="wgtUrl" width="200">
</el-table-column>
<el-table-column label="更新内容" align="center" prop="updatedContent">
</el-table-column>
<el-table-column label="创建人" align="center" prop="creator" width="100">
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="100">
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200">
<template #default="scope">
<el-button link type="warning" @click="handleUpdate(scope.row)"
v-hasPermi="['manager:news:edit']" size="small">修改</el-button>
<el-button link type="danger" @click="handleDelete(scope.row)"
v-hasPermi="['manager:news:remove']" size="small">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改公告对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="800" append-to-body
class="spec-dialog">
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-row>
<el-col :span="12">
<el-form-item label="版本号" prop="versionNum" label-width="100">
<el-input v-model="form.versionNum" placeholder="请输入版本号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="版本名称" prop="versionName" label-width="100">
<el-input v-model="form.versionName" placeholder="请输入版本名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否热更新" label-width="100" prop="isHotUpdate">
<el-radio-group v-model="form.isHotUpdate" class="ml-4">
<el-radio :label="1" size="large">是</el-radio>
<el-radio :label="0" size="large">否</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="apk链接" prop="apkUrl" v-if="form.isHotUpdate=='0'" label-width="100">
<el-input v-model="form.apkUrl" placeholder="请输入版本名称" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="wgt链接" prop="wgtUrl" v-if="form.isHotUpdate=='1'" label-width="100">
<el-input v-model="form.wgtUrl" placeholder="请输入版本名称" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="更新内容" prop="updatedContent" label-width="100">
<el-input t v-model="form.updatedContent" placeholder="请输入更新内容" type="textarea" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { getVersionList, saveVersionAPi } from '#/api/newPage/appUpdate';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const listData = ref<any[]>([]);
const loading = ref(false);
const form = ref<any>({})
const dialog = ref({
visible: false,
title: '新增',
})
const queryParams = ref({
pageNum: 1,
pageSize: 10,
})
const total = ref(0)
/** 新增按钮操作 */
const handleAdd = () => {
reset();
form.value.content = ''
dialog.value.visible = true;
dialog.value.title = "新增版本信息";
}
const handleUpdate=(row: any) => {
reset();
form.value = row;
dialog.value.visible = true;
dialog.value.title = "修改版本信息";
}
const rules = reactive({
versionNum: [
{ required: true, message: "请输入版本号", trigger: "blur" },
],
versionName: [
{ required: true, message: "请输入版本名称", trigger: "blur" },
],
isHotUpdate: [
{ required: true, message: "请选择", trigger: "blur" },
],
wgtUrl: [
{ required: true, message: "请输入wgt下载链接", trigger: "blur" },
],
apkUrl: [
{ required: true, message: "请输入apk下载链接", trigger: "blur" },
],
updatedContent: [
{ required: true, message: "请输入版本内容", trigger: "blur" },
],
})
const getList = async () => {
loading.value = true
const { rows } = await getVersionList(queryParams.value)
listData.value =rows
loading.value = false
}
const reset = () => {
form.value = {
isHotUpdate:1,
}
}
const cancel=() => {
dialog.value.visible = false;
}
const formRef=ref()
const submitForm=async () => {
await formRef.value.validate(async (valid:any) => {
if(valid){
await saveVersionAPi(form.value,!form.value?.id?'add':'edit')
dialog.value.visible = false;
proxy?.$modal.msgSuccess('操作成功')
await getList()
}
})
}
const handleDelete=async (row: any) => {
await saveVersionAPi(row.id,'delete')
await getList()
}
onMounted(() => {
getList()
})
</script>
2、小程序端创建更新组件updateApp.vue
1、创建一个updateApp.vue组件
2、让后台写一个获取当前最新版本的接口
js
<template>
<!-- 版本信息弹窗 -->
<div class="updateApp">
<div class="uv-popup__content" v-if="showPopup">
<view class="logoBox">
<div class="imgeBox">
<image :src="updateImg" mode=""></image>
</div>
<view class="headLogoText">发现新版本 V{{ updateInfo.versionName }}</view>
</view>
<view class="labledec">当前版本:V{{ versionInfo.version }}</view>
<view class="lable">更新内容</view>
<view class="updateInfo">{{ updateInfo.updatedContent }}</view>
<template v-if="!updateLoading">
<div class="update-btn" v-if="updateInfo.isHotUpdate == 1" @click="updateApp">
立即更新
</div>
<div class="update-btn" v-else @click="downApp()">
下载安装包
</div>
</template>
<div class="progressBox" v-if="updateLoading">
<!-- <uv-line-progress :percentage="updateProgress" activeColor="#449FFB"></uv-line-progress> -->
<div class="text">正在下载({{updateProgress }}%)...</div>
</div>
<div class="progressBox" v-if="restartFlag">
<div class="text">安装成功,正在重启</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import updateImg from '@/static/images/update.png';
// import updateImg from '@/static/logo.png'
import { getNewestVersion } from '@/api/home';
const showPopup = ref(false);
// 状态管理
onMounted(() => {
// 获取版本信息
setTimeout(() => {
checkUpdate()
}, 500)
})
const updateInfo = ref<any>({})
const versionInfo = ref<any>({})
// 检查更新
const checkUpdate = async () => {
const res: any = await getNewestVersion()
if (res.code == 200) {
updateInfo.value = res.data
if (plus) {
plus.runtime.getProperty(plus.runtime.appid, (wgtinfo) => {
versionInfo.value = wgtinfo
if (updateInfo.value.versionNum > versionInfo.value.versionCode) {
showPopup.value = true
}
})
}
}
};
const downApp = async () => {
if (plus) {
if (updateInfo.value.apkUrl) {
plus.runtime.openURL(updateInfo.value.apkUrl);
} else {
uni.showToast({
title: '更新包不存在',
icon: 'none'
});
}
}
};
const updateProgress = ref(0)
const updateLoading = ref(false)
const updateApp = async () => {
if (!updateInfo.value.wgtUrl) {
uni.showToast({
title: '更新包不存在',
icon: 'none'
});
return
}
updateLoading.value=true
const downloadTask = uni.downloadFile({
url: updateInfo.value.wgtUrl,
success: (res) => {
if (res.statusCode === 200) {
updateLoading.value=false
installUpdate(res.tempFilePath);
}
},
fail: (err) => {
uni.showToast({ title: '下载失败', icon: 'none' });
console.error('下载失败:', err);
}
});
// 进度更新
downloadTask.onProgressUpdate((res) => {
updateProgress.value = res.progress;
});
};
const restartFlag = ref(false);
// 安装更新
const installUpdate = (tempFilePath: string) => {
// #ifdef APP-PLUS
plus.runtime.install(
tempFilePath,
{ force: false },
() => {
restartFlag.value = true;
setTimeout(() => {
plus.runtime.restart();
}, 1500);
},
(err) => {
uni.showToast({ title: '安装失败', icon: 'none' });
console.error('安装失败:', err);
}
);
// #endif
};
</script>
<style scoped lang="scss">
.updateApp {
/* 弹窗内容样式 */
.uv-popup__content {
position: absolute;
top: 40%;
left: 5vw;
width: 80vw;
min-height: 300rpx;
border-radius: 20rpx;
padding: 30rpx;
background: linear-gradient(to bottom, #ffffff, #ffffff, #E1F1FD);
display: flex;
flex-direction: column;
.logoBox {
width: 100%;
display: flex;
box-sizing: border-box;
flex-direction: column;
align-items: center;
.imgeBox {
background-color: #fff;
width: 150rpx;
height: 150rpx;
margin-top: -100rpx;
border-radius: 50%;
display: flex;
box-sizing: border-box;
flex-direction: column;
align-items: center;
image {
width: 120rpx;
height: 120rpx;
}
}
.headLogoText {
margin-top: 20rpx;
font-size: 32rpx;
font-weight: bold;
color: #449FFB;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: center;
}
}
.lable {
width: 100%;
font-size: 28rpx;
color: #000;
font-weight: bold;
}
.labledec {
width: 100%;
font-size: 24rpx;
color: #666;
// font-weight: bold;
text-align: center;
}
.updateInfo {
width: 100%;
color: #666;
font-size: 28rpx;
padding: 20rpx;
}
.update-btn {
margin-top: 30rpx;
padding: 20rpx 0;
text-align: center;
background: #578DFB;
color: #fff;
border-radius: 10rpx;
}
.progressBox {
width: 100%;
margin: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.text{
margin-top: 20rpx;
font-size: 28rpx;
color: #666;
}
}
}
}
/* 按钮样式 */
.update-btn {
margin-top: 30rpx;
padding: 20rpx 0;
text-align: center;
background: #409eff;
color: #fff;
border-radius: 10rpx;
}
.cancel-btn {
margin-top: 20rpx;
background: #f2f2f2;
color: #666;
}
</style>
注意打包时候要填写版本号
热更新时候选择打包wgt包(注意:涉及添加安卓权限的话,要打包apk包)


将打包好的文件上传对应的文件服务,将链接填写进去
