接入移动端原生扫码能力
新增 scanner.scanQrCode HostBridge 契约与二维码结果归一。 接入 Expo Camera 扫码 overlay 并保留 H5 浏览器扫码回退。 让 Tauri 白名单识别扫码 method 但不声明桌面扫码能力。 同步原生壳门禁、Expo 权限检查、方案文档和共享决策记录。
This commit is contained in:
@@ -3175,6 +3175,40 @@ test('profile scan action opens camera scanner instead of recharge panel', async
|
||||
expect(stopTrack).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('profile scan action uses native QR scanner before browser camera', async () => {
|
||||
const user = userEvent.setup();
|
||||
const getUserMedia = vi.fn();
|
||||
const scanHostQrCodeSpy = vi
|
||||
.spyOn(hostBridgeServices, 'scanHostQrCode')
|
||||
.mockResolvedValue({
|
||||
value: 'https://app.genarrative.world/works/detail?work=PZ-1',
|
||||
format: 'qr_code',
|
||||
});
|
||||
|
||||
mockNarrowMobileLayout();
|
||||
Object.defineProperty(navigator, 'mediaDevices', {
|
||||
configurable: true,
|
||||
value: { getUserMedia },
|
||||
});
|
||||
|
||||
renderProfileView();
|
||||
const topbar = document.querySelector('.platform-mobile-topbar');
|
||||
expect(topbar).toBeTruthy();
|
||||
|
||||
await user.click(
|
||||
within(topbar as HTMLElement).getByRole('button', { name: '扫码' }),
|
||||
);
|
||||
|
||||
const qrScannerDialog = await screen.findByRole('dialog', { name: '扫码' });
|
||||
|
||||
expect(scanHostQrCodeSpy).toHaveBeenCalledTimes(1);
|
||||
expect(qrScannerDialog.textContent).toContain(
|
||||
'已识别:https://app.genarrative.world/works/detail?work=PZ-1',
|
||||
);
|
||||
expect(getUserMedia).not.toHaveBeenCalled();
|
||||
expect(mockGetRpgProfileRechargeCenter).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('desktop account entry uses saved avatar image when available', async () => {
|
||||
mockDesktopLayout();
|
||||
const avatarUrl = 'data:image/png;base64,AAAA';
|
||||
|
||||
@@ -73,6 +73,7 @@ import {
|
||||
canUseNativeHostCapability,
|
||||
type HostFileImportImageResult,
|
||||
importHostImageFile,
|
||||
scanHostQrCode,
|
||||
} from '../../services/host-bridge/hostBridge';
|
||||
import { shouldShowRechargeEntry } from '../../services/payment/paymentPlatform';
|
||||
import type { CustomWorldProfile } from '../../types';
|
||||
@@ -2595,6 +2596,9 @@ export function RpgEntryHomeView({
|
||||
const [activeWorkSearchKeyword, setActiveWorkSearchKeyword] = useState('');
|
||||
const [isQrScannerOpen, setIsQrScannerOpen] = useState(false);
|
||||
const [qrScannerError, setQrScannerError] = useState<string | null>(null);
|
||||
const [qrScannerMode, setQrScannerMode] = useState<'camera' | 'resultOnly'>(
|
||||
'camera',
|
||||
);
|
||||
const [qrScannerResult, setQrScannerResult] = useState<string | null>(null);
|
||||
const [selectedCategoryTag, setSelectedCategoryTag] = useState<string | null>(
|
||||
null,
|
||||
@@ -3160,7 +3164,32 @@ export function RpgEntryHomeView({
|
||||
|
||||
setQrScannerError(null);
|
||||
setQrScannerResult(null);
|
||||
setIsQrScannerOpen(true);
|
||||
setQrScannerMode('camera');
|
||||
|
||||
void scanHostQrCode()
|
||||
.then((result) => {
|
||||
if (result === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
setQrScannerMode('resultOnly');
|
||||
setQrScannerResult(result.value);
|
||||
setIsQrScannerOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setQrScannerMode('camera');
|
||||
setIsQrScannerOpen(true);
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
setQrScannerMode('resultOnly');
|
||||
setQrScannerResult(null);
|
||||
setQrScannerError(
|
||||
error instanceof Error ? error.message : '扫码失败,请稍后重试',
|
||||
);
|
||||
setIsQrScannerOpen(true);
|
||||
});
|
||||
};
|
||||
const clearWorkSearch = () => {
|
||||
setActiveWorkSearchKeyword('');
|
||||
@@ -4873,9 +4902,11 @@ export function RpgEntryHomeView({
|
||||
const qrScannerModal: ReactNode = isQrScannerOpen ? (
|
||||
<PlatformProfileQrScannerModal
|
||||
error={qrScannerError}
|
||||
mode={qrScannerMode}
|
||||
result={qrScannerResult}
|
||||
onClose={() => {
|
||||
setIsQrScannerOpen(false);
|
||||
setQrScannerMode('camera');
|
||||
setQrScannerError(null);
|
||||
setQrScannerResult(null);
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user