每日生鲜知识体系
相关资料
二、Sticky 粘性布局(吸顶) 请看有赞ui文档
三、sku 商品规格
SKU全称为Stock Keeping Unit(库存量单位),即库存进出计量的基本单元,可以是以件,盒,托盘等为单位。SKU这是对于大型连锁超市DC(配送中心)物流管理的一个必要的方法。现在已经被引申为产品统一编号的简称,每种产品均对应有唯一的SKU号。单品:对一种商品而言,当其品牌、型号、配置、等级、花色、包装容量、单位、生产日期、保质期、用途、价格、产地等属性中任一属性与其他商品存在不同时,可称为一个单品。
四、滚动后跳转页面返回顶部问题
const routes = [...];
const router = new Router({
scrollBehavior: () => ({
y: 0
}),
routes
});
五、全局filter的使用
vue中分全局mixin和局部mixin ,平时记得使用
// 在mixin里面定义filters
export default {
data() {
return {};
},
filters: { //这里主要是设置格式化时间的
fomatDate(time) {
let date = new Date(time);
let Y = date.getFullYear();
let M = date.getMonth() + 1;
let D = date.getDate();
return `${Y}年-${M}月-${D}日`;
},
formatMoney(num) {
return '¥' + num / 100;
}
},
//filters使用的的时候,在vue文档需要使用的时候,只需要添加:|formatMoney
//如下栗子:
<!-- 购物车编辑完成的 -->
<div class="goods-price" v-show="show">
<span class="price">{{item.price|formatMoney}}</span> //这里是实用的例子
<span class="old-price">¥55.9</span>
<span class="count">X {{item.buyNum}}</span>
</div>
//===================================================================================
methods: {
$loading(flag) { //这里是全局封装loading 方法一:
if (flag) {
this.$toast.loading("努力加载中...");
} else {
this.$toast.clear();
}
}
}
//==============================
//全局封装loading方法二:
$loading() { //封装losding方法
const toast = this.$toast.loading({
duration: 0, // 持续展示 toast
forbidClick: true,
message: '加载中...'
});
},
$loadingclon() { //封装清除loading
this.$toast.clear();
}
//方法二实际使用例子:
//获取购物车所有列表
grtcart() {
this.$loading() //加载loading //使用
let url = "/cart/all";
this.$axios
.get(url)
.then(res => {
this.Shoppinglist = res.list;
console.log(res);
this.$loadingclon() //清除loading //清除 注意:在移动端,失败的时候不需要清除loading
})
.catch(err => {
console.log(err);
});
}
};
// 使用
<span class="price">{{item.price | formatMoney }}</span>
六、组件的事件传参问题
七、keep-alive
// router的配置
{
path: "list",
meta: {
title: "商品列表",
keepAlive: true //表示需要缓存
},
component: () => import("@/views/product/list/index")
},
// app.vue配置
<template>
<div>
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
</div>
</template>
八、登陆和注册共用组件
- 判断页面是注册还是登陆
- mixin的使用
- 正则表达式
九、组件内的路由守卫
data() {
return {
// 从哪个页面跳过来
from: "",
phone: "15013795539",
smsCode: "",
password: ""
};
},
beforeRouteEnter(to, from, next) {
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
// console.log('router',this);
console.log(to.path, from.path);
next(vm => {
// 通过 `vm` 访问组件实例
console.log(vm.from);
vm.from = from.path;
});
},
十、token和axios拦截器
// import axios from "axios";
import store from "../store";
import router from "../router";
// 创建axios实例
const service = axios.create({
baseURL: process.env.VUE_APP_URL, // api 的 VUE_APP_URL
timeout: 50000 // 请求超时时间(因为需要调试后台,所以设置得比较大)
});
// request拦截器,在请求之前做一些处理
service.interceptors.request.use(
config => {
if (store.state.token) {
// 给请求头添加user-token
config.headers["user-token"] = store.state.token;
}
return config;
},
error => {
console.log(error); // for debug
return Promise.reject(error);
}
);
十一、路由守卫之登录验证
-
在需要登录的路由上添加 needLogin: true
{ path: 'submit', meta: { title: '确认订单', needLogin: true }, component: ()=>import('@/views/order/children/submit') }
-
在路由守卫进行判断
router.beforeEach((to, from, next) => { let { title, needLogin } = to.meta; let { isLogin } = store.state; document.title = title; if (needLogin && !isLogin) { next({ path: "/login" }); } else { next(); } });
十二、需要登陆的接口,可以使用axios统一进行拦截
service.interceptors.response.use(
response => {
const res = response.data;
if (res.code == "666") {
return res;
} else if (res.code == "603") {
// code为603代表token已经失效,
// 提示用户,然后跳转到登陆页面
router.push("/login");
} else {
return Promise.reject({
message: res.msg
});
}
},
error => {
return Promise.reject(error);
}
);
十三、vuex模块化
// cart.js
export default {
state: {
preOrderId: ''
},
mutations: {
updatePreOrderId(state, payload) {
state.preOrderId = payload;
}
}
}
// index.js
export default Vuex.Store({
modules: {
cart
}
})
// 使用
computed: {
preOrderId() {
return this.$store.state.cart.preOrderId;
}
},
十四、阻止form表单的默认行为
<form onsubmit="return false;"></form>
十五、router的history模式(了解)
http://huruqing.cn/docs/Vue/advance/03.router.html
十六、devtools
十六、active-class
http://huruqing.cn/docs/Vue/advance/03.router.html
十七、定制主题和按需加载组件
定制主题的原理,通过修改less变量来实现
http://huruqing.cn/docs/Vant/list/101-%E5%AE%9A%E5%88%B6%E4%B8%BB%E9%A2%98%E6%A0%B7%E5%BC%8F.html
十八、rem
http://huruqing.cn/docs/Vue/advance/06.rem.html
vue-cli3用rem进行适配步骤
-
安装手淘的flexible,插件名称叫amfe-flexible
npm i amfe-flexible --save-dev
-
在main.js导入
import 'amfe-flexible'
-
在/vue.config.js添加px2rem插件,把项目中的px转为rem
const autoprefixer = require("autoprefixer"); const pxtorem = require("postcss-pxtorem"); module.exports = { // 关闭eslint检查 lintOnSave: false, // 配置css前缀,px转rem css: { loaderOptions: { // 后处理器配置 postcss: { plugins: [ // 配置样式前缀 autoprefixer(), // 把px转为rem pxtorem({ rootValue: 37.5, propList: ["*"] }) ] } } } };
十九、父子通信踩坑指南
有一种场景: 父给子传参(本例是username),子组件通过input标签修改后再传回给父的情况
例1, 子组件有输入的情况, 使用原生js实现
通过event.target.value获取输入况的值,如果想使用v-model请看例子2(无异步请求)和例子3(有异步请求)
// 父组件
<template>
<div>
<p>
父组件的username: {{username}}
</p>
<Child :username="username" @changeName="changeName"/>
</div>
</template>
<script>
import Child from './Child'
export default {
components: {
Child
},
data() {
return {
username: 'huruqing'
}
},
methods: {
changeName(data) {
this.username = data;
}
}
};
</script>
// 子组件
<template>
<div>
<hr />
<p>
子组件的username:
<input type="text" :value="username" @input="fn" />
</p>
</div>
</template>
<script>
export default {
props: {
username: {
type: String,
required: true
}
},
methods: {
fn(evnet) {
// console.log(event.target.value);
let value = event.target.value;
this.$emit("changeName", value);
}
}
};
</script>
例子2
通过定义个中间变量来实现父子传参(没有异步请求),同时也能使用v-model
// 父组件
<template>
<div>
<p>父组件的username: {{username}}</p>
<Child :username="username" @changeName="changeName" />
</div>
</template>
<script>
import Child from "./Child";
export default {
components: {
Child
},
data() {
return {
username: ""
};
},
methods: {
changeName(data) {
this.username = data;
}
}
};
</script>
// 子组件
<template>
<div>
<hr />
<p>
子组件的username:
<input type="text" v-model="username2" @input="fn" />
</p>
</div>
</template>
<script>
export default {
data() {
return {
username2: ""
};
},
props: ["username"],
created() {
this.username2 = this.username;
},
methods: {
fn() {
this.$emit("changeName", this.username2);
}
}
};
</script>
例子3 通过定义个中间变量来实现父子传参(有异步请求),同时也能使用v-model
ps: 需要用watch来监听username的变化,如果用created只能拿到第一次的值
// 父组件
<template>
<div>
<p>父组件的username: {{username}}</p>
<Child :username="username" @changeName="changeName" />
</div>
</template>
<script>
import Child from "./Child";
export default {
components: {
Child
},
data() {
return {
username: ""
};
},
created() {
this.getData();
},
methods: {
getData() {
// 获得数据 username
setTimeout(() => {
let res = "huruqing";
this.username = res;
}, 5000);
},
changeName(data) {
this.username = data;
}
}
};
</script>
// 子组件
<template>
<div>
<hr />
<p>
子组件的username:
<input type="text" v-model="username2" @input="fn" />
</p>
</div>
</template>
<script>
export default {
data() {
return {
username2: ""
};
},
props: ["username"],
// created() {
// console.log("username", this.username);
// this.username2 = this.username;
// },
watch: {
username: {
handler(newVal, oldVal) {
console.log("username的值:", newVal);
this.username2 = newVal;
},
immediate: true
}
},
methods: {
fn() {
this.$emit("changeName", this.username2);
}
}
};
</script>
二十、vuex踩坑指南
- 手动更改state的值,却发现值没有变化
原因: 我们使用了本地持久化插件 vuex-persistedstate,只有使用mutation修改state的值才会生效,手动修改无效,state的值都会存储到localStoreage里面,如果想修改默认的初始值,需要先清除localStoreage里面的数据,清除方法有两种:- 清除localStoreage某个值,使用localStoreage.removeItem('vuex');
- localStoreage.clear(); 清除所有的缓存信息
- 类似十九点父子通信的情况,有以下应用场景
从vuex取值,修改后重新存储,并且使用的input标签进行修改
例子(没有异步请求情况), 有异步操作的情况像十九那样,使用watch监听
<template>
<div>
<div>vuex里的username {{username}}</div>
<hr />
<p>
子组件的username:
<input type="text" v-model="username2" @input="fn" />
</p>
</div>
</template>
<script>
export default {
data() {
return {
username2: ""
};
},
computed: {
username() {
return this.$store.state.username;
}
},
created() {
console.log("username", this.username);
this.username2 = this.username;
},
methods: {
fn() {
this.$store.commit("updateUsername", this.username2);
}
}
};
</script>
二十一、$toast轻提示的处理
-
设置默认显示时间
import Vue from 'vue'; import {Toast} from 'vant' Toast.setDefaultOptions({ duration: 500 }); Vue.use(Toast);
-
错误提示问题,根据不同的情况去做处理
// 在mixin封装一个$faild 的方法 // 封裝失敗的提示 $fail(err) { debugger; if (typeof err === 'object') { if (err.code !=603) { this.$toast.fail(err.message); } } else { this.$toast.fail(err); } },
ps: 当我们要对错误进行提示的时候就调用上面的方法this.$fail(xxObj)
二十二、性能优化
-
使用cdn https://www.bootcdn.cn/
// 用script导入库 <script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.min.js"></script> // vue.config.js(vue-cli3)的配置 module.exports = { configureWebpack: { externals: { axios: "axios" // 配置使用CDN } } }
压缩图片 https://tinypng.com/
默认图片占位
二十三、 vuex使用
1、首先在vue文档中建立一个vuex文件夹:store,然后建立一个子文件index
import Vue from "vue"
import Vuex from "vuex"
import createPersistedState from 'vuex-persistedstate'
import index from "../store/modules/index"
Vue.use(Vuex)
const config = {
// 本地持久化
plugins: [createPersistedState()],
modules:{index}, //将modules导入进来,记得是对象{},别写成数组了。这里是vuex模块化管理
state:{ //这里是vuex存值的地方
username:"",
payWayFlag:false,
phone:'',
},
getters:{
payWayFlag:state=>state.payWayFlag,
phone:state=>state.phone,
},
mutations:{ //这里是mutations方法,需要用vuex 必须要用这个
// 支付方式页面的显示与隐藏
payWayFlagChange(state,payload){
state.payWayFlag = payload
},
updatedPhone(state,payload){ //获取手机号
state.phone = payload
},
},
}
export default new Vuex.Store(config)
2、更改数据/存数据:
Order() {
//点击提交传数据
if (this.show) {
let url = "/preOrder/add";
let data = {
cartId: this.result, //这里的result是个数组,是组件自带的一个装有所有cartid的数组
totalMoney: this.allPrice //这里是将总价传过去
};
this.$axios
.post(url, data)
.then(res => {
console.log(res);
this.$store.commit("updatedPreOrderId", res.result.preOrderId); //这里存数据
this.$store.commit("payWayFlagChange", true); //这里是为了防止支付框开始就弹起来
})
.catch(err => {
console.log(err);
});
}
this.$router.push("/order/confirm");
},
3、使用你存在vuex中的数据:
getOrder() {
let url = "/order/detail";
let data={
orderId:this.$store.state.index.orderId //这里就是vuex你需在哪里使用,就按照这个格式来写就行
}
二十四 map方法;
// 获取购物车列表
getCartList() {
this.$loading(true);
let url = "/cart/all";
this.$axios
.get(url)
.then(res => {
this.cartList = res.list.map(item => { //map方法
return {
...item,
checked: false
};
});
})
.catch(err => {
this.$toast.fail(err.message);
})
.finally(() => {
this.$loading(false);
});
},
二十五 filter方法;
selectOne(value) {
// 被选中商品的数量
let selectList = this.cartList.filter(item => { //filter方法
// return item.checked === true;
return item.checked;
});
// 判断被选中商品和全部商品的数量是否相等
if (selectList.length === this.cartList.length) {
this.allChecked = true;
} else {
this.allChecked = false;
}
},
二十六 合并登录和注册页面
1、首先在vue页面中监听方法中监听:
computed: {
pageName() {
//这里是将登录和注册的页面合并,监听路径
let path = this.$route.path;
let name = path === "/user/login" ? "login" : "register";
return name;
}
}
2、 然后在html文档使用的时候按如下格式填写:
<div class="register-hander flex jc-sb aic">
<span></span>
<span class="ml-40" v-if="pageName ==='register'">注册</span>
<router-link v-if="pageName === 'login'" to="/user/register" tag="span" class="mr-15">注册</router-link> // 登录的页面用v-if判断
<router-link v-else to="/user/login" tag="span" class="mr-15">登录</router-link>
</div> //注册的页面用v-else判断
二十七 forEach方法
getDeilist() {
//获取城市列表和地区列表
this.$loading() //加载loading
let categoryUrl = "/category/all"; //获取商品列表,路径是接口里的
let productareaUrl = "/product/all"; //获取所有的商品,路径是接口里的
let categoryPormise = this.$axios.get(categoryUrl); //获取城市列表的Promise
let productPormise = this.$axios.get(productareaUrl); //获取地区列表的Promise
Promise.all([categoryPormise, productPormise])
.then(res => {
console.log(res);
// 把商品列表和所有商品需要的数据
let categoryList = res[0].list; // 获取到的商品列表赋值给声明的一个categoryList
let productList = res[1].list;
let newCommodityList = categoryList.map(item => {
//在这里用map方法给侧边栏增加一个 children
let children = []; //新增一个 children数组
productList.forEach(i => {
//用forEach选出相同的id,
if (item.categoryId === i.categoryId) {
children.push(i); //将相同的id,push进新建的数组
}
this.$loadingclon() //清除loading
});
return {
...item,
children //给侧边栏返回children值
};
});
console.log(newCommodityList);
this.items = newCommodityList;
})
.catch(err => {
console.log(err);
});
}
二十八 合并地址栏的编辑和新建
1、在router里面讲这两个页面的路由设置如下:
{
path: "/address",
component: () => import("@/pages/address/Index"),
meta: {
title: '地址',
},
children: [
{
path: "area",
meta: {
title: '新增地址',
},
component: () => import("@/pages/address/children/SelectArea")
},
{
path: "编辑地址",
component: () => import("@/pages/address/children/SelectArea")
}
]
},
2、在计算属性中监听方法:
computed: {
isArea() {
//这里是合并新建地址和编辑地址
return this.$route.path.includes(`area`);
}
},
3、页面上实际应用:
<van-nav-bar :title="isArea?'新建地址':'编辑地址'" left-text left-arrow @click-left="onClickLeft" />
二十九 组件内路由守卫
beforeRouteEnter(to, from, next) {
//组件内路由守卫
// ...
next(vm => {
//组件内的this无法指向data,所以用vm ,这个可以上网查资料
vm.from = from.path;
});
},
三十、计算优惠券
优惠券是分为父子组件的
//父组件
// 获取优惠券对象
getCoupon(couponObj) {
// // 根据优惠券对象,计算优惠金额
this. discountMoney = this.getDiscountMoney(couponObj);
// // 计算总价
// this.allFees =
// this.totalMoney / 1 + this.expressFee / 1 - this.discountMoney / 1; //这里除以1是将他转换成数字
// this.$store.commit("updatedAllFee", this.allFees); // 这里是将总价存在vuex里面
},
// 根据优惠券对象,计算优惠金额
getDiscountMoney(couponObj) {
// 不适用优惠券
if (!couponObj) { //后面遇到这个情况,可以不要写这个判断,很麻烦
let discount = 0; //这里是将优惠金额存进vuex
this.$store.commit("updatedCouponObj", discount); //这里是将优惠金额存进vuex
return 0;
}
// 选中折扣券
if (couponObj.type === "02") {
let discount = this.totalMoney * (1- couponObj.value/10); //这里是将优惠金额存进vuex
this.$store.commit("updatedCouponObj", discount); //这里是将优惠金额存进vuex
return (this.totalMoney * (1- couponObj.value/10)) ;
}
// 选中普通的优惠券
let discount=couponObj.value //这里是将优惠金额存进vuex
this.$store.commit("updatedCouponObj", discount); //这里是将优惠金额存进vuex
return couponObj.value;
},
computed: {
list() {
return this.$store.state.index.list; //这里是在vuex存的地址,在这里用
},
// 计算总价,这里的总价直接渲染在页面上面
allFee() {
let allFees=this.totalMoney / 1 + this.expressFee / 1 - this.discountMoney / 1 //这里除以1是将他转换成数字
this.$store.commit("updatedAllFee", allFees); // 这里是将总价存在vuex里面
return this.totalMoney / 1 + this.expressFee / 1 - this.discountMoney / 1;
},
}
//子组件
// 选中优惠券时的事件
onChange(index) {
this.showList = false;
this.chosenCoupon = index;
this.$emit("getCoupon", this.coupons[index]);
}
computed: {
coupons() {
let list = this.couponList.filter(item => {
//这里是筛选出总价格和能用优惠券的金额
return item.conditionValue < this.totalMoney; //如果商品总价格大于优惠券金额,那么优惠券就可以用
});
return list;
},
disabledCoupons() {
let list = this.couponList.filter(item => {
//这里是筛选出总价格和不能用优惠券的金额
return item.conditionValue >= this.totalMoney; //如果商品总价格大于优惠券金额,那么优惠券就可以用
});
return list;
},
discount(){
return this.$store.state.index.discount
}
},
三十一 父子组件通信
1、子传父
//子组件
// 选中优惠券时的事件
onChange(index) {
this.showList = false;
this.chosenCoupon = index;
this.$emit("getCoupon", this.coupons[index]); //通过emit传值
}
//父组件
//template里面的标签
<offer @getCoupon="getCoupon"></offer> //建立一个点击事件
//script
// 获取优惠券对象
getCoupon(couponObj) {
// // 根据优惠券对象,计算优惠金额
this. discountMoney = this.getDiscountMoney(couponObj);
}
2、父传子 不懂的地方可以参考卖座网后台管理系统
//父组件
//template里面的标签
<offer :totalMoney="totalMoney"></offer>
//子组件
1: props: ["totalMoney"],
2:
computed: {
coupons() {
let list = this.couponList.filter(item => {
//这里是筛选出总价格和能用优惠券的金额
return item.conditionValue < this.totalMoney; //如果商品总价格大于优惠券金额,那么优惠券就可以用
});
return list;
},
disabledCoupons() {
let list = this.couponList.filter(item => {
//这里是筛选出总价格和不能用优惠券的金额
return item.conditionValue >= this.totalMoney; //如果商品总价格大于优惠券金额,那么优惠券就可以用
});
return list;
},
discount(){
return this.$store.state.index.discount
}
},
三十二 watch监听
watch: {
totalMoney: {
//子组件传来的值监听
handler(n, o) {
//深度监听
console.log(n, o); //n 和o 是参数,n是新值,o是旧值, n才是我们要的值
this.listArr = n;
},
deep: true, //深度监听
immediate: true //深度监听
}
}