diff --git a/go-gost/x/socket/websocket_reporter.go b/go-gost/x/socket/websocket_reporter.go index 2de97a5..2009e27 100644 --- a/go-gost/x/socket/websocket_reporter.go +++ b/go-gost/x/socket/websocket_reporter.go @@ -1033,7 +1033,7 @@ func tcpPingHost(ip string, port int, count int, timeoutMs int) (float64, float6 totalTime += elapsed.Seconds() * 1000 // 转换为毫秒 successCount++ } - + // 如果不是最后一次,等待一下再进行下次测试 if i < count-1 { time.Sleep(100 * time.Millisecond) diff --git a/gost.sql b/gost.sql index 4e0ab86..9a2fac0 100644 --- a/gost.sql +++ b/gost.sql @@ -154,9 +154,20 @@ CREATE TABLE `user_tunnel` ( `status` int(10) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; --- --- 转储表的索引 --- +CREATE TABLE `vite_config` ( + `id` int(10) NOT NULL, + `name` varchar(200) NOT NULL, + `value` varchar(200) NOT NULL, + `time` bigint(20) NOT NULL, + UNIQUE KEY `unique_name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +INSERT INTO `vite_config` (`id`, `name`, `value`, `time`) VALUES +(1, 'app_name', '哆啦A梦', 1753344708000); + +ALTER TABLE `vite_config` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `unique_name` (`name`); -- -- 表的索引 `forward` @@ -202,37 +213,42 @@ ALTER TABLE `user_tunnel` -- 使用表AUTO_INCREMENT `forward` -- ALTER TABLE `forward` - MODIFY `id` int(10) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=164; + MODIFY `id` int(10) NOT NULL AUTO_INCREMENT; -- -- 使用表AUTO_INCREMENT `node` -- ALTER TABLE `node` - MODIFY `id` int(10) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=27; + MODIFY `id` int(10) NOT NULL AUTO_INCREMENT; -- -- 使用表AUTO_INCREMENT `speed_limit` -- ALTER TABLE `speed_limit` - MODIFY `id` int(10) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=70; + MODIFY `id` int(10) NOT NULL AUTO_INCREMENT; -- -- 使用表AUTO_INCREMENT `tunnel` -- ALTER TABLE `tunnel` - MODIFY `id` int(10) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=59; + MODIFY `id` int(10) NOT NULL AUTO_INCREMENT; -- -- 使用表AUTO_INCREMENT `user` -- ALTER TABLE `user` - MODIFY `id` int(10) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=38; + MODIFY `id` int(10) NOT NULL AUTO_INCREMENT; -- -- 使用表AUTO_INCREMENT `user_tunnel` -- ALTER TABLE `user_tunnel` - MODIFY `id` int(10) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=56; + MODIFY `id` int(10) NOT NULL AUTO_INCREMENT; +COMMIT; + + +ALTER TABLE `vite_config` + MODIFY `id` int(10) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2; COMMIT; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; diff --git a/panel_install.sh b/panel_install.sh index 4fd09ec..96ebf88 100755 --- a/panel_install.sh +++ b/panel_install.sh @@ -754,6 +754,23 @@ UPDATE \`forward\` SET \`strategy\` = 'fifo' WHERE \`strategy\` IS NULL; +-- 创建 vite_config 表(如果不存在) +CREATE TABLE IF NOT EXISTS \`vite_config\` ( + \`id\` int(10) NOT NULL AUTO_INCREMENT, + \`name\` varchar(200) NOT NULL, + \`value\` varchar(200) NOT NULL, + \`time\` bigint(20) NOT NULL, + PRIMARY KEY (\`id\`), + UNIQUE KEY \`unique_name\` (\`name\`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 插入默认应用名称配置(如果不存在) +INSERT INTO \`vite_config\` (\`name\`, \`value\`, \`time\`) +SELECT 'app_name', '哆啦A梦', 1753344708000 +WHERE NOT EXISTS ( + SELECT 1 FROM \`vite_config\` WHERE \`name\` = 'app_name' +); + EOF diff --git a/springboot-backend/src/main/java/com/admin/config/WebMvcConfig.java b/springboot-backend/src/main/java/com/admin/config/WebMvcConfig.java index a8b10e5..c23c905 100644 --- a/springboot-backend/src/main/java/com/admin/config/WebMvcConfig.java +++ b/springboot-backend/src/main/java/com/admin/config/WebMvcConfig.java @@ -57,6 +57,7 @@ public class WebMvcConfig implements WebMvcConfigurer { registry.addInterceptor(jwtInterceptor()) .addPathPatterns("/api/**") .excludePathPatterns("/flow/**") + .excludePathPatterns("/api/v1/config/get") .excludePathPatterns("/api/v1/user/login"); } } diff --git a/springboot-backend/src/main/java/com/admin/controller/BaseController.java b/springboot-backend/src/main/java/com/admin/controller/BaseController.java index 0a04065..3ce3080 100644 --- a/springboot-backend/src/main/java/com/admin/controller/BaseController.java +++ b/springboot-backend/src/main/java/com/admin/controller/BaseController.java @@ -20,4 +20,6 @@ public class BaseController { @Autowired ForwardService forwardService; + @Autowired + ViteConfigService viteConfigService; } diff --git a/springboot-backend/src/main/java/com/admin/controller/ViteConfigController.java b/springboot-backend/src/main/java/com/admin/controller/ViteConfigController.java new file mode 100644 index 0000000..19d1732 --- /dev/null +++ b/springboot-backend/src/main/java/com/admin/controller/ViteConfigController.java @@ -0,0 +1,69 @@ +package com.admin.controller; + + +import com.admin.common.annotation.RequireRole; +import com.admin.common.aop.LogAnnotation; +import com.admin.common.lang.R; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + *

+ * 网站配置控制器 + *

+ * + * @author QAQ + * @since 2025-07-24 + */ +@RestController +@CrossOrigin +@RequestMapping("/api/v1/config") +public class ViteConfigController extends BaseController { + + /** + * 获取所有网站配置 + * 前端无需权限即可访问,用于获取网站基本信息 + */ + @LogAnnotation + @PostMapping("/list") + public R getConfigs() { + return viteConfigService.getConfigs(); + } + + /** + * 根据配置名获取配置值 + * 前端无需权限即可访问,用于获取特定配置 + */ + @LogAnnotation + @PostMapping("/get") + public R getConfigByName(@RequestBody Map params) { + String name = params.get("name").toString(); + return viteConfigService.getConfigByName(name); + } + + /** + * 批量更新网站配置 + * 需要管理员权限 + */ + @LogAnnotation + @RequireRole + @PostMapping("/update") + public R updateConfigs(@RequestBody Map configMap) { + return viteConfigService.updateConfigs(configMap); + } + + /** + * 更新单个配置项 + * 需要管理员权限 + */ + @LogAnnotation + @RequireRole + @PostMapping("/update-single") + public R updateConfig(@RequestBody Map params) { + String name = params.get("name").toString(); + String value = params.get("value").toString(); + return viteConfigService.updateConfig(name, value); + } + +} diff --git a/springboot-backend/src/main/java/com/admin/entity/ViteConfig.java b/springboot-backend/src/main/java/com/admin/entity/ViteConfig.java new file mode 100644 index 0000000..69ca6c5 --- /dev/null +++ b/springboot-backend/src/main/java/com/admin/entity/ViteConfig.java @@ -0,0 +1,31 @@ +package com.admin.entity; + +import com.admin.entity.BaseEntity; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * + *

+ * + * @author QAQ + * @since 2025-07-24 + */ +@Data +public class ViteConfig { + + private static final long serialVersionUID = 1L; + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + private String name; + + private String value; + + private Long time; + + +} diff --git a/springboot-backend/src/main/java/com/admin/mapper/ViteConfigMapper.java b/springboot-backend/src/main/java/com/admin/mapper/ViteConfigMapper.java new file mode 100644 index 0000000..cf807cb --- /dev/null +++ b/springboot-backend/src/main/java/com/admin/mapper/ViteConfigMapper.java @@ -0,0 +1,16 @@ +package com.admin.mapper; + +import com.admin.entity.ViteConfig; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * Mapper 接口 + *

+ * + * @author QAQ + * @since 2025-07-24 + */ +public interface ViteConfigMapper extends BaseMapper { + +} diff --git a/springboot-backend/src/main/java/com/admin/service/ViteConfigService.java b/springboot-backend/src/main/java/com/admin/service/ViteConfigService.java new file mode 100644 index 0000000..fbde030 --- /dev/null +++ b/springboot-backend/src/main/java/com/admin/service/ViteConfigService.java @@ -0,0 +1,47 @@ +package com.admin.service; + +import com.admin.entity.ViteConfig; +import com.admin.common.lang.R; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.Map; + +/** + *

+ * 网站配置服务类 + *

+ * + * @author QAQ + * @since 2025-07-24 + */ +public interface ViteConfigService extends IService { + + /** + * 获取所有网站配置 + * @return 配置Map,key为配置名,value为配置值 + */ + R getConfigs(); + + /** + * 根据配置名获取配置值 + * @param name 配置名 + * @return 配置值 + */ + R getConfigByName(String name); + + /** + * 批量更新网站配置 + * @param configMap 配置Map,key为配置名,value为新的配置值 + * @return 更新结果 + */ + R updateConfigs(Map configMap); + + /** + * 更新单个配置项 + * @param name 配置名 + * @param value 配置值 + * @return 更新结果 + */ + R updateConfig(String name, String value); + +} diff --git a/springboot-backend/src/main/java/com/admin/service/impl/ViteConfigServiceImpl.java b/springboot-backend/src/main/java/com/admin/service/impl/ViteConfigServiceImpl.java new file mode 100644 index 0000000..47bc9d5 --- /dev/null +++ b/springboot-backend/src/main/java/com/admin/service/impl/ViteConfigServiceImpl.java @@ -0,0 +1,160 @@ +package com.admin.service.impl; + +import com.admin.entity.ViteConfig; +import com.admin.mapper.ViteConfigMapper; +import com.admin.service.ViteConfigService; +import com.admin.common.lang.R; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

+ * 网站配置服务实现类 + *

+ * + * @author QAQ + * @since 2025-07-24 + */ +@Service +public class ViteConfigServiceImpl extends ServiceImpl implements ViteConfigService { + + // ========== 常量定义 ========== + + /** 成功响应消息 */ + private static final String SUCCESS_UPDATE_MSG = "配置更新成功"; + + /** 错误响应消息 */ + private static final String ERROR_UPDATE_MSG = "配置更新失败"; + private static final String ERROR_CONFIG_NOT_FOUND = "配置不存在"; + private static final String ERROR_CONFIG_NAME_REQUIRED = "配置名称不能为空"; + private static final String ERROR_CONFIG_VALUE_REQUIRED = "配置值不能为空"; + + // ========== 公共接口实现 ========== + + /** + * 获取所有网站配置 + * + * @return 包含所有配置的Map + */ + @Override + public R getConfigs() { + List configList = this.list(); + Map configMap = new HashMap<>(); + + for (ViteConfig config : configList) { + configMap.put(config.getName(), config.getValue()); + } + + return R.ok(configMap); + } + + /** + * 根据配置名称获取配置值 + * + * @param name 配置名称 + * @return 配置响应对象 + */ + @Override + public R getConfigByName(String name) { + if (!StringUtils.hasText(name)) { + return R.err(ERROR_CONFIG_NAME_REQUIRED); + } + + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("name", name); + ViteConfig config = this.getOne(queryWrapper); + + if (config == null) { + return R.err(ERROR_CONFIG_NOT_FOUND); + } + + return R.ok(config); + } + + /** + * 批量更新网站配置 + * + * @param configMap 配置Map + * @return 更新结果响应 + */ + @Override + public R updateConfigs(Map configMap) { + if (configMap == null || configMap.isEmpty()) { + return R.err("配置数据不能为空"); + } + + try { + for (Map.Entry entry : configMap.entrySet()) { + String name = entry.getKey(); + String value = entry.getValue(); + + if (!StringUtils.hasText(name)) { + continue; // 跳过无效的配置名 + } + + updateOrCreateConfig(name, value); + } + return R.ok(SUCCESS_UPDATE_MSG); + } catch (Exception e) { + return R.err(ERROR_UPDATE_MSG + ": " + e.getMessage()); + } + } + + /** + * 更新单个配置项 + * + * @param name 配置名 + * @param value 配置值 + * @return 更新结果响应 + */ + @Override + public R updateConfig(String name, String value) { + // 1. 验证必填字段 + if (!StringUtils.hasText(name)) { + return R.err(ERROR_CONFIG_NAME_REQUIRED); + } + if (!StringUtils.hasText(value)) { + return R.err(ERROR_CONFIG_VALUE_REQUIRED); + } + + try { + updateOrCreateConfig(name, value); + return R.ok(SUCCESS_UPDATE_MSG); + } catch (Exception e) { + return R.err(ERROR_UPDATE_MSG + ": " + e.getMessage()); + } + } + + // ========== 私有辅助方法 ========== + + /** + * 更新或创建配置项 + * 如果配置存在则更新,不存在则创建 + */ + private void updateOrCreateConfig(String name, String value) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("name", name); + ViteConfig existingConfig = this.getOne(queryWrapper); + + if (existingConfig != null) { + // 更新现有配置 + existingConfig.setValue(value); + existingConfig.setTime(System.currentTimeMillis()); + this.updateById(existingConfig); + } else { + // 创建新配置 + ViteConfig newConfig = new ViteConfig(); + newConfig.setName(name); + newConfig.setValue(value); + newConfig.setTime(System.currentTimeMillis()); + this.save(newConfig); + } + } + +} diff --git a/springboot-backend/src/main/resources/mapper/ViteConfigMapper.xml b/springboot-backend/src/main/resources/mapper/ViteConfigMapper.xml new file mode 100644 index 0000000..6a35c4e --- /dev/null +++ b/springboot-backend/src/main/resources/mapper/ViteConfigMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/vite-frontend/index.html b/vite-frontend/index.html index 400ab31..cf8801f 100644 --- a/vite-frontend/index.html +++ b/vite-frontend/index.html @@ -40,7 +40,7 @@ } html.dark body { - background-color: #1f2937; + background-color: #000000; } #root { diff --git a/vite-frontend/src/App.tsx b/vite-frontend/src/App.tsx index 378b5ca..05070ab 100644 --- a/vite-frontend/src/App.tsx +++ b/vite-frontend/src/App.tsx @@ -9,8 +9,10 @@ import TunnelPage from "@/pages/tunnel"; import NodePage from "@/pages/node"; import UserPage from "@/pages/user"; import LimitPage from "@/pages/limit"; +import ConfigPage from "@/pages/config"; import { isLoggedIn } from "@/utils/auth"; +import { siteConfig } from "@/config/site"; // 简化的路由保护组件 - 使用浏览器重定向避免React循环 const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { @@ -34,6 +36,7 @@ const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { return <>{children}; }; + // 登录页面路由组件 - 已登录则重定向到dashboard const LoginRoute = () => { const authenticated = isLoggedIn(); @@ -57,6 +60,30 @@ const LoginRoute = () => { }; function App() { + // 立即设置页面标题(使用已从缓存读取的配置) + useEffect(() => { + document.title = siteConfig.name; + + // 异步检查是否有配置更新 + const checkTitleUpdate = async () => { + try { + // 引入必要的函数 + const { getCachedConfig } = await import('@/config/site'); + const cachedAppName = await getCachedConfig('app_name'); + if (cachedAppName && cachedAppName !== document.title) { + document.title = cachedAppName; + } + } catch (error) { + console.warn('检查标题更新失败:', error); + } + }; + + // 延迟检查,避免阻塞初始渲染 + const timer = setTimeout(checkTitleUpdate, 100); + + return () => clearTimeout(timer); + }, []); + return ( } /> @@ -116,6 +143,14 @@ function App() { } /> + + + + } + /> ); } diff --git a/vite-frontend/src/api/index.ts b/vite-frontend/src/api/index.ts index cca0e08..f939ccf 100644 --- a/vite-frontend/src/api/index.ts +++ b/vite-frontend/src/api/index.ts @@ -70,4 +70,10 @@ export const updateSpeedLimit = (data: any) => Network.post("/speed-limit/update export const deleteSpeedLimit = (id: number) => Network.post("/speed-limit/delete", { id }); // 修改密码接口 -export const updatePassword = (data: any) => Network.post("/user/updatePassword", data); \ No newline at end of file +export const updatePassword = (data: any) => Network.post("/user/updatePassword", data); + +// 网站配置相关接口 +export const getConfigs = () => Network.post("/config/list"); +export const getConfigByName = (name: string) => Network.post("/config/get", { name }); +export const updateConfigs = (configMap: Record) => Network.post("/config/update", configMap); +export const updateConfig = (name: string, value: string) => Network.post("/config/update-single", { name, value }); \ No newline at end of file diff --git a/vite-frontend/src/components/navbar.tsx b/vite-frontend/src/components/navbar.tsx index f86b8a6..4149c40 100644 --- a/vite-frontend/src/components/navbar.tsx +++ b/vite-frontend/src/components/navbar.tsx @@ -1,3 +1,4 @@ +import { useState, useEffect } from "react"; import { Link } from "@heroui/link"; import { Navbar as HeroUINavbar, @@ -7,19 +8,62 @@ import { import { ThemeSwitch } from "@/components/theme-switch"; import { Logo } from "@/components/icons"; +import { siteConfig, getCachedConfig } from "@/config/site"; export const Navbar = () => { + // 初始状态使用siteConfig中已经从缓存读取的值,避免闪烁 + const [appName, setAppName] = useState(siteConfig.name); + + useEffect(() => { + // 异步检查是否有更新的配置 + const checkForUpdates = async () => { + try { + const cachedAppName = await getCachedConfig('app_name'); + if (cachedAppName && cachedAppName !== appName) { + setAppName(cachedAppName); + // 同步更新siteConfig + siteConfig.name = cachedAppName; + } + } catch (error) { + console.warn('检查配置更新失败:', error); + } + }; + + // 延迟执行,避免阻塞初始渲染 + const timer = setTimeout(checkForUpdates, 100); + + // 监听配置更新事件 + const handleConfigUpdate = async () => { + try { + const cachedAppName = await getCachedConfig('app_name'); + if (cachedAppName) { + setAppName(cachedAppName); + siteConfig.name = cachedAppName; + } + } catch (error) { + console.warn('更新配置失败:', error); + } + }; + + window.addEventListener('configUpdated', handleConfigUpdate); + + return () => { + clearTimeout(timer); + window.removeEventListener('configUpdated', handleConfigUpdate); + }; + }, [appName]); + return ( - + - + - -

哆啦A梦

+ +

{appName}

diff --git a/vite-frontend/src/config/site.ts b/vite-frontend/src/config/site.ts index cffcbf9..aea4654 100644 --- a/vite-frontend/src/config/site.ts +++ b/vite-frontend/src/config/site.ts @@ -1,15 +1,153 @@ +import { getConfigByName, getConfigs } from '@/api'; + export type SiteConfig = typeof siteConfig; -export const siteConfig = { - name: "哆啦A梦", - description: "哆啦A梦", - navItems: [], - navMenuItems: [], - links: { - github: "https://github.com/go-gost/gost", - twitter: "", - docs: "", - discord: "", - sponsor: "", - }, +// 缓存相关常量 +const CACHE_PREFIX = 'vite_config_'; + +// 缓存策略: +// - 长期有效,不设置过期时间 +// - 只在以下情况清除:1. 更新配置时 2. 退出登录时(safeLogout会清除localStorage) + +// 立即从缓存获取配置(同步操作,避免闪烁) +const getInitialConfig = () => { + if (typeof window === 'undefined') { + return { + name: "哆啦A梦", + version: "1.0.9", + }; + } + + const cachedAppName = localStorage.getItem(CACHE_PREFIX + 'app_name'); + if (cachedAppName) { + return { + name: cachedAppName, + version: "1.0.9", + }; + } + return { + name: "哆啦A梦", + version: "1.0.9", + }; }; + +export const siteConfig = getInitialConfig(); + +// 缓存工具函数 +export const configCache = { + // 获取缓存的配置 + get: (key: string): string | null => { + const cacheKey = CACHE_PREFIX + key; + return localStorage.getItem(cacheKey); + }, + + // 设置缓存的配置 + set: (key: string, value: string): void => { + const cacheKey = CACHE_PREFIX + key; + localStorage.setItem(cacheKey, value); + }, + + // 删除指定配置的缓存 + remove: (key: string): void => { + const cacheKey = CACHE_PREFIX + key; + localStorage.removeItem(cacheKey); + }, + + // 清空所有配置缓存 + clear: (): void => { + // 获取所有localStorage的key + const keys = Object.keys(localStorage); + keys.forEach(key => { + if (key.startsWith(CACHE_PREFIX)) { + localStorage.removeItem(key); + } + }); + } +}; + +// 获取单个配置(优先从缓存) +export const getCachedConfig = async (key: string): Promise => { + const cachedValue = configCache.get(key); + if (cachedValue !== null) { + return cachedValue; + } + + const response = await getConfigByName(key); + if (response.code === 0 && response.data?.value) { + const value = response.data.value; + configCache.set(key, value); + return value; + } + + return null; +}; + +// 获取所有配置(优先从缓存) +export const getCachedConfigs = async (): Promise> => { + // 尝试从缓存获取所有配置 + const configKeys = ['app_name', 'site_title', 'site_description', 'contact_email', 'footer_text', 'logo_url']; + const cachedConfigs: Record = {}; + let hasCachedData = false; + + configKeys.forEach(key => { + const cachedValue = configCache.get(key); + if (cachedValue !== null) { + cachedConfigs[key] = cachedValue; + hasCachedData = true; + } + }); + + + + // 从API获取最新配置 + try { + const response = await getConfigs(); + if (response.code === 0 && response.data) { + const configs = response.data; + // 将所有配置存入缓存 + Object.entries(configs).forEach(([key, value]) => { + configCache.set(key, value as string); + }); + return configs; + } + } catch (error) { + // API失败时返回缓存的数据 + if (hasCachedData) { + return cachedConfigs; + } + } + + return {}; +}; + +// 动态更新网站配置 +export const updateSiteConfig = async () => { + const appName = await getCachedConfig('app_name'); + if (appName && appName !== siteConfig.name) { + siteConfig.name = appName; + // 更新页面标题 + document.title = appName; + } +}; + +// 清除配置缓存的工具函数 +// 缓存清除时机: +// 1. 配置更新时:调用此函数清除所有缓存 +// 2. 退出登录时:safeLogout()中的localStorage.clear()会自动清除 +export const clearConfigCache = (keys?: string[]) => { + if (keys && keys.length > 0) { + // 删除指定的配置缓存 + keys.forEach(key => configCache.remove(key)); + } else { + // 清空所有配置缓存 + configCache.clear(); + } +}; + +// 在页面加载时异步更新配置(如果有更新的话) +if (typeof window !== 'undefined') { + // 延迟执行,避免阻塞初始渲染 + setTimeout(() => { + updateSiteConfig(); + }, 200); +} diff --git a/vite-frontend/src/layouts/admin.tsx b/vite-frontend/src/layouts/admin.tsx index 41bd84a..f4dc194 100644 --- a/vite-frontend/src/layouts/admin.tsx +++ b/vite-frontend/src/layouts/admin.tsx @@ -10,6 +10,7 @@ import { Logo } from '@/components/icons'; import { ThemeSwitch } from '@/components/theme-switch'; import { updatePassword } from '@/api'; import { safeLogout } from '@/utils/logout'; +import { siteConfig } from '@/config/site'; interface MenuItem { path: string; @@ -105,6 +106,16 @@ export default function AdminLayout({ ), adminOnly: true + }, + { + path: '/config', + label: '网站配置', + icon: ( + + + + ), + adminOnly: true } ]; @@ -245,7 +256,7 @@ export default function AdminLayout({