智慧教务系统
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1055 lines
24 KiB

<template>
<view>
<view style="height: 500rpx;background-color:#fff;">
<view style="height: 150rpx;"></view>
<view class="image-container;">
<image :src="$util.img('/uniapp_src/static/images/login/login1.png')" class="base-image"></image>
<image :src="$util.img('/uniapp_src/static/images/login/login2.png')" class="overlay-image"></image>
</view>
<view style="width: 100%;font-size: 60rpx;color: #ccc;text-align: center;margin-top: 60rpx;">
运动识堂
</view>
</view>
<view :style="{'background-color':'#fff','width':'100%','height':'100vh' }">
<view style="width: 95%;height: 30rpx;"></view>
<view style="width: 95%;margin:30rpx auto;">
<fui-input borderTop placeholder="登录账号" v-model="user" backgroundColor="#f2f2f2"></fui-input>
</view>
<view style="width: 95%;margin: auto;">
<fui-input borderTop :padding="['20rpx','32rpx']" v-model="password1" placeholder="登录密码"
:password="password" backgroundColor="#f2f2f2">
<fui-icon :name="password?'invisible':'visible'" color="#B2B2B2" :size="50"
@click="change"></fui-icon>
</fui-input>
</view>
<view style="width: 95%;margin:30rpx auto;">
<fui-input @click="picker_show_loginType=true" v-model="loginType_str" placeholder="请选择登录类型"
backgroundColor="#f2f2f2">
<fui-icon name="arrowdown" color="#B2B2B2" :size="50" @click="change"></fui-icon>
</fui-input>
<fui-picker layer="1" :linkage="true" :options="loginType_Arr" :show="picker_show_loginType"
@change="changePicker_loginType" @cancel="picker_show_loginType=false"></fui-picker>
</view>
<view style="width: 95%;margin:60rpx auto;">
<fui-button background="#00be8c" radius="5rpx" @click="login">登录</fui-button>
</view>
<view style="width: 95%;margin:60rpx auto;">
<fui-button background="#fff" radius="5rpx" @click="forgot" color="#00be8c">忘记登录密码</fui-button>
</view>
<!-- 微信自动登录按钮 -->
<!-- <view style="width: 95%;margin:30rpx auto;" v-if="loginType === 'member'">-->
<!-- <fui-button background="#07c160" radius="5rpx" @click="wechatAutoLogin" color="#fff">-->
<!-- <fui-icon name="wechat" color="#fff" :size="40" style="margin-right: 10rpx;"></fui-icon>-->
<!-- 微信一键登录-->
<!-- </fui-button>-->
<!-- </view>-->
</view>
<!-- 忘记密码弹窗 -->
<view v-if="showForgotModal" class="forgot-modal-overlay" @click="closeForgotModal">
<view class="forgot-modal" @click.stop>
<!-- 步骤指示器 -->
<view class="step-indicator">
<view class="step-item" :class="{ active: currentStep >= 1 }">
<text class="step-number" :class="{ active: currentStep >= 1 }">1</text>
<text class="step-text" :class="{ active: currentStep >= 1 }">验证手机号码</text>
</view>
<view class="step-line" :class="{ active: currentStep >= 2 }"></view>
<view class="step-item" :class="{ active: currentStep >= 2 }">
<text class="step-number" :class="{ active: currentStep >= 2 }">2</text>
<text class="step-text" :class="{ active: currentStep >= 2 }">设置新密码</text>
</view>
</view>
<!-- 步骤1:验证手机号码 -->
<view v-if="currentStep === 1" class="step-content">
<view class="input-group">
<input
class="input-field"
type="number"
placeholder="请输入手机号"
v-model="forgotForm.mobile"
maxlength="11"
/>
</view>
<view class="input-group">
<input
class="input-field verification-input"
type="number"
placeholder="请输入短信验证码"
v-model="forgotForm.code"
maxlength="6"
/>
<view
class="send-code-btn"
:class="{ disabled: codeCountdown > 0 }"
@click="sendVerificationCode"
>
{{ codeCountdown > 0 ? `${codeCountdown}s` : '发送验证码' }}
</view>
</view>
<view class="user-type-selector" @click="showUserTypeModal = true">
<text class="selector-text">{{ selectedUserType.text || '请选择用户类型' }}</text>
<text class="selector-arrow">></text>
</view>
</view>
<!-- 步骤2:设置新密码 -->
<view v-if="currentStep === 2" class="step-content">
<view class="input-group">
<input
class="input-field"
:type="showNewPassword ? 'text' : 'password'"
placeholder="请输入新密码"
v-model="forgotForm.newPassword"
/>
<view class="password-toggle" @click="showNewPassword = !showNewPassword">
<text class="iconfont" :class="showNewPassword ? 'icon-eye' : 'icon-eye-close'">👁</text>
</view>
</view>
<view class="input-group">
<input
class="input-field"
:type="showConfirmPassword ? 'text' : 'password'"
placeholder="请确认新密码"
v-model="forgotForm.confirmPassword"
/>
<view class="password-toggle" @click="showConfirmPassword = !showConfirmPassword">
<text class="iconfont" :class="showConfirmPassword ? 'icon-eye' : 'icon-eye-close'">👁</text>
</view>
</view>
</view>
<!-- 操作按钮 -->
<view class="action-buttons">
<view v-if="currentStep === 1" class="next-btn" @click="nextStep">下一步</view>
<view v-if="currentStep === 2" class="submit-btn" @click="resetPassword">确认修改</view>
</view>
<!-- 关闭按钮 -->
<view class="close-btn" @click="closeForgotModal">×</view>
</view>
</view>
<!-- 用户类型选择弹窗 -->
<view v-if="showUserTypeModal" class="user-type-modal-overlay" @click="showUserTypeModal = false">
<view class="user-type-modal" @click.stop>
<view class="user-type-title">选择用户类型</view>
<view
v-for="(item, index) in userTypeOptions"
:key="index"
class="user-type-option"
@click="selectUserType(item)"
>
{{ item.text }}
</view>
</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js';
export default {
data() {
return {
inited: false, // 添加标记位控制 init 只执行一次
password: true,
user: '', //账户
password1: '', //密码
mini_wx_openid: '', //微信小程序openid
loginType: '', //登陆类型|staff=员工,member=学员
loginType_str: '', //登陆类型中文名字
loginType_Arr: [{
value: 'staff',
text: '员工登录'
},
{
value: 'member',
text: '学员登录'
},
],
picker_show_loginType: false, //是否显示下拉窗组件
path_arr: {
'staff': '/pages/common/home/index', //员工
'member': '/pages/student/home/index', //学员端新首页
},
// 忘记密码弹窗相关数据
showForgotModal: false,
currentStep: 1,
showUserTypeModal: false,
codeCountdown: 0,
showNewPassword: false,
showConfirmPassword: false,
forgotForm: {
mobile: '',
code: '',
userType: '',
newPassword: '',
confirmPassword: ''
},
selectedUserType: {},
userTypeOptions: [
{ value: 'staff', text: '员工' },
{ value: 'member', text: '学员' }
],
}
},
onLoad(options) {
this.loginType = options.loginType ?? 'staff' //登陆类型|staff=员工,member=学员
const selectedItem = this.loginType_Arr.find(item => item.value === String(this.loginType));
this.loginType_str = selectedItem ? selectedItem.text : '未知类型';
let res_codes = options.res_codes || '' //这个是axios.js传错来的接口请求返回的code,如果是401时则说明登陆报错,此时不应进入下方初始化方法
if (!this.inited && !res_codes) {
this.openViewHome()
this.inited = true
}
// 监听微信绑定成功事件
uni.$on('wechatBindSuccess', () => {
this.handleWechatBindSuccess();
});
},
onUnload() {
// 移除事件监听
uni.$off('wechatBindSuccess');
},
methods: {
async init() {
//条件编译 微信小程序
//#ifdef MP-WEIXIN
await this.getMiNiWxOpenId()
//#endif
},
change() {
this.password = !this.password
},
forgot() {
this.showForgotModal = true;
this.currentStep = 1;
this.resetForgotForm();
},
// 忘记密码相关方法
closeForgotModal() {
this.showForgotModal = false;
this.currentStep = 1;
this.resetForgotForm();
},
resetForgotForm() {
this.forgotForm = {
mobile: '',
code: '',
userType: '',
newPassword: '',
confirmPassword: ''
};
this.selectedUserType = {};
this.codeCountdown = 0;
},
selectUserType(item) {
this.selectedUserType = item;
this.forgotForm.userType = item.value;
this.showUserTypeModal = false;
},
async sendVerificationCode() {
if (this.codeCountdown > 0) return;
if (!this.forgotForm.mobile) {
uni.showToast({
title: '请输入手机号',
icon: 'none'
});
return;
}
if (!/^1[3-9]\d{9}$/.test(this.forgotForm.mobile)) {
uni.showToast({
title: '请输入正确的手机号',
icon: 'none'
});
return;
}
if (!this.forgotForm.userType) {
uni.showToast({
title: '请选择用户类型',
icon: 'none'
});
return;
}
try {
uni.showLoading({ title: '发送中...' });
// 调用发送验证码接口
const res = await apiRoute.sendVerificationCode({
mobile: this.forgotForm.mobile,
type: 'reset_password',
user_type: this.forgotForm.userType
});
uni.hideLoading();
if (res.code === 1) {
uni.showToast({
title: '验证码已发送',
icon: 'success'
});
// 开始倒计时
this.startCountdown();
} else {
uni.showToast({
title: res.msg || '发送失败',
icon: 'none'
});
}
} catch (error) {
uni.hideLoading();
uni.showToast({
title: '发送失败,请重试',
icon: 'none'
});
}
},
startCountdown() {
this.codeCountdown = 60;
const timer = setInterval(() => {
this.codeCountdown--;
if (this.codeCountdown <= 0) {
clearInterval(timer);
}
}, 1000);
},
nextStep() {
if (!this.forgotForm.mobile) {
uni.showToast({
title: '请输入手机号',
icon: 'none'
});
return;
}
if (!this.forgotForm.code) {
uni.showToast({
title: '请输入验证码',
icon: 'none'
});
return;
}
if (!this.forgotForm.userType) {
uni.showToast({
title: '请选择用户类型',
icon: 'none'
});
return;
}
// 验证验证码
this.verifyCode();
},
async verifyCode() {
try {
uni.showLoading({ title: '验证中...' });
const res = await apiRoute.verifyCode({
mobile: this.forgotForm.mobile,
code: this.forgotForm.code,
type: 'reset_password',
user_type: this.forgotForm.userType
});
uni.hideLoading();
if (res.code === 1) {
this.currentStep = 2;
} else {
uni.showToast({
title: res.msg || '验证码错误',
icon: 'none'
});
}
} catch (error) {
uni.hideLoading();
uni.showToast({
title: '验证失败,请重试',
icon: 'none'
});
}
},
async resetPassword() {
if (!this.forgotForm.newPassword) {
uni.showToast({
title: '请输入新密码',
icon: 'none'
});
return;
}
if (this.forgotForm.newPassword.length < 6) {
uni.showToast({
title: '密码长度不能少于6位',
icon: 'none'
});
return;
}
if (this.forgotForm.newPassword !== this.forgotForm.confirmPassword) {
uni.showToast({
title: '两次密码输入不一致',
icon: 'none'
});
return;
}
try {
uni.showLoading({ title: '修改中...' });
const res = await apiRoute.resetPassword({
mobile: this.forgotForm.mobile,
code: this.forgotForm.code,
new_password: this.forgotForm.newPassword,
user_type: this.forgotForm.userType
});
uni.hideLoading();
if (res.code === 1) {
uni.showToast({
title: '密码修改成功',
icon: 'success'
});
setTimeout(() => {
this.closeForgotModal();
}, 1500);
} else {
uni.showToast({
title: res.msg || '修改失败',
icon: 'none'
});
}
} catch (error) {
uni.hideLoading();
uni.showToast({
title: '修改失败,请重试',
icon: 'none'
});
}
},
//登陆
async login() {
try {
// 参数验证
if (!this.user) {
uni.showToast({
title: '请输入手机号',
icon: 'none'
});
return;
}
if (!this.password1) {
uni.showToast({
title: '请输入密码',
icon: 'none'
});
return;
}
if (!this.loginType) {
uni.showToast({
title: '请选择登录类型',
icon: 'none'
});
return;
}
// 使用统一登录接口
const params = {
username: this.user,
password: this.password1,
login_type: this.loginType // 直接使用选择的登录类型 'staff' 或 'member'
};
console.log('登录参数:', params);
let res;
// 统一调用登录接口
res = await apiRoute.unifiedLogin(params);
if (res && res.code === 1) { // 成功状态码为1
// 登录成功
if (res.data && res.data.token) {
// 保存Token
uni.setStorageSync("token", res.data.token);
// 保存用户信息
if (res.data.user_info) {
uni.setStorageSync('userInfo', res.data.user_info);
// 根据登录类型设置userType
uni.setStorageSync("userType", this.loginType);
}
// 保存角色信息
if (res.data.role_info) {
uni.setStorageSync('roleInfo', res.data.role_info);
}
// 保存菜单信息
if (res.data.menu_list) {
uni.setStorageSync('menuList', res.data.menu_list);
}
console.log('Token保存成功:', res.data.token.substring(0, 20) + '...');
console.log('用户类型保存成功:', this.loginType);
}
uni.showToast({
title:'登录成功',
icon: 'success'
});
// 延迟执行跳转,确保数据保存完成
setTimeout(() => {
this.openViewHome();
}, 500);
} else {
uni.showToast({
title: res.msg || '登录失败',
icon: 'none'
});
}
} catch (error) {
uni.showModal({
title: '登录失败',
content: error,
showCancel: false
});
}
},
//微信自动登录
async wechatAutoLogin() {
try {
// 显示加载提示
uni.showLoading({
title: '微信登录中...'
});
// 获取微信小程序openid
await this.getMiNiWxOpenId();
if (!this.mini_wx_openid) {
uni.hideLoading();
uni.showToast({
title: '获取微信信息失败',
icon: 'none'
});
return;
}
// 尝试微信登录
const loginParams = {
openid: this.mini_wx_openid,
login_type: 'member'
};
const res = await apiRoute.wechatLogin(loginParams);
uni.hideLoading();
if (res && res.code === 1) {
// 登录成功
if (res.data && res.data.token) {
// 保存Token和用户信息
uni.setStorageSync("token", res.data.token);
if (res.data.user_info) {
uni.setStorageSync('userInfo', res.data.user_info);
uni.setStorageSync("userType", 'member');
}
if (res.data.role_info) {
uni.setStorageSync('roleInfo', res.data.role_info);
}
if (res.data.menu_list) {
uni.setStorageSync('menuList', res.data.menu_list);
}
uni.showToast({
title: '微信登录成功',
icon: 'success'
});
setTimeout(() => {
this.openViewHome();
}, 500);
}
} else if (res.code === 10001) {
// 需要绑定微信账号
this.showWechatBindDialog();
} else {
uni.showToast({
title: res.msg || '微信登录失败',
icon: 'none'
});
}
} catch (error) {
uni.hideLoading();
console.error('微信登录失败:', error);
uni.showToast({
title: '微信登录失败,请重试',
icon: 'none'
});
}
},
//显示微信绑定对话框
showWechatBindDialog() {
uni.showModal({
title: '微信账号未绑定',
content: '您的微信账号尚未绑定手机号,是否前往绑定?',
confirmText: '去绑定',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
this.openWechatBindPage();
}
}
});
},
//打开微信绑定页面
async openWechatBindPage() {
try {
// 获取微信公众号授权URL
const params = {
mini_openid: this.mini_wx_openid
};
const res = await apiRoute.getWechatAuthUrl(params);
if (res && res.code === 1 && res.data.auth_url) {
// 打开webview页面进行微信公众号授权
uni.navigateTo({
url: `/pages/student/login/wechat-bind?auth_url=${encodeURIComponent(res.data.auth_url)}&mini_openid=${this.mini_wx_openid}`
});
} else {
uni.showToast({
title: '获取授权链接失败',
icon: 'none'
});
}
} catch (error) {
console.error('获取授权链接失败:', error);
uni.showToast({
title: '获取授权链接失败',
icon: 'none'
});
}
},
//获取微信小程序openid
// 获取微信登录 code 并请求 openid
async getMiNiWxOpenId() {
uni.login({
provider: 'weixin',
success: (res) => {
const code = res.code;
this.fetchOpenId(code); // 调用单独方法
},
fail: () => {
uni.showToast({
title: '微信登录失败',
icon: 'none'
});
}
});
},
// 获取微信登录 code 并请求 openid
async fetchOpenId(code) {
let params = {
code: code,
}
let res = await apiRoute.common_getMiniWxOpenId(params)
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
this.mini_wx_openid = res.data.openid
},
//处理微信绑定成功
async handleWechatBindSuccess() {
// 绑定成功后自动进行微信登录
if (this.mini_wx_openid) {
setTimeout(() => {
this.wechatAutoLogin();
}, 500);
}
},
//登录类型选择相关
changePicker_loginType(e) {
console.log('监听选择', e)
this.loginType = e.value
this.loginType_str = e.text
this.picker_show_loginType = false
},
//检测首页是否正确跳转-不正确则进行重定向
async openViewHome() {
const userType = String(uni.getStorageSync('userType') || '');
const token = uni.getStorageSync('token') || ''
if (!userType) {
uni.showToast({
title: '您的角色不能访问本功能,请联系管理员',
icon: 'none'
})
return
}
if (!token) {
uni.showToast({
title: '请先登录',
icon: 'none'
})
return
}
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const thisPath = '/' + currentPage.route;
const openPath = this.path_arr[userType];
console.log('当前路径:', thisPath);
console.log('用户类型:', userType);
console.log('应跳转路径:', openPath);
if (thisPath !== openPath) {
// 如果不是正确的首页路径,则跳转到对应角色首页
uni.setStorageSync('tabBerIndex', 0)
// 检查是否为tabBar页面,使用对应的跳转方法
if (openPath === '/pages/common/home/index' || openPath === '/pages/common/profile/index') {
// tabBar页面使用switchTab
uni.switchTab({
url: openPath,
complete(e) {
console.log('switchTab result:', e)
}
});
} else {
// 非tabBar页面使用redirectTo
uni.redirectTo({
url: openPath,
complete(e) {
console.log('redirectTo result:', e)
}
});
}
return
}
// 如果是正确的路径,交给 onShow 控制初始化
},
}
}
</script>
<style lang="less" scoped>
page {
font-weight: normal;
}
.fui-section__title {
margin-left: 32rpx;
}
.fui-left__icon {
padding-right: 24rpx;
}
.image-container {
margin: auto;
position: relative;
width: 150rpx;
/* 设置与第一张图片相同的宽度 */
height: 150rpx;
/* 设置与第一张图片相同的高度 */
}
.base-image {
width: 100%;
height: 100%;
}
.overlay-image {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100rpx;
height: 100rpx;
}
/* 忘记密码弹窗样式 */
.forgot-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.forgot-modal {
width: 90%;
max-width: 600rpx;
background-color: #fff;
border-radius: 20rpx;
padding: 60rpx 40rpx 40rpx;
position: relative;
}
.close-btn {
position: absolute;
top: 20rpx;
right: 30rpx;
width: 60rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 40rpx;
color: #999;
font-weight: bold;
}
/* 步骤指示器 */
.step-indicator {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 60rpx;
}
.step-item {
display: flex;
flex-direction: column;
align-items: center;
}
.step-number {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background-color: #f5f5f5;
color: #999;
display: flex;
justify-content: center;
align-items: center;
font-size: 28rpx;
font-weight: bold;
margin-bottom: 10rpx;
}
.step-number.active {
background-color: #00be8c;
color: #fff;
}
.step-text {
font-size: 24rpx;
color: #999;
}
.step-text.active {
color: #00be8c;
}
.step-line {
width: 100rpx;
height: 4rpx;
background-color: #f5f5f5;
margin: 0 20rpx;
margin-bottom: 34rpx;
}
.step-line.active {
background-color: #00be8c;
}
/* 输入框样式 */
.step-content {
margin-bottom: 60rpx;
}
.input-group {
position: relative;
margin-bottom: 30rpx;
}
.input-field {
width: 100%;
height: 100rpx;
background-color: #f5f5f5;
border-radius: 10rpx;
padding: 0 30rpx;
font-size: 30rpx;
color: #333;
box-sizing: border-box;
border: none;
}
.verification-input {
padding-right: 180rpx;
}
.send-code-btn {
position: absolute;
right: 20rpx;
top: 50%;
transform: translateY(-50%);
background-color: #00be8c;
color: #fff;
padding: 15rpx 25rpx;
border-radius: 8rpx;
font-size: 24rpx;
text-align: center;
min-width: 140rpx;
}
.send-code-btn.disabled {
background-color: #ccc;
color: #999;
}
.password-toggle {
position: absolute;
right: 30rpx;
top: 50%;
transform: translateY(-50%);
font-size: 40rpx;
color: #999;
}
/* 用户类型选择器 */
.user-type-selector {
display: flex;
justify-content: space-between;
align-items: center;
height: 100rpx;
background-color: #f5f5f5;
border-radius: 10rpx;
padding: 0 30rpx;
margin-bottom: 30rpx;
}
.selector-text {
font-size: 30rpx;
color: #333;
}
.selector-arrow {
font-size: 30rpx;
color: #999;
}
/* 操作按钮 */
.action-buttons {
margin-top: 40rpx;
}
.next-btn,
.submit-btn {
width: 100%;
height: 100rpx;
background-color: #00be8c;
color: #fff;
border-radius: 10rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 32rpx;
font-weight: bold;
}
/* 用户类型选择弹窗 */
.user-type-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
}
.user-type-modal {
width: 80%;
max-width: 500rpx;
background-color: #fff;
border-radius: 20rpx;
padding: 40rpx;
}
.user-type-title {
text-align: center;
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 40rpx;
}
.user-type-option {
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
color: #333;
border-bottom: 1rpx solid #f5f5f5;
}
.user-type-option:last-child {
border-bottom: none;
}
.user-type-option:active {
background-color: #f5f5f5;
}
</style>