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