更新:登录功能
This commit is contained in:
11
uni_modules/wot-design-uni/components/composables/index.ts
Normal file
11
uni_modules/wot-design-uni/components/composables/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export { useCell } from './useCell'
|
||||
export { useChildren, flattenVNodes, sortChildren } from './useChildren'
|
||||
export { useCountDown } from './useCountDown'
|
||||
export { useLockScroll } from './useLockScroll'
|
||||
export { useParent } from './useParent'
|
||||
export { usePopover } from './usePopover'
|
||||
export { useQueue } from './useQueue'
|
||||
export { useRaf } from './useRaf'
|
||||
export { useTouch } from './useTouch'
|
||||
export { useTranslate } from './useTranslate'
|
||||
export { useUpload } from './useUpload'
|
||||
13
uni_modules/wot-design-uni/components/composables/useCell.ts
Normal file
13
uni_modules/wot-design-uni/components/composables/useCell.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { computed } from 'vue'
|
||||
import { useParent } from './useParent'
|
||||
import { CELL_GROUP_KEY } from '../wd-cell-group/types'
|
||||
|
||||
export function useCell() {
|
||||
const { parent: cellGroup, index } = useParent(CELL_GROUP_KEY)
|
||||
|
||||
const border = computed(() => {
|
||||
return cellGroup && cellGroup.props.border && index.value
|
||||
})
|
||||
|
||||
return { border }
|
||||
}
|
||||
113
uni_modules/wot-design-uni/components/composables/useChildren.ts
Normal file
113
uni_modules/wot-design-uni/components/composables/useChildren.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import {
|
||||
provide,
|
||||
reactive,
|
||||
getCurrentInstance,
|
||||
type VNode,
|
||||
type InjectionKey,
|
||||
type VNodeNormalizedChildren,
|
||||
type ComponentPublicInstance,
|
||||
type ComponentInternalInstance
|
||||
} from 'vue'
|
||||
|
||||
// 小程序端不支持从vue导出的isVNode方法,参考uni-mp-vue的实现
|
||||
function isVNode(value: any): value is VNode {
|
||||
return value ? value.__v_isVNode === true : false
|
||||
}
|
||||
|
||||
export function flattenVNodes(children: VNodeNormalizedChildren) {
|
||||
const result: VNode[] = []
|
||||
|
||||
const traverse = (children: VNodeNormalizedChildren) => {
|
||||
if (Array.isArray(children)) {
|
||||
children.forEach((child) => {
|
||||
if (isVNode(child)) {
|
||||
result.push(child)
|
||||
|
||||
if (child.component?.subTree) {
|
||||
result.push(child.component.subTree)
|
||||
traverse(child.component.subTree.children)
|
||||
}
|
||||
|
||||
if (child.children) {
|
||||
traverse(child.children)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
traverse(children)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const findVNodeIndex = (vnodes: VNode[], vnode: VNode) => {
|
||||
const index = vnodes.indexOf(vnode)
|
||||
if (index === -1) {
|
||||
return vnodes.findIndex((item) => vnode.key !== undefined && vnode.key !== null && item.type === vnode.type && item.key === vnode.key)
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
// sort children instances by vnodes order
|
||||
export function sortChildren(
|
||||
parent: ComponentInternalInstance,
|
||||
publicChildren: ComponentPublicInstance[],
|
||||
internalChildren: ComponentInternalInstance[]
|
||||
) {
|
||||
const vnodes = parent && parent.subTree && parent.subTree.children ? flattenVNodes(parent.subTree.children) : []
|
||||
|
||||
internalChildren.sort((a, b) => findVNodeIndex(vnodes, a.vnode) - findVNodeIndex(vnodes, b.vnode))
|
||||
|
||||
const orderedPublicChildren = internalChildren.map((item) => item.proxy!)
|
||||
|
||||
publicChildren.sort((a, b) => {
|
||||
const indexA = orderedPublicChildren.indexOf(a)
|
||||
const indexB = orderedPublicChildren.indexOf(b)
|
||||
return indexA - indexB
|
||||
})
|
||||
}
|
||||
|
||||
export function useChildren<
|
||||
// eslint-disable-next-line
|
||||
Child extends ComponentPublicInstance = ComponentPublicInstance<{}, any>,
|
||||
ProvideValue = never
|
||||
>(key: InjectionKey<ProvideValue>) {
|
||||
const publicChildren: Child[] = reactive([])
|
||||
const internalChildren: ComponentInternalInstance[] = reactive([])
|
||||
const parent = getCurrentInstance()!
|
||||
|
||||
const linkChildren = (value?: ProvideValue) => {
|
||||
const link = (child: ComponentInternalInstance) => {
|
||||
if (child.proxy) {
|
||||
internalChildren.push(child)
|
||||
publicChildren.push(child.proxy as Child)
|
||||
sortChildren(parent, publicChildren, internalChildren)
|
||||
}
|
||||
}
|
||||
|
||||
const unlink = (child: ComponentInternalInstance) => {
|
||||
const index = internalChildren.indexOf(child)
|
||||
publicChildren.splice(index, 1)
|
||||
internalChildren.splice(index, 1)
|
||||
}
|
||||
|
||||
provide(
|
||||
key,
|
||||
Object.assign(
|
||||
{
|
||||
link,
|
||||
unlink,
|
||||
children: publicChildren,
|
||||
internalChildren
|
||||
},
|
||||
value
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
children: publicChildren,
|
||||
linkChildren
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import { ref, computed, onBeforeUnmount } from 'vue'
|
||||
import { isDef } from '../common/util'
|
||||
import { useRaf } from './useRaf'
|
||||
|
||||
// 定义倒计时时间的数据结构
|
||||
export type CurrentTime = {
|
||||
days: number
|
||||
hours: number
|
||||
total: number
|
||||
minutes: number
|
||||
seconds: number
|
||||
milliseconds: number
|
||||
}
|
||||
|
||||
// 定义倒计时的配置项
|
||||
export type UseCountDownOptions = {
|
||||
time: number // 倒计时总时间,单位为毫秒
|
||||
millisecond?: boolean // 是否开启毫秒级倒计时,默认为 false
|
||||
onChange?: (current: CurrentTime) => void // 倒计时每次变化时的回调函数
|
||||
onFinish?: () => void // 倒计时结束时的回调函数
|
||||
}
|
||||
|
||||
// 定义常量
|
||||
const SECOND = 1000
|
||||
const MINUTE = 60 * SECOND
|
||||
const HOUR = 60 * MINUTE
|
||||
const DAY = 24 * HOUR
|
||||
|
||||
// 将时间转换为倒计时数据结构
|
||||
function parseTime(time: number): CurrentTime {
|
||||
const days = Math.floor(time / DAY)
|
||||
const hours = Math.floor((time % DAY) / HOUR)
|
||||
const minutes = Math.floor((time % HOUR) / MINUTE)
|
||||
const seconds = Math.floor((time % MINUTE) / SECOND)
|
||||
const milliseconds = Math.floor(time % SECOND)
|
||||
|
||||
return {
|
||||
total: time,
|
||||
days,
|
||||
hours,
|
||||
minutes,
|
||||
seconds,
|
||||
milliseconds
|
||||
}
|
||||
}
|
||||
|
||||
// 判断两个时间是否在同一秒内
|
||||
function isSameSecond(time1: number, time2: number): boolean {
|
||||
return Math.floor(time1 / 1000) === Math.floor(time2 / 1000)
|
||||
}
|
||||
|
||||
// 定义 useCountDown 函数
|
||||
export function useCountDown(options: UseCountDownOptions) {
|
||||
let endTime: number // 结束时间
|
||||
let counting: boolean // 是否计时中
|
||||
|
||||
const { start: startRaf, cancel: cancelRaf } = useRaf(tick)
|
||||
|
||||
const remain = ref(options.time) // 剩余时间
|
||||
const current = computed(() => parseTime(remain.value)) // 当前倒计时数据
|
||||
|
||||
// 暂停倒计时
|
||||
const pause = () => {
|
||||
counting = false
|
||||
cancelRaf()
|
||||
}
|
||||
|
||||
// 获取当前剩余时间
|
||||
const getCurrentRemain = () => Math.max(endTime - Date.now(), 0)
|
||||
|
||||
// 设置剩余时间
|
||||
const setRemain = (value: number) => {
|
||||
remain.value = value
|
||||
isDef(options.onChange) && options.onChange(current.value)
|
||||
if (value === 0) {
|
||||
pause()
|
||||
isDef(options.onFinish) && options.onFinish()
|
||||
}
|
||||
}
|
||||
|
||||
// 每毫秒更新一次倒计时
|
||||
const microTick = () => {
|
||||
if (counting) {
|
||||
setRemain(getCurrentRemain())
|
||||
if (remain.value > 0) {
|
||||
startRaf()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 每秒更新一次倒计时
|
||||
const macroTick = () => {
|
||||
if (counting) {
|
||||
const remainRemain = getCurrentRemain()
|
||||
if (!isSameSecond(remainRemain, remain.value) || remainRemain === 0) {
|
||||
setRemain(remainRemain)
|
||||
}
|
||||
|
||||
if (remain.value > 0) {
|
||||
startRaf()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 根据配置项选择更新方式
|
||||
function tick() {
|
||||
if (options.millisecond) {
|
||||
microTick()
|
||||
} else {
|
||||
macroTick()
|
||||
}
|
||||
}
|
||||
|
||||
// 开始倒计时
|
||||
const start = () => {
|
||||
if (!counting) {
|
||||
endTime = Date.now() + remain.value
|
||||
counting = true
|
||||
startRaf()
|
||||
}
|
||||
}
|
||||
|
||||
// 重置倒计时
|
||||
const reset = (totalTime: number = options.time) => {
|
||||
pause()
|
||||
remain.value = totalTime
|
||||
}
|
||||
|
||||
// 在组件卸载前暂停倒计时
|
||||
onBeforeUnmount(pause)
|
||||
|
||||
return {
|
||||
start,
|
||||
pause,
|
||||
reset,
|
||||
current
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { onBeforeUnmount, onDeactivated, ref, watch } from 'vue'
|
||||
|
||||
export function useLockScroll(shouldLock: () => boolean) {
|
||||
const scrollLockCount = ref(0)
|
||||
|
||||
const lock = () => {
|
||||
if (scrollLockCount.value === 0) {
|
||||
document.getElementsByTagName('body')[0].style.overflow = 'hidden'
|
||||
}
|
||||
scrollLockCount.value++
|
||||
}
|
||||
|
||||
const unlock = () => {
|
||||
if (scrollLockCount.value > 0) {
|
||||
scrollLockCount.value--
|
||||
if (scrollLockCount.value === 0) {
|
||||
document.getElementsByTagName('body')[0].style.overflow = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const destroy = () => {
|
||||
shouldLock() && unlock()
|
||||
}
|
||||
|
||||
watch(shouldLock, (value) => {
|
||||
value ? lock() : unlock()
|
||||
})
|
||||
|
||||
onDeactivated(destroy)
|
||||
onBeforeUnmount(destroy)
|
||||
|
||||
return {
|
||||
lock,
|
||||
unlock
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
ref,
|
||||
inject,
|
||||
computed,
|
||||
onUnmounted,
|
||||
type InjectionKey,
|
||||
getCurrentInstance,
|
||||
type ComponentPublicInstance,
|
||||
type ComponentInternalInstance
|
||||
} from 'vue'
|
||||
|
||||
type ParentProvide<T> = T & {
|
||||
link(child: ComponentInternalInstance): void
|
||||
unlink(child: ComponentInternalInstance): void
|
||||
children: ComponentPublicInstance[]
|
||||
internalChildren: ComponentInternalInstance[]
|
||||
}
|
||||
|
||||
export function useParent<T>(key: InjectionKey<ParentProvide<T>>) {
|
||||
const parent = inject(key, null)
|
||||
|
||||
if (parent) {
|
||||
const instance = getCurrentInstance()!
|
||||
const { link, unlink, internalChildren } = parent
|
||||
|
||||
link(instance)
|
||||
onUnmounted(() => unlink(instance))
|
||||
|
||||
const index = computed(() => internalChildren.indexOf(instance))
|
||||
|
||||
return {
|
||||
parent,
|
||||
index
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
parent: null,
|
||||
index: ref(-1)
|
||||
}
|
||||
}
|
||||
176
uni_modules/wot-design-uni/components/composables/usePopover.ts
Normal file
176
uni_modules/wot-design-uni/components/composables/usePopover.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import { getCurrentInstance, ref } from 'vue'
|
||||
import { getRect, isObj } from '../common/util'
|
||||
|
||||
export function usePopover(visibleArrow = true) {
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
const popStyle = ref<string>('')
|
||||
const arrowStyle = ref<string>('')
|
||||
const showStyle = ref<string>('')
|
||||
const arrowClass = ref<string>('')
|
||||
const popWidth = ref<number>(0)
|
||||
const popHeight = ref<number>(0)
|
||||
const left = ref<number>(0)
|
||||
const bottom = ref<number>(0)
|
||||
const width = ref<number>(0)
|
||||
const height = ref<number>(0)
|
||||
const top = ref<number>(0)
|
||||
|
||||
function noop() {}
|
||||
|
||||
function init(
|
||||
placement:
|
||||
| 'top'
|
||||
| 'top-start'
|
||||
| 'top-end'
|
||||
| 'bottom'
|
||||
| 'bottom-start'
|
||||
| 'bottom-end'
|
||||
| 'left'
|
||||
| 'left-start'
|
||||
| 'left-end'
|
||||
| 'right'
|
||||
| 'right-start'
|
||||
| 'right-end',
|
||||
visibleArrow: boolean,
|
||||
selector: string
|
||||
) {
|
||||
// 初始化 class
|
||||
if (visibleArrow) {
|
||||
const arrowClassArr = [
|
||||
`wd-${selector}__arrow`,
|
||||
placement === 'bottom' || placement === 'bottom-start' || placement === 'bottom-end' ? `wd-${selector}__arrow-up` : '',
|
||||
placement === 'left' || placement === 'left-start' || placement === 'left-end' ? `wd-${selector}__arrow-right` : '',
|
||||
placement === 'right' || placement === 'right-start' || placement === 'right-end' ? `wd-${selector}__arrow-left` : '',
|
||||
placement === 'top' || placement === 'top-start' || placement === 'top-end' ? `wd-${selector}__arrow-down` : ''
|
||||
]
|
||||
arrowClass.value = arrowClassArr.join(' ')
|
||||
}
|
||||
|
||||
// 初始化数据获取
|
||||
getRect('#target', false, proxy).then((rect) => {
|
||||
if (!rect) return
|
||||
left.value = rect.left as number
|
||||
bottom.value = rect.bottom as number
|
||||
width.value = rect.width as number
|
||||
height.value = rect.height as number
|
||||
top.value = rect.top as number
|
||||
})
|
||||
// 用透明度可在初始化时获取到pop尺寸
|
||||
getRect('#pos', false, proxy).then((rect) => {
|
||||
if (!rect) return
|
||||
popWidth.value = rect.width as number
|
||||
popHeight.value = rect.height as number
|
||||
})
|
||||
}
|
||||
|
||||
function control(
|
||||
placement:
|
||||
| 'top'
|
||||
| 'top-start'
|
||||
| 'top-end'
|
||||
| 'bottom'
|
||||
| 'bottom-start'
|
||||
| 'bottom-end'
|
||||
| 'left'
|
||||
| 'left-start'
|
||||
| 'left-end'
|
||||
| 'right'
|
||||
| 'right-start'
|
||||
| 'right-end',
|
||||
offset: number | number[] | Record<'x' | 'y', number>
|
||||
) {
|
||||
// arrow size
|
||||
const arrowSize = visibleArrow ? 9 : 0
|
||||
// 上下位(纵轴)对应的距离左边的距离
|
||||
const verticalX = width.value / 2
|
||||
// 上下位(纵轴)对应的距离底部的距离
|
||||
const verticalY = arrowSize + height.value + 5
|
||||
// 左右位(横轴)对应的距离左边的距离
|
||||
const horizontalX = width.value + arrowSize + 5
|
||||
// 左右位(横轴)对应的距离底部的距离
|
||||
const horizontalY = height.value / 2
|
||||
|
||||
let offsetX = 0
|
||||
let offsetY = 0
|
||||
if (Array.isArray(offset)) {
|
||||
offsetX = (verticalX - 17 > 0 ? 0 : verticalX - 25) + offset[0]
|
||||
offsetY = (horizontalY - 17 > 0 ? 0 : horizontalY - 25) + (offset[1] ? offset[1] : offset[0])
|
||||
} else if (isObj(offset)) {
|
||||
offsetX = (verticalX - 17 > 0 ? 0 : verticalX - 25) + offset.x
|
||||
offsetY = (horizontalY - 17 > 0 ? 0 : horizontalY - 25) + offset.y
|
||||
} else {
|
||||
offsetX = (verticalX - 17 > 0 ? 0 : verticalX - 25) + offset
|
||||
offsetY = (horizontalY - 17 > 0 ? 0 : horizontalY - 25) + offset
|
||||
}
|
||||
// const offsetX = (verticalX - 17 > 0 ? 0 : verticalX - 25) + offset
|
||||
// const offsetY = (horizontalY - 17 > 0 ? 0 : horizontalY - 25) + offset
|
||||
|
||||
const placements = new Map([
|
||||
// 上
|
||||
['top', [`left: ${verticalX}px; bottom: ${verticalY}px; transform: translateX(-50%);`, 'left: 50%;']],
|
||||
[
|
||||
'top-start',
|
||||
[
|
||||
`left: ${offsetX}px; bottom: ${verticalY}px;`,
|
||||
`left: ${(popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX}px;`
|
||||
]
|
||||
],
|
||||
[
|
||||
'top-end',
|
||||
[
|
||||
`right: ${offsetX}px; bottom: ${verticalY}px;`,
|
||||
`right: ${(popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX}px; transform: translateX(50%);`
|
||||
]
|
||||
],
|
||||
// 下
|
||||
['bottom', [`left: ${verticalX}px; top: ${verticalY}px; transform: translateX(-50%);`, 'left: 50%;']],
|
||||
[
|
||||
'bottom-start',
|
||||
[`left: ${offsetX}px; top: ${verticalY}px;`, `left: ${(popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX}px;`]
|
||||
],
|
||||
[
|
||||
'bottom-end',
|
||||
[
|
||||
`right: ${offsetX}px; top: ${verticalY}px;`,
|
||||
`right: ${(popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX}px; transform: translateX(50%);`
|
||||
]
|
||||
],
|
||||
// 左
|
||||
['left', [`right: ${horizontalX}px; top: ${horizontalY}px; transform: translateY(-50%);`, 'top: 50%']],
|
||||
[
|
||||
'left-start',
|
||||
[
|
||||
`right: ${horizontalX}px; top: ${offsetY}px;`,
|
||||
`top: ${(popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY}px;`
|
||||
]
|
||||
],
|
||||
[
|
||||
'left-end',
|
||||
[
|
||||
`right: ${horizontalX}px; bottom: ${offsetY}px;`,
|
||||
`bottom: ${(popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY}px; transform: translateY(50%);`
|
||||
]
|
||||
],
|
||||
// 右
|
||||
['right', [`left: ${horizontalX}px; top: ${horizontalY}px; transform: translateY(-50%);`, 'top: 50%']],
|
||||
[
|
||||
'right-start',
|
||||
[
|
||||
`left: ${horizontalX}px; top: ${offsetY}px;`,
|
||||
`top: ${(popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY}px;`
|
||||
]
|
||||
],
|
||||
[
|
||||
'right-end',
|
||||
[
|
||||
`left: ${horizontalX}px; bottom: ${offsetY}px;`,
|
||||
`bottom: ${(popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY}px; transform: translateY(50%);`
|
||||
]
|
||||
]
|
||||
])
|
||||
popStyle.value = placements.get(placement)![0]
|
||||
arrowStyle.value = placements.get(placement)![1]
|
||||
}
|
||||
|
||||
return { popStyle, arrowStyle, showStyle, arrowClass, init, control, noop }
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { type Ref, provide, ref } from 'vue'
|
||||
|
||||
export const queueKey = '__QUEUE_KEY__'
|
||||
|
||||
export interface Queue {
|
||||
queue: Ref<any[]>
|
||||
pushToQueue: (comp: any) => void
|
||||
removeFromQueue: (comp: any) => void
|
||||
closeOther: (comp: any) => void
|
||||
closeOutside: () => void
|
||||
}
|
||||
|
||||
export function useQueue() {
|
||||
const queue = ref<any[]>([])
|
||||
|
||||
function pushToQueue(comp: any) {
|
||||
queue.value.push(comp)
|
||||
}
|
||||
|
||||
function removeFromQueue(comp: any) {
|
||||
queue.value = queue.value.filter((item) => {
|
||||
return item.$.uid !== comp.$.uid
|
||||
})
|
||||
}
|
||||
|
||||
function closeOther(comp: any) {
|
||||
queue.value.forEach((item) => {
|
||||
if (item.$.uid !== comp.$.uid) {
|
||||
item.$.exposed.close()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function closeOutside() {
|
||||
queue.value.forEach((item) => {
|
||||
item.$.exposed.close()
|
||||
})
|
||||
}
|
||||
|
||||
provide(queueKey, {
|
||||
queue,
|
||||
pushToQueue,
|
||||
removeFromQueue,
|
||||
closeOther,
|
||||
closeOutside
|
||||
})
|
||||
|
||||
return {
|
||||
closeOther,
|
||||
closeOutside
|
||||
}
|
||||
}
|
||||
37
uni_modules/wot-design-uni/components/composables/useRaf.ts
Normal file
37
uni_modules/wot-design-uni/components/composables/useRaf.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { ref, onUnmounted } from 'vue'
|
||||
import { isDef, isH5, isNumber } from '../common/util'
|
||||
|
||||
// 定义回调函数类型
|
||||
type RafCallback = (time: number) => void
|
||||
|
||||
export function useRaf(callback: RafCallback) {
|
||||
const requestRef = ref<number | null | ReturnType<typeof setTimeout>>(null)
|
||||
|
||||
// 启动动画帧
|
||||
const start = () => {
|
||||
const handle = (time: number) => {
|
||||
callback(time)
|
||||
}
|
||||
|
||||
if (isH5) {
|
||||
requestRef.value = requestAnimationFrame(handle)
|
||||
} else {
|
||||
requestRef.value = setTimeout(() => handle(Date.now()), 1000 / 30)
|
||||
}
|
||||
}
|
||||
|
||||
// 取消动画帧
|
||||
const cancel = () => {
|
||||
if (isH5 && isNumber(requestRef.value)) {
|
||||
cancelAnimationFrame(requestRef.value!)
|
||||
} else if (isDef(requestRef.value)) {
|
||||
clearTimeout(requestRef.value)
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
cancel()
|
||||
})
|
||||
|
||||
return { start, cancel }
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
export function useTouch() {
|
||||
const direction = ref<string>('')
|
||||
const deltaX = ref<number>(0)
|
||||
const deltaY = ref<number>(0)
|
||||
const offsetX = ref<number>(0)
|
||||
const offsetY = ref<number>(0)
|
||||
const startX = ref<number>(0)
|
||||
const startY = ref<number>(0)
|
||||
|
||||
function touchStart(event: any) {
|
||||
const touch = event.touches[0]
|
||||
direction.value = ''
|
||||
deltaX.value = 0
|
||||
deltaY.value = 0
|
||||
offsetX.value = 0
|
||||
offsetY.value = 0
|
||||
startX.value = touch.clientX
|
||||
startY.value = touch.clientY
|
||||
}
|
||||
|
||||
function touchMove(event: any) {
|
||||
const touch = event.touches[0]
|
||||
deltaX.value = touch.clientX - startX.value
|
||||
deltaY.value = touch.clientY - startY.value
|
||||
offsetX.value = Math.abs(deltaX.value)
|
||||
offsetY.value = Math.abs(deltaY.value)
|
||||
direction.value = offsetX.value > offsetY.value ? 'horizontal' : offsetX.value < offsetY.value ? 'vertical' : ''
|
||||
}
|
||||
|
||||
return {
|
||||
touchStart,
|
||||
touchMove,
|
||||
direction,
|
||||
deltaX,
|
||||
deltaY,
|
||||
offsetX,
|
||||
offsetY,
|
||||
startX,
|
||||
startY
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { camelCase, getPropByPath, isDef, isFunction } from '../common/util'
|
||||
import Locale from '../../locale'
|
||||
|
||||
export const useTranslate = (name?: string) => {
|
||||
const prefix = name ? camelCase(name) + '.' : ''
|
||||
const translate = (key: string, ...args: unknown[]) => {
|
||||
const currentMessages = Locale.messages()
|
||||
const message = getPropByPath(currentMessages, prefix + key)
|
||||
return isFunction(message) ? message(...args) : isDef(message) ? message : `${prefix}${key}`
|
||||
}
|
||||
return { translate }
|
||||
}
|
||||
364
uni_modules/wot-design-uni/components/composables/useUpload.ts
Normal file
364
uni_modules/wot-design-uni/components/composables/useUpload.ts
Normal file
@@ -0,0 +1,364 @@
|
||||
import { isArray, isDef, isFunction } from '../common/util'
|
||||
import type { ChooseFile, ChooseFileOption, UploadFileItem, UploadMethod, UploadStatusType } from '../wd-upload/types'
|
||||
|
||||
export const UPLOAD_STATUS: Record<string, UploadStatusType> = {
|
||||
PENDING: 'pending',
|
||||
LOADING: 'loading',
|
||||
SUCCESS: 'success',
|
||||
FAIL: 'fail'
|
||||
}
|
||||
|
||||
export interface UseUploadReturn {
|
||||
// 开始上传文件
|
||||
startUpload: (file: UploadFileItem, options: UseUploadOptions) => UniApp.UploadTask | void | Promise<void>
|
||||
// 中断上传
|
||||
abort: (task?: UniApp.UploadTask) => void
|
||||
// 上传状态常量
|
||||
UPLOAD_STATUS: Record<string, UploadStatusType>
|
||||
// 选择文件
|
||||
chooseFile: (options: ChooseFileOption) => Promise<ChooseFile[]>
|
||||
}
|
||||
|
||||
export interface UseUploadOptions {
|
||||
// 上传地址
|
||||
action: string
|
||||
// 请求头
|
||||
header?: Record<string, any>
|
||||
// 文件对应的 key
|
||||
name?: string
|
||||
// 其它表单数据
|
||||
formData?: Record<string, any>
|
||||
// 文件类型 仅支付宝支持且在支付宝平台必填
|
||||
fileType?: 'image' | 'video' | 'audio'
|
||||
// 成功状态码
|
||||
statusCode?: number
|
||||
// 文件状态的key
|
||||
statusKey?: string
|
||||
// 自定义上传方法
|
||||
uploadMethod?: UploadMethod
|
||||
// 上传成功回调
|
||||
onSuccess?: (res: UniApp.UploadFileSuccessCallbackResult, file: UploadFileItem, formData: Record<string, any>) => void
|
||||
// 上传失败回调
|
||||
onError?: (res: UniApp.GeneralCallbackResult, file: UploadFileItem, formData: Record<string, any>) => void
|
||||
// 上传进度回调
|
||||
onProgress?: (res: UniApp.OnProgressUpdateResult, file: UploadFileItem) => void
|
||||
// 是否自动中断之前的上传任务
|
||||
abortPrevious?: boolean
|
||||
// 根据文件拓展名过滤(H5支持全部类型过滤,微信小程序支持all和file时过滤,其余平台不支持)
|
||||
extension?: string[]
|
||||
}
|
||||
|
||||
export function useUpload(): UseUploadReturn {
|
||||
let currentTask: UniApp.UploadTask | null = null
|
||||
|
||||
// 中断上传
|
||||
const abort = (task?: UniApp.UploadTask) => {
|
||||
if (task) {
|
||||
task.abort()
|
||||
} else if (currentTask) {
|
||||
currentTask.abort()
|
||||
currentTask = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认上传方法
|
||||
*/
|
||||
const defaultUpload: UploadMethod = (file, formData, options) => {
|
||||
// 如果配置了自动中断,则中断之前的上传任务
|
||||
if (options.abortPrevious) {
|
||||
abort()
|
||||
}
|
||||
|
||||
const uploadTask = uni.uploadFile({
|
||||
url: options.action,
|
||||
header: options.header,
|
||||
name: options.name,
|
||||
fileName: options.name,
|
||||
fileType: options.fileType,
|
||||
formData,
|
||||
filePath: file.url,
|
||||
success(res) {
|
||||
if (res.statusCode === options.statusCode) {
|
||||
// 上传成功
|
||||
options.onSuccess(res, file, formData)
|
||||
} else {
|
||||
// 上传失败
|
||||
options.onError({ ...res, errMsg: res.errMsg || '' }, file, formData)
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
// 上传失败
|
||||
options.onError(err, file, formData)
|
||||
}
|
||||
})
|
||||
|
||||
currentTask = uploadTask
|
||||
|
||||
// 获取当前文件加载的百分比
|
||||
uploadTask.onProgressUpdate((res) => {
|
||||
options.onProgress(res, file)
|
||||
})
|
||||
|
||||
// 返回上传任务实例,让外部可以控制上传过程
|
||||
return uploadTask
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始上传文件
|
||||
*/
|
||||
const startUpload = (file: UploadFileItem, options: UseUploadOptions) => {
|
||||
const {
|
||||
uploadMethod,
|
||||
formData = {},
|
||||
action,
|
||||
name = 'file',
|
||||
header = {},
|
||||
fileType = 'image',
|
||||
statusCode = 200,
|
||||
statusKey = 'status',
|
||||
abortPrevious = false
|
||||
} = options
|
||||
|
||||
// 设置上传中状态
|
||||
file[statusKey] = UPLOAD_STATUS.LOADING
|
||||
|
||||
const uploadOptions = {
|
||||
action,
|
||||
header,
|
||||
name,
|
||||
fileName: name,
|
||||
fileType,
|
||||
statusCode,
|
||||
abortPrevious,
|
||||
onSuccess: (res: UniApp.UploadFileSuccessCallbackResult, file: UploadFileItem, formData: Record<string, any>) => {
|
||||
// 更新文件状态
|
||||
file[statusKey] = UPLOAD_STATUS.SUCCESS
|
||||
currentTask = null
|
||||
options.onSuccess?.(res, file, formData)
|
||||
},
|
||||
onError: (error: UniApp.GeneralCallbackResult, file: UploadFileItem, formData: Record<string, any>) => {
|
||||
// 更新文件状态和错误信息
|
||||
file[statusKey] = UPLOAD_STATUS.FAIL
|
||||
file.error = error.errMsg
|
||||
currentTask = null
|
||||
options.onError?.(error, file, formData)
|
||||
},
|
||||
onProgress: (res: UniApp.OnProgressUpdateResult, file: UploadFileItem) => {
|
||||
// 更新上传进度
|
||||
file.percent = res.progress
|
||||
options.onProgress?.(res, file)
|
||||
}
|
||||
}
|
||||
|
||||
// 返回上传任务实例,支持外部获取uploadTask进行操作
|
||||
if (isFunction(uploadMethod)) {
|
||||
return uploadMethod(file, formData, uploadOptions)
|
||||
} else {
|
||||
return defaultUpload(file, formData, uploadOptions)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化图片信息
|
||||
*/
|
||||
function formatImage(res: UniApp.ChooseImageSuccessCallbackResult): ChooseFile[] {
|
||||
// #ifdef MP-DINGTALK
|
||||
// 钉钉文件在files中
|
||||
res.tempFiles = isDef((res as any).files) ? (res as any).files : res.tempFiles
|
||||
// #endif
|
||||
if (isArray(res.tempFiles)) {
|
||||
return res.tempFiles.map((item: any) => ({
|
||||
path: item.path || '',
|
||||
name: item.name || '',
|
||||
size: item.size,
|
||||
type: 'image',
|
||||
thumb: item.path || ''
|
||||
}))
|
||||
}
|
||||
return [
|
||||
{
|
||||
path: (res.tempFiles as any).path || '',
|
||||
name: (res.tempFiles as any).name || '',
|
||||
size: (res.tempFiles as any).size,
|
||||
type: 'image',
|
||||
thumb: (res.tempFiles as any).path || ''
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化视频信息
|
||||
*/
|
||||
function formatVideo(res: UniApp.ChooseVideoSuccess): ChooseFile[] {
|
||||
return [
|
||||
{
|
||||
path: res.tempFilePath || (res as any).filePath || '',
|
||||
name: res.name || '',
|
||||
size: res.size,
|
||||
type: 'video',
|
||||
thumb: (res as any).thumbTempFilePath || '',
|
||||
duration: res.duration
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化媒体信息
|
||||
*/
|
||||
function formatMedia(res: UniApp.ChooseMediaSuccessCallbackResult): ChooseFile[] {
|
||||
return res.tempFiles.map((item) => ({
|
||||
type: item.fileType,
|
||||
path: item.tempFilePath,
|
||||
thumb: item.fileType === 'video' ? item.thumbTempFilePath : item.tempFilePath,
|
||||
size: item.size,
|
||||
duration: item.duration
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择文件
|
||||
*/
|
||||
function chooseFile({
|
||||
multiple,
|
||||
sizeType,
|
||||
sourceType,
|
||||
maxCount,
|
||||
accept,
|
||||
compressed,
|
||||
maxDuration,
|
||||
camera,
|
||||
extension
|
||||
}: ChooseFileOption): Promise<ChooseFile[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
switch (accept) {
|
||||
case 'image':
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.chooseMedia({
|
||||
count: multiple ? maxCount : 1,
|
||||
mediaType: ['image'],
|
||||
sourceType,
|
||||
sizeType,
|
||||
camera,
|
||||
success: (res) => resolve(formatMedia(res)),
|
||||
fail: reject
|
||||
})
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
uni.chooseImage({
|
||||
count: multiple ? maxCount : 1,
|
||||
sizeType,
|
||||
sourceType,
|
||||
// #ifdef H5
|
||||
extension,
|
||||
// #endif
|
||||
success: (res) => resolve(formatImage(res)),
|
||||
fail: reject
|
||||
})
|
||||
// #endif
|
||||
break
|
||||
case 'video':
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.chooseMedia({
|
||||
count: multiple ? maxCount : 1,
|
||||
mediaType: ['video'],
|
||||
sourceType,
|
||||
camera,
|
||||
maxDuration,
|
||||
success: (res) => resolve(formatMedia(res)),
|
||||
fail: reject
|
||||
})
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
uni.chooseVideo({
|
||||
sourceType,
|
||||
compressed,
|
||||
maxDuration,
|
||||
camera,
|
||||
// #ifdef H5
|
||||
extension,
|
||||
// #endif
|
||||
success: (res) => resolve(formatVideo(res)),
|
||||
fail: reject
|
||||
})
|
||||
// #endif
|
||||
break
|
||||
// #ifdef MP-WEIXIN
|
||||
case 'media':
|
||||
uni.chooseMedia({
|
||||
count: multiple ? maxCount : 1,
|
||||
sourceType,
|
||||
sizeType,
|
||||
camera,
|
||||
maxDuration,
|
||||
success: (res) => resolve(formatMedia(res)),
|
||||
fail: reject
|
||||
})
|
||||
break
|
||||
case 'file':
|
||||
uni.chooseMessageFile({
|
||||
count: multiple ? (isDef(maxCount) ? maxCount : 100) : 1,
|
||||
type: accept,
|
||||
extension,
|
||||
success: (res) => resolve(res.tempFiles),
|
||||
fail: reject
|
||||
})
|
||||
break
|
||||
// #endif
|
||||
case 'all':
|
||||
// #ifdef H5
|
||||
uni.chooseFile({
|
||||
count: multiple ? maxCount : 1,
|
||||
type: accept,
|
||||
extension,
|
||||
success: (res) => resolve(res.tempFiles as ChooseFile[]),
|
||||
fail: reject
|
||||
})
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.chooseMessageFile({
|
||||
count: multiple ? Number(maxCount) : 1,
|
||||
type: accept,
|
||||
extension,
|
||||
success: (res) => resolve(res.tempFiles),
|
||||
fail: reject
|
||||
})
|
||||
// #endif
|
||||
|
||||
break
|
||||
default:
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.chooseMedia({
|
||||
count: multiple ? maxCount : 1,
|
||||
mediaType: ['image'],
|
||||
sourceType,
|
||||
sizeType,
|
||||
camera,
|
||||
success: (res) => resolve(formatMedia(res)),
|
||||
fail: reject
|
||||
})
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
uni.chooseImage({
|
||||
count: multiple ? maxCount : 1,
|
||||
sizeType,
|
||||
sourceType,
|
||||
// #ifdef H5
|
||||
extension,
|
||||
// #endif
|
||||
success: (res) => resolve(formatImage(res)),
|
||||
fail: reject
|
||||
})
|
||||
// #endif
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
startUpload,
|
||||
abort,
|
||||
UPLOAD_STATUS,
|
||||
chooseFile
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user