1.为什么用computed,依赖项注意点;2.正则语法,i.test();3.UA解释;4.join语法
1 判断移动端的代码解读
助手回复:
代码解析
import { computed } from 'vue'
export const isMobileTerminal = computed(() => {
// 方式1:UA字符串检测设备
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
)
})
- 用途:Vue3 computed 计算属性,通过
navigator.userAgent用户代理字符串判断是否为移动端设备。 - 正则:匹配安卓、苹果手机/平板、黑莓、老移动浏览器标识,
i忽略大小写。
⚠️关键问题
-
computed 不会自动响应UA
navigator.userAgent不是vue响应式变量,切换设备/模拟器,不会自动重新计算,只有组件重新执行setup才会更新,适合页面初始化一次性判断,不适合窗口拖拽实时切换设备场景。 -
iPadOS 13+坑点 新版iPad平板UA返回
Macintosh,不再携带iPad关键字,该正则识别不到新版iPad,会误判为PC端。 -
SSR/SSG报错 服务端没有
navigator对象,Nuxt等SSR项目直接运行会直接报错,需要套process.client/onMounted浏览器环境判断。
两种主流完整版本
版本1 UA检测(你的原版,兼容注意点)
import { computed } from 'vue'
// 仅浏览器环境执行
export const isMobileTerminal = computed(() => {
if (typeof navigator === 'undefined') return false
const ua = navigator.userAgent
// 兼容新iPad:MacIntel + 触摸屏
const isNewIpad = navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua) || isNewIpad
})
版本2 视口宽度判断(常用方案,vueuse)
按屏幕断点区分,跟随窗口大小实时响应,适合响应式布局,不检测设备,只看屏幕尺寸。
import { computed } from 'vue'
import { useWindowSize } from '@vueuse/core'
const { width } = useWindowSize()
const PC_WIDTH = 1200
export const isMobileTerminal = computed(() => width.value < PC_WIDTH)
使用示例
<script setup>
import { isMobileTerminal } from '@/utils/device'
</script>
<template>
<div v-if="isMobileTerminal">移动端布局</div>
<div v-else>PC端布局</div>
</template>
选型建议
- 需要识别真实物理设备(手机/平板访问):用UA方案,注意iPad兼容、SSR处理。
- 只做页面响应式布局:优先用窗口宽度判断(vueuse)。
补充:UA可以被浏览器插件篡改,不能100%可靠。
需要我给你写一个nuxt3可用的安全版本吗?
2 用户:详细解释,不懂为什么用computed,正则怎么匹配,i.test啥意思
用户输入:详细解释,不懂为什么用computed,正则怎么匹配,i.test啥意思
助手回复:
逐行拆解完整代码
import { computed } from 'vue'
export const isMobileTerminal = computed(() => {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
)
})
我们分成 4 块讲:
computed是什么,这里为什么要用它/Android|webOS|.../i正则每一部分含义.test()是什么navigator.userAgent是什么export const
1. computed 计算属性
computed(()=>{ ... })
computed:Vue 的计算属性,返回一个响应式对象
.value
普通函数:
//普通函数,调用才执行,返回普通布尔值 true/false
function getIsMobile(){
return xxx
}
computed 包装之后:
export const isMobileTerminal = computed(()=>{...})
- 返回的不是普通布尔,是一个ref包装对象,使用要写
isMobileTerminal.value - 在模板
<template>里使用,不用写.value,vue自动解包
<template>
<div v-if="isMobileTerminal">手机端</div>
</template>
❗那这里有一个大坑(重点)
这里 computed 内部读取的是 navigator.userAgent,这个不是Vue响应式数据!
👉 computed 只有内部依赖的响应式变量变化,才会重新执行函数。
navigator.userAgent是浏览器原生API,不是响应式,所以这个computed只会执行1次!组件初始化跑一次,后面不会更新。
那为什么还写 computed,不直接写普通函数?
- 模板中可以直接使用,语法统一;
- 如果以后内部改成依赖响应式变量(比如窗口宽度),computed会自动更新;
- 代码习惯:把需要在模板使用的布尔判断统一包成computed;
注意:当前这段代码,computed不会自动更新,只是一次性求值。
2. 正则表达式
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i
- 前后两个
/是JS正则字面量语法,代表正则的开始和结束 |代表或,匹配任意一个字符串即可
等价语义:匹配字符串中包含
Android或者webOS或者iPhone……任意一个
- 末尾的
i:正则修饰符,ignoreCase忽略大小写 比如 UA 里面出现android小写、IPHONE大写,也照样匹配成功。
举例子:
/a|b/i 匹配 A、a、B、b
正则逻辑翻译成人话
在目标字符串里面,只要出现下面任意一个单词,就匹配成功 Android、webOS、iPhone、iPad、iPod、BlackBerry、IEMobile、Opera Mini
3. .test(字符串)
正则.test(要检测的字符串)
test() 是正则对象自带方法:
- 在传入的字符串中查找能不能匹配正则规则
- 返回布尔值:
true匹配成功,false匹配失败
示例:
const reg = /iphone|android/i
reg.test("iphone 15") // true
reg.test("Android 14") // true
reg.test("Windows pc") // false
所以代码:
/xxx/i.test(navigator.userAgent)
含义:拿浏览器UA字符串,用正则去检测,是否包含手机设备关键字,返回true/false。
4. navigator.userAgent
navigator 浏览器BOM原生对象,只有浏览器环境才有,node/ssr没有。
navigator.userAgent:用户代理字符串,浏览器会上报自己设备、系统信息的长字符串。
👉安卓手机的ua片段示例:
Mozilla/5.0 (Android 14; Mobile) ...
👉苹果iphone:
Mozilla/5.0 (iPhone; CPU iPhone OS 17 ...)
JS读取这个字符串,用正则判断里面有没有手机设备关键词,来区分手机还是电脑。
5. export const isMobileTerminal = xxx
export:导出变量,别的文件可以import导入使用
//别的文件
import {isMobileTerminal} from '@/utils/xxx'
完整伪代码翻译整段代码
//导入vue计算属性
import { computed } from 'vue'
//导出一个响应式计算属性变量 isMobileTerminal
export const isMobileTerminal = computed(() => {
// 1.拿到浏览器的设备信息字符串 navigator.userAgent
// 2.用正则去检测这个字符串是否包含任意手机设备关键词
// 3.test返回true就是移动端;false就是pc端
return 匹配结果true/false
})
常见误区再总结
- computed 不是写了就会自动更新!只有里面用到的Vue响应式数据改变才会重跑。这里navigator不是响应式,只会跑一次。
i修饰符:忽略大小写,防止UA大小写错乱匹配失败|正则是或;.test()返回布尔- 新版iPadOS的userAgent不再出现iPad字段,这段代码识别不出新版iPad,会判定成PC
- SSR环境(Nuxt)服务端没有navigator,直接运行直接报错。
小测试demo,你可以复制浏览器控制台运行
const reg = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i
console.log(reg.test(navigator.userAgent))
如果你需要,我可以给你写一段不带vue的原生JS版本,对比看,更容易看懂。
3 用户:UA是啥
用户输入:UA是啥
助手回复:
UA = User Agent 用户代理
就是浏览器自我介绍的一大串字符串,浏览器告诉网站:我是什么浏览器、什么系统、什么设备。
navigator.userAgent 拿到的就是 UA 字符串。
举真实例子
📱 iPhone手机 UA(简化)
Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 …
里面出现单词 iPhone,我们代码正则抓到 iPhone → 判断是移动端 ✅
📱安卓手机 UA(简化)
Mozilla/5.0 (Android 15; Mobile; xxx ) Chrome/128.0 …
里面有 Android,正则匹配成功 →移动端 ✅
💻 Windows电脑Chrome UA
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 …
里面没有 Android / iPhone 这些关键词 →返回 false,判定PC端 ✅
📌新版iPad大坑
iPadOS13之后,iPad的UA不再写iPad,写的是MacIntel
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit …
看不到iPad字眼,老正则就会把iPad识别成电脑,这就是之前说的bug。
UA能干什么?
网站拿到UA字符串,可以做这些事:
- 判断是手机还是电脑(咱们现在写的逻辑)
- 判断你用的什么浏览器:Chrome、Safari
- 判断操作系统:Windows、安卓、iOS
⚠️UA缺点(很重要)
- 可以被篡改:浏览器插件、模拟器可以手动修改UA字符串,伪装成手机/电脑,所以UA检测不是100%靠谱。
- SSR(Node环境)没有这个东西,只有真实浏览器才有。
- 设备厂商会不断更新UA字符串,老正则有可能以后失效。
快速看自己浏览器UA
打开浏览器F12控制台,粘贴运行:
console.log(navigator.userAgent)
就可以打印出你本机完整UA字符串。
回到之前代码一句话总结
/Android|webOS|iPhone.../i.test(navigator.userAgent)
拿浏览器自我介绍(UA)那一大段文字,看看里面有没有手机设备关键词,有就是移动端。
对比两种判断思路
- UA检测:看「设备身份介绍」 →你是什么设备
- 窗口宽度检测(vueuse):看「屏幕多大」 →屏幕小就当移动端,不管你是电脑还是平板
业务做响应式页面,优先用屏幕宽度,不要迷信UA。UA适合统计设备类型。
4 用户:join啥意思,语法
用户输入:join啥意思,语法
助手回复:
path.join()
'@': join(__dirname, '/src')
join来自 node 的内置模块path,专门用来拼接文件路径。 完整写法:const path = require('path')或者import path from 'path'实际是:path.join(__dirname, '/src')
先拆开两个关键字:
__dirname:node 内置变量,当前这个配置文件所在文件夹的绝对路径path.join(路径1,路径2,路径3...):拼接路径,自动处理/和\斜杠,兼容 windows / mac /linux
1. __dirname 是什么
假设你的项目:
D:/code/my-project/vite.config.js
在 vite.config.js 文件里面打印 __dirname
console.log(__dirname)
//输出: D:/code/my-project
✅就是当前文件所在目录的完整绝对路径。
注意:浏览器环境没有
__dirname,只有 Node(vite/webpack配置文件运行在node)才有。
2. path.join() 语法
path.join(a,b,c...)
作用:把多个路径片段拼接到一起,自动帮你处理斜杠,不用手动纠结写 / 还是 \。
Windows 路径分隔符是 \,Mac/Linux 是 /。手写字符串拼接很容易写错。
path.join 会根据操作系统自动选正确分隔符。
示例
path.join('D:/code/my-project','/src')
// → D:\code\my-project\src (windows)
// → D:/code/my-project/src (mac)
哪怕你多写斜杠、少写斜杠都没事:
path.join('D:/a', 'b') // D:/a/b
path.join('D:/a/', '/b') // D:/a/b 自动去掉重复斜杠
3. '@': path.join(__dirname, '/src') 完整含义
这是 vite/webpack 的路径别名配置。
{
resolve: {
alias: {
'@': path.join(__dirname, '/src')
}
}
}
含义:
当代码里写
@/components/xxx.vue把@替换成项目根目录/src的绝对路径
等价映射:
@ → D:/code/my-project/src
@/components → D:/code/my-project/src/components
业务代码中:
import Hello from '@/components/Hello.vue'
//等价于
import Hello from 'D:/code/my-project/src/components/Hello.vue'
好处:
- 不用写一长串
../../相对路径 - 文件移动位置,导入路径不会乱
❗容易踩坑点
path.join返回的是真实硬盘绝对路径字符串- 只在 node 配置文件(vite.config、webpack.config)使用;vue、ts业务代码不能直接用
__dirname - vite现在也可以用
import.meta.url替代__dirname(esmodule模式)
补充对比
path.join():拼接路径片段,普通拼接path.resolve():会从根目录解析,支持../向上找目录
path.join(__dirname,'/src')
path.resolve(__dirname,'src')
//这个场景两者结果一样
极简总结
path.join(__dirname, '/src')
__dirname:当前配置文件所在文件夹完整路径join:安全拼接路径,自动处理不同系统的斜杠- 整体得到项目中
src文件夹的绝对路径,用来设置别名@。
所以写
@就等价于直接指向 src 文件夹。
4.Q:v‑if和v‑show区别
A:# v‑if vs v‑show
核心一句话
v‑if:条件销毁/重建 DOM;条件为假时直接没有这个DOM节点v‑show:条件控制 CSSdisplay:none;DOM始终存在,只是看不见
<!-- v‑if 为false:直接从页面删掉这个元素 -->
<div v-if="ok">hello</div>
<!-- v‑show 为false:元素还在,style="display: none" -->
<div v-show="ok">hello</div>
详细对比
| 对比项 | v‑if | v‑show |
|---|---|---|
| 原理 | 条件为false,销毁DOM;true时重新创建DOM | DOM始终保留,切换 display CSS属性 |
| 切换开销 | 切换开销大(创建/销毁组件、触发生命周期) | 切换开销小,只是改样式 |
| 初始渲染开销 | 条件false:初始不渲染,开销很小 | 不管真假,DOM都会渲染出来,初始有开销 |
支持v‑else/v‑else‑if | ✅支持 | ❌不支持 |
支持<template> | ✅可以用<template v-if>分组,不生成真实DOM | ❌<template v-show>无效 |
| 组件生命周期 | 条件切换会触发 created/mounted/unmounted | 不会触发组件销毁重建钩子 |
使用场景
- 一次性条件判断,很少切换 → 用 v‑if 比如权限控制、页面模块,打开页面就基本不会变。
<div v-if="isAdmin">管理员面板</div>
- 频繁显示隐藏(弹窗、tab切换) → 用 v‑show 频繁来回切换,避免反复创建销毁DOM,性能更好。
<div v-show="isPopupOpen">弹窗内容</div>
坑点注意
v‑if和v‑for不要放同一个标签上,v‑for优先级更高,会造成性能问题;v‑if为false,内部组件不会mount;v‑show隐藏时组件已经mount完成,只是看不见;<template>只能搭配v‑if做分组,不能用v‑show。
示例 template分组
<template v-if="ok">
<p>段落1</p>
<p>段落2</p>
</template>
不会多套一层div,满足一组元素一起条件渲染。
记忆口诀
少变用v‑if,多变用v‑show;if删DOM,show藏样式。
需要我顺带讲下源码层面简单原理吗?
5.Q:axios是啥,常见代码
A:# axios 是什么 axios 是一个基于 Promise 的 HTTP 请求库,专门用来发 ajax 请求,前端向后端拿数据/提交数据。
- 浏览器端:封装
XMLHttpRequest - Node端:封装
http模块 - 返回 Promise,支持 async/await,比原生
fetch、XMLHttpRequest好用很多
不是浏览器自带API,需要安装。
安装
npm install axios
# 或者 yarn add axios
常用写法
1. 简单 get 请求(获取数据)
import axios from 'axios'
// 方式1 .then 链式
axios.get('/api/user', {
params: { id: 1 } // url查询参数 ?id=1
}).then(res => {
console.log(res.data) // 后端返回的数据
}).catch(err => {
console.error(err)
})
// 方式2 async await(项目最常用)
async function getUser() {
try {
const res = await axios.get('/api/user', { params: { id: 1 } })
console.log(res.data)
} catch (err) {
console.error(err)
}
}
2. post 请求(提交数据,新增/表单)
async function addUser() {
const data = { name:'张三', age:20 } // 请求体,json格式
const res = await axios.post('/api/user', data)
console.log(res.data)
}
put(修改)、delete(删除)
await axios.put('/api/user/1', { name:'李四' })
await axios.delete('/api/user/1')
⭐创建实例(项目必用,统一配置baseURL)
不要每次请求写完整地址,创建实例统一管理:
// request.js
import axios from 'axios'
const service = axios.create({
baseURL: '/api', // 接口基础地址
timeout: 5000 // 请求超时时间5秒
})
// 请求拦截器:发请求之前做处理,比如带上token
service.interceptors.request.use(config => {
// 从localStorage拿token放到请求头
const token = localStorage.getItem('token')
if(token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// 响应拦截器:拿到后端返回统一处理
service.interceptors.response.use(
response => {
return response.data // 直接把data吐出去,组件不用每次 .data
},
error => {
// 统一错误处理,比如401未登录跳转登录页
console.log('请求出错', error)
return Promise.reject(error)
}
)
export default service
组件中使用:
import request from '@/utils/request'
async function fn() {
// 自动拼接 baseURL → /api/user
const data = await request.get('/user', { params:{id:1} })
console.log(data)
}
response 返回对象结构
{
data: {}, // 后端返回真实数据
status: 200, // http状态码
statusText: "OK",
headers: {}, // 响应头
config: {} // 请求配置
}
常见坑
get参数用params;post直接传对象,放第二个参数- 跨域:前端不能直接解决跨域,需要后端配置,或者 vite/webpack配置代理
- post传表单格式(不是json)需要改
headers['Content‑Type'] - 拦截器一定要
return config,否则请求发不出去
快速记忆
- get:拿数据,参数
params - post:提交数据,参数放请求体
- 实例+拦截器是实际项目标准写法,几乎不会直接用原生axios.get
需要我顺带对比 axios vs fetch 的区别吗?
6.Q:跨域问题是什么?怎么解决?(用axios举例说明)
A:# 跨域是什么 浏览器的同源策略:协议、域名、端口,三者有一个不一样,就是跨域,浏览器会拦截响应。
同源:
http://localhost:5173访问http://localhost:5173/api✅ 跨域:前端http://localhost:5173,后端http://localhost:3000❌ 浏览器不是不让发请求,请求发出去了,后端收到了,但是浏览器把返回结果拦截掉。
⚠️跨域是浏览器安全限制,node环境发axios没有跨域问题。
三种解决方式
- 后端配置CORS(生产环境首选)
- 前端开发环境代理(vite/webpack,仅本地开发用)
- jsonp(只支持get,现在基本不用)
方式1:后端开启CORS(上线真正用)
后端设置响应头,告诉浏览器允许跨域访问。 前端axios代码不需要改任何东西。
// axios直接请求后端完整地址
const service = axios.create({
baseURL: 'http://127.0.0.1:3000'
})
跨域报错由后端处理,前端不用处理。
方式2:Vite开发环境代理(本地调试,只在npm run dev生效,打包上线失效)
重点:代理是vite服务器帮你转发请求,绕过浏览器同源策略。 ⚠️打包之后代理配置失效,上线还是需要后端CORS。
vite.config.js配置
import { defineConfig } from 'vite'
export default defineConfig({
server: {
proxy: {
// 匹配 /api 开头的请求
'/api': {
target: 'http://127.0.0.1:3000', // 真实后端地址
changeOrigin: true, // 开启跨域模拟
rewrite: (path) => path.replace(/^\/api/, '') // 去掉路径上的/api前缀
}
}
}
})
axios代码
❌不要写完整后端地址!!
// request.js
const service = axios.create({
// 开发时写/api,不要写http://127.0.0.1:3000
baseURL: '/api',
timeout: 5000
})
浏览器发送:http://localhost:5173/api/list
vite代理转发:http://127.0.0.1:3000/list
不写
rewrite的情况:后端接口本身就带/api,就不要替换 target + '/api/list'
常见踩坑
- 代理只本地dev有效,build打包后完全没用,上线必须后端CORS
- axios baseURL不能写target完整地址,要写
/api - 修改vite.config 必须重启开发服务器才生效
常见报错信息
Access to XMLHttpRequest at 'xxx' from origin 'xxx' has been blocked by CORS policy
看到这个就是跨域。
面试一句话总结
- 跨域是浏览器同源策略,协议、域名、端口不一致触发。
- 生产环境:后端CORS
- 开发环境:前端配置dev代理做请求转发
- jsonp仅get,现在很少用。
补充:为什么不能前端axios直接解决跨域?
axios只是封装XMLHttpRequest,没有能力修改浏览器安全策略,真正解决跨域要么后端放行,要么中间服务器转发请求(vite代理)。
如果你需要,我可以给你对比webpack版代理写法。
7.Q:前端怎么测试接口是否正确,移动端怎么测试
A:# 前端测试接口 + 移动端测试
一、前端怎么测试接口是否正确
1. 后端工具(优先,不写代码)
- Postman / Apifox 输入接口地址、请求方式、params/body、token,直接发送,看返回数据、状态码。
作用:先确认后端接口本身有没有bug,排除后端问题,再去写axios代码。
流程:Apifox跑通接口 → 再写前端axios。避免上来就写代码,分不清是前端错还是后端错。
2. 浏览器Network面板(最常用)
浏览器F12 → Network,发起axios请求。 看4个关键点:
- Request URL:实际请求地址是否正确(代理有没有拼错)
- Request Method:get/post 是否写对
- Payload / Query String Parameters:看参数有没有传过去
- Response:后端返回真实数据;Status状态码
- 200:正常
- 400:参数错误
- 401:token失效未登录
- 404:接口地址写错
- 500:后端代码报错
- CORS报错:跨域问题
小技巧:Network勾选Preserve log,页面刷新不清空请求日志。
3. 代码层面测试 axios
// 直接在vue组件onMounted里临时写测试代码
import request from '@/utils/request'
onMounted(async ()=>{
try {
const res = await request.get('/list')
console.log('接口返回:', res)
} catch(e) {
console.error('接口报错:',e)
}
})
控制台看打印,区分:
- 请求成功拿到数据
- 请求失败看error.response(后端返回错误信息)
4. mock接口(后端还没写完)
- vite插件:vite‑plugin‑mock
- apifox 本地mock服务,前端直接调mock地址
面试话术:先使用接口调试工具确认接口可用性,再到浏览器network观察真实请求,核对地址、参数、响应,定位问题。
二、移动端怎么测试(Vue项目)
两种模式:模拟器、真机测试(真实手机)
方式1:浏览器模拟移动端(PC快速看样式,不能完全替代真机)
F12,点击左上角手机图标,切换设备,选择手机型号。
⚠️只能看页面布局,部分手机特有bug(滑动、输入框、软键盘、兼容性)复现不出来。
方式2:真机调试(重点,面试常问)
前提:电脑和手机连接同一个WiFi局域网
① vite 修改启动配置
vite.config.js
export default defineConfig({
server:{
host:'0.0.0.0', // 关键!允许局域网访问
port:5173
}
})
启动后终端会出现:
Local: http://127.0.0.1:5173/
Network: http://192.168.x.x:5173/
把 Network那行地址复制到手机浏览器打开,手机就能访问电脑本地项目。
⚠️此时axios跨域问题: 手机访问局域网地址,vite代理依旧生效,不要写死后端完整ip地址,baseURL依旧写
/api。
② 手机上查看控制台日志(手机看不到console)
两种方案:
- vconsole(项目引入,最常用)
npm install vconsole
main.js
import VConsole from 'vconsole'
// 只开发环境开启
if(import.meta.env.DEV){
new VConsole()
}
手机页面右下角出现小v按钮,点开:可以看console日志、network网络请求、localStorage。 👉手机上直接看接口请求报错,不用插数据线。
- Chrome远程调试(安卓手机)
- 手机开启开发者选项,USB调试;数据线连接电脑
- chrome浏览器输入
chrome://inspect,可以直接在电脑F12调试手机页面。
iPhone真机调试:需要Mac电脑 + Safari浏览器。Windows不支持苹果真机调试。
打包后的H5测试(上线包)
- 打包
npm run build - 本地预览dist包工具:
serve
npm i serve -g
serve dist
手机同一局域网访问serve给出的network地址,测试打包后的真实效果。
移动端常见坑
- vite不写
host:'0.0.0.0',手机打不开页面 - 手机电脑不在同一个wifi,访问不通
- vconsole记得生产环境关闭,不要带到线上
- 模拟器样式正常,真机出问题:css兼容、flex、1px边框、软键盘遮挡
面试简答总结
接口测试:先用Apifox/postman验证接口本身;再通过浏览器Network看真实请求,核对url、参数、状态码;代码打印结果排查。
移动端测试:
- PC浏览器设备模拟快速预览;
- 真机:vite配置host:0.0.0.0,手机电脑同一局域网访问局域网地址;引入vconsole在手机查看日志和网络请求;安卓可用chrome远程调试。