This commit is contained in:
sjk
2026-01-09 23:27:52 +08:00
parent 8446c004e7
commit 3b66018271
18 changed files with 2006 additions and 508 deletions

View File

@@ -254,11 +254,38 @@ Page({
return;
}
if (!this.data.canLogin) {
const { phone, code, password, loginType, countdown } = this.data;
// 检查手机号
if (phone.length !== 11) {
wx.showToast({
title: '请输入正确的手机号',
icon: 'none',
duration: 2000
});
return;
}
const { phone, code, password, loginType } = this.data;
// 检查验证码或密码
if (loginType === 'code') {
if (!code || code.length < 4) {
wx.showToast({
title: '请输入验证码',
icon: 'none',
duration: 2000
});
return;
}
} else {
if (!password || password.length < 6) {
wx.showToast({
title: '请输入密码至少6位',
icon: 'none',
duration: 2000
});
return;
}
}
// 显示加载提示
wx.showLoading({

View File

@@ -17,6 +17,7 @@ Page({
countryCodeIndex: 0,
pollTimer: null as any, // 轮询定时器
pollCount: 0, // 轮询次数
socketTask: null as any, // WebSocket连接
// 验证码相关
needCaptcha: false, // 是否需要验证码
captchaType: '', // 验证码类型
@@ -38,6 +39,15 @@ Page({
onLoad() {
console.log('小红书绑定页面加载');
// 页面加载时就生成session_id并建立WebSocket连接
const sessionId = `xhs_login_${Date.now().toString(36)}${Math.random().toString(36).substr(2, 9)}`;
console.log('[页面加载] 生成session_id:', sessionId);
this.setData({ sessionId });
// 建立WebSocket连接
console.log('[页面加载] 建立WebSocket连接...');
this.connectWebSocket(sessionId);
},
onUnload() {
@@ -48,6 +58,10 @@ Page({
if (this.data.pollTimer) {
clearInterval(this.data.pollTimer);
}
// 关闭WebSocket连接
if (this.data.socketTask) {
this.data.socketTask.close();
}
},
// 区号选择
@@ -77,7 +91,7 @@ Page({
return;
}
const { phone, countryCodes, countryCodeIndex } = this.data;
const { phone, countryCodes, countryCodeIndex, sessionId, socketTask } = this.data;
if (phone.length !== 11) {
wx.showToast({
title: '请输入正确的手机号',
@@ -87,43 +101,52 @@ Page({
return;
}
// 显示加载
this.setData({
showLoading: true
});
try {
// 调用后端API发送验证码
const countryCode = countryCodes[countryCodeIndex];
console.log('发送验证码:', phone, '区号:', countryCode);
console.log('[发送验证码] 开始,手机号:', phone, '区号:', countryCode);
console.log('[发送验证码] 使用现有session_id:', sessionId);
const result = await EmployeeService.sendXHSCode(phone);
// 保存session_id用于后续复用浏览器
if (result.data && result.data.session_id) {
this.setData({
sessionId: result.data.session_id
});
console.log('已保存session_id:', result.data.session_id);
// 检查WebSocket连接
if (!socketTask) {
console.error('[发送验证码] WebSocket连接不存在重新建立...');
this.connectWebSocket(sessionId);
await new Promise(resolve => setTimeout(resolve, 500));
}
this.setData({
showLoading: false
// 通过WebSocket发送send_code消息
console.log('[发送验证码] 通过WebSocket发送请求...');
const currentSocketTask = this.data.socketTask;
if (!currentSocketTask) {
throw new Error('WebSocket连接未建立');
}
currentSocketTask.send({
data: JSON.stringify({
type: 'send_code',
phone: phone,
country_code: countryCode,
login_page: 'home' // 使用小红书首页登录
}),
success: () => {
console.log('[发送验证码] WebSocket消息发送成功');
wx.showToast({
title: '正在发送验证码...',
icon: 'loading',
duration: 20000
});
},
fail: (err: any) => {
console.error('[发送验证码] WebSocket消息发送失败:', err);
wx.showToast({
title: '发送失败,请重试',
icon: 'none',
duration: 2000
});
}
});
wx.showToast({
title: '验证码已发送请在小红书APP中查看',
icon: 'none',
duration: 2000
});
// 开始倒计时
this.startCountdown();
} catch (error: any) {
this.setData({
showLoading: false
});
console.error('[发送验证码] 异常:', error);
wx.showToast({
title: error.message || '发送失败,请重试',
icon: 'none',
@@ -164,7 +187,12 @@ Page({
// 绑定账号
async bindAccount() {
const { phone, code, sessionId } = this.data;
const { phone, code, sessionId, countryCodes, countryCodeIndex } = this.data;
console.log('[绑定账号] 开始绑定');
console.log('[绑定账号] phone:', phone);
console.log('[绑定账号] code:', code);
console.log('[绑定账号] sessionId:', sessionId);
// 验证手机号
if (!phone || phone.length !== 11) {
@@ -196,42 +224,50 @@ Page({
return;
}
// 显示加载
this.setData({
showLoading: true,
loadingText: '正在验证...',
pollCount: 0
});
try {
// 调用后端API进行绑定异步处理
console.log('绑定小红书账号:', { phone, code, sessionId });
console.log('[绑定账号] 通过WebSocket发送验证码验证请求...');
const result = await EmployeeService.bindXHS(phone, code, sessionId);
const socketTask = this.data.socketTask;
if (!socketTask) {
throw new Error('WebSocket连接已断开请重新发送验证码');
}
// 后端立即返回,开始轮询绑定状态
this.setData({
loadingText: '正在登录小红书...'
const countryCode = countryCodes[countryCodeIndex];
wx.showToast({
title: '正在验证...',
icon: 'loading',
duration: 60000
});
// 开始轮询绑定状态
this.startPollingBindStatus();
socketTask.send({
data: JSON.stringify({
type: 'verify_code',
phone: phone,
code: code,
country_code: countryCode,
login_page: 'home' // 使用小红书首页登录
}),
success: () => {
console.log('[绑定账号] WebSocket消息发送成功等待后端响应...');
},
fail: (err: any) => {
console.error('[绑定账号] WebSocket消息发送失败:', err);
wx.showToast({
title: '发送失败,请重试',
icon: 'none',
duration: 2000
});
}
});
} catch (error: any) {
this.setData({
showLoading: false
console.error('[绑定账号] 异常:', error);
wx.showToast({
title: error.message || '绑定失败,请重试',
icon: 'none',
duration: 2000
});
// 绑定失败
this.setData({
showFail: true
});
setTimeout(() => {
this.setData({
showFail: false
});
}, 2000);
}
},
@@ -242,6 +278,11 @@ Page({
clearInterval(this.data.pollTimer);
}
// 初始化轮询计数
this.setData({
pollCount: 0
});
// 立即查询一次
this.checkBindStatus();
@@ -264,17 +305,12 @@ Page({
// 超过60次轮询60秒停止轮询
if (pollCount > 60) {
this.stopPolling();
this.setData({
showLoading: false,
showFail: true
wx.hideToast();
wx.showToast({
title: '登录超时,请重试',
icon: 'none',
duration: 2000
});
setTimeout(() => {
this.setData({
showFail: false
});
}, 2000);
return;
}
@@ -293,10 +329,12 @@ Page({
if (status.status === 'success') {
// 绑定成功
this.stopPolling();
wx.hideToast();
this.setData({
showLoading: false,
showSuccess: true
wx.showToast({
title: '绑定成功',
icon: 'success',
duration: 2000
});
// 更新本地绑定状态缓存
@@ -312,10 +350,6 @@ Page({
console.log('[手机号登录] 绑定成功,已更新本地缓存');
setTimeout(() => {
this.setData({
showSuccess: false
});
// 绑定成功后返回个人中心
wx.navigateBack();
}, 2000);
@@ -323,43 +357,36 @@ Page({
} else if (status.status === 'failed') {
// 绑定失败
this.stopPolling();
wx.hideToast();
this.setData({
showLoading: false,
showFail: true
});
wx.showToast({
title: status.error || '绑定失败',
icon: 'none',
duration: 3000
});
setTimeout(() => {
this.setData({
showFail: false
});
}, 2000);
} else if (status.status === 'need_captcha') {
// 需要验证码验证
this.stopPolling();
wx.hideToast();
console.log('需要验证码验证:', status.captcha_type);
wx.showToast({
title: status.message || '需要验证码验证',
icon: 'none',
duration: 3000
});
this.setData({
showLoading: false,
needCaptcha: true,
captchaType: status.captcha_type || 'unknown',
qrcodeImage: status.qrcode_image || '',
loadingText: status.message || '需要验证码验证'
qrcodeImage: status.qrcode_image || ''
});
} else if (status.status === 'processing') {
// 仍在处理中,继续轮询
this.setData({
loadingText: status.message || '正在登录小红书...'
});
console.log('登录处理中:', status.message);
}
} catch (error: any) {
@@ -380,9 +407,22 @@ Page({
// 切换到手机号登录
switchToPhone() {
console.log('[切换登录] 切换到手机号登录');
// 停止二维码轮询
this.stopQRCodePolling();
// 关闭二维码弹窗(如果正在显示)
if (this.data.needCaptcha) {
console.log('[切换登录] 检测到风控弹窗,关闭...');
this.setData({
needCaptcha: false
});
// 关闭WebSocket连接
this.closeWebSocket();
}
// 清空所有扫码登录相关数据
this.setData({
loginType: 'phone',
@@ -401,6 +441,8 @@ Page({
countdown: 0,
isCounting: false
});
console.log('[切换登录] 切换完成');
},
// 切换到扫码登录
@@ -848,5 +890,536 @@ Page({
this.stopQRCodePolling();
this.switchToPhone();
}
},
// 建立WebSocket连接
connectWebSocket(sessionId: string) {
// 关闭旧连接
if (this.data.socketTask) {
try {
this.data.socketTask.close();
} catch (e) {
console.log('[WebSocket] 关闭旧连接失败(可能已关闭):', e);
}
}
// 获取Python服务地址WebSocket端点在Python后端
const pythonURL = API.pythonURL || API.baseURL;
// 将http/https转为ws/wss
const wsURL = pythonURL.replace('http://', 'ws://').replace('https://', 'wss://');
const url = `${wsURL}/ws/login/${sessionId}`;
console.log('[WebSocket] 开始连接:', url);
console.log('[WebSocket] Python服务地址:', pythonURL);
// 小程序环境检查
if (url.includes('localhost') || url.includes('127.0.0.1')) {
console.warn('[WebSocket] 检测到localhost地址请确保开发工具中已勾选"不校验合法域名"');
console.warn('[WebSocket] 另外需要安装websockets库: pip install websockets');
}
let socketConnected = false; // 标记连接是否成功建立
const socketTask = wx.connectSocket({
url: url,
success: () => {
console.log('[WebSocket] 连接请求发送成功');
},
fail: (err) => {
console.error('[WebSocket] 连接请求失败:', err);
socketConnected = false;
// 检查是否是localhost问题
if (url.includes('localhost') || url.includes('127.0.0.1')) {
wx.showToast({
title: '请在开发工具中勾选"不校验合法域名"并确保Python服务已安装websockets库',
icon: 'none',
duration: 5000
});
}
}
});
// 监听连接打开
socketTask.onOpen(() => {
console.log('[WebSocket] 连接已打开');
socketConnected = true;
// 重置重连计数
this.setData({ reconnectCount: 0 } as any);
// 启动ping/pong保持连接
const pingTimer = setInterval(() => {
if (socketTask && socketConnected) {
socketTask.send({
data: 'ping',
success: () => console.log('[WebSocket] Ping发送成功'),
fail: (err) => console.error('[WebSocket] Ping发送失败:', err)
});
}
}, 30000); // 每30秒ping一次
this.setData({ pingTimer } as any);
});
// 监听消息
socketTask.onMessage((res) => {
console.log('[WebSocket] 收到消息:', res.data);
// 过滤pong消息服务端的心跳响应
if (res.data === 'pong') {
console.log('[WebSocket] 收到pong响应');
return;
}
try {
const data = JSON.parse(res.data as string);
// 处理扫码成功消息(发送验证码阶段的风控)
if (data.type === 'qrcode_scan_success') {
console.log('✅ 扫码验证完成!', data.message);
// 关闭验证码弹窗
this.setData({
needCaptcha: false
});
// 显示提示:扫码成功,请重新发送验证码
wx.showToast({
title: data.message || '扫码成功,请重新发送验证码',
icon: 'success',
duration: 3000
});
console.log('[WebSocket] 扫码验证完成,提示用户重新发送验证码');
console.log('[WebSocket] 保持连接,等待后续操作');
// 不关闭WebSocket保持连接用于后续登录流程
}
// 处理二维码失效消息
else if (data.type === 'qrcode_expired') {
console.log('⚠️ 二维码已失效!', data.message);
// 关闭验证码弹窗
this.setData({
needCaptcha: false
});
// 显示提示:二维码已失效
wx.showToast({
title: data.message || '二维码已失效,请重新发送验证码',
icon: 'none',
duration: 3000
});
console.log('[WebSocket] 二维码已失效,关闭弹窗');
console.log('[WebSocket] 保持连接,等待用户重新操作');
// 不关闭WebSocket保持连接用于重新发送验证码
}
// 处理登录成功消息(点击登录按钮阶段的风控)
else if (data.type === 'login_success') {
// 判断是扫码验证成功还是真正的登录成功
if (data.storage_state) {
// 真正的登录成功,包含 storage_state
console.log('✅ 登录成功!', data);
wx.hideToast();
// 关闭验证码弹窗
this.setData({
needCaptcha: false
});
// 关闭WebSocket
this.closeWebSocket();
// 显示绑定成功动画
this.setData({
showSuccess: true
});
setTimeout(() => {
this.setData({
showSuccess: false
});
// 跳转回上一页
wx.navigateBack({
delta: 1,
success: () => {
console.log('✅ 跳转回上一页');
},
fail: (err) => {
console.error('⚠️ 跳转失败:', err);
}
});
}, 2000);
} else {
// 扫码验证成功,但还需要继续登录
console.log('✅ 扫码验证成功!', data.user_info);
// 关闭验证码弹窗
this.setData({
needCaptcha: false
});
// 显示提示:扫码成功,请重新发送验证码
wx.showToast({
title: '扫码成功,请重新发送验证码',
icon: 'success',
duration: 3000
});
console.log('[WebSocket] 扫码验证完成,提示用户重新发送验证码');
console.log('[WebSocket] 保持连接,等待后续登录操作');
// 不关闭WebSocket保持连接用于后续登录流程
}
}
// 处理need_captcha消息发送验证码或登录时触发风控
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] 已显示风控二维码');
}
// 处理code_sent消息验证码发送结果
else if (data.type === 'code_sent') {
console.log('[WebSocket] 验证码发送结果:', data);
wx.hideToast();
if (data.success) {
wx.showToast({
title: data.message || '验证码已发送',
icon: 'success',
duration: 2000
});
// 开始倒计时
this.startCountdown();
} else {
wx.showToast({
title: data.message || '发送失败',
icon: 'none',
duration: 2000
});
}
}
// 处理login_result消息登录结果
else if (data.type === 'login_result') {
console.log('[WebSocket] 登录结果:', data);
wx.hideToast();
if (!data.success) {
wx.showToast({
title: data.message || '登录失败',
icon: 'none',
duration: 2000
});
}
}
} catch (e) {
console.error('[WebSocket] 解析消息失败:', e, '原始数据:', res.data);
}
});
// 监听错误
socketTask.onError((err) => {
console.error('[WebSocket] 连接错误:', err);
socketConnected = false;
// 记录错误,准备重连
console.log('[WebSocket] 将在关闭后尝试重连');
});
// 监听关闭
socketTask.onClose(() => {
console.log('[WebSocket] 连接关闭');
socketConnected = false;
// 清理ping定时器
if ((this.data as any).pingTimer) {
clearInterval((this.data as any).pingTimer);
}
// 检查是否需要重连(只有弹窗还在时才重连)
if (this.data.needCaptcha && sessionId) {
// 增加重连计数
const reconnectCount = (this.data as any).reconnectCount || 0;
// 最多重连3次
if (reconnectCount < 3) {
const delay = Math.min(1000 * Math.pow(2, reconnectCount), 5000); // 指数退避: 1s, 2s, 4s
console.log(`[WebSocket] 将在${delay}ms后进行第${reconnectCount + 1}次重连`);
setTimeout(() => {
if (this.data.needCaptcha) { // 再次检查弹窗是否还在
console.log('[WebSocket] 开始重连...');
this.setData({ reconnectCount: reconnectCount + 1 } as any);
this.connectWebSocket(sessionId);
}
}, delay);
} else {
console.log('[WebSocket] 已达到最大重连次数,停止重连');
wx.showToast({
title: 'WebSocket连接失败请重新发送验证码',
icon: 'none',
duration: 3000
});
}
}
});
this.setData({ socketTask });
},
// 关闭WebSocket连接
closeWebSocket() {
console.log('[WebSocket] 开始关闭连接');
// 重置重连计数(主动关闭不需要重连)
this.setData({ reconnectCount: 999 } as any); // 设置为很大的数阻止重连
// 清理ping定时器
if ((this.data as any).pingTimer) {
clearInterval((this.data as any).pingTimer);
console.log('[WebSocket] 已清理ping定时器');
}
// 关闭WebSocket连接
const socketTask = this.data.socketTask;
if (socketTask) {
try {
// 检查WebSocket连接状态
// readyState: 0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED
const readyState = (socketTask as any).readyState;
if (readyState === 0 || readyState === 1) {
// 只有连接中或已打开的连接才关闭
console.log(`[WebSocket] 连接状态: ${readyState}, 执行关闭`);
socketTask.close({
success: () => {
console.log('[WebSocket] 连接关闭成功');
},
fail: (err: any) => {
// 忽略关闭失败错误,可能已经关闭
console.log('[WebSocket] 连接关闭失败(忽略):', err.errMsg);
}
});
} else {
console.log(`[WebSocket] 连接已关闭或正在关闭, readyState: ${readyState}`);
}
// 清空socketTask引用
this.setData({ socketTask: null });
} catch (e) {
console.log('[WebSocket] 关闭连接异常(忽略):', e);
// 仍然清空socketTask引用
this.setData({ socketTask: null });
}
} else {
console.log('[WebSocket] 没有活跃的WebSocket连接');
}
},
// 保存二维码
saveQRCode() {
if (!this.data.qrcodeImage) {
wx.showToast({
title: '二维码不存在',
icon: 'none',
duration: 2000
});
return;
}
console.log('[保存二维码] 开始保存');
// base64转为临时文件
const base64Data = this.data.qrcodeImage.replace(/^data:image\/\w+;base64,/, '');
const filePath = `${wx.env.USER_DATA_PATH}/qrcode_${Date.now()}.png`;
// 写入临时文件
const fs = wx.getFileSystemManager();
fs.writeFile({
filePath: filePath,
data: base64Data,
encoding: 'base64',
success: () => {
console.log('[保存二维码] 临时文件写入成功:', filePath);
// 保存到相册
wx.saveImageToPhotosAlbum({
filePath: filePath,
success: () => {
console.log('[保存二维码] 保存成功');
wx.showToast({
title: '二维码已保存到相册',
icon: 'success',
duration: 2000
});
},
fail: (err) => {
console.error('[保存二维码] 保存到相册失败:', err);
// 检查是否是权限问题
if (err.errMsg.includes('auth')) {
wx.showModal({
title: '需要授权',
content: '请允许保存图片到相册',
success: (res) => {
if (res.confirm) {
wx.openSetting({
success: (settingRes) => {
if (settingRes.authSetting['scope.writePhotosAlbum']) {
// 获得授权后重试
this.saveQRCode();
}
}
});
}
}
});
} else {
wx.showToast({
title: '保存失败,请重试',
icon: 'none',
duration: 2000
});
}
}
});
},
fail: (err) => {
console.error('[保存二维码] 临时文件写入失败:', err);
wx.showToast({
title: '保存失败,请重试',
icon: 'none',
duration: 2000
});
}
});
},
// 测试WebSocket连接
testWebSocket() {
console.log('[测试] 开始测试WebSocket...');
// 生成测试session_id
const testSessionId = `test_${Date.now()}`;
console.log('[测试] 测试session_id:', testSessionId);
// 关闭旧连接
if (this.data.socketTask) {
try {
this.data.socketTask.close();
} catch (e) {
console.log('[测试] 关闭旧连接失败:', e);
}
}
// 获取WebSocket地址
const pythonURL = API.pythonURL || API.baseURL;
const wsURL = pythonURL.replace('http://', 'ws://').replace('https://', 'wss://');
const url = `${wsURL}/ws/login/${testSessionId}`;
console.log('[测试] WebSocket地址:', url);
const socketTask = wx.connectSocket({
url: url,
success: () => {
console.log('[测试] 连接请求成功');
},
fail: (err) => {
console.error('[测试] 连接请求失败:', err);
wx.showToast({
title: '连接失败',
icon: 'none'
});
}
});
// 监听连接打开
socketTask.onOpen(() => {
console.log('[测试] 连接已打开');
wx.showToast({
title: 'WebSocket连接成功',
icon: 'success'
});
// 3秒后发送测试消息
setTimeout(() => {
console.log('[测试] 发送测试消息...');
socketTask.send({
data: JSON.stringify({
type: 'test',
message: 'This is a test message from frontend'
}),
success: () => {
console.log('[测试] 测试消息发送成功');
},
fail: (err: any) => {
console.error('[测试] 测试消息发送失败:', err);
}
});
}, 3000);
});
// 监听消息
socketTask.onMessage((res) => {
console.log('[测试] 收到消息:', res.data);
// 过滤pong消息
if (res.data === 'pong') {
console.log('[测试] 收到pong响应');
return;
}
try {
const data = JSON.parse(res.data as string);
console.log('[测试] 解析后的消息:', data);
// 显示消息
wx.showModal({
title: '收到WebSocket消息',
content: `类型: ${data.type}\n内容: ${data.message || JSON.stringify(data)}`,
showCancel: false
});
} catch (e) {
console.error('[测试] 解析消息失败:', e);
}
});
// 监听错误
socketTask.onError((err) => {
console.error('[测试] 连接错误:', err);
wx.showToast({
title: '连接错误',
icon: 'none'
});
});
// 监听关闭
socketTask.onClose(() => {
console.log('[测试] 连接关闭');
});
this.setData({ socketTask });
}
});

View File

@@ -1,5 +1,10 @@
<!--pages/profile/platform-bind/platform-bind.wxml-->
<view class="container">
<!-- WebSocket测试按钮 -->
<view style="position: fixed; top: 20rpx; right: 20rpx; z-index: 9999;">
<button style="padding: 10rpx 20rpx; font-size: 24rpx; background: #4CAF50; color: white; border-radius: 8rpx;" bindtap="testWebSocket">测试WebSocket</button>
</view>
<!-- 绑定内容 -->
<view class="bind-content">
<!-- 小红书Logo -->
@@ -69,11 +74,18 @@
<!-- 手机号登录区域 -->
<view class="phone-login-section" wx:if="{{loginType === 'phone'}}">
<!-- 加载中 -->
<view class="inline-loading" wx:if="{{showLoading}}">
<view class="loading-spinner"></view>
<text class="loading-text">{{loadingText}}</text>
</view>
<!-- 二维码验证区域(验证码验证时出现) -->
<view class="qrcode-section" wx:if="{{needCaptcha && qrcodeImage}}">
<text class="qrcode-title">请使用小红书APP扫描二维码</text>
<image class="qrcode" src="{{qrcodeImage}}" mode="aspectFit"></image>
<text class="qrcode-hint">扫码后即可继续绑定流程</text>
<button class="save-qrcode-btn" bindtap="saveQRCode">保存二维码</button>
</view>
<view class="bind-form" wx:if="{{!needCaptcha}}">
@@ -127,13 +139,6 @@
</view>
</view>
<view class="toast-overlay" wx:if="{{showLoading && loginType === 'phone'}}">
<view class="toast">
<view class="toast-loading"></view>
<text class="toast-text">{{loadingText}}</text>
</view>
</view>
<view class="toast-overlay" wx:if="{{showFail}}">
<view class="toast">
<view class="toast-icon info">

View File

@@ -248,6 +248,33 @@ page {
color: #999;
text-align: center;
line-height: 1.6;
margin-bottom: 24rpx;
}
/* 保存二维码按钮 */
.save-qrcode-btn {
width: 100%;
height: 80rpx;
background: linear-gradient(135deg, #FF2442 0%, #FF4F6A 100%);
border-radius: 12rpx;
font-size: 30rpx;
font-weight: 500;
color: #fff;
border: none;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 16rpx rgba(255, 36, 66, 0.3);
transition: all 0.3s;
}
.save-qrcode-btn::after {
border: none;
}
.save-qrcode-btn:active {
opacity: 0.8;
transform: scale(0.98);
}
.bind-form {
@@ -465,6 +492,17 @@ page {
color: #666;
}
/* 内联加载提示(手机号登录区域) */
.inline-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 24rpx;
padding: 80rpx 0;
min-height: 300rpx;
}
/* 二维码错误提示 */
.qrcode-error {
position: absolute;

View File

@@ -74,9 +74,10 @@ export class EmployeeService {
* 发送小红书验证码
* 返回 session_id 用于后续复用浏览器
*/
static async sendXHSCode(xhsPhone: string, showLoading = true) {
static async sendXHSCode(xhsPhone: string, showLoading = true, sessionId?: string) {
return post<{ sent_at: string; session_id: string }>(API.xhs.sendCode, {
xhs_phone: xhsPhone
xhs_phone: xhsPhone,
session_id: sessionId // 传递session_id给后端
}, showLoading);
}