| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- import { useCallback, useEffect, useRef, useState } from "react";
- import { HttpError } from "../../shared/api/errors";
- import type { PageResult } from "../../shared/api/types";
- import type { DocumentSummary, MainPlanBrief } from "../documents/documentTypes";
- import type { RemotePageState } from "../documents/documentState";
- import { attachmentApi } from "./attachmentApi";
- import type {
- AttachmentBindingSummary,
- AttachmentListQuery,
- MountedAttachmentQuery,
- } from "./attachmentTypes";
- import { useContentRevision } from "../../shared/state/contentRefresh";
- function toHttpError(error: unknown): HttpError {
- return error instanceof HttpError
- ? error
- : new HttpError({
- httpStatus: 0,
- code: "INTERNAL_ERROR",
- message: "请求失败",
- details: null,
- requestId: "unknown",
- cause: error,
- });
- }
- function usePage<T>(
- loader: (signal: AbortSignal) => Promise<PageResult<T>>,
- dependencies: readonly unknown[],
- debounceKey: string | undefined,
- enabled = true,
- debounceMs = 300,
- ): RemotePageState<T> {
- const [state, setState] = useState<RemotePageState<T>>({
- data: null,
- loading: true,
- error: null,
- retry: () => undefined,
- });
- const version = useRef(0);
- const previousDebounceKey = useRef(debounceKey);
- const [retryVersion, setRetryVersion] = useState(0);
- const retry = useCallback(() => setRetryVersion((value) => value + 1), []);
- useEffect(() => {
- if (!enabled) {
- setState((previous) => ({ ...previous, loading: false, error: null, retry }));
- return;
- }
- const current = ++version.current;
- const controller = new AbortController();
- const delay = previousDebounceKey.current === debounceKey ? 0 : debounceMs;
- previousDebounceKey.current = debounceKey;
- const timer = window.setTimeout(() => {
- setState((previous) => ({ ...previous, loading: true, error: null, retry }));
- void loader(controller.signal)
- .then((data) => {
- if (version.current === current) {
- setState({ data, loading: false, error: null, retry });
- }
- })
- .catch((error: unknown) => {
- if (version.current !== current || controller.signal.aborted) return;
- setState({ data: null, loading: false, error: toHttpError(error), retry });
- });
- }, delay);
- return () => {
- window.clearTimeout(timer);
- controller.abort();
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [...dependencies, enabled, retryVersion]);
- return { ...state, retry };
- }
- export function useAttachmentList(
- query: AttachmentListQuery,
- ): RemotePageState<DocumentSummary> {
- const revision = useContentRevision("attachments");
- return usePage(
- (signal) => attachmentApi.list(query, signal),
- [
- query.keyword,
- query.attachmentType,
- query.fileExtension,
- query.updatedFrom,
- query.updatedTo,
- query.sortBy,
- query.sortDirection,
- query.page,
- query.pageSize,
- revision,
- ],
- query.keyword,
- );
- }
- export function useMountedAttachments(
- mainPlanId: string | null,
- query: MountedAttachmentQuery,
- enabled = true,
- ): RemotePageState<AttachmentBindingSummary> {
- const documentRevision = useContentRevision("documents");
- const attachmentRevision = useContentRevision("attachments");
- return usePage(
- (signal) =>
- mainPlanId
- ? attachmentApi.mountedByMainPlan(mainPlanId, query, signal)
- : Promise.resolve({
- items: [],
- page: query.page ?? 1,
- pageSize: query.pageSize ?? 20,
- total: 0,
- totalPages: 0,
- }),
- [
- mainPlanId,
- query.keyword,
- query.attachmentType,
- query.updatedFrom,
- query.updatedTo,
- query.page,
- query.pageSize,
- documentRevision,
- attachmentRevision,
- ],
- query.keyword,
- enabled,
- );
- }
- export function useAttachmentMainPlans(id: string | null): {
- data: MainPlanBrief[] | null;
- loading: boolean;
- error: HttpError | null;
- } {
- const revision = useContentRevision("attachments");
- const [state, setState] = useState<{
- data: MainPlanBrief[] | null;
- loading: boolean;
- error: HttpError | null;
- }>({ data: null, loading: false, error: null });
- useEffect(() => {
- if (!id) {
- setState({ data: null, loading: false, error: null });
- return;
- }
- const controller = new AbortController();
- setState({ data: null, loading: true, error: null });
- void attachmentApi
- .mainPlans(id, controller.signal)
- .then((data) => setState({ data, loading: false, error: null }))
- .catch((error: unknown) => {
- if (!controller.signal.aborted) {
- setState({ data: null, loading: false, error: toHttpError(error) });
- }
- });
- return () => controller.abort();
- }, [id, revision]);
- return state;
- }
|