fix(scancode): 小程序扫码登录修复黑屏并统一为微信扫码→授权页

- scan-login: 去 useLoad/useDidShow/getEnterOptionsSync 元钩子(原生计时器调度React hook致黑屏),改纯React useEffect+getCurrentInstance 读scene;window.setInterval→setInterval
- mine: 移除内部 Taro.scanCode 跨端口扫码登录入口/按钮/状态,改为引导卡片,统一「微信扫小程序码→进小程序→授权页确认」

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Pine
2026-08-26 19:03:27 +08:00
parent 169e880d37
commit 55c6a026df
2 changed files with 21 additions and 36 deletions
+4 -25
View File
@@ -2,7 +2,7 @@ import { View, Text, Button, Input, Image } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { useState, useEffect } from 'react';
import { useTabIndex } from '@/utils/tab';
import { isAuthed, getUser, logout, wxLogin, phoneLogin, bindPhone, sendCode, wxPhoneBind, updateProfile, getMe, mpQrConfirm } from '@/utils/auth';
import { isAuthed, getUser, logout, wxLogin, phoneLogin, bindPhone, sendCode, wxPhoneBind, updateProfile, getMe } from '@/utils/auth';
import { getMyBookings, checkIn, parseIso, BK_STATUS_OPTIONS, BK_TOPIC_OPTIONS, BK_SOURCE_OPTIONS } from '@/utils/booking';
import Skeleton from '@/components/skeleton';
import CountdownBox from '@/components/CountdownBox';
@@ -200,8 +200,6 @@ export default function Mine() {
const [pTopics, setPTopics] = useState((user && user.topics) || []);
const [pSource, setPSource] = useState((user && user.source) || '');
const [saving, setSaving] = useState(false);
const [scanBusy, setScanBusy] = useState(false);
const [scanErr, setScanErr] = useState('');
useEffect(() => {
setPName((user && user.name) || '');
setPStatus((user && user.status) || '');
@@ -264,23 +262,6 @@ export default function Mine() {
Taro.showToast({ title: '已退出登录', icon: 'none' });
};
/* ============ 跨端口扫码登录:扫电脑/网页登录页二维码 → 小程序确认 ============ */
const onScanLogin = async () => {
setScanErr(''); setScanBusy(true);
try {
const res = await Taro.scanCode(); // 扫其它端口登录页的「小程序扫码」二维码
const str = res.result || '';
const m = str.match(/[?&]scene=([^&#]+)/); // 二维码内容=跨端口 login url,含 scene
if (!m) throw new Error('无效的扫码登录二维码');
const scene = decodeURIComponent(m[1]);
const { code } = await Taro.login(); // 微信登录 code
await mpQrConfirm(scene, code); // 确认 → 目标端口轮询取到登录令牌
Taro.showToast({ title: '已登录目标端口,请回电脑/网页查看', icon: 'none' });
} catch (e) {
setScanErr((e && e.message) || '扫码登录失败');
} finally { setScanBusy(false); }
};
/* ============ 未登录:登录入口 ============ */
if (!user || !isAuthed()) {
return (
@@ -354,12 +335,10 @@ export default function Mine() {
<Button className="btn-ghost logout-btn" onClick={onLogout}>退出</Button>
</View>
{/* 跨端口扫码登录:电脑/网页登录页「小程序码」二维码 → 用本小程序确认登录该端口 */}
{/* 跨端口扫码登录:电脑/网页登录页展示「小程序码」,微信扫码自动进入小程序授权页确认 */}
<View className="card next-card">
<Text className="next-lbl">跨端口登录</Text>
<Text className="bind-tip">在电脑/网页登录页点小程序扫码本小程序扫一扫即可登录该端口</Text>
<Button className="btn-main" loading={scanBusy} onClick={onScanLogin}>扫码登录其它端口</Button>
{scanErr && <Text className="scan-err">{scanErr}</Text>}
<Text className="next-lbl">登录其它端</Text>
<Text className="bind-tip">在电脑/网页登录页点小程序扫码微信扫一扫进入小程序授权页确认即可登录</Text>
</View>
{/* 下一场活动倒计时(个人信息卡之后显示;取最早的待参加且未开始的报名场次) */}
+17 -11
View File
@@ -1,6 +1,6 @@
import { View, Text, Button, Input } from '@tarojs/components';
import Taro, { useDidShow } from '@tarojs/taro';
import { useState } from 'react';
import Taro from '@tarojs/taro';
import { useState, useEffect } from 'react';
import { isAuthed, wxLogin, phoneLogin, sendCode, mpQrConfirm } from '@/utils/auth';
import './index.scss';
@@ -20,13 +20,18 @@ export default function ScanLogin() {
const [gateCode, setGateCode] = useState('');
const [cd, setCd] = useState(0);
useDidShow(() => {
const opts = Taro.getEnterOptionsSync();
const q = (opts && opts.query) || {};
const s = String(q.scene || opts.scene || '');
setScene(s);
setDone(false); setMsg('');
});
// 冷启动/扫码进入:纯 React useEffect 读当前实例参数(wxacode 的 scene 在 router.params.scene)。
// 不再用 useLoad/useDidShow 等 Taro 元钩子(其内部在原生计时器里调度 React hook,会触发
// "Invalid attempt to destruct non-iterable" → 页面黑屏)。
useEffect(() => {
try {
const inst = Taro.getCurrentInstance();
const router = (inst && inst.router) || {};
const params = (router && (router.params || router.query)) || {};
const s = String(params.scene || router.scene || '');
if (s) setScene(s);
} catch { /* 忽略取参异常 */ }
}, []);
const wxLoginNow = async () => {
setBusy(true); setMsg('');
@@ -47,7 +52,8 @@ export default function ScanLogin() {
const r = await sendCode(gatePhone);
setMsg(`验证码已发送${r.debugCode ? `(演示 ${r.debugCode}` : ''}`);
setCd(60);
const t = window.setInterval(() => setCd((c) => (c <= 1 ? (window.clearInterval(t), 0) : c - 1)), 1000);
// 小程序无 window,用全局 setInterval/clearInterval(源码里 window.* 会导致运行时 ReferenceError
const t = setInterval(() => setCd((c) => (c <= 1 ? (clearInterval(t), 0) : c - 1)), 1000);
} catch (e) { setMsg((e && e.message) || '发送失败'); }
finally { setBusy(false); }
};
@@ -106,7 +112,7 @@ export default function ScanLogin() {
<View className="sl-icon">{done ? '✓' : '✅'}</View>
<Text className="sl-title">{done ? '已完成' : '确认扫码操作'}</Text>
<Text className="sl-desc">
{done ? msg : `确认使用当前微信账号${scene.includes('bind') || true ? '' : ''}在你操作的端口${scene ? '' : ' '}完成登录 / 绑定?`}
{done ? msg : `确认使用当前微信账号在你操作的端口完成登录 / 绑定?`}
</Text>
{!done && scene && <Text className="sl-scene">请求 #{scene.slice(0, 12)}</Text>}
{!done && (