一、退出登录
思路:
- 在头部组件
user/PageHeader.vue找到点击退出时的事件- 点击登录时传递空对象使数据为空,插件再更新本地储存为空
// 用户退出
handleLogout() {
this.$store.commit("user/userInfo", {});
},
二、注册功能
2.1注册表单的静态布局
element组件:
Form表单Input输入框Button按钮文件:
component/user/RegisterFrom
<template>
<!-- el-form 中:model绑定的是整个表单的数据 -->
<!-- ref:标识符 -->
<!-- rules:校验数据 -->
<!-- el-form-item:每个输入框 -->
<el-form :model="form" ref="form" :rules="rules" class="form">
<el-form-item class="form-item">
<el-input placeholder="用户名手机"> </el-input>
</el-form-item>
<el-form-item class="form-item">
<el-input placeholder="验证码">
<template slot="append">
<!-- handleSendCaptcha:点击发送验证码时调用事件 -->
<el-button @click="handleSendCaptcha"> 发送验证码 </el-button>
</template>
</el-input>
</el-form-item>
<el-form-item class="form-item">
<el-input placeholder="你的名字"> </el-input>
</el-form-item>
<el-form-item class="form-item">
<el-input placeholder="密码" type="password"></el-input>
</el-form-item>
<el-form-item class="form-item">
<el-input placeholder="确认密码" type="password"> </el-input>
</el-form-item>
<!-- handleRegSubmit:点击事件发送注册请求 -->
<el-button class="submit" type="primary" @click="handleRegSubmit">
注册
</el-button>
</el-form>
</template>
<script>
export default {
data() {
return {
//表单数据
form: {},
// 表单规则
rules: {},
};
},
methods: {
//发送验证码
handleSendCaptcha() {
console.log("我要验证码");
},
//注册请求
handleRegSubmit() {
console.log("我要注册");
},
},
};
</script>
<style scoped lang="less">
.form {
padding: 25px;
}
.form-item {
margin-bottom: 20px;
}
.form-text {
font-size: 12px;
color: #409eff;
text-align: right;
line-height: 1;
}
.submit {
width: 100%;
margin-top: 10px;
}
</style>
效果
2.2引入登录页
- 这部分前面写了,这里码云没记录
引入
import RegisterForm from "@/components/user/RegisterForm";
注册
components: {
RegisterForm, //注册表单
},
把模块代码注释打开
<!-- 注册功能组件 -->
<RegisterForm v-if="currentTab == 1" />
2.3绑定数据
- 根据接口要求设置变量,再用
v-model绑定到对应input上- 这里接口并未要求
checkPassWord参数,添加是为了校验密码
export default {
data() {
return {
//表单数据
form: {
username: "", //登录用户名,手机
nickname: "", //昵称
captcha: "", //手机验证
password: "", //密码
checkPassWord: "", //确认密码
},
//其他代码
},
},
};
<el-form-item class="form-item">
<el-input placeholder="用户名手机" v-model="form.username"> </el-input>
</el-form-item>
<el-form-item class="form-item">
<el-input placeholder="验证码" v-model="form.captcha">
<template slot="append">
<!-- handleSendCaptcha:点击发送验证码时调用事件 -->
<el-button @click="handleSendCaptcha"> 发送验证码 </el-button>
</template>
</el-input>
</el-form-item>
<el-form-item class="form-item">
<el-input placeholder="你的名字" v-model="form.nickname"> </el-input>
</el-form-item>
<el-form-item class="form-item">
<el-input
placeholder="密码"
type="password"
v-model="form.password"
></el-input>
</el-form-item>
<el-form-item class="form-item">
<el-input
placeholder="确认密码"
type="password"
v-model="form.checkPassWord"
>
</el-input>
</el-form-item>
效果
2.4数据校验与自定义校验函数
- 函数式校验表单信息---validator
- 注意:在
el-form-item加上prop
export default {
data() {
//由于是在data内完成校验,所以不写在methods也可以
const validateCheckPass = (rule, value, callback) => {
// rule:当前标准
//value:当前输入框的值
// callback:Element传入的固定回调函数,校验完成就需要执行
// 1,如果校验合法直接执行,没有参数
// 2.如果不合法,创建一个错误对象参数new Error
if (!value) {
callback(new Error("请输入必填信息"));
} else if (value != this.form.password) {
callback(new Error("两次密码必须一致"));
} else {
callback();
}
};
return {
//表单数据
form: {
username: "", //登录用户名,手机
nickname: "", //昵称
captcha: "", //手机验证
password: "", //密码
checkPassWord: "", //确认密码
},
// 表单规则
rules: {
username: [
{
required: true,
message: "请输入用户名",
trigger: "blur",
},
],
nickname: [
{
required: true,
message: "请输入昵称",
trigger: "blur",
},
],
captcha: [
{
required: true,
message: "请输入验证码",
trigger: "blur",
},
],
password: [
{
required: true,
message: "请输入密码",
trigger: "blur",
},
],
checkPassWord: [
{
trigger: "blur",
//这里的校验并非默认自带的校验方式
//判断标准和提示语都需要自定义
// 自定义校验函数validator
validator: validateCheckPass,
},
],
},
};
},
}
2.5发送验证码
- 发送验证码的逻辑是前端发送验证码请求到后台,后台生成验证码通过中间的电信公司发送到客户手上.发送注册请求时带上验证码给后台校验.
- 而前端的话只需要发送
axios请求即可
2.5.1接口
user/user文件
//验证码请求
export const sendCaptcha = (tel) => {
return axios({
url: '/captchas',
method: 'post',
data: {
tel
}
})
}
2.5.2发送验证码请求
user/RegisterForm文件
//发送验证码
handleSendCaptcha() {
const regexp = /^1[3456789]\d{9}$/;
if (!regexp.test(this.form.username)) {
return this.$message.error("请输入合法手机号");
}
sendCaptcha(this.form.username).then((res) => {
console.log(res.data);
});
},
2.5.3效果
2.6发送注册
- 这里注册接口参数只有四个,并没有确认密码.所以在发送的时候需要把确认密码那项数据筛选下来.
2.6.1接口
user/user文件
//注册接口
export const register = (data) => {
return axios({
url: '/accounts/register',
method: 'post',
data
})
}
2.6.2发送注册请求
user/RegisterForm文件- 使用
...剩余运算法分离确认密码的数据- 发送前先进行全表单校验
//注册请求
handleRegSubmit() {
//发送前调用element自带的验证validate,如果校验通过才发送
this.$refs.form.validate().then((valid) => {
if (valid) {
const { checkPassWord, ...data } = this.form;
register(data).then((res) => {
console.log(res.data);
});
}
});
},
2.6.3效果
三、错误拦截器
3.1效果
四、机票首页
pages/air/index.vueelement组件:
Layout布局
4.1静态布局
<template>
<section class="container">
<h2 class="air-title"><span class="iconfont iconfeiji"></span>
<i>国内机票</i>
</h2>
<!-- 搜索广告栏 -->
<el-row type="flex" justify="space-between">
<!-- 搜索表单 -->
<div>搜索</div>
<!-- banner广告 -->
<div class="sale-banner">
<img src="http://157.122.54.189:9093/images/pic_sale.jpeg">
</div>
</el-row>
<!-- 广告 -->
<el-row type="flex" class="statement">
<el-col :span="8">
<i class="iconfont iconweibiaoti-_huabanfuben" style="color:#409EFF;"></i>
<span>100%航协认证</span>
</el-col>
<el-col :span="8">
<i class="iconfont iconbaozheng" style="color:green;"></i>
<span>出行保证</span>
</el-col>
<el-col :span="8">
<i class="iconfont icondianhua" style="color:#409EFF;"></i>
<span>7x24小时服务</span>
</el-col>
</el-row>
<h2 class="air-sale-title">
<span class="iconfont icontejiajipiao"></span>
<i>特价机票</i>
</h2>
<!-- 特价机票 -->
<div class="air-sale">
</div>
</section>
</template>
<script>
export default {
}
</script>
<style scoped lang="less">
.air-sale{
border: 1px #ddd solid;
padding:20px;
margin-bottom:50px;
.air-sale-pic{
> div{
width:225px;
height:140px;
position: relative;
overflow: hidden;
img{
width:100%;
}
.layer-bar{
position:absolute;
bottom:0;
left:0;
background: rgba(0,0,0,0.5);
color:#fff;
height:30px;
line-height: 30px;
width:100%;
box-sizing: border-box;
padding: 0 15px;
font-size: 14px;
span:last-child{
font-size:18px;
}
}
}
}
}
.air-sale-group{
margin-top:20px;
padding-top:8px;
border-right:1px #eee solid;
&:last-child{
border-right:none;
}
.air-sale-row{
font-size:12px;
color:#666;
margin-bottom:8px;
.air-sale-price{
color:orange;
font-size: 20px;
}
}
}
.container{
width:1000px;
margin:0 auto;
}
.air-title{
margin:15px 0;
font-size:20px;
font-weight: normal;
color:orange;
span{
font-size:20px;
}
}
.statement{
margin:15px 0;
border:1px #ddd solid;
background:#f5f5f5;
height: 58px;
padding:10px 0;
box-sizing:border-box;
> div{
text-align: center;
line-height: 38px;
border-right:1px #ddd solid;
&:last-child{
border-right: none;
}
*{
vertical-align: middle;
}
i{
font-size:30px;
}
}
}
.air-sale-title{
margin:15px 0;
font-size:20px;
font-weight: normal;
color:#409EFF;
span{
font-size:20px;
}
}
</style>
4.2静态效果
4.3封装表单搜索组件(静态)
- 新建机票搜索表单组件
components/air/searchForm.vue- element组件:
autocomplete远程搜索自动补全页面(在input里面)form表单TimePicker时间选择器 -点击单双程切换tab,用户输入字段时匹配数据给出关键字,点击关键字时做出处理
<template>
<div class="search-form">
<!-- 头部tab切换 -->
<el-row type="flex" class="search-tab">
<!-- active:循环点击的索引赋值class -->
<!-- handleSearchTab:点击切换单双程样式事件 -->
<span
v-for="(item, index) in tabs"
:key="index"
@click="handleSearchTab(index)"
:class="{ active: index === currentTab }"
>
<i :class="item.icon"></i>{{ item.name }}
</span>
</el-row>
<el-form class="search-form-content" ref="form" label-width="80px">
<el-form-item label="出发城市">
<!-- fetch-suggestions 返回输入建议的方法 -->
<!-- select 点击选中建议项时触发 -->
<!-- fetch-suggestions:返回输入建议的方法属性,
返回两个参数,第一个是当前用户输入的字段 ,第二个是显示建议的函数-->
<!-- select:建议被选择之后的回调事件 -->
<el-autocomplete
:fetch-suggestions="queryDepartSearch"
placeholder="请搜索出发城市"
@select="handleDepartSelect"
class="el-autocomplete"
></el-autocomplete>
</el-form-item>
<el-form-item label="到达城市">
<el-autocomplete
:fetch-suggestions="queryDestSearch"
placeholder="请搜索到达城市"
@select="handleDestSelect"
class="el-autocomplete"
></el-autocomplete>
</el-form-item>
<el-form-item label="出发时间">
<!-- change 用户确认选择日期时触发 -->
<el-date-picker
type="date"
placeholder="请选择日期"
style="width: 100%"
@change="handleDate"
>
</el-date-picker>
</el-form-item>
<el-form-item label="">
<el-button
style="width: 100%"
type="primary"
icon="el-icon-search"
@click="handleSubmit"
>
搜索
</el-button>
</el-form-item>
<div class="reverse">
<span @click="handleReverse">换</span>
</div>
</el-form>
</div>
</template>
<script>
export default {
data() {
return {
tabs: [
{ icon: "iconfont icondancheng", name: "单程" },
{ icon: "iconfont iconshuangxiang", name: "往返" },
],
currentTab: 0,
};
},
methods: {
// 切换tab
handleSearchTab(index) {
this.currentTab = index;
},
// 搜索
handleSubmit() {},
//单双程切换
handleReverse() {},
// 出发城市建议被选择之后的回调事件
handleDepartSelect(item) {
//这里是用户选择了其中一个建议选项之后出发的事件
//可以接收到用户选择选项的对象本身
//不单单是渲染项,而是整个对象,可以进行后续操作
console.log(item);
},
//到达城市的建议回调
handleDestSelect(item) {},
// 出发城市的返回输入建议的方法属性
queryDepartSearch(string, callback) {
//这个是显示建议的函数
// 第一个参数是当前输入框的值
//第二个是用来显示建议列表的回调函数
//在调用这个函数的时候往里面输入一个建议数组即可
//数组中每个元素都是一个建议对象
this.$axios({
url: "/airs/city",
params: {
name: string,
},
}).then((res) => {
console.log(res.data.data);
const list = res.data.data.map((city) => {
return {
...city, //展开数据
value: city.name, //框架需要value值
};
});
callback(list);
});
},
// 到达城市的建议函数
queryDestSearch(string, callback) {},
// 日期选择
handleDate() {},
},
};
</script>
<style scoped lang="less">
.search-form {
border: 1px #ddd solid;
border-top: none;
width: 360px;
height: 350px;
box-sizing: border-box;
}
.search-tab {
span {
display: block;
flex: 1;
text-align: center;
height: 48px;
line-height: 42px;
box-sizing: border-box;
border-top: 3px #eee solid;
background: #eee;
}
.active {
border-top-color: orange;
background: #fff;
}
i {
margin-right: 5px;
font-size: 18px;
&:first-child {
font-size: 16px;
}
}
}
.search-form-content {
padding: 15px 50px 15px 15px;
position: relative;
.el-autocomplete {
width: 100%;
}
}
.reverse {
position: absolute;
top: 35px;
right: 15px;
&:after,
&:before {
display: block;
content: "";
position: absolute;
left: -35px;
width: 25px;
height: 1px;
background: #ccc;
}
&:after {
top: 0;
}
&:before {
top: 60px;
}
span {
display: block;
position: absolute;
top: 20px;
right: 0;
font-size: 12px;
background: #999;
color: #fff;
width: 20px;
height: 20px;
line-height: 18px;
text-align: center;
border-radius: 2px;
cursor: pointer;
&:after,
&:before {
display: block;
content: "";
position: absolute;
left: 10px;
width: 1px;
height: 20px;
background: #ccc;
}
&:after {
top: -20px;
}
&:before {
top: 20px;
}
}
}
</style>
4.4引入pages/air/index注册使用
//引入
import searchForm from "../../components/air/searchForm";
//注册
components: {
searchForm,
},
使用
<!-- 搜索表单 -->
<div>
<searchForm />
</div>