feat: first commit
This commit is contained in:
209
src/components/anchor-list/index.tsx
Normal file
209
src/components/anchor-list/index.tsx
Normal file
@ -0,0 +1,209 @@
|
||||
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 { EventName } from '@/constants/app';
|
||||
import { AnchorSortType } from '@/constants/material';
|
||||
import { AnchorInfo, GetAnchorListRequest, IAnchorFilters } from '@/types/material';
|
||||
import { logWithPrefix } from '@/utils/common';
|
||||
import { requestAnchorList as requestData } from '@/utils/material';
|
||||
|
||||
import './index.less';
|
||||
|
||||
interface IRequestProps extends Partial<GetAnchorListRequest> {
|
||||
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<AnchorInfo[]>([]);
|
||||
const currentPage = useRef<number>(FIRST_PAGE);
|
||||
const requestProps = useRef<IRequestProps>({});
|
||||
const prevRequestProps = useRef<IRequestProps>({});
|
||||
const onListEmptyRef = useRef(onListEmpty);
|
||||
|
||||
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]
|
||||
);
|
||||
|
||||
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 (
|
||||
<PullRefresh
|
||||
className={classNames(`${PREFIX}__pull-refresh`, className)}
|
||||
loading={refreshing}
|
||||
onRefresh={handleRefresh}
|
||||
disabled={refreshDisabled || !ready}
|
||||
>
|
||||
<List
|
||||
hasMore={hasMore}
|
||||
onLoad={handleLoadMore}
|
||||
loading={loadingMore || refreshing}
|
||||
disabled={loadMoreError || !ready}
|
||||
fixedHeight={typeof listHeight !== 'undefined'}
|
||||
style={listHeight ? { height: `${listHeight}px` } : undefined}
|
||||
>
|
||||
{dataList.map(item => (
|
||||
<AnchorCard data={item} jobId={jobId} key={item.id} />
|
||||
))}
|
||||
<ListPlaceholder hasMore={hasMore} loadingMore={loadingMore} loadMoreError={loadMoreError} />
|
||||
</List>
|
||||
</PullRefresh>
|
||||
);
|
||||
}
|
||||
|
||||
export default AnchorList;
|
||||
Reference in New Issue
Block a user