513 lines
16 KiB
TypeScript
513 lines
16 KiB
TypeScript
// 登录模块 - 处理用户登录、授权、用户信息管理
|
||
import { showToast } from '../../../utils/helpers';
|
||
import { UserInfo } from '../../../types';
|
||
import userService from '../../../services/userService';
|
||
import locationTrackingService from '../../../services/locationTrackingService';
|
||
import { DataModule } from './dataModule';
|
||
|
||
export class LoginModule {
|
||
private dataModule: DataModule;
|
||
private pageContext: any;
|
||
|
||
constructor(pageContext: any, dataModule: DataModule) {
|
||
this.pageContext = pageContext;
|
||
this.dataModule = dataModule;
|
||
}
|
||
|
||
/**
|
||
* 处理用户信息
|
||
*/
|
||
public async processUserInfo(userInfo: UserInfo): Promise<UserInfo> {
|
||
// 直接返回用户信息,不进行额外处理
|
||
return userInfo;
|
||
}
|
||
|
||
/**
|
||
* 登录成功后更新页面状态
|
||
*/
|
||
public updatePageAfterLogin(userInfo: UserInfo): void {
|
||
this.dataModule.updateUserInfo(userInfo);
|
||
}
|
||
|
||
/**
|
||
* 检查登录状态
|
||
*/
|
||
public checkLoginStatus(): boolean {
|
||
const globalUserInfo = userService.getGlobalUserInfo();
|
||
|
||
if (globalUserInfo) {
|
||
this.dataModule.updateUserInfo(globalUserInfo);
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 用户退出登录
|
||
*/
|
||
public async logout(): Promise<void> {
|
||
try {
|
||
// 停止位置追踪服务
|
||
try {
|
||
await locationTrackingService.stopTracking();
|
||
console.log('位置追踪服务已停止');
|
||
} catch (trackingError) {
|
||
console.warn('停止位置追踪失败,但不影响退出登录:', trackingError);
|
||
}
|
||
|
||
// 注意:这里不调用userService.logout(),因为服务器端的logout接口会删除token
|
||
// 而用户只是签退不接单,不是完全退出应用,需要保持token有效
|
||
console.log('用户已退出登录(本地签退,保持token有效)');
|
||
showToast('已退出登录');
|
||
} catch (error) {
|
||
console.error('退出登录失败:', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 显示手动登录模态框:如果静默登录失败后,让用户尝试手动登录
|
||
*/
|
||
public async showManualLoginModal(): Promise<boolean> {
|
||
console.log('显示手动登录模态框');
|
||
return new Promise((resolve) => {
|
||
wx.showModal({
|
||
title: '手动登录',
|
||
content: '静默登录失败,请手动登录以使用完整功能',
|
||
confirmText: '手动登录',
|
||
cancelText: '暂不',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
// 用户点击确定,执行手动登录
|
||
console.log('用户选择手动登录');
|
||
const success = await this.performLogin();
|
||
if (!success) {
|
||
// 登录失败,再次提供选项
|
||
this.handleLoginFailure(resolve);
|
||
} else {
|
||
resolve(success);
|
||
}
|
||
} else {
|
||
// 用户取消登录,显示关闭小程序选项
|
||
console.log('用户选择暂不登录');
|
||
this.showCloseAppOption(resolve);
|
||
}
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 执行登录流程 - 调用userService的登录方法
|
||
* 静默登录失败后,只专注于登录本身,不涉及绑定、签到等复杂逻辑
|
||
*/
|
||
private async performLogin(): Promise<boolean> {
|
||
try {
|
||
// 执行微信登录
|
||
const result = await userService.wxLogin();
|
||
if (result.success && result.userInfo) {
|
||
// 登录成功,更新页面状态
|
||
this.updatePageAfterLogin(result.userInfo);
|
||
console.log('手动登录成功');
|
||
return true;
|
||
}
|
||
|
||
console.log('手动登录失败');
|
||
return false;
|
||
} catch (error) {
|
||
console.error('执行登录流程失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理登录失败的情况
|
||
*/
|
||
public async handleLoginFailure(resolve: (value: boolean) => void) {
|
||
console.log('登录失败,显示重试选项');
|
||
wx.showModal({
|
||
title: '登录失败',
|
||
content: '登录遇到问题,是否重试?',
|
||
confirmText: '重新登录',
|
||
cancelText: '取消并关闭',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
console.log('用户选择重新登录');
|
||
const success = await this.performLogin();
|
||
if (!success) {
|
||
// 再次登录失败,递归调用以避免嵌套过深
|
||
this.handleLoginFailure(resolve);
|
||
} else {
|
||
resolve(success);
|
||
}
|
||
} else {
|
||
console.log('用户选择取消并关闭小程序');
|
||
wx.showToast({
|
||
title: '即将退出小程序',
|
||
icon: 'none',
|
||
duration: 1500,
|
||
complete: () => {
|
||
setTimeout(() => {
|
||
// 使用类型断言解决类型问题
|
||
(wx as any).exitMiniProgram();
|
||
}, 1500);
|
||
}
|
||
});
|
||
resolve(false);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 显示关闭小程序选项
|
||
*/
|
||
public showCloseAppOption(resolve: (value: boolean) => void) {
|
||
console.log('显示关闭小程序选项');
|
||
wx.showModal({
|
||
title: '确认退出',
|
||
content: '不登录将无法使用完整功能,是否退出小程序?',
|
||
confirmText: '退出小程序',
|
||
cancelText: '留在页面',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
console.log('用户确认退出小程序');
|
||
// 使用类型断言解决类型问题
|
||
(wx as any).exitMiniProgram();
|
||
resolve(false);
|
||
} else {
|
||
console.log('用户选择留在页面');
|
||
resolve(false);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 处理签退流程
|
||
*/
|
||
public async handleSignOut(): Promise<boolean> {
|
||
try {
|
||
// 显示加载中提示
|
||
wx.showLoading({
|
||
title: '签退中...',
|
||
});
|
||
|
||
// 调用签退接口
|
||
const signOutResult = await userService.signOut();
|
||
|
||
wx.hideLoading();
|
||
|
||
if (signOutResult.success) {
|
||
console.log('签退成功:', signOutResult);
|
||
wx.showToast({
|
||
title: '签退成功',
|
||
icon: 'success',
|
||
duration: 2000
|
||
});
|
||
|
||
// 停止位置追踪服务
|
||
try {
|
||
await locationTrackingService.stopTracking();
|
||
console.log('位置追踪服务已停止');
|
||
} catch (trackingError) {
|
||
console.warn('停止位置追踪失败,但不影响签退:', trackingError);
|
||
}
|
||
|
||
// 设置用户状态为已签退
|
||
if (this.pageContext && this.pageContext.setData) {
|
||
this.pageContext.setData({
|
||
'authStatus.userStatus': 'signed_out'
|
||
});
|
||
|
||
// 更新按钮显示状态
|
||
if (this.pageContext.updateButtonDisplayStatus) {
|
||
this.pageContext.updateButtonDisplayStatus();
|
||
}
|
||
}
|
||
|
||
return true;
|
||
} else {
|
||
console.warn('签退失败:', signOutResult.message);
|
||
wx.showToast({
|
||
title: signOutResult.message || '签退失败',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
return false;
|
||
}
|
||
} catch (error) {
|
||
console.error('签退过程中发生错误:', error);
|
||
wx.hideLoading();
|
||
wx.showToast({
|
||
title: '签退失败,请重试',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理签到流程
|
||
*/
|
||
public async handleSignIn(): Promise<boolean> {
|
||
try {
|
||
// 显示加载中提示
|
||
wx.showLoading({
|
||
title: '签到中...',
|
||
});
|
||
|
||
// 先获取地图位置和当前时间
|
||
let initialLocation: { latitude: number; longitude: number; timestamp: number };
|
||
try {
|
||
const location = await locationTrackingService.getCurrentLocation();
|
||
// 使用秒级时间戳(除以1000取整)
|
||
initialLocation = {
|
||
latitude: location.latitude,
|
||
longitude: location.longitude,
|
||
timestamp: Math.floor(Date.now() / 1000)
|
||
};
|
||
console.log('获取到签到位置数据:', initialLocation);
|
||
} catch (error) {
|
||
console.error('获取位置失败:', error);
|
||
throw new Error('获取位置失败,请检查位置权限设置');
|
||
}
|
||
|
||
// 调用实际的签到接口,传递位置数据
|
||
const signInResult = await userService.signIn(initialLocation);
|
||
|
||
wx.hideLoading();
|
||
|
||
if (signInResult.success) {
|
||
console.log('签到成功:', signInResult);
|
||
wx.showToast({
|
||
title: '签到成功',
|
||
icon: 'success',
|
||
duration: 2000
|
||
});
|
||
|
||
// 更新用户信息
|
||
if (signInResult.employeeInfo) {
|
||
// 将员工信息合并到用户信息中
|
||
const app = getApp<any>();
|
||
if (app.globalData.userInfo) {
|
||
app.globalData.userInfo = {
|
||
...app.globalData.userInfo,
|
||
name: signInResult.employeeInfo.name,
|
||
phone: signInResult.employeeInfo.phone
|
||
};
|
||
this.updatePageAfterLogin(app.globalData.userInfo);
|
||
}
|
||
}
|
||
|
||
// 设置用户状态为已签到
|
||
if (this.pageContext && this.pageContext.setData) {
|
||
this.pageContext.setData({
|
||
'authStatus.userStatus': 'signed_in'
|
||
});
|
||
|
||
// 更新按钮显示状态
|
||
if (this.pageContext.updateButtonDisplayStatus) {
|
||
this.pageContext.updateButtonDisplayStatus();
|
||
}
|
||
}
|
||
|
||
// 启动位置追踪服务(WebSocket连接)
|
||
try {
|
||
// 启动位置追踪服务(建立WebSocket连接)
|
||
await locationTrackingService.startTracking();
|
||
console.log('位置追踪服务已启动');
|
||
} catch (trackingError) {
|
||
console.warn('启动位置追踪失败,但不影响签到:', trackingError);
|
||
}
|
||
|
||
// 加载业务数据(所有登录用户)
|
||
try {
|
||
console.log('用户签到成功,开始加载业务数据');
|
||
// 获取主页面模块并加载业务数据
|
||
const mainPageModule = this.pageContext.data.mainPageModule;
|
||
if (mainPageModule && mainPageModule.loadBusinessData) {
|
||
await mainPageModule.loadBusinessData();
|
||
console.log('业务数据加载完成');
|
||
}
|
||
} catch (businessError) {
|
||
console.warn('加载业务数据失败,但不影响签到:', businessError);
|
||
}
|
||
|
||
// 如果是管理员用户,加载员工数据
|
||
if (signInResult.employeeInfo && signInResult.employeeInfo.role === 'ADMIN') {
|
||
try {
|
||
console.log('管理员用户签到成功,开始加载员工数据');
|
||
// 获取主页面模块并加载员工数据
|
||
const mainPageModule = this.pageContext.data.mainPageModule;
|
||
if (mainPageModule && mainPageModule.loadEmployeeData) {
|
||
await mainPageModule.loadEmployeeData();
|
||
console.log('员工数据加载完成');
|
||
}
|
||
} catch (employeeError) {
|
||
console.warn('加载员工数据失败,但不影响签到:', employeeError);
|
||
}
|
||
}
|
||
|
||
return true;
|
||
} else {
|
||
console.warn('签到失败:', signInResult.message);
|
||
wx.showToast({
|
||
title: signInResult.message || '签到失败',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
return false;
|
||
}
|
||
} catch (error) {
|
||
console.error('签到过程中发生错误:', error);
|
||
wx.hideLoading();
|
||
wx.showToast({
|
||
title: '签到失败,请重试',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理授权登录流程
|
||
*/
|
||
public async handleAuthLogin(): Promise<boolean> {
|
||
try {
|
||
const success = await this.showManualLoginModal();
|
||
|
||
if (success) {
|
||
console.log('手动登录成功');
|
||
return true;
|
||
} else {
|
||
console.log('用户取消手动登录');
|
||
return false;
|
||
}
|
||
} catch (error) {
|
||
console.error('手动登录过程中发生错误:', error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 判断用户是否为游客(已登录但信息不完整)
|
||
*/
|
||
public isTourist(): boolean {
|
||
const app = getApp<any>();
|
||
// 游客定义:已登录但没有完善基本信息(姓名和电话)的用户
|
||
if (app.globalData.isLoggedIn && app.globalData.userInfo) {
|
||
const userInfo = app.globalData.userInfo;
|
||
return !userInfo.name || !userInfo.phone;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 计算是否显示签到按钮(基于用户状态和角色)
|
||
*/
|
||
public shouldShowSignInButton(): boolean {
|
||
// 从页面上下文中获取数据
|
||
const pageData = this.dataModule.getData();
|
||
const authStatus = pageData.authStatus || {};
|
||
const userInfo = pageData.userInfo;
|
||
|
||
// 显示条件:已获取微信code、用户状态不是已签到、且用户不是游客(已绑定用户)
|
||
const result = (
|
||
authStatus.hasWxCode &&
|
||
(authStatus.userStatus === 'registered' || authStatus.userStatus === 'signed_out') &&
|
||
userInfo !== null &&
|
||
userInfo.role !== 'GUEST'
|
||
);
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 计算是否显示注册按钮(基于用户状态和角色)
|
||
*/
|
||
public shouldShowRegisterButton(): boolean {
|
||
// 从页面上下文中获取数据
|
||
const pageData = this.dataModule.getData();
|
||
const authStatus = pageData.authStatus || {};
|
||
const userInfo = pageData.userInfo;
|
||
|
||
// 显示条件:已获取微信code、用户状态为未绑定、且用户是游客
|
||
const result = (
|
||
authStatus.hasWxCode &&
|
||
authStatus.userStatus === 'unregistered' &&
|
||
userInfo !== null &&
|
||
userInfo.role === 'GUEST'
|
||
);
|
||
|
||
// 调试信息已删除
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 根据用户信息确定用户状态(从服务器获取真实状态)
|
||
*/
|
||
public async determineUserStatus(userInfo: any): Promise<'registered' | 'unregistered' | 'signed_in' | 'signed_out'> {
|
||
if (!userInfo) return 'signed_out';
|
||
|
||
try {
|
||
// 从服务器获取用户真实状态
|
||
const serverStatus = await this.getUserStatusFromServer();
|
||
|
||
if (serverStatus) {
|
||
console.log('从服务器获取的用户状态:', serverStatus);
|
||
return serverStatus;
|
||
}
|
||
|
||
// 如果服务器获取失败,使用本地逻辑作为降级方案
|
||
console.warn('服务器状态获取失败,使用本地逻辑判断');
|
||
const isRegistered = userInfo.name && userInfo.phone;
|
||
return isRegistered ? 'registered' : 'unregistered';
|
||
|
||
} catch (error) {
|
||
console.error('获取用户状态失败:', error);
|
||
// 网络错误时使用本地逻辑
|
||
const isRegistered = userInfo.name && userInfo.phone;
|
||
return isRegistered ? 'registered' : 'unregistered';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 从服务器获取用户签到状态
|
||
*/
|
||
private async getUserStatusFromServer(): Promise<'registered' | 'unregistered' | 'signed_in' | 'signed_out' | null> {
|
||
try {
|
||
// 调用服务器接口获取用户状态
|
||
const response = await userService.getUserStatus();
|
||
|
||
// 如果返回null,表示服务器接口不存在
|
||
if (response === null) {
|
||
console.log('服务器状态接口不存在,跳过服务器状态获取');
|
||
return null;
|
||
}
|
||
|
||
if (response && response.status) {
|
||
// 根据服务器返回的状态映射到前端状态
|
||
switch (response.status) {
|
||
case 'signed_in':
|
||
return 'signed_in';
|
||
case 'signed_out':
|
||
return 'signed_out';
|
||
case 'registered':
|
||
return 'registered';
|
||
case 'unregistered':
|
||
return 'unregistered';
|
||
default:
|
||
return null;
|
||
}
|
||
}
|
||
return null;
|
||
} catch (error) {
|
||
console.error('获取服务器状态失败:', error);
|
||
return null;
|
||
}
|
||
}
|
||
} |