上一篇,把基本的构建做成了。不过,页面不够完善。现在结合接口,来做一波。尽管如此,这篇没有使用的 vuex 状态管理,也是不完美的。
welcome.vue
<template>
<div class="user">
<div v-if="user">
<div class="info">Hello, 第 {{ user.id }} 号用户。你创建于 {{ user.created_at}}</div>
<el-button type="primary" @click="logout" size="small">退出登录</el-button>
</div>
<div v-else>
<span>你还没有登录,请先登录</span>
<el-link type="primary" :underline="false" href="./#/admin/user/login">开始登录</el-link>
</div>
<div>
<el-button type="primary" @click="test" size="small">测试有权限接口</el-button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
user: null,
};
},
mounted() {
this.checkUser();
},
methods: {
checkUser() {
let user = localStorage.getItem("user");
if (user) {
this.user = JSON.parse(user);
} else {
this.user = null;
}
},
logout() {
axios
.get("logout", null)
.then((res) => {
if (res && res.data) {
localStorage.clear();
this.checkUser();
}
})
.catch((error) => {});
},
test() {
axios
.get("currentUser", null)
.then((res) => {
this.$message({ message: "拥有权限,测试成功", type: "success" });
})
.catch((error) => {
this.$message('你还没有登录,无权操作');
});
},
},
};
</script>
<style lang="scss" scoped>
.user {
display: flex;
flex-direction: column;
justify-content: center;
box-sizing: border-box;
padding: 20px;
.info {
margin-bottom: 10px;
}
div {
margin-top: 20px;
}
}
</style>
login.vue
<template>
<div class="login-panel">
<el-card class="box-card" shadow="always">
<div slot="header" class="clearfix">
<span>用户登录</span>
</div>
<el-row>
<el-form
label-position="right"
label-width="80px"
:model="formData"
ref="form"
:rules="formRule"
>
<el-form-item label="账号" prop="account">
<el-input
@change="accountChange"
v-model="formData.account"
autocomplete="on"
name="account"
placeholder="手机号码或邮箱"
></el-input>
</el-form-item>
<el-form-item label="验证码" prop="captchas" v-if="showCaptcha">
<el-input
autocomplete="on"
v-model="captchas.form.captcha_code"
name="captcha_code"
placeholder="请输入验证码"
></el-input>
<div @click="getCaptchas" class="captchas-btn">
<el-image style="width: 100px; height: 36px" :src="captchas.src" fit="contain"></el-image>
</div>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
type="password"
v-model="formData.password"
autocomplete="on"
name="password"
@keyup.enter.native="login"
></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="login" size="samll">登录</el-button>
<div class="other-way">
<span>还没有账号?</span>
<el-link type="primary" :underline="false" href="./#/admin/user/register">免费注册</el-link>
</div>
</el-form-item>
</el-form>
</el-row>
</el-card>
</div>
</template>
<script>
export default {
data() {
return {
showCaptcha: false,
type: "phone",
formData: {
account: "",
password: "",
},
captchas: {
requested: false,
src: "",
form: {
captcha_key: "",
captcha_code: "",
},
},
formRule: {
username: [
{ required: true, message: "请输入用户名", trigger: "blur" },
],
password: [{ required: true, message: "请输入密码", trigger: "blur" }],
},
};
},
mounted() {
this.testJump();
this.test();
},
methods: {
testJump() {
let user = localStorage.getItem("user");
if (user) {
this.$router.push("/");
}
},
accountChange() {
this.type = "";
if (
/^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199)\d{8}$/.test(
this.formData.account
)
) {
this.type = "phone";
}
if (
/^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/.test(
this.formData.account
)
) {
this.type = "email";
}
if (this.type) {
this.getCaptchas();
}
},
getCaptchas() {
let data = { type: this.type };
if (this.type == "phone") {
if (!this.formData.account) {
this.$message.error("手机号码不能为空");
return;
}
if (
!/^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199)\d{8}$/.test(
this.formData.account
)
) {
this.$message.error("手机号码格式错误");
return;
}
data.phone = this.formData.account;
} else {
if (!this.formData.account) {
this.$message.error("邮箱不能为空");
return;
}
if (
!/^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/.test(
this.formData.account
)
) {
this.$message.error("邮箱格式错误");
return;
}
data.email = this.formData.account;
}
axios
.post("captchas", data)
.then((res) => {
if (res && res.data) {
this.captchas.requested = true;
this.captchas.src = res.data.captcha_image_content;
this.captchas.form.captcha_key = res.data.captcha_key;
}
})
.catch((error) => {});
},
login() {
if (!this.formData.account) {
this.$message.error("账号不能为空");
return;
}
if (!this.formData.password) {
this.$message.error("密码不能为空");
return;
}
if (!this.type) {
this.$message.error("类型不能为空");
return;
}
let data = { used: "login", type: this.type, ...this.formData };
axios
.get("/sanctum/csrf-cookie")
.then((response) => {
axios.post("login", data).then((res) => {
if (res && res.data) {
localStorage.setItem("user", JSON.stringify(res.data));
}
this.testJump();
this.test();
});
})
.catch((error) => {});
},
test() {
return;
axios
.get("currentUser", null)
.then((res) => {
console.log("res", res);
})
.catch((error) => {
console.log(error.response);
});
},
},
};
</script>
<style lang="scss" scoped>
.login-panel {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100vh;
background-color: rgba(10, 20, 100, 0.2);
.box-card {
width: 640px;
.el-menu {
margin-bottom: 20px;
}
.captchas-btn {
position: absolute;
right: 2px;
top: 2px;
cursor: pointer;
}
.oneline-input {
display: block;
.el-form-item__content {
display: flex !important;
width: 100%;
.el-input {
display: flex;
flex: 1;
}
}
}
.mail-btn {
position: absolute;
right: 4px;
top: 4px;
}
.other-way {
display: inline-block;
margin-left: 16px;
span {
margin-right: 4px;
}
}
}
}
</style>
register.vue
<template>
<div class="login-panel">
<el-card class="box-card" shadow="always">
<el-row>
<el-form
label-position="right"
label-width="80px"
:model="formData"
ref="form"
:rules="formRule"
>
<el-form-item>
<el-menu
:default-active="activeIndex"
class="el-menu"
mode="horizontal"
@select="menuSelect"
>
<el-menu-item index="1">手机号注册</el-menu-item>
<el-menu-item index="2">邮箱注册</el-menu-item>
</el-menu>
</el-form-item>
<el-form-item v-if="type == 'phone'" label="手机号码" prop="account">
<el-input
@change="phoneChange"
v-model="formData.account"
autocomplete="on"
name="account"
placeholder="请输入手机号码"
></el-input>
</el-form-item>
<el-form-item v-if="type == 'email'" label="邮箱" prop="account">
<el-input
@change="emailChange"
v-model="formData.account"
autocomplete="on"
name="account"
placeholder="请输入邮箱"
></el-input>
</el-form-item>
<el-form-item label="验证码" prop="captchas" v-if="showCaptcha">
<el-input
autocomplete="on"
v-model="captchas.form.captcha_code"
name="captcha_code"
placeholder="请输入验证码"
></el-input>
<div @click="getCaptchas" class="captchas-btn">
<el-image style="width: 100px; height: 36px" :src="captchas.src" fit="contain"></el-image>
</div>
</el-form-item>
<el-form-item v-if="type == 'phone' && showCaptcha" label="短信验证" prop="mail">
<el-input
v-model="mail.form.verification_code"
autocomplete="on"
name="mail"
maxlength="6"
></el-input>
<el-button
@click="verificationMail"
class="mail-btn"
type="primary"
size="small"
:disabled="!captchas.requested || mail.mailWating"
>{{mail.mailStr}}</el-button>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
type="password"
v-model="formData.password"
autocomplete="on"
name="password"
@keyup.enter.native="register"
></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="register" size="samll">注册</el-button>
<div class="other-way">
<span>已有账号</span>
<el-link type="primary" :underline="false" href="./#/admin/user/login">开始登录</el-link>
</div>
</el-form-item>
</el-form>
</el-row>
</el-card>
</div>
</template>
<script>
export default {
data() {
return {
showCaptcha: false,
activeIndex: "1",
type: "phone",
formData: {
account: "",
password: "",
},
mail: {
mailStr: "获取验证码",
mailWating: false,
form: {
verification_key: "",
verification_code: "",
},
},
captchas: {
requested: false,
src: "",
form: {
captcha_key: "",
captcha_code: "",
},
},
formRule: {
username: [
{ required: true, message: "请输入用户名", trigger: "blur" },
],
password: [{ required: true, message: "请输入密码", trigger: "blur" }],
},
};
},
mounted() {
this.testJump();
this.test();
},
methods: {
testJump() {
let user = localStorage.getItem("user");
if (user) {
this.$router.push("/");
}
},
menuSelect(key, keyPath) {
this.type = key == 1 ? "phone" : "email";
this.showCaptcha = false;
this.formData.account = "";
this.captchas.src = "";
this.captchas.src = "";
this.captchas.required = false;
},
phoneChange() {
if (
/^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199)\d{8}$/.test(
this.formData.account
)
) {
this.showCaptcha = true;
this.getCaptchas();
}
},
emailChange() {
if (
/^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/.test(
this.formData.account
)
) {
this.showCaptcha = true;
this.getCaptchas();
}
},
getCaptchas() {
let data = { type: this.type };
if (this.type == "phone") {
if (!this.formData.account) {
this.$message.error("手机号码不能为空");
return;
}
if (
!/^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199)\d{8}$/.test(
this.formData.account
)
) {
this.$message.error("手机号码格式错误");
return;
}
data.phone = this.formData.account;
} else {
if (!this.formData.account) {
this.$message.error("邮箱不能为空");
return;
}
if (
!/^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/.test(
this.formData.account
)
) {
this.$message.error("邮箱格式错误");
return;
}
data.email = this.formData.account;
}
axios
.post("captchas", data)
.then((res) => {
if (res && res.data) {
this.captchas.requested = true;
this.captchas.src = res.data.captcha_image_content;
this.captchas.form.captcha_key = res.data.captcha_key;
}
})
.catch((error) => {});
},
verificationMail() {
if (!this.formData.account) {
this.$message.error("手机号码不能为空");
return;
}
if (!this.captchas.form.captcha_code) {
this.$message.error("验证码不能为空");
return;
}
const data = {
phone: this.formData.account,
...this.captchas.form,
};
axios
.post("verificationCodes", data)
.then((res) => {
this.$message({ message: "短信已发送", type: "success" });
this.mail.mailWating = true;
this.mail.form.verification_key = res.data.key;
let duration = 60;
let timer = setInterval(() => {
this.mail.mailStr = `${duration}秒后重新获取`;
duration--;
}, 1000);
})
.catch((error) => {
if (error && error.response && error.response.data) {
this.$message.error(error.response.data.message);
this.mail.mailWating = false;
this.mail.mailStr = "获取验证码";
}
});
},
register() {
if (!this.formData.password) {
this.$message.error("密码不能为空");
return;
}
let data = { used: "register", type: this.type, ...this.formData };
if (this.type == "phone") {
if (!this.formData.account) {
this.$message.error("手机号码不能为空");
return;
}
if (
!this.mail.form.verification_key ||
!this.mail.form.verification_code
) {
this.$message.error("验证码不能为空");
return;
}
data = { ...data, ...this.mail.form };
} else if (this.type == "email") {
if (!this.formData.account) {
this.$message.error("邮箱不能为空");
return;
}
if (
!this.captchas.form.captcha_key ||
!this.captchas.form.captcha_code
) {
this.$message.error("验证码不能为空");
return;
}
data = { ...data, ...this.captchas.form };
}
axios
.get("/sanctum/csrf-cookie")
.then((response) => {
axios.post("register", data).then((res) => {
if (res && res.data) {
localStorage.setItem("user", JSON.stringify(res.data));
}
this.testJump();
this.test();
});
})
.catch((error) => {});
},
test() {
return;
axios
.get("currentUser", null)
.then((res) => {})
.catch((error) => {});
},
},
};
</script>
<style lang="scss" scoped>
.login-panel {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100vh;
background-color: rgba(10, 20, 100, 0.2);
.box-card {
width: 640px;
.el-menu {
margin-bottom: 20px;
}
.captchas-btn {
position: absolute;
right: 2px;
top: 2px;
cursor: pointer;
}
.oneline-input {
display: block;
.el-form-item__content {
display: flex !important;
width: 100%;
.el-input {
display: flex;
flex: 1;
}
}
}
.mail-btn {
position: absolute;
right: 4px;
top: 4px;
}
.other-way {
display: inline-block;
margin-left: 16px;
span {
margin-right: 4px;
}
}
}
}
</style>
后端,使用 laravel 提供接口。这里使用 Sanctum 做 api 认证,使用的是 cookie 方式。已经走通。但是非同域名下,该方式存在问题。还没解决。如果是用 token 的方式 ,就不会有这个问题。
最后
这里没有使用 vuex ,也没有使用 vue-element-admin 后边有时间会去操作一波。