赞
踩
目录
使用前端路由,当页面切换新路由时,想要页面滚到顶部,或者保持原先的滚动位置,可以自定义路由切换时页面的滚动行为
- let router = new VueRouter({
- routes
- scrollBehavior(to,before,savedPosition) {
- //return 期望滚动到哪个位置
- //滚动到顶部
- return { y: 0 }
- }
- })
to和from表示路由对象savedPosition当且仅当popstate(通过浏览器的前进后退按钮触发)导航时才可用
return的信息格式为{x:number,y:number}
点击的标签加上样式: 要获得被点击的属性值以及属性值列表,当发生点击事件的时候,先将属性列表中所有的属性都清除样式,然后再给被点击的属性加上样式
- changeClass(spuSaleAttrValueList,spuSaleAttrValue){
- for(let i=0;i<spuSaleAttrValueList.length;i++){
- spuSaleAttrValueList[i].isChecked=0;
- }
- spuSaleAttrValue.isChecked=1;
- }
放大镜结构如下,下面为轮播图,与之前的轮播图不同的就是再swiper里面设置了一次展示几张【slidesPerView:3】

- <div class="spec-preview">
- <!-- 背景图片 -->
- <img :src=" imgObj.imgUrl" />
- <!-- 触发事件的地方 -->
- <div class="event" @mousemove="small" ref="box"></div>
- <!-- 放大的部分 -->
- <div class="big" ref="big">
- <img :src=" imgObj.imgUrl" ref="img" />
- </div>
- <!-- 覆盖层 -->
- <div class="mask" ref="mask"></div>
- </div>
event.offsetX,event.offsetY:鼠标相对于父盒子的位置
offsetWidth,offsetHeight
style.top,style.left:元素相对于父盒子的定位
逻辑部分:首先要判断mask的位置是否超出边界,如果超出就做相应处理,img的移动部分主要是利用公式
img移动距离/mask移动距离=-img可移动最大距离/mask可移动最大距离
因为img和mask移动方向相反,所以有一个负号
- small(event){
- let mask=this.$refs.mask;
- let box=this.$refs.box;
- let big=this.$refs.big;
- let img=this.$refs.img;
- let left=event.offsetX-mask.offsetWidth/2;
- let top=event.offsetY-mask.offsetHeight/2;
- if(event.offsetX-mask.offsetWidth/2<0){
- left=0
- }
- if(event.offsetX-mask.offsetWidth/2>box.offsetWidth-mask.offsetWidth){
- left=box.offsetWidth-mask.offsetWidth
- }
- if(event.offsetY-mask.offsetHeight/2<0){
- top=0
- }
- if(event.offsetY-mask.offsetHeight/2>=box.offsetHeight-mask.offsetHeight){
- top=box.offsetHeight-mask.offsetHeight
- }
- mask.style.top=top+'px';
- mask.style.left=left+'px';
- //最大偏移量的比
- img.style.left=-((img.offsetWidth-big.offsetWidth)/(box.offsetWidth-mask.offsetWidth))*left+'px'
- img.style.top=-((img.offsetHeight-big.offsetHeight)/(box.offsetHeight-mask.offsetHeight))*top+'px'
-
- }

加减按钮要注意不能为负数,给输入框绑定change事件
正则表达式:
- changeSkuNum(){
- //正则,可以输入0-9中任何数,并且每个数出现的次数可以是0次或无数次
- let rg=/^[0-9]*$/;
- this.skuNum=Math.ceil(this.skuNum);
- if(!rg.test(this.skuNum)){
- this.skuNum=1;
- }else{
- //隐式转换成数字
- this.skuNum=this.skuNum*1;
- }
- }
不用正则:
- changeSkuNum(){
- //隐式转换成number,如果是数字以外的类型转换成number就会是NaN
- let value=this.skuNum*1
- //排除负数的情况
- if(isNaN(this.skuNum)||value<1){
- this.skuNum=1
- }
- //不能是小数
- this.skuNum=Math.ceil(this.skuNum);
- }
点击加入购物车后,首先要先向购物车发送信息,如果成功返回则将相关数据存储在本地,最后进行路由跳转。
skuInfo不建议用路由传参,因为会显示在地址栏中,会很丑,所以这里用会话存储传参(因为是单页面的应用),简单的数据再用路由传参
浏览器存储功能(html5新增):一般存储的是字符串 可以用JSON.stringify()把对象转换成字符串存储,取的时候用JSON.parse()转回去
- 本地存储:不删除会一直存在
- 会话存储:会话结束就删除
- postAddToCart(){
- this.$store.dispatch('detail/postAddToCart',{skuId:this.$route.params.skuid,skuNum:this.skuNum}).then(
- response=>{
- sessionStorage.setItem('skuInfo',JSON.stringify(this.skuInfo))
- this.$router.push({name:'addcartsuccess',query:{skuNum:this.skuNum}})
-
- },error=>{
- alert('error')
- }
- )
-
- }
uuid:能够生成一个随机的id
单独写一个js文件,放在新建的utils中,写一个生成uuid的函数,返回值为生成的uuid。
先看看本地存储里面是否已经有uuid,如果没有则生成uuid并存储后返回,有就直接返回
- import { v4 as uuidv4 } from 'uuid'
- export const getUUID = () => {
- let uuid_token = localStorage.getItem('UUIDTOKEN');
- if (!uuid_token) {
- uuid_token = uuidv4()
- localStorage.setItem('UUIDTOKEN', uuid_token);
- }
- return uuid_token
- }
在store里面生成并存储(应该在detail里面生成,因为在detail中加入购物车的时候就需要把uuid传回去)
- const state = {
- detail: {},
- uuid_token: getUUID()
- };
在请求拦截器中设置,在request中引入store就可以获取store中的数值。
在请求头的userTepId(这是和后台商量好的)中携带
- if (store.state.detail.uuid_token) {
- config.headers.userTempId = store.state.detail.uuid_token
- };
Array.forEach(item=>{})遍历每个数组
Arry.every(item=>判断)如果都符合返回真否则为假
- //计算总价格
- totalPrice(){
- let sum=0
- this.cartInfoList.forEach(item=>{
- if(item.isChecked==1)
- {sum=sum+item.skuNum*item.skuPrice}
- })
- return sum
- },
- //判断是否全选
- isAllCheck(){
- return this.cartInfoList.every(item=>{return item.isChecked==1})
- }
该接口需要传递商品数量的变化量正数为增加,负数为减少,因此点击+号num=1,点击减号,当当前的skuNum大于1则num=-1否则num=0;在输入框输入数字的时候,如果输入的是非法字符或者负数则不改变,因此num=0,如果是合法的数字则取整数然后减去原先的skuNum获得变化量。最后重新发送请求,如果发送成功则重新获取购物车数据。
为了避免频繁修改并发送请求,添加了节流函数
- changeNum:throttle(async function(type,num,cartInfo){
- switch(type){
- case 'add':
- num=1;
- break;
- case 'sec':
- if(cartInfo.skuNum>=2){
- num=-1
- }else{
- num=0
- }
- break;
- case 'change':
- if(isNaN(num)||num<1){
- num=0
- }else{
- // 这里的num没有双向数据绑定,所以num是改变后的值
- num=parseInt(num)-cartInfo.skuNum;
- }
- }
- try{
- await this.$store.dispatch('detail/postAddToCart',{skuId:cartInfo.skuId,skuNum:num});
- this.getCartList()
- }catch(error){
- alert('error')
- }
-
- },500),

剩余的删除产品、改变产品状态的操作和前面差不多
在store里面处理逻辑,遍历购物车信息,选择的话就派发之前写的删除请求,并将返回的promise存在数组中,等遍历结束之后用promise.all来判断之前返回的promise中有没有失败的,只要有失败promise.all都会返回失败。
- deleteAll({ getters, dispatch }) {
- let promiseAll = []
- getters.cartInfoList.forEach(item => {
- let result = item.isChecked && dispatch('deleteCart', item.skuId)
- promiseAll.push(result)
- });
- // context.dispatch('deleteCart')
- //只要有一个失败,就会返回失败
- return Promise.all(promiseAll)
- },
Promise.all()是一个内置的辅助函数,接受一组promise(或者一个可迭代的对象),并返回一个promiseconst allPromise = Promise.all([promise1, promise2, ...]);
通过全选改变商品状态的方法和删除方法差不多
点击获取验证码的时候,先判断是否已经输入手机号,如果输入了手机号那就派发一个action。因为给手机发验证码要钱,这里就直接将返回的验证码存储在仓库里面,然后自动显示在验证码框上
- async getCode(){
- try{
- this.phone && await this.$store.dispatch('users/getCode',this.phone);
- this.code=this.$store.state.users.code;
- }catch(error){
- console.log('error')
- }
-
- }
首先确认所有要填写的内容都有并且密码和确认密码相同 ,并向后台要数据,如果成功返回,那就跳转到登录页面
- async userRegister(){
- try{
- const {phone,password,code,passwordSure,agree}=this;
- if(phone&&password&&code&&password==passwordSure&&agree){
- await this.$store.dispatch('users/getRegister',{phone,password,code});
- this.$router.push('/login')
- }
- }catch(error){
- console.log(error.message)
- }
-
- }
确认用户名和密码都有,就请求数据,成功则返回原来要去的界面(例如没有登录的情况下,如果去个人中心,会自动跳转到登录界面,登录成功后再跳转到个人中心界面)
- async login(){
- try{
- const {phone,password}=this
- if( phone&&password)
- {
- await this.$store.dispatch('users/getLogin',{phone,password})
- let toPath=this.$route.query.toPath||'/home'
- this.$router.push(toPath)
-
- }
- }catch(error){
- console.log(error.message)
- }
-
- }
注意:表单标签form有默认行为,要取消可以用prevant
<button class="btn" @click.prevent="login">登 录</button>
登录成功后,后台返回的数据中有token,是用户登录的唯一标识,拿到token要先存在localstorage(因为vuex不是持久化的,如果不存在本地,刷新一下就没有了)并且存在state里面
- async getLogin(context, { phone, password }) {
- let result = await reqLogin({ phone, password });
- if (result.code == 200) {
- localStorage.setItem('TOKEN', result.data.token)
- context.commit('GETTOKEN', result.data.token);
- return 'ok'
- } else {
- alert(result.message)
- return Promise.reject(new Error(result.message));
- }
- },
使用请求拦截器,在请求头中加上token
- if (store.state.users.token) {
- config.headers.token = store.state.users.token
- }
登录成功后,登录的地方要显示用户名,因此需要获得用户信息,
由于header不是路由组件,只挂载一次,因此不能在header的mounted里面获取;
如果在App.vue里面获取,由于App只加载一次,第一次加载的时候没有登录就没有token,因此无法获取用户信息,必须要手动刷新一次。
因此在路由守卫里面设置,发送路由变化的时候,如果已经有token那就获取用户信息
向服务器请求删除数据,然后返回首页
- async logout(){
- try{
- await this.$store.dispatch('users/getLogout');
- this.$router.push('/home')
- }catch(error){
- console.log(error.message)
- }
-
- }
服务器返回成功后,要将仓库中的token和用户信息需要清除,本地存储的token也需要情况
- //退出登录后原先的token就没用了要清除
- async getLogout(context) {
- let result = await reqLogout();
- if (result.code == 200) {
- context.commit('CLEARINFO')
- return 'ok'
- } else {
-
- return Promise.reject(new Error(result.message));
- }
- },
- CLEARINFO(state) {
- state.token = '',
- state.userInfo = {},
- localStorage.removeItem('TOKEN')
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。