1manifest.json配置权限

说明:
-
WiFi:启用uni.startWifi / uni.getWifiList / uni.onGetWifiList等 API -
Geolocation:Wi-Fi 扫描在 Android 8+ 必须依赖定位模块
✅ 这是正确且必须 的配置
⚠️ 没有 Geolocation,Wi-Fi 列表在真机上会直接返回空
2UniApp 权限弹窗描述(Android 13+ 必须)
"permissions": {
"Location": {
"desc": "应用需要访问位置信息"
},
"NearbyWiFiDevices": {
"desc": "应用需要扫描附近 Wi-Fi 设备"
},
"AccessNetworkState": {
"desc": "应用需要访问网络状态"
},
"ChangeNetworkState": {
"desc": "应用需要更改网络状态"
},
"ChangeWiFiState": {
"desc": "应用需要更改 Wi-Fi 状态"
}
}
说明:
-
这些
desc是:- Android 12+ / 13+ 弹窗文案来源
-
没有
desc:-
打包正常
-
真机运行 → 权限请求失败 / 静默拒绝
-
✅ 你这里是完整且合规的
3Android 权限声明(Manifest 层)
"distribute": {
"android": {
/* android打包配置 */
"permissions": [
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.NEARBY_WIFI_DEVICES\"/>"
]
}
}
下面是我的完整配置
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.NEARBY_WIFI_DEVICES\"/>" //Android 13+ 必须
]
vue业务代码
html
<template>
<view class="wifi-wrapper">
<view class="wifi-container">
<!-- 顶部当前连接信息 -->
<view class="title">当前连接</view>
<view class="current-connection">
<text class="wifi-info">
<image src="/static/icons/WiFiok.svg" class="icon" />
{{ currentWifi.SSID || '未连接' }}
</text>
<text class="connection-status" v-if="currentWifi.SSID">已连接</text>
<text class="connection-status" v-else>未连接</text>
</view>
<!-- Tab 切换 -->
<view class="tab-container">
<text class="tab-item" :style="{ color: activeTab === 'available' ? '#fff' : '#888' }"
@click="switchTab('available')">
本地可连接WiFi
</text>
<text class="tab-item" :style="{ color: activeTab === 'history' ? '#fff' : '#888' }"
@click="switchTab('history')">
历史连接WiFi
</text>
</view>
<!-- WiFi 列表 -->
<scroll-view class="wifi-list" scroll-y="true">
<view v-for="(wifi, index) in displayedWifiList" :key="index" class="wifi-item">
<view class="wifi-left">
<image :src="getSignalIcon(typeof wifi.signal === 'number' ? wifi.signal : signalToPercent(wifi.signal))"
class="signal-icon" />
<text class="wifi-name">{{ wifi.SSID }}</text>
</view>
<text class="connect-btn" :style="{ color: activeTab === 'available' ? '#4e9dc2' : '#888' }"
@click="connectToWifi(wifi)">
{{ activeTab === 'available' ? '立即连接' : '连接' }}
</text>
</view>
</scroll-view>
<!-- 底部按钮 -->
<view class="button-container">
<button class="back-btn" @click="goBack">返回</button>
<button class="next-btn" @click="goToNext">下一步</button>
</view>
</view>
<view class="side"></view>
<!-- 弹窗组件 -->
<uni-popup ref="isModalVisible" @close="closeModal">
<view class="popupContent">
<view class="top">
<view>连接网络</view>
<uni-icons type="closeempty" size="18" color="#fff" @click="closeModal"></uni-icons>
</view>
<view class="popcontent">
<view class="title">
请输入
<text class="colortitle">{{ wifiInfo }}</text>的密码
</view>
<view class="popinpbox">
<uni-easyinput v-model="passwordvalue" type="password" :clearable="false" passwordIcon
placeholder="请输入密码" />
</view>
</view>
<view class="foolter">
<view class="cancel" @click="closeModal">取消</view>
<view class="confirm" @click="handleConfirm">连接</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
<script setup>
import { ref, computed, onMounted } from 'vue'
// ---------- 响应式变量 ----------
const isModalVisible = ref(false)
const useinfo = ref({})
const wifi = ref({})
const wifiInfo = ref('')
const passwordvalue = ref('')
const activeTab = ref('available')
const currentWifi = ref({ SSID: '' })
const wifiList = ref([])
const historyWifiList = ref([])
const selectedWifi = ref({})
// ---------- 信号强度辅助 ----------
const getSignalIcon = (signal) => {
if (!signal && signal !== 0) return '/static/icons/wifi4.svg'
if (signal <= 25) return '/static/icons/wifi3.svg'
if (signal <= 50) return '/static/icons/wifi2.svg'
if (signal <= 75) return '/static/icons/wifi1.svg'
return '/static/icons/wifi3.svg'
}
// ---------- 显示列表 ----------
const displayedWifiList = computed(() => {
return activeTab.value === 'available' ? wifiList.value : historyWifiList.value
})
// ---------- Tab 切换 ----------
const switchTab = (tab) => {
activeTab.value = tab
if (tab === 'history') loadHistoryWifi()
}
// ---------- 历史 Wi-Fi ----------
const saveHistoryWifi = (wifi) => {
let history = uni.getStorageSync('historyWifi') || []
const existIndex = history.findIndex(item => item.SSID === wifi.SSID)
if (existIndex >= 0) history.splice(existIndex, 1)
history.unshift(wifi)
if (history.length > 20) history = history.slice(0, 20)
uni.setStorageSync('historyWifi', history)
}
const loadHistoryWifi = () => {
historyWifiList.value = uni.getStorageSync('historyWifi') || []
}
// ---------- 连接 Wi-Fi ----------
const connectToWifi = (wifiObj) => {
selectedWifi.value = wifiObj
wifiInfo.value = wifiObj.SSID || ''
if (!wifiInfo.value) return
isModalVisible.value.open()
}
const closeModal = () => {
isModalVisible.value.close()
passwordvalue.value = ''
}
const handleConfirm = () => {
console.log(333333333);
if (!passwordvalue.value) {
uni.showToast({ title: '请输入密码', icon: 'none' })
return
}
uni.showLoading({ title: '连接中...' })
uni.connectWifi({
SSID: selectedWifi.value.SSID,
password: passwordvalue.value, // 只传密码
success: () => {
uni.hideLoading()
uni.showToast({ title: `已连接 ${selectedWifi.value.SSID}`, icon: 'success' })
saveHistoryWifi(selectedWifi.value)
currentWifi.value = selectedWifi.value
closeModal()
},
fail: (err) => {
uni.hideLoading()
uni.showToast({ title: '连接失败,请检查密码', icon: 'none' })
console.error('连接 Wi-Fi 失败', err)
}
})
}
// ---------- 页面跳转和登录 ----------
const goBack = () => uni.navigateTo({ url: '/pages/login/index' })
const goToNext = () => {
useinfo.value.userWifiSsid = currentWifi.value.SSID
useinfo.value.userEquipmentNumber = useinfo.value.userName
uni.setStorageSync('useinfo', useinfo.value)
const wifiInfoStr = encodeURIComponent(JSON.stringify(currentWifi.value))
uni.navigateTo({
url: `/sub-device/user/index?wifiInfo=${wifiInfoStr}`
})
}
// ---------- 初始化 Wi-Fi 插件 ----------
const initWifiPlugin = () => {
// Android App-plus 原生获取位置权限
// Android App-plus 原生动态申请 Wi-Fi 所需权限
if (plus && plus.os.name === 'Android') {
const main = plus.android.runtimeMainActivity()
const PackageManager = plus.android.importClass('android.content.pm.PackageManager')
const permission = plus.android.importClass('android.Manifest$permission')
const ActivityCompat = plus.android.importClass('androidx.core.app.ActivityCompat')
// 需要申请的权限列表
const permissions = [
permission.ACCESS_FINE_LOCATION,
permission.ACCESS_COARSE_LOCATION,
permission.ACCESS_WIFI_STATE,
permission.CHANGE_WIFI_STATE,
permission.INTERNET,
permission.ACCESS_NETWORK_STATE
]
// 检查哪些权限未被授予
const needRequest = permissions.filter(p =>
plus.android.invoke(main, 'checkSelfPermission', p) !== PackageManager.PERMISSION_GRANTED
)
if (needRequest.length > 0) {
ActivityCompat.requestPermissions(main, needRequest, 0)
console.log('请求 Wi-Fi 相关权限中...')
return
}
}
uni.getLocation({
type: 'wgs84',
success() {
// 初始化 Wi-Fi
startWifiScan()
},
fail() {
uni.showModal({
title: '提示',
content: '请开启手机定位服务,否则无法扫描 Wi-Fi',
showCancel: false
})
}
})
// 获取当前已连接 Wi-Fi
uni.getConnectedWifi({
success(res) {
currentWifi.value = res.wifi
},
fail(err) {
uni.showToast({ title: '获取当前连接 Wi-Fi失败', icon: 'none' })
console.error('获取当前连接 Wi-Fi失败', err)
}
})
// uni.startWifi({
// success() {
// console.log('Wi-Fi 初始化成功')
// // 获取当前已连接 Wi-Fi
// uni.getConnectedWifi({
// success(res) {
// currentWifi.value = res.wifi
// },
// fail(err) {
// console.error('获取当前连接 Wi-Fi失败', err)
// uni.showToast({ title: '获取当前连接 Wi-Fi失败', icon: 'none' })
// }
// })
// // 扫描 Wi-Fi
// uni.onGetWifiList(res => {
// console.log('扫描到 Wi-Fi 列表', res.wifiList)
// wifiList.value = res.wifiList.map(item => ({
// SSID: item.SSID,
// BSSID: item.BSSID,
// signal: item.signalStrength || item.signal,
// capabilities: item.security
// }))
// })
// // ⚠️ 必须传一个空对象 {} 避免 NullPointerException
// uni.getWifiList({})
// },
// fail(err) {
// console.error('Wi-Fi 初始化失败', err)
// }
// })
}
// 抽离 Wi-Fi 扫描逻辑
const startWifiScan = () => {
uni.startWifi({
success() {
console.log('Wi-Fi 初始化成功')
uni.getConnectedWifi({
success(res) {
currentWifi.value = res.wifi
},
fail(err) {
console.error('获取当前连接 Wi-Fi失败', err)
uni.showToast({ title: '获取当前 Wi-Fi 失败', icon: 'none' })
}
})
uni.onGetWifiList(res => {
console.log('扫描到 Wi-Fi 列表', res.wifiList)
wifiList.value = res.wifiList.map(item => ({
SSID: item.SSID,
BSSID: item.BSSID,
signal: item.signalStrength || item.signal,
capabilities: item.security
}))
})
uni.getWifiList({})
},
fail(err) {
console.error('Wi-Fi 初始化失败', err)
uni.showToast({ title: 'Wi-Fi 初始化失败,请检查权限', icon: 'none' })
}
})
}
// ---------- 页面初始化 ----------
onMounted(() => {
useinfo.value = uni.getStorageSync('useinfo') || {}
setTimeout(() => {
initWifiPlugin()
}, 600);
loadHistoryWifi()
})
</script>