import classNames from 'classnames' import PropTypes, { InferProps } from 'prop-types' import React from 'react' import { ScrollView, View } from '@tarojs/components' import { CommonEvent, ITouchEvent } from '@tarojs/components/types/common' import Taro from '@tarojs/taro' import { delayQuerySelector, pxTransform, uuid } from '../../utils/utils'; import { CSSProperties } from 'react' import { AtToast } from 'taro-ui' import { List } from 'tea-component-mobile'; export interface AtComponent { className?: string customStyle?: string | CSSProperties } export interface Item { /** * 列表项内容 */ name: string [propName: string]: any } export interface AtIndexesProps extends AtComponent { /** * 是否开启跳转过渡动画 * @default false */ animation?: boolean /** * 右侧导航第一个名称 * @default Top */ topKey?: string /** * 是否切换 key 的震动 * **注意:** 只在微信小程序有效 * @default true */ isVibrate?: boolean /** * 是否用弹框显示当前 key * @default true */ isShowToast?: boolean /** * 列表 */ list: Array /** * 点击列表项触发事件 */ onClick?: (item: Item) => void /** * 获取跳转事件跳转到指定 key */ onScrollIntoView?: (fn: (key: string) => void) => void } export interface ListItem { /** * 列表标题 */ title: string /** * 右侧导航标题 */ key: string /** * 列表项 */ items: Array } export interface AtIndexesState { _scrollIntoView: string _scrollTop: number _tipText: string _isShowToast: boolean isWEB: boolean } const ENV = Taro.getEnv() export default class AtIndexes extends React.Component< AtIndexesProps, AtIndexesState > { public static defaultProps: AtIndexesProps public static propTypes: InferProps private menuHeight: number private startTop: number private itemHeight: number private currentIndex: number private listId: string private timeoutTimer: NodeJS.Timeout | number | undefined private listRef: any public constructor(props: AtIndexesProps) { super(props) this.state = { _scrollIntoView: '', _scrollTop: 0, _tipText: '', _isShowToast: false, isWEB: Taro.getEnv() === Taro.ENV_TYPE.WEB } // 右侧导航高度 this.menuHeight = 0 // 右侧导航距离顶部高度 this.startTop = 0 // 右侧导航元素高度 this.itemHeight = 0 // 当前索引 this.currentIndex = -1 this.listId = `list-${uuid()}` } private handleClick = (item: Item): void => { this.props.onClick && this.props.onClick(item) } private handleTouchMove = (event: ITouchEvent): void => { event.stopPropagation() event.preventDefault() const { list } = this.props const pageY = event.touches[0].pageY const index = Math.floor((pageY - this.startTop) / this.itemHeight) if (index >= 0 && index <= list.length && this.currentIndex !== index) { this.currentIndex = index const key = index > 0 ? list[index - 1].key : 'top' const touchView = `at-indexes__list-${key}` this.jumpTarget(touchView, index) } } private handleTouchEnd = (): void => { this.currentIndex = -1 } private jumpTarget(_scrollIntoView: string, idx: number): void { const { topKey = 'Top', list } = this.props const _tipText = idx === 0 ? topKey : list[idx - 1].key if (ENV === Taro.ENV_TYPE.WEB) { delayQuerySelector('.at-indexes', 0).then(rect => { const targetNode = this.listRef.querySelectorAll('.at-indexes__list')[idx - 1]; const targetOffsetTop = targetNode && targetNode.offsetTop || 0 const _scrollTop = targetOffsetTop - ( rect[0] && rect[0].top || 0) this.updateState({ _scrollTop, _scrollIntoView, _tipText }) }) return } this.updateState({ _scrollIntoView, _tipText }) } private __jumpTarget(key: string): void { const { list } = this.props // const index = _findIndex(list, ['key', key]) const index = list.findIndex(item => item.key === key) const targetView = `at-indexes__list-${key}` this.jumpTarget(targetView, index + 1) } private updateState(state: Partial): void { const { isShowToast, isVibrate } = this.props const { _scrollIntoView, _tipText, _scrollTop } = state // TODO: Fix dirty hack /* eslint-disable @typescript-eslint/no-non-null-assertion */ this.setState( { _scrollIntoView: _scrollIntoView!, _tipText: _tipText!, _scrollTop: _scrollTop!, _isShowToast: isShowToast! }, /* eslint-enable @typescript-eslint/no-non-null-assertion */ () => { clearTimeout(this.timeoutTimer as number) this.timeoutTimer = setTimeout(() => { this.setState({ _tipText: '', _isShowToast: false }) }, 3000) } ) if (isVibrate) { Taro.vibrateShort() } } private initData(): void { delayQuerySelector('.at-indexes__menu').then(rect => { const len = this.props.list.length if (rect && rect[0]) { this.menuHeight = rect[0].height this.startTop = rect[0].top this.itemHeight = Math.floor(this.menuHeight / (len + 1)) } }) } private handleScroll(e: CommonEvent): void { if (e && e.detail) { this.setState({ _scrollTop: e.detail.scrollTop }) } } public UNSAFE_componentWillReceiveProps(nextProps: AtIndexesProps): void { if (nextProps.list.length !== this.props.list.length) { this.initData() } } public componentDidMount(): void { if (ENV === Taro.ENV_TYPE.WEB) { this.listRef = document.getElementById(this.listId) } this.initData() } public UNSAFE_componentWillMount(): void { this.props.onScrollIntoView && this.props.onScrollIntoView(this.__jumpTarget.bind(this)) } public render(): JSX.Element { const { className, customStyle, animation, topKey, list } = this.props const { _scrollTop, _scrollIntoView, _tipText, _isShowToast, isWEB } = this.state const toastStyle = { minWidth: pxTransform(100) } const rootCls = classNames('at-indexes', className) const menuList = list.map((dataList, i) => { const { key } = dataList const targetView = `at-indexes__list-${key}` return ( {key} ) }) const indexesList = list.map(dataList => { return ( { Array.isArray(dataList.items) && dataList.items.length ? dataList.items.map((item, idx) => { return ( ) }) : null } ) }) // console.log('_scrollTop', _scrollTop) return ( {topKey} {menuList} {this.props.children} {indexesList} ) } } AtIndexes.propTypes = { customStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), className: PropTypes.oneOfType([PropTypes.array, PropTypes.string]), animation: PropTypes.bool, isVibrate: PropTypes.bool, isShowToast: PropTypes.bool, topKey: PropTypes.string, list: PropTypes.array, onClick: PropTypes.func, onScrollIntoView: PropTypes.func } AtIndexes.defaultProps = { customStyle: '', className: '', animation: false, topKey: 'Top', isVibrate: true, isShowToast: true, list: [] }