微信小程序三 之 "小团点评吧"
1.学前准备
- 工具
- 微信开发者工具
- 微信小程序账号
- 前置知识
- html、css基础知识
- js基础知识
2.学习主题
- 小程序云存储
- 小程序云函数
- mpvue构建项目
- 定位功能实现
- 城市列表选择实现
- 商铺列表实现
- 商铺详细页面
- 上拉加载更多
2.1 云开发
-
云存储
-
数据库curd
-
增加
db.collection('news').add({ data:{ title: this.data.title, content: this.data.content } })
-
修改
db.collection('news').doc("0ec685215e369f22094d528257476fcb").update({ data:{content: "修改的内容"} }).then(res=>{ console.log(res); })
-
删除
db.collection("news").doc("0ec685215e369f22094d528257476fcb").remove().then(res=>{ console.log(res); })
-
查询
db.collection("news").get().then(res=>{ console.log(res); })
-
-
-
云函数
- 一个云函数的写法与一个在本地定义的 JavaScript 方法无异,代码运行在云端 Node.js 中
2.2 mpvue构建项目
-
mpvue简介
-
mpvue的使用
# 全局安装 vue-cli $ npm install --global vue-cli # 创建一个基于 mpvue-quickstart 模板的新项目 $ vue init mpvue/mpvue-quickstart my-project # 安装依赖 $ cd my-project $ npm install # 启动构建 $ npm run dev
2.3 实现定位及城市选择功能
-
点击位置跳转城市页面;
wx.navigateTo({ url:"/pages/city/main" })
-
实现定位功能;
-
获取定位信息
wx.getLocation({ type: "wgs84", success(res) { console.log(res); } })
-
app.json配置permission字段
"pages": ["pages/index/index"], "permission": { "scope.userLocation": { "desc": "你的位置信息将用于小程序位置接口的效果展示" // 高速公路行驶持续后台定位 } }
-
-
将经纬度转换成地址;
-
申请地图申请开发者密钥(key)
-
下载微信小程序JavaScriptSDK 微信小程序JavaScriptSDK v1.2
-
引入qqmap-wx-jssdk
- 解决mpvue不支持 commonjs规范
- 修改
.babellrc
文件,plugins添加"transform-es2015-modules-commonjs"
"plugins": ["transform-runtime","transform-es2015-modules-commonjs"]
-
逆地址解析reverseGeocoder
qqmapsdk.reverseGeocoder({ location: { latitude: res.latitude, longitude: res.longitude }, success: res => { console.log(res); } })
-
渲染城市选择页面
-
引入城市数据cityData.js
-
循环数据
<div class="letter" v-for="(item,key) in cityData" :key="key"> </div>
-
点击字母定位到对应的城市位置;
-
通过scroll-view来实现;
-
scroll-into-view:值应为某子元素id(id不能以数字开头)
-
-
点击选中城市
-
通过全局变量来带参数
let app = getApp(); app.globalData.address = "北京"
-
返还上层目录
wx.navigateBack({ delta: 1 })
-
主页显示
onShow(){ if( typeof app.globalData.address !== 'undefined'){ this.loca = app.globalData.address; } },
-
-
-
-
2.4 实现列表及详细页面
-
自定义导航
"navigationStyle":"custom"
页面配置 -
云储存数据
-
初始化云开发
-
在
src/main.js
文件添加以下语句wx.cloud.init({ traceUser: true })
-
-
添加数据
- 导入图片资源
- 添加列表数据
- 处理json文件
- 导入json文件
-
-
获取云列表数据
-
初始化数据库
const db = wx.cloud.database()
-
查询数据库
db.collection('myremark').get().then(res=>{ console.log(res); })
-
-
获取详细内容
-
实现列表上拉加载更多
onReachBottom(){}
3.总结及代码
- mpvue构建项目
- 定位功能实现
- 城市列表选择实现
- 商铺列表实现
- 商铺详细页面
- 上拉加载更多
- main.js
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
App.mpType = 'app'
wx.cloud.init();
const app = new Vue(App)
app.$mount()
- app.json
{
"pages": [
"pages/home/main",
"pages/user/main",
"pages/city/main",
"pages/detail/main",
"pages/collection/main"
],
"permission": {
"scope.userLocation":{
"desc":"你的位置信息将用于小程序位置接口的效果展示"
}
},
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#181818",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle": "white"
}
}
3.1 home文件夹
3.1.1 home \ main.js
import Vue from 'vue'
import App from './index'
// add this to handle exception
Vue.config.errorHandler = function (err) {
if (console && console.error) {
console.error(err)
}
}
const app = new Vue(App)
app.$mount()
3.1.2 home \ main.json
{
"usingComponents": {},
"navigationBarTitleText": "小团点评吧"
}
3.1.3 home \ index.vue
<template>
<div class="home">
<div class="usertop">
<div class="usert-left">
<span @click="goToCity">{{address}}</span>
<img class="usertopl-avatar" src="/static/images/down.png" />
</div>
<div class="usert-right">
<img @click="goToPersonal" class="usertopr-avatar" src="/static/images/people.png" />
</div>
</div>
<div class="userinfo">
<div>
<img class="userinfo-avatar" src="/static/images/eat.png" background-size="cover" />
<div class="userinfo-nickname">美食</div>
</div>
<div>
<img class="userinfo-avatar" src="/static/images/ktv.png" background-size="cover" />
<div class="userinfo-nickname">ktv</div>
</div>
<div>
<img class="userinfo-avatar" src="/static/images/foot.png" background-size="cover" />
<div class="userinfo-nickname">足疗/按摩</div>
</div>
<div>
<img class="userinfo-avatar" src="/static/images/hotel.png" background-size="cover" />
<div class="userinfo-nickname">酒店</div>
</div>
<div>
<img class="userinfo-avatar" src="/static/images/improve.png" background-size="cover" />
<div class="userinfo-nickname">丽人/美发</div>
</div>
</div>
<div class="usermotto">
<div class="homelike">猜你喜欢</div>
<div class="user-motto" @click="goDetail(v)" v-for="(v,k) in myLikeData" :key="k">
<div class="mottoimg">
<img class="mottoimg-avatar" v-if="v.image_path" :src="v.image_path" alt />
</div>
<div class="mottoright">
<div class="mrtop">{{v.title}}</div>
<div class="mrstar">
<star v-if="v.score" :Grade="v.score"></star>
<div class="pprice">¥{{v.price}}/人</div>
</div>
<div class="mrsite">{{v.specific}}</div>
<div class="mreval">
<div v-for="(val,key) in v.label" :key="key">{{val}}</div>
</div>
</div>
</div>
<div class="load"> 正在加载中。。。 </div>
</div>
</div>
</template>
<script>
import star from "../star.vue"
// 引入SDK核心类
var QQMapWX = require('../../utils/qqmap-wx-jssdk.js');
// 实例化API核心类
var qqmapsdk = new QQMapWX({
key: 'KTPBZ-QIBCX-BW54M-Z4JG3-YA7WZ-PLFC3' // 必填
});
let app = getApp();
console.log(app);
// 连接云数据库
const db = wx.cloud.database();
export default {
data () {
return {
address:"加载中... ",
myLikeData: [],
showState: true,
finish: false,
i:0
}
},
components: {
star
},
methods: {
goToCity () {
wx.navigateTo({
url: "/pages/city/main?address="+this.address
});
},
goDetail(val) {
wx.navigateTo({
url: "/pages/detail/main?id=" + val._id
});
},
goToPersonal(){
wx.navigateTo({
// url:"/pages/personal/main"
url:"/pages/user/main"
});
}
},
onLoad() {
console.log("啦啦啦,德玛西亚")
// 1. 定位地址
wx.getLocation({
type: 'wgs84',
success: (res)=>{
console.log(res);// 获取定位信息 获取到经纬度
// 逆地址解析reverseGeocoder
qqmapsdk.reverseGeocoder({
location: {
latitude: res.latitude,
longitude: res.longitude
},
success:r=>{
console.log(r.result.address_component.city);
this.address = r.result.address_component.city
}
})
}
});
// 2.获取数据 -- 猜你喜欢part
db.collection("newList").limit(10).get().then(res=>{
// console.log("???",res);
// console.log(res.data[1]);
// console.log(res.data[1].label);// 环境优雅, 服务热情, 音响效果好
// console.log(typeof res.data[1].label);//string
res.data.forEach(e => {
return (e.label = e.label.split(","));
});
console.log(res.data[1].label);// ["环境优雅", "服务热情", "音响效果好"]
res.data.forEach(e => {
return (e.image_path ="cloud://gym-ayqef.6779-gym-ayqef-1302385623/" + e.image_path);
});
this.myLikeData = res.data;
})
},
// 上拉加载功能
onReachBottom: function () {
this.i = this.i + 1
db.collection("newList").skip(this.i * 10).limit(10).get().then(res => {
res.data.forEach(e => {
return (e.label = e.label.split(","));
});
res.data.forEach(e => {
return (e.image_path = "cloud://gym-ayqef.6779-gym-ayqef-1302385623/" + e.image_path);
});
console.log(this.myLikeData);
this.myLikeData.push(...res.data)//...展开运算符
console.log(this.myLikeData);
});
},
onShow(){
if(typeof app.globalData.c != "undefined"){
this.address = app.globalData.c
}
},
}
</script>
<style scoped>
...
</style>
3.2 city文件夹
3.2.1 city \ index.vue
<template>
<div class="city_container">
<!-- 点击右边栏索引字母定位到对应的城市位置 -->
<scroll-view class="scroll-view" scroll-y :scroll-into-view="letter">
<div class="right-letter">
<span>热门</span>
<span v-for="(v,k) in letters" :key="k" @click="scrollIntoView(v)">{{v}}</span>
</div>
<div class="location">
<span class="choice-city">{{address}}</span>
<span class="gps">GPS定位</span>
</div>
<!-- 热门城市 -->
<div class="hot-city">
<div class="city-title">热门城市</div>
<div class="hot-city-container">
<div v-for="(v,k) in hotcity" :key="k" @click="goHotCity(v)">{{v}}</div>
</div>
</div>
<div v-for="(v,k) in cityData" :key="k" :id="v.letter">
<div class="letter">{{v.letter}}</div>
<ul class="city-list" v-for="(city,key) in v.citys" :key="key">
<li @click="showCity(city)">{{city}}</li>
</ul>
</div>
</scroll-view>
</div>
</template>
<script>
// import cityData from "../../utils/cityData"
let cityData = require("../../utils/cityData.js");
console.log(cityData);
// 通过全局变量来带参数
let app = getApp();
export default {
data () {
return {
address:"",
cityData,
letters:[
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
],
hotcity:[
"上海",
"北京",
"广东",
"深圳",
"天津",
"杭州",
"南京",
"苏州",
"成都",
"武汉",
"重庆",
"西安"
],
letter:""
}
},
onLoad(options){
console.log(options);
this.address = options.address;
},
components: {
},
methods: {
scrollIntoView (letter) {
console.log(letter);
this.letter = letter;
},
showCity(city){
// console.log(city);
// 通过全局变量来带参数
this.address = city;
app.globalData.c = city;
// 置空
this.letter = "";
// 返还上层目录
wx.navigateBack({
delta:1
})
},
goHotCity(city){
this.address = city;
app.globalData.c = city;
this.letter = "";
wx.navigateBack({
delta:1
})
}
},
created () {
// let app = getApp()
}
}
</script>
<style scoped>
...
</style>
3.3 detail文件夹
3.3.1 detail \ index.vue
<template>
<div class="detail">
<div class="detail-top">
<!-- 商户基本信息 -->
<div class="user-motto">
<div class="mottoimg">
<img class="mottoimg-avatar" :src="imageurl" alt />
</div>
<div class="mottoright">
<div class="mrtop">{{obj.title}}</div>
<div class="mrstar">
<star v-if="obj.score" :Grade="obj.score"></star>
<div class="zbranch">{{obj.cover_id}}条</div>
<div class="pprice">¥{{obj.category_id}}/人</div>
</div>
<div class="mrsite">口味:{{obj.score}} 环境:{{obj.environment}} 服务:{{obj.service}}</div>
<div class="mreval">
<div>{{obj.classify}}</div>
</div>
</div>
</div>
<!-- 自身条件 -->
<div class="personal-bottom">
<div>
<div class="personal-icon">
<img src="/static/images/clock.png" alt="" class="moreico">
</div>
<div class="detailinfor">{{obj.shopHours}}</div>
</div>
<div>
<div class="personal-icon">
<img src="/static/images/wifi.png" alt="" class="moreico">
</div>
<div class="detailinfor">WiFi</div>
</div>
<div>
<div class="personal-icon">
<img src="/static/images/car.png" alt="" class="moreico">
</div>
<div class="detailinfor">停车场</div>
</div>
<div class="demore-icon">
<img src="/static/images/more.png" alt="" class="moreico">
</div>
</div>
<!-- 地址及电话 -->
<div class="address">
<div class="ad-icon">
<img src="/static/images/position.png" alt="">
</div>
<div class="de-address">
{{obj.specific}}
</div>
<div class="ad-phone">
<img src="/static/images/phone.png" alt="">
</div>
</div>
</div>
<!-- 热榜 -->
<div class="hotlist">
<div class="hotleft">
<div class="hot-icon">
<img src="/static/images/rank.png" alt="">
</div>
<div class="hottext">
{{obj.place}}
</div>
</div>
<div class="more-icon">
<img src="/static/images/more.png" alt="" class="moreico">
</div>
</div>
<!-- 团购 -->
<div class="group-buy">
<div class="group-icon">
<img src="/static/images/group-buy.png" alt="">
</div>
<div class="group-detail">
<div class="grouplist">
<div class="groupimg">
<img class="groupimage" :src="imageurl" alt />
</div>
<div class="groupcenter">
<div class="gct">单人商务套餐,提供免费WIFI</div>
<div class="gcb">
<span class="gcbleft">¥{{obj.price}}</span>
<span class="gcbright">¥{{obj.id}}</span>
</div>
</div>
<div class="groupright">已售{{obj.time}}</div>
</div>
<div class="grouplist">
<div class="groupimg">
<img class="groupimage" :src="imageurl" alt />
</div>
<div class="groupcenter">
<div class="gct">单人商务套餐,提供免费WIFI</div>
<div class="gcb">
<span class="gcbleft">¥{{obj.price}}</span>
<span class="gcbright">¥{{obj.id}}</span>
</div>
</div>
<div class="groupright">已售{{obj.time}}</div>
</div>
</div>
</div>
<div class="look">查看其他4个团购﹀</div>
<div class="jumpouter">
<div class="jump">
<div class="collect" @click="collected">
<img src="/static/images/collect.png" v-if="!collectState" />
<img src="/static/images/collect-active.png" v-else />
<span>{{collectState?'已收藏':'收藏'}}</span>
</div>
<div class="centen">|</div>
<div class="remark">
<img src="/static/images/remark.png" alt />
<span>写点评</span>
</div>
</div>
</div>
</div>
</template>
<script>
import star from "../star"
// 连接云数据库
let db = wx.cloud.database();
export default {
data() {
return {
obj:{},
imageurl:'',
// id:'',
collectState:false //使默认收藏状态为否
// userData:''
};
},
components:{
star
},
// 从新数据库里获取当前状态,来影响打开时收藏图标样式
onLoad(options){
db.collection("userCollect").doc(options.id).get({
success:res=>{
this.collectState = true
this.obj = res.data;
},
fail:res=>{
db.collection("newList").doc(options.id).get().then(res => {
this.imageurl = "cloud://gym-ayqef.6779-gym-ayqef-1302385623/" + res.data.image_path
this.obj = res.data;
this.collectState = false
});
}
})
},
// 点击后,判断当前收藏状态,并决定存储与否
methods: {
collected(){
this.collectState = !this.collectState
if(this.collectState){
db.collection("userCollect").add({
data:{
...this.obj
},
success:res=>{
console.log(res)
}
})
}else{
db.collection("userCollect").doc(this.obj._id).remove({
success:res=>{
console.log(res)
}
})
}
}
},
};
</script>
...
</style>
3.4 user文件夹
3.4.1 user \ index.vue
<template>
<div class="personal">
<button v-show="show" type="primary" @click="goLogin" class="login" open-type="getUserInfo">点击登录</button>
<div class="personal-top" v-show="!show">
<div class="personal-photo">
<img class="perphoto" :src="userImg" />
</div>
<div class="namesex">名字 {{userName}}</div>
<div class="country">China</div>
</div>
<div class="personal-bottom" v-show="!show" @click="collect">
<div class="collect">收藏列表</div>
<div class="more-icon">
<img src="/static/images/more.png" alt="" class="moreico">
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
show:true,
userImg:'',
userName:''
}
},
async onLoad(){
let openid = await this.getOpenId()
if(wx.getStorageSync(openid)){
this.show = false;
await this.getUserInfo();
}
},
components: {},
methods: {
goLogin(){
// 用户当前授权状态
wx.getSetting({
success:async res=>{
if(res.authSetting['scope.userInfo']){
let userInfo = await this.getUserInfo();
this.show = false;
let openid = await this.getOpenId();
wx.setStorage({
data:userInfo,
key:openid
})
}
}
})
},
// 获取用户信息
getUserInfo(){
return new Promise((resolve,reject)=>{
wx.getUserInfo({
success:(res)=>{
let userInfo = res.userInfo;
this.userName = userInfo.nickName;
this.userImg = userInfo.avatarUrl;
resolve(userInfo)
},
fail:(err)=>{
reject(err)
}
})
})
},
// 获取openId
getOpenId(){
return new Promise((reslove,rej)=>{
//登录,获取openId(需要code来换取)
wx.login({
success: (result) => {
let code = result.code;
//通过code换取openId
wx.request({
url: `https://api.weixin.qq.com/sns/jscode2session?appid=wxd719bf74ab43f85d&secret=2494b199be2276fa6a3b9b9830748bf3&js_code=${code}&grant_type=authorization_code`,
success:(r)=>{
let openid = r.data.openid
console.log(openid)
reslove(openid);
}
})
},
fail:err=>{
rej(err)
}
})
})
},
// 跳转收藏列表页
collect(){
wx.navigateTo({
url:'/pages/collection/main'
})
}
},
created () {
}
}
</script>
<style scoped>
...
</style>
3.5 collection文件夹
3.5.1 collection \ index.vue
<template>
<div>
<div class="user-motto" @click="goDetail(v)" v-for="(v,k) in collectedData" :key="k">
<div class="mottoimg">
<img class="mottoimg-avatar" v-if="v.image_path" :src="v.image_path" alt />
</div>
<div class="mottoright">
<div class="mrtop">{{v.title}}</div>
<div class="mrstar">
<star v-if="v.score" :Grade="v.score"></star>
<div class="pprice">¥{{v.price}}/人</div>
</div>
<div class="mrsite">{{v.specific}}</div>
<div class="mreval">
<div v-for="(val,key) in v.label" :key="key">{{val||"无"}}</div>
</div>
</div>
</div>
</div>
</template>
<script>
import star from "../star.vue"
// 连接云数据库
const db = wx.cloud.database();
export default {
data () {
return {
collectedData: [],
}
},
components: {
star
},
methods: {
goDetail(val) {
wx.navigateTo({
url: "/pages/detail/main?id=" + val._id
});
}
},
onLoad() {
this.collectedData = []
db.collection("userCollect").get().then(res=>{
res.data.forEach(v=>{
console.log(v);
return v.label = v.label.split(",")
})
res.data.forEach(v=>{
return v.image_path = "cloud://gym-ayqef.6779-gym-ayqef-1302385623/" + v.image_path
})
this.collectedData = [...this.collectedData,...res.data]
})
}
}
</script>
...
</style>
3.6 star.vue
<template>
<div class="stars">
<div class="astar" v-for="(v,k) in wholeStar" :key="k">
<img src="/static/images/b-star.png" alt="">
</div>
<div class="halfstar" v-for="(v,k) in halfStar" :key="k">
<img src="/static/images/b-half.png" alt="">
</div>
<div class="graystar" v-for="(v,k) in grayStar" :key="k">
<img src="/static/images/gray-star.png" alt="">
</div>
</div>
</template>
<script>
let db = wx.cloud.database();
export default {
data(){
return{
// myStar:null,
wholeStar:null, // 整颗星的数量
halfStar:null, // 半颗星的数量
grayStar:null, // 灰星的数量
}
},
props:['Grade'],
onLoad(){
db.collection("newList").get().then(res=>{
this.wholeStar = Math.floor(Number(this.Grade) / 2); //完整星星数量 向下取整
// console.log("评分:" + this.Grade);
// console.log("完整星:" + this.wholeStar);
this.halfStar = this.Grade - 2*this.wholeStar //半颗星星数量
// console.log("半星:" + this.halfStar);
this.grayStar = 5 - this.wholeStar - this.halfStar; //灰星星数量
// console.log("灰星:" + this.grayStar);
})
}
}
</script>
<style scoped>
.stars{
display: flex;
flex-direction: row;
justify-content: left;
align-items: center;
}
.stars div{
display: inline-block;
}
img{
width: 25rpx;
height: 25rpx;
padding: 5rpx;
}
</style>