返回 AiToEarn
1 /**
2 * ErrorSummary - 错误汇总组件
3 *
4 * 功能:
5 * - 在发布弹框中集中展示所有选中账号的校验错误和警告信息
6 * - 支持显示每个账号的所有错误和警告
7 * - 支持收起/展开
8 * - 点击账号可跳转到该账号的参数设置
9 */
10
11 'use client'
12
13 import type {
14 ErrPubParamsItem,
15 ErrPubParamsMapType,
16 } from '@/components/PublishDialog/hooks/usePubParamsVerify'
17 import type { PubItem } from '@/components/PublishDialog/publishDialog.type'
18 import { AlertTriangle, ChevronDown, ChevronRight, Info } from 'lucide-react'
19 import { memo, useMemo, useState } from 'react'
20 import { useTransClient } from '@/app/i18n/client'
21 import AvatarPlat from '@/components/common/AvatarPlat'
22 import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
23 import { usePlatformInfoMap } from '@/hooks/usePlatformMetadata'
24 import { cn } from '@/utils/className'
25
26 export interface IErrorSummaryProps {
27 // 选中的发布列表
28 pubListChoosed: PubItem[]
29 // 错误参数映射
30 errParamsMap?: ErrPubParamsMapType
31 // 警告参数映射
32 warningParamsMap?: ErrPubParamsMapType
33 // 点击账号时的回调
34 onAccountClick?: (accountId: string) => void
35 }
36
37 // 单个账号的问题项
38 interface AccountIssue {
39 pubItem: PubItem
40 errors: string[]
41 warnings: string[]
42 }
43
44 const ErrorSummary = memo(
45 ({ pubListChoosed, errParamsMap, warningParamsMap, onAccountClick }: IErrorSummaryProps) => {
46 const { t } = useTransClient('publish')
47 const [isOpen, setIsOpen] = useState(false)
48 const platformInfoMap = usePlatformInfoMap()
49
50 // 筛选出有错误或警告的账号列表
51 const accountsWithIssues = useMemo(() => {
52 const issues: AccountIssue[] = []
53
54 for (const pubItem of pubListChoosed) {
55 const accountId = pubItem.account.id
56 const errorItem = errParamsMap?.get(accountId)
57 const warningItem = warningParamsMap?.get(accountId)
58
59 // 获取所有错误消息(优先使用 parErrMsgs 数组,兼容旧版 parErrMsg)
60 const errors = getMessages(errorItem)
61 const warnings = getMessages(warningItem)
62
63 // 只要有错误或警告就加入列表
64 if (errors.length > 0 || warnings.length > 0) {
65 issues.push({
66 pubItem,
67 errors,
68 warnings,
69 })
70 }
71 }
72
73 return issues
74 }, [pubListChoosed, errParamsMap, warningParamsMap])
75
76 // 如果没有问题,不显示组件
77 if (accountsWithIssues.length === 0) {
78 return null
79 }
80
81 return (
82 <Collapsible open={isOpen} onOpenChange={setIsOpen} className="mt-4">
83 {/* 标题栏 - 可点击收起/展开 */}
84 <CollapsibleTrigger className="flex w-full items-center gap-2 p-3 rounded-t-md bg-yellow-50 dark:bg-yellow-950/30 border border-yellow-200 dark:border-yellow-800 hover:bg-yellow-100 dark:hover:bg-yellow-950/50 transition-colors cursor-pointer">
85 {isOpen ? (
86 <ChevronDown className="h-4 w-4 text-yellow-600 dark:text-yellow-500 shrink-0" />
87 ) : (
88 <ChevronRight className="h-4 w-4 text-yellow-600 dark:text-yellow-500 shrink-0" />
89 )}
90 <AlertTriangle className="h-4 w-4 text-yellow-600 dark:text-yellow-500 shrink-0" />
91 <span className="text-sm font-medium text-yellow-800 dark:text-yellow-200">
92 {t('errorSummary.title', { count: accountsWithIssues.length })}
93 </span>
94 </CollapsibleTrigger>
95
96 {/* 账号问题列表 */}
97 <CollapsibleContent className="border border-t-0 border-yellow-200 dark:border-yellow-800 rounded-b-md overflow-hidden">
98 <div className="divide-y divide-yellow-200 dark:divide-yellow-800">
99 {accountsWithIssues.map(({ pubItem, errors, warnings }) => {
100 const platConfig = platformInfoMap.get(pubItem.account.type)
101
102 return (
103 <div
104 key={pubItem.account.id}
105 className={cn(
106 'p-3 bg-yellow-50/50 dark:bg-yellow-950/20',
107 onAccountClick
108 && 'cursor-pointer hover:bg-yellow-100/50 dark:hover:bg-yellow-950/40 transition-colors',
109 )}
110 onClick={() => onAccountClick?.(pubItem.account.id)}
111 >
112 {/* 账号信息 */}
113 <div className="flex items-center gap-2 mb-2">
114 <AvatarPlat account={pubItem.account} size="small" />
115 <span className="text-sm font-medium text-yellow-800 dark:text-yellow-200 truncate">
116 {pubItem.account.nickname}
117 </span>
118 {platConfig && (
119 <span className="text-xs text-yellow-600 dark:text-yellow-400 shrink-0">
120 {platConfig.name}
121 </span>
122 )}
123 </div>
124
125 {/* 错误信息列表 - 黄色 */}
126 {errors.map((errMsg, index) => (
127 <div key={`error-${index}`} className="ml-8 flex items-start gap-1.5 mb-1">
128 <AlertTriangle className="h-3.5 w-3.5 text-yellow-600 dark:text-yellow-500 shrink-0 mt-0.5" />
129 <span className="text-xs text-yellow-700 dark:text-yellow-300">{errMsg}</span>
130 </div>
131 ))}
132
133 {/* 警告信息列表 - 琥珀/橙色 */}
134 {warnings.map((warnMsg, index) => (
135 <div
136 key={`warning-${index}`}
137 className="ml-8 flex items-start gap-1.5 mb-1 last:mb-0"
138 >
139 <Info className="h-3.5 w-3.5 text-amber-600 dark:text-amber-500 shrink-0 mt-0.5" />
140 <span className="text-xs text-amber-700 dark:text-amber-400">{warnMsg}</span>
141 </div>
142 ))}
143 </div>
144 )
145 })}
146 </div>
147 </CollapsibleContent>
148 </Collapsible>
149 )
150 },
151 )
152
153 /**
154 * 从 ErrPubParamsItem 中获取所有消息
155 * 优先使用 parErrMsgs 数组,兼容旧版 parErrMsg
156 */
157 function getMessages(item?: ErrPubParamsItem): string[] {
158 if (!item)
159 return []
160
161 // 优先使用 parErrMsgs 数组
162 if (item.parErrMsgs && item.parErrMsgs.length > 0) {
163 return item.parErrMsgs
164 }
165
166 // 兼容旧版 parErrMsg
167 if (item.parErrMsg) {
168 return [item.parErrMsg]
169 }
170
171 return []
172 }
173
174 ErrorSummary.displayName = 'ErrorSummary'
175
176 export default ErrorSummary
177
177 lines Plain Text