feat(web): add authenticated app shell

This commit is contained in:
zl-q
2026-05-09 16:00:29 +08:00
parent c12320cb79
commit 5aa46d3311
73 changed files with 2571 additions and 250 deletions
+83
View File
@@ -0,0 +1,83 @@
import { useState } from 'react';
interface Props {
locale: string;
dashboard: { brandName: string; navHome: string; navStore: string; navDivination: string; navManual: string; navAuto: string; navHistory: string; navLanguage: string; navSettings: string; logout: string };
history: { title: string; statTotal: string; statFollow: string; statLatest: string; filters: string; filterAll: string; filterCareer: string; filterLove: string; filterWealth: string; noResults: string };
}
const MOCK_HISTORY = [
{ id: 1, question: '今年转岗是否合适?', date: '2025-05-08', category: '事业', hexagram: '天雷无妄', rating: '上上签', followUp: false },
{ id: 2, question: '最近感情是否能推进?', date: '2025-05-07', category: '感情', hexagram: '泽火革', rating: '中上签', followUp: true },
{ id: 3, question: '投资理财近期运势如何?', date: '2025-05-05', category: '财富', hexagram: '水地比', rating: '中签', followUp: false },
{ id: 4, question: '学业考试能否顺利通过?', date: '2025-05-03', category: '学业', hexagram: '山火贲', rating: '上签', followUp: true },
];
const CATEGORY_COLORS: Record<string, string> = {
'事业': 'bg-blue-50 text-blue-500', '感情': 'bg-pink-50 text-pink-500', '财富': 'bg-amber-50 text-amber-600', '学业': 'bg-green-50 text-green-600',
};
export default function HistoryListPage({ locale, history: h }: Props) {
const [selectedId, setSelectedId] = useState(1);
const [filter, setFilter] = useState('all');
const filters = [
{ id: 'all', label: h.filterAll },
{ id: 'career', label: h.filterCareer },
{ id: 'love', label: h.filterLove },
{ id: 'wealth', label: h.filterWealth },
];
return (
<div className="flex flex-col gap-5 min-h-full">
{/* Stats */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{[{ label: h.statTotal, value: '12' }, { label: h.statFollow, value: '3' }, { label: h.statLatest, value: '5/8' }].map((stat, i) => (
<div key={i} className="bg-white rounded-xl p-4 border border-slate-200 flex flex-col gap-1.5">
<p className="text-slate-400 text-xs">{stat.label}</p>
<p className="text-slate-900 text-xl font-bold">{stat.value}</p>
</div>
))}
</div>
{/* Main: List + Filters */}
<div className="flex flex-col lg:flex-row gap-5 flex-1 min-h-0">
<div className="flex-1 bg-white rounded-2xl p-[18px] border border-slate-200 flex flex-col gap-3 overflow-y-auto">
<div className="flex items-center justify-between">
<h3 className="text-slate-900 text-sm font-bold">{h.title}</h3>
</div>
{MOCK_HISTORY.map(item => (
<a key={item.id} href={`/${locale}/history/${item.id}`}
onClick={(e) => { e.preventDefault(); setSelectedId(item.id); }}
className={`flex items-center gap-3.5 rounded-xl p-4 cursor-pointer transition-colors border ${selectedId === item.id ? 'bg-violet-50 border-violet-400' : 'bg-white border-slate-200 hover:bg-slate-50'}`}>
<div className="w-10 h-10 rounded-[10px] bg-blue-50 flex items-center justify-center shrink-0">
<span className="text-blue-400 text-lg"></span>
</div>
<div className="flex-1 min-w-0">
<p className="text-slate-900 text-sm font-medium truncate">{item.question}</p>
<div className="flex items-center gap-2 mt-1">
<span className={`px-2 py-0.5 rounded-md text-xs font-medium ${CATEGORY_COLORS[item.category] || 'bg-slate-100 text-slate-500'}`}>{item.category}</span>
<span className="px-2 py-0.5 rounded-md bg-violet-50 text-violet-600 text-xs font-medium">{item.hexagram}</span>
</div>
</div>
<span className="text-slate-400 text-xs shrink-0">{item.date}</span>
</a>
))}
</div>
{/* Side: Filters */}
<div className="w-full lg:w-[300px] flex flex-col gap-4 shrink-0">
<div className="bg-white rounded-2xl p-[18px] border border-slate-200 flex flex-col gap-2.5">
<h3 className="text-slate-900 text-sm font-bold">{h.filters}</h3>
{filters.map(f => (
<button key={f.id} onClick={() => setFilter(f.id)}
className={`px-3 py-2 rounded-lg text-sm text-left transition-colors ${filter === f.id ? 'bg-violet-50 text-violet-600 font-medium' : 'text-slate-500 hover:bg-slate-50'}`}>
{f.label}
</button>
))}
</div>
</div>
</div>
</div>
);
}