HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

ios应用可通过企业证书打包直接安装在iphone

安装

做过安卓开发的同学们都知道,安卓的安装是比较简单的,可以直接在android手机上安装,不需要通过在线商店安装。

但是iphone和ipad这些ios系统的手机不一样,假如你是使用普通苹果账号打包的app store类型的ipa包,是无法直接安装在ios手机的,需要上架到app store苹果商店才能安装。

不过,ios想直接安装,还是有办法的,就是注册苹果企业开发者账号,通过企业开发者账号,生成in house类型的证书和描述文件(证书profile文件),通过in house类型的证书和描述文件打包出来的ipa文件,可以上传到像香蕉云编这种应用内测的平台,生成二维码安装。

注册企业账号和生成企业证书的过程比较麻烦,假如需要,可参考下面这个教程:

https://blog.csdn.net/weixin_48914851/article/details/140758473

继续阅读 »

做过安卓开发的同学们都知道,安卓的安装是比较简单的,可以直接在android手机上安装,不需要通过在线商店安装。

但是iphone和ipad这些ios系统的手机不一样,假如你是使用普通苹果账号打包的app store类型的ipa包,是无法直接安装在ios手机的,需要上架到app store苹果商店才能安装。

不过,ios想直接安装,还是有办法的,就是注册苹果企业开发者账号,通过企业开发者账号,生成in house类型的证书和描述文件(证书profile文件),通过in house类型的证书和描述文件打包出来的ipa文件,可以上传到像香蕉云编这种应用内测的平台,生成二维码安装。

注册企业账号和生成企业证书的过程比较麻烦,假如需要,可参考下面这个教程:

https://blog.csdn.net/weixin_48914851/article/details/140758473

收起阅读 »

使用switch无法进行数据的双向绑定

uni-app switch

当你form表单使用switch进行操作时,
<switch @change="binddata('itExistDimension', $event.detail.value)" :checked="formData.itExistDimension"></switch>
可以这样使用,但是无法更新formData的itExistDimension的值,必须重构binddata方法进行手动绑定才行
binddata(name,value){
this.formData[name]=value
this.$refs.form.setValue(name,value)
},
否则如果你使用v-if=“itExistDimension”必然无效

继续阅读 »

当你form表单使用switch进行操作时,
<switch @change="binddata('itExistDimension', $event.detail.value)" :checked="formData.itExistDimension"></switch>
可以这样使用,但是无法更新formData的itExistDimension的值,必须重构binddata方法进行手动绑定才行
binddata(name,value){
this.formData[name]=value
this.$refs.form.setValue(name,value)
},
否则如果你使用v-if=“itExistDimension”必然无效

收起阅读 »

vue3+arco网页版webos系统|vite5+pinia2仿mac/windows桌面os

vite vue3

vue3-webos一款基于最新前端技术vite5.x+vue3+pinia2+arco-design+sortablejs搭建的网页版os管理系统。内置macos和windows两种桌面、支持自定义桌面栅格布局引擎、可拖拽式桌面菜单/程序坞菜单等功能。

原创自研vite5+vue3+arco-design仿macOS网页版os桌面管理系统

img

img

vue3-webos使用vite.js搭建项目模板,采用arco.design组件库。

img

运用技术

  • 编辑器:Vscode
  • 技术框架:vite5.3.3+vue3.4.31+vue-router4.4+pinia2
  • UI组件库:arco-design^2.55.3 (字节桌面版vue3组件库)
  • 状态管理:pinia^2.1.7
  • 图表插件:echarts^5.5.1
  • 拖拽组件:sortablejs^1.15.2
  • 富文本编辑器:wangeditor^4.7.15
  • 模拟数据:mockjs^1.1.0
  • 样式编译:sass^1.77.8
  • 构建工具:vite^5.3.3

img

img

前段时间有分享一款vite5+vue3原创网页版聊天实例。感兴趣的可以去看看。
https://ask.dcloud.net.cn/article/41153

项目结构目录

img

img

img

目前整个vue3-macos项目已经托管到我的原创作品集,有需要的可以去瞅瞅。
https://gf.bilibili.com/item/detail/1106413011

main.js配置

import { createApp } from 'vue'  
import './style.scss'  
import App from './App.vue'  

// 引入arco.design组件库  
import ArcoDesign from '@arco-design/web-vue'  
import '@arco-design/web-vue/dist/arco.css'  
// 额外引入图标库  
import ArcoIcon from '@arco-design/web-vue/es/icon'  
import VEPlus from 've-plus'  
import 've-plus/dist/ve-plus.css'  

// 引入路由及状态管理  
import Router from './router'  
import Pinia from './pinia'  

const app = createApp(App)  

app  
.use(ArcoDesign)  
.use(ArcoIcon)  
.use(VEPlus)  
.use(Router)  
.use(Pinia)  
.mount('#app')

vue3批量路由配置

使用vite.js提供的import.meta.glob批量导入路由配置。

/**  
 * 路由管理Router  
 * @author andy  
 */  

import { createRouter, createWebHashHistory } from 'vue-router'  
import { authState } from '@/pinia/modules/auth'  

import Layout from '@/layouts/index.vue'  

// 批量导入路由  
const modules = import.meta.glob('./modules/*.js', { eager: true })  
console.log(modules)  
const patchRouters = Object.keys(modules).map(key => modules[key].default).flat()  
console.log(patchRouters)  

/**  
 * meta配置  
 * @param meta.requireAuth 需登录验证页面  
 */  
const routes = [  
  {  
    path: '/',  
    redirect: '/desktop',  
  },  
  ...patchRouters,  
  // 错误模块  
  {  
    path: '/:pathMatch(.*)*',  
    redirect: '/404',  
    component: Layout,  
    meta: {  
      title: '404error',  
    },  
    children: [  
      {  
        path: '404',  
        component: () => import('@/views/error/404.vue'),  
      }  
    ]  
  },  
]  

const router = createRouter({  
  history: createWebHashHistory(),  
  routes,  
})  

// 全局路由钩子拦截  
router.beforeEach((to, from) => {  
  const authstate = authState()  
  // 登录验证  
  if(to?.meta?.requireAuth && !authstate.authorization) {  
    console.log('你还未登录!')  
    return {  
      path: '/login'  
    }  
  }  
})  

router.afterEach(() => {  
  // ...  
})  

router.onError(error => {  
  console.warn('[Router Error]', error)  
})

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

桌面公共布局模板

vite-osx内置了macos和windows两种桌面。

<script setup>  
  import { appState } from '@/pinia/modules/app'  

  // 引入布局模板  
  import MacosLayout from './template/macos.vue'  
  import WindowsLayout from './template/windows.vue'  

  const appstate = appState()  

  const DeskLayout = {  
    macos: MacosLayout,  
    windows: WindowsLayout  
  }  
</script>  

<template>  
  <div  
    class="vu__container desktop flexbox flex-alignc flex-justifyc"  
    :style="{'--themeSkin': appstate.config.skin}"  
    @contextmenu.prevent  
  >  
    <component :is="DeskLayout[appstate.config.layout]" />  
  </div>  
</template>

img

<template>  
  <div class="vu__layout flexbox flex-col">  
    <div class="vu__layout-header">  
      <Toolbar />  
    </div>  
    <div class="vu__layout-body flex1 flexbox">  
      <Desk />  
    </div>  
    <div class="vu__layout-footer">  
      <Dock />  
    </div>  
    <!-- 悬浮球(辅助触控) -->  
    <Touch />  
  </div>  
</template>

vue3栅格布局桌面

img

img

img

// 自定义变量(桌面图标)  
const deskVariable = ref({  
  '--icon-radius': '8px', // 圆角  
  '--icon-size': '60px', // 图标尺寸(设置rpx自定义手机设备)  
  '--icon-gap-col': '30px', // 水平间距  
  '--icon-gap-row': '30px', // 垂直间距  
  '--icon-labelSize': '12px', // 标签文字大小  
  '--icon-labelColor': '#fff', // 标签颜色  
  '--icon-fit': 'contain', // 图标自适应模式  
})

img

img

/*  
 * 桌面菜单json配置项 by Andy  Q:282310962  
 */  

const deskMenu = [  
  {  
    pid: 20240507001,  
    list: [  
      {imgico: markRaw(Today), size: '2x2'},  
      {label: '便签', imgico: markRaw(NoteBook), size: '4x2'},  
      ...  
    ]  
  },  
  {  
    pid: 20240509002,  
    list: [  
      {label: 'Appstore', imgico: '/static/mac/appstore.png'},  
      {label: '地图', imgico: '/static/mac/maps.png'},  
      ...  
    ]  
  },  
  {  
    pid: 20240510001,  
    list: [  
      {label: 'Github', imgico: '/static/svg/github.svg', link: 'https://github.com/', background: '#607d8b',},  
      ...  
    ]  
  },  
  {  
    uid: 'd141f210-207e-1e8e-9950-9deefac27e48',  
    list: [  
      {label: 'Vite^5.3.3', imgico: 'https://vitejs.dev/logo.svg', link: 'https://vitejs.dev/'},  
      ...  
      {  
        label: '组件',  
        children: [  
          {label: '表格', imgico: '/static/svg/table.svg', path: '/components/table/all'},  
          {label: '自定义表格', imgico: '/static/svg/table.svg', path: '/components/table/custom'},  
          ...  
        ]  
      },  
      {label: 'ChatGPT', imgico: '/static/svg/chatgpt.svg', link: 'https://openai.com/chatgpt/', background: '#15A17F',},  
      {label: 'Bilibili', imgico: '/static/svg/bilibili.svg', link: 'https://www.bilibili.com/', background: '#ff6899',},  
      {  
        label: '个人中心',  
        children: [  
          {label: '主页', imgico: '/static/svg/my.svg', path: '/setting'},  
          ...  
        ]  
      },  
      {  
        label: '设置',  
        children: [  
          {label: '网站设置', imgico: '/static/svg/settings.svg', path: '/setting/system/website'},  
          {label: '邮件服务', imgico: '/static/mac/mail.png', path: '/setting/system/mail'},  
        ]  
      },  
      {  
        label: '公众号', imgico: markRaw(IconWechat), color: '#07c160',  
        onClick: () => {  
          ...  
        }  
      },  
    ]  
  }  
]

以上就是vue3+pinia2+arco.design实战仿macos和windows桌面os系统的一些分享知识。

作者:xiaoyan2017
链接: https://segmentfault.com/a/1190000045102738
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

继续阅读 »

vue3-webos一款基于最新前端技术vite5.x+vue3+pinia2+arco-design+sortablejs搭建的网页版os管理系统。内置macos和windows两种桌面、支持自定义桌面栅格布局引擎、可拖拽式桌面菜单/程序坞菜单等功能。

原创自研vite5+vue3+arco-design仿macOS网页版os桌面管理系统

img

img

vue3-webos使用vite.js搭建项目模板,采用arco.design组件库。

img

运用技术

  • 编辑器:Vscode
  • 技术框架:vite5.3.3+vue3.4.31+vue-router4.4+pinia2
  • UI组件库:arco-design^2.55.3 (字节桌面版vue3组件库)
  • 状态管理:pinia^2.1.7
  • 图表插件:echarts^5.5.1
  • 拖拽组件:sortablejs^1.15.2
  • 富文本编辑器:wangeditor^4.7.15
  • 模拟数据:mockjs^1.1.0
  • 样式编译:sass^1.77.8
  • 构建工具:vite^5.3.3

img

img

前段时间有分享一款vite5+vue3原创网页版聊天实例。感兴趣的可以去看看。
https://ask.dcloud.net.cn/article/41153

项目结构目录

img

img

img

目前整个vue3-macos项目已经托管到我的原创作品集,有需要的可以去瞅瞅。
https://gf.bilibili.com/item/detail/1106413011

main.js配置

import { createApp } from 'vue'  
import './style.scss'  
import App from './App.vue'  

// 引入arco.design组件库  
import ArcoDesign from '@arco-design/web-vue'  
import '@arco-design/web-vue/dist/arco.css'  
// 额外引入图标库  
import ArcoIcon from '@arco-design/web-vue/es/icon'  
import VEPlus from 've-plus'  
import 've-plus/dist/ve-plus.css'  

// 引入路由及状态管理  
import Router from './router'  
import Pinia from './pinia'  

const app = createApp(App)  

app  
.use(ArcoDesign)  
.use(ArcoIcon)  
.use(VEPlus)  
.use(Router)  
.use(Pinia)  
.mount('#app')

vue3批量路由配置

使用vite.js提供的import.meta.glob批量导入路由配置。

/**  
 * 路由管理Router  
 * @author andy  
 */  

import { createRouter, createWebHashHistory } from 'vue-router'  
import { authState } from '@/pinia/modules/auth'  

import Layout from '@/layouts/index.vue'  

// 批量导入路由  
const modules = import.meta.glob('./modules/*.js', { eager: true })  
console.log(modules)  
const patchRouters = Object.keys(modules).map(key => modules[key].default).flat()  
console.log(patchRouters)  

/**  
 * meta配置  
 * @param meta.requireAuth 需登录验证页面  
 */  
const routes = [  
  {  
    path: '/',  
    redirect: '/desktop',  
  },  
  ...patchRouters,  
  // 错误模块  
  {  
    path: '/:pathMatch(.*)*',  
    redirect: '/404',  
    component: Layout,  
    meta: {  
      title: '404error',  
    },  
    children: [  
      {  
        path: '404',  
        component: () => import('@/views/error/404.vue'),  
      }  
    ]  
  },  
]  

const router = createRouter({  
  history: createWebHashHistory(),  
  routes,  
})  

// 全局路由钩子拦截  
router.beforeEach((to, from) => {  
  const authstate = authState()  
  // 登录验证  
  if(to?.meta?.requireAuth && !authstate.authorization) {  
    console.log('你还未登录!')  
    return {  
      path: '/login'  
    }  
  }  
})  

router.afterEach(() => {  
  // ...  
})  

router.onError(error => {  
  console.warn('[Router Error]', error)  
})

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

桌面公共布局模板

vite-osx内置了macos和windows两种桌面。

<script setup>  
  import { appState } from '@/pinia/modules/app'  

  // 引入布局模板  
  import MacosLayout from './template/macos.vue'  
  import WindowsLayout from './template/windows.vue'  

  const appstate = appState()  

  const DeskLayout = {  
    macos: MacosLayout,  
    windows: WindowsLayout  
  }  
</script>  

<template>  
  <div  
    class="vu__container desktop flexbox flex-alignc flex-justifyc"  
    :style="{'--themeSkin': appstate.config.skin}"  
    @contextmenu.prevent  
  >  
    <component :is="DeskLayout[appstate.config.layout]" />  
  </div>  
</template>

img

<template>  
  <div class="vu__layout flexbox flex-col">  
    <div class="vu__layout-header">  
      <Toolbar />  
    </div>  
    <div class="vu__layout-body flex1 flexbox">  
      <Desk />  
    </div>  
    <div class="vu__layout-footer">  
      <Dock />  
    </div>  
    <!-- 悬浮球(辅助触控) -->  
    <Touch />  
  </div>  
</template>

vue3栅格布局桌面

img

img

img

// 自定义变量(桌面图标)  
const deskVariable = ref({  
  '--icon-radius': '8px', // 圆角  
  '--icon-size': '60px', // 图标尺寸(设置rpx自定义手机设备)  
  '--icon-gap-col': '30px', // 水平间距  
  '--icon-gap-row': '30px', // 垂直间距  
  '--icon-labelSize': '12px', // 标签文字大小  
  '--icon-labelColor': '#fff', // 标签颜色  
  '--icon-fit': 'contain', // 图标自适应模式  
})

img

img

/*  
 * 桌面菜单json配置项 by Andy  Q:282310962  
 */  

const deskMenu = [  
  {  
    pid: 20240507001,  
    list: [  
      {imgico: markRaw(Today), size: '2x2'},  
      {label: '便签', imgico: markRaw(NoteBook), size: '4x2'},  
      ...  
    ]  
  },  
  {  
    pid: 20240509002,  
    list: [  
      {label: 'Appstore', imgico: '/static/mac/appstore.png'},  
      {label: '地图', imgico: '/static/mac/maps.png'},  
      ...  
    ]  
  },  
  {  
    pid: 20240510001,  
    list: [  
      {label: 'Github', imgico: '/static/svg/github.svg', link: 'https://github.com/', background: '#607d8b',},  
      ...  
    ]  
  },  
  {  
    uid: 'd141f210-207e-1e8e-9950-9deefac27e48',  
    list: [  
      {label: 'Vite^5.3.3', imgico: 'https://vitejs.dev/logo.svg', link: 'https://vitejs.dev/'},  
      ...  
      {  
        label: '组件',  
        children: [  
          {label: '表格', imgico: '/static/svg/table.svg', path: '/components/table/all'},  
          {label: '自定义表格', imgico: '/static/svg/table.svg', path: '/components/table/custom'},  
          ...  
        ]  
      },  
      {label: 'ChatGPT', imgico: '/static/svg/chatgpt.svg', link: 'https://openai.com/chatgpt/', background: '#15A17F',},  
      {label: 'Bilibili', imgico: '/static/svg/bilibili.svg', link: 'https://www.bilibili.com/', background: '#ff6899',},  
      {  
        label: '个人中心',  
        children: [  
          {label: '主页', imgico: '/static/svg/my.svg', path: '/setting'},  
          ...  
        ]  
      },  
      {  
        label: '设置',  
        children: [  
          {label: '网站设置', imgico: '/static/svg/settings.svg', path: '/setting/system/website'},  
          {label: '邮件服务', imgico: '/static/mac/mail.png', path: '/setting/system/mail'},  
        ]  
      },  
      {  
        label: '公众号', imgico: markRaw(IconWechat), color: '#07c160',  
        onClick: () => {  
          ...  
        }  
      },  
    ]  
  }  
]

以上就是vue3+pinia2+arco.design实战仿macos和windows桌面os系统的一些分享知识。

作者:xiaoyan2017
链接: https://segmentfault.com/a/1190000045102738
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

收起阅读 »

安装插件不成功 插件安装 插件 插件下载

插件安装 插件

是不是手动下载插件后,解压都不成功?
是不是手动创建文件夹都需要再次确认一下?
没有权限,去更改文件夹的权限

是不是手动下载插件后,解压都不成功?
是不是手动创建文件夹都需要再次确认一下?
没有权限,去更改文件夹的权限

微信小程序开发者工具 [error] Error: Fail to open IDE

微信小程序 IDE 微信开发者工具


保证这个appId和代码里的匹配,并且微信开发者工具的登录账号有对应开发权限。


保证这个appId和代码里的匹配,并且微信开发者工具的登录账号有对应开发权限。

uni.chooseLocation打开查找不到周边位置信息

调用uni.chooseLocation方法本地运行和真机测试没问题;但是打包后就查找不到周边位置;(用的高德地图;可以确定地图配置没问题;位置信息权限也打开了)

调用uni.chooseLocation方法本地运行和真机测试没问题;但是打包后就查找不到周边位置;(用的高德地图;可以确定地图配置没问题;位置信息权限也打开了)

unipush1.0 ios离线收不到消息的问题

unipush

最近有项目使用了uni-push1.0来进行消息推送功能,这里主要分享一下IOS的离线推送收不到消息的问题
IOS离线收不到消息,基本是上传的推送证书有问题

贴一下我的相关文件:

证书文件

两个注意点:

1.profile/CertificateSigningRequest.certSigningRequest :证书请求文件
iOS Dev Center创建certificates时使用同一个证书请求文件,包括创建推送证书

2.profile/push.p12:推送证书
需要上传的推送证书,不要用这个证书去打包!!或者上传你的打包证书

上传推送证书

如何测试上传的推送证书是否生效

在【Uni Push】-【配置管理】-【故障排查】-【 检测CID的状态及信息:】中输入CID查询,拿到苹果的devicetoken

token

把这个devicetoken复制一下

在【Uni Push】-【配置管理】-【故障排查】- 【应用配置】点击右下方的 立即测试,粘贴刚才的devicetoken,去掉后面的【,,,】
如果devicetoken后面带着 developement,说明是开发环境,在测试的时候需要选择开发环境,也是同样需要去掉后面【,,developement,】

开发环境token

test

测试如果弹出 连接异常 就是证书不对,建议再走一遍证书生成的流程,重新上传证书需要重新提交云打包

证书正常,应用离线也可以收到消息

接收到的离线消息通知

设置角标

离线推送一般会设置应用角标,由于使用unipush1.0,后端调用个推推送接口的参数:auto_badge 设置为 1就行

   "push_channel": {  
        “android”:{},  
        },  
        "ios": {  
            "aps": {  
                "alert": {  
                    "body": "告警测试",  
                    "title": "测试"  
                },  
                "content_available": 0,  
                "sound": "default"  
            },  
            "auto_badge": " 1",  
            "payload": "{\"title\":\"测试\",\"content\":\"告警测试\"}"  
        }  
    }

应用监听通知栏的点击事件,然后再将角标清0,建议写在App.vue里面

            plus.push.addEventListener('click', (msg) => {  
                console.log('click', msg);  
                plus.runtime.setBadgeNumber(0)  
                                // TODO  
            })

有问题可以联系 Q:2377995017

继续阅读 »

最近有项目使用了uni-push1.0来进行消息推送功能,这里主要分享一下IOS的离线推送收不到消息的问题
IOS离线收不到消息,基本是上传的推送证书有问题

贴一下我的相关文件:

证书文件

两个注意点:

1.profile/CertificateSigningRequest.certSigningRequest :证书请求文件
iOS Dev Center创建certificates时使用同一个证书请求文件,包括创建推送证书

2.profile/push.p12:推送证书
需要上传的推送证书,不要用这个证书去打包!!或者上传你的打包证书

上传推送证书

如何测试上传的推送证书是否生效

在【Uni Push】-【配置管理】-【故障排查】-【 检测CID的状态及信息:】中输入CID查询,拿到苹果的devicetoken

token

把这个devicetoken复制一下

在【Uni Push】-【配置管理】-【故障排查】- 【应用配置】点击右下方的 立即测试,粘贴刚才的devicetoken,去掉后面的【,,,】
如果devicetoken后面带着 developement,说明是开发环境,在测试的时候需要选择开发环境,也是同样需要去掉后面【,,developement,】

开发环境token

test

测试如果弹出 连接异常 就是证书不对,建议再走一遍证书生成的流程,重新上传证书需要重新提交云打包

证书正常,应用离线也可以收到消息

接收到的离线消息通知

设置角标

离线推送一般会设置应用角标,由于使用unipush1.0,后端调用个推推送接口的参数:auto_badge 设置为 1就行

   "push_channel": {  
        “android”:{},  
        },  
        "ios": {  
            "aps": {  
                "alert": {  
                    "body": "告警测试",  
                    "title": "测试"  
                },  
                "content_available": 0,  
                "sound": "default"  
            },  
            "auto_badge": " 1",  
            "payload": "{\"title\":\"测试\",\"content\":\"告警测试\"}"  
        }  
    }

应用监听通知栏的点击事件,然后再将角标清0,建议写在App.vue里面

            plus.push.addEventListener('click', (msg) => {  
                console.log('click', msg);  
                plus.runtime.setBadgeNumber(0)  
                                // TODO  
            })

有问题可以联系 Q:2377995017

收起阅读 »

5+app重写mui.back

mui 5+App开发
mui.plusReady(function() {  
    // 保存原始的mui.back函数  
    var originalBack = mui.back;  
    // 重写mui.back函数  
    mui.back = function() {  
        // 在此处添加自定义逻辑  
        console.log('自定义的返回操作');  
        // 调用原始的mui.back函数  
        // originalBack();  
    };  
})
继续阅读 »
mui.plusReady(function() {  
    // 保存原始的mui.back函数  
    var originalBack = mui.back;  
    // 重写mui.back函数  
    mui.back = function() {  
        // 在此处添加自定义逻辑  
        console.log('自定义的返回操作');  
        // 调用原始的mui.back函数  
        // originalBack();  
    };  
})
收起阅读 »

Hb工具连Apple芯片都不适配吗

HBuilderX

下载Hb界面,没有选择芯片的类型,结果下载下来是适配Inter芯片的,卡的要死,无语。

下载Hb界面,没有选择芯片的类型,结果下载下来是适配Inter芯片的,卡的要死,无语。

在manifest,json中填入的密钥,打包成apk后被解包,在AndroidManifest.xml中可以明文看到

安全漏洞

如题,请问这个问题作为开发者要怎么规避?

在manifest,json中填入的密钥,打包成apk后被解包,在AndroidManifest.xml中可以明文看到

如题,请问这个问题作为开发者要怎么规避?

在manifest,json中填入的密钥,打包成apk后被解包,在AndroidManifest.xml中可以明文看到

【安卓权限说明顶部蒙层】uniapp项目全局监听权限,HBuilderX (4.0+) android 平台支持;上架华为应用市场审核所需

应用上架 华为应用市场 权限 Android 安卓

文章不再更新,请前往插件市场下载使用,插件地址:https://ext.dcloud.net.cn/plugin?id=22675

安卓离线打包(本地打包)需要添加一些sdk,具体看这篇文章https://ask.dcloud.net.cn/article/41380


更新时间:2024-08-12 14:29
旧版时间:2024-08-12 14:06


测试部分,可能有bug
目前发现bug未解决
1、保存代码,代码热更新后,触发监听会报Error: [JS Framework] Failed to receiveTasks, instance (1) is not available.这个错误,重新编译正常

模拟器效果截图

permissionListener.js

/**  
 * @author cai  
 * @time 2024-08-12 14:29  
 * uni.createRequestPermissionListener()  
 * 文档地址:https://uniapp.dcloud.net.cn/api/system/create-request-permission-listener.html  
 * 注意:HBuilderX (4.0+) android 平台支持;HBuilderX 4.01 Vue2项目需要使用自定义基座测试监听权限申请的功能,标准基座暂不支持测试。  
 */  
// #ifndef APP  
export default null;  
// #endif   
// #ifdef APP  
const { osName } = uni.getSystemInfoSync();  
let permissionListener = null;  
// 是安卓平台,同时有uni.createRequestPermissionListener这个api  
if (osName === "android" && uni.createRequestPermissionListener) {  
    permissionListener = uni.createRequestPermissionListener();  
}  
let canRunListener = true;  // 是否可以执行所有监听方法  
let canStopListener = true; // 是否可以执行取消所有监听方法,避免唤起权限会触发App.vue的onHide生命周期  
const permissionEnums = {  
    "ACCESS_COARSE_LOCATION": {  
        name: "定位",  
        explain: "展示附近店铺、填写收货地址等相关功能"  
    },  
    "ACCESS_FINE_LOCATION": {  
        name: "定位",  
        explain: "展示附近店铺、填写收货地址等相关功能"  
    },  
    "READ_EXTERNAL_STORAGE": {  
        name: "存储",  
        explain: "上传图片、上传视频等相关功能"  
    },  
    "CAMERA": {  
        name: "相机",  
        explain: "扫二维码、拍摄图片等相关功能"  
    },  
    "WRITE_EXTERNAL_STORAGE": {  
        name: "存储",  
        explain: "把图片保存到相册等相关功能"  
    }  
}  

/**  
 * 权限说明文字  
 * @param {String} = permissionName  例如:ACCESS_COARSE_LOCATION  
 */  
const texts = (permissionName) => {  
    let title = "";  
    let content = "";  
    let permissionInfo = permissionEnums[permissionName] || null;  
    if (permissionInfo) {  
        const { name, explain } = permissionInfo;  
        title = `${name}权限使用说明`;  
        content = `将获取${name}权限,用于${explain}`;  
    } else {  
        title = "";  
        content = "";  
    }  
    return {  
        title,  
        content  
    }  
};  

/**  
 * 绘画顶部权限说明  
 * 文档地址:https://www.html5plus.org/doc/zh_cn/nativeobj.html  
 * @function drawView title标题,content描述使用说明  
 * @function hideView 隐藏顶部权限说明  
 */  
let view = null;  
const drawView = ({ title, content }) => {  
    console.log("drawView方法的参数值:", title, content);  
    if (view || !title || !content) return;     // 没有标题和内容则return出去  
    const { windowTop, windowWidth, statusBarHeight } = uni.getSystemInfoSync();  
    const topHeight = windowTop + statusBarHeight;  
    const distance = {  
        box: 10,    // 盒子距离视图两边的距离  
        text: 20    // 文字距离视图两边的距离  
    }  
    // 标题的相关样式  
    const titleStyle = {  
        size: 16,  
        height: 16,  
        top: `${topHeight + 22}`,  
        color: "#000",  
    }  
    // 内容的相关样式  
    const contentStyle = {  
        size: 14,  
        height: 0,  
        top: `${parseInt(titleStyle.top) + titleStyle.height + 6}`,  
        color: "#656563",  
    }  
    const contentLength = content.length;   // 权限说明内容文字长度  
    const contentWidth = windowWidth - distance.text * 2;   // 内容的宽度  
    const contentRowCount = Math.floor(contentWidth / contentStyle.size);   // 一行占几个文字  
    const contentRows = Math.ceil(contentLength / contentRowCount);     // 当前内容占几行  
    contentStyle.height = contentRows * (contentStyle.size + 4);    // 内容的高度  
    /**  
     * @description 计算盒子的高度  
     * 获取content到盒子顶部距离:parseInt(contentStyle.top) - topHeight - distance.box  
     * content的高度:contentStyle.height  
     * 获取content到盒子底部的距离:(distance.text - distance.box)  
     */  
    const boxHeight = (parseInt(contentStyle.top) - topHeight - distance.box) + contentStyle.height + (distance.text - distance.box);  
    view = new plus.nativeObj.View('per-modal', {  
        top: '0',  
        left: '0',  
        width: '100%',  
        backgroundColor: 'rgba(0,0,0,0.2)'  
    })  
    view.drawRect({  
        color: '#fff',  
        radius: '5px',  
    }, {  
        top: `${topHeight + distance.box}px`,  
        left: `${distance.box}px`,  
        right: `${distance.box}px`,  
        height: `${boxHeight}px`  
    })  
    view.drawText(title, {  
        top: `${titleStyle.top}px`,  
        left: `${distance.text}px`,  
        left: `${distance.text}px`,  
        height: `${titleStyle.height}px`  
    }, {  
        size: `${titleStyle.size}px`,  
        align: "left",  
        color: titleStyle.color,  
        weight: "bold"  
    })  
    view.drawText(content, {  
        top: `${contentStyle.top}px`,  
        left: `${distance.text}px`,  
        right: `${distance.text}px`,  
        height: `${contentStyle.height}px`,  
    }, {  
        size: `${contentStyle.size}px`,  
        lineSpacing: "2px",  
        align: "left",  
        color: contentStyle.color,  
        verticalAlign: "top",  
        whiteSpace: "normal"  
    })  
    let timer = setTimeout(() => {  
        view && view.show();  
        clearTimeout(timer);  
        timer = null;  
    }, 200)  
}  
// 关闭顶部权限说明  
const hideView = () => {  
    if (view) {  
        view.hide();  
        view = null;  
    }  
}  

// 监听权限方法  
const listenerFunc = () => {  
    stopFunc();     // 取消所有监听方法  
    if (canRunListener && permissionListener) {  
        let permissionName = "";    // 权限名称  
        let hasConfirm = false;     // 是否有权限弹窗(触发permissionListener.onConfirm这个回调)  
        canRunListener = false;  
        canStopListener = false;  
        // 监听申请系统权限  
        permissionListener.onRequest((e) => {  
            console.log("permissionListener.onRequest回调:", e);  
            if (Array.isArray(e) && e.length > 0) {  
                const stringToArray = e[0].split(".");  
                permissionName = stringToArray[stringToArray.length - 1];  
                console.log("权限名称:", permissionName);  
            }  
        });  
        // 监听弹出系统权限授权框  
        permissionListener.onConfirm((e) => {  
            console.log("permissionListener.onConfirm回调:", e);  
            hasConfirm = true;  
            if (permissionName) {  
                drawView(texts(permissionName));  
            }  
        });  
        // 监听权限申请完成  
        permissionListener.onComplete((e) => {  
            console.log("permissionListener.onComplete回调:", e);  
            // e.length === 0:权限列表无值,则不继续做相对逻辑  
            if (e.length === 0) return;  
            let name = "";      // 权限名称  
            let explain = "";   // 权限说明  
            if (permissionName && permissionEnums[permissionName]) {  
                name = permissionEnums[permissionName].name;  
                explain = permissionEnums[permissionName].explain;  
            }  
            const Manifest = plus.android.importClass("android.Manifest");  
            const MainActivity = plus.android.runtimeMainActivity();  
            const permissionStatus = MainActivity.checkSelfPermission(Manifest.permission[permissionName]);  
            console.log("当前权限状态:", permissionStatus);  
            /**  
             * @description 永久拒绝该权限,则引导用户前往设置页  
             * permissionStatus != 0:权限状态是拒绝  
             * !hasConfirm:没有permissionListener.onConfirm这个回调  
             */  
            if (permissionStatus != 0 && !hasConfirm && name && explain) {  
                uni.showModal({  
                    title: "温馨提示",  
                    content: `开启${name}权限后,才能${explain}`,  
                    showCancel: true,  
                    confirmText: "去设置",  
                    success: (res) => {  
                        if (res.confirm) {  
                            uni.openAppAuthorizeSetting();  
                        }  
                    }  
                })  
                return;  
            }  
            canStopListener = true;  
            hideView();  
        });  
    }  
}   

// 取消所有监听方法  
const stopFunc = () => {  
    if (canStopListener && permissionListener) {  
        console.log("执行permissionListener.stop()方法");  
        canRunListener = true;  
        hideView();  
        permissionListener.stop();  
    }  
}  

let exportObj = null;  
if (permissionListener) {  
    exportObj = {  
        listenerFunc,  
        stopFunc  
    };  
} else {  
    exportObj = null;  
}  

export default exportObj;  
// #endif

App.vue

    /**  
     * @author cai  
     * @time 2024-07-24 10:52  
     */  
    import permissionListener from  "@/utils/permissionListener.js";  
    export default {  
        onLaunch: function() {  
            console.log('App Launch')  
        },  
        onShow: function() {  
            permissionListener && permissionListener.listenerFunc();  
            console.log('App Show')   
        },  
        onHide: function() {   
            permissionListener && permissionListener.stopFunc();  
            console.log('App Hide')  
        }  
    }

↓↓↓ 各位大佬点点赞

继续阅读 »

文章不再更新,请前往插件市场下载使用,插件地址:https://ext.dcloud.net.cn/plugin?id=22675

安卓离线打包(本地打包)需要添加一些sdk,具体看这篇文章https://ask.dcloud.net.cn/article/41380


更新时间:2024-08-12 14:29
旧版时间:2024-08-12 14:06


测试部分,可能有bug
目前发现bug未解决
1、保存代码,代码热更新后,触发监听会报Error: [JS Framework] Failed to receiveTasks, instance (1) is not available.这个错误,重新编译正常

模拟器效果截图

permissionListener.js

/**  
 * @author cai  
 * @time 2024-08-12 14:29  
 * uni.createRequestPermissionListener()  
 * 文档地址:https://uniapp.dcloud.net.cn/api/system/create-request-permission-listener.html  
 * 注意:HBuilderX (4.0+) android 平台支持;HBuilderX 4.01 Vue2项目需要使用自定义基座测试监听权限申请的功能,标准基座暂不支持测试。  
 */  
// #ifndef APP  
export default null;  
// #endif   
// #ifdef APP  
const { osName } = uni.getSystemInfoSync();  
let permissionListener = null;  
// 是安卓平台,同时有uni.createRequestPermissionListener这个api  
if (osName === "android" && uni.createRequestPermissionListener) {  
    permissionListener = uni.createRequestPermissionListener();  
}  
let canRunListener = true;  // 是否可以执行所有监听方法  
let canStopListener = true; // 是否可以执行取消所有监听方法,避免唤起权限会触发App.vue的onHide生命周期  
const permissionEnums = {  
    "ACCESS_COARSE_LOCATION": {  
        name: "定位",  
        explain: "展示附近店铺、填写收货地址等相关功能"  
    },  
    "ACCESS_FINE_LOCATION": {  
        name: "定位",  
        explain: "展示附近店铺、填写收货地址等相关功能"  
    },  
    "READ_EXTERNAL_STORAGE": {  
        name: "存储",  
        explain: "上传图片、上传视频等相关功能"  
    },  
    "CAMERA": {  
        name: "相机",  
        explain: "扫二维码、拍摄图片等相关功能"  
    },  
    "WRITE_EXTERNAL_STORAGE": {  
        name: "存储",  
        explain: "把图片保存到相册等相关功能"  
    }  
}  

/**  
 * 权限说明文字  
 * @param {String} = permissionName  例如:ACCESS_COARSE_LOCATION  
 */  
const texts = (permissionName) => {  
    let title = "";  
    let content = "";  
    let permissionInfo = permissionEnums[permissionName] || null;  
    if (permissionInfo) {  
        const { name, explain } = permissionInfo;  
        title = `${name}权限使用说明`;  
        content = `将获取${name}权限,用于${explain}`;  
    } else {  
        title = "";  
        content = "";  
    }  
    return {  
        title,  
        content  
    }  
};  

/**  
 * 绘画顶部权限说明  
 * 文档地址:https://www.html5plus.org/doc/zh_cn/nativeobj.html  
 * @function drawView title标题,content描述使用说明  
 * @function hideView 隐藏顶部权限说明  
 */  
let view = null;  
const drawView = ({ title, content }) => {  
    console.log("drawView方法的参数值:", title, content);  
    if (view || !title || !content) return;     // 没有标题和内容则return出去  
    const { windowTop, windowWidth, statusBarHeight } = uni.getSystemInfoSync();  
    const topHeight = windowTop + statusBarHeight;  
    const distance = {  
        box: 10,    // 盒子距离视图两边的距离  
        text: 20    // 文字距离视图两边的距离  
    }  
    // 标题的相关样式  
    const titleStyle = {  
        size: 16,  
        height: 16,  
        top: `${topHeight + 22}`,  
        color: "#000",  
    }  
    // 内容的相关样式  
    const contentStyle = {  
        size: 14,  
        height: 0,  
        top: `${parseInt(titleStyle.top) + titleStyle.height + 6}`,  
        color: "#656563",  
    }  
    const contentLength = content.length;   // 权限说明内容文字长度  
    const contentWidth = windowWidth - distance.text * 2;   // 内容的宽度  
    const contentRowCount = Math.floor(contentWidth / contentStyle.size);   // 一行占几个文字  
    const contentRows = Math.ceil(contentLength / contentRowCount);     // 当前内容占几行  
    contentStyle.height = contentRows * (contentStyle.size + 4);    // 内容的高度  
    /**  
     * @description 计算盒子的高度  
     * 获取content到盒子顶部距离:parseInt(contentStyle.top) - topHeight - distance.box  
     * content的高度:contentStyle.height  
     * 获取content到盒子底部的距离:(distance.text - distance.box)  
     */  
    const boxHeight = (parseInt(contentStyle.top) - topHeight - distance.box) + contentStyle.height + (distance.text - distance.box);  
    view = new plus.nativeObj.View('per-modal', {  
        top: '0',  
        left: '0',  
        width: '100%',  
        backgroundColor: 'rgba(0,0,0,0.2)'  
    })  
    view.drawRect({  
        color: '#fff',  
        radius: '5px',  
    }, {  
        top: `${topHeight + distance.box}px`,  
        left: `${distance.box}px`,  
        right: `${distance.box}px`,  
        height: `${boxHeight}px`  
    })  
    view.drawText(title, {  
        top: `${titleStyle.top}px`,  
        left: `${distance.text}px`,  
        left: `${distance.text}px`,  
        height: `${titleStyle.height}px`  
    }, {  
        size: `${titleStyle.size}px`,  
        align: "left",  
        color: titleStyle.color,  
        weight: "bold"  
    })  
    view.drawText(content, {  
        top: `${contentStyle.top}px`,  
        left: `${distance.text}px`,  
        right: `${distance.text}px`,  
        height: `${contentStyle.height}px`,  
    }, {  
        size: `${contentStyle.size}px`,  
        lineSpacing: "2px",  
        align: "left",  
        color: contentStyle.color,  
        verticalAlign: "top",  
        whiteSpace: "normal"  
    })  
    let timer = setTimeout(() => {  
        view && view.show();  
        clearTimeout(timer);  
        timer = null;  
    }, 200)  
}  
// 关闭顶部权限说明  
const hideView = () => {  
    if (view) {  
        view.hide();  
        view = null;  
    }  
}  

// 监听权限方法  
const listenerFunc = () => {  
    stopFunc();     // 取消所有监听方法  
    if (canRunListener && permissionListener) {  
        let permissionName = "";    // 权限名称  
        let hasConfirm = false;     // 是否有权限弹窗(触发permissionListener.onConfirm这个回调)  
        canRunListener = false;  
        canStopListener = false;  
        // 监听申请系统权限  
        permissionListener.onRequest((e) => {  
            console.log("permissionListener.onRequest回调:", e);  
            if (Array.isArray(e) && e.length > 0) {  
                const stringToArray = e[0].split(".");  
                permissionName = stringToArray[stringToArray.length - 1];  
                console.log("权限名称:", permissionName);  
            }  
        });  
        // 监听弹出系统权限授权框  
        permissionListener.onConfirm((e) => {  
            console.log("permissionListener.onConfirm回调:", e);  
            hasConfirm = true;  
            if (permissionName) {  
                drawView(texts(permissionName));  
            }  
        });  
        // 监听权限申请完成  
        permissionListener.onComplete((e) => {  
            console.log("permissionListener.onComplete回调:", e);  
            // e.length === 0:权限列表无值,则不继续做相对逻辑  
            if (e.length === 0) return;  
            let name = "";      // 权限名称  
            let explain = "";   // 权限说明  
            if (permissionName && permissionEnums[permissionName]) {  
                name = permissionEnums[permissionName].name;  
                explain = permissionEnums[permissionName].explain;  
            }  
            const Manifest = plus.android.importClass("android.Manifest");  
            const MainActivity = plus.android.runtimeMainActivity();  
            const permissionStatus = MainActivity.checkSelfPermission(Manifest.permission[permissionName]);  
            console.log("当前权限状态:", permissionStatus);  
            /**  
             * @description 永久拒绝该权限,则引导用户前往设置页  
             * permissionStatus != 0:权限状态是拒绝  
             * !hasConfirm:没有permissionListener.onConfirm这个回调  
             */  
            if (permissionStatus != 0 && !hasConfirm && name && explain) {  
                uni.showModal({  
                    title: "温馨提示",  
                    content: `开启${name}权限后,才能${explain}`,  
                    showCancel: true,  
                    confirmText: "去设置",  
                    success: (res) => {  
                        if (res.confirm) {  
                            uni.openAppAuthorizeSetting();  
                        }  
                    }  
                })  
                return;  
            }  
            canStopListener = true;  
            hideView();  
        });  
    }  
}   

// 取消所有监听方法  
const stopFunc = () => {  
    if (canStopListener && permissionListener) {  
        console.log("执行permissionListener.stop()方法");  
        canRunListener = true;  
        hideView();  
        permissionListener.stop();  
    }  
}  

let exportObj = null;  
if (permissionListener) {  
    exportObj = {  
        listenerFunc,  
        stopFunc  
    };  
} else {  
    exportObj = null;  
}  

export default exportObj;  
// #endif

App.vue

    /**  
     * @author cai  
     * @time 2024-07-24 10:52  
     */  
    import permissionListener from  "@/utils/permissionListener.js";  
    export default {  
        onLaunch: function() {  
            console.log('App Launch')  
        },  
        onShow: function() {  
            permissionListener && permissionListener.listenerFunc();  
            console.log('App Show')   
        },  
        onHide: function() {   
            permissionListener && permissionListener.stopFunc();  
            console.log('App Hide')  
        }  
    }

↓↓↓ 各位大佬点点赞

收起阅读 »