From 64b92c9d376ea7df13145d698d513278e4eb9e94 Mon Sep 17 00:00:00 2001 From: coder-new <2578417052@qq.com> Date: Wed, 10 Apr 2024 17:29:10 +0800 Subject: [PATCH] =?UTF-8?q?docs=F0=9F=93=9D:=20=E6=96=B0=E5=A2=9E=E7=AC=94?= =?UTF-8?q?=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/README.md | 2 +- src/web/javascript/event-binding.md | 57 +++ src/web/mobile-dev/uniapp/api.md | 319 +++++++++++++ src/web/mobile-dev/uniapp/attention.md | 19 + src/web/mobile-dev/uniapp/component.md | 418 ++++++++++++++++++ .../{uniapp-template.md => template.md} | 10 +- src/web/practical-skills/api.md | 198 +++++++++ 7 files changed, 1017 insertions(+), 6 deletions(-) create mode 100644 src/web/javascript/event-binding.md create mode 100644 src/web/mobile-dev/uniapp/api.md create mode 100644 src/web/mobile-dev/uniapp/attention.md create mode 100644 src/web/mobile-dev/uniapp/component.md rename src/web/mobile-dev/uniapp/{uniapp-template.md => template.md} (94%) create mode 100644 src/web/practical-skills/api.md diff --git a/src/README.md b/src/README.md index aa52550..ce66ccf 100644 --- a/src/README.md +++ b/src/README.md @@ -43,7 +43,7 @@ projects: footer: - 蜀ICP备2023023297号-2 + 蜀ICP备2023023297号
copyright: MIT Licensed | Copyright © 2020-present 杨不旧 diff --git a/src/web/javascript/event-binding.md b/src/web/javascript/event-binding.md new file mode 100644 index 0000000..676d881 --- /dev/null +++ b/src/web/javascript/event-binding.md @@ -0,0 +1,57 @@ +--- +title: JavaScript事件绑定 +date: 2024-02-12 +category: + - javascript +--- + + + + +## 事件类型 + +JavaScript事件类型有以下几种: +1. 鼠标事件(Mouse Events):包括点击、双击、鼠标移入移出、鼠标按下释放等。 +2. 键盘事件(Keyboard Events):包括按键按下、按键释放、按键组合等。 +3. 表单事件(Form Events):包括表单提交、表单重置等。 +4. 焦点事件(Focus Events):包括元素获得焦点、元素失去焦点等。 +5. 触摸事件(Touch Events):包括触摸开始、触摸移动、触摸结束等。 +6. 窗口事件(Window Events):包括窗口打开、窗口关闭、窗口刷新等。 + +### 鼠标事件 + +鼠标单击左键-`click`、鼠标单击右键、鼠标双击-`dbclick` +鼠标移入-`mouseover`、鼠标悬停-`mouseover`、鼠标移出-`mouseleave` + + +### 键盘事件 + +键盘按下-`keydown`、键盘松开-`keyup`、`keypress` + +### 表单事件 + +`input`、`change`、`submit`、`blur`、`focus`、`reset` + +### 触摸事件 +开始触摸-`touchstart`、触摸移动-`touchmove`、触摸结束-`touchend`、触摸打算-`touchcancel` + +轻按-`tap`、长按-`longtap` + +columnchange: 'columnchange', +linechange: 'linechange', +error: 'error', +scrolltoupper: 'scrolltoupper', +scrolltolower: 'scrolltolower', +scroll: 'scroll' + + +## 事件委托 + +### 什么是事件委托 + +JavaScript事件委托(Event delegation)又叫事件代理,是一种在父元素上监听事件,然后通过事件冒泡机制来处理子元素的事件的技术。通过事件委托,可以避免为每个子元素都绑定事件处理程序,提高性能并简化代码。 + +### 事件委托基本原理 + +事件委托的基本原理是将事件处理程序绑定在父元素上,然后通过事件冒泡捕获到子元素的事件触发。这样,无论子元素是现有的还是动态生成的,它们的事件都会被父元素捕获并处理。 + diff --git a/src/web/mobile-dev/uniapp/api.md b/src/web/mobile-dev/uniapp/api.md new file mode 100644 index 0000000..6984137 --- /dev/null +++ b/src/web/mobile-dev/uniapp/api.md @@ -0,0 +1,319 @@ +--- +title: 自封装的hook +date: 2024-02-25 +category: + - 移动开发 +--- + + +### 根据path对文件进行签名 +:::details 查看代码 +```ts +import SparkMD5 from "spark-md5"; +export const md5File = ( + path: string, +): Promise<{ + md5: string; + size: number; +}> => { + return new Promise((resolve, reject) => { + plus.io.requestFileSystem( + plus.io.PRIVATE_WWW, + (fs: PlusIoFileSystem) => { + fs.root?.getFile( + path, + { create: false }, + (fileEntry: PlusIoFileEntry) => { + fileEntry.file( + (file: PlusIoFile) => { + const fileReader: PlusIoFileReader = new plus.io.FileReader(); + fileReader.onloadend = (evt: PlusIoFileEvent) => { + resolve({ + // @ts-ignore + md5: calculateBase64Hash(evt.target?.result), + size: file.size || 0, + }); + }; + fileReader.readAsDataURL(file, "utf-8"); + }, + fileError => { + reject("获取文件对象失败:" + fileError); + }, + ); + }, + fileEntryError => { + reject("读取文件失败:" + fileEntryError); + }, + ); + }, + fsError => { + reject("读取文件失败:" + fsError); + }, + ); + }); +}; + +/** + * 根据文件的base64生成md5 + * + * @param base64 文件的base64 + * @returns 文件的md5码 + */ +const calculateBase64Hash = (base64: string): string => { + const spark = new SparkMD5(); + spark.appendBinary(base64); + return spark.end(); +}; +``` +::: + +### 根据path判断文件是否存在 +:::details 查看代码 +```ts +/** + * 根据文件路径异步判断该路径下的文件是否存在 + * + * @param path 文件路径 + * @returns 返回true代表该文件存在,返回false代表该文件不存在 + */ +export const checkFileExists = (path: string): Promise => { + return new Promise(resolve => { + if (path) { + // #ifdef APP-PLUS + plus.io.requestFileSystem( + plus.io.PRIVATE_DOC, + (fs: PlusIoFileSystem) => { + fs.root?.getFile( + path, + { create: false }, + () => { + resolve(true); + }, + error => { + resolve(false); + }, + ); + }, + () => { + resolve(false); + }, + ); + // #endif + // #ifdef H5 + resolve(false); + // #endif + } else { + resolve(false); + } + }); +}; +``` +::: + +### 获取手机缓存文件总共多大 +:::details 查看代码 +```ts +/** + * 异步获取手机缓存有多大 + * + */ +export const calculateDeviceCacheSize = (): Promise => { + return new Promise(resolve => { + // #ifdef APP-PLUS + let isGetSuccess: boolean = false; + plus.io.requestFileSystem( + plus.io.PRIVATE_DOC, + (fs: PlusIoFileSystem) => { + /** + * 允许最大时间查询文件大小,避免一些情况下卡住无法返回文件的大小 + */ + let timer: NodeJS.Timeout = setTimeout(() => { + !isGetSuccess && + resolve({ + flag: 1, + data: formatBytes(0), + }); + clearTimeout(timer); + }, 2000); + fs.root?.getMetadata( + (directoryEntry: PlusIoMetadata) => { + isGetSuccess = true; + resolve({ + flag: 1, + data: formatBytes(Number(directoryEntry.size)), + }); + clearTimeout(timer); + }, + getError => { + resolve({ + flag: 0, + errMsg: getError, + }); + }, + true, + ); + }, + error => { + resolve({ + flag: 0, + errMsg: error, + }); + }, + ); + // #endif + // #ifdef H5 + resolve({ + flag: 0, + errMsg: "仅支持在APP上获取缓存文件大小", + }); + // #endif + }); +}; +``` +::: +### 清除手机缓存文件 +:::details 查看代码 +```ts +/** + * 异步清除设备缓存文件 + */ +export const clearDeviceCache = (): Promise => { + return new Promise(resolve => { + // #ifdef APP-PLUS + uni.showLoading({ + title: "清除设备缓存中", + }); + plus.io.requestFileSystem( + plus.io.PRIVATE_DOC, + (fs: PlusIoFileSystem) => { + fs.root?.removeRecursively( + () => { + uni.hideLoading(); + resolve({ + flag: 1, + errMsg: "", + }); + }, + failRes => { + uni.hideLoading(); + resolve({ + flag: 0, + errMsg: failRes.message, + }); + }, + ); + }, + error => { + uni.hideLoading(); + resolve({ + flag: 0, + errMsg: error.message, + }); + }, + ); + // #endif + // #ifdef H5 + resolve({ + flag: 0, + errMsg: "仅在APP下支持清除缓存功能", + }); + + // #endif + }); +}; +``` +::: +### 根据bytes转换单位 +:::details 查看代码 +```ts +/** + * 将字节大小格式化如:1KB、1MB、1GB等 + * + * @param bytes 字节大小 + * @returns 转换单位类似为:1KB、1MB、1GB等 + */ +export const formatBytes = (bytes: number): string => { + if (bytes === 0) return "0 MB"; + const k = 1024; + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; +}; +``` +::: + + +### 下载安装包 +:::details 查看代码 +```ts +export const downloadAPK = (url: string, errorCallback: (...args: any) => void) => { + if (plus.os.name?.toLocaleLowerCase() == "ios") { + plus.runtime.openURL(url); + } else { + const waitingToast: PlusNativeUIWaitingObj = plus.nativeUI.showWaiting("正在初始化下载..."); + let downloadTask: PlusDownloaderDownload = plus.downloader.createDownload( + url + useJsonToUrl({ version: userInfoStore.userConf.latestAppVersion, appId: defaultConfig.appid }), + { + method: "GET", + timeout: 3, + retryInterval: 1, + retry: 2, + }, + (download: PlusDownloaderDownload, status: number) => { + plus.downloader.clear(); + waitingToast.setTitle("下载完成,等待安装..."); + if (status == 200) { + download.filename && + plus.runtime.install( + plus.io.convertLocalFileSystemURL(download.filename), + { + force: true, + }, + () => { + plus.nativeUI.closeWaiting(); + }, + installErrorMsg => { + plus.nativeUI.closeWaiting(); + errorCallback("安装错误:" + installErrorMsg); + }, + ); + } else { + plus.nativeUI.closeWaiting(); + errorCallback("下载最新APP版本失败,请稍后重试或联系APP开发者"); + } + }, + ); + + try { + downloadTask.setRequestHeader("Authorization", userInfoStore.token.authorization); + downloadTask.start(); + downloadTask.addEventListener("statechanged", (task: PlusDownloaderDownload) => { + switch (task.state) { + case 1: + waitingToast.setTitle("正在连接服务器..."); + break; + case 2: + waitingToast.setTitle("服务器连接成功,开始下载..."); + break; + case 3: + if (task.downloadedSize && task.totalSize) { + const percent = parseInt(((parseFloat(task.downloadedSize.toString()) / parseFloat(task.totalSize.toString())) * 100).toString()); + waitingToast.setTitle("正在下载(" + percent + "%)..."); + } + break; + case 4: + break; + } + }); + } catch (err) { + plus.nativeUI.closeWaiting(); + errorCallback("下载失败,请稍后重试"); + } + } +}; +``` +::: + + + + diff --git a/src/web/mobile-dev/uniapp/attention.md b/src/web/mobile-dev/uniapp/attention.md new file mode 100644 index 0000000..feb1e56 --- /dev/null +++ b/src/web/mobile-dev/uniapp/attention.md @@ -0,0 +1,19 @@ +--- +title: 开发APP注意事项 +date: 2024-01-21 +category: + - 移动开发 +--- + + +1. uniapp中没有File文件对象,html5plus中的文件数据对象并不是前端中的File文件对象,需要注意区分。 +2. uniapp中没有`URL.createObjectURL`方法。 +3. uniapp中的`display:grid`是不起作用的,若想配置高度动画变换,需要设置定高、`overflow:hidden`,同时计算隐藏那部分的高度,当显示的时候就是设置的高度 + 计算隐藏的高度 就等于整个容器的高度,但该方法不适合用于内部高度变化比较多的地方。 + + +### 组件实例 +在一些如果想通过组件实例获取webview组件的时候,同样,也不能嵌套太深,可以在跟组件实例化:`getCurrentInstance()`,然后通过`inject`、`provide` + +### 弹框 +1. 弹框弹出的时候,滚动穿透问题。具体表现为:当在弹出的弹框在滑动时,页面也会随着滚动,但设置`@touchmove.stop.prevent="()=>{}"`后,此时滑动页面不会随着滑动了,但是如果弹框内的内容想滚动时,就会滚动不了。 +2. 一些弹框组件使用的位置组件嵌套不能太深,太深遮罩层无法覆盖整个页面。此时可以建议用`inject`和`provide`,将组件实例注入到子组件中,子组件想使用弹框可以用`inject`进行接收。 \ No newline at end of file diff --git a/src/web/mobile-dev/uniapp/component.md b/src/web/mobile-dev/uniapp/component.md new file mode 100644 index 0000000..e314e9b --- /dev/null +++ b/src/web/mobile-dev/uniapp/component.md @@ -0,0 +1,418 @@ +--- +title: 自封装的组件 +date: 2024-01-21 +category: + - 移动开发 +--- + + +### 录音组件 + +:::details 查看代码 + +::: tabs +@tab useVModel +```ts +import { computed } from "vue"; +export const useVModel = (props: AnyObject, propName: string, emit: (...args: any) => void) => { + return computed({ + get() { + return new Proxy(props, { + set(obj, name, val) { + emit("update:" + propName, { + ...obj, + [name]: val, + }); + return true; + }, + }); + }, + set(val) { + emit("update:" + propName, val); + }, + }); +}; + +``` + +@tab 使用示例 +```vue + + + +``` + +@tab 组件源码 +```vue + + + + + + +``` +::: + +### 动画组件 + +仿vue原生Transition组件 + +**注意事项:需要下载animate.css库,同时注意引入的时候不能命名为Transition进行使用,会与vue原生的Transition组件冲突,而原生组件在APP中是不支持的** +:::details 查看代码 +```vue + + + + + + +``` +::: \ No newline at end of file diff --git a/src/web/mobile-dev/uniapp/uniapp-template.md b/src/web/mobile-dev/uniapp/template.md similarity index 94% rename from src/web/mobile-dev/uniapp/uniapp-template.md rename to src/web/mobile-dev/uniapp/template.md index 26900b8..5049559 100644 --- a/src/web/mobile-dev/uniapp/uniapp-template.md +++ b/src/web/mobile-dev/uniapp/template.md @@ -1,5 +1,5 @@ --- -title: uni-app模板 +title: 配置模板 date: 2024-02-21 category: - 移动开发 @@ -20,16 +20,16 @@ category: } ``` -2. 代码分包。代码分包可以减少app首次加载启动时所耗费的时间,需要在`manifest.json`和`pages.json`两个文件同时配置才会生效: +1. 代码分包。代码分包可以减少app首次加载启动时所耗费的时间,需要在`manifest.json`和`pages.json`两个文件同时配置才会生效: - [manifest.json参考配置](https://uniapp.dcloud.net.cn/collocation/manifest.html#app-vue-optimization) - [pages.json参考配置](https://uniapp.dcloud.net.cn/collocation/pages.html#subpackages),注意`pages.json`是需要配置`subPackages`和`preloadRule`的。 - 代码分包不止可以运用在app上,小程序上也是可以的,具体看官方的介绍 -3. 开发app竖屏锁定,不运行app页面跟随页面旋转而进行旋转。这个需要在`pages.json`下的`globalstyle/pageOrientation`下进行配置,[pageOrientation配置参考](https://uniapp.dcloud.net.cn/collocation/pages.html#globalstyle) +2. 开发app竖屏锁定,不运行app页面跟随页面旋转而进行旋转。这个需要在`pages.json`下的`globalstyle/pageOrientation`下进行配置,[pageOrientation配置参考](https://uniapp.dcloud.net.cn/collocation/pages.html#globalstyle) -4. `rpxCalcBaseDeviceWidth`、`rpxCalcBaseDeviceWidth`、`rpxCalcIncludeWidth`。[三个值的介绍](https://uniapp.dcloud.net.cn/collocation/pages.html#globalstyle),一般默认即可,或者可能设计师给的稿纸宽度不是375,此时可以设置一下`rpxCalcBaseDeviceWidth`,这三个值方便我们进行响应式的布局,另外`dynamicRpx`根据实际情况是否设置为true,为true会根据屏幕大小变化进而重新进行页面布局 +3. `rpxCalcBaseDeviceWidth`、`rpxCalcBaseDeviceWidth`、`rpxCalcIncludeWidth`。[三个值的介绍](https://uniapp.dcloud.net.cn/collocation/pages.html#globalstyle),一般默认即可,或者可能设计师给的稿纸宽度不是375,此时可以设置一下`rpxCalcBaseDeviceWidth`,这三个值方便我们进行响应式的布局,另外`dynamicRpx`根据实际情况是否设置为true,为true会根据屏幕大小变化进而重新进行页面布局 -5. `transformPx`,[transformPx配置参考](https://uniapp.dcloud.net.cn/collocation/manifest.html#%E9%85%8D%E7%BD%AE%E9%A1%B9%E5%88%97%E8%A1%A8)设为true时,方便我们将px转化为rpx,从而方便我们进行响应式的布局,当然,也可以部根据第四五点所介绍的uniapp内置的响应式布局配置,你还可以使用第三方的插件,如`postcss-px-to-viewport`,仅在开发环境使用即可,它可以将你所指定的单位和稿纸宽度全部转换成你所期待的单位。如果你的项目是ts声明,需要手动书写声明文件,也可以参考以下声明文件: +4. `transformPx`,[transformPx配置参考](https://uniapp.dcloud.net.cn/collocation/manifest.html#%E9%85%8D%E7%BD%AE%E9%A1%B9%E5%88%97%E8%A1%A8)设为true时,方便我们将px转化为rpx,从而方便我们进行响应式的布局,当然,也可以部根据第四五点所介绍的uniapp内置的响应式布局配置,你还可以使用第三方的插件,如`postcss-px-to-viewport`,仅在开发环境使用即可,它可以将你所指定的单位和稿纸宽度全部转换成你所期待的单位。如果你的项目是ts声明,需要手动书写声明文件,也可以参考以下声明文件: ```ts declare module "postcss-px-to-viewport" { type Unit = "px" | "rem" | "cm" | "em" | "rpx"; diff --git a/src/web/practical-skills/api.md b/src/web/practical-skills/api.md new file mode 100644 index 0000000..adaa6d9 --- /dev/null +++ b/src/web/practical-skills/api.md @@ -0,0 +1,198 @@ +--- +title: 开发中常用API集合 +date: 2021-12-12 +category: + - 实用技巧 +--- + +### 判断某个坐标是否在电子围栏内 + +:::details 查看代码 +```ts +/** + * 判断当前位置是否在电子围栏内(注意坐标类型都是wgs84) + * @param currentPoint 当前位置坐标,如:[longitude, latitude] + * @param polygonPoint 电子围栏坐标,如:[ [longitude, latitude], [longitude, latitude] ] + */ +export const useIsPointInPolygon = (userLocation: [number, number], fence: [number, number][]): boolean => { + const userLongitude = userLocation[0]; + const userLatitude = userLocation[1]; + + // 通过射线法判断点是否在多边形内部 + let isIn = false; + for (let i = 0, j = fence.length - 1; i < fence.length; j = i++) { + const fenceLongitude1 = fence[i][0]; + const fenceLatitude1 = fence[i][1]; + const fenceLongitude2 = fence[j][0]; + const fenceLatitude2 = fence[j][1]; + + // 判断点与多边形的边是否相交 + const intersect = + fenceLatitude1 > userLatitude !== fenceLatitude2 > userLatitude && + userLongitude < ((fenceLongitude2 - fenceLongitude1) * (userLatitude - fenceLatitude1)) / (fenceLatitude2 - fenceLatitude1) + fenceLongitude1; + if (intersect) isIn = !isIn; + } + + console.log("isIn", isIn); + + return isIn; +}; + +``` +::: + +### wgs84与gcj02坐标类型互转 + +:::details 查看代码 +```ts +const x_PI: number = (3.14159265358979324 * 3000.0) / 180.0; +const PI: number = 3.1415926535897932384626; +const a: number = 6378245.0; //卫星椭球坐标投影到平面地图坐标系的投影因子。 +const ee: number = 0.00669342162296594323; //椭球的偏心率。 + +/** + * 判断是否是中国范围内的坐标 + * @param {array} coordinate 传入坐标,格式:[经度, 纬度] + * @returns {boolean} 是否在中国范围内 + */ +const out_of_china = (coordinate: Coordinate): boolean => { + return coordinate[0] < 72.004 || coordinate[0] > 137.8347 || coordinate[1] < 0.8293 || coordinate[1] > 55.8271 || false; +}; + +/** + * 转为纬度 + * @param {number} lng 经度 + * @param {number} lat 纬度 + * @returns {number} 转化后的纬度 + */ +const transformlat = (lng: number, lat: number): number => { + let ret: number = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)); + ret += ((20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0) / 3.0; + ret += ((20.0 * Math.sin(lat * PI) + 40.0 * Math.sin((lat / 3.0) * PI)) * 2.0) / 3.0; + ret += ((160.0 * Math.sin((lat / 12.0) * PI) + 320 * Math.sin((lat * PI) / 30.0)) * 2.0) / 3.0; + return ret; +}; +/** + * 转为经度 + * @param {number} lng 经度 + * @param {number} lat 纬度 + * @returns {number} 转化后的经度 + */ +const transformlng = (lng: number, lat: number): number => { + let ret: number = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)); + ret += ((20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0) / 3.0; + ret += ((20.0 * Math.sin(lng * PI) + 40.0 * Math.sin((lng / 3.0) * PI)) * 2.0) / 3.0; + ret += ((150.0 * Math.sin((lng / 12.0) * PI) + 300.0 * Math.sin((lng / 30.0) * PI)) * 2.0) / 3.0; + return ret; +}; + +/** + * wgs84 和 gcj02 坐标互转 + * @param {array} coordinate 传入坐标,格式:[经度, 纬度] + * @param {string} type 默认值为:"wgs84-gcj02",即wgs84转gcj02,可选值为:"gcj02-wgs84" + * @returns {array} 转化结果,格式:[经度, 纬度] + */ +export const useCoordinateTransform = (coordinate: Coordinate, type: string = "wgs84-gcj02"): Coordinate => { + if (out_of_china(coordinate)) return coordinate; + if (type === "wgs84-gcj02") { + var dlat = transformlat(coordinate[0] - 105.0, coordinate[1] - 35.0); + var dlng = transformlng(coordinate[0] - 105.0, coordinate[1] - 35.0); + var radlat = (coordinate[1] / 180.0) * PI; + var magic = Math.sin(radlat); + magic = 1 - ee * magic * magic; + var sqrtmagic = Math.sqrt(magic); + dlat = (dlat * 180.0) / (((a * (1 - ee)) / (magic * sqrtmagic)) * PI); + dlng = (dlng * 180.0) / ((a / sqrtmagic) * Math.cos(radlat) * PI); + var mglat = coordinate[1] + dlat; + var mglng = coordinate[0] + dlng; + + return [mglng, mglat]; + } else if (type === "gcj02-wgs84") { + var dlat = transformlat(coordinate[0] - 105.0, coordinate[1] - 35.0); + var dlng = transformlng(coordinate[0] - 105.0, coordinate[1] - 35.0); + var radlat = (coordinate[1] / 180.0) * PI; + var magic = Math.sin(radlat); + magic = 1 - ee * magic * magic; + var sqrtmagic = Math.sqrt(magic); + dlat = (dlat * 180.0) / (((a * (1 - ee)) / (magic * sqrtmagic)) * PI); + dlng = (dlng * 180.0) / ((a / sqrtmagic) * Math.cos(radlat) * PI); + mglat = coordinate[1] + dlat; + mglng = coordinate[0] + dlng; + return [coordinate[0] * 2 - mglng, coordinate[1] * 2 - mglat]; + } + return coordinate; +}; + +``` +::: + + +### useVModel +:::details 查看代码 +```ts +import { computed } from "vue"; +export const useVModel = (props: AnyObject, propName: string, emit: (...args: any) => void) => { + return computed({ + get() { + return new Proxy(props, { + set(obj, name, val) { + emit("update:" + propName, { + ...obj, + [name]: val, + }); + return true; + }, + }); + }, + set(val) { + emit("update:" + propName, val); + }, + }); +}; + +``` +::: + + +### useSelecterNodeInfo + +:::details 查看代码 +```ts +import { getCurrentInstance } from "vue"; +export const useSelecterNodeInfo = (selector: string): Promise => { + const query = uni.createSelectorQuery().in(getCurrentInstance()); + return new Promise(resolve => { + query + .select(selector) + .boundingClientRect((nodeRef: UniApp.NodeInfo | UniApp.NodeInfo[]) => { + resolve(nodeRef); + }) + .exec(); + }); +}; + +``` +::: + +### useGetStringSize + +:::details 查看代码 +```ts +export const useGetStringSize = (str: string): number => { + let len: number = 0; + for (let i = 0; i < str.length; i++) { + const code = str.charCodeAt(i); + if (code <= 0x007f) { + len += 1; + } else if (code <= 0x07ff) { + len += 2; + } else if (code <= 0xffff) { + len += 3; + } else { + len += 4; + } + } + return len; +}; +``` +::: \ No newline at end of file