From 00ce8544453ae8476c9994473646c78744e42870 Mon Sep 17 00:00:00 2001 From: doublex001 <16157008+doublex001@user.noreply.gitee.com> Date: Mon, 31 Aug 2026 14:50:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(hooks):=20=E6=96=B0=E5=A2=9E=E9=9A=90?= =?UTF-8?q?=E7=A7=81=E7=9B=B8=E5=85=B3=20hooks=EF=BC=886=20=E4=B8=AA?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - usePrivacyDetailSetting:按 id 拉取隐私二级设置并切换开关 - useDndSetting:勿扰模式读取与更新 - useKeywordBlock:关键词屏蔽的读取与增删 - useAccountCancel:注销前置信息拉取与提交 - useDeleteAllData:可删除分类拉取与提交 - useLegalDocument:隐私政策 / 用户协议正文拉取 均基于 useAsyncData 封装,统一在 src/hooks/index.ts 导出 --- src/hooks/useAccountCancel.ts | 31 ++++++++++++++++++ src/hooks/useDeleteAllData.ts | 31 ++++++++++++++++++ src/hooks/useDndSetting.ts | 34 ++++++++++++++++++++ src/hooks/useKeywordBlock.ts | 47 ++++++++++++++++++++++++++++ src/hooks/useLegalDocument.ts | 24 ++++++++++++++ src/hooks/usePrivacyDetailSetting.ts | 37 ++++++++++++++++++++++ 6 files changed, 204 insertions(+) create mode 100644 src/hooks/useAccountCancel.ts create mode 100644 src/hooks/useDeleteAllData.ts create mode 100644 src/hooks/useDndSetting.ts create mode 100644 src/hooks/useKeywordBlock.ts create mode 100644 src/hooks/useLegalDocument.ts create mode 100644 src/hooks/usePrivacyDetailSetting.ts diff --git a/src/hooks/useAccountCancel.ts b/src/hooks/useAccountCancel.ts new file mode 100644 index 0000000..cead9df --- /dev/null +++ b/src/hooks/useAccountCancel.ts @@ -0,0 +1,31 @@ +/** + * 账号注销 hook,获取前置信息并提交注销申请。 + */ +import { useCallback } from 'react'; +import { fetchAccountCancel, submitAccountCancel } from '../api'; +import type { AccountCancelData } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export interface AccountCancelState { + data: AccountCancelData | null; + loading: boolean; + error: Error | null; + refresh: () => void; + submit: (reason: string) => Promise<{ success: boolean; message: string }>; +} + +export function useAccountCancel(): AccountCancelState { + const fetcher = useCallback(async (): Promise => fetchAccountCancel(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + const submit = useCallback(async (reason: string) => { + try { + return await submitAccountCancel(reason); + } catch (err) { + console.error('[useAccountCancel] submit error', err); + return { success: false, message: '提交失败,请稍后重试' }; + } + }, []); + + return { data: data ?? null, loading, error, refresh, submit }; +} diff --git a/src/hooks/useDeleteAllData.ts b/src/hooks/useDeleteAllData.ts new file mode 100644 index 0000000..0e89fc4 --- /dev/null +++ b/src/hooks/useDeleteAllData.ts @@ -0,0 +1,31 @@ +/** + * 删除全部数据 hook,获取可删除分类并提交删除申请。 + */ +import { useCallback } from 'react'; +import { fetchDeleteAllData, submitDeleteData } from '../api'; +import type { DeleteAllData } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export interface DeleteAllDataState { + data: DeleteAllData | null; + loading: boolean; + error: Error | null; + refresh: () => void; + submit: (categoryIds: string[]) => Promise<{ success: boolean; message: string }>; +} + +export function useDeleteAllData(): DeleteAllDataState { + const fetcher = useCallback(async (): Promise => fetchDeleteAllData(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + const submit = useCallback(async (categoryIds: string[]) => { + try { + return await submitDeleteData(categoryIds); + } catch (err) { + console.error('[useDeleteAllData] submit error', err); + return { success: false, message: '提交失败,请稍后重试' }; + } + }, []); + + return { data: data ?? null, loading, error, refresh, submit }; +} diff --git a/src/hooks/useDndSetting.ts b/src/hooks/useDndSetting.ts new file mode 100644 index 0000000..db54e28 --- /dev/null +++ b/src/hooks/useDndSetting.ts @@ -0,0 +1,34 @@ +/** + * 勿扰模式设置 hook。 + */ +import { useCallback } from 'react'; +import { fetchDndSetting, updateDndSetting } from '../api'; +import type { DndSetting } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export interface DndSettingState { + setting: DndSetting | null; + loading: boolean; + error: Error | null; + refresh: () => void; + update: (payload: Partial) => Promise; +} + +export function useDndSetting(): DndSettingState { + const fetcher = useCallback(async (): Promise => fetchDndSetting(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + const update = useCallback( + async (payload: Partial) => { + try { + await updateDndSetting(payload); + refresh(); + } catch (err) { + console.error('[useDndSetting] update error', err); + } + }, + [refresh], + ); + + return { setting: data ?? null, loading, error, refresh, update }; +} diff --git a/src/hooks/useKeywordBlock.ts b/src/hooks/useKeywordBlock.ts new file mode 100644 index 0000000..3381a94 --- /dev/null +++ b/src/hooks/useKeywordBlock.ts @@ -0,0 +1,47 @@ +/** + * 关键词屏蔽设置 hook,支持关键词的增删。 + */ +import { useCallback } from 'react'; +import { fetchKeywordBlock, addBlockKeyword, removeBlockKeyword } from '../api'; +import type { KeywordBlockSetting } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export interface KeywordBlockState { + setting: KeywordBlockSetting | null; + loading: boolean; + error: Error | null; + refresh: () => void; + addKeyword: (keyword: string) => Promise; + removeKeyword: (keyword: string) => Promise; +} + +export function useKeywordBlock(): KeywordBlockState { + const fetcher = useCallback(async (): Promise => fetchKeywordBlock(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + const addKeyword = useCallback( + async (keyword: string) => { + try { + await addBlockKeyword(keyword); + refresh(); + } catch (err) { + console.error('[useKeywordBlock] add error', err); + } + }, + [refresh], + ); + + const removeKeyword = useCallback( + async (keyword: string) => { + try { + await removeBlockKeyword(keyword); + refresh(); + } catch (err) { + console.error('[useKeywordBlock] remove error', err); + } + }, + [refresh], + ); + + return { setting: data ?? null, loading, error, refresh, addKeyword, removeKeyword }; +} diff --git a/src/hooks/useLegalDocument.ts b/src/hooks/useLegalDocument.ts new file mode 100644 index 0000000..fcc6a9b --- /dev/null +++ b/src/hooks/useLegalDocument.ts @@ -0,0 +1,24 @@ +/** + * 法律文档(隐私政策 / 用户协议)hook,按 id 拉取正文。 + */ +import { useCallback } from 'react'; +import { fetchLegalDocument } from '../api'; +import type { LegalDocument } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export interface LegalDocumentState { + doc: LegalDocument | null; + loading: boolean; + error: Error | null; + refresh: () => void; +} + +export function useLegalDocument(id: string): LegalDocumentState { + const fetcher = useCallback( + async (): Promise => fetchLegalDocument(id), + [id], + ); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + return { doc: data ?? null, loading, error, refresh }; +} diff --git a/src/hooks/usePrivacyDetailSetting.ts b/src/hooks/usePrivacyDetailSetting.ts new file mode 100644 index 0000000..0f4a60e --- /dev/null +++ b/src/hooks/usePrivacyDetailSetting.ts @@ -0,0 +1,37 @@ +/** + * 隐私二级设置(开关类子页)hook,按页面 id 拉取并切换开关。 + */ +import { useCallback } from 'react'; +import { fetchPrivacyDetailSetting, updatePrivacyDetailSetting } from '../api'; +import type { PrivacyDetailSetting } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export interface PrivacyDetailState { + detail: PrivacyDetailSetting | null; + loading: boolean; + error: Error | null; + refresh: () => void; + toggle: (optionId: string, enabled: boolean) => Promise; +} + +export function usePrivacyDetailSetting(id: string): PrivacyDetailState { + const fetcher = useCallback( + async (): Promise => fetchPrivacyDetailSetting(id), + [id], + ); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + const toggle = useCallback( + async (optionId: string, enabled: boolean) => { + try { + await updatePrivacyDetailSetting(id, optionId, enabled); + refresh(); + } catch (err) { + console.error('[usePrivacyDetailSetting] toggle error', err); + } + }, + [id, refresh], + ); + + return { detail: data ?? null, loading, error, refresh, toggle }; +}