| 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 |