feat: 接入深色配色(浅色 / 深色 / 跟随系统)
- settings.site 新增 theme,落在 <html data-scheme> 上 - 深色完全由组件库的令牌块接管,页面与组件都不用改 - 后台「页头 → 配色」可选,保存后前后台即时切换 - 顶部背景图是浅色渐变图,深色下隐藏(.site-background)
This commit is contained in:
@@ -206,6 +206,17 @@ impl Default for ActionSettings {
|
||||
}
|
||||
}
|
||||
|
||||
/// 配色:浅色 / 深色 / 跟随系统。
|
||||
/// 落到 `<html data-scheme>` 上,深色由组件库的令牌块接管,页面不用改组件。
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ThemeSetting {
|
||||
Light,
|
||||
Dark,
|
||||
#[default]
|
||||
Auto,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(default)]
|
||||
pub struct SiteMetaSettings {
|
||||
@@ -213,6 +224,8 @@ pub struct SiteMetaSettings {
|
||||
pub title: String,
|
||||
/// favicon 地址;空字符串表示用内置图标
|
||||
pub favicon: String,
|
||||
/// 配色
|
||||
pub theme: ThemeSetting,
|
||||
pub brand: BrandSettings,
|
||||
pub actions: ActionSettings,
|
||||
}
|
||||
@@ -222,6 +235,7 @@ impl Default for SiteMetaSettings {
|
||||
Self {
|
||||
title: "Stash · 本地应用工作区".into(),
|
||||
favicon: String::new(),
|
||||
theme: ThemeSetting::Auto,
|
||||
brand: BrandSettings::default(),
|
||||
actions: ActionSettings::default(),
|
||||
}
|
||||
|
||||
@@ -34,6 +34,11 @@
|
||||
background: linear-gradient(to bottom, transparent, var(--su-bg-page));
|
||||
}
|
||||
|
||||
/* 这张背景图是浅色渐变图,深色下留着会糊成一层脏灰,直接不显示 */
|
||||
[data-scheme='dark'] .site-background {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ---------- 页头 ---------- */
|
||||
|
||||
.site-header-inner {
|
||||
|
||||
@@ -284,6 +284,7 @@ export default function AppNext() {
|
||||
site: {
|
||||
title: 'Stash · 本地应用工作区',
|
||||
favicon: '',
|
||||
theme: 'auto',
|
||||
brand: { name: 'STASH', mark: 'S', logo: '' },
|
||||
actions: { refresh: true, settings: true, session: true },
|
||||
},
|
||||
@@ -291,6 +292,11 @@ export default function AppNext() {
|
||||
|
||||
const keywordLower = keyword.trim().toLowerCase()
|
||||
|
||||
// 配色:写在 <html data-scheme> 上,深色由组件库的令牌块接管
|
||||
useEffect(() => {
|
||||
document.documentElement.dataset.scheme = site.site.theme
|
||||
}, [site.site.theme])
|
||||
|
||||
// 站点级设置:浏览器标签标题与 favicon(空值回落到内置)
|
||||
useEffect(() => {
|
||||
const title = site.site.title.trim()
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
type SettingsDocument,
|
||||
type SettingsTone,
|
||||
type SiteSettings,
|
||||
type SiteTheme,
|
||||
} from '../../settings'
|
||||
import {
|
||||
createCard,
|
||||
@@ -126,13 +127,14 @@ export function AdminNext() {
|
||||
void load()
|
||||
}, [])
|
||||
|
||||
// 后台壳也要吃站点级设置:保存后标签标题 / favicon 立刻变,否则会以为「设了没反应」
|
||||
// 后台壳也要吃站点级设置:保存后标签标题 / favicon / 配色立刻变,否则会以为「设了没反应」
|
||||
// 注意:这里的 `document` 是设置文档,浏览器文档要用 `window.document`
|
||||
useEffect(() => {
|
||||
const meta = document?.settings.site
|
||||
if (meta === undefined) return
|
||||
const title = meta.title.trim()
|
||||
if (title !== '') window.document.title = title
|
||||
window.document.documentElement.dataset.scheme = meta.theme
|
||||
const href = meta.favicon.trim() === '' ? '/favicon.svg' : meta.favicon.trim()
|
||||
let link = window.document.head.querySelector<HTMLLinkElement>('link[rel="icon"]')
|
||||
if (link === null) {
|
||||
@@ -141,7 +143,7 @@ export function AdminNext() {
|
||||
window.document.head.appendChild(link)
|
||||
}
|
||||
link.href = href
|
||||
}, [document?.settings.site.title, document?.settings.site.favicon])
|
||||
}, [document?.settings.site.title, document?.settings.site.favicon, document?.settings.site.theme])
|
||||
|
||||
const authenticated = session?.authenticated === true
|
||||
|
||||
@@ -1762,6 +1764,12 @@ function CardTable({
|
||||
|
||||
const TONE_SELECT = TONE_OPTIONS.map((option) => ({ value: option.value, label: option.label }))
|
||||
|
||||
const THEME_SELECT = [
|
||||
{ value: 'auto', label: '跟随系统' },
|
||||
{ value: 'light', label: '浅色' },
|
||||
{ value: 'dark', label: '深色' },
|
||||
]
|
||||
|
||||
function HeaderPanel({
|
||||
value,
|
||||
onChange,
|
||||
@@ -1794,6 +1802,13 @@ function HeaderPanel({
|
||||
onChange={(event) => onChangeSite({ ...site, favicon: event.target.value })}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="配色" hint="深色由组件库的令牌块接管,页面与组件都不用改">
|
||||
<Select
|
||||
options={THEME_SELECT}
|
||||
value={site.theme}
|
||||
onChange={(theme) => onChangeSite({ ...site, theme: theme as SiteTheme })}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<Text size="sm" strong>
|
||||
首页 Logo
|
||||
|
||||
@@ -64,11 +64,15 @@ export type SiteSettings = {
|
||||
site: SiteMetaSettings
|
||||
}
|
||||
|
||||
export type SiteTheme = 'light' | 'dark' | 'auto'
|
||||
|
||||
export type SiteMetaSettings = {
|
||||
/** 浏览器标签标题 */
|
||||
title: string
|
||||
/** favicon 地址;空表示用内置图标 */
|
||||
favicon: string
|
||||
/** 配色:浅色 / 深色 / 跟随系统 */
|
||||
theme: SiteTheme
|
||||
brand: {
|
||||
/** 页头品牌文字,可留空 */
|
||||
name: string
|
||||
@@ -137,6 +141,7 @@ export function defaultSettings(): SiteSettings {
|
||||
site: {
|
||||
title: 'Stash · 本地应用工作区',
|
||||
favicon: '',
|
||||
theme: 'auto',
|
||||
brand: { name: 'STASH', mark: 'S', logo: '' },
|
||||
actions: { refresh: true, settings: true, session: true },
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user