UNPKG

zarm

Version:

基于 React 的移动端UI库

275 lines (235 loc) 9.49 kB
# Pull 上拉加载下拉刷新 ## 基本用法 ```jsx import { useState, useEffect, useRef } from 'react'; import { Pull, List, Message, Button, Loading, BackTop } from 'zarm'; import { WarningCircle, SuccessCircle, CloseCircle } from '@zarm-design/icons'; const REFRESH_STATE = { normal: 0, // 普通 pull: 1, // 下拉刷新(未满足刷新条件) drop: 2, // 释放立即刷新(满足刷新条件) loading: 3, // 加载中 success: 4, // 加载成功 failure: 5, // 加载失败 }; const LOAD_STATE = { normal: 0, // 普通 abort: 1, // 中止 loading: 2, // 加载中 success: 3, // 加载成功 failure: 4, // 加载失败 complete: 5, // 加载完成(无新数据) }; const getRandomNum = (min, max) => { const Range = max - min; const Rand = Math.random(); return min + Math.round(Rand * Range); }; const fetchData = (length, dataSource = []) => { let newData = [].concat(dataSource); const startIndex = newData.length; for (let i = startIndex; i < startIndex + length; i++) { newData.push(<List.Item key={+i} title={`${i + 1} 行`} />); } return newData; }; let mounted = true; const Demo = () => { const pullRef = useRef(); const [bodyScroll, setBodyScroll] = useState(false); const [dataSource, setDataSource] = useState([]); const [refreshing, setRefreshing] = useState(REFRESH_STATE.normal); const [loading, setLoading] = useState(LOAD_STATE.normal); const toggleScrollContainer = () => { const newBodyScroll = !bodyScroll; setBodyScroll(newBodyScroll); if (newBodyScroll) { document.body.style.overflow = 'auto'; } else { document.body.style.overflow = 'hidden'; } }; // 模拟请求数据 const refreshData = () => { setRefreshing(REFRESH_STATE.loading); setTimeout(() => { if (!mounted) return; setDataSource(fetchData(20)); setRefreshing(REFRESH_STATE.success); }, 2000); }; // 模拟加载更多数据 const loadData = () => { setLoading(LOAD_STATE.loading); setTimeout(() => { if (!mounted) return; const randomNum = getRandomNum(0, 5); console.log(`状态: ${randomNum === 0 ? '失败' : randomNum === 1 ? '完成' : '成功'}`); let loadingState = LOAD_STATE.success; if (randomNum === 0) { loadingState = LOAD_STATE.failure; } else if (randomNum === 1) { loadingState = LOAD_STATE.complete; } else { setDataSource(fetchData(20, dataSource)); } setLoading(loadingState); }, 2000); }; useEffect(() => { setDataSource(fetchData(20)); return () => { mounted = false; document.body.style.overflow = 'auto'; }; }, []); const style = bodyScroll ? {} : { overflowY: 'auto', maxHeight: 400 }; const mountContainer = () => { return bodyScroll ? document.body : pullRef.current; }; const scrollContainer = () => { return bodyScroll ? window : pullRef.current; }; return ( <> <Message theme="warning" icon={<WarningCircle />}> 当前使用的是 `{bodyScroll ? 'window' : 'div'}` 作为滚动容器。 <Button theme="primary" size="xs" onClick={toggleScrollContainer}> 点击切换 </Button> </Message> <Pull ref={pullRef} style={style} refresh={{ state: refreshing, handler: refreshData, // render: (refreshState, percent) => { // const cls = 'custom-control'; // switch (refreshState) { // case REFRESH_STATE.pull: // return ( // <div className={cls}> // <Loading loading={false} percent={percent} /> // <span>下拉刷新</span> // </div> // ); // case REFRESH_STATE.drop: // return ( // <div className={cls}> // <Loading loading={false} percent={100} /> // <span>释放立即刷新</span> // </div> // ); // case REFRESH_STATE.loading: // return ( // <div className={cls}> // <Loading type="spinner" /> // <span>加载中</span> // </div> // ); // case REFRESH_STATE.success: // return ( // <div className={cls}> // <SuccessCircle theme="success" /> // <span>加载成功</span> // </div> // ); // case REFRESH_STATE.failure: // return ( // <div className={cls}> // <CloseCircle theme="danger" /> // <span>加载失败</span> // </div> // ); // default: // } // }, }} load={{ state: loading, distance: 200, handler: loadData, // render: (loadState) => { // const cls = 'custom-control'; // switch (loadState) { // case LOAD_STATE.loading: // return <div className={cls}><Loading type="spinner" /></div>; // case LOAD_STATE.failure: // return <div className={cls}>加载失败</div>; // case LOAD_STATE.complete: // return <div className={cls}>我是有底线的</div>; // } // }, }} > <List>{dataSource}</List> </Pull> <BackTop mountContainer={mountContainer} scrollContainer={scrollContainer} onClick={() => console.log('click back to top')} > <div style={{ width: 60, height: 60, lineHeight: '60px', textAlign: 'center', backgroundColor: '#fff', color: '#999', fontSize: 20, borderRadius: 30, boxShadow: '0 2px 10px 0 rgba(0, 0, 0, 0.2)', cursor: 'pointer', }} > Up </div> </BackTop> </> ); }; ReactDOM.render(<Demo />, mountNode); ``` ## API | 属性 | 类型 | 默认值 | 说明 | | :---------------- | :----- | :----- | :----------------------- | | refresh | Action | - | 下拉刷新的参数配置 | | load | Action | - | 上拉加载的参数配置 | | animationDuration | number | 400 | 动画执行时间,单位:毫秒 | | stayTime | number | 1000 | 加载成功停留时间 | ### Action 类型定义 | 属性 | 类型 | 默认值 | 说明 | | :------------ | :---------------------------------------------------------------------------- | :----- | :-------------------------------------------------------------------- | | state | REFRESH_STATE &#124; LOAD_STATE | 0 | 状态枚举 | | startDistance | number | 30 | 下拉时的助跑距离,单位:px | | distance | number | 30 | 触发距离阀值,单位:px;下拉刷新阀值默认为 30px,上拉加载阀值默认为 0 | | render | (refreshState: REFRESH_STATE &#124; LOAD_STATE, percent: number) => ReactNode | - | 各状态渲染的回调函数 | | handler | () => void | - | 达到阀值后释放触发的回调函数 | ### REFRESH_STATE 枚举定义 | 枚举值 | 说明 | | :------ | :--------------------------- | | normal | 普通状态 | | pull | 下拉状态(未满足刷新条件) | | drop | 释放立即刷新(满足刷新条件) | | loading | 加载中 | | success | 加载成功 | | failure | 加载失败 | ### LOAD_STATE 枚举定义 | 枚举值 | 说明 | | :------- | :------- | | normal | 普通状态 | | abort | 终止状态 | | loading | 加载中 | | success | 加载成功 | | failure | 加载失败 | | complete | 加载完成 | ## CSS 变量 | 属性 | 默认值 | 说明 | | :------------------------- | :----------------------------- | :------------------------- | | --control-height | '50px' | 刷新节点、加载节点高度 | | --control-font-size | '14px' | 刷新节点、加载节点字体大小 | | --control-padding-vertical | '20px' | 垂直方向内边距大小 | | --control-text-color | 'var(--za-color-text-caption)' | 字体颜色 | | --control-icon-size | '22px' | 图标大小 |