) => api(...args).then(resp => ({ ...resp, curosr: customCursor2PageCursor(resp.cursor) }))
}
const iter = makeAsyncIter(
- compose(apiCursorNormalizer, fetchRecipesCustomCursor),
+ apiCursorNormalizer(fetchRecipesCustomCursor),
resp => resp.recipes
)
```
+## 在vue2 options api中使用
+
+
+makeAsyncIter是使用的composition api的风格写法,只不过因为没有钩子和useXXX所以不需要强制与setup同步运行才没有以use开头,这种写法对options api不友好,不能适合直接用需要使用reactive包一层。
+其他的基本一致
+
+参考下图
+1. 在js中
+
+
+
+2. 在模板中
+
+
+
+[源码位置](https://github.com/xiachufang/jupiter/blob/master/ganymede/src/shared/util/makeAyncIter.ts), 实现和本库的有点微小差别。
+
+## 在小程序中使用
+[源码位置](https://github.com/xiachufang/weapp/blob/master/source/lib/asyncIterator.ts),由于小程序和vue完全不同的响应式系统,使用起来有点差别,同时也抛弃了composition api的风格写法改用了class。
+### 最小无限滚动加载收藏的例子
+```ts
+Page({
+ data: {},
+ iter: new AsyncIterator(
+ cursor => pagedCollectedBoards({ cursor, size: 10 }),
+ resp => resp.content.cells,
+ { dataUpdateStrategy: 'merge' } // 无限滚动要保留之前获取的资源所以选择merge
+ ),
+ onLoad () {
+ this.iter.bindPage(this) // 绑定页面,为了在迭代器状态变化时通知页面
+ this.iter.next() // 进行首次加载
+ },
+ onReachBottom () {
+ this.iter.next() // 滚到底部时继续加载
+ }
+})
+// next(), reset() , abort() 与vue3版本一致,用法参考vue3版本
+```
+> 模板内存在res,loading,completed3个没写明在data中的变量,在后续部分会介绍
+
+```html
+
+
+
+
+
+
+
+ -- 到底了 --
+
+```
+
+### 在ts/js中获取asyncIter的状态
+直接通过`this.iter.state`来获取,有足够完善的类型推导
+![origin_img_v2_4159e30e-6ae4-4e04-b960-75dd959be45g](https://user-images.githubusercontent.com/25872019/178453985-f36ab0b0-fea5-4a34-9a83-c393b9852124.png)
+
+### 在wxml中获取asyncIter的状态
+小程序并没有类似vue的响应式值,所有要如何去通知页面更新这块需要单独写,这边使用最简单的回调实现。
+`asyncIter`内部有个值`stateUpdatedCallback`,在`asyncIter`状态变化后将会调用它。
+`asyncIter`的状态包括3种,任意一个改变都会触发回调
+1. loading 迭代器是否处于加载中
+2. completed 迭代器是否加载完成,对应主版本中的load
+3. res 迭代后获取到资源
+
+#### 通过设置回调来实现状态变化时更新 setStateUpdatedCallback
+```ts
+this.iter.setStateUpdatedCallback(() => {
+ this.setData(this.iter.state) // 将会把loading,completed,res隐式的更新到this.data上。即使你没在page.data里面写
+}) // 在模板中 {{res}} {{completed}} 使用
+// 不嫌麻烦也可以
+this.iter.setStateUpdatedCallback(() => {
+ const { res, laoding, completed } = this.iter.state
+ this.setData({ list: res, pending: loading, hasMore: !completed }) // list, pending,hasMore为data中写明
+}) // 在模板中 {{list}} {{hasMore}} 使用
+```
+#### 简写方式 bindPage
+是`setStateUpdatedCallback`的进一步简化,需要注意的是`setStateUpdatedCallback`和`bingPage`同时只生效一个
+```ts
+this.iter.bindPage(this) // 在模板中 {{res}} {{completed}} 使用
+this.iter.bindPage(this, 'recommend') // 在模板中 {{recommend.res}} {{recommend.completed}} 使用
+```
+然后无论是使用`setStateUpdatedCallback`还是`bingPage`,在脚本文件中我都不推荐使用`this.data.res`取获取状态,而是应该`this.iter.state.res`。
+### 如何知道asyncIter引发的界面修改完成时机
+在vue3,vue2中我们直接
+```ts
+await iter.next()
+await nextTick()
+// 现在就已经界面更新完成
+```
+而在小程序中不是nextTick可以通过setData的回调来实现,因此可以这样
+```ts
+ this.iter.setStateUpdatedCallback(() => {
+ this.setData(this.iter.state, () => {
+ // do something
+ })
+})
+```
+
## 常用场景的使用
直接使用的makeAsyncIter的场景并不多,makeAsyncIter是对分页资源的一种可迭代的抽象。
日常中更多的是使用针对不同场景使用不同的适配,makeAsyncIter与这些的关系有点类似zrender和echarts
@@ -135,8 +370,7 @@ const iter = makeAsyncIter(
参考[useAntdListPagination](#useAntdListPagination)
### 无限滚动
参考[useInfiniteScrolling](#useInfiniteScrolling)
-
-# useInfiniteScrolling
+# useInfiniteScrolling 无限滚动
useInfiniteScrolling是针对无限滚动做的一个适配,包含了两种触发模式,探底触发和交叉触发。
## 探底触发
探底触发适用于整个页面向下滚动,页面滚动到底部达到一定阈值是进行资源迭代,场景例如厨房装备页的滚动到底部加载。
@@ -186,7 +420,7 @@ await hooks.iterationPost?.()
```
-# useAntdListPagination
+# useAntdListPagination / GeneralPagination 翻页管理
useAntdListPagination是makeAsyncIter针对翻页做的一个适配,与GeneralPagation组件搭配使用,可以很容易写的出来一个翻页的组件
## 使用参考
```ts
diff --git a/doc/other.md b/doc/other.md
new file mode 100644
index 0000000..2d13e53
--- /dev/null
+++ b/doc/other.md
@@ -0,0 +1,106 @@
+- [deepComputed](#deepcomputed)
+ - [主要的使用场景](#主要的使用场景)
+ - [性能相关](#性能相关)
+- [events/typedEventEmitter 类型安全的EventEmitter](#eventstypedeventemitter-类型安全的eventemitter)
+- [image/getImageUrl 从下厨房用的图像结构构造url](#imagegetimageurl-从下厨房用的图像结构构造url)
+- [assigIncrId 生成一个全局自增id](#assigincrid-生成一个全局自增id)
+- [unid/typedID/ID 使用symbol实现的ID生成器](#unidtypedidid-使用symbol实现的id生成器)
+- [delay,delayFn 延时,推迟控制流执行](#delaydelayfn-延时推迟控制流执行)
+- [promise2ref promsie转成ref](#promise2ref-promsie转成ref)
+- [promiseSetRef 在promise完成时设置某个ref](#promisesetref-在promise完成时设置某个ref)
+- [momentConvert 一个函数实现下厨房常用的多种时间转换](#momentconvert-一个函数实现下厨房常用的多种时间转换)
+
+desc: 其余不好分类的函数
+# deepComputed
+其概念类似computed函数,使用get获取初始化值,在调用get时收集外部依赖,在外部依赖变化时重新生成自己。.value = xxx时调用set函数。不过computed仅支持最外层的变化,而deepComputed则是支持更深层次的变化。
+改用deepComputed最明显的几个好处则是不需要手动外部数据的变化,不需要在数据修改后各种烦人的dispatch('xxxx',xxx.value)或者ctx.emit('update:xxx',xxx.value)`,在数据变化后自动帮你提交。
+## 主要的使用场景
+例如一个超大型的表单拆分成数个小组件,每个小组件都通过v-model&ctx.emit与父组件通信.
+```ts
+ // 例如是控制表单日期的部分
+ const date = deepComputed({
+ get: () => props.date,
+ set: v => ctx.emit('update:date', v)
+ })
+```
+或者是需要读写vuex表单,官方推荐则是对表单的所有字段写个action commit,而使用deepComputed则不需要这么麻烦,像正常的computed那样就行,并且可以严格保证flux的架构
+```ts
+const recipe = deepComputed({
+ get: () => {
+ const { createRecipe } = store.state
+ return createRecipe.recipe
+ },
+ set: v => dispatch('createRecipe/setRecipe', v)
+ })
+```
+这两种deepComputed都能保证两处的值在操作后是一致的,因此我感觉可以理解为deepComputed结果的值是对另外一个值部分引用的持有
+## 性能相关
+在性能方面deepComputed,支持set和get时的双向防抖,默认关闭。以及set, get时的clone,默认启用。
+该函数不是简单的watch({deep:true})实现,那样会导致watch和set相互调用栈溢出,而是使用proxy实现,默认监控浅层的object以及持续的array,在vue2中不可用,与vue2的响应式实现冲突。
+![image](https://user-images.githubusercontent.com/25872019/178645084-055e3e18-8514-4df0-b0ef-8aca3015c5f7.png)
+图 替换为deepComputed前后的两种写法,二者等价
+
+
+# events/typedEventEmitter 类型安全的EventEmitter
+用法和node 的EventEmitter一样,不过新增了类型检查和hook风格管理的监听 useEventListen
+```ts
+const { eventEmitter, useEventListen } = typedEventEmitter<{ userInfoLoaded: undefined, cancelTask: number }>()
+eventEmitter.emit('userInfoLoaded') // ok
+eventEmitter.emit('userInfo') // 类型错误
+eventEmitter.emit('cancelTask') // 类型错误
+eventEmitter.emit('cancelTask', 1) // ok
+eventEmitter.on('cancelTask', (v: string) => { // 类型错误
+ ///
+})
+eventEmitter.on('cancelTask', (v) => { // ok
+ v // 这是一个数字
+})
+useEventListen('userInfoLoaded', () => console.log('userInfoLoaded')) // 和其他hook一样,组件卸载前会清理掉,不用手动删除
+```
+
+
+# image/getImageUrl 从下厨房用的图像结构构造url
+```ts
+const url = getImageUrl(image)
+```
+# assigIncrId 生成一个全局自增id
+# unid/typedID/ID 使用symbol实现的ID生成器
+使用symbol实现,相较于直接添加数字或者是字符串id,最大的好处是不会污染数据本身,id不会被序列化,key也是迭代不出来。
+适用于需要比较场合,例如v-for的key,如果是回进行数组内部的删除或者是调整顺序那么就不能使用index作为key,这时候就需要ID生成器。当然如果后端有返回id,那直接使用返回的。
+
+例如以下的情况,同时编辑多种用料,用料位置可拖拽调整,可新增删除
+```ts
+import { ID, unid, UniqueId, typedID, idKey } from 'vue3-ts-util'
+interface Ing extends UniqueId {
+ name: string
+ amount: numebr
+}
+const ings: Ing[] = []
+// 正常情况下就这样,可以获取足够完善的类型提示
+const newIng = ID({ name: '胡萝卜', amount: 1 })
+// 但是如果你这样
+const newIng: Ing = ID({ name: '胡萝卜', amount: 1 })
+// 或者这样时,虽然也有类型检测,输入name时也能提示,但是相对于第一种不够好,因为比较的是id的返回值和push函数
+ing.push(ID({ name: '胡萝卜', amount: 1 }))
+
+
+// 那么可以这样,就可以获得足够完善的类型提示
+const ingId = typedID()
+const newIng = ingId({ name: '胡萝卜', amount: 1 })
+ing.push(ingId({ name: '胡萝卜', amount: 1 }))
+
+if (newIng[idKey] !== oldIng[idKey]) {
+ // 比较可以这样
+}
+```
+当然如果对这种方法觉得麻烦直接`obj._id = uniqueId()`
+# delay,delayFn 延时,推迟控制流执行
+```ts
+await delay(300)
+doSomething() // 延迟300ms执行
+```
+
+# promise2ref promsie转成ref
+# promiseSetRef 在promise完成时设置某个ref
+# momentConvert 一个函数实现下厨房常用的多种时间转换
+使用方法见单元测试momentConvert部分
diff --git a/doc/type.md b/doc/type.md
new file mode 100644
index 0000000..0ec2a36
--- /dev/null
+++ b/doc/type.md
@@ -0,0 +1,70 @@
+- [globalComponents](#globalcomponents)
+- [DeepReadonly转换一个类型为深度只读](#deepreadonly转换一个类型为深度只读)
+ - [仅使用类型](#仅使用类型)
+ - [也可以使用这种方式](#也可以使用这种方式)
+- [ok 先验条件断言](#ok-先验条件断言)
+- [thruthy 真值断言](#thruthy-真值断言)
+- [Columns 描述antd表格结构的类型](#columns-描述antd表格结构的类型)
+- [Image 下厨房的图像结构](#image-下厨房的图像结构)
+- [WithRequired 将对象部分字段转为不可空](#withrequired-将对象部分字段转为不可空)
+- [customPropType vue props用于推导自定义类型的辅助函数,使用interface风格写props](#customproptype-vue-props用于推导自定义类型的辅助函数使用interface风格写props)
+desc: 类型及类型推导辅助相关
+
+# globalComponents
+给volar提给该库组件的类型提示
+# DeepReadonly转换一个类型为深度只读
+## 仅使用类型
+
+
+## 也可以使用这种方式
+```ts
+function getADeepReadonlyObject() {
+ return deepReadonly({
+ foo: 'caillo'
+ })
+}
+```
+# ok 先验条件断言
+断言除了用于测试外还用于先验条件,即检测接下来代码的执行,如果条件不满足则断言失败抛异常或者退出进程。和异常不同,断言失败只应该发生在开发阶段,如果断言失败那么就是你代码写错了,而异常则可能是由于外界条件的不足。
+将断言用于先验条件这种做法在一些编译型语言上尤其流行,这些断言在debug编译时生效,release编译时又会被移除。
+在ts中除了先验条件外,断言还支持用于控制流的类型推导。
+```ts
+ok(typeof next === 'string')
+ok(ele)
+```
+# thruthy 真值断言
+和非空断言有点像,好处是会更早引发断言失败
+```ts
+let numMayBeNull: number | null
+doSomething(numMayBeNull!) // 如果函数没校验,可能会随着多次传递而难以debug
+doSomething(thruthy(numMayBeNull)) // 函数未调用就断言失败
+```
+# Columns 描述antd表格结构的类型
+主要是用于推导antd表格,方便在ts里描述antd表格的行列
+
+
+
+# Image 下厨房的图像结构
+# WithRequired 将对象部分字段转为不可空
+
+```ts
+
+type AllOptional = {
+ str?: string;
+ num?: number;
+ bool?: boolean
+}
+
+type SomeOptional = WithRequired
+```
+# customPropType vue props用于推导自定义类型的辅助函数,使用interface风格写props
+适用于vue2/3,例子直接看本库的组件就行,如果你使用vue3的setup我更推荐使用[defineProps](https://v3.cn.vuejs.org/api/sfc-script-setup.html#defineprops-%E5%92%8C-defineemits),这个只是不能直接书写props interface时的一个workaround
+```ts
+props: {
+ seg: customPropsType(), // 非空,自定义接口
+ getState: customPropsType<(id: number) => ArchState>(false), // 可空,函数
+ enable: customPropsType(false, Boolean) // 可空,运行时类型检查,
+ direction: customPropType((): 'vertical' | 'horizontal' => 'horizontal') // 默认参数
+ }
+```
+
diff --git a/doc/vue3components.md b/doc/vue3components.md
new file mode 100644
index 0000000..481bd87
--- /dev/null
+++ b/doc/vue3components.md
@@ -0,0 +1,64 @@
+- [GeneralPagination 翻页器和相关hook](#generalpagination-翻页器和相关hook)
+- [SplitView 支持鼠标拖拽调整的视图分割](#splitview-支持鼠标拖拽调整的视图分割)
+ - [props](#props)
+ - [例子](#例子)
+- [SearchSelect 支持搜索的选择,追求尽可能少的代码来描述](#searchselect-支持搜索的选择追求尽可能少的代码来描述)
+ - [props](#props-1)
+ - [例子](#例子-1)
+desc: 本库的vue3组件
+
+记得先全局导入,或者单独导入
+```ts
+import { SearchSelect, SplitView, GeneralPagination } from 'vue3-ts-util'
+
+const app = createApp(App)
+Object.entries({
+ SplitView,
+ GeneralPagination,
+ SpinSection
+}).forEach(args => app.component(...args))
+```
+# GeneralPagination 翻页器和相关hook
+具体见[io的相关部分](./io.md#useantdlistpagination--generalpagination-翻页管理)
+# SplitView 支持鼠标拖拽调整的视图分割
+本库只实现了左右分割,需要上下分割,我在这里实现了[vue-ts-util-lite](https://github.com/zanllp/vue3-ts-util-lite/blob/main/src/SplitView/index.vue)
+## props
+percent: 左边的视图的占比,number,默认50
+border: bool 一个浅灰色的框用来区分视图,默认关闭
+## 例子
+```html
+
+
+
+
+
+
+
+
+```
+
+# SearchSelect 支持搜索的选择,追求尽可能少的代码来描述
+1. 支持按照输入来搜索,关键字将会使用红色表明并按照出现顺序排序
+2. 支持虚拟列表
+3. 支持多选
+4. 把转换移到了ts,尽可能减少繁琐的模板,增加类型推导,还可以直接闭包
+
+![iShot2022-07-14 17 32 11](https://user-images.githubusercontent.com/25872019/178951654-a1258dac-3084-43bd-bed7-c093c6749935.gif)
+## props
+options 选项数组
+conv 定义如何从选项数组转换到值以及选项的文本,key回填时显示的文本,具体见SearchSelectConv
+value v-model的值,如果为多选类型则为array,否则是conv.value的返回类型
+mode 模式 多选的话multipie,单选不需要写
+## 例子
+```ts
+const options = '黄瓜,土豆,胡萝卜,西红柿,茄子'.split(',').map((name,idx) => ({ id: idx + 1, name }))
+const conv: SearchSelectConv<{ id: number; name: string }> = {
+ text: v => v.name,
+ value: v => v.id
+ // 还有optionText, key 可空
+}
+const selectedID = ref()
+```
+```html
+
+```
diff --git a/doc/vuex.md b/doc/vuex.md
new file mode 100644
index 0000000..039e3b8
--- /dev/null
+++ b/doc/vuex.md
@@ -0,0 +1,75 @@
+- [mutation 生成mutation函数的辅助函数](#mutation-生成mutation函数的辅助函数)
+- [VuexPersistence 用于持久化的vuex插件](#vuexpersistence-用于持久化的vuex插件)
+ - [feature](#feature)
+ - [最小化例子](#最小化例子)
+
+desc: vuex相关的
+# mutation 生成mutation函数的辅助函数
+```ts
+const mutationSetter = mutation()
+
+const mutations = {
+ setTagList: mutationSetter('tagList') // 将会提供所有的key给你选择
+}
+
+// 等价于
+
+const mutations = {
+ setTagList(state: AnnotationState, tagList: Tag[]) {
+ state.tagList = tagList
+ }
+}
+```
+
+# VuexPersistence 用于持久化的vuex插件
+## feature
+1. 监控mutations的commit并持久化
+2. 支持自定义序列化和反序列化
+3. 支持设置过期时间
+4. 支持设置名称空间,对微前端友好
+5. 由vuex-dispatch-infer提供类型推导,从mutation type中直接推存储键名,不需要再去思考名字
+## 最小化例子
+```ts
+// src/store/app.ts
+const state = (): AppState => ({
+ isSuperUser: persistence.get('app/setSu').or(false) // 从持久化中获取,如果没找到则使用false
+})
+
+const mutationSetter = mutation()
+
+const mutations = {
+ setSu: mutationSetter('isSuperUser')
+}
+
+export default {
+ namespaced: true,
+ state,
+ mutations
+}
+
+
+// src/store/index.ts
+import app from './app.ts'
+import { GetOverloadDict } from 'vuex-dispatch-infer'
+
+type StoreParams = { modules: { app: typeof app } }
+type Mutations = keyof GetOverloadDict
+export const persistence = new VuexPersistence('ciallo')
+
+export const store = createStore({
+ modules: {
+ app
+ },
+ plugins: [persistence.watch([
+ 'app/setSu', // 最简单的用法,不过期,使用json进行序列化和反序列化,名字会帮你推出来
+ { // 需要自定义可以用object的选项来替代上面的那个
+ type: 'app/setSu', // type是必要的,其他的是可选的
+ expire: moment.duration(1, 'h'), // 下次打开页面时如果距离上次修改超过一小时会删除
+ serialize: v => v ? '1' : '', // 自定义序列化方法
+ deserialize: str => !!str
+ }
+ ])]
+})
+
+store.commit('app/setSu', true) // 在每次commit时会保存到本地,下次打开时恢复
+```
diff --git a/package.json b/package.json
index ddba025..d2fab86 100644
--- a/package.json
+++ b/package.json
@@ -65,6 +65,7 @@
"build": "yarn clean && yarn rollup -c",
"clean": "rm -rf ./dist ./es",
"dev-watch": "ts-node --project tsnode.config.json scripts/index.ts --dev-watch",
+ "gen-contents": "ts-node --project tsnode.config.json scripts/index.ts --gen-contents",
"import-optimize": "ts-node --project tsnode.config.json scripts/index.ts --import-optimize",
"pre-release": "yarn build && yarn import-optimize && yarn test && nrm use npm",
"test": "yarn jest",
diff --git a/scripts/genContents.ts b/scripts/genContents.ts
new file mode 100644
index 0000000..7166ac3
--- /dev/null
+++ b/scripts/genContents.ts
@@ -0,0 +1,40 @@
+import * as fs from 'fs/promises'
+import path from 'path'
+
+/**
+ * 从doc文件夹下的文件的目录生成总的目录
+ * 文件目录由markdown in one生成
+ * https://user-images.githubusercontent.com/25872019/179443451-6c974bf3-18d8-463f-a4df-1dcb0f787086.png
+ */
+const reg = /(.+)/s
+const readmeFilename = './README.md'
+export const genContents = async () => {
+ const docProfiles = new Array<{ contents: string, desc: string, fileName: string }>()
+ const files = await fs.readdir('./doc')
+ for (const fileName of files) {
+ const file = (await fs.readFile(path.join('./doc', fileName))).toString()
+ const [_, contents, desc] = /^((?:.|\n)*)?desc:(.*)?\n/.exec(file)!
+ docProfiles.push({
+ contents: contents
+ .trim()
+ .split('\n')
+ .map(line => line.replace(/]\((.*)\)/, (_, url) => `](./doc/${fileName}` + url + ')'))
+ .join('\n'),
+ desc,
+ fileName
+ })
+ }
+ const readme = (await fs.readFile(readmeFilename)).toString()
+ const buildGeneralDoc = () => {
+ return docProfiles.map((doc) => {
+ return `## ${doc.desc}
+${doc.contents}
+`
+ }).join('\n')
+ }
+ const newReadme = readme.replace(reg,
+`
+${buildGeneralDoc()}
+`)
+ await fs.writeFile(readmeFilename, newReadme)
+}
diff --git a/scripts/index.ts b/scripts/index.ts
index 37c0249..bf9feb2 100644
--- a/scripts/index.ts
+++ b/scripts/index.ts
@@ -1,6 +1,7 @@
import { importOptimize } from './importOptimize'
import { startGenVueType } from './generateVueType'
import { devWatch } from './watch'
+import { genContents } from './genContents'
const { argv } = process
const type = argv?.[2].substr(2)
@@ -14,4 +15,7 @@ switch (type) {
case 'dev-watch':
devWatch()
break
+ case 'gen-contents':
+ genContents()
+ break
}
diff --git a/src/SearchSelect/typedef.ts b/src/SearchSelect/typedef.ts
index 556e8e2..3a1eb5d 100644
--- a/src/SearchSelect/typedef.ts
+++ b/src/SearchSelect/typedef.ts
@@ -33,5 +33,6 @@ export interface Props {
value: unknown
options: any[]
conv: SearchSelectConv
+ mode?: 'multipie'
}
diff --git a/src/makeAsyncIterator.ts b/src/makeAsyncIterator.ts
index 2ddcb72..1316d77 100644
--- a/src/makeAsyncIterator.ts
+++ b/src/makeAsyncIterator.ts
@@ -17,6 +17,7 @@ export type ResetParams = {
/**
* 创建异步迭代器
+ * https://github.com/xiachufang/vue3-ts-util/blob/main/doc/io.md#makeasynciter
* 分页资源获取,不需要手动管理cursor的迭代
* @param resFetch 资源获取函数
* @param resp2res 响应体转获取额资源
diff --git a/src/useResizable.ts b/src/useResizable.ts
index cc280f6..f8281c3 100644
--- a/src/useResizable.ts
+++ b/src/useResizable.ts
@@ -10,6 +10,13 @@ interface Point {
}
const twoPointDistance = (a: Point, b: Point) => Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2))
+/**
+ * 用于鼠标拖拽调整调整某个元素的大小位置
+ * @param ele 控制的对象
+ * @param initRect 初始大小
+ * @param triggerWidth 触发可重置的范围大小
+ * @returns
+ */
export const useResizable = (ele: Ref, initRect: { width: number, height: number; x: number; y: number }, triggerWidth = 50) => {
const rect = reactive(initRect)
const pressed = ref(false)