简单商城小程序全栈开发(mpvue koa mongodb)
接触小程序一段时间,或多或少做了一些项目后,我们开始了vue之旅。受其核心思想的影响,我们对数据/状态管理、组件化、跨平台等有很高的追求。mpvue是一个用Vue.js开发小程序的前端框架,由此,我们开启了踩mpvue坑的旅程,希望能提高代码的可读性,增加一点Vue.js的开发体验。
技术栈
前端:微信小程序,mpvue
后端:koa
数据库:mongodb数据库可视化工具:Robo3T
商城小程序开跑
是一个基本的商城小程序,它包括前端首页、分类、购物车和我的(订单)四个标签页,以及后端的数据定义、分类和访问。各有各的颜色。我将介绍一些主要功能,比较原生小程序和vue.js踩过的坑,以及后端数据库的功能应用。如果你想知道或者有什么问题,可以去源代码了解一下。
成果分享
一、前台页面及功能展示
主页:
加入购物车:
购物车选择结算:
地址管理:
1.浅谈组件包装
以一个栗子为例。首页由三部分组成:头部旋转推荐中间水平滑动推荐垂直滚动商品列表。这三个部分几乎是所有商城应用的必备功能。众所周知,头部的轮播推荐、中间的水平滑动推荐的包等功能组件,在每个app中基本都是不可或缺的。我从vue学到的第一件事是组件代码的高重用性。当一些组件被重用并迁移到其他组件或页面时,它们可以直接使用,而无需更改代码或一点代码,可以说是相当方便的。对于仍然支持mpvue组件中原生小程序的swiper和scroll,对于小程序和vue兼容后熟悉的开发者来说,这个功能可以高效完成。
最后,主页文件由组件组成,可读性很强。对于初学者来说,首先要有模块封装的思想。
template div class=' container ' @ click=' click handle '(test click ',$ event)' div class=' swiper list ' swiper : text='座右铭' : wiper list=' swiper list '/swiper/div class=' navTab ' div class=' recTab '
> <text> —— 为你推荐 ——</text> </div> </div> <scroll></scroll> <div class="hot"> <span> —— 热门商品 ——</span> </div> <hot :v-text="motto"></hot> <div class="fixed-img"> <img :src="fixImg" alt="" class="fix-img"> </div> </div></template>复制代码不过关于组件封装与组合的问题,由于最近有研究vue性能优化和用户体验的一些知识点,考虑了一个比较严肃的问题:
先看一下常见的vue写法:在html里放一个app组件,app组件里又引用了其他的子组件,形成一棵以app为根节点的组件树:
<body> <app></app> </body>复制代码
而这种做法就引发了性能问题,要初始化一个父组件,必然需要先初始化它的子组件,而子组件又有它自己的子组件。那么要初始化根标签,就需要从底层开始冒泡,将页面所有组件都初始化完。所以我们的页面会在所有组件都初始化完才开始显示。
这个结果显然不是我们要的,用户每次点开页面,还要面对一阵子的空白和响应,因为页面启动后不止要响应初始化页面的组件,还有包含在app里的其他组件,这样严重拖慢了页面打开的速度。
更好的结果是页面可以从上到下按顺序流式渲染,这样可能总体时间增长了,但首屏时间缩减,在用户看来,页面打开速度就更快了。网上一些办法大同小异,各有优缺点,所以...本人也在疯狂试验中,静待好消息。
**2.Class、Style的绑定 **
在不同父组件中引用同一子组件时,但是各自需要接收绑定的动态样式去呈现不同的样式,在绑定css style样式这一关上,踩了个大坑:mpvue居然不支持用object的形式传style,起先处于样式一直上不去的抓狂当中,网上对于mpvue这方面的细节也少之又少,后来查找了许多地方,发现class和style的绑定都是不支持classObj和styleObj形式,就尝试用了字符串,果然...改代码改到怀疑人生,结果你告诉我人生起步就是错误,怎能不心痛?...
解决:
<template><div class="swiper-list"> <d-swiper :swiperList="swiperlist" :styleObject="styleobject"></d-swiper></div></template><script> data() { return { styleobject:'width:100%;height:750rpx;position:absolute;top:0;z-index:3' } }</script>复制代码
3. “v-for嵌套”陷阱
在做vue项目的时候难免会用到循环,需要用到index索引值,但是v-for在嵌套时index没办法重复用,内循环与外循环不能共用一个index。
<swiper-item v-for="(items,index) in swiperList" :key="index"> <div v-for="item in items" class="swiper-info" :key="item.id" @click="choose" > <image :src="item.url" class="swiper-image" :style="styleObject"/> </div></swiper-item>复制代码
以上代码就会报错:
而给内循环再加上另一个索引,便没有报错:
<swiper-item v-for="(items,index) in swiperList" :key="index"> <div v-for="(item,i) in items" class="swiper-info" :key="i" @click="choose" > <image :src="item.url" class="swiper-image" :style="styleObject"/> </div></swiper-item>复制代码
4.this指向问题与箭头函数的应用
这是vue文档里的原话:All lifecycle hooks are called with their 'this' context pointing to the Vue instance invoking it.
意思是:在Vue所有的生命周期钩子方法(如created,mounted, updated以及destroyed)里使用this,this指向调用它的Vue实例,即(new Vue)。 mpvue里同理。 我们都知道,生命周期函数中的this都是指向Vue实例的,因此我们就可以访问数据,对属性和方法进行运算。
props:{ goods:Array},mounted: function(options){ let category = [ {id: 0, name: '全部'}, {id: 1, name: 'JAVA'}, {id: 2, name: 'C++'}, {id: 3, name: 'PHP'}, {id: 4, name: 'VUE'}, {id: 5, name: 'CSS'}, {id: 6, name: 'HTML'}, {id: 7, name: 'JavaScript'} ] this.categories = category this.getGoodsList(0) },methods: { getGoodsList(categoryId){ console.log(categoryId); if(categoryId == 0){ categoryId = '' } wx.request({ url: 'http://localhost:3030/shop/goods/list', data: { categoryId: categoryId }, method: 'POST', success: res => { console.log(res); this.goods = res.data.data; } }) },}复制代码
普通函数this指向这个函数运行的上下文环境,也就是调用它的上下文,所以在这里,对于生命周期函数用普通函数还是箭头函数其实并没有影响,因为它的定义环境与运行环境是同一个,所以同样能取到vue实例中数据、属性和方法。 箭头函数中,this指向的是定义它的最外层代码块,()=>{} 等价于 function(){}.bind(this);所以this当然指向的是vue实例。起初并没有考虑到this指向的问题,在wx.request({})中success用了普通函数,结果一直报错“goods is not defined”,用了箭头函数才解决,起初普通函数的this指向 getGoodsList()的上下文环境,所以一直没办法取到值。
5.onLoad与onShow
在进行首页点击商品跳转到详情页时,onLoad()无法获取更新数据。
首先虽然onLoad: function (options) 这个是可以接受到值的,但是这个只是加载一次,不是我想要的效果,我需要在本页面(不关闭的情况下)到另外一个页面在跳转进来,接收到对应商品的数据。
所以需要将代码放在onshow内部, 在每次页面加载的时候都会进行当前状态的查询,查询对应数据的子对象,更新渲染到详情页上。
onShow: function(options){ // console.log(this.styleobject) // console.log(options) wx.getStorage({ key: 'shopCarInfo', success: (res) =>{ // success console.log(`initshopCarInfo:${res.data}`) this.shopCarInfo = res.data; this.shopNum = res.data.shopNum } }) wx.request({ url: 'http://localhost:3030/shop/goods/detail',//请求detail数据表的数据 method: 'POST', data: { id: options.id }, success: res =>{ // console.log(res); const dataInfo = res.data.data.basicInfo; this.saveShopCar = dataInfo; this.goodsDetail.name = dataInfo.name; this.goodsDetail.minPrice = dataInfo.minPrice; this.goodsDetail.goodsDescribe = dataInfo.characteristic; let goodsLabel = this.goodsLabel goodsLabel = res.data.data; // console.log(goodsLabel); this.selectSizePrice = dataInfo.minPrice; this.goodsLabel.pic = dataInfo.pic; this.goodsLabel.name = dataInfo.name; this.buyNumMax = dataInfo.stores; this.buyNumMin = (dataInfo.stores > 0) ? 1 : 0; } }) }复制代码
了解小程序onLoad与onShow生命周期函数:
onLoad:生命周期函数–监听小程序初始化,当小程序初始化完成时,会触发 onLoadh(全局只触发一次)。
onShow:生命周期函数–监听小程序显示,当小程序启动,或从后台进入前台显示,会触发 onShow。
二、后台数据库及数据存取
1.架设 HTTP 服务
在全局配置文件中: 1).引入koa并实例化
const Koa = require('koa');const app = new Koa()复制代码
2).app.listen(端口号):创建并返回 HTTP 服务器,将给定的参数传递给Server#listen()。
const Koa = require('koa');//引入koa框架const app = new Koa();app.listen(3000);这里的app.listen()方法只是以下方法的语法糖:const http = require('http');const Koa = require('koa');const app = new Koa();http.createServer(app.callback()).listen(3000);复制代码
这样基本的配置完毕,我们就可以用“http://localhost3030+请求地址参数”获取到数据库的值了。
2.Koa-router路由中间件
koa-router 是常用的 koa 的路由库。
如果依靠ctx.request.url去手动处理路由,将会写很多处理代码,这时候就需要对应的路由的中间件对路由进行控制,这里介绍一个比较好用的路由中间件koa-router。
以路由切换催动界面切换,”数据化”界面。
3.建立对象模型
在构建函数库之前,先来聊聊对象的建模。
Mongoose是在node.js异步环境下对mongodb进行便捷操作的对象模型工具。该npm包封装了操作mongodb的方法。
Mongoose有两个特点:
1、通过关系型数据库的思想来设计非关系型数
2、基于mongodb驱动,简化操作
const mongoose = require('mongoose')const db = mongoose.createConnection('mongodb://localhost/shop') //建立与shop数据库的连接(shop是我本地数据库名)复制代码
本地数据库shop中建了分别“地址管理”、“商品详情”、“订单详情”、“商品列表”、“用户列表”五个数据表:
Schema界面定义数据模型:
Schema用于定义数据库的结构。类似创建表时的数据定义(不仅仅可以定义文档的结构和属性,还可以定义文档的实例方法、静态模型方法、复合索引等),每个Schema会映射到mongodb中的一个collection,但是Schema不具备操作数据库的能力。
数据表跟对象的映射,同时具有检查效果,检查每组数据是否满足模型中定义的条件 同时,每个对象映射成一个数据报表,就可用该对象进行保存操作,等同操作数据表,而非mysql命令行般繁琐的操作
以“商品列表”数据表为例:
// 模型通过Schema界面定义。var Schema = mongoose.Schema;const listSchema = new Schema({ barCode: String, categoryId: Number, characteristic: String, commission: Number, commissionType: Number, dateAdd: String, dateStart: String, id: Schema.Types.ObjectId, logisticsId: Number, minPrice: Number, minScore: Number, name: String, numberFav: Number, numberGoodReputation: Number, numberOrders: Number, originalPrice: Number, paixu: Number, pic: String, pingtuan: Boolean, pingtuanPrice: Number, propertyIds: String, recommendStatus: Number, recommendStatusStr: String, shopId: Number, status: Number, statusStr: String, stores: Number, userId: Number, videoId: String, views: Number, weight: Number,})复制代码
定义了数据表中需要的数据项的类型,数据表传入数据后会一一对应:
4.koa-router“路由库”
const Router = require('koa-router')()//引入koa-routerconst router = new Router();// 创建 router 实例对象//注册路由router.post('/shop/goods/list', async (ctx, next) => { const params = ctx.request.body //以‘listSchema’的模型去取到Goods的数据 const Goods = db.db.model('Goods', db.listSchema) //第一个‘db’是require来的自定义的,第二个‘db’是取到连接到mongodb的数据库,model代指实体数据(根据schema获取该字段下的数据,然后传给Goods)) ctx.body = await new Promise((resolve, reject) => {//ctx.body是ctx.response.body的缩写,代指响应数据 //异步,等到获取到数据之后再将body发出去 if (params.categoryId) { Goods.find({categoryId: params.categoryId},(err, docs) => { if (err) { reject(err) } resolve({ code: 0, errMsg: 'success', data: docs }) }) } else { Goods.find((err, docs) => { if (err) { reject(err) } resolve({ code: 0, errMsg: 'success', data: docs }) }) } })})复制代码
所有的数据库操作都是异步的操作,所以需要封装promise来实现,由此通过POST “http://localhost3030/shop/goods/list”便可访问本地shop数据库了。 这里顺便提一下“ctx”的使用,ctx(context)上下文,我们都知道有node.js 中有request(请求)对象和respones(响应)对象。Koa把这两个对象封装在ctx对象中。 参数ctx是由koa传入的封装了request和response的变量,我们可以通过它访问request和response (前端通过ajax请求http获取数据) 我们可以通过ctx请求or获取数据库中的数据。
Ctx.body 属性就是发送给用户的内容
body是http协议中的响应体,header是指响应头
ctx.body = ctx.res.body = ctx.response.body
5.数据缓存之模型层设置
1).为什么要做数据缓存?
在这里不得不提一句数据缓存的重要性,虽然我是从本地数据库获取的数据,但是由于需要的数据量较多,再者前面说的性能优化还未完成,每次还是有一定的请求时间,没必要每次打开都去请求一遍后端,渲染页面较慢,所以需要将需要经常用到的数据做本地缓存,这样能大大提高页面渲染速度。
2).设置模型层
setGoodsList: function (saveHidden, total, allSelect, noSelect, list) { this.saveHidden = saveHidden, this.totalPrice = total, this.allSelect = allSelect, this.noSelect = noSelect, this.goodsList = list var shopCarInfo = {}; var tempNumber = 0; var list = []; shopCarInfo.shoplist = list; for (var i = 0; i < list.length; i++) { tempNumber = tempNumber + list[i].number } shopCarInfo.shopNum = tempNumber; wx.setStorage({ key: "shopCarInfo", data: shopCarInfo }) },复制代码
将需要做本地存储数据的方法封装成一个方法模型,当需要做本地存储时,直接做引用,如今vue、react中多用到的架构思想,都对模型层封装有一定的要求。
bindAllSelect() { var list = this.goodsList; var currentAllSelect = this.allSelect if (currentAllSelect) { list.forEach((item) => { item.active = false }) } else { list.forEach((item) => { item.active = true }) } this.setGoodsList(this.getSaveHide(), this.totalPrice(), !currentAllSelect, this.noSelect(), list); },复制代码
版权声明:简单商城小程序全栈开发(mpvue koa mongodb)是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。