feat: 添加iOS隐私权限说明及统一权限确认逻辑
- 在manifest.json中添加iOS隐私权限使用描述,以满足App Store审核要求
- 创建统一的权限确认工具函数,在涉及相机、相册操作前向用户说明权限用途
- 在扫码邀请码、上传头像、营业执照、客服发送图片、保存二维码等场景添加权限确认
- 修复极光推送通知处理中的硬编码值问题
| | |
| | | jpushModule.initJPushService(); |
| | | // 监听是否连接成功 |
| | | jpushModule.addConnectEventListener(result => { |
| | | // let connectEnable = result.connectEnable; |
| | | // console.log(connectEnable, '极光连接成功'); |
| | | console.log('qweqweqwe', result); |
| | | let connectEnable = result.connectEnable; |
| | | console.log(connectEnable, '极光连接成功'); |
| | | if (uni.getStorageSync('token')) { |
| | | |
| | | } |
| | | }); |
| | | |
| | | jpushModule.addNotificationListener(result => { |
| | | plus.runtime.setBadgeNumber(res.data); |
| | | jpushModule.setBadge(res.data) |
| | | // getUnreadCount().then((res) => { |
| | | plus.runtime.setBadgeNumber('111111'); |
| | | jpushModule.setBadge('2222222222222') |
| | | |
| | | // }); |
| | | |
| | | }); |
| | | // #endif |
| | |
| | | /* ios打包配置 */ |
| | | "ios": { |
| | | "dSYMs": false, |
| | | "privacyDescription": { |
| | | "NSCameraUsageDescription": "需要使用相机拍摄照片 / 视频,用于扫码自动填充邀请码、拍摄照片作为头像", |
| | | "NSPhotoLibraryUsageDescription": "需要访问手机相册,用于选择图片 / 视频上传、更换个人头像、选择邀请二维码", |
| | | "NSPhotoLibraryAddUsageDescription": "需要访问手机相册,用于选择图片 / 视频上传、更换个人头像、选择邀请二维码" |
| | | }, |
| | | "capabilities": { |
| | | "entitlements": { |
| | | "com.apple.developer.associated-domains": [ |
| | |
| | | "modules": {} |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | updateClinicInfo, |
| | | uploadClinicLicense, |
| | | } from './service.js' |
| | | import { confirmMediaForLicense } from '@/utils/permission.js' |
| | | |
| | | interface RegionItem { |
| | | label?: string |
| | |
| | | /** |
| | | * 选择营业执照图片,仅缓存本地路径等待提交前上传。 |
| | | */ |
| | | function uploadLicense() { |
| | | async function uploadLicense() { |
| | | if (uploadingLicense.value) { |
| | | return |
| | | } |
| | | const confirmed = await confirmMediaForLicense() |
| | | if (!confirmed) { |
| | | return |
| | | } |
| | | uni.chooseImage({ |
| | | count: 1, |
| | | sourceType: ['album', 'camera'], |
| | | success: (res) => { |
| | | const filePath = res.tempFilePaths && res.tempFilePaths.length > 0 ? res.tempFilePaths[0] : '' |
| | | if (!filePath) { |
| | |
| | | <view v-if="!loading && allVideos.length === 0" class="empty-text">暂无课程章节</view> |
| | | </view> |
| | | </template> |
| | | |
| | | |
| | | <view v-if="loading" class="loading-text">加载中...</view> |
| | | <view v-if="isReadonlyDisplayMode" class="open-app-banner" @tap="openApp"> |
| | | <view class="app-logo-placeholder"></view> |
| | |
| | | |
| | | <script setup lang="ts"> |
| | | import { ref, onMounted, nextTick } from 'vue'; |
| | | import { confirmMediaForCustomerService } from '@/utils/permission.js'; |
| | | |
| | | const statusBarHeight = ref(20); |
| | | const inputText = ref(''); |
| | |
| | | }, 1000); |
| | | }; |
| | | |
| | | const chooseImage = () => { |
| | | const chooseImage = async () => { |
| | | const confirmed = await confirmMediaForCustomerService(); |
| | | if (!confirmed) { |
| | | return; |
| | | } |
| | | uni.chooseImage({ |
| | | count: 1, |
| | | sizeType: ['compressed'], |
| | |
| | | import { onLoad, onShow } from '@dcloudio/uni-app' |
| | | import { getMyShareQrcode } from './service' |
| | | import { normalizeOptionalText, saveImage } from '@/utils/utils.js' |
| | | import { confirmAlbumForInviteQrCode } from '@/utils/permission.js' |
| | | |
| | | interface ShareBoundUserItem { |
| | | id: number | null |
| | |
| | | /** |
| | | * 保存当前二维码图片到相册。 |
| | | */ |
| | | function saveQRCode() { |
| | | async function saveQRCode() { |
| | | const saveTarget = qrcodeData.value.qrCodeImageUrl || qrcodeData.value.qrCodeDataUrl |
| | | if (!saveTarget) { |
| | | uni.showToast({ |
| | |
| | | }) |
| | | return |
| | | } |
| | | const confirmed = await confirmAlbumForInviteQrCode() |
| | | if (!confirmed) { |
| | | return |
| | | } |
| | | saveImage(saveTarget) |
| | | } |
| | | |
| | |
| | | import { computed, onMounted, reactive, ref } from 'vue' |
| | | import { getSettingsProfile, updateSettingsProfile, uploadSettingsAvatar } from '../settings/service.js' |
| | | import { cacheSettingsProfile, isSuccessResponse, maskPhone, normalizeSettingsProfile } from '../settings/utils.js' |
| | | import { confirmMediaForAvatar } from '@/utils/permission.js' |
| | | |
| | | const statusBarHeight = ref(20) |
| | | const saving = ref(false) |
| | |
| | | /** |
| | | * 选择头像并上传到后端,成功后只更新页面临时状态。 |
| | | */ |
| | | function chooseAvatar() { |
| | | async function chooseAvatar() { |
| | | if (uploadingAvatar.value) { |
| | | return |
| | | } |
| | | const confirmed = await confirmMediaForAvatar() |
| | | if (!confirmed) { |
| | | return |
| | | } |
| | | uni.chooseImage({ |
| | | count: 1, |
| | | sizeType: ['compressed'], |
| | |
| | | validateInviteBindTicket |
| | | } from './service.js' |
| | | import { extractBindTicket, extractInviteCode, normalizeOptionalText } from '@/utils/utils.js' |
| | | import { confirmCameraForInviteCodeScan, confirmMediaForLicense } from '@/utils/permission.js' |
| | | |
| | | interface RegionItem { |
| | | label?: string |
| | |
| | | /** |
| | | * 扫码填充邀请码,支持从邀请下载页 URL 中提取推广码。 |
| | | */ |
| | | const scanCode = () => { |
| | | const scanCode = async () => { |
| | | const confirmed = await confirmCameraForInviteCodeScan() |
| | | if (!confirmed) { |
| | | return |
| | | } |
| | | uni.scanCode({ |
| | | success: (res) => { |
| | | const inviteCode = normalizeInviteCode(res.result) |
| | |
| | | /** |
| | | * 选择营业执照图片,仅缓存本地路径等待提交前上传。 |
| | | */ |
| | | const uploadLicense = () => { |
| | | const uploadLicense = async () => { |
| | | if (uploadingLicense.value) { |
| | | return |
| | | } |
| | | const confirmed = await confirmMediaForLicense() |
| | | if (!confirmed) { |
| | | return |
| | | } |
| | | uni.chooseImage({ |
| | | count: 1, |
| | | sourceType: ['album', 'camera'], |
| | | success: (res) => { |
| | | const filePath = res.tempFilePaths && res.tempFilePaths.length > 0 ? res.tempFilePaths[0] : '' |
| | | if (!filePath) { |
| New file |
| | |
| | | const showPermissionPurposeModal = ({ title = '权限使用说明', content = '' }) => { |
| | | return new Promise((resolve) => { |
| | | uni.showModal({ |
| | | title, |
| | | content, |
| | | confirmText: '继续', |
| | | cancelText: '取消', |
| | | success: (res) => { |
| | | resolve(!!res.confirm) |
| | | }, |
| | | fail: () => { |
| | | resolve(false) |
| | | } |
| | | }) |
| | | }) |
| | | } |
| | | |
| | | /** |
| | | * 扫码前说明相机权限用途。 |
| | | */ |
| | | export const confirmCameraForInviteCodeScan = () => { |
| | | return showPermissionPurposeModal({ |
| | | content: '相机:需要使用相机拍摄照片 / 视频,用于扫码自动填充邀请码。' |
| | | }) |
| | | } |
| | | |
| | | /** |
| | | * 选择或拍摄头像前说明相机、相册权限用途。 |
| | | */ |
| | | export const confirmMediaForAvatar = () => { |
| | | return showPermissionPurposeModal({ |
| | | content: [ |
| | | '相机:需要使用相机拍摄照片 / 视频,用于拍摄照片作为头像。', |
| | | '相册:需要访问手机相册,用于选择图片 / 视频上传、更换个人头像。' |
| | | ].join('\n') |
| | | }) |
| | | } |
| | | |
| | | /** |
| | | * 选择或拍摄营业执照前说明相机、相册权限用途。 |
| | | */ |
| | | export const confirmMediaForLicense = () => { |
| | | return showPermissionPurposeModal({ |
| | | content: [ |
| | | '相机:需要使用相机拍摄照片 / 视频,用于拍摄营业执照照片并上传。', |
| | | '相册:需要访问手机相册,用于选择营业执照图片 / 视频上传。' |
| | | ].join('\n') |
| | | }) |
| | | } |
| | | |
| | | /** |
| | | * 客服发送图片前说明相机、相册权限用途。 |
| | | */ |
| | | export const confirmMediaForCustomerService = () => { |
| | | return showPermissionPurposeModal({ |
| | | content: [ |
| | | '相机:需要使用相机拍摄照片 / 视频,用于拍摄图片发送给客服。', |
| | | '相册:需要访问手机相册,用于选择图片 / 视频发送给客服。' |
| | | ].join('\n') |
| | | }) |
| | | } |
| | | |
| | | /** |
| | | * 保存二维码前说明相册权限用途。 |
| | | */ |
| | | export const confirmAlbumForInviteQrCode = () => { |
| | | return showPermissionPurposeModal({ |
| | | content: '相册:需要访问手机相册,用于保存邀请二维码到本地相册。' |
| | | }) |
| | | } |