/** * NavigatorUAData 接口定义 */ interface NavigatorUAData { readonly mobile: boolean readonly platform: string } /** * 设备类型 */ export type DeviceType = 'mobile' | 'tablet' | 'desktop' /** * 平台类型 */ export type DevicePlatform = | 'ios' | 'android' | 'windows' | 'macos' | 'linux' | 'unknown' /** * 设备检测结果 */ export interface DeviceInfo { /** 当前设备类型 */ type: DeviceType /** 是否为手机 */ isMobile: boolean /** 是否为平板 */ isTablet: boolean /** 是否为桌面设备 */ isDesktop: boolean /** 是否支持触摸输入 */ isTouch: boolean /** 是否为小屏设备(通常 <= 768px) */ isSmallScreen: boolean /** 操作系统 / 平台 */ platform: DevicePlatform } /** * 检测当前运行环境的设备信息 * * 检测顺序: * 1. 使用浏览器现代标准 `navigator.userAgentData`(UA-CH) * 2. 不支持时降级为 User-Agent + 设备特征判断 * * @returns {DeviceInfo} 当前设备信息 * * @example * ```ts * const device = detectDevice() * * if (device.isMobile) { * enableMobileUI() * } * * if (device.isTablet) { * enableTabletLayout() * } * ``` */ export function detectDevice(): DeviceInfo { /* ============================= * 0. 通用设备能力 * ============================= */ const isTouch = typeof window !== 'undefined' && // deno-lint-ignore no-explicit-any ('ontouchstart' in window || (navigator as any).maxTouchPoints > 0) const isSmallScreen = typeof window !== 'undefined' && // deno-lint-ignore no-explicit-any (window as any).matchMedia('(max-width: 768px)').matches /* ============================= * 1. 现代标准(UA-CH) * ============================= */ if (typeof navigator !== 'undefined' && 'userAgentData' in navigator) { const uaData = navigator.userAgentData as NavigatorUAData // 明确标注为 mobile if (uaData.mobile === true) { return buildResult('mobile', isTouch, isSmallScreen, uaData.platform) } // 非 mobile + 触摸设备 + 非小屏 → 平板 if (uaData.mobile === false && isTouch && !isSmallScreen) { return buildResult('tablet', isTouch, isSmallScreen, uaData.platform) } // 其余情况默认为桌面 return buildResult('desktop', isTouch, isSmallScreen, uaData.platform) } /* ============================= * 2. 通用兜底(UA + 特征) * ============================= */ const ua = typeof navigator !== 'undefined' ? navigator.userAgent : '' const platform = // deno-lint-ignore no-explicit-any typeof navigator !== 'undefined' ? (navigator as any).platform : '' const isAndroid = /Android/i.test(ua) const isIOSPhone = /iPhone|iPod/i.test(ua) const isIPadUA = /iPad/i.test(ua) /** * iPadOS 13+: * - UA 显示为 Mac * - platform 为 MacIntel * - 支持多点触控 */ const isIPadOS = platform === 'MacIntel' && typeof navigator !== 'undefined' && // deno-lint-ignore no-explicit-any (navigator as any).maxTouchPoints > 1 const isTablet = isIPadUA || isIPadOS || (isAndroid && !/Mobile/i.test(ua)) const isMobile = isIOSPhone || (isAndroid && /Mobile/i.test(ua)) let type: DeviceType = 'desktop' if (isTablet) type = 'tablet' else if (isMobile) type = 'mobile' return buildResult(type, isTouch, isSmallScreen, resolvePlatform(ua, platform)) } /* ============================= * 内部工具函数 * ============================= */ /** * 构建标准化的设备检测结果 */ function buildResult( type: DeviceType, isTouch: boolean, isSmallScreen: boolean, platform: string ): DeviceInfo { return { type, isMobile: type === 'mobile', isTablet: type === 'tablet', isDesktop: type === 'desktop', isTouch, isSmallScreen, platform: normalizePlatform(platform) } } /** * 根据 UA / platform 推断操作系统 */ function resolvePlatform( ua: string, platform: string ): DevicePlatform { if (/iPhone|iPad|iPod/i.test(ua)) return 'ios' if (/Android/i.test(ua)) return 'android' if (/Win/i.test(platform)) return 'windows' if (/Mac/i.test(platform)) return 'macos' if (/Linux/i.test(platform)) return 'linux' return 'unknown' } /** * 规范化 UA-CH 返回的平台字段 */ function normalizePlatform(p?: string): DevicePlatform { if (!p) return 'unknown' const value = p.toLowerCase() if (value.includes('ios')) return 'ios' if (value.includes('android')) return 'android' if (value.includes('win')) return 'windows' if (value.includes('mac')) return 'macos' if (value.includes('linux')) return 'linux' return 'unknown' }