vueProject_尚品汇
项目源码
项目配置
+ 浏览器自动打开
- app/package.json
```
"scripts": {
"serve": "vue-cli-service serve --open",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
```
+ 关闭eslint校验工具
- app/vue.config.js
```
module.exports = {
//关闭eslint
lintOnSave: false
}
```
+ src别名
+ 在css中写的时候要写成~@
- app/jsconfig.json
```
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": [
"src/*"
]
}
},
"exclude": [
"node_modules",
"dist"
]
}
```
+ 安装less-loader
+ npm install --save less less-loader@5
组件通信
+ 组件通信方式
+ props
+ 父子通信
+ 书写方式:['todo'] {type:Array} {type:Array,default:[]}
+ 自定义事件
+ 子组件给父组件传递数据
+ $on $emit
+ 全局事件总线
+ 万能
+ vue.prototype.$bus = this;
+ pubsub-js
+ 万能
+ react中使用较多
+ vuex
+ 万能
+ 插槽
+ 父子组件通信...(一般结构)
+ 默认插槽
+ 具名插槽
+ 作用域插槽
+ 原生DOM(如button)可以触发click事件
+ 对于自定义标签(如组件标签)上的@click,相当于自定义事件,一般是不可以触发的点击事件的,需要添加修饰符.native,进行修饰
+ v-model
+ 原理是通过 :value + @input 实现的
+ 在子组件上可以通过v-model实现父子数据同步
```
//父组件
<child :value="msg" @input="msg = $event" />
//v-model简写
<child v-model="msg" />
//子组件
<input :value="value" @input="$emit('input',$event.target.value)" >
//...javascript
props:["value"],
```
+ sync
+ 原理是通过 :value + @update:value 实现的
+ 子组件可以通过sync修饰符实现父子数据同步
```
//父组件
<child :value="data" @update:value="data=$event" />
//sync简写
<child :data.sync="data"/>
//子组件
<button @click="$emit('update:value',data-=100)" ></button>
//...javascript
props:['data']
```
+ $attr and $listeners
+ $attr $listeners 都属于组件的属性
+ 可以获取从父组件传递过来的数据
```
//二次封装elementUI中的组件例如button
<myButton type="success" icon="el-icon-delete" size="mini" title="提示按钮" @click="handel"></myButton>
//子组件
<el-button v-bind="$attr" v-on="$listeners" ></el-button>
//v-on不可用@替换
```
+ $children and $parent
+ 每个组件都有$children $parent两个属性,该属性返回当前组件的所有组件信息
跨域问题
+ 同源策略,协议 端口 域名 不同导致
+ 解决:jsonp cors 代理
+ 代理
- app/vue.config.js
```
module.exports = {
//关闭eslint
lintOnSave: false,
devServer: {
proxy: {
'/api': {
target: 'http://39.98.123.211'
}
},
}
}
```
vueRouter
+ 安装vue-router
+ npm install --save vue-router
+ 路由组件
- @/pages/xxx/index.vue
+ 路由配置
- @/router/index.js
```
import Vue from "vue";
import VueRouter from 'vue-router';
Vue.use(VueRouter);
import Home from '@/pages/Home'
import Login from '@/pages/Login'
import Search from '@/pages/Search'
import Register from '@/pages/Register'
export default new VueRouter({
routes: [
{
path: "/home",
component: Home,
meta: { show: true }
},
{
path: "/search/:keyword?",
component: Search,
meta: { show: true },
name: "search",
props: ($route) => ({keyword:$route.query.keyword})
},
{
path: "/login",
component: Login,
meta: { show: false }
},
{
path: "/register",
component: Register,
meta: { show: false }
},
{
path: "*",
redirect: "/home"
}
]
})
```
+ 注册路由
- app/main.js
```
import router from '@/router';
new Vue({
render: h => h(App),
router,
}).$mount('#app')
```
+ 所有的路由和非路由组件身上都会拥有$router $route属性
+ $router:一般进行编程式导航进行路由跳转
+ $route: 一般获取路由信息(name path params等)
+ 路由跳转方式
+ 声明式导航router-link标签 ,可以把router-link理解为一个a标签,它 也可以加class修饰
+ 编程式导航 :声明式导航能做的编程式都能做,而且还可以处理一些业务
+ push replace
+ 路由传参
+ query
+ 不属于路径当中的一部分,类似于get请求,地址栏表现为 /search?k1=v1&k2=v2
+ 对应的路由信息 path: "/search"
+ params
+ 属于路径当中的一部分,需要注意,在配置路由的时候,需要占位 ,地址栏表现为 /search/v1/v2
+ 对应的路由信息要修改为path: "/search/:keyword" 这里的/:keyword就是一个params参数的占位符
+ 占位符后加?可表示改参数可不传
+ 加?后若传递空串,通过undefined解决
+ this.$router.push({name:"Search",query:{keyword:this.keyword},params:{keyword:''||undefined}})
多次执行相同的push问题,控制台会出现警告(重写push,replace)
+ 多次push警告问题
+ push是一个promise,promise需要传递成功和失败两个参数,我们的push中没有传递。
- @router/index.js
```
let originPush = VueRouter.prototype.push
let originReplace = VueRouter.prototype.replace
VueRouter.prototype.push = function(location,resole,reject){
if(resole && reject){
originPush.call(this,location,resole,reject)
}else{
originPush.call(this,location,()=>{},()=>{})
}
}
VueRouter.prototype.replace = function(location,resole,reject){
if(resole && reject){
originReplace.call(this,location,resole,reject)
}else{
originReplace.call(this,location,()=>{},()=>{})
}
}
```
封装axios
+ 安装axios
+ cnpm install --save axios
+ https://www.axios-http.cn/
+ 封装axios
- @/api/ajax.js
```
import axios from "axios";
import nprogress from "nprogress";
import "nprogress/nprogress.css";
const requests = axios.create({
baseURL: "/api",
timeout: 5000
})
//请求拦截器
requests.interceptors.request.use((config)=>{
nprogress.start()
return config
})
//响应拦截器
requests.interceptors.response.use((res)=>{
nprogress.done()
return res.data
},(error)=>{
console.log(error)
return Promise.reject(new Error("faild"))
})
export default requests;
```
+ 接口统一封装
- @/api/index.js
```
import requests from "./ajax";
export const reqCategoryList = () => requests({url:'/product/getBaseCategoryList',method:'get'})
//export const reqCategoryList = () => requests.get('/product/getBaseCategoryList')
```
+ usage
```
import {reqCateGoryList} from './api'
reqCateGoryList();
```
nprogress进度条
+ 安装nprogress
+ cnpm install --save nprogress
+ https://www.npmjs.com/package/nprogress
- @/api/request.js
```
import nprogress from "nprogress";
import "nprogress/nprogress.css";
nprogress.start();
nprogress.done();
```
vuex
+ 安装vuex
+ npm install --save vuex
+ vue组件数据仓库
+ https://vuex.vuejs.org/zh/
+ store
- @/store/home/index.js
```
const state = {};
const mutations = {};
const actions = {};
const getters = {};
export default {
state,mutations,actions,getters
}
```
- @/store/index.js
```
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
import home from './home';
import search from './search'
export default new Vuex.Store({
//仓库模块化
modules:{
home,search
}
})
```
- app/main.js
```
import store from './store';
new Vue({
render: h => h(App),
router,
store
}).$mount('#app')
```
+ usage
- @/xxx.vue
```
<script>
import { mapState } from "vuex";
export default {
name: "",
data() {
return {};
},
mounted() {
this.$store.dispatch('getBannerList');
},
methods: {},
computed: {
...mapState({
bannerList: (state) => state.home.bannerList,
}),
}
};
</script>
```
async await
+ 使用封装后的axios,进程异步问题
+ async 标识函数体中await标识的部分优先执行
+ 方法加上async,返回的一定是一个Promise
- @/store/home/index.js
```
const actions = {
async categoryList({commit}){
let res = await reqCategoryList();
if(res.code == 200){
commit("CATEGORYLIST",res.data)
}
}
};
```
loadsh防抖和节流
+ lodash.js
+ https://www.lodashjs.com/
+ 防抖 用户操作很频繁,但是只执行一次,减少业务负担。
_.debounce(func, [wait=0], [options=])
+ 节流 用户操作很频繁,但是把频繁的操作变为少量的操作,使浏览器有充分时间解析代码
_.throttle(func, [wait=0], [options=])
+ 事件高频触发,性能下降,卡顿
- @/components/TypeNav/index.vue
```
methods: {
changeIndex: _.throttle(function (index) {
this.currentIndex = index;
}, 50),
}
```
编程式导航+事件委托
+ 三级标签列表有很多,每一个标签都是一个页面链接,我们要实现通过点击表现进行路由跳转
对于导航式路由,我们有多少个a标签就会生成多少个router-link标签,这样当我们频繁操作时会出现卡顿现象
对于编程式路由,我们是通过触发点击事件实现路由跳转。同理有多少个a标签就会有多少个触发函数。虽然不会出现卡顿,但是也会影响性能
件委派即把子节点的触发事件都委托给父节点。这样只需要一个回调函数goSearch就可以解决,数据由自定义数据传递
- @/components/TypeNav/index.js
```
<div class="all-sort-list2" @click="goSearch">
<!-- 一级 -->
<a :data-categoryName="c1.categoryName" :data-category1Id="c1.categoryId">{{ c1.categoryName }}</a>
<!-- 二级 -->
<a :data-categoryName="c2.categoryName" :data-category2Id="c2.categoryId">{{ c2.categoryName }}</a>
<!-- 三级 -->
<a :data-categoryName="c3.categoryName" :data-category3Id="c3.categoryId">{{ c3.categoryName }}</a>
</div>
methods: {
goSearch(event) {
let element = event.target;
let { categoryname, category1id, category2id, category3id } = element.dataset;
if (categoryname) {
let location = { name: "search" };
let query = { categoryName: categoryname };
if (category1id) {
query.category1Id = category1id;
} else if (category2id) {
query.category2Id = category2id;
} else {
query.category3Id = category3id;
}
location.query = query;
this.$router.push(location);
}
},
}
```
mock
+ mockjs数据模拟
+ cnpm install --save mockjs
- @/mock/banner.json
```
[
{
"id": "1",
"imgUrl": "/images/banner1.jpg"
},
{
"id": "2",
"imgUrl": "/images/banner2.jpg"
},
{
"id": "3",
"imgUrl": "/images/banner3.jpg"
},
{
"id": "4",
"imgUrl": "/images/banner4.jpg"
}
]
```
- @/mock/mockServer.js
```
import Mock from 'mockjs';
import banner from './banner.json';
import floor from './floor.json';
Mock.mock('/mock/banner',{code: 200, data: banner});
Mock.mock('/mock/floor',{code: 200, data: floor});
```
+ 由于这里的数据请求的地址为mock路径下的,所以前面封装的axios要加一个新的,修改路径
- @/api/mockAjax.js
```
import axios from "axios";
import nprogress from "nprogress";
import "nprogress/nprogress.css";
const requests = axios.create({
baseURL: "/mock",
timeout: 5000
})
//请求拦截器
requests.interceptors.request.use((config)=>{
nprogress.start()
return config
})
//响应拦截器
requests.interceptors.response.use((res)=>{
nprogress.done()
return res.data
},(error)=>{
console.log(error)
return Promise.reject(new Error("faild"))
})
export default requests;
```
+ 将新的请求函数reqBannerList()暴露导出,使用参考'封装axios.md'
- @/api/index.js
```
import mockRequests from "./mockAjax";
//pages/Home/ListContainer/ 轮播数据 mock 模拟
export const reqBannerList = () => mockRequests({url:'/banner',method:'get'});
```
swiper
+ 将swiper封装成全局组件,数据通过props传递
- @\components\Carousel\index.vue
```
<template>
<div class="swiper-container" ref="cur">
<div class="swiper-wrapper">
<div
class="swiper-slide"
v-for="carousel in carouselList"
:key="carousel.id"
>
<img :src="carousel.imgUrl" />
</div>
</div>
<div class="swiper-pagination"></div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
</template>
<script>
import Swiper from "swiper";
import "swiper/css/swiper.css";
export default {
name: "Carousel",
props: ["carouselList"],
data() {
return {};
},
mounted() {},
methods: {},
watch: {
carouselList: {
immediate: true,
handler() {
this.$nextTick(() => {
let mySwiper = new Swiper(this.$refs.cur, {
loop: true, // 循环模式选项
pagination: {
el: ".swiper-pagination",
clickable: true,
},
navigation: {
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev",
},
});
});
},
},
},
};
</script>
<style lang="scss" scoped></style>
```
+ usage
```
<Carousel :carouselList="bannerList"/>
```
swiper+nexTick
+ swiper
+ npm install --save swiper@5
+ https://www.swiper.com.cn/usage/index.html
+ 由于在创建swiper实例时需要传入dom元素,官方给出的是一个固定的class,但当页面出现多个轮播图时,会导致多个轮播图使用同一数据,所以我们采用ref来标识
因数据是通过ajax进行异步获取的,而dom元素是根据获取的数据进行生成的,固然不能直接在mounted中创建实例,所以我们通过watch监测所获取的属性并需要结合nexTick(将回调延迟到下一次DOM更新循环之后执行)来完成
+ usage
- @\pages\Home\ListContainer\index.vue
```
import Swiper from "swiper";
import "swiper/css/swiper.css";
watch: {
bannerList(newValue, oldValue) {
this.$nextTick(() => {
let mySwiper = new Swiper(this.$refs.cur, {
loop: true, // 循环模式选项
pagination: {
el: ".swiper-pagination",
clickable: true,
},
navigation: {
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev",
},
});
});
},
}
```
Object.asign
+ object.assign
+ Object.assign(this.searchParams,this.$route.query,this.$route.params);
+ Object.assign({},this.searchParams,this.$route.query,this.$route.params);
面包屑
+ searchSelector中相关面包屑标签,点击进行分类搜索
+ 点击修改整合搜索参数searchParams,进行搜索
+ 围绕searchParams中的参数,进行修改删除再重新获取数据,xxxInfo(),removexxxInfo(),getDate()
+ 父子间通信,采用自定义事件进行传递参数
- @/pages/Search/SearchSelector/index.vue
```
<li v-for="trademark in trademarkList" :key="trademark.tmId" @click="trademarkInfo(trademark)">{{trademark.tmName}}</li>
....
<li v-for="(attrValue,index) in attr.attrValueList" :key="index" @click="attrInfo(attr,attrValue)">
<a>{{attrValue}}</a>
</li>
```
- @/pages/Search/index.vue
```
<SearchSelector @trademarkInfo="trademarkInfo" @attrInfo="attrInfo" />
<script>
import { mapGetters } from "vuex";
import SearchSelector from "./SearchSelector";
export default {
name: "Search",
data() {
return {
searchParams: {
//分类
category1Id: "",
category2Id: "",
category3Id: "",
categoryName: "",
//关键字
keyword: "",
//排序
order: "",
pageNo: 1,
pageSize: 10,
//平台售卖所带的属性
props: [],
//品牌
trademark: "",
},
};
},
components: {
SearchSelector,
},
beforeMount() {
Object.assign(this.searchParams, this.$route.query, this.$route.params);
},
mounted() {
//search
this.getDate();
},
methods: {
//ajax获取数据
getDate() {
this.$store.dispatch("getSearchInfo", this.searchParams);
},
//面包屑
removeCategoryName() {
this.searchParams.categoryName = undefined;
this.searchParams.category1Id = undefined;
this.searchParams.category2Id = undefined;
this.searchParams.category3Id = undefined;
if (this.$route.params) {
this.$router.push({ name: "search", params: this.$route.params });
}
},
removeKeyword() {
this.searchParams.keyword = undefined;
this.$bus.$emit("clear");
if (this.$route.query) {
this.$router.push({ name: "search", query: this.$route.query });
}
},
trademarkInfo(trademark) {
this.searchParams.trademark = `${trademark.tmId}:${trademark.tmName}`;
this.getDate();
},
removeTrademark() {
this.searchParams.trademark = "";
this.getDate();
},
attrInfo(attr, attrValue) {
let props = `${attr.attrId}:${attrValue}:${attr.attrName}`;
//props 去重
if (this.searchParams.props.indexOf(props) === -1) {
this.searchParams.props.push(props);
this.getDate();
}
},
removeAttrInfo(index) {
this.searchParams.props.splice(index, 1);
this.getDate();
},
},
computed: {
...mapGetters(["goodsList", "pageInfo"]),
pageLength() {
let pageInfo = this.$store.getters.pageInfo;
let len = pageInfo.total / pageInfo.pageSize;
return Math.ceil(len);
},
},
watch: {
$route(newVaule, oldValue) {
//整合params
Object.assign(this.searchParams, this.$route.query, this.$route.params);
this.getDate();
//重置
this.searchParams.category1Id = undefined;
this.searchParams.category2Id = undefined;
this.searchParams.category3Id = undefined;
},
},
};
</script>
```
商品排序
+ 修改searchParams中order属性即可
- app\src\pages\Search\index.vue
```
<li :class="{ active: isOne }" @click="changeOrder('1')">
<a
>综合<span v-show="isOne"
><i
class="iconfont"
:class="{
'icon-jiantou_qiehuanxiangxia_o': isDesc,
'icon-jiantou_qiehuanxiangshang_o': isAsc,
}"
></i></span
></a>
</li>
<li :class="{ active: isTwo }" @click="changeOrder('2')">
<a
>价格<span v-show="isTwo"
><i
class="iconfont"
:class="{
'icon-jiantou_qiehuanxiangxia_o': isDesc,
'icon-jiantou_qiehuanxiangshang_o': isAsc,
}"
></i></span
></a>
</li>
//computed
isOne() {
return this.searchParams.order.indexOf("1") !== -1;
},
isTwo() {
return this.searchParams.order.indexOf("2") !== -1;
},
isDesc() {
return this.searchParams.order.indexOf("desc") !== -1;
},
isAsc() {
return this.searchParams.order.indexOf("asc") !== -1;
},
//methods
changeOrder(flag) {
let newOrder = this.searchParams.order;
let Order_flag = newOrder.split(":")[0];
let Order_sc = newOrder.split(":")[1];
if (Order_flag === flag) {
newOrder = `${flag}:${Order_sc === "desc" ? "asc" : "desc"}`;
} else {
newOrder = `${flag}:desc`;
}
this.searchParams.order = newOrder;
this.getDate();
},
```
分页器
+ 封装成全局组件pagination
+ 数据通过props,从父组件传入pageInfo
+ 通过pageInfo.pageNo
+ 确定class-active的绑定,当前页的样式(蓝底)
+ 确定<上一页><下一页>的disabled状态
+ 通过pageInfo.pageNo pageInfo.totalPages确定中间页centerPage的{arr,start,end}
+ 当arr长度不足5时,通过pageInfo.totalPages再次修改start,end
+ 通过自定义事件进行传递修改后的pageNo,在父组件进行修改并再次获取数据
- app\src\components\Pagination\index.vue
```
<template>
<div class="pagination">
<button @click="pageDec()" :disabled="pageInfo.pageNo===1">上一页</button>
<button
v-show="centerPage.start > 1"
:class="{ active: pageInfo.pageNo === 1 }"
@click="pageNum(1)"
>
1
</button>
<button v-show="centerPage.start - 1 > 1">···</button>
<button
v-for="(item, index) in centerPage.arr"
:key="index"
:class="{ active: pageInfo.pageNo === item }"
@click="pageNum(item)"
>
{{ item }}
</button>
<button v-show="pageInfo.totalPages - centerPage.end > 1">···</button>
<button
@click="pageNum(pageInfo.totalPages)"
v-show="centerPage.end < pageInfo.totalPages"
>
{{ pageInfo.totalPages }}
</button>
<button @click="pageAdd()" :disabled="pageInfo.pageNo===pageInfo.totalPages">下一页</button>
<button style="margin-left: 30px">共 {{ pageInfo.total }} 条</button>
</div>
</template>
<script>
export default {
name: "Pagination",
props: ["pageInfo"],
data() {
return {};
},
methods: {
pageAdd() {
this.pageInfo.pageNo++;
this.$emit("changePageNo", this.pageInfo.pageNo);
},
pageDec() {
this.pageInfo.pageNo--;
this.$emit("changePageNo", this.pageInfo.pageNo);
},
pageNum(pageNo) {
this.pageInfo.pageNo = pageNo;
this.$emit("changePageNo", this.pageInfo.pageNo);
},
},
computed: {
centerPage() {
let arr = new Array();
let start = this.pageInfo.pageNo - 2;
let end = this.pageInfo.pageNo + 2;
start = start > 0 ? start : 1;
start = start > this.pageInfo.totalPages-5 ? this.pageInfo.totalPages-4 : start;
end = end > 4 ? end : 5;
end = end > this.pageInfo.totalPages ? this.pageInfo.totalPages : end;
for (let i = start > 0 ? start : 1; i <= end; i++) {
arr.push(i);
}
return { arr, start, end };
},
},
};
</script>
```
vue滚动行为
+ https://router.vuejs.org/zh/guide/advanced/scroll-behavior.html
+ 使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样
+ useage
- app\src\router\index.js
```
export default new VueRouter({
routes,
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}
})
```
购物车组件
+ 静态组件
+ 配置请求
- app\src\api\index.js
```
// shopCar
// cartList /api/cart/cartList get
export const reqCarList = () => requests({
url: '/cart/cartList', method: 'get'
});
// checkCart /api/cart/checkCart/{skuID}/{isChecked} get
export const reqCheckCart = (skuID, isChecked) => requests({
url: `/cart/checkCart/${skuID}/${isChecked}`, method: 'get'
});
// deleteCart /api/cart/deleteCart/{skuId} delete
export const reqDeleteCart = (skuId) => requests({
url: `/cart/deleteCart/${skuId}`, method: 'delete'
})
```
+ 配置仓库
- app\src\store\shopCar\index.js
```
import { reqCarList, reqCheckCart, reqAddShopCar, reqDeleteCart } from '@/api';
const state = {
cartInfoList: []
};
const mutations = {
GETCARLIST(state, cartInfoList) {
state.cartInfoList = cartInfoList;
}
};
const actions = {
async getCarList({ commit }) {
let res = await reqCarList();
if (res.code == 200) {
commit('GETCARLIST', res.data[0].cartInfoList);
}
},
async updateSkuNum({ commit }, { skuId, skuNum }) {
let res = await reqAddShopCar(skuId, skuNum);
if (res.code === 200) {
return 'ok';
} else {
return Promise.reject(new Error('faile'));
}
},
async updateIsChecked({ commit }, { skuId, isChecked }) {
let res = await reqCheckCart(skuId, isChecked);
if (res.code === 200) {
return 'ok';
} else {
return Promise.reject(new Error('faile'));
}
},
async deleteCart({ commit }, { skuId }) {
let res = await reqDeleteCart(skuId);
if (res.code === 200) {
return 'ok';
} else {
return Promise.reject(new Error('faile'));
}
}
};
const getters = {
cartInfoList(state) {
return state.cartInfoList || [];
}
};
export default {
state, mutations, actions, getters
}
```
+ 单选框(单选或全选)
- app\src\pages\ShopCart\index.vue
```
//computed
//全选是否选中,通过every遍历判定
isAllChecked() {
return this.cartInfoList.every((item) => item.isChecked == 1);
},
//选中几件商品,通过filter过滤
isCheckedNum() {
return this.cartInfoList.filter((item) => item.isChecked == 1).length;
},
//methods
//store派发请求
async updateIsChecked(skuId, isChecked) {
await this.$store.dispatch("updateIsChecked", { skuId, isChecked });
},
//单选框click事件
async changeIsChecked(skuId, isChecked) {
isChecked = isChecked == 1 ? 0 : 1;
try {
await this.updateIsChecked(skuId, isChecked);
await this.getCarList();
} catch (error) {
alert(error.message);
}
},
//全选单选框click事件
async checkAll(event) {
let isChecked = event.target.checked ? 1 : 0;
for (let cart of this.cartInfoList) {
let skuId = cart.skuId;
try {
await this.updateIsChecked(skuId, isChecked);
} catch (error) {
alert(error.message);
}
}
await this.getCarList();
},
```
+ 删除以及删除所选
- app\src\pages\ShopCart\index.vue
```
//methods
//store派发
deleteSku(skuId) {
this.$store.dispatch("deleteCart", { skuId });
},
//删除按钮click事件
async deleteCart(skuId) {
try {
await this.deleteSku(skuId);
await this.getCarList();
} catch (error) {
alert(error.message);
}
},
//删除所选全部,通过filter过滤后逐个删除
async deleteIscheckedCart() {
let cartInfoList = this.cartInfoList.filter(
(item) => item.isChecked == 1
);
for (let cart of cartInfoList) {
let skuId = cart.skuId;
try {
await this.deleteSku(skuId);
} catch (error) {
alert(error.message);
}
}
await this.getCarList();
},
```
+ 修改商品数量
- app\src\pages\ShopCart\index.vue
```
//methods
//添加节流防止多次点击异步问题
updateNum: _.throttle(async function (cart, flag, disNum) {
switch (flag) {
case "add":
disNum = 1;
break;
case "minus":
if (cart.skuNum - 1 < 1) {
disNum = 0;
} else {
disNum = -1;
}
break;
}
try {
await this.$store.dispatch("updateSkuNum", {
skuId: cart.skuId,
skuNum: disNum,
});
//再次获取新的数据
this.getCarList();
} catch (error) {
alert(error.message);
}
}, 500),
```
加入购物车成功
+ 导入静态AddCartSuccess组件
+ 注册router
- app\src\router\routes.js
```
...
import AddCartSuccess from "@/pages/AddCartSuccess";
export default [
{
path: "/addCartSuccess",
name: "addCartSuccess",
component: AddCartSuccess,
meta: { show: false },
},
...
]
```
+ 注册请求req_reqAddShopCar()
- app\src\api\index.js
```
// detail addShopCar /api/cart/addToCart/{ skuId }/{ skuNum } post
export const reqAddShopCar = (skuId, skuNum) => requests({ url: `/cart/addToCart/${skuId}/${skuNum}`, method: 'post' });
```
+ store-actions,调用reqAddShopCar()
- app\src\store\detail\index.js
```
const actions = {
async AddShopCar({commit},{skuId,skuNum}){
let res = await reqAddShopCar(skuId,skuNum);
if(res.code === 200){
return 'ok';
}else{
return Promise.reject(new Error('faile'));
}
}
};
```
+ @click事件,派发dispath(),判断req结果,编程式导航
- app\src\pages\Detail\index.vue
```
...
<a @click="addShopCar">加入购物车</a>
...
methods: {
async addShopCar() {
try {
await this.$store.dispatch("AddShopCar", {
skuId: this.$route.params.skuId,
skuNum: this.count,
});
this.$router.push({
name: "addCartSuccess",
query: { 'skuNum': this.count },
});
} catch (error) {
alert(error.message);
}
//一些简单的数据,比如skuNum通过query传过去
//复杂的数据通过session存储,
//sessionStorage、localStorage只能存储字符串 sessionStorage.setItem("SKUINFO",JSON.stringify(this.skuInfo))
}
},
```
ES6 const新用法
data:{
id: "",
name: ""
}
let {id,name} = this
uuid
+ 生成随机id_token
+ npm install --save uuid
+ https://www.npmjs.com/package/uuid
- usage
```
import { v4 as uuidv4 } from 'uuid';
uuidv4();
```
token问题
+ 对于完成登录后,刷新页面,自动登出问题的解决
+ 首先,我们在登录的时候将获取到的token通过localStorage存储在内存中,并通过mutation修改存储于vuex
+ 但一旦在home页刷新后,token不再提交到mutation中,固然token变成none了,所以我们需要在state中直接获取localStorage中的token并赋值
+ 然后分别在Header Home Login中派发getUserInfo获取登陆信息(后面可以通过路由守卫进行派发,不用一个一个的写)
+ 对于getUserInfo中的请求,需要加上特殊的请求头,也就是token,获取方法有两种,一种是在state中获取,一种是直接在localStorage中获取
- app\src\api\ajax.js
```
//请求拦截器
requests.interceptors.request.use((config) => {
let uuid_token = store.state.detail.uuid_token;
let token = localStorage.getItem("TOKEN") || store.state.user.token; //单用前面一个好像也可以
console.log(token);
if (token) {
config.headers.token = token;
} else if (uuid_token) {
config.headers.userTempId = uuid_token;
}
nprogress.start()
return config
})
```
sessionStorage
+ 本地存储
+ 存储对象是要通过JSON转化后再存储
+ sessionStorage.setItem("skuInfo",JSON.stringify(this.skuInfo));
+ JSON.parse(sessionStorage.getItem("skuInfo"));
表单验证
+ https://element.eleme.cn/#/zh-CN/component/form
+ elementUI示例
路由懒加载
+ 当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。
+ 如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效。
+ https://router.vuejs.org/zh/guide/advanced/lazy-loading.html
- @/router/routes.js
```
// 将
// import UserDetails from './views/UserDetails'
// 替换成
const UserDetails = () => import('./views/UserDetails')
const router = createRouter({
// ...
routes: [{ path: '/users/:id', component: UserDetails }],
})
```
图片懒加载
+ https://www.npmjs.com/package/vue-lazyload
+ cnpm i vue-lazyload -S
+ usage
- @/main.js
```
import VueLazyload from 'vue-lazyload';
Vue.use(VueLazyload);
<img v-lazy="img.src">
```
打包部署
+ npm run build
+ 购买服务器 阿里云/腾讯云
+ 设置安全组,让服务器打开一些端口
+ Xshell 登录管理服务器
+ linux 常用指令
+ cd 跳转目录
+ ls 查看目录
+ mkdir 新建目录
+ 用到的文件夹
+ root(根目录) etc(nginx配置)
+ Xftp 向远程服务器上传管理文件
+ nginx配置
+ 进入etc目录,没有就新建
+ 安装nginx yum install nginx
+ vim nginx.conf 编辑配置
+ 默认访问location
```
location / {
root /root/***/dist;
index index.html;
try_files $uri $uri/ /index.html;
}
```
+ 默认api location
```
location /api {
proxy_pass http://39.98.123.211;
}
```
+ 启动nginx service nginx start