commit
This commit is contained in:
@@ -35,16 +35,16 @@ const API_CONFIG: Record<EnvType, EnvConfig> = {
|
||||
// 测试环境 - 服务器测试
|
||||
test: {
|
||||
baseURL: 'https://lehang.tech', // 测试服务器Go服务
|
||||
pythonURL: 'https://lehang.tech', // 测试服务器Python服务
|
||||
websocketURL: 'wss://lehang.tech', // 测试服务器WebSocket服务
|
||||
pythonURL: 'https://api.lehang.tech', // 测试服务器Python服务
|
||||
websocketURL: 'wss://api.lehang.tech', // 测试服务器WebSocket服务
|
||||
timeout: 90000
|
||||
},
|
||||
|
||||
// 生产环境
|
||||
prod: {
|
||||
baseURL: 'https://lehang.tech', // 生产环境Go服务
|
||||
pythonURL: 'https://lehang.tech', // 生产环境Python服务
|
||||
websocketURL: 'wss://lehang.tech', // 生产环境WebSocket服务
|
||||
pythonURL: 'https://api.lehang.tech', // 生产环境Python服务
|
||||
websocketURL: 'wss://api.lehang.tech', // 生产环境WebSocket服务
|
||||
timeout: 90000
|
||||
}
|
||||
};
|
||||
|
||||
@@ -41,7 +41,10 @@ Page({
|
||||
qrcodeError: '', // 二维码加载错误提示
|
||||
qrcodeLoading: false, // 二维码是否正在加载
|
||||
isDevelopment: isDevelopment(), // 是否开发环境
|
||||
isGettingCode: false // 是否正在获取验证码(防抖)
|
||||
isScanning: false, // 是否正在扫码过程中(用户保存二维码后切到小红书)
|
||||
|
||||
// 消息确认机制
|
||||
processedMessageIds: [] as string[], // 已处理的消息ID集合,避免重复处理
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
@@ -73,6 +76,9 @@ Page({
|
||||
console.log('[页面生命周期] 当前登录方式:', this.data.loginType);
|
||||
console.log('[页面生命周期] 是否正在等待登录结果:', (this.data as any).waitingLoginResult);
|
||||
console.log('[页面生命周期] 是否需要验证码:', this.data.needCaptcha);
|
||||
console.log('[页面生命周期] 是否正在扫码:', this.data.isScanning);
|
||||
console.log('[页面生命周期] sessionId:', this.data.sessionId);
|
||||
console.log('[页面生命周期] WebSocket连接状态:', this.data.socketConnected);
|
||||
|
||||
// 临时标记为隐藏状态,阻止重连
|
||||
this.setData({ pageHidden: true } as any);
|
||||
@@ -95,6 +101,18 @@ Page({
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果用户正在扫码过程中(保存二维码后切到小红书),不关闭WebSocket
|
||||
if (this.data.isScanning) {
|
||||
console.log('[页面生命周期] 用户正在扫码,保持WebSocket连接');
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果sessionId存在且WebSocket连接活跃,保持连接(用户可能正在等待验证码或准备登录)
|
||||
if (this.data.sessionId && this.data.socketConnected) {
|
||||
console.log('[页面生命周期] 有活跃的WebSocket连接,保持连接(用户可能正在登录流程中)');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[页面生命周期] 关闭WebSocket连接');
|
||||
// 页面隐藏时也关闭WebSocket连接
|
||||
this.closeWebSocket();
|
||||
@@ -114,6 +132,24 @@ Page({
|
||||
// readyState: 0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED
|
||||
if (readyState === 0 || readyState === 1) {
|
||||
console.log('[页面生命周期] WebSocket连接已存在且正常,无需重建');
|
||||
|
||||
// 连接正常,主动拉取未确认消息
|
||||
if (readyState === 1) {
|
||||
console.log('[WebSocket] 主动拉取未确认消息');
|
||||
try {
|
||||
socketTask.send({
|
||||
data: JSON.stringify({ type: 'pull_unconfirmed' }),
|
||||
success: () => {
|
||||
console.log('[WebSocket] 拉取未确认消息请求已发送');
|
||||
},
|
||||
fail: (err: any) => {
|
||||
console.error('[WebSocket] 拉取未确认消息请求失败:', err);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('[WebSocket] 拉取未确认消息异常:', e);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
console.log(`[页面生命周期] WebSocket连接状态异常: ${readyState},准备重建`);
|
||||
@@ -163,12 +199,6 @@ Page({
|
||||
|
||||
// 获取验证码
|
||||
async getVerifyCode() {
|
||||
// 防抖:如果正在获取中,直接返回
|
||||
if (this.data.isGettingCode) {
|
||||
console.log('[发送验证码] 正在处理中,忽略重复点击');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.data.countdown > 0) {
|
||||
return;
|
||||
}
|
||||
@@ -183,9 +213,6 @@ Page({
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置防抖标记
|
||||
this.setData({ isGettingCode: true });
|
||||
|
||||
// 立即显示加载动画
|
||||
wx.showToast({
|
||||
title: '正在连接...',
|
||||
@@ -284,8 +311,6 @@ Page({
|
||||
},
|
||||
fail: (err: any) => {
|
||||
console.error('[发送验证码] WebSocket消息发送失败:', err);
|
||||
// 清除防抖标记
|
||||
this.setData({ isGettingCode: false });
|
||||
wx.showToast({
|
||||
title: '发送失败,请重试',
|
||||
icon: 'none',
|
||||
@@ -296,8 +321,6 @@ Page({
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[发送验证码] 异常:', error);
|
||||
// 清除防抖标记
|
||||
this.setData({ isGettingCode: false });
|
||||
wx.showToast({
|
||||
title: error.message || '发送失败,请重试',
|
||||
icon: 'none',
|
||||
@@ -538,17 +561,35 @@ Page({
|
||||
|
||||
console.log('需要验证码验证:', status.captcha_type);
|
||||
|
||||
wx.showToast({
|
||||
title: status.message || '需要验证码验证',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
|
||||
this.setData({
|
||||
needCaptcha: true,
|
||||
captchaType: status.captcha_type || 'unknown',
|
||||
qrcodeImage: status.qrcode_image || ''
|
||||
});
|
||||
// 判断是否成功获取到二维码
|
||||
if (status.qrcode_image) {
|
||||
wx.showToast({
|
||||
title: status.message || '需要验证码验证',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
|
||||
this.setData({
|
||||
needCaptcha: true,
|
||||
captchaType: status.captcha_type || 'unknown',
|
||||
qrcodeImage: status.qrcode_image
|
||||
});
|
||||
} else {
|
||||
// 未能获取到二维码,显示Toast提示
|
||||
console.warn('验证码发送成功,但未能获取二维码');
|
||||
|
||||
wx.showToast({
|
||||
title: '页面加载异常,请重试',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
|
||||
this.setData({
|
||||
needCaptcha: false, // 不显示弹窗
|
||||
captchaType: '',
|
||||
qrcodeImage: ''
|
||||
});
|
||||
}
|
||||
|
||||
} else if (status.status === 'processing') {
|
||||
// 仍在处理中,继续轮询
|
||||
@@ -815,8 +856,82 @@ Page({
|
||||
|
||||
// 刷新二维码
|
||||
async refreshQRCode() {
|
||||
console.log('[刷新二维码] 用户点击刷新按钮');
|
||||
console.log('[刷新二维码] needCaptcha:', this.data.needCaptcha);
|
||||
console.log('[刷新二维码] qrcodeStatus:', this.data.qrcodeStatus);
|
||||
console.log('[刷新二维码] socketTask存在:', !!this.data.socketTask);
|
||||
console.log('[刷新二维码] qrcodeSessionId:', this.data.qrcodeSessionId);
|
||||
|
||||
// 判断是风控二维码还是扫码登录二维码
|
||||
// 风控二维码:needCaptcha=true 且 qrcodeStatus=5,使用WebSocket刷新
|
||||
// 扫码登录二维码:qrcodeSessionId存在,使用HTTP API刷新
|
||||
if (this.data.needCaptcha && this.data.qrcodeStatus === 5) {
|
||||
// 风控二维码刷新(验证码登录时)
|
||||
console.log('[刷新二维码] 确认是风控二维码,使用WebSocket刷新');
|
||||
|
||||
// 检查WebSocket连接
|
||||
if (!this.data.socketTask) {
|
||||
console.error('[刷新二维码] WebSocket未连接');
|
||||
wx.showToast({
|
||||
title: 'WebSocket未连接,请重新发送验证码',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示加载提示
|
||||
wx.showLoading({
|
||||
title: '正在刷新...',
|
||||
mask: true
|
||||
});
|
||||
|
||||
// 发送刷新请求
|
||||
try {
|
||||
this.data.socketTask.send({
|
||||
data: JSON.stringify({
|
||||
type: 'refresh_qrcode'
|
||||
}),
|
||||
success: () => {
|
||||
console.log('[刷新二维码] 刷新请求发送成功');
|
||||
|
||||
// 更新状态为加载中
|
||||
this.setData({
|
||||
qrcodeStatus: 0,
|
||||
captchaTitle: '正在刷新二维码...'
|
||||
});
|
||||
},
|
||||
fail: (err: any) => {
|
||||
console.error('[刷新二维码] 发送失败:', err);
|
||||
wx.hideLoading();
|
||||
wx.showToast({
|
||||
title: '刷新失败,请重试',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[刷新二维码] 异常:', error);
|
||||
wx.hideLoading();
|
||||
wx.showToast({
|
||||
title: '刷新失败,请重试',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 扫码登录二维码刷新(HTTP API)
|
||||
console.log('[刷新二维码] 使用HTTP API刷新扫码登录二维码');
|
||||
const { qrcodeSessionId } = this.data;
|
||||
if (!qrcodeSessionId) {
|
||||
wx.showToast({
|
||||
title: '请重新发送验证码',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1114,17 +1229,17 @@ Page({
|
||||
// 建立WebSocket连接
|
||||
connectWebSocket(sessionId: string) {
|
||||
console.log('[WebSocket] ========== connectWebSocket被调用 ==========');
|
||||
console.log('[WebSocket] 调用栈:', new Error().stack?.split('\n').slice(1, 4).join('\n'));
|
||||
const stack = new Error().stack;
|
||||
console.log('[WebSocket] 调用栈:', stack ? stack.split('\n').slice(1, 4).join('\n') : '无法获取');
|
||||
console.log('[WebSocket] SessionID:', sessionId);
|
||||
console.log('[WebSocket] 当前needCaptcha状态:', this.data.needCaptcha);
|
||||
console.log('[WebSocket] ===========================================');
|
||||
|
||||
// 清除正常关闭标记,新连接默认为非正常关闭
|
||||
// 重置重连计数,允许新连接重连
|
||||
this.setData({
|
||||
normalClose: false,
|
||||
reconnectCount: 0
|
||||
} as any);
|
||||
// 如果已有连接且处于OPEN状态,不重复连接
|
||||
if (this.data.socketTask && this.data.socketReadyState === 1) {
|
||||
console.log('[WebSocket] 连接已存在且活跃,不重复连接');
|
||||
return;
|
||||
}
|
||||
|
||||
// 关闭旧连接
|
||||
if (this.data.socketTask) {
|
||||
@@ -1139,14 +1254,25 @@ Page({
|
||||
console.log('[WebSocket] 已调用close(),等待关闭...');
|
||||
} catch (e) {
|
||||
console.log('[WebSocket] 关闭旧连接失败(可能已关闭):', e);
|
||||
} finally {
|
||||
// 重置标记,允许新连接重连
|
||||
this.setData({
|
||||
normalClose: false,
|
||||
reconnectCount: 0
|
||||
} as any);
|
||||
}
|
||||
// 等待50ms让旧连接完全关闭
|
||||
setTimeout(() => {
|
||||
this._doConnect(sessionId);
|
||||
}, 50);
|
||||
} else {
|
||||
// 没有旧连接,直接连接
|
||||
this._doConnect(sessionId);
|
||||
}
|
||||
},
|
||||
|
||||
// 执行实际的WebSocket连接(内部方法)
|
||||
_doConnect(sessionId: string) {
|
||||
// 清除正常关闭标记,新连接默认为非正常关闭
|
||||
// 重置重连计数,允许新连接重连
|
||||
this.setData({
|
||||
normalClose: false,
|
||||
reconnectCount: 0
|
||||
} as any);
|
||||
|
||||
// 获取WebSocket服务地址(使用配置化地址)
|
||||
const wsURL = API.websocketURL;
|
||||
@@ -1229,6 +1355,44 @@ Page({
|
||||
try {
|
||||
const data = JSON.parse(res.data as string);
|
||||
|
||||
// 提取message_id
|
||||
const messageId = data.message_id;
|
||||
|
||||
// 检查是否已处理过该消息(去重)
|
||||
if (messageId && this.data.processedMessageIds.includes(messageId)) {
|
||||
console.log('[WebSocket] 消息已处理,忽略重复消息:', messageId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加到已处理集合
|
||||
if (messageId) {
|
||||
const processedIds = this.data.processedMessageIds;
|
||||
processedIds.push(messageId);
|
||||
// 保持集合大小,最多100条
|
||||
if (processedIds.length > 100) {
|
||||
processedIds.shift();
|
||||
}
|
||||
this.setData({ processedMessageIds: processedIds });
|
||||
|
||||
// 立即发送ACK确认
|
||||
try {
|
||||
socketTask.send({
|
||||
data: JSON.stringify({
|
||||
type: 'ack',
|
||||
message_id: messageId
|
||||
}),
|
||||
success: () => {
|
||||
console.log('[WebSocket] ACK确认已发送:', messageId);
|
||||
},
|
||||
fail: (err: any) => {
|
||||
console.error('[WebSocket] ACK确认发送失败:', messageId, err);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('[WebSocket] 发送ACK异常:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理二维码状态消息
|
||||
if (data.type === 'qrcode_status') {
|
||||
console.log('📡 二维码状态变化:', `status=${data.status}`, data.message);
|
||||
@@ -1269,13 +1433,16 @@ Page({
|
||||
}
|
||||
// 处理扫码成功消息(发送验证码阶段的风控)
|
||||
else if (data.type === 'qrcode_scan_success') {
|
||||
console.log('✅ 扫码验证完成!', data.message);
|
||||
console.log('扫码验证完成!', data.message);
|
||||
|
||||
// 关闭验证码弹窗
|
||||
// 清除扫码标记
|
||||
this.setData({
|
||||
needCaptcha: false
|
||||
needCaptcha: false,
|
||||
isScanning: false
|
||||
});
|
||||
|
||||
console.log('[扫码成功] 已清除isScanning标记');
|
||||
|
||||
// 显示提示:扫码成功,请重新发送验证码
|
||||
wx.showToast({
|
||||
title: data.message || '扫码成功,请重新发送验证码',
|
||||
@@ -1290,24 +1457,51 @@ Page({
|
||||
}
|
||||
// 处理二维码失效消息
|
||||
else if (data.type === 'qrcode_expired') {
|
||||
console.log('⚠️ 二维码已失效!', data.message);
|
||||
console.log('二维码已失效!', data.message);
|
||||
|
||||
// 关闭验证码弹窗
|
||||
// 不关闭弹窗,保持显示,等待用户点击刷新
|
||||
// 更新二维码状态为5(过期)
|
||||
this.setData({
|
||||
needCaptcha: false
|
||||
qrcodeStatus: 5,
|
||||
captchaTitle: data.message || '二维码已过期,点击二维码区域刷新'
|
||||
});
|
||||
|
||||
// 显示提示:二维码已失效
|
||||
wx.showToast({
|
||||
title: data.message || '二维码已失效,请重新发送验证码',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
console.log('[二维码过期] 已更新状态为5,等待用户点击刷新');
|
||||
console.log('[WebSocket] 保持连接,等待用户点击刷新按钮');
|
||||
|
||||
console.log('[WebSocket] 二维码已失效,关闭弹窗');
|
||||
console.log('[WebSocket] 保持连接,等待用户重新操作');
|
||||
// 不关闭WebSocket,保持连接用于刷新二维码
|
||||
}
|
||||
// 处理二维码刷新结果
|
||||
else if (data.type === 'qrcode_refreshed') {
|
||||
console.log('二维码刷新结果:', data);
|
||||
|
||||
// 不关闭WebSocket,保持连接用于重新发送验证码
|
||||
wx.hideLoading(); // 隐藏loading
|
||||
|
||||
if (data.success) {
|
||||
// 刷新成功,更新二维码图片
|
||||
this.setData({
|
||||
qrcodeImage: data.qrcode_image,
|
||||
qrcodeStatus: 1, // 恢复为等待扫码状态
|
||||
captchaTitle: '请使用小红书APP扫码'
|
||||
});
|
||||
|
||||
console.log('[二维码刷新] 刷新成功,已更新二维码图片');
|
||||
|
||||
wx.showToast({
|
||||
title: data.message || '二维码已刷新',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
} else {
|
||||
// 刷新失败
|
||||
console.error('[二维码刷新] 失败:', data.message);
|
||||
|
||||
wx.showToast({
|
||||
title: data.message || '刷新失败,请重试',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
}
|
||||
// 处理登录成功消息(点击登录按钮阶段的风控)
|
||||
else if (data.type === 'login_success') {
|
||||
@@ -1317,15 +1511,18 @@ Page({
|
||||
// 判断是扫码验证成功还是真正的登录成功
|
||||
if (data.storage_state) {
|
||||
// 真正的登录成功,包含 storage_state
|
||||
console.log('✅ 登录成功!', data);
|
||||
console.log('登录成功!', data);
|
||||
|
||||
wx.hideToast();
|
||||
|
||||
// 关闭验证码弹窗
|
||||
// 关闭验证码弹窗并清除扫码标记
|
||||
this.setData({
|
||||
needCaptcha: false
|
||||
needCaptcha: false,
|
||||
isScanning: false
|
||||
});
|
||||
|
||||
console.log('[登录成功] 已清除isScanning标记');
|
||||
|
||||
// 关闭WebSocket
|
||||
this.closeWebSocket();
|
||||
|
||||
@@ -1395,29 +1592,45 @@ Page({
|
||||
else if (data.type === 'need_captcha') {
|
||||
console.log('⚠️ 需要扫码验证:', data.captcha_type);
|
||||
|
||||
// 显示二维码弹窗
|
||||
this.setData({
|
||||
needCaptcha: true,
|
||||
captchaType: data.captcha_type || 'unknown',
|
||||
qrcodeImage: data.qrcode_image || ''
|
||||
});
|
||||
|
||||
wx.hideToast();
|
||||
wx.showToast({
|
||||
title: data.message || '需要扫码验证',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
|
||||
console.log('[WebSocket] 已显示风控二维码');
|
||||
// 判断是否成功获取到二维码
|
||||
if (data.qrcode_image) {
|
||||
// 显示二维码弹窗
|
||||
this.setData({
|
||||
needCaptcha: true,
|
||||
captchaType: data.captcha_type || 'unknown',
|
||||
qrcodeImage: data.qrcode_image
|
||||
});
|
||||
|
||||
wx.hideToast();
|
||||
wx.showToast({
|
||||
title: data.message || '需要扫码验证',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
|
||||
console.log('[WebSocket] 已显示风控二维码');
|
||||
} else {
|
||||
// 未能获取到二维码,显示Toast提示
|
||||
console.warn('[WebSocket] 验证码发送成功,但未能获取二维码');
|
||||
|
||||
this.setData({
|
||||
needCaptcha: false, // 不显示弹窗
|
||||
captchaType: '',
|
||||
qrcodeImage: ''
|
||||
});
|
||||
|
||||
wx.hideToast();
|
||||
wx.showToast({
|
||||
title: '页面加载异常,请重试',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
}
|
||||
// 处理code_sent消息(验证码发送结果)
|
||||
else if (data.type === 'code_sent') {
|
||||
console.log('[WebSocket] 验证码发送结果:', data);
|
||||
|
||||
// 清除防抖标记
|
||||
this.setData({ isGettingCode: false });
|
||||
|
||||
wx.hideToast();
|
||||
|
||||
if (data.success) {
|
||||
@@ -1486,35 +1699,44 @@ Page({
|
||||
console.log('[WebSocket] 关闭代码:', res.code || '未知');
|
||||
console.log('[WebSocket] 是否正常关闭:', res.code === 1000 ? '是' : '否');
|
||||
console.log('[WebSocket] 是否主动关闭:', (this.data as any).normalClose ? '是' : '否');
|
||||
console.log('=========================================')
|
||||
console.log('[WebSocket] 当前sessionId:', this.data.sessionId);
|
||||
console.log('[WebSocket] 页面是否隐藏:', (this.data as any).pageHidden);
|
||||
console.log('[WebSocket] 当前重连计数:', (this.data as any).reconnectCount || 0);
|
||||
console.log('=========================================');
|
||||
socketConnected = false;
|
||||
|
||||
|
||||
// 更新页面状态
|
||||
this.setData({
|
||||
socketConnected: false,
|
||||
socketReadyState: 3 // CLOSED
|
||||
});
|
||||
|
||||
|
||||
// 清理ping定时器
|
||||
if ((this.data as any).pingTimer) {
|
||||
clearInterval((this.data as any).pingTimer);
|
||||
}
|
||||
|
||||
|
||||
// 判断是否是正常关闭
|
||||
const isNormalClose = (this.data as any).normalClose || res.code === 1000;
|
||||
|
||||
|
||||
// 清除正常关闭标记
|
||||
this.setData({ normalClose: false } as any);
|
||||
|
||||
|
||||
// 检查是否需要重连(只要页面还在且未隐藏就重连)
|
||||
// 增加重连计数
|
||||
const reconnectCount = (this.data as any).reconnectCount || 0;
|
||||
|
||||
|
||||
// 如果是主动关闭(reconnectCount >= 999),不重连
|
||||
if (reconnectCount >= 999) {
|
||||
console.log('[WebSocket] 主动关闭,不重连');
|
||||
return;
|
||||
}
|
||||
|
||||
// 最多重连5次
|
||||
if (reconnectCount < 5) {
|
||||
const delay = Math.min(1000 * Math.pow(2, reconnectCount), 10000); // 指数退避: 1s, 2s, 4s, 8s, 10s
|
||||
console.log(`[WebSocket] 将在${delay}ms后进行第${reconnectCount + 1}次重连`);
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
// 检查页面是否还在且未隐藏(通过sessionId存在和pageHidden来判断)
|
||||
if (this.data.sessionId && !(this.data as any).pageHidden) {
|
||||
@@ -1527,7 +1749,7 @@ Page({
|
||||
}, delay);
|
||||
} else {
|
||||
console.log('[WebSocket] 已达到最大重连次数,停止重连');
|
||||
|
||||
|
||||
// 只有非正常关闭才提示用户
|
||||
if (!isNormalClose) {
|
||||
wx.showToast({
|
||||
@@ -1624,6 +1846,10 @@ Page({
|
||||
|
||||
console.log('[保存二维码] 开始保存');
|
||||
|
||||
// 标记用户正在扫码过程中
|
||||
this.setData({ isScanning: true });
|
||||
console.log('[保存二维码] 已设置isScanning=true,保持WebSocket连接');
|
||||
|
||||
// base64转为临时文件
|
||||
const base64Data = this.data.qrcodeImage.replace(/^data:image\/\w+;base64,/, '');
|
||||
const filePath = `${wx.env.USER_DATA_PATH}/qrcode_${Date.now()}.png`;
|
||||
@@ -1643,9 +1869,9 @@ Page({
|
||||
success: () => {
|
||||
console.log('[保存二维码] 保存成功');
|
||||
wx.showToast({
|
||||
title: '二维码已保存到相册',
|
||||
title: '二维码已保存,请在小红书中扫码',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
duration: 3000
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
@@ -1693,13 +1919,18 @@ Page({
|
||||
// 关闭验证码弹窗
|
||||
closeCaptcha() {
|
||||
console.log('[关闭弹窗] 用户手动关闭二维码弹窗');
|
||||
|
||||
// 关闭弹窗时清除扫码标记(用户放弃扫码)
|
||||
this.setData({
|
||||
needCaptcha: false,
|
||||
qrcodeImage: '',
|
||||
captchaTitle: '',
|
||||
qrcodeStatus: 0,
|
||||
qrcodeStatusText: ''
|
||||
qrcodeStatusText: '',
|
||||
isScanning: false
|
||||
});
|
||||
|
||||
console.log('[关闭弹窗] 已清除isScanning标记');
|
||||
},
|
||||
|
||||
// 测试WebSocket连接
|
||||
|
||||
@@ -24,29 +24,26 @@
|
||||
<text class="page-title">请绑定小红书账号</text>
|
||||
<text class="page-subtitle">手机号未注册小红书会导致绑定失败</text>
|
||||
|
||||
<!-- 登录方式切换 -->
|
||||
<view class="login-type-tabs">
|
||||
<!-- 登录方式切换 - 暂时隐藏扫码登录 -->
|
||||
<!-- <view class="login-type-tabs">
|
||||
<view class="tab {{loginType === 'phone' ? 'active' : ''}}" bindtap="switchToPhone">
|
||||
<text>手机号登录</text>
|
||||
</view>
|
||||
<view class="tab {{loginType === 'qrcode' ? 'active' : ''}}" bindtap="switchToQRCode">
|
||||
<text>扫码登录</text>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<!-- 二维码扫码登录区域 -->
|
||||
<view class="qrcode-login-section" wx:if="{{loginType === 'qrcode'}}">
|
||||
<!-- 二维码扫码登录区域 - 暂时隐藏 -->
|
||||
<!-- <view class="qrcode-login-section" wx:if="{{loginType === 'qrcode'}}">
|
||||
<view class="qrcode-container">
|
||||
<!-- 加载中 -->
|
||||
<view class="qrcode-loading" wx:if="{{qrcodeLoading}}">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">{{loadingText}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 二维码图片 -->
|
||||
<image class="qrcode-img" src="{{qrcodeImage}}" mode="aspectFit" wx:if="{{qrcodeImage && !qrcodeLoading}}"></image>
|
||||
|
||||
<!-- 二维码状态覆盖层(只在过期或出错时显示) -->
|
||||
<view class="qrcode-status" wx:if="{{qrcodeExpired && !qrcodeLoading}}">
|
||||
<view class="status-content">
|
||||
<text class="status-text">{{statusText || '二维码已过期'}}</text>
|
||||
@@ -54,7 +51,6 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<view class="qrcode-error" wx:if="{{qrcodeError && !qrcodeLoading}}">
|
||||
<view class="error-content">
|
||||
<text class="error-icon">⚠️</text>
|
||||
@@ -64,7 +60,6 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 登录链接显示区域 -->
|
||||
<view class="qr-url-section" wx:if="{{qrUrl}}">
|
||||
<view class="url-label">登录链接(可复制到浏览器或小红书APP)</view>
|
||||
<view class="url-content">
|
||||
@@ -77,7 +72,7 @@
|
||||
<text class="tip-text">请使用小红书APP扫描二维码</text>
|
||||
<text class="tip-desc">扫码后即可完成绑定</text>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<!-- 手机号登录区域 -->
|
||||
<view class="phone-login-section" wx:if="{{loginType === 'phone'}}">
|
||||
@@ -106,9 +101,17 @@
|
||||
</view>
|
||||
|
||||
<!-- 已过期透明层 -->
|
||||
<view class="scan-overlay error" wx:if="{{qrcodeStatus === 5}}">
|
||||
<view class="scan-icon">
|
||||
<text class="icon-cross">×</text>
|
||||
<view class="scan-overlay expired" wx:if="{{qrcodeStatus === 5}}">
|
||||
<view class="expired-container">
|
||||
<view class="expired-icon-box">
|
||||
<text class="expired-icon">⟳</text>
|
||||
</view>
|
||||
<view class="expired-content">
|
||||
<text class="expired-title">二维码已过期</text>
|
||||
</view>
|
||||
<view class="refresh-button" bindtap="refreshQRCode">
|
||||
<text class="refresh-text">刷新</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -116,9 +119,8 @@
|
||||
<!-- 提示文本 -->
|
||||
<view class="qr-tips">
|
||||
<text class="tip-main">{{captchaTitle || '请使用小红书APP扫码'}}</text>
|
||||
<text class="tip-sub" wx:if="{{qrcodeStatus === 2}}">在APP中确认完成验证</text>
|
||||
<text class="tip-sub" wx:if="{{qrcodeStatus === 2}}">请在APP中确认</text>
|
||||
<text class="tip-sub" wx:if="{{qrcodeStatus !== 2 && qrcodeStatus !== 5}}">长按二维码可保存</text>
|
||||
<text class="tip-sub error" wx:if="{{qrcodeStatus === 5}}">请关闭后重新获取</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -128,7 +130,7 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="bind-form" wx:if="{{!needCaptcha}}">
|
||||
<view class="bind-form">
|
||||
<view class="input-row">
|
||||
<text class="label">手机号</text>
|
||||
<picker mode="selector" range="{{countryCodes}}" value="{{countryCodeIndex}}" bindchange="onCountryCodeChange">
|
||||
|
||||
@@ -348,6 +348,271 @@ page {
|
||||
box-shadow: 0 8rpx 24rpx rgba(255, 77, 79, 0.4);
|
||||
}
|
||||
|
||||
/* 过期状态样式 */
|
||||
.scan-overlay.expired {
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
backdrop-filter: blur(20rpx);
|
||||
-webkit-backdrop-filter: blur(20rpx);
|
||||
}
|
||||
|
||||
.expired-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx 40rpx;
|
||||
animation: slideUp 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(40rpx);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 过期图标容器 */
|
||||
.expired-icon-box {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 12rpx 32rpx rgba(255, 107, 107, 0.3);
|
||||
margin-bottom: 20rpx;
|
||||
animation: rotateIn 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
@keyframes rotateIn {
|
||||
from {
|
||||
transform: rotate(-180deg) scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: rotate(0deg) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 过期图标 */
|
||||
.expired-icon {
|
||||
font-size: 60rpx;
|
||||
color: #fff;
|
||||
font-weight: 300;
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 过期内容 */
|
||||
.expired-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.expired-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
/* 刷新按钮 */
|
||||
.refresh-button {
|
||||
width: 200rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 36rpx;
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 6rpx 16rpx rgba(255, 107, 107, 0.4);
|
||||
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.refresh-button::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
||||
transition: left 0.5s;
|
||||
}
|
||||
|
||||
.refresh-button:active {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 107, 107, 0.3);
|
||||
}
|
||||
|
||||
.refresh-button:active::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.refresh-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
/* 错误提示样式 */
|
||||
.qr-error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60rpx 40rpx;
|
||||
min-height: 500rpx;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 24rpx;
|
||||
animation: bounceIn 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
@keyframes bounceIn {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.error-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.error-desc {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.retry-button {
|
||||
width: 280rpx;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
box-shadow: 0 8rpx 24rpx rgba(255, 107, 107, 0.4);
|
||||
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
.retry-button::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.retry-button:active {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 107, 107, 0.3);
|
||||
}
|
||||
|
||||
/* 内联错误提示样式 */
|
||||
.qr-error-inline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFE8E8 100%);
|
||||
padding: 24rpx 32rpx;
|
||||
margin: 0 32rpx 32rpx;
|
||||
border-radius: 16rpx;
|
||||
border: 2rpx solid #FFCDD2;
|
||||
animation: slideDown 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
transform: translateY(-20rpx);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.qr-error-inline .error-icon {
|
||||
font-size: 48rpx;
|
||||
margin: 0;
|
||||
animation: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.qr-error-inline .error-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.qr-error-inline .error-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #D32F2F;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.qr-error-inline .error-desc {
|
||||
font-size: 24rpx;
|
||||
color: #F44336;
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.retry-button-inline {
|
||||
padding: 12rpx 32rpx;
|
||||
height: auto;
|
||||
line-height: 1.4;
|
||||
border-radius: 20rpx;
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 107, 107, 0.3);
|
||||
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.retry-button-inline::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.retry-button-inline:active {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 2rpx 6rpx rgba(255, 107, 107, 0.25);
|
||||
}
|
||||
|
||||
@keyframes scaleIn {
|
||||
from {
|
||||
transform: scale(0);
|
||||
|
||||
Reference in New Issue
Block a user