Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions database/init.db.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ func InitDB() {
hideAdmin BOOLEAN,
hideGithub BOOLEAN,
hideToggleJumpTarget BOOLEAN,
jumpTargetBlank BOOLEAN
jumpTargetBlank BOOLEAN,
hideThemeSwitch BOOLEAN,
disableTimeBasedTheme BOOLEAN
);
`
_, err = DB.Exec(sql_create_table)
Expand Down Expand Up @@ -84,6 +86,14 @@ func InitDB() {
if !columnExists("nav_setting", "hideToggleJumpTarget") {
DB.Exec(`ALTER TABLE nav_setting ADD COLUMN hideToggleJumpTarget BOOLEAN;`)
}
// 设置表表结构升级-隐藏主题切换按钮
if !columnExists("nav_setting", "hideThemeSwitch") {
DB.Exec(`ALTER TABLE nav_setting ADD COLUMN hideThemeSwitch BOOLEAN;`)
}
// 设置表表结构升级-禁用时间主题切换
if !columnExists("nav_setting", "disableTimeBasedTheme") {
DB.Exec(`ALTER TABLE nav_setting ADD COLUMN disableTimeBasedTheme BOOLEAN;`)
}

// 默认 tools 用的 表
sql_create_table = `
Expand Down Expand Up @@ -246,12 +256,12 @@ func InitDB() {
utils.CheckErr(err)
if !rows.Next() {
sql_add_setting := `
INSERT INTO nav_setting (favicon, title, govRecord, logo192, logo512, hideAdmin, hideGithub, hideToggleJumpTarget, jumpTargetBlank)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
INSERT INTO nav_setting (favicon, title, govRecord, logo192, logo512, hideAdmin, hideGithub, hideToggleJumpTarget, jumpTargetBlank, hideThemeSwitch, disableTimeBasedTheme)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
`
stmt, err := DB.Prepare(sql_add_setting)
utils.CheckErr(err)
res, err := stmt.Exec("favicon.ico", "Van Nav", "", "logo192.png", "logo512.png", false, false, false, true)
res, err := stmt.Exec("favicon.ico", "Van Nav", "", "logo192.png", "logo512.png", false, false, false, true, false, false)
utils.CheckErr(err)
_, err = res.LastInsertId()
utils.CheckErr(err)
Expand Down
56 changes: 40 additions & 16 deletions service/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import (

func GetSetting() types.Setting {
sql_get_user := `
SELECT id,favicon,title,govRecord,logo192,logo512,hideAdmin,hideGithub,hideToggleJumpTarget,jumpTargetBlank
FROM nav_setting
ORDER BY id ASC
SELECT id,favicon,title,govRecord,logo192,logo512,hideAdmin,hideGithub,hideToggleJumpTarget,jumpTargetBlank,hideThemeSwitch,disableTimeBasedTheme
FROM nav_setting
ORDER BY id ASC
LIMIT 1;
`
var setting types.Setting
Expand All @@ -20,20 +20,24 @@ func GetSetting() types.Setting {
var hideAdmin interface{}
var hideToggleJumpTarget interface{}
var jumpTargetBlank interface{}
err := row.Scan(&setting.Id, &setting.Favicon, &setting.Title, &setting.GovRecord, &setting.Logo192, &setting.Logo512, &hideAdmin, &hideGithub, &hideToggleJumpTarget, &jumpTargetBlank)
var hideThemeSwitch interface{}
var disableTimeBasedTheme interface{}
err := row.Scan(&setting.Id, &setting.Favicon, &setting.Title, &setting.GovRecord, &setting.Logo192, &setting.Logo512, &hideAdmin, &hideGithub, &hideToggleJumpTarget, &jumpTargetBlank, &hideThemeSwitch, &disableTimeBasedTheme)
if err != nil {
logger.LogError("获取配置失败: %s", err)
return types.Setting{
Id: 1,
Favicon: "favicon.ico",
Title: "Van Nav",
GovRecord: "",
Logo192: "logo192.png",
Logo512: "logo512.png",
HideAdmin: false,
HideGithub: false,
HideToggleJumpTarget: false,
JumpTargetBlank: true,
Id: 1,
Favicon: "favicon.ico",
Title: "Van Nav",
GovRecord: "",
Logo192: "logo192.png",
Logo512: "logo512.png",
HideAdmin: false,
HideGithub: false,
HideToggleJumpTarget: false,
JumpTargetBlank: true,
HideThemeSwitch: false,
DisableTimeBasedTheme: false,
}
}
if hideGithub == nil {
Expand Down Expand Up @@ -75,21 +79,41 @@ func GetSetting() types.Setting {
}
}

if hideThemeSwitch == nil {
setting.HideThemeSwitch = false
} else {
if hideThemeSwitch.(int64) == 0 {
setting.HideThemeSwitch = false
} else {
setting.HideThemeSwitch = true
}
}

if disableTimeBasedTheme == nil {
setting.DisableTimeBasedTheme = false
} else {
if disableTimeBasedTheme.(int64) == 0 {
setting.DisableTimeBasedTheme = false
} else {
setting.DisableTimeBasedTheme = true
}
}

return setting
}

func UpdateSetting(data types.Setting) error {
sql_update_setting := `
UPDATE nav_setting
SET favicon = ?, title = ?, govRecord = ?, logo192 = ?, logo512 = ?, hideAdmin = ?, hideGithub = ?, hideToggleJumpTarget = ?, jumpTargetBlank = ?
SET favicon = ?, title = ?, govRecord = ?, logo192 = ?, logo512 = ?, hideAdmin = ?, hideGithub = ?, hideToggleJumpTarget = ?, jumpTargetBlank = ?, hideThemeSwitch = ?, disableTimeBasedTheme = ?
WHERE id = (SELECT id FROM nav_setting ORDER BY id ASC LIMIT 1);
`

stmt, err := database.DB.Prepare(sql_update_setting)
if err != nil {
return err
}
res, err := stmt.Exec(data.Favicon, data.Title, data.GovRecord, data.Logo192, data.Logo512, data.HideAdmin, data.HideGithub, data.HideToggleJumpTarget, data.JumpTargetBlank)
res, err := stmt.Exec(data.Favicon, data.Title, data.GovRecord, data.Logo192, data.Logo512, data.HideAdmin, data.HideGithub, data.HideToggleJumpTarget, data.JumpTargetBlank, data.HideThemeSwitch, data.DisableTimeBasedTheme)
if err != nil {
return err
}
Expand Down
22 changes: 12 additions & 10 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ package types

// 默认是 0
type Setting struct {
Id int `json:"id"`
Favicon string `json:"favicon"`
Title string `json:"title"`
GovRecord string `json:"govRecord"`
Logo192 string `json:"logo192"`
Logo512 string `json:"logo512"`
HideAdmin bool `json:"hideAdmin"`
HideGithub bool `json:"hideGithub"`
HideToggleJumpTarget bool `json:"hideToggleJumpTarget"`
JumpTargetBlank bool `json:"jumpTargetBlank"`
Id int `json:"id"`
Favicon string `json:"favicon"`
Title string `json:"title"`
GovRecord string `json:"govRecord"`
Logo192 string `json:"logo192"`
Logo512 string `json:"logo512"`
HideAdmin bool `json:"hideAdmin"`
HideGithub bool `json:"hideGithub"`
HideToggleJumpTarget bool `json:"hideToggleJumpTarget"`
JumpTargetBlank bool `json:"jumpTargetBlank"`
HideThemeSwitch bool `json:"hideThemeSwitch"`
DisableTimeBasedTheme bool `json:"disableTimeBasedTheme"`
}

type Token struct {
Expand Down
18 changes: 16 additions & 2 deletions ui/src/components/Content/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,16 @@ const Content = (props: any) => {
const hide = data?.setting?.hideGithub === true
return !hide;
}, [data])


const showThemeSwitch = useMemo(() => {
const hide = data?.setting?.hideThemeSwitch === true
return !hide;
}, [data])

const disableTimeBasedTheme = useMemo(() => {
return data?.setting?.disableTimeBasedTheme === true
}, [data])

const loadData = useCallback(async () => {
try {
setLoading(true);
Expand Down Expand Up @@ -228,7 +237,12 @@ const Content = (props: any) => {
<a href="https://beian.miit.gov.cn" target="_blank" rel="noreferrer">{data?.setting?.govRecord ?? ""}</a>
</div>
{showGithub && <GithubLink />}
<DarkSwitch showGithub={showGithub} />
<DarkSwitch
showGithub={showGithub}
forceAuto={!showThemeSwitch}
hidden={!showThemeSwitch}
disableTimeBasedTheme={disableTimeBasedTheme}
/>
</>
);
};
Expand Down
11 changes: 11 additions & 0 deletions ui/src/components/DarkSwitch/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,14 @@
.theme-switch-box svg:hover {
fill: #121212;
}

/* 隐藏主题切换按钮 */
.theme-switch-box.hidden {
display: none !important;
}

/* 禁用状态(强制 auto 模式时) */
.theme-switch-box.disabled {
cursor: not-allowed;
opacity: 0.5;
}
114 changes: 104 additions & 10 deletions ui/src/components/DarkSwitch/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,111 @@ import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { applyTheme, decodeTheme, initTheme } from "../../utils/theme";
import "./index.css";

const DarkSwitch = ({ showGithub }: { showGithub: boolean }) => {
interface DarkSwitchProps {
showGithub: boolean;
forceAuto?: boolean;
hidden?: boolean;
disableTimeBasedTheme?: boolean;
}

const DarkSwitch = ({ showGithub, forceAuto = false, hidden = false, disableTimeBasedTheme = false }: DarkSwitchProps) => {
const [theme, setTheme] = useState(initTheme());
const { current } = useRef<any>({ hasInit: false });
const { current: currentTimer } = useRef<any>({ timer: null });

useEffect(() => {
// 清理定时器
if (currentTimer.timer) {
clearInterval(currentTimer.timer);
currentTimer.timer = null;
}

// 清理系统主题监听器
if (currentTimer.mediaQueryListener) {
try {
currentTimer.mediaQuery?.removeEventListener('change', currentTimer.mediaQueryListener);
} catch (e) {
// 降级:某些旧浏览器可能不支持 removeEventListener
}
currentTimer.mediaQueryListener = null;
currentTimer.mediaQuery = null;
}

localStorage.setItem("theme", theme)
const realTheme = decodeTheme(theme as any);
applyTheme(realTheme, 'setTheme', true);
const realTheme = decodeTheme(theme as any, disableTimeBasedTheme);
applyTheme(realTheme);

if (realTheme.includes("auto")) {
currentTimer.timer = setInterval(() => {
const realTheme = decodeTheme("auto");
applyTheme(realTheme, "autoThemeTimer", true);
}, 10000);
// 检查是否支持 matchMedia 和事件监听
const supportsMediaQuery = typeof window !== 'undefined' &&
window.matchMedia &&
typeof window.matchMedia === 'function';

let hasMediaListener = false;

// 尝试添加系统主题变化监听
if (supportsMediaQuery) {
try {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const listener = () => {
const realTheme = decodeTheme("auto", disableTimeBasedTheme);
applyTheme(realTheme);
};

// 尝试添加监听器
if (mediaQuery.addEventListener) {
mediaQuery.addEventListener('change', listener);
currentTimer.mediaQuery = mediaQuery;
currentTimer.mediaQueryListener = listener;
hasMediaListener = true;
}
} catch (e) {
// 静默回退到定时扫描
}
}

// 根据配置决定是否需要定时器
// 1. 如果开启了"仅跟随系统主题"且成功添加了监听器,则不需要定时器
// 2. 如果未开启"仅跟随系统主题",需要定时器检查时间切换
// 3. 如果无法添加监听器,回退到定时器
const needTimer = !disableTimeBasedTheme || !hasMediaListener;

if (needTimer) {
currentTimer.timer = setInterval(() => {
const realTheme = decodeTheme("auto", disableTimeBasedTheme);
applyTheme(realTheme);
}, 10000);
}
}

// 清理函数
return () => {
if (currentTimer.timer) {
clearInterval(currentTimer.timer);
currentTimer.timer = null;
}
if (currentTimer.mediaQueryListener) {
try {
currentTimer.mediaQuery?.removeEventListener('change', currentTimer.mediaQueryListener);
} catch (e) {
// 忽略清理错误
}
currentTimer.mediaQueryListener = null;
currentTimer.mediaQuery = null;
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [theme])
}, [theme, disableTimeBasedTheme])


useLayoutEffect(() => {
if (!current.hasInit) {
current.hasInit = true;
if (!!!localStorage.getItem("theme")) {
if (forceAuto) {
// 强制使用 auto 模式
setTheme("auto");
localStorage.setItem("theme", "auto");
} else if (!!!localStorage.getItem("theme")) {
// 第一次用默认的
setTheme("auto");
} else {
Expand All @@ -39,6 +117,15 @@ const DarkSwitch = ({ showGithub }: { showGithub: boolean }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

// 当 forceAuto 改变时,强制设置为 auto 模式
useEffect(() => {
if (forceAuto && theme !== "auto") {
setTheme("auto");
localStorage.setItem("theme", "auto");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [forceAuto]);

const lightIcon = (<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1024 1024"
Expand Down Expand Up @@ -69,6 +156,10 @@ const DarkSwitch = ({ showGithub }: { showGithub: boolean }) => {
<path d="M512 992C246.92 992 32 777.08 32 512S246.92 32 512 32s480 214.92 480 480-214.92 480-480 480zm0-840c-198.78 0-360 161.22-360 360 0 198.84 161.22 360 360 360s360-161.16 360-360c0-198.78-161.22-360-360-360zm0 660V212c165.72 0 300 134.34 300 300 0 165.72-134.28 300-300 300z"></path>
</svg>)
const handleSwitch = () => {
// 如果强制 auto 模式,则禁用点击
if (forceAuto) {
return;
}
if (theme === "light") {
setTheme("dark");
} else if (theme === "dark") {
Expand All @@ -78,7 +169,10 @@ const DarkSwitch = ({ showGithub }: { showGithub: boolean }) => {
}
};
return (
<div className={`theme-switch-box ${showGithub ? "" : "hide-github"}`} onClick={handleSwitch}>
<div
className={`theme-switch-box ${showGithub ? "" : "hide-github"} ${hidden ? "hidden" : ""} ${forceAuto ? "disabled" : ""}`}
onClick={handleSwitch}
>
{theme === "light" ? lightIcon : theme === "dark" ? darkIcon : autoIcon}
</div>
);
Expand Down
16 changes: 16 additions & 0 deletions ui/src/pages/admin/tabs/Setting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,22 @@ export const Setting: React.FC<SettingProps> = (props) => {
<Form.Item label="隐藏管理员后台卡片" name="hideAdmin" tooltip="默认展示,开启后将在前台隐藏管理员卡片" >
<Switch defaultChecked={Boolean(store?.setting?.hideAdmin)} />
</Form.Item>
<Form.Item label="隐藏主题切换按钮" name="hideThemeSwitch" tooltip="开启后主题切换按钮将被隐藏,主题将根据系统主题模式和时间(18:00-08:00)自动切换。" >
<Switch defaultChecked={Boolean(store?.setting?.hideThemeSwitch)} />
</Form.Item>
<Form.Item noStyle shouldUpdate={(prevValues, currentValues) => prevValues.hideThemeSwitch !== currentValues.hideThemeSwitch}>
{({ getFieldValue }) =>
getFieldValue('hideThemeSwitch') ? (
<Form.Item
label=" └ 仅跟随系统主题"
name="disableTimeBasedTheme"
tooltip="开启后仅根据系统主题模式切换,不根据时间(18:00-08:00)自动切换。"
>
<Switch defaultChecked={Boolean(store?.setting?.disableTimeBasedTheme)} />
</Form.Item>
) : null
}
</Form.Item>
<Form.Item label="隐藏 Github 按钮" name="hideGithub" tooltip="默认展示,开启后将在前台隐藏 Github 按钮" >
<Switch defaultChecked={Boolean(store?.setting?.hideGithub)} />
</Form.Item>
Expand Down
Loading