refactor(announcements,messaging,notifications): V1+V2 审计重构 — i18n 命名空间独立 + 通知标题 i18n 化 + 服务端过滤 + 编排下沉 + 表单错误展示 + 架构图同步
V1 改进(已完成): - P0-4/P1-4/P1-5: 通知组件和 CRUD Action 从 messaging 迁移至 notifications 模块 - P1-5: 新增 getMessagesPageData / getAdminAnnouncementsPageData 编排函数 - P1-6: announcements schema 添加 superRefine 条件校验 - P1-7: 新增 useMessageSearch hook(防抖 + 请求竞态取消)+ 客户端分页 UI - P1-9: deleteMessage 事务化 - P2-11: 全模块 trackEvent 埋点 - 全模块 i18n 接入 + Error Boundary + a11y 改进 V2 改进(本次完成): - V2-P0-1: 通知 i18n 命名空间独立(notifications.json),useTranslations 从 "messages" 切换到 "notifications" - V2-P0-2: 公告/消息通知标题 i18n 化,Server Action 中使用 getTranslations 生成通知标题 - V2-P1-1: AnnouncementList 纯服务端过滤,移除客户端 useState/useMemo - V2-P1-2: MessageList 客户端过滤仅在初始数据时执行,搜索结果由服务端按 tab 过滤 - V2-P1-3: 消息详情页编排下沉,新增 getMessageDetailPageData 编排函数 - V2-P1-4: 表单服务端校验错误展示(fieldErrors + aria-invalid) - V2-P2-1: 轮询间隔常量化(POLL_INTERVAL_MS) - V2-P2-2: 架构图同步(004 + 005)
This commit is contained in:
106
src/modules/messaging/hooks/use-message-search.ts
Normal file
106
src/modules/messaging/hooks/use-message-search.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
|
||||
import type { ActionState } from "@/shared/types/action-state"
|
||||
import type { Message, MessageType } from "../types"
|
||||
|
||||
type SearchMessagesResult = ActionState<{
|
||||
items: Message[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
totalPages: number
|
||||
}>
|
||||
|
||||
interface UseMessageSearchParams {
|
||||
/** 搜索动作(注入的接口,避免组件直接依赖具体 action) */
|
||||
searchAction: (params: {
|
||||
type: MessageType
|
||||
keyword: string
|
||||
page?: number
|
||||
pageSize?: number
|
||||
}) => Promise<SearchMessagesResult>
|
||||
/** 当前标签页 */
|
||||
tab: "inbox" | "sent"
|
||||
/** 防抖延迟(毫秒),默认 400 */
|
||||
debounceMs?: number
|
||||
}
|
||||
|
||||
interface UseMessageSearchResult {
|
||||
keyword: string
|
||||
setKeyword: (v: string) => void
|
||||
results: Message[] | null
|
||||
searching: boolean
|
||||
/** 当前 keyword 为空时为 true,表示应使用 prop 传入的初始消息 */
|
||||
isUsingInitial: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息搜索 hook:将搜索逻辑从 MessageList 组件抽离为独立 hook,
|
||||
* 提升可测试性与可复用性。支持防抖、请求取消、状态匹配。
|
||||
*
|
||||
* 使用方式:
|
||||
* const { keyword, setKeyword, results, searching } = useMessageSearch({
|
||||
* searchAction: getMessagesAction,
|
||||
* tab,
|
||||
* })
|
||||
*/
|
||||
export function useMessageSearch({
|
||||
searchAction,
|
||||
tab,
|
||||
debounceMs = 400,
|
||||
}: UseMessageSearchParams): UseMessageSearchResult {
|
||||
const [keyword, setKeyword] = useState("")
|
||||
const [results, setResults] = useState<Message[] | null>(null)
|
||||
const [searching, setSearching] = useState(false)
|
||||
const requestIdRef = useRef(0)
|
||||
|
||||
useEffect(() => {
|
||||
const kw = keyword.trim()
|
||||
if (kw.length === 0) {
|
||||
setResults(null)
|
||||
setSearching(false)
|
||||
return
|
||||
}
|
||||
|
||||
setSearching(true)
|
||||
const currentRequestId = ++requestIdRef.current
|
||||
const timer = setTimeout(async () => {
|
||||
// 在异步执行前检查是否已被后续请求取代
|
||||
if (currentRequestId !== requestIdRef.current) return
|
||||
|
||||
try {
|
||||
const res = await searchAction({ type: tab, keyword: kw })
|
||||
// 请求返回后再次检查,避免竞态
|
||||
if (currentRequestId !== requestIdRef.current) return
|
||||
if (res.success && res.data) {
|
||||
setResults(res.data.items)
|
||||
} else {
|
||||
setResults([])
|
||||
}
|
||||
} catch {
|
||||
if (currentRequestId !== requestIdRef.current) return
|
||||
setResults([])
|
||||
} finally {
|
||||
if (currentRequestId === requestIdRef.current) {
|
||||
setSearching(false)
|
||||
}
|
||||
}
|
||||
}, debounceMs)
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
}, [keyword, tab, searchAction, debounceMs])
|
||||
|
||||
const isUsingInitial = keyword.trim().length === 0
|
||||
|
||||
return {
|
||||
keyword,
|
||||
setKeyword,
|
||||
results,
|
||||
searching,
|
||||
isUsingInitial,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user