This commit is contained in:
mnz 2024-12-04 11:10:13 +08:00
parent 4c1f42de56
commit ecc98b14a2
603 changed files with 64613 additions and 2 deletions

3
.gitignore vendored
View File

@ -1,2 +1 @@
/uni_modules
/unpackage/dist

View File

@ -0,0 +1,33 @@
## 0.1.32023-08-19
- fix: 修复使用remove导致样式错乱
## 0.1.22023-08-09
- fix: 修复nvue没有获取节点的问题
- fix: 修复因延时导致卡在中途
- fix: 修复change事件有时失效的问题
## 0.1.12023-07-03
- chore: 更新文档
## 0.1.02023-07-03
- fix: 外面的事件冒泡导致点击调动内部移动方法错乱
## 0.0.92023-05-30
- fix: 修复因手机事件为`onLongpress`导致,在手机上无法长按
- fix: 无法因css导致滚动
## 0.0.82023-04-23
- feat: 更新文档
## 0.0.72023-04-23
- feat: 由于删除是一个危险的动作,故把方法暴露出来,而不在内部处理。如果之前有使用删除的,需要注意
- feat: 原来的`add`变更为`push`,增加`unshift`
## 0.0.62023-04-12
- fix: 修复`handle`不生效问题
- feat: 增加 `to`方法
## 0.0.52023-04-11
- chore: `grid` 插槽增加 `nindex`、`oindex`
## 0.0.42023-04-04
- chore: 去掉 script-setup 语法糖
- chore: 文档增加 vue2 使用方法
## 0.0.32023-03-30
- feat: 重要说明 更新 list 只会再次初始化
- feat: 更新文档
## 0.0.22023-03-29
- 修改文档
## 0.0.12023-03-29
- 初次提交

View File

@ -0,0 +1,93 @@
$drag-handle-size: var(--l-drag-handle-size, 50rpx);
$drag-delete-size: var(--l-drag-delete-size, 32rpx);
.l-drag {
// min-height: 100rpx;
overflow: hidden;
margin: 24rpx 30rpx 0 30rpx;
// padding: 30rpx 0;
/* #ifdef APP-NVUE */
// flex: 1;
/* #endif */
/* #ifndef APP-NVUE */
// width: 100%;
/* #endif */
}
.l-drag__inner {
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
/* #ifndef APP-NVUE */
width: 100%;
/* #endif */
min-height: 100rpx;
}
.l-drag__view {
// touch-action: none;
// user-select: none;
// -webkit-user-select: auto;
z-index: 2;
transition: opacity 300ms ease;
.mask {
position: absolute;
inset: 0;
background-color: transparent;
z-index: 9;
}
/* #ifndef APP-NVUE */
> view {
&:last-child {
width: 100%;
height: 100%;
}
}
box-sizing: border-box;
/* #endif */
}
.l-drag-enter {
opacity: 0;
}
.l-drag__ghost {
/* #ifndef APP-NVUE */
> view {
&:last-child {
width: 100%;
height: 100%;
}
}
box-sizing: border-box;
/* #endif */
}
.l-is-active {
z-index: 3;
}
.l-is-hidden {
opacity: 0;
}
.l-drag__delete {
position: absolute;
z-index: 10;
width: $drag-delete-size;
height: $drag-delete-size;
}
.l-drag__handle {
position: absolute;
z-index: 10;
width: $drag-handle-size;
height: $drag-handle-size;
}
/* #ifndef APP-NVUE */
.l-drag__delete::before,.l-drag__handle::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
z-index: 10;
}
/* #endif */

View File

@ -0,0 +1,532 @@
<template>
<view class="l-drag l-class" :style="[areaStyles]" ref="dragRef" @touchstart="setDisabled">
<movable-area class="l-drag__inner" v-if="isReset" :style="[innerStyles]">
<slot></slot>
<movable-view class="l-drag__ghost" v-if="isDrag && props.ghost" :animation="true" :style="[viewStyles]" direction="all" :x="ghostEl.x" :y="ghostEl.y" key="l-drag-clone">
<slot name="ghost"></slot>
</movable-view>
<movable-view v-if="props.before" class="l-drag__before" disabled :animation="false" :style="[viewStyles]" :x="beforeEl.x" :y="beforeEl.y">
<slot name="before"></slot>
</movable-view>
<movable-view
v-for="(item, oindex) in cloneList" :key="item.id"
direction="all"
:data-oindex="oindex"
:style="[viewStyles]"
class="l-drag__view"
:class="[{'l-is-active': oindex == active, 'l-is-hidden': !item.show}, item.class]"
:x="item.x"
:y="item.y"
:friction="friction"
:damping="damping"
:animation="animation"
:disabled="isDisabled || props.disabled"
@touchstart="touchStart"
@change="touchMove"
@touchend="touchEnd"
@touchcancel="touchEnd"
@longpress="setDisabled"
>
<!-- <view v-if="props.remove" class="l-drag__remove" :style="removeStyle" data-remove="true">
<slot name="remove" :oindex="oindex" data-remove="true" />
</view> -->
<!-- <view v-if="props.handle" class="l-drag__handle" :style="handleStyle" data-handle="true">
<slot name="handle" :oindex="oindex" :active="!isDisabled && !isDisabled && oindex == active" />
</view> -->
<slot name="grid" :oindex="oindex" :index="item.index" :oldindex="item.oldindex" :content="item.content" :active="!isDisabled && !isDisabled && oindex == active" />
<view class="mask" v-if="!(isDisabled || props.disabled) && props.longpress"></view>
</movable-view>
<movable-view v-if="props.after" class="l-drag__after" disabled :animation="true" direction="all" :style="[viewStyles]" :x="afterEl.x" :y="afterEl.y">
<slot name="after"></slot>
</movable-view>
</movable-area>
</view>
</template>
<script lang="ts">
// @ts-nocheck
import { computed, onMounted, ref, getCurrentInstance, watch, nextTick, reactive , triggerRef, onUnmounted, defineComponent} from "./vue";
import DragProps from './props';
import type {GridRect, Grid, Position} from './type'
// #ifdef APP-NVUE
const dom = weex.requireModule('dom')
// #endif
export default defineComponent({
name: 'l-drag',
externalClasses: ['l-class'],
options: {
addGlobalClass: true,
virtualHost: true,
},
props: DragProps,
emits: ['change'],
setup(props, {emit, expose}) {
const res = wx.getSystemInfoSync();
const statusHeight = res.statusBarHeight; //
const cusnavbarheight = (statusHeight + 44) + "px";
// #ifdef APP-NVUE
const dragRef = ref(null)
// #endif
const app = getCurrentInstance()
const isDrag = ref(false)
const isInit = ref(false)
const isReset = ref(true)
const colmunId = ref(-1)
/** 选中项原始下标 */
const active = ref(-1)
const maxIndex = ref(-1)
const animation = ref(true)
const isDisabled = ref(props.handle || props.longpress ? true: false)
const dragEl = reactive({
content: null,
/** 当前视图下标*/
index: 0,
/** 旧视图下标 */
oldindex: -1,
/** 上次原始下标 */
lastindex: -1
})
const ghostEl = reactive({
content: null,
x: 0,
y: 0
})
const beforeEl = reactive({
x: 0,
y: 0
})
const afterEl = reactive({
x: 0,
y: 0
})
let gridRects = [] //ref<GridRect[]>([])
const areaWidth = ref(0)
const cloneList = ref<Grid[]>([])
//
const leaveRow = ref(0)
const extra = computed(() => (props.before ? 1 :0) + (props.after ? 1 : 0))
const rows = computed(() => Math.ceil( ((isInit.value ? cloneList.value.length : props.list.length) + extra.value) / props.column ))
const gridHeight = computed(() => props.aspectRatio ? girdWidth.value / props.aspectRatio : (/rpx$/.test(`${props.gridHeight}`) ? uni.upx2px(parseInt(`${props.gridHeight}`)) : parseInt(`${props.gridHeight}`)))
const girdWidth = computed(() => areaWidth.value / props.column)
const viewStyles = computed(() => ({width: girdWidth.value + 'px',height: gridHeight.value + 'px'}))
const areaStyles = computed(() => ({height: (rows.value + leaveRow.value ) * gridHeight.value + 'px'}))
const innerStyles = computed(() => ({
// #ifdef APP-NVUE
width: areaWidth.value + 'px',
// #endif
height: (rows.value + props.extraRow + leaveRow.value) * gridHeight.value + 'px'}))
const sleep = (cb: Function, time = 1000/60) => setTimeout(cb, time)
const createGrid = (content: any, position?:Position|null): Grid => {
colmunId.value++
maxIndex.value++
const index = maxIndex.value
const colmun = gridRects[index]
let x = 0
let y = 0
if(colmun) {
if(props.after) {
let nxet = gridRects[index + 1]
if(!nxet) {
nxet = createGridRect(gridRects.length + (props.before ? 1 : 0))
gridRects.push(nxet)
}
setReset(() => setAfter(nxet))
} else {
setReset()
}
x = colmun.x
y = colmun.y
} else {
const nxet = createGridRect(gridRects.length + (props.before ? 1 : 0))
gridRects.push(nxet)
setReset()
x = nxet.x
y = nxet.y
}
if(position) {
x = position.x
y = position.y
}
return {id: `l-drag-item-${colmunId.value}`, index, oldindex: index, content, x, y, class: '', show: true}
}
const setReset = (cb?: any) => {
// const newRow = (cloneList.value.length + extra.value) % (props.column)
if(isInit.value) {
cb&&sleep(cb)
}
}
const setAfter = ({x, y} = {x: 0, y: 0}) => {
if(props.after) {
afterEl.x = x
afterEl.y = y
}
}
const setDisabled = (e: any, flag?: boolean= false) => {
// e?.preventDefault()
const type = `${e.type}`.toLowerCase()
const {handle = props.touchHandle} = e.target.dataset
if(props.handle && !handle) {
isDisabled.value = true
} else if(props.handle && handle && !props.longpress) {
isDisabled.value = flag
} else if(props.handle && handle && props.longpress && type.includes('longpress')) {
isDisabled.value = false
} else if(props.longpress && type.includes('longpress') && !props.handle) {
isDisabled.value = false
}
if(type.includes('touchend') && props.longpress) {
isDisabled.value = true
}
}
const createGridRect = (i: number, last?: GridRect): GridRect => {
let { row } = last || gridRects[gridRects.length - 1] || { row: 0 }
const col = i % (props.column)
const grid = (row: number, x: number, y: number):GridRect => {
return {row, x, y, x1: x + girdWidth.value, y1: y + gridHeight.value}
}
if(col == 0 && i != 0) {row++}
return grid(row, col * girdWidth.value, row * gridHeight.value)
}
const createGridRects = () => {
let rects: GridRect[] = []
const length = rows.value * props.column + extra.value
gridRects = []
for (var i = 0; i < length; i++) {
const item = createGridRect(i, rects[rects.length - 1])
rects.push(item)
}
if(props.before) {
const {x, y} = rects.shift()
beforeEl.x = x
beforeEl.y = y
}
setAfter(rects[props.list.length])
gridRects = rects as GridRect[]
}
const updateList = (v: any[]) => {
cloneList.value = v.map((content) => createGrid(content))
}
const touchStart = (e: any) => {
if(e.target.dataset.remove) return
//
const {oindex} = e.currentTarget?.dataset || e.target?.dataset || {}
if(typeof oindex !== 'number') return
const target = cloneList.value[oindex]
isDrag.value = true
//
active.value = oindex
//
dragEl.index = dragEl.oldindex = target.index
ghostEl.x = target.x||0
ghostEl.y = target.y||0
dragEl.content = ghostEl.content = target.content
}
const touchEnd = (e: any) => {
setTimeout(() => {
if(e.target.dataset.remove || active.value==-1) return
setDisabled(e, true)
isDrag.value = false
const isEmit = dragEl.index !== dragEl.oldindex && dragEl.oldindex > -1 // active.value !== dragEl.index
dragEl.lastindex = active.value
dragEl.oldindex = active.value = -1
const last = cloneList.value[dragEl.lastindex]
const position = gridRects[dragEl.index]
nextTick(() => {
last.x = position.x + 0.001
last.y = position.y + 0.001
sleep(() => {
last.x = position.x
last.y = position.y
isEmit && emitting()
})
})
},80)
}
const emitting = () => {
const clone = [...cloneList.value].sort((a, b) => a.index - b.index)//.map(item => ref(item.content))
emit('change', clone)
}
const touchMove = (e: any) => {
if(!isDrag.value) return
// #ifndef APP-NVUE
let {oindex} = e.currentTarget.dataset
// #endif
// #ifdef APP-NVUE
oindex = e.currentTarget.dataset['-oindex']
// #endif
if(oindex != active.value) return
const {x, y} = e.detail
const centerX = x + girdWidth.value / 2
const centerY = y + gridHeight.value / 2
for (let i = 0; i < cloneList.value.length; i++) {
const item = gridRects[i]
if(centerX > item.x && centerX < item.x1 && centerY > item.y && centerY < item.y1) {
ghostEl.x = item.x
ghostEl.y = item.y
if(dragEl.index != i) {
_move(active.value, i)
}
break;
}
}
}
const getDragEl = (oindex: number) => {
if(isDrag.value) {return dragEl}
return cloneList.value[oindex]
}
/**
* 把原始数据中排序为index的项 移动到 toIndex
* @param oindex 原始数据的下标
* @param toIndex 视图中的下标
* @param position 指定坐标
*/
const _move = (oindex: number, toIndex: number, position?: Position|null, emit: boolean = true) => {
const length = cloneList.value.length - 1
if(toIndex > length || toIndex < 0) return
// oIdnex
const dragEl = getDragEl(oindex)
let speed = 0
let start = dragEl.index
// indexindex
if(start < toIndex) {speed = 1}
if(start > toIndex) {speed = -1}
if(!speed) return
//
let distance = start - toIndex
//
while(distance) {
distance += speed
//
const target = isDrag.value ? (dragEl.index += speed) : (start += speed)
let targetOindex = cloneList.value.findIndex(item => item.index == target && item.content != dragEl.content)
if (targetOindex == oindex) return
if (targetOindex < 0) {targetOindex = cloneList.value.length - 1}
let targetEl = cloneList.value[targetOindex]
if(!targetEl) return;
// index
const lastIndex = target - speed
const activeEl = cloneList.value[oindex]
const rect = gridRects[lastIndex]
targetEl.x = rect.x
targetEl.y = rect.y
targetEl.oldindex = targetEl.index
targetEl.index = lastIndex
activeEl.oldindex = activeEl.index //oIndex
activeEl.index = toIndex
//
if(!distance && !isDrag.value) {
const rect = gridRects[toIndex]
const {x, y} = position||rect
activeEl.x = dragEl.x = x
activeEl.y = dragEl.y = y
// triggerRef(cloneList)
if(emit) {
emitting()
}
}
}
}
/**
* 为区分是主动调用还是内部方法
*/
const move = (oindex: number, toIndex: number) => {
active.value = -1
isDrag.value = false
_move(oindex, toIndex)
}
//
const REMOVE_TIME = 400
let removeTimer = null
const remove = (oindex: number) => {
active.value = -1
isDrag.value = false
clearTimeout(removeTimer)
const item = cloneList.value[oindex]
if(props.disabled || !item) return
item.show = false
const after = cloneList.value.length - 1
_move(oindex, after, item, false)
setAfter(gridRects[after])
maxIndex.value--
const _remove = (_index = oindex) => {
//
// animation.value = false
const row = Math.ceil((cloneList.value.length - 1 + extra.value) / props.column)
if( row < rows.value) {
leaveRow.value = (rows.value - row)
}
cloneList.value.splice(_index, 1)[0]
emitting()
removeTimer = setTimeout(() => {
leaveRow.value = 0
}, REMOVE_TIME)
}
_remove()
}
const push = (...args: any) => {
if(props.disabled) return
if(Array.isArray(args)) {
Promise.all(args.map(async item => await add(item, true))).then(emitting)
}
}
const add = (content: any, after: boolean) => {
return new Promise((resolve) => {
const item = createGrid(content, after ? null : {x: -100, y:0})
item.class = 'l-drag-enter'
cloneList.value.push(item)
const length = cloneList.value.length - 1
nextTick(() => {
sleep(() => {
item.class = 'l-drag-leave'
_move(length, (after ? length : 0), null, false)
triggerRef(cloneList)
resolve(true)
})
})
})
}
const unshift = (...args: any) => {
if(props.disabled) return
if(Array.isArray(args)) {
Promise.all(args.map(async (item) => await add(item))).then(emitting)
}
}
//
const shift = () => {
if(!cloneList.value.length) return
remove(cloneList.value.findIndex(item => item.index == 0) || 0)
}
const pop = () => {
const length = cloneList.value.length-1
if(length < 0 ) return
remove(cloneList.value.findIndex(item => item.index == length) || length)
}
// const splice = (start, count, ...context) => {
// //
// }
const clear = () => {
isInit.value = isDrag.value = false
maxIndex.value = colmunId.value = active.value = -1
cloneList.value = []
gridRects = []
}
const init = () => {
clear()
createGridRects()
nextTick(() => {
updateList(props.list)
isInit.value = true
})
}
let count = 0
const getRect = () => {
count++
// #ifndef APP-NVUE
uni.createSelectorQuery().in(app.proxy).select('.l-drag').boundingClientRect((res: UniNamespace.NodeInfo) => {
if(res) {
areaWidth.value = res.width || 0
//
init()
}
}).exec()
// #endif
// #ifdef APP-NVUE
sleep(() => {
nextTick(() => {
dom.getComponentRect(dragRef.value, (res) => {
if(!res.size.width && count < 5) {
getRect()
} else {
areaWidth.value = res.size.width || 0
init()
}
})
})
})
// #endif
}
onMounted(getRect)
onUnmounted(clear)
watch(() => props.list, init)
// #ifdef VUE3
expose({
remove,
// add,
move,
push,
unshift,
shift,
pop
})
// #endif
return {
// #ifdef APP-NVUE
dragRef,
// #endif
cloneList,
areaStyles,
innerStyles,
viewStyles,
setDisabled,
isDisabled,
isReset,
isDrag,
active,
animation,
afterEl,
ghostEl,
beforeEl,
touchStart,
touchMove,
touchEnd,
remove,
// add,
move,
push,
unshift,
// shift,
// pop,
props
// isDelete: props.delete,
// ...toRefs(props)
}
}
})
</script>
<style lang="scss">
.l-drag{
margin-top: v-bind(cusnavbarheight);
}
@import './index';
</style>

View File

@ -0,0 +1,47 @@
// @ts-nocheck
export default {
list: {
type: Array,
default: []
},
column: {
type: Number,
default: 2
},
/**宽高比 填写这项, gridHeight 失效*/
aspectRatio: Number,
gridHeight: {
type: [Number, String],
default: '120rpx'
},
// removeStyle: String,
// handleStyle: String,
damping: {
type: Number,
default: 40
},
friction: {
type: Number,
default: 2
},
/**
* movable-area
*/
extraRow: {
type: Number,
default: 0
},
/**
* movable-area vif , BUG uni官方好像修复了
*/
// reset: Boolean,
// sort: Boolean,
// remove: Boolean,
ghost: Boolean,
handle: Boolean,
touchHandle: Boolean,
before: Boolean,
after: Boolean,
disabled: Boolean,
longpress: Boolean,
}

View File

@ -0,0 +1,21 @@
export interface Position {
x: number
y: number
}
export interface GridRect extends Position{
row : number
// x : number
// y : number
x1 : number
y1 : number
}
export interface Grid extends Position{
id : string
index : number
oldindex : number
content : any
// x : number
// y : number
class : string
show: boolean
}

View File

@ -0,0 +1,9 @@
// @ts-nocheck
// export * from '@/uni_modules/lime-vue'
// #ifdef VUE3
export * from 'vue';
// #endif
// #ifndef VUE3
export * from '@vue/composition-api';
// #endif

View File

@ -0,0 +1,268 @@
<template>
<demo-block type="ultra" title="拖拽">
<demo-block title="基础">
<l-drag :list="list">
<template #grid="{active, index}">
<view class="inner" :class="{active}">
<text class="text" :class="{'text-active': active}">{{index}}</text>
</view>
</template>
</l-drag>
</demo-block>
<demo-block title="多列 长按">
<!-- 列后 删除 幽灵 长按 -->
<l-drag ref="dragRef2" :list="list" @change="change2" :aspectRatio="1" :column="4" after ghost longpress>
<template #grid="{oindex, content, active}">
<view class="grid">
<view class="remove" @click="onRemove2(oindex)"></view>
<view class="inner" :class="{active}">
<text class="text" :class="{'text-active': active}">{{content}}</text>
</view>
</view>
</template>
<template #ghost>
<view class="grid">
<!-- 幽灵样式 -->
<view class="inner ghost"></view>
</view>
</template>
<!-- 示例未设置 before -->
<template #before>
<view class="grid">
<view class="inner extra" @click="onAdd2">
增加
</view>
</view>
</template>
<template #after>
<view class="grid">
<view class="inner extra" @click="onAdd2">
增加
</view>
</view>
</template>
</l-drag>
<button @click="refresh2">更新列表</button>
<button @click="onUnshift2">向前增加</button>
<button @click="onPush2">向后增加</button>
<button @click="onShift2">shift</button>
<button @click="onPop2">pop</button>
</demo-block>
<demo-block title="单列 按手柄">
<!-- 幽灵 手柄 -->
<view class="inputs">
<text>把原始下标为</text>
<input type="text" v-model="move3.index">
<text>的项移动到</text>
<input type="text" v-model="move3.nindex">
</view>
<view style="height: 600rpx; overflow: auto;">
<l-drag ref="dragRef3" :list="list3" @change="change3" grid-height="200rpx" :column="1" :touchHandle="touchHandle3" ghost handle>
<template #grid="{active, content, index, oindex}">
<view class="grid">
<view class="mover" @touchstart="touchHandle3 = true" @touchend="touchHandle3 = false" style="left: 100rpx; top: 50%; transform: translateY(-50%); position: absolute; z-index: 1;"></view>
<view class="inner" :class="{active}">
<text class="text" :class="{'text-active': active}">{{content}}</text>
<view @click.stop.prevent="to(oindex, index - 1)">上移</view>
<view @click.stop.prevent="to(oindex, index + 1)">下移</view>
</view>
</view>
</template>
<template #ghost>
<view class="grid">
<!-- 幽灵样式 -->
<view class="inner ghost"></view>
</view>
</template>
</l-drag>
</view>
</demo-block>
</demo-block>
</template>
<script>
import {ref, watch, defineComponent, reactive} from '../l-drag/vue'
export default defineComponent({
setup() {
// 12list
const list = ref(new Array(7).fill(0).map((v,i) => i));
// 2
const dragRef2 = ref(null)
const newList2 = ref([])
const change2 = v => {
console.log('示例2数据发生变化', v)
newList2.value = [...v]
}
const onRemove2 = (index) => {
if(dragRef2.value && index >= 0) {
dragRef2.value.remove(index)
}
}
const onAdd2 = () => {
dragRef2.value.push(Math.round(Math.random() * 1000))
}
const onShift2 = () => {
dragRef2.value.shift()
}
const onPop2 = () => {
dragRef2.value.pop()
}
const onUnshift2 = () => {
dragRef2.value.unshift(Math.round(Math.random() * 1000), Math.round(Math.random() * 1000))
}
const onPush2 = () => {
dragRef2.value.push(Math.round(Math.random() * 1000), Math.round(Math.random() * 1000))
}
const refresh2 = () => {
list.value = new Array(10).fill(0).map((v,i) => i);
}
// 3
const list3 = new Array(5).fill(0).map((v,i) => i);
const dragRef3 = ref(null)
const newList3 = ref([])
const touchHandle3 = ref(false)
const change3 = v => {
newList3.value = v
}
const move3 = reactive({
index: 0,
nindex: 0
})
watch(() => move3.nindex, (v, o) => {
dragRef3.value.move(move3.index*1, move3.nindex*1)
})
const to = (oindex, index) => {
dragRef3.value.move(oindex, index)
}
return {
list,
// 2
dragRef2,
change2,
onRemove2,
onAdd2,
refresh2,
onUnshift2,
onPush2,
onShift2,
onPop2,
// 3
list3,
dragRef3,
change3,
move3,
touchHandle3,
to
}
}
})
</script>
<style lang="scss">
.grid {
height: 100%;
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
/* #ifndef APP-NVUE */
width: 100%;
/* #endif */
padding: 16rpx;
box-sizing: border-box;
position: relative;
}
.remove {
width: 32rpx;
height: 32rpx;
background-color: red;
border-radius: 50%;
font-size: 16rpx;
color: white;
display: flex;
text-align: center;
justify-content: center;
z-index: 10;
position: absolute;
right: 0;
top: 0;
}
.inner {
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
/* #ifndef APP-NVUE */
height: 100%;
width: 100%;
/* #endif */
border: 1rpx solid #eee;
display: flex;
align-items: center;
justify-content: center;
background-color: white;
font-size: 50rpx;
font-weight: bold;
color: blue;
transition: all 300ms ease;
position: relative;
}
.extra {
color: #ddd
}
.mover {
position: relative;
width: 50rpx;
margin-top: 10rpx;
height: 30rpx;
border: 2px solid #ddd;
border-left: none;
border-right: none;
/* background-color: #ddd; */
}
.mover::before {
content: '';
position: absolute;
width: 50rpx;
top: 15rpx;
border-top: 2px solid #ddd;
}
.active {
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.1);
background-color: blue;
color: white;
border: 1rpx solid blue;
transform: scale(1.1);
}
/* #ifdef APP-NVUE */
.text {
font-size: 50rpx;
font-weight: bold;
}
.text-active {
font-size: 50rpx;
font-weight: bold;
color: white;
}
/* #endif */
.ghost {
background-color: rgba(0, 0, 255, 0.1);
}
.inputs {
display: flex;
align-items: center;
input {
background-color: #fff;
width: 80rpx;
border: 1rpx solid #ddd;
padding: 5rpx 10rpx;
margin: 0 8rpx;
}
}
</style>

View File

@ -0,0 +1,87 @@
{
"id": "lime-drag",
"displayName": "拖拽排序-拖动排序-LimeUI",
"version": "0.1.3",
"description": "uniapp vue3 拖拽排序插件,用于图片或列表的拖动排序,可设置列数、增加删除等功能, vue2只要配置@vue/composition-api",
"keywords": [
"拖拽",
"拖拽排序",
"排序",
"拖动",
"拖动排序"
],
"repository": "",
"engines": {
"HBuilderX": "^3.7.12"
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [
"lime-shared"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "n",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "y",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@ -0,0 +1,170 @@
# lime-drag 拖拽排序
- 当前为初版 可能会有BUG
- 基于uniapp vue3
- Q群 1169785031
### 安装
- 在市场导入插件即可在任意页面使用,无须再`import`
### 使用
- 提供简单的使用示例更多请查看下方的demo
```html
<l-drag :list="list" @change="change">
<!-- // 每一项的插槽 grid 的 content 您传入的数据 -->
<template #grid="{active, content}">
<!-- // grid.active 是否为当前拖拽项目 根据自己需要写样式 -->
<view class="inner" :class="{active: active}">
<text class="text" :class="{'text-active': active}">{{grid.content}}</text>
</view>
</template>
</l-drag>
```
```js
const list = new Array(7).fill(0).map((v,i) => i);
// 拖拽后新的数据
const newList = ref([])
const change = v => newList.value = v
```
#### 增删
- 不要给list赋值这样只会重新初始化
- 增加数据 调用暴露的`push`
- 删除某条数据调用暴露的`remove`方法,需要传入`oindex`
```html
<l-drag :list="list" @change="change" ref="dragRef" after remove>
<!-- 每一项插槽 grid 的 content 是您传入的数据 -->
<template #grid="{active, index, oldindex, oindex}">
<!-- active 是否为当前拖拽项目 根据自己需要写样式 -->
<!-- index 排序后列表下标 -->
<!-- oldindex 排序后列表旧下标 -->
<!-- oindex 列表原始下标,输入的数据排位不会因为排序而改变 -->
<view class="remove" @click="onRemove(oindex)"></view>
<view class="inner" :class="{active}">
<text class="text" :class="{'text-active': active}">{{content}}</text>
</view>
</template>
<template #after>
<view class="grid">
<view class="inner extra" @click="onAdd">
增加
</view>
</view>
</template>
</l-drag>
```
```js
const dragRef = ref(null)
const list = new Array(7).fill(0).map((v,i) => i);
const onAdd = () => {
dragRef.value.push(Math.round(Math.random() * 1000))
}
const onRemove = (oindex) => {
if(dragRef.value && oindex >= 0) {
// 记得oindex为数组的原始index
dragRef.value.remove(oindex)
}
}
```
#### 插槽
```html
<l-drag :list="list">
<!-- 每一项的插槽 -->
<template #grid="{active, index, oldindex, oindex, content}"></template>
<!-- 当前拖拽项幽灵插槽 设置`ghost`后使用 主要为实现拖拽时 有个影子跟着 -->
<template #ghots></template>
<!-- 前后方插槽为固定在列表前方和后方,不能拖动 -->
<!-- 列表前方的插槽 设置`before`后使用 -->
<template #before></template>
<!-- 列表后方的插槽 设置`after`后使用 -->
<template #after></template>
</l-drag>
```
### 查看示例
- 导入后直接使用这个标签查看演示效果
```html
<!-- // 代码位于 uni_modules/lime-drag/compoents/lime-drag -->
<lime-drag />
```
### 插件标签
- 默认 l-drag 为 component
- 默认 lime-drag 为 demo
### 关于vue2的使用方式
- 插件使用了`composition-api`, 如果你希望在vue2中使用请按官方的教程[vue-composition-api](https://uniapp.dcloud.net.cn/tutorial/vue-composition-api.html)配置
- 关键代码是: 在main.js中 在vue2部分加上这一段即可,官方是把它单独成了一个文件.
```js
// vue2
import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)
```
- 另外插件也用到了TSvue2可能会遇过官方的TS版本过低的问题,找到HX目录下的`compile-typescript`目录
```cmd
// \HBuilderX\plugins\compile-typescript
yarn add typescript -D
- or -
npm install typescript -D
```
- 小程序需要在`manifest.json`启用`slotMultipleInstance`
```json
"mp-weixin" : {
"slotMultipleInstance" : true
}
```
## API
### Props
| 参数 | 说明 | 类型 | 默认值 |
| --------------------------| ------------------------------------------------------------ | ---------------- | ------------ |
| list | 列表数组,不可变化,变化后会重新初始化 | <em>array</em> | `[]` |
| column | 列数 | <em>number</em> | `2` |
| gridHeight | 行高,宫格高度 | <em>string</em> | `120rpx` |
| damping | 阻尼系数用于控制x或y改变时的动画和过界回弹的动画值越大移动越快 | <em>string</em> | `-` |
| friction | 摩擦系数用于控制惯性滑动的动画值越大摩擦力越大滑动越快停止必须大于0否则会被设置成默认值 | <em>number</em> | `2` |
| extraRow | 额外行数 | <em>number</em> | `0` |
| ghost | 开启幽灵插槽 | <em>boolean</em> | `false` |
| before | 开启列前插槽 | <em>boolean</em> | `false` |
| after | 开启列后插槽 | <em>boolean</em> | `false` |
| disabled | 是否禁用 | <em>boolean</em> | `false` |
| longpress | 是否长按 | <em>boolean</em> | `false` |
### Events
| 参数 | 说明 | 参数 |
| --------------------------| ------------------------------------------------------------ | ---------------- |
| change | 返回新数据 | list |
### Expose
| 参数 | 说明 | 参数 |
| --------------------------| ------------------------------------------------------------ | ---------------- |
| remove | 删除, 传入`oindex`,即数据列表原始的index | |
| push | 向后增加,可以是数组或单数据 | |
| unshift | 向前增加,可以是数组或单数据 | |
| move | 移动, 传入(`oindex`, `toindex`),将数据列表原始的index项移到视图中的目标位置 | |
### TODO
将来实现的功能
- splice
## 打赏
如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。
![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/alipay.png)
![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/wpay.png)

View File

@ -0,0 +1,42 @@
// @ts-nocheck
import {isNumeric} from '../isNumeric'
import {isDef} from '../isDef'
/**
* px
* @param value
* @returns null null
*/
// #ifndef APP-IOS || APP-ANDROID
export function addUnit(value?: string | number): string | null {
if (!isDef(value)) {
return null;
}
value = String(value); // 将值转换为字符串
// 如果值是数字,则在后面添加单位 "px",否则保持原始值
return isNumeric(value) ? `${value}px` : value;
}
// #endif
// #ifdef APP-IOS || APP-ANDROID
function addUnit(value: string): string
function addUnit(value: number): string
function addUnit(value: any|null): string|null {
if (!isDef(value)) {
return null;
}
value = `${value}` //value.toString(); // 将值转换为字符串
// 如果值是数字,则在后面添加单位 "px",否则保持原始值
return isNumeric(value) ? `${value}px` : value;
}
export {addUnit}
// #endif
// console.log(addUnit(100)); // 输出: "100px"
// console.log(addUnit("200")); // 输出: "200px"
// console.log(addUnit("300px")); // 输出: "300px"(已经包含单位)
// console.log(addUnit()); // 输出: undefined值为 undefined
// console.log(addUnit(null)); // 输出: undefined值为 null

View File

@ -0,0 +1,82 @@
export function cubicBezier(p1x : number, p1y : number, p2x : number, p2y : number):(x: number)=> number {
const ZERO_LIMIT = 1e-6;
// Calculate the polynomial coefficients,
// implicit first and last control points are (0,0) and (1,1).
const ax = 3 * p1x - 3 * p2x + 1;
const bx = 3 * p2x - 6 * p1x;
const cx = 3 * p1x;
const ay = 3 * p1y - 3 * p2y + 1;
const by = 3 * p2y - 6 * p1y;
const cy = 3 * p1y;
function sampleCurveDerivativeX(t : number) : number {
// `ax t^3 + bx t^2 + cx t` expanded using Horner's rule
return (3 * ax * t + 2 * bx) * t + cx;
}
function sampleCurveX(t : number) : number {
return ((ax * t + bx) * t + cx) * t;
}
function sampleCurveY(t : number) : number {
return ((ay * t + by) * t + cy) * t;
}
// Given an x value, find a parametric value it came from.
function solveCurveX(x : number) : number {
let t2 = x;
let derivative : number;
let x2 : number;
// https://trac.webkit.org/browser/trunk/Source/WebCore/platform/animation
// first try a few iterations of Newton's method -- normally very fast.
// http://en.wikipedia.org/wikiNewton's_method
for (let i = 0; i < 8; i++) {
// f(t) - x = 0
x2 = sampleCurveX(t2) - x;
if (Math.abs(x2) < ZERO_LIMIT) {
return t2;
}
derivative = sampleCurveDerivativeX(t2);
// == 0, failure
/* istanbul ignore if */
if (Math.abs(derivative) < ZERO_LIMIT) {
break;
}
t2 -= x2 / derivative;
}
// Fall back to the bisection method for reliability.
// bisection
// http://en.wikipedia.org/wiki/Bisection_method
let t1 = 1;
/* istanbul ignore next */
let t0 = 0;
/* istanbul ignore next */
t2 = x;
/* istanbul ignore next */
while (t1 > t0) {
x2 = sampleCurveX(t2) - x;
if (Math.abs(x2) < ZERO_LIMIT) {
return t2;
}
if (x2 > 0) {
t1 = t2;
} else {
t0 = t2;
}
t2 = (t1 + t0) / 2;
}
// Failure
return t2;
}
return function (x : number) : number {
return sampleCurveY(solveCurveX(x));
}
// return solve;
}

View File

@ -0,0 +1,2 @@
import {cubicBezier} from './bezier';
export let ease = cubicBezier(0.25, 0.1, 0.25, 1);

View File

@ -0,0 +1,10 @@
// @ts-nocheck
// #ifdef APP-IOS || APP-ANDROID
export * from './uvue.uts'
// #endif
// #ifndef APP-IOS || APP-ANDROID
export * from './vue.ts'
// #endif

View File

@ -0,0 +1,97 @@
// @ts-nocheck
import { ComponentPublicInstance } from 'vue'
import { ease } from './ease';
import { Timeline, Animation } from './';
export type UseTransitionOptions = {
duration ?: number
immediate ?: boolean
context ?: ComponentPublicInstance
}
// #ifndef APP-IOS || APP-ANDROID
import { ref, watch, Ref } from '@/uni_modules/lime-shared/vue'
export function useTransition(percent : Ref<number>|(() => number), options : UseTransitionOptions) : Ref<number> {
const current = ref(0)
const { immediate, duration = 300 } = options
let tl:Timeline|null = null;
let timer = -1
const isFunction = typeof percent === 'function'
watch(isFunction ? percent : () => percent.value, (v) => {
if(tl == null){
tl = new Timeline()
}
tl.start();
tl.add(
new Animation(
current.value,
v,
duration,
0,
ease,
nowValue => {
current.value = nowValue
clearTimeout(timer)
if(current.value == v){
timer = setTimeout(()=>{
tl?.pause();
tl = null
}, duration)
}
}
)
);
}, { immediate })
return current
}
// #endif
// #ifdef APP-IOS || APP-ANDROID
type UseTransitionReturnType = Ref<number>
export function useTransition(source : any, options : UseTransitionOptions) : UseTransitionReturnType {
const outputRef : Ref<number> = ref(0)
const immediate = options.immediate ?? false
const duration = options.duration ?? 300
const context = options.context //as ComponentPublicInstance | null
let tl:Timeline|null = null;
let timer = -1
const watchFunc = (v : number) => {
if(tl == null){
tl = new Timeline()
}
tl!.start();
tl!.add(
new Animation(
outputRef.value,
v,
duration,
0,
ease,
nowValue => {
outputRef.value = nowValue //nowValue < 0.0001 ? 0 : Math.abs(v - nowValue) < 0.00001 ? v : nowValue;
clearTimeout(timer)
if(outputRef.value == v){
timer = setTimeout(()=>{
tl?.pause();
tl = null
}, duration)
}
}
), null
);
}
if (context != null && typeof source == 'string') {
context.$watch(source, watchFunc, { immediate } as WatchOptions)
} else {
watch(source, watchFunc, { immediate } as WatchOptions)
}
const stop = ()=>{
}
return outputRef //as UseTransitionReturnType
}
// #endif

View File

@ -0,0 +1,112 @@
// @ts-nocheck
export class Timeline {
state : string
animations : Set<Animation> = new Set<Animation>()
delAnimations : Animation[] = []
startTimes : Map<Animation, number> = new Map<Animation, number>()
pauseTime : number = 0
pauseStart : number = Date.now()
tickHandler : number = 0
tickHandlers : number[] = []
tick : (() => void) | null = null
constructor() {
this.state = 'Initiated';
}
start() {
if (!(this.state === 'Initiated')) return;
this.state = 'Started';
let startTime = Date.now();
this.pauseTime = 0;
this.tick = () => {
let now = Date.now();
this.animations.forEach((animation : Animation) => {
let t:number;
const ani = this.startTimes.get(animation)
if (ani == null) return
if (ani < startTime) {
t = now - startTime - animation.delay - this.pauseTime;
} else {
t = now - ani - animation.delay - this.pauseTime;
}
if (t > animation.duration) {
this.delAnimations.push(animation)
// 不能在 foreach 里面 对 集合进行删除操作
// this.animations.delete(animation);
t = animation.duration;
}
if (t > 0) animation.run(t);
})
// 不能在 foreach 里面 对 集合进行删除操作
while (this.delAnimations.length > 0) {
const animation = this.delAnimations.pop();
if (animation == null) return
this.animations.delete(animation);
}
clearTimeout(this.tickHandler);
if (this.state != 'Started') return
this.tickHandler = setTimeout(() => {
this.tick!()
}, 1000 / 60)
// this.tickHandlers.push(this.tickHandler)
}
this.tick!()
}
pause() {
if (!(this.state === 'Started')) return;
this.state = 'Paused';
this.pauseStart = Date.now();
clearTimeout(this.tickHandler);
}
resume() {
if (!(this.state === 'Paused')) return;
this.state = 'Started';
this.pauseTime += Date.now() - this.pauseStart;
this.tick!();
}
reset() {
this.pause();
this.state = 'Initiated';
this.pauseTime = 0;
this.pauseStart = 0;
this.animations.clear()
this.delAnimations.clear()
this.startTimes.clear()
this.tickHandler = 0;
}
add(animation : Animation, startTime ?: number | null) {
if (startTime == null) startTime = Date.now();
this.animations.add(animation);
this.startTimes.set(animation, startTime);
}
}
export class Animation {
startValue : number
endValue : number
duration : number
timingFunction : (t : number) => number
delay : number
template : (t : number) => void
constructor(
startValue : number,
endValue : number,
duration : number,
delay : number,
timingFunction : (t : number) => number,
template : (v : number) => void) {
this.startValue = startValue;
this.endValue = endValue;
this.duration = duration;
this.timingFunction = timingFunction;
this.delay = delay;
this.template = template;
}
run(time : number) {
let range = this.endValue - this.startValue;
let progress = time / this.duration
if(progress != 1) progress = this.timingFunction(progress)
this.template(this.startValue + range * progress)
}
}

View File

@ -0,0 +1,123 @@
// @ts-nocheck
const TICK = Symbol('tick');
const TICK_HANDLER = Symbol('tick-handler');
const ANIMATIONS = Symbol('animations');
const START_TIMES = Symbol('start-times');
const PAUSE_START = Symbol('pause-start');
const PAUSE_TIME = Symbol('pause-time');
const _raf = typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame : function(cb: Function) {return setTimeout(cb, 1000/60)}
const _caf = typeof cancelAnimationFrame !== 'undefined' ? cancelAnimationFrame: function(id: any) {clearTimeout(id)}
// const TICK = 'tick';
// const TICK_HANDLER = 'tick-handler';
// const ANIMATIONS = 'animations';
// const START_TIMES = 'start-times';
// const PAUSE_START = 'pause-start';
// const PAUSE_TIME = 'pause-time';
// const _raf = function(callback):number|null {return setTimeout(callback, 1000/60)}
// const _caf = function(id: number):void {clearTimeout(id)}
export class Timeline {
state: string
constructor() {
this.state = 'Initiated';
this[ANIMATIONS] = new Set();
this[START_TIMES] = new Map();
}
start() {
if (!(this.state === 'Initiated')) return;
this.state = 'Started';
let startTime = Date.now();
this[PAUSE_TIME] = 0;
this[TICK] = () => {
let now = Date.now();
this[ANIMATIONS].forEach((animation) => {
let t: number;
if (this[START_TIMES].get(animation) < startTime) {
t = now - startTime - animation.delay - this[PAUSE_TIME];
} else {
t = now - this[START_TIMES].get(animation) - animation.delay - this[PAUSE_TIME];
}
if (t > animation.duration) {
this[ANIMATIONS].delete(animation);
t = animation.duration;
}
if (t > 0) animation.run(t);
})
// for (let animation of this[ANIMATIONS]) {
// let t: number;
// console.log('animation', animation)
// if (this[START_TIMES].get(animation) < startTime) {
// t = now - startTime - animation.delay - this[PAUSE_TIME];
// } else {
// t = now - this[START_TIMES].get(animation) - animation.delay - this[PAUSE_TIME];
// }
// if (t > animation.duration) {
// this[ANIMATIONS].delete(animation);
// t = animation.duration;
// }
// if (t > 0) animation.run(t);
// }
this[TICK_HANDLER] = _raf(this[TICK]);
};
this[TICK]();
}
pause() {
if (!(this.state === 'Started')) return;
this.state = 'Paused';
this[PAUSE_START] = Date.now();
_caf(this[TICK_HANDLER]);
}
resume() {
if (!(this.state === 'Paused')) return;
this.state = 'Started';
this[PAUSE_TIME] += Date.now() - this[PAUSE_START];
this[TICK]();
}
reset() {
this.pause();
this.state = 'Initiated';
this[PAUSE_TIME] = 0;
this[PAUSE_START] = 0;
this[ANIMATIONS] = new Set();
this[START_TIMES] = new Map();
this[TICK_HANDLER] = null;
}
add(animation: any, startTime?: number) {
if (arguments.length < 2) startTime = Date.now();
this[ANIMATIONS].add(animation);
this[START_TIMES].set(animation, startTime);
}
}
export class Animation {
startValue: number
endValue: number
duration: number
timingFunction: (t: number) => number
delay: number
template: (t: number) => void
constructor(startValue: number, endValue: number, duration: number, delay: number, timingFunction: (t: number) => number, template: (v: number) => void) {
timingFunction = timingFunction || (v => v);
template = template || (v => v);
this.startValue = startValue;
this.endValue = endValue;
this.duration = duration;
this.timingFunction = timingFunction;
this.delay = delay;
this.template = template;
}
run(time: number) {
let range = this.endValue - this.startValue;
let progress = time / this.duration
if(progress != 1) progress = this.timingFunction(progress)
this.template(this.startValue + range * progress)
}
}

View File

@ -0,0 +1,10 @@
// @ts-nocheck
// #ifndef APP-IOS || APP-ANDROID
export * from './vue.ts'
// #endif
// #ifdef UNI-APP-X
// #ifdef APP-IOS || APP-ANDROID
export * from './uvue.uts'
// #endif
// #endif

View File

@ -0,0 +1,10 @@
// @ts-nocheck
// import {platform} from '../platform'
/**
* buffer转路径
* @param {Object} buffer
*/
// @ts-nocheck
export function arrayBufferToFile(buffer: ArrayBuffer, name?: string, format?:string):Promise<(File|string)> {
console.error('[arrayBufferToFile] 当前环境不支持')
}

View File

@ -0,0 +1,63 @@
// @ts-nocheck
import {platform} from '../platform'
/**
* buffer转路径
* @param {Object} buffer
*/
// @ts-nocheck
export function arrayBufferToFile(buffer: ArrayBuffer | Blob, name?: string, format?:string):Promise<(File|string)> {
return new Promise((resolve, reject) => {
// #ifdef MP
const fs = uni.getFileSystemManager()
//自定义文件名
if (!name && !format) {
reject(new Error('ERROR_NAME_PARSE'))
}
const fileName = `${name || new Date().getTime()}.${format.replace(/(.+)?\//,'')}`;
let pre = platform()
const filePath = `${pre.env.USER_DATA_PATH}/${fileName}`
fs.writeFile({
filePath,
data: buffer,
success() {
resolve(filePath)
},
fail(err) {
console.error(err)
reject(err)
}
})
// #endif
// #ifdef H5
const file = new File([buffer], name, {
type: format,
});
resolve(file)
// #endif
// #ifdef APP-PLUS
const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
const base64 = uni.arrayBufferToBase64(buffer)
bitmap.loadBase64Data(base64, () => {
if (!name && !format) {
reject(new Error('ERROR_NAME_PARSE'))
}
const fileNmae = `${name || new Date().getTime()}.${format.replace(/(.+)?\//,'')}`;
const filePath = `_doc/uniapp_temp/${fileNmae}`
bitmap.save(filePath, {},
() => {
bitmap.clear()
resolve(filePath)
},
(error) => {
bitmap.clear()
reject(error)
})
}, (error) => {
bitmap.clear()
reject(error)
})
// #endif
})
}

View File

@ -0,0 +1,13 @@
// @ts-nocheck
// 未完成
export function base64ToArrayBuffer(base64 : string) {
const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || [];
if (!format) {
new Error('ERROR_BASE64SRC_PARSE')
}
if(uni.base64ToArrayBuffer) {
return uni.base64ToArrayBuffer(bodyData)
} else {
}
}

View File

@ -0,0 +1,9 @@
// @ts-nocheck
// #ifndef APP-IOS || APP-ANDROID
export * from './vue.ts'
// #endif
// #ifdef APP-IOS || APP-ANDROID
export * from './uvue.uts'
// #endif

View File

@ -0,0 +1,22 @@
// @ts-nocheck
import { processFile, ProcessFileOptions } from '@/uni_modules/lime-file-utils'
/**
* base64转路径
* @param {Object} base64
*/
export function base64ToPath(base64: string, filename: string | null = null):Promise<string> {
return new Promise((resolve,reject) => {
processFile({
type: 'toDataURL',
path: base64,
filename,
success(res: string){
resolve(res)
},
fail(err){
reject(err)
}
} as ProcessFileOptions)
})
}

View File

@ -0,0 +1,75 @@
// @ts-nocheck
import {platform} from '../platform'
/**
* base64转路径
* @param {Object} base64
*/
export function base64ToPath(base64: string, filename?: string):Promise<string> {
const [, format] = /^data:image\/(\w+);base64,/.exec(base64) || [];
return new Promise((resolve, reject) => {
// #ifdef MP
const fs = uni.getFileSystemManager()
//自定义文件名
if (!filename && !format) {
reject(new Error('ERROR_BASE64SRC_PARSE'))
}
// const time = new Date().getTime();
const name = filename || `${new Date().getTime()}.${format}`;
let pre = platform()
const filePath = `${pre.env.USER_DATA_PATH}/${name}`
fs.writeFile({
filePath,
data: base64.split(',')[1],
encoding: 'base64',
success() {
resolve(filePath)
},
fail(err) {
console.error(err)
reject(err)
}
})
// #endif
// #ifdef H5
// mime类型
let mimeString = base64.split(',')[0].split(':')[1].split(';')[0];
//base64 解码
let byteString = atob(base64.split(',')[1]);
//创建缓冲数组
let arrayBuffer = new ArrayBuffer(byteString.length);
//创建视图
let intArray = new Uint8Array(arrayBuffer);
for (let i = 0; i < byteString.length; i++) {
intArray[i] = byteString.charCodeAt(i);
}
resolve(URL.createObjectURL(new Blob([intArray], {
type: mimeString
})))
// #endif
// #ifdef APP-PLUS
const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
bitmap.loadBase64Data(base64, () => {
if (!filename && !format) {
reject(new Error('ERROR_BASE64SRC_PARSE'))
}
// const time = new Date().getTime();
const name = filename || `${new Date().getTime()}.${format}`;
const filePath = `_doc/uniapp_temp/${name}`
bitmap.save(filePath, {},
() => {
bitmap.clear()
resolve(filePath)
},
(error) => {
bitmap.clear()
reject(error)
})
}, (error) => {
bitmap.clear()
reject(error)
})
// #endif
})
}

View File

@ -0,0 +1,21 @@
/**
* camelCase PascalCase
* @param str
* @param isPascalCase PascalCase false
* @returns
*/
export function camelCase(str: string, isPascalCase: boolean = false): string {
// 将字符串分割成单词数组
let words: string[] = str.split(/[\s_-]+/);
// 将数组中的每个单词首字母大写(除了第一个单词)
let camelCased: string[] = words.map((word, index):string => {
if (index == 0 && !isPascalCase) {
return word.toLowerCase(); // 第一个单词全小写
}
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
});
// 将数组中的单词拼接成一个字符串
return camelCased.join('');
};

View File

@ -0,0 +1,67 @@
// @ts-nocheck
// #ifndef APP-IOS || APP-ANDROID
// #ifdef MP-ALIPAY
interface My {
SDKVersion: string
}
declare var my: My
// #endif
function compareVersion(v1:string, v2:string) {
let a1 = v1.split('.');
let a2 = v2.split('.');
const len = Math.max(a1.length, a2.length);
while (a1.length < len) {
a1.push('0');
}
while (a2.length < len) {
a2.push('0');
}
for (let i = 0; i < len; i++) {
const num1 = parseInt(a1[i], 10);
const num2 = parseInt(a2[i], 10);
if (num1 > num2) {
return 1;
}
if (num1 < num2) {
return -1;
}
}
return 0;
}
function gte(version: string) {
let {SDKVersion} = uni.getSystemInfoSync();
// #ifdef MP-ALIPAY
SDKVersion = my.SDKVersion
// #endif
return compareVersion(SDKVersion, version) >= 0;
}
// #endif
/** 环境是否支持canvas 2d */
export function canIUseCanvas2d(): boolean {
// #ifdef MP-WEIXIN
return gte('2.9.0');
// #endif
// #ifdef MP-ALIPAY
return gte('2.7.0');
// #endif
// #ifdef MP-TOUTIAO
return gte('1.78.0');
// #endif
// #ifndef MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO
return false
// #endif
// #ifdef APP-IOS || APP-ANDROID || APP-NVUE || APP-VUE
return false;
// #endif
}

View File

@ -0,0 +1,36 @@
## 0.1.62024-07-24
- fix: vue2 app ts需要明确的后缀所有补全
- chore: 减少依赖
## 0.1.52024-07-21
- feat: 删除 Hooks
- feat: 兼容uniappx
## 0.1.42023-09-05
- feat: 增加 Hooks `useIntersectionObserver`
- feat: 增加 `floatAdd`
- feat: 因为本人插件兼容 vue2 需要使用 `composition-api`故增加vue文件代码插件的条件编译
## 0.1.32023-08-13
- feat: 增加 `camelCase`
## 0.1.22023-07-17
- feat: 增加 `getClassStr`
## 0.1.12023-07-06
- feat: 增加 `isNumeric` 区别于 `isNumber`
## 0.1.02023-06-30
- fix: `clamp`忘记导出了
## 0.0.92023-06-27
- feat: 增加`arrayBufferToFile`
## 0.0.82023-06-19
- feat: 增加`createAnimation`、`clamp`
## 0.0.72023-06-08
- chore: 更新注释
## 0.0.62023-06-08
- chore: 增加`createImage`为`lime-watermark`和`lime-qrcode`提供依赖
## 0.0.52023-06-03
- chore: 更新注释
## 0.0.42023-05-22
- feat: 增加`range`,`exif`,`selectComponent`
## 0.0.32023-05-08
- feat: 增加`fillZero`,`debounce`,`throttle`,`random`
## 0.0.22023-05-05
- chore: 更新文档
## 0.0.12023-05-05
- 无

View File

@ -0,0 +1,16 @@
// @ts-nocheck
/**
*
* @param val
* @param min
* @param max
* @returns
*/
export function clamp(val: number, min: number, max: number): number {
return Math.max(min, Math.min(max, val));
}
// console.log(clamp(5 ,0, 10)); // 输出: 5在范围内不做更改
// console.log(clamp(-5 ,0, 10)); // 输出: 0小于最小值被限制为最小值
// console.log(clamp(15 ,0, 10)); // 输出: 10大于最大值被限制为最大值

View File

@ -0,0 +1,10 @@
// @ts-nocheck
// #ifdef APP-IOS || APP-ANDROID
export * from './uvue.ts'
// #endif
// #ifndef APP-IOS || APP-ANDROID
export * from './vue.ts'
// #endif

View File

@ -0,0 +1,17 @@
// @ts-nocheck
/**
*
* @param obj
* @returns
*/
export function cloneDeep<T>(obj: any): T {
// 如果传入的对象是基本数据类型(如字符串、数字等),则直接返回
// if(['number', 'string'].includes(typeof obj) || Array.isArray(obj)){
// return obj as T
// }
if(typeof obj == 'object'){
return JSON.parse(JSON.stringify(obj as T)) as T;
}
return obj as T
}

View File

@ -0,0 +1,103 @@
// @ts-nocheck
/**
*
* @param obj
* @returns
*/
export function cloneDeep<T>(obj: any): T {
// 如果传入的对象为空,返回空
if (obj === null) {
return null as unknown as T;
}
// 如果传入的对象是 Set 类型,则将其转换为数组,并通过新的 Set 构造函数创建一个新的 Set 对象
if (obj instanceof Set) {
return new Set([...obj]) as unknown as T;
}
// 如果传入的对象是 Map 类型,则将其转换为数组,并通过新的 Map 构造函数创建一个新的 Map 对象
if (obj instanceof Map) {
return new Map([...obj]) as unknown as T;
}
// 如果传入的对象是 WeakMap 类型,则直接用传入的 WeakMap 对象进行赋值
if (obj instanceof WeakMap) {
let weakMap = new WeakMap();
weakMap = obj;
return weakMap as unknown as T;
}
// 如果传入的对象是 WeakSet 类型,则直接用传入的 WeakSet 对象进行赋值
if (obj instanceof WeakSet) {
let weakSet = new WeakSet();
weakSet = obj;
return weakSet as unknown as T;
}
// 如果传入的对象是 RegExp 类型,则通过新的 RegExp 构造函数创建一个新的 RegExp 对象
if (obj instanceof RegExp) {
return new RegExp(obj) as unknown as T;
}
// 如果传入的对象是 undefined 类型,则返回 undefined
if (typeof obj === 'undefined') {
return undefined as unknown as T;
}
// 如果传入的对象是数组,则递归调用 cloneDeep 函数对数组中的每个元素进行克隆
if (Array.isArray(obj)) {
return obj.map(cloneDeep) as unknown as T;
}
// 如果传入的对象是 Date 类型,则通过新的 Date 构造函数创建一个新的 Date 对象
if (obj instanceof Date) {
return new Date(obj.getTime()) as unknown as T;
}
// 如果传入的对象是普通对象,则使用递归调用 cloneDeep 函数对对象的每个属性进行克隆
if (typeof obj === 'object') {
const newObj: any = {};
for (const [key, value] of Object.entries(obj)) {
newObj[key] = cloneDeep(value);
}
const symbolKeys = Object.getOwnPropertySymbols(obj);
for (const key of symbolKeys) {
newObj[key] = cloneDeep(obj[key]);
}
return newObj;
}
// 如果传入的对象是基本数据类型(如字符串、数字等),则直接返回
return obj;
}
// 示例使用
// // 克隆一个对象
// const obj = { name: 'John', age: 30 };
// const clonedObj = cloneDeep(obj);
// console.log(clonedObj); // 输出: { name: 'John', age: 30 }
// console.log(clonedObj === obj); // 输出: false (副本与原对象是独立的)
// // 克隆一个数组
// const arr = [1, 2, 3];
// const clonedArr = cloneDeep(arr);
// console.log(clonedArr); // 输出: [1, 2, 3]
// console.log(clonedArr === arr); // 输出: false (副本与原数组是独立的)
// // 克隆一个包含嵌套对象的对象
// const person = {
// name: 'Alice',
// age: 25,
// address: {
// city: 'New York',
// country: 'USA',
// },
// };
// const clonedPerson = cloneDeep(person);
// console.log(clonedPerson); // 输出: { name: 'Alice', age: 25, address: { city: 'New York', country: 'USA' } }
// console.log(clonedPerson === person); // 输出: false (副本与原对象是独立的)
// console.log(clonedPerson.address === person.address); // 输出: false (嵌套对象的副本也是独立的)

View File

@ -0,0 +1,22 @@
// @ts-nocheck
/**
*
* @param arr
* @param target
* @returns
*/
export function closest(arr: number[], target: number):number {
return arr.reduce((pre: number, cur: number):number =>
Math.abs(pre - target) < Math.abs(cur - target) ? pre : cur
);
}
// 示例
// // 定义一个数字数组
// const numbers = [1, 3, 5, 7, 9];
// // 在数组中找到最接近目标数字 6 的元素
// const closestNumber = closest(numbers, 6);
// console.log(closestNumber); // 输出结果: 5

View File

@ -0,0 +1,139 @@
<template>
<view id="shared" style="height: 500px; width: 300px; background-color: aqua;">
</view>
</template>
<script lang="ts">
import { getRect, getAllRect } from '@/uni_modules/lime-shared/getRect'
import { camelCase } from '@/uni_modules/lime-shared/camelCase'
import { canIUseCanvas2d } from '@/uni_modules/lime-shared/canIUseCanvas2d'
import { clamp } from '@/uni_modules/lime-shared/clamp'
import { cloneDeep } from '@/uni_modules/lime-shared/cloneDeep'
import { closest } from '@/uni_modules/lime-shared/closest'
import { debounce } from '@/uni_modules/lime-shared/debounce'
import { fillZero } from '@/uni_modules/lime-shared/fillZero'
import { floatAdd } from '@/uni_modules/lime-shared/floatAdd'
import { getClassStr } from '@/uni_modules/lime-shared/getClassStr'
import { getCurrentPage } from '@/uni_modules/lime-shared/getCurrentPage'
import { getStyleStr } from '@/uni_modules/lime-shared/getStyleStr'
import { hasOwn } from '@/uni_modules/lime-shared/hasOwn'
import { isBase64 } from '@/uni_modules/lime-shared/isBase64'
import { isBrowser } from '@/uni_modules/lime-shared/isBrowser'
import { isDef } from '@/uni_modules/lime-shared/isDef'
import { isEmpty } from '@/uni_modules/lime-shared/isEmpty'
import { isFunction } from '@/uni_modules/lime-shared/isFunction'
import { isNumber } from '@/uni_modules/lime-shared/isNumber'
import { isNumeric } from '@/uni_modules/lime-shared/isNumeric'
import { isObject } from '@/uni_modules/lime-shared/isObject'
import { isPromise } from '@/uni_modules/lime-shared/isPromise'
import { isString } from '@/uni_modules/lime-shared/isString'
import { kebabCase } from '@/uni_modules/lime-shared/kebabCase'
import { raf, doubleRaf } from '@/uni_modules/lime-shared/raf'
import { random } from '@/uni_modules/lime-shared/random'
import { range } from '@/uni_modules/lime-shared/range'
import { sleep } from '@/uni_modules/lime-shared/sleep'
import { throttle } from '@/uni_modules/lime-shared/throttle'
import { toArray } from '@/uni_modules/lime-shared/toArray'
import { toBoolean } from '@/uni_modules/lime-shared/toBoolean'
import { toNumber } from '@/uni_modules/lime-shared/toNumber'
import { unitConvert } from '@/uni_modules/lime-shared/unitConvert'
import { getCurrentInstance } from '@/uni_modules/lime-shared/vue'
// #ifdef VUE2
type UTSJSONObject = any
// #endif
const context = getCurrentInstance()
getRect('#shared', context!).then(res =>{
console.log('res', res.bottom)
})
getAllRect('#shared', context).then(res =>{
console.log('res', res)
})
console.log('camelCase::', camelCase("hello world"));
console.log('camelCase::', camelCase("my_name_is_john", true));
console.log('canIUseCanvas2d::', canIUseCanvas2d());
console.log('clamp::', clamp(5 ,0, 10));
console.log('cloneDeep::', cloneDeep<UTSJSONObject>({a:5}));
console.log('closest::', closest([1, 3, 5, 7, 9], 6));
const saveData = (data: any) => {
//
console.log(`Saving data: ${data}`);
}
const debouncedSaveData = debounce(saveData, 500);
debouncedSaveData('Data 1');
debouncedSaveData('Data 2');
console.log('fillZero', fillZero(1))
console.log('floatAdd', floatAdd(0.1, 0.2))
console.log('getClassStr', getClassStr({hover: true}))
console.log('getStyleStr', getStyleStr({ color: 'red', fontSize: '16px', backgroundColor: '', border: null }))
console.log('hasOwn', hasOwn({a: true}, 'key'))
console.log('isBase64::', isBase64("SGVsbG8sIFdvcmxkIQ=="));
console.log('isBrowser::', isBrowser);
console.log('isDef::', isDef('6'));
console.log('isEmpty::', isEmpty({a: true}));
const b = () =>{}
console.log('isFunction::', isFunction(b));
console.log('isNumber::', isNumber('6'));
console.log('isNumeric::', isNumeric('6'));
console.log('isObject::', isObject({}));
const promise = ():Promise<boolean> => {
return new Promise((resolve) => {
resolve(true)
})
}
const a = promise()
console.log('isPromise::', isPromise(a));
console.log('isString::', isString('null'));
console.log('kebabCase::', kebabCase('my love'));
console.log('raf::', raf(()=>{
console.log('raf:::1')
}));
console.log('doubleRaf::', doubleRaf(()=>{
console.log('doubleRaf:::1')
}));
console.log('random', random(0, 10))
console.log('random', random(0, 1, 2))
console.log('range', range(0, 10, 2))
console.log('sleep', sleep(300).then(res => {
console.log('log')
}))
const handleScroll = (a: string) => {
console.log("Scroll event handled!", a);
}
// // 使 handleScroll 500
const throttledScroll = throttle(handleScroll, 500);
throttledScroll('5');
const page = getCurrentPage()
console.log('getCurrentPage::', page)
console.log('toArray', toArray<number>(5))
console.log('toBoolean', toBoolean(5))
console.log('toNumber', toNumber('5'))
console.log('unitConvert', unitConvert('5'))
// uni.getImageInfo({
// src: '/static/logo.png',
// success(res) {
// console.log('res', res)
// }
// })
</script>
<style>
</style>

View File

@ -0,0 +1,9 @@
// @ts-nocheck
// #ifndef UNI-APP-X
export * from './type.ts'
export * from './vue.ts'
// #endif
// #ifdef UNI-APP-X
export * from './uvue.ts'
// #endif

View File

@ -0,0 +1,25 @@
export type CreateAnimationOptions = {
/**
* ms
*/
duration ?: number;
/**
*
* - linear: 动画从头到尾的速度是相同的
* - ease: 动画以低速开始
* - ease-in:
* - ease-in-out: 动画以低速开始和结束
* - ease-out: 动画以低速结束
* - step-start: 动画第一帧就跳至结束状态直到结束
* - step-end: 动画一直保持开始状态
*/
timingFunction ?: string //'linear' | 'ease' | 'ease-in' | 'ease-in-out' | 'ease-out' | 'step-start' | 'step-end';
/**
* ms
*/
delay ?: number;
/**
* transform-origin
*/
transformOrigin ?: string;
}

View File

@ -0,0 +1,5 @@
// @ts-nocheck
// export * from '@/uni_modules/lime-animateIt'
export function createAnimation() {
console.error('当前环境不支持,请使用lime-animateIt')
}

View File

@ -0,0 +1,148 @@
// @ts-nocheck
// nvue 需要在节点上设置ref或在export里传入
// const animation = createAnimation({
// ref: this.$refs['xxx'],
// duration: 0,
// timingFunction: 'linear'
// })
// animation.opacity(1).translate(x, y).step({duration})
// animation.export(ref)
// 抹平nvue 与 uni.createAnimation的使用差距
// 但是nvue动画太慢
import { CreateAnimationOptions } from './type'
// #ifdef APP-NVUE
const nvueAnimation = uni.requireNativePlugin('animation')
type AnimationTypes = 'matrix' | 'matrix3d' | 'rotate' | 'rotate3d' | 'rotateX' | 'rotateY' | 'rotateZ' | 'scale' | 'scale3d' | 'scaleX' | 'scaleY' | 'scaleZ' | 'skew' | 'skewX' | 'skewY' | 'translate' | 'translate3d' | 'translateX' | 'translateY' | 'translateZ'
| 'opacity' | 'backgroundColor' | 'width' | 'height' | 'left' | 'right' | 'top' | 'bottom'
interface Styles {
[key : string] : any
}
interface StepConfig {
duration?: number
timingFunction?: string
delay?: number
needLayout?: boolean
transformOrigin?: string
}
interface StepAnimate {
styles?: Styles
config?: StepConfig
}
interface StepAnimates {
[key: number]: StepAnimate
}
// export interface CreateAnimationOptions extends UniApp.CreateAnimationOptions {
// ref?: string
// }
type Callback = (time: number) => void
const animateTypes1 : AnimationTypes[] = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
'translateZ'
]
const animateTypes2 : AnimationTypes[] = ['opacity', 'backgroundColor']
const animateTypes3 : AnimationTypes[] = ['width', 'height', 'left', 'right', 'top', 'bottom']
class LimeAnimation {
ref : any
context : any
options : UniApp.CreateAnimationOptions
// stack : any[] = []
next : number = 0
currentStepAnimates : StepAnimates = {}
duration : number = 0
constructor(options : CreateAnimationOptions) {
const {ref} = options
this.ref = ref
this.options = options
}
addAnimate(type : AnimationTypes, args: (string | number)[]) {
let aniObj = this.currentStepAnimates[this.next]
let stepAnimate:StepAnimate = {}
if (!aniObj) {
stepAnimate = {styles: {}, config: {}}
} else {
stepAnimate = aniObj
}
if (animateTypes1.includes(type)) {
if (!stepAnimate.styles.transform) {
stepAnimate.styles.transform = ''
}
let unit = ''
if (type === 'rotate') {
unit = 'deg'
}
stepAnimate.styles.transform += `${type}(${args.map((v: number) => v + unit).join(',')}) `
} else {
stepAnimate.styles[type] = `${args.join(',')}`
}
this.currentStepAnimates[this.next] = stepAnimate
}
animateRun(styles: Styles = {}, config:StepConfig = {}, ref: any) {
const el = ref || this.ref
if (!el) return
return new Promise((resolve) => {
const time = +new Date()
nvueAnimation.transition(el, {
styles,
...config
}, () => {
resolve(+new Date() - time)
})
})
}
nextAnimate(animates: StepAnimates, step: number = 0, ref: any, cb: Callback) {
let obj = animates[step]
if (obj) {
let { styles, config } = obj
// this.duration += config.duration
this.animateRun(styles, config, ref).then((time: number) => {
step += 1
this.duration += time
this.nextAnimate(animates, step, ref, cb)
})
} else {
this.currentStepAnimates = {}
cb && cb(this.duration)
}
}
step(config:StepConfig = {}) {
this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
this.next++
return this
}
export(ref: any, cb?: Callback) {
ref = ref || this.ref
if(!ref) return
this.duration = 0
this.next = 0
this.nextAnimate(this.currentStepAnimates, 0, ref, cb)
return null
}
}
animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
LimeAnimation.prototype[type] = function(...args: (string | number)[]) {
this.addAnimate(type, args)
return this
}
})
// #endif
export function createAnimation(options : CreateAnimationOptions) {
// #ifndef APP-NVUE
return uni.createAnimation({ ...options })
// #endif
// #ifdef APP-NVUE
return new LimeAnimation(options)
// #endif
}

View File

@ -0,0 +1,70 @@
// @ts-nocheck
// #ifndef APP-IOS || APP-ANDROID
import {isBrowser} from '../isBrowser'
class Image {
currentSrc: string | null = null
naturalHeight: number = 0
naturalWidth: number = 0
width: number = 0
height: number = 0
tagName: string = 'IMG'
path: string = ''
crossOrigin: string = ''
referrerPolicy: string = ''
onload: () => void = () => {}
onerror: () => void = () => {}
complete: boolean = false
constructor() {}
set src(src: string) {
console.log('src', src)
if(!src) {
return this.onerror()
}
src = src.replace(/^@\//,'/')
this.currentSrc = src
uni.getImageInfo({
src,
success: (res) => {
const localReg = /^\.|^\/(?=[^\/])/;
// #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-TOUTIAO
res.path = localReg.test(src) ? `/${res.path}` : res.path;
// #endif
this.complete = true
this.path = res.path
this.naturalWidth = this.width = res.width
this.naturalHeight = this.height = res.height
this.onload()
},
fail: () => {
this.onerror()
}
})
}
get src() {
return this.currentSrc
}
}
interface UniImage extends WechatMiniprogram.Image {
complete?: boolean
naturalHeight?: number
naturalWidth?: number
}
/** 创建用于 canvas 的 img */
export function createImage(canvas?: any): HTMLImageElement | UniImage {
if(canvas && canvas.createImage) {
return (canvas as WechatMiniprogram.Canvas).createImage()
} else if(this && this['tagName'] == 'canvas' && !('toBlob' in this) || canvas && !('toBlob' in canvas)){
return new Image()
} else if(isBrowser) {
return new window.Image()
}
return new Image()
}
// #endif
// #ifdef APP-IOS || APP-ANDROID
export function createImage(){
console.error('当前环境不支持')
}
// #endif

View File

@ -0,0 +1,10 @@
// @ts-nocheck
// #ifdef APP-IOS || APP-ANDROID
export * from './uvue.ts'
// #endif
// #ifndef APP-IOS || APP-ANDROID
export * from './vue.ts'
// #endif

View File

@ -0,0 +1,36 @@
// @ts-nocheck
/**
*
* @param fn
* @param wait
* @returns
*/
export function debounce<A extends any>(fn : (args: A)=> void, wait = 300): (args: A)=> void {
let timer = -1
return (args: A) => {
if (timer >-1) {clearTimeout(timer)};
timer = setTimeout(()=>{
fn(args)
}, wait)
}
};
// 示例
// 定义一个函数
// function saveData(data: string) {
// // 模拟保存数据的操作
// console.log(`Saving data: ${data}`);
// }
// // 创建一个防抖函数,延迟 500 毫秒后调用 saveData 函数
// const debouncedSaveData = debounce(saveData, 500);
// // 连续调用防抖函数
// debouncedSaveData('Data 1'); // 不会立即调用 saveData 函数
// debouncedSaveData('Data 2'); // 不会立即调用 saveData 函数
// 在 500 毫秒后,只会调用一次 saveData 函数,输出结果为 "Saving data: Data 2"

View File

@ -0,0 +1,40 @@
// @ts-nocheck
type Timeout = ReturnType<typeof setTimeout> | null;
/**
*
* @param fn
* @param wait
* @returns
*/
export function debounce<A extends any, R>(
fn : (...args : A) => R,
wait : number = 300) : (...args : A) => void {
let timer : Timeout = null;
return function (...args : A) {
if (timer) clearTimeout(timer); // 如果上一个 setTimeout 存在,则清除它
// 设置一个新的 setTimeout在指定的等待时间后调用防抖函数
timer = setTimeout(() => {
fn.apply(this, args); // 使用提供的参数调用原始函数
}, wait);
};
};
// 示例
// 定义一个函数
// function saveData(data: string) {
// // 模拟保存数据的操作
// console.log(`Saving data: ${data}`);
// }
// // 创建一个防抖函数,延迟 500 毫秒后调用 saveData 函数
// const debouncedSaveData = debounce(saveData, 500);
// // 连续调用防抖函数
// debouncedSaveData('Data 1'); // 不会立即调用 saveData 函数
// debouncedSaveData('Data 2'); // 不会立即调用 saveData 函数
// 在 500 毫秒后,只会调用一次 saveData 函数,输出结果为 "Saving data: Data 2"

View File

@ -0,0 +1,9 @@
// @ts-nocheck
// #ifndef APP-IOS || APP-ANDROID
export * from './vue.ts'
// #endif
// #ifdef APP-IOS || APP-ANDROID
export * from './uvue.ts'
// #endif

View File

@ -0,0 +1,7 @@
class EXIF {
constructor(){
console.error('当前环境不支持')
}
}
export const exif = new EXIF()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
// @ts-nocheck
/**
*
* @param number
* @param length 2
* @returns
*/
export function fillZero(number: number, length: number = 2): string {
// 将数字转换为字符串,然后使用 padStart 方法填充零到指定长度
return `${number}`.padStart(length, '0');
}

View File

@ -0,0 +1,36 @@
import {isNumber} from '../isNumber'
/**
*
* @param num1
* @param num2
* @returns
*/
export function floatAdd(num1: number, num2: number): number {
// 检查 num1 和 num2 是否为数字类型
if (!(isNumber(num1) || isNumber(num2))) {
console.warn('Please pass in the number type');
return NaN;
}
let r1: number, r2: number, m: number;
try {
// 获取 num1 小数点后的位数
r1 = num1.toString().split('.')[1].length;
} catch (error) {
r1 = 0;
}
try {
// 获取 num2 小数点后的位数
r2 = num2.toString().split('.')[1].length;
} catch (error) {
r2 = 0;
}
// 计算需要扩大的倍数
m = Math.pow(10, Math.max(r1, r2));
// 返回相加结果
return (num1 * m + num2 * m) / m;
}

View File

@ -0,0 +1,53 @@
// @ts-nocheck
// #ifdef APP-IOS || APP-ANDROID
import { isNumber } from '../isNumber'
import { isString } from '../isString'
import { isDef } from '../isDef'
// #endif
/**
*
* @param obj -
* @returns
*/
export function getClassStr<T>(obj : T) : string {
let classNames : string[] = [];
// #ifdef APP-IOS || APP-ANDROID
if (obj instanceof UTSJSONObject) {
(obj as UTSJSONObject).toMap().forEach((value, key) => {
if (isDef(value)) {
if (isNumber(value)) {
classNames.push(key);
}
if (isString(value) && value !== '') {
classNames.push(key);
}
if (typeof value == 'boolean' && (value as boolean)) {
classNames.push(key);
}
}
})
}
// #endif
// #ifndef APP-IOS || APP-ANDROID
// 遍历对象的属性
for (let key in obj) {
// 检查属性确实属于对象自身且其值为true
if ((obj as any).hasOwnProperty(key) && obj[key]) {
// 将属性名添加到类名数组中
classNames.push(key);
}
}
// #endif
// 将类名数组用空格连接成字符串并返回
return classNames.join(' ');
}
// 示例
// const obj = { foo: true, bar: false, baz: true };
// const classNameStr = getClassStr(obj);
// console.log(classNameStr); // 输出: "foo baz"

View File

@ -0,0 +1,9 @@
// @ts-nocheck
// #ifndef APP-IOS || APP-ANDROID
export * from './vue.ts'
// #endif
// #ifdef APP-IOS || APP-ANDROID
export * from './uvue.uts'
// #endif

View File

@ -0,0 +1,5 @@
// @ts-nocheck
export const getCurrentPage = ():Page => {
const pages = getCurrentPages();
return pages[pages.length - 1]
};

View File

@ -0,0 +1,6 @@
// @ts-nocheck
/** 获取当前页 */
export const getCurrentPage = () => {
const pages = getCurrentPages();
return pages[pages.length - 1] //as T & WechatMiniprogram.Page.TrivialInstance;
};

View File

@ -0,0 +1,62 @@
// @ts-nocheck
// #ifdef APP-NVUE || APP-VUE
export const getLocalFilePath = (path : string) => {
if (typeof plus == 'undefined') return path
if (/^(_www|_doc|_documents|_downloads|file:\/\/|\/storage\/emulated\/0\/)/.test(path)) return path
if (/^\//.test(path)) {
const localFilePath = plus.io.convertAbsoluteFileSystem(path)
if (localFilePath !== path) {
return localFilePath
} else {
path = path.slice(1)
}
}
return '_www/' + path
}
// #endif
// #ifdef APP-ANDROID || APP-IOS
export { getResourcePath as getLocalFilePath } from '@/uni_modules/lime-file-utils'
// export const getLocalFilePath = (path : string) : string => {
// let uri = path
// if (uri.startsWith("http") || uri.startsWith("<svg") || uri.startsWith("data:image/svg+xml")) {
// return uri
// }
// if (uri.startsWith("file://")) {
// uri = uri.substring("file://".length)
// } else if (uri.startsWith("unifile://")) {
// uri = UTSAndroid.convert2AbsFullPath(uri)
// } else {
// uri = UTSAndroid.convert2AbsFullPath(uri)
// if (uri.startsWith("/android_asset/")) {
// uri = uri.replace("/android_asset/", "")
// }
// }
// if (new File(uri).exists()) {
// return uri
// } else {
// return null
// }
// // return UTSAndroid.convert2AbsFullPath(path)
// }
// #endif
// #ifdef APP-IOS
// export const getLocalFilePath = (path : string) : string => {
// try {
// let uri = path
// if (uri.startsWith("http") || uri.startsWith("<svg") || uri.startsWith("data:image/svg+xml")) {
// return uri
// }
// if (uri.startsWith("file://")) {
// return uri.substring("file://".length)
// } else if (path.startsWith("/var/")) {
// return path
// }
// return UTSiOS.getResourcePath(path)
// } catch (e) {
// return null
// }
// // return UTSiOS.getResourcePath(path)
// }
// #endif

View File

@ -0,0 +1,9 @@
// @ts-nocheck
// #ifdef APP-IOS || APP-ANDROID
export * from './uvue.uts'
// #endif
// #ifndef APP-IOS || APP-ANDROID
export * from './vue.ts'
// #endif

View File

@ -0,0 +1,16 @@
// @ts-nocheck
export function getRect(selector : string, context: ComponentInternalInstance):Promise<NodeInfo> {
return new Promise((resolve)=>{
uni.createSelectorQuery().in(context).select(selector).boundingClientRect(res =>{
resolve(res as NodeInfo)
}).exec();
})
}
export function getAllRect(selector : string, context: ComponentInternalInstance):Promise<NodeInfo[]> {
return new Promise((resolve)=>{
uni.createSelectorQuery().in(context).selectAll(selector).boundingClientRect(res =>{
resolve(res as NodeInfo[])
}).exec();
})
}

View File

@ -0,0 +1,117 @@
// @ts-nocheck
// #ifdef APP-NVUE
// 当编译环境是 APP-NVUE 时,引入 uni.requireNativePlugin('dom'),具体插件用途未知
const dom = uni.requireNativePlugin('dom')
// #endif
/**
*
* @param selector
* @param context ComponentInternalInstance
* @param node node
* @returns Promise
*/
export function getRect(selector : string, context : ComponentInternalInstance, node: boolean = false) {
// 之前是个对象,现在改成实例,防止旧版会报错
if(context== null) {
return Promise.reject('context is null')
}
if(context.context){
context = context.context
}
// #ifdef MP || VUE2
if (context.proxy) context = context.proxy
// #endif
return new Promise<UniNamespace.NodeInfo>((resolve, reject) => {
// #ifndef APP-NVUE
const dom = uni.createSelectorQuery().in(context).select(selector);
const result = (rect: UniNamespace.NodeInfo) => {
if (rect) {
resolve(rect)
} else {
reject('no rect')
}
}
if (!node) {
dom.boundingClientRect(result).exec()
} else {
dom.fields({
node: true,
size: true,
rect: true
}, result).exec()
}
// #endif
// #ifdef APP-NVUE
let { context } = options
if (/#|\./.test(selector) && context.refs) {
selector = selector.replace(/#|\./, '')
if (context.refs[selector]) {
selector = context.refs[selector]
if(Array.isArray(selector)) {
selector = selector[0]
}
}
}
dom.getComponentRect(selector, (res) => {
if (res.size) {
resolve(res.size)
} else {
reject('no rect')
}
})
// #endif
});
};
export function getAllRect(selector : string, context: ComponentInternalInstance, node:boolean = false) {
if(context== null) {
return Promise.reject('context is null')
}
// #ifdef MP || VUE2
if (context.proxy) context = context.proxy
// #endif
return new Promise<UniNamespace.NodeInfo>((resolve, reject) => {
// #ifndef APP-NVUE
const dom = uni.createSelectorQuery().in(context).selectAll(selector);
const result = (rect: UniNamespace.NodeInfo[]) => {
if (rect) {
resolve(rect)
} else {
reject('no rect')
}
}
if (!node) {
dom.boundingClientRect(result).exec()
} else {
dom.fields({
node: true,
size: true,
rect: true
}, result).exec()
}
// #endif
// #ifdef APP-NVUE
let { context } = options
if (/#|\./.test(selector) && context.refs) {
selector = selector.replace(/#|\./, '')
if (context.refs[selector]) {
selector = context.refs[selector]
if(Array.isArray(selector)) {
selector = selector[0]
}
}
}
dom.getComponentRect(selector, (res) => {
if (res.size) {
resolve([res.size])
} else {
reject('no rect')
}
})
// #endif
});
};

View File

@ -0,0 +1,54 @@
// @ts-nocheck
// #ifndef APP-IOS || APP-ANDROID
interface CSSProperties {
[key : string] : string | number | null
}
// #endif
// #ifdef VUE3
// #ifdef APP-IOS || APP-ANDROID
type CSSProperties = UTSJSONObject
// #endif
// #endif
/**
*
* @param key -
* @returns
*/
export function toLowercaseSeparator(key : string):string {
return key.replace(/([A-Z])/g, '-$1').toLowerCase();
}
/**
*
* @param style - CSS样式对象
* @returns
*/
export function getStyleStr(style : CSSProperties) : string {
// #ifdef APP-IOS || APP-ANDROID
let styleStr = '';
style.toMap().forEach((value, key) => {
if(value !== null && value != '') {
styleStr += `${toLowercaseSeparator(key as string)}: ${value};`
}
})
return styleStr
// #endif
// #ifndef APP-IOS || APP-ANDROID
return Object.keys(style)
.filter(
(key) =>
style[key] !== undefined &&
style[key] !== null &&
style[key] !== '')
.map((key : string) => `${toLowercaseSeparator(key)}: ${style[key]};`)
.join(' ');
// #endif
}
// 示例
// const style = { color: 'red', fontSize: '16px', backgroundColor: '', border: null };
// const styleStr = getStyleStr(style);
// console.log(styleStr);
// 输出: "color: red; font-size: 16px;"

View File

@ -0,0 +1,39 @@
// @ts-nocheck
// #ifndef UNI-APP-X
interface CSSProperties {
[key : string] : string | number
}
// #endif
// #ifdef UNI-APP-X
type CSSProperties = UTSJSONObject
// #endif
/**
* 将字符串转换为带有连字符分隔的小写形式
* @param key - 要转换的字符串
* @returns 转换后的字符串
*/
export function toLowercaseSeparator(key : string) : string {
return key.replace(/([A-Z])/g, '-$1').toLowerCase();
}
/**
* 获取样式对象对应的样式字符串
* @param style - CSS样式对象
* @returns 由非空有效样式属性键值对组成的字符串
*/
export function getStyleStr(style : CSSProperties) : string {
let styleStr = '';
style.toMap().forEach((value, key) => {
if(value !== null && value != '') {
styleStr += `${toLowercaseSeparator(key as string)}: ${value};`
}
})
return styleStr
}
// 示例
// const style = { color: 'red', fontSize: '16px', backgroundColor: '', border: null };
// const styleStr = getStyleStr(style);
// console.log(styleStr);
// 输出: "color: red; font-size: 16px;"

View File

@ -0,0 +1,9 @@
// @ts-nocheck
// #ifdef APP-IOS || APP-ANDROID
export * from './uvue.ts'
// #endif
// #ifndef APP-IOS || APP-ANDROID
export * from './vue.ts'
// #endif

View File

@ -0,0 +1,39 @@
// @ts-nocheck
/**
*
* @param obj
* @param key
* @returns truefalse
*/
function hasOwn(obj: UTSJSONObject, key: string): boolean
function hasOwn(obj: Map<string, unknown>, key: string): boolean
function hasOwn(obj: any, key: string): boolean {
if(obj instanceof UTSJSONObject){
return obj[key] != null
}
if(obj instanceof Map<string, unknown>){
return (obj as Map<string, unknown>).has(key)
}
return false
}
export {
hasOwn
}
// 示例
// const obj = { name: 'John', age: 30 };
// if (hasOwn(obj, 'name')) {
// console.log("对象具有 'name' 属性");
// } else {
// console.log("对象不具有 'name' 属性");
// }
// // 输出: 对象具有 'name' 属性
// const arr = [1, 2, 3];
// if (hasOwn(arr, 'length')) {
// console.log("数组具有 'length' 属性");
// } else {
// console.log("数组不具有 'length' 属性");
// }
// 输出: 数组具有 'length' 属性

View File

@ -0,0 +1,30 @@
// @ts-nocheck
const hasOwnProperty = Object.prototype.hasOwnProperty
/**
*
* @param obj
* @param key
* @returns truefalse
*/
export function hasOwn(obj: Object | Array<any>, key: string): boolean {
return hasOwnProperty.call(obj, key);
}
// 示例
// const obj = { name: 'John', age: 30 };
// if (hasOwn(obj, 'name')) {
// console.log("对象具有 'name' 属性");
// } else {
// console.log("对象不具有 'name' 属性");
// }
// // 输出: 对象具有 'name' 属性
// const arr = [1, 2, 3];
// if (hasOwn(arr, 'length')) {
// console.log("数组具有 'length' 属性");
// } else {
// console.log("数组不具有 'length' 属性");
// }
// 输出: 数组具有 'length' 属性

View File

@ -0,0 +1,43 @@
// @ts-nocheck
// validator
// export * from './isString'
// export * from './isNumber'
// export * from './isNumeric'
// export * from './isDef'
// export * from './isFunction'
// export * from './isObject'
// export * from './isPromise'
// export * from './isBase64'
// export * from './hasOwn'
// // 单位转换
// export * from './addUnit'
// export * from './unitConvert'
// export * from './toNumber'
// export * from './random'
// export * from './range'
// export * from './fillZero'
// // image
// export * from './base64ToPath'
// export * from './pathToBase64'
// export * from './exif'
// // canvas
// export * from './canIUseCanvas2d'
// // page
// export * from './getCurrentPage'
// // dom
// export * from './getRect'
// export * from './selectComponent'
// export * from './createAnimation'
// // delay
// export * from './sleep'
// export * from './debounce'
// export * from './throttle'

View File

@ -0,0 +1,23 @@
// @ts-nocheck
/**
* Base64编码
* Base64编码的字符串只包含A-Za-z0-9+/ =
* @param {string} str -
* @returns {boolean} Base64编码truefalse
*/
export function isBase64(str: string): boolean {
const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
return base64Regex.test(str);
}
/**
* Base64编码的data URI
* Base64编码的data URI通常以"data:"MIME类型和编码信息Base64编码的数据
* @param {string} str -
* @returns {boolean} Base64编码的data URItruefalse
*/
export function isBase64DataUri(str: string): boolean {
const dataUriRegex = /^data:([a-zA-Z]+\/[a-zA-Z0-9-+.]+)(;base64)?,([a-zA-Z0-9+/]+={0,2})$/;
return dataUriRegex.test(str);
}

View File

@ -0,0 +1,8 @@
// @ts-nocheck
// #ifdef WEB
export const isBrowser = typeof window !== 'undefined';
// #endif
// #ifndef WEB
export const isBrowser = false;
// #endif

View File

@ -0,0 +1,23 @@
// @ts-nocheck
/**
* undefined null
* @param value
* @returns null true false
*/
// #ifndef UNI-APP-X
export function isDef(value: unknown): boolean {
return value !== undefined && value !== null;
}
// #endif
// #ifdef UNI-APP-X
export function isDef(value : any|null) : boolean {
// #ifdef APP-ANDROID || APP-IOS
return value != null;
// #endif
// #ifndef APP-ANDROID || APP-IOS
return value != null && value != undefined;
// #endif
}
// #endif

View File

@ -0,0 +1,83 @@
// @ts-nocheck
import {isDef} from '../isDef'
import {isString} from '../isString'
import {isNumber} from '../isNumber'
/**
*
*
* 0
* 0
* 0
* null或undefinedtrue
*
*
* @param {any} value -
* @returns {boolean} truefalse
*/
// #ifdef APP-IOS || APP-ANDROID
export function isEmpty(value : any | null) : boolean {
// 为null
if(!isDef(value)){
return true
}
// 为空字符
if(isString(value)){
return value.toString().trim().length == 0
}
// 为数值
if(isNumber(value)){
return false
}
if(typeof value == 'object'){
// 数组
if(Array.isArray(value)){
return (value as Array<unknown>).length == 0
}
// Map
if(value instanceof Map<unknown, unknown>) {
return value.size == 0
}
// Set
if(value instanceof Set<unknown>) {
return value.size == 0
}
if(value instanceof UTSJSONObject) {
return value.toMap().size == 0
}
return JSON.stringify(value) == '{}'
}
return true
}
// #endif
// #ifndef APP-IOS || APP-ANDROID
export function isEmpty(value: any): boolean {
// 检查是否为null或undefined
if (value == null) {
return true;
}
// 检查字符串是否为空
if (typeof value === 'string') {
return value.trim().length === 0;
}
// 检查数组是否为空
if (Array.isArray(value)) {
return value.length === 0;
}
// 检查对象是否为空
if (typeof value === 'object') {
return Object.keys(value).length === 0;
}
// 其他类型(如数字、布尔值等)不为空
return false;
}
// #endif

View File

@ -0,0 +1,16 @@
// @ts-nocheck
/**
*
* @param val
* @returns true false
*/
// #ifdef APP-IOS || APP-ANDROID
export const isFunction = (val: any):boolean => typeof val == 'function';
// #endif
// #ifndef APP-IOS || APP-ANDROID
export const isFunction = (val: unknown): val is Function =>
typeof val === 'function';
// #endif

View File

@ -0,0 +1,26 @@
// @ts-nocheck
/**
*
* @param value number string
* @returns NaN true false
*/
// #ifndef UNI-APP-X
export function isNumber(value: number | string | null): boolean {
return typeof value === 'number' && !isNaN(value);
}
// #endif
// #ifdef UNI-APP-X
export function isNumber(value: any|null): boolean {
// #ifdef APP-ANDROID
return ['Byte', 'UByte','Short','UShort','Int','UInt','Long','ULong','Float','Double','number'].includes(typeof value)
// #endif
// #ifdef APP-IOS
return ['Int8', 'UInt8','Int16','UInt16','Int32','UInt32','Int64','UInt64','Int','UInt','Float','Float16','Float32','Float64','Double', 'number'].includes(typeof value)
// #endif
// #ifndef APP-ANDROID || APP-IOS
return typeof value === 'number' && !isNaN(value);
// #endif
}
// #endif

View File

@ -0,0 +1,33 @@
// @ts-nocheck
/**
*
* @param value string number
* @returns true false
*/
// #ifndef APP-IOS || APP-ANDROID
export function isNumeric(value: string | number | undefined | null): boolean {
return /^(-)?\d+(\.\d+)?$/.test(value);
}
// #endif
// #ifdef APP-IOS || APP-ANDROID
import {isNumber} from '../isNumber';
import {isString} from '../isString';
export function isNumeric(value : any|null) : boolean {
if(value == null) {
return false
}
if(isNumber(value)) {
return true
} else if(isString(value)) {
// const regex = "-?\\d+(\\.\\d+)?".toRegex()
const regex = new RegExp("^(-)?\\d+(\\.\\d+)?$")
return regex.test(value as string) //regex.matches(value as string)
}
return false
// return /^(-)?\d+(\.\d+)?$/.test(value);
}
// #endif

View File

@ -0,0 +1,19 @@
// @ts-nocheck
/**
*
* @param val
* @returns true false
*/
// #ifndef APP-IOS || APP-ANDROID
export const isObject = (val : unknown) : val is Record<any, any> =>
val !== null && typeof val === 'object';
// #endif
// #ifdef APP-IOS || APP-ANDROID
export const isObject = (val : any | null) : boolean =>{
return val !== null && typeof val === 'object';
}
// #endif

View File

@ -0,0 +1,22 @@
// @ts-nocheck
import {isFunction} from '../isFunction'
import {isObject} from '../isObject'
/**
* Promise
* @param val
* @returns Promise true false
*/
// #ifndef APP-ANDROID
export const isPromise = <T = any>(val: unknown): val is Promise<T> => {
// 使用 isObject 函数判断值是否为对象类型
// 使用 isFunction 函数判断值是否具有 then 方法和 catch 方法
return isObject(val) && isFunction(val.then) && isFunction(val.catch);
};
// #endif
// #ifdef APP-ANDROID
export const isPromise = (val: any): boolean => {
return val instanceof Promise<unknown>
};
// #endif

View File

@ -0,0 +1,19 @@
// @ts-nocheck
/**
*
* @param str
* @returns true false
*/
// #ifndef APP-IOS || APP-ANDROID
// export const isString = (str: unknown): str is string => typeof str === 'string';
export function isString (str: unknown): str is string {
return typeof str == 'string'
}
// #endif
// #ifdef APP-IOS || APP-ANDROID
export function isString (str: any): boolean {
return typeof str == 'string'
}
// #endif

View File

@ -0,0 +1,24 @@
// @ts-nocheck
// export function toLowercaseSeparator(key: string) {
// return key.replace(/([A-Z])/g, '-$1').toLowerCase();
// }
/**
*
* @param str
* @param separator "-"
* @returns
*/
export function kebabCase(str : string, separator : string = "-") : string {
return str
// #ifdef APP-IOS || APP-ANDROID
.replace(/[A-Z]/g, (match : string, _ : number, _ : string) : string => `${separator}${match.toLowerCase()}`) // 将大写字母替换为连接符加小写字母
// #endif
// #ifndef APP-IOS || APP-ANDROID
.replace(/[A-Z]/g, (match : string) : string => `${separator}${match.toLowerCase()}`) // 将大写字母替换为连接符加小写字母
// #endif
.replace(/[\s_-]+/g, separator) // 将空格、下划线和短横线替换为指定连接符
.replace(new RegExp(`^${separator}|${separator}$`, "g"), "") // 删除开头和结尾的连接符
.toLowerCase(); // 将结果转换为全小写
}

View File

@ -0,0 +1,86 @@
{
"id": "lime-shared",
"displayName": "lime-shared",
"version": "0.1.6",
"description": "本人插件的几个公共函数获取当前页图片的base64转临时路径图片的exif信息等",
"keywords": [
"lime-shared",
"exif"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"type": "sdk-js",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "n"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-uvue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y",
"钉钉": "y",
"快手": "y",
"飞书": "y",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@ -0,0 +1,9 @@
// @ts-nocheck
// #ifndef APP-IOS || APP-ANDROID
export * from './vue.ts'
// #endif
// #ifdef APP-IOS || APP-ANDROID
export * from './uvue.uts'
// #endif

View File

@ -0,0 +1,17 @@
// @ts-nocheck
// import { processFile, ProcessFileOptions } from '@/uni_modules/lime-file-utils'
export function pathToBase64(path : string) : Promise<string> {
console.error('pathToBase64: 当前环境不支持,请使用 【lime-file-utils】')
// return new Promise((resolve, reject) => {
// processFile({
// type: 'toDataURL',
// path,
// success(res : string) {
// resolve(res)
// },
// fail(err: any){
// reject(err)
// }
// } as ProcessFileOptions)
// })
}

View File

@ -0,0 +1,121 @@
// @ts-nocheck
// #ifdef APP-PLUS
import { getLocalFilePath } from '../getLocalFilePath'
// #endif
function isImage(extension : string) {
const imageExtensions = ["jpg", "jpeg", "png", "gif", "bmp", "svg"];
return imageExtensions.includes(extension.toLowerCase());
}
// #ifdef H5
function getSVGFromURL(url: string) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'text';
xhr.onload = function () {
if (xhr.status === 200) {
const svg = xhr.responseText;
resolve(svg);
} else {
reject(new Error(xhr.statusText));
}
};
xhr.onerror = function () {
reject(new Error('Network error'));
};
xhr.send();
});
}
// #endif
/**
* base64
* @param {Object} string
*/
export function pathToBase64(path : string) : Promise<string> {
if (/^data:/.test(path)) return path
let extension = path.substring(path.lastIndexOf('.') + 1);
const isImageFile = isImage(extension)
let prefix = ''
if (isImageFile) {
prefix = 'image/';
if(extension == 'svg') {
extension += '+xml'
}
} else if (extension === 'pdf') {
prefix = 'application/pdf';
} else if (extension === 'txt') {
prefix = 'text/plain';
} else {
// 添加更多文件类型的判断
// 如果不是图片、PDF、文本等类型可以设定默认的前缀或采取其他处理
prefix = 'application/octet-stream';
}
return new Promise((resolve, reject) => {
// #ifdef H5
if (isImageFile) {
if(extension == 'svg') {
getSVGFromURL(path).then(svg => {
const base64 = btoa(svg);
resolve(`data:image/svg+xml;base64,${base64}`);
})
} else {
let image = new Image();
image.setAttribute("crossOrigin", 'Anonymous');
image.onload = function () {
let canvas = document.createElement('canvas');
canvas.width = this.naturalWidth;
canvas.height = this.naturalHeight;
canvas.getContext('2d').drawImage(image, 0, 0);
let result = canvas.toDataURL(`${prefix}${extension}`)
resolve(result);
canvas.height = canvas.width = 0
}
image.src = path + '?v=' + Math.random()
image.onerror = (error) => {
reject(error);
};
}
} else {
reject('not image');
}
// #endif
// #ifdef MP
if (uni.canIUse('getFileSystemManager')) {
uni.getFileSystemManager().readFile({
filePath: path,
encoding: 'base64',
success: (res) => {
resolve(`data:${prefix}${extension};base64,${res.data}`)
},
fail: (error) => {
console.error({ error, path })
reject(error)
}
})
}
// #endif
// #ifdef APP-PLUS
plus.io.resolveLocalFileSystemURL(getLocalFilePath(path), (entry) => {
entry.file((file : any) => {
const fileReader = new plus.io.FileReader()
fileReader.onload = (data) => {
resolve(data.target.result)
}
fileReader.onerror = (error) => {
console.error({ error, path })
reject(error)
}
fileReader.readAsDataURL(file)
}, reject)
}, reject)
// #endif
})
}

View File

@ -0,0 +1,34 @@
// @ts-nocheck
export function getPlatform():Uni {
// #ifdef MP-WEIXIN
return wx
// #endif
// #ifdef MP-BAIDU
return swan
// #endif
// #ifdef MP-ALIPAY
return my
// #endif
// #ifdef MP-JD
return jd
// #endif
// #ifdef MP-QQ
return qq
// #endif
// #ifdef MP-360
return qh
// #endif
// #ifdef MP-KUAISHOU
return ks
// #endif
// #ifdef MP-LARK||MP-TOUTIAO
return tt
// #endif
// #ifdef MP-DINGTALK
return dd
// #endif
// #ifdef QUICKAPP-WEBVIEW || QUICKAPP-WEBVIEW-UNION || QUICKAPP-WEBVIEW-HUAWEI
return qa
// #endif
return uni
}

View File

@ -0,0 +1,10 @@
// @ts-nocheck
// #ifdef APP-IOS || APP-ANDROID
export * from './vue.ts'
// #endif
// #ifndef APP-IOS || APP-ANDROID
export * from './uvue.ts'
// #endif

View File

@ -0,0 +1,20 @@
// @ts-nocheck
// import {isBrowser} from '../isBrowser'
// 是否支持被动事件监听
export const supportsPassive = true;
// 请求动画帧
export function raf(fn: TimerCallback): number {
return setTimeout(fn, 1000 / 30);
}
// 取消动画帧
export function cancelRaf(id: number) {
clearTimeout(id);
}
// 双倍动画帧
export function doubleRaf(fn: TimerCallback): void {
raf(() => raf(fn)); // 在下一帧回调中再次请求动画帧,实现双倍动画帧效果
}

View File

@ -0,0 +1,33 @@
// @ts-nocheck
// import { isBrowser } from '../isBrowser'
type Callback = () => void//Function
// 是否支持被动事件监听
export const supportsPassive = true;
// 请求动画帧
export function raf(fn : Callback) : number {
// #ifndef WEB
return setTimeout(fn, 1000 / 30); // 请求动画帧
// #endif
// #ifdef WEB
return requestAnimationFrame(fn); // 请求动画帧
// #endif
}
// 取消动画帧
export function cancelRaf(id : number) {
// 如果是在浏览器环境下,使用 cancelAnimationFrame 方法
// #ifdef WEB
cancelAnimationFrame(id); // 取消动画帧
// #endif
// #ifndef WEB
clearTimeout(id); // 取消动画帧
// #endif
}
// 双倍动画帧
export function doubleRaf(fn : Callback) : void {
raf(() => {
raf(fn)
}); // 在下一帧回调中再次请求动画帧,实现双倍动画帧效果
}

View File

@ -0,0 +1,24 @@
// @ts-nocheck
/**
*
* @param min
* @param max
* @param fixed 0
* @returns
*/
export function random(min: number, max: number, fixed: number = 0):number {
// 将 min 和 max 转换为数字类型
// min = +min || 0;
// max = +max || 0;
// 计算随机数范围内的一个随机数
const num = Math.random() * (max - min) + min;
// 如果 fixed 参数为 0则返回四舍五入的整数随机数否则保留固定小数位数
// Number
return fixed == 0 ? Math.round(num) : parseFloat(num.toFixed(fixed));
}
// 示例
// console.log(random(0, 10)); // 输出:在 0 和 10 之间的一个整数随机数
// console.log(random(0, 1, 2)); // 输出:在 0 和 1 之间的一个保留两位小数的随机数
// console.log(random(1, 100, 3)); // 输出:在 1 和 100 之间的一个保留三位小数的随机数

View File

@ -0,0 +1,36 @@
// @ts-nocheck
/**
*
* @param start
* @param end
* @param step 1
* @param fromRight false
* @returns
*/
export function range(start : number, end : number, step : number = 1, fromRight : boolean = false) : number[] {
let index = -1;
// 计算范围的长度
let length = Math.max(Math.ceil((end - start) / step), 0);
// 创建一个长度为 length 的数组
// #ifdef APP-ANDROID
const result = Array.fromNative(new IntArray(length.toInt()));
// #endif
// #ifndef APP-ANDROID
const result = new Array(length);
// #endif
// 使用循环生成数字范围数组
let _start = start
while (length-- > 0) {
// 根据 fromRight 参数决定从左侧还是右侧开始填充数组
result[fromRight ? length : ++index] = _start;
_start += step;
}
return result;
}
// 示例
// console.log(range(0, 5)); // 输出: [0, 1, 2, 3, 4]
// console.log(range(1, 10, 2, true)); // 输出: [9, 7, 5, 3, 1]
// console.log(range(5, 0, -1)); // 输出: [5, 4, 3, 2, 1]

View File

@ -0,0 +1,445 @@
# lime-shared 工具库
- 本人插件的几个公共函数
- 按需引入
## 引入
按需引入只会引入相关的方法,不要看着 插件函数列表多 而占空间,只要不引用不会被打包
```js
import {getRect} from '@/uni_modules/lime-shared/getRect'
```
## 目录
+ [getRect](#api_getRect): 获取节点尺寸信息
+ [addUnit](#api_addUnit): 将未带单位的数值添加px如果有单位则返回原值
+ [unitConvert](#api_unitConvert): 将带有rpx|px的字符转成number,若本身是number则直接返回
+ [canIUseCanvas2d](#api_canIUseCanvas2d): 环境是否支持使用 canvas 2d
+ [getCurrentPage](#api_getCurrentPage): 获取当前页
+ [base64ToPath](#api_base64ToPath): 把base64的图片转成临时路径
+ [pathToBase64](#api_pathToBase64): 把图片的临时路径转成base64
+ [sleep](#api_sleep): async 内部程序等待一定时间后再执行
+ [throttle](#api_throttle): 节流
+ [debounce](#api_debounce): 防抖
+ [random](#api_random): 返回指定范围的随机数
+ [range](#api_range): 生成区间数组
+ [clamp](#api_clamp): 夹在min和max之间的数值
+ [floatAdd](#api_floatAdd): 返回两个浮点数相加的结果
+ [fillZero](#api_fillZero): 补零如果传入的是个位数则在前面补0
+ [exif](#api_exif): 获取图片exif
+ [selectComponent](#api_selectComponent): 获取页面或当前实例的指定组件
+ [createAnimation](#api_createAnimation): uni.createAnimation
+ [animation](#api_animation): 数值从一个值到另一个值的过渡
+ [camelCase](#api_camelCase): 字符串转换为 camelCase 或 PascalCase 风格的命名约定
+ [kebabCase](#api_kebabCase): 将字符串转换为指定连接符的命名约定
+ [closest](#api_closest): 在给定数组中找到最接近目标数字的元素
+ [isBase64](#api_isBase64): 判断字符串是否为base64
+ [isNumber](#api_isNumber): 检查一个值是否为数字类型
+ [isNumeric](#api_isNumeric): 检查一个值是否为数字类型或表示数字的字符串
+ [isString](#api_isString): 检查一个值是否为字符串类型
+ [composition-api](#api_composition-api): 为兼容vue2
## Utils
### getRect <a id="api_getRect"></a>
- 返回节点尺寸信息
```js
// 组件内需要传入上下文
// 如果是nvue 则需要在节点上加与id或class同名的ref
getRect('#id',{context: this}).then(res => {})
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### addUnit <a id="api_addUnit"></a>
- 将未带单位的数值添加px如果有单位则返回原值
```js
addUnit(10)
// 10px
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### unitConvert <a id="api_unitConvert"></a>
- 将带有rpx|px的字符转成number,若本身是number则直接返回
```js
unitConvert('10rpx')
// 5 设备不同 返回的值也不同
unitConvert('10px')
// 10
unitConvert(10)
// 10
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### canIUseCanvas2d <a id="api_canIUseCanvas2d"></a>
- 环境是否支持使用 canvas 2d
```js
canIUseCanvas2d()
// 若支持返回 true 否则 false
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### getCurrentPage <a id="api_getCurrentPage"></a>
- 获取当前页
```js
const page = getCurrentPage()
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### base64ToPath <a id="api_base64ToPath"></a>
- 把base64的图片转成临时路径
```js
base64ToPath(`xxxxx`).then(res => {})
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### pathToBase64 <a id="api_pathToBase64"></a>
- 把图片的临时路径转成base64
```js
pathToBase64(`xxxxx/xxx.png`).then(res => {})
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### sleep <a id="api_sleep"></a>
- 睡眠,让 async 内部程序等待一定时间后再执行
```js
async next () => {
await sleep(300)
console.log('limeui');
}
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### throttle <a id="api_throttle"></a>
- 节流
```js
throttle((nama) => {console.log(nama)}, 200)('limeui');
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### debounce <a id="api_debounce"></a>
- 防抖
```js
debounce((nama) => {console.log(nama)}, 200)('limeui');
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### random <a id="api_random"></a>
- 返回指定范围的随机数
```js
random(1, 5);
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### range <a id="api_range"></a>
- 生成区间数组
```js
range(0, 5)
// [0,1,2,3,4,5]
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### clamp <a id="api_clamp"></a>
- 夹在min和max之间的数值如小于min返回min, 如大于max返回max否侧原值返回
```js
clamp(0, 10, -1)
// 0
clamp(0, 10, 11)
// 10
clamp(0, 10, 9)
// 9
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### floatAdd <a id="api_floatAdd"></a>
- 返回两个浮点数相加的结果
```js
floatAdd(0.1, 0.2) // 0.3
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### fillZero <a id="api_fillZero"></a>
- 补零,如果传入的是`个位数`则在前面补0
```js
fillZero(9);
// 09
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### exif <a id="api_exif"></a>
- 获取图片exif
- 支持临时路径、base64
```js
uni.chooseImage({
count: 1, //最多可以选择的图片张数
sizeType: "original",
success: (res) => {
exif.getData(res.tempFiles[0], function() {
let tagj = exif.getTag(this, "GPSLongitude");
let Orientation = exif.getTag(this, 'Orientation');
console.log(tagj, Orientation)
})
}
})
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | x |
### selectComponent <a id="api_selectComponent"></a>
- 获取页面或当前实例的指定组件,会在页面或实例向所有的节点查找(包括子组件或子子组件)
- 仅vue3vue2没有测试过
```js
// 当前页面
const page = getCurrentPage()
selectComponent('.custom', {context: page}).then(res => {
})
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | x |
### createAnimation <a id="api_createAnimation"></a>
- 创建动画与uni.createAnimation使用方法一致只为了抹平nvue
```html
<view ref="ball" :animation="animationData"></view>
```
```js
const ball = ref(null)
const animation = createAnimation({
transformOrigin: "50% 50%",
duration: 1000,
timingFunction: "ease",
delay: 0
})
animation.scale(2,2).rotate(45).step()
// nvue 无导出数据,这样写只为了平台一致,
// nvue 需要把 ref 传入,其它平台不需要
const animationData = animation.export(ball.value)
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### camelCase <a id="api_camelCase"></a>
- 将字符串转换为 camelCase 或 PascalCase 风格的命名约定
```js
camelCase("hello world") // helloWorld
camelCase("hello world", true) // HelloWorld
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### kebabCase <a id="api_kebabCase"></a>
- 将字符串转换为指定连接符的命名约定
```js
kebabCase("helloWorld") // hello-world
kebabCase("hello world_example") // hello-world-example
kebabCase("helloWorld", "_") // hello_world
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### closest <a id="api_closest"></a>
- 在给定数组中找到最接近目标数字的元素
```js
closest([1, 3, 5, 7, 9], 6) // 5
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### isBase64 <a id="api_isBase64"></a>
- 判断字符串是否为base64
```js
isBase64('xxxxx')
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### isNumber <a id="api_isNumber"></a>
- 检查一个值是否为数字类型
```js
isNumber('0') // false
isNumber(0) // true
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### isNumeric <a id="api_isNumeric"></a>
- 检查一个值是否为数字类型或表示数字的字符串
```js
isNumeric('0') // true
isNumeric(0) // true
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### isString <a id="api_isString"></a>
- 检查一个值是否为数字类型或表示数字的字符串
```js
isString('0') // true
isString(0) // false
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
## composition-api <a id="api_composition-api"></a>
- 因本人插件需要兼容vue2/vue3故增加一个vue文件,代替条件编译
- vue2需要在main.js加上这一段
```js
// vue2
import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)
```
```js
//使用
import {computed, onMounted, watch, reactive} from '@/uni_modules/lime-shared/vue'
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | x |

View File

@ -0,0 +1,8 @@
// @ts-nocheck
// #ifdef UNI-APP-X
export * from './uvue.uts'
// #endif
// #ifndef UNI-APP-X
export * from './vue.ts'
// #endif

View File

@ -0,0 +1,39 @@
// @ts-nocheck
import { type ComponentPublicInstance } from 'vue';
type SelectOptions = {
context : ComponentPublicInstance,
needAll : boolean | null,
}
export function selectAllComponent(selector : string, options : UTSJSONObject) : ComponentPublicInstance[]|null {
const context = options.get('context')! as ComponentPublicInstance;
let needAll = options.get('needAll') as boolean;
let result:ComponentPublicInstance[] = []
if(needAll == null) { needAll = true };
if(context.$children.length > 0) {
const queue:ComponentPublicInstance[] = [...context.$children];
while(queue.length > 0) {
const child = queue.shift();
const name = child?.$options?.name;
if(name == selector) {
result.push(child as ComponentPublicInstance)
} else {
const children = child?.$children
if(children !== null) {
queue.push(...children)
}
}
if(result.length > 0 && !needAll) {
break;
}
}
}
if(result.length > 0) {
return result
}
return null
}

View File

@ -0,0 +1,151 @@
// @ts-nocheck
interface SelectOptions {
context?: any
needAll?: boolean
node?: boolean
}
// #ifdef MP
function selectMPComponent(key: string, name: string, context: any, needAll: boolean) {
const {proxy, $vm} = context
context = $vm || proxy
if(!['ref','component'].includes(key)) {
const queue = [context]
let result = null
const selector = (key == 'id' ? '#': '.') + name;
while(queue.length > 0) {
const child = queue.shift();
const flag = child?.selectComponent(selector)
if(flag) {
if(!needAll) {return result = flag.$vm}
return result = child.selectAllComponents(selector).map(item => item.$vm)
} else {
child.$children && (queue.push(...child.$children));
}
}
return result
} else {
const {$templateRefs} = context.$
const nameMap = {}
for (var i = 0; i < $templateRefs.length; i++) {
const item = $templateRefs[i]
nameMap[item.i] = item.r
}
let result = []
if(context.$children.length) {
const queue = [...context.$children]
while(queue.length > 0) {
const child = queue.shift();
if(key == 'component' && (child.type?.name === name || child.$?.type?.name === name)) {
result.push(child)
} else if(child.$refs && child.$refs[name]) {
result = child.$refs[name]
} else if(nameMap[child.id] === name){
result.push(child)
} else {
child.$children && (queue.push(...child.$children));
}
if(result.length && !needAll) {
return;
}
}
}
return needAll ? result : result[0]
}
}
// #endif
// #ifdef H5
function selectH5Component(key: string, name: string, context: any, needAll: boolean) {
const {_, component } = context
const child = {component: _ || component || context, children: null , subTree: null, props: null}
let result = []
let queue = [child]
while(queue.length > 0 ) {
const child = queue.shift()
const {component, children , props, subTree} = child
if(key === 'component' && component?.type?.name == name) {
result.push(component)
} else if(key === 'ref' && component && (props?.ref == name || component[key][name])) {
if(props?.ref == name) {
//exposed
result.push(component)
} else if(component[key][name]) {
result.push(component[key][name])
}
} else if(key !== 'ref' && component?.exposed && new RegExp(`\\b${name}\\b`).test(component.attrs[key])) {
// exposed
result.push(component)
} else if(children && Array.isArray(children)) {
queue.push(...children)
} else if(!component && subTree) {
queue.push(subTree)
} else if(component?.subTree) {
queue.push(component.subTree)
}
if(result.length && !needAll) {
break
}
}
return needAll ? result : result[0]
}
// #endif
// #ifdef APP
function selectAPPComponent(key: string, name: string, context: any, needAll: boolean, node: boolean) {
let result = []
// const {_, component} = context
// const child = {component: _ || component || context, children: null, props: null, subTree: null}
const queue = [context]
while(queue.length > 0) {
const child = queue.shift()
const {component, children, props, subTree} = child
const isComp = component && props && component.exposed && !node
if(key == 'component' && child.type && child.type.name === name) {
result.push(component)
} else if(props?.[key] === name && node) {
result.push(child)
} else if(key === 'ref' && isComp && (props.ref === name || props.ref_key === name)) {
// exposed
result.push(component)
} else if(key !== 'ref' && isComp && new RegExp(`\\b${name}\\b`).test(props[key])) {
// exposed
result.push(component)
}
// else if(component && component.subTree && Array.isArray(component.subTree.children)){
// queue.push(...component.subTree.children)
// }
else if(subTree) {
queue.push(subTree)
} else if(component && component.subTree){
queue.push(component.subTree)
}
else if(children && Array.isArray(children)) {
queue.push(...children)
}
if(result.length && !needAll) {
break;
}
}
return needAll ? result : result[0]
}
// #endif
export function selectAllComponent(selector: string, options: SelectOptions = {}) {
// . class
// # id
// $ ref
// @ component name
const reg = /^(\.|#|@|\$)([a-zA-Z_0-9\-]+)$/;
if(!reg.test(selector)) return null
let { context, needAll = true, node} = options
const [,prefix, name] = selector.match(reg)
const symbolMappings = {'.': 'class', '#': 'id', '$':'ref', '@':'component'}
const key = symbolMappings [prefix] //prefix === '.' ? 'class' : prefix === '#' ? 'id' : 'ref';
// #ifdef MP
return selectMPComponent(key, name, context, needAll)
// #endif
// #ifdef H5
return selectH5Component(key, name, context, needAll)
// #endif
// #ifdef APP
return selectAPPComponent(key, name, context, needAll, node)
// #endif
}

View File

@ -0,0 +1,7 @@
// @ts-nocheck
// #ifndef UNI-APP-X
export * from './vue.ts'
// #endif
// #ifdef UNI-APP-X
export * from './uvue.uts'
// #endif

View File

@ -0,0 +1,75 @@
// @ts-nocheck
import { type ComponentPublicInstance } from 'vue';
// #ifdef APP
function findChildren(selector: string, context: ComponentPublicInstance, needAll: boolean): ComponentPublicInstance [] | null{
let result:ComponentPublicInstance[] = []
if(context !== null && context.$children.length > 0) {
const queue:ComponentPublicInstance[] = [...context.$children];
while(queue.length > 0) {
const child = queue.shift();
const name = child?.$options?.name;
if(name == selector) {
result.push(child as ComponentPublicInstance)
} else {
const children = child?.$children
if(children !== null) {
queue.push(...children)
}
}
if(result.length > 0 && !needAll) {
break;
}
}
}
if(result.length > 0) {
return result
}
return null
}
class Query {
context : ComponentPublicInstance | null = null
selector : string = ''
// components : ComponentPublicInstance[] = []
constructor(selector : string, context : ComponentPublicInstance | null) {
this.selector = selector
this.context = context
}
in(context : ComponentPublicInstance) : Query {
return new Query(this.selector, context)
}
find(): ComponentPublicInstance | null {
const selector = this.selector
if(selector == '') return null
const component = findChildren(selector, this.context!, false)
return component != null ? component[0]: null
}
findAll():ComponentPublicInstance[] | null {
const selector = this.selector
if(selector == '') return null
return findChildren(selector, this.context!, true)
}
closest(): ComponentPublicInstance | null {
const selector = this.selector
if(selector == '') return null
let parent = this.context!.$parent
let name = parent?.$options?.name;
while (parent != null && (name == null || selector != name)) {
parent = parent.$parent
if (parent != null) {
name = parent.$options.name
}
}
return parent
}
}
export function selectComponent(selector: string): Query{
return new Query(selector, null)
}
// #endif
// selectComponent('selector').in(this).find()
// selectComponent('selector').in(this).findAll()
// selectComponent('selector').in(this).closest()

View File

@ -0,0 +1,149 @@
// @ts-nocheck
// #ifdef MP
function findChildren(selector : string, context : ComponentPublicInstance, needAll : boolean) {
const { proxy, $vm } = context
context = $vm || proxy
if ((selector.startsWith('.') || selector.startsWith('#'))) {
const queue = [context]
let result = null
while (queue.length > 0) {
const child = queue.shift();
const flag = child?.selectComponent(selector)
if (flag) {
if (!needAll) { return result = flag.$vm }
return result = child.selectAllComponents(selector).map(item => item.$vm)
} else {
child.$children && (queue.push(...child.$children));
}
}
return result
} else {
const { $templateRefs } = context.$
const selectorValue = /#|\.|@|$/.test(selector) ? selector.substring(1) : selector
const nameMap = {}
for (var i = 0; i < $templateRefs.length; i++) {
const item = $templateRefs[i]
nameMap[item.i] = item.r
}
let result = []
if (context.$children.length) {
const queue = [...context.$children]
while (queue.length > 0) {
const child = queue.shift();
if (child.type?.name === selectorValue || child.$?.type?.name === selectorValue) {
result.push(child)
} else if (child.$refs && child.$refs[selectorValue]) {
result = child.$refs[selectorValue]
} else if (nameMap[child.id] === selectorValue) {
result.push(child)
} else {
child.$children && (queue.push(...child.$children));
}
if (result.length && !needAll) {
return;
}
}
}
return needAll ? result : result[0]
}
}
// #endif
// #ifdef H5
function findChildren(selector : string, context : ComponentPublicInstance, needAll : boolean){
const {_, component } = context
const child = {component: _ || component || context, children: null , subTree: null, props: null}
let result = []
let queue = [child]
const selectorValue = /#|\.|@|$/.test(selector) ? selector.substring(1) : selector
while(queue.length > 0 ) {
const child = queue.shift()
const {component, children , props, subTree} = child
if(component?.type?.name == selectorValue) {
result.push(component)
} else if(selector.startsWith('$') && component && (props?.ref == selectorValue || component[key][selectorValue])) {
if(props?.ref == selectorValue) {
//exposed
result.push(component)
} else if(component[key][selectorValue]) {
result.push(component[key][selectorValue])
}
} else if(!selector.startsWith('$') && component?.exposed && new RegExp(`\\b${selectorValue}\\b`).test(component.attrs[key])) {
// exposed
result.push(component)
} else if(children && Array.isArray(children)) {
queue.push(...children)
} else if(!component && subTree) {
queue.push(subTree)
} else if(component?.subTree) {
queue.push(component.subTree)
}
if(result.length && !needAll) {
break
}
}
return needAll ? result : result[0]
}
// #endif
// #ifdef APP
function findChildren(selector : string, context : ComponentPublicInstance, needAll : boolean){
let result = []
const selectorValue = /#|\.|@|$/.test(selector) ? selector.substring(1) : selector
const queue = [context]
while(queue.length > 0) {
const child = queue.shift()
const {component, children, props, subTree} = child
const isComp = component && props && component.exposed && !node
if(child.type && child.type.name === selectorValue) {
result.push(component)
} else if(props?.[key] === selectorValue && node) {
result.push(child)
} else if(selector.startsWith('$') && isComp && (props.ref === selectorValue || props.ref_key === selectorValue)) {
// exposed
result.push(component)
} else if(!selector.startsWith('$') && isComp && new RegExp(`\\b${selectorValue}\\b`).test(props[key])) {
// exposed
result.push(component)
}
else if(subTree) {
queue.push(subTree)
} else if(component && component.subTree){
queue.push(component.subTree)
}
else if(children && Array.isArray(children)) {
queue.push(...children)
}
if(result.length && !needAll) {
break;
}
}
return needAll ? result : result[0]
}
// #endif
class Query {
context : ComponentPublicInstance | null = null
selector : string = ''
// components : ComponentPublicInstance[] = []
constructor(selector : string, context : ComponentPublicInstance | null) {
this.selector = selector
this.context = context
}
in(context : ComponentPublicInstance) : Query {
return new Query(this.selector, context)
}
find() : ComponentPublicInstance | null {
return findChildren(this.selector, this.context, false)
}
findAll() : ComponentPublicInstance[] | null {
return findChildren(this.selector, this.context, true)
}
closest() : ComponentPublicInstance | null {
return null
}
}
export function selectComponent(selector: string) {
return new Query(selector)
}

View File

@ -0,0 +1,275 @@
// @ts-nocheck
import {isDef} from '../isDef'
import {ComponentPublicInstance} from 'vue'
type HasSelectorFunc = (selector : string, element : UniElement) => boolean
const hasSelectorClassName : HasSelectorFunc = (selector : string, element : UniElement) : boolean => {
return element.classList.includes(selector)
}
const hasSelectorId : HasSelectorFunc = (selector : string, element : UniElement) : boolean => {
return element.getAttribute("id") == selector
}
const hasSelectorTagName : HasSelectorFunc = (selector : string, element : UniElement) : boolean => {
return element.tagName!.toLowerCase() == selector.toLowerCase()
}
type ProcessSelectorResult = {
selectorValue : string
hasSelector : HasSelectorFunc
}
const processSelector = (selector : string) : ProcessSelectorResult => {
const selectorValue = /#|\./.test(selector) ? selector.substring(1) : selector
let hasSelector : HasSelectorFunc
if (selector.startsWith('.')) {
hasSelector = hasSelectorClassName
} else if (selector.startsWith('#')) {
hasSelector = hasSelectorId
} else {
hasSelector = hasSelectorTagName
}
return {
selectorValue,
hasSelector
} as ProcessSelectorResult
}
function isNotEmptyString(str:string): boolean {
return str.length > 0;
}
function isElement(element:UniElement|null):boolean {
return isDef(element) && element?.tagName != 'COMMENT';
}
type ElementArray = Array<UniElement|null>
class Query {
context : ComponentPublicInstance | null = null
selector : string = ''
elements : ElementArray = []
constructor(selector : string | null, context : ComponentPublicInstance | null) {
this.context = context
if(selector != null){
this.selector = selector
}
this.find(this.selector)
}
in(context : ComponentPublicInstance) : Query {
return new Query(this.selector, context)
}
findAll(selector : string): Query {
if (isDef(this.context)) {
const root = this.context?.$el //as Element | null;
if (isDef(root)) {
this.elements = [root!] //as ElementArray
}
const { selectorValue, hasSelector } = processSelector(selector)
const foundElements : ElementArray = [];
function findChildren(element : UniElement) {
element.children.forEach((child : UniElement) => {
if (hasSelector(selectorValue, child)) {
foundElements.push(child)
}
})
}
this.elements.forEach(el => {
findChildren(el!);
});
this.elements = foundElements
} else if (selector.startsWith('#')) {
const element = uni.getElementById(selector)
if (isElement(element!)) {
this.elements = [element]
}
}
return this;
}
/**
* 在当前元素集合中查找匹配的元素
*/
find(selector : string) : Query {
if (isDef(this.context)) {
const root = this.context?.$el //as Element | null;
if (isElement(root)) {
this.elements = [root] //as ElementArray
}
if(isNotEmptyString(selector) && this.elements.length > 0){
const { selectorValue, hasSelector } = processSelector(selector)
const foundElements : ElementArray = [];
function findChildren(element : UniElement) {
element.children.forEach((child : UniElement) => {
if (hasSelector(selectorValue, child) && foundElements.length < 1) {
foundElements.push(child)
}
if (foundElements.length < 1) {
findChildren(child);
}
})
}
this.elements.forEach(el => {
findChildren(el!);
});
this.elements = foundElements
}
} else if (selector.startsWith('#')) {
const element = uni.getElementById(selector)
if (isElement(element!)) {
this.elements = [element]
}
}
return this;
}
/**
* 获取当前元素集合的直接子元素
*/
children() : Query {
// if (this.elements.length > 0) {
// const children = this.elements.reduce((acc, el) => [...acc, ...Array.from(el.children)], []);
// this.elements = children;
// }
return this;
}
/**
* 获取当前元素集合的父元素
*/
parent() : Query {
// if (this.elements.length > 0) {
// const parents = this.elements.map(el => el.parentElement).filter(parent => parent !== null) as ElementArray;
// this.elements = parents
// // this.elements = Array.from(new Set(parents));
// }
return this;
}
/**
* 获取当前元素集合的兄弟元素
*/
siblings() : Query {
// if (this.elements.length > 0) {
// const siblings = this.elements.reduce((acc, el) => [...acc, ...Array.from(el.parentElement?.children || [])], []);
// this.elements = siblings.filter(sibling => sibling !== null && !this.elements?.includes(sibling));
// }
return this;
}
/**
* 获取当前元素集合的下一个兄弟元素
*/
next() : Query {
// if (this.elements.length > 0) {
// const nextElements = this.elements.map(el => el.nextElementSibling).filter(next => next !== null) as ElementArray;
// this.elements = nextElements;
// }
return this;
}
/**
* 获取当前元素集合的上一个兄弟元素
*/
prev() : Query {
// if (this.elements.length > 0) {
// const prevElements = this.elements.map(el => el.previousElementSibling).filter(prev => prev !== null) as ElementArray;
// this.elements = prevElements;
// }
return this;
}
/**
* 从当前元素开始向上查找匹配的元素
*/
closest(selector : string) : Query {
if (isDef(this.context)) {
// && this.context.$parent != null && this.context.$parent.$el !== null
if(this.elements.length == 0 && isDef(this.context?.$parent) && isElement(this.context!.$parent?.$el)){
this.elements = [this.context!.$parent?.$el!]
}
const selectorsArray = selector.split(',')
// const { selectorValue, hasSelector } = processSelector(selector)
const processedSelectors = selectorsArray.map((selector: string):ProcessSelectorResult => processSelector(selector))
const closestElements = this.elements.map((el) : UniElement | null => {
let closestElement : UniElement | null = el
while (closestElement !== null) {
// if (hasSelector(selectorValue, closestElement)) {
// break;
// }
const isMatchingSelector = processedSelectors.some(({selectorValue, hasSelector}):boolean => {
return hasSelector(selectorValue, closestElement!)
})
if(isMatchingSelector){
break;
}
closestElement = closestElement.parentElement;
}
return closestElement
})
this.elements = closestElements.filter((closest : UniElement | null) : boolean => isDef(closest))// as ElementArray
}
return this;
}
/**
* 从当前元素集合中过滤出匹配的元素
*/
filter() : Query {
return this;
}
/**
* 从当前元素集合中排除匹配的元素
*/
not() { }
/**
* 从当前元素集合中查找包含匹配元素的元素
*/
has() { }
/**
* 获取当前元素集合的第一个
*/
first() : Query {
if (this.elements.length > 0) {
// this.elements = [this.elements[0]];
}
return this;
}
/**
* 最后一个元素
*/
last() : Query {
if (this.elements.length > 0) {
// this.elements = [this.elements[this.elements.length - 1]];
}
return this;
}
/**
* 获取当前元素在其兄弟元素中的索引
*/
index() : number | null {
// if (this.elements.length > 0 && this.elements.length > 0 && this.elements[0].parentElement !== null) {
// return Array.from(this.elements[0].parentElement.children).indexOf(this.elements[0]);
// }
return null;
}
get(index : number) : UniElement | null {
if (this.elements.length > index) {
return this.elements[index] //as Element
}
return null
}
}
export function selectElement(selector : string | null = null) : Query {
// if(typeof selector == 'string' || selector == null){
// return new Query(selector as string | null, null)
// }
// else if(selector instanceof ComponentPublicInstance){
// return new Query(null, selector)
// }
return new Query(selector, null)
}
// $('xxx').in(this).find('xxx')
// $('xxx').in(this).get()

View File

@ -0,0 +1,44 @@
// @ts-nocheck
/**
* Promise
* @param delay 300
* @returns Promise
*/
// #ifdef APP-IOS || APP-ANDROID
function sleep(delay: number = 300):Promise<boolean> {
return new Promise((resolve):void => {setTimeout(() => {resolve(true)}, delay)});
}
export {
sleep
}
// #endif
// #ifndef APP-IOS || APP-ANDROID
export const sleep = (delay: number = 300) =>
new Promise(resolve => setTimeout(resolve, delay));
// #endif
// 示例
// async function example() {
// console.log("Start");
// // 延迟 1 秒后执行
// await sleep(1000);
// console.log("1 second later");
// // 延迟 500 毫秒后执行
// await sleep(500);
// console.log("500 milliseconds later");
// // 延迟 2 秒后执行
// await sleep(2000);
// console.log("2 seconds later");
// console.log("End");
// }
// example();

View File

@ -0,0 +1,77 @@
// @ts-nocheck
/**
*
* @param fn
* @param delay
* @returns
*/
// #ifndef APP-IOS || APP-ANDROID
export function throttle(fn: (...args: any[]) => void, delay: number) {
let flag = true; // 标记是否可以执行函数
return (...args: any[]) => {
if (flag) {
flag = false; // 设置为不可执行状态
fn(...args); // 执行传入的函数
setTimeout(() => {
flag = true; // 经过指定时间后,设置为可执行状态
}, delay);
}
};
}
// #endif
// #ifdef APP-IOS || APP-ANDROID
// type Rfun = (...args: any[]) => void
// type Rfun = (...args: any[]) => void
export function throttle<T extends any|null>(
fn: (args : T) => void,
delay: number):(args : T) => void {
let flag = true; // 标记是否可以执行函数
return (args : T) =>{
if(flag){
flag = false;
fn(args);
setTimeout(()=>{
flag = true;
}, delay)
}
}
// return (...args: any[]) => {
// // if (flag) {
// // flag = false; // 设置为不可执行状态
// // fn(...args); // 执行传入的函数
// // setTimeout(() => {
// // flag = true; // 经过指定时间后,设置为可执行状态
// // }, delay);
// // }
// };
}
// #endif
// // 示例
// // 定义一个被节流的函数
// function handleScroll() {
// console.log("Scroll event handled!");
// }
// // 使用节流函数对 handleScroll 进行节流,间隔时间为 500 毫秒
// const throttledScroll = throttle(handleScroll, 500);
// // 模拟多次调用 handleScroll
// throttledScroll(); // 输出 "Scroll event handled!"
// throttledScroll(); // 不会输出
// throttledScroll(); // 不会输出
// // 经过 500 毫秒后,再次调用 handleScroll
// setTimeout(() => {
// throttledScroll(); // 输出 "Scroll event handled!"
// }, 500);

View File

@ -0,0 +1,21 @@
// @ts-nocheck
/**
*
* @param item
* @returns
*/
// #ifndef APP-IOS || APP-ANDROID
export const toArray = <T>(item: T | T[]): T[] => Array.isArray(item) ? item : [item];
// #endif
// #ifdef APP-IOS || APP-ANDROID
export function toArray<T extends any>(item: any): T[] {
return Array.isArray(item) ? item as T[] : [item as T]// as T[]
};
// #endif
// 示例
// console.log(toArray(5)); // 输出: [5]
// console.log(toArray("hello")); // 输出: ["hello"]
// console.log(toArray([1, 2, 3])); // 输出: [1, 2, 3]
// console.log(toArray(["apple", "banana"])); // 输出: ["apple", "banana"]

View File

@ -0,0 +1,40 @@
// @ts-nocheck
import { isNumber } from '../isNumber'
import { isString } from '../isString'
// 函数重载,定义多个函数签名
// function toBoolean(value : any) : boolean;
// function toBoolean(value : string) : boolean;
// function toBoolean(value : number) : boolean;
// function toBoolean(value : boolean) : boolean;
// #ifdef APP-IOS || APP-ANDROID
function toBoolean(value : any | null) : boolean {
// 根据输入值的类型,返回相应的布尔值
// if (isNumber(value)) {
// return (value as number) != 0;
// }
// if (isString(value)) {
// return `${value}`.length > 0;
// }
// if (typeof value == 'boolean') {
// return value as boolean;
// }
// #ifdef APP-IOS
return value != null && value != undefined
// #endif
// #ifdef APP-ANDROID
return value != null
// #endif
}
// #endif
// #ifndef APP-IOS || APP-ANDROID
function toBoolean(value : any | null) : value is NonNullable<typeof value> {
return !!value//value !== null && value !== undefined;
}
// #endif
export {
toBoolean
}

View File

@ -0,0 +1,28 @@
// @ts-nocheck
/**
*
* @param val
* @returns
*/
// #ifdef APP-IOS || APP-ANDROID
// function toNumber(val: string): number
// function toNumber(val: string): string
function toNumber(val: string): number|null {
const n = parseFloat(val); // 使用 parseFloat 函数将字符串转换为浮点数
return isNaN(n) ? null : n; // 使用 isNaN 函数判断是否为非数字,返回转换后的数字或原始字符串
}
export {toNumber}
// #endif
// #ifndef APP-IOS || APP-ANDROID
export function toNumber(val: string): number | string {
const n = parseFloat(val); // 使用 parseFloat 函数将字符串转换为浮点数
return isNaN(n) ? val : n; // 使用 isNaN 函数判断是否为非数字,返回转换后的数字或原始字符串
}
// #endif
// 示例
// console.log(toNumber("123")); // 输出: 123
// console.log(toNumber("3.14")); // 输出: 3.14
// console.log(toNumber("hello")); // 输出: "hello"

View File

@ -0,0 +1,73 @@
// @ts-nocheck
import { isString } from '../isString'
import { isNumeric } from '../isNumeric'
/**
*
* @param value
* @returns 0
*/
// #ifndef APP-IOS || APP-ANDROID
export function unitConvert(value : string | number) : number {
// 如果是字符串数字
if (isNumeric(value)) {
return Number(value);
}
// 如果有单位
if (isString(value)) {
const reg = /^-?([0-9]+)?([.]{1}[0-9]+){0,1}(em|rpx|px|%)$/g;
const results = reg.exec(value);
if (!value || !results) {
return 0;
}
const unit = results[3];
value = parseFloat(value);
if (unit === 'rpx') {
return uni.upx2px(value);
}
if (unit === 'px') {
return value * 1;
}
// 如果是其他单位,可以继续添加对应的转换逻辑
}
return 0;
}
// #endif
// #ifdef APP-IOS || APP-ANDROID
import { isNumber } from '../isNumber'
export function unitConvert(value : any | null) : number {
if (isNumber(value)) {
return value as number
}
// 如果是字符串数字
if (isNumeric(value)) {
return parseFloat(value as string);
}
// 如果有单位
if (isString(value)) {
const reg = /^-?([0-9]+)?([.]{1}[0-9]+){0,1}(em|rpx|px|%)$/g;
const results = reg.exec(value as string);
if (results == null) {
return 0;
}
const unit = results[3];
const v = parseFloat(value);
if (unit == 'rpx') {
const { windowWidth } = uni.getWindowInfo()
return windowWidth / 750 * v;
}
if (unit == 'px') {
return v;
}
// 如果是其他单位,可以继续添加对应的转换逻辑
}
return 0;
}
// #endif
// 示例
// console.log(unitConvert("123")); // 输出: 123 (字符串数字转换为数字)
// console.log(unitConvert("3.14em")); // 输出: 0 (无法识别的单位)
// console.log(unitConvert("20rpx")); // 输出: 根据具体情况而定 (根据单位进行转换)
// console.log(unitConvert(10)); // 输出: 10 (数字不需要转换)

View File

@ -0,0 +1,16 @@
// @ts-nocheck
// #ifdef VUE3
export * from 'vue';
// #endif
// #ifndef VUE3
export * from '@vue/composition-api';
// #ifdef APP-NVUE
import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)
// #endif
// #endif

View File

@ -0,0 +1,6 @@
## 0.0.32022-11-11
- 修复 config 方法获取根节点为数组格式配置时错误的转化为了对象的Bug
## 0.0.22021-04-16
- 修改插件package信息
## 0.0.12021-03-15
- 初始化项目

View File

@ -0,0 +1,81 @@
{
"id": "uni-config-center",
"displayName": "uni-config-center",
"version": "0.0.3",
"description": "uniCloud 配置中心",
"keywords": [
"配置",
"配置中心"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "unicloud-template-function"
},
"directories": {
"example": "../../../scripts/dist"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "u",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "u"
}
}
}
}
}

View File

@ -0,0 +1,93 @@
# 为什么使用uni-config-center
实际开发中很多插件需要配置文件才可以正常运行,如果每个插件都单独进行配置的话就会产生下面这样的目录结构
```bash
cloudfunctions
└─────common 公共模块
├─plugin-a // 插件A对应的目录
│ ├─index.js
│ ├─config.json // plugin-a对应的配置文件
│ └─other-file.cert // plugin-a依赖的其他文件
└─plugin-b // plugin-b对应的目录
├─index.js
└─config.json // plugin-b对应的配置文件
```
假设插件作者要发布一个项目模板,里面使用了很多需要配置的插件,无论是作者发布还是用户使用都是一个大麻烦。
uni-config-center就是用了统一管理这些配置文件的使用uni-config-center后的目录结构如下
```bash
cloudfunctions
└─────common 公共模块
├─plugin-a // 插件A对应的目录
│ └─index.js
├─plugin-b // plugin-b对应的目录
│ └─index.js
└─uni-config-center
├─index.js // config-center入口文件
├─plugin-a
│ ├─config.json // plugin-a对应的配置文件
│ └─other-file.cert // plugin-a依赖的其他文件
└─plugin-b
└─config.json // plugin-b对应的配置文件
```
使用uni-config-center后的优势
- 配置文件统一管理,分离插件主体和配置信息,更新插件更方便
- 支持对config.json设置schema插件使用者在HBuilderX内编写config.json文件时会有更好的提示后续HBuilderX会提供支持
# 用法
在要使用uni-config-center的公共模块或云函数内引入uni-config-center依赖请参考[使用公共模块](https://uniapp.dcloud.net.cn/uniCloud/cf-common)
```js
const createConfig = require('uni-config-center')
const uniIdConfig = createConfig({
pluginId: 'uni-id', // 插件id
defaultConfig: { // 默认配置
tokenExpiresIn: 7200,
tokenExpiresThreshold: 600,
},
customMerge: function(defaultConfig, userConfig) { // 自定义默认配置和用户配置的合并规则,不设置的情况侠会对默认配置和用户配置进行深度合并
// defaudltConfig 默认配置
// userConfig 用户配置
return Object.assign(defaultConfig, userConfig)
}
})
// 以如下配置为例
// {
// "tokenExpiresIn": 7200,
// "passwordErrorLimit": 6,
// "bindTokenToDevice": false,
// "passwordErrorRetryTime": 3600,
// "app-plus": {
// "tokenExpiresIn": 2592000
// },
// "service": {
// "sms": {
// "codeExpiresIn": 300
// }
// }
// }
// 获取配置
uniIdConfig.config() // 获取全部配置注意uni-config-center内不存在对应插件目录时会返回空对象
uniIdConfig.config('tokenExpiresIn') // 指定键值获取配置返回7200
uniIdConfig.config('service.sms.codeExpiresIn') // 指定键值获取配置返回300
uniIdConfig.config('tokenExpiresThreshold', 600) // 指定键值获取配置如果不存在则取传入的默认值返回600
// 获取文件绝对路径
uniIdConfig.resolve('custom-token.js') // 获取uni-config-center/uni-id/custom-token.js文件的路径
// 引用文件require
uniIDConfig.requireFile('custom-token.js') // 使用require方式引用uni-config-center/uni-id/custom-token.js文件。文件不存在时返回undefined文件内有其他错误导致require失败时会抛出错误。
// 判断是否包含某文件
uniIDConfig.hasFile('custom-token.js') // 配置目录是否包含某文件true: 文件存在false: 文件不存在
```

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More