Vue3 Demo—— fyShop(中)

4 项目页面——商品详情页

通过首页和分类页面点击都可以进入商品详情页

1 获取商品详情

截屏2021-08-10 12.55.16.png

GoodsListItem.vue >>

<template>
  <!-- 商品列表项 -->
  <!-- 1-1 点击路由跳转带 id :@click="itemClick(product.id)" -->
  <div class="good-item" @click="itemClick">
    <img v-lazy="product.cover_url" alt="">
    <div class="goods-info">
      <p>{{product.title}}</p>
      <span class="price"><small></small>{{product.price}}</span>
      <span class="collect">{{product.collects_count}}</span>
    </div>
  </div>
</template>

<script >
  import {useRouter} from 'vue-router';  // 1-2 引入路由器


  export default {
    name: "GoodsListItem",
    props:{// 接收父组件传值
      product:Object,
      default(){
        return{}
      }
    },
    setup(props) {
      const router = useRouter() // 1-3 声明路由器

      return {
        itemClick: () => {
          router.push({path:'/detail',query:{id:props.product.id}}) // 1-4 本页有 id,在这里传; 页面有缓存
          // console.log(props.product.id)
        }
      }
    }
  }
</script>

<style scoped lang="scss">
  .good-item{
    width: 46%;
    padding-bottom: 40px;
    position: relative;// 内层,相对外层定位

    img{
      width: 100%;
      border-radius: 5%;

    }
    .goods-info{
      font-size: 12px;
      bottom: 5px;
      overflow: hidden;
      text-align: center;

      /*居中*/
      position: absolute;
      left: 0;
      right: 0;


      p{
        overflow: hidden;
        // 标题超出范围隐藏
        text-overflow: ellipsis;
        // 文本超出范围显示 ...
        white-space: nowrap;
        // 文本不进行换行
        margin-bottom: 3px;

      }
      .price{
        color: darkred;
        margin-right: 20px;

      }
      .collect{
        position: relative;

      }
      .collect::before{
        center:'';
        position: absolute;
        left: -15px;
        width: 14px;
        height: 14px;
        top:-1;
        background:url('~assets/images/collect.png')0 0 /14px 14px;
      }

    }
  }

  </style>
复制代码

category.vue >>

<template>
  <div>
    <nav-bar>
      <template v-slot:default>分类详情</template>
    </nav-bar>
    <div id="mainbox">
      <!--6 不同排序方式-->
      <div class="ordertab">
        <van-tabs v-model="active" @click="tabClick">
          <van-tab title="销量排序"></van-tab>
          <van-tab title="价格排序"></van-tab>
          <van-tab title="评价排序"></van-tab>
        </van-tabs>

      </div>
      <!--5 折叠-->
      <van-sidebar class="leftmenu" v-model="activeKey">
        <van-collapse v-model="activeName" accordion><!--手风琴折叠-->
          <van-collapse-item
            v-for="item in categories" :key="item.id"
            :title="item.name"
            :name="item.name">

<!--            子菜单-->
          <van-sidebar-item
            v-for="sub in item.children"
            :title="sub.name"
            :key="sub.id"
            @click="getGoods(sub.id)"
          /><!--    4.变量categories.value中数据-->

          </van-collapse-item>
        </van-collapse>
      </van-sidebar>
<!--商品列表-->
      <div class="goodslist">
        <div class="content">
          <van-card
            v-for="item in showGoods" :key="item.id"
            @click="itemClick(item.id)"
            :num="item.comments_count"
            :tag="item.comments_count >=0 ? '流行':'标签'"
            :price="item.price"
            :desc="item.update_at"
            :title="item.title"
            :thumb="item.cover_url"
            :lazy-load="true"
          />

        </div>
      </div>
    </div>

    <back-top @bTop="bTop" v-show="isShowBackTop"></back-top>

  </div>
</template>

<script>
  import NavBar from "../../components/common/navbar/NavBar";
  import BackTop from "../../components/common/backtop/BackTop";// ️ 2-1 回到顶部
  import {useRouter} from 'vue-router'
  import {ref, reactive, onMounted, computed, watchEffect, nextTick} from 'vue';
  import {getCategory,getCategoryGoods} from "../../network/category";
  import BScroll from "better-scroll";


  export default {
    name: "Category",
    setup(){
      const router = useRouter()
      let active = ref(1)
      let activeKay = ref(0)
      let activeName = ref(1)
      //2.申明数组
      let categories = ref([])

      let isShowBackTop =ref(false)

      //8当前的排序条件
      let currentOrder = ref(['sales'])
      //11 当前的分类id
      let currentCid = ref(0)

      //12数据模型
      const goods = reactive({
        sales:{page:1,list:[]},
        price:{page:1,list:[]},
        comments_count:{page:1,list:[]}
      })
      //13响应式的,更新数据
      const showGoods = computed(()=>{
        return goods[currentOrder.value].list
      })

      //13 初始化数据,将接口里的数据放到数据模型中去
      const init =()=> {
        getCategoryGoods('sales',currentCid.value).then(res=>{
          goods.sales.list = res.goods.data
        })
        getCategoryGoods('price',currentCid.value).then(res=>{
          goods.price.list = res.goods.data
        })
        getCategoryGoods('comments_count',currentCid.value).then(res=>{
          goods.comments_count.list = res.goods.data
        })
      }

      let bscroll = reactive({}); //   声明在外层 共用

      onMounted(()=>{
        //1 获取分类
        getCategory().then(res=>{
          // console.log(res)
          // 3.获取res.categories的值,并赋给categories.value
          categories.value  = res.categories
          // console.log(categories.value)
        })
        //只需一个,因为默认就是「sales」
        getCategoryGoods('sales',currentCid.value).then(res=>{
          goods.sales.list = res.goods.data
        })

        //  6 创建 BetterScroll 对象
        bscroll = new BScroll(document.querySelector(".goodslist"), {
          // 获取到最外层元素
          probeType: 3, // 0,1,2,3, 3 只要在运行就触发 scroll 事件
          click: true, // 是否允许点击
          pullUpLoad: true, // 上拉加载更多,默认 false
        });
        //注册滚动事件
        bscroll.on('scroll',(position)=>{
         isShowBackTop.value = (-position.y)>30
        })
        // 10 上拉加载更多数据,触发 pullingUp
        bscroll.on("pullingUp", () => {
          console.log('上拉加载更多......')

          const pages = goods[currentOrder.value].page +1

          getCategoryGoods(currentOrder.value ,currentCid.value).then(res=>{
            goods[currentOrder.value].list.push(...res.goods.data)
            goods[currentOrder.value].page += 1

          })


          // 完成上拉,等数据请求完成,要将新数据展示出来
          bscroll.finishPullUp();

          //延迟效果
          nextTick(() => {
            // 当 DOM 渲染完了执行方法
            // 重新计算高度
            bscroll && bscroll.refresh();
          });

          // 刷新 重新计算高度
          bscroll.refresh();
          console.log('centerHeight:' + document.querySelector('.content').clientHeight)
          console.log('当前类型:' + currentType.value + ',当前页:' + page)
        });
      });


      //7 排序选项卡
      const tabClick = (index)=>{
        let orders = ['sales','price','comment_count']

        //9
        currentOrder.value = orders[index]
        //14 更新,重新排序
        getCategoryGoods(currentOrder.value ,currentCid.value).then(res=>{
          goods[currentOrder.value].list = res.goods.data

          //延迟效果
          nextTick(() => {
            // 当 DOM 渲染完了执行方法
            // 重新计算高度
            bscroll && bscroll.refresh();
          });
        })

        console.log('当前分类id'+ currentCid.value)
        console.log('排序的序号'+currentOrder.value)

      }
      //10 通过分类得到商品
      const getGoods = (cid) =>{
        currentCid.value = cid
        init()//重新赋值

        console.log('当前分类Id'+currentCid.value)
        console.log('排序的序号'+ currentCid.value)

      }

      //   监听 任何一个变量有变化就会被触发
      watchEffect(() => {
        nextTick(() => {
          // 当 DOM 渲染完了执行方法
          // 重新计算高度
          bscroll && bscroll.refresh();
        });
      });

      const bTop =()=>{
        bscroll.scrollTo(0,0,300)
      }

      return{
        activeKay,
        categories,
        activeName,
        active,
        tabClick,
        getGoods,
        currentCid,
        goods,
        showGoods,
        bscroll,
        isShowBackTop,
        bTop,
        itemClick:(id)=>{
          // console.log(id)
          router.push({path:'/detail',query:{id}})
        }

      }
    },
    components: {
      NavBar,
      BackTop
    }
  }
</script>

<style scoped lang="scss">
  #mainbox{
    margin-top: 45px;
    display: flex;

    .ordertab{
      height: 50px;
      flex: 1;
      float: right;
      z-index: 9;
      position: fixed;
      top: 45px;
      right: 0;
      left: 130px;

    }
    .leftmenu{
      width: 130px;
      position: fixed;
      top: 95px;
      left: 0;
      width: 130px;
    }
    .goodslist{
      flex: 1;
      position: absolute;
      top: 100px;
      left: 130px;
      right: 0;
      height: 100vh; /*  占领整个可用大小*/
      padding: 10px;
      text-align: left !important;
      /*.content{*/
      /*  !*background-color: darkred;*!*/
      /*  padding-top: 10px;*/
      /*}*/
    }
  }

  .van-card__thumb{
    width: 68px !important;
  }

</style>
复制代码

Detail.vue>>

<template>
  <div>
    <nav-bar>
      <template v-slot:default>商品详情:{{id}}</template>
    </nav-bar>
  </div>
</template>

<script>
  import NavBar from "../../components/common/navbar/NavBar";
  import {useRoute} from 'vue-router'
  import {ref} from 'vue'
  export default {
    name: "Detail",
    components: {
      NavBar
    },
    setup(){
      const route  = useRoute()
      let id = ref(0)
      id.value = route.query.id
      return{
        id
      }
    }
  }
</script>

<style scoped>

</style>
复制代码

detail.js >>

import {request} from "./request";

export function getDetail(id) {
  return request({
    url:'/api/goods/'+id,
  })
}
复制代码

detail.vue>>

<template>
  <div>
    <nav-bar>
      <template v-slot:default>商品详情:{{id}}</template>
    </nav-bar>


   
  </div>

</template>

<script>
  import NavBar from "../../components/common/navbar/NavBar";
  import {useRoute} from 'vue-router'
  import {ref,reactive,onMounted,toRefs} from 'vue'
  import {getDetail} from "../../network/detail";

  export default {
    name: "Detail",
    components: {
      NavBar
    },
    setup(){
      const route  = useRoute()
      let id = ref(0)
      let book = reactive({
        detail:{},
        like_goods:[]//相似图书
      })
      //1 挂载路由
      onMounted(()=>{
        id.value = route.query.id

      //  2 添加方法,通过  getDetail(),可以接收id
        getDetail(id.value).then(res=>{
          // console.log(res)
          book.detail = res.goods
          book.like_goods = res.like_goods
        })

      })
      return{
        id,
        ...toRefs(book)
      //  解构

      }
    }
  }
</script>

<style scoped>

</style>
复制代码

2 渲染产品数据到模板中

截屏2021-08-10 12.40.14.png

截屏2021-08-10 13.45.36.png

截屏2021-08-10 13.46.11.png

Image 引入

增强版的 img 标签,提供多种图片填充模式,支持图片懒加载、加载中提示、加载失败提示。

import Vue from 'vue';
import { Image as VanImage } from 'vant';

Vue.use(VanImage);
复制代码

图片懒加载

设置 lazy-load 属性来开启图片懒加载,需要搭配 Lazyload 组件使用。

<van-image
  width="100"
  height="100"
  lazy-load
  src="https://img01.yzcdn.cn/vant/cat.jpeg"
/>
复制代码

Card自定义内容

Card 组件提供了多个插槽,可以灵活地自定义内容。

<van-card
  num="2"
  price="2.00"
  desc="描述信息"
  title="商品标题"
  thumb="https://img01.yzcdn.cn/vant/ipad.jpeg"
>
  <template #tags>
    <van-tag plain type="danger">标签</van-tag>
    <van-tag plain type="danger">标签</van-tag>
  </template>
  <template #footer>
    <van-button size="mini">按钮</van-button>
    <van-button size="mini">按钮</van-button>
  </template>
</van-card>
复制代码

bass.css

@import "normalize.css";

:root{
    --color-text:#666;
    --color-high-text:pink;
    --color-tint:pink;
    --color-background:#FFFFFF;
    --font-size:14px;
    --line-height: 1.5;
}
*,*::before,*::after{
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
body{
    user-select: none;
    /*background-color: #FFFFFF;*/
    background: var(--color-background);
    color: var(--color-text);
    width: 100vw;
}
a{
    color: var(--color-text);
    text-decoration: none;
}
.left{
    float: left;
}
.right{
    float: right;
}

/*这里设置商品详情里的图片大小*/
img{
    max-width: 100%;
    height: auto ;
}
复制代码

detail.vue

<template>
  <div>
    <nav-bar>
      <template v-slot:default>商品详情:{{id}}</template>
    </nav-bar>

    <van-image style="margin-top: 50px"
      width="100%"
      lazy-load
      :src="detail.cover_url"
    />

    <van-card style="text-align: left"
      :num="detail.stock"
      :price="detail.price+'.00'"
      :desc="detail.description"
      :title="detail.title"
    >
      <template #tags>
        <van-tag plain type="danger">新书</van-tag>
        <van-tag plain type="danger">推荐</van-tag>
      </template>
      <template #footer>
        <van-button type="warning">加入购物车</van-button>
        <van-button type="danger">立即购买</van-button>
      </template>
    </van-card>

    <van-tabs v-model="active">
      <van-tab title="概述">
        <div class="content" v-html="detail.details"><!--v-html 解析了detail里面的内容,不建议使用{{detail.details}}-->
<!--          {{detail.details}}-->
        </div>
      </van-tab>
      <van-tab title="热评">

      </van-tab>
      <van-tab title="相关图书">
        <goods-list :goods="like_goods"></goods-list>

      </van-tab>
    </van-tabs>



  </div>

</template>

<script>
  import NavBar from "../../components/common/navbar/NavBar";
  import GoodsList from "../../components/content/goods/GoodsList";
  import {useRoute} from 'vue-router'
  import {ref,reactive,onMounted,toRefs} from 'vue'
  import {getDetail} from "../../network/detail";

  export default {
    name: "Detail",
    components: {
      NavBar,
      GoodsList
    },
    setup(){
      const route  = useRoute()
      let id = ref(0)
      let book = reactive({
        detail:{},
        like_goods:[]//相似图书
      })
      //1 挂载路由
      onMounted(()=>{
        id.value = route.query.id

      //  2 添加方法,通过  getDetail(),可以接收id
        getDetail(id.value).then(res=>{
          // console.log(res)
          book.detail = res.goods
          book.like_goods = res.like_goods
          // console.log(book.like_goods)
        })

      })
      return{
        id,
        ...toRefs(book)
      //  解构

      }
    }
  }
</script>

<style scoped lang="scss">
.content{
  padding: 10px;
}
</style>
复制代码

5 项目页面—— 用户界面

需要授权(token)

1 用户注册的组件开发

截屏2021-08-10 15.09.50.png

Form 表单 引入

用于数据录入、校验,支持输入框、单选框、复选框、文件上传等类型,需要与 Field 输入框 组件搭配使用。2.5 版本开始支持此组件。

import Vue from 'vue';
import { Form } from 'vant';
import { Field } from 'vant';

Vue.use(Form);
Vue.use(Field);
复制代码

基础用法

在表单中,每个 Field 组件 代表一个表单项,使用 Field 的 rules 属性定义校验规则。

<van-form @submit="onSubmit">
  <van-field
    v-model="username"
    name="用户名"
    label="用户名"
    placeholder="用户名"
    :rules="[{ required: true, message: '请填写用户名' }]"
  />
  <van-field
    v-model="password"
    type="password"
    name="密码"
    label="密码"
    placeholder="密码"
    :rules="[{ required: true, message: '请填写密码' }]"
  />
  <div style="margin: 16px;">
    <van-button round block type="info" native-type="submit">提交</van-button>
  </div>
</van-form>
复制代码

register.vue

<template>
  <div>
    <nav-bar>
      <template v-slot:default>新用户注册</template>
    </nav-bar>
    <div style="margin-top: 50px">
      <div style="text-align: center;padding: 50px">
        <van-image
          width="10rem"
          height="5rem"
          fit="contain"
          src="https://avatars.githubusercontent.com/u/49421343?s=60&v=4"
        />
      </div>
      <van-form @submit="onSubmit">
        <van-field
          v-model="name"
          name="用户名"
          label="用户名"
          placeholder="用户名"
          :rules="[{ required: true, message: '请填写用户名' }]"
        />
        <van-field
          v-model="password"
          type="password"
          name="密码"
          label="密码"
          placeholder="密码"
          :rules="[{ required: true, message: '请填写密码' }]"
        />
        <van-field
          v-model="password_confirmation"
          type="password"
          name="确认密码"
          label="确认密码"
          placeholder="确认密码"
          :rules="[{ required: true, message: '请填写一致的密码' }]"
        />
        <van-field
          v-model="email"
          name="电子邮箱"
          label="电子邮箱"
          placeholder="请输入正确的电子邮箱格式"
          :rules="[{ required: true, message: '请填写电子邮箱' }]"
        />
        <div style="margin: 16px;">
          <van-button round block type="info" color="pink" native-type="submit">提交</van-button>
        </div>
      </van-form>
    </div>
  </div>
</template>

<script>
  import NavBar from "../../components/common/navbar/NavBar";
  import {ref,reactive,toRefs} from 'vue'
  export default {
    name: "Register",
    components:{
      NavBar
    },
    setup(){
      const userinfo = reactive({
        name:'',
        password:'',
        password_confirmation:'',
        email:''

      })
      const onSubmit = ()=>{
        console.log('####')//前端验证
      }

      return{
        ...toRefs(userinfo),
        onSubmit
      }
    }
  }
</script>

<style scoped>

</style>
复制代码

2 用户注册和验证

截屏2021-08-10 15.34.42.png

register.vue

<template>
  <div>
    <nav-bar>
      <template v-slot:default>新用户注册</template>
    </nav-bar>
    <div style="margin-top: 50px">
      <div style="text-align: center;padding: 50px">
        <van-image
          width="10rem"
          height="5rem"
          fit="contain"
          src="https://avatars.githubusercontent.com/u/49421343?s=60&v=4"
        />
      </div>
      <van-form @submit="onSubmit">
        <van-field
          v-model="name"
          name="用户名"
          label="用户名"
          placeholder="用户名"
          :rules="[{ required: true, message: '请填写用户名' }]"
        />
        <van-field
          v-model="password"
          type="password"
          name="密码"
          label="密码"
          placeholder="密码"
          :rules="[{ required: true, message: '请填写密码' }]"
        />
        <van-field
          v-model="password_confirmation"
          type="password"
          name="确认密码"
          label="确认密码"
          placeholder="确认密码"
          :rules="[{ required: true, message: '请填写一致的密码' }]"
        />
        <van-field
          v-model="email"
          name="电子邮箱"
          label="电子邮箱"
          placeholder="请输入正确的电子邮箱格式"
          :rules="[{ required: true, message: '请填写电子邮箱' }]"
        />
        <div style="margin: 16px;">
          <van-button round block type="info" color="pink" native-type="submit">提交</van-button>
        </div>
      </van-form>
    </div>
  </div>
</template>

<script>

  import NavBar from "../../components/common/navbar/NavBar";
  import {ref,reactive,toRefs} from 'vue';
  import {register} from "../../network/user";
  import {Notify,Toast} from 'vant';//消息通知方法,Toast 轻提示
  import {useRouter} from 'vue-router'

  export default {
    name: "Register",
    components:{
      NavBar
    },
    setup(){
      const router = useRouter()
      const userinfo = reactive({
        name:'',
        password:'',
        password_confirmation:'',
        email:''
      })
      const onSubmit = ()=>{
        // console.log('####')//响应拦截
      // 1 先验证。。。
        if (userinfo.password != userinfo.password_confirmation) {
          Notify('两次密码不一致。。。');
        }else {
          // 再提交,把所有提交的数据返回,并
          register(userinfo).then((res) => {
            if (res.status === 201) {
              Toast.success("注册成功");
              setTimeout(() => {
                // 注册成功之后 1秒钟 跳转到登录页面
                router.push({ path: "/login" });
              }, 1000);
            }
            // 清空密码表单
            userinfo.password = "";
            userinfo.password_confirmation = "";
          });
          
        }

      // 2 提交给后端
      }

      return{
        ...toRefs(userinfo),
        onSubmit,


      }
    }
  }
</script>

<style scoped>

</style>
复制代码

request.js

import axios from 'axios';
import { Notify } from 'vant';//消息通知方法

export function request(config) {
  const instance = axios.create({
    baseURL:'https://api.shop.eduwork.cn',
    timeout:5000
  })
//  请求拦截
  instance.interceptors.request.use(config=>{
    //如果有一个接口需要认证才可以访问,就在此统一设置,(token)

    //直接放行
    return config
  },error => {

  })

//  响应拦截
  instance.interceptors.response.use(res=>{
    // console.log(res)
    return res.data ? res.data : res
  },err => {
  //如果需要授权才可以访问的接口,统一去login授权

  //  如果有错误,在这里处理,根据状态码判断错误类型,
  //   console.log(err.response.data.errors[Object.keys(err.response.data.errors)][0])
    Notify(err.response.data.errors[Object.keys(err.response.data.errors)[0]][0]);

  })
  return instance(config)

}
复制代码

3 用户登录开发

截屏2021-08-12 10.21.03.png

Login.vue

<template>
  <div>
    <nav-bar>
      <template v-slot:default>用户登录</template>
    </nav-bar>
    <div style="margin-top: 50px">
      <div style="text-align: center;padding: 50px">
        <van-image
          width="10rem"
          height="5rem"
          fit="contain"
          src="https://avatars.githubusercontent.com/u/49421343?s=60&v=4"
        />
      </div>
      <van-form @submit="onSubmit">
        <van-field
          v-model="email"
          name="email"
          label="email"
          placeholder="email"
          :rules="[{ required: true, message: '请填写email' }]"
        />
        <van-field
          v-model="password"
          type="password"
          name="密码"
          label="密码"
          placeholder="密码"
          :rules="[{ required: true, message: '请填写密码' }]"
        />

        <div style="margin: 16px;">
          <div class="link_login" @click="$router.push({path:'/register'})">
            没有账号,立即注册
          </div>
          <van-button round block type="info" color="pink" native-type="submit">提交</van-button>
        </div>
      </van-form>
    </div>
  </div>
</template>

<script>
   // 用户名: eduwork2 密码:user123 邮箱:eduwork2@imonkey.com

  import NavBar from "../../components/common/navbar/NavBar";
  import {ref,reactive,toRefs} from 'vue';
  import {login} from "../../network/user";
  import {Notify,Toast} from 'vant';//消息通知方法,Toast 轻提示
  import {useRouter} from 'vue-router'

  export default {
    name: "Login",
    components:{
      NavBar
    },
    setup(){
      const router = useRouter()
      const userinfo = reactive({
        email:'',
        password:'',
      })
      const onSubmit = ()=>{
        // console.log('####')//响应拦截
        //双向绑定
        login(userinfo).then(res=>{
        //1、服务器会返回token:acsess-token,将token保存到本地,window.LocalStorage setItem(key,value) getItem(key)
          window.localStorage.setItem('token',res.access_token)
        // (或者是) 在vuex里islogin判断保存
          Toast.success('登录成功')
          
          userinfo.email=''
          userinfo.password=''

          setTimeout(()=>{
            router.go(-1)
          },500)
        //
        })

      }


      return{
        ...toRefs(userinfo),
        onSubmit
      }
    }
  }
</script>

<style scoped>
.link_login{
  font-size: 14px;
  margin-bottom: 20px;
  color: #990055;
  display: inline-block;
  text-align: left;
  float: left;
}
</style>
复制代码

user.js

//所有跟用户相关
//注册
//登录
//验证

import {request} from "./request";


export function register(data) {
  return request({
    url:'/api/auth/register',
    method:'post',
    data
  })
}


export function login(data) {
  return request({
    url:'/api/auth/login',
    method:'post',
    data
  })
}
复制代码

4 用户登录授权方案处理

截屏2021-08-12 10.24.37.png

关于授权方案有两个:

1.页面级别的授权

2.接口级别的授权

profile.vue 先搭好个人界面

截屏2021-08-12 12.06.01.png

<template>
  <div>
    <nav-bar>
      <template v-slot:default>个人主页</template>
    </nav-bar>

    <div style="margin:15px;margin-top: 100px">
      <van-button @click="tologout" round block color="pink">退出登录</van-button>
    </div>
  </div>
</template>

<script>
  import NavBar from "../../components/common/navbar/NavBar";
  import {logout} from "../../network/user";
  import {Toast} from 'vant'
  import {useRouter} from 'vue-router'
  import {useStore} from 'vuex'

  export default {
    name: "Profile",
    components: {
      NavBar
    },
    setup(){
      const router = useRouter()

      const tologout =()=>{
        logout().then(res=>{
          if (res.status === 204) {
            Toast.success('退出成功')
          //  清除token window。localStorage
            window.localStorage.setItem('token','')

            store.commit('setIsLogin',false)

            setTimeout(()=>{
              router.push({path:'/login'})
            },500)
          }
        })
      }
      return{
        tologout,
      }
    }
  }
</script>

<style scoped>

</style>
复制代码

router/index.js

import { createRouter, createWebHistory } from 'vue-router'
const Home = () => import('../views/home/Home.vue')
const Category = () => import('../views/category/Category.vue')
const Detail = () => import('../views/detail/Detail.vue')
const Profile = () => import('../views/profile/Profile.vue')
const ShopCart = () => import('../views/shopcart/ShopCart.vue')
const Register =()=>import('../views/profile/Register.vue')
const Login =()=>import('../views/profile/Login.vue')
import store from '../store'
import {Notify, Toast} from 'vant'

//写路由
const routes = [
  {
    path: '',
    name: 'DefaultHome',
    component: Home,
    meta:{
      title:'fy-shop'
    }
  },
  {
    path: '/',
    name: 'Home',
    component: Home,
    meta:{
      title:'fy-shop'
    }
  },
  {
    path: '/category',
    name: 'Category',
    component: Category,
    meta:{
      title:'商品分类'
    }
  },
  {
    path: '/detail',
    name: 'Detail',
    component: Detail,
    meta:{
      title:'商品详情'
    }
  },{
    path: '/profile',
    name: 'Profile',
    component: Profile,
    meta:{
      title:'个人中心',
      isAuthRequired:true
    }
  },{
    path: '/register',
    name: 'Register',
    component: Register,
    meta:{
      title:'用户注册',

    }
  },{
    path: '/login',
    name: 'Login',
    component: Login,
    meta:{
      title:'用户登录'
    }
  },{
    path: '/shopcart',
    name: 'ShopCart',
    component: ShopCart,
    meta:{
      title:'购物车',
      isAuthRequired:true

    }
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

router.beforeEach((to,from,next)=>{
    //如果没有登录,在这里到login
  if (to.meta.isAuthRequired && store.state.user.isLogin === false){
    //提示一下
    Notify('还未登录,请登录')
    return next('/login')
  }else {
    next()
  }

  document.title = to.meta.title
}
  )
export default router
复制代码

Login.vue

<template>
  <div>
    <nav-bar>
      <template v-slot:default>用户登录</template>
    </nav-bar>
    <div style="margin-top: 50px">
      <div style="text-align: center;padding: 50px">
        <van-image
          width="10rem"
          height="5rem"
          fit="contain"
          src="https://avatars.githubusercontent.com/u/49421343?s=60&v=4"
        />
      </div>
      <van-form @submit="onSubmit">
        <van-field
          v-model="email"
          name="email"
          label="email"
          placeholder="email"
          :rules="[{ required: true, message: '请填写email' }]"
        />
        <van-field
          v-model="password"
          type="password"
          name="密码"
          label="密码"
          placeholder="密码"
          :rules="[{ required: true, message: '请填写密码' }]"
        />

        <div style="margin: 16px;">
          <div class="link_login" @click="$router.push({path:'/register'})">
            没有账号,立即注册
          </div>
          <van-button round block type="info" color="pink" native-type="submit">提交</van-button>
        </div>
      </van-form>
    </div>
  </div>
</template>

<script>
   // 用户名: eduwork2 密码:user123 邮箱:eduwork2@imonkey.com

  import NavBar from "../../components/common/navbar/NavBar";
  import {ref,reactive,toRefs} from 'vue';
  import {login} from "../../network/user";
  import {Notify,Toast} from 'vant';//消息通知方法,Toast 轻提示
  import {useRouter} from 'vue-router'
  import {useStore} from 'vuex'

  export default {
    name: "Login",
    components:{
      NavBar
    },
    setup(){
      const router = useRouter()
      const store = useStore()
      const userinfo = reactive({
        email:'',
        password:'',
      })
      const onSubmit = ()=>{
        //双向绑定
        login(userinfo).then(res=>{
          //1、服务器会返回token:acsess-token,将token保存到本地,window.LocalStorage setItem(key,value) getItem(key)
          window.localStorage.setItem('token',res.access_token)
        // (或者是) 在vuex里islogin判断保存
          store.commit('setIsLogin',true)

          Toast.success('登录成功')

          userinfo.email=''
          userinfo.password=''

          setTimeout(()=>{
            router.go(-1)
          },500)
        //
        })

      }


      return{
        ...toRefs(userinfo),
        onSubmit
      }
    }
  }
</script>

<style scoped>
.link_login{
  font-size: 14px;
  margin-bottom: 20px;
  color: #990055;
  display: inline-block;
  text-align: left;
  float: left;
}
</style>
复制代码

request.js

import axios from 'axios';
import { Notify,Toast } from 'vant';
import router from "../router";

//消息通知方法

export function request(config) {
  const instance = axios.create({
    baseURL:'https://api.shop.eduwork.cn',
    timeout:5000
  })
//  请求拦截
  instance.interceptors.request.use(config=>{
    //如果有一个接口需要认证才可以访问,就在此统一设置,(token)
    const token = window.localStorage.getItem('token')

    if (token) {
      //头信息
      config.headers.Authorization = 'Beater'+token
    }
    //直接放行
    return config
  },error => {

  })

//  响应拦截
  instance.interceptors.response.use(res=>{
    // console.log(res)
    return res.data ? res.data : res
  },err => {
  //如果需要授权才可以访问的接口,统一去login授权
    if (err.response.status == '401'){
      Toast.fail('请先登录')
      router.push({path:'/login'})
    }

  //  如果有错误,在这里处理,根据状态码判断错误类型,
  //   console.log(err.response.data.errors[Object.keys(err.response.data.errors)][0])
    Notify(err.response.data.errors[Object.keys(err.response.data.errors)[0]][0]);

  })
  return instance(config)

}
复制代码

store

截屏2021-08-12 12.22.05.png
store/index.js

import { createStore } from 'vuex'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'

const state = {
  user:{
    isLogin:window.localStorage.getItem('token')? true:false
  }
}
export default createStore({
  state,
  mutations,
  actions,
  getters

})
复制代码

mutations.js

const mutations ={
  setIsLogin(state,payload){
    state.user.isLogin = payload
  }
}

export default mutations
复制代码

actions.js

const actions = {


}

export default actions
复制代码

getters.js

const getters = {

}

export default getters
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享