提交 edbd7400 作者: 方治民

feat: 添加 usePermissions hook 组件用来实现通用的 Android 权限监听

上级 a0b9d9b2
......@@ -161,5 +161,6 @@
"engines": {
"node": ">=18",
"pnpm": ">=9.0.0"
}
},
"appName": "基础工程"
}
<script setup lang="ts">
// import * as Push from '@/utils/push'
import { onExit } from '@dcloudio/uni-app'
import { isDevMode } from '@/utils/env'
import { usePermissions } from '@/hooks/app/usePermissions'
// import * as Push from '@/utils/push'
// 监听权限获取
const { listen, stop } = usePermissions()
onLaunch(() => {
console.log('App Launch')
// #ifdef APP-PLUS
// 监听权限获取
listen()
// #endif
// 清除消息角标
// Push.setBadge(0)
......@@ -21,6 +32,11 @@
// #endif
})
onExit(() => {
// 停用监听权限
stop()
})
onShow(() => {
console.log('App Show')
})
......
import { debounce } from 'lodash-es'
import { Storage } from '@/utils/storage'
export interface Permissions {
listen: () => void
stop: () => void
gotoAppPermissionSetting: () => void
}
export interface RequestPermissionListener {
onRequest: (e: any) => void
onConfirm: (e: any) => void
onComplete: (e: any) => void
stop: () => void
}
/**
* 权限申请说明(注:仅适用于 Android)
* @link https://uniapp.dcloud.net.cn/tutorial/app-nativeresource-android.html#permissions
*/
export function getRequestPermissionTipMessage(id: string) {
switch (id) {
case 'android.permission.INTERNET':
return { name: '网络权限', message: '允许访问网络,便于使用应用相关功能。' }
case 'android.permission.CAMERA':
return {
name: '相机权限',
message:
'允许访问摄像头,便于拍照上传照片/图片用于意见反馈、保存相册、缓存数据提升访问速度等场景中拍摄照片。',
}
case 'android.permission.READ_MEDIA_IMAGES':
case 'android.permission.READ_EXTERNAL_STORAGE':
case 'android.permission.WRITE_EXTERNAL_STORAGE':
return {
name: '存储权限',
message:
'允许访问存储,便于上传照片/图片用于意见反馈、保存相册、缓存数据提升访问速度等场景中读取和写入相册和文件内容。',
}
case 'android.permission.READ_PHONE_STATE':
case 'android.permission.ACCESS_NETWORK_STATE':
case 'android.permission.ACCESS_WIFI_STATE':
return {
name: '设备状态权限',
message: '允许访问设备状态,便于收集应用异常信息。',
}
case 'android.permission.ACCESS_COARSE_LOCATION':
case 'android.permission.ACCESS_FINE_LOCATION':
case 'android.permission.ACCESS_LOCATION_EXTRA_COMMANDS':
case 'android.permission.ACCESS_MOCK_LOCATION':
return {
name: '位置权限',
message: '允许访问位置信息,便于确认上传随手拍问题时的所在位置。',
}
case 'android.permission.INSTALL_PACKAGES':
case 'android.permission.REQUEST_INSTALL_PACKAGES':
return {
name: '安装应用权限',
message: '允许访问安装应用,便于应用更新包的安装。',
}
}
}
export function usePermissions(): Permissions {
const system = uni.getSystemInfoSync()
const listener = ref<RequestPermissionListener>()
const requestPermissionId = ref<string>()
const show = ref(false)
const debounceShow = debounce((value) => {
show.value = value
}, 200)
watch(
() => show.value,
(value) => {
const id = requestPermissionId.value
if (value) {
const statusBarHeight = system.statusBarHeight
const navigationBarHeight = system.platform === 'android' ? 48 : 44
const totalHeight = statusBarHeight + navigationBarHeight
let view = new plus.nativeObj.View(id, {
top: '0px',
left: '0px',
width: '100%',
backgroundColor: 'rgba(0,0,0,0.3)',
})
view.drawRect(
{
color: '#fff',
radius: '5px',
},
{
top: `${totalHeight}px`,
left: '5%',
width: '90%',
height: '100px',
},
)
view.drawText(
'权限使用说明',
{
top: `${totalHeight + 5}px`,
left: '8%',
height: '30px',
},
{
align: 'left',
color: '#000',
size: '20px',
},
)
view.drawText(
getRequestPermissionTipMessage(id)?.message,
{
top: `${totalHeight + 35}px`,
height: '60px',
left: '8%',
width: '84%',
},
{
whiteSpace: 'normal',
size: '14px',
align: 'left',
color: '#656563',
},
)
view.show()
view = null
} else {
// @ts-expect-error
let view = plus.nativeObj.View.getViewById(id)
view && view.close()
view = null
requestPermissionId.value = null
}
},
)
function listen() {
// #ifdef APP-PLUS
// FIXED: 由于 iOS 下无问题,此处仅处理安卓平台的权限申请问题
if (system.platform === 'android') {
// @ts-expect-error
listener.value = uni.createRequestPermissionListener()
listener.value.onConfirm((e: string[]) => {
console.log('onConfirm', e)
// 创建弹窗视图
requestPermissionId.value = e[0]
debounceShow(true)
})
listener.value.onComplete((e: string[]) => {
console.log('onComplete', e)
// 关闭弹窗视图
const id = e[0]
// 判断是否触发了对应的权限显示
if (requestPermissionId.value !== id && Storage.get(id) !== 'granted') {
if (Storage.get(id) === 'denied') {
handlerPermissionModal(id)
} else {
plus.android.requestPermissions(e, (v) => {
console.log('requestPermissions', e, v)
if (v.granted.includes(id)) {
Storage.set(id, 'granted')
}
if (v.deniedAlways.includes(id)) {
Storage.set(id, 'denied')
handlerPermissionModal(id)
}
})
}
} else {
requestPermissionId.value = id
debounceShow(false)
}
})
console.log('[usePermissions] start listen...')
}
// #endif
}
function stop() {
// #ifdef APP-PLUS
listener.value?.stop()
// 清除权限标记,防止用户主动设置了权限允许重新打开仍然无法使用的问题
Storage.keys().forEach((key) => {
if (key.startsWith('android.permission.')) {
Storage.remove(key)
}
})
console.log('[usePermissions] stop listen')
// #endif
}
// 权限设置弹窗
const settingConfirmVisible = ref(false)
function handlerPermissionModal(id: string) {
if (settingConfirmVisible.value) {
return
}
settingConfirmVisible.value = true
Message.confirm(`缺少${getRequestPermissionTipMessage(id).name},立即去设置开启?`, '温馨提示', '去设置').then(
(confirm) => {
settingConfirmVisible.value = false
if (confirm) {
try {
Storage.remove(id)
gotoAppPermissionSetting()
} catch (e) {
console.error('[Permissions] gotoAppPermissionSetting fail!', e)
}
}
},
)
}
// 跳转到 APP 权限设置
function gotoAppPermissionSetting() {
// #ifdef APP-PLUS
if (system.platform === 'ios') {
const UIApplication = plus.ios.importClass('UIApplication')
const application = UIApplication.sharedApplication()
const NSURL = plus.ios.importClass('NSURL')
const setting = NSURL.URLWithString('app-settings:')
application.openURL(setting)
plus.ios.deleteObject(setting)
plus.ios.deleteObject(NSURL)
plus.ios.deleteObject(application)
} else {
// console.log(plus.device.vendor);
const Intent = plus.android.importClass('android.content.Intent')
const Settings = plus.android.importClass('android.provider.Settings')
const Uri = plus.android.importClass('android.net.Uri')
const mainActivity = plus.android.runtimeMainActivity()
// @ts-expect-error
const intent = new Intent()
// @ts-expect-error
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
// @ts-expect-error
const uri = Uri.fromParts('package', mainActivity.getPackageName(), null)
intent.setData(uri)
// @ts-expect-error
mainActivity.startActivity(intent)
}
// #endif
}
return {
listen,
stop,
gotoAppPermissionSetting,
}
}
{
"name" : "Beta App",
"appid" : "__UNI__2E9441A",
"description" : "APP 基础工程",
"versionName" : "1.0.1",
"versionCode" : 10001,
"transformPx" : false,
"locale" : "zh-Hans",
"vueVersion" : "3",
"name": "Beta App",
"appid": "__UNI__2E9441A",
"description": "APP 基础工程",
"versionName": "1.0.1",
"versionCode": 10001,
"transformPx": false,
"locale": "zh-Hans",
"vueVersion": "3",
/* 5+App特有相关 */// 配置文件详细说明
// https://uniapp.dcloud.net.cn/collocation/manifest-app.html#full-manifest
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : false,
"autoclose" : false,
"waiting" : true
},
"screenOrientation" : [ "portrait-primary", "landscape-primary" ],
"compatible" : {
"app-plus": {
"usingComponents": true,
"nvueCompiler": "uni-app",
"nvueStyleCompiler": "uni-app",
"nvueLaunchMode": "fast",
"compilerVersion": 3,
"splashscreen": {
"alwaysShowBeforeRender": false,
"autoclose": false,
"waiting": true
},
"screenOrientation": ["portrait-primary", "landscape-primary"],
"compatible": {
// 忽略版本提示
"ignoreVersion" : true
"ignoreVersion": true
},
/* 模块配置 */
"modules" : {
"Geolocation" : {},
"Camera" : {}
"modules": {
"Geolocation": {},
"Camera": {}
},
/* 应用发布信息 */
"distribute" : {
"distribute": {
/* android打包配置 */
"android" : {
"permissions" : [
"android": {
"permissions": [
"<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\"/>",
......@@ -49,94 +51,97 @@
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
],
"permissionPhoneState" : {
"permissionPhoneState": {
// app首次启动关闭权限申请
"request" : "none"
}
"request": "none"
},
"minSdkVersion": 23,
"targetSdkVersion": 30,
"abiFilters": ["armeabi-v7a", "arm64-v8a"]
},
/* ios打包配置 */
"ios" : {
"dSYMs" : false,
"privacyDescription" : {
"NSUserTrackingUsageDescription" : "请放心,开启权限不会获取您在其他站点的隐私信息,该权限仅用于标识设备并保障服务安全与提示浏览体验",
"NSLocationAlwaysAndWhenInUseUsageDescription" : "该应用需要你的地理位置,以便检查你当前位置信息",
"NSLocationAlwaysUsageDescription" : "该应用需要你的地理位置,以便检查你当前位置信息",
"NSLocationWhenInUseUsageDescription" : "该应用需要你的地理位置,以便检查你当前位置信息",
"NSPhotoLibraryUsageDescription" : "请允许访问您的照片图库,以便能够上传应用异常问题的截图"
"ios": {
"dSYMs": false,
"privacyDescription": {
"NSUserTrackingUsageDescription": "请放心,开启权限不会获取您在其他站点的隐私信息,该权限仅用于标识设备并保障服务安全与提示浏览体验",
"NSLocationAlwaysAndWhenInUseUsageDescription": "该应用需要你的地理位置,以便检查你当前位置信息",
"NSLocationAlwaysUsageDescription": "该应用需要你的地理位置,以便检查你当前位置信息",
"NSLocationWhenInUseUsageDescription": "该应用需要你的地理位置,以便检查你当前位置信息",
"NSPhotoLibraryUsageDescription": "请允许访问您的照片图库,以便能够上传应用异常问题的截图"
}
},
/* SDK配置 */
"sdkConfigs" : {
"ad" : {},
"statics" : {},
"geolocation" : {
"system" : {
"__platform__" : [ "ios", "android" ]
"sdkConfigs": {
"ad": {},
"statics": {},
"geolocation": {
"system": {
"__platform__": ["ios", "android"]
}
}
},
"icons" : {
"android" : {
"hdpi" : "unpackage/res/icons/72x72.png",
"xhdpi" : "unpackage/res/icons/96x96.png",
"xxhdpi" : "unpackage/res/icons/144x144.png",
"xxxhdpi" : "unpackage/res/icons/192x192.png"
},
"ios" : {
"appstore" : "unpackage/res/icons/1024x1024.png",
"ipad" : {
"app" : "unpackage/res/icons/76x76.png",
"app@2x" : "unpackage/res/icons/152x152.png",
"notification" : "unpackage/res/icons/20x20.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"proapp@2x" : "unpackage/res/icons/167x167.png",
"settings" : "unpackage/res/icons/29x29.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"spotlight" : "unpackage/res/icons/40x40.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png"
},
"iphone" : {
"app@2x" : "unpackage/res/icons/120x120.png",
"app@3x" : "unpackage/res/icons/180x180.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"notification@3x" : "unpackage/res/icons/60x60.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"settings@3x" : "unpackage/res/icons/87x87.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png",
"spotlight@3x" : "unpackage/res/icons/120x120.png"
"icons": {
"android": {
"hdpi": "unpackage/res/icons/72x72.png",
"xhdpi": "unpackage/res/icons/96x96.png",
"xxhdpi": "unpackage/res/icons/144x144.png",
"xxxhdpi": "unpackage/res/icons/192x192.png"
},
"ios": {
"appstore": "unpackage/res/icons/1024x1024.png",
"ipad": {
"app": "unpackage/res/icons/76x76.png",
"app@2x": "unpackage/res/icons/152x152.png",
"notification": "unpackage/res/icons/20x20.png",
"notification@2x": "unpackage/res/icons/40x40.png",
"proapp@2x": "unpackage/res/icons/167x167.png",
"settings": "unpackage/res/icons/29x29.png",
"settings@2x": "unpackage/res/icons/58x58.png",
"spotlight": "unpackage/res/icons/40x40.png",
"spotlight@2x": "unpackage/res/icons/80x80.png"
},
"iphone": {
"app@2x": "unpackage/res/icons/120x120.png",
"app@3x": "unpackage/res/icons/180x180.png",
"notification@2x": "unpackage/res/icons/40x40.png",
"notification@3x": "unpackage/res/icons/60x60.png",
"settings@2x": "unpackage/res/icons/58x58.png",
"settings@3x": "unpackage/res/icons/87x87.png",
"spotlight@2x": "unpackage/res/icons/80x80.png",
"spotlight@3x": "unpackage/res/icons/120x120.png"
}
}
},
"splashscreen" : {
"useOriginalMsgbox" : true
"splashscreen": {
"useOriginalMsgbox": true
}
},
"uniStatistics" : {
"enable" : true
"uniStatistics": {
"enable": true
}
},
"uniStatistics" : {
"enable" : true,
"version" : "2",
"debug" : true
"uniStatistics": {
"enable": true,
"version": "2",
"debug": true
},
/* 快应用特有相关 */
"quickapp" : {},
"quickapp": {},
/* 小程序特有相关 */
"mp-weixin" : {
"appid" : "",
"setting" : {
"urlCheck" : false
"mp-weixin": {
"appid": "",
"setting": {
"urlCheck": false
},
"usingComponents" : true
"usingComponents": true
},
"mp-alipay" : {
"usingComponents" : true
"mp-alipay": {
"usingComponents": true
},
"mp-baidu" : {
"usingComponents" : true
"mp-baidu": {
"usingComponents": true
},
"mp-toutiao" : {
"usingComponents" : true
"mp-toutiao": {
"usingComponents": true
}
}
/**
* App 信息
*/
type Application = {
interface Application {
/**
* 应用名称
*/
appName: string
/**
* 项目名称
*/
......@@ -31,7 +36,7 @@ type Application = {
/**
* App 作者信息
*/
type Author = {
interface Author {
/**
* 作者名称
*/
......
......@@ -45,6 +45,7 @@ export default ({ mode }: ConfigEnv): UserConfig => {
},
define: {
__APP__: {
appName: pkg.appName || pkg.description,
name: pkg.name,
version: pkg.version,
description: pkg.description,
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论