跳到主要内容

获取系统设备

declare global {
interface Navigator {
userAgentData?: {
getHighEntropyValues(hints: string[]): Promise<{
platform?: string;
platformVersion?: string;
architecture?: string;
model?: string;
brands?: { brand: string; version: string }[];
fullVersionList?: { brand: string; version: string }[];
}>;
brands?: { brand: string; version: string }[];
mobile?: boolean;
platform?: string;
};
}
}

/**
* 获取设备类型
* @returns {string} 设备类型
*/
export function getDeviceType() {
const ua = navigator.userAgent.toLowerCase();
const hasTouchScreen = navigator.maxTouchPoints > 0;
const screenWidth = window.screen.width;

// 手机
if (/iphone|ipod|android.*mobile|windows phone|blackberry|opera mini|iemobile/.test(ua)) {
return 'phone';
}

// 平板
if (/ipad|android(?!.*mobile)|tablet|kindle|silk|playbook/.test(ua)) {
return 'tablet';
}

// iPadOS 13+ 的 Safari UA 伪装成了 Mac 桌面端
// iPadOS 13+ 检测
if (/macintosh/.test(ua) && navigator.maxTouchPoints > 1) {
return 'tablet';
}

// 兜底:有触摸屏 + 屏幕较小 → 可能是手机/平板
if (hasTouchScreen && screenWidth < 768) {
return 'phone';
}
if (hasTouchScreen && screenWidth < 1200) {
return 'tablet';
}

return 'desktop';
}

/**
* 获取操作系统信息
* @returns {Promise<{os: string, version: string, arch: string}>}
*/
async function getDetailedOS() {
// 优先使用 userAgentData(更准确)
if (navigator.userAgentData) {
try {
const data = await navigator.userAgentData.getHighEntropyValues(['platform', 'platformVersion', 'architecture']);

let os = data.platform;
let version = data.platformVersion;

if (os === 'Windows' && version) {
const verNum = parseFloat(version);
os =
verNum >= 10.0 && version.split('.').length >= 3 && parseInt(version.split('.')[2], 10) >= 22000
? 'Windows 11'
: 'Windows 10';
} else if (os === 'macOS') {
os = `macOS ${version}`;
}

return { os, version, arch: data.architecture };
} catch (e) {}
}

// 降级到 userAgent 解析
const ua = navigator.userAgent;

if (/Windows NT/.test(ua)) {
const ver = ua.match(/Windows NT ([\d.]+)/)?.[1];
return {
os: 'Windows',
version: ver,
arch: /Win64|x64/.test(ua) ? 'x64' : 'x86',
};
}
if (/Macintosh/.test(ua)) {
const ver = ua.match(/Mac OS X ([\d_.]+)/)?.[1]?.replace(/_/g, '.');
const isIPad = navigator.maxTouchPoints > 1;
let arch = 'unknown';
// 优先用 userAgentData,已使用
// 降级用 WebGL 渲染器推断
if (arch === 'unknown') {
try {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl')!;
const ext = gl.getExtension('WEBGL_debug_renderer_info');
const renderer = gl.getParameter(ext!.UNMASKED_RENDERER_WEBGL);
arch = /Apple M\d/.test(renderer) ? 'arm' : 'x86';
} catch (e) {}
}
return { os: isIPad ? 'iPadOS' : 'macOS', version: ver, arch };
}
if (/Android/.test(ua)) {
const ver = ua.match(/Android ([\d.]+)/)?.[1];
return { os: 'Android', version: ver, arch: 'arm' };
}
if (/iPhone|iPad|iPod/.test(ua)) {
const ver = ua.match(/OS ([\d_]+)/)?.[1]?.replace(/_/g, '.');
return { os: 'iOS', version: ver, arch: 'arm' };
}
if (/CrOS/.test(ua)) return { os: 'Chrome OS', version: '', arch: 'unknown' };
if (/Linux/.test(ua))
return {
os: 'Linux',
version: '',
arch: /x86_64/.test(ua) ? 'x64' : 'unknown',
};

return { os: 'Unknown', version: '', arch: 'unknown' };
}

export async function getOS() {
const { os, version, arch } = await getDetailedOS();
return { os, version, arch };
}

/**
* 获取浏览器信息
* @returns {Promise<{name: string, version: string, platform: string, model: string, source: string}>}
*/
export async function getBrowserInfo() {
// 优先使用 userAgentData(Chromium 86+ 支持)
if (navigator.userAgentData) {
try {
const uaData = await navigator.userAgentData.getHighEntropyValues([
'brands',
'platform',
'platformVersion',
'model',
'fullVersionList',
]);

const fullVersionList = uaData.fullVersionList || uaData.brands || [];

// fullVersionList 中会包含真实的浏览器品牌信息
const brandMap: { [key: string]: string } = {
'Microsoft Edge': 'Edge',
Opera: 'Opera',
'Samsung Internet': '三星浏览器',
Brave: 'Brave',
'Google Chrome': 'Chrome',
Chromium: 'Chromium',
};

for (const { brand, version } of fullVersionList) {
// 过滤掉故意混淆的品牌名(Chromium 的 GREASE 策略)
if (/not.a|greasy/i.test(brand)) continue;
if (brandMap[brand]) {
return {
name: brandMap[brand],
version,
platform: uaData.platform,
model: uaData.model,
source: 'userAgentData',
};
}
}
} catch (e) {
// getHighEntropyValues 可能被拒绝,降级到 UA 解析
}
}

// 降级方案:解析 userAgent 字符串
return parseBrowserFromUA();
}

function parseBrowserFromUA() {
const ua = navigator.userAgent;

const rules = [
{ name: '微信浏览器', regex: /MicroMessenger\/([\d.]+)/ },
{ name: 'QQ浏览器', regex: /(?:MQQBrowser|QQBrowser)\/([\d.]+)/ },
{ name: 'UC浏览器', regex: /(?:UCBrowser|UCWEB)\/([\d.]+)/ },
{ name: '360浏览器', regex: /(?:QihooBrowser|QHBrowser)\/([\d.]+)/ },
{ name: '百度浏览器', regex: /(?:BaiduBoxApp|baidubrowser)\/([\d.]+)/ },
{ name: '搜狗浏览器', regex: /(?:SogouMobileBrowser|MetaSr)\/([\d.]+)/ },
{ name: '猎豹浏览器', regex: /(?:LieBaoFast|LBBrowser)\/([\d.]+)/ },
{ name: '遨游浏览器', regex: /Maxthon\/([\d.]+)/ },
{ name: '华为浏览器', regex: /HuaweiBrowser\/([\d.]+)/ },
{ name: '小米浏览器', regex: /MiuiBrowser\/([\d.]+)/ },
{ name: 'vivo浏览器', regex: /VivoBrowser\/([\d.]+)/ },
{ name: 'OPPO浏览器', regex: /OppoBrowser\/([\d.]+)/ },
{ name: '三星浏览器', regex: /SamsungBrowser\/([\d.]+)/ },
{ name: 'Opera', regex: /(?:OPR|Opera)\/([\d.]+)/ },
{ name: 'Edge', regex: /Edg(?:e|A|iOS)?\/([\d.]+)/ },
{ name: 'Firefox', regex: /(?:Firefox|FxiOS)\/([\d.]+)/ },
{ name: 'Chrome', regex: /(?:Chrome|CriOS)\/([\d.]+)/, exclude: /Edg|OPR/ },
{
name: 'Safari',
regex: /Version\/([\d.]+).*Safari/,
exclude: /Chrome|CriOS/,
},
];

for (const { name, regex, exclude } of rules) {
if (exclude && exclude.test(ua)) continue;
const match = ua.match(regex);
if (match) {
return {
name,
version: match[1],
platform: /Android/i.test(ua) ? 'Android' : /iPhone|iPad/i.test(ua) ? 'iOS' : 'Unknown',
model: (ua.match(/;\s*([\w\s-]+)\s*Build/) || [])[1]?.trim() || '',
source: 'userAgent',
};
}
}

return {
name: '未知浏览器',
version: '',
platform: '',
model: '',
source: 'userAgent',
};
}

// 使用示例
getBrowserInfo().then(info => {
console.log('浏览器:', info.name);
console.log('版本:', info.version);
console.log('平台:', info.platform);
console.log('设备型号:', info.model);
console.log('检测来源:', info.source);
});