1.项目初始化

 

npm -v  //确认安装node基本环境

npm i vue-cli -g //-g 全局安装vue-cli

vue init webpack //使用webpack模板安装

 

不安装eslint,eslint用于多人开发,按照谷歌和facebook的代码标准开发。

unit --no //大公司有专门测试人员

ese  --no

 

 

package-lock.json  别人不会因为版本不同而运行不了。

README.md 如何运行 关键点  项目注意事项 API接口 都写在里面。

 

  1. 优雅引入Vant样式组件库

 

Vant简介

vant是有赞前端团队提供的Vue组件库。它的有点有以下5个:
1. 国人制造,合中国网站样式和交互习惯;
2. 单元测试超过90%,有些个人的小样式组件是不作单元测试的;
3. 支持babel-plugin-import引入,按需加载插件,无需单独引入样式;
4. 支持TypeScript,这个是2018年前端最应该学的技术;
5. 支持SSR,服务端渲染也是可以使用这个组件库的;

安装:

npm i vant -S  //S 用于生产环境

npm install vant --save --registry=https://registry.npm.taobao.org//可以提升速度

 

全局引入:main.js

import Vant from 'vant'

import 'vant/lib/vant-css/index.css'

Vue.use(Vant)

 

不建议全局引入整个文件,把整个组件库引入会导致打开好慢,应按需引入。

优雅的引入Vant:打包文件会减少很多,速度会加快。

npm i babel-plugin-import -D

完整:

npm install babel-plugin-import --save-dev

 

在.babelrc中配置plugins(插件)

"plugins": [

    "transform-vue-jsx",

    "transform-runtime",

    ["import",{"libraryName":"vant","style":true}]

  ]

在src/main.js里加入下面的代码:

import { Button } from 'vant'

Vue.use(Button)

 

有了这段代码之后,我们就可以在需要的组件页面中加入Button了.

 <van-button type="primary">主要按钮</van-button>

 

3.移动端屏幕适配基础

 

rem单位介绍

rem(font size of the root element)是相对长度单位。相对于根元素(即html元素)font-size计算值的倍数。

  • 适配原理:将px替换成rem,动态修改html的font-size适配。它可以很好的根据根元素的字体大小来进行变化,从而达到各种屏幕基本一直的效果体验。

//得到手机屏幕的宽度

let htmlWidth = document.documentElement.clientWidth || document.body.clientWidth;

//得到html的Dom元素

let htmlDom = document.getElementsByTagName('html')[0];

//大屏设备会比较难看

if(htmlWidth>750){

htmlWidth=750

}

//设置根元素字体大小

htmlDom.style.fontSize= htmlWidth/20 + 'px'; //以ip5 宽320PX 字体16PX为基准

 

 

  1. 首页布局

三个基础知识:
1. 首页路由的配置:

routes: [

{path:"/",name:'ShoppingMall',component:ShoppingMall},

]


2. 建立首页组件(components):先把首页组件建立起来。插件:vue VSCode Snippets

   vba生成基本框架代码

   vda 生成data

3. 讲解Vant的布局方法:24格布局法

   按需引入组件 mai.js

import {Button,Row,Col} from 'vant'

Vue.use(Button).use(Row).use(Col)

     --vue --

<van-row class="vanrow">

<van-col span="8">span8</van-col>

<van-col span="8">span8</van-col>

<van-col span="8">span8</van-col>

</van-row>

 

 

5.首页搜索布局

在vue-cli使用less

npm install less less-loader --save-dev

<style scoped lang="less">

引入方法:

@import url(../../**.less)   不要用  @import  "../../**.less"

 

引入阿里字体:

把字体下载到src/assets/iconfont目录

main.js

import './assets/iconfont/iconfont.css'

<i class="iconfont icon-iconfontzhizuobiaozhun16"></i>

 

插入图片方式:保证build

<van-col span="3"><img :src="locationIcon" width="100%" /></van-col>

data() {

        return {

            locationIcon: require('../../assets/images/location.png')

        }

    }

 

 

  1. 首页轮播图制作

 

Swipe 轮播

https://youzan.github.io/vant/#/zh-CN/swipe

 

Lazyload 图片懒加载

https://youzan.github.io/vant/#/zh-CN/lazyload

 

 

  1. easyMock和axios的使用

 

Easy-mock 创建接口数据

https://www.easy-mock.com/

 

 

安装axios

npm i -D axios

 

在shoppingMall.vue 引入axios

import axios form axios

 

created(){

axios({

url:"https://www.easy-mock.com/mock/5b6eaead121e7e30aea261d2/smile/index",

method:"get"

}).then(response=>{

console.log(response)

}).catch(error=>{

console.log(error)

})

}

 

 

8.mock数据使用和flex布局

 

div.type-bar>div>img+span

 

.type-bar{

background-color: #fff;

margin:0 .3rem .3rem .3rem;

border-radius: .3rem;

font-size:14px;

display:flex;

flex-direction:row; //横向布局

flex-wrap:nowrap;//不换行

  div{

padding:.3rem;

font-size:12px;

text-align: center;

flex:1;

}

}

 

换行实例

ul {

    width: 100%;

    display: flex;

    flex-wrap: wrap;

    li {

      flex: 1;

      width: 25%;

      min-width: 25%;

      max-width: 25%;

 

获取数据并渲染

if(response.status == 200){

this.category = response.data.data.category

this.adBanner = response.data.data.advertesPicture.PICTURE_ADDRESS

this.bannerPicArray = response.data.data.slides

}

 

  1. vue-awesome-swipe 

 

npm install vue-awesome-swiper --save

 

全局引入:(不推荐,有些页面不需要使用)

import VueAwesomeSwiper from 'vue-awesome-swiper'

import 'swiper/dist/css/swiper.css'

Vue.use(VueAwesomeSwiper)

 

组件形式引入:

import 'swiper/dist/css/swiper.css'

import { swiper, swiperSlide } from 'vue-awesome-swiper'

export default {

  components: {

    swiper,

    swiperSlide

  }

}

 

 

<swiper :options="swiperOption">

<swiper-slide v-for="(item,index) in recommendGoods " :key="index" >

<div class="recommend-item">

<img :src="item.image" width="80%">

<div>{{item.goodsName}}</div>

<div>¥{{item.price}}(¥{{item.mallPrice}})</div>

</div>

</swiper-slide> 

</swiper>

 

 

data() {

return {

swiperOption:{

          slidesPerView:3  //一屏显示数量 3

}

}

},

 

  1. vue-awesome-swiper详讲

 

添加分页器

swiperOption:{

        loop:true,//无限循环

    pagination:{

        el:'.swiper-pagination',

                clickable:true// 点击分页器可以切换

    }

}

swiper-slide外层

<div class="swiper-pagination" slot="pagination"></div>

<template>

  <div >

      <swiper :options="swiperOption">

        <swiper-slide class="swiper-slide" v-for="(item, index) in slide" :key="index">

           Slide {{item}} 

        </swiper-slide>

        <div class="swiper-pagination" slot="pagination"></div>

      </swiper>

  </div>

</template>

竖屏切换效果

swiperOption:{

    direction:'vertical',

    pagination:{

        el:'.swiper-pagination'

    }

}

区域滚动效果

<template>

  <div >

      <swiper class="swiper" :options="swiperOption">

        <swiper-slide class="text">

            <div class="centent">

               一大堆文章.........

            </div>

        </swiper-slide>

      </swiper>

  </div>

</template>

<script>

    import 'swiper/dist/css/swiper.css' 

    import { swiper, swiperSlide } from 'vue-awesome-swiper'

    export default {

        data() {

            return {

                swiperOption:{

                    direction:'vertical',//竖向

                    slidesPerView: 'auto',//一屏显示数量 有多少个swiper-slide

                    freeMode:true,//默认:false 每次只滑动一格,并贴合。True:随意滑动

                    mousewheel:true //鼠标滚轮

                }

            }

        },

        components:{swiper,swiperSlide}

    }

</script>

<style scoped>

  .swiper{

      height: 300px;//设置宽度

      overflow: hidden;//隐藏溢出

  }

  .text{

      font-size: 18px !important;

      text-align: left;

      padding:30px;

      height: auto;//溢出宽度

      -webkit-box-sizing: border-box;

      box-sizing: border-box;

  }

</style>

 

11任务13:首页楼层效果制作

Img 外套一个 div ,把div背景色设置与图片颜色相同,然后再设置div边框,div设置为怪异盒模型。

box-sizing: border-box;

 

12楼层组件封装和watch使用技巧

 

使用组件三部曲:

 

导入

import floorComponent from '../component/floorComponent'

注册

components:{

floorComponent

}

使用:

<floor-component :floorData="floor1">//传递数据

</floor-component>

 

封装组件:

 

接收数据

props:['floorData']

设置好数据:

data() {

return {

floorData0:{},

floorData1:{},

floorData2:{}

}

}

 

获取数据,因为是异步请求,必须监控数据,因为不会马上接收到数据

watch:{

floorData:function(val){

this.floorData0 = this.floorData[0]

this.floorData1 = this.floorData[1]

this.floorData2 = this.floorData[2]

}

}

 

 使用方法:

 watch:{
example0(curVal,oldVal){
console.log(curVal,oldVal);
}

}

13.Filter使用和代码优化

使用

<div>¥{{item.price|moneyFilter}}(¥{{item.mallPrice|moneyFilter}})</div>

 

引入

import {toMoney} from '@/filter/moneyFilter.js'

 

调用:

filters:{

moneyFilter(money){

return toMoney(money)

}

}

 

--moneyFilter.js--

 

export function toMoney(money){

let newMoney = money

if(newMoney){

newMoney = newMoney.toFixed(2)

}else{

newMoney = 0;

newMoney = newMoney.toFixed(2)

}

return newMoney

}

=====优化后======>

export function toMoney(money=0){

return money.toFixed(2)

}

 

 

14.首页热卖模块的Van-list组件使用

<div class="hot-area">

<div class="hot-title">热卖商品</div>

<div class="hot-goods">

<!--这里需要一个list组件-->

<van-list> //van-list 可以下拉刷新

<van-row gutter="20"><!--列之间20的空间-->

     <van-col span="12" v-for="(item,index) in hotGoods" :key="index">

     //对单个商品抽出来做组件

<goods-info :goodsId="item.goodsId" :goodsImage="item.image" :goodsName="item.name" :goodsPrice="item.price">

</goods-info>

</van-col>

</van-row>

</van-list>

</div>

</div>

import hotgoodComponent from '../component/hotgoodComponent'

components:{

goodsInfo:hotgoodComponent

},

---hotgoodComponent.vue---

<template>

<div class="goods-info" @click="goGoodsPage()">

<div class="goods-image">

  <img v-lazy="goodsImage" width="90%" />

</div>

<div class="goods-name">{{goodsName}}</div>

<div class="goods-price">¥{{goodsPrice | moneyFilter }}</div>

</div>

</template>

<script>

import {toMoney} from '@/filter/moneyFilter.js'

export default {

props:['goodsImage','goodsName','goodsPrice','goodsId'],

filters:{

moneyFilter(money){

return toMoney(money)

} },

</script>

 

 

 

15.服务接口API配置文件制作

把要请求的接口提出来做成一个文件serviceAPI.config.js

const BASEURL = "https://www.easy-mock.com/mock/5ae2eeb23fbbf24d8cd7f0b6/SmileVue/"

const LOCALURL = "http://localhost:3000/"

const URL ={

getShopingMallInfo : BASEURL+'index', //商城首页所有信息

getGoodsInfo : BASEURL+'getGoodsInfo',

registerUser : LOCALURL+'user/register', //用户注册接口

login : LOCALURL+'user/login', //用户登录接口

getDetailGoodsInfo : LOCALURL+'goods/getDetailGoodsInfo', //获取商品详情

getCateGoryList : LOCALURL+'goods/getCateGoryList', //得到大类信息

getCateGorySubList : LOCALURL+'goods/getCategorySubList', //得到小类信息

getGoodsListByCategorySubID : LOCALURL+'goods/getGoodsListByCategorySubID', //得到小类商品信息

}

module.exports = URL //暴露url对象出去

 

调用:

import url from '@/serviceAPI.config.js'

 

axios({

url:url.getShopingMallInfo,

method:"get"

}).then(...)

 

 

  1. koa2  mongodb  Robo 3T

 

Koa2 前端服务器

新建service目录,启动一个最基本的服务

const Koa = require("koa")

const app = new Koa();

app.use(async(ctx)=>{

ctx.body = "hello,world"

})

app.listen(3000);

console.log("app is start")

 

 

Mongodb

1.安装 3.4 版本

2.设置环境变量

C:\Program Files\MongoDB\Server\3.4\bin

  1. 建立目录 c:\data\db
  2. 运行:mongod

 

Robo 3T

建立连接

 

 

 

 

  1. Koa用Mongoose连接数据库

 

Mongoose是一个开源的封装好的实现Node和MongoDB数据通讯的数据建模库

Mongoose的安装

npm install mongoose --save

 

==service/database/init.js==

const mongoose = require('mongoose')

const db = "mongodb://localhost/smile-db" //创建一个smile-db的数据库

exports.connect = ()=>{

//连接数据库

mongoose.connect(db)

let maxConnectTimes = 0 

return new Promise((resolve,reject)=>{

//把所有连接放到这里//增加数据库监听事件

mongoose.connection.on('disconnected',()=>{

console.log('***********数据库断开***********')

if(maxConnectTimes<3){ //当失败,尝试3次连接

maxConnectTimes++

mongoose.connect(db)

}else{

     reject()

     throw new Error('数据库出现问题,程序无法搞定,请人为修理......')

}

})

mongoose.connection.on('error',err=>{

console.log('***********数据库错误***********')

if(maxConnectTimes<3){

maxConnectTimes++

mongoose.connect(db)

}else{

reject(err)

throw new Error('数据库出现问题,程序无法搞定,请人为修理......')

}

})

//链接打开的时

mongoose.connection.once('open',()=>{

console.log('MongoDB connected successfully')

resolve()

})

})

}

 

===========service/index.js

const { connect } = require('./database/init.js')

 

;(async () =>{

await connect()

})()

 

18.Mongoose的Schema建模介绍

Schema,他相当于MongoDB数据库的一个映射。Schema是一种以文件形式存储的数据库模型骨架,无法直接通往数据库端,也就是说它不具备对数据库的操作能力。Schema是以key-value形式Json格式的数据。

Schema中的数据类型

  • String :字符串类型
  • Number :数字类型
  • Date : 日期类型
  • Boolean: 布尔类型
  • Buffer : NodeJS buffer 类型
  • ObjectID : 主键,一种特殊而且非常重要的类型
  • Mixed :混合类型
  • Array :集合类型

Mongoose中的三个概念

  • schema :用来定义表的模版,实现和MongoDB数据库的映射。用来实现每个字段的类型,长度,映射的字段,不具备表的操作能力。
  • model :具备某张表操作能力的一个集合,是mongoose的核心能力。我们说的模型就是这个Mondel。
  • entity :类似记录,由Model创建的实体,也具有影响数据库的操作能力。

/servcie/database/文件夹下新建一个schema文件夹,然后新建一个User.js文件

 

const mongoose = require('mongoose')    //引入Mongoose

const Schema = mongoose.Schema          //声明Schema

let ObjectId = Schema.Types.ObjectId    //声明Object类型

//创建我们的用户Schema

const userSchema = new Schema({

    UserId:ObjectId,

    userName:{unique:true,type:String},

    password:String,

    createAt:{type:Date,default:Date.now()},

    lastLoginAt:{type:Date,default:Date.now()}

})

//发布模型

mongoose.model('User',userSchema)

 

19.载入Schema和插入查出数据

 

schema建立好以后,需要我们载入这些数据库,当然最好的方法就是在后台服务已启动的时候就把载入做好,所以我们在service/init.js里作这件事,然后在index.js里直接执行。

载入所有Schema

直接在service\init.js 先引入一个glob和一个resolve

首先安装glob

npm install glob --save

 

const glob = require('glob')

const {resolve} = require('path')

 

  • glob:node的glob模块允许你使用 * 等符号,来写一个glob规则,像在shell里一样,获取匹配对应规则文件。
  • resolve: 将一系列路径或路径段解析为绝对路径。

了解两个引入的模块用法后,我们就可以一次性引入所有的Schema文件了。

exports.initSchemas = () =>{

    glob.sync(resolve(__dirname,'./schema/','**/*.js')).forEach(require)

}

使用了glob.sync同步引入所有的schema文件,然后用forEach的方法require(引入)进来。这比你一条条引入要优雅的多。

 

插入一条数据

====index.js=======

const mongoose = require('mongoose')

const {connect , initSchemas} = require('./database/init.js')

我们直接在service/index.js的立即执行函数里插入一User数据

;(async () =>{

    await connect()

    initSchemas()

    const User = mongoose.model('User')

    let oneUser = new User({userName:'jspang',password:'123456'})

    oneUser.save().then(()=>{

        console.log('插入成功')

    })

 

})()

 读出已经插入进去的数据

let  users = await User.findOne({}).exec()

console.log('------------------')

console.log(users)

console.log('------------------')

 

 

exec() 方法用于检索字符串中的正则表达式的匹配。

RegExpObject.exec(string)

 

19.打造安全的用户密码加密机制

加密处理

密码的加密有很多种加密算法,比如我们使用的MD5加密或者hash256加密算法,其实他们都是hash的算法。就是把你的密码进行一次不可逆的编译,这样就算别人得到了这个密码值,也不能进行直接登录操作。
我们可以通过(http://www.atool.org/hash.php) 网站,直观的看一下加密的算法。

加盐处理

有了加密的处理,我们的密码就安全多了,但是有用户的密码设置的太过简单,很好进行暴力破解或者用彩虹表破解,这时候感觉我们的密码又不堪一击了。这时候我们要使用加盐技术,其实就是把原来的密码里,加入一些其他的字符串,并且我们可以自己设置加入字符串的强度。

把加盐的数据库密码进行hash处理后,再存入数据库就比较安全了。

bcrypt的使用

简介: bcrypt是一种跨平台的文件加密工具。bcrypt 使用的是布鲁斯·施内尔在1993年发布的 Blowfish 加密算法。由它加密的文件可在所有支持的操作系统和处理器上进行转移。它的口令必须是8至56个字符,并将在内部被转化为448位的密钥。

首先是用npm 进行安装

npm instal --save bcrypt

引入bcrypt

const bcrypt = require('bcrypt')

然后是用pre每次进行保存时都进行加盐加密的操作。

const SALT_WORK_FACTOR = 10//加密强度

 

/每次存储数据时都要执行

userSchema.pre('save', function(next){

    //let user = this

    console.log(this)

    bcrypt.genSalt( SALT_WORK_FACTOR,(err,salt)=>{

        if(err) return next(err)

        bcrypt.hash(this.password,salt, (err,hash)=>{

            if(err) return next(err)

            this.password = hash

            next()

        }) 

 

    })

})


20.注册页面

 

vue的路由配置文件 router/index.js

 

import Register from '@/components/pages/Register'

export default new Router({

  routes: [

    {path: '/',name: 'ShoppingMall',component: ShoppingMall},

    {path: '/register',name: 'Register',component: Register},

  ]

})

--Register.vue--

<template>

    <div>

       <van-nav-bar

        title="用户注册"

        left-text="返回"

        left-arrow

        @click-left="goBack"

        />

 

        <div class="register-panel">

        <van-field

            v-model="username"

            label="用户名"

            icon="clear"

            placeholder="请输入用户名"

            required

            @click-icon="username = ''"

        />

 

        <van-field

            v-model="password"

            type="password"

            label="密码"

            placeholder="请输入密码"

            required

        />

        <div class="register-button">

            <van-button type="primary" size="large">马上注册</van-button>

        </div>

       </div>

 

    </div>

</template>

 

<script>

    export default {

        data() {

            return {

                username: '',

                password: '',

            }

        },

        methods: {

            goBack() {

                this.$router.go(-1)  

            }

        },

    }

</script>

 

21.Koa2的用户操作的路由模块化

 

安装路由中间件

npm install koa-router --save

 

新建一个appApi的文件夹

 

--service/appApi/User.js--

const Router = require ('koa-router')

let router = new Router()

router.get('/',async(ctx)=>{

    ctx.body="这是用户操作首页"

})

router.get('/register',async(ctx)=>{

    ctx.body="用户注册接口"

})

module.exports=router;

 

--service/idnex.js--

const Router = require('koa-router')

//引入我们的user.js模块

let user = require('./appApi/user.js')

//装载所有子路由

let router = new Router();

router.use('/user',user.routes())

//加载路由中间件

 

app.use(router.routes())

app.use(router.allowedMethods())

 

22.打通注册用户的前后端通讯

Post 请求中间件:

npm install --save koa-bodyparser 

---service/index.js---

const bodyParser = require('koa-bodyparser')

app.use(bodyParser());

 

的axios请求处理

---register.vue---

<van-button type="primary" @click="axiosRegisterUser" size="large">马上注册</van-button>

 

import axios from 'axios'

在methods属性里,写入如下方法

axiosRegisterUser(){

        axios({

        url: url.registerUser,

        method: 'post',

        data:{

            username:this.username,

            password:this.password 

        }

    })

    .then(response => {

        console.log(response)

    })

    .catch((error) => {

        console.log(error)

    })

}

koa2支持跨域请求

安装koa2-cors中间件

npm install --save koa2-cors

const cors = require('koa2-cors')

app.use(cors())

 

---service/appApi/user.js----

router.post('/register',async(ctx)=>{

    console.log(ctx.request.body)

    ctx.body= ctx.request.body

})

 

 

23.用户注册数据库操作

 

-----service/appApi/user.js-----

const mongoose = require('mongoose')

 

router.post('/register',async(ctx)=>{

const User = mongoose.model('User')

let newUser = new User(ctx.request.body)

await newUser.save().then(()=>{

ctx.body={

code:200,

message:'注册成功'

}

}).catch(error=>{

ctx.body={

code:500,

message:error

}

})

})

前端Vue的业务处理---Register.vue----

import { Toast } from 'vant'

axiosRegisterUser(){

        axios({

        url: url.registerUser,

        method: 'post',

        data:{

            userName:this.username,

            password:this.password 

        }

    })

    .then(response => {

        console.log(response)

        //如果返回code为200,代表注册成功,我们给用户作Toast提示

        if(response.data.code == 200){

            Toast.success('注册成功')

        }else{

            console.log(response.data.message)

            Toast.fail('注册失败')

        }

            console.log(response.data.code)

    })

    .catch((error) => {  

        Toast.fail('注册失败')  

    })         

}

 

24.注册的防重复提交

 

方法:设置vant-button 的loading属性

 

<div class="register-button">

<van-button class="van-button" size="large" @click="axiosRegisterUser" :loading="openLoading" >注册</van-button>

</div>

 

openLoading: false, //是否开启用户的Loading

 

axios({

    url: url.registerUser,

    method: 'post',

    data:{

        userName:this.username,

        password:this.password 

    }

})

.then(response => {

    console.log(response)

    //如果返回code为200,代表注册成功,我们给用户作Toast提示

    if(response.data.code == 200){

        Toast.success('注册成功')

        this.$router.push('/')

    }else{

        console.log(response.data.message)

        Toast.fail('注册失败')

            this.openLoading=false

    }

        

})

.catch((error) => {  

    Toast.fail('注册失败')

    this.openLoading=false

})

 

25.登录的服务端业务逻辑代码

Shema中的比对实例方法

需要在Shema中制作一个比对的实例方法,这个方法就是比对我们加盐加密后的密码的。在service/database/schema/User.js下增加下面的代码:

userSchema.methods = {

    //密码比对的方法

    comparePassword:(_password,password)=>{

        return new Promise((resolve,reject)=>{

            bcrypt.compare(_password,password,(err,isMatch)=>{

                if(!err) resolve(isMatch)

                else reject(err)

            })

        })

    }

}

上面的代码声明了一个实例方法,方法叫做comparePassword,然后传递两个参数,一个是客户端密码,一个是数据库取出来的密码。用bcrypt提供的compare方法就可以比对,最后包装成Promise返回就可以了。

编写登录的Api接口

进入service/appApi/user.js,增加一个login路由,并在路由内写入业务逻辑代码。

/*登录的实践 */

router.post('/login',async(ctx)=>{

    //得到前端传递过来的数据

    let loginUser = ctx.request.body

    console.log(loginUser)

    let userName = loginUser.userName

    let password = loginUser.password

    //引入User的model

    const User = mongoose.model('User')

    //查找用户名是否存在,如果存在开始比对密码

   await User.findOne({userName:userName}).exec().then(async(result)=>{

        console.log(result)

        if(result){

           //console.log(User)

            //当用户名存在时,开始比对密码

            let newUser = new User()  //因为是实例方法,所以要new出对象,才能调用

            await newUser.comparePassword(password,result.password)

            .then( (isMatch)=>{

                //返回比对结果

                ctx.body={ code:200, message:isMatch} 

            })

            .catch(error=>{

                //出现异常,返回异常

                console.log(error)

                ctx.body={ code:500, message:error}

            })

        }else{

            ctx.body={ code:200, message:'用户名不存在'}

        } 

    }).catch(error=>{

        console.log(error)

        ctx.body={ code:500, message:error  }

    })

})

 

 

26.登录的前端交互效果制作和登录状态存储

--Login.vue--

axiosLoginUser(){

this.openLoading=true;

axios({

url:url.login,

method: 'post',

data:{

userName:this.username,

password:this.password

}

})

.then(response=>{

 if(response.data.code==200 && response.data.message){ //判断状态和验证结果true

new Promise((resolve,reject)=>{

 localStorage.userInfo= {userName:this.username}//设置本地存储

 setTimeout(()=>{resolve()},500)//认为延迟,因为localStoage没回调函数

}).then(()=>{

  Toast.success('登录成功')

  this.$router.push('/')

}).catch(err=>{ //抛出promise错误

  Toast.fail('登录状态保存失败')

  console.log(err)

})

}else{

Toast.fail('登录失败')

this.openLoading = false;

}

})

.catch((error)=>{ //抛出请求的错误

Toast.fail('登录失败')

this.openLoading=false

})

}

 

created(){ //加载时检查是否保持了用户,有则表示已经登陆

if(localStorage.userInfo){

Toast.success('您已经登录过了')

this.$router.push('/')

}}

 

27.商品详细数据的提纯操作

在service文件夹下,新建一个fsJson.js的文件使用node的fs模块,可以轻松把文件读取到程序中,然后进行便利,把有用的数据提取出来,写入到一个新的数组中

const fs = require('fs')

 

 

fs.readFile('.goods.json', 'utf8', function(err, data){

 

    let newData= JSON.parse(data)

    let i=0

    let pushData=[]

    newData.RECORDS.map(function(value,index){

        if(value.IMAGE1!=null){

            i++

            console.log(value.NAME)

            pushData.push(value)

        }  

    })

    console.log(i)

    console.log(pushData)

});

//写入到新的文件中

 

fs.writeFile('./newGoods.json',JSON.stringify(pushData),function(err){

    if(err) console.log('写文件操作失败');

    else console.log('写文件操作成功');

});

 

28.批量插入商品详情数据到MongoDB中

建立Goods的Schema

建立servic/database/schema/Goods.js文件,然后根据我们的数据表结构建立模型

const mongoose = require('mongoose')    //引入Mongoose

const Schema = mongoose.Schema          //声明Schema

let ObjectId = Schema.Types.ObjectId    //声明Object类型

 

const goodsSchema = new Schema({

    ID:{unique:true,type:String},

    GOODS_SERIAL_NUMBER:String,

    SHOP_ID:String,

    SUB_ID:String,

    GOOD_TYPE:Number,

    STATE:Number,

    NAME:String,

    ORI_PRICE:Number,

    PRESENT_PRICE:Number,

    AMOUNT:Number,

    DETAIL:String,

    BRIEF:String,

    SALES_COUNT:Number,

    IMAGE1:String,

    IMAGE2:String,

    IMAGE3:String,

    IMAGE4:String,

    IMAGE5:String,

    ORIGIN_PLACE:String,

    GOOD_SCENT:String,

    CREATE_TIME:String,

    UPDATE_TIME:String,

    IS_RECOMMEND:Number,

    PICTURE_COMPERSS_PATH:String

},{

    collections:'Goods'

})

 

mongoose.model('Goods',goodsSchema)

 

 

批量插入数据库的路由方法

新建一个service/appApi/goods.js以后关于商品的操作就都在这个api文件中编写了

const Koa = require('koa')

const app = new Koa()

const Router = require ('koa-router')

let router = new Router()

 

const mongoose = require('mongoose')

const fs = require('fs')

 

 

router.get('/insertAllGoodsInfo',async(ctx)=>{

  

     fs.readFile('./goods.json','utf8',(err,data)=>{

        data=JSON.parse(data)

        let saveCount=0

        const Goods = mongoose.model('Goods')

        data.map((value,index)=>{

            console.log(value)

            let newGoods = new Goods(value)

            newGoods.save().then(()=>{

                saveCount++

                console.log('成功'+saveCount)

            }).catch(error=>{

                 console.log('失败:'+error)

            })

        })

      

        

    })

    ctx.body="开始导入数据"

 

 

})

 

module.exports=router;

把路由加入到index.js里

let goods = require('./appApi/goods.js')

router.use('/goods',goods.routes())

 

运行一下了http://localhost:3000/goods/insertAllGoodsInfo,运行结束后,可以到数据库看一下插入情况

 

29.商品大类的Shema建立和导入数据库

编写Category的Schema

const mongoose = require('mongoose')    //引入Mongoose

const Schema = mongoose.Schema          //声明Schema

const categorySchema = new Schema({

    ID:{unique:true,type:String},

    MALL_CATEGORY_NAME:{type:String},

    IMAGE:{type:String},

    TYPE:{type:Number},

    SORT:{type:Number},

    COMMENTS:{type:String}

})

mongoose.model('Category',categorySchema)

插入Mongodb数据库

1. 用fs读取category.json的数据
2. 把数据进行循环存入数据库。

router.get('/insertAllCategory',async(ctx)=>{

    fs.readFile('./data_json/category.json','utf8',(err,data)=>{

        data=JSON.parse(data)

        let saveCount=0

        const Category = mongoose.model('Category')

        data.RECORDS.map((value,index)=>{

            console.log(value)

            let newCategory = new Category(value)

            newCategory.save().then(()=>{

                saveCount++

                console.log('成功'+saveCount)

            }).catch(error=>{

                 console.log('失败:'+error)

            })

        })     

    })

    ctx.body="开始导入数据

})

 

然后访问http://localhost:3000/goods/insertAllCategory,数据就可以顺利插入到数据库里

 

30.商品子类的Shema建立和导入数据库

categorySub的Schema建立

const mongoose = require('mongoose')    //引入Mongoose

const Schema = mongoose.Schema          //声明Schema

 

const categorySubSchema = new Schema({

    ID:{unique:true,type:String},

    MALL_CATEGORY_ID:{type:String},

    MALL_SUB_NAME:{type:String},

    COMMENTS:{type:String},

    SORT:{type:Number}

})

 

mongoose.model('categorySubSchema',categorySubSchema)//'CategorySub'表名,数据库里会自动加上S

保存到数据库的业务逻辑

router.get('/insertCategory_sub',async(ctx)=>{

fs.readFile("./data_json/category_sub.json","utf8",(err,data)=>{

data=JSON.parse(data)

let saveCount=0

const Categorysub = mongoose.model('categorySubschema') //把Category这个表模块引过来

data.RECORDS.map((value,index)=>{

let newCategorysub = new Categorysub(value)//new一个Category,并把value对象传进去

newCategorysub.save().then(()=>{

saveCount++

  console.log('成功'+saveCount)

}).catch(error=>{

  console.log('失败'+error)

})

})

})

ctx.body="开始导入Category数据"

})

module.exports = router

 

  1. 编写商品详情页的数据接口

直接在service/appApi/goods.js里,新编写一个路由业务逻辑,并用findeOne的形式查找出一条商品数据

router.post('/getDetailGoodsInfo',async(ctx)=>{  //**获取商品详情信息的接口

try{

let goodsId = ctx.request.body.goodsId

const Goods = mongoose.model('Goods')

console.log(goodsId)

let result= await Goods.findOne({ID:goodsId}).exec()

ctx.body={code:200,message:result}

}catch(error){

ctx.body={code:500,message:error}

}

})

新建/src/components/pages/Goods.vue文件 测试接口运行情况,

data(){

return{

        goodsId:"775e575ce28a4f89b1dfe2c99eb08ae7",//先用一个数据测试

}

},

created(){

this.getInfo()

},

methods: {

getInfo() {

axios({

url:url.getDetailGoodsInfo,

method:"post",

data:{ goodsId:this.goodsId,}

})

.then(response =>{

console.log(response)

})

.catch(err=>{

console.log(err)

})

}

}

设置路由

import Goods from '@/components/pages/Goods'

routes: [

{path:"/goods",name:"Goods",component:Goods}

]

 

32.商品详情页路由的制作和参数的传递

------hotgoodComponent.vue------

修改这个文件主要是让它具有跳转能力和传递参数的能力:
1. 在这个组件里我们新加入一个props,接受goodsId.
2. 编写一个页面跳转的方法,这里起名为goGoodsPage
3. 绑定单击事件进行跳转@click='goGoodsPage'

 

<div class="goods-info" @click="goGoodsPage()">

props:["goodsImage","goodsName","goodsPrice","goodsId"],//添加goodsId

methods:{

goGoodsPage(){

        this.$router.push({name:'Goods',query:{goodsId:this.goodsId}}) //编程式路由

}

}

父组件上添加:goodsId="item.goodsId"这个传值

<goods-info :goodsId="item.goodsId" :goodsImage="item.image" :goodsName="item.name" :goodsPrice="item.price">

</goods-info>

 

点击热门商品就会跳转到:http://localhost:8080/#/goods?goodsId=fb0f913950944b66a97ae262ad14609a

 

33.商品详情的页面模板编写

--Good.vue--

<template>

<div>

<div class="navbar-div">

<van-nav-bar

title="商品详情"

left-text="返回"

left-arrow

@click-left="onClickLeft"

class="navbar-con"

/>

</div>

<div class="topimage-div">

<img :src="goodsInfo.IMAGE1" width="100%" />

<div class="goods-name">{{goodsInfo.NAME}} </div>

<div class="goods-price">价格 :¥{{goodsInfo.PRESENT_PRICE | moneyFilter}}元</div>

</div>

 

<div>

<van-tabs swipeable sticky>  //滑动切换和吸顶效果  全局导入tabs tab  Vue.use(Tab).use(Tabs)

<van-tab title="商品详情">

<div class="detail" v-html="goodsInfo.DETAIL">

</div>

     </van-tab>

    

<van-tab title="评论">

 暂无评论

</van-tab>

</van-tabs>

</div>

 

<div class="goods-bottom">

<div>

<van-button size="large" type="warning" @click="addGoodsToCart">加入购物车</van-button>

</div>

<div>

<van-button size="large" type="danger">直接购买</van-button>

</div>

</div>

</div>

</template>

 

<script>

import axios from "axios"

import url from '@/serviceAPI.config.js'

import {Toast} from 'vant'

import {toMoney} from '@/filter/moneyFilter.js'

export default {

data(){

return{

goodsId:"",

goodsInfo:{},

}

},

filters:{

moneyFilter(money){

return toMoney(money)

}

},

created(){

this.goodsId = this.$route.query.goodsId

this.getInfo()

},

methods: {

getInfo() {

axios({

url:url.getDetailGoodsInfo,

method:"post",

data:{

goodsId:this.goodsId,

}

})

.then(response =>{

if(response.data.code== 200 && response.data.message){//一定要多判断,状态为200,而且有数据存在

this.goodsInfo= response.data.message

}else{

Toast('服务器错误,数据获取失败')

}

})

.catch(err=>{

console.log(err)

})

},

onClickLeft(){

this.$router.go(-1) //顶部左侧后退

},

},

}

</script>

<style scoped lang="less">

.navbar-con{

background-color:#e5017d;

color:#fff;

}

.van-button--warning{height:40px;line-height: 40px}

.van-button--danger{height:40px;line-height: 40px}

.goods-name{

background-color: #fff;

margin-left:4px;

}

.goods-price{

background-color: #fff;

font-size:14px;

font-weight: 500;

color:#f44;

margin-left:5px;

}

.topimage-div{

margin-bottom:10px;

}

.detail {

font-size:0px; //图片下面有一个空白字符,导致有一个空白行

}

.goods-bottom{

position:fixed;

bottom:0px;

left:0px;

background-color: #FFF;

width:100%;

display: flex;

flex-direction: row;

flex-flow:nowrap;

background:#FFF;

}

.goods-bottom > div {

flex:1;

padding:5px;

}

</style>

 

 

34.商品类别页后台接口编写

//读取大类分类接口

router.get("/getCategoryList",async(ctx)=>{

try{

const Category = mongoose.model("Category")

let result = await Category.find().exec()

ctx.body={

code:200,

message:result

}

}catch(err){

console.log({code:500,message:err})

}

})

 

//读取小类分类接口

router.post("/getCategorySubList",async (ctx)=>{

try{

let categoryId = ctx.request.body.categoryId

const CategorySub = mongoose.model("categorySubschema")

//let cateoryId = 2

let result = await CategorySub.find({MALL_CATEGORY_ID:cateoryId}).exec()

ctx.body={code:200,message:result} //页面返回

}catch(err){

console.log({code:500,messate:err})

}

})

/**根据类别获取商品列表 */

 

router.get('/getGoodsListByCategorySubID',async(ctx)=>{

try{

//let categorySubId = ctx.request.body.categoryId

let categorySubId = '2c9f6c946016ea9b016016f79c8e0000'  //测试使用,真正获取是改成POST

const Goods = mongoose.model('Goods')

let result = await Goods.find({SUB_ID:categorySubId}).exec()

ctx.body={code:200,message:result}

}catch(err){

ctx.body={code:500,message:err}

}

})

 

 

34.商品类别页的前端制作

配置列表页路由

import CategoryList from '@/components/pages/CategoryList'

Vue.use(Router)

 

export default new Router({

  routes: [

    {path: '/',name: 'ShoppingMall',component: ShoppingMall},

    {path: '/register',name: 'Register',component: Register},

    {path: '/login',name: 'Login',component: Login},

    {path: '/Goods',name: 'Goods',component: Goods},

    {path: '/CategoryList',name: 'CategoryList',component: CategoryList},

  ]

})

 

新建:CategoryList.vue

 

import axios from 'axios'

import url from '@/serviceAPI.config.js'

 

getCategory() {

    axios({

        url:url.getCategoryList,

        method:'get',

    })

    .then(response=>{

 

        console.log(response)

 

        if(response.data.code == 200 && response.data.message ){

            

        }else{

            Toast('服务器错误,数据取得失败')

        }

        

    })

    .catch(error=>{

        console.log(error)

    }) 

}

 

在声明周期里加入getCategory方法

created(){

    this.getCategory();

},

 

 

  1. 商品列表页的左侧大类交互效果制作

-------CategoryList.vue----------

<van-col span="6">

<div id="leftNav" ref="leftNav">

<ul>  //template部分利用li标签把数据循环出来

<li @click="clickCategory(index)" :class="{categoryActice:categoryIndex==index}" v-for="(item , index) in category" :key="index">  //改变li 颜色  点击后交互效果制作-反白操作

  {{item.MALL_CATEGORY_NAME}}

</li>

</ul>

</div>

</van-col>

 

data() {

return {

category: [], //在data属性里注册category变量为数组类型

categoryIndex:0,//li的颜色索引

}

},

methods: {

getCategory() {

axios({

url:url.getCateGoryList,

method:'get',

})

.then(response=>{

        if(response.data.code == 200 && response.data.message ){

     this.category = response.data.message //绑定数据

}else{

     Toast('服务器错误,数据取得失败')

}

})

.catch(error=>{

    console.log(error)

})

},

clickCategory(index){

this.categoryIndex=index   //点击时改变li的颜色索引

    },

},

----css---

.categoryActice{

background-color: #fff;

}

 

36.右侧小分类制作:一二级分类的联动效果制作

 

 

<van-col span="6">

<div id="leftNav" ref="leftNav">

<ul>

<li    

 @click="clickCategory(index,item.ID)" //点击传入小类

:class="{categoryActice:categoryIndex==index}"

v-for="(item , index) in category" :key="index">

      {{item.MALL_CATEGORY_NAME}}

</li>

</ul>

</div>

</van-col>

 

<van-col span="18">

<div class="tabCategorySub">

<van-tabs v-model="active" >

<van-tab v-for="(item,index) in categorySub" :key="index" :title="item.MALL_SUB_NAME">

  //用Vant的Tabs组建实现联动

</van-tab>

</van-tabs>

</div>

</van-col>

 

data() {

return {

category: [],

categoryIndex:0,

categorySub:[], //小类类别

active:0, //激活标签的值

}

},

 

//根据大类ID读取小类类别列表

getCategorySubByCategoryID(categoryId){

axios({

url:url.getCateGorySubList,

method:'post',

data:{categoryId}

})

.then(response=>{

console.log(response)

if(response.data.code==200 && response.data.message){

this.categorySub=response.data.message

this.active=0

}

})

.catch(error=>{

     console.log(error)

})

},

 

clickCategory(index,categoryId){ //点击时获取小类ID

this.categoryIndex=index

this.getCategorySubByCategoryID(categoryId) //传给getCategorySubByCategoryID

}

//刚进入页面就显示第一个小类ID

created(){

this.getCategory()

this.getCategorySubByCategoryID(1)

}

 

  1. 商品列表页上拉加载效果的实现

 

<div id="list-div" ref="listdiv">

<van-list

v-model="loading"

:finished="finished"

@load="onLoad"

>

<div class="list-item" v-for="item in goodList" :key="item">

{{item}}

</div>

</van-list>

</div>

 

//上拉加载方法  先用数字模拟一整行,测试结果

onLoad(){

setTimeout(()=>{

for(let i=0;i<10;i++){

this.goodList.push(this.goodList.length+1)

}

this.loading=false;

if (this.goodList.length >= 40) {

this.finished = true;

}

},500)

},

mounted(){

let winHeight = document.documentElement.clientHeight

this.$refs.leftNav.style.height=winHeight -46-50 +'px' //操作dom

this.$refs.listdiv.style.height=winHeight -90-50 +'px' //给与滚动的高度

//document.getElementById("list-div").style.height=winHeight -90-50 +'px'

},

 

 

//给与基本样式

.list-item{

border-bottom: 1px solid #f0f0f0;

background-color: #fff;

line-height:100px;

}

#list-div{

overflow: scroll;

}

 

Logo

前往低代码交流专区

更多推荐