Files
WXProgram/miniprogram/pages/index/modules/loginModule.ts
2025-10-19 13:40:20 +08:00

477 lines
15 KiB
TypeScript
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.

// 登录模块 - 处理用户登录、授权、用户信息管理
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 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();
}
}
// 启动位置追踪服务
try {
// 先启动位置追踪服务
await locationTrackingService.startTrackingAfterSignIn();
console.log('位置追踪服务已启动');
// 然后调用位置模块的实时跟踪功能
const locationModule = this.dataModule.getLocationModule();
if (locationModule) {
await locationModule.startRealTimeTracking();
console.log('位置模块实时跟踪已启动');
}
// 订阅位置更新回调,采用统一方式更新所有用户位置
locationTrackingService.subscribeToLocationUpdates((onlineUsers) => {
console.log('🚚 位置更新回调 - 在线用户列表已更新,用户数量:', onlineUsers.length);
// 统一更新所有在线用户的位置标记点
onlineUsers.forEach(user => {
if (user.lastLocation) {
console.log(`📍 更新用户 ${user.userId} 标记点位置:`, user.lastLocation);
// 统一调用数据模块更新用户标记点位置
this.dataModule.updateUserMarkerPosition(
user.lastLocation.longitude,
user.lastLocation.latitude,
user.userId
);
}
});
});
} catch (trackingError) {
console.warn('启动位置追踪失败,但不影响签到:', trackingError);
}
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'
);
// 调试信息:打印不满足条件的原因
if (!result) {
console.log('签到按钮不显示原因:');
if (!authStatus.hasWxCode) console.log(' - 未获取微信code');
if (authStatus.userStatus === 'signed_in') console.log(' - 用户状态为已签到');
if (authStatus.userStatus === 'unregistered') console.log(' - 用户状态为未注册');
if (userInfo === null) console.log(' - 用户信息为空');
if (userInfo && userInfo.role === 'GUEST') console.log(' - 用户角色为游客');
console.log('当前状态:', {
hasWxCode: authStatus.hasWxCode,
userStatus: authStatus.userStatus,
userInfo: userInfo ? { role: userInfo.role } : null
});
} else {
console.log('签到按钮显示条件满足');
}
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'
);
// 调试信息:打印不满足条件的原因
if (!result) {
console.log('注册按钮不显示原因:');
if (!authStatus.hasWxCode) console.log(' - 未获取微信code');
if (authStatus.userStatus !== 'unregistered') console.log(` - 用户状态为${authStatus.userStatus},不是未注册`);
if (userInfo === null) console.log(' - 用户信息为空');
if (userInfo && userInfo.role !== 'GUEST') console.log(` - 用户角色为${userInfo.role},不是游客`);
console.log('当前状态:', {
hasWxCode: authStatus.hasWxCode,
userStatus: authStatus.userStatus,
userInfo: userInfo ? { role: userInfo.role } : null
});
} else {
console.log('注册按钮显示条件满足');
}
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;
}
}
}