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({