提交 d76f4ede 编写于 作者: L liuyijun17

feat: taro适配

上级 416856a3
...@@ -521,7 +521,7 @@ ...@@ -521,7 +521,7 @@
"desc": "用于将本地的图片或文件上传至服务器。", "desc": "用于将本地的图片或文件上传至服务器。",
"sort": 1, "sort": 1,
"show": true, "show": true,
"taro": false, "taro": true,
"author": "swag~jun" "author": "swag~jun"
}, },
{ {
...@@ -614,7 +614,7 @@ ...@@ -614,7 +614,7 @@
"desc": "列表滚动到底部自动加载更多数据。", "desc": "列表滚动到底部自动加载更多数据。",
"sort": 5, "sort": 5,
"show": true, "show": true,
"taro": false, "taro": true,
"author": "swag~jun" "author": "swag~jun"
}, },
{ {
...@@ -685,7 +685,7 @@ ...@@ -685,7 +685,7 @@
"desc": "展示操作或任务的当前进度。", "desc": "展示操作或任务的当前进度。",
"sort": 7, "sort": 7,
"show": true, "show": true,
"taro": false, "taro": true,
"author": "swag~jun" "author": "swag~jun"
}, },
{ {
...@@ -962,7 +962,7 @@ ...@@ -962,7 +962,7 @@
"desc": "用于话语和词组的轮播展示,适用于视频中或其他类似需求中。", "desc": "用于话语和词组的轮播展示,适用于视频中或其他类似需求中。",
"sort": 2, "sort": 2,
"show": true, "show": true,
"taro": false, "taro": true,
"author": "swag~jun" "author": "swag~jun"
}, },
{ {
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
box-sizing: border-box; box-sizing: border-box;
background-color: #f7f8fa; background-color: #f7f8fa;
.barrage-item { .barrage-item {
width: 100px; width: 120px;
// max-width: 150px; // max-width: 150px;
display: block; display: block;
position: absolute; position: absolute;
...@@ -19,7 +19,12 @@ ...@@ -19,7 +19,12 @@
text-align: center; text-align: center;
white-space: pre; white-space: pre;
transform: translateX(100%); transform: translateX(100%);
background: linear-gradient(to right, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0)); background: linear-gradient(
to right,
rgba(0, 0, 0, 0.15),
rgba(0, 0, 0, 0)
);
box-sizing: border-box;
&.move { &.move {
will-change: transform; will-change: transform;
animation-name: moving; animation-name: moving;
......
...@@ -45,7 +45,6 @@ const InternalBarrage: ForwardRefRenderFunction< ...@@ -45,7 +45,6 @@ const InternalBarrage: ForwardRefRenderFunction<
} }
const barrageBody = useRef<HTMLDivElement>(null) const barrageBody = useRef<HTMLDivElement>(null)
const barrageContainer = useRef<HTMLDivElement>(null) const barrageContainer = useRef<HTMLDivElement>(null)
const barrageCWidth = useRef(0)
const timer = useRef(0) const timer = useRef(0)
const index = useRef(0) const index = useRef(0)
...@@ -60,10 +59,7 @@ const InternalBarrage: ForwardRefRenderFunction< ...@@ -60,10 +59,7 @@ const InternalBarrage: ForwardRefRenderFunction<
})) }))
useEffect(() => { useEffect(() => {
if (barrageBody.current) { run()
barrageCWidth.current = barrageBody.current.offsetWidth
run()
}
return () => { return () => {
clearInterval(timer.current) clearInterval(timer.current)
} }
...@@ -84,17 +80,15 @@ const InternalBarrage: ForwardRefRenderFunction< ...@@ -84,17 +80,15 @@ const InternalBarrage: ForwardRefRenderFunction<
el.classList.add('barrage-item') el.classList.add('barrage-item')
;(barrageContainer.current as HTMLDivElement).appendChild(el) ;(barrageContainer.current as HTMLDivElement).appendChild(el)
const query = Taro.createSelectorQuery() Taro.createSelectorQuery()
.select('.barrage-item') .select('.barrage-item')
.boundingClientRect((res) => { .boundingClientRect((res) => {
const width = res?.width console.log('res', res)
const height = res?.height const height = res?.height
el.classList.add('move') el.classList.add('move')
el.style.animationDuration = `${speeds}ms` el.style.animationDuration = `${speeds}ms`
el.style.top = `${(_index % rows) * (height + top) + 20}px` el.style.top = `${(_index % rows) * (height + top) + 20}px`
// el.style.width = `${width}px`
el.style.setProperty('--move-distance', `-${barrageCWidth.current}px`)
el.dataset.index = `${_index}` el.dataset.index = `${_index}`
el.addEventListener('animationend', () => { el.addEventListener('animationend', () => {
;(barrageContainer.current as HTMLDivElement).removeChild(el) ;(barrageContainer.current as HTMLDivElement).removeChild(el)
......
...@@ -88,7 +88,7 @@ const InternalBarrage: ForwardRefRenderFunction< ...@@ -88,7 +88,7 @@ const InternalBarrage: ForwardRefRenderFunction<
el.classList.add('move') el.classList.add('move')
el.style.animationDuration = `${speeds}ms` el.style.animationDuration = `${speeds}ms`
el.style.top = `${(_index % rows) * (height + top) + 20}px` el.style.top = `${(_index % rows) * (height + top) + 20}px`
el.style.width = `${width + 20}px` el.style.width = `${width}px`
el.style.setProperty('--move-distance', `-${barrageCWidth.current}px`) el.style.setProperty('--move-distance', `-${barrageCWidth.current}px`)
el.dataset.index = `${_index}` el.dataset.index = `${_index}`
el.addEventListener('animationend', () => { el.addEventListener('animationend', () => {
......
.demo-barrage {
min-height: 100vh;
}
.barrage-demo-wrap, .barrage-demo-wrap,
.barrage-demo { .barrage-demo {
padding: 20px 0; padding: 20px 0;
......
...@@ -76,7 +76,7 @@ const BarrageDemo = () => { ...@@ -76,7 +76,7 @@ const BarrageDemo = () => {
return ( return (
<> <>
<div className="demo"> <div className="demo demo-barrage">
<h2>{translated['84aa6bce']}</h2> <h2>{translated['84aa6bce']}</h2>
<Cell className="barrage-demo-wrap"> <Cell className="barrage-demo-wrap">
<Barrage <Barrage
......
...@@ -65,7 +65,7 @@ const BarrageDemo = () => { ...@@ -65,7 +65,7 @@ const BarrageDemo = () => {
translated['4d14b3e0'], translated['4d14b3e0'],
translated['448f995e'], translated['448f995e'],
] ]
console.log(11, translated)
const barrageRef = useRef<barrageRefState>(null) const barrageRef = useRef<barrageRefState>(null)
const addBarrage = () => { const addBarrage = () => {
const n = Math.random() const n = Math.random()
......
.demo-circleprogress {
min-height: 100vh;
}
.nut-circleprogress { .nut-circleprogress {
position: relative; position: relative;
......
import React, { FunctionComponent } from 'react' import React, { useState, useEffect, FunctionComponent } from 'react'
import classNames from 'classnames' import classNames from 'classnames'
import { isObject } from '@/utils' import { isObject } from '@/utils'
import bem from '@/utils/bem' import bem from '@/utils/bem'
...@@ -20,8 +20,8 @@ const defaultProps = { ...@@ -20,8 +20,8 @@ const defaultProps = {
strokeWidth: 5, strokeWidth: 5,
radius: 50, radius: 50,
strokeLinecap: 'round', strokeLinecap: 'round',
circleColor: '', circleColor: '#fa2c19',
pathColor: '', pathColor: '#e5e9f2',
clockwise: true, clockwise: true,
} as CircleProgressProps } as CircleProgressProps
...@@ -44,9 +44,12 @@ export const CircleProgress: FunctionComponent< ...@@ -44,9 +44,12 @@ export const CircleProgress: FunctionComponent<
...defaultProps, ...defaultProps,
...props, ...props,
} }
const [oldValue, setOldValue] = useState(progress)
const b = bem('circleprogress') const b = bem('circleprogress')
const classes = classNames(className, b('')) const classes = classNames(className, b(''))
const refRandomId = Math.random().toString(36).slice(-8) const refRandomId = Math.random().toString(36).slice(-8)
let lastTime = 0
const styles: React.CSSProperties = { const styles: React.CSSProperties = {
height: `${Number(radius) * 2}px`, height: `${Number(radius) * 2}px`,
...@@ -54,31 +57,45 @@ export const CircleProgress: FunctionComponent< ...@@ -54,31 +57,45 @@ export const CircleProgress: FunctionComponent<
...style, ...style,
} }
const pathStyle = { useEffect(() => {
stroke: pathColor, let rafId: number | undefined
} const startTime = Date.now()
const startRate = Number(oldValue) // 30
const hoverStyle = () => { const endRate = Number(progress) // 40
const perimeter = 283 const duration = Math.abs(((startRate - endRate) * 1000) / +100) // 100
const offset = (perimeter * Number(progress)) / 100 const animate = () => {
return { const now = Date.now()
stroke: isObject(circleColor) ? `url(#${refRandomId})` : circleColor, const progress = Math.min((now - startTime) / duration, 1)
strokeDasharray: `${offset}px ${perimeter}px`, const rate = progress * (endRate - startRate) + startRate
setOldValue(Math.min(Math.max(+rate, 0), 100))
if (endRate > startRate ? rate < endRate : rate > endRate) {
rafId = requestAnimationFrame(animate)
}
} }
} if (rafId) {
cancelAnimationFrame(rafId)
}
rafId = requestAnimationFrame(animate)
}, [progress])
const path = () => { const requestAnimationFrame = function (callback: Function) {
const isWise = clockwise ? 1 : 0 var currTime = new Date().getTime()
return `M 50 50 m -45 0 a 45 45 0 1 ${isWise} 90 0 a 45 45 0 1 ${isWise} -90 0` var timeToCall = Math.max(0, 16.7 - (currTime - lastTime))
lastTime = currTime + timeToCall
var id = setTimeout(function () {
callback()
}, timeToCall)
lastTime = currTime + timeToCall
return id
} }
const hoverColor = () => { const cancelAnimationFrame = function (id: any) {
return isObject(circleColor) ? `url(#${refRandomId})` : circleColor clearTimeout(id)
} }
const stop = () => { const stop = () => {
if (!isObject(circleColor)) { if (!isObject(circleColor)) {
return return []
} }
const color = circleColor as IColor const color = circleColor as IColor
const colorArr = Object.keys(color).sort( const colorArr = Object.keys(color).sort(
...@@ -97,36 +114,50 @@ export const CircleProgress: FunctionComponent< ...@@ -97,36 +114,50 @@ export const CircleProgress: FunctionComponent<
return stopArr return stopArr
} }
const transColor = (color: string | undefined) => {
return color && color.replace('#', '%23')
}
const format = (progress: string | number) =>
Math.min(Math.max(+progress, 0), 100)
const circleStyle = () => {
let stopArr: Array<object> = stop()
let stopDom: string[] = []
if (stopArr) {
stopArr.map((item: { key?: string; value?: string }) => {
let obj = ''
obj = `%3Cstop offset='${item.key}' stop-color='${transColor(
item.value
)}'/%3E`
stopDom.push(obj)
})
}
let perimeter = 283
let progress = +oldValue
let offset =
(perimeter * Number(format(parseFloat(progress.toFixed(1))))) / 100
const isWise = props.clockwise ? 1 : 0
const color = isObject(circleColor)
? `url(%23${refRandomId})`
: transColor(circleColor)
let d = `M 50 50 m 0 -45 a 45 45 0 1 ${isWise} 0 90 a 45 45 0 1, ${isWise} 0 -90`
const pa = `%3Cdefs%3E%3ClinearGradient id='${refRandomId}' x1='100%25' y1='0%25' x2='0%25' y2='0%25'%3E${stopDom}%3C/linearGradient%3E%3C/defs%3E`
const path = `%3Cpath d='${d}' stroke-width='${strokeWidth}' stroke='${transColor(
props.pathColor
)}' fill='none'/%3E`
const path1 = `%3Cpath d='${d}' stroke-width='${strokeWidth}' stroke-dasharray='${offset},${perimeter}' stroke-linecap='round' stroke='${transColor(
color
)}' fill='none'/%3E`
return {
background: `url("data:image/svg+xml,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E${pa}${path}${path1}%3C/svg%3E")`,
width: '100%',
height: '100%',
}
}
return ( return (
<div className={classes} style={styles} {...restProps}> <div className={classes} style={styles} {...restProps}>
<svg viewBox="0 0 100 100"> <div style={circleStyle()}></div>
<defs>
<linearGradient id={refRandomId} x1="100%" y1="0%" x2="0%" y2="0%">
{stop()?.map((item: any, index) => {
return (
<stop key={index} offset={item.key} stopColor={item.value} />
)
})}
</linearGradient>
</defs>
<path
className="nut-circleprogress-path"
style={pathStyle}
d={path()}
fill="none"
strokeWidth={strokeWidth}
/>
<path
className="nut-circleprogress-hover"
style={hoverStyle()}
d={path()}
fill="none"
stroke={hoverColor()}
strokeLinecap={strokeLinecap}
transform="rotate(90,50,50)"
strokeWidth={strokeWidth}
/>
</svg>
<div className="nut-circleprogress-text"> <div className="nut-circleprogress-text">
{children || <div>{progress}%</div>} {children || <div>{progress}%</div>}
</div> </div>
......
...@@ -74,7 +74,7 @@ const CircleProgressDemo = () => { ...@@ -74,7 +74,7 @@ const CircleProgressDemo = () => {
return ( return (
<> <>
<div className="demo"> <div className="demo demo-circleprogress">
<h2>{translated['84aa6bce']}</h2> <h2>{translated['84aa6bce']}</h2>
<div className="demo__piece"> <div className="demo__piece">
<CircleProgress progress={20} /> <CircleProgress progress={20} />
......
...@@ -132,29 +132,6 @@ const InfiniteloadingDemo = () => { ...@@ -132,29 +132,6 @@ const InfiniteloadingDemo = () => {
</ul> </ul>
</Cell> </Cell>
<h2>{translated.eb4236fe}</h2>
<Cell>
<ul className="infiniteUl" id="refreshScroll">
<Infiniteloading
pullIcon="JD"
containerId="refreshScroll"
useWindow={false}
isOpenRefresh
hasMore={refreshHasMore}
loadMore={refreshLoadMore}
refresh={refresh}
>
{refreshList.map((item, index) => {
return (
<li className="infiniteLi" key={index}>
{item}
</li>
)
})}
</Infiniteloading>
</ul>
</Cell>
<h2>{translated['9ed40460']}</h2> <h2>{translated['9ed40460']}</h2>
<Cell> <Cell>
<ul className="infiniteUl" id="customScroll"> <ul className="infiniteUl" id="customScroll">
......
# InfiniteLoading 滚动加载
### 介绍
列表滚动到底部自动加载更多数据。
#### 直接使用 Taro 现有 ScrollView 组件开发 [参考文档](https://docs.taro.zone/docs/components/viewContainer/scroll-view)
\ No newline at end of file
...@@ -2,6 +2,8 @@ import React, { useState, useEffect, useRef, FunctionComponent } from 'react' ...@@ -2,6 +2,8 @@ import React, { useState, useEffect, useRef, FunctionComponent } from 'react'
import classNames from 'classnames' import classNames from 'classnames'
import bem from '@/utils/bem' import bem from '@/utils/bem'
import Icon from '@/packages/icon/index.taro' import Icon from '@/packages/icon/index.taro'
import { ScrollView } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { useConfig } from '@/packages/configprovider/configprovider.taro' import { useConfig } from '@/packages/configprovider/configprovider.taro'
import { IComponent, ComponentDefaults } from '@/utils/typings' import { IComponent, ComponentDefaults } from '@/utils/typings'
...@@ -9,6 +11,7 @@ import { IComponent, ComponentDefaults } from '@/utils/typings' ...@@ -9,6 +11,7 @@ import { IComponent, ComponentDefaults } from '@/utils/typings'
export interface InfiniteloadingProps extends IComponent { export interface InfiniteloadingProps extends IComponent {
hasMore: boolean hasMore: boolean
threshold: number threshold: number
upperThreshold: number
containerId: string containerId: string
useWindow: boolean useWindow: boolean
useCapture: boolean useCapture: boolean
...@@ -31,6 +34,7 @@ const defaultProps = { ...@@ -31,6 +34,7 @@ const defaultProps = {
...ComponentDefaults, ...ComponentDefaults,
hasMore: true, hasMore: true,
threshold: 200, threshold: 200,
upperThreshold: 40,
containerId: '', containerId: '',
useWindow: true, useWindow: true,
useCapture: false, useCapture: false,
...@@ -52,6 +56,7 @@ export const Infiniteloading: FunctionComponent< ...@@ -52,6 +56,7 @@ export const Infiniteloading: FunctionComponent<
children, children,
hasMore, hasMore,
threshold, threshold,
upperThreshold,
containerId, containerId,
useWindow, useWindow,
useCapture, useCapture,
...@@ -65,7 +70,6 @@ export const Infiniteloading: FunctionComponent< ...@@ -65,7 +70,6 @@ export const Infiniteloading: FunctionComponent<
refresh, refresh,
loadMore, loadMore,
scrollChange, scrollChange,
...restProps
} = { } = {
...defaultProps, ...defaultProps,
...props, ...props,
...@@ -74,8 +78,10 @@ export const Infiniteloading: FunctionComponent< ...@@ -74,8 +78,10 @@ export const Infiniteloading: FunctionComponent<
const scroller = useRef<HTMLDivElement>(null) const scroller = useRef<HTMLDivElement>(null)
const refreshTop = useRef<HTMLDivElement>(null) const refreshTop = useRef<HTMLDivElement>(null)
const scrollEl = useRef<Window | HTMLElement | (Node & ParentNode)>(window) const scrollEl = useRef<Window | HTMLElement | (Node & ParentNode)>(window)
const scrollHeight = useRef(0)
const scrollTop = useRef(0)
const direction = useRef('down')
const isTouching = useRef(false) const isTouching = useRef(false)
const beforeScrollTop = useRef(0)
const refreshMaxH = useRef(0) const refreshMaxH = useRef(0)
const y = useRef(0) const y = useRef(0)
const distance = useRef(0) const distance = useRef(0)
...@@ -84,27 +90,21 @@ export const Infiniteloading: FunctionComponent< ...@@ -84,27 +90,21 @@ export const Infiniteloading: FunctionComponent<
const classes = classNames(className, b()) const classes = classNames(className, b())
useEffect(() => { useEffect(() => {
const parentElement = getParentElement( refreshMaxH.current = upperThreshold
scroller.current as HTMLDivElement setTimeout(() => {
) as Node & ParentNode getScrollHeight()
scrollEl.current = useWindow ? window : parentElement }, 200)
scrollEl.current.addEventListener('scroll', handleScroll, useCapture)
return () => {
scrollEl.current.removeEventListener('scroll', handleScroll, useCapture)
}
}, [hasMore, isInfiniting]) }, [hasMore, isInfiniting])
useEffect(() => { /** 获取需要滚动的距离 */
const element = scroller.current as HTMLDivElement const getScrollHeight = () => {
element.addEventListener('touchmove', touchMove, { passive: false }) const parentElement = getParentElement('scroller')
parentElement
return () => { .boundingClientRect((rect) => {
element.removeEventListener('touchmove', touchMove, { scrollHeight.current = rect.height
passive: false, })
} as EventListenerOptions) .exec()
} }
}, [])
const getStyle = () => { const getStyle = () => {
return { return {
...@@ -115,20 +115,10 @@ export const Infiniteloading: FunctionComponent< ...@@ -115,20 +115,10 @@ export const Infiniteloading: FunctionComponent<
} }
} }
const getParentElement = (el: HTMLElement) => { const getParentElement = (el: string) => {
return containerId return Taro.createSelectorQuery().select(
? document.querySelector(`#${containerId}`) !!containerId ? `#${containerId} #${el}` : `#${el}`
: el && el.parentNode )
}
const handleScroll = () => {
requestAniFrame()(() => {
if (!isScrollAtBottom() || !hasMore || isInfiniting) {
return false
}
setIsInfiniting(true)
loadMore && loadMore(infiniteDone)
})
} }
const infiniteDone = () => { const infiniteDone = () => {
...@@ -143,112 +133,43 @@ export const Infiniteloading: FunctionComponent< ...@@ -143,112 +133,43 @@ export const Infiniteloading: FunctionComponent<
isTouching.current = false isTouching.current = false
} }
const touchStart = (event: React.TouchEvent<HTMLDivElement>) => { const scrollAction = (e: any) => {
if (beforeScrollTop.current === 0 && !isTouching.current && isOpenRefresh) { if (e.detail.scrollTop <= 0) {
y.current = event.touches[0].pageY // 滚动到最顶部
isTouching.current = true e.detail.scrollTop = 0
const childHeight = ( } else if (e.detail.scrollTop >= scrollHeight.current) {
(refreshTop.current as HTMLDivElement).firstElementChild as HTMLElement // 滚动到最底部
).offsetHeight e.detail.scrollTop = scrollHeight.current
refreshMaxH.current = Math.floor(childHeight * 1 + 10)
}
}
const touchMove = (event: any) => {
distance.current = event.touches[0].pageY - y.current
if (distance.current > 0 && isTouching.current) {
event.preventDefault()
if (distance.current >= refreshMaxH.current) {
distance.current = refreshMaxH.current
;(
refreshTop.current as HTMLDivElement
).style.height = `${distance.current}px`
} else {
;(
refreshTop.current as HTMLDivElement
).style.height = `${distance.current}px`
}
} else {
distance.current = 0
;(
refreshTop.current as HTMLDivElement
).style.height = `${distance.current}px`
isTouching.current = false
} }
} if (
e.detail.scrollTop > scrollTop.current ||
const touchEnd = () => { e.detail.scrollTop >= scrollHeight.current
if (distance.current < refreshMaxH.current) { ) {
distance.current = 0 direction.current = 'down'
;(
refreshTop.current as HTMLDivElement
).style.height = `${distance.current}px`
} else { } else {
refresh && refresh(refreshDone) direction.current = 'up'
} }
scrollTop.current = e.detail.scrollTop
scrollChange && scrollChange(e.detail.scrollTop)
} }
const requestAniFrame = () => { const lower = () => {
return ( if (direction.current == 'up' || !hasMore || isInfiniting) {
window.requestAnimationFrame || return false
window.webkitRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60)
}
)
}
const getWindowScrollTop = () => {
return window.pageYOffset !== undefined
? window.pageYOffset
: (document.documentElement || document.body.parentNode || document.body)
.scrollTop
}
const calculateTopPosition = (el: HTMLElement): number => {
return !el
? 0
: el.offsetTop + calculateTopPosition(el.offsetParent as HTMLElement)
}
const isScrollAtBottom = () => {
let offsetDistance = 0
let resScrollTop = 0
let direction = 'down'
const windowScrollTop = getWindowScrollTop()
if (useWindow) {
if (scroller.current) {
offsetDistance =
calculateTopPosition(scroller.current) +
scroller.current.offsetHeight -
windowScrollTop -
window.innerHeight
}
resScrollTop = windowScrollTop
} else { } else {
const { scrollHeight, clientHeight, scrollTop } = setIsInfiniting(true)
scrollEl.current as HTMLElement loadMore && loadMore(infiniteDone)
offsetDistance = scrollHeight - clientHeight - scrollTop
resScrollTop = scrollTop
}
if (beforeScrollTop.current > resScrollTop) {
direction = 'up'
} else {
direction = 'down'
} }
beforeScrollTop.current = resScrollTop
scrollChange && scrollChange(resScrollTop)
return offsetDistance <= threshold && direction == 'down'
} }
return ( return (
<div <ScrollView
className={classes} className={classes}
ref={scroller} scrollY={true}
onTouchStart={touchStart} id="scroller"
onTouchMove={touchMove} style={{ height: '100%' }}
onTouchEnd={touchEnd} onScroll={scrollAction}
{...restProps} onScrollToLower={lower}
> >
<div className="nut-infinite-top" ref={refreshTop} style={getStyle()}> <div className="nut-infinite-top" ref={refreshTop} style={getStyle()}>
<div className="top-box"> <div className="top-box">
...@@ -285,7 +206,7 @@ export const Infiniteloading: FunctionComponent< ...@@ -285,7 +206,7 @@ export const Infiniteloading: FunctionComponent<
) )
)} )}
</div> </div>
</div> </ScrollView>
) )
} }
......
...@@ -156,6 +156,7 @@ export const Infiniteloading: FunctionComponent< ...@@ -156,6 +156,7 @@ export const Infiniteloading: FunctionComponent<
} }
const touchMove = (event: any) => { const touchMove = (event: any) => {
console.log('touchMove', event)
distance.current = event.touches[0].pageY - y.current distance.current = event.touches[0].pageY - y.current
if (distance.current > 0 && isTouching.current) { if (distance.current > 0 && isTouching.current) {
event.preventDefault() event.preventDefault()
......
.demo-overlay {
min-height: 100vh;
}
.wrapper { .wrapper {
display: flex; display: flex;
height: 100%; height: 100%;
......
...@@ -60,7 +60,7 @@ const OverlayDemo = () => { ...@@ -60,7 +60,7 @@ const OverlayDemo = () => {
} }
return ( return (
<> <>
<div className="demo"> <div className="demo demo-overlay">
<h2>{translated['84aa6bce']}</h2> <h2>{translated['84aa6bce']}</h2>
<Cell> <Cell>
<Button type="primary" onClick={handleToggleShow}> <Button type="primary" onClick={handleToggleShow}>
......
...@@ -62,7 +62,7 @@ const OverlayDemo = () => { ...@@ -62,7 +62,7 @@ const OverlayDemo = () => {
} }
return ( return (
<> <>
<div className="demo"> <div className="demo demo-overlay">
<h2>{translated['84aa6bce']}</h2> <h2>{translated['84aa6bce']}</h2>
<Cell> <Cell>
<Button type="primary" onClick={handleToggleShow}> <Button type="primary" onClick={handleToggleShow}>
......
import React, { useRef } from 'react' import React, { useRef } from 'react'
import { useTranslate } from '@/sites/assets/locale/taro' import { useTranslate } from '@/sites/assets/locale/taro'
import { Button, Uploader } from '@/packages/nutui.react.taro' import { Button, Uploader } from '@/packages/nutui.react.taro'
import Taro from '@tarojs/taro'
export type FileItemStatus = export type FileItemStatus =
| 'ready' | 'ready'
...@@ -183,7 +184,7 @@ const UploaderDemo = () => { ...@@ -183,7 +184,7 @@ const UploaderDemo = () => {
canvas.toBlob((blob) => resolve(blob), type, quality) canvas.toBlob((blob) => resolve(blob), type, quality)
) )
} }
const onOversize = (files: File[]) => { const onOversize = (files: Taro.chooseImage.ImageFile[]) => {
console.log(translated['25e04d44'], files) console.log(translated['25e04d44'], files)
} }
const onStart = () => { const onStart = () => {
......
...@@ -3,6 +3,8 @@ export class UploadOptions { ...@@ -3,6 +3,8 @@ export class UploadOptions {
name = 'file' name = 'file'
fileType? = 'image'
formData?: FormData formData?: FormData
method = 'post' method = 'post'
...@@ -24,6 +26,8 @@ export class UploadOptions { ...@@ -24,6 +26,8 @@ export class UploadOptions {
onSuccess?: Function onSuccess?: Function
onFailure?: Function onFailure?: Function
beforeXhrUpload?: Function
} }
export class Upload { export class Upload {
options: UploadOptions options: UploadOptions
...@@ -65,4 +69,51 @@ export class Upload { ...@@ -65,4 +69,51 @@ export class Upload {
console.warn('浏览器不支持 XMLHttpRequest') console.warn('浏览器不支持 XMLHttpRequest')
} }
} }
uploadTaro(uploadFile: Function, env: string) {
const options = this.options
if (env === 'WEB') {
this.upload()
} else {
if (options.beforeXhrUpload) {
options.beforeXhrUpload(uploadFile, options)
} else {
const uploadTask = uploadFile({
url: options.url,
filePath: options.taroFilePath,
fileType: options.fileType,
header: {
'Content-Type': 'multipart/form-data',
...options.headers,
}, //
formData: options.formData,
name: options.name,
success(response: { errMsg: any; statusCode: number; data: string }) {
if (options.xhrState == response.statusCode) {
options.onSuccess?.(response, options)
} else {
options.onFailure?.(response, options)
}
},
fail(e: any) {
options.onFailure?.(e, options)
},
})
options.onStart?.(options)
uploadTask.progress(
(res: {
progress: any
totalBytesSent: any
totalBytesExpectedToSend: any
}) => {
options.onProgress?.(res, options)
// console.log('上传进度', res.progress);
// console.log('已经上传的数据长度', res.totalBytesSent);
// console.log('预期需要上传的数据总长度', res.totalBytesExpectedToSend);
}
)
// uploadTask.abort(); // 取消上传任务
}
}
}
} }
...@@ -6,7 +6,9 @@ import React, { ...@@ -6,7 +6,9 @@ import React, {
useEffect, useEffect,
} from 'react' } from 'react'
import classNames from 'classnames' import classNames from 'classnames'
import Icon from '@/packages/icon' import Icon from '@/packages/icon/index.taro'
import Button from '@/packages/button/index.taro'
import Taro from '@tarojs/taro'
import { Upload, UploadOptions } from './upload' import { Upload, UploadOptions } from './upload'
import bem from '@/utils/bem' import bem from '@/utils/bem'
import { useConfig } from '@/packages/configprovider/configprovider.taro' import { useConfig } from '@/packages/configprovider/configprovider.taro'
...@@ -20,11 +22,33 @@ export type FileItemStatus = ...@@ -20,11 +22,33 @@ export type FileItemStatus =
| 'error' | 'error'
| 'removed' | 'removed'
/** 图片的尺寸 */
interface sizeType {
/** 原图 */
original: string
/** compressed */
compressed: string
}
/** 图片的来源 */
interface sourceType {
/** 从相册选图 */
album: string
/** 使用相机 */
camera: string
/** 使用前置摄像头(仅H5纯浏览器使用) */
user: string
/** 使用后置摄像头(仅H5纯浏览器) */
environment: string
}
import { IComponent, ComponentDefaults } from '@/utils/typings' import { IComponent, ComponentDefaults } from '@/utils/typings'
export interface UploaderProps extends IComponent { export interface UploaderProps extends IComponent {
url: string url: string
maximum: string | number maximum: string | number
sizeType: (keyof sizeType)[]
sourceType: (keyof sourceType)[]
maximize: number maximize: number
defaultFileList: FileType<string>[] defaultFileList: FileType<string>[]
listType: string listType: string
...@@ -41,7 +65,6 @@ export interface UploaderProps extends IComponent { ...@@ -41,7 +65,6 @@ export interface UploaderProps extends IComponent {
xhrState: number | string xhrState: number | string
headers: object headers: object
withCredentials: boolean withCredentials: boolean
clearInput: boolean
isPreview: boolean isPreview: boolean
isDeletable: boolean isDeletable: boolean
capture: boolean capture: boolean
...@@ -63,12 +86,12 @@ export interface UploaderProps extends IComponent { ...@@ -63,12 +86,12 @@ export interface UploaderProps extends IComponent {
option: UploadOptions option: UploadOptions
}) => void }) => void
update?: (fileList: any[]) => void update?: (fileList: any[]) => void
oversize?: (file: File[]) => void oversize?: (file: Taro.chooseImage.ImageFile[]) => void
change?: (param: { change?: (param: { fileList: any[] }) => void
fileList: any[] beforeUpload?: (file: any[]) => Promise<any[]>
event: React.ChangeEvent<HTMLInputElement> beforeXhrUpload?: (
}) => void file: Taro.chooseImage.ImageFile[]
beforeUpload?: (file: File[]) => Promise<File[]> ) => Promise<Taro.chooseImage.ImageFile[]>
beforeDelete?: (file: FileItem, files: FileItem[]) => boolean beforeDelete?: (file: FileItem, files: FileItem[]) => boolean
fileItemClick?: (file: FileItem) => void fileItemClick?: (file: FileItem) => void
} }
...@@ -76,7 +99,9 @@ export interface UploaderProps extends IComponent { ...@@ -76,7 +99,9 @@ export interface UploaderProps extends IComponent {
const defaultProps = { const defaultProps = {
...ComponentDefaults, ...ComponentDefaults,
url: '', url: '',
maximum: 1, maximum: 9,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
uploadIcon: 'photograph', uploadIcon: 'photograph',
uploadIconSize: '', uploadIconSize: '',
listType: 'picture', listType: 'picture',
...@@ -93,7 +118,6 @@ const defaultProps = { ...@@ -93,7 +118,6 @@ const defaultProps = {
xhrState: 200, xhrState: 200,
timeout: 1000 * 30, timeout: 1000 * 30,
withCredentials: false, withCredentials: false,
clearInput: false,
isPreview: true, isPreview: true,
isDeletable: true, isDeletable: true,
capture: false, capture: false,
...@@ -115,7 +139,9 @@ export class FileItem { ...@@ -115,7 +139,9 @@ export class FileItem {
type?: string type?: string
formData: FormData = new FormData() path?: string
formData: any = {}
} }
const InternalUploader: ForwardRefRenderFunction< const InternalUploader: ForwardRefRenderFunction<
unknown, unknown,
...@@ -147,7 +173,8 @@ const InternalUploader: ForwardRefRenderFunction< ...@@ -147,7 +173,8 @@ const InternalUploader: ForwardRefRenderFunction<
maximize, maximize,
className, className,
autoUpload, autoUpload,
clearInput, sizeType,
sourceType,
start, start,
removeImage, removeImage,
change, change,
...@@ -158,6 +185,7 @@ const InternalUploader: ForwardRefRenderFunction< ...@@ -158,6 +185,7 @@ const InternalUploader: ForwardRefRenderFunction<
failure, failure,
oversize, oversize,
beforeUpload, beforeUpload,
beforeXhrUpload,
beforeDelete, beforeDelete,
...restProps ...restProps
} = { ...defaultProps, ...props } } = { ...defaultProps, ...props }
...@@ -177,7 +205,7 @@ const InternalUploader: ForwardRefRenderFunction< ...@@ -177,7 +205,7 @@ const InternalUploader: ForwardRefRenderFunction<
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
submit: () => { submit: () => {
Promise.all(uploadQueue).then((res) => { Promise.all(uploadQueue).then((res) => {
res.forEach((i) => i.upload()) res.forEach((i) => i.uploadTaro(Taro.uploadFile, Taro.getEnv()))
}) })
}, },
})) }))
...@@ -191,22 +219,37 @@ const InternalUploader: ForwardRefRenderFunction< ...@@ -191,22 +219,37 @@ const InternalUploader: ForwardRefRenderFunction<
} }
} }
const clearInputValue = (el: HTMLInputElement) => { const chooseImage = () => {
el.value = '' if (disabled) {
return
}
Taro.chooseImage({
// 选择数量
count: (maximum as number) * 1 - fileList.length,
// 可以指定是原图还是压缩图,默认二者都有
sizeType: sizeType,
sourceType: sourceType,
success: onChange,
})
} }
const executeUpload = (fileItem: FileItem, index: number) => { const executeUpload = (fileItem: FileItem, index: number) => {
console.log('executeUpload fileItem', fileItem, index)
const uploadOption = new UploadOptions() const uploadOption = new UploadOptions()
uploadOption.name = name
uploadOption.url = url uploadOption.url = url
for (const [key, value] of Object.entries(data)) { uploadOption.fileType = fileItem.type
fileItem.formData.append(key, value)
}
uploadOption.formData = fileItem.formData uploadOption.formData = fileItem.formData
uploadOption.timeout = timeout * 1 uploadOption.timeout = timeout * 1
uploadOption.method = method uploadOption.method = method
uploadOption.xhrState = xhrState uploadOption.xhrState = xhrState
uploadOption.headers = headers uploadOption.headers = headers
uploadOption.withCredentials = withCredentials uploadOption.taroFilePath = fileItem.path
uploadOption.beforeXhrUpload = beforeXhrUpload
console.log('uploadOption', uploadOption)
uploadOption.onStart = (option: UploadOptions) => { uploadOption.onStart = (option: UploadOptions) => {
clearUploadQueue(index) clearUploadQueue(index)
setFileList((fileList: FileItem[]) => { setFileList((fileList: FileItem[]) => {
...@@ -220,6 +263,7 @@ const InternalUploader: ForwardRefRenderFunction< ...@@ -220,6 +263,7 @@ const InternalUploader: ForwardRefRenderFunction<
}) })
start && start(option) start && start(option)
} }
uploadOption.onProgress = ( uploadOption.onProgress = (
e: ProgressEvent<XMLHttpRequestEventTarget>, e: ProgressEvent<XMLHttpRequestEventTarget>,
option: UploadOptions option: UploadOptions
...@@ -235,6 +279,7 @@ const InternalUploader: ForwardRefRenderFunction< ...@@ -235,6 +279,7 @@ const InternalUploader: ForwardRefRenderFunction<
}) })
progress && progress({ e, option }) progress && progress({ e, option })
} }
uploadOption.onSuccess = ( uploadOption.onSuccess = (
responseText: XMLHttpRequest['responseText'], responseText: XMLHttpRequest['responseText'],
option: UploadOptions option: UploadOptions
...@@ -255,6 +300,7 @@ const InternalUploader: ForwardRefRenderFunction< ...@@ -255,6 +300,7 @@ const InternalUploader: ForwardRefRenderFunction<
option, option,
}) })
} }
uploadOption.onFailure = ( uploadOption.onFailure = (
responseText: XMLHttpRequest['responseText'], responseText: XMLHttpRequest['responseText'],
option: UploadOptions option: UploadOptions
...@@ -274,9 +320,10 @@ const InternalUploader: ForwardRefRenderFunction< ...@@ -274,9 +320,10 @@ const InternalUploader: ForwardRefRenderFunction<
option, option,
}) })
} }
const task = new Upload(uploadOption) const task = new Upload(uploadOption)
if (props.autoUpload) { if (props.autoUpload) {
task.upload() task.uploadTaro(Taro.uploadFile, Taro.getEnv())
} else { } else {
uploadQueue.push( uploadQueue.push(
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
...@@ -287,38 +334,47 @@ const InternalUploader: ForwardRefRenderFunction< ...@@ -287,38 +334,47 @@ const InternalUploader: ForwardRefRenderFunction<
} }
} }
const readFile = (files: File[]) => { const readFile = (files: Taro.chooseImage.ImageFile[]) => {
files.forEach((file: File, index: number) => { const imgReg = /\.(png|jpeg|jpg|webp|gif)$/i
const formData = new FormData() files.forEach((file: Taro.chooseImage.ImageFile, index: number) => {
formData.append(name, file) let fileType = file.type
const fileItem = new FileItem() const fileItem = new FileItem()
fileItem.name = file.name if (!fileType && imgReg.test(file.path)) {
fileType = 'image'
}
fileItem.path = file.path
fileItem.name = file.path
fileItem.status = 'ready' fileItem.status = 'ready'
fileItem.type = file.type
fileItem.formData = formData
fileItem.uid = file.lastModified + fileItem.uid
fileItem.message = locale.uploader.readyUpload fileItem.message = locale.uploader.readyUpload
executeUpload(fileItem, index) fileItem.type = fileType
if (isPreview && file.type.includes('image')) { if (Taro.getEnv() == 'WEB') {
const reader = new FileReader() const formData = new FormData()
reader.onload = (event: ProgressEvent<FileReader>) => { for (const [key, value] of Object.entries(data)) {
fileItem.url = (event.target as FileReader).result as string formData.append(key, value)
fileList.push(fileItem)
setFileList([...fileList])
} }
reader.readAsDataURL(file) formData.append(name, file.originalFileObj as Blob)
fileItem.name = file.originalFileObj?.name
fileItem.type = file.originalFileObj?.type
fileItem.formData = formData
} else { } else {
fileList.push(fileItem) fileItem.formData = data as any
setFileList([...fileList]) }
if (isPreview) {
fileItem.url = file.path
} }
fileList.push(fileItem)
setFileList([...fileList])
executeUpload(fileItem, index)
}) })
} }
const filterFiles = (files: File[]) => { const filterFiles = (files: Taro.chooseImage.ImageFile[]) => {
const maximum = (props.maximum as number) * 1 const maximum = (props.maximum as number) * 1
const oversizes = new Array<File>() const maximize = (props.maximize as number) * 1
const filterFile = files.filter((file: File) => { const oversizes = new Array<Taro.chooseImage.ImageFile>()
const filterFile = files.filter((file: Taro.chooseImage.ImageFile) => {
if (file.size > maximize) { if (file.size > maximize) {
oversizes.push(file) oversizes.push(file)
return false return false
...@@ -329,14 +385,10 @@ const InternalUploader: ForwardRefRenderFunction< ...@@ -329,14 +385,10 @@ const InternalUploader: ForwardRefRenderFunction<
oversize && oversize(files) oversize && oversize(files)
} }
if (filterFile.length > maximum) { let currentFileLength = filterFile.length + fileList.length
filterFile.splice(maximum, filterFile.length - maximum) if (currentFileLength > maximum) {
filterFile.splice(filterFile.length - (currentFileLength - maximum))
} }
if (fileList.length !== 0) {
const index = maximum - fileList.length
filterFile.splice(index, filterFile.length - index)
}
return filterFile return filterFile
} }
...@@ -351,30 +403,20 @@ const InternalUploader: ForwardRefRenderFunction< ...@@ -351,30 +403,20 @@ const InternalUploader: ForwardRefRenderFunction<
} }
} }
const fileChange = (event: React.ChangeEvent<HTMLInputElement>) => { const onChange = (res: Taro.chooseImage.SuccessCallbackResult) => {
if (disabled) { // 返回选定照片的本地文件路径列表,tempFilePath可以作为img标签的src属性显示图片
return const { tempFiles } = res
}
const $el = event.target
const { files } = $el
if (beforeUpload) { if (beforeUpload) {
beforeUpload(new Array<File>().slice.call(files)).then( beforeUpload(tempFiles).then((f: Array<Taro.chooseImage.ImageFile>) => {
(f: Array<File>) => { const _files: Taro.chooseImage.ImageFile[] = filterFiles(f)
const _files: File[] = filterFiles(new Array<File>().slice.call(f)) readFile(_files)
readFile(_files) })
}
)
} else { } else {
const _files = filterFiles(new Array<File>().slice.call(files)) const _files: Taro.chooseImage.ImageFile[] = filterFiles(tempFiles)
readFile(_files) readFile(_files)
} }
props.change && props.change({ fileList, event }) props.change && props.change({ fileList })
if (clearInput) {
clearInputValue($el)
}
} }
const handleItemClick = (file: FileItem) => { const handleItemClick = (file: FileItem) => {
...@@ -388,30 +430,7 @@ const InternalUploader: ForwardRefRenderFunction< ...@@ -388,30 +430,7 @@ const InternalUploader: ForwardRefRenderFunction<
<> <>
{children} {children}
{maximum > fileList.length && ( {maximum > fileList.length && (
<> <Button className="nut-uploader__input" onClick={chooseImage} />
{capture ? (
<input
className="nut-uploader__input"
type="file"
capture="user"
name={name}
accept={accept}
disabled={disabled}
multiple={multiple}
onChange={fileChange}
/>
) : (
<input
className="nut-uploader__input"
type="file"
name={name}
accept={accept}
disabled={disabled}
multiple={multiple}
onChange={fileChange}
/>
)}
</>
)} )}
</> </>
</div> </div>
...@@ -454,7 +473,7 @@ const InternalUploader: ForwardRefRenderFunction< ...@@ -454,7 +473,7 @@ const InternalUploader: ForwardRefRenderFunction<
color="rgba(0,0,0,0.6)" color="rgba(0,0,0,0.6)"
className="close" className="close"
name="failure" name="failure"
click={() => onDelete(item, index)} onClick={() => onDelete(item, index)}
/> />
)} )}
...@@ -518,7 +537,7 @@ const InternalUploader: ForwardRefRenderFunction< ...@@ -518,7 +537,7 @@ const InternalUploader: ForwardRefRenderFunction<
color="#808080" color="#808080"
className="nut-uploader__preview-img__file__del" className="nut-uploader__preview-img__file__del"
name="del" name="del"
click={() => onDelete(item, index)} onClick={() => onDelete(item, index)}
/> />
{/* 缺少进度条组件,待更新 */} {/* 缺少进度条组件,待更新 */}
</div> </div>
...@@ -536,28 +555,7 @@ const InternalUploader: ForwardRefRenderFunction< ...@@ -536,28 +555,7 @@ const InternalUploader: ForwardRefRenderFunction<
color="#808080" color="#808080"
name={uploadIcon} name={uploadIcon}
/> />
{capture ? ( <Button className="nut-uploader__input" onClick={chooseImage} />
<input
className="nut-uploader__input"
type="file"
capture="user"
name={name}
accept={accept}
disabled={disabled}
multiple={multiple}
onChange={fileChange}
/>
) : (
<input
className="nut-uploader__input"
type="file"
name={name}
accept={accept}
disabled={disabled}
multiple={multiple}
onChange={fileChange}
/>
)}
</div> </div>
)} )}
</div> </div>
......
# Video 视频播放器
### 介绍
原生video实现的视频播放器
#### 直接使用 Taro 现有 video 组件开发 [参考文档](https://taro-docs.jd.com/taro/docs/components/media/video)
\ No newline at end of file
...@@ -39,6 +39,7 @@ const subPackages = [ ...@@ -39,6 +39,7 @@ const subPackages = [
'pages/picker/index', 'pages/picker/index',
'pages/shortpassword/index', 'pages/shortpassword/index',
'pages/textarea/index', 'pages/textarea/index',
'pages/uploader/index',
'pages/searchbar/index', 'pages/searchbar/index',
'pages/numberkeyboard/index', 'pages/numberkeyboard/index',
], ],
...@@ -47,6 +48,7 @@ const subPackages = [ ...@@ -47,6 +48,7 @@ const subPackages = [
root: 'feedback', root: 'feedback',
pages: [ pages: [
'pages/actionsheet/index', 'pages/actionsheet/index',
'pages/infiniteloading/index',
'pages/switch/index', 'pages/switch/index',
'pages/pulltorefresh/index', 'pages/pulltorefresh/index',
], ],
...@@ -54,6 +56,7 @@ const subPackages = [ ...@@ -54,6 +56,7 @@ const subPackages = [
{ {
root: 'exhibition', root: 'exhibition',
pages: [ pages: [
'pages/circleprogress/index',
'pages/noticebar/index', 'pages/noticebar/index',
'pages/steps/index', 'pages/steps/index',
'pages/swiper/index', 'pages/swiper/index',
...@@ -71,7 +74,11 @@ const subPackages = [ ...@@ -71,7 +74,11 @@ const subPackages = [
}, },
{ {
root: 'business', root: 'business',
pages: ['pages/card/index', 'pages/timeselect/index'], pages: [
'pages/barrage/index',
'pages/card/index',
'pages/timeselect/index',
],
}, },
] ]
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册