uniapp快速集成WiFi扫描功能
1. 为什么你的uniapp项目需要WiFi扫描功能?
最近好几个做智能硬件配套App的朋友都来问我同一个问题:“我的设备需要配网,用户得先连上家里的WiFi,这第一步怎么在App里优雅地实现?” 我一看,嘿,这不就是典型的WiFi扫描和列表获取需求嘛。在物联网、智能家居、打印机配网、甚至是一些需要获取网络环境的工具类App里,这个功能太常见了。用户打开你的App,点击“添加设备”或“连接网络”,一个清晰、快速的WiFi列表弹出来,体验一下子就上去了。
你可能之前用过原生开发,觉得调用系统API挺麻烦,权限一堆,兼容性还要操心。现在用uniapp,一套代码跑多个端,是不是更头大了?别担心,我刚开始也这么想。但实测下来,在uniapp里集成WiFi扫描,尤其是面向微信小程序平台,其实有现成的“捷径”可走,核心逻辑甚至比原生还清晰。这个功能的核心价值在于提升用户体验和操作效率。想象一下,让用户手动输入一长串晦涩的WiFi名称(SSID)有多反人类;而自动弹出列表让用户点选,这才是现代App该有的样子。
当然,这里有个非常重要的前提需要跟你强调:出于安全和隐私考虑,这个功能目前主要、且最稳定可靠的实现平台是微信小程序。支付宝小程序等平台可能有类似能力,但接口和限制各有不同。而App端(iOS和Android)由于系统级别的严格限制,uniapp默认的API是无法直接实现系统级WiFi扫描的。在App端,你通常需要对接原生插件或使用其他技术方案(比如引导用户跳转到系统设置页面),这完全是另一个话题了。所以,今天我们聚焦在微信小程序这个最主流、最可行的场景下,聊聊怎么用uniapp的语法,快速把WiFi列表“搬”到你的页面上。只要你项目的目标平台包含微信小程序,那这套方法就是你的“速效救心丸”。
2. 动手之前:理清权限与运行环境
好,咱们决定开干了。但在敲代码之前,有几步准备工作绝对不能跳过,这些“坑”我早期项目都踩过,现在帮你提前标出来。
### 2.1 配置小程序权限
uniapp编译到微信小程序,最终跑的是小程序代码。所以,微信小程序平台的要求就是我们的“圣旨”。WiFi相关接口属于敏感接口,需要用户授权。你不需要在uniapp的manifest.json里配,但必须在微信小程序的项目配置文件app.json中声明。
怎么操作呢?在HBuilderX里,找到你项目根目录下的 manifest.json 文件,切换到“微信小程序配置”视图。或者,你也可以直接去项目根目录的 unpackage/dist/dev/mp-weixin 目录下(编译后生成),找到 app.json。你需要确保其中有这样一段声明:
{
"requiredPrivateInfos": [
"getWifiList",
"onWifiConnected",
"startWifi"
]
}
注意,requiredPrivateInfos 这个字段是微信小程序要求声明隐私接口的。从2023年下半年开始,微信加强了隐私合规,这里没声明,你调用相关API可能直接失败。通常,我们至少需要声明 getWifiList 和 startWifi。我建议把可能用到的都写上,避免后续扩展时忘记。
### 2.2 理解API的“平台差异”与条件编译
这是uniapp开发的核心思维。虽然我们用的是uniapp的语法,但uni对象下并没有一个叫做uni.scanWifi的万能API。我们需要使用条件编译,在微信小程序环境下,调用微信原生的API。
在页面脚本中,你会看到这样的结构:
// #ifdef MP-WEIXIN
// 这里写微信小程序专用的WiFi API代码
wx.startWifi({...});
// #endif
// #ifdef APP-PLUS
// 这里写App端的处理方案,比如提示用户或调用插件
uni.showModal({
title: '提示',
content: 'App端需要跳转系统设置连接WiFi,或使用原生插件。'
});
// #endif
// #ifdef MP-WEIXIN 和 // #endif 就是条件编译指令。这意味着,这段代码只会在编译到微信小程序平台时被包含进去。编译到App或其他平台时,这段代码就像不存在一样。这样做保证了代码的平台兼容性,不会在非微信环境报错。你可能会问,那其他小程序平台呢?是的,支付宝、百度等小程序也有自己的WiFi API,但接口名和参数可能不同,你需要查阅对应平台的文档,并添加类似的条件编译块。今天我们主攻微信小程序这个最大公约数。
### 2.3 用户的手机系统与权限弹窗
即便你配置都对了,代码也写了,在真机上运行时,用户还会遇到两道“关卡”。
第一关是微信小程序基础库版本。WiFi接口不是最古老的API,但也不算新。为了保险起见,你可以设置一个较低的基础库版本要求。不过,现在绝大多数用户的微信版本都足够新,这个问题不大。
第二关,也是体验关键的一关,是用户授权弹窗。当你的代码第一次调用wx.startWifi()时,微信会向用户弹出一个授权窗口,询问“是否允许使用WiFi”。用户必须点击“允许”,后续所有操作才能正常进行。如果用户点了“拒绝”或“取消”,你的代码就会走入fail回调。作为开发者,我们必须优雅地处理这个拒绝情况:通常需要提示用户“功能需要WiFi权限”,并提供一个按钮,引导用户手动点击进入小程序设置页去打开权限(可以通过uni.openSetting接口)。这个流程设计的好坏,直接关系到你的功能开通率。
3. 核心三步曲:从初始化到列表展示
铺垫了这么多,终于到了最核心的代码环节。我把整个过程提炼成三个清晰的步骤,你跟着做,十分钟就能跑通。
### 3.1 第一步:初始化WiFi模块
你可以把WiFi模块想象成一个特殊的硬件设备,在使用前,需要先给它通电、启动。wx.startWifi() 就是那个“开机键”。这个操作通常是异步的,所以我们需要在它的回调函数里知道是否成功。
我习惯在页面的onLoad生命周期里,或者在一个明显的用户操作(比如点击一个“搜索WiFi”按钮)后调用它。代码如下:
// 在Page的methods中定义一个方法
initWifiModule() {
const that = this;
wx.startWifi({
success(res) {
console.log('WiFi模块初始化成功', res);
that.isWifiModuleReady = true; // 用一个状态变量标记
uni.showToast({ title: 'WiFi模块已就绪', icon: 'success' });
// 初始化成功,紧接着就可以获取列表了
that.getWifiList();
},
fail(err) {
console.error('WiFi模块初始化失败', err);
uni.showModal({
title: '初始化失败',
content: `失败原因:${err.errMsg || '未知错误'}。请检查手机WiFi是否开启,或是否授权小程序使用WiFi功能。`,
showCancel: false
});
that.isWifiModuleReady = false;
}
});
}
注意几个细节:第一,我在成功回调里设置了一个标记isWifiModuleReady,这样页面其他部分可以知道当前状态。第二,失败回调里我用了uni.showModal给用户明确的错误提示,并把微信返回的err.errMsg展示出来,这对于调试和用户理解问题非常有帮助。第三,成功之后,我直接链式调用了that.getWifiList(),让流程自动进行下去。
### 3.2 第二步:发起获取WiFi列表的请求
模块准备好了,我们就可以命令系统去扫描周边的WiFi热点。这就是wx.getWifiList()干的事。它像一个侦察兵,派出去搜集情报。
getWifiList() {
if (!this.isWifiModuleReady) {
uni.showToast({ title: '请先初始化WiFi模块', icon: 'none' });
return;
}
wx.getWifiList({
success(res) {
console.log('已发出获取WiFi列表指令', res);
// 注意:这里success仅仅表示指令发送成功,不代表拿到数据了!
// 真正的列表数据在另一个监听回调里。
},
fail(err) {
console.error('获取WiFi列表指令发送失败', err);
uni.showToast({ title: '扫描指令失败', icon: 'none' });
}
});
}
这里有一个超级重要的坑点,我当年就在这里卡了半天:wx.getWifiList()的success回调,只代表“系统收到了你的扫描请求”,并不代表扫描完成并把数据给你了!真正的WiFi列表数据,是通过一个事件监听器来获取的。所以,你光调用这个方法,页面上是等不来数据的。很多人做到这一步就懵了,觉得代码没错怎么没数据?原因就在这儿。
### 3.3 第三步:监听并处理返回的WiFi列表数据
上一步派出了侦察兵,这一步我们要设置一个“通讯员”,专门等待侦察兵带回情报。这个“通讯员”就是 wx.onGetWifiList 监听函数。这个监听只需要注册一次,通常放在页面初始化的时候(比如onLoad里),或者紧接在startWifi成功之后、第一次调用getWifiList之前。
// 在onLoad或初始化方法中注册监听
onLoad() {
this.initWifiListener();
},
methods: {
initWifiListener() {
wx.onGetWifiList((res) => {
console.log('监听到WiFi列表数据', res);
const wifiList = res.wifiList || [];
if (wifiList.length === 0) {
uni.showToast({ title: '未扫描到任何WiFi网络', icon: 'none' });
this.wifiList = [];
return;
}
// 对列表进行简单处理:按信号强度排序
const sortedList = wifiList.sort((a, b) => b.signalStrength - a.signalStrength);
// 处理数据,方便页面渲染
const processedList = sortedList.map(item => {
return {
ssid: item.SSID, // WiFi名称
bssid: item.BSSID, // MAC地址
secure: item.secure, // 是否加密
signalStrength: item.signalStrength, // 信号强度
// 信号强度转成图标或等级,比如0-4格
signalLevel: this.calcSignalLevel(item.signalStrength)
};
});
// 更新页面数据,触发视图渲染
this.wifiList = processedList;
uni.showToast({ title: `找到${processedList.length}个网络`, icon: 'success' });
});
},
// 一个简单的信号强度计算函数
calcSignalLevel(rssi) {
if (rssi >= -50) return 4; // 信号极好
else if (rssi >= -60) return 3; // 信号好
else if (rssi >= -70) return 2; // 信号一般
else if (rssi >= -80) return 1; // 信号弱
else return 0; // 信号极弱
}
}
现在,整个流程就串起来了:初始化模块 -> 发送扫描指令 -> 监听器自动接收数据 -> 处理并渲染数据。你可能会注意到,res.wifiList里的每个WiFi对象包含了很多字段,比如SSID(名称)、BSSID(路由器MAC地址)、secure(是否加密)、signalStrength(信号强度,单位dBm,负值,越大越好)。我通常会对列表按信号强度排个序,把最强的放在最前面,方便用户选择。同时,将信号强度这个数值转换成更直观的“信号格数”,用户体验会更好。
4. 打造一个健壮好用的WiFi列表组件
光有数据还不够,我们得把它漂亮、稳定地展示给用户,并且能处理用户交互。这一步才是功能价值的最终体现。
### 4.1 页面数据绑定与渲染
首先,我们在页面的data中定义好存放列表的变量。
data() {
return {
wifiList: [], // 处理后的WiFi列表数据
isScanning: false, // 是否正在扫描
currentConnectedSSID: '' // 当前手机已连接的WiFi名(可选)
};
}
然后,在模板中用一个v-for循环来渲染列表。这里我用一个简单的view列表展示,你可以根据自己项目的UI库(如uView、uni-ui)来美化。
<template>
<view class="container">
<view class="scan-btn">
<button @tap="handleScanWifi" :loading="isScanning">
{{ isScanning ? '扫描中...' : '点击扫描周边WiFi' }}
</button>
</view>
<view class="wifi-list" v-if="wifiList.length > 0">
<view class="list-header">
<text>选择可用的WiFi网络</text>
<text>共 {{ wifiList.length }} 个</text>
</view>
<view class="wifi-item" v-for="(item, index) in wifiList" :key="index" @tap="handleSelectWifi(item)">
<view class="wifi-info">
<view class="ssid">{{ item.ssid }}</view>
<view class="details">
<text v-if="item.secure" class="secure-lock">🔒</text>
<text class="bssid">{{ item.bssid }}</text>
</view>
</view>
<view class="wifi-signal">
<!-- 根据signalLevel显示信号格 -->
<view class="signal-bars">
<view v-for="n in 4" :key="n"
:class="['signal-bar', n <= item.signalLevel ? 'active' : '']">
</view>
</view>
<text class="strength">{{ item.signalStrength }} dBm</text>
</view>
</view>
</view>
<view class="empty-tip" v-else>
<text>暂无WiFi列表,请点击上方按钮扫描</text>
</view>
</view>
</template>
对应的CSS样式(<style>部分)你需要自己设计,比如设置.wifi-item的边框、内边距、.signal-bar的宽度颜色等,让列表看起来清晰美观。信号格可以用几个小长条view,通过active类控制高亮数量来模拟。
### 4.2 处理用户选择与连接
用户点击列表中的某个WiFi项后,我们通常需要做两件事:第一,展示该WiFi的详情(比如密码输入框);第二,调用连接API。这里我们先处理第一件事。
handleSelectWifi(wifiItem) {
console.log('用户选择了WiFi:', wifiItem);
this.selectedWifi = wifiItem; // 存储当前选中的WiFi
// 弹出底部弹窗或跳转新页面,让用户输入密码
uni.showModal({
title: `连接至 ${wifiItem.ssid}`,
content: '请输入WiFi密码',
editable: true, // 允许输入
placeholderText: '请输入密码',
success: (modalRes) => {
if (modalRes.confirm) {
const password = modalRes.content;
this.connectToWifi(wifiItem, password);
}
}
});
},
### 4.3 实现WiFi连接功能
用户输入密码后,我们调用 wx.connectWifi API 进行连接。这是一个关键操作,需要处理好各种状态。
connectToWifi(wifiItem, password) {
uni.showLoading({ title: '连接中...', mask: true });
wx.connectWifi({
SSID: wifiItem.ssid,
BSSID: wifiItem.bssid, // BSSID可选,但指定后更精确
password: password,
success: (res) => {
console.log('连接指令发送成功', res);
uni.hideLoading();
// 连接指令成功,同样不代表已连上,需要监听连接状态变化
uni.showToast({ title: '正在连接,请稍候...', icon: 'none' });
},
fail: (err) => {
console.error('连接指令失败', err);
uni.hideLoading();
let errMsg = '连接失败';
// 根据错误码给出友好提示
switch(err.errCode) {
case 12002: errMsg = '网络超时,请重试'; break;
case 12003: errMsg = '密码错误,请重新输入'; break;
case 12004: errMsg = '连接被拒绝'; break;
case 12005: errMsg = '未找到指定WiFi'; break;
default: errMsg = `连接失败: ${err.errMsg}`;
}
uni.showModal({ title: '提示', content: errMsg, showCancel: false });
}
});
// 监听WiFi连接成功事件
wx.onWifiConnected((res) => {
console.log('WiFi已连接', res);
const connectedWifi = res.wifi;
uni.showToast({ title: `已成功连接到 ${connectedWifi.SSID}`, icon: 'success' });
// 更新状态,比如清空列表或标记已连接项
this.currentConnectedSSID = connectedWifi.SSID;
// 可以在这里进行下一步业务逻辑,比如设备配网
});
}
注意,wx.connectWifi 和之前的 getWifiList 类似,它的success也只代表连接请求已发出。真正的连接结果,需要通过监听 wx.onWifiConnected 事件来获取。同时,你也应该监听 wx.onWifiConnectedFail 来处理连接失败的事件,给用户更精准的反馈。为了代码清晰,我这里只展示了成功监听。
5. 避坑指南与高级技巧
功能跑通了,但想做得更专业、更稳定,下面这些我踩过的坑和总结的技巧,你一定要看看。
### 5.1 常见错误码与排查方法
在实际使用中,你肯定会遇到各种fail回调。看懂错误码是快速解决问题的关键。我整理了几个最常见的:
| 错误码 | 含义 | 可能原因与解决方案 |
|---|---|---|
| 12000 | 未先调用 startWifi | 调用顺序错了。确保先startWifi成功,再调其他API。 |
| 12001 | 系统WiFi开关未打开 | 提示用户去手机设置打开WiFi。 |
| 12002 | 连接超时 | 网络环境复杂或信号太弱。提示用户重试,或靠近路由器。 |
| 12003 | 密码错误 | 用户输入错误。引导用户重新输入,并确保显示密码功能可用。 |
| 12004 | 连接请求被拒绝 | 路由器端拒绝了请求(如MAC过滤)。提示用户检查路由器设置。 |
| 12005 | 未找到指定SSID的网络 | 可能WiFi已隐藏,或设备已远离。重新扫描。 |
| 12006 | 系统操作失败 | 系统底层错误。尝试重启小程序或手机WiFi。 |
在fail回调里,通过err.errCode判断类型,给用户友好的提示,而不是直接抛出一串代码,体验会好很多。
### 5.2 性能优化与体验提升
- 防抖与加载状态:那个“扫描”按钮,用户可能会疯狂点击。不做处理的话会重复发送请求。我通常会用个变量
isScanning锁住按钮,并在点击后设置为true,等到列表返回或超时后再设为false。同时按钮显示“扫描中...”的加载状态。 - 定时刷新与缓存:在一些需要实时显示网络环境的场景,你可以设置一个定时器(比如每10秒自动扫描一次)。但要注意功耗和用户体验,不宜太频繁。对于静态场景,扫描一次后缓存列表,除非用户手动刷新。
- 处理隐藏网络:
wx.getWifiList获取不到隐藏网络(不广播SSID的网络)。如果需要连接隐藏网络,你需要让用户手动输入SSID,然后直接调用wx.connectWifi,并确保SSID参数准确。 - Android/iOS的细微差异:虽然都是微信小程序,但不同手机系统在权限提示、返回数据细节上可能有微小差别。真机测试时务必覆盖主流机型。
### 5.3 封装成可复用的组件或模块
如果你的项目里多个页面都需要WiFi功能,强烈建议把核心逻辑封装起来。我习惯创建一个wifiManager.js的工具模块。
// utils/wifiManager.js
class WifiManager {
constructor() {
this.isModuleReady = false;
this._initListener();
}
// 初始化监听
_initListener() {
wx.onGetWifiList((res) => {
if (this.onWifiListUpdate) {
this.onWifiListUpdate(res.wifiList);
}
});
wx.onWifiConnected((res) => {
if (this.onWifiConnected) {
this.onWifiConnected(res.wifi);
}
});
}
// 公开方法:开始扫描
scan() {
return new Promise((resolve, reject) => {
if (!this.isModuleReady) {
wx.startWifi({
success: () => {
this.isModuleReady = true;
this._doGetWifiList(resolve, reject);
},
fail: reject
});
} else {
this._doGetWifiList(resolve, reject);
}
});
}
_doGetWifiList(resolve, reject) {
wx.getWifiList({
success: resolve,
fail: reject
});
}
// 公开方法:连接WiFi
connect({ SSID, BSSID, password }) {
return new Promise((resolve, reject) => {
wx.connectWifi({
SSID,
BSSID,
password,
success: resolve,
fail: reject
});
});
}
}
export default new WifiManager();
然后在页面中,你可以这样优雅地使用:
import wifiManager from '@/utils/wifiManager.js';
export default {
methods: {
async handleScan() {
try {
this.isScanning = true;
await wifiManager.scan();
// 监听回调会通过 onWifiListUpdate 触发
} catch (err) {
// 处理错误
} finally {
this.isScanning = false;
}
}
},
onLoad() {
// 设置回调
wifiManager.onWifiListUpdate = (list) => {
this.wifiList = this.processList(list);
};
wifiManager.onWifiConnected = (wifi) => {
console.log('连接成功回调', wifi);
};
}
}
这样封装后,代码清晰多了,业务页面只关心数据和UI,所有WiFi相关的底层调用和状态管理都交给了wifiManager。
更多推荐



所有评论(0)