fix+polish(frontend): 预测弹窗打磨 + 全量语义色替换 + 前台交互小项
预测弹窗: - 修复模板字符串直写 JSX,反引号原样显示在结果面板的 bug - 入场动画(遮罩淡入+面板上浮);初始聚焦/Tab 焦点陷阱/关闭还焦; 打开时锁定背景滚动 - 「关闭窗口即取消」→ 如实描述:前端仅停止进度显示,后台任务 可能仍在执行并消耗额度 语义色:全部 emerald/amber/rose 裸色替换为 ok/warn/bad token (Standings 分区与走势、KeyRing、定时任务、任务状态点、 数据完整性进度条、预测命中标记、净胜球)。 前台交互: - 空态跳后台 a[href] → Link,不再整页刷新 - 联赛 tab 溢出右缘渐隐提示 + aria-current - 状态行右侧计数组加细线分隔,小屏 wrap 后层级仍清晰
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
import { useEffect, useState, useCallback } from 'react'
|
||||
import BackTop from '../components/BackTop'
|
||||
import { fetchStandings } from '../admin/dal'
|
||||
import type { StandingsLeague, StandingRow } from '../admin/dal'
|
||||
import { useLeagues } from './matches/hooks/useLeagues'
|
||||
@@ -13,24 +14,24 @@ import { Spinner } from '../admin/components'
|
||||
|
||||
const ZONE_META: Record<string, { label: string; cls: string }> = {
|
||||
// 欧战资格
|
||||
'Champions League': { label: '欧冠区', cls: 'bg-emerald-100 text-emerald-700' },
|
||||
'Champions League Qualification': { label: '欧冠资格', cls: 'bg-emerald-100 text-emerald-700' },
|
||||
'Europa League': { label: '欧联区', cls: 'bg-amber-100 text-amber-700' },
|
||||
'Champions League': { label: '欧冠区', cls: 'bg-ok-100 text-ok-700' },
|
||||
'Champions League Qualification': { label: '欧冠资格', cls: 'bg-ok-100 text-ok-700' },
|
||||
'Europa League': { label: '欧联区', cls: 'bg-warn-100 text-warn-700' },
|
||||
'Conference League': { label: '欧协杯', cls: 'bg-sky-100 text-sky-700' },
|
||||
'Conference League Qualification': { label: '欧协杯', cls: 'bg-sky-100 text-sky-700' },
|
||||
'Europa Conference League': { label: '欧协杯', cls: 'bg-sky-100 text-sky-700' },
|
||||
'Europa Conference League Qualification': { label: '欧协杯', cls: 'bg-sky-100 text-sky-700' },
|
||||
// 升级
|
||||
'Championship': { label: '升级区', cls: 'bg-emerald-100 text-emerald-700' },
|
||||
'Promotion': { label: '升级区', cls: 'bg-emerald-100 text-emerald-700' },
|
||||
'Promotion Group': { label: '升级组', cls: 'bg-emerald-100 text-emerald-700' },
|
||||
'Championship': { label: '升级区', cls: 'bg-ok-100 text-ok-700' },
|
||||
'Promotion': { label: '升级区', cls: 'bg-ok-100 text-ok-700' },
|
||||
'Promotion Group': { label: '升级组', cls: 'bg-ok-100 text-ok-700' },
|
||||
// 降级
|
||||
'Relegation': { label: '降级区', cls: 'bg-rose-100 text-rose-700' },
|
||||
'Relegation': { label: '降级区', cls: 'bg-bad-100 text-bad-700' },
|
||||
'Relegation Playoffs': { label: '降级附加赛', cls: 'bg-orange-100 text-orange-700' },
|
||||
'Relegation Group': { label: '降级组', cls: 'bg-rose-100 text-rose-700' },
|
||||
'Relegation Group': { label: '降级组', cls: 'bg-bad-100 text-bad-700' },
|
||||
// 附加赛
|
||||
'Playoffs': { label: '附加赛', cls: 'bg-amber-100 text-amber-700' },
|
||||
'Championship Playoffs': { label: '升级附加赛', cls: 'bg-amber-100 text-amber-700' },
|
||||
'Playoffs': { label: '附加赛', cls: 'bg-warn-100 text-warn-700' },
|
||||
'Championship Playoffs': { label: '升级附加赛', cls: 'bg-warn-100 text-warn-700' },
|
||||
'Qualification Playoffs': { label: '资格附加赛', cls: 'bg-sky-100 text-sky-700' },
|
||||
'Qualification': { label: '资格赛', cls: 'bg-sky-100 text-sky-700' },
|
||||
}
|
||||
@@ -44,7 +45,7 @@ function zoneBadge(zone?: string | null) {
|
||||
/** 近期走势串(W/D/L) → 彩色圆点 */
|
||||
function FormDots({ form }: { form?: string | null }) {
|
||||
if (!form) return <span className="text-2xs text-ink-400">—</span>
|
||||
const colorMap: Record<string, string> = { W: 'bg-emerald-500', D: 'bg-ink-300', L: 'bg-rose-500' }
|
||||
const colorMap: Record<string, string> = { W: 'bg-ok-500', D: 'bg-ink-300', L: 'bg-bad-500' }
|
||||
return (
|
||||
<span className="inline-flex gap-0.5">
|
||||
{form.slice(0, 5).split('').map((c, i) => (
|
||||
@@ -62,17 +63,6 @@ export default function StandingsPage() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [switching, setSwitching] = useState(false) // 切换联赛中
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [showBackTop, setShowBackTop] = useState(false) // 回到顶部按钮显示态
|
||||
|
||||
// 监听滚动,超过 300px 显示回到顶部按钮
|
||||
useEffect(() => {
|
||||
const handleScroll = () => setShowBackTop(window.scrollY > 300)
|
||||
window.addEventListener('scroll', handleScroll, { passive: true })
|
||||
return () => window.removeEventListener('scroll', handleScroll)
|
||||
}, [])
|
||||
|
||||
const scrollToTop = () => window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
|
||||
const load = useCallback(async (code?: string) => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
@@ -134,7 +124,7 @@ export default function StandingsPage() {
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="border border-rose-300 bg-rose-50 px-4 py-3 text-sm text-rose-700">
|
||||
<div className="border border-bad-300 bg-bad-50 px-4 py-3 text-sm text-bad-700">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@@ -202,7 +192,7 @@ export default function StandingsPage() {
|
||||
<td className="text-center py-2 text-ink-500">{r.drawn}</td>
|
||||
<td className="text-center py-2 text-ink-500">{r.lost}</td>
|
||||
<td className="text-center py-2 text-ink-500">{r.goals_for}/{r.goals_against}</td>
|
||||
<td className={`text-center py-2 ${r.goal_diff > 0 ? 'text-emerald-600' : r.goal_diff < 0 ? 'text-rose-600' : 'text-ink-500'}`}>
|
||||
<td className={`text-center py-2 ${r.goal_diff > 0 ? 'text-ok-600' : r.goal_diff < 0 ? 'text-bad-600' : 'text-ink-500'}`}>
|
||||
{r.goal_diff > 0 ? `+${r.goal_diff}` : r.goal_diff}
|
||||
</td>
|
||||
<td className="text-center py-2 font-bold text-ink-900">{r.points}</td>
|
||||
@@ -221,18 +211,8 @@ export default function StandingsPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 回到顶部按钮 */}
|
||||
<button
|
||||
onClick={scrollToTop}
|
||||
className={`fixed bottom-6 right-6 z-40 flex h-10 w-10 items-center justify-center rounded-full border border-ink-200 bg-paper-50 text-ink-600 shadow-lg transition-all duration-300 hover:border-ink-400 hover:text-ink-900 ${
|
||||
showBackTop ? 'translate-y-0 opacity-100' : 'translate-y-4 opacity-0 pointer-events-none'
|
||||
}`}
|
||||
aria-label="回到顶部"
|
||||
>
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 15l7-7 7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
{/* 回到顶部(共享组件,方角纸片风) */}
|
||||
<BackTop />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user