feat: complete frontend redesign — all 6 pages implemented per prototype specs

Fix UI components (Badge, Button, Card, Input, ProgressBar, ScoreBar,
ChatPanel) to use CSS variables instead of old theme object pattern.
Clean up barrel exports (common/index.ts, layout/index.ts) and remove
stale router/tabs import from shell-config.ts. Build: tsc zero errors,
Vite production build succeeds (1760 modules, 270 kB JS, 22 kB CSS).
This commit is contained in:
2026-06-03 18:42:42 +08:00
parent 6414d67b3b
commit bc8ccc1143
10 changed files with 92 additions and 107 deletions

View File

@@ -1,3 +1 @@
export { TLogo } from './TLogo';
export { ThemeToggle } from './ThemeToggle';
export { TPattern } from './TPattern';
// common UI helpers — add exports here as new shared components are created

View File

@@ -1,4 +1,3 @@
export { AppShell } from './AppShell';
export { ContentLayout } from './ContentLayout';
export { FooterLayout } from './FooterLayout';
export { HeaderLayout } from './HeaderLayout';
export { Sidebar } from './Sidebar';
export { Topbar } from './Topbar';

View File

@@ -1,5 +1,3 @@
import { appTabs } from '../../router/tabs';
export const shellFrameClassName = 'mx-auto w-full max-w-[1680px] px-8';
export const shellMeta = {
@@ -8,5 +6,3 @@ export const shellMeta = {
status: 'ONLINE',
surface: 'Desktop Web',
} as const;
export const shellModuleSummary = appTabs.map((tab) => tab.label).join(' / ');

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { useTheme } from '../../contexts';
interface BadgeProps {
children: React.ReactNode;
@@ -7,32 +6,29 @@ interface BadgeProps {
size?: 'sm' | 'md';
}
const colorMap: Record<string, { bg: string; text: string }> = {
accent: { bg: 'var(--accent)', text: '#fff' },
green: { bg: 'var(--success)', text: '#fff' },
orange: { bg: 'var(--warn)', text: '#fff' },
red: { bg: 'var(--danger)', text: '#fff' },
};
export const Badge: React.FC<BadgeProps> = ({
children,
color = 'accent',
size = 'sm',
}) => {
const { theme } = useTheme();
const colorStyles = {
accent: { bg: theme.gradientAccent, text: '#fff' },
green: { bg: theme.green, text: '#fff' },
orange: { bg: theme.orange, text: '#fff' },
red: { bg: '#ff4444', text: '#fff' },
};
const sizeStyles = {
const sizeStyles: Record<string, string> = {
sm: 'px-2 py-0.5 text-xs',
md: 'px-3 py-1 text-sm',
};
const c = colorMap[color] ?? colorMap.accent;
return (
<span
className={`${sizeStyles[size]} rounded font-mono font-medium`}
style={{
background: colorStyles[color].bg,
color: colorStyles[color].text,
}}
style={{ background: c.bg, color: c.text }}
>
{children}
</span>

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { useTheme } from '../../contexts';
interface ButtonProps {
variant?: 'primary' | 'secondary';
@@ -18,8 +17,6 @@ export const Button: React.FC<ButtonProps> = ({
disabled = false,
className = '',
}) => {
const { theme } = useTheme();
const baseStyles = `
inline-flex items-center justify-center
font-semibold rounded-xl cursor-pointer
@@ -27,13 +24,13 @@ export const Button: React.FC<ButtonProps> = ({
disabled:cursor-not-allowed disabled:opacity-50
`;
const sizeStyles = {
const sizeStyles: Record<string, string> = {
sm: 'px-3 py-1.5 text-xs',
md: 'px-5 py-3 text-sm',
lg: 'px-8 py-5 text-base',
};
const variantStyles = {
const variantStyles: Record<string, string> = {
primary: `
bg-gradient-to-r from-t-accent to-t-accent-dark
text-white hover:shadow-t-accent hover:-translate-y-0.5
@@ -50,9 +47,6 @@ export const Button: React.FC<ButtonProps> = ({
onClick={onClick}
disabled={disabled}
className={`${baseStyles} ${sizeStyles[size]} ${variantStyles[variant]} ${className}`}
style={{
color: variant === 'primary' ? '#fff' : theme.text2,
}}
>
{children}
</button>

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { useTheme } from '../../contexts';
interface CardProps {
accent?: boolean;
@@ -18,9 +17,7 @@ export const Card: React.FC<CardProps> = ({
className = '',
onClick,
}) => {
const { theme, isDark } = useTheme();
const paddingStyles = {
const paddingStyles: Record<string, string> = {
sm: 'p-4',
md: 'p-5',
lg: 'p-8',
@@ -30,22 +27,22 @@ export const Card: React.FC<CardProps> = ({
<div
onClick={onClick}
className={`
rounded-xl border transition-all duration-200 cursor-default
relative rounded-xl border transition-all duration-200 cursor-default
${paddingStyles[padding]}
${accent ? 'border-t-accent' : 'border-t-border'}
${highlight ? 'border-2 border-t-accent' : ''}
${!isDark ? 'shadow-t-card' : ''}
${onClick ? 'cursor-pointer hover:border-t-accent' : ''}
${accent ? 'border-[color:var(--accent)]' : 'border-[color:var(--border)]'}
${highlight ? 'border-2 border-[color:var(--accent)]' : ''}
${onClick ? 'cursor-pointer hover:border-[color:var(--accent)]' : ''}
${className}
`}
style={{
backgroundColor: theme.bgCard,
backgroundColor: 'var(--surface)',
boxShadow: 'var(--shadow-card)',
}}
>
{accent && (
<div
className="absolute top-0 left-0 right-0 h-[3px] rounded-t-xl"
style={{ background: theme.gradientAccent }}
style={{ background: 'linear-gradient(90deg, var(--accent), var(--accent-hover))' }}
/>
)}
{children}

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { useTheme } from '../../contexts';
interface InputProps {
value: string;
@@ -18,8 +17,6 @@ export const Input: React.FC<InputProps> = ({
className = '',
type = 'text',
}) => {
const { theme } = useTheme();
return (
<input
type={type}
@@ -28,17 +25,24 @@ export const Input: React.FC<InputProps> = ({
onKeyDown={onKeyDown}
placeholder={placeholder}
className={`
w-full px-4 py-3 text-sm
bg-t-bg-card border border-t-border rounded-lg
text-t-text outline-none
focus:border-t-accent focus:ring-1 focus:ring-t-accent
placeholder:text-t-text3
w-full px-4 py-3 text-sm rounded-lg outline-none
focus:ring-1
placeholder:text-[color:var(--muted)]
${className}
`}
style={{
backgroundColor: theme.bgCard,
borderColor: theme.border,
color: theme.text,
backgroundColor: 'var(--surface)',
borderColor: 'var(--border)',
border: '1px solid var(--border)',
color: 'var(--fg)',
}}
onFocus={(e) => {
e.currentTarget.style.borderColor = 'var(--accent)';
e.currentTarget.style.boxShadow = '0 0 0 1px var(--accent)';
}}
onBlur={(e) => {
e.currentTarget.style.borderColor = 'var(--border)';
e.currentTarget.style.boxShadow = 'none';
}}
/>
);

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { useTheme } from '../../contexts';
interface ProgressBarProps {
percent: number;
@@ -7,36 +6,39 @@ interface ProgressBarProps {
showLabel?: boolean;
}
const colorBg: Record<string, string> = {
accent: 'linear-gradient(90deg, var(--accent), var(--accent-hover))',
green: 'linear-gradient(90deg, var(--success), #00ff88)',
orange: 'linear-gradient(90deg, var(--warn), #ffaa00)',
red: 'var(--danger)',
};
export const ProgressBar: React.FC<ProgressBarProps> = ({
percent,
color = 'accent',
showLabel = false,
}) => {
const { theme } = useTheme();
const colorStyles = {
accent: theme.gradientAccent,
green: `linear-gradient(90deg, ${theme.green}, #00ff88)`,
orange: `linear-gradient(90deg, ${theme.orange}, #ffaa00)`,
red: '#ff4444',
};
return (
<div className="flex items-center gap-3">
<div
className="h-2 rounded-full flex-1"
style={{ backgroundColor: theme.bgHover }}
style={{ backgroundColor: 'var(--border)' }}
>
<div
className="h-full rounded-full transition-all duration-300"
style={{
width: `${percent}%`,
background: colorStyles[color],
background: colorBg[color] ?? colorBg.accent,
}}
/>
</div>
{showLabel && (
<span className="font-mono text-xs text-t-accent">{percent}%</span>
<span
className="font-mono text-xs"
style={{ color: 'var(--accent)' }}
>
{percent}%
</span>
)}
</div>
);

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { useTheme } from '../../contexts';
interface ScoreBarProps {
score: number; // 0-100
@@ -12,27 +11,25 @@ export const ScoreBar: React.FC<ScoreBarProps> = ({
label,
accent = false,
}) => {
const { theme } = useTheme();
return (
<div
className="p-5 rounded-xl border"
style={{
backgroundColor: theme.bgCard,
borderColor: accent ? theme.accent : theme.border,
backgroundColor: 'var(--surface)',
borderColor: accent ? 'var(--accent)' : 'var(--border)',
}}
>
{label && (
<div
className="font-mono text-xs mb-2"
style={{ color: theme.text3, letterSpacing: '1px' }}
style={{ color: 'var(--muted)', letterSpacing: '1px' }}
>
{label}
</div>
)}
<div
className="font-mono text-3xl font-bold"
style={{ color: accent ? theme.accent : theme.text }}
style={{ color: accent ? 'var(--accent)' : 'var(--fg)' }}
>
{score}
</div>
@@ -42,7 +39,7 @@ export const ScoreBar: React.FC<ScoreBarProps> = ({
key={i}
className="w-2 h-4 rounded-sm"
style={{
backgroundColor: i < score / 10 ? theme.accent : theme.bgHover,
backgroundColor: i < score / 10 ? 'var(--accent)' : 'var(--border)',
}}
/>
))}

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { useTheme } from '../../contexts';
import type { ComplianceChunk } from '../../types';
interface ChatPanelProps {
@@ -29,7 +28,6 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
'法规的具体要求是什么?',
],
}) => {
const { theme } = useTheme();
const activeChunk = chunks.find(c => c.id === activeChunkId);
return (
@@ -40,8 +38,8 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
top: 0,
bottom: 0,
width: 420,
background: theme.bgCard,
borderLeft: `1px solid ${theme.border}`,
background: 'var(--surface)',
borderLeft: '1px solid var(--border)',
display: 'flex',
flexDirection: 'column',
zIndex: 50,
@@ -51,14 +49,14 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
{/* Chat Header */}
<div style={{
padding: '20px 24px',
borderBottom: `1px solid ${theme.border}`,
borderBottom: '1px solid var(--border)',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}>
<div>
<div style={{ fontSize: 14, fontWeight: 600, color: theme.text }}></div>
<div className="mono" style={{ fontSize: 11, color: theme.text3 }}>
<div style={{ fontSize: 14, fontWeight: 600, color: 'var(--fg)' }}></div>
<div className="mono" style={{ fontSize: 11, color: 'var(--muted)' }}>
#{activeChunk?.index} · {activeChunk?.regulations.length}
</div>
</div>
@@ -68,7 +66,7 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
width: 32,
height: 32,
borderRadius: 8,
background: theme.bgHover,
background: 'var(--rail-hover)',
border: 'none',
cursor: 'pointer',
display: 'flex',
@@ -77,7 +75,7 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
}}
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M18 6L6 18M6 6L18 18" stroke={theme.text3} strokeWidth="2" strokeLinecap="round"/>
<path d="M18 6L6 18M6 6L18 18" stroke="var(--muted)" strokeWidth="2" strokeLinecap="round"/>
</svg>
</button>
</div>
@@ -85,15 +83,15 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
{/* Current Chunk Info */}
<div style={{
padding: '16px 24px',
background: theme.bgHover,
borderBottom: `1px solid ${theme.border}`,
background: 'var(--rail-hover)',
borderBottom: '1px solid var(--border)',
}}>
<div style={{ fontSize: 13, fontWeight: 500, marginBottom: 8, color: theme.text }}>
<div style={{ fontSize: 13, fontWeight: 500, marginBottom: 8, color: 'var(--fg)' }}>
{activeChunk?.intent}
</div>
<div style={{
fontSize: 12,
color: theme.text2,
color: 'var(--muted)',
lineHeight: 1.5,
maxHeight: 60,
overflow: 'hidden',
@@ -122,7 +120,7 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
width: 32,
height: 32,
borderRadius: 8,
background: theme.gradientAccent,
background: 'linear-gradient(90deg, var(--accent), var(--accent-hover))',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
@@ -136,13 +134,15 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
<div style={{
maxWidth: '80%',
padding: '12px 16px',
background: msg.role === 'user' ? theme.gradientAccent : theme.bgElevated,
background: msg.role === 'user'
? 'linear-gradient(90deg, var(--accent), var(--accent-hover))'
: 'var(--surface)',
borderRadius: 12,
color: msg.role === 'user' ? '#fff' : theme.text,
color: msg.role === 'user' ? '#fff' : 'var(--fg)',
fontSize: 14,
lineHeight: 1.6,
whiteSpace: 'pre-wrap',
border: msg.role === 'assistant' ? `1px solid ${theme.border}` : 'none',
border: msg.role === 'assistant' ? '1px solid var(--border)' : 'none',
}}>
{msg.content}
</div>
@@ -154,7 +154,7 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
width: 32,
height: 32,
borderRadius: 8,
background: theme.gradientAccent,
background: 'linear-gradient(90deg, var(--accent), var(--accent-hover))',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
@@ -165,15 +165,15 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
</div>
<div style={{
padding: '12px 16px',
background: theme.bgElevated,
background: 'var(--surface)',
borderRadius: 12,
border: `1px solid ${theme.border}`,
border: '1px solid var(--border)',
display: 'flex',
alignItems: 'center',
gap: 8,
}}>
<div style={{ width: 6, height: 6, borderRadius: '50%', background: theme.accent, animation: 'pulse 1s infinite' }} />
<span style={{ fontSize: 13, color: theme.text2 }}>...</span>
<div style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--accent)', animation: 'pulse 1s infinite' }} />
<span style={{ fontSize: 13, color: 'var(--muted)' }}>...</span>
</div>
</div>
)}
@@ -193,10 +193,10 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
style={{
padding: '6px 12px',
fontSize: 12,
background: theme.bgHover,
border: `1px solid ${theme.border}`,
background: 'var(--rail-hover)',
border: '1px solid var(--border)',
borderRadius: 6,
color: theme.text2,
color: 'var(--muted)',
cursor: 'pointer',
}}
>{q}</button>
@@ -206,7 +206,7 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
{/* Chat Input */}
<div style={{
padding: '16px 24px',
borderTop: `1px solid ${theme.border}`,
borderTop: '1px solid var(--border)',
display: 'flex',
gap: 12,
}}>
@@ -219,10 +219,10 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
flex: 1,
padding: 12,
fontSize: 14,
background: theme.bgHover,
border: `1px solid ${theme.border}`,
background: 'var(--rail-hover)',
border: '1px solid var(--border)',
borderRadius: 8,
color: theme.text,
color: 'var(--fg)',
outline: 'none',
}}
/>
@@ -233,8 +233,10 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
padding: '12px 20px',
fontSize: 14,
fontWeight: 600,
background: chatLoading || !chatInput.trim() ? theme.bgHover : theme.gradientAccent,
color: chatLoading || !chatInput.trim() ? theme.text3 : '#fff',
background: chatLoading || !chatInput.trim()
? 'var(--rail-hover)'
: 'linear-gradient(90deg, var(--accent), var(--accent-hover))',
color: chatLoading || !chatInput.trim() ? 'var(--muted)' : '#fff',
border: 'none',
borderRadius: 8,
cursor: chatLoading || !chatInput.trim() ? 'not-allowed' : 'pointer',