4 项目页面——商品详情页
通过首页和分类页面点击都可以进入商品详情页
1 获取商品详情
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 渲染产品数据到模板中
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 用户注册的组件开发
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 用户注册和验证
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 用户登录开发
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 用户登录授权方案处理
关于授权方案有两个:
1.页面级别的授权
2.接口级别的授权
profile.vue 先搭好个人界面
<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
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