refactor(admin): tokenize 语义色并补暗黑模式变量覆盖
将 admin 8 文件 ~50 处 raw red/green/yellow/amber/blue 类替换为语义 token (error/success/warning/info),使其错误/成功/警告框在暗黑模式正确反色。 - tailwind.config.js: error 补 text 子键(原缺,text-error 暗黑底仅 3.3:1 不达 AA) - globals.css: 新增 --color-error-text;html[data-theme='dark'] 补四语义色的 *-bg(深色底)与 *-text(浅色,暗底达 AA)覆盖,原暗黑块完全漏掉这些变量 - 映射纪律:语义框 bg-<c>-bg border border-border-secondary text-<c>-text; 危险按钮 bg-error/bg-success hover:bg-*-hover text-white;字段校验 border-error 验证:type-check 0 errors;lint 0 errors;单测 1624 passed;视觉回归 22 passed; Playwright 实测 8 变量 light/dark 翻转 + 真实登录错误框背景反色确认。 Co-Authored-By: 阿睿 <workbuddy@tencent.com>
This commit is contained in:
@@ -106,12 +106,12 @@ function JsonEditor({
|
||||
placeholder={placeholder || '输入 JSON 数据'}
|
||||
rows={10}
|
||||
className={`w-full px-3 py-2 border rounded-lg text-sm font-mono focus:outline-none focus:ring-2 focus:ring-border-dark focus:border-transparent resize-y ${
|
||||
error || parseError ? 'border-red-300 bg-red-50' : 'border-border-secondary'
|
||||
error || parseError ? 'border-error bg-error-bg' : 'border-border-secondary'
|
||||
}`}
|
||||
spellCheck={false}
|
||||
/>
|
||||
{parseError && <p className="mt-1 text-xs text-red-500">{parseError}</p>}
|
||||
{error && !parseError && <p className="mt-1 text-xs text-red-500">{error}</p>}
|
||||
{parseError && <p className="mt-1 text-xs text-error-text">{parseError}</p>}
|
||||
{error && !parseError && <p className="mt-1 text-xs text-error-text">{error}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -119,7 +119,7 @@ function JsonEditor({
|
||||
function FieldError({ error }: { error?: string }) {
|
||||
if (!error) return null;
|
||||
return (
|
||||
<p className="mt-1 text-xs text-red-500 flex items-center gap-1">
|
||||
<p className="mt-1 text-xs text-error-text flex items-center gap-1">
|
||||
<AlertTriangle className="w-3 h-3" />
|
||||
{error}
|
||||
</p>
|
||||
@@ -422,7 +422,7 @@ export default function ContentEditorPage() {
|
||||
<div key={key}>
|
||||
<label htmlFor={key} className="block text-sm font-medium text-text-secondary mb-1">
|
||||
{field.label}
|
||||
{field.required && <span className="text-red-500 ml-1">*</span>}
|
||||
{field.required && <span className="text-error-text ml-1">*</span>}
|
||||
</label>
|
||||
{field.description && (
|
||||
<p className="text-xs text-text-muted mb-1.5">{field.description}</p>
|
||||
@@ -435,7 +435,7 @@ export default function ContentEditorPage() {
|
||||
onBlur={() => handleFieldBlur(field.name)}
|
||||
placeholder={field.placeholder}
|
||||
className={`w-full px-3 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-border-dark focus:border-transparent ${
|
||||
error ? 'border-red-300 bg-red-50' : 'border-border-secondary'
|
||||
error ? 'border-error bg-error-bg' : 'border-border-secondary'
|
||||
}`}
|
||||
/>
|
||||
<FieldError error={error} />
|
||||
@@ -447,7 +447,7 @@ export default function ContentEditorPage() {
|
||||
<div key={key}>
|
||||
<label htmlFor={key} className="block text-sm font-medium text-text-secondary mb-1">
|
||||
{field.label}
|
||||
{field.required && <span className="text-red-500 ml-1">*</span>}
|
||||
{field.required && <span className="text-error-text ml-1">*</span>}
|
||||
</label>
|
||||
{field.description && (
|
||||
<p className="text-xs text-text-muted mb-1.5">{field.description}</p>
|
||||
@@ -460,7 +460,7 @@ export default function ContentEditorPage() {
|
||||
placeholder={field.placeholder}
|
||||
rows={4}
|
||||
className={`w-full px-3 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-border-dark focus:border-transparent resize-y ${
|
||||
error ? 'border-red-300 bg-red-50' : 'border-border-secondary'
|
||||
error ? 'border-error bg-error-bg' : 'border-border-secondary'
|
||||
}`}
|
||||
/>
|
||||
<FieldError error={error} />
|
||||
@@ -472,7 +472,7 @@ export default function ContentEditorPage() {
|
||||
<div key={key}>
|
||||
<label htmlFor={key} className="block text-sm font-medium text-text-secondary mb-1">
|
||||
{field.label}
|
||||
{field.required && <span className="text-red-500 ml-1">*</span>}
|
||||
{field.required && <span className="text-error-text ml-1">*</span>}
|
||||
</label>
|
||||
<input
|
||||
id={key}
|
||||
@@ -480,7 +480,7 @@ export default function ContentEditorPage() {
|
||||
value={(value as number) ?? ''}
|
||||
onChange={(e) => handleFieldChange(field.name, parseFloat(e.target.value) || 0)}
|
||||
className={`w-full px-3 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-border-dark focus:border-transparent ${
|
||||
error ? 'border-red-300 bg-red-50' : 'border-border-secondary'
|
||||
error ? 'border-error bg-error-bg' : 'border-border-secondary'
|
||||
}`}
|
||||
/>
|
||||
<FieldError error={error} />
|
||||
@@ -532,7 +532,7 @@ export default function ContentEditorPage() {
|
||||
<div key={key}>
|
||||
<label htmlFor={key} className="block text-sm font-medium text-text-secondary mb-1">
|
||||
{field.label}
|
||||
{field.required && <span className="text-red-500 ml-1">*</span>}
|
||||
{field.required && <span className="text-error-text ml-1">*</span>}
|
||||
</label>
|
||||
{field.description && (
|
||||
<p className="text-xs text-text-muted mb-1.5">{field.description}</p>
|
||||
@@ -636,13 +636,13 @@ export default function ContentEditorPage() {
|
||||
{!isNew && (
|
||||
<div className="flex items-center gap-1.5 text-xs">
|
||||
{autoSaveStatus === 'saving' && (
|
||||
<span className="text-amber-600 flex items-center gap-1">
|
||||
<span className="text-warning-text flex items-center gap-1">
|
||||
<Loader2 className="w-3 h-3 animate-spin" />
|
||||
保存中...
|
||||
</span>
|
||||
)}
|
||||
{autoSaveStatus === 'saved' && (
|
||||
<span className="text-green-600 flex items-center gap-1">
|
||||
<span className="text-success-text flex items-center gap-1">
|
||||
<CheckCircle className="w-3 h-3" />
|
||||
已保存
|
||||
{lastSaved && (
|
||||
@@ -653,7 +653,7 @@ export default function ContentEditorPage() {
|
||||
</span>
|
||||
)}
|
||||
{autoSaveStatus === 'unsaved' && (
|
||||
<span className="text-amber-600 flex items-center gap-1">
|
||||
<span className="text-warning-text flex items-center gap-1">
|
||||
<CloudOff className="w-3 h-3" />
|
||||
未保存
|
||||
</span>
|
||||
@@ -683,7 +683,7 @@ export default function ContentEditorPage() {
|
||||
<button
|
||||
onClick={handlePublishClick}
|
||||
disabled={saving || publishPending}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-green-700 text-white rounded-lg text-sm font-medium hover:bg-green-600 disabled:opacity-50 transition-colors"
|
||||
className="flex items-center gap-2 px-4 py-2 bg-success hover:bg-success-hover text-white rounded-lg text-sm font-medium disabled:opacity-50 transition-colors"
|
||||
>
|
||||
{publishPending ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
@@ -707,19 +707,19 @@ export default function ContentEditorPage() {
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 text-red-700 rounded-lg px-4 py-3 text-sm">
|
||||
<div className="bg-error-bg border border-border-secondary text-error-text rounded-lg px-4 py-3 text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 验证错误汇总 */}
|
||||
{Object.keys(validationErrors).length > 0 && (
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-lg px-4 py-3">
|
||||
<div className="flex items-center gap-2 text-amber-700 text-sm font-medium mb-1">
|
||||
<div className="bg-warning-bg border border-border-secondary rounded-lg px-4 py-3">
|
||||
<div className="flex items-center gap-2 text-warning-text text-sm font-medium mb-1">
|
||||
<AlertTriangle className="w-4 h-4" />
|
||||
请修正以下错误
|
||||
</div>
|
||||
<ul className="list-disc list-inside text-xs text-amber-600 space-y-0.5">
|
||||
<ul className="list-disc list-inside text-xs text-warning-text space-y-0.5">
|
||||
{Object.entries(validationErrors).map(([field, msg]) => (
|
||||
<li key={field}>{msg}</li>
|
||||
))}
|
||||
@@ -732,7 +732,7 @@ export default function ContentEditorPage() {
|
||||
{/* 标题 */}
|
||||
<div>
|
||||
<label htmlFor="title" className="block text-sm font-medium text-text-secondary mb-1">
|
||||
标题 <span className="text-red-500">*</span>
|
||||
标题 <span className="text-error-text">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="title"
|
||||
@@ -753,7 +753,7 @@ export default function ContentEditorPage() {
|
||||
}}
|
||||
placeholder="请输入标题"
|
||||
className={`w-full px-3 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-border-dark focus:border-transparent ${
|
||||
validationErrors.title ? 'border-red-300 bg-red-50' : 'border-border-secondary'
|
||||
validationErrors.title ? 'border-error bg-error-bg' : 'border-border-secondary'
|
||||
}`}
|
||||
/>
|
||||
<FieldError error={validationErrors.title} />
|
||||
@@ -784,7 +784,7 @@ export default function ContentEditorPage() {
|
||||
}}
|
||||
placeholder="URL 友好标识(留空自动生成)"
|
||||
className={`w-full px-3 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-border-dark focus:border-transparent ${
|
||||
validationErrors.slug ? 'border-red-300 bg-red-50' : 'border-border-secondary'
|
||||
validationErrors.slug ? 'border-error bg-error-bg' : 'border-border-secondary'
|
||||
}`}
|
||||
/>
|
||||
<FieldError error={validationErrors.slug} />
|
||||
@@ -850,7 +850,7 @@ export default function ContentEditorPage() {
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
{Object.keys(validationErrors).length > 0 && (
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-lg px-3 py-2 text-xs text-amber-700">
|
||||
<div className="bg-warning-bg border border-border-secondary rounded-lg px-3 py-2 text-xs text-warning-text">
|
||||
注意:表单中存在 {Object.keys(validationErrors).length} 个错误,发布可能会失败。
|
||||
</div>
|
||||
)}
|
||||
@@ -858,7 +858,7 @@ export default function ContentEditorPage() {
|
||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handlePublishConfirm}
|
||||
className="bg-green-700 hover:bg-green-600 text-white"
|
||||
className="bg-success hover:bg-success-hover text-white"
|
||||
>
|
||||
确认发布
|
||||
</AlertDialogAction>
|
||||
|
||||
@@ -147,7 +147,7 @@ export default function ContentListPage() {
|
||||
|
||||
{/* Error */}
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 text-red-700 rounded-lg px-4 py-3 text-sm">
|
||||
<div className="bg-error-bg border border-border-secondary text-error-text rounded-lg px-4 py-3 text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@@ -195,9 +195,9 @@ export default function ContentListPage() {
|
||||
<span
|
||||
className={`inline-flex text-xs px-2 py-0.5 rounded-full font-medium ${
|
||||
item.status === 'published'
|
||||
? 'bg-green-50 text-green-700'
|
||||
? 'bg-success-bg text-success-text'
|
||||
: item.status === 'draft'
|
||||
? 'bg-yellow-50 text-yellow-700'
|
||||
? 'bg-warning-bg text-warning-text'
|
||||
: 'bg-bg-hover text-text-tertiary'
|
||||
}`}
|
||||
>
|
||||
@@ -218,7 +218,7 @@ export default function ContentListPage() {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => openDeleteDialog(item.id)}
|
||||
className="p-1.5 rounded hover:bg-red-50 text-text-muted hover:text-red-500 transition-colors"
|
||||
className="p-1.5 rounded hover:bg-error-bg text-text-muted hover:text-error-text transition-colors"
|
||||
title="删除"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
@@ -271,7 +271,7 @@ export default function ContentListPage() {
|
||||
<AlertDialogCancel onClick={() => setPendingDeleteId(null)}>取消</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handleConfirmDelete}
|
||||
className="bg-red-600 hover:bg-red-700 text-white"
|
||||
className="bg-error hover:bg-error-hover text-white"
|
||||
>
|
||||
删除
|
||||
</AlertDialogAction>
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function AdminLoginPage() {
|
||||
className="bg-bg-primary rounded-xl shadow-sm border border-border-primary p-6 space-y-5"
|
||||
>
|
||||
{error && (
|
||||
<div className="bg-error-bg border border-border-secondary text-error rounded-lg px-4 py-3 text-sm">
|
||||
<div className="bg-error-bg border border-border-secondary text-error-text rounded-lg px-4 py-3 text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -172,7 +172,7 @@ export default function MediaPage() {
|
||||
title="复制链接"
|
||||
>
|
||||
{copiedId === item.id ? (
|
||||
<Check className="w-3 h-3 text-green-500" />
|
||||
<Check className="w-3 h-3 text-success-text" />
|
||||
) : (
|
||||
<Copy className="w-3 h-3" />
|
||||
)}
|
||||
@@ -180,7 +180,7 @@ export default function MediaPage() {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => openDeleteDialog(item.id)}
|
||||
className="flex-1 flex items-center justify-center gap-1 py-1.5 text-xs text-text-muted hover:bg-red-50 hover:text-red-500 transition-colors"
|
||||
className="flex-1 flex items-center justify-center gap-1 py-1.5 text-xs text-text-muted hover:bg-error-bg hover:text-error-text transition-colors"
|
||||
title="删除"
|
||||
>
|
||||
<Trash2 className="w-3 h-3" />
|
||||
@@ -224,7 +224,7 @@ export default function MediaPage() {
|
||||
<AlertDialogCancel onClick={() => setPendingDeleteId(null)}>取消</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handleConfirmDelete}
|
||||
className="bg-red-600 hover:bg-red-700 text-white"
|
||||
className="bg-error hover:bg-error-hover text-white"
|
||||
>
|
||||
删除
|
||||
</AlertDialogAction>
|
||||
|
||||
@@ -38,16 +38,16 @@ const NOTIFICATION_TYPE_LABELS: Record<string, string> = {
|
||||
};
|
||||
|
||||
const NOTIFICATION_TYPE_ICONS: Record<string, React.ReactNode> = {
|
||||
review_pending: <AlertCircle className="w-4 h-4 text-amber-500" />,
|
||||
review_approved: <CheckCircle className="w-4 h-4 text-green-500" />,
|
||||
review_rejected: <XCircle className="w-4 h-4 text-red-500" />,
|
||||
review_pending: <AlertCircle className="w-4 h-4 text-warning-text" />,
|
||||
review_approved: <CheckCircle className="w-4 h-4 text-success-text" />,
|
||||
review_rejected: <XCircle className="w-4 h-4 text-error-text" />,
|
||||
item_archived: <Archive className="w-4 h-4 text-text-muted" />,
|
||||
};
|
||||
|
||||
const NOTIFICATION_TYPE_COLORS: Record<string, string> = {
|
||||
review_pending: 'bg-amber-50 border-amber-200',
|
||||
review_approved: 'bg-green-50 border-green-200',
|
||||
review_rejected: 'bg-red-50 border-red-200',
|
||||
review_pending: 'bg-warning-bg border-border-secondary',
|
||||
review_approved: 'bg-success-bg border-border-secondary',
|
||||
review_rejected: 'bg-error-bg border-border-secondary',
|
||||
item_archived: 'bg-bg-secondary border-border-primary',
|
||||
};
|
||||
|
||||
@@ -156,7 +156,7 @@ export default function NotificationsPage() {
|
||||
<p className="text-sm text-text-muted mt-1">
|
||||
共 {total} 条通知
|
||||
{unreadCount > 0 && (
|
||||
<span className="ml-2 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-700">
|
||||
<span className="ml-2 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-error-bg text-error-text">
|
||||
{unreadCount} 条未读
|
||||
</span>
|
||||
)}
|
||||
@@ -194,7 +194,7 @@ export default function NotificationsPage() {
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="p-4 bg-red-50 border border-red-200 rounded-lg text-sm text-red-600">
|
||||
<div className="p-4 bg-error-bg border border-border-secondary rounded-lg text-sm text-error-text">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@@ -223,12 +223,12 @@ export default function NotificationsPage() {
|
||||
className={`relative flex items-start gap-4 p-4 rounded-lg border transition-colors ${
|
||||
item.read
|
||||
? 'bg-bg-primary border-border-light'
|
||||
: NOTIFICATION_TYPE_COLORS[item.type] || 'bg-blue-50 border-blue-200'
|
||||
: NOTIFICATION_TYPE_COLORS[item.type] || 'bg-info-bg border-border-secondary'
|
||||
}`}
|
||||
>
|
||||
{/* Unread indicator */}
|
||||
{!item.read && (
|
||||
<div className="absolute top-4 left-4 w-2 h-2 rounded-full bg-blue-500" />
|
||||
<div className="absolute top-4 left-4 w-2 h-2 rounded-full bg-info" />
|
||||
)}
|
||||
|
||||
{/* Type icon */}
|
||||
@@ -278,7 +278,7 @@ export default function NotificationsPage() {
|
||||
{item.relatedItemId && item.relatedModelCode && (
|
||||
<button
|
||||
onClick={() => handleViewRelated(item)}
|
||||
className="text-xs text-blue-600 hover:text-blue-800 flex items-center gap-1"
|
||||
className="text-xs text-info-text hover:text-info flex items-center gap-1"
|
||||
>
|
||||
<ExternalLink className="w-3 h-3" />
|
||||
查看详情
|
||||
|
||||
+14
-14
@@ -50,7 +50,7 @@ interface DashboardStats {
|
||||
const modelCards = [
|
||||
{ code: 'news', name: '新闻资讯', icon: Newspaper, color: 'bg-blue-50 text-blue-600' },
|
||||
{ code: 'case-study', name: '案例研究', icon: Briefcase, color: 'bg-amber-50 text-amber-600' },
|
||||
{ code: 'service', name: '服务管理', icon: Settings, color: 'bg-green-50 text-green-600' },
|
||||
{ code: 'service', name: '服务管理', icon: Settings, color: 'bg-success-bg text-success-text' },
|
||||
{ code: 'product', name: '产品管理', icon: Box, color: 'bg-purple-50 text-purple-600' },
|
||||
{ code: 'solution', name: '解决方案', icon: Layers, color: 'bg-teal-50 text-teal-600' },
|
||||
{ code: 'hero-banner', name: 'Hero Banner', icon: Image, color: 'bg-rose-50 text-rose-600' },
|
||||
@@ -81,15 +81,15 @@ const STATUS_LABELS: Record<string, string> = {
|
||||
|
||||
const STATUS_COLORS: Record<string, string> = {
|
||||
draft: 'bg-bg-hover text-text-secondary',
|
||||
review: 'bg-amber-100 text-amber-700',
|
||||
published: 'bg-green-100 text-green-700',
|
||||
archived: 'bg-red-100 text-red-700',
|
||||
review: 'bg-warning-bg text-warning-text',
|
||||
published: 'bg-success-bg text-success-text',
|
||||
archived: 'bg-error-bg text-error-text',
|
||||
};
|
||||
|
||||
const NOTIFICATION_TYPE_ICONS: Record<string, React.ReactNode> = {
|
||||
review_pending: <AlertCircle className="w-4 h-4 text-amber-500" />,
|
||||
review_approved: <CheckCircle className="w-4 h-4 text-green-500" />,
|
||||
review_rejected: <XCircle className="w-4 h-4 text-red-500" />,
|
||||
review_pending: <AlertCircle className="w-4 h-4 text-warning-text" />,
|
||||
review_approved: <CheckCircle className="w-4 h-4 text-success-text" />,
|
||||
review_rejected: <XCircle className="w-4 h-4 text-error-text" />,
|
||||
item_archived: <Archive className="w-4 h-4 text-text-muted" />,
|
||||
};
|
||||
|
||||
@@ -133,12 +133,12 @@ export default function AdminDashboardPage() {
|
||||
{ label: '内容模型', value: stats?.models ?? '-', icon: FileText, color: 'bg-blue-50' },
|
||||
{ label: '内容条目', value: stats?.items ?? '-', icon: Layers, color: 'bg-purple-50' },
|
||||
{ label: '页面区域', value: stats?.zones ?? '-', icon: Briefcase, color: 'bg-amber-50' },
|
||||
{ label: '活跃用户', value: stats?.activeUsers ?? '-', icon: Users, color: 'bg-green-50' },
|
||||
{ label: '活跃用户', value: stats?.activeUsers ?? '-', icon: Users, color: 'bg-success-bg' },
|
||||
{
|
||||
label: '待审核',
|
||||
value: stats?.pendingReviews ?? '-',
|
||||
icon: AlertCircle,
|
||||
color: 'bg-red-50',
|
||||
color: 'bg-error-bg',
|
||||
highlight: (stats?.pendingReviews ?? 0) > 0,
|
||||
},
|
||||
{
|
||||
@@ -152,21 +152,21 @@ export default function AdminDashboardPage() {
|
||||
<div
|
||||
key={stat.label}
|
||||
className={`bg-bg-primary rounded-lg border p-4 ${
|
||||
stat.highlight ? 'border-red-200 ring-1 ring-red-100' : 'border-border-primary'
|
||||
stat.highlight ? 'border-error ring-1 ring-error-bg' : 'border-border-primary'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-text-muted">{stat.label}</p>
|
||||
<p className={`text-2xl font-bold mt-1 ${
|
||||
stat.highlight ? 'text-red-600' : 'text-text-primary'
|
||||
stat.highlight ? 'text-error-text' : 'text-text-primary'
|
||||
}`}>
|
||||
{fetching ? '-' : stat.value}
|
||||
</p>
|
||||
</div>
|
||||
<div className={`w-10 h-10 rounded-lg ${stat.color} flex items-center justify-center`}>
|
||||
<stat.icon className={`w-5 h-5 ${
|
||||
stat.highlight ? 'text-red-500' : 'text-text-tertiary'
|
||||
stat.highlight ? 'text-error-text' : 'text-text-tertiary'
|
||||
}`} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -202,7 +202,7 @@ export default function AdminDashboardPage() {
|
||||
<h2 className="text-sm font-semibold text-text-primary">最近通知</h2>
|
||||
<button
|
||||
onClick={() => router.push('/admin/notifications')}
|
||||
className="text-xs text-blue-600 hover:text-blue-800"
|
||||
className="text-xs text-info-text hover:text-info"
|
||||
>
|
||||
查看全部
|
||||
</button>
|
||||
@@ -230,7 +230,7 @@ export default function AdminDashboardPage() {
|
||||
{new Date(n.createdAt).toLocaleDateString('zh-CN')}
|
||||
</p>
|
||||
</div>
|
||||
{!n.read && <div className="w-2 h-2 rounded-full bg-blue-500 flex-shrink-0 mt-1.5" />}
|
||||
{!n.read && <div className="w-2 h-2 rounded-full bg-info flex-shrink-0 mt-1.5" />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -154,7 +154,7 @@ export default function RolesPage() {
|
||||
|
||||
{/* Alerts */}
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 text-red-700 rounded-lg px-4 py-3 text-sm">
|
||||
<div className="bg-error-bg border border-border-secondary text-error-text rounded-lg px-4 py-3 text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -45,10 +45,10 @@ const ROLE_LABELS: Record<string, string> = {
|
||||
};
|
||||
|
||||
const ROLE_COLORS: Record<string, string> = {
|
||||
super_admin: 'bg-red-100 text-red-700',
|
||||
content_admin: 'bg-blue-100 text-blue-700',
|
||||
content_editor: 'bg-green-100 text-green-700',
|
||||
reviewer: 'bg-amber-100 text-amber-700',
|
||||
super_admin: 'bg-error-bg text-error-text',
|
||||
content_admin: 'bg-info-bg text-info-text',
|
||||
content_editor: 'bg-success-bg text-success-text',
|
||||
reviewer: 'bg-warning-bg text-warning-text',
|
||||
readonly: 'bg-bg-hover text-text-secondary',
|
||||
};
|
||||
|
||||
@@ -266,7 +266,7 @@ export default function UsersPage() {
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="p-4 bg-red-50 border border-red-200 rounded-lg text-sm text-red-600">
|
||||
<div className="p-4 bg-error-bg border border-border-secondary rounded-lg text-sm text-error-text">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@@ -344,8 +344,8 @@ export default function UsersPage() {
|
||||
<span
|
||||
className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${
|
||||
u.status === 1
|
||||
? 'bg-green-100 text-green-700'
|
||||
: 'bg-red-100 text-red-700'
|
||||
? 'bg-success-bg text-success-text'
|
||||
: 'bg-error-bg text-error-text'
|
||||
}`}
|
||||
>
|
||||
{u.status === 1 ? '启用' : '禁用'}
|
||||
@@ -368,7 +368,7 @@ export default function UsersPage() {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setPendingDeleteId(u.id); setDeleteDialogOpen(true); }}
|
||||
className="p-1.5 rounded hover:bg-red-50 text-text-muted hover:text-red-600"
|
||||
className="p-1.5 rounded hover:bg-error-bg text-text-muted hover:text-error-text"
|
||||
title="删除"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
@@ -529,7 +529,7 @@ export default function UsersPage() {
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleDelete} className="bg-red-600 hover:bg-red-700">
|
||||
<AlertDialogAction onClick={handleDelete} className="bg-error hover:bg-error-hover">
|
||||
确认删除
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
|
||||
@@ -114,6 +114,9 @@ body {
|
||||
--color-error-hover: #A01830;
|
||||
--color-error-bg: #FEF2F4;
|
||||
--color-error-rgb: 196 30 58;
|
||||
/* 文字专用:--color-error(品牌红)在暗黑底仅 3.3:1,不达 AA;本值浅红,
|
||||
暗黑模式经 html[data-theme='dark'] 覆盖(#F87171)后于暗底达 AA。 */
|
||||
--color-error-text: #C41E3A;
|
||||
|
||||
--color-info: #3B82F6;
|
||||
--color-info-rgb: 59 130 246;
|
||||
@@ -419,6 +422,17 @@ html[data-theme='dark'] {
|
||||
/* 链接反色 */
|
||||
--color-link: #F8FAFC;
|
||||
--color-link-hover: #E04A68;
|
||||
|
||||
/* 功能色反色:浅色调背景 → 深色同色调底;文字/图标 → 同色调浅色,于暗底达 AA。
|
||||
按钮底色(DEFAULT)与边框(border-*)保持品牌饱和色,暗黑底仍可读/可见,故不覆盖。 */
|
||||
--color-success-bg: #0E2A1A;
|
||||
--color-success-text: #4ADE80;
|
||||
--color-warning-bg: #2A2008;
|
||||
--color-warning-text: #FBBF24;
|
||||
--color-error-bg: #2A1418;
|
||||
--color-error-text: #F87171;
|
||||
--color-info-bg: #0E1A2A;
|
||||
--color-info-text: #60A5FA;
|
||||
}
|
||||
|
||||
/* html/body 背景跟随主题 token(覆盖顶部 !important 硬编码) */
|
||||
|
||||
Reference in New Issue
Block a user