Dev #25

Merged
zhangxiang merged 46 commits from dev into main 2026-09-01 14:55:21 +08:00
10 changed files with 85 additions and 68 deletions
Showing only changes of commit 4af238bdd9 - Show all commits
@@ -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>
+5 -5
View File
@@ -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>
+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"
>
{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>
)}
+3 -3
View File
@@ -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>
+11 -11
View File
@@ -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
View File
@@ -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>
+1 -1
View File
@@ -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>
)}
+9 -9
View File
@@ -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>
+14
View File
@@ -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 硬编码) */
+3
View File
@@ -93,6 +93,9 @@ module.exports = {
hover: 'var(--color-error-hover)',
bg: 'var(--color-error-bg)',
rgb: 'var(--color-error-rgb)',
/** 文字请用此变体:DEFAULT(品牌红 #C41E3A)在暗黑底仅 3.3:1,不达 AA;
* 本值浅红,暗黑模式经 html[data-theme='dark'] 覆盖后达 AA。 */
text: 'var(--color-error-text)',
},
info: {
DEFAULT: 'rgb(var(--color-info-rgb) / <alpha-value>)',