feat: 添加手机浏览器环境检测与警告功能

- 新增 browser-utils.js 模块,提供 isMobileBrowser 检测和 showMobileWarning 警告功能
- 实现会话级去重机制,避免重复显示手机环境警告通知
- 在 SimyoApp 的登录和设备更换流程中添加手机环境检测逻辑
- 在 GiffgaffApp 的 OAuth 登录流程中添加手机环境检测逻辑
- 添加多语言翻译支持,包括中文和英文版本的环境警告提示文本
This commit is contained in:
Abner
2026-05-19 23:15:55 +08:00
parent 2047d1f294
commit ae7e30b5b1
5 changed files with 105 additions and 7 deletions

View File

@@ -20,6 +20,7 @@ import {
downloadQRCode
} from './modules/utils.js';
import { t, tl } from '../../js/modules/i18n.js';
import { isMobileBrowser, showMobileWarning } from '../../js/modules/browser-utils.js';
import captchaManager from '../../js/modules/captcha-manager.js';
import Logger from '../../js/modules/logger.js';
@@ -428,6 +429,11 @@ class GiffgaffApp {
const { elements } = uiController;
console.log('[Giffgaff] === OAuth 登录开始 ===');
// 手机环境检测非PC环境弹出提醒
if (isMobileBrowser()) {
showMobileWarning();
}
// 检查服务时间
if (!isServiceTimeAvailable()) {
console.log('[Giffgaff] 服务时间外,显示警告');

View File

@@ -0,0 +1,32 @@
/**
* 浏览器环境检测与提示工具
* 提供手机浏览器检测和环境警告功能,支持会话级去重
*/
import { tl } from './i18n.js';
import NotificationManager from './notification-manager.js';
const DEDUP_KEY = 'esim-mobile-warning-shown';
/**
* 检测当前是否为手机浏览器
* @returns {boolean} 是否为手机浏览器
*/
export function isMobileBrowser() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}
/**
* 显示手机环境警告通知
* 每个会话仅展示一次,避免重复点击造成通知堆叠
*/
export function showMobileWarning() {
try {
if (sessionStorage.getItem(DEDUP_KEY)) return;
sessionStorage.setItem(DEDUP_KEY, '1');
} catch (_) {}
NotificationManager.warning(
tl('当前为手机浏览器环境,部分功能可能受限。如遇错误请切换到 PC 环境操作。'),
7000
);
}

View File

@@ -689,6 +689,7 @@ export const TRANSLATIONS = {
};
export const LITERAL_TRANSLATIONS = {
'当前为手机浏览器环境,部分功能可能受限。如遇错误请切换到 PC 环境操作。': 'You are using a mobile browser. Some features may be limited. If you encounter errors, please switch to a PC.',
'点击返回此步骤': 'Click to revisit this step',
'请按顺序完成前序步骤': 'Complete earlier steps in order',
'已选择OAuth 2.0登录方式': 'OAuth 2.0 login selected',

View File

@@ -15,6 +15,7 @@ import {
delay
} from './modules/utils.js';
import { t, tl } from '../../js/modules/i18n.js';
import { isMobileBrowser, showMobileWarning } from '../../js/modules/browser-utils.js';
import Logger from '../../js/modules/logger.js';
class SimyoApp {
@@ -187,6 +188,11 @@ class SimyoApp {
const loginStatus = document.getElementById('loginStatus');
console.log('[Simyo] === 登录开始 ===');
// 手机环境检测非PC环境弹出提醒
if (isMobileBrowser()) {
showMobileWarning();
}
if (!phoneNumberEl || !passwordEl) {
console.error('[Simyo] 登录表单元素未找到!');
return;
@@ -235,6 +241,11 @@ class SimyoApp {
const statusEl = document.getElementById('applyNewEsimStatus');
console.log('[Simyo] === 请求设备更换 ===');
// 手机环境检测非PC环境弹出提醒
if (isMobileBrowser()) {
showMobileWarning();
}
try {
applyBtn.innerHTML = `<span class="loading"></span> ${tl('处理中...')}`;
applyBtn.disabled = true;

View File

@@ -8,6 +8,9 @@ describe('SimyoApp 设备更换主流程自动化', () => {
let mockSkipDeviceChange;
let mockApplyNewEsim;
let mockVerifyCode;
let mockIsMobileBrowser;
let mockShowMobileWarning;
let mockAuthLogin;
beforeEach(async () => {
jest.resetModules();
@@ -18,6 +21,8 @@ describe('SimyoApp 设备更换主流程自动化', () => {
<button id="verifyCodeBtn" disabled></button>
<div id="verifyCodeStatus"></div>
<input id="validationCodeInput" />
<input id="phoneNumber" />
<input id="password" type="password" />
<button id="loginBtn"></button>
<div id="loginStatus"></div>
<button id="getEsimBtn"></button>
@@ -76,13 +81,16 @@ describe('SimyoApp 设备更换主流程自动化', () => {
}
}));
jest.doMock('../../src/simyo/js/modules/auth-handler.js', () => ({
authHandler: {
login: jest.fn(),
isLoggedIn: jest.fn(() => true),
getSessionToken: jest.fn(() => 'token')
}
}));
jest.doMock('../../src/simyo/js/modules/auth-handler.js', () => {
mockAuthLogin = jest.fn();
return {
authHandler: {
login: mockAuthLogin,
isLoggedIn: jest.fn(() => true),
getSessionToken: jest.fn(() => 'token')
}
};
});
jest.doMock('../../src/simyo/js/modules/esim-service.js', () => ({
esimService: {
@@ -111,6 +119,15 @@ describe('SimyoApp 设备更换主流程自动化', () => {
delay: jest.fn(() => Promise.resolve())
}));
jest.doMock('../../src/js/modules/browser-utils.js', () => {
mockIsMobileBrowser = jest.fn(() => false);
mockShowMobileWarning = jest.fn();
return {
isMobileBrowser: mockIsMobileBrowser,
showMobileWarning: mockShowMobileWarning
};
});
jest.doMock('../../src/js/modules/i18n.js', () => ({
t: (key, params = {}) => {
if (!params || Object.keys(params).length === 0) return key;
@@ -161,5 +178,36 @@ describe('SimyoApp 设备更换主流程自动化', () => {
expect(mockVerifyCode).toHaveBeenCalledWith('123456');
expect(mockSkipDeviceChange).toHaveBeenCalledTimes(1);
});
it('手机浏览器登录时应弹出环境警告', async () => {
mockIsMobileBrowser.mockReturnValue(true);
document.getElementById('phoneNumber').value = '0612345678';
document.getElementById('password').value = 'pass';
mockAuthLogin.mockResolvedValue({ success: true, mode: 'standard' });
await app.handleLogin();
expect(mockShowMobileWarning).toHaveBeenCalledTimes(1);
});
it('手机浏览器设备更换时应弹出环境警告', async () => {
mockIsMobileBrowser.mockReturnValue(true);
mockApplyNewEsim.mockResolvedValueOnce({ success: true, message: 'ok' });
await app.handleApplyNewEsim();
expect(mockShowMobileWarning).toHaveBeenCalledTimes(1);
});
it('PC浏览器登录时不应弹出环境警告', async () => {
mockIsMobileBrowser.mockReturnValue(false);
document.getElementById('phoneNumber').value = '0612345678';
document.getElementById('password').value = 'pass';
mockAuthLogin.mockResolvedValue({ success: true, mode: 'standard' });
await app.handleLogin();
expect(mockShowMobileWarning).not.toHaveBeenCalled();
});
});