uni-app使用uView、导航相关、云函数、上传预览、地图、证书制作、打包发布、语音转文字、视频

本文详细介绍了uni-app的使用,包括uView插件的安装与配置,导航栏的定制,云数据库和云函数的运用,图片上传与预览,地图功能配置,打包发布过程,以及语音转文字和视频相关功能。同时,还涉及到下拉刷新、上拉加载、Echarts图表和隐私弹窗等内容。

1. 使用uView插件

1.1 安装(2.0):

npm install uview-ui@2.0.28

1.2 配置:

1.2.1 在根目录的pages.json中添加

"easycom": {
	"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
},

1.2.2 在根目录的uni.scss中添加

@import 'uview-ui/theme.scss';

1.2.3 在App.vue的style中添加

<style lang="scss">
	/*每个页面公共css */
		@import "uview-ui/index.scss";
		@import url("./static/font/icon.scss");
		
		uni-page-body,
		html,
		body {
			height: 100%;
		}

</style>

在这里插入图片描述

1.3 使用1.0和Nvue的处理

因为uview1.0 不支持nvue,所以会报很多错,在上步骤1.2.3时,加个判断

<style lang="scss">
	/*每个页面公共css */
	/* #ifndef APP-NVUE */
		@import "uview-ui/index.scss";
		@import url("./static/font/icon.scss");
		
		uni-page-body,
		html,
		body {
			height: 100%;
		}
	/* #endif */
</style>

2. 配置导航栏:

2.1 底部导航栏 图标及未选中图标

在pages.json文件

"tabBar": {
		"color": "#7A7E83",
		"selectedColor": "#007AFF",
		"borderStyle": "black",
		"backgroundColor": "#F8F8F8",
		"list": [{
				"navigationBarBackgroundColor": "#8A2BE2",
				"pagePath": "pages/index/index",
				"iconPath": "static/home.png",	// 未选中
				"selectedIconPath": "static/home_cur.png", // 选中
				"text": "首页"
			}
		]
	}

2.2 顶部导航栏

在pages.json文件

2.2.1 一般导航栏

"pages": [
	{
		"path": "pages/xuncha/xunchaList",
		"style": {
			"navigationBarBackgroundColor": "#3B6FFF",
			"navigationBarTextStyle": "white",
			"navigationBarTitleText": "巡查处置",
			"app-plus": {
				"titleNView": true
			}
	},

2.2.2 导航栏添加文字、图标、事件

"pages": [
    "style": {
				"navigationBarBackgroundColor": "#8A2BE2",
				"navigationBarTextStyle": "white",
				"navigationBarTitleText": "设施管理",
				"app-plus": {
					"titleNView": {
						"backgroundImage": "linear-gradient(to right, #C280FF, #5E7EDB, #5E7EDB, #C280FF)",
						"buttons": [{
							"text": "\ue468",	// 注意:此处只能使用Unicode码
							"fontSrc": "./static/uni.ttf",	// 需要引入字体
							"fontSize": "25",
							"float": "right"
						}]
					}
				}
			}
]

请添加图片描述

图标的点击事件:在该文件中,生命周期位置

		// 监听tabbar右侧按钮的点击事件
		onNavigationBarButtonTap(e) {
            // 如果有多个图标,可以判断 e.float == 'right'
			uni.navigateTo({
				url: "xxx可以路由跳转"
			})
		},

2.2.3 定制导航栏

将titleNView 设为false

"pages": [
	{
		"path": "pages/xuncha/xunchaList",
		"style": {
			"navigationBarBackgroundColor": "#3B6FFF",
			"navigationBarTextStyle": "white",
			"navigationBarTitleText": "巡查处置",
			"app-plus": {
				"titleNView": false  //去掉app+h5顶部导航
			}
	},

在文件中,配置地址:https://ext.dcloud.net.cn/plugin?name=uni-nav-bar

		<uni-nav-bar fixed="true" background-color="#62e6b9" color="#fff" title="巡查处置" status-bar="true"
			left-icon="back" :right-icon="userType != 1 ? 'plusempty' : ''" @clickRight="clickNavRight()" @clickLeft="clickNavLeft()">
		</uni-nav-bar>

// 可以控制图标,还能自定义点击事件

2.2.4 导航栏加图片,加副标题

链接:https://uniapp.dcloud.io/component/navigation-bar.html#navigation-bar

<page-meta>
		<navigation-bar
			:title="nbTitle"
			:title-icon="titleIcon"
			:title-icon-radius="titleIconRadius"
			:subtitle-text="subtitleText"
      		:subtitle-color="nbFrontColor"
			:loading="nbLoading"
			:front-color="nbFrontColor"
			:background-color="nbBackgroundColor"
			:color-animation-duration="2000"
			color-animation-timing-func="easeIn"
		/>
	</page-meta>


data() {
  return {
	nbTitle: '大洋湾综合管理平台',
	titleIcon: '/static/images/home/titleIcon.png',
	titleIconRadius: '20px',
	subtitleText: 'subtitleText',
	nbLoading: false,
	nbFrontColor: '#fff',
	nbBackgroundColor: '#79C0D1',
  }
}



2.3 底部导航栏,根权限展示不同的栏目

利用 uni.setTabBarItemvisible 来实现
在这里插入图片描述
在pages.json中配置齐全,然后在登陆后,根据后端返回的角色,来判断设置

if(res.data.data.userInfo.userType == 1) {
	uni.setTabBarItem({
		index: 2,
		visible: false,
	})
}

在这里插入图片描述

3. 下拉刷新 和 上拉加载

3.1 下拉刷新

  • ① 首先在pages.json中开启
{
	"path": "pages/xx/xx",
	"style": {
		// ...略
		"enablePullDownRefresh": true,	// 开启下拉动画
		"app-plus": {
			"titleNView": {
				"backgroundImage": "linear-gradient(to right, #62e6b9, #20bd8d)"
			}
		}
	}
},
  • ② 然后在使用的地方,与onLoad等平级
onPullDownRefresh() {
	uni.startPullDownRefresh({
		success: () => {
			this.getList();	// 每次下拉重新调用
			uni.stopPullDownRefresh();	// 手动关闭下拉动画
		}
	})
},

3.2 上拉加载(分页)

  • 在使用页面,与onLoad等平级
// 在模板底部加上
<u-loadmore :status="status" />

// js部分
data() {
  return {
    status: 'loadmore',
	pageNo: 1
  }
},
onReachBottom() {
	this.status = 'loading';
	setTimeout(() => {
		this.pageNo += 1;
		this.getDatas();
	}, 1000)
},
methods: {
	getDatas() {
		// 请求时将pageNo传给后端即可
	}
}
  • 提示如果有搜索、或者下拉时,pageNo = 1即可。

4. 使用云数据库和云函数

4.1 新建云函数

在项目目录上右键 -> 新建uniCloud云开发环境 -> 阿里云(腾讯云只能认证一个) -> 在生成的uniCloud下的cloudfunctions 右键,新建云函数

'use strict';

const db = uniCloud.database(); // 获取数据库的引用
exports.main = async (event, context) => {
	
	let num = event.scope*10;
	const dbcmd = db.command; // 获取指令集
	const { data } = await db.collection('infoClassify').where({	// 获取 数据库集合引用, 并查询
		// id: event.scope 	// 查找id 为请求值的数据
        // id: parserInt(event.scope)	// 有时候数据是字符串类型,需要转换一下(路由传参就是字符串型)
        id: dbcmd.gte(num).and(dbcmd.lt(num+10))	// 查找 大于num且小于num+10的数据
	}).get();	// 记得要get一下,否则查不到
	
	//返回数据给客户端
	return data
};

写完云函数后,记得右键【上传部署】

4.2 云数据库

在云数据库中,新建表,全部是 json格式的

4.3 使用云函数

// 在onLoad生命周期中使用
async onLoad() {
	let { result, success} = await uniCloud.callFunction({
		name: "getClassify"	// 刚才新建的云函数文件夹名字
	})
	console.log(result, success);
},
// 在监听器中使用
watch: {
	activeIndex: {
		handler: async function(val) {
			let res = await uniCloud.callFunction({
				name: 'leftClassify', // 云函数的文件夹名字
				data: {
					scope: val + 1
				}
			})
			this.tabbar = res.result;
			// 默认选中第一个
			this.classifyId = this.tabbar[0].classifyId;
		},
		immediate: true
	}
},

参考文档:https://uniapp.dcloud.net.cn/uniCloud/cf-functions?id=clientcallfunction

5. 图片上传和预览

5.1 uView 2.0版本

<u-form-item label="照片" labelWidth="100"></u-form-item>
<u-upload :fileList="info.xc" width="120" height="120" @afterRead="after" @delete="deletePic" multiple :maxCount="1" :previewFullImage="true" ></u-upload>

data() {
  return {
      fileList: [{	// 使用多张上传方式,可以实现预览
		url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-4917ad8f-2b3c-4371-a1e5-dfe5771201c5/c814d40f-9041-4069-9ef2-063a94a08079.png'
	}],
  }
}

methods: {
	// 删除图片
	deletePic(event) {
		this.fileList.splice(event.index, 1)
	},
	// 新增图片 - 单张(很少用)
	async after(event) {
		this.info.xc.push({
			status: 'uploading',
			message: '上传中'
		})
		const result = await this.uploadFilePromise(event.file[0].url, 99)

		// 要先预览回显成功
        // 方法一:使用splice和Object.assign 
		this.info.mp.splice(0, 1, Object.assign(this.info.mp[0], {
			status: 'success',
			message: '',
			url: result
		}))
		// 方法二: 使用 $set 改变属性
		// this.$set(this.info.xc[0], 'status', 'success')
		// this.$set(this.info.xc[0], 'message', '')
		// this.$set(this.info.xc[0], 'url', result)
        
	},
	// 新增图片 - 多张(尽量用)
	async afterRead(event) {

		// 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式
		let lists = [].concat(event.file)
		let fileListLen = this.fileList.length
		lists.map((item) => {
			this.fileList.push({
				...item,
				status: 'uploading',
				message: '上传中'
			})
		})
	
		for (let i = 0; i < lists.length; i++) {
			const result = await this.uploadFilePromise(lists[i].url, i)
			let item = this.info.fileList[fileListLen]
			this.fileList.splice(fileListLen, 1, Object.assign(item, {
				status: 'success',
				message: '',
				url: result
			}))
			fileListLen++
		}
	},
	// 上传图片
	uploadFilePromise(url, i) {
		return new Promise((resolve, reject) => {
            // 方式一:如果是上传到自己的云存储中
			//uniCloud.uploadFile({
				//cloudPath: `test${i}.jpg`,   // 文件名
				//filePath: url,    // 文件信息
				//success(res) {
					//setTimeout(() => {
						//resolve(res.fileID)
					//}, 1000)
				//}
			//})
            // 方式二:如果是调用后端接口
            let a = uni.uploadFile({
                url: 'http://192.168.2.21:7001/upload', // 仅为示例,非真实的接口地址
                filePath: url,
                name: 'file',
                formData: {
                    user: 'test'
                },
                success: (res) => {
                    setTimeout(() => {
                        resolve(res.data.data)
                    }, 1000)
                }
            });
		})
		
	},
}

5.2 uView 1.0版本

<u-upload :action="uploadURL()" :fileList="fileList" width="120"  height="120" multiple :maxCount="3" 
		:previewFullImage="true" :showProgress="false" @on-success="onSuccess" @on-remove="onRemove" />


fileList: [
	{
		url: 'https://cdn.uviewui.com/uview/album/1.jpg'
	},
	{
		url: 'https://cdn.uviewui.com/uview/album/2.jpg'
	}
],


// 图片上传地址
uploadURL() {
	return 'http://112.2.52.60:10000/bcz/app/test/api/personnelInfo/uploadPersonnelImage'
},
// 删除图片
onRemove(index) {
	this.fileList.splice(index, 1)
},
// 新增图片
onSuccess(event, index, lists) {
	// 解决上传时,多显示的bug
	lists[index] = {
		"url": data.data,
		"error": false,
		"progress": 100
	};
	// 放到数组里
	this.fileList.push({
		url: event.data
	})
},


5.3 自定义封装

插件地址:
在static文件夹下 新建文件 video-icon.svg

<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd"><path d="M24 23h-24v-21h24v21zm-20-1v-4h-3v4h3zm15 0v-19h-14v19h14zm4 0v-4h-3v4h3zm-6-9.5l-9 5v-10l9 5zm3 .5v4h3v-4h-3zm-16 4v-4h-3v4h3zm5-1.2l5.941-3.3-5.941-3.3v6.6zm11-7.8v4h3v-4h-3zm-16 4v-4h-3v4h3zm16-9v4h3v-4h-3zm-16 4v-4h-3v4h3z"/></svg>

在components 文件夹下 新建 htz-image-upload

<template>
	<view class="htz-image-upload-list">
		<view class="htz-image-upload-Item" v-for="(item,index) in uploadLists" :key="index">
			<view class="htz-image-upload-Item-video" v-if="isVideo(item)">
				<!-- #ifndef APP-PLUS -->
				<video :disabled="false" :controls="false" :src="getFileUrl(item)">
					<cover-view class="htz-image-upload-Item-video-fixed" @click="previewVideo(getFileUrl(item))">
					</cover-view>

					<cover-view class="htz-image-upload-Item-del-cover" v-if="remove && previewVideoSrc==''"
						@click="imgDel(index)">×</cover-view>

				</video>
				<!-- #endif -->
				<!-- #ifdef APP-PLUS -->
				<view class="htz-image-upload-Item-video-fixed" @click="previewVideo(getFileUrl(item))"></view>
				<image class="htz-image-upload-Item-video-app-poster" mode="widthFix" :src="appVideoPoster"></image>
				<!-- #endif -->
			</view>

			<image v-else :src="getFileUrl(item)" @click="imgPreview(getFileUrl(item))"></image>

			<view class="htz-image-upload-Item-del" v-if="remove" @click="imgDel(index)">×</view>
		</view>
		<view class="htz-image-upload-Item htz-image-upload-Item-add" v-if="uploadLists.length<max && add"
			@click="chooseFile">
			+
		</view>
		<view class="preview-full" v-if="previewVideoSrc!=''">
			<video :autoplay="true" :src="previewVideoSrc" :show-fullscreen-btn="false">
				<cover-view class="preview-full-close" @click="previewVideoClose"> ×
				</cover-view>
			</video>
		</view>


		<!--  -->
	</view>
</template>

<style>
	.ceshi {
		width: 100%;
		height: 100%;
		position: relative;
		top: 0;
		left: 0;
		bottom: 0;
		right: 0;
		background-color: #FFFFFF;
		color: #2C405A;
		opacity: 0.5;
		z-index: 100;
	}
</style>

<script>
	export default {
		name: 'htz-image-upload',
		props: {
			max: { //展示图片最大值
				type: Number,
				default: 1,
			},
			chooseNum: { //选择图片数
				type: Number,
				default: 9,
			},
			name: { //发到后台的文件参数名
				type: String,
				default: 'file',
			},
			dataType: { //v-model的数据结构类型
				type: Number,
				default: 0, // 0: ['http://xxxx.jpg','http://xxxx.jpg'] 1:[{type:0,url:'http://xxxx.jpg'}]  type 0 图片 1 视频 url 文件地址 此类型是为了给没有文件后缀的状况使用的
			},
			remove: { //是否展示删除按钮
				type: Boolean,
				default: true,
			},
			add: { //是否展示添加按钮
				type: Boolean,
				default: true,
			},
			disabled: { //是否禁用
				type: Boolean,
				default: false,
			},
			sourceType: { //选择照片来源 【ps:H5就别费劲了,设置了也没用。不是我说的,官方文档就这样!!!】
				type: Array,
				default: () => ['album', 'camera'],
			},
			action: { //上传地址 如需使用uniCloud服务,设置为uniCloud即可
				type: String,
				default: '',
			},
			headers: { //上传的请求头部
				type: Object,
				default: () => {},
			},
			formData: { //HTTP 请求中其他额外的 form data
				type: Object,
				default: () => {},
			},
			compress: { //是否需要压缩
				type: Boolean,
				default: true,
			},
			quality: { //压缩质量,范围0~100
				type: Number,
				default: 80,
			},
			value: { //受控图片列表
				type: Array,
				default: () => [],
			},
			uploadSuccess: {
				default: (res) => {
					return {
						success: false,
						url: ''
					}
				},
			},
			mediaType: { //文件类型 image/video/all
				type: String,
				default: 'image',
			},
			maxDuration: { //拍摄视频最长拍摄时间,单位秒。最长支持 60 秒。 (只针对拍摄视频有用)
				type: Number,
				default: 60,
			},
			camera: { //'front'、'back',默认'back'(只针对拍摄视频有用)
				type: String,
				default: 'back',
			},
			appVideoPoster: { //app端视频展示封面 只对app有效
				type: String,
				// default: '/static/htz-image-upload/play.png',
				default: '/static/video-icon.svg',
			},
		},
		data() {
			return {
				uploadLists: [],
				mediaTypeData: ['image', 'video', 'all'],
				previewVideoSrc: '',
			}
		},
		mounted: function() {
			this.$nextTick(function() {
				this.uploadLists = this.value;
				if (this.mediaTypeData.indexOf(this.mediaType) == -1) {
					uni.showModal({
						title: '提示',
						content: 'mediaType参数不正确',
						showCancel: false,
						success: function(res) {
							if (res.confirm) {
								//console.log('用户点击确定');
							} else if (res.cancel) {
								//console.log('用户点击取消');
							}
						}
					});
				}
			});
		},
		watch: {
			value(val, oldVal) {
				//console.log('value',val, oldVal)
				this.uploadLists = val;
			},
		},
		methods: {
			isVideo(item) {
				let isPass = false
				if ((!/.(gif|jpg|jpeg|png|gif|jpg|png)$/i.test(item) && this.dataType == 0) || (this.dataType == 1 && item
						.type == 1)) {
					isPass = true
				}
				return isPass
			},
			getFileUrl(item) {
				var url = item;
				if (this.dataType == 1) {
					url = item.url
				}
				//console.log('url', url)
				return url
			},
			previewVideo(src) {
				this.previewVideoSrc = src;
				// this.previewVideoSrc =
				// 	'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-fbd63a76-dc76-485c-b711-f79f2986daeb/ba804d82-860b-4d1a-a706-5a4c8ce137c3.mp4'
			},
			previewVideoClose() {
				this.previewVideoSrc = ''
				//console.log('previewVideoClose', this.previewVideoSrc)
			},
			imgDel(index) {
				uni.showModal({
					title: '提示',
					content: '您确定要删除么?',
					success: (res) => {
						if (res.confirm) {
							// this.uploadLists.splice(index, 1)
							// this.$emit("input", this.uploadLists);
							// this.$emit("imgDelete", this.uploadLists);
							let delUrl = this.uploadLists[index]
							this.uploadLists.splice(index, 1)
							this.$emit("input", this.uploadLists);
							this.$emit("imgDelete", {
								del: delUrl,
								tempFilePaths: this.uploadLists
							});
						} else if (res.cancel) {}
					}
				});
			},
			imgPreview(index) {

				var imgData = []

				this.uploadLists.forEach(item => {
					if (!this.isVideo(item)) {
						imgData.push(this.getFileUrl(item))
					}
				})


				//console.log('imgPreview', imgData)
				uni.previewImage({
					urls: imgData,
					current: index,
					loop: true,
				});


			},
			chooseFile() {
				if (this.disabled) {
					return false;
				}
				switch (this.mediaTypeData.indexOf(this.mediaType)) {
					case 1: //视频
						this.videoAdd();
						break;
					case 2: //全部
						uni.showActionSheet({
							itemList: ['相册', '视频'],
							success: (res) => {
								if (res.tapIndex == 1) {
									this.videoAdd();
								} else if (res.tapIndex == 0) {
									this.imgAdd();
								}
							},
							fail: (res) => {
								console.log(res.errMsg);
							}
						});
						break;
					default: //图片
						this.imgAdd();
						break;
				}


				//if(this.mediaType=='image'){


			},
			videoAdd() {
				//console.log('videoAdd')
				let nowNum = Math.abs(this.uploadLists.length - this.max);
				let thisNum = (this.chooseNum > nowNum ? nowNum : this.chooseNum) //可选数量
				uni.chooseVideo({
					compressed: this.compress,
					sourceType: this.sourceType,
					camera: this.camera,
					maxDuration: this.maxDuration,
					success: (res) => {
						// console.log('videoAdd', res)
						// console.log(res.tempFilePath)
						this.chooseSuccessMethod([res.tempFilePath], 1)
						//this.imgUpload([res.tempFilePath]);
						//console.log('tempFiles', res)
						// if (this.action == '') { //未配置上传路径
						// 	this.$emit("chooseSuccess", res.tempFilePaths);
						// } else {
						// 	if (this.compress && (res.tempFiles[0].size / 1024 > 1025)) { //设置了需要压缩 并且 文件大于1M,进行压缩上传
						// 		this.imgCompress(res.tempFilePaths);
						// 	} else {
						// 		this.imgUpload(res.tempFilePaths);
						// 	}
						// }
					}
				});
			},
			imgAdd() {
				//console.log('imgAdd')
				let nowNum = Math.abs(this.uploadLists.length - this.max);
				let thisNum = (this.chooseNum > nowNum ? nowNum : this.chooseNum) //可选数量
				//console.log('nowNum', nowNum)
				//console.log('thisNum', thisNum)
				// #ifdef APP-PLUS
				if (this.sourceType.length > 1) {
					uni.showActionSheet({
						itemList: ['拍摄', '从手机相册选择'],
						success: (res) => {
							if (res.tapIndex == 1) {
								this.appGallery(thisNum);
							} else if (res.tapIndex == 0) {
								this.appCamera();
							}
						},
						fail: (res) => {
							console.log(res.errMsg);
						}
					});
				}
				if (this.sourceType.length == 1 && this.sourceType.indexOf('album') > -1) {
					this.appGallery(thisNum);
				}

				if (this.sourceType.length == 1 && this.sourceType.indexOf('camera') > -1) {
					this.appCamera();
				}
				// #endif
				//#ifndef APP-PLUS
				uni.chooseImage({
					count: thisNum,
					sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
					sourceType: this.sourceType,
					success: (res) => {
						this.chooseSuccessMethod(res.tempFilePaths, 0)
						//console.log('tempFiles', res)
						// if (this.action == '') { //未配置上传路径
						// 	this.$emit("chooseSuccess", res.tempFilePaths);
						// } else {
						// 	if (this.compress && (res.tempFiles[0].size / 1024 > 1025)) { //设置了需要压缩 并且 文件大于1M,进行压缩上传
						// 		this.imgCompress(res.tempFilePaths);
						// 	} else {
						// 		this.imgUpload(res.tempFilePaths);
						// 	}
						// }
					}
				});
				// #endif
			},
			appCamera() {
				var cmr = plus.camera.getCamera();
				var res = cmr.supportedImageResolutions[0];
				var fmt = cmr.supportedImageFormats[0];
				//console.log("Resolution: " + res + ", Format: " + fmt);
				cmr.captureImage((path) => {
						//alert("Capture image success: " + path);
						this.chooseSuccessMethod([path], 0)
					},
					(error) => {
						//alert("Capture image failed: " + error.message);
						console.log("Capture image failed: " + error.message)
					}, {
						resolution: res,
						format: fmt
					}
				);
			},
			appGallery(maxNum) {
				plus.gallery.pick((res) => {
					this.chooseSuccessMethod(res.files, 0)
				}, function(e) {
					//console.log("取消选择图片");
				}, {
					filter: "image",
					multiple: true,
					maximum: maxNum
				});
			},
			chooseSuccessMethod(filePaths, type) {
				if (this.action == '') { //未配置上传路径
					this.$emit("chooseSuccess", filePaths, type); //filePaths 路径 type 0 为图片 1为视频
				} else {
					if (type == 1) {
						this.imgUpload(filePaths, type);
					} else {
						if (this.compress) { //设置了需要压缩
							this.imgCompress(filePaths, type);
						} else {
							this.imgUpload(filePaths, type);
						}
					}

				}
			},
			imgCompress(tempFilePaths, type) { //type 0 为图片 1为视频
				uni.showLoading({
					title: '压缩中...'
				});

				let compressImgs = [];
				let results = [];
				tempFilePaths.forEach((item, index) => {
					compressImgs.push(new Promise((resolve, reject) => {
						// #ifndef H5
						uni.compressImage({
							src: item,
							quality: this.quality,
							success: res => {
								//console.log('compressImage', res.tempFilePath)
								results.push(res.tempFilePath);
								resolve(res.tempFilePath);
							},
							fail: (err) => {
								//console.log(err.errMsg);
								reject(err);
							},
							complete: () => {
								//uni.hideLoading();
							}
						})
						// #endif
						// #ifdef H5
						this.canvasDataURL(item, {
							quality: this.quality / 100
						}, (base64Codes) => {
							//this.imgUpload(base64Codes);
							results.push(base64Codes);
							resolve(base64Codes);
						})
						// #endif
					}))
				})
				Promise.all(compressImgs) //执行所有需请求的接口
					.then((results) => {
						uni.hideLoading();
						//console.log('imgCompress', type)
						this.imgUpload(results, type);
					})
					.catch((res, object) => {
						uni.hideLoading();
					});
			},
			imgUpload(tempFilePaths, type) { //type 0 为图片 1为视频
				// if (this.action == '') {
				// 	uni.showToast({
				// 		title: '未配置上传地址',
				// 		icon: 'none',
				// 		duration: 2000
				// 	});
				// 	return false;
				// }
				if (this.action == 'uniCloud') {
					this.uniCloudUpload(tempFilePaths, type)
					return
				}
				uni.showLoading({
					title: '上传中'
				});
				//console.log('imgUpload', tempFilePaths)
				let uploadImgs = [];
				tempFilePaths.forEach((item, index) => {
					uploadImgs.push(new Promise((resolve, reject) => {
						//console.log(index, item)
						const uploadTask = uni.uploadFile({
							url: this.action, //仅为示例,非真实的接口地址
							filePath: item,
							name: this.name,
							fileType: 'image',
							formData: this.formData,
							header: this.headers,
							success: (uploadFileRes) => {
								//uni.hideLoading();
								//console.log(typeof this.uploadSuccess)
								//console.log('')
								uploadFileRes.fileType = type
								if (typeof this.uploadSuccess == 'function') {

									let thisUploadSuccess = this.uploadSuccess(
										uploadFileRes)
									if (thisUploadSuccess.success) {

										if (this.dataType == 0) {
											this.value.push(thisUploadSuccess.url)
										} else {
											this.value.push({
												type: type,
												url: thisUploadSuccess.url
											})
										}

										this.$emit("input", this.uploadLists);
									}
								}
								resolve(uploadFileRes);
								this.$emit("uploadSuccess", uploadFileRes);
							},
							fail: (err) => {
								console.log(err);
								//uni.hideLoading();
								reject(err);
								this.$emit("uploadFail", err);
							},
							complete: () => {
								//uni.hideLoading();
							}
						});
					}))
				})
				Promise.all(uploadImgs) //执行所有需请求的接口
					.then((results) => {
						uni.hideLoading();
					})
					.catch((res, object) => {
						uni.hideLoading();
						this.$emit("uploadFail", res);
					});
				// uploadTask.onProgressUpdate((res) => {
				// 	//console.log('',)
				// 	uni.showLoading({
				// 		title: '上传中' + res.progress + '%'
				// 	});
				// 	if (res.progress == 100) {
				// 		uni.hideLoading();
				// 	}
				// });
			},
			uniCloudUpload(tempFilePaths, type) {
				uni.showLoading({
					title: '上传中'
				});
				console.log('uniCloudUpload', tempFilePaths);
				let uploadImgs = [];
				tempFilePaths.forEach((item, index) => {
					uploadImgs.push(new Promise((resolve, reject) => {

						uniCloud.uploadFile({
							filePath: item,
							cloudPath: this.guid() + '.' + this.getFileType(item, type),
							success(uploadFileRes) {
								if (uploadFileRes.success) {
									resolve(uploadFileRes.fileID);
								}
							},
							fail(err) {
								console.log(err);
								reject(err);
							},
							complete() {}
						});

					}))
				})
				Promise.all(uploadImgs) //执行所有需请求的接口
					.then((results) => {
						uni.hideLoading();
						// console.log('then', results)

						uniCloud.getTempFileURL({
							fileList: results,
							success: (res) => {
								//console.log('success',res.fileList)
								res.fileList.forEach(item => {
									//console.log(item.tempFileURL)
									this.value.push(item.tempFileURL)
								})
							},
							fail() {},
							complete() {}
						});
					})
					.catch((res, object) => {
						uni.hideLoading();
					});
			},
			getFileType(path, type) { //手机端默认图片为jpg 视频为mp4
				// #ifdef H5 
				var result = type == 0 ? 'jpg' : 'mp4';
				// #endif


				// #ifndef H5
				var result = path.split('.').pop().toLowerCase();
				// #ifdef MP 
				if (this.compress) {//微信小程序压缩完没有后缀
					result = type == 0 ? 'jpg' : 'mp4';
				}
				// #endif
				// #endif
				return result;
			},
			guid() {
				return 'xxxxxxxx-date-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
					var r = Math.random() * 16 | 0,
						v = c == 'x' ? r : (r & 0x3 | 0x8);
					return v.toString(16);
				}).replace(/date/g, function(c) {
					return Date.parse(new Date());
				});
			},
			canvasDataURL(path, obj, callback) {
				var img = new Image();
				img.src = path;
				img.onload = function() {
					var that = this;
					// 默认按比例压缩
					var w = that.width,
						h = that.height,
						scale = w / h;
					w = obj.width || w;
					h = obj.height || (w / scale);
					var quality = 0.8; // 默认图片质量为0.8
					//生成canvas
					var canvas = document.createElement('canvas');
					var ctx = canvas.getContext('2d');
					// 创建属性节点
					var anw = document.createAttribute("width");
					anw.nodeValue = w;
					var anh = document.createAttribute("height");
					anh.nodeValue = h;
					canvas.setAttributeNode(anw);
					canvas.setAttributeNode(anh);
					ctx.drawImage(that, 0, 0, w, h);
					// 图像质量
					if (obj.quality && obj.quality <= 1 && obj.quality > 0) {
						quality = obj.quality;
					}
					// quality值越小,所绘制出的图像越模糊
					var base64 = canvas.toDataURL('image/jpeg', quality);
					// 回调函数返回base64的值
					callback(base64);
				}
			},
		}
	}
</script>

<style>
	.preview-full {
		position: fixed;
		top: 0;
		left: 0;
		bottom: 0;
		width: 100%;
		height: 100%;
		z-index: 1002;
	}

	.preview-full video {
		width: 100%;
		height: 100%;
		z-index: 1002;
	}

	.preview-full-close {
		position: fixed;
		right: 32rpx;
		top: 25rpx;
		width: 80rpx;
		height: 80rpx;
		line-height: 60rpx;
		text-align: center;
		z-index: 1003;
		/* 	background-color: #808080; */
		color: #fff;
		font-size: 65rpx;
		font-weight: bold;
		text-shadow: 1px 2px 5px rgb(0 0 0);
	}



	/* .preview-full-close-before,
	.preview-full-close-after {
		position: absolute;
		top: 50%;
		left: 50%;
		content: '';
		height: 60rpx;
		margin-top: -30rpx;
		width: 6rpx;
		margin-left: -3rpx;
		background-color: #FFFFFF;
		z-index: 20000;
	}

	.preview-full-close-before {
		transform: rotate(45deg);

	}

	.preview-full-close-after {
		transform: rotate(-45deg);

	} */

	.htz-image-upload-list {
		display: flex;
		flex-wrap: wrap;
	}

	.htz-image-upload-Item {
		width: 160rpx;
		height: 160rpx;
		margin: 13rpx;
		border-radius: 10rpx;
		position: relative;
	}

	.htz-image-upload-Item image {
		width: 100%;
		height: 100%;
		border-radius: 10rpx;
	}

	.htz-image-upload-Item-video {
		width: 100%;
		height: 100%;
		border-radius: 10rpx;
		position: relative;

	}

	.htz-image-upload-Item-video-fixed {
		position: absolute;
		top: 0;
		left: 0;
		bottom: 0;
		width: 100%;
		height: 100%;
		border-radius: 10rpx;
		z-index: 996;

	}

	.htz-image-upload-Item video {
		width: 100%;
		height: 100%;
		border-radius: 10rpx;

	}

	.htz-image-upload-Item-add {
		font-size: 105rpx;
		/* line-height: 160rpx; */
		text-align: center;
		border: 1px dashed #d9d9d9;
		color: #d9d9d9;
	}

	.htz-image-upload-Item-del {
		background-color: #f5222d;
		font-size: 24rpx;
		position: absolute;
		width: 35rpx;
		height: 35rpx;
		line-height: 35rpx;
		text-align: center;
		top: 0;
		right: 0;
		z-index: 2;
		color: #fff;
	}

	.htz-image-upload-Item-del-cover {
		background-color: #f5222d;
		font-size: 24rpx;
		position: absolute;
		width: 35rpx;
		height: 35rpx;
		text-align: center;
		top: 0;
		right: 0;
		color: #fff;
		/* #ifdef APP-PLUS */
		line-height: 25rpx;
		/* #endif */
		/* #ifndef APP-PLUS */
		line-height: 35rpx;
		/* #endif */
		z-index: 997;

	}

	.htz-image-upload-Item-video-app-poster {
		width: 100%;
		height: 100%;
	}
</style>

引用: 在 main.js

// #ifdef APP-PLUS
	// 上传组件
	Component('htzImageUpload', () => import('@/components/htz-image-upload/htz-image-upload.vue'));
// #endif

// #ifdef H5
	// 上传组件
	Vue.component('htzImageUpload', () => import('@/components/htz-image-upload/htz-image-upload.vue'));
// #endif

使用:

<!-- 上传 -->
<htz-image-upload :max="9" v-model="info.eventFileListAPP" :dataType='1' mediaType="all" @uploadSuccess="uploadSuccess" :action="action" />

<!-- 回显 -->
<htz-image-upload v-model="info.eventFileListAPP" :dataType='1' mediaType="all" :remove="false" :add="false" />


import configService from '@/services/config.service.js'

data() {
  return {
	action: configService.apiUrl + '/personnelInfo/uploadPersonnelImage', // 图片、视频上传地址
	// action: 'http://xxx.2x.xx.xx:10000/app/test/api/personnelInfo/uploadPersonnelImage', // 本地调试时
	info: {
		eventFileListAPP: []
	}
  }
},
methods: {
	uploadSuccess(res) { //上传成功
		let _res = JSON.parse(res.data);
		if (_res.code == 0) {
			let index = _res.data.lastIndexOf(".");
			let ext = _res.data.substr(index+1);
				this.info.eventFileListAPP.push({
					type: ext == 'mp4' ? 1 : 0,
					url: _res.data
				});
		}
	},
}



6. 地图

6.1 appkey

6.1.1 配置appkey_Android

key是在高德官网上自行注册:点击进入:高德官网
在这里插入图片描述
这样会生成 key 值。

6.1.2 打包配置

在打包之前,需要在manifest.json -> App模块配置 -> Maps(地图,只能选一个) -> 高德地图 -> 输入appkey_android(否则打包后,地图会报错),

6.2 使用

		<!-- 地图 -->
		<map style="width: 100%; height: 180px;" :latitude="examineList.lat" :longitude="examineList.lon"
			:markers="covers">
		</map>
		
covers: [{
	latitude: 33.301783,
	longitude: 120.220362,
	iconPath: require('@/static/safetyindex/myicon.png'),
	width: 20, //宽
	height: 35, //高
}],
examineList: {
	lat: 33.301783,
	lon: 120.220362,
},


6.3 层级

由于地图的层级较高,使用了下拉列表什么的,地图会在最上面,解决方法:

<u-form-item label="所属村" labelWidth="100" borderBottom>
	<u--input v-model="info.vest" disabled disabledColor="#ffffff" border="none"></u--input>
	<u-icon slot="right" name="arrow-right" @click="showVest = true;mapIsHidden = false;"></u-icon>
</u-form-item>

<view class="container">
	<map v-show="mapIsHidden" class="map" style="width: 200px; height: 100px;" :latitude="latitude" :longitude="longitude"
		:enable-satellite="true" :enable-traffic="true" :enable-building="true"scale="14":markers="covers"></map>
	<view v-show="!mapIsHidden" style="width: 200px; height: 100px;"></view>
</view>

<u-picker :show="showVest" :columns="columnsVest" @confirm="confirmVest" @cancel="showVest = false;mapIsHidden = true;"></u-picker>



data() {
    return {
        mapIsHidden: true,	// 地图层级过高,在下拉框上面,所以切换隐藏
        latitude: 33.259857,
        longitude: 120.269415,
        covers: [
            {
                latitude: 33.259857,
                longitude: 120.269415,
                iconPath: '../../../static/myicon.png',	// marker图标
                width: 20, // 宽
                height: 35, // 高
            }
        ],
        showVest: false,
		columnsVest: [['下拉一', '下拉二', '下拉三', '下拉四']],
    }
},
methods: {
	confirmTime(e) {
        this.info.times = e;
        this.showTime = false;
        setTimeout(() => {
            this.mapIsHidden = true;
        }, 200)
    }
}

6.4 使用Nvue平铺页面且居中

<template>
	<view>
		<!-- 地图 -->
		<view class="container" :style="{ height: screenHeight + 'px' }">
			<map class="map" :latitude="latitude" :longitude="longitude" :markers="covers" />
		</view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				screenHeight: null,
				latitude: 39.909,
				longitude: 116.39742,
				covers: [{
					latitude: 39.909,
					longitude: 116.39742,
					iconPath: '/static/images/Map/location.png'
				}, {
					latitude: 39.90,
					longitude: 116.39,
					iconPath: '/static/images/Map/location.png'
				}],
			};
		},
		onShow() {
			uni.getSystemInfo({
				success: res => {
					this.screenHeight = res.windowHeight;
				}
			});
		},
	}
</script>

<style lang="scss" scoped>

	.container {
		display: flex;
	
		.map {
			flex: 1;
		}
	}

</style>

7. 证书打包发布

7.1 证书

7.1.1 傻瓜式:

制作证书的网站,网上一大堆,https://www.dibaqu.com/utils/android-cert, 用户名和密码要记住,一个证书只能用在一个app上。

7.1.2 手动制作

前提电脑先安装jdk:点击查看如何安装jdk和配置环境变量
打开cmd -> 输入 keytool -genkey -alias testalias -keyalg RSA -keysize 2048 -validity 36500 -keystore test.keystore 然后看图操作
在这里插入图片描述

附上官网指南:点击

7.2 打包

点击HBuilder X上的【发行】-> 【原生App-云打包】-> 输入刚才制作的别名和密码,浏览刚才的证书。-> 安心打包 -> 【打包】

8. 使用echarts

8.1 定制

①、先定制 echarts 只选择用到的部分,减少体积。链接https://echarts.apache.org/zh/builder.html

②、将定制的echarts.js文件 放到static目录下

8.2 封装组件

在components目录下,新建 echartComp.vue 文件
参考插件市场:https://ext.dcloud.net.cn/plugin?id=1207

<template>
	<view :id="this.chartName" :prop="options" :change:prop="echarts.updateEcharts"
		:propName="chartName" :change:propName="echarts.updateEcharts"
	></view>
</template>

<script>

	export default {
		name: "echartComp",
		props: {
			chartOptions: {
				required: true,
				type: Object
			},
			nameId: {
				required: true,
				type: String
			}
		},
		data() {
			return {
				options: {},
				chartName: ''
			};
		},
		watch: {
			chartOptions: {
				handler(newVal) {
					this.options = newVal;
				},
				immediate: true
			},
			nameId: {
				handler(newVal) {
					this.chartName = newVal;
				},
				immediate: true
			}
		}
	}
</script>
<script module="echarts" lang="renderjs">
	export default {
		data() {
			return {
				chartInstance: null
			}
		},
		mounted() {
			const script = document.createElement('script')
			// view 层的页面运行在 www 根目录,其相对路径相对于 www 计算
			script.src = 'static/echarts.js'
			script.onload = this.initEcharts.bind(this)
			document.head.appendChild(script)
		},
		methods: {
			initEcharts() {
				this.chartInstance = echarts.init(document.getElementById(this.chartName))
				// 观测更新的数据在 view 层可以直接访问到
				this.chartInstance.setOption(this.options)
			},
			updateEcharts(newValue, oldValue, ownerInstance, instance) {
				// 监听 service 层数据变更
				// chartInstance.setOption(newValue) // Cannot read property 'setOption' of undefined at view
				this.chartInstance && this.chartInstance.setOption(newValue)
				// 也可以
				// let timer = setInterval(() =>{
				// 	if(mypieChart){
				// 		mypieChart.setOption(newValue);
				// 		clearInterval(timer)
				// 	}
				// },10)
			},
		}
	}
</script>

8.3 使用

8.3.1 标准echart图:

根据需求,传对应的echart配置即可

<echartComp style="width: 100%; height: 500upx;" :chartOptions="chartOptions" :nameId="'weiyi_Id'" />

import echartComp from '../../components/echartComp.vue'

components: { echartComp },
data() {
  return {
  	chartOptions: {
		series: [{
			name: 'Access From',
			type: 'pie',
			radius: [20, 80],
			center: ['50%', '50%'],
			roseType: 'area',
			itemStyle: {
				borderRadius: 5
			},
			data: [
				{ value: 30, name: 'rose 1' },
				{ value: 28, name: 'rose 2' },
				{ value: 26, name: 'rose 3' },
				{ value: 24, name: 'rose 4' },
				{ value: 22, name: 'rose 5' },
				{ value: 20, name: 'rose 6' },
				{ value: 18, name: 'rose 7' },
				{ value: 16, name: 'rose 8' }
			]
		}]
	},
  }
}

8.3.2 定制echart图:

在上面的基础上,手动引入

// 如果不加后续的new调用会出错(渐变色哪里)
import * as echarts from '../../static/echarts.js'

示例配置:
data() {
  return {
  	chartOptions: {
	grid: {
		top:'10%',
		left: '3%',
		right: '4%',
		bottom: '5%',
		containLabel: true
	},
	xAxis: {
		type: 'category',
		data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
		axisLabel: {
			interval: 0,
			rotate: 40,
			inside: false,
			textStyle: {
				color: '#000',
				fontSize: '8',
				itemSize: ''
			}
		}
	},
	yAxis: {
		type: 'value'
	},
	series: [{
		data: [120, 200, 150, 80, 70, 110, 130],
		barWidth: 20,
		type: 'bar',

		itemStyle: {
			normal: {
				// 上面的引用,是这里用的
				color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
					offset: 0,
					color: '#00CCFF'
				}, {
					offset: 1,
					color: '#4FACFE'
				}]),
			}
		},
	}]
},
  }
}

9.下拉列表菜单,可滚动(uView 1.0)

// 模板
<u-dropdown class="topNav-right" ref="oneDropdown">
	<u-dropdown-item :title="oneName.eventName" >
		<scroll-view scroll-y="true" style="height: 500upx;">
			<view class="topNav-right-item" v-for="(item,index) in oneOptions" :key="index" @click="oneDrop(item)">
				{{ item.eventName }}
			</view>
		</scroll-view>
	</u-dropdown-item>
</u-dropdown>

// js
data() {
  return {
	// 类型下拉框
	oneName: {},
	oneOptions: [],
  }
},
methods: {
	// 点击类型
	oneDrop(item) {
		this.oneName = item;
		this.$refs.oneDropdown.close();
		// this.getType(item.id)	// 如果有联动的话
	},

}

// 样式
.topNav-right {
	flex: 2.5;
	.topNav-right-item {
		z-index: 9999;
		height: 80upx;
		line-height: 80upx;
		text-align: center;
		border-bottom: 1upx solid rgba(220, 220, 220, 0.4);
		background-color: #FFFFFF;
	}
}
/deep/.u-dropdown__menu {
	z-index: 0;
}
/deep/.u-dropdown__menu__item {
	justify-content: flex-end;
}

10. 全局组件

main.js 中, H5和App是用法是不同的见如下示例,
(目前发现bug,也可能不是bug:组件名和文件名要一致,否则引入不成功。例如 ‘htzImageUpload’, 对应后面的文件名也是 ‘htz-image-upload.vue’, 还有在components的组件要用-分割,使用小驼峰也会不成功)

// #ifdef APP-PLUS
	Component('htzImageUpload', () => import('@/components/htz-image-upload/htz-image-upload.vue'));
// #endif

// #ifdef H5
	Vue.component('htzImageUpload', () => import('@/components/htz-image-upload/htz-image-upload.vue'));
// #endif

还有图片上传组件,多选组件,语音组件 等等

11. 语音转文字 组件

新建 voice.svg 文件,以便引入

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1561448511664"
    class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2055"
    xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
    <defs>
        <style type="text/css"></style>
    </defs>
    <path
        d="M515.041727 670.617766c91.584167 0 166.095647-74.504255 166.095647-166.095646V229.350568c0-91.584167-74.51148-166.095647-166.095647-166.095647-91.598617 0-166.102872 74.51148-166.102871 166.095647V504.52212c0 91.591392 74.504255 166.095647 166.102871 166.095646z"
        fill="#8a8a8a" p-id="2056"></path>
    <path
        d="M801.643237 874.001665H558.391759v-45.423608c161.507768-21.313766 286.60151-159.788217 286.60151-327.01819a43.350032 43.350032 0 0 0-86.700064 0c0 134.124998-109.12648 243.251478-243.258703 243.251479-134.124998 0-243.244253-109.12648-243.244253-243.251479a43.350032 43.350032 0 0 0-86.700063 0c0 167.229972 125.100967 305.711649 286.601509 327.01819v45.423608H228.440217a43.350032 43.350032 0 0 0 0 86.700064h573.20302a43.350032 43.350032 0 0 0 0-86.700064z"
        fill="#8a8a8a" p-id="2057"></path>
</svg>

新建 audio-input.vue 文件

<template>
	<view class="audioInput">
		<image src="@/static/images/voice.svg" mode="aspectFit" @click="startRecognize()" class="voice-icon"></image>
		<input type="text" class="reportContent" :placeholder='tipText' v-model="spokenWords" />
	</view>
</template>

<script>
	export default {
		name:"audio-input",
		props: {
			tipText: {
				default: '请输入操作内容',
				type: String
			}
		},
		data() {
			return {
				spokenWords: '',
				voiceOption: {
					engine: {
						type: String,
						default: 'baidu'	//语音引擎=>讯飞:iFly,百度:'baidu'
					},
					punctuation: false,
					timeout: 10 * 1000
				}
			};
		},
		methods: {
			startRecognize: function() {	//语音输入
				let _this = this;
				plus.speech.startRecognize(_this.voiceOption, function(s) {
					if(_this.spokenWords) {
						_this.spokenWords += s;
					} else {
						_this.spokenWords = s;
					}
					_this.$emit('spoken', _this.spokenWords);
				});
			},
		}
	}
</script>

<style lang="less" scoped>
	.audioInput {
		position: relative;
		height: 80upx;
		border: 1px solid gainsboro;
		.voice-icon {
			position: absolute;
			top: 10upx;
			left: 20upx;
			width: 60upx;
			height: 60upx;
			z-index: 10;
		}
		.reportContent {
			position: absolute;
			left: 0;
			right: 0;
			height: 80upx;
			padding-left: 90upx;
		}
	}
</style>

使用:

<audio-input @spoken="(SubmitText = $event)" :tipText="'请填写扣分描述'" />

配置
百度语音地址:https://console.bce.baidu.com
在这里插入图片描述

12. 更新升级

判断平台

uni.getSystemInfoSync().platform == 'ios'
uni.getSystemInfoSync().platform == 'android'

IOS

//在App Store Connect中的App Store下的app信息,可找到appleId
let appleId = 123456
plus.runtime.launchApplication({
	action: `itms-apps://itunes.apple.com/cn/app/id${appleId}?mt=8`
}, function(e) {
	// console.log('Open system default browser failed: ' + e.message);''
});

Android

view v-if='this.versionFlag==true && JSON.stringify(versionObject) !== "{}" && MobileTypeAndroid==true'>
	<version class="versionModel" :versionObject="versionObject"></version>
</view>

version组件代码

<template>
	<view class="mask flex-center">
		<view class="content botton-radius">
			<view class="content-top">
				<!-- 更新标题 -->
				<text class="content-top-text">{{this.versionObject.name}}</text>
				<image class="content-top" style="top: 0;" width="100%" height="100%" src="@/static/images/bg_top.png"></image>
			</view>
			<view class="content-header"></view>
			<view class="content-body">
				<view class="title">
					<text>{{subTitle}}</text>
				</view>
				<!-- 更新内容 -->
				<view class="body">
					<scroll-view class="box-des-scroll" scroll-y="true">
						<text class="box-des">
							{{this.versionObject.remark}}
						</text>
					</scroll-view>
				</view>
				<view class="footer flex-center">
					<template>
						<template v-if="!downloadSuccess">
							<view class="progress-box flex-column" v-if="downloading">
								<progress class="progress" border-radius="35" :percent="downLoadPercent"
									activeColor="#3DA7FF" show-info stroke-width="10" />
								<view style="width:100%;font-size: 28rpx;display: flex;justify-content: space-around;">
									<text>{{downLoadingText}}</text>
									<text>({{downloadedSize}}/{{packageFileSize}}M)</text>
								</view>
							</view>

							<button v-else class="content-button" style="border: none;color: #fff;" plain
								@click="downloadPackage">
								{{downLoadBtnText}}
							</button>
						</template>
						<button v-else-if="downloadSuccess && !installed" class="content-button"
							style="border: none;color: #fff;" plain :loading="installing" :disabled="installing"
							@click="installPackage">
							{{installing ? '正在安装……' : '下载完成,立即安装'}}
						</button>

						<button v-if="installed && isWGT" class="content-button" style="border: none;color: #fff;" plain
							@click="restart">
							安装完毕,点击重启
						</button>
					</template>
				</view>
			</view>
<!-- 			<image v-if="!is_mandatory" class="close-img" src="@/static/images/app_update_close.png"
				@click.stop="closeUpdate"></image> -->
		</view>
	</view>
</template>

<script>
	const localFilePathKey = '__localFilePath__'
	const platform_iOS = 'iOS';
	let downloadTask = null;
	

	/**
	 * 对比版本号,如需要,请自行修改判断规则
	 * 支持比对	("3.0.0.0.0.1.0.1", "3.0.0.0.0.1")	("3.0.0.1", "3.0")	("3.1.1", "3.1.1.1") 之类的
	 * @param {Object} v1
	 * @param {Object} v2
	 * v1 > v2 return 1
	 * v1 < v2 return -1
	 * v1 == v2 return 0
	 */
	function compare(v1 = '0', v2 = '0') {
		v1 = String(v1).split('.')
		v2 = String(v2).split('.')
		const minVersionLens = Math.min(v1.length, v2.length);

		let result = 0;
		for (let i = 0; i < minVersionLens; i++) {
			const curV1 = Number(v1[i])
			const curV2 = Number(v2[i])

			if (curV1 > curV2) {
				result = 1
				break;
			} else if(curV1 < curV2) {
				result = -1
				break;
			}
		}

		if (result === 0 && (v1.length !== v2.length)) {
			const v1BiggerThenv2 = v1.length > v2.length;
			const maxLensVersion = v1BiggerThenv2 ? v1 : v2;
			for (let i = minVersionLens; i < maxLensVersion.length; i++) {
				const curVersion = Number(maxLensVersion[i])
				if (curVersion > 0) {
					v1BiggerThenv2 ? result = 1 : result = -1
					break;
				}
			}
		}

		return result;
	}

	export default {
		props:{
			versionObject:[Object,Array]
		},
		data() {
			return {
				// 从之前下载安装
				installForBeforeFilePath: '',

				// 安装
				installed: false,
				installing: false,

				// 下载
				downloadSuccess: false,
				downloading: false,

				downLoadPercent: 0,
				downloadedSize: 0,
				packageFileSize: 0,

				tempFilePath: '', // 要安装的本地包地址

				// 默认安装包信息
				title: '更新日志',
				contents: '',
				is_mandatory: false,

				// 可自定义属性
				subTitle: '发现新版本',
				downLoadBtnTextiOS: '立即跳转更新',
				downLoadBtnText: '立即下载更新',
				downLoadingText: '安装包下载中,请稍后'
			}
		},
		onLoad({local_storage_key}) {
			if (!local_storage_key) {
				console.error('local_storage_key为空,请检查后重试')
				uni.navigateBack()
				return;
			};

			const localPackageInfo = uni.getStorageSync(local_storage_key);
			if (!localPackageInfo) {
				console.error('安装包信息为空,请检查后重试')
				uni.navigateBack()
				return;
			};

			const requiredKey = ['version', 'url', 'type']
			for (let key in localPackageInfo) {
				if (requiredKey.indexOf(key) !== -1 && !localPackageInfo[key]) {
					console.error(`参数 ${key} 必填,请检查后重试`)
					uni.navigateBack()
					return;
				}
			}

			Object.assign(this, localPackageInfo)
			this.checkLocalStoragePackage()
		},
		mounted() {
		},
		onBackPress() {
			// 强制更新不允许返回
			if (this.is_mandatory) {
				return true
			}

			downloadTask && downloadTask.abort()
		},
		computed: {
			isWGT() {
				return this.type === 'wgt'
			},
		},
		methods: {
			checkLocalStoragePackage() {
				// 如果已经有下载好的包,则直接提示安装
				const localFilePathRecord = uni.getStorageSync(localFilePathKey)
				if (localFilePathRecord) {
					const {
						version,
						savedFilePath,
						installed
					} = localFilePathRecord
					
					// 比对版本
					if (!installed && compare(version, this.version) === 0) {
						this.downloadSuccess = true;
						this.installForBeforeFilePath = savedFilePath;
						this.tempFilePath = savedFilePath
					} else {
						// 如果保存的包版本小 或 已安装过,则直接删除
						this.deleteSavedFile(savedFilePath)
					}
				}
			},
			async closeUpdate() {
				if (this.downloading) {
					if (this.is_mandatory) {
						return uni.showToast({
							title: '下载中,请稍后……',
							icon: 'none',
							duration: 500
						})
					}
					uni.showModal({
						title: '是否取消下载?',
						cancelText: '否',
						confirmText: '是',
						success: res => {
							if (res.confirm) {
								downloadTask && downloadTask.abort()
								uni.navigateBack()
							}
						}
					});
					return;
				}

				if (this.downloadSuccess && this.tempFilePath) {
					// 包已经下载完毕,稍后安装,将包保存在本地
					await this.saveFile(this.tempFilePath, this.version)
					uni.navigateBack()
					return;
				}

				uni.navigateBack()
			},
			downloadPackage() {
				this.downloading = true;
				//下载包
				downloadTask = uni.downloadFile({
					url: this.versionObject.path,
					success: res => {
						if (res.statusCode == 200) {
							this.downloadSuccess = true;
							this.tempFilePath = res.tempFilePath
							// 强制更新,直接安装
							if (this.is_mandatory) {
								this.installPackage();
							}
						}
					},
					complete: () => {
						this.downloading = false;
						this.downLoadPercent = 0
						this.downloadedSize = 0
						this.packageFileSize = 0
						downloadTask = null;
					}
				});
				downloadTask.onProgressUpdate(res => {
					this.downLoadPercent = res.progress;
					this.downloadedSize = (res.totalBytesWritten / Math.pow(1024, 2)).toFixed(2);
					this.packageFileSize = (res.totalBytesExpectedToWrite / Math.pow(1024, 2)).toFixed(2);
				});
			},
			installPackage() {
				// #ifdef APP-PLUS
				// wgt资源包安装
				if (this.isWGT) {
					this.installing = true;
				}

				plus.runtime.install(this.tempFilePath, {
					force: false
				}, async res => {
					this.installing = false;
					this.installed = true;

					// wgt包,安装后会提示 安装成功,是否重启
					if (this.isWGT) {
						// 强制更新安装完成重启
						if (this.is_mandatory) {
							uni.showLoading({
								icon: 'none',
								title: '安装成功,正在重启……'
							})

							setTimeout(() => {
								uni.hideLoading()
								this.restart();
							}, 1000)
						} else {
							uni.showLoading({
								icon: 'none',
								title: '安装成功,重启应用体验新版',
								duration: 1000
							})
						}
					} else {
						const localFilePathRecord = uni.getStorageSync(localFilePathKey)
						uni.setStorageSync(localFilePathKey, {
							...localFilePathRecord,
							installed: true
						})
					}
				}, async err => {
					// 如果是安装之前的包,安装失败后删除之前的包
					if (this.installForBeforeFilePath) {
						await this.deleteSavedFile(this.installForBeforeFilePath)
						this.installForBeforeFilePath = '';
					}

					// 安装失败需要重新下载安装包
					this.installing = false;
					this.installed = false;

					uni.showModal({
						title: `更新失败${this.isWGT ? '' : ',APK文件不存在'},请重新下载`,
						content: err.message,
						showCancel: false
					});
				});

				// 非wgt包,安装跳出覆盖安装,此处直接返回上一页
				if (!this.isWGT) {
					uni.navigateBack()
				}
				// #endif
			},
			restart() {
				this.installed = false;
				// #ifdef APP-PLUS
				//更新完重启app
				plus.runtime.restart();
				// #endif
			},
			async saveFile(tempFilePath, version) {
				const [err, res] = await uni.saveFile({
					tempFilePath
				})
				if (err) {
					return;
				}
				uni.setStorageSync(localFilePathKey, {
					version,
					savedFilePath: res.savedFilePath
				})
			},
			deleteSavedFile(filePath) {
				uni.removeStorageSync(localFilePathKey)
				return uni.removeSavedFile({
					filePath
				})
			},
			jumpToAppStore() {
				plus.runtime.openURL(this.url);
			}
		}
	}
</script>

<style>
	page {
		background: transparent;
	}

	.flex-center {
		/* #ifndef APP-NVUE */
		display: flex;
		/* #endif */
		justify-content: center;
		align-items: center;
	}

	.mask {
		position: fixed;
		left: 0;
		top: 0;
		right: 0;
		bottom: 0;
		background-color: rgba(0, 0, 0, .65);
	}

	.botton-radius {
		border-bottom-left-radius: 30rpx;
		border-bottom-right-radius: 30rpx;
	}

	.content {
		position: relative;
		top: 0;
		width: 600rpx;
		background-color: #fff;
		box-sizing: border-box;
		padding: 0 50rpx;
		font-family: Source Han Sans CN;
	}

	.text {
		/* #ifndef APP-NVUE */
		display: block;
		/* #endif */
		line-height: 200px;
		text-align: center;
		color: #FFFFFF;
	}

	.content-top {
		position: absolute;
		top: -195rpx;
		left: 0;
		width: 600rpx;
		height: 270rpx;
	}

	.content-top-text {
		font-size: 45rpx;
		font-weight: bold;
		color: #F8F8FA;
		position: absolute;
		top: 120rpx;
		left: 50rpx;
		z-index: 1;
	}

	.content-header {
		height: 70rpx;
	}

	.title {
		font-size: 33rpx;
		font-weight: bold;
		color: #3DA7FF;
		line-height: 38px;
	}

	.footer {
		height: 150rpx;
		display: flex;
		align-items: center;
		justify-content: space-around;
	}

	.box-des-scroll {
		box-sizing: border-box;
		padding: 0 40rpx;
		height: 200rpx;
		text-align: left;
	}

	.box-des {
		font-size: 26rpx;
		color: #000000;
		line-height: 50rpx;
	}

	.progress-box {
		width: 100%;
	}

	.progress {
		width: 90%;
		height: 40rpx;
		border-radius: 35px;
	}

	.close-img {
		width: 70rpx;
		height: 70rpx;
		z-index: 1000;
		position: absolute;
		bottom: -120rpx;
		left: calc(50% - 70rpx / 2);
	}

	.content-button {
		text-align: center;
		flex: 1;
		font-size: 30rpx;
		font-weight: 400;
		color: #FFFFFF;
		border-radius: 40rpx;
		margin: 0 18rpx;

		height: 80rpx;
		line-height: 80rpx;

		background: linear-gradient(to right, #1785ff, #3DA7FF);
	}

	.flex-column {
		display: flex;
		flex-direction: column;
		align-items: center;
	}
</style>

13. 视频

<video style="width:100%; margin-top: 55%; height:400upx;object-fit: fill " :src="videoLists" preload="auto"
				:autoplay="true" :custom-cache="false" :playStrategy="1" objectFit="fill" :http-cache="false" :muted="true"></video>





		onLoad(options) {
			let res = JSON.parse(options.data);
			this.videoname = res.name;
			this.videoLists = res.hlsUrl;
		},
		// 监听页面卸载函数
		onUnload() {
			// 将img的src重置为空
			this.videoLists = ''
		},

14. 隐私弹窗

在这里插入图片描述

在项目根目录下:新建 androidPrivacy.json

{
    "version" : "1",
    "prompt" : "template",
    "title" : "服务协议和隐私政策",
    "message" : "  请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/>  你可阅读<a href=\"http://xxx.com/yysm/\">《服务协议》</a>和<a href=\"http://xxx.com/yysm/\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。",
    "buttonAccept" : "同意并接受",
    "buttonRefuse" : "暂不同意",
    "second" : {
        "title" : "确认提示",
        "message" : "  进入应用前,你需先同意<a href=\"http://xxx.com/yysm/\">《服务协议》</a>和<a href=\"http://xxx.com/yysm/\">《隐私政策》</a>,否则将退出应用。",
        "buttonAccept" : "同意并继续",
        "buttonRefuse" : "退出应用"
    },
    "styles" : {
        "backgroundColor" : "#ffffff",
        "borderRadius" : "5px",
        "title" : {
            "color" : "#333333"
        },
        "buttonAccept" : {
            "color" : "#333333"
        },
        "buttonRefuse" : {
            "color" : "#9B9AB5"
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值