From 0811213db0474cd80caff1445f98334f5de2bebe Mon Sep 17 00:00:00 2001 From: DrSmoothl <1787882683@qq.com> Date: Mon, 16 Mar 2026 22:19:05 +0800 Subject: [PATCH 1/5] feat: enhance background layer handling and uploader functionality - Introduced automatic overlay opacity and gradient based on layer ID in BackgroundLayer component. - Added disabled state to BackgroundUploader, preventing actions when disabled. - Updated component CSS editor to handle disabled state, preventing changes when disabled. - Modified Header and Layout components to manage background inheritance from the page layer. - Improved Sidebar and Card components to respect background inheritance and layering. - Refactored theme management to include default accent color and normalization functions. - Enhanced AppearanceTab to manage accent color changes with debouncing and validation. - Added UI feedback for inherited background layers in AppearanceTab. --- .../background-effects-controls.tsx | 95 ++++------- dashboard/src/components/background-layer.tsx | 40 ++++- .../src/components/background-uploader.tsx | 19 ++- .../src/components/component-css-editor.tsx | 9 +- dashboard/src/components/layout/Header.tsx | 14 +- dashboard/src/components/layout/Layout.tsx | 17 +- dashboard/src/components/layout/Sidebar.tsx | 13 +- .../components/ui/card-with-background.tsx | 8 +- .../components/ui/dialog-with-background.tsx | 8 +- dashboard/src/hooks/use-background.ts | 20 ++- dashboard/src/index.css | 44 +++--- dashboard/src/lib/settings-manager.ts | 4 +- dashboard/src/lib/theme/palette.ts | 36 ++++- dashboard/src/lib/theme/storage.ts | 9 +- dashboard/src/lib/theme/tokens.ts | 44 +++--- .../src/routes/settings/AppearanceTab.tsx | 149 +++++++++++++++--- 16 files changed, 359 insertions(+), 170 deletions(-) diff --git a/dashboard/src/components/background-effects-controls.tsx b/dashboard/src/components/background-effects-controls.tsx index 3c31b20a..e466dd30 100644 --- a/dashboard/src/components/background-effects-controls.tsx +++ b/dashboard/src/components/background-effects-controls.tsx @@ -18,18 +18,9 @@ import { defaultBackgroundEffects, } from '@/lib/theme/tokens' -// ============================================================================ -// Helper Functions -// ============================================================================ - -/** - * 将 HSL 字符串转换为 HEX 格式 - * (从 settings.tsx 移植) - */ function hslToHex(hsl: string): string { if (!hsl) return '#000000' - // 解析 "221.2 83.2% 53.3%" 格式 const parts = hsl.split(' ').filter(Boolean) if (parts.length < 3) return '#000000' @@ -39,72 +30,65 @@ function hslToHex(hsl: string): string { const sDecimal = s / 100 const lDecimal = l / 100 - const c = (1 - Math.abs(2 * lDecimal - 1)) * sDecimal const x = c * (1 - Math.abs(((h / 60) % 2) - 1)) const m = lDecimal - c / 2 - let r = 0, - g = 0, - b = 0 + let r = 0 + let g = 0 + let b = 0 if (h >= 0 && h < 60) { r = c g = x - b = 0 } else if (h >= 60 && h < 120) { r = x g = c - b = 0 } else if (h >= 120 && h < 180) { - r = 0 g = c b = x } else if (h >= 180 && h < 240) { - r = 0 g = x b = c } else if (h >= 240 && h < 300) { r = x - g = 0 b = c } else if (h >= 300 && h < 360) { r = c - g = 0 b = x } - const toHex = (n: number) => { - const hex = Math.round((n + m) * 255).toString(16) - return hex.length === 1 ? '0' + hex : hex + const toHex = (value: number) => { + const hex = Math.round((value + m) * 255).toString(16) + return hex.length === 1 ? `0${hex}` : hex } return `#${toHex(r)}${toHex(g)}${toHex(b)}` } -// ============================================================================ -// Component -// ============================================================================ - type BackgroundEffectsControlsProps = { effects: BackgroundEffects onChange: (effects: BackgroundEffects) => void + disabled?: boolean } export function BackgroundEffectsControls({ effects, onChange, + disabled = false, }: BackgroundEffectsControlsProps) { - // 处理数值变更 const handleValueChange = (key: keyof BackgroundEffects, value: number) => { + if (disabled) return + onChange({ ...effects, [key]: value, }) } - // 处理颜色变更 const handleColorChange = (e: React.ChangeEvent) => { + if (disabled) return + const hex = e.target.value const hsl = hexToHSL(hex) onChange({ @@ -113,35 +97,38 @@ export function BackgroundEffectsControls({ }) } - // 处理位置变更 const handlePositionChange = (value: string) => { + if (disabled) return + onChange({ ...effects, position: value as BackgroundEffects['position'], }) } - // 处理渐变变更 const handleGradientChange = (e: React.ChangeEvent) => { + if (disabled) return + onChange({ ...effects, gradientOverlay: e.target.value, }) } - // 重置为默认值 const handleReset = () => { + if (disabled) return onChange(defaultBackgroundEffects) } return ( -
+

背景效果调节

- {/* 1. Blur (模糊) */}
- - {effects.blur}px - + {effects.blur}px
handleValueChange('blur', vals[0])} />
- {/* 2. Overlay Color (遮罩颜色) */}
@@ -176,18 +160,19 @@ export function BackgroundEffectsControls({ type="color" value={hslToHex(effects.overlayColor)} onChange={handleColorChange} + disabled={disabled} className="h-[150%] w-[150%] -translate-x-1/4 -translate-y-1/4 cursor-pointer border-0 p-0" />
- {/* 3. Overlay Opacity (遮罩不透明度) */}
@@ -200,17 +185,15 @@ export function BackgroundEffectsControls({ min={0} max={100} step={1} - onValueChange={(vals) => - handleValueChange('overlayOpacity', vals[0] / 100) - } + disabled={disabled} + onValueChange={(vals) => handleValueChange('overlayOpacity', vals[0] / 100)} />
- {/* 4. Position (位置) */}
- + @@ -222,69 +205,61 @@ export function BackgroundEffectsControls({
- {/* 5. Brightness (亮度) */}
- - {effects.brightness}% - + {effects.brightness}%
handleValueChange('brightness', vals[0])} />
- {/* 6. Contrast (对比度) */}
- - {effects.contrast}% - + {effects.contrast}%
handleValueChange('contrast', vals[0])} />
- {/* 7. Saturate (饱和度) */}
- - {effects.saturate}% - + {effects.saturate}%
handleValueChange('saturate', vals[0])} />
- {/* 8. Gradient Overlay (渐变叠加) */}
-

- 可选:输入有效的 CSS gradient 字符串 -

+

可选:输入有效的 CSS gradient 字符串

diff --git a/dashboard/src/components/background-layer.tsx b/dashboard/src/components/background-layer.tsx index e1f335ca..15dc7551 100644 --- a/dashboard/src/components/background-layer.tsx +++ b/dashboard/src/components/background-layer.tsx @@ -8,6 +8,31 @@ type BackgroundLayerProps = { layerId: string } +function getAutoOverlayOpacity(layerId: string): number { + switch (layerId) { + case 'page': + return 0.62 + case 'header': + return 0.72 + case 'sidebar': + return 0.78 + case 'card': + return 0.82 + case 'dialog': + return 0.88 + default: + return 0.68 + } +} + +function getAutoGradientOverlay(layerId: string): string | undefined { + if (layerId !== 'page') { + return undefined + } + + return 'linear-gradient(to bottom, hsl(var(--background) / 0.82), hsl(var(--background) / 0.52) 28%, hsl(var(--background) / 0.7) 100%)' +} + function buildFilterString(effects: BackgroundConfig['effects']): string { const parts: string[] = [] if (effects.blur > 0) parts.push(`blur(${effects.blur}px)`) @@ -84,10 +109,17 @@ export function BackgroundLayer({ config, layerId }: BackgroundLayerProps) { const filterString = buildFilterString(config.effects) const { overlayColor, overlayOpacity, gradientOverlay } = config.effects + const hasExplicitOverlay = overlayOpacity > 0 + const effectiveOverlayOpacity = hasExplicitOverlay ? overlayOpacity : getAutoOverlayOpacity(layerId) + const effectiveOverlayColor = hasExplicitOverlay + ? `hsl(${overlayColor} / ${effectiveOverlayOpacity})` + : `hsl(var(--background) / ${effectiveOverlayOpacity})` + const effectiveGradientOverlay = gradientOverlay || getAutoGradientOverlay(layerId) return (
)} - {overlayOpacity > 0 && ( + {effectiveOverlayOpacity > 0 && (
)} - {gradientOverlay && ( + {effectiveGradientOverlay && (
diff --git a/dashboard/src/components/background-uploader.tsx b/dashboard/src/components/background-uploader.tsx index 215b75dc..503e36b0 100644 --- a/dashboard/src/components/background-uploader.tsx +++ b/dashboard/src/components/background-uploader.tsx @@ -12,9 +12,10 @@ type BackgroundUploaderProps = { assetId?: string onAssetSelect: (id: string | undefined) => void className?: string + disabled?: boolean } -export function BackgroundUploader({ assetId, onAssetSelect, className }: BackgroundUploaderProps) { +export function BackgroundUploader({ assetId, onAssetSelect, className, disabled = false }: BackgroundUploaderProps) { const { getAssetUrl } = useAssetStore() const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState(null) @@ -62,6 +63,7 @@ export function BackgroundUploader({ assetId, onAssetSelect, className }: Backgr }, [assetId, getAssetUrl, onAssetSelect]) const handleFile = async (file: File) => { + if (disabled) return setError(null) setIsLoading(true) @@ -87,7 +89,7 @@ export function BackgroundUploader({ assetId, onAssetSelect, className }: Backgr } const handleUrlUpload = async () => { - if (!urlInput) return + if (disabled || !urlInput) return setError(null) setIsLoading(true) @@ -118,6 +120,7 @@ export function BackgroundUploader({ assetId, onAssetSelect, className }: Backgr const handleDrag = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() + if (disabled) return if (e.type === 'dragenter' || e.type === 'dragover') { setDragActive(true) } else if (e.type === 'dragleave') { @@ -130,12 +133,15 @@ export function BackgroundUploader({ assetId, onAssetSelect, className }: Backgr e.stopPropagation() setDragActive(false) + if (disabled) return + if (e.dataTransfer.files && e.dataTransfer.files[0]) { handleFile(e.dataTransfer.files[0]) } } const handleClear = () => { + if (disabled) return onAssetSelect(undefined) setPreviewUrl(undefined) setAssetType(undefined) @@ -143,7 +149,7 @@ export function BackgroundUploader({ assetId, onAssetSelect, className }: Backgr } return ( -
+
@@ -151,6 +157,7 @@ export function BackgroundUploader({ assetId, onAssetSelect, className }: Backgr
@@ -212,6 +220,7 @@ export function BackgroundUploader({ assetId, onAssetSelect, className }: Backgr variant="outline" size="sm" onClick={() => fileInputRef.current?.click()} + disabled={disabled} > 选择文件 @@ -224,6 +233,7 @@ export function BackgroundUploader({ assetId, onAssetSelect, className }: Backgr className="hidden" accept="image/*,video/mp4,video/webm" onChange={(e) => { + if (disabled) return if (e.target.files?.[0]) { handleFile(e.target.files[0]) } @@ -250,6 +260,7 @@ export function BackgroundUploader({ assetId, onAssetSelect, className }: Backgr placeholder="https://example.com/image.jpg" className="pl-9" value={urlInput} + disabled={disabled} onChange={(e) => setUrlInput(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') { @@ -262,7 +273,7 @@ export function BackgroundUploader({ assetId, onAssetSelect, className }: Backgr diff --git a/dashboard/src/components/component-css-editor.tsx b/dashboard/src/components/component-css-editor.tsx index f4449693..d91136b4 100644 --- a/dashboard/src/components/component-css-editor.tsx +++ b/dashboard/src/components/component-css-editor.tsx @@ -16,6 +16,7 @@ export type ComponentCSSEditorProps = { label?: string /** 编辑器高度,默认 200px */ height?: string + disabled?: boolean } /** @@ -28,12 +29,13 @@ export function ComponentCSSEditor({ onChange, label, height = '200px', + disabled = false, }: ComponentCSSEditorProps) { // 实时计算 CSS 警告 const { warnings } = sanitizeCSS(value) return ( -
+