uniapp打包安卓App热更新,及提示下载安装

效果图

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包)

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

相关推荐
Dream耀6 分钟前
CSS选择器完全手册:精准控制网页样式的艺术
前端·css·html
wordbaby6 分钟前
React 19 亮点:让异步请求和数据变更也能用 Transition 管理!
前端·react.js
月亮慢慢圆7 分钟前
VUE3基础之Hooks
前端
我想说一句8 分钟前
CSS 基础知识小课堂:从“选择器”到“声明块”,带你玩转网页的时尚穿搭!
前端·javascript·面试
阿幸软件杂货间18 分钟前
PPT转图片拼贴工具 v2.0
android·python·powerpoint
红衣信18 分钟前
深入浅出 CSS 基础:从概念到选择器实战
前端·css
饮茶三千20 分钟前
五分钟!带你开发一个 VS Code 插件,实现状态栏文案轮播效果
前端
GIS之路21 分钟前
OpenLayers 地图投影转换
前端
用户48183772080321 分钟前
css grid实现流体布局
前端