商品详情
01-商品详情-组件基础
目标:完成路由配置和组件准备,渲染面包屑
大致步骤:
- 组件准备
- 路由配置
- 数据获取
- 面包屑渲染,加载中效果
具体代码:
- 组件基础
views/goods/index.vue
<template>
<div class="xtx-goods-page">
<div class="container">
<XtxBread>
<XtxBreadItem to="/">首页</XtxBreadItem>
<XtxBreadItem to="/">类目</XtxBreadItem>
<XtxBreadItem>商品名称</XtxBreadItem>
</XtxBread>
</div>
</div>
</template>
<script>
export default {
name: "xtx-goods-page",
};
</script>
<style lang="less" scoped>
.loading {
height: 600px;
border-top: 72px solid #f5f5f5;
background: #fff url(../../assets/load.gif) no-repeat center / 80px 80px;
}
</style>
- 路由规则
router/index.js
const Goods = () => import("@/views/goods/index");
children: [
{ path: "/", component: Home },
{ path: "/category/:id", component: Category },
{ path: "/search", component: Search },
+ { path: "/product/:id", component: Goods },
],
- 数据获取
api/goods.js
// 商品详情
export const getGoods = (id) => request("/goods", "get", { id });
views/goods/index.vue
import { onMounted, ref } from "vue";
import { getGoods } from "@/api/goods";
import { onBeforeRouteUpdate, useRoute } from "vue-router";
export default {
name: "xtx-goods-page",
setup() {
const route = useRoute();
const goods = ref({});
// 加载数据函数
const loadData = async (id) => {
const data = await getGoods(id);
goods.value = data.result;
};
// 初始化加载
onMounted(() => loadData(route.params.id));
// 将来切换商品加载
onBeforeRouteUpdate((to, from, next) => {
loadData(to.params.id);
next();
});
return { goods };
},
};
- 面包屑渲染
<div class="xtx-goods-page">
<div v-if="loading" class="container">
<div class="loading"></div>
</div>
<div v-else class="container">
<XtxBread>
<XtxBreadItem to="/">首页</XtxBreadItem>
<XtxBreadItem :to="`/category/${goods.categories[1].id}`">
{{ goods.categories[1].name }}
</XtxBreadItem>
<XtxBreadItem>{{ goods.name }}</XtxBreadItem>
</XtxBread>
</div>
</div>
02-商品详情-商品信息区块
目标:完成商品图片,基本信息展示
大致步骤:
- 图片预览组件
- 销售统计组件
- 商品信息组件
- 商品组件使用以上两个组件
- 渲染,城市组件使用
落地代码:
- 销售统计组件
goods/components/goods-sales.vue
<template>
<ul class="goods-sales">
<li>
<p>销量人气</p>
<p>200+</p>
<p><i class="iconfont icon-task-filling"></i>销量人气</p>
</li>
<li>
<p>商品评价</p>
<p>400+</p>
<p><i class="iconfont icon-comment-filling"></i>查看评价</p>
</li>
<li>
<p>收藏人气</p>
<p>600+</p>
<p><i class="iconfont icon-favorite-filling"></i>收藏商品</p>
</li>
<li>
<p>品牌信息</p>
<p>苏宁电器</p>
<p><i class="iconfont icon-dynamic-filling"></i>品牌主页</p>
</li>
</ul>
</template>
<script>
export default {
name: "GoodsSales",
};
</script>
<style scoped lang="less">
.goods-sales {
display: flex;
width: 400px;
align-items: center;
text-align: center;
height: 140px;
li {
flex: 1;
position: relative;
~ li::after {
position: absolute;
top: 10px;
left: 0;
height: 60px;
border-left: 1px solid #e4e4e4;
content: "";
}
p {
&:first-child {
color: #999;
}
&:nth-child(2) {
color: var(--price-color);
margin-top: 10px;
}
&:last-child {
color: #666;
margin-top: 10px;
i {
color: var(--xtx-color);
font-size: 14px;
margin-right: 2px;
}
&:hover {
color: var(--xtx-color);
cursor: pointer;
}
}
}
}
}
</style>
- 商品信息组件
goods/components/goods-info.vue
<template>
<p class="g-name">2件装 粉釉花瓣心意点缀 点心盘*2 碟子盘子</p>
<p class="g-desc">花瓣造型干净简约 多功能使用堆叠方便</p>
<p class="g-price">
<span>108.00</span>
<span>199.00</span>
</p>
<div class="g-service">
<dl>
<dt>促销</dt>
<dd>12月好物放送,App领券购买直降120元</dd>
</dl>
<dl>
<dt>配送</dt>
<dd>至 </dd>
</dl>
<dl>
<dt>服务</dt>
<dd>
<span>无忧退货</span>
<span>快速退款</span>
<span>免费包邮</span>
<a href="javascript:;">了解详情</a>
</dd>
</dl>
</div>
</template>
<script>
export default {
name: "GoodsInfo",
};
</script>
<style lang="less" scoped>
.g-name {
font-size: 22px;
}
.g-desc {
color: #999;
margin-top: 10px;
}
.g-price {
margin-top: 10px;
span {
&::before {
content: "¥";
font-size: 14px;
}
&:first-child {
color: var(--price-color);
margin-right: 10px;
font-size: 22px;
}
&:last-child {
color: #999;
text-decoration: line-through;
font-size: 16px;
}
}
}
.g-service {
background: #f5f5f5;
width: 500px;
padding: 20px 10px 0 10px;
margin-top: 10px;
dl {
padding-bottom: 20px;
display: flex;
align-items: center;
dt {
width: 50px;
color: #999;
}
dd {
color: #666;
&:last-child {
span {
margin-right: 10px;
&::before {
content: "•";
color: var(--xtx-color);
margin-right: 2px;
}
}
a {
color: var(--xtx-color);
}
}
}
}
}
</style>
- 使用以上两个组件
goods/index.vue
import GoodsSales from "./components/goods-sales.vue";
import GoodsInfo from "./components/goods-info.vue";
export default {
name: "xtx-goods-page",
components: { GoodsSales, GoodsInfo },
<!-- 商品信息 -->
<div class="goods-wrapper">
<div class="media">
+ <XtxImageView :images="goods.mainPictures"></XtxImageView>
+ <GoodsSales />
</div>
<div class="spec">
+ <GoodsInfo :goods="goods" />
</div>
</div>
.goods-wrapper {
display: flex;
background: #fff;
padding: 40px;
.media {
width: 480px;
margin-right: 40px;
}
}
- 渲染,城市组件使用
import { ref } from "vue";
export default {
name: "GoodsInfo",
props: {
goods: {
type: Object,
default: () => ({}),
},
},
setup() {
// 城市组件默认地区
const fullLocation = ref("北京 北京市 东城区");
const change = (ret) => {
// 选择地区后修改
fullLocation.value = ret.fullLocation;
};
return { fullLocation, change };
},
};
<p class="g-name">{{ goods.name }}</p>
<p class="g-desc">{{ goods.desc }}</p>
<p class="g-price">
<span>{{ goods.price }}</span>
<span>{{ goods.oldPrice }}</span>
</p>
<XtxCity :fullLocation="fullLocation" @change="change" />
03-商品详情-SKU说明
目标:理解电商SPU和SKU概念,测试SKU组件数据
大致步骤:
- 理解SPU和SKU概念
- 使用SKU组件
- 使用数量组件
- 使用按钮组件
落地代码:
-
理解SPU和SKU概念
- SPU = Standard Product Unit (标准产品单位)如: iphone13 Redmi 1A iphone13 pro
- SKU = Stock keeping Unit (库存量单位) 如: iphone13(64G+金色) iphone13(64G+银色)
-
使用SKU组件
<!-- sku组件 -->
<XtxSku :goods="goods" @change="changeSku"></XtxSku>
// 选择SKU
const changeSku = (sku) => {
console.log(sku);
};
return { goods, loading, changeSku };
====== 打印sku如下
{
// sku的ID,现价,原价,库存,属性集合
"skuId": "300485161",
"price": "259.00",
"oldPrice": "279.00",
"inventory": 9857,
"specsText": "颜色:白粉/紫 尺码:22"
}
- 使用数量组件
<!-- 数量 -->
<XtxNumbox :min="1" v-model="count" label="数量" />
// 数量
const count = ref(1);
return { goods, loading, changeSku, count };
- 使用按钮组件
<!-- 按钮 -->
<XtxButton type="primary" style="margin-top: 20px; margin-left: 60px">
加入购物车
</XtxButton>
总结:
- 加入购物车,去下订单,去支付用商品的SKUID
04-商品详情-商品底部区块
目标:展示商品详情,展示每日热榜
大致步骤:
- 底部组件准备和使用
- 渲染组件
落地代码:
- 准备底部组件
goods/components/goods-footer.vue
<template>
<!-- 商品详情 -->
<div class="goods-footer">
<div class="left">
<div class="goods-detail">
<div class="head">商品详情</div>
<div class="content">
<img
src="https://yanxuan-item.nosdn.127.net/38e2952b2ad8ce881860e0416b07d6ce.jpg"
alt=""
/>
</div>
</div>
</div>
<div class="right">
<div class="goods-hot">
<h3>每日热榜</h3>
<GoodsItem v-for="item in 4" :key="item" />
</div>
</div>
</div>
</template>
<script>
export default {
name: "GoodsFooter",
};
</script>
<style lang="less" scoped>
.goods-footer {
display: flex;
margin-top: 20px;
.left {
width: 940px;
margin-right: 20px;
}
.right {
width: 280px;
min-height: 1000px;
}
}
.goods-detail {
background: #fff;
.head {
height: 70px;
line-height: 70px;
font-size: 18px;
padding: 0 40px;
border-bottom: 1px solid #f5f5f5;
}
.content {
padding: 40px;
img {
width: 100%;
}
}
}
.goods-hot {
h3 {
height: 70px;
background: var(--help-color);
color: #fff;
font-size: 18px;
line-height: 70px;
padding-left: 25px;
margin-bottom: 10px;
font-weight: normal;
}
::v-deep .goods-item {
background: #fff;
width: 100%;
margin-bottom: 10px;
img {
width: 200px;
height: 200px;
}
p {
margin: 0 10px;
}
&:hover {
transform: none;
box-shadow: none;
}
}
}
</style>
goods/index.vue
+ <!-- 商品底部 -->
+ <GoodsFooter :goods="goods" />
</div>
</div>
</template>
+import GoodsFooter from "./components/goods-footer.vue";
export default {
name: "xtx-goods-page",
+ components: { GoodsSales, GoodsInfo, GoodsFooter },
- 渲染组件
<template>
<!-- 商品详情 -->
<div class="goods-footer">
<div class="left">
<div class="goods-detail">
<div class="head">商品详情</div>
<div class="content">
<img v-for="(img, i) in goods.details.pictures" :src="img" :key="i" />
</div>
</div>
</div>
<div class="right">
<div class="goods-hot">
<h3>每日热榜</h3>
<GoodsItem
v-for="item in goods.hotByDay"
:key="item.id"
:goods="item"
/>
</div>
</div>
</div>
</template>
<script>
export default {
name: "GoodsFooter",
props: {
goods: {
type: Object,
default: () => ({}),
},
},
};
</script>
总结:
- 商品的图片一般是分割开,拼在一起
登录模块
05-登录-组件基础
目标:配置路由和基准组件布局
大致步骤:
- 路由与组件
- 组件基础布局
具体代码:
1)路由与组件
组件:src/views/login/index.vue
<template>
<div class="xtx-login-page">
Login
</div>
</template>
<script>
export default {
name: 'xtx-login-page'
}
</script>
<style scoped lang="less"></style>
路由:src/router/index.js 一级路由规则
+const Login = () => import('@/views/login/index')
...
const routes = [
...
+ { path: '/login', component: Login }
]
链接:src/components/app-topnav.vue
<li><RouterLink to="/login">请先登录</RouterLink></li>
2)组件基础布局
- 头部组件
src/views/login/components/login-header.vue
<template>
<header class="login-header">
<div class="container">
<h1 class="logo"><RouterLink to="/">小兔鲜</RouterLink></h1>
<h3 class="sub"><slot /></h3>
<RouterLink class="entry" to="/">
进入网站首页
<i class="iconfont icon-angle-right"></i>
<i class="iconfont icon-angle-right"></i>
</RouterLink>
</div>
</header>
</template>
<script>
export default {
name: 'LoginHeader'
}
</script>
<style scoped lang='less'>
.login-header {
background: #fff;
border-bottom: 1px solid #e4e4e4;
.container {
display: flex;
align-items: flex-end;
justify-content: space-between;
}
.logo {
width: 200px;
a {
display: block;
height: 132px;
width: 100%;
text-indent: -9999px;
background: url(../../../assets/logo.png) no-repeat center 18px / contain;
}
}
.sub {
flex: 1;
font-size: 24px;
font-weight: normal;
margin-bottom: 38px;
margin-left: 20px;
color: #666;
}
.entry {
width: 120px;
margin-bottom: 38px;
font-size: 16px;
i {
font-size: 14px;
color: var(--xtx-color);
letter-spacing: -5px;
}
}
}
</style>
- 底部组件
src/views/login/components/login-footer.vue
<template>
<footer class="login-footer">
<div class='container'>
<p>
<a href="javascript:;">关于我们</a>
<a href="javascript:;">帮助中心</a>
<a href="javascript:;">售后服务</a>
<a href="javascript:;">配送与验收</a>
<a href="javascript:;">商务合作</a>
<a href="javascript:;">搜索推荐</a>
<a href="javascript:;">友情链接</a>
</p>
<p>CopyRight © 小兔鲜儿</p>
</div>
</footer>
</template>
<script>
export default {
name: 'LoginFooter'
}
</script>
<style scoped lang='less'>
.login-footer {
padding: 30px 0 50px;
background: #fff;
p {
text-align: center;
color: #999;
padding-top: 20px;
a {
line-height: 1;
padding:0 10px;
color: #999;
display: inline-block;
~ a {
border-left: 1px solid #ccc;
}
}
}
}
</style>
- 主体组件
src/views/login/index.vue
<template>
<LoginHeader>欢迎登录</LoginHeader>
<section class="login-section">
<div class="wrapper">
<nav>
<a href="javascript:;" class="active">账户登录</a>
<a href="javascript:;">扫码登录</a>
</nav>
</div>
</section>
<LoginFooter />
</template>
<script>
import LoginHeader from "./components/login-header";
import LoginFooter from "./components/login-footer";
export default {
name: "xtx-login-page",
components: {
LoginHeader,
LoginFooter,
},
};
</script>
<style scoped lang="less">
.login-section {
background: url(../../assets/login-bg.png) no-repeat center / cover;
height: 488px;
position: relative;
.wrapper {
width: 380px;
background: #fff;
min-height: 400px;
position: absolute;
left: 50%;
top: 54px;
transform: translate3d(100px, 0, 0);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
nav {
height: 55px;
border-bottom: 1px solid #f5f5f5;
display: flex;
padding: 0 40px;
text-align: right;
align-items: center;
a {
flex: 1;
line-height: 1;
display: inline-block;
font-size: 18px;
position: relative;
&:first-child {
border-right: 1px solid #f5f5f5;
text-align: left;
}
&.active {
color: var(--xtx-color);
font-weight: bold;
}
}
}
}
}
</style>
06-登录-表单组件
目的:完成表单布局和帐号登录,短信登录切换。
大致步骤:
- 组件基础结构
- 使用组件
- 交互效果
- 通过isMsgLogin切换短信登录
- 通过form.isAgree绑定同意协议
落的代码:
**1)**定义组件 src/views/login/component/login-form.vue
<template>
<div class="login-form">
<div class="toggle">
<a href="javascript:;">
<i class="iconfont icon-user"></i> 使用账号登录
</a>
<a href="javascript:;">
<i class="iconfont icon-msg"></i> 使用短信登录
</a>
</div>
<div class="form">
<div class="form-item">
<div class="input">
<i class="iconfont icon-user"></i>
<input type="text" placeholder="请输入账号" />
</div>
<!-- <div class="error"><i class="iconfont icon-warning" />请输入手机号</div> -->
</div>
<div class="form-item">
<div class="input">
<i class="iconfont icon-lock"></i>
<input type="password" placeholder="请输入密码" />
</div>
</div>
<div class="form-item">
<div class="input">
<i class="iconfont icon-user"></i>
<input type="text" placeholder="请输入手机号" />
</div>
</div>
<div class="form-item">
<div class="input">
<i class="iconfont icon-code"></i>
<input type="password" placeholder="请输入验证码" />
<span class="code">发送验证码</span>
</div>
</div>
<div class="form-item">
<div class="agree">
<XtxCheckbox />
<span>我已同意</span>
<a href="javascript:;">《隐私条款》</a>
<span>和</span>
<a href="javascript:;">《服务条款》</a>
</div>
</div>
<a href="javascript:;" class="btn">登录</a>
</div>
<div class="action">
<div class="url">
<a href="javascript:;">忘记密码</a>
<a href="javascript:;">免费注册</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: "LoginForm",
};
</script>
<style lang="less" scoped>
// 账号容器
.login-form {
.toggle {
padding: 15px 40px;
text-align: right;
a {
color: var(--xtx-color);
i {
font-size: 14px;
}
}
}
.form {
padding: 0 40px;
&-item {
margin-bottom: 28px;
.input {
position: relative;
height: 36px;
> i {
width: 34px;
height: 34px;
background: #cfcdcd;
color: #fff;
position: absolute;
left: 1px;
top: 1px;
text-align: center;
line-height: 34px;
font-size: 18px;
}
input {
padding-left: 44px;
border: 1px solid #cfcdcd;
height: 36px;
line-height: 36px;
width: 100%;
&.error {
border-color: var(--price-color);
}
&.active,
&:focus {
border-color: var(--xtx-color);
}
}
.code {
position: absolute;
right: 1px;
top: 1px;
text-align: center;
line-height: 34px;
font-size: 14px;
background: #f5f5f5;
color: #666;
width: 90px;
height: 34px;
cursor: pointer;
}
}
> .error {
position: absolute;
font-size: 12px;
line-height: 28px;
color: var(--price-color);
i {
font-size: 14px;
margin-right: 2px;
}
}
}
.agree {
a {
color: #069;
}
}
.btn {
display: block;
width: 100%;
height: 40px;
color: #fff;
text-align: center;
line-height: 40px;
background: var(--xtx-color);
&.disabled {
background: #cfcdcd;
}
}
}
.action {
padding: 20px 40px;
display: flex;
justify-content: flex-end;
align-items: center;
.url {
a {
color: #999;
margin-left: 10px;
}
}
}
}
</style>
**2)**使用组件 src/views/login/index.vue
+<LoginForm v-if="activeName==='account'"></LoginForm>
+import LoginForm from './components/login-form'
import { ref } from 'vue'
export default {
name: 'PageLogin',
components: {
LoginHeader,
LoginFooter,
+ LoginForm
},
**3)**交互效果
<a @click="isMsgLogin = false" href="javascript:;" v-if="isMsgLogin">
<i class="iconfont icon-user"></i> 使用账号登录
</a>
<a @click="isMsgLogin = true" href="javascript:;" v-else>
<i class="iconfont icon-msg"></i> 使用短信登录
</a>
+ <template v-if="!isMsgLogin">
<div class="form-item">//...</div>
<div class="form-item">//...</div>
</template>
+ <template v-else>
<div class="form-item">//...</div>
<div class="form-item">//...</div>
</template>
<XtxCheckbox v-model="form.isAgree" />
import { reactive, ref } from 'vue'
export default {
name: 'LoginForm',
setup () {
// 是否短信登录
const isMsgLogin = ref(false)
// 表单信息对象
const form = reactive({
isAgree: true
})
return { isMsgLogin, form }
}
}
07-登录-表单校验
目的:表单校验,兜底校验
大致步骤:
- 知道
vee-validate插件基本使用 - 使用
vee-validate插件- 安装
- 定义表单数据
- 定义校验规则
- 表单项校验
- 1.使用
Form组件添加校验规则 - 2.使用
Field组件替换元素表单元素,双向数据绑定 - 3.使用
ErrorMessage组件显示错误提示
- 1.使用
- 切换表单重置表单和数据
- 兜底校验
具体落地:
- 知道
vee-validate插件基本使用
// 1. 定义form表单数据对象
// 2. 定义form表单校验规则对象
// 3. from表单绑定校验规则对象
// 4. 表单元素field双向绑定form表单数据对象的属性,name属性指定校验规则
// 5. 错误组件显示错误提示
-
使用
vee-validate插件- 安装
yarn add vee-validate@4.0.3- 定义表单数据
// 表单信息对象 const form = ref({ isAgree: true, mobile: null, account: null, password: null, code: null, });- 定义校验规则
utils/schema.js
// 定义校验规则提供给vee-validate组件使用 export default { // 校验account account(value) { // value是将来使用该规则的表单元素的值 // 1. 必填 // 2. 6-20个字符,需要以字母开头 // 如何反馈校验成功还是失败,返回true才是成功,其他情况失败,返回失败原因。 if (!value) return "请输入用户名"; if (!/^[a-zA-Z]\w{5,19}$/.test(value)) return "字母开头且6-20个字符"; return true; }, password(value) { if (!value) return "请输入密码"; if (!/^\w{6,24}$/.test(value)) return "密码是6-24个字符"; return true; }, mobile(value) { if (!value) return "请输入手机号"; if (!/^1[3-9]\d{9}$/.test(value)) return "手机号格式错误"; return true; }, code(value) { if (!value) return "请输入验证码"; if (!/^\d{6}$/.test(value)) return "验证码是6个数字"; return true; }, isAgree(value) { if (!value) return "请勾选同意用户协议"; return true; }, };- 使用
Form组件添加校验规则
import { Form} from "vee-validate";const { account, password, mobile, code, isAgree } = schema; const formSchema = { account, password, mobile, code, isAgree }; return { isMsgLogin, form, formSchema };<Form :validation-schema="formSchema" autocomplete="off">- 使用
Field组件替换元素表单元素,双向数据绑定
import { Field } from "vee-validate";<Field v-model="form.account" name="account" type="text" placeholder="请输入账号" /><Field v-model="form.password" name="password" type="password" placeholder="请输入密码" /><Field v-model="form.mobile" name="mobile" type="text" placeholder="请输入手机号" /><Field v-model="form.code" name="code" type="text" placeholder="请输入验证码" /><!-- 自定组件:as XtxCheckbox 指定渲染的组件 --> <Field name="isAgree" as="XtxCheckbox" v-model="form.isAgree" />- 使用
ErrorMessage组件显示错误提示
<ErrorMessage name="account" v-slot="{ message }"> <div class="error" v-if="message"> <i class="iconfont icon-warning" />{{ message }} </div> </ErrorMessage><ErrorMessage name="password" v-slot="{ message }"> <div class="error" v-if="message"> <i class="iconfont icon-warning" />{{ message }} </div> </ErrorMessage><ErrorMessage name="mobile" v-slot="{ message }"> <div class="error" v-if="message"> <i class="iconfont icon-warning" />{{ message }} </div> </ErrorMessage><ErrorMessage name="code" v-slot="{ message }"> <div class="error" v-if="message"> <i class="iconfont icon-warning" />{{ message }} </div> </ErrorMessage><ErrorMessage name="isAgree" v-slot="{ message }"> <div class="error" v-if="message"> <i class="iconfont icon-warning" />{{ message }} </div> </ErrorMessage>- 切换表单重置表单和数据
const formCom = ref(null); watch(isMsgLogin, () => { form.value = { isAgree: true, mobile: null, account: null, password: null, code: null, }; formCom.value.resetForm(); }); // ... return { isMsgLogin, form, formSchema, formCom };- 兜底校验
const submit = async () => { const valid = await formCom.value.validate(); console.log(valid); } // ... return { isMsgLogin, form, formSchema, formCom, submit};<a @click="submit" href="javascript:;" class="btn">登录</a>
08-登录-账号登录
目的:使用账号密码登录
大致步骤:
- 定义账号登录API函数
- 判断校验通过,且是账号登录,发请求登录
- 成功后,存入vuex和本地
- 失败后,提示错误
落地代码:
- 定义账号登录API
api/user.js
import request from "@/utils/request";
// 账号密码登录
export const userAccountLogin = ({ account, password }) =>
request("/login", "post", { account, password });
- 判断校验通过,且是账号登录,发请求登录
import Message from 'erabbit-ui/packages/components/Message'
const store = useStore();
const router = useRouter();
const submit = async () => {
const valid = await formCom.value.validate();
console.log(valid);
if (valid) {
// 登录
if (!isMsgLogin.value) {
try {
const data = await userAccountLogin(form.value);
const { id, account, nickname, avatar, token, mobile } =
data.result;
store.commit("user/setProfile", {
id,
account,
nickname,
avatar,
token,
mobile,
});
router.push("/");
} catch (e) {
Message({
type: "error",
text: e.response.data.message || "登录失败",
});
}
}
}
};
消息提示组件
// type: warn 警告 error 错误 success 成功
Message({type: "",text: "提示文字"});
09-登录-退出登录
目的:退出登录,跳转页面
大致步骤:
- 绑定点击
- 清除用户信息,跳转登录
落地代码:
- 绑定点击
<template v-if="profile.token">
<li>
<a href="javascript:;"><i class="iconfont icon-user"></i>
{{profile.account}}
</a>
</li>
<li><a @click="logout()" href="javascript:;">退出登录</a></li>
</template>
- 清除用户信息,跳转登录
import { computed } from 'vue'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
export default {
name: 'AppTopnav',
setup () {
const store = useStore()
const profile = computed(() => {
return store.state.user.profile
})
+ const router = userRouter()
+ const logout = () => {
+ store.commit('user/setUser',{})
+ router.push('/login')
+ }
+ return { profile, logout}
}
}
10-登录-手机号登录
目的:使用手机号和验证码登录
大致步骤:
- 定义接口API函数(手机短信登录,获取短信验证码)
- 发送验证码
- 手机号短信登录 (验证码123456)
落地代码:
- 定义接口API函数(手机短信登录,获取短信验证码)
api/user.js
// 短信登录
export const userMobileLogin = ({ mobile, code }) => {
return request("/login/code", "post", { mobile, code });
};
// 获取短信登录验证码
export const userMobileLoginMsg = (mobile) => {
return request("/login/code", "get", { mobile });
};
- 发送验证码
<span @click="send" class="code">
{{ time > 0 ? `${time}秒后发送` : "发送验证码" }}
</span>
import { useIntervalFn } from "@vueuse/core";
// 倒计时
const time = ref(0);
const { pause, resume, isActive } = useIntervalFn(
() => {
time.value--;
if (time.value <= 0) {
pause();
}
},
1000,
false
);
// 发短信
const send = async () => {
const valid = mobile(form.value.mobile);
if (valid === true) {
if (!isActive.value) {
await userMobileLoginMsg(form.value.mobile);
Message({ type: "success", text: "发送成功" });
time.value = 60;
resume();
}
} else {
formCom.value.setFieldError("mobile", valid);
}
};
- 短信登录
// 账号登录
try {
let data = null;
if (!isMsgLogin.value) {
data = await userAccountLogin(form.value);
} else {
data = await userMobileLogin(form.value);
}