YingXingAI/pages/login/login/index.vue

732 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="login-container">
<view class="login-header">
<view class="logo-container">
<image
class="logo-icon"
src="/static/common/images/images_logo.png"
></image>
<text class="logo-text"
>我是 <text class="logo-name">源小新</text></text
>
</view>
<view class="welcome-text">
欢迎使用源小新校园AI助手快让我来帮助你吧
</view>
</view>
<view
class="login-form"
:style="{ 'padding-top': isTeacher ? '40rpx' : '80rpx' }"
>
<view class="login-type" v-if="isTeacher">
<view
class="type-item"
:class="{ active: loginType === 'psd' }"
@click="switchLoginType('psd')"
>密码登录</view
>
<view class="divider">|</view>
<view
class="type-item"
:class="{ active: loginType === 'code' }"
@click="switchLoginType('code')"
>验证码登录</view
>
</view>
<view class="form-content">
<view class="form-item">
<view class="form-label"
><image
class="image_icon"
src="/static/common/images/icon_phoneNumber.png"
mode="scaleToFill"
/>
<text>手机号</text>
</view>
<view class="input-wrapper">
<input
type="number"
class="form-input"
placeholder="请输入手机号"
v-model="loginParams.phone"
@blur="validatePhone"
maxlength="11"
/>
</view>
<text v-if="errors.phone" class="error-tip">{{ errors.phone }}</text>
</view>
<view class="form-item" v-if="isTeacher && loginType === 'psd'">
<view class="form-label"
><image
class="image_icon"
src="/static/common/images/icon_password.png"
mode="scaleToFill"
/>
<text>密码</text>
</view>
<view class="input-wrapper">
<input
type="text"
class="form-input"
placeholder="请输入密码"
v-model="loginParams.password"
@blur="validatePassword"
/>
</view>
<text v-if="errors.password" class="error-tip">{{
errors.password
}}</text>
</view>
<view class="form-item" v-if="loginType === 'code'">
<view class="form-label">
<image
class="image_icon"
src="/static/common/images/icon_verificationCode.png"
mode="scaleToFill"
/>
<text>图形验证码</text>
</view>
<view class="input-wrapper">
<input
type="text"
class="form-input"
placeholder="请输入验证码"
v-model="loginParams.captcha"
@blur="validateCaptcha"
/>
<view class="captcha-img" @click="refreshCaptcha">
<image
v-if="captchaUrl"
:src="captchaUrl"
mode="widthFix"
></image>
</view>
</view>
<text v-if="errors.captcha" class="error-tip">{{
errors.captcha
}}</text>
</view>
<view class="form-item" v-if="loginType === 'code'">
<view class="form-label">
<image
class="image_icon"
src="/static/common/images/icon_verificationCode.png"
mode="scaleToFill"
/>
<text>验证码</text>
</view>
<view class="input-wrapper">
<input
type="text"
class="form-input"
placeholder="请输入验证码"
v-model="loginParams.code"
@blur="validateCode"
/>
<text
class="get-code-btn"
:class="{ 'get-code-btn-normal': codeText == '获取验证码' }"
@click="getCode"
>{{ codeText }}</text
>
</view>
<text v-if="errors.code" class="error-tip">{{ errors.code }}</text>
</view>
</view>
<view class="button-container">
<button class="login-button" @click="login">登录</button>
</view>
</view>
<u-toast ref="uToast" />
</view>
</template>
<script>
import { generateSign } from "@/utils/signUtil.js";
import { getUserInfoFromJWT } from "@/utils/jwt-util";
import md5 from "js-md5";
export default {
data() {
return {
isTeacher: false, // 是否是教师
// 登录参数
loginParams: {
phone: "",
password: "",
code: "",
captcha: "", // 图形验证码
captchaId: "", // 图形验证码ID
},
captchaUrl: "", // 图形验证码URL实际使用时需替换
loginType: "psd", // 教师默认密码登录
codeText: "获取验证码",
countdown: 60,
timer: null,
errors: {
phone: "",
password: "",
code: "",
captcha: "",
},
};
},
onLoad(e) {
// console.log("onLoad", e.type); // 0:学生 1:教师
this.isTeacher = e.type == 1;
this.loginType = e.type == 1 ? "psd" : "code";
},
mounted() {
this.refreshCaptcha();
},
beforeDestroy() {
if (this.timer) {
clearInterval(this.timer);
}
},
methods: {
switchLoginType(type) {
this.clearErrors();
this.clearLoginParams();
this.loginType = type;
if (this.loginType === "code") {
this.refreshCaptcha();
}
},
// 刷新图形验证码
refreshCaptcha() {
// 获取图形验证码
this.$u.api.GetCaptcha().then((res) => {
console.log("获取图形验证码...", res);
this.loginParams.captchaId = res.captchaId;
this.captchaUrl = "data:image/png;base64," + res.imageBase64;
});
},
// 获取短信验证码
getCode() {
if (
this.codeText !== "获取验证码" ||
!this.validatePhone() ||
(this.loginType === "code" && !this.validateCaptcha())
) {
return;
}
const sign = generateSign(this.loginParams.phone);
const params = {
phone: this.loginParams.phone,
ip: "",
sign: sign,
captchaId: this.loginParams.captchaId,
captchaCode: this.loginParams.captcha,
};
console.log("获取验证码...", params);
const apiMethod = this.isTeacher
? "GetTeacherVerifyCode"
: "GetStuVerifyCode";
this.$u.api[apiMethod](params).then((res) => {
console.log("获取验证码...", res);
if (res.succeed) {
this.$refs.uToast.show({
title: "发送成功",
type: "success",
});
this.startCountdown();
} else {
this.$refs.uToast.show({
title: res.error || "发送失败",
type: "error",
});
this.refreshCaptcha();
}
});
},
// 开始倒计时
startCountdown() {
this.countdown = 60;
this.codeText = `${this.countdown}秒后重试`;
this.timer = setInterval(() => {
this.countdown--;
this.codeText = `${this.countdown}秒后重试`;
if (this.countdown <= 0) {
clearInterval(this.timer);
this.codeText = "获取验证码";
}
}, 1000);
},
// 校验手机号
validatePhone() {
if (!this.loginParams.phone) {
this.errors.phone = "请输入手机号";
return false;
}
if (!/^1[3-9]\d{9}$/.test(this.loginParams.phone)) {
this.errors.phone = "手机号格式不正确";
return false;
}
this.errors.phone = "";
return true;
},
// 校验密码
validatePassword() {
if (!this.loginParams.password) {
this.errors.password = "请输入密码";
return false;
}
// if (this.password.length < 6) {
// this.errors.password = "密码不能少于6位";
// return false;
// }
this.errors.password = "";
return true;
},
// 校验图形验证码
validateCaptcha() {
if (!this.loginParams.captcha) {
this.errors.captcha = "请输入图形验证码";
return false;
}
// if (this.captcha.length < 4) {
// this.errors.captcha = "图形验证码错误请重新输入";
// return false;
// }
// 实际应用中应该验证图形验证码是否正确
this.errors.captcha = "";
return true;
},
// 校验短信验证码
validateCode() {
if (!this.loginParams.code) {
this.errors.code = "请输入验证码";
return false;
}
if (this.loginParams.code.length < 4) {
this.errors.code = "验证码错误请重新输入";
return false;
}
this.errors.code = "";
return true;
},
// 清除所有错误提示
clearErrors() {
this.errors = {
phone: "",
password: "",
code: "",
captcha: "",
};
},
// 清除登录参数
clearLoginParams() {
this.loginParams = {
phone: "",
password: "",
code: "",
captcha: "", // 图形验证码
captchaId: "", // 图形验证码ID
};
},
// 登录
login() {
// const res = {
// data: {
// // token:
// // "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbiIsIlVzZXJJbmZvIjoie1wiSWRcIjpcImI1MjY5ZWQxLTViYzgtMTFmMC05YTg5LTAwMTYzZTJkMmRkM1wiLFwiTmFtZVwiOlwi6J-56ICB5biIXCIsXCJQaG9uZU51bWJlclwiOlwiMTM1ODg4ODg4ODhcIixcIlNleFwiOjEsXCJBY2NvdW50VHlwZVwiOjJ9IiwibmJmIjoxNzUyMTE4ODU0LCJleHAiOjE3NTIyMDUyNTR9.aF0Q-X3ebIld7RIKMMW68tXjmmR4OO08dXLAtHWuuwc",
// token:
// "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJVc2VyIiwiVXNlckluZm8iOiJ7XCJJZFwiOlwiNTljNzIxNmMtMTRhZS00NmRmLTkxYTItNGYzMjFlZjM2YjQ5XCIsXCJOYW1lXCI6XCIxNzMzNTM3NDg4N1wiLFwiUGhvbmVOdW1iZXJcIjpcIjE3MzM1Mzc0ODg3XCIsXCJTZXhcIjoyLFwiQWNjb3VudFR5cGVcIjowfSIsIm5iZiI6MTc1MjExMDcwOSwiZXhwIjoxNzUyMTk3MTA5fQ.VDv2KjGq3s3BbZxZjB3P-BaeDU_1vIdqFEWGGvdpIxU",
// },
// };
// const token = res.data.token;
// // 解析获取用户信息
// const userInfo = getUserInfoFromJWT(token);
// // 保存登录后得到的用户数据
// this.$u.vuex("vuex_token", token);
// this.$u.vuex("vuex_user", userInfo);
// // 跳转至首页
// uni.reLaunch({
// url: "/pages/home/index/index",
// });
// return;
// 校验手机号
if (!this.validatePhone()) {
return;
}
// 根据登录类型校验其他字段
if (this.loginType === "psd") {
if (!this.validatePassword()) {
return;
}
} else {
// 验证码登录需要验证图形验证码和短信验证码
if (!this.validateCaptcha() || !this.validateCode()) {
return;
}
}
// 校验通过,执行登录
uni.showLoading({
title: "登录中...",
});
if (this.isTeacher) {
if (this.loginType === "psd") {
this.teacherLogin(); // 密码登录
} else {
this.teacherLoginByCode(); // 验证码登录
}
} else {
this.stuLogin(); // 学生登录
}
},
// 通用登录处理函数
handleLogin(apiMethod, params, userType = 0) {
// console.log("登录参数:", params);
this.$u.api[apiMethod](params).then((res) => {
console.log("登录结果:", res);
if (!res.succeed) {
this.$refs.uToast.show({
title: res.error || "登录失败",
type: "error",
});
return;
}
this.$refs.uToast.show({
title: "登录成功",
type: "success",
});
const token = res.data.token;
// 解析获取用户信息
const userInfo = getUserInfoFromJWT(token);
// 保存登录后得到的用户数据
this.$u.vuex("vuex_token", token);
this.$u.vuex("vuex_user", userInfo);
this.$u.vuex("vuex_userType", userType); // 0:学生 1:教师
// 保存用户类型到本地存储,用于退出登录时重定向
// uni.setStorageSync("userType", userType);
// 跳转至首页
const url = this.isTeacher
? "/pages/consultation/index"
: "/pages/home/index/index";
uni.reLaunch({
url: url,
});
});
},
// 学生登录
stuLogin() {
// 构建登录参数
const params = {
phone: this.loginParams.phone,
password: "",
code: this.loginParams.code,
};
// 使用通用登录处理函数,传入学生特定的参数
this.handleLogin("StuLogin", params, 0);
},
// 教师密码登录
teacherLogin() {
// 构建登录参数
const params = {
phone: this.loginParams.phone,
password: md5(this.loginParams.password),
code: this.loginParams.code,
};
this.handleLogin("TeacherLogin", params, 1);
},
// 教师验证码登录
teacherLoginByCode() {
// 构建登录参数
const params = {
phone: this.loginParams.phone,
password: "",
code: this.loginParams.code,
};
this.handleLogin("TeacherLoginByCode", params, 1);
},
// 手机号输入过滤
phoneInputFilter(e) {
// 确保输入的只是数字
let value = e.detail.value.replace(/\D/g, "");
// 限制长度为11位
if (value.length > 11) {
value = value.slice(0, 11);
}
// 直接更新值
this.loginParams.phone = value;
},
},
};
</script>
<style lang="scss" scoped>
.login-container {
// min-height: 100vh;
height: 100vh;
width: 100%;
padding: 0 30rpx;
display: flex;
flex-direction: column;
box-sizing: border-box;
background-image: url("@/static/common/images/images_bg.png");
background-repeat: no-repeat;
background-size: 100% 100%;
background-position: 50%;
.login-header {
margin-top: 120rpx;
display: flex;
flex-direction: column;
align-items: center;
.logo-container {
display: flex;
align-items: center;
margin-bottom: 32rpx;
.logo-icon {
width: 48rpx;
height: 38rpx;
margin-right: 16rpx;
}
.logo-text {
font-size: 32rpx;
color: #333;
.logo-name {
font-family: DouyinSans;
font-weight: bold;
font-size: 48rpx;
color: #3e6aff;
line-height: 24rpx;
margin-left: 16rpx;
}
}
}
.welcome-text {
font-family: DouyinSans;
font-weight: bold;
font-size: 27rpx;
color: #5a5f76;
margin-bottom: 60rpx;
}
}
.login-form {
background: linear-gradient(0deg, #f6f6f6 0%, #ffffff 100%);
flex: 1;
border-radius: 40rpx 40rpx 0 0;
padding: 80rpx 40rpx;
display: flex;
flex-direction: column;
.login-type {
display: flex;
align-items: center;
justify-content: flex-start;
margin-bottom: 60rpx;
font-size: 28rpx;
color: #999999;
.type-item {
padding: 0 20rpx;
position: relative;
font-family: PingFang SC;
&.active {
font-size: 32rpx;
font-weight: bold;
color: #4f6aff;
&::after {
content: "";
position: absolute;
bottom: 4px;
left: 50%;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
width: 76%;
height: 0px;
border-radius: 3px;
box-shadow: 0px 0px 4px 2px rgba(79, 106, 255, 1);
}
}
}
.divider {
color: #5a5f76;
margin: 0 30rpx;
font-weight: 300;
}
}
.form-content {
.form-item {
margin-bottom: 20rpx;
position: relative;
padding-bottom: 40rpx; /* 为错误提示预留空间 */
.error-tip {
position: absolute;
bottom: 0;
font-size: 24rpx;
color: #ff6481;
height: 40rpx; /* 固定高度 */
line-height: 40rpx;
}
.form-label {
display: flex;
align-items: center;
font-family: PingFang SC;
font-size: 28rpx;
color: #333333;
margin-bottom: 16rpx;
}
.image_icon {
width: 28rpx;
height: 28rpx;
margin-right: 16rpx;
}
&:nth-child(2) {
.form-label::before {
border-radius: 50%;
}
}
.input-wrapper {
position: relative;
.form-input {
width: 100%;
height: 90rpx;
background-color: #f2f2f2;
border-radius: 20rpx;
font-size: 28rpx;
padding: 0 32rpx;
box-sizing: border-box;
.uni-input-placeholder {
color: #cccccc;
}
}
.get-code-btn {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 220rpx;
height: 60rpx;
line-height: 60rpx;
padding-left: 30rpx;
color: #cccccc;
font-size: 28rpx;
border-left: 1px solid #b7c1ff;
}
.get-code-btn-normal {
color: #4370fe;
}
.captcha-img {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
height: 60rpx;
width: 220rpx;
padding-left: 30rpx;
border-left: 1px solid #b7c1ff;
image {
width: 80%;
height: 100%;
}
}
}
}
}
.button-container {
margin-top: 60rpx;
.login-button {
width: 100%;
height: 90rpx;
background-color: #4f6aff;
color: #ffffff;
font-size: 32rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
border: none;
}
}
}
}
/* 响应式布局 - PC端样式 */
@media screen and (min-width: 768px) {
.login-container {
width: 100%;
margin: 0 auto;
.login-form {
width: 1200rpx;
margin: 0 auto;
}
}
}
</style>