feat(console): 按钮改细描边大图标;下拉贴紧+玻璃化+圆角统一;纯色/流光背景 HSL 二级设置

This commit is contained in:
Aisland
2026-09-01 12:31:46 +08:00
parent f59ea41c6e
commit 92862c0f40
5 changed files with 255 additions and 49 deletions
+72 -11
View File
@@ -243,6 +243,16 @@ type SplitEnv = {
setTheme: (th: 'dark' | 'light' | 'system') => void
getCols: () => number
setCols: (n: number) => void
getShape: () => 'square' | 'circle'
setShape: (s: 'square' | 'circle') => void
getBg: () => 'plain' | 'glow' | 'photo'
setBg: (m: 'plain' | 'glow' | 'photo') => void
getPhoto: () => string
setPhoto: (dataUrl: string) => void
getPlainHsl: () => { h: number; s: number; l: number }
setPlainHsl: (v: { h: number; s: number; l: number }) => void
getGlowHsl: () => { h: number; s: number; l: number }
setGlowHsl: (v: { h: number; s: number; l: number }) => void
}
}
let splitEnv: SplitEnv | null = null
@@ -1021,6 +1031,9 @@ function ConsolePane() {
const [bg, setBgState] = useState<'plain' | 'glow' | 'photo'>(() => splitEnv?.console?.getBg?.() ?? 'glow')
const [bgPhoto, setBgPhoto] = useState<string>(() => splitEnv?.console?.getPhoto?.() ?? '')
const [openMenu, setOpenMenu] = useState<string | null>(null)
const [bgEdit, setBgEdit] = useState<'plain' | 'glow' | null>(null)
const [plainHsl, setPlainHslState] = useState<{ h: number; s: number; l: number }>(() => splitEnv?.console?.getPlainHsl?.() ?? { h: 220, s: 31, l: 6 })
const [glowHsl, setGlowHslState] = useState<{ h: number; s: number; l: number }>(() => splitEnv?.console?.getGlowHsl?.() ?? { h: 0, s: 100, l: 100 })
const gridRef = useRef<HTMLDivElement | null>(null)
const firstRectsRef = useRef<Map<string, { x: number; y: number }> | null>(null)
const onCols = (n: number) => {
@@ -1077,6 +1090,26 @@ function ConsolePane() {
setBgState(m)
splitEnv?.console?.setBg?.(m)
}
const editHsl = (kind: 'plain' | 'glow', patch: Partial<{ h: number; s: number; l: number }>) => {
if (kind === 'plain') {
const next = { ...plainHsl, ...patch }
setPlainHslState(next)
splitEnv?.console?.setPlainHsl?.(next)
} else {
const next = { ...glowHsl, ...patch }
setGlowHslState(next)
splitEnv?.console?.setGlowHsl?.(next)
}
}
const resetHsl = (kind: 'plain' | 'glow') => {
if (kind === 'plain') {
setPlainHslState({ h: 220, s: 31, l: 6 })
splitEnv?.console?.setPlainHsl?.({ h: 220, s: 31, l: 6 })
} else {
setGlowHslState({ h: 0, s: 100, l: 100 })
splitEnv?.console?.setGlowHsl?.({ h: 0, s: 100, l: 100 })
}
}
useLayoutEffect(() => {
const first = firstRectsRef.current
if (!first) return
@@ -1152,8 +1185,8 @@ function ConsolePane() {
{ mode: 'system', icon: '🖥️', key: 'console.themeSystem' },
]
return (
<div className="dsh-wt_console" data-wt-theme={resolvedTheme} data-wt-shape={shape}>
<span className="dsh-wt_consoleBg" aria-hidden style={bg === 'photo' && bgPhoto ? { backgroundImage: 'url("' + bgPhoto + '")', backgroundSize: 'cover', backgroundPosition: 'center' } : undefined}>{bg === 'glow' && (<><i className="dsh-wt_blob dsh-wt_blob1" /><i className="dsh-wt_blob dsh-wt_blob2" /><i className="dsh-wt_blob dsh-wt_blob3" /><i className="dsh-wt_blob dsh-wt_blob4" /></>)}</span>
<div className="dsh-wt_console" data-wt-theme={resolvedTheme} data-wt-shape={shape} style={bg === 'plain' ? { ['--wt-bg' as any]: 'hsl(' + plainHsl.h + ', ' + plainHsl.s + '%, ' + plainHsl.l + '%)' } : undefined}>
<span className="dsh-wt_consoleBg" aria-hidden style={bg === 'photo' && bgPhoto ? { backgroundImage: 'url("' + bgPhoto + '")', backgroundSize: 'cover', backgroundPosition: 'center' } : bg === 'glow' && (glowHsl.h !== 0 || glowHsl.s !== 100 || glowHsl.l !== 100) ? { filter: 'hue-rotate(' + glowHsl.h + 'deg) saturate(' + glowHsl.s + '%) brightness(' + glowHsl.l + '%)' } : undefined}>{bg === 'glow' && (<><i className="dsh-wt_blob dsh-wt_blob1" /><i className="dsh-wt_blob dsh-wt_blob2" /><i className="dsh-wt_blob dsh-wt_blob3" /><i className="dsh-wt_blob dsh-wt_blob4" /></>)}</span>
{openMenu !== null && <div className="dsh-wt_dropMask" onClick={() => setOpenMenu(null)} />}
<div className="dsh-wt_consoleScroll">
<div ref={gridRef} className="dsh-wt_consoleGrid" style={{ ['--wt-cols' as any]: cols }}>
@@ -1205,10 +1238,10 @@ function ConsolePane() {
</div>
<div className="dsh-wt_consoleDockWrap">
<div className="dsh-wt_consoleDock">
<button type="button" className={'dsh-wt_dockBtn' + (openMenu === 'theme' ? ' dsh-wt_dockBtnOn' : '')} title={T('console.themeLabel')} aria-label={T('console.themeLabel')} onClick={() => setOpenMenu(openMenu === 'theme' ? null : 'theme')}><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden><path d="M9.2 1.4A6.6 6.6 0 1 0 14.6 9.2 5.4 5.4 0 0 1 9.2 1.4z" fill="currentColor" /></svg></button>
<button type="button" className={'dsh-wt_dockBtn' + (openMenu === 'shape' ? ' dsh-wt_dockBtnOn' : '')} title={T('console.shapeLabel')} aria-label={T('console.shapeLabel')} onClick={() => setOpenMenu(openMenu === 'shape' ? null : 'shape')}><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden><rect x="2.2" y="2.2" width="7" height="7" rx="1.5" fill="none" stroke="currentColor" strokeWidth="1.4" /><circle cx="10.6" cy="10.6" r="3.8" fill="none" stroke="currentColor" strokeWidth="1.4" /></svg></button>
<button type="button" className={'dsh-wt_dockBtn' + (openMenu === 'bg' ? ' dsh-wt_dockBtnOn' : '')} title={T('console.bgLabel')} aria-label={T('console.bgLabel')} onClick={() => setOpenMenu(openMenu === 'bg' ? null : 'bg')}><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden><rect x="2.2" y="3.2" width="11.6" height="9.6" rx="1.6" fill="none" stroke="currentColor" strokeWidth="1.4" /><circle cx="5.8" cy="6.5" r="1.3" fill="currentColor" /><path d="M3.4 11.4l3-3 2.3 2.3 1.9-1.9 3 2.6" fill="none" stroke="currentColor" strokeWidth="1.4" /></svg></button>
<button type="button" className={'dsh-wt_dockBtn' + (openMenu === 'cols' ? ' dsh-wt_dockBtnOn' : '')} title={T('console.colsLabel')} aria-label={T('console.colsLabel')} onClick={() => setOpenMenu(openMenu === 'cols' ? null : 'cols')}><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden><rect x="2" y="3" width="3.2" height="10" rx="1.2" fill="currentColor" /><rect x="6.4" y="3" width="3.2" height="10" rx="1.2" fill="currentColor" /><rect x="10.8" y="3" width="3.2" height="10" rx="1.2" fill="currentColor" /></svg></button>
<button type="button" className={'dsh-wt_dockBtn' + (openMenu === 'theme' ? ' dsh-wt_dockBtnOn' : '')} title={T('console.themeLabel')} aria-label={T('console.themeLabel')} onClick={() => setOpenMenu(openMenu === 'theme' ? null : 'theme')}><svg width="18" height="18" viewBox="0 0 16 16" aria-hidden><path d="M9.2 1.4A6.6 6.6 0 1 0 14.6 9.2 5.4 5.4 0 0 1 9.2 1.4z" fill="none" stroke="currentColor" strokeWidth="1.1" /></svg></button>
<button type="button" className={'dsh-wt_dockBtn' + (openMenu === 'shape' ? ' dsh-wt_dockBtnOn' : '')} title={T('console.shapeLabel')} aria-label={T('console.shapeLabel')} onClick={() => setOpenMenu(openMenu === 'shape' ? null : 'shape')}><svg width="18" height="18" viewBox="0 0 16 16" aria-hidden><rect x="2.2" y="2.2" width="7" height="7" rx="1.5" fill="none" stroke="currentColor" strokeWidth="1.1" /><circle cx="10.6" cy="10.6" r="3.8" fill="none" stroke="currentColor" strokeWidth="1.1" /></svg></button>
<button type="button" className={'dsh-wt_dockBtn' + (openMenu === 'bg' ? ' dsh-wt_dockBtnOn' : '')} title={T('console.bgLabel')} aria-label={T('console.bgLabel')} onClick={() => { setOpenMenu(openMenu === 'bg' ? null : 'bg'); setBgEdit(null) }}><svg width="18" height="18" viewBox="0 0 16 16" aria-hidden><rect x="2.2" y="3.2" width="11.6" height="9.6" rx="1.6" fill="none" stroke="currentColor" strokeWidth="1.1" /><circle cx="5.9" cy="6.7" r="1.05" fill="none" stroke="currentColor" strokeWidth="1" /><path d="M3.4 11.4l3-3 2.3 2.3 1.9-1.9 3 2.6" fill="none" stroke="currentColor" strokeWidth="1.1" /></svg></button>
<button type="button" className={'dsh-wt_dockBtn' + (openMenu === 'cols' ? ' dsh-wt_dockBtnOn' : '')} title={T('console.colsLabel')} aria-label={T('console.colsLabel')} onClick={() => setOpenMenu(openMenu === 'cols' ? null : 'cols')}><svg width="18" height="18" viewBox="0 0 16 16" aria-hidden><rect x="2.4" y="3.2" width="3" height="9.6" rx="1" fill="none" stroke="currentColor" strokeWidth="1.1" /><rect x="6.5" y="3.2" width="3" height="9.6" rx="1" fill="none" stroke="currentColor" strokeWidth="1.1" /><rect x="10.6" y="3.2" width="3" height="9.6" rx="1" fill="none" stroke="currentColor" strokeWidth="1.1" /></svg></button>
</div>
{openMenu === 'theme' && (
<div className="dsh-wt_drop">
@@ -1224,11 +1257,39 @@ function ConsolePane() {
</div>
)}
{openMenu === 'bg' && (
<div className="dsh-wt_drop">
<button type="button" className={'dsh-wt_dropItem' + (bg === 'plain' ? ' dsh-wt_dropItemOn' : '')} onClick={() => { onBg('plain'); setOpenMenu(null) }}><svg width="13" height="13" viewBox="0 0 16 16" aria-hidden><rect x="3" y="3" width="10" height="10" rx="2" fill="currentColor" /></svg>{T('console.bgPlain')}</button>
<button type="button" className={'dsh-wt_dropItem' + (bg === 'glow' ? ' dsh-wt_dropItemOn' : '')} onClick={() => { onBg('glow'); setOpenMenu(null) }}><svg width="13" height="13" viewBox="0 0 16 16" aria-hidden><circle cx="8" cy="8" r="3" fill="currentColor" /><circle cx="8" cy="8" r="5.5" fill="none" stroke="currentColor" strokeWidth="1" opacity=".5" /></svg>{T('console.bgGlow')}</button>
<button type="button" className={'dsh-wt_dropItem' + (bg === 'photo' ? ' dsh-wt_dropItemOn' : '')} onClick={() => { onBg('photo'); setOpenMenu(null) }}><svg width="13" height="13" viewBox="0 0 16 16" aria-hidden><rect x="2.5" y="3.5" width="11" height="9" rx="1.5" fill="none" stroke="currentColor" strokeWidth="1.2" /><circle cx="5.8" cy="6.6" r="1.1" fill="currentColor" /><path d="M3.2 11.2l2.8-2.8 2.2 2.2 1.8-1.8 2.8 2.4" fill="none" stroke="currentColor" strokeWidth="1.2" /></svg>{T('console.bgPhoto')}</button>
</div>
bgEdit === null ? (
<div className="dsh-wt_drop">
<div className="dsh-wt_dropRow">
<button type="button" className={'dsh-wt_dropItem' + (bg === 'plain' ? ' dsh-wt_dropItemOn' : '')} onClick={() => { onBg('plain'); setOpenMenu(null) }}><svg width="13" height="13" viewBox="0 0 16 16" aria-hidden><rect x="3" y="3" width="10" height="10" rx="2" fill="none" stroke="currentColor" strokeWidth="1.2" /></svg>{T('console.bgPlain')}</button>
<button type="button" className="dsh-wt_dropGear" title={T('console.bgEdit')} aria-label={T('console.bgEdit')} onClick={() => setBgEdit('plain')}><svg width="12" height="12" viewBox="0 0 16 16" aria-hidden><circle cx="8" cy="8" r="2.2" fill="none" stroke="currentColor" strokeWidth="1.1" /><path d="M8 2.4v1.8M8 11.8v1.8M2.4 8h1.8M11.8 8h1.8M4 4l1.3 1.3M10.7 10.7L12 12M12 4l-1.3 1.3M5.3 10.7L4 12" fill="none" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" /></svg></button>
</div>
<div className="dsh-wt_dropRow">
<button type="button" className={'dsh-wt_dropItem' + (bg === 'glow' ? ' dsh-wt_dropItemOn' : '')} onClick={() => { onBg('glow'); setOpenMenu(null) }}><svg width="13" height="13" viewBox="0 0 16 16" aria-hidden><circle cx="8" cy="8" r="3" fill="currentColor" /><circle cx="8" cy="8" r="5.5" fill="none" stroke="currentColor" strokeWidth="1" opacity=".5" /></svg>{T('console.bgGlow')}</button>
<button type="button" className="dsh-wt_dropGear" title={T('console.bgEdit')} aria-label={T('console.bgEdit')} onClick={() => setBgEdit('glow')}><svg width="12" height="12" viewBox="0 0 16 16" aria-hidden><circle cx="8" cy="8" r="2.2" fill="none" stroke="currentColor" strokeWidth="1.1" /><path d="M8 2.4v1.8M8 11.8v1.8M2.4 8h1.8M11.8 8h1.8M4 4l1.3 1.3M10.7 10.7L12 12M12 4l-1.3 1.3M5.3 10.7L4 12" fill="none" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" /></svg></button>
</div>
<div className="dsh-wt_dropRow">
<button type="button" className={'dsh-wt_dropItem' + (bg === 'photo' ? ' dsh-wt_dropItemOn' : '')} onClick={() => { onBg('photo'); setOpenMenu(null) }}><svg width="13" height="13" viewBox="0 0 16 16" aria-hidden><rect x="2.5" y="3.5" width="11" height="9" rx="1.5" fill="none" stroke="currentColor" strokeWidth="1.2" /><circle cx="5.8" cy="6.6" r="1.1" fill="currentColor" /><path d="M3.2 11.2l2.8-2.8 2.2 2.2 1.8-1.8 2.8 2.4" fill="none" stroke="currentColor" strokeWidth="1.2" /></svg>{T('console.bgPhoto')}</button>
</div>
</div>
) : (
<div className="dsh-wt_drop">
<div className="dsh-wt_hslHead">
<button type="button" className="dsh-wt_hslBack" title={T('console.bgEditBack')} aria-label={T('console.bgEditBack')} onClick={() => setBgEdit(null)}>‹</button>
<span className="dsh-wt_hslTitle">{bgEdit === 'plain' ? T('console.bgEditPlain') : T('console.bgEditGlow')}</span>
</div>
{(() => {
const v = bgEdit === 'plain' ? plainHsl : glowHsl
const sMax = bgEdit === 'plain' ? 100 : 200
const lMax = bgEdit === 'plain' ? 100 : 200
return (<>
<div className="dsh-wt_hslRow"><span className="dsh-wt_hslLabel">H</span><input className="dsh-wt_hslSlider" type="range" min={0} max={360} step={1} value={v.h} onChange={(e) => editHsl(bgEdit, { h: Number(e.target.value) })} /><span className="dsh-wt_hslVal">{v.h}°</span></div>
<div className="dsh-wt_hslRow"><span className="dsh-wt_hslLabel">S</span><input className="dsh-wt_hslSlider" type="range" min={0} max={sMax} step={1} value={v.s} onChange={(e) => editHsl(bgEdit, { s: Number(e.target.value) })} /><span className="dsh-wt_hslVal">{v.s}%</span></div>
<div className="dsh-wt_hslRow"><span className="dsh-wt_hslLabel">L</span><input className="dsh-wt_hslSlider" type="range" min={0} max={lMax} step={1} value={v.l} onChange={(e) => editHsl(bgEdit, { l: Number(e.target.value) })} /><span className="dsh-wt_hslVal">{v.l}%</span></div>
<button type="button" className="dsh-wt_hslReset" onClick={() => resetHsl(bgEdit)}>{T('console.bgEditReset')}</button>
</>)
})()}
</div>
)
)}
{openMenu === 'cols' && (
<div className="dsh-wt_drop">