微信小程序三 之 “小团点评吧“

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简介

    • 是一个使用 Vue.js 开发小程序的前端框架。框架基于 Vue.js 核心,mpvue 修改了 Vue.jsruntimecompiler 实现,使其可以运行在小程序环境中,从而为小程序开发引入了整套 Vue.js 开发体验
  • 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": "你的位置信息将用于小程序位置接口的效果展示" // 高速公路行驶持续后台定位
            }
          }
        
    • 将经纬度转换成地址;

      • 腾讯地图小程序JavaScript SDK

      • 申请地图申请开发者密钥(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>
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值