文章目录
- 不会真的有前端不知道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开发的微信小程序效果:
大家可以微信搜索“去哪吃喝玩乐呢”,搜索小程序试一试,也可以扫码:
前提
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项目”模板,最后点击”创建“,项目结构如图所示;
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上,这样是不对的╮(╯▽╰)╭)
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目录下:
使用也比较简单(例如定位图标):
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>