release(android): close 0.1.17 device candidate
This commit is contained in:
@@ -53,9 +53,48 @@ const DEFAULT_ROUTES = [
|
||||
'pages/candidate/index/index',
|
||||
'pages/supervisor/index/index',
|
||||
];
|
||||
const routes = (argValue('--routes') || DEFAULT_ROUTES.join(',')).split(',').map((s) => s.trim()).filter(Boolean);
|
||||
const AUTH_DEFAULT_ROUTES = [
|
||||
'pages/user/today/index',
|
||||
'pages/user/sop/index',
|
||||
'pages/user/practice/index',
|
||||
'pages/user/direct/index',
|
||||
'pages/user/work-results/index',
|
||||
'pages/candidate/index/index',
|
||||
'pages/supervisor/index/index',
|
||||
];
|
||||
const apiBase = (argValue('--api') || 'http://127.0.0.1:8081').replace(/\/$/, '');
|
||||
const loginPhone = argValue('--phone') || process.env.AIHR_UI_AUDIT_PHONE || '';
|
||||
const loginCode = argValue('--code') || process.env.AIHR_UI_AUDIT_CODE || '';
|
||||
if (loginPhone && !loginCode) {
|
||||
throw new Error('认证态审计需要通过 --code 或 AIHR_UI_AUDIT_CODE 显式提供验证码');
|
||||
}
|
||||
const routes = (argValue('--routes') || (loginPhone ? AUTH_DEFAULT_ROUTES : DEFAULT_ROUTES).join(','))
|
||||
.split(',').map((s) => s.trim()).filter(Boolean);
|
||||
|
||||
// ---------- H5 静态服务(SPA 回退) ----------
|
||||
// ---------- 登录态(凭据只留在进程内,不写入日志、截图或报告) ----------
|
||||
async function loginForToken() {
|
||||
const smsResp = await fetch(`${apiBase}/resource/sms/code?phonenumber=${encodeURIComponent(loginPhone)}`);
|
||||
const smsPayload = await smsResp.json().catch(() => ({}));
|
||||
if (!smsResp.ok || (smsPayload.code !== undefined && smsPayload.code !== 200)) {
|
||||
// 验证码按手机号粒度限频;已下发且在有效期内的固定码仍可登录,输错不作废
|
||||
console.log(`[auth] 验证码请求被限频,沿用已下发验证码重试登录(${smsPayload.msg || smsResp.status})`);
|
||||
}
|
||||
const loginResp = await fetch(`${apiBase}/auth/mobile/sms-login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ phonenumber: loginPhone, smsCode: loginCode }),
|
||||
});
|
||||
const loginPayload = await loginResp.json().catch(() => ({}));
|
||||
const token = loginPayload?.data?.access_token;
|
||||
const clientId = loginPayload?.data?.client_id;
|
||||
if (!loginResp.ok || !token || !clientId) {
|
||||
throw new Error(`登录失败:HTTP ${loginResp.status} ${loginPayload.msg || ''}`);
|
||||
}
|
||||
console.log('[auth] 已完成认证(手机号、验证码、token 与 clientId 均不写入报告)');
|
||||
return { token, clientId };
|
||||
}
|
||||
|
||||
// ---------- H5 静态服务(SPA 回退 + /dev-api 代理) ----------
|
||||
const MIME = {
|
||||
'.html': 'text/html; charset=utf-8', '.js': 'text/javascript', '.css': 'text/css',
|
||||
'.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg', '.webp': 'image/webp',
|
||||
@@ -67,8 +106,31 @@ function serveH5() {
|
||||
if (!existsSync(path.join(h5Dir, 'index.html'))) {
|
||||
throw new Error(`未找到 H5 构建产物 ${h5Dir},请先 npm run build:h5,或用 --url 指向已有服务`);
|
||||
}
|
||||
const server = http.createServer((req, res) => {
|
||||
const server = http.createServer(async (req, res) => {
|
||||
let p = decodeURIComponent(new URL(req.url, 'http://x').pathname);
|
||||
// 与 vite dev 一致的代理行为:/dev-api/* → apiBase/*(去前缀)
|
||||
if (p.startsWith('/dev-api/')) {
|
||||
try {
|
||||
const body = ['GET', 'HEAD'].includes(req.method) ? undefined : await new Promise((resolve) => {
|
||||
const chunks = [];
|
||||
req.on('data', (c) => chunks.push(c));
|
||||
req.on('end', () => resolve(Buffer.concat(chunks)));
|
||||
});
|
||||
const headers = {};
|
||||
for (const h of ['content-type', 'authorization', 'clientid', 'content-language']) {
|
||||
if (req.headers[h]) headers[h] = req.headers[h];
|
||||
}
|
||||
const upstream = await fetch(`${apiBase}${p.slice(8)}${new URL(req.url, 'http://x').search}`, {
|
||||
method: req.method, headers, body,
|
||||
});
|
||||
res.writeHead(upstream.status, { 'Content-Type': upstream.headers.get('content-type') || 'application/json' });
|
||||
res.end(Buffer.from(await upstream.arrayBuffer()));
|
||||
} catch (error) {
|
||||
res.writeHead(502, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ code: 502, msg: `dev-api 代理失败:${error.message}` }));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (p.startsWith('/h5/')) p = p.slice(3);
|
||||
if (p === '/' || p === '') p = '/index.html';
|
||||
let file = path.join(h5Dir, p);
|
||||
@@ -193,6 +255,7 @@ const COLLECTOR = `(() => {
|
||||
})()`;
|
||||
|
||||
// ---------- 主流程 ----------
|
||||
const session = loginPhone ? await loginForToken() : null;
|
||||
const served = argValue('--url') ? null : await serveH5();
|
||||
const baseUrl = argValue('--url') || served.url;
|
||||
console.log(`[setup] baseUrl=${baseUrl}`);
|
||||
@@ -208,6 +271,17 @@ const pages = [];
|
||||
try {
|
||||
await page.send('Page.enable');
|
||||
await page.send('Runtime.enable');
|
||||
if (session) {
|
||||
// 与应用 saveAuth 相同的 storage 键,注入已登录会话(不经过 UI,不触发额外短信)
|
||||
await page.send('Page.addScriptToEvaluateOnNewDocument', {
|
||||
source: [
|
||||
`localStorage.setItem('aihr_mobile_access_token', ${JSON.stringify(session.token)});`,
|
||||
`localStorage.setItem('aihr_mobile_client_id', ${JSON.stringify(session.clientId)});`,
|
||||
`localStorage.setItem('aihr_mobile_phone', ${JSON.stringify(loginPhone)});`,
|
||||
`localStorage.setItem('aihr_mobile_tenant_id', '000000');`,
|
||||
].join('\n'),
|
||||
});
|
||||
}
|
||||
await page.send('Emulation.setDeviceMetricsOverride', {
|
||||
width: VIEW_W, height: VIEW_H, deviceScaleFactor: 2, mobile: true,
|
||||
});
|
||||
@@ -334,6 +408,7 @@ lines.push('# mobile-uni 渲染态 UI 审计');
|
||||
lines.push('');
|
||||
lines.push(`- 生成时间:${new Date().toISOString()}`);
|
||||
lines.push(`- 入口:${baseUrl}(${argValue('--url') ? '外部服务' : '本地静态服务 dist/build/h5'}),视口 ${VIEW_W}×${VIEW_H} @2x`);
|
||||
lines.push(`- 会话:${session ? `已认证(凭据不入报告),API 代理 ${apiBase}` : '未登录(页面呈现登录门禁/空态/错误态)'}`);
|
||||
lines.push(`- Chrome:${chrome.chromeBin}`);
|
||||
lines.push(`- palette:${palette.size} 色;字阶:${[...fontScale].sort((a, b) => a - b).join('/')}px;半径 token:${[...radiusTokens].sort((a, b) => a - b).join('/')}px`);
|
||||
lines.push(`- 口径:按可见元素的 getComputedStyle 计次(一个元素一次),非源码声明次数;默认未登录态渲染,异步接口失败的页面呈现错误/空态`);
|
||||
|
||||
Reference in New Issue
Block a user