Files
boluo-app-main/src/components/anchor-list/index.tsx

260 lines
7.7 KiB
TypeScript

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<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 [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 (
<>
<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} validator={validator} />
))}
<ListPlaceholder hasMore={hasMore} loadingMore={loadingMore} loadMoreError={loadMoreError} />
</List>
</PullRefresh>
<AgreementPopup
open={openAssignment}
onCancel={handleCancel}
onConfirm={handleConfirm}
needBindPhone={needLogin}
/>
{openLogin && <LoginDialog onCancel={handleLoginCancel} onSuccess={handleLoginSuccess} />}
</>
);
}
export default AnchorList;