From 6d3e975f49cf99458a4a5ac13790a0f2ca24ade5 Mon Sep 17 00:00:00 2001
From: Ironsp <1522278303@qq.com>
Date: Sat, 12 Sep 2026 16:28:37 +0800
Subject: [PATCH] =?UTF-8?q?feat=20=E5=A2=9E=E5=8A=A0=E5=AE=9A=E4=BD=8D?=
=?UTF-8?q?=E6=9D=83=E9=99=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
manifest.config.ts | 6 +-
src/auto-imports.d.ts | 5 +
src/components/settings/wifiNetwork.vue | 10 +-
src/utils/wifi.ts | 493 ++++++++++++++++++++++++
4 files changed, 511 insertions(+), 3 deletions(-)
create mode 100644 src/utils/wifi.ts
diff --git a/manifest.config.ts b/manifest.config.ts
index 2446468..9aad389 100644
--- a/manifest.config.ts
+++ b/manifest.config.ts
@@ -34,12 +34,14 @@ export default defineManifestConfig({
'',
'',
'',
- '',
+ '',
'',
+ '',
+ '',
+ '',
'',
'',
'',
- '',
'',
'',
'',
diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts
index 956bb72..ebdb95e 100644
--- a/src/auto-imports.d.ts
+++ b/src/auto-imports.d.ts
@@ -329,6 +329,7 @@ declare global {
const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
const whenever: typeof import('@vueuse/core')['whenever']
+ const wifi: typeof import('./utils/wifi')['default']
}
// for type re-export
declare global {
@@ -338,6 +339,9 @@ declare global {
// @ts-ignore
export type { SystemInfo } from './utils/device'
import('./utils/device')
+ // @ts-ignore
+ export type { WifiInfo, ConnectWifiParams, WifiStateCallback, WifiScanCallback } from './utils/wifi'
+ import('./utils/wifi')
}
// for vue template auto import
@@ -667,5 +671,6 @@ declare module 'vue' {
readonly watchTriggerable: UnwrapRef
readonly watchWithFilter: UnwrapRef
readonly whenever: UnwrapRef
+ readonly wifi: UnwrapRef
}
}
\ No newline at end of file
diff --git a/src/components/settings/wifiNetwork.vue b/src/components/settings/wifiNetwork.vue
index 2e4750f..84462ee 100644
--- a/src/components/settings/wifiNetwork.vue
+++ b/src/components/settings/wifiNetwork.vue
@@ -1,4 +1,6 @@
@@ -42,7 +50,7 @@ function handleConnect(name: string) {
可用网络
-
+
diff --git a/src/utils/wifi.ts b/src/utils/wifi.ts
new file mode 100644
index 0000000..47b168d
--- /dev/null
+++ b/src/utils/wifi.ts
@@ -0,0 +1,493 @@
+/**
+ * WiFi 工具类 (Android App 专用)
+ * 用于 uni-app Android 平台 WiFi 操作
+ *
+ * ======================== 使用说明 ========================
+ *
+ * import wifi from '@/utils/wifi'
+ *
+ * // 检查 WiFi 是否可用
+ * const available = await wifi.isWifiAvailable()
+ *
+ * // 获取当前连接信息
+ * const info = await wifi.getConnectedWifi()
+ *
+ * // 扫描附近 WiFi
+ * const networks = await wifi.scanWifi()
+ *
+ * // 连接指定 WiFi
+ * await wifi.connectWifi({ SSID: 'MyWiFi', password: '12345678' })
+ *
+ * // 断开当前连接
+ * await wifi.disconnectWifi()
+ *
+ * // 连接已保存的 WiFi(免密)
+ * await wifi.connectSavedWifi({ SSID: 'MyWiFi' })
+ *
+ * // 获取已保存的 WiFi 列表
+ * const savedList = await wifi.getSavedWifiList()
+ *
+ * // 移除已保存的 WiFi
+ * await wifi.removeSavedWifi({ SSID: 'MyWiFi' })
+ *
+ * // 监听 WiFi 状态变化
+ * const unwatch = wifi.onWifiStateChanged((state) => {
+ * console.log(state.isConnected ? '已连接' : '未连接')
+ * })
+ * unwatch() // 取消监听
+ *
+ * // 监听扫描结果
+ * const unwatch = wifi.onWifiScan((networks) => {
+ * console.log('扫描到', networks.length, '个网络')
+ * })
+ * unwatch() // 取消监听
+ *
+ * // 获取信号强度描述
+ * wifi.getSignalDescription(-55) // "强"
+ *
+ * // 判断频段
+ * wifi.is5GHz(5180) // true
+ *
+ * ======================== 前置条件 ========================
+ *
+ * - 仅支持 Android App 环境
+ * - 需要在 manifest.json 配置权限:
+ * - android.permission.ACCESS_WIFI_STATE
+ * - android.permission.CHANGE_WIFI_STATE
+ * - android.permission.ACCESS_NETWORK_STATE
+ */
+
+// ======================== 类型定义 ========================
+
+/** WiFi 网络信息 */
+export interface WifiInfo {
+ SSID: string
+ BSSID: string
+ signalStrength: number
+ securityType?: string
+ frequency?: number
+ connected?: boolean
+}
+
+/** 连接 WiFi 参数 */
+export interface ConnectWifiParams {
+ SSID: string
+ password?: string
+ timeout?: number
+}
+
+/** WiFi 状态变化回调 */
+export type WifiStateCallback = (state: {
+ isConnected: boolean
+ wifiInfo: WifiInfo | null
+}) => void
+
+/** WiFi 扫描结果回调 */
+export type WifiScanCallback = (networks: WifiInfo[]) => void
+
+// ======================== 内部工具 ========================
+
+const listeners = {
+ stateChange: new Map(),
+ scan: new Map(),
+}
+
+let receiverId = 0
+
+/** 获取 WiFi Manager 实例 */
+function getWifiManager() {
+ const ctx = (plus.android as any).importClass('android.content.Context')
+ const activity = (plus.android as any).runtimeMainActivity()
+ return activity.getSystemService(ctx.WIFI_SERVICE)
+}
+
+/** 调用 Android 方法 */
+function invoke(obj: any, method: string, ...args: any[]) {
+ return (plus.android as any).invoke(obj, method, ...args)
+}
+
+/** 导入 Android 类 */
+function importClass(name: string) {
+ return (plus.android as any).importClass(name)
+}
+
+// ======================== WiFi 工具 ========================
+
+const wifi = {
+ /** 检查 WiFi 是否可用 */
+ isWifiAvailable(): Promise {
+ return new Promise((resolve) => {
+ try {
+ const wm = getWifiManager()
+ resolve(!!invoke(wm, 'isWifiEnabled'))
+ }
+ catch {
+ resolve(false)
+ }
+ })
+ },
+
+ /** 获取当前 WiFi 连接信息 */
+ getConnectedWifi(): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ const wm = getWifiManager()
+ const info = invoke(wm, 'getConnectionInfo')
+ const ssid = invoke(info, 'getSSID')
+ const bssid = invoke(info, 'getBSSID')
+ const rssi = invoke(info, 'getRssi')
+ const networkId = invoke(info, 'getNetworkId')
+
+ if (!ssid || ssid === '' || networkId === -1) {
+ resolve(null)
+ return
+ }
+
+ resolve({
+ SSID: ssid.replace(/"/g, ''),
+ BSSID: bssid || '',
+ signalStrength: rssi,
+ connected: true,
+ })
+ }
+ catch (err) {
+ reject(err)
+ }
+ })
+ },
+
+ /** 扫描附近 WiFi 网络 */
+ scanWifi(): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ const wm = getWifiManager()
+
+ // 检查 WiFi 是否开启
+ if (!invoke(wm, 'isWifiEnabled')) {
+ console.warn('WiFi 未开启,无法扫描')
+ resolve([])
+ return
+ }
+
+ // 启动扫描
+ const started = invoke(wm, 'startScan')
+ console.log('WiFi 扫描启动:', started)
+
+ // 等待 3 秒确保扫描完成
+ setTimeout(() => {
+ try {
+ const results = invoke(wm, 'getScanResults')
+ if (!results) {
+ console.log('扫描结果为 null')
+ resolve([])
+ return
+ }
+
+ const size = invoke(results, 'size')
+ console.log('扫描到网络数量:', size)
+
+ const networks: WifiInfo[] = []
+
+ for (let i = 0; i < size; i++) {
+ const r = invoke(results, 'get', i)
+ const ssid = (invoke(r, 'SSID') || '').replace(/"/g, '')
+ if (!ssid || ssid === '')
+ continue
+
+ networks.push({
+ SSID: ssid,
+ BSSID: invoke(r, 'BSSID') || '',
+ signalStrength: invoke(r, 'level') || 0,
+ securityType: invoke(r, 'capabilities') || '',
+ frequency: invoke(r, 'frequency') || 0,
+ connected: false,
+ })
+ }
+
+ console.log('解析后网络数量:', networks.length)
+ resolve(networks)
+ }
+ catch (err) {
+ console.error('获取扫描结果失败:', err)
+ reject(err)
+ }
+ }, 3000)
+ }
+ catch (err) {
+ console.error('扫描启动失败:', err)
+ reject(err)
+ }
+ })
+ },
+
+ /** 连接指定 WiFi */
+ connectWifi(params: ConnectWifiParams): Promise {
+ return new Promise((resolve, reject) => {
+ const { SSID, password, timeout = 10000 } = params
+ if (!SSID) {
+ reject(new Error('SSID 不能为空'))
+ return
+ }
+
+ try {
+ const wm = getWifiManager()
+ const Config = importClass('android.net.wifi.WifiConfiguration')
+ const config = new Config()
+
+ invoke(config, 'SSID', `"${SSID}"`)
+ config.networkId = -1
+
+ const keyMgmt = invoke(config, 'allowedKeyManagement')
+ if (password) {
+ config.preSharedKey = `"${password}"`
+ invoke(keyMgmt, 'set', 1) // WPA_PSK
+ }
+ else {
+ invoke(keyMgmt, 'set', 0) // NONE
+ }
+
+ const networkId = invoke(wm, 'addNetwork', config)
+
+ if (networkId === -1) {
+ const networks = invoke(wm, 'getConfiguredNetworks')
+ const size = invoke(networks, 'size')
+ for (let i = 0; i < size; i++) {
+ const existing = invoke(networks, 'get', i)
+ if (invoke(existing, 'SSID') === `"${SSID}"`) {
+ invoke(wm, 'updateNetwork', existing, config)
+ invoke(wm, 'disconnect')
+ invoke(wm, 'enableNetwork', invoke(existing, 'networkId'), true)
+ invoke(wm, 'reconnect')
+ resolve()
+ return
+ }
+ }
+ reject(new Error('无法连接到网络'))
+ return
+ }
+
+ invoke(wm, 'disconnect')
+ invoke(wm, 'enableNetwork', networkId, true)
+ invoke(wm, 'reconnect')
+
+ let elapsed = 0
+ const timer = setInterval(() => {
+ const info = invoke(wm, 'getConnectionInfo')
+ if (invoke(info, 'getSupplicantState') === 3) {
+ clearInterval(timer)
+ resolve()
+ return
+ }
+ elapsed += 500
+ if (elapsed >= timeout) {
+ clearInterval(timer)
+ reject(new Error('连接超时'))
+ }
+ }, 500)
+ }
+ catch (err) {
+ reject(err)
+ }
+ })
+ },
+
+ /** 断开当前 WiFi 连接 */
+ disconnectWifi(): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ const wm = getWifiManager()
+ invoke(wm, 'disableNetwork', -1)
+ const result = invoke(wm, 'disconnect')
+ result ? resolve() : reject(new Error('断开连接失败'))
+ }
+ catch (err) {
+ reject(err)
+ }
+ })
+ },
+
+ /** 连接已保存的 WiFi(免密) */
+ connectSavedWifi(params: { SSID: string }): Promise {
+ return new Promise((resolve, reject) => {
+ const { SSID } = params
+ try {
+ const wm = getWifiManager()
+ const networks = invoke(wm, 'getConfiguredNetworks')
+ const size = invoke(networks, 'size')
+
+ for (let i = 0; i < size; i++) {
+ const config = invoke(networks, 'get', i)
+ if (invoke(config, 'SSID') === `"${SSID}"`) {
+ invoke(wm, 'disconnect')
+ invoke(wm, 'enableNetwork', invoke(config, 'networkId'), true)
+ invoke(wm, 'reconnect')
+ resolve()
+ return
+ }
+ }
+
+ reject(new Error(`未找到已保存的网络: ${SSID}`))
+ }
+ catch (err) {
+ reject(err)
+ }
+ })
+ },
+
+ /** 获取已保存的 WiFi 列表 */
+ getSavedWifiList(): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ const wm = getWifiManager()
+ const networks = invoke(wm, 'getConfiguredNetworks')
+ const size = invoke(networks, 'size')
+ const result: WifiInfo[] = []
+
+ for (let i = 0; i < size; i++) {
+ const config = invoke(networks, 'get', i)
+ const ssid = invoke(config, 'SSID').replace(/"/g, '')
+ if (!ssid)
+ continue
+
+ result.push({
+ SSID: ssid,
+ BSSID: invoke(config, 'BSSID') || '',
+ signalStrength: 0,
+ connected: false,
+ })
+ }
+
+ resolve(result)
+ }
+ catch (err) {
+ reject(err)
+ }
+ })
+ },
+
+ /** 移除已保存的 WiFi 网络 */
+ removeSavedWifi(params: { SSID: string }): Promise {
+ return new Promise((resolve, reject) => {
+ const { SSID } = params
+ try {
+ const wm = getWifiManager()
+ const networks = invoke(wm, 'getConfiguredNetworks')
+ const size = invoke(networks, 'size')
+
+ for (let i = 0; i < size; i++) {
+ const config = invoke(networks, 'get', i)
+ if (invoke(config, 'SSID') === `"${SSID}"`) {
+ invoke(wm, 'removeNetwork', invoke(config, 'networkId'))
+ invoke(wm, 'saveConfiguration')
+ resolve()
+ return
+ }
+ }
+
+ reject(new Error(`未找到已保存的网络: ${SSID}`))
+ }
+ catch (err) {
+ reject(err)
+ }
+ })
+ },
+
+ /** 监听 WiFi 状态变化 */
+ onWifiStateChanged(callback: WifiStateCallback): () => void {
+ const id = `state_${++receiverId}`
+ listeners.stateChange.set(id, callback)
+
+ try {
+ const activity = (plus.android as any).runtimeMainActivity()
+ const Filter = importClass('android.content.IntentFilter')
+ const Receiver = importClass('android.content.BroadcastReceiver')
+
+ const filter = new Filter()
+ filter.addAction('android.net.wifi.STATE_CHANGE')
+
+ const receiver = new Receiver({
+ onReceive(_ctx: any, intent: any) {
+ const action = invoke(intent, 'getAction')
+ if (action !== 'android.net.wifi.STATE_CHANGE')
+ return
+
+ const networkInfo = invoke(intent, 'getParcelableExtra', 'networkInfo')
+ const state = invoke(networkInfo, 'getState')
+
+ if (state === 2) {
+ wifi.getConnectedWifi().then(info => callback({ isConnected: true, wifiInfo: info }))
+ }
+ else {
+ callback({ isConnected: false, wifiInfo: null })
+ }
+ },
+ })
+
+ activity.registerReceiver(receiver, filter)
+ }
+ catch {}
+
+ return () => listeners.stateChange.delete(id)
+ },
+
+ /** 监听 WiFi 扫描结果 */
+ onWifiScan(callback: WifiScanCallback): () => void {
+ const id = `scan_${++receiverId}`
+ listeners.scan.set(id, callback)
+
+ try {
+ const activity = (plus.android as any).runtimeMainActivity()
+ const Filter = importClass('android.content.IntentFilter')
+ const Receiver = importClass('android.content.BroadcastReceiver')
+
+ const filter = new Filter()
+ filter.addAction('android.net.wifi.SCAN_RESULTS')
+
+ const receiver = new Receiver({
+ onReceive() {
+ wifi.scanWifi().then(callback)
+ },
+ })
+
+ activity.registerReceiver(receiver, filter)
+ }
+ catch {}
+
+ return () => listeners.scan.delete(id)
+ },
+
+ /** 获取信号强度等级 (0-4) */
+ getSignalLevel(rssi: number): number {
+ if (rssi >= -50)
+ return 4
+ if (rssi >= -60)
+ return 3
+ if (rssi >= -70)
+ return 2
+ if (rssi >= -80)
+ return 1
+ return 0
+ },
+
+ /** 获取信号强度描述 */
+ getSignalDescription(rssi: number): string {
+ const level = this.getSignalLevel(rssi)
+ return ['无信号', '弱', '一般', '强', '非常强'][level] || '未知'
+ },
+
+ /** 判断是否为 5GHz 频段 */
+ is5GHz(frequency: number): boolean {
+ return frequency >= 5000 && frequency <= 5900
+ },
+
+ /** 获取频段描述 */
+ getFrequencyDescription(frequency: number): string {
+ if (this.is5GHz(frequency))
+ return '5GHz'
+ if (frequency >= 2400 && frequency <= 2500)
+ return '2.4GHz'
+ return '未知'
+ },
+}
+
+export default wifi