import Taro from '@tarojs/taro'; import { List, PullRefresh } from '@taroify/core'; import classNames from 'classnames'; import { isEqual } from 'lodash-es'; import { useCallback, useEffect, useRef, useState } from 'react'; import AnchorCard from '@/components/anchor-card'; import ListPlaceholder from '@/components/list-placeholder'; import LoginDialog from '@/components/login-dialog'; import { EventName } from '@/constants/app'; import { AnchorSortType } from '@/constants/material'; import useUserInfo from '@/hooks/use-user-info'; import { AnchorInfo, GetAnchorListRequest, IAnchorFilters } from '@/types/material'; import { logWithPrefix } from '@/utils/common'; import { requestAnchorList as requestData } from '@/utils/material'; import { getAgreementSigned, isNeedLogin, setAgreementSigned } from '@/utils/user'; import { AgreementPopup } from '../agreement-popup'; import './index.less'; interface IRequestProps extends Partial { filters?: IAnchorFilters; } export interface IAnchorListProps extends IRequestProps { ready?: boolean; refreshDisabled?: boolean; listHeight?: number; className?: string; onListEmpty?: () => void; } const FIRST_PAGE = 0; const PAGE_SIZE = 10; const PREFIX = 'anchor-list'; const log = logWithPrefix(PREFIX); function AnchorList(props: IAnchorListProps) { const { className, listHeight, refreshDisabled, jobId, filters, cityCode = 'ALL', sortType = AnchorSortType.Recommend, latitude, longitude, ready, onListEmpty, } = props; const [refreshing, setRefreshing] = useState(false); const [hasMore, setHasMore] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const [loadMoreError, setLoadMoreError] = useState(false); const [dataList, setDataList] = useState([]); const currentPage = useRef(FIRST_PAGE); const requestProps = useRef({}); const prevRequestProps = useRef({}); const onListEmptyRef = useRef(onListEmpty); const [openLogin, setLoginOpen] = useState(false); const [openAssignment, setAssignmentOpen] = useState(false); const successCallback = useRef<() => void>(() => {}); const userInfo = useUserInfo(); const needLogin = isNeedLogin(userInfo); const handleRefresh = useCallback(async () => { log('start pull refresh'); try { setRefreshing(true); setLoadMoreError(false); const { page, hasMore: more, data: anchorResults } = await requestData({ ...requestProps.current, page: 1 }); setHasMore(more); setDataList(anchorResults); currentPage.current = page; !anchorResults.length && onListEmptyRef.current?.(); log('pull refresh success'); } catch (e) { setDataList([]); setHasMore(false); setLoadMoreError(true); currentPage.current = FIRST_PAGE; log('pull refresh failed'); } finally { setRefreshing(false); } }, []); const handleLoadMore = useCallback(async () => { log('start load more', hasMore); if (!hasMore) { return; } setLoadMoreError(false); setLoadingMore(true); try { const { page, hasMore: more, data: anchorResults, } = await requestData({ ...requestProps.current, page: currentPage.current + 1 }); setDataList([...dataList, ...anchorResults]); setHasMore(more); currentPage.current = page; log('load more success'); } catch (e) { setLoadMoreError(true); log('load more failed'); } finally { setLoadingMore(false); } }, [dataList, hasMore]); const handleReadMaterial = useCallback( (materialId: string) => { const index = dataList.findIndex(d => String(d.id) === materialId); if (index < 0) { return; } const material = dataList[index]; if (!material || material.isRead) { return; } log('auto mark read', materialId); dataList.splice(index, 1, { ...material, isRead: true }); setDataList([...dataList]); }, [dataList] ); const handleCancel = useCallback(() => { setAssignmentOpen(false); setAgreementSigned(false); }, []); const handleConfirm = useCallback(() => { setAssignmentOpen(false); setAgreementSigned(true); successCallback.current(); }, []); const handleLoginSuccess = useCallback(() => { setLoginOpen(false); successCallback.current(); }, []); const handleLoginCancel = useCallback(() => { setLoginOpen(false); }, []); const validator = (onSuccess: () => void) => { successCallback.current = onSuccess; if (!getAgreementSigned()) { setAssignmentOpen(true); } else if (needLogin) { setLoginOpen(true); } else { onSuccess(); } }; useEffect(() => { onListEmptyRef.current = onListEmpty; }, [onListEmpty]); useEffect(() => { log('request params changed'); requestProps.current = { ...filters, jobId, cityCode, sortType, latitude, longitude, pageSize: PAGE_SIZE, }; }, [jobId, filters, cityCode, sortType, latitude, longitude]); useEffect(() => { Taro.eventCenter.on(EventName.VIEW_MATERIAL_SUCCESS, handleReadMaterial); return () => { Taro.eventCenter.off(EventName.VIEW_MATERIAL_SUCCESS, handleReadMaterial); }; }, [handleReadMaterial]); // 初始化数据&配置变更后刷新数据 useEffect(() => { // 相比前一次可见时没有数据变更时,不再重新请求 if (isEqual(prevRequestProps.current, requestProps.current)) { log('visible/city changed, but request params not change, ignore'); return; } // 列表不可见时,先不做处理 if (!ready) { log('visible/city changed, but is not ready, only refresh list'); return; } prevRequestProps.current = requestProps.current; const refresh = async () => { log('visible/city changed, start refresh list data'); try { setDataList([]); setLoadingMore(true); setLoadMoreError(false); const { page, hasMore: more, data: anchorResults } = await requestData({ ...requestProps.current, page: 1 }); setHasMore(more); setDataList(anchorResults); currentPage.current = page; !anchorResults.length && onListEmptyRef.current?.(); } catch (e) { setDataList([]); setHasMore(false); setLoadMoreError(true); currentPage.current = FIRST_PAGE; } finally { log('visible/city changed, refresh list data end'); setLoadingMore(false); } }; refresh(); }, [jobId, ready, filters, cityCode, sortType]); return ( <> {dataList.map(item => ( ))} {openLogin && } ); } export default AnchorList;