import { RefreshRounded, StorageOutlined } from '@mui/icons-material' import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, Divider, IconButton, LinearProgress, List, ListItem, ListItemText, Typography, alpha, styled, } from '@mui/material' import { useLockFn } from 'ahooks' import dayjs from 'dayjs' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { updateProxyProvider } from 'tauri-plugin-mihomo-api' import { useAppRefreshers, useProxiesData } from '@/providers/app-data-context' import { showNotice } from '@/services/notice-service' import parseTraffic from '@/utils/parse-traffic' // 样式化组件 - 类型框 const TypeBox = styled(Box)<{ component?: React.ElementType }>(({ theme }) => ({ display: 'inline-block', border: '1px solid #ccc', borderColor: alpha(theme.palette.secondary.main, 0.5), color: alpha(theme.palette.secondary.main, 0.8), borderRadius: 4, fontSize: 10, marginRight: '4px', padding: '0 2px', lineHeight: 1.25, })) // 解析过期时间 const parseExpire = (expire?: number) => { if (!expire) return '-' return dayjs(expire * 1000).format('YYYY-MM-DD') } export const ProviderButton = () => { const { t } = useTranslation() const [open, setOpen] = useState(false) const { proxyView } = useProxiesData() const { refreshProxy } = useAppRefreshers() const [updating, setUpdating] = useState>({}) const providers = proxyView?.providers ?? [] const providerUnavailable = proxyView?.providerState === 'unavailable' // 更新单个代理提供者 const updateProvider = useLockFn(async (name: string) => { try { // 设置更新状态 setUpdating((prev) => ({ ...prev, [name]: true })) await updateProxyProvider(name) // 刷新数据 await refreshProxy() showNotice.success( 'proxies.feedback.notifications.provider.updateSuccess', { name, }, ) } catch (err) { showNotice.error('proxies.feedback.notifications.provider.updateFailed', { name, message: String(err), }) } finally { // 清除更新状态 setUpdating((prev) => ({ ...prev, [name]: false })) } }) // 更新所有代理提供者 const updateAllProviders = useLockFn(async () => { try { // 获取所有provider的名称 const allProviders = providers.map(({ name }) => name) if (allProviders.length === 0) { showNotice.info('proxies.feedback.notifications.provider.none') return } // 设置所有provider为更新中状态 const newUpdating = allProviders.reduce( (acc, key) => { acc[key] = true return acc }, {} as Record, ) setUpdating(newUpdating) // 改为串行逐个更新所有provider for (const name of allProviders) { try { await updateProxyProvider(name) // 每个更新完成后更新状态 setUpdating((prev) => ({ ...prev, [name]: false })) } catch (err) { console.error(`更新 ${name} 失败`, err) // 继续执行下一个,不中断整体流程 } } // 刷新数据 await refreshProxy() showNotice.success('proxies.feedback.notifications.provider.allUpdated') } catch (err) { showNotice.error('proxies.feedback.notifications.provider.genericError', { message: String(err), }) } finally { // 清除所有更新状态 setUpdating({}) } }) const handleClose = () => { setOpen(false) } if (providers.length === 0 && !providerUnavailable) return null return ( <> {t('proxies.page.provider.title')} {providers.map((provider) => { const key = provider.name const updatedAt = provider.updatedAt ? dayjs(provider.updatedAt) : null const isUpdating = updating[key] // 订阅信息 const sub = provider.subscriptionInfo const hasSubInfo = !!sub const upload = sub?.upload || 0 const download = sub?.download || 0 const total = sub?.total || 0 const expire = sub?.expire || 0 // 流量使用进度 const progress = total > 0 ? Math.min( Math.round(((download + upload) * 100) / total) + 1, 100, ) : 0 return ( { const bgcolor = mode === 'light' ? '#ffffff' : '#24252f' const hoverColor = mode === 'light' ? alpha(primary.main, 0.1) : alpha(primary.main, 0.2) return { backgroundColor: bgcolor, '&:hover': { backgroundColor: hoverColor, }, } }, ]} > {key} {provider.proxyRecordIds.length} {provider.vehicleType} {t('shared.labels.updateAt')}: {updatedAt?.fromNow() ?? '-'} } secondary={ <> {/* 订阅信息 */} {hasSubInfo && ( <> {parseTraffic(upload + download)} /{' '} {parseTraffic(total)} {parseExpire(expire)} {/* 进度条 */} 0 ? 1 : 0, }} /> )} } /> { updateProvider(key) }} disabled={isUpdating} sx={{ animation: isUpdating ? 'spin 1s linear infinite' : 'none', '@keyframes spin': { '0%': { transform: 'rotate(0deg)' }, '100%': { transform: 'rotate(360deg)' }, }, }} title={t('proxies.page.provider.actions.update')} aria-label={t('proxies.page.provider.actions.update')} > ) })} ) }