# 一、 uni_permission.js权限申请类代码

/common/mixins/uni_permission.js

// /common/mixins/uni_permission.js

const PERMISSION_TYPES = {
    LOCATION: 'location',
    MICROPHONE: 'microphone',
    CAMERA: 'camera',
    PHOTO: 'photo',
    CONTACTS: 'contacts',
    CALENDAR: 'calendar',
    MEMO: 'memo'
};

// 权限状态缓存
const permissionCache = {
    mp: {},
    android: {},
    ios: {}
};

// 全局安卓权限回调处理
let androidPermissionCallback = null;
let androidPermissionRegistered = false;

export default class UniPermission {
    constructor() {
        this.platform = this.getPlatform();
        // 注册安卓权限回调
        this._registerAndroidCallback();
    }

    // 注册安卓权限回调
    _registerAndroidCallback() {
        // #ifdef APP-PLUS
        if (this.platform === 'APP_ANDROID' && !androidPermissionRegistered) {
            androidPermissionRegistered = true;
            
            const mainActivity = plus.android.runtimeMainActivity();
            if (mainActivity.setRequestPermissionsResultHandler) {
                mainActivity.setRequestPermissionsResultHandler((requestCode, permissions, grantResults) => {
                    console.log('[安卓权限回调触发] 请求码:', requestCode, '权限:', permissions, '结果:', grantResults);
                    if (requestCode === 100 && androidPermissionCallback) {
                        androidPermissionCallback(requestCode, permissions, grantResults);
                        androidPermissionCallback = null; // 清除回调
                    }
                });
            }
        }
        // #endif
    }

    // 获取当前平台
    getPlatform() {
        // #ifdef H5
        return 'H5';
        // #endif
        // #ifdef MP-WEIXIN
        return 'MP_WEIXIN';
        // #endif
        // #ifdef APP-PLUS
        return plus.os.name.toLowerCase() === 'android' ? 'APP_ANDROID' : 'APP_IOS';
        // #endif
        return 'UNKNOWN';
    }

    // 统一权限申请方法
    async requestPermission(permissionType, purposeText, title = "权限申请") {
        this._showLoading('正在检查权限...');

        try {
            // 检查权限缓存
            const cacheKey = `${this.platform}_${permissionType}`;
            if (permissionCache[cacheKey] === true) {
                this._hideLoading();
                return true;
            }

            let result;
            switch (this.platform) {
                case 'H5':
                    result = await this._requestH5Permission(permissionType, purposeText, title);
                    break;
                case 'MP_WEIXIN':
                    result = await this._requestMpPermission(permissionType, purposeText, title);
                    break;
                case 'APP_ANDROID':
                    result = await this._requestAndroidPermission(permissionType, purposeText, title);
                    break;
                case 'APP_IOS':
                    result = await this._requestIosPermission(permissionType, purposeText, title);
                    break;
                default:
                    result = false;
            }

            // 缓存授权结果
            if (result) {
                permissionCache[cacheKey] = true;
            }

            return result;
        } catch (error) {
            console.error('权限申请异常:', error);
            return false;
        } finally {
            this._hideLoading();
        }
    }

    // H5平台权限申请
    async _requestH5Permission(permissionType, purposeText, title = "权限申请") {
        try {
            switch (permissionType) {
                case PERMISSION_TYPES.LOCATION:
                    return await this._showCustomDialog(purposeText, title).then(async () => {
                        const position = await this._getH5Location();
                        return !!position;
                    });
                case PERMISSION_TYPES.MICROPHONE:
                    return await this._showCustomDialog(purposeText, title).then(async () => {
                        const stream = await navigator.mediaDevices.getUserMedia({
                            audio: true
                        });
                        stream.getTracks().forEach(track => track.stop());
                        return true;
                    });
                case PERMISSION_TYPES.CAMERA:
                    return await this._showCustomDialog(purposeText, title).then(async () => {
                        const stream = await navigator.mediaDevices.getUserMedia({
                            video: true
                        });
                        stream.getTracks().forEach(track => track.stop());
                        return true;
                    });
                case PERMISSION_TYPES.PHOTO:
                    // H5不需要特殊权限,直接返回true
                    return true;
                default:
                    console.warn(`H5 does not support ${permissionType} permission request`);
                    return true;
            }
        } catch (error) {
            console.error('H5 permission request error:', error);
            return false;
        }
    }

    // 微信小程序权限申请
    async _requestMpPermission(permissionType, purposeText, title = "权限申请") {
        const scopeMap = {
            [PERMISSION_TYPES.LOCATION]: 'scope.userLocation',
            [PERMISSION_TYPES.MICROPHONE]: 'scope.record',
            [PERMISSION_TYPES.CAMERA]: 'scope.camera',
            [PERMISSION_TYPES.PHOTO]: 'scope.writePhotosAlbum'
        };

        const scope = scopeMap[permissionType];
        if (!scope) {
            console.warn(`Unsupported permission type for MP: ${permissionType}`);
            return true;
        }

        // 第一步:检查权限状态
        try {
            this._updateLoading('正在检查权限状态...');
            const settingRes = await uni.getSetting();

            // 1. 如果已经是授权状态,直接返回true
            if (settingRes.authSetting[scope] === true) {
                return true;
            }

            // 2. 如果是拒绝状态,引导用户打开设置
            if (settingRes.authSetting[scope] === false) {
                return await this._guideToMpSetting(scope, purposeText);
            }
        } catch (error) {
            console.error('获取权限设置失败:', error);
        }

        // 第二步:显示自定义对话框(仅当权限状态未知时)
        this._updateLoading('');
        const userAgreed = await this._showCustomDialog(purposeText, title);
        if (!userAgreed) return false;

        // 第三步:调用微信API申请权限
        try {
            this._showLoading('请求权限中...');
            const authRes = await uni.authorize({
                scope
            });
            return authRes.errMsg === 'authorize:ok';
        } catch (error) {
            if (error.errMsg.includes('auth deny') || error.errMsg.includes('auth denied')) {
                return await this._guideToMpSetting(scope, purposeText);
            }
            return false;
        }
    }

    // 引导用户打开小程序设置
    async _guideToMpSetting(scope, purposeText) {
        const res = await uni.showModal({
            title: '权限被拒绝',
            content: `您已拒绝${purposeText},是否去设置页面开启权限?`,
            confirmText: '去设置',
            cancelText: '取消'
        });

        if (res.confirm) {
            const settingRes = await uni.openSetting();
            return settingRes.authSetting[scope] === true;
        }
        return false;
    }

    // Android平台权限申请
    async _requestAndroidPermission(permissionType, purposeText, title = "权限申请") {
        const permissions = {
            [PERMISSION_TYPES.PHOTO]: [
                'android.permission.READ_EXTERNAL_STORAGE',
                'android.permission.WRITE_EXTERNAL_STORAGE'
            ],
            // ...其他权限
        }[permissionType] || [];
    
        if (permissions.length === 0) return true;
    
        // 1. 检查权限状态
        this._updateLoading('正在检查权限状态...');
        const Context = plus.android.importClass('android.content.Context');
        const PackageManager = plus.android.importClass('android.content.pm.PackageManager');
        const mainActivity = plus.android.runtimeMainActivity();
        const context = mainActivity.getApplicationContext();
    
        let hasPermission = true;
        for (const perm of permissions) {
            if (context.checkSelfPermission(perm) !== PackageManager.PERMISSION_GRANTED) {
                hasPermission = false;
                break;
            }
        }
    
        // 2. 如果已有权限,直接返回
        if (hasPermission) {
            return true;
        }
    
        // 3. 显示自定义弹窗说明权限用途
        this._updateLoading('');
        const userAgreed = await this._showCustomDialog(purposeText, title);
        if (!userAgreed) return false;
    
        // 4. 动态请求权限
        this._showLoading('请求权限中...');
        
        return new Promise((resolve) => {
            // 使用全局回调处理
            androidPermissionCallback = (requestCode, perms, grantResults) => {
                console.log('[安卓权限回调处理] 请求码:', requestCode, '权限:', perms, '结果:', grantResults);
                if (requestCode !== 100) {
                    console.log('[警告] 收到非预期请求码:', requestCode);
                    resolve(false);
                    return;
                }
                
                const PackageManager = plus.android.importClass('android.content.pm.PackageManager');
                let granted = true;
                
                // 检查所有权限是否都被授予
                for (let i = 0; i < grantResults.length; i++) {
                    if (grantResults[i] !== PackageManager.PERMISSION_GRANTED) {
                        granted = false;
                        break;
                    }
                }
                
                if (granted) {
                    console.log('[成功] 所有权限已授予');
                    resolve(true);
                } else {
                    console.log('[警告] 部分权限被拒绝');
                    // 部分权限被拒绝,引导设置
                    this._guideToAppSetting(purposeText, title).then(resolve);
                }
            };
    
            // 发起权限请求
            console.log('[发起安卓权限请求]', permissions);
            
            // 在权限请求中添加监听
            const handleAppShow = (event) => {
                console.log('应用回到前台,隐藏时长:', event.hideDuration || 0);
                
                // 根据隐藏时间调整延迟
                const delay = event.hideDuration > 3000 ? 1500 : 800;
                console.log(`设置延迟 ${delay}ms 后检查权限`);
                
                setTimeout(async () => {
                    console.log('延迟后检查权限状态');
                    
                    // 重新检查权限状态
                    let allGranted = true;
                    for (const perm of permissions) {
                        const result = context.checkSelfPermission(perm);
                        console.log(`权限 ${perm} 状态: ${result}`);
                        
                        if (result !== PackageManager.PERMISSION_GRANTED) {
                            allGranted = false;
                        }
                    }
                    
                    if (allGranted) {
                        console.log('[成功] 应用回到前台后权限已授予');
                        resolve(true);
                        clearTimeout(timeoutId);
                        uni.$off('app_show', handleAppShow);
                    } else {
                        console.log('[警告] 应用回到前台后权限未授予');
                        
                        // 尝试再次检查(最多3次)
                        if (retryCount < 3) {
                            retryCount++;
                            console.log(`[重试] 第${retryCount}次检查权限`);
                            checkPermission();
                        } else {
                            console.log('[失败] 多次检查后权限仍未授予');
                            resolve(false);
                        }
                    }
                }, delay);
            };
            
            // 重试计数器
            let retryCount = 0;
            const checkPermission = () => {
                setTimeout(() => {
                    let allGranted = true;
                    for (const perm of permissions) {
                        const result = context.checkSelfPermission(perm);
                        console.log(`[重试检查] 权限 ${perm} 状态: ${result}`);
                        
                        if (result !== PackageManager.PERMISSION_GRANTED) {
                            allGranted = false;
                        }
                    }
                    
                    if (allGranted) {
                        console.log('[成功] 重试检查后权限已授予');
                        resolve(true);
                        clearTimeout(timeoutId);
                        uni.$off('app_show', handleAppShow);
                    } else if (retryCount < 3) {
                        retryCount++;
                        console.log(`[重试] 第${retryCount}次检查权限`);
                        checkPermission();
                    } else {
                        console.log('[失败] 多次重试后权限仍未授予');
                        resolve(false);
                    }
                }, 500); // 每次重试间隔500ms
            };
            
            // 监听应用回到前台事件
            uni.$on('app_show', handleAppShow);
            
            // 设置超时清除监听
            const timeoutId = setTimeout(() => {
                console.log('[超时] 权限请求超时,清除监听');
                uni.$off('app_show', handleAppShow);
                resolve(false);
            }, 15000); // 延长到15秒
            
            // 发起请求
            mainActivity.requestPermissions(permissions, 100);
        });
    }
	
    // iOS平台权限申请
    async _requestIosPermission(permissionType, purposeText, title = '权限申请') {
        const permissionMap = {
            [PERMISSION_TYPES.LOCATION]: 'location',
            [PERMISSION_TYPES.MICROPHONE]: 'microphone',
            [PERMISSION_TYPES.CAMERA]: 'camera',
            [PERMISSION_TYPES.PHOTO]: 'photo',
            [PERMISSION_TYPES.CONTACTS]: 'contacts',
            [PERMISSION_TYPES.CALENDAR]: 'calendar',
            [PERMISSION_TYPES.MEMO]: '' // iOS没有直接的备忘录权限
        };

        const permission = permissionMap[permissionType];
        if (!permission) return true;

        // 第一步:显示自定义对话框
        const userAgreed = await this._showCustomDialog(purposeText, title);
        if (!userAgreed) return false;

        // 第二步:申请权限
        try {
            const result = await plus.ios.requestPermissions([permission]);
            if (result[permission] === 3) { // 3: 已授权
                return true;
            }
            return await this._guideToAppSetting(purposeText, title);
        } catch (error) {
            console.error('iOS permission request error:', error);
            return await this._guideToAppSetting(purposeText, title);
        }
    }

    // 显示加载提示
    _showLoading(title = '处理中...') {
        // 避免重复显示
        if (!this._loadingShown) {
            uni.showLoading({
                title: title,
                mask: true
            });
            this._loadingShown = true;
        }
    }

    // 更新加载提示
    _updateLoading(title) {
        if (this._loadingShown && title) {
            uni.hideLoading();
            uni.showLoading({
                title: title,
                mask: true
            });
        }
    }

    // 隐藏加载提示
    _hideLoading() {
        return new Promise(resolve => {
            if (this._loadingShown) {
                uni.hideLoading({
                    complete: () => {
                        this._loadingShown = false;
                        resolve();
                    }
                });
            } else {
                resolve();
            }
        });
    }

    // 显示自定义权限申请对话框
    async _showCustomDialog(purposeText, title) {
        return new Promise((resolve) => {
            // 先隐藏加载提示
            this._hideLoading().then(() => {
                uni.showModal({
                    title: title || '权限申请',
                    content: purposeText || '我们需要您的授权以提供完整功能',
                    confirmText: '允许',
                    cancelText: '拒绝',
                    success: (res) => {
                        resolve(res.confirm);
                    },
                    fail: () => {
                        resolve(false);
                    }
                });
            });
        });
    }

    // 引导用户去应用设置
    _guideToAppSetting(purposeText, title) {
        return new Promise((resolve) => {
            // 先隐藏加载提示
            this._hideLoading().then(() => {
                uni.showModal({
                    title: title || '权限被永久拒绝',
                    content: `请在系统设置中手动开启「${purposeText}`,
                    confirmText: '去设置',
                    success: async (res) => {
                        if (res.confirm) {
                            // 跳转系统设置
                            // #ifdef APP-ANDROID
                            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 intent = new Intent();
                            intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                            const uri = Uri.fromParts("package", plus.android.runtimeMainActivity()
                                .getPackageName(), null);
                            intent.setData(uri);
    
                            try {
                                plus.android.runtimeMainActivity().startActivity(intent);
                                
                                // 监听应用回到前台
                                const handleAppShow = () => {
                                    console.log('从设置返回,检查权限状态');
                                    setTimeout(() => {
                                        const Context = plus.android.importClass('android.content.Context');
                                        const PackageManager = plus.android.importClass('android.content.pm.PackageManager');
                                        const context = plus.android.runtimeMainActivity().getApplicationContext();
                                        
                                        let allGranted = true;
                                        for (const perm of permissions) {
                                            if (context.checkSelfPermission(perm) !== PackageManager.PERMISSION_GRANTED) {
                                                allGranted = false;
                                                break;
                                            }
                                        }
                                        
                                        if (allGranted) {
                                            console.log('[成功] 从设置返回后权限已授予');
                                            resolve(true);
                                        } else {
                                            console.log('[警告] 从设置返回后权限未授予');
                                            resolve(false);
                                        }
                                        
                                        uni.$off('app_show', handleAppShow);
                                    }, 1000);
                                };
                                
                                uni.$on('app_show', handleAppShow);
                                
                                // 设置超时
                                setTimeout(() => {
                                    uni.$off('app_show', handleAppShow);
                                    resolve(false);
                                }, 10000);
                                
                            } catch (e) {
                                console.error('跳转设置失败:', e);
                                resolve(false);
                            }
                            // #endif
                            // ...其他平台
                        } else {
                            resolve(false);
                        }
                    },
                    fail: () => resolve(false)
                });
            });
        });
    }

    // H5获取位置
    _getH5Location() {
        return new Promise((resolve, reject) => {
            if (!navigator.geolocation) {
                reject(new Error('Geolocation not supported'));
                return;
            }
            navigator.geolocation.getCurrentPosition(resolve, reject);
        });
    }
}

# 二、安卓端app需要在 App.vue 中添加以下代码

...
<script>
	export default {
		onLaunch: function() {
			console.log('App Launch')
			...
			// 监听应用状态变化
			// #ifdef APP-PLUS
			let appHideTime = 0;
			
			plus.globalEvent.addEventListener('pause', () => {
				console.log('应用进入后台');
				appHideTime = Date.now();
				uni.$emit('app_hide');
			});
			
			plus.globalEvent.addEventListener('resume', () => {
				console.log('应用回到前台');
				const hideDuration = Date.now() - appHideTime;
				console.log(`应用在后台停留时间: ${hideDuration}ms`);
				uni.$emit('app_show', { hideDuration });
			});
			// #endif
		},
		onShow: function() {
			console.log('App Show')
		},
		onHide: function() {
			console.log('App Hide')
		}
	}
</script>

<style>
	...
</style>

# 三、调用权限类示例

如,在页面 /pages/chat/chat.nvue

<script>
	import UniPermission from '@/common/mixins/uni_permission.js';
    export default {
        methods: {
            // 处理相册照片
			async handlePhoto() {
			    try {
                    const permission = new UniPermission();
                    const granted = await permission.requestPermission('photo', 
                    '需要访问您的相册来选择照片','本功能需要您打开相册');
                    if (granted) {
                        //console.log('相册权限已授予,开始选择图片');
                        //this.chooseImage(); // 选择照片
                        // setTimeout(() => {this.chooseImage();}, 300);
                    } else {
                        uni.showToast({ title: '无相册权限', icon: 'none',duration: 2000});
                    }
			    } catch (error) {
                    console.error('权限申请异常:', error);
                    uni.showToast({ 
                        title: '权限申请失败: ' + error.message, 
                        icon: 'none',
                        duration: 3000
                    });
                }
			},
        }
    }
</script>
更新时间: 2025年6月26日星期四下午1点53分