一、通過?vue-cli
?創建?uni-app
?項目
-
創建 vue3 項目
- 創建以 javascript 開發的工程(如命令行創建失敗,請直接訪問?gitee?下載模板)
復制代碼npx degit dcloudio/uni-preset-vue#vite my-vue3-project
復制代碼npx degit dcloudio/uni-preset-vue#vite-alpha my-vue3-project
- 創建以 typescript 開發的工程(如命令行創建失敗,請直接訪問?gitee?下載模板)
npx degit dcloudio/uni-preset-vue#vite-ts my-vue3-project
- 創建以 javascript 開發的工程(如命令行創建失敗,請直接訪問?gitee?下載模板)
?二、通過命令腳本打wgt文件
官網提示:目前使用npm run build:app-plus
會在/dist/build/app-plus
下生成app打包資源。如需制作wgt包,將app-plus
中的文件壓縮成zip(注意:不要包含app-plus目錄
),再重命名為${appid}.wgt
,?appid
為manifest.json
文件中的appid
。
實際項目中package.json 文件中未找到build:app-plus命令,且發現執行以下倆條命令效果相同,在項目目錄下找到dist/build/app文件夾
"build:app-plus": "uni build -p app-plus"
"build:app": "uni build -p app"
制作wgt包,將dist/build/app中的文件壓縮成zip(注意:不要包含app目錄
),再重命名為xxx.wgt
三、通過自定義命令腳本自動打wgt文件
1.在package.json中注冊腳本
"scripts": { "build:wgt": "node wgtScripts.js" }
2. 安裝archiver
yarn add archiver
3.在項目根目錄下增加wgtScripts.js文件
注意manifest.json文件不要有注釋,否則會報錯
// 腳本執行命令 yarn run build:wgt
const { exec } = require("child_process");
const fs = require("fs");
const archiver = require("archiver"); // 用于壓縮文件
const path = require("path");// 獲取項目根目錄路徑
const projectRoot = path.resolve(__dirname);
// 定義要執行的命令,打包 uniapp wgt 資源
const command = "uni build -p app";// 讀取 manifest.json
const getManifestJSON = () => {const manifestPath = path.resolve(__dirname, "src/manifest.json");return JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
};// 將資源文件打壓縮包
const compress = (manifest) => {console.log("編譯成功!!!");const outputDirectory = path.resolve(__dirname, "dist/build/app");const wgtFolderPath = path.resolve(__dirname, "dist/wgt");// 創建 wgt 文件夾if (!fs.existsSync(wgtFolderPath)) {fs.mkdirSync(wgtFolderPath);}const outputZip = path.resolve(__dirname,`dist/wgt/${manifest.versionCode}.wgt`);console.log("正在壓縮中...");const output = fs.createWriteStream(outputZip);const archive = archiver("zip", { zlib: { level: 9 } });// 監聽輸出流關閉事件output.on("close", () => {const wgtPath = path.resolve(__dirname,`dist/wgt/${manifest.name}_${manifest.versionCode}.wgt`);console.log(`壓縮成功!!!`);console.log(`${manifest.name}的wgt導出路徑 ${wgtPath}`);});// 監聽警告事件archive.on("warning", (err) => {err.code === "ENOENT" ? console.warn(err) : console.error(err);});// 監聽錯誤事件archive.on("error", (err) => {console.error(err);});archive.pipe(output);archive.directory(outputDirectory, false);archive.finalize();
};const build = () => {console.log("正在編譯中...");exec(command, { cwd: projectRoot }, (error) => {if (error) {console.error(`執行命令時出錯: ${error}`);return;}const manifest = getManifestJSON();compress(manifest);});
};build();
4.執行yarn run build:wgt
即可獲得wgt文件
5.后續優化可以增加上傳文件服務器功能
四、Android/IOS權限判斷文件
var isIos
// #ifdef APP-PLUS
isIos = (plus.os.name == "iOS")
// #endif // 判斷推送權限是否開啟
function judgeIosPermissionPush() {var result = false;var UIApplication = plus.ios.import("UIApplication");var app = UIApplication.sharedApplication();var enabledTypes = 0;if (app.currentUserNotificationSettings) {var settings = app.currentUserNotificationSettings();enabledTypes = settings.plusGetAttribute("types");console.log("enabledTypes1:" + enabledTypes);if (enabledTypes == 0) {console.log("推送權限沒有開啟");} else {result = true;console.log("已經開啟推送功能!")}plus.ios.deleteObject(settings);} else {enabledTypes = app.enabledRemoteNotificationTypes();if (enabledTypes == 0) {console.log("推送權限沒有開啟!");} else {result = true;console.log("已經開啟推送功能!")}console.log("enabledTypes2:" + enabledTypes);}plus.ios.deleteObject(app);plus.ios.deleteObject(UIApplication);return result;
}// 判斷定位權限是否開啟
function judgeIosPermissionLocation() {var result = false;var cllocationManger = plus.ios.import("CLLocationManager");var status = cllocationManger.authorizationStatus();result = (status != 2)console.log("定位權限開啟:" + result);// 以下代碼判斷了手機設備的定位是否關閉,推薦另行使用方法 checkSystemEnableLocation /* var enable = cllocationManger.locationServicesEnabled(); var status = cllocationManger.authorizationStatus(); console.log("enable:" + enable); console.log("status:" + status); if (enable && status != 2) { result = true; console.log("手機定位服務已開啟且已授予定位權限"); } else { console.log("手機系統的定位沒有打開或未給予定位權限"); } */plus.ios.deleteObject(cllocationManger);return result;
}// 判斷麥克風權限是否開啟
function judgeIosPermissionRecord() {var result = false;var avaudiosession = plus.ios.import("AVAudioSession");var avaudio = avaudiosession.sharedInstance();var permissionStatus = avaudio.recordPermission();console.log("permissionStatus:" + permissionStatus);if (permissionStatus == 1684369017 || permissionStatus == 1970168948) {console.log("麥克風權限沒有開啟");} else {result = true;console.log("麥克風權限已經開啟");}plus.ios.deleteObject(avaudiosession);return result;
}// 判斷相機權限是否開啟
function judgeIosPermissionCamera() {var result = false;var AVCaptureDevice = plus.ios.import("AVCaptureDevice");var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');console.log("authStatus:" + authStatus);if (authStatus == 3) {result = true;console.log("相機權限已經開啟");} else {console.log("相機權限沒有開啟");}plus.ios.deleteObject(AVCaptureDevice);return result;
}// 判斷相冊權限是否開啟
function judgeIosPermissionPhotoLibrary() {var result = false;var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");var authStatus = PHPhotoLibrary.authorizationStatus();console.log("authStatus:" + authStatus);if (authStatus == 3) {result = true;console.log("相冊權限已經開啟");} else {console.log("相冊權限沒有開啟");}plus.ios.deleteObject(PHPhotoLibrary);return result;
}// 判斷通訊錄權限是否開啟
function judgeIosPermissionContact() {var result = false;var CNContactStore = plus.ios.import("CNContactStore");var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);if (cnAuthStatus == 3) {result = true;console.log("通訊錄權限已經開啟");} else {console.log("通訊錄權限沒有開啟");}plus.ios.deleteObject(CNContactStore);return result;
}// 判斷日歷權限是否開啟
function judgeIosPermissionCalendar() {var result = false;var EKEventStore = plus.ios.import("EKEventStore");var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0);if (ekAuthStatus == 3) {result = true;console.log("日歷權限已經開啟");} else {console.log("日歷權限沒有開啟");}plus.ios.deleteObject(EKEventStore);return result;
}// 判斷備忘錄權限是否開啟
function judgeIosPermissionMemo() {var result = false;var EKEventStore = plus.ios.import("EKEventStore");var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1);if (ekAuthStatus == 3) {result = true;console.log("備忘錄權限已經開啟");} else {console.log("備忘錄權限沒有開啟");}plus.ios.deleteObject(EKEventStore);return result;
}// Android權限查詢
function requestAndroidPermission(permissionID) {return new Promise((resolve, reject) => {plus.android.requestPermissions(permissionID.split(","),// [permissionID], // 理論上支持多個權限同時查詢,但實際上本函數封裝只處理了一個權限的情況。有需要的可自行擴展封裝 function(resultObj) {var result = 0;for (var i = 0; i < resultObj.granted.length; i++) {var grantedPermission = resultObj.granted[i];console.log('已獲取的權限:' + grantedPermission);result = 1}for (var i = 0; i < resultObj.deniedPresent.length; i++) {var deniedPresentPermission = resultObj.deniedPresent[i];console.log('拒絕本次申請的權限:' + deniedPresentPermission);result = 0}for (var i = 0; i < resultObj.deniedAlways.length; i++) {var deniedAlwaysPermission = resultObj.deniedAlways[i];console.log('永久拒絕申請的權限:' + deniedAlwaysPermission);result = -1}resolve(result);// 若所需權限被拒絕,則打開APP設置界面,可以在APP設置界面打開相應權限 // if (result != 1) { // gotoAppPermissionSetting() // } },function(error) {console.log('申請權限錯誤:' + error.code + " = " + error.message);resolve({code: error.code,message: error.message});});});
}// 使用一個方法,根據參數判斷權限
function judgeIosPermission(permissionID) {if (permissionID == "location") {return judgeIosPermissionLocation()} else if (permissionID == "camera") {return judgeIosPermissionCamera()} else if (permissionID == "photoLibrary") {return judgeIosPermissionPhotoLibrary()} else if (permissionID == "record") {return judgeIosPermissionRecord()} else if (permissionID == "push") {return judgeIosPermissionPush()} else if (permissionID == "contact") {return judgeIosPermissionContact()} else if (permissionID == "calendar") {return judgeIosPermissionCalendar()} else if (permissionID == "memo") {return judgeIosPermissionMemo()}return false;
}// 跳轉到**應用**的權限頁面
function gotoAppPermissionSetting() {if (isIos) {var UIApplication = plus.ios.import("UIApplication");var application2 = UIApplication.sharedApplication();var NSURL2 = plus.ios.import("NSURL");// var setting2 = NSURL2.URLWithString("prefs:root=LOCATION_SERVICES"); var setting2 = NSURL2.URLWithString("app-settings:");application2.openURL(setting2);plus.ios.deleteObject(setting2);plus.ios.deleteObject(NSURL2);plus.ios.deleteObject(application2);} else {// console.log(plus.device.vendor); var Intent = plus.android.importClass("android.content.Intent");var Settings = plus.android.importClass("android.provider.Settings");var Uri = plus.android.importClass("android.net.Uri");var mainActivity = plus.android.runtimeMainActivity();var intent = new Intent();intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);intent.setData(uri);mainActivity.startActivity(intent);}
}// 檢查系統的設備服務是否開啟
// var checkSystemEnableLocation = async function () {
function checkSystemEnableLocation() {if (isIos) {var result = false;var cllocationManger = plus.ios.import("CLLocationManager");var result = cllocationManger.locationServicesEnabled();console.log("系統定位開啟:" + result);plus.ios.deleteObject(cllocationManger);return result;} else {var context = plus.android.importClass("android.content.Context");var locationManager = plus.android.importClass("android.location.LocationManager");var main = plus.android.runtimeMainActivity();var mainSvr = main.getSystemService(context.LOCATION_SERVICE);var result = mainSvr.isProviderEnabled(locationManager.GPS_PROVIDER);console.log("系統定位開啟:" + result);return result}
}let permissionMap = {"android": {"CALL_PHONE": {"name": "android.permission.CALL_PHONE","title": "撥打電話權限說明","mtitle": "撥打電話權限","content": "向您獲取撥打電話權限,便于聯系客服等"},"CAMERA&STORAGE": {"name": "android.permission.WRITE_EXTERNAL_STORAGE,android.permission.CAMERA","title": "相機/相冊權限說明","mtitle": "相機/相冊權限","content": "便于您使用該功能上傳您的照片/圖片,用于上傳證件等場景中讀取和寫入相冊和文件內容"},"CAMERA": {"name": "android.permission.CAMERA","title": "相機權限說明","mtitle": "相機權限","content": "便于您使用該功能上傳您的照片/圖片,用于上傳證件等場景中拍攝內容"},"PHOTO": {"name": "android.permission.READ_EXTERNAL_STORAGE","title": "相冊權限說明","mtitle": "相冊權限","content": "便于您使用該功能上傳您的照片/圖片,用于上傳證件等場景中選擇相冊內容"},"Location": {"name": "android.permission.ACCESS_COARSE_LOCATION,android.permission.ACCESS_FINE_LOCATION","title": "獲取當前定位權限說明","mtitle": "獲取當前定位權限","content": "向您獲取當前的地理位置信息,便于查看位置、地圖選點與導航等功能"},"STORAGE": {"name": "android.permission.WRITE_EXTERNAL_STORAGE","title": "存儲權限申請說明","mtitle": "存儲權限","content": "為了將圖片保存到手機,我們需要向您申請存儲權限。"}},"ios": {}
}let view = null;function showViewDesc(permission) {let plat = isIos ? "ios" : "android";view = new plus.nativeObj.View('per-modal', {top: '0px',left: '0px',width: '100%',backgroundColor: 'rgba(0,0,0,0.2)',//opacity: '.9' })view.drawRect({color: '#fff',radius: '5px'}, {top: '30px',left: '5%',width: '90%',height: "100px",})view.drawText(permissionMap[plat][permission]["title"], {top: '40px',left: "8%",height: "30px"}, {align: "left",color: "#000",}, {onClick: function(e) {console.log(e);}})view.drawText(permissionMap[plat][permission]["content"], {top: '65px',height: "60px",left: "8%",width: "84%"}, {whiteSpace: 'normal',size: "14px",align: "left",color: "#656563"})view.show()
}function premissionCheck(permission) {console.log('premissionCheck')return new Promise(async (resolve, reject) => {let plat = isIos ? "ios" : "android";if (isIos) { // ios // const camera = permission.judgeIosPermission("camera");//判斷ios是否給予攝像頭權限 // //ios相冊沒權限,系統會自動彈出授權框 // //let photoLibrary = permission.judgeIosPermission("photoLibrary");//判斷ios是否給予相冊權限 // if(camera){ // resolve(); // }else{ // reject('需要開啟相機使用權限'); // } resolve(1)} else { // android console.log('android')let permission_arr = permissionMap[plat][permission]["name"].split(",");let flag = true;for (let i = 0; i < permission_arr.length; i++) {let status = plus.navigator.checkPermission(permission_arr[i]);if (status == "undetermined") {flag = false;}}console.log("flag", flag)if (flag == false) { // 未完全授權 showViewDesc(permission);requestAndroidPermission(permissionMap[plat][permission]["name"]).then((res) => {view.close();if (res == -1) {uni.showModal({title: '提示',content: permissionMap[plat][permission]["mtitle"] + '已被拒絕,請手動前往設置',confirmText: "立即設置",success: (res) => {if (res.confirm) {gotoAppPermissionSetting()}}})}resolve(res)})} else {resolve(1)}}})
}export default {judgeIosPermission: judgeIosPermission,requestAndroidPermission: requestAndroidPermission,checkSystemEnableLocation: checkSystemEnableLocation,gotoAppPermissionSetting: gotoAppPermissionSetting,premissionCheck: premissionCheck
}
五、熱更新wgt
1.下載彈窗
<template><view class="mask flex-center"><view class="content"><view style="position: relative;"><image class="content-img" src="./card.png" /><view class="content-version" v-if="data?.edition_name">版本號:{{data?.edition_name}}</view></view><view class="content-body"><view class="content-body-row" v-for="(item,index) of data.describe.split(';')" :key="new Date().getTime()"><view class="dot" /><view class="content-body-lable">{{item}}</view></view><view v-if="showProgress"><u-line-progress :striped="true" :percent="percent" active-color="#E80A1E" :striped-active="true" /><view>正在下載,請稍后</view></view><view v-else><view v-if="!cancleBtn"><view class="content-button-now content-button" @click="confirm">立即更新</view></view><view class="content-button-row" v-else><view class="content-button-cancel content-button" @click="cancel">以后再說</view><view class="content-button-confirm content-button" @click="confirm">立即更新</view></view></view></view></view></view>
</template><script setup>import {ref,reactive,toRefs} from 'vue'import {onLoad,onBackPress} from '@dcloudio/uni-app'const dataMap = reactive({percent: 0, //進度條百分比cancleBtn: true, //是否強制立即更新showProgress: false, //是否顯示按鈕data: {describe: '1. 修復已知問題;2. 優化用戶體驗',edition_url: 'http://download.rongtongkeji.com/榮煤寶.apk', //安裝包下載地址或者通用應用市場地址edition_force: 1, //是否強制更新 0代表否 1代表是package_type: 1 //0是整包升級 1是wgt升級}})const {percent,cancleBtn,data,showProgress} = toRefs(dataMap)onLoad((e) => {if (e?.obj) {dataMap.data = JSON.parse(e.obj);}dataMap.cancleBtn = !dataMap.data.edition_force;})onBackPress((e) => {// 強制更新不允許返回if (dataMap.data.edition_force || dataMap.percent > 0) {return true;}})const cancel = () => {//取消升級 返回上一頁uni.navigateBack({delta: 1});}const confirm = () => {// const packageName = ''// let url = ''// let deviceBrand = uni.getSystemInfoSync()?.deviceBrand?.toLowerCase() || ''// if (dataMap.data.package_type == 0) {// if (uni.getSystemInfoSync().platform != 'android') {// url = "https://apps.apple.com/cn/app/xxx"// } else {// if (deviceBrand.indexOf("huawei") > -1) {// url = "appmarket://details?id=" + packageName;// } else if (deviceBrand.indexOf("oppo") > -1) {// url = "market://details?id=" + packageName;// } else if (deviceBrand.indexOf("vivo") > -1) {// url = "vivoMarket://details?id=" + packageName;// } else if (deviceBrand.indexOf("mi") > -1) {// url = "mimarket://details?id=" + packageName;// } else if (deviceBrand.indexOf("samsung") > -1) {// url = "samsungapps://ProductDetail/" + packageName;// } else if (deviceBrand.indexOf("lenovo") > -1) {// url = "http://market.lenovomm.com/details?id=" + packageName;// } else {// // url = "https://a.app.qq.com/o/simple.jsp?pkgname="+packageName;// url = "http://download/xx.apk";// }// }// //apk整包升級// if (url.includes('.apk')) {// download();// } else {// plus.runtime.openURL(url);// }// } else {// //wgt資源包升級// download();// }download();}const download = () => {dataMap.data.edition_force = 0dataMap.showProgress = trueconst downloadTask = uni.downloadFile({url: dataMap.data.edition_url,success: res => {if (res.statusCode === 200) {plus.runtime.install(res.tempFilePath, {force: true //true表示強制安裝,不進行版本號的校驗;false則需要版本號校驗,},function() {if (dataMap.data.package_type == 1) {// wgt升級需要重啟plus.runtime.restart();}else {plus.runtime.quit();}},function(e) {console.error('install fail...');});}}});downloadTask.onProgressUpdate(res => {dataMap.percent = res.progress;});}
</script><style lang="scss">page {background: transparent;}.flex-center {display: flex;justify-content: center;align-items: center;}.mask {position: fixed;left: 0;top: 0;right: 0;bottom: 0;background-color: rgba(0, 0, 0, 0.65);}.content {width: 600rpx;background: #FFFFFF;border-radius: 32rpx;.content-img {width: 600rpx;height: 344rpx;}.content-version {position: absolute;bottom: 22rpx;left: 70rpx;font-size: 28rpx;font-family: PingFangSC-Medium, PingFang SC;font-weight: 500;color: #E90F21;}.content-body {padding: 0 40rpx 40rpx 40rpx;.content-body-row {display: flex;.dot {width: 14rpx;height: 14rpx;background-color: #E90D20;border-radius: 50px;margin-top: 16rpx;margin-right: 16rpx;}.content-body-lable {width: 506rpx;font-size: 28rpx;font-family: PingFangSC-Regular, PingFang SC;font-weight: 400;color: #333333;}}.content-button-now {width: 520rpx;color: #FFFFFF;background: linear-gradient(90deg, #FF775B 0%, #E80A1E 100%);margin-top: 32rpx;}.content-button-row {display: flex;justify-content: space-between;margin-top: 32rpx;.content-button-confirm {width: 244rpx;background: linear-gradient(90deg, #FF775B 0%, #E80A1E 100%);color: #FFFFFF;}.content-button-cancel {width: 244rpx;border: 2rpx solid #999999;color: #999999;}}.content-button {height: 80rpx;border-radius: 40rpx;font-size: 28rpx;font-family: PingFangSC-Regular, PingFang SC;font-weight: 400;display: flex;justify-content: center;align-items: center;}}}
</style>
2.silence-update文件從服務器判斷是否需要更新
import { config } from '@/config/config';
export const silenceUpdate = (url) => {uni.downloadFile({url,success: res => {if (res.statusCode === 200) {plus.runtime.install(res.tempFilePath, {force: true //true表示強制安裝,不進行版本號的校驗;false則需要版本號校驗,},function() {uni.showModal({title: '更新提示',content: '新版本已經準備好,請重啟應用',showCancel: false,success: function(res) {if (res.confirm) {plus.runtime.restart()}}});// console.log('install success...');},function(e) {console.error('install fail...');});}}});
}export const checkVersion = (appid, platform, version_code, loadding) => {loadding && uni.showLoading({title: '加載中',mask: true})let url = config.baseUrl + '/edition/get_renew_edition'//獲取服務器的版本號uni.request({url: url,method: 'POST',data: {edition_type: appid,version_type: platform,edition_number: version_code,},header: {"content-type": "application/json",},success: (res) => {console.log(res, 'res')if (res.statusCode == 200 && res.data.ok) {//判斷后臺返回版本號是否大于當前應用版本號 && 是否發行if (Number(res.data.data.edition_number) == version_code && res.data.data.edition_issue && loadding) {uni.showToast({title: '已是最新版本',icon: 'none',mask: true})return}if (Number(res.data.data.edition_number) > version_code && res.data.data.edition_issue) {//注意 這里obj盡量不要修改,如果非要修改,rt-uni-update里的onload的obj也得改if (res.data.data.package_type == '1' && res.data.data.edition_silence) {//調用靜默更新方法 傳入下載地址silenceUpdate(res.data.data.edition_url)} else {const pages = getCurrentPages()let flag = falsefor (let s of pages) {if(s.route == 'components/hot-update-gc/index') flag = true}!flag && uni.navigateTo({url: '/components/hot-update-gc/index?obj=' + JSON.stringify(res.data.data)})}}}},fail: (err) => {console.log(err, 'err')},complete: () => {loadding && uni.hideLoading()}})
}
3.調用checkVersion?
import { checkVersion } from "@/xxx/silence-update.js"
plus.runtime.getProperty(plus.runtime.appid!, async (inf) => {let version_code = +inf.versionCode!checkVersion(plus.runtime.appid, uni.getSystemInfoSync().platform, version_code, false)
})