HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

关于右键菜单

右键菜单的 复制、剪切,就好了。整那么一长串文字干啥?不觉得是脱裤子放屁吗?

右键菜单的 复制、剪切,就好了。整那么一长串文字干啥?不觉得是脱裤子放屁吗?

uniApp获取通话记录

经验分享

uniApp获取通话记录

首先讲一个大坑

要拿到通过记录真机调试时候必须前给足权限,必须打包成自定义基座再测试!!!!!!一定要先打包!!!!

必要权限

首先勾取manifest.json里APP模块里通讯录权限
添加以下权限

<uses-feature android:name="android.permission.READ_CONTACTS"/>  
<uses-permission android:name="android.permission.READ_CALL_LOG"/>

为了怕遗漏我把我所有的权限都放出来提供参考

<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.CALL_PHONE"/>  
<uses-feature android:name="android.permission.READ_CONTACTS"/>  
<uses-permission android:name="android.permission.READ_CALL_LOG"/>  
<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.WRITE_CONTACTS"/>  
<uses-permission android:name="android.permission.READ_CONTACTS"/>  

然后打包制作自定义基座

获取通话记录代码

我也是从其他地方粘的

getCalllog(){  
                var CallLog = plus.android.importClass('android.provider.CallLog');  
                var Activity = plus.android.runtimeMainActivity();  
                var ContentResolver = plus.android.importClass('android.content.ContentResolver');  
                var resolver = Activity.getContentResolver();  
                plus.android.importClass(resolver);  
                var String = plus.android.importClass("java.lang.String");  
                var cs = resolver.query(CallLog.Calls.CONTENT_URI, null, null, null, CallLog.Calls.DEFAULT_SORT_ORDER);  
                var talist = [];  
                uni.showLoading({  
                    title: "匹配通话记录中.."  
                });  
                var count = 0; // 记录多少条 用于处理循环跳出  
                while (plus.android.invoke(cs, "moveToNext")) {  
                    count++;  
                    talist.push({  
                        xm: plus.android.invoke(cs, "getString", plus.android.invoke(cs, "getColumnIndex", CallLog.Calls.CACHED_NAME)),  
                        telphone: plus.android.invoke(cs, "getString", plus.android.invoke(cs, "getColumnIndex", CallLog.Calls.NUMBER)),  
                        duration: plus.android.invoke(cs, "getString", plus.android.invoke(cs, "getColumnIndex", CallLog.Calls.DURATION)),  
                        date: plus.android.invoke(cs, "getString", plus.android.invoke(cs, "getColumnIndex", CallLog.Calls.DATE)),  
                        type: plus.android.invoke(cs, "getString", plus.android.invoke(cs, "getColumnIndex", CallLog.Calls.TYPE))  
                    });  
                    if(count > 50){  
                        break;  
                    }  
                }  
                uni.hideLoading();  
                console.info("talist",talist);  
            },
继续阅读 »

uniApp获取通话记录

首先讲一个大坑

要拿到通过记录真机调试时候必须前给足权限,必须打包成自定义基座再测试!!!!!!一定要先打包!!!!

必要权限

首先勾取manifest.json里APP模块里通讯录权限
添加以下权限

<uses-feature android:name="android.permission.READ_CONTACTS"/>  
<uses-permission android:name="android.permission.READ_CALL_LOG"/>

为了怕遗漏我把我所有的权限都放出来提供参考

<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.CALL_PHONE"/>  
<uses-feature android:name="android.permission.READ_CONTACTS"/>  
<uses-permission android:name="android.permission.READ_CALL_LOG"/>  
<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.WRITE_CONTACTS"/>  
<uses-permission android:name="android.permission.READ_CONTACTS"/>  

然后打包制作自定义基座

获取通话记录代码

我也是从其他地方粘的

getCalllog(){  
                var CallLog = plus.android.importClass('android.provider.CallLog');  
                var Activity = plus.android.runtimeMainActivity();  
                var ContentResolver = plus.android.importClass('android.content.ContentResolver');  
                var resolver = Activity.getContentResolver();  
                plus.android.importClass(resolver);  
                var String = plus.android.importClass("java.lang.String");  
                var cs = resolver.query(CallLog.Calls.CONTENT_URI, null, null, null, CallLog.Calls.DEFAULT_SORT_ORDER);  
                var talist = [];  
                uni.showLoading({  
                    title: "匹配通话记录中.."  
                });  
                var count = 0; // 记录多少条 用于处理循环跳出  
                while (plus.android.invoke(cs, "moveToNext")) {  
                    count++;  
                    talist.push({  
                        xm: plus.android.invoke(cs, "getString", plus.android.invoke(cs, "getColumnIndex", CallLog.Calls.CACHED_NAME)),  
                        telphone: plus.android.invoke(cs, "getString", plus.android.invoke(cs, "getColumnIndex", CallLog.Calls.NUMBER)),  
                        duration: plus.android.invoke(cs, "getString", plus.android.invoke(cs, "getColumnIndex", CallLog.Calls.DURATION)),  
                        date: plus.android.invoke(cs, "getString", plus.android.invoke(cs, "getColumnIndex", CallLog.Calls.DATE)),  
                        type: plus.android.invoke(cs, "getString", plus.android.invoke(cs, "getColumnIndex", CallLog.Calls.TYPE))  
                    });  
                    if(count > 50){  
                        break;  
                    }  
                }  
                uni.hideLoading();  
                console.info("talist",talist);  
            },
收起阅读 »

全栈开发在线接单(有团队)

uniapp 外包接单 外包

uniapp项目开发在线接单,可定制,可二开,时间充裕,保质保量
有需要请联系 vx : docxxlsx

uniapp项目开发在线接单,可定制,可二开,时间充裕,保质保量
有需要请联系 vx : docxxlsx

记录一个支付宝小程序问题:切换组件出现生命周期不存在

组件 tabbar 支付宝小程序

最近写一个支付宝、微信、h5三端互通 的程序,用uniapp开发,

然后涉及到不同身份,不同的主页,所以原生的TabBar理所当然的GG,接着就自己定义了一个tabBar组件;然后发现了狗屎,,,小程序不支持component动态组件,,,就只能v-if切换组件的方式实现tabBar了;

接着,由于一开始使用的原生tabBar,所以页面是放在pages下面的,改成组件切换后,tabBar里切换的组件页面在支付宝小程序全部失去生命周期-.-||,绝了。

一开始以为是v-if的问题,后面排查完,改一个路径即可,换到components下

特此记录,狗屎小程序!!!

继续阅读 »

最近写一个支付宝、微信、h5三端互通 的程序,用uniapp开发,

然后涉及到不同身份,不同的主页,所以原生的TabBar理所当然的GG,接着就自己定义了一个tabBar组件;然后发现了狗屎,,,小程序不支持component动态组件,,,就只能v-if切换组件的方式实现tabBar了;

接着,由于一开始使用的原生tabBar,所以页面是放在pages下面的,改成组件切换后,tabBar里切换的组件页面在支付宝小程序全部失去生命周期-.-||,绝了。

一开始以为是v-if的问题,后面排查完,改一个路径即可,换到components下

特此记录,狗屎小程序!!!

收起阅读 »

【分享】分享一次AppStore成功上架经验

最近刚刚在AppStore成功上架了一款VPN类软件,App采用离线打包,期间也是经历一系列问题,现在整理出来分享给有需要的朋友。

Guideline 4.3(a) - Design - Spam
这个是比较常见的打回原因,大意就是app雷同,这个雷同分2方面,一是UI/功能与其他APP雷同,二是代码雷同,根据我们上架后的修改情况来看,我们遇到的第一次4.3,是因为代码雷同(因为自始至终我们都不曾修改UI及布局)。这里说一下我们的使用过的处理方式:

  1. 离线打包的项目名,必须修改,不要使用默认命名“HBuilder-Hello”,项目中所有涉及到HBuilder的关键字都搜索并修改
  2. HBuidler发布离线资源后,使用“javascript-obfuscator”等工具进行混淆,具体方式网上很多,这里不再赘述
  3. 在appstoreconnect上提审/回复审核消息时,提前备注,“app使用的是uniapp这类hybrid框架,底层框架代码可能部分雷同”,
  4. 尽可能详细的准备app资质类文件,由于我们是VPN这类敏感软件,本身上架就有诸多限制,所以相关证书不能少,有“代码版权”最佳
  5. 4.3这个打回,很奇葩的,我们居然遇到2次,首次提审遇到一次,上线成功后,进行过几次修改,发布新版,后面第5次更新版时,居然再次被4.3打回,这次重点强调第3点,态度良好的跟苹果审核员沟通,就再次过了。

Guideline 5.1.1 - Legal - Data Collection and Storage
这个比较简单,根据具体反馈内容显示,苹果的内购,必须有游客购买功能,很多人初次上架都会遇到,开发相应功能即可

Guideline 2.1 - Performance - App Completeness
这个也比较简单,app不可以有假死,用户无法操作的情况出现,属于业务范畴,比较容易修改,覆盖测试到位就行

Guideline 5.1.1(v) - Data Collection and Storage
神奇的再次遇到5.1.1,而且还是成功上架后,第4次更新提审时被打回,打回原因居然是app必须要有注销功能,无奈加上注销后,审核通过

总体心得:

  1. 与苹果审核人员沟通,态度要诚恳,各类资质提前备好,尽量让审核人员审核方便,提审功能尽量写清楚怎么操作,入口在哪里(审核员也是人,你让他舒服,他也让你舒服,能省不少麻烦)
  2. 离线打包资源如果混淆不够,可以尝试对app进行加固混淆:ipaguard。不过很遗憾,我们的app有vpn进程服务,加固后找不到进程入口,所以未使用。
  3. 很多人遇到的隐私类问题,我们这次完美规避。因为之前上架android各大商店,频繁被隐私类问题打回(动辄是XXsdk违规收集用户隐私),这次的推送、广告、一键登录、内购等等uni-sdk一概未用,仅使用uniapp的基础sdk包,使用uniapp做UI及上层业务逻辑,有需要的sdk,尽量使用原生开发,便于找问题,比如内购、一键登录,都是自己接的原生。
  4. 关于热更,uniapp本身是有热更功能的,我们在android上保留了热更功能,但是为了在苹果上少一些麻烦,所以关掉了热更。
  5. 总体来说,使用uniapp负责上层业务,做到了一套代码,android及ios双端复用(底层tun2sock加速分别采用java及swift原生开发),省去不少重复内容的开发时间。离线打包+部分原生开发,提供了很高的自由度,在审核时,省去许多麻烦。
  6. 本项目是外包性质,先上架的另一个android同类app,后上架ios,均使用uniapp,开发效率不错。

欢迎加Q交流:272669509

继续阅读 »

最近刚刚在AppStore成功上架了一款VPN类软件,App采用离线打包,期间也是经历一系列问题,现在整理出来分享给有需要的朋友。

Guideline 4.3(a) - Design - Spam
这个是比较常见的打回原因,大意就是app雷同,这个雷同分2方面,一是UI/功能与其他APP雷同,二是代码雷同,根据我们上架后的修改情况来看,我们遇到的第一次4.3,是因为代码雷同(因为自始至终我们都不曾修改UI及布局)。这里说一下我们的使用过的处理方式:

  1. 离线打包的项目名,必须修改,不要使用默认命名“HBuilder-Hello”,项目中所有涉及到HBuilder的关键字都搜索并修改
  2. HBuidler发布离线资源后,使用“javascript-obfuscator”等工具进行混淆,具体方式网上很多,这里不再赘述
  3. 在appstoreconnect上提审/回复审核消息时,提前备注,“app使用的是uniapp这类hybrid框架,底层框架代码可能部分雷同”,
  4. 尽可能详细的准备app资质类文件,由于我们是VPN这类敏感软件,本身上架就有诸多限制,所以相关证书不能少,有“代码版权”最佳
  5. 4.3这个打回,很奇葩的,我们居然遇到2次,首次提审遇到一次,上线成功后,进行过几次修改,发布新版,后面第5次更新版时,居然再次被4.3打回,这次重点强调第3点,态度良好的跟苹果审核员沟通,就再次过了。

Guideline 5.1.1 - Legal - Data Collection and Storage
这个比较简单,根据具体反馈内容显示,苹果的内购,必须有游客购买功能,很多人初次上架都会遇到,开发相应功能即可

Guideline 2.1 - Performance - App Completeness
这个也比较简单,app不可以有假死,用户无法操作的情况出现,属于业务范畴,比较容易修改,覆盖测试到位就行

Guideline 5.1.1(v) - Data Collection and Storage
神奇的再次遇到5.1.1,而且还是成功上架后,第4次更新提审时被打回,打回原因居然是app必须要有注销功能,无奈加上注销后,审核通过

总体心得:

  1. 与苹果审核人员沟通,态度要诚恳,各类资质提前备好,尽量让审核人员审核方便,提审功能尽量写清楚怎么操作,入口在哪里(审核员也是人,你让他舒服,他也让你舒服,能省不少麻烦)
  2. 离线打包资源如果混淆不够,可以尝试对app进行加固混淆:ipaguard。不过很遗憾,我们的app有vpn进程服务,加固后找不到进程入口,所以未使用。
  3. 很多人遇到的隐私类问题,我们这次完美规避。因为之前上架android各大商店,频繁被隐私类问题打回(动辄是XXsdk违规收集用户隐私),这次的推送、广告、一键登录、内购等等uni-sdk一概未用,仅使用uniapp的基础sdk包,使用uniapp做UI及上层业务逻辑,有需要的sdk,尽量使用原生开发,便于找问题,比如内购、一键登录,都是自己接的原生。
  4. 关于热更,uniapp本身是有热更功能的,我们在android上保留了热更功能,但是为了在苹果上少一些麻烦,所以关掉了热更。
  5. 总体来说,使用uniapp负责上层业务,做到了一套代码,android及ios双端复用(底层tun2sock加速分别采用java及swift原生开发),省去不少重复内容的开发时间。离线打包+部分原生开发,提供了很高的自由度,在审核时,省去许多麻烦。
  6. 本项目是外包性质,先上架的另一个android同类app,后上架ios,均使用uniapp,开发效率不错。

欢迎加Q交流:272669509

收起阅读 »

1

1

1

vue3+electron31+element-plus后台系统管理应用程序

整合vite5.x+electron31+pinia2+vue-i18n+echarts全新后台管理方案Vue3ElectronAdmin。提供了4种通用布局模板,支持中英文/繁体国际化解决方案、动态路由权限,实现了表格、表单、列表、图表、编辑器、错误处理等模块。

原创Electron31+Vue3+ElementPlus桌面端后台管理系统

img

img

技术栈

  • 编辑器:vscode
  • 框架技术:vite^5.3.4+vue^3.4.31+vue-router^4.4.0
  • 跨端框架:electron^31.3.0
  • 组件库:element-plus^2.7.8
  • 状态管理:pinia^2.2.0
  • 国际化方案:vue-i18n@9
  • 图表组件:echarts^5.5.1
  • markdown编辑器:md-editor-v3^4.18.0
  • 模拟数据:mockjs^1.1.0
  • 打包工具:electron-builder^24.13.3
  • electron+vite桥接插件:vite-plugin-electron^0.28.7

img

img

img

功能性

1、最新前端技术栈Vite5.x、Vue3、Electron31、ElementPlus、Vue-I18n、Echarts
2、支持中英文/繁体国际化解决方案
3、支持动态权限路由、多页签缓存路由
4、封装多窗口管理器
5、内置4种通用布局模板、自由切换风格
6、整合通用的表格、表单、列表、图表、编辑器、错误处理等模块
7、高颜值UI界面、轻量级模块化、高定制性

img

项目结构

整个项目整合vite.js+electronjs跨平台技术,采用vue3 setup语法编码。

img

img

electronjs主线程配置

/**  
 * electron主线程配置  
 * @author andy  
 */  

import { app, BrowserWindow } from 'electron'  

import { WindowManager } from '../src/windows/index.js'  

// 忽略安全警告提示 Electron Security Warning (Insecure Content-Security-Policy)  
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = true  

const createWindow = () => {  
  let win = new WindowManager()  
  win.create({isMajor: true})  
  // 系统托盘管理  
  win.trayManager()  
  // 监听ipcMain事件  
  win.ipcManager()  
}  

app.whenReady().then(() => {  
  createWindow()  

  app.on('activate', () => {  
    if(BrowserWindow.getAllWindows().length === 0) createWindow()  
  })  
})  

app.on('window-all-closed', () => {  
  if(process.platform !== 'darwin') app.quit()  
})

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

electron31-vue3-admin布局模板

img

img

/**  
 * 通用布局模板  
 * @author Andy  
*/  

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

  // 引入布局模板  
  import Classic from './template/classic/index.vue'  
  import Columns from './template/columns/index.vue'  
  import Vertical from './template/vertical/index.vue'  
  import Horizontal from './template/horizontal/index.vue'  

  const appstate = appState()  

  const LayoutMap = {  
    'classic': Classic,  
    'columns': Columns,  
    'vertical': Vertical,  
    'horizontal': Horizontal  
  }  
</script>  

<template>  
  <div class="vuadmin__container" :style="{'--themeSkin': appstate.config.skin}">  
    <component :is="LayoutMap[appstate.config.layout]" />  
  </div>  
</template>

electron31-admin国际化配置

img

img

/**  
 * 国际化配置  
 * @author YXY  
 */  

import { createI18n } from 'vue-i18n'  
import { appState } from '@/pinia/modules/app'  

// 引入语言配置  
import enUS from './en-US'  
import zhCN from './zh-CN'  
import zhTW from './zh-TW'  

// 默认语言  
export const langVal = 'zh-CN'  

export default async (app) => {  
  const appstate = appState()  
  const lang = appstate.lang || langVal  
  appstate.setLang(lang)  

  const i18n = createI18n({  
    legacy: false,  
    locale: lang,  
    messages: {  
      'en': enUS,  
      'zh-CN': zhCN,  
      'zh-TW': zhTW  
    }  
  })  

  app.use(i18n)  
}

vue3封装echarts图表hook

img

img

/**  
 * 动态图表Hook  
 */  

import { onMounted, onBeforeUnmount, ref } from 'vue'  
import * as echarts from 'echarts'  
import elementResizeDetectorMaker from 'element-resize-detector'  

export function useEcharts(el, options) {  
  let chartEl  
  let chartRef = ref(null)  
  let erd = elementResizeDetectorMaker()  

  const resizeHandle = () => {  
    chartEl && chartEl.resize()  
  }  

  onMounted(() => {  
    if(el?.value) {  
      chartEl = echarts.init(el.value)  
      chartEl.setOption(options)  
      chartRef.value = chartEl  
    }  
    erd.listenTo(el.value, resizeHandle)  
  })  

  onBeforeUnmount(() => {  
    chartEl.dispose()  
    erd.removeListener(el.value, resizeHandle)  
  })  

  return chartRef  
}

OK,以上就是Electron31+Vue3+ElementPlus跨平台实战开发后台管理系统的一些分享。整个项目涉及到的知识点还是蛮多的,限于篇幅就暂时分享到这里。希望对大家有所帮助哈~

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

继续阅读 »

整合vite5.x+electron31+pinia2+vue-i18n+echarts全新后台管理方案Vue3ElectronAdmin。提供了4种通用布局模板,支持中英文/繁体国际化解决方案、动态路由权限,实现了表格、表单、列表、图表、编辑器、错误处理等模块。

原创Electron31+Vue3+ElementPlus桌面端后台管理系统

img

img

技术栈

  • 编辑器:vscode
  • 框架技术:vite^5.3.4+vue^3.4.31+vue-router^4.4.0
  • 跨端框架:electron^31.3.0
  • 组件库:element-plus^2.7.8
  • 状态管理:pinia^2.2.0
  • 国际化方案:vue-i18n@9
  • 图表组件:echarts^5.5.1
  • markdown编辑器:md-editor-v3^4.18.0
  • 模拟数据:mockjs^1.1.0
  • 打包工具:electron-builder^24.13.3
  • electron+vite桥接插件:vite-plugin-electron^0.28.7

img

img

img

功能性

1、最新前端技术栈Vite5.x、Vue3、Electron31、ElementPlus、Vue-I18n、Echarts
2、支持中英文/繁体国际化解决方案
3、支持动态权限路由、多页签缓存路由
4、封装多窗口管理器
5、内置4种通用布局模板、自由切换风格
6、整合通用的表格、表单、列表、图表、编辑器、错误处理等模块
7、高颜值UI界面、轻量级模块化、高定制性

img

项目结构

整个项目整合vite.js+electronjs跨平台技术,采用vue3 setup语法编码。

img

img

electronjs主线程配置

/**  
 * electron主线程配置  
 * @author andy  
 */  

import { app, BrowserWindow } from 'electron'  

import { WindowManager } from '../src/windows/index.js'  

// 忽略安全警告提示 Electron Security Warning (Insecure Content-Security-Policy)  
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = true  

const createWindow = () => {  
  let win = new WindowManager()  
  win.create({isMajor: true})  
  // 系统托盘管理  
  win.trayManager()  
  // 监听ipcMain事件  
  win.ipcManager()  
}  

app.whenReady().then(() => {  
  createWindow()  

  app.on('activate', () => {  
    if(BrowserWindow.getAllWindows().length === 0) createWindow()  
  })  
})  

app.on('window-all-closed', () => {  
  if(process.platform !== 'darwin') app.quit()  
})

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

electron31-vue3-admin布局模板

img

img

/**  
 * 通用布局模板  
 * @author Andy  
*/  

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

  // 引入布局模板  
  import Classic from './template/classic/index.vue'  
  import Columns from './template/columns/index.vue'  
  import Vertical from './template/vertical/index.vue'  
  import Horizontal from './template/horizontal/index.vue'  

  const appstate = appState()  

  const LayoutMap = {  
    'classic': Classic,  
    'columns': Columns,  
    'vertical': Vertical,  
    'horizontal': Horizontal  
  }  
</script>  

<template>  
  <div class="vuadmin__container" :style="{'--themeSkin': appstate.config.skin}">  
    <component :is="LayoutMap[appstate.config.layout]" />  
  </div>  
</template>

electron31-admin国际化配置

img

img

/**  
 * 国际化配置  
 * @author YXY  
 */  

import { createI18n } from 'vue-i18n'  
import { appState } from '@/pinia/modules/app'  

// 引入语言配置  
import enUS from './en-US'  
import zhCN from './zh-CN'  
import zhTW from './zh-TW'  

// 默认语言  
export const langVal = 'zh-CN'  

export default async (app) => {  
  const appstate = appState()  
  const lang = appstate.lang || langVal  
  appstate.setLang(lang)  

  const i18n = createI18n({  
    legacy: false,  
    locale: lang,  
    messages: {  
      'en': enUS,  
      'zh-CN': zhCN,  
      'zh-TW': zhTW  
    }  
  })  

  app.use(i18n)  
}

vue3封装echarts图表hook

img

img

/**  
 * 动态图表Hook  
 */  

import { onMounted, onBeforeUnmount, ref } from 'vue'  
import * as echarts from 'echarts'  
import elementResizeDetectorMaker from 'element-resize-detector'  

export function useEcharts(el, options) {  
  let chartEl  
  let chartRef = ref(null)  
  let erd = elementResizeDetectorMaker()  

  const resizeHandle = () => {  
    chartEl && chartEl.resize()  
  }  

  onMounted(() => {  
    if(el?.value) {  
      chartEl = echarts.init(el.value)  
      chartEl.setOption(options)  
      chartRef.value = chartEl  
    }  
    erd.listenTo(el.value, resizeHandle)  
  })  

  onBeforeUnmount(() => {  
    chartEl.dispose()  
    erd.removeListener(el.value, resizeHandle)  
  })  

  return chartRef  
}

OK,以上就是Electron31+Vue3+ElementPlus跨平台实战开发后台管理系统的一些分享。整个项目涉及到的知识点还是蛮多的,限于篇幅就暂时分享到这里。希望对大家有所帮助哈~

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

收起阅读 »

uniapp开发的网页SEO优化方案-适用于uni-app开发的web站点, uniapp手机端网页适配优化

优化 SEO uniapp

基于nginx服务器优化的方案, 适用于 uniapp开发的网站, 可适用于老项目

由于官网的插件暂时不支持老的项目,所以使用这种方法
本教程亲测可用, 以及上线的项目网址: http://al.jn1tea.com/
打开调试工具, 手机模式, ctrl + u 可以查看每个页面的 seo内容**

教程内容有点长, 请耐心看完

1、uniapp设置为history模式 2、nginx设置好转发规则 3、后端服务器(类同云函数),动态给模版html设置标题、描述、关键词(根据自己需要)返回html(注意:同uniapp h5 的首页html模版一致)

教程, 前置要求, 通过服务器转发,
必须能安装nginx, 以及 node.js, express 服务器框架

在宝塔, 配置反向代理配置文件, 如果是linux 注意格式问题

处理动态生成的HTML模板, 通配符匹配, uniapp前端路径都是pages/

8332, 为你的 node.js 中express 服务器框架的端口号
注意: 由于原来的系统要求, 配置了一个全局代理, 所以这个路由匹配转发需要在全局代理上面才能生效

location ~ ^/pages/.*{  
    proxy_pass http://127.0.0.1:8332;  
    proxy_http_version 1.1;  
    proxy_read_timeout 360s;     
    proxy_redirect off;   
    proxy_set_header Upgrade $http_upgrade;  
    proxy_set_header Connection "upgrade";  
    proxy_set_header Host $host;  
    proxy_set_header X-Real-IP $remote_addr;  
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  
    proxy_set_header REMOTE-HOST $remote_addr;  

    add_header X-Cache $upstream_cache_status;  
}  

# 以下是原来的反向代理文件, 不用动  
location / {  
    proxy_pass http://127.0.0.1:8325;  
    proxy_http_version 1.1;  
    proxy_read_timeout 360s;     
    proxy_redirect off;   
    proxy_set_header Upgrade $http_upgrade;  
    proxy_set_header Connection "upgrade";  
    proxy_set_header Host $host;  
    proxy_set_header X-Real-IP $remote_addr;  
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  
    proxy_set_header REMOTE-HOST $remote_addr;  

    add_header X-Cache $upstream_cache_status;  
    #Set Nginx Cache  

    set $static_filetKzRZE5L 0;  
    if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" )  
    {  
        set $static_filetKzRZE5L 1;  
        expires 1m;  
    }  
    if ( $static_filetKzRZE5L = 0 )  
    {  
        add_header Cache-Control no-cache;  
    }  
}  
#PROXY-END/

在宝塔中安装node.js, 以及express服务器
其他linux系统可以用命令行安装,
参考教程 https://www.cnblogs.com/front-web/p/15672575.html

安装node.js, 记得设置命令行版本一致

安装完成后需要启动一个node.js的网站
首先在服务器目录下创建一个文件夹, 刚开始是空的

进入命令行终端 cd /www/wwwroot/你创建的文件夹命
在终端中运行以下命令来初始化Node.js项目:npm init -y
在终端中运行以下命令来安装Express: npm install express
安装成功后创建 views 文件夹, app.js 文件 , 修改package.json文件

package.json 启动脚本文件内容  
{  
  "name": "putao_seo",  
  "version": "1.0.0",  
  "description": "",  
  "main": "index.js",  
  "scripts": {  
    "start": "node app.js",  
    "test": "echo \"Error: no test specified\" && exit 1"  
  },  
  "keywords": [],  
  "author": "",  
  "license": "ISC",  
  "dependencies": {  
    "ejs": "^3.1.8",  
    "express": "^4.18.2"  
  }  
}

app.js 文件内容, 重点, 里面内容需要根据自己的网站配置

1. 端口号, 要和上面配置反向代理一致

app.listen(8332, () => {  
  console.log('Server is running on port 8332');  
  console.log('express服务器启动成功! 端口号:8332');  
});

2. 公共配置

const commonConfig = {  
    BASE_URL: 'http://al.jn1tea.com/', // 设置BASE_URL, 你的域名, 例如:  http://al.jn1tea.com/pages/goods_details 设置为http://al.jn1tea.com/  
    VUE_APP_INDEX_CSS_HASH: 'asxga454' //  uniapp 打包后会在static中生成一个css文件, index.xxx.css, 在这里填写  
};  

// 这里就是你的uniapp打包后的h5 每个页面的访问路径, seo优化就在这里修改title, description, keywords

// 想优化哪个页面就继续添加

app.get('/pages/goods_cate/goods_cate', (req, res) => {  
    const pageData = {  
        title: '商品分类页',  
        description: '商品分类页描述',  
        keywords: '商品, 分类',  
        ...commonConfig  
    };  
    res.render('index', pageData);  
});

// 下面是全部的源码, 根据个人网站自行修改

const express = require('express');  
const ejs = require('ejs');  
const path = require('path');  

const app = express();  

// 设置模板引擎  
app.set('view engine', 'ejs');  
app.set('views', path.join(__dirname, 'views'));  

// 静态文件目录  
app.use(express.static(path.join(__dirname, 'public')));  

// 公共配置  
const commonConfig = {  
    BASE_URL: 'http://xxx.xxxcom/', // 根据实际情况设置BASE_URL  
    VUE_APP_INDEX_CSS_HASH: 'xxx' // 根据实际情况设置VUE_APP_INDEX_CSS_HASH  
};  

// 动态生成HTML  
app.get('/pages/goods_cate/goods_cate', (req, res) => {  
    const pageData = {  
        title: '商品分类页',  
        description: '商品分类页描述',  
        keywords: '商品, 分类',  
        ...commonConfig  
    };  
    res.render('index', pageData);  
});  

// 添加新的路由处理 /pages/store/home/index  
app.get('/pages/store/home/index', (req, res) => {  
    const pageData = {  
        title: '店铺首页',  
        description: '店铺首页描述',  
        keywords: '店铺, 首页',  
        ...commonConfig  
    };  
    res.render('index', pageData);  
});  

app.listen(8332, () => {  
  console.log('Server is running on port 8332');  
  console.log('express服务器启动成功! 端口号:8332');  
});

进入views文件夹, 创建index.ejs 文件

// 配置文件, 刚才在app.js 的文件会动态解析到这里的title等标签中

<!DOCTYPE html>  
<html lang="zh-CN">  
    <head>  
        <meta charset="utf-8">  
        <meta http-equiv="X-UA-Compatible" content="IE=edge">  
        <title><%= title %> | 葡萄有约</title>  
        <meta name="Keywords" content="<%= keywords %>" />  
        <meta name="Description" content="<%= description %>" />  

        <script>  
            var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))  
            document.write('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + (coverSupport ? ', viewport-fit=cover' : '') + '" />')  
            if(window.location.protocol == 'http:'){  
                document.write('<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">')  
            }  
        </script>  
        <link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />  
    </head>  
    <body>  
        <noscript>  
            <strong>Please enable JavaScript to continue.</strong>  
        </noscript>  
        <div id="app"></div>  
        <!-- built files will be auto injected -->  
    </body>  
</html>

接下来创建一个node.js 网站

启动网站,

cd进入刚才初始化创建的目录, 用终端打开
输入命令 node app.js 启动express 服务器, 看到日志说明启动成功

注意, 如果中途修改配置文件, 记得重启 node app.js, 同理nginx 修改也要重启生效
如果没问题, 就可以访问你配置后的页面, ctrl + u 查看是否生效

继续阅读 »

基于nginx服务器优化的方案, 适用于 uniapp开发的网站, 可适用于老项目

由于官网的插件暂时不支持老的项目,所以使用这种方法
本教程亲测可用, 以及上线的项目网址: http://al.jn1tea.com/
打开调试工具, 手机模式, ctrl + u 可以查看每个页面的 seo内容**

教程内容有点长, 请耐心看完

1、uniapp设置为history模式 2、nginx设置好转发规则 3、后端服务器(类同云函数),动态给模版html设置标题、描述、关键词(根据自己需要)返回html(注意:同uniapp h5 的首页html模版一致)

教程, 前置要求, 通过服务器转发,
必须能安装nginx, 以及 node.js, express 服务器框架

在宝塔, 配置反向代理配置文件, 如果是linux 注意格式问题

处理动态生成的HTML模板, 通配符匹配, uniapp前端路径都是pages/

8332, 为你的 node.js 中express 服务器框架的端口号
注意: 由于原来的系统要求, 配置了一个全局代理, 所以这个路由匹配转发需要在全局代理上面才能生效

location ~ ^/pages/.*{  
    proxy_pass http://127.0.0.1:8332;  
    proxy_http_version 1.1;  
    proxy_read_timeout 360s;     
    proxy_redirect off;   
    proxy_set_header Upgrade $http_upgrade;  
    proxy_set_header Connection "upgrade";  
    proxy_set_header Host $host;  
    proxy_set_header X-Real-IP $remote_addr;  
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  
    proxy_set_header REMOTE-HOST $remote_addr;  

    add_header X-Cache $upstream_cache_status;  
}  

# 以下是原来的反向代理文件, 不用动  
location / {  
    proxy_pass http://127.0.0.1:8325;  
    proxy_http_version 1.1;  
    proxy_read_timeout 360s;     
    proxy_redirect off;   
    proxy_set_header Upgrade $http_upgrade;  
    proxy_set_header Connection "upgrade";  
    proxy_set_header Host $host;  
    proxy_set_header X-Real-IP $remote_addr;  
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  
    proxy_set_header REMOTE-HOST $remote_addr;  

    add_header X-Cache $upstream_cache_status;  
    #Set Nginx Cache  

    set $static_filetKzRZE5L 0;  
    if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" )  
    {  
        set $static_filetKzRZE5L 1;  
        expires 1m;  
    }  
    if ( $static_filetKzRZE5L = 0 )  
    {  
        add_header Cache-Control no-cache;  
    }  
}  
#PROXY-END/

在宝塔中安装node.js, 以及express服务器
其他linux系统可以用命令行安装,
参考教程 https://www.cnblogs.com/front-web/p/15672575.html

安装node.js, 记得设置命令行版本一致

安装完成后需要启动一个node.js的网站
首先在服务器目录下创建一个文件夹, 刚开始是空的

进入命令行终端 cd /www/wwwroot/你创建的文件夹命
在终端中运行以下命令来初始化Node.js项目:npm init -y
在终端中运行以下命令来安装Express: npm install express
安装成功后创建 views 文件夹, app.js 文件 , 修改package.json文件

package.json 启动脚本文件内容  
{  
  "name": "putao_seo",  
  "version": "1.0.0",  
  "description": "",  
  "main": "index.js",  
  "scripts": {  
    "start": "node app.js",  
    "test": "echo \"Error: no test specified\" && exit 1"  
  },  
  "keywords": [],  
  "author": "",  
  "license": "ISC",  
  "dependencies": {  
    "ejs": "^3.1.8",  
    "express": "^4.18.2"  
  }  
}

app.js 文件内容, 重点, 里面内容需要根据自己的网站配置

1. 端口号, 要和上面配置反向代理一致

app.listen(8332, () => {  
  console.log('Server is running on port 8332');  
  console.log('express服务器启动成功! 端口号:8332');  
});

2. 公共配置

const commonConfig = {  
    BASE_URL: 'http://al.jn1tea.com/', // 设置BASE_URL, 你的域名, 例如:  http://al.jn1tea.com/pages/goods_details 设置为http://al.jn1tea.com/  
    VUE_APP_INDEX_CSS_HASH: 'asxga454' //  uniapp 打包后会在static中生成一个css文件, index.xxx.css, 在这里填写  
};  

// 这里就是你的uniapp打包后的h5 每个页面的访问路径, seo优化就在这里修改title, description, keywords

// 想优化哪个页面就继续添加

app.get('/pages/goods_cate/goods_cate', (req, res) => {  
    const pageData = {  
        title: '商品分类页',  
        description: '商品分类页描述',  
        keywords: '商品, 分类',  
        ...commonConfig  
    };  
    res.render('index', pageData);  
});

// 下面是全部的源码, 根据个人网站自行修改

const express = require('express');  
const ejs = require('ejs');  
const path = require('path');  

const app = express();  

// 设置模板引擎  
app.set('view engine', 'ejs');  
app.set('views', path.join(__dirname, 'views'));  

// 静态文件目录  
app.use(express.static(path.join(__dirname, 'public')));  

// 公共配置  
const commonConfig = {  
    BASE_URL: 'http://xxx.xxxcom/', // 根据实际情况设置BASE_URL  
    VUE_APP_INDEX_CSS_HASH: 'xxx' // 根据实际情况设置VUE_APP_INDEX_CSS_HASH  
};  

// 动态生成HTML  
app.get('/pages/goods_cate/goods_cate', (req, res) => {  
    const pageData = {  
        title: '商品分类页',  
        description: '商品分类页描述',  
        keywords: '商品, 分类',  
        ...commonConfig  
    };  
    res.render('index', pageData);  
});  

// 添加新的路由处理 /pages/store/home/index  
app.get('/pages/store/home/index', (req, res) => {  
    const pageData = {  
        title: '店铺首页',  
        description: '店铺首页描述',  
        keywords: '店铺, 首页',  
        ...commonConfig  
    };  
    res.render('index', pageData);  
});  

app.listen(8332, () => {  
  console.log('Server is running on port 8332');  
  console.log('express服务器启动成功! 端口号:8332');  
});

进入views文件夹, 创建index.ejs 文件

// 配置文件, 刚才在app.js 的文件会动态解析到这里的title等标签中

<!DOCTYPE html>  
<html lang="zh-CN">  
    <head>  
        <meta charset="utf-8">  
        <meta http-equiv="X-UA-Compatible" content="IE=edge">  
        <title><%= title %> | 葡萄有约</title>  
        <meta name="Keywords" content="<%= keywords %>" />  
        <meta name="Description" content="<%= description %>" />  

        <script>  
            var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))  
            document.write('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + (coverSupport ? ', viewport-fit=cover' : '') + '" />')  
            if(window.location.protocol == 'http:'){  
                document.write('<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">')  
            }  
        </script>  
        <link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />  
    </head>  
    <body>  
        <noscript>  
            <strong>Please enable JavaScript to continue.</strong>  
        </noscript>  
        <div id="app"></div>  
        <!-- built files will be auto injected -->  
    </body>  
</html>

接下来创建一个node.js 网站

启动网站,

cd进入刚才初始化创建的目录, 用终端打开
输入命令 node app.js 启动express 服务器, 看到日志说明启动成功

注意, 如果中途修改配置文件, 记得重启 node app.js, 同理nginx 修改也要重启生效
如果没问题, 就可以访问你配置后的页面, ctrl + u 查看是否生效

收起阅读 »

团队接单!诚心做事,敏捷团队,线上案例多

团队成员来自深圳、上海杭州等地,人均十年工作经验,现回家四线城市发展

团队成员在一起(敏捷团队)
前端全栈,主要围绕Uniapp、Vue2,3,React等
后端全栈,主要以Java、PHP、Golang三者为主
服务端,精通服务器部署,Docker部署,运维

现有第三方资源清单
1、在线支付(费率优惠)
2、服务器共享(合作后上线初期或者中小项目免费部署使用)
3、域名商业化共享(二级域名)

有以上几点可以快速满足需求落地,直接利用我方已有的资源

以定制需求制作系统为主,根据需求初步制作功能清单,评估工时进行报价,价格合理欢迎来谈,有需要的加V或电联:(尧)13539565631

继续阅读 »

团队成员来自深圳、上海杭州等地,人均十年工作经验,现回家四线城市发展

团队成员在一起(敏捷团队)
前端全栈,主要围绕Uniapp、Vue2,3,React等
后端全栈,主要以Java、PHP、Golang三者为主
服务端,精通服务器部署,Docker部署,运维

现有第三方资源清单
1、在线支付(费率优惠)
2、服务器共享(合作后上线初期或者中小项目免费部署使用)
3、域名商业化共享(二级域名)

有以上几点可以快速满足需求落地,直接利用我方已有的资源

以定制需求制作系统为主,根据需求初步制作功能清单,评估工时进行报价,价格合理欢迎来谈,有需要的加V或电联:(尧)13539565631

收起阅读 »

vue3兼容APP和微信小程序的base64图片保存到相册的方法

<view style="padding:40rpx;">
<canvas id="myCanvas" canvas-id="myCanvas" type="2d" style="width: 670rpx; height: 670rpx;" @longpress="saveCode"></canvas>
</view>

API请求获取得到buffer

codeSrc.value = "data:image/png;base64," + res.data.buffer; // <image :src="codeSrc"></image>仅展示,要保存到相册,无法直接调用saveImageToPhotosAlbum,也无法间接通过getImageInfo、downloadFile、saveFile获得的临时文件地址再保存,故采用canvasToTempFilePath API解决;如您后端返回是图链该方法不适用。
const bufferRaw = res.data.buffer

由于个人原因小程序端fields获取的context接连抽风绘制不出图形

uni.createSelectorQuery().in(app.proxy).select('#myCanvas').context((res) => {let ctx=res})
uni.createSelectorQuery().in(app.proxy).select('#myCanvas').fields({node: true,context: true})((res) => {let canvas=res[0].node;let ctx=canvas.getContext('2d'); //或 let ctx=res[0].context})

所以小程序只能从canvas入手,最终


// 确保canvas已经挂载后执行下面的代码片段  

const tempFilePath = ref("")  
// #ifdef APP  
                    const ctx = uni.createCanvasContext("myCanvas", app.proxy)  

                    ctx.rect(0, 0, uni.upx2px(670), uni.upx2px(670))  
                    ctx.setFillStyle('white')  
                    ctx.fill()  
                    ctx.drawImage(codeSrc.value, 0, 0, uni.upx2px(  
                        670), uni.upx2px(670));  

                    // !!!一定要在回调中获得这个canvas的临时地址  
                    ctx.draw(false, async () => {  

                        uni.canvasToTempFilePath({  
                            canvasId: "myCanvas",  
                            success: function(res) {  
                                console.log(res  
                                    .tempFilePath)  
                                tempFilePath.value = res  
                                    .tempFilePath  

                                // 能预览就是画成功  
                                // uni.previewImage({  
                                //  urls: [res  
                                //      .tempFilePath  
                                //  ],  
                                // })  
                            }  
                        })  
                    })  

                    // #endif  

                    // #ifdef MP  

                    // 小程序废弃了createCanvasContext  
                    const query = uni.createSelectorQuery().in(app.proxy)  
                    query.select('#myCanvas')  
                        .fields({  
                            node: true,  
                            size: true  
                        })  
                        .exec(async (res) => {  
                            // console.log(res)  
                            let canvas = res[0].node  
                            let ctx = canvas.getContext('2d')  
                            // console.log(ctx, canvas)  

                            const dpr = wx.getSystemInfoSync().pixelRatio  
                            canvas.width = res[0].width * dpr  
                            canvas.height = res[0].height * dpr  
                            ctx.scale(dpr, dpr)  

                            const fsm = wx.getFileSystemManager() // 文件管理器  
                            const fname = app.proxy.$utils.$helper.generateRandomString(16)  
                            const FILE_BASE_NAME = `tmp_base64src_${fname}` // 文件名  
                            const format = 'png' // 文件后缀  
                            const buffer = wx.base64ToArrayBuffer(bufferRaw) // base 转二进制  
                            const filePath = `${wx.env.USER_DATA_PATH}/${FILE_BASE_NAME}.${format}` // 文件名  

                            fsm.writeFile({ // 写文件  
                                filePath,  
                                data: buffer,  
                                encoding: 'binary',  
                                success: (res) => {  
                                    // 能预览就是画成功  
                                    // wx.previewImage({  
                                    //  urls: [filePath],  
                                    // })  
                                    var img = canvas.createImage();  
                                    img.src = filePath  
                                    img.onload = function() {  
                                        ctx.drawImage(img, 0, 0,  
                                            uni  
                                            .upx2px(  
                                                670), uni.upx2px(670));  
                                        // ctx.draw()  

                                        uni.canvasToTempFilePath({  
                                            canvasId: 'myCanvas',  
                                            canvas: canvas,  
                                            success: function(res) {  
                                                console.log(res  
                                                    .tempFilePath)  
                                                tempFilePath.value = res  
                                                    .tempFilePath  
                                            },  
                                            fail: function(error) {  
                                                console.error(error)  
                                            }  
                                        }, app.proxy);  
                                    }  

                                },  
                                fail: (err) => {  
                                    console.error(err)  
                                }  
                            })  
                        })  
                    // #endif  

// 保存到相册  
const saveCode = () => {  
        uni.saveImageToPhotosAlbum({  
            filePath: tempFilePath.value,  
            success: function() {  
                uni.showToast({  
                    title: '保存成功',  
                    icon: 'success'  
                });  
            },  
            fail: function(error) {  
                uni.showToast({  
                    title: '未保存成功',  
                    icon: 'none'  
                });  
            }  
        });  
    }
继续阅读 »

<view style="padding:40rpx;">
<canvas id="myCanvas" canvas-id="myCanvas" type="2d" style="width: 670rpx; height: 670rpx;" @longpress="saveCode"></canvas>
</view>

API请求获取得到buffer

codeSrc.value = "data:image/png;base64," + res.data.buffer; // <image :src="codeSrc"></image>仅展示,要保存到相册,无法直接调用saveImageToPhotosAlbum,也无法间接通过getImageInfo、downloadFile、saveFile获得的临时文件地址再保存,故采用canvasToTempFilePath API解决;如您后端返回是图链该方法不适用。
const bufferRaw = res.data.buffer

由于个人原因小程序端fields获取的context接连抽风绘制不出图形

uni.createSelectorQuery().in(app.proxy).select('#myCanvas').context((res) => {let ctx=res})
uni.createSelectorQuery().in(app.proxy).select('#myCanvas').fields({node: true,context: true})((res) => {let canvas=res[0].node;let ctx=canvas.getContext('2d'); //或 let ctx=res[0].context})

所以小程序只能从canvas入手,最终


// 确保canvas已经挂载后执行下面的代码片段  

const tempFilePath = ref("")  
// #ifdef APP  
                    const ctx = uni.createCanvasContext("myCanvas", app.proxy)  

                    ctx.rect(0, 0, uni.upx2px(670), uni.upx2px(670))  
                    ctx.setFillStyle('white')  
                    ctx.fill()  
                    ctx.drawImage(codeSrc.value, 0, 0, uni.upx2px(  
                        670), uni.upx2px(670));  

                    // !!!一定要在回调中获得这个canvas的临时地址  
                    ctx.draw(false, async () => {  

                        uni.canvasToTempFilePath({  
                            canvasId: "myCanvas",  
                            success: function(res) {  
                                console.log(res  
                                    .tempFilePath)  
                                tempFilePath.value = res  
                                    .tempFilePath  

                                // 能预览就是画成功  
                                // uni.previewImage({  
                                //  urls: [res  
                                //      .tempFilePath  
                                //  ],  
                                // })  
                            }  
                        })  
                    })  

                    // #endif  

                    // #ifdef MP  

                    // 小程序废弃了createCanvasContext  
                    const query = uni.createSelectorQuery().in(app.proxy)  
                    query.select('#myCanvas')  
                        .fields({  
                            node: true,  
                            size: true  
                        })  
                        .exec(async (res) => {  
                            // console.log(res)  
                            let canvas = res[0].node  
                            let ctx = canvas.getContext('2d')  
                            // console.log(ctx, canvas)  

                            const dpr = wx.getSystemInfoSync().pixelRatio  
                            canvas.width = res[0].width * dpr  
                            canvas.height = res[0].height * dpr  
                            ctx.scale(dpr, dpr)  

                            const fsm = wx.getFileSystemManager() // 文件管理器  
                            const fname = app.proxy.$utils.$helper.generateRandomString(16)  
                            const FILE_BASE_NAME = `tmp_base64src_${fname}` // 文件名  
                            const format = 'png' // 文件后缀  
                            const buffer = wx.base64ToArrayBuffer(bufferRaw) // base 转二进制  
                            const filePath = `${wx.env.USER_DATA_PATH}/${FILE_BASE_NAME}.${format}` // 文件名  

                            fsm.writeFile({ // 写文件  
                                filePath,  
                                data: buffer,  
                                encoding: 'binary',  
                                success: (res) => {  
                                    // 能预览就是画成功  
                                    // wx.previewImage({  
                                    //  urls: [filePath],  
                                    // })  
                                    var img = canvas.createImage();  
                                    img.src = filePath  
                                    img.onload = function() {  
                                        ctx.drawImage(img, 0, 0,  
                                            uni  
                                            .upx2px(  
                                                670), uni.upx2px(670));  
                                        // ctx.draw()  

                                        uni.canvasToTempFilePath({  
                                            canvasId: 'myCanvas',  
                                            canvas: canvas,  
                                            success: function(res) {  
                                                console.log(res  
                                                    .tempFilePath)  
                                                tempFilePath.value = res  
                                                    .tempFilePath  
                                            },  
                                            fail: function(error) {  
                                                console.error(error)  
                                            }  
                                        }, app.proxy);  
                                    }  

                                },  
                                fail: (err) => {  
                                    console.error(err)  
                                }  
                            })  
                        })  
                    // #endif  

// 保存到相册  
const saveCode = () => {  
        uni.saveImageToPhotosAlbum({  
            filePath: tempFilePath.value,  
            success: function() {  
                uni.showToast({  
                    title: '保存成功',  
                    icon: 'success'  
                });  
            },  
            fail: function(error) {  
                uni.showToast({  
                    title: '未保存成功',  
                    icon: 'none'  
                });  
            }  
        });  
    }
收起阅读 »

分享自用HBuilderX主题 仿 VsCode Default Dark Modern主题

主题 HBuilderX

自用 基于HBuiderX酷黑主题上修改的 仿VsCode的 现代深色Default Dark Modern 主题

有一些缺陷,比如:

  • 不会修改菜单的背景颜色,所以是酷黑主题原有的颜色
  • 没有修改代码颜色,是酷黑主题原有的颜色
    如下图

主题预览图:

{  
    "editor.colorScheme": "Monokai",  
    "workbench.colorCustomizations": {  
        "[Default]": {},  
        "[Monokai]": {  
            "console.background":"#181818",  
            "menubar.background":"#181818",  
            "editor.background": "#1f1f1f",  
            "editor.foreground": "#808080",  
            "editor.indentguide": "#404040",  
            "editor.indicator.sameword": "#343a40",  
            "editor.selectRegion": "#2a2b2d",  
            "editor.selection": "#264f78",  
            "editor.unactive_selection.background": "#9e6a03",  
            "editor.whitespace": "#1f1f1f",  
            "editorGroup.border": "#333333",  
            "editorGroupHeader.tabsBackground": "#181818",  
            "editorSuggestWidget.background": "#202020",  
            "editorSuggestWidget.border": "#454545",  
            "editorSuggestWidget.selectedBackground": "#454545",  
            "focusBorder": "#0078d4",  
            "input.background": "#313131",  
            "input.foreground": "#c3c3c3",  
            "input.searchbar.foreground": "#989898",  
            "input.searchbar.foreground.notfinded": "#aeaeae",  
            "inputList.border": "#313131",  
            "inputList.foreground": "#8b8b8b",  
            "inputList.hoverBackground": "#2a2d2e",  
            "inputList.titleColor": "#cccccc",  
            "inputValidation.infoBackground": "#222222",  
            "list.activeSelectionBackground": "#04395e",  
            "list.activeSelectionForeground": "#ffffff",  
            "list.foreground": "#cccccc",  
            "list.highlightForeground": "#ffffff",  
            "list.hoverBackground": "#2a2d2e",  
            "minimap.handle.background": "#1f1f1f",  
            "outlineBackground": "#1f1f1f",  
            "scrollbarSlider.background": "#434343",  
            "scrollbarSlider.hoverBackground": "#4f4f4f",  
            "sideBar.background": "#181818",  
            "sideBar.border": "#2b2b2b",  
            "statusBar.background": "#181818",  
            "statusBar.button.hoverbackground": "#343434",  
            "statusBar.foreground": "#cccccc",  
            "tab.activeBackground": "#1f1f1f",  
            "tab.activeForeground": "#ffffff",  
            "tab.border": "#2b2b2b",  
            "tab.hoverBackground": "#1f1f1f",  
            "tab.inactiveBackground": "#181818",  
            "tab.inactiveForeground": "#9d9d9d",  
            "tab.unfocusedActiveForeground": "#737373",  
            "tab.unfocusedHoverBackground": "#1f1f1f",  
            "tab.unfocusedInactiveForeground": "#181818",  
            "terminal.background": "#181818",  
            "toolBar.background": "#181818",  
            "toolBar.border": "#2b2b2b",  
            "toolBar.hoverBackground": "#2d2e2e"  
        }  
    },  
    "editor.tokenColorCustomizations": {  
        "[Default]": {},  
        "[Monokai]": {  
            "rules": [  
                {  
                    "scope": [  
                        "comment"  
                    ],  
                    "settings": {  
                        "foreground": "#6a9955"  
                    }  
                }  
            ]  
        }  
    }  
}  
继续阅读 »

自用 基于HBuiderX酷黑主题上修改的 仿VsCode的 现代深色Default Dark Modern 主题

有一些缺陷,比如:

  • 不会修改菜单的背景颜色,所以是酷黑主题原有的颜色
  • 没有修改代码颜色,是酷黑主题原有的颜色
    如下图

主题预览图:

{  
    "editor.colorScheme": "Monokai",  
    "workbench.colorCustomizations": {  
        "[Default]": {},  
        "[Monokai]": {  
            "console.background":"#181818",  
            "menubar.background":"#181818",  
            "editor.background": "#1f1f1f",  
            "editor.foreground": "#808080",  
            "editor.indentguide": "#404040",  
            "editor.indicator.sameword": "#343a40",  
            "editor.selectRegion": "#2a2b2d",  
            "editor.selection": "#264f78",  
            "editor.unactive_selection.background": "#9e6a03",  
            "editor.whitespace": "#1f1f1f",  
            "editorGroup.border": "#333333",  
            "editorGroupHeader.tabsBackground": "#181818",  
            "editorSuggestWidget.background": "#202020",  
            "editorSuggestWidget.border": "#454545",  
            "editorSuggestWidget.selectedBackground": "#454545",  
            "focusBorder": "#0078d4",  
            "input.background": "#313131",  
            "input.foreground": "#c3c3c3",  
            "input.searchbar.foreground": "#989898",  
            "input.searchbar.foreground.notfinded": "#aeaeae",  
            "inputList.border": "#313131",  
            "inputList.foreground": "#8b8b8b",  
            "inputList.hoverBackground": "#2a2d2e",  
            "inputList.titleColor": "#cccccc",  
            "inputValidation.infoBackground": "#222222",  
            "list.activeSelectionBackground": "#04395e",  
            "list.activeSelectionForeground": "#ffffff",  
            "list.foreground": "#cccccc",  
            "list.highlightForeground": "#ffffff",  
            "list.hoverBackground": "#2a2d2e",  
            "minimap.handle.background": "#1f1f1f",  
            "outlineBackground": "#1f1f1f",  
            "scrollbarSlider.background": "#434343",  
            "scrollbarSlider.hoverBackground": "#4f4f4f",  
            "sideBar.background": "#181818",  
            "sideBar.border": "#2b2b2b",  
            "statusBar.background": "#181818",  
            "statusBar.button.hoverbackground": "#343434",  
            "statusBar.foreground": "#cccccc",  
            "tab.activeBackground": "#1f1f1f",  
            "tab.activeForeground": "#ffffff",  
            "tab.border": "#2b2b2b",  
            "tab.hoverBackground": "#1f1f1f",  
            "tab.inactiveBackground": "#181818",  
            "tab.inactiveForeground": "#9d9d9d",  
            "tab.unfocusedActiveForeground": "#737373",  
            "tab.unfocusedHoverBackground": "#1f1f1f",  
            "tab.unfocusedInactiveForeground": "#181818",  
            "terminal.background": "#181818",  
            "toolBar.background": "#181818",  
            "toolBar.border": "#2b2b2b",  
            "toolBar.hoverBackground": "#2d2e2e"  
        }  
    },  
    "editor.tokenColorCustomizations": {  
        "[Default]": {},  
        "[Monokai]": {  
            "rules": [  
                {  
                    "scope": [  
                        "comment"  
                    ],  
                    "settings": {  
                        "foreground": "#6a9955"  
                    }  
                }  
            ]  
        }  
    }  
}  
收起阅读 »

uni-app 最专业的代码生成器

Universal Links自动生成指南 uni_app

mui 专业代码生成器

1.数据库专业工具

  1. 后端专业工具
  2. UI 采用MUI 一键生成

欢迎讨论,提供技术支持
下载地址:
https://download.csdn.net/download/qq512929249/89639950

mui 专业代码生成器

1.数据库专业工具

  1. 后端专业工具
  2. UI 采用MUI 一键生成

欢迎讨论,提供技术支持
下载地址:
https://download.csdn.net/download/qq512929249/89639950