文章目录

  • 不会真的有前端不知道uniapp吧?!
  • 初试uniapp,爱了爱了
  • 前提
  • 涉及内容
  • 开发
  • 1、uniapp新建项目
  • 2、新建页面
  • 3、页面布局
  • 4、高德地图定位与pois获取
  • 5、uniapp中字体图标的使用
  • 6、微信小程分享
  • 完整页面代码
  • 项目代码获取


我终于终于用上了uniapp!!!

为什么发出这样的感叹呢?uniapp从诞生到现在已经有两年了吧,这两年间断断续续听到过uniapp的声音,但是一直没有兴趣去尝试下,总的原因有一下几点:

  • 1、开始阶段,相对于vscode,HbuilderX简直就是个弟弟,体验差,小bug多,手感不行!
  • 2、潜意识作怪,一个框架啥都想做估计啥都做好,然后就不想试;
  • 3、自己懒

不会真的有前端不知道uniapp吧?!

那么,什么是uniapp?

官网:uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、H5、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉/淘宝)、快应用等多个平台。

我:行行行,你说的都对,我们还是有突出一个“但是”;uniapp的愿景是好的,整体上来说基本符合官网的描述,但是,限制也是有的,有兴趣的小伙伴可以去官网(https://uniapp.dcloud.io/)看看,uniapp的很多api的对于不同的平台也是有兼容问题的。即使是这样,也不影响我们对uniapp的使用,因为他的确很方便,尤其是对于使用Vue的前端来说!

初试uniapp,爱了爱了

就像女朋友总是问我,你为什么喜欢我?这个题我回答不了女朋友,但是可以回答uniapp呀:

  • 1、我跟uniapp它爸Vue熟呀,这两就像一个模子(语法)刻出来的!
  • 2、作为前端,我累了,谁能实现最好的跨平台,我就爱了!
  • 3、看谁活的久点,uniapp的两个“金主”(H5、微信小程序)应该能够支撑它吧,这又是我认为其主要优势!

话不多说,先看看uniapp开发的微信小程序效果:

uiniapp如何在微信开发工具中运行小程序 uniapp开发微信小程序怎么样_高德地图

大家可以微信搜索“去哪吃喝玩乐呢”,搜索小程序试一试,也可以扫码:

uiniapp如何在微信开发工具中运行小程序 uniapp开发微信小程序怎么样_vue_02

前提

1、熟悉Vue.js开发,不会的同学可以过一遍官网(https://cn.vuejs.org/);

2、会在uniapp官网(https://uniapp.dcloud.io/)查找api和组件,也就是搜索能力;

(官网上说还要熟悉微信小程序开发,本人觉得没必要,直接开uniapp的api就可以了)

涉及内容

1、uniapp基本开发和调试;

2、uniapp开发微信小程序;

3、uniapp引用高德sdk定位、获取poi等;

4、uniapp使用字体图标;

5、微信小程序授权和分享;

开发

1、uniapp新建项目

打开HBuilderX,点击“文件”—“新建”—“项目”,根据自己的项目需求选择模板,此处我选择“uni-app项目”模板,最后点击”创建“,项目结构如图所示;

uiniapp如何在微信开发工具中运行小程序 uniapp开发微信小程序怎么样_uni-app_03


uiniapp如何在微信开发工具中运行小程序 uniapp开发微信小程序怎么样_微信小程序_04

2、新建页面

在pages(视图目录)下新建index/index.vue文件,用于页面开发;

在pages.json(视图配置)下设置微信小程序的全局样式,以及index.vue页面的路由和相关样式;

具体api可以查看uni-app官网;

pages.json:

{
	"pages": [{
		"path": "pages/index/index",
		"style": {
			"navigationBarTitleText": "去哪·吃喝玩购"
		}
	}],
	"globalStyle": {
		"navigationBarTextStyle": "black",
		"navigationBarTitleText": "去哪·吃喝玩购",
		"navigationBarBackgroundColor": "#abd8cf",
		"backgroundColor": "#FFFFFF"
	}
}
3、页面布局

开发之前,现将整体布局弄清楚,便于后面的组件化/模板化开发;(在此说明一点,由于个人偷懒和初试uniapp,因此并没有进行划分,都写在index.vue上,这样是不对的╮(╯▽╰)╭)

uiniapp如何在微信开发工具中运行小程序 uniapp开发微信小程序怎么样_高德地图_05


uiniapp如何在微信开发工具中运行小程序 uniapp开发微信小程序怎么样_iconfont_06

4、高德地图定位与pois获取

uniapp自带了地图定位的功能函数uni.getLocation(OBJECT),但是获取的位置信息不全且不适用,所以此处直接代用高德地图sdk进行定理信息相关操作。高德地图开发者的注册和sdk下载可以参考《uniapp 如何引入高德地图》文章https://www.jianshu.com/p/7935272776b1;

下载好高德地图的sdk(amap-wx.js)后将其放在目录components下;页面中引入:

import amap from '../../components/amap-wx.js'

微信小程序对于很多功能的调用都要求用户授权,地理位置也需要授权,下列代码进行授权和地理信息获取:

methods: {
			//授权获取地理位置信息
			getAuthorizeInfo() {
				uni.authorize({  //调用授权操作
					scope: 'scope.userLocation',
					success: () => { // 允许授权
						this.getLocationInfo();
					},
					fail: () => { // 拒绝授权
						this.openConfirm();
						console.log("你拒绝了授权,无法获得周边信息")
					}
				})
			},
			// 获取地理位置
			getLocationInfo() {
				this.amapPlugin.getRegeo({ //调用高德地图sdk进行位置信息获取
					success: (res) => { //获取地理位置信息成功
						console.log("lbs", res)
						//地址,用于显示
						this.address = res[0].regeocodeData.formatted_address;
						//经纬度,用户查询周边吃喝玩乐信息
						this.lat = res[0].latitude;
						this.lng = res[0].longitude;
					},
					fail: (error) => { //获取地理位置信息失败
						console.log(error)
						//弹窗提示获取信息失败
						uni.showToast({
							title: '无法获得周边信息' + error,
							icon: 'none',
							duration: 2000
						})
					}
				});
			},
			//获取周边poi数据
			getPoiInfoAround() {
				uni.showLoading({
					title: '获取数据中'
				});
				//按照参数形式拼接经纬度
				let loc = this.lat + "," + this.lng
				//调用高德sdk获取周围的pois信息
				this.amapPlugin.getPoiAround({
					"location": loc,
					"querytypes": this.searchPoiType, //查询的pois类型(吃喝玩乐)
					"querykeywords": this.searchPoiKw, //关键字
					success: (data) => { //获取成功信息存储
						//成功回调
						this.searchPois = data.poisData
						this.setShowPois()
						uni.hideLoading();
						console.log("poi", this.searchPois)
					},
					fail: (info) => {  //获取失败信息说明
						//失败回调
						console.log(info)
						uni.hideLoading();
						uni.showToast({
							title: '无法获得周边信息,您可以手动添加内容',
							icon: 'none',
							duration: 2000
						})
					}
				})
			},
			// 再次获取授权
			// 当用户第一次拒绝后再次请求授权
			openConfirm() {
				uni.showModal({
					title: '请求授权当前位置',
					content: '需要获取您的地理位置,请确认授权',
					success: (res) => {
						if (res.confirm) {
							uni.openSetting(); // 打开地图权限设置
						} else if (res.cancel) {
							uni.showToast({
								title: '你拒绝了授权,无法获得周边信息',
								icon: 'none',
								duration: 2000
							})
						}
					}
				});
			},
		}
5、uniapp中字体图标的使用

字体图标是现在用的比较多的一种图标形式,体积小,适用于小的图片/图标的显示,并且颜色和大小都是可设置的,和设置字体的方式相仿;现在用的比较多的是阿里巴巴矢量图标库,iconfont的具体下载和使用方式大家自行百度,此处不做具体说明。可参考如何在uni-app使用iconfont;

下载好iconfont文件后解压,放到static/fonticon目录下:

uiniapp如何在微信开发工具中运行小程序 uniapp开发微信小程序怎么样_高德地图_07

使用也比较简单(例如定位图标):

1、在main.js中引入iconfont图标:

import "./static/fonticon/iconfont.css"

2、在页面样式中使用:

<text class="iconfont icon-icon-test"></text>

**注意:**fonticon文件夹下的iconfont.js文件一定要删除,因为在微信小程序(uniapp)上线发布后iconfont的js文件会导致,线上发布版本运行报错且页面白屏;

6、微信小程分享

小程序中用户点击分享后,在 js 中定义 onShareAppMessage 处理函数(和 onLoad 等生命周期函数同级),设置该页面的分享信息;

onShareAppMessage(res) {
			if (res.from === 'button') { // 来自页面内分享按钮
				console.log(res.target)
			}
			return {
				title: '分享去哪·吃喝玩乐小程序',
				path: '/pages/index/index'
			}
		}

完整页面代码

至此,已经介绍了该小程序开发中的一些主要功能点和应该注重的地方,接下来将页面代码放上来,里面的注释还算比较多,供大家自行学习:

<template>
	<view class="container">
		<view class="location">
			<text class="iconfont icon-icon-test icon-style"></text>
			<text class="address-text">{{ showAddress }}</text>
		</view>
		<view class="theme">
			<view class="sub-theme" @click="getPois(1)">
				<view class="item-style" :style="activeTheme==1 ? activeThemeStyle:''">
					吃
				</view>
			</view>
			<view class="sub-theme" @click="getPois(2)">
				<view class="item-style" :style="activeTheme==2 ? activeThemeStyle:''">
					喝
				</view>
			</view>
			<view class="sub-theme" @click="getPois(3)">
				<view class="item-style" :style="activeTheme==3 ? activeThemeStyle:''">
					玩
				</view>
			</view>
			<view class="sub-theme" @click="getPois(4)">
				<view class="item-style" :style="activeTheme==4 ? activeThemeStyle:''">
					购
				</view>
			</view>
		</view>
		<view class="pois-show">
			<scroll-view :scroll-top="scrollTop" scroll-y="true" class="scroll-y">
				<view class="item" v-for="poi in showPois" :key="poi.id">
					<text class="name-style">{{ poi.name }} </text>
					<text class="iconfont icon-icon_huabanfuben del-style" @click="delPoi(poi.id)"></text>
				</view>
			</scroll-view>
			<view class="limit-pois">
				<view class="limit-item" :style="activeLimit==5 ? activeLimitStyle:''" @click="changeShowPois(5)">
					5
				</view>
				<view class="limit-item" :style="activeLimit==10 ? activeLimitStyle:''" @click="changeShowPois(10)">
					10
				</view>
				<view class="limit-item" :style="activeLimit==15 ? activeLimitStyle:''" @click="changeShowPois(15)">
					15
				</view>
				<view class="limit-item" :style="activeLimit==20 ? activeLimitStyle:''" @click="changeShowPois(20)">
					20
				</view>
				<view class="limit-item" :style="activeLimit==0 ? activeLimitStyle:''" @click="changeShowPois(0)">
					清除
				</view>
			</view>
			<view class="user-defined">
				<input class="uni-input" @input="onKeyInput" :value="inputValue" placeholder="自定义添加" />
				<text class="iconfont icon-xinzeng add-style" @click="addItem"></text>
			</view>
		</view>
		<view class="touch-decision">
			<view class="tip-text" :animation="animationData">
				<view class="text-left">不知如何选择?</view>
				<view :animation="animationData" class="iconfont icon-choujiang zhanpan" @click="selectPoi">
				</view>
				<view class="text-right">点击我帮你决定!</view>
			</view>
		</view>
		<view v-if="isShowRandom" class="zp-mask"></view>
		<view v-if="isShowRandom" class="zp-style">
			{{ showResult }}
		</view>
		<view v-if="isShowRandom" class="iconfont icon-guanbi zp-close" @click="closeMask"></view>
	</view>
</template>

<script>
	import amap from '../../components/amap-wx.js'
	export default {
		data() {
			return {
				amapPlugin: null, //new地图
				key: '···', //对应wx小程序的高德地图key
				address: '', //详细地址
				lat: '', //纬度
				lng: '', //经度
				searchPoiType: "", //根据类型搜索poiß
				searchPoiKw: "", //根据关键词搜索poi
				searchPois: [], //存储获取的poi数据
				showPois: [], //用于显示的poi
				activeTheme: 1, //默认激活第一个主题
				poiType: {
					1: "050100|050200|050300|050400|050800|050900", //吃相关
					2: "050500|050600|050700", //喝相关
					3: "080100|080300|080400|080500|080600", //休闲娱乐
					4: "060100|060200|060400|060900|061000|061400" //购物
				},
				activeLimit: 10,
				addItemValue: {},
				inputValue: "",
				animationData: {},
				prizeIndex: 0, // 下一次操作的奖品,预先准备好
				isShowRandom: false,
				showResult: ""
			}
		},
		computed: {
			activeThemeStyle() {
				return "background-color:#abd8cf;color:#d2000f"
			},
			activeLimitStyle() {
				console.log(this.activeLimit)
				return "background-color:#d2000f;color:#abd8cf"
			},
			showAddress() {
				let index = this.address.indexOf("市")
				return this.address.substring(index + 1)
			}
		},
		onReady() {
			console.log("index onReady")
			//new实例化高德sdk
			this.amapPlugin = new amap.AMapWX({
				key: this.key
			});
			//默认初始化poi搜索的类型为吃
			this.searchPoiType = this.poiType[this.activeTheme]
			this.getAuthorizeInfo();

			console.log("index onshow")
			//初始化动画
			this.animation = uni.createAnimation({
				timingFunction: "ease-in-out",
			})
			//使用动画循环
			setInterval(() => {
				this.animation.scale(1.07 + Math.random() * 0.01).step({
					duration: 1000
				})
				this.animation.scale(1).step({
					duration: 1000
				})
				this.animationData = this.animation.export()
			}, 2500)
		},
		onShareAppMessage(res) {
			if (res.from === 'button') { // 来自页面内分享按钮
				console.log(res.target)
			}
			return {
				title: '分享去哪·吃喝玩乐小程序',
				path: '/pages/index/index'
			}
		},
		watch: {
			//当经纬度变化时重新获取poi
			lat() {
				this.getPoiInfoAround()
			}
		},
		methods: {
			//授权获取地理位置信息
			getAuthorizeInfo() {
				uni.authorize({
					scope: 'scope.userLocation',
					success: () => { // 允许授权
						this.getLocationInfo();
					},
					fail: () => { // 拒绝授权
						this.openConfirm();
						console.log("你拒绝了授权,无法获得周边信息")
					}
				})
			},
			// 获取地理位置
			getLocationInfo() {
				this.amapPlugin.getRegeo({
					success: (res) => {
						console.log("lbs", res)
						this.address = res[0].regeocodeData.formatted_address;
						this.lat = res[0].latitude;
						this.lng = res[0].longitude;
					},
					fail: (error) => {
						console.log(error)
						uni.showToast({
							title: '无法获得周边信息' + error,
							icon: 'none',
							duration: 2000
						})
					}
				});
			},
			//获取周边poi数据
			getPoiInfoAround() {
				uni.showLoading({
					title: '获取数据中'
				});
				let loc = this.lat + "," + this.lng
				this.amapPlugin.getPoiAround({
					"location": loc,
					"querytypes": this.searchPoiType,
					"querykeywords": this.searchPoiKw,
					success: (data) => {
						//成功回调
						this.searchPois = data.poisData
						this.setShowPois()
						uni.hideLoading();
						console.log("poi", this.searchPois)
					},
					fail: (info) => {
						//失败回调
						console.log(info)
						uni.hideLoading();
						uni.showToast({
							title: '无法获得周边信息,您可以手动添加内容',
							icon: 'none',
							duration: 2000
						})
					}
				})
			},
			// 再次获取授权
			// 当用户第一次拒绝后再次请求授权
			openConfirm() {
				uni.showModal({
					title: '请求授权当前位置',
					content: '需要获取您的地理位置,请确认授权',
					success: (res) => {
						if (res.confirm) {
							uni.openSetting(); // 打开地图权限设置
						} else if (res.cancel) {
							uni.showToast({
								title: '你拒绝了授权,无法获得周边信息',
								icon: 'none',
								duration: 2000
							})
						}
					}
				});
			},
			getPois(index) {
				this.activeTheme = index
				this.searchPoiType = this.poiType[this.activeTheme]
				uni.showLoading({
					title: '加载中'
				});
				setTimeout(function() {
					uni.hideLoading();
				}, 100);
				this.getPoiInfoAround()
			},
			delPoi(id) {
				console.log("删除poi", id)
				let temp = this.showPois
				for (let index = 0; index < temp.length; index++) {
					let poi = temp[index]
					if (poi.id == id) {
						this.showPois.splice(index, 1)
					}
				}
			},
			setShowPois() {
				this.showPois = this.searchPois.slice(0, this.activeLimit)
			},
			changeShowPois(index) {
				uni.showLoading({
					title: '加载中'
				});
				setTimeout(function() {
					uni.hideLoading();
				}, 100);
				this.activeLimit = index;
				this.setShowPois()
			},
			onKeyInput(e) {
				let ip = e.target.value
				this.inputValue = ip
				if (ip) {
					this.addItemValue["name"] = ip
					this.addItemValue["id"] = new Date().valueOf().toString()
				} else {
					this.addItemValue = {}
				}
			},
			addItem() {
				if (Object.keys(this.addItemValue).length == 0) {
					uni.showToast({
						title: '请先输入自定义内容',
						icon: 'none',
						duration: 1000
					})
				} else {
					this.showPois.push(this.addItemValue)
					uni.showLoading({
						title: '添加中'
					});
					setTimeout(function() {
						uni.hideLoading();
					}, 100);
					this.addItemValue = {}
					this.inputValue = ""
				}
			},
			selectPoi() {
				this.isShowRandom = true
				let range = this.showPois.length
				this.prizeIndex = Math.floor(Math.random() * range);

				let index = 0
				let sit = setInterval(() => {
					if (index >= this.showPois.length) {
						index = 0
					}
					this.showResult = this.showPois[index]["name"]
					console.log(this.showResult)
					index += 1
				}, 100)
				setTimeout(() => {
					clearInterval(sit)
					this.showResult = this.showPois[this.prizeIndex]["name"]
				}, 3500)
			},
			closeMask() {
				this.isShowRandom = false

			}
		}
	}
</script>

<style lang="less">
	.container {
		height: 100vh;
		width: 100vw;
		background-image: url('data:image/png;base64,···'); //小程序对于图片大小有限制,此处背景图转换为为base64
		background-repeat: no-repeat;
		background-size: 100% 100%;
		display: flex;
		flex-direction: column;
		justify-content: flex-start;
		align-items: center;
		opacity: 0.9;

		.location {
			height: 60upx;
			width: 100%;
			margin-top: 7%;
			display: flex;
			flex-direction: row;
			justify-content: center;
			align-items: center;
			font-size: 28upx;
			color: #ffffff;

			.icon-style {
				color: #ffffff;
				font-size: 30upx;
				font-weight: bolder;
				margin-right: 5upx;
			}

			.address-text {
				width: 60%;
				overflow: hidden;
				text-overflow: ellipsis;
				white-space: nowrap; //文本不换行,这样超出一行的部分被截取,显示...
			}
		}

		.theme {
			height: 110upx;
			width: 100%;
			margin-top: 30upx;
			display: flex;
			flex-direction: row;
			justify-content: center;

			.sub-theme {
				width: 100upx;
				height: 100upx;
				border-radius: 50%;
				border: 5upx solid #d2000f;
				padding: 4upx;
				margin: 0 15upx;
				box-sizing: border-box;

				.item-style {
					border-radius: 50%;
					background-color: #d2000f;
					width: 100%;
					height: 100%;
					font-size: 45upx;
					color: #ffffff;
					align-items: center;
					margin: 0 auto;
					display: flex;
					justify-content: center;
				}
			}
		}

		.pois-show {
			height: 700upx;
			width: 80%;
			background-image: url(/static/images/pois_bg.png);
			background-size: 100% 100%;
			margin-top: 30upx;
			padding: 20upx 30upx;

			.scroll-y {
				height: 75%;
				width: 100%;
				margin-top: 28upx;
				padding: 5upx 0 5upx 0;
				border-bottom: 1upx solid #d2000f;
				border-top: 1upx solid #d2000f;

				.item {
					height: 50upx;
					line-height: 50upx;
					margin: 2upx 30upx;
					border: 1upx solid #d2000f;
					border-radius: 30upx;
					font-size: 25upx;
					text-align: center;
					display: flex;
					flex-direction: row;
					justify-content: center;
					align-items: center;
					color: #d2000f;
					background-color: #abd8cf;
					opacity: 0.8;

					.name-style {
						width: 90%;
						overflow: hidden;
						text-overflow: ellipsis;
						white-space: nowrap;
					}

					.del-style {
						margin-right: 10upx;
						font-size: 30upx;
						font-weight: bolder;
					}
				}
			}

			.limit-pois {
				display: flex;
				flex-direction: row;
				justify-content: center;
				width: 100%;

				.limit-item {
					margin: 10upx;
					height: 45upx;
					line-height: 45upx;
					width: 15%;
					border-radius: 20upx;
					border: 1upx solid #d2000f;
					background-color: #abd8cf;
					opacity: 0.9;
					color: #d2000f;
					text-align: center;
					font-size: 25upx;
				}
			}

			.user-defined {
				display: flex;
				flex-direction: row;
				justify-content: center;
				margin-top: 10upx;

				.uni-input {
					height: 50upx;
					border: 5upx solid #abd8cf;
					border-radius: 20upx;
					color: #ffffff;
					margin-right: 10upx;
					font-size: 30upx;
					background-color: #d2000f;
					padding: 0upx 15upx;
					overflow: hidden;
					text-overflow: ellipsis;
					word-wrap: nowrap;
				}

				.add-style {
					width: 10%;
					line-height: 50upx;
					text-align: center;
					font-size: 35upx;
					font-weight: bolder;
					color: #fff;
					border: 5upx solid #abd8cf;
					border-radius: 5upx;
					background-color: #d2000f;

				}
			}
		}

		.touch-decision {
			margin-top: 10upx;
			display: flex;
			flex-direction: column;
			justify-content: center;
			align-items: center;

			.tip-text {
				width: 100%;
				display: flex;
				flex-direction: row;
				justify-content: space-around;
				align-items: center;
				margin-top: 5upx;

				.text-left {
					color: #fffe0e;
					height: 50upx;
					line-height: 50upx;
					font-size: 30upx;
					font-weight: bolder;
					border: 5upx solid #ffffff;
					background-color: #d2000f;
					margin-right: -5upx;
					border-right: none;
					padding-right: 10upx;
				}

				.text-right {
					color: #fffe0e;
					height: 50upx;
					line-height: 50upx;
					font-size: 30upx;
					font-weight: bolder;
					border: 5upx solid #ffffff;
					background-color: #d2000f;
					margin-left: -5upx;
					border-left: none;
					padding-left: 10upx;
				}

				.zhanpan {
					color: #abd8cf;
					font-size: 90upx;
					line-height: 90upx;
					border: 5upx solid #ffffff;
					border-radius: 50%;
					width: 90upx;
					height: 90upx;
				}
			}


		}

		.zp-mask {
			position: fixed;
			height: 100%;
			width: 100%;
			background-color: rgba(255, 255, 255, 0.6);
			z-index: 10;
		}

		.zp-style {
			display: fixed;
			width: 600upx;
			height: 60upx;
			background-color: #d2000f;
			margin-top: -100%;
			z-index: 20;
			opacity: 0.8;
			font-size: 40upx;
			color: #ffffff;
			border-radius: 40upx;
			text-align: center;
			padding-top: 10upx;
		}

		.zp-close {
			display: fixed;
			width: 100upx;
			height: 100upx;
			color: #d2000f;
			margin-top: 15%;
			z-index: 25;
			opacity: 0.8;
			font-size: 80upx;
			text-align: center;
		}
	}
</style>