Vue.js+Koa2电商实战笔记
1.项目初始化 npm -v //确认安装node基本环境npm i vue-cli -g //-g 全局安装vue-clivue init webpack //使用webpack模板安装 不安装eslint,eslint用于多人开发,按照谷歌和facebook的代码标准开发。unit --no //大公司有专门测试人员ese --no packa...
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接口 都写在里面。
- 优雅引入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. 首页路由的配置:
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')
}
}
- 首页轮播图制作
Swipe 轮播
https://youzan.github.io/vant/#/zh-CN/swipe
Lazyload 图片懒加载
https://youzan.github.io/vant/#/zh-CN/lazyload
- easyMock和axios的使用
Easy-mock 创建接口数据
安装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
}
- 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
}
}
},
- 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(...)
- 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
- 建立目录 c:\data\db
- 运行:mongod
Robo 3T
建立连接
- 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
- 编写商品详情页的数据接口
直接在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();
},
- 商品列表页的左侧大类交互效果制作
-------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)
}
- 商品列表页上拉加载效果的实现
<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;
}
更多推荐
所有评论(0)