diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue index ca18e21..77672c0 100644 --- a/frontend/src/views/Home.vue +++ b/frontend/src/views/Home.vue @@ -84,6 +84,79 @@ + + + + + +
+ + +
+
+
+ + {{ username }} +
+ + + + + +
+ +
+ +
+ +
+ + +
+ +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+ +
+
@@ -95,8 +168,10 @@ import { InfoCircleOutlined, CalendarOutlined, ClockCircleOutlined, + UserOutlined, } from '@ant-design/icons-vue' -import { Service } from '@/api' +import { Service } from '@/api/services/Service' +import dayjs from 'dayjs' interface ActivityInfo { Tip: string @@ -114,9 +189,22 @@ interface ActivityItem { Activity: ActivityInfo } +interface ProxyInfo { + LastProxyDate: string + ProxyTimes: number + ErrorTimes: number + ErrorInfo: Record +} + +interface ApiResponse { + Stage: ActivityItem[] + Proxy: Record +} + const loading = ref(false) const error = ref('') const activityData = ref([]) +const proxyData = ref>({}) // 获取当前活动信息 const currentActivity = computed(() => { @@ -124,6 +212,11 @@ const currentActivity = computed(() => { return activityData.value[0]?.Activity }) +const formatProxyDisplay = (dateStr: string) => { + const ts = getProxyTimestamp(dateStr) + return dayjs(ts).format('YYYY-MM-DD HH:mm:ss') // 需要别的格式可改这里 +} + // 格式化时间显示 - 直接使用给定时间,不进行时区转换 const formatTime = (timeString: string, timeZone: number) => { try { @@ -180,6 +273,32 @@ const getCountdownStyle = (expireTime: string) => { } } +const getProxyTimestamp = (dateStr: string) => { + if (!dateStr) return Date.now() + + // 1) 先尝试解析中文格式:2025年08月15日 14:01:02 + // 捕获:年、月、日、时、分、秒 + const m = dateStr.match( + /(\d{4})[年/](\d{1,2})[月/](\d{1,2})[日T\s]+(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?/ + ) + if (m) { + const [, y, mo, d, h, mi, s] = m + const ts = new Date( + Number(y), + Number(mo) - 1, + Number(d), + Number(h), + Number(mi), + Number(s ?? 0) + ).getTime() + if (!Number.isNaN(ts)) return ts + } + + // 2) 兜底:尝试让浏览器自己解析 + const t = new Date(dateStr).getTime() + return Number.isNaN(t) ? Date.now() : t +} + // 倒计时结束回调 const onCountdownFinish = () => { message.warning('活动已结束') @@ -207,13 +326,19 @@ const fetchActivityData = async () => { try { const response = await Service.addOverviewApiInfoGetOverviewPost() - if (response.code === 200 && response.data?.ALL) { - activityData.value = response.data.ALL + if (response.code === 200) { + const data = response.data as ApiResponse + if (data.Stage) { + activityData.value = data.Stage + } + if (data.Proxy) { + proxyData.value = data.Proxy + } } else { - error.value = response.message || '获取活动数据失败' + error.value = response.message || '获取数据失败' } } catch (err) { - console.error('获取活动数据失败:', err) + console.error('获取数据失败:', err) error.value = '网络请求失败,请检查连接' } finally { loading.value = false @@ -227,6 +352,12 @@ const refreshActivity = async () => { } } +// 获取代理状态颜色 +const getProxyStatusColor = () => { + const hasError = Object.values(proxyData.value).some(proxy => proxy.ErrorTimes > 0) + return hasError ? 'error' : 'success' +} + const greeting = computed(() => { const hour = new Date().getHours() if (hour >= 5 && hour < 11) { @@ -313,11 +444,6 @@ onMounted(() => { flex-wrap: wrap; } -.activity-icon { - font-size: 16px; - color: var(--ant-color-primary); -} - .activity-title { font-size: 18px; font-weight: 600; @@ -328,19 +454,6 @@ onMounted(() => { font-size: 12px; } -.activity-time { - display: flex; - flex-direction: column; - gap: 8px; -} - -.time-item { - display: flex; - align-items: center; - gap: 6px; - font-size: 14px; -} - .time-icon { font-size: 14px; color: var(--ant-color-text-secondary); @@ -466,6 +579,99 @@ onMounted(() => { } } +/* 代理状态样式 */ +.proxy-card { + margin-bottom: 24px; +} + +.proxy-card :deep(.ant-card-head-title) { + font-size: 18px; + font-weight: 600; +} + +.proxy-list { + display: flex; + flex-direction: column; + gap: 16px; +} + +.proxy-item { + padding: 16px; + background: var(--ant-color-bg-container); + border: 1px solid var(--ant-color-border); + border-radius: 8px; + transition: all 0.2s ease; +} + +.proxy-item:hover { + border-color: var(--ant-color-primary); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.proxy-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; +} + +.proxy-username { + display: flex; + align-items: center; + gap: 8px; +} + +.user-icon { + font-size: 16px; + color: var(--ant-color-primary); +} + +.username { + font-size: 16px; + font-weight: 600; + color: var(--ant-color-text); +} + +.proxy-status { + flex-shrink: 0; +} + +.proxy-stats { + display: flex; + flex-wrap: wrap; + gap: 16px; + margin-bottom: 12px; +} + +.stat-item { + flex: 1; + min-width: 0; +} + +.stat-item.full-width { + flex: 0 0 100%; /* 占满整行 */ +} + +.stat-item.half-width { + flex: 0 0 calc(50% - 8px); /* 每个占一半宽度,减去间距 */ +} + +.stat-item { + flex: 1; + min-width: 0; +} + +/* 小屏时自动折行成两列或一列 */ +@media (max-width: 768px) { + .proxy-stats { + flex-wrap: wrap; + } + + .stat-item { + flex: 1 1 100%; + } +} + @media (max-width: 768px) { .page-container { padding: 16px; @@ -484,5 +690,19 @@ onMounted(() => { height: 40px; margin-right: 8px; } + + .proxy-item { + padding: 12px; + } + + .proxy-header { + flex-direction: column; + align-items: flex-start; + gap: 8px; + } + + .proxy-stats :deep(.ant-col) { + margin-bottom: 8px; + } } diff --git a/frontend/src/views/Plans.vue b/frontend/src/views/Plans.vue index 4ea0485..d70cab1 100644 --- a/frontend/src/views/Plans.vue +++ b/frontend/src/views/Plans.vue @@ -894,4 +894,12 @@ onMounted(() => { justify-content: center; align-items: center; } + +.empty-content-fancy h2 { + font-size: 26px; + font-weight: 700; + margin: 0 0 12px 0; + letter-spacing: 1px; + color: var(--ant-color-text); +}