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:
2026-09-01 07:26:53 +08:00
co-authored by 阿睿
parent e0828db0cc
commit 4af238bdd9
10 changed files with 85 additions and 68 deletions
@@ -106,12 +106,12 @@ function JsonEditor({
placeholder={placeholder || '输入 JSON 数据'} placeholder={placeholder || '输入 JSON 数据'}
rows={10} 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 ${ 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} spellCheck={false}
/> />
{parseError && <p className="mt-1 text-xs text-red-500">{parseError}</p>} {parseError && <p className="mt-1 text-xs text-error-text">{parseError}</p>}
{error && !parseError && <p className="mt-1 text-xs text-red-500">{error}</p>} {error && !parseError && <p className="mt-1 text-xs text-error-text">{error}</p>}
</div> </div>
); );
} }
@@ -119,7 +119,7 @@ function JsonEditor({
function FieldError({ error }: { error?: string }) { function FieldError({ error }: { error?: string }) {
if (!error) return null; if (!error) return null;
return ( 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" /> <AlertTriangle className="w-3 h-3" />
{error} {error}
</p> </p>
@@ -422,7 +422,7 @@ export default function ContentEditorPage() {
<div key={key}> <div key={key}>
<label htmlFor={key} className="block text-sm font-medium text-text-secondary mb-1"> <label htmlFor={key} className="block text-sm font-medium text-text-secondary mb-1">
{field.label} {field.label}
{field.required && <span className="text-red-500 ml-1">*</span>} {field.required && <span className="text-error-text ml-1">*</span>}
</label> </label>
{field.description && ( {field.description && (
<p className="text-xs text-text-muted mb-1.5">{field.description}</p> <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)} onBlur={() => handleFieldBlur(field.name)}
placeholder={field.placeholder} 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 ${ 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} /> <FieldError error={error} />
@@ -447,7 +447,7 @@ export default function ContentEditorPage() {
<div key={key}> <div key={key}>
<label htmlFor={key} className="block text-sm font-medium text-text-secondary mb-1"> <label htmlFor={key} className="block text-sm font-medium text-text-secondary mb-1">
{field.label} {field.label}
{field.required && <span className="text-red-500 ml-1">*</span>} {field.required && <span className="text-error-text ml-1">*</span>}
</label> </label>
{field.description && ( {field.description && (
<p className="text-xs text-text-muted mb-1.5">{field.description}</p> <p className="text-xs text-text-muted mb-1.5">{field.description}</p>
@@ -460,7 +460,7 @@ export default function ContentEditorPage() {
placeholder={field.placeholder} placeholder={field.placeholder}
rows={4} 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 ${ 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} /> <FieldError error={error} />
@@ -472,7 +472,7 @@ export default function ContentEditorPage() {
<div key={key}> <div key={key}>
<label htmlFor={key} className="block text-sm font-medium text-text-secondary mb-1"> <label htmlFor={key} className="block text-sm font-medium text-text-secondary mb-1">
{field.label} {field.label}
{field.required && <span className="text-red-500 ml-1">*</span>} {field.required && <span className="text-error-text ml-1">*</span>}
</label> </label>
<input <input
id={key} id={key}
@@ -480,7 +480,7 @@ export default function ContentEditorPage() {
value={(value as number) ?? ''} value={(value as number) ?? ''}
onChange={(e) => handleFieldChange(field.name, parseFloat(e.target.value) || 0)} 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 ${ 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} /> <FieldError error={error} />
@@ -532,7 +532,7 @@ export default function ContentEditorPage() {
<div key={key}> <div key={key}>
<label htmlFor={key} className="block text-sm font-medium text-text-secondary mb-1"> <label htmlFor={key} className="block text-sm font-medium text-text-secondary mb-1">
{field.label} {field.label}
{field.required && <span className="text-red-500 ml-1">*</span>} {field.required && <span className="text-error-text ml-1">*</span>}
</label> </label>
{field.description && ( {field.description && (
<p className="text-xs text-text-muted mb-1.5">{field.description}</p> <p className="text-xs text-text-muted mb-1.5">{field.description}</p>
@@ -636,13 +636,13 @@ export default function ContentEditorPage() {
{!isNew && ( {!isNew && (
<div className="flex items-center gap-1.5 text-xs"> <div className="flex items-center gap-1.5 text-xs">
{autoSaveStatus === 'saving' && ( {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" /> <Loader2 className="w-3 h-3 animate-spin" />
保存中... 保存中...
</span> </span>
)} )}
{autoSaveStatus === 'saved' && ( {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" /> <CheckCircle className="w-3 h-3" />
已保存 已保存
{lastSaved && ( {lastSaved && (
@@ -653,7 +653,7 @@ export default function ContentEditorPage() {
</span> </span>
)} )}
{autoSaveStatus === 'unsaved' && ( {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" /> <CloudOff className="w-3 h-3" />
未保存 未保存
</span> </span>
@@ -683,7 +683,7 @@ export default function ContentEditorPage() {
<button <button
onClick={handlePublishClick} onClick={handlePublishClick}
disabled={saving || publishPending} 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 ? ( {publishPending ? (
<Loader2 className="w-4 h-4 animate-spin" /> <Loader2 className="w-4 h-4 animate-spin" />
@@ -707,19 +707,19 @@ export default function ContentEditorPage() {
</div> </div>
{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} {error}
</div> </div>
)} )}
{/* 验证错误汇总 */} {/* 验证错误汇总 */}
{Object.keys(validationErrors).length > 0 && ( {Object.keys(validationErrors).length > 0 && (
<div className="bg-amber-50 border border-amber-200 rounded-lg px-4 py-3"> <div className="bg-warning-bg border border-border-secondary rounded-lg px-4 py-3">
<div className="flex items-center gap-2 text-amber-700 text-sm font-medium mb-1"> <div className="flex items-center gap-2 text-warning-text text-sm font-medium mb-1">
<AlertTriangle className="w-4 h-4" /> <AlertTriangle className="w-4 h-4" />
请修正以下错误 请修正以下错误
</div> </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]) => ( {Object.entries(validationErrors).map(([field, msg]) => (
<li key={field}>{msg}</li> <li key={field}>{msg}</li>
))} ))}
@@ -732,7 +732,7 @@ export default function ContentEditorPage() {
{/* 标题 */} {/* 标题 */}
<div> <div>
<label htmlFor="title" className="block text-sm font-medium text-text-secondary mb-1"> <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> </label>
<input <input
id="title" id="title"
@@ -753,7 +753,7 @@ export default function ContentEditorPage() {
}} }}
placeholder="请输入标题" 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 ${ 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} /> <FieldError error={validationErrors.title} />
@@ -784,7 +784,7 @@ export default function ContentEditorPage() {
}} }}
placeholder="URL 友好标识(留空自动生成)" 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 ${ 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} /> <FieldError error={validationErrors.slug} />
@@ -850,7 +850,7 @@ export default function ContentEditorPage() {
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
{Object.keys(validationErrors).length > 0 && ( {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} 个错误,发布可能会失败。 注意:表单中存在 {Object.keys(validationErrors).length} 个错误,发布可能会失败。
</div> </div>
)} )}
@@ -858,7 +858,7 @@ export default function ContentEditorPage() {
<AlertDialogCancel>取消</AlertDialogCancel> <AlertDialogCancel>取消</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={handlePublishConfirm} onClick={handlePublishConfirm}
className="bg-green-700 hover:bg-green-600 text-white" className="bg-success hover:bg-success-hover text-white"
> >
确认发布 确认发布
</AlertDialogAction> </AlertDialogAction>
+5 -5
View File
@@ -147,7 +147,7 @@ export default function ContentListPage() {
{/* Error */} {/* Error */}
{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} {error}
</div> </div>
)} )}
@@ -195,9 +195,9 @@ export default function ContentListPage() {
<span <span
className={`inline-flex text-xs px-2 py-0.5 rounded-full font-medium ${ className={`inline-flex text-xs px-2 py-0.5 rounded-full font-medium ${
item.status === 'published' item.status === 'published'
? 'bg-green-50 text-green-700' ? 'bg-success-bg text-success-text'
: item.status === 'draft' : item.status === 'draft'
? 'bg-yellow-50 text-yellow-700' ? 'bg-warning-bg text-warning-text'
: 'bg-bg-hover text-text-tertiary' : 'bg-bg-hover text-text-tertiary'
}`} }`}
> >
@@ -218,7 +218,7 @@ export default function ContentListPage() {
</button> </button>
<button <button
onClick={() => openDeleteDialog(item.id)} 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="删除" title="删除"
> >
<Trash2 className="w-4 h-4" /> <Trash2 className="w-4 h-4" />
@@ -271,7 +271,7 @@ export default function ContentListPage() {
<AlertDialogCancel onClick={() => setPendingDeleteId(null)}>取消</AlertDialogCancel> <AlertDialogCancel onClick={() => setPendingDeleteId(null)}>取消</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={handleConfirmDelete} onClick={handleConfirmDelete}
className="bg-red-600 hover:bg-red-700 text-white" className="bg-error hover:bg-error-hover text-white"
> >
删除 删除
</AlertDialogAction> </AlertDialogAction>
+1 -1
View File
@@ -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" className="bg-bg-primary rounded-xl shadow-sm border border-border-primary p-6 space-y-5"
> >
{error && ( {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} {error}
</div> </div>
)} )}
+3 -3
View File
@@ -172,7 +172,7 @@ export default function MediaPage() {
title="复制链接" title="复制链接"
> >
{copiedId === item.id ? ( {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" /> <Copy className="w-3 h-3" />
)} )}
@@ -180,7 +180,7 @@ export default function MediaPage() {
</button> </button>
<button <button
onClick={() => openDeleteDialog(item.id)} 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="删除" title="删除"
> >
<Trash2 className="w-3 h-3" /> <Trash2 className="w-3 h-3" />
@@ -224,7 +224,7 @@ export default function MediaPage() {
<AlertDialogCancel onClick={() => setPendingDeleteId(null)}>取消</AlertDialogCancel> <AlertDialogCancel onClick={() => setPendingDeleteId(null)}>取消</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={handleConfirmDelete} onClick={handleConfirmDelete}
className="bg-red-600 hover:bg-red-700 text-white" className="bg-error hover:bg-error-hover text-white"
> >
删除 删除
</AlertDialogAction> </AlertDialogAction>
+11 -11
View File
@@ -38,16 +38,16 @@ const NOTIFICATION_TYPE_LABELS: Record<string, string> = {
}; };
const NOTIFICATION_TYPE_ICONS: Record<string, React.ReactNode> = { const NOTIFICATION_TYPE_ICONS: Record<string, React.ReactNode> = {
review_pending: <AlertCircle className="w-4 h-4 text-amber-500" />, review_pending: <AlertCircle className="w-4 h-4 text-warning-text" />,
review_approved: <CheckCircle className="w-4 h-4 text-green-500" />, review_approved: <CheckCircle className="w-4 h-4 text-success-text" />,
review_rejected: <XCircle className="w-4 h-4 text-red-500" />, review_rejected: <XCircle className="w-4 h-4 text-error-text" />,
item_archived: <Archive className="w-4 h-4 text-text-muted" />, item_archived: <Archive className="w-4 h-4 text-text-muted" />,
}; };
const NOTIFICATION_TYPE_COLORS: Record<string, string> = { const NOTIFICATION_TYPE_COLORS: Record<string, string> = {
review_pending: 'bg-amber-50 border-amber-200', review_pending: 'bg-warning-bg border-border-secondary',
review_approved: 'bg-green-50 border-green-200', review_approved: 'bg-success-bg border-border-secondary',
review_rejected: 'bg-red-50 border-red-200', review_rejected: 'bg-error-bg border-border-secondary',
item_archived: 'bg-bg-secondary border-border-primary', 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"> <p className="text-sm text-text-muted mt-1">
共 {total} 条通知 共 {total} 条通知
{unreadCount > 0 && ( {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} 条未读 {unreadCount} 条未读
</span> </span>
)} )}
@@ -194,7 +194,7 @@ export default function NotificationsPage() {
</div> </div>
{error && ( {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} {error}
</div> </div>
)} )}
@@ -223,12 +223,12 @@ export default function NotificationsPage() {
className={`relative flex items-start gap-4 p-4 rounded-lg border transition-colors ${ className={`relative flex items-start gap-4 p-4 rounded-lg border transition-colors ${
item.read item.read
? 'bg-bg-primary border-border-light' ? '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 */} {/* Unread indicator */}
{!item.read && ( {!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 */} {/* Type icon */}
@@ -278,7 +278,7 @@ export default function NotificationsPage() {
{item.relatedItemId && item.relatedModelCode && ( {item.relatedItemId && item.relatedModelCode && (
<button <button
onClick={() => handleViewRelated(item)} 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" /> <ExternalLink className="w-3 h-3" />
查看详情 查看详情
+14 -14
View File
@@ -50,7 +50,7 @@ interface DashboardStats {
const modelCards = [ const modelCards = [
{ code: 'news', name: '新闻资讯', icon: Newspaper, color: 'bg-blue-50 text-blue-600' }, { 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: '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: 'product', name: '产品管理', icon: Box, color: 'bg-purple-50 text-purple-600' },
{ code: 'solution', name: '解决方案', icon: Layers, color: 'bg-teal-50 text-teal-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' }, { 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> = { const STATUS_COLORS: Record<string, string> = {
draft: 'bg-bg-hover text-text-secondary', draft: 'bg-bg-hover text-text-secondary',
review: 'bg-amber-100 text-amber-700', review: 'bg-warning-bg text-warning-text',
published: 'bg-green-100 text-green-700', published: 'bg-success-bg text-success-text',
archived: 'bg-red-100 text-red-700', archived: 'bg-error-bg text-error-text',
}; };
const NOTIFICATION_TYPE_ICONS: Record<string, React.ReactNode> = { const NOTIFICATION_TYPE_ICONS: Record<string, React.ReactNode> = {
review_pending: <AlertCircle className="w-4 h-4 text-amber-500" />, review_pending: <AlertCircle className="w-4 h-4 text-warning-text" />,
review_approved: <CheckCircle className="w-4 h-4 text-green-500" />, review_approved: <CheckCircle className="w-4 h-4 text-success-text" />,
review_rejected: <XCircle className="w-4 h-4 text-red-500" />, review_rejected: <XCircle className="w-4 h-4 text-error-text" />,
item_archived: <Archive className="w-4 h-4 text-text-muted" />, 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?.models ?? '-', icon: FileText, color: 'bg-blue-50' },
{ label: '内容条目', value: stats?.items ?? '-', icon: Layers, color: 'bg-purple-50' }, { label: '内容条目', value: stats?.items ?? '-', icon: Layers, color: 'bg-purple-50' },
{ label: '页面区域', value: stats?.zones ?? '-', icon: Briefcase, color: 'bg-amber-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: '待审核', label: '待审核',
value: stats?.pendingReviews ?? '-', value: stats?.pendingReviews ?? '-',
icon: AlertCircle, icon: AlertCircle,
color: 'bg-red-50', color: 'bg-error-bg',
highlight: (stats?.pendingReviews ?? 0) > 0, highlight: (stats?.pendingReviews ?? 0) > 0,
}, },
{ {
@@ -152,21 +152,21 @@ export default function AdminDashboardPage() {
<div <div
key={stat.label} key={stat.label}
className={`bg-bg-primary rounded-lg border p-4 ${ 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 className="flex items-center justify-between">
<div> <div>
<p className="text-xs text-text-muted">{stat.label}</p> <p className="text-xs text-text-muted">{stat.label}</p>
<p className={`text-2xl font-bold mt-1 ${ <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} {fetching ? '-' : stat.value}
</p> </p>
</div> </div>
<div className={`w-10 h-10 rounded-lg ${stat.color} flex items-center justify-center`}> <div className={`w-10 h-10 rounded-lg ${stat.color} flex items-center justify-center`}>
<stat.icon className={`w-5 h-5 ${ <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>
</div> </div>
@@ -202,7 +202,7 @@ export default function AdminDashboardPage() {
<h2 className="text-sm font-semibold text-text-primary">最近通知</h2> <h2 className="text-sm font-semibold text-text-primary">最近通知</h2>
<button <button
onClick={() => router.push('/admin/notifications')} 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> </button>
@@ -230,7 +230,7 @@ export default function AdminDashboardPage() {
{new Date(n.createdAt).toLocaleDateString('zh-CN')} {new Date(n.createdAt).toLocaleDateString('zh-CN')}
</p> </p>
</div> </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>
))} ))}
</div> </div>
+1 -1
View File
@@ -154,7 +154,7 @@ export default function RolesPage() {
{/* Alerts */} {/* Alerts */}
{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} {error}
</div> </div>
)} )}
+9 -9
View File
@@ -45,10 +45,10 @@ const ROLE_LABELS: Record<string, string> = {
}; };
const ROLE_COLORS: Record<string, string> = { const ROLE_COLORS: Record<string, string> = {
super_admin: 'bg-red-100 text-red-700', super_admin: 'bg-error-bg text-error-text',
content_admin: 'bg-blue-100 text-blue-700', content_admin: 'bg-info-bg text-info-text',
content_editor: 'bg-green-100 text-green-700', content_editor: 'bg-success-bg text-success-text',
reviewer: 'bg-amber-100 text-amber-700', reviewer: 'bg-warning-bg text-warning-text',
readonly: 'bg-bg-hover text-text-secondary', readonly: 'bg-bg-hover text-text-secondary',
}; };
@@ -266,7 +266,7 @@ export default function UsersPage() {
</div> </div>
{error && ( {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} {error}
</div> </div>
)} )}
@@ -344,8 +344,8 @@ export default function UsersPage() {
<span <span
className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${ className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${
u.status === 1 u.status === 1
? 'bg-green-100 text-green-700' ? 'bg-success-bg text-success-text'
: 'bg-red-100 text-red-700' : 'bg-error-bg text-error-text'
}`} }`}
> >
{u.status === 1 ? '启用' : '禁用'} {u.status === 1 ? '启用' : '禁用'}
@@ -368,7 +368,7 @@ export default function UsersPage() {
</button> </button>
<button <button
onClick={() => { setPendingDeleteId(u.id); setDeleteDialogOpen(true); }} 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="删除" title="删除"
> >
<Trash2 className="w-4 h-4" /> <Trash2 className="w-4 h-4" />
@@ -529,7 +529,7 @@ export default function UsersPage() {
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>取消</AlertDialogCancel> <AlertDialogCancel>取消</AlertDialogCancel>
<AlertDialogAction onClick={handleDelete} className="bg-red-600 hover:bg-red-700"> <AlertDialogAction onClick={handleDelete} className="bg-error hover:bg-error-hover">
确认删除 确认删除
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
+14
View File
@@ -114,6 +114,9 @@ body {
--color-error-hover: #A01830; --color-error-hover: #A01830;
--color-error-bg: #FEF2F4; --color-error-bg: #FEF2F4;
--color-error-rgb: 196 30 58; --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: #3B82F6;
--color-info-rgb: 59 130 246; --color-info-rgb: 59 130 246;
@@ -419,6 +422,17 @@ html[data-theme='dark'] {
/* 链接反色 */ /* 链接反色 */
--color-link: #F8FAFC; --color-link: #F8FAFC;
--color-link-hover: #E04A68; --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 硬编码) */ /* html/body 背景跟随主题 token(覆盖顶部 !important 硬编码) */
+3
View File
@@ -93,6 +93,9 @@ module.exports = {
hover: 'var(--color-error-hover)', hover: 'var(--color-error-hover)',
bg: 'var(--color-error-bg)', bg: 'var(--color-error-bg)',
rgb: 'var(--color-error-rgb)', rgb: 'var(--color-error-rgb)',
/** 文字请用此变体:DEFAULT(品牌红 #C41E3A)在暗黑底仅 3.3:1,不达 AA;
* 本值浅红,暗黑模式经 html[data-theme='dark'] 覆盖后达 AA。 */
text: 'var(--color-error-text)',
}, },
info: { info: {
DEFAULT: 'rgb(var(--color-info-rgb) / <alpha-value>)', DEFAULT: 'rgb(var(--color-info-rgb) / <alpha-value>)',