项目--4
1、回顾
2、调整首页的布局
<van-list
v-if="flag"
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<Prolist :prolist="prolist"/>
</van-list>
<div v-else>
登陆之后才能看到更多的信息
<router-link to="/login">登陆</router-link>
</div>
3、加入购物车的功能
接口: /cart/add?userid=1&proid=2&num=1&token=111
3.1 修改登陆接口,登陆成功返回加字段 userid 和 username,前端保存到本地
- day06/myapp/routes/users.js
// 实现登陆功能
router.post('/login', (req, res, next) => {
// 1、获取表单信息
let { tel, password } = req.body;
// 2、依据手机号查询有没有该用户
sql.find(User, { tel }, { _id: 0 }).then(data => {
// 2.1 判断有么有该用户
if (data.length === 0) {
// 2.2 没有该用户
res.send(utils.unregister)
} else {
// 2.3 有该用户,验证密码
// 2.3.1 获取数据库中的密码
let pwd = data[0].password;
// 2.3.2 比较 输入的 密码和数据库中的密码
var flag = bcrypt.compareSync(password, pwd) // 前为输入,后为数据库
if (flag) {
// 2.3.3 密码正确,生成token
let userid = data[0].userid
let username = data[0].username // +++++++++++++++++
let token = jwt.sign({ userid }, 'daxunxun', {
// expiresIn: 60*60*24// 授权时效24小时
expiresIn: 60*60*24*7// 授权时效7天
})
res.send({
code: '10010',
message: '登陆成功',
token: token,
userid, // ++++++++++++++++++++++++++++++++++++++++++++++++++++++
username // +++++++++++++++++++++++++++++++++++++++++++++++++++++
})
} else {
// 2.3.4 密码错误
res.send({
code: '10100',
message: '密码错误'
})
}
}
})
})
- 登陆页面将userid保存到本地
day09/src/views/login/index.vue
methods: {
login () {
console.log('11111')
if (this.tel === '' || this.teltip !== '') {
this.tip = '手机号格式错误'
return
}
if (this.password === '' || this.passwordtip !== '') {
this.tip = '密码格式错误'
return
}
// 登陆
axios.post('/users/login', {
tel: this.tel,
password: this.password
}).then(res => {
console.log(res.data)
/***
* 10086 未注册
* 10100 密码错误
* 10010 登陆成功
*/
if (res.data.code === '10086') {
this.tip = '该用户未注册,请先注册'
} else if (res.data.code === '10100') {
this.tip = '密码错误'
} else {
// 此时为登陆成功,获取token信息存入本地
this.tip = ''
const token = res.data.token
localStorage.setItem('token', token)
localStorage.setItem('userid', res.data.userid) // +++++++++++
localStorage.setItem('username', res.data.username) // +++++++++++++
this.$router.back()
}
})
}
}
3.2 详情页面点击加入购物车 执行加入购物车逻辑
<van-goods-action-icon icon="cart-o" @click="toCart" text="购物车" />
<van-goods-action-button type="warning" @click="addCart" text="加入购物车" />
methods: {
addCart () {
let userid = localStorage.getItem('userid')
let token = localStorage.getItem('token')
let proid = this.proid
let num = 1
let url = '/cart/add?userid=' + userid + '&proid=' + proid + '&num=' + num + '&token=' + token
axios.get(url).then(res => {
// 如果未登录,跳转到登陆
if (res.data.code === '10119') {
this.$router.push('/login')
} else {
Toast('加入购物车成功')
}
})
},
toCart () {
this.$router.push('/cart')
}
}
4、查看购物车
<template>
<div class="box">
<header class="header">购物车头部</header>
<div class="content">
<ul class="prolist" v-if="flag">
<li class="proitem" v-for="(item, index) of cartlist" :key="item.proid" @click="toDetail(item.proid)">
<div class="itemimg">
<img :src="item.proimg" alt="">
</div>
<div class="iteminfo">
<h2>{{ index }}-{{ item.proname }}</h2>
<h3>{{ item.brand }}</h3>
<p>{{ '¥' + item.price }}</p>
<div>
<button>-</button>{{ item.num }}<button>+</button>
</div>
</div>
</li>
</ul>
<div v-else>
购物车空空如也,<router-link to="/home">去购物</router-link>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
data () {
return {
cartlist: [],
flag: false // false 表示没有数据
}
},
created () {
let userid = localStorage.getItem('userid')
let token = localStorage.getItem('token')
let url = '/cart?userid=' + userid + '&token=' + token
axios.get(url).then(res => {
// 10119 未登录
// 11000 没有数据
// 有数据
if (res.data.code === '10119') {
this.$router.push('/login')
} else if (res.data.code === '11000') {
this.flag = false
} else {
this.flag = true
this.cartlist = res.data.data
}
})
}
}
</script>
<style lang="scss">
@import '@/lib/reset.scss';
.prolist {
@include rect(100%, auto);
.proitem {
@include rect(100%, 1rem);
@include border(0 0 1px 0, #efefef, solid); // 设定的是一个物理像素
@include flexbox();
.itemimg {
@include rect(1rem, 1rem);
img {
@include rect(0.9rem, 0.9rem);
@include border(1px, #f66, solid);
@include margin(0.05rem);
@include display(block);
}
}
.iteminfo {
@include flex();
}
}
}
</style>
5、购物车的数量加减、删除、选择
<div>
<button @click="reduce(item)">-</button>{{ item.num }}<button @click="add(item)">+</button>
<button @click="deleteItem(item, index)"> 删除 </button>
</div>
methods: {
// 不管加还是减,记住传递参数为 对象
reduce (item) {
let token = localStorage.getItem('token')
// 如果数量为1,不可以再减
let num = item.num > 1 ? --item.num : 1
// 请求数据库更新
axios.get('/cart/update?token=' + token + '&cartid=' + item.cartid + '&num=' + num).then(res => {
// 未登录
if (res.data.code === '10119') {
this.$router.push('/login')
} else {
// 更新成功 --- 更新对象的属性值-- 可以引起视图的二次渲染 -- 为什么传对象
item.num = num
}
})
},
add (item) {
let token = localStorage.getItem('token')
let num = ++item.num
console.log(num)
axios.get('/cart/update?token=' + token + '&cartid=' + item.cartid + '&num=' + num).then(res => {
console.log('cart', res.data)
if (res.data.code === '10119') {
this.$router.push('/login')
} else {
item.num = num
}
})
},
// 删除 需要传递对象和索引值 --- 目的是 删哪个 截取那个数据
deleteItem (item, index) {
let token = localStorage.getItem('token')
axios.get('/cart/delete?token=' + token + '&userid=' + item.userid + '&proid=' + item.proid).then(res => {
if (res.data.code === '10119') {
this.$router.push('/login')
} else {
// 删除成功 --- 调整视图
this.cartlist.splice(index, 1)
}
})
}
}
6、计算总价以及总数量
- 布局
<div class="submitOrder">
<ul>
<li>
<p>
总数: <span>11</span>
</p>
</li>
<li>
<p>
合计: <span></span>
</p>
</li>
<li>
提交订单
</li>
</ul>
</div>
.submitOrder {
@include rect(100%, 0.5rem);
@include border(1px 0 0 0, #f66, solid);
@include fixed();
@include bottom(0.5rem);
@include background-color(#fff);
ul {
@include rect(100%, 100%);
@include flexbox();
li {
@include flexbox();
@include justify-content();
@include align-items();
&:nth-child(1) {
@include flex(4);
}
&:nth-child(2) {
@include flex(4);
}
&:nth-child(3) {
@include flex(2);
@include background-color(#f66);
@include color(#fff);
}
p {
span{
@include color(#f66);
}
}
}
}
}
- 使用计算属性获取总价格总数量
<li>
<p>
总数: <span>{{ totalNum }}</span>
</p>
</li>
<li>
<p>
合计: <span>{{ totalPrice }}</span>
</p>
</li>
computed: {
totalNum () {
let num = 0
this.cartlist.map((item) => {
num += item.num
})
return num
},
totalPrice () {
let totalPrice = 0
this.cartlist.map((item) => {
totalPrice += item.num * item.price
})
return totalPrice.toFixed(2)
}
},
7、添加选择和全选
-
给每一个列表前添加一个checkbox,底部添加一个checkbox --- cart/index.1.vue
-
修改购物车列表,给每一条数据添加一个字段 flag,设置其值为true
created () {
let userid = localStorage.getItem('userid')
let token = localStorage.getItem('token')
let url = '/cart?userid=' + userid + '&token=' + token
axios.get(url).then(res => {
// 10119 未登录
// 11000 没有数据
// 有数据
if (res.data.code === '10119') {
this.$router.push('/login')
} else if (res.data.code === '11000') {
this.flag = false
} else {
this.flag = true
// ++++++++++++++++++++++++++++++
let arr = res.data.data
arr.map(item => { // 处理数据。每一项添加一个字段
item.flag = true
})
this.cartlist = arr
// ++++++++++++++++++++++++++++
}
})
},
- 选中计算总数和总价
computed: {
totalNum () {
let num = 0
this.cartlist.map((item) => {
item.flag ? num += item.num : num += 0
})
return num
},
totalPrice () {
let totalPrice = 0
this.cartlist.map((item) => {
item.flag ? totalPrice += item.num * item.price : totalPrice += 0
})
return totalPrice.toFixed(2)
}
},
- 全选 --- 点击全选 控制列表的选择 ----- 侦听属性 -------- 废弃
<input type="checkbox" v-model="all">全选
data () {
return {
cartlist: [],
flag: false, // false 表示没有数据
all: true // ++++++++++++++++++++++++++++++++
}
},
watch: {
all (newval) {
if (newval) {
this.cartlist.map(item => {
item.flag = true
})
} else {
this.cartlist.map(item => {
item.flag = false
})
}
}
},
- 点击列表的选择框 改变 全选
// 添加点击事件
<input type="checkbox" v-model="item.flag" @change="changeFlag(item)">
changeFlag (item) {
console.log(item)
if (item.flag) { // 如果此值为真 --- 该项是被选中的,判断别的项是不是选中
// 如果b为真,表示所有的都被选中
// arr.every() 所有的条件为真结果才为真
let b = this.cartlist.every(item => {
return item.flag === true
})
if (b) { // 全部被选中
this.all = true
} else {
this.all = false
}
} else { // 有某一项未被选中,肯定全部不被选中
this.all = false
}
}
- 废弃了侦听属性改变,通过事件 点击全选改变列表
// 全选添加事件
<input type="checkbox" v-model="all" @change="selectAll">全选
selectAll () {
if (this.all) {
this.cartlist.map(item => {
item.flag = true
})
} else {
this.cartlist.map(item => {
item.flag = false
})
}
}
- 原则上购物车业务逻辑已经完毕结束,但是实际上环没有
8、将购物车选中的商品提交到确认订单页面
- 1、点击提交订单是,获取选中的 商品(包含商品的id, 商品的数量,外加用户的id),提交这些信息到 相关的订单接口
- 2、将选中的购物车的列表的相对应的 购物车的数据(以cartid),将此数据复制到订单的数据库,并且删除购物车中的该项数据库
9、分类
9.1 编写相关接口
day06/myapp/routes/pro.js
// 获取分类类型对应的品牌
router.get('/category', (req, res, next) => {
let { type } = req.query
sql.find(Pro, { type }, {_id: 0, brand:1, barndimg: 1}).then(data => {
// 数组去重 https://www.cnblogs.com/le220/p/9130656.html
let obj = {}
// 利用reduce方法遍历数组,reduce第一个参数是遍历需要执行的函数,第二个参数是item的初始值
data = data.reduce((item, next) => {
obj[next.brand] ? '' : obj[next.brand] = true && item.push(next)
return item
}, [])
res.send({
code: '200',
message: '获取分类类型列表',
data: data
})
})
})
// 获取品牌类型对应的产品
router.get('/brandcategory', (req, res, next) => {
let { brand } = req.query
sql.find(Pro, { brand: brand }, {_id: 0}).then(data => {
res.send({
code: '200',
message: '获取品牌分类列表',
data: data
})
})
})
// 搜索
router.get('/search', (req, res, next) => {
let { text } = req.query
sql.find(Pro, { proname: eval('/' + text + '/') }, {_id: 0}).then(data => {
res.send({
code: '200',
message: '搜索列表',
data: data
})
})
})
9.2 编写分类页面结构
<template>
<div class="box">
<header class="header">分类头部</header>
<div class="content">
<div class="kind">
<div class="left">
<ul>
<li>手机</li>
<li>安装</li>
</ul>
</div>
<div class="right"></div>
</div>
</div>
</div>
</template>
<style lang="scss">
@import '@/lib/reset.scss';
.content {
.kind {
@include rect(100%, 100%);
@include flexbox();
.left {
@include rect(1rem, 100%);
@include background-color(#00f);
ul {
@include rect(100%, 100%);
li{
@include rect(100%, 0.36rem);
@include border(0 0 1px, #efefef, solid);
@include line-height(0.36rem);
@include text-align();
}
}
}
.right {
@include flex();
@include rect(auto, 100%);
@include background-color(#0f0);
@include overflow();
}
}
}
</style>
9.3 获取左侧的列表数据并且渲染
<div class="left">
<ul>
<li v-for="(item, index) of kindlist" :key="index">{{ item }}</li>
</ul>
</div>
data () {
return {
kindlist: []
}
},
created () {
let token = localStorage.getItem('token')
let url = '/pro/type?type=type&token=' + token
axios.get(url).then(res => {
console.log(res.data)
if (res.data.code === '10119') {
this.$router.push('/login')
} else {
this.kindlist = res.data.data
}
})
}
9.4 点击左侧的类型获取品牌数据并且渲染
// @click="getBrand(item)"
<ul>
<li v-for="(item, index) of kindlist" @click="getBrand(item)" :key="index">{{ item }}</li>
</ul>
data () {
return {
kindlist: [],
brandlist: []
}
},
methods: {
getBrand (item) {
let token = localStorage.getItem('token')
let url = '/pro/category?token=' + token + '&type=' + item
// 接口为新增接口
axios.get(url).then((res) => {
console.log(res.data)
if (res.data.code === '10119') {
this.$router.push('/login')
} else {
this.brandlist = res.data.data
}
})
}
}
<div class="right">
<div class="top">
<ul>
<li v-for="(item, index) of brandlist" :key = "index">
<!-- <img :src="item.barndimg" alt=""> -->
{{ item.brand }}
</li>
</ul>
</div>
</div>
9.5 点击品牌获取列表的数据并且渲染
引入列表组件,组件传值即可
// @click="getlist(item)
<div class="top">
<ul>
<li v-for="(item, index) of brandlist" :key = "index" @click="getlist(item)">
<!-- <img :src="item.barndimg" alt=""> -->
{{ item.brand }}
</li>
</ul>
</div>
// +++++++++++++++
components: {
Prolist
},
data () {
return {
kindlist: [],
brandlist: [],
prolist: [] // ++++++++++++++++++
}
},
methods: {
// 注意对应的接口
getlist (item) {
let token = localStorage.getItem('token')
let url = '/pro/brandcategory?token=' + token + '&brand=' + item.brand
axios.get(url).then((res) => {
console.log(res.data)
if (res.data.code === '10119') {
this.$router.push('/login')
} else {
this.prolist = res.data.data
}
})
}
}
9.6 添加分类的默认值
- 左侧默认以及点击样式
// :class="kindindex === index ? 'active' : ''"
// @click="getBrand(item, index)"
<li :class="kindindex === index ? 'active' : ''" v-for="(item, index) of kindlist" @click="getBrand(item, index)" :key="index">{{ item }}</li>
data () {
return {
kindlist: [],
brandlist: [],
prolist: [],
kindindex: 0 // ++++++++++++++
}
},
methods: {
getBrand (item, index) {
this.kindindex = index
}
}
- 右侧默认以及点击样式
// @click="getlist(item, index)"
// :class="brandindex === index ? 'active' : ''"
<li :class="brandindex === index ? 'active' : ''" v-for="(item, index) of brandlist" :key = "index" @click="getlist(item, index)">
<!-- <img :src="item.barndimg" alt=""> -->
{{ item.brand }}
</li>
data () {
return {
kindlist: [],
brandlist: [],
prolist: [],
kindindex: 0,
brandindex: 0 // ++++++++++++++++++++++++++++
}
},
methods: {
getBrand (item, index) {
this.kindindex = index
this.brandindex = 0 // 点击左侧 永远选中的是右侧的第一个
},
getlist (item, index) {
this.brandindex = index
}
}
- 默认显示第一个分类的品牌
created () {
let token = localStorage.getItem('token')
let url = '/pro/type?type=type&token=' + token
axios.get(url).then(res => {
console.log(res.data)
if (res.data.code === '10119') {
this.$router.push('/login')
} else {
this.kindlist = res.data.data
this.getBrand(this.kindlist[0], 0) // +++++++++++++++++
}
})
},
- 默认显示第一个品牌的列表
getBrand (item, index) {
this.kindindex = index
this.brandindex = 0
let token = localStorage.getItem('token')
let url = '/pro/category?token=' + token + '&type=' + item
axios.get(url).then((res) => {
console.log(res.data)
if (res.data.code === '10119') {
this.$router.push('/login')
} else {
this.brandlist = res.data.data
this.getlist(this.brandlist[0], 0) // +++++++++++++++++++++
}
})
},
10 搜索功能
一般首先显示的搜索框不是真正的 input
views/search/index.vue + router/index.js
长风破浪会有时,直挂云帆济沧海