Ver Fonte

将routes_sys.ts拆分为5个独立的路由文件

yourname há 6 meses atrás
pai
commit
b0aad49fd7

+ 5 - 7
server/app.tsx

@@ -14,13 +14,11 @@ import utc from 'dayjs/plugin/utc';
 import type { SystemSettingRecord, GlobalConfig } from '../client/share/types.ts';
 import { SystemSettingKey, OssType, MapMode } from '../client/share/types.ts';
 
-import {
-  createKnowInfoRoutes,
-  createFileCategoryRoutes,
-  createFileUploadRoutes,
-  createThemeRoutes,
-  createSystemSettingsRoutes,
-} from "./routes_sys.ts";
+import { createKnowInfoRoutes } from "./routes_know_info.ts";
+import { createFileCategoryRoutes } from "./routes_file_category.ts";
+import { createFileUploadRoutes } from "./routes_file_upload.ts";
+import { createThemeRoutes } from "./routes_theme.ts";
+import { createSystemSettingsRoutes } from "./routes_system_settings.ts";
 
 import {
   createMapRoutes,

+ 147 - 0
server/routes_file_category.ts

@@ -0,0 +1,147 @@
+import { Hono } from "hono";
+import debug from "debug";
+import type {
+  FileCategory,
+} from "../client/share/types.ts";
+
+import {
+  DeleteStatus,
+} from "../client/share/types.ts";
+
+import type { Variables, WithAuth } from "./app.tsx";
+
+const log = {
+  api: debug("api:sys"),
+};
+
+// 创建文件分类路由
+export function createFileCategoryRoutes(withAuth: WithAuth) {
+  const fileCategoryRoutes = new Hono<{ Variables: Variables }>();
+
+  // 获取文件分类列表
+  fileCategoryRoutes.get("/", withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+
+      const page = Number(c.req.query("page")) || 1;
+      const pageSize = Number(c.req.query("pageSize")) || 10;
+      const offset = (page - 1) * pageSize;
+
+      const search = c.req.query("search") || "";
+
+      let query = apiClient.database.table("file_category").orderBy("id", "desc");
+
+      if (search) {
+        query = query.where("name", "like", `%${search}%`);
+      }
+
+      const total = await query.clone().count();
+
+      const categories = await query
+        .select("id", "name", "code", "description")
+        .limit(pageSize)
+        .offset(offset);
+
+      return c.json({
+        data: categories,
+        total: Number(total),
+        page,
+        pageSize,
+      });
+    } catch (error) {
+      log.api("获取文件分类列表失败:", error);
+      return c.json({ error: "获取文件分类列表失败" }, 500);
+    }
+  });
+
+  // 创建文件分类
+  fileCategoryRoutes.post("/", withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      const data = (await c.req.json()) as Partial<FileCategory>;
+
+      // 验证必填字段
+      if (!data.name) {
+        return c.json({ error: "分类名称不能为空" }, 400);
+      }
+
+      // 插入文件分类
+      const [id] = await apiClient.database.table("file_category").insert({
+        ...data,
+        created_at: apiClient.database.fn.now(),
+        updated_at: apiClient.database.fn.now(),
+      });
+
+      return c.json({
+        message: "文件分类创建成功",
+        data: {
+          id,
+          ...data,
+        },
+      });
+    } catch (error) {
+      log.api("创建文件分类失败:", error);
+      return c.json({ error: "创建文件分类失败" }, 500);
+    }
+  });
+
+  // 更新文件分类
+  fileCategoryRoutes.put("/:id", withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      const id = Number(c.req.param("id"));
+
+      if (!id || isNaN(id)) {
+        return c.json({ error: "无效的分类ID" }, 400);
+      }
+
+      const data = (await c.req.json()) as Partial<FileCategory>;
+
+      // 更新文件分类
+      await apiClient.database
+        .table("file_category")
+        .where("id", id)
+        .update({
+          ...data,
+          updated_at: apiClient.database.fn.now(),
+        });
+
+      return c.json({
+        message: "文件分类更新成功",
+        data: {
+          id,
+          ...data,
+        },
+      });
+    } catch (error) {
+      log.api("更新文件分类失败:", error);
+      return c.json({ error: "更新文件分类失败" }, 500);
+    }
+  });
+
+  // 删除文件分类
+  fileCategoryRoutes.delete("/:id", withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      const id = Number(c.req.param("id"));
+
+      if (!id || isNaN(id)) {
+        return c.json({ error: "无效的分类ID" }, 400);
+      }
+
+      await apiClient.database.table("file_category").where("id", id).update({
+        is_deleted: DeleteStatus.DELETED,
+        updated_at: apiClient.database.fn.now(),
+      });
+
+      return c.json({
+        message: "文件分类删除成功",
+      });
+    } catch (error) {
+      log.api("删除文件分类失败:", error);
+      return c.json({ error: "删除文件分类失败" }, 500);
+    }
+  });
+  
+  return fileCategoryRoutes;
+}

+ 258 - 0
server/routes_file_upload.ts

@@ -0,0 +1,258 @@
+import { Hono } from "hono";
+import debug from "debug";
+import type {
+  FileLibrary,
+} from "../client/share/types.ts";
+
+import {
+  DeleteStatus,
+  EnableStatus,
+} from "../client/share/types.ts";
+
+import type { Variables, WithAuth } from "./app.tsx";
+
+const log = {
+  api: debug("api:sys"),
+};
+
+// 创建文件上传路由
+export function createFileUploadRoutes(withAuth: WithAuth) {
+  const fileUploadRoutes = new Hono<{ Variables: Variables }>();
+
+  // 获取 MinIO 上传策略
+  fileUploadRoutes.get("/policy", withAuth, async (c) => {
+    try {
+      const prefix = c.req.query("prefix") || "uploads/";
+      const filename = c.req.query("filename");
+      const maxSize = Number(c.req.query("maxSize")) || 10 * 1024 * 1024; // 默认10MB
+
+      if (!filename) {
+        return c.json({ error: "文件名不能为空" }, 400);
+      }
+
+      const apiClient = c.get('apiClient');
+      const policy = await apiClient.storage.getUploadPolicy(
+        prefix,
+        filename,
+        maxSize
+      );
+
+      return c.json({
+        message: "获取上传策略成功",
+        data: policy,
+      });
+    } catch (error) {
+      log.api("获取上传策略失败:", error);
+      return c.json({ error: "获取上传策略失败" }, 500);
+    }
+  });
+
+  // 保存文件信息到文件库
+  fileUploadRoutes.post("/save", withAuth, async (c) => {
+    try {
+      const fileData = (await c.req.json()) as Partial<FileLibrary>;
+      const user = c.get("user");
+
+      // 验证必填字段
+      if (!fileData.file_name || !fileData.file_path || !fileData.file_type) {
+        return c.json({ error: "文件名、路径和类型不能为空" }, 400);
+      }
+
+      const apiClient = c.get('apiClient');
+
+      // 设置上传者信息
+      if (user) {
+        fileData.uploader_id = user.id;
+        fileData.uploader_name = user.nickname || user.username;
+      }
+
+      // 插入文件库记录
+      const [id] = await apiClient.database.table("file_library").insert({
+        ...fileData,
+        download_count: 0,
+        is_disabled: EnableStatus.ENABLED,
+        is_deleted: DeleteStatus.NOT_DELETED,
+        created_at: apiClient.database.fn.now(),
+        updated_at: apiClient.database.fn.now(),
+      });
+
+      // 获取插入的数据
+      const [insertedFile] = await apiClient.database
+        .table("file_library")
+        .where("id", id);
+
+      return c.json({
+        message: "文件信息保存成功",
+        data: insertedFile,
+      });
+    } catch (error) {
+      log.api("保存文件信息失败:", error);
+      return c.json({ error: "保存文件信息失败" }, 500);
+    }
+  });
+
+  // 获取文件列表
+  fileUploadRoutes.get("/list", withAuth, async (c) => {
+    try {
+      const page = Number(c.req.query("page")) || 1;
+      const pageSize = Number(c.req.query("pageSize")) || 10;
+      const category_id = c.req.query("category_id");
+      const fileType = c.req.query("fileType");
+      const keyword = c.req.query("keyword");
+
+      const apiClient = c.get('apiClient');
+      let query = apiClient.database
+        .table("file_library")
+        .where("is_deleted", DeleteStatus.NOT_DELETED)
+        .orderBy("created_at", "desc");
+
+      // 应用过滤条件
+      if (category_id) {
+        query = query.where("category_id", category_id);
+      }
+
+      if (fileType) {
+        query = query.where("file_type", fileType);
+      }
+
+      if (keyword) {
+        query = query.where((builder) => {
+          builder
+            .where("file_name", "like", `%${keyword}%`)
+            .orWhere("description", "like", `%${keyword}%`)
+            .orWhere("tags", "like", `%${keyword}%`);
+        });
+      }
+
+      // 获取总数
+      const total = await query.clone().count();
+
+      // 分页查询
+      const files = await query.limit(pageSize).offset((page - 1) * pageSize);
+
+      return c.json({
+        message: "获取文件列表成功",
+        data: {
+          list: files,
+          pagination: {
+            current: page,
+            pageSize,
+            total: Number(total),
+          },
+        },
+      });
+    } catch (error) {
+      log.api("获取文件列表失败:", error);
+      return c.json({ error: "获取文件列表失败" }, 500);
+    }
+  });
+
+  // 获取单个文件信息
+  fileUploadRoutes.get("/:id", withAuth, async (c) => {
+    try {
+      const id = Number(c.req.param("id"));
+
+      if (!id || isNaN(id)) {
+        return c.json({ error: "无效的文件ID" }, 400);
+      }
+
+      const apiClient = c.get('apiClient');
+      const file = await apiClient.database
+        .table("file_library")
+        .where("id", id)
+        .where("is_deleted", DeleteStatus.NOT_DELETED)
+        .first();
+
+      if (!file) {
+        return c.json({ error: "文件不存在" }, 404);
+      }
+
+      return c.json({
+        message: "获取文件信息成功",
+        data: file,
+      });
+    } catch (error) {
+      log.api("获取文件信息失败:", error);
+      return c.json({ error: "获取文件信息失败" }, 500);
+    }
+  });
+
+  // 增加文件下载计数
+  fileUploadRoutes.post("/:id/download", withAuth, async (c) => {
+    try {
+      const id = Number(c.req.param("id"));
+
+      if (!id || isNaN(id)) {
+        return c.json({ error: "无效的文件ID" }, 400);
+      }
+
+      const apiClient = c.get('apiClient');
+
+      // 查询文件是否存在
+      const file = await apiClient.database
+        .table("file_library")
+        .where("id", id)
+        .where("is_deleted", DeleteStatus.NOT_DELETED)
+        .first();
+
+      if (!file) {
+        return c.json({ error: "文件不存在" }, 404);
+      }
+
+      // 增加下载计数
+      await apiClient.database
+        .table("file_library")
+        .where("id", id)
+        .update({
+          download_count: apiClient.database.raw("download_count + 1"),
+          updated_at: apiClient.database.fn.now(),
+        });
+
+      return c.json({
+        message: "更新下载计数成功",
+      });
+    } catch (error) {
+      log.api("更新下载计数失败:", error);
+      return c.json({ error: "更新下载计数失败" }, 500);
+    }
+  });
+
+  // 删除文件
+  fileUploadRoutes.delete("/:id", withAuth, async (c) => {
+    try {
+      const id = Number(c.req.param("id"));
+
+      if (!id || isNaN(id)) {
+        return c.json({ error: "无效的文件ID" }, 400);
+      }
+
+      const apiClient = c.get('apiClient');
+
+      // 查询文件是否存在
+      const file = await apiClient.database
+        .table("file_library")
+        .where("id", id)
+        .where("is_deleted", DeleteStatus.NOT_DELETED)
+        .first();
+
+      if (!file) {
+        return c.json({ error: "文件不存在" }, 404);
+      }
+
+      // 软删除文件
+      await apiClient.database.table("file_library").where("id", id).update({
+        is_deleted: DeleteStatus.DELETED,
+        updated_at: apiClient.database.fn.now(),
+      });
+
+      return c.json({
+        message: "文件删除成功",
+      });
+    } catch (error) {
+      log.api("删除文件失败:", error);
+      return c.json({ error: "删除文件失败" }, 500);
+    }
+  });
+  
+  return fileUploadRoutes;
+}

+ 228 - 0
server/routes_know_info.ts

@@ -0,0 +1,228 @@
+import { Hono } from "hono";
+import debug from "debug";
+import type {
+  KnowInfo,
+} from "../client/share/types.ts";
+
+import {
+  EnableStatus,
+  DeleteStatus,
+} from "../client/share/types.ts";
+
+import type { Variables, WithAuth } from "./app.tsx";
+
+const log = {
+  api: debug("api:sys"),
+};
+
+// 创建知识库管理路由
+export function createKnowInfoRoutes(withAuth: WithAuth) {
+  const knowInfoRoutes = new Hono<{ Variables: Variables }>();
+
+  // 获取知识库文章列表
+  knowInfoRoutes.get("/", withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+
+      // 获取分页参数
+      const page = Number(c.req.query("page")) || 1;
+      const limit = Number(c.req.query("limit")) || 10;
+      const offset = (page - 1) * limit;
+
+      // 获取筛选参数
+      const title = c.req.query("title");
+      const category = c.req.query("category");
+      const tags = c.req.query("tags");
+
+      // 构建查询
+      let query = apiClient.database
+        .table("know_info")
+        .where("is_deleted", 0)
+        .orderBy("id", "desc");
+
+      // 应用筛选条件
+      if (title) {
+        query = query.where("title", "like", `%${title}%`);
+      }
+
+      if (category) {
+        query = query.where("category", category);
+      }
+
+      if (tags) {
+        query = query.where("tags", "like", `%${tags}%`);
+      }
+
+      // 克隆查询以获取总数
+      const countQuery = query.clone();
+
+      // 执行分页查询
+      const articles = await query.limit(limit).offset(offset);
+
+      // 获取总数
+      const count = await countQuery.count();
+
+      return c.json({
+        data: articles,
+        pagination: {
+          total: Number(count),
+          current: page,
+          pageSize: limit,
+          totalPages: Math.ceil(Number(count) / limit),
+        },
+      });
+    } catch (error) {
+      log.api("获取知识库文章列表失败:", error);
+      return c.json({ error: "获取知识库文章列表失败" }, 500);
+    }
+  });
+
+  // 获取单个知识库文章
+  knowInfoRoutes.get("/:id", withAuth, async (c) => {
+    try {
+      const id = Number(c.req.param("id"));
+
+      if (!id || isNaN(id)) {
+        return c.json({ error: "无效的文章ID" }, 400);
+      }
+
+      const apiClient = c.get('apiClient');
+      const [article] = await apiClient.database
+        .table("know_info")
+        .where({ id, is_deleted: 0 });
+
+      if (!article) {
+        return c.json({ error: "文章不存在" }, 404);
+      }
+
+      return c.json({message: '获取知识库文章详情成功', data: article});
+    } catch (error) {
+      log.api("获取知识库文章详情失败:", error);
+      return c.json({ error: "获取知识库文章详情失败" }, 500);
+    }
+  });
+
+  // 创建知识库文章
+  knowInfoRoutes.post("/", withAuth, async (c) => {
+    try {
+      const articleData = (await c.req.json()) as Partial<KnowInfo>;
+
+      // 验证必填字段
+      if (!articleData.title) {
+        return c.json({ error: "文章标题不能为空" }, 400);
+      }
+
+      // 如果作者为空,则使用当前用户的用户名
+      if (!articleData.author) {
+        const user = c.get("user");
+        articleData.author = user ? user.username : "unknown";
+      }
+
+      const apiClient = c.get('apiClient');
+      const [id] = await apiClient.database
+        .table("know_info")
+        .insert(articleData);
+
+      // 获取创建的文章
+      const [createdArticle] = await apiClient.database
+        .table("know_info")
+        .where("id", id);
+
+      return c.json({
+        message: "知识库文章创建成功",
+        data: createdArticle,
+      });
+    } catch (error) {
+      log.api("创建知识库文章失败:", error);
+      return c.json({ error: "创建知识库文章失败" }, 500);
+    }
+  });
+
+  // 更新知识库文章
+  knowInfoRoutes.put("/:id", withAuth, async (c) => {
+    try {
+      const id = Number(c.req.param("id"));
+
+      if (!id || isNaN(id)) {
+        return c.json({ error: "无效的文章ID" }, 400);
+      }
+
+      const articleData = (await c.req.json()) as Partial<KnowInfo>;
+
+      // 验证必填字段
+      if (!articleData.title) {
+        return c.json({ error: "文章标题不能为空" }, 400);
+      }
+
+      const apiClient = c.get('apiClient');
+
+      // 检查文章是否存在
+      const [existingArticle] = await apiClient.database
+        .table("know_info")
+        .where({ id, is_deleted: 0 });
+
+      if (!existingArticle) {
+        return c.json({ error: "文章不存在" }, 404);
+      }
+
+      // 更新文章
+      await apiClient.database
+        .table("know_info")
+        .where("id", id)
+        .update({
+          ...articleData,
+          updated_at: apiClient.database.fn.now(),
+        });
+
+      // 获取更新后的文章
+      const [updatedArticle] = await apiClient.database
+        .table("know_info")
+        .where("id", id);
+
+      return c.json({
+        message: "知识库文章更新成功",
+        data: updatedArticle,
+      });
+    } catch (error) {
+      log.api("更新知识库文章失败:", error);
+      return c.json({ error: "更新知识库文章失败" }, 500);
+    }
+  });
+
+  // 删除知识库文章(软删除)
+  knowInfoRoutes.delete("/:id", withAuth, async (c) => {
+    try {
+      const id = Number(c.req.param("id"));
+
+      if (!id || isNaN(id)) {
+        return c.json({ error: "无效的文章ID" }, 400);
+      }
+
+      const apiClient = c.get('apiClient');
+
+      // 检查文章是否存在
+      const [existingArticle] = await apiClient.database
+        .table("know_info")
+        .where({ id, is_deleted: 0 });
+
+      if (!existingArticle) {
+        return c.json({ error: "文章不存在" }, 404);
+      }
+
+      // 软删除文章
+      await apiClient.database.table("know_info").where("id", id).update({
+        is_deleted: 1,
+        updated_at: apiClient.database.fn.now(),
+      });
+
+      return c.json({
+        message: "知识库文章删除成功",
+      });
+    } catch (error) {
+      log.api("删除知识库文章失败:", error);
+      return c.json({ error: "删除知识库文章失败" }, 500);
+    }
+  });
+  
+  return knowInfoRoutes;
+}

+ 0 - 1063
server/routes_sys.ts

@@ -1,1063 +0,0 @@
-import { Hono } from "hono";
-import debug from "debug";
-import type {
-  FileLibrary,
-  FileCategory,
-  KnowInfo,
-  ThemeSettings,
-  SystemSetting,
-  SystemSettingGroupData,
-} from "../client/share/types.ts";
-
-import {
-  EnableStatus,
-  DeleteStatus,
-  ThemeMode,
-  FontSize,
-  CompactMode,
-} from "../client/share/types.ts";
-
-import type { Variables, WithAuth } from "./app.tsx";
-
-const log = {
-  api: debug("api:sys"),
-};
-
-// 创建知识库管理路由
-export function createKnowInfoRoutes(withAuth: WithAuth) {
-  const knowInfoRoutes = new Hono<{ Variables: Variables }>();
-
-  // 获取知识库文章列表
-  knowInfoRoutes.get("/", withAuth, async (c) => {
-    try {
-      const apiClient = c.get('apiClient');
-
-      // 获取分页参数
-      const page = Number(c.req.query("page")) || 1;
-      const limit = Number(c.req.query("limit")) || 10;
-      const offset = (page - 1) * limit;
-
-      // 获取筛选参数
-      const title = c.req.query("title");
-      const category = c.req.query("category");
-      const tags = c.req.query("tags");
-
-      // 构建查询
-      let query = apiClient.database
-        .table("know_info")
-        .where("is_deleted", 0)
-        .orderBy("id", "desc");
-
-      // 应用筛选条件
-      if (title) {
-        query = query.where("title", "like", `%${title}%`);
-      }
-
-      if (category) {
-        query = query.where("category", category);
-      }
-
-      if (tags) {
-        query = query.where("tags", "like", `%${tags}%`);
-      }
-
-      // 克隆查询以获取总数
-      const countQuery = query.clone();
-
-      // 执行分页查询
-      const articles = await query.limit(limit).offset(offset);
-
-      // 获取总数
-      const count = await countQuery.count();
-
-      return c.json({
-        data: articles,
-        pagination: {
-          total: Number(count),
-          current: page,
-          pageSize: limit,
-          totalPages: Math.ceil(Number(count) / limit),
-        },
-      });
-    } catch (error) {
-      log.api("获取知识库文章列表失败:", error);
-      return c.json({ error: "获取知识库文章列表失败" }, 500);
-    }
-  });
-
-  // 获取单个知识库文章
-  knowInfoRoutes.get("/:id", withAuth, async (c) => {
-    try {
-      const id = Number(c.req.param("id"));
-
-      if (!id || isNaN(id)) {
-        return c.json({ error: "无效的文章ID" }, 400);
-      }
-
-      const apiClient = c.get('apiClient');
-      const [article] = await apiClient.database
-        .table("know_info")
-        .where({ id, is_deleted: 0 });
-
-      if (!article) {
-        return c.json({ error: "文章不存在" }, 404);
-      }
-
-      return c.json({message: '获取知识库文章详情成功', data: article});
-    } catch (error) {
-      log.api("获取知识库文章详情失败:", error);
-      return c.json({ error: "获取知识库文章详情失败" }, 500);
-    }
-  });
-
-  // 创建知识库文章
-  knowInfoRoutes.post("/", withAuth, async (c) => {
-    try {
-      const articleData = (await c.req.json()) as Partial<KnowInfo>;
-
-      // 验证必填字段
-      if (!articleData.title) {
-        return c.json({ error: "文章标题不能为空" }, 400);
-      }
-
-      // 如果作者为空,则使用当前用户的用户名
-      if (!articleData.author) {
-        const user = c.get("user");
-        articleData.author = user ? user.username : "unknown";
-      }
-
-      const apiClient = c.get('apiClient');
-      const [id] = await apiClient.database
-        .table("know_info")
-        .insert(articleData);
-
-      // 获取创建的文章
-      const [createdArticle] = await apiClient.database
-        .table("know_info")
-        .where("id", id);
-
-      return c.json({
-        message: "知识库文章创建成功",
-        data: createdArticle,
-      });
-    } catch (error) {
-      log.api("创建知识库文章失败:", error);
-      return c.json({ error: "创建知识库文章失败" }, 500);
-    }
-  });
-
-  // 更新知识库文章
-  knowInfoRoutes.put("/:id", withAuth, async (c) => {
-    try {
-      const id = Number(c.req.param("id"));
-
-      if (!id || isNaN(id)) {
-        return c.json({ error: "无效的文章ID" }, 400);
-      }
-
-      const articleData = (await c.req.json()) as Partial<KnowInfo>;
-
-      // 验证必填字段
-      if (!articleData.title) {
-        return c.json({ error: "文章标题不能为空" }, 400);
-      }
-
-      const apiClient = c.get('apiClient');
-
-      // 检查文章是否存在
-      const [existingArticle] = await apiClient.database
-        .table("know_info")
-        .where({ id, is_deleted: 0 });
-
-      if (!existingArticle) {
-        return c.json({ error: "文章不存在" }, 404);
-      }
-
-      // 更新文章
-      await apiClient.database
-        .table("know_info")
-        .where("id", id)
-        .update({
-          ...articleData,
-          updated_at: apiClient.database.fn.now(),
-        });
-
-      // 获取更新后的文章
-      const [updatedArticle] = await apiClient.database
-        .table("know_info")
-        .where("id", id);
-
-      return c.json({
-        message: "知识库文章更新成功",
-        data: updatedArticle,
-      });
-    } catch (error) {
-      log.api("更新知识库文章失败:", error);
-      return c.json({ error: "更新知识库文章失败" }, 500);
-    }
-  });
-
-  // 删除知识库文章(软删除)
-  knowInfoRoutes.delete("/:id", withAuth, async (c) => {
-    try {
-      const id = Number(c.req.param("id"));
-
-      if (!id || isNaN(id)) {
-        return c.json({ error: "无效的文章ID" }, 400);
-      }
-
-      const apiClient = c.get('apiClient');
-
-      // 检查文章是否存在
-      const [existingArticle] = await apiClient.database
-        .table("know_info")
-        .where({ id, is_deleted: 0 });
-
-      if (!existingArticle) {
-        return c.json({ error: "文章不存在" }, 404);
-      }
-
-      // 软删除文章
-      await apiClient.database.table("know_info").where("id", id).update({
-        is_deleted: 1,
-        updated_at: apiClient.database.fn.now(),
-      });
-
-      return c.json({
-        message: "知识库文章删除成功",
-      });
-    } catch (error) {
-      log.api("删除知识库文章失败:", error);
-      return c.json({ error: "删除知识库文章失败" }, 500);
-    }
-  });
-  
-  return knowInfoRoutes;
-}
-
-// 创建文件分类路由
-export function createFileCategoryRoutes(withAuth: WithAuth) {
-  const fileCategoryRoutes = new Hono<{ Variables: Variables }>();
-
-  // 获取文件分类列表
-  fileCategoryRoutes.get("/", withAuth, async (c) => {
-    try {
-      const apiClient = c.get('apiClient');
-
-      const page = Number(c.req.query("page")) || 1;
-      const pageSize = Number(c.req.query("pageSize")) || 10;
-      const offset = (page - 1) * pageSize;
-
-      const search = c.req.query("search") || "";
-
-      let query = apiClient.database.table("file_category").orderBy("id", "desc");
-
-      if (search) {
-        query = query.where("name", "like", `%${search}%`);
-      }
-
-      const total = await query.clone().count();
-
-      const categories = await query
-        .select("id", "name", "code", "description")
-        .limit(pageSize)
-        .offset(offset);
-
-      return c.json({
-        data: categories,
-        total: Number(total),
-        page,
-        pageSize,
-      });
-    } catch (error) {
-      log.api("获取文件分类列表失败:", error);
-      return c.json({ error: "获取文件分类列表失败" }, 500);
-    }
-  });
-
-  // 创建文件分类
-  fileCategoryRoutes.post("/", withAuth, async (c) => {
-    try {
-      const apiClient = c.get('apiClient');
-      const data = (await c.req.json()) as Partial<FileCategory>;
-
-      // 验证必填字段
-      if (!data.name) {
-        return c.json({ error: "分类名称不能为空" }, 400);
-      }
-
-      // 插入文件分类
-      const [id] = await apiClient.database.table("file_category").insert({
-        ...data,
-        created_at: apiClient.database.fn.now(),
-        updated_at: apiClient.database.fn.now(),
-      });
-
-      return c.json({
-        message: "文件分类创建成功",
-        data: {
-          id,
-          ...data,
-        },
-      });
-    } catch (error) {
-      log.api("创建文件分类失败:", error);
-      return c.json({ error: "创建文件分类失败" }, 500);
-    }
-  });
-
-  // 更新文件分类
-  fileCategoryRoutes.put("/:id", withAuth, async (c) => {
-    try {
-      const apiClient = c.get('apiClient');
-      const id = Number(c.req.param("id"));
-
-      if (!id || isNaN(id)) {
-        return c.json({ error: "无效的分类ID" }, 400);
-      }
-
-      const data = (await c.req.json()) as Partial<FileCategory>;
-
-      // 更新文件分类
-      await apiClient.database
-        .table("file_category")
-        .where("id", id)
-        .update({
-          ...data,
-          updated_at: apiClient.database.fn.now(),
-        });
-
-      return c.json({
-        message: "文件分类更新成功",
-        data: {
-          id,
-          ...data,
-        },
-      });
-    } catch (error) {
-      log.api("更新文件分类失败:", error);
-      return c.json({ error: "更新文件分类失败" }, 500);
-    }
-  });
-
-  // 删除文件分类
-  fileCategoryRoutes.delete("/:id", withAuth, async (c) => {
-    try {
-      const apiClient = c.get('apiClient');
-      const id = Number(c.req.param("id"));
-
-      if (!id || isNaN(id)) {
-        return c.json({ error: "无效的分类ID" }, 400);
-      }
-
-      await apiClient.database.table("file_category").where("id", id).update({
-        is_deleted: DeleteStatus.DELETED,
-        updated_at: apiClient.database.fn.now(),
-      });
-
-      return c.json({
-        message: "文件分类删除成功",
-      });
-    } catch (error) {
-      log.api("删除文件分类失败:", error);
-      return c.json({ error: "删除文件分类失败" }, 500);
-    }
-  });
-  
-  return fileCategoryRoutes;
-}
-
-// 创建文件上传路由
-export function createFileUploadRoutes(withAuth: WithAuth) {
-  const fileUploadRoutes = new Hono<{ Variables: Variables }>();
-
-  // 获取 MinIO 上传策略
-  fileUploadRoutes.get("/policy", withAuth, async (c) => {
-    try {
-      const prefix = c.req.query("prefix") || "uploads/";
-      const filename = c.req.query("filename");
-      const maxSize = Number(c.req.query("maxSize")) || 10 * 1024 * 1024; // 默认10MB
-
-      if (!filename) {
-        return c.json({ error: "文件名不能为空" }, 400);
-      }
-
-      const apiClient = c.get('apiClient');
-      const policy = await apiClient.storage.getUploadPolicy(
-        prefix,
-        filename,
-        maxSize
-      );
-
-      return c.json({
-        message: "获取上传策略成功",
-        data: policy,
-      });
-    } catch (error) {
-      log.api("获取上传策略失败:", error);
-      return c.json({ error: "获取上传策略失败" }, 500);
-    }
-  });
-
-  // 保存文件信息到文件库
-  fileUploadRoutes.post("/save", withAuth, async (c) => {
-    try {
-      const fileData = (await c.req.json()) as Partial<FileLibrary>;
-      const user = c.get("user");
-
-      // 验证必填字段
-      if (!fileData.file_name || !fileData.file_path || !fileData.file_type) {
-        return c.json({ error: "文件名、路径和类型不能为空" }, 400);
-      }
-
-      const apiClient = c.get('apiClient');
-
-      // 设置上传者信息
-      if (user) {
-        fileData.uploader_id = user.id;
-        fileData.uploader_name = user.nickname || user.username;
-      }
-
-      // 插入文件库记录
-      const [id] = await apiClient.database.table("file_library").insert({
-        ...fileData,
-        download_count: 0,
-        is_disabled: EnableStatus.ENABLED,
-        is_deleted: DeleteStatus.NOT_DELETED,
-        created_at: apiClient.database.fn.now(),
-        updated_at: apiClient.database.fn.now(),
-      });
-
-      // 获取插入的数据
-      const [insertedFile] = await apiClient.database
-        .table("file_library")
-        .where("id", id);
-
-      return c.json({
-        message: "文件信息保存成功",
-        data: insertedFile,
-      });
-    } catch (error) {
-      log.api("保存文件信息失败:", error);
-      return c.json({ error: "保存文件信息失败" }, 500);
-    }
-  });
-
-  // 获取文件列表
-  fileUploadRoutes.get("/list", withAuth, async (c) => {
-    try {
-      const page = Number(c.req.query("page")) || 1;
-      const pageSize = Number(c.req.query("pageSize")) || 10;
-      const category_id = c.req.query("category_id");
-      const fileType = c.req.query("fileType");
-      const keyword = c.req.query("keyword");
-
-      const apiClient = c.get('apiClient');
-      let query = apiClient.database
-        .table("file_library")
-        .where("is_deleted", DeleteStatus.NOT_DELETED)
-        .orderBy("created_at", "desc");
-
-      // 应用过滤条件
-      if (category_id) {
-        query = query.where("category_id", category_id);
-      }
-
-      if (fileType) {
-        query = query.where("file_type", fileType);
-      }
-
-      if (keyword) {
-        query = query.where((builder) => {
-          builder
-            .where("file_name", "like", `%${keyword}%`)
-            .orWhere("description", "like", `%${keyword}%`)
-            .orWhere("tags", "like", `%${keyword}%`);
-        });
-      }
-
-      // 获取总数
-      const total = await query.clone().count();
-
-      // 分页查询
-      const files = await query.limit(pageSize).offset((page - 1) * pageSize);
-
-      return c.json({
-        message: "获取文件列表成功",
-        data: {
-          list: files,
-          pagination: {
-            current: page,
-            pageSize,
-            total: Number(total),
-          },
-        },
-      });
-    } catch (error) {
-      log.api("获取文件列表失败:", error);
-      return c.json({ error: "获取文件列表失败" }, 500);
-    }
-  });
-
-  // 获取单个文件信息
-  fileUploadRoutes.get("/:id", withAuth, async (c) => {
-    try {
-      const id = Number(c.req.param("id"));
-
-      if (!id || isNaN(id)) {
-        return c.json({ error: "无效的文件ID" }, 400);
-      }
-
-      const apiClient = c.get('apiClient');
-      const file = await apiClient.database
-        .table("file_library")
-        .where("id", id)
-        .where("is_deleted", DeleteStatus.NOT_DELETED)
-        .first();
-
-      if (!file) {
-        return c.json({ error: "文件不存在" }, 404);
-      }
-
-      return c.json({
-        message: "获取文件信息成功",
-        data: file,
-      });
-    } catch (error) {
-      log.api("获取文件信息失败:", error);
-      return c.json({ error: "获取文件信息失败" }, 500);
-    }
-  });
-
-  // 增加文件下载计数
-  fileUploadRoutes.post("/:id/download", withAuth, async (c) => {
-    try {
-      const id = Number(c.req.param("id"));
-
-      if (!id || isNaN(id)) {
-        return c.json({ error: "无效的文件ID" }, 400);
-      }
-
-      const apiClient = c.get('apiClient');
-
-      // 查询文件是否存在
-      const file = await apiClient.database
-        .table("file_library")
-        .where("id", id)
-        .where("is_deleted", DeleteStatus.NOT_DELETED)
-        .first();
-
-      if (!file) {
-        return c.json({ error: "文件不存在" }, 404);
-      }
-
-      // 增加下载计数
-      await apiClient.database
-        .table("file_library")
-        .where("id", id)
-        .update({
-          download_count: apiClient.database.raw("download_count + 1"),
-          updated_at: apiClient.database.fn.now(),
-        });
-
-      return c.json({
-        message: "更新下载计数成功",
-      });
-    } catch (error) {
-      log.api("更新下载计数失败:", error);
-      return c.json({ error: "更新下载计数失败" }, 500);
-    }
-  });
-
-  // 删除文件
-  fileUploadRoutes.delete("/:id", withAuth, async (c) => {
-    try {
-      const id = Number(c.req.param("id"));
-
-      if (!id || isNaN(id)) {
-        return c.json({ error: "无效的文件ID" }, 400);
-      }
-
-      const apiClient = c.get('apiClient');
-
-      // 查询文件是否存在
-      const file = await apiClient.database
-        .table("file_library")
-        .where("id", id)
-        .where("is_deleted", DeleteStatus.NOT_DELETED)
-        .first();
-
-      if (!file) {
-        return c.json({ error: "文件不存在" }, 404);
-      }
-
-      // 软删除文件
-      await apiClient.database.table("file_library").where("id", id).update({
-        is_deleted: DeleteStatus.DELETED,
-        updated_at: apiClient.database.fn.now(),
-      });
-
-      return c.json({
-        message: "文件删除成功",
-      });
-    } catch (error) {
-      log.api("删除文件失败:", error);
-      return c.json({ error: "删除文件失败" }, 500);
-    }
-  });
-  
-  return fileUploadRoutes;
-}
-
-// 创建主题设置路由
-export function createThemeRoutes(withAuth: WithAuth) {
-  const themeRoutes = new Hono<{ Variables: Variables }>();
-
-  // 获取当前主题设置
-  themeRoutes.get("/", withAuth, async (c) => {
-    try {
-      const apiClient = c.get('apiClient');
-      const user = c.get('user');
-
-      if (!user) {
-        return c.json({ error: "未授权访问" }, 401);
-      }
-
-      // 获取用户的主题设置
-      let themeSettings = await apiClient.database
-        .table("theme_settings")
-        .where("user_id", user.id)
-        .first();
-
-      // 如果用户没有主题设置,则创建默认设置
-      if (!themeSettings) {
-        const defaultSettings = {
-          theme_mode: ThemeMode.LIGHT,
-          primary_color: '#1890ff',
-          font_size: FontSize.MEDIUM,
-          is_compact: CompactMode.NORMAL
-        };
-
-        const [id] = await apiClient.database.table("theme_settings").insert({
-          user_id: user.id,
-          settings: defaultSettings,
-          created_at: apiClient.database.fn.now(),
-          updated_at: apiClient.database.fn.now(),
-        });
-
-        themeSettings = await apiClient.database
-          .table("theme_settings")
-          .where("id", id)
-          .first();
-      }
-
-      return c.json({
-        message: "获取主题设置成功",
-        data: themeSettings?.settings,
-      });
-    } catch (error) {
-      log.api("获取主题设置失败:", error);
-      return c.json({ error: "获取主题设置失败" }, 500);
-    }
-  });
-
-  // 更新主题设置
-  themeRoutes.put("/", withAuth, async (c) => {
-    try {
-      const user = c.get('user');
-      const apiClient = c.get('apiClient');
-
-      if (!user) {
-        return c.json({ error: "未授权访问" }, 401);
-      }
-
-      const themeData = (await c.req.json()) as Partial<ThemeSettings>;
-
-      // 检查用户是否已有主题设置
-      const existingTheme = await apiClient.database
-        .table("theme_settings")
-        .where("user_id", user.id)
-        .first();
-
-      if (existingTheme) {
-        // 更新现有设置
-        const currentSettings = existingTheme.settings || {};
-        const updatedSettings = {
-          ...currentSettings,
-          ...themeData
-        };
-
-        await apiClient.database
-          .table("theme_settings")
-          .where("user_id", user.id)
-          .update({
-            settings: JSON.stringify(updatedSettings),
-            updated_at: apiClient.database.fn.now(),
-          });
-      } else {
-        // 创建新设置
-        const defaultSettings = {
-          theme_mode: ThemeMode.LIGHT,
-          primary_color: '#1890ff',
-          font_size: FontSize.MEDIUM,
-          is_compact: CompactMode.NORMAL
-        };
-
-        const updatedSettings = {
-          ...defaultSettings,
-          ...themeData
-        };
-
-        await apiClient.database.table("theme_settings").insert({
-          user_id: user.id,
-          settings: updatedSettings,
-          created_at: apiClient.database.fn.now(),
-          updated_at: apiClient.database.fn.now(),
-        });
-      }
-
-      // 获取更新后的主题设置
-      const updatedTheme = await apiClient.database
-        .table("theme_settings")
-        .where("user_id", user.id)
-        .first();
-
-      return c.json({
-        message: "主题设置更新成功",
-        data: updatedTheme,
-      });
-    } catch (error) {
-      log.api("更新主题设置失败:", error);
-      return c.json({ error: "更新主题设置失败" }, 500);
-    }
-  });
-
-  // 重置主题设置为默认值
-  themeRoutes.post("/reset", withAuth, async (c) => {
-    try {
-      const user = c.get('user');
-      const apiClient = c.get('apiClient');
-
-      if (!user) {
-        return c.json({ error: "未授权访问" }, 401);
-      }
-
-      // 默认主题设置
-      const defaultSettings = {
-        theme_mode: ThemeMode.LIGHT,
-        primary_color: '#1890ff',
-        font_size: FontSize.MEDIUM,
-        is_compact: CompactMode.NORMAL
-      };
-
-      // 更新用户的主题设置
-      await apiClient.database
-        .table("theme_settings")
-        .where("user_id", user.id)
-        .update({
-          settings: JSON.stringify(defaultSettings),
-          updated_at: apiClient.database.fn.now(),
-        });
-
-      // 获取更新后的主题设置
-      const updatedTheme = await apiClient.database
-        .table("theme_settings")
-        .where("user_id", user.id)
-        .first();
-
-      return c.json({
-        message: "主题设置已重置为默认值",
-        data: updatedTheme,
-      });
-    } catch (error) {
-      log.api("重置主题设置失败:", error);
-      return c.json({ error: "重置主题设置失败" }, 500);
-    }
-  });
-
-  return themeRoutes;
-}
-
-// 创建系统设置路由
-export function createSystemSettingsRoutes(withAuth: WithAuth) {
-  const settingsRoutes = new Hono<{ Variables: Variables }>();
-
-  // 获取所有系统设置(按分组)
-  settingsRoutes.get('/', withAuth, async (c) => {
-    try {
-      const apiClient = c.get('apiClient');
-      const settings = await apiClient.database
-        .table('system_settings')
-        .select('*');
-
-      // 按分组整理数据
-      const groupedSettings = settings.reduce((acc: SystemSettingGroupData[], setting) => {
-        const groupIndex = acc.findIndex((g: SystemSettingGroupData) => g.name === setting.group);
-        if (groupIndex === -1) {
-          acc.push({
-            name: setting.group,
-            description: `${setting.group}组设置`,
-            settings: [{
-              id: setting.id,
-              key: setting.key,
-              value: setting.value,
-              description: setting.description,
-              group: setting.group
-            }]
-          });
-        } else {
-          acc[groupIndex].settings.push({
-            id: setting.id,
-            key: setting.key,
-            value: setting.value,
-            description: setting.description,
-            group: setting.group
-          });
-        }
-        return acc;
-      }, []);
-
-      return c.json({
-        message: '获取系统设置成功',
-        data: groupedSettings
-      });
-    } catch (error) {
-      log.api('获取系统设置失败:', error);
-      return c.json({ error: '获取系统设置失败' }, 500);
-    }
-  });
-
-  // 获取指定分组的系统设置
-  settingsRoutes.get('/group/:group', withAuth, async (c) => {
-    try {
-      const apiClient = c.get('apiClient');
-      const group = c.req.param('group');
-      
-      const settings = await apiClient.database
-        .table('system_settings')
-        .where('group', group)
-        .select('*');
-
-      return c.json({
-        message: '获取分组设置成功',
-        data: settings
-      });
-    } catch (error) {
-      log.api('获取分组设置失败:', error);
-      return c.json({ error: '获取分组设置失败' }, 500);
-    }
-  });
-
-  // 更新系统设置
-  settingsRoutes.put('/:key', withAuth, async (c) => {
-    try {
-      const apiClient = c.get('apiClient');
-      const key = c.req.param('key');
-      const settingData = await c.req.json();
-
-      // 验证设置是否存在
-      const existingSetting = await apiClient.database
-        .table('system_settings')
-        .where('key', key)
-        .first();
-
-      if (!existingSetting) {
-        return c.json({ error: '设置项不存在' }, 404);
-      }
-
-      // 更新设置
-      await apiClient.database
-        .table('system_settings')
-        .where('key', key)
-        .update({
-          value: settingData.value,
-          updated_at: apiClient.database.fn.now()
-        });
-
-      // 获取更新后的设置
-      const updatedSetting = await apiClient.database
-        .table('system_settings')
-        .where('key', key)
-        .first();
-
-      return c.json({
-        message: '系统设置已更新',
-        data: updatedSetting
-      });
-    } catch (error) {
-      log.api('更新系统设置失败:', error);
-      return c.json({ error: '更新系统设置失败' }, 500);
-    }
-  });
-
-  // 批量更新系统设置
-  settingsRoutes.put('/', withAuth, async (c) => {
-    try {
-      const apiClient = c.get('apiClient');
-      const settingsData = await c.req.json();
-
-      // 验证数据格式
-      if (!Array.isArray(settingsData)) {
-        return c.json({ error: '无效的请求数据格式,应为数组' }, 400);
-      }
-
-      const trxProvider = apiClient.database.transactionProvider();
-      const trx = await trxProvider();
-
-      for (const setting of settingsData) {
-        if (!setting.key) continue;
-
-        // 验证设置是否存在
-        const existingSetting = await trx.table('system_settings')
-          .where('key', setting.key)
-          .first();
-
-        if (!existingSetting) {
-          throw new Error(`设置项 ${setting.key} 不存在`);
-        }
-
-        // 更新设置
-        await trx.table('system_settings')
-          .where('key', setting.key)
-          .update({
-            value: setting.value,
-            updated_at: trx.fn.now()
-          });
-      }
-
-      await trx.commit();
-
-      // 获取所有更新后的设置
-      const updatedSettings = await apiClient.database
-        .table('system_settings')
-        .whereIn('key', settingsData.map(s => s.key))
-        .select('*');
-
-      return c.json({
-        message: '系统设置已批量更新',
-        data: updatedSettings
-      });
-    } catch (error) {
-      log.api('批量更新系统设置失败:', error);
-      return c.json({ error: '批量更新系统设置失败' }, 500);
-    }
-  });
-
-  // 重置系统设置
-  settingsRoutes.post('/reset', withAuth, async (c) => {
-    try {
-      const apiClient = c.get('apiClient');
-
-      // 重置为迁移文件中定义的初始值
-      const trxProvider = apiClient.database.transactionProvider();
-      const trx = await trxProvider();
-
-      // 清空现有设置
-      await trx.table('system_settings').delete();
-      
-      // 插入默认设置
-      await trx.table('system_settings').insert([
-        // 基础设置组
-        {
-          key: 'SITE_NAME',
-          value: '应用管理系统',
-          description: '站点名称',
-          group: 'basic',
-          created_at: apiClient.database.fn.now(),
-          updated_at: apiClient.database.fn.now()
-        },
-        {
-          key: 'SITE_DESCRIPTION',
-          value: '一个强大的应用管理系统',
-          description: '站点描述',
-          group: 'basic',
-          created_at: apiClient.database.fn.now(),
-          updated_at: apiClient.database.fn.now()
-        },
-        {
-          key: 'SITE_KEYWORDS',
-          value: '应用管理,系统管理,后台管理',
-          description: '站点关键词',
-          group: 'basic',
-          created_at: apiClient.database.fn.now(),
-          updated_at: apiClient.database.fn.now()
-        },
-        // 功能设置组
-        {
-          key: 'ENABLE_REGISTER',
-          value: 'true',
-          description: '是否开启注册',
-          group: 'feature',
-          created_at: apiClient.database.fn.now(),
-          updated_at: apiClient.database.fn.now()
-        },
-        {
-          key: 'ENABLE_CAPTCHA',
-          value: 'true',
-          description: '是否开启验证码',
-          group: 'feature',
-          created_at: apiClient.database.fn.now(),
-          updated_at: apiClient.database.fn.now()
-        },
-        {
-          key: 'LOGIN_ATTEMPTS',
-          value: '5',
-          description: '登录尝试次数',
-          group: 'feature',
-          created_at: apiClient.database.fn.now(),
-          updated_at: apiClient.database.fn.now()
-        },
-        // 上传设置组
-        {
-          key: 'UPLOAD_MAX_SIZE',
-          value: '10',
-          description: '最大上传大小(MB)',
-          group: 'upload',
-          created_at: apiClient.database.fn.now(),
-          updated_at: apiClient.database.fn.now()
-        },
-        {
-          key: 'ALLOWED_FILE_TYPES',
-          value: 'jpg,jpeg,png,gif,doc,docx,xls,xlsx,pdf',
-          description: '允许的文件类型',
-          group: 'upload',
-          created_at: apiClient.database.fn.now(),
-          updated_at: apiClient.database.fn.now()
-        },
-        // 通知设置组
-        {
-          key: 'NOTIFY_ON_LOGIN',
-          value: 'true',
-          description: '登录通知',
-          group: 'notify',
-          created_at: apiClient.database.fn.now(),
-          updated_at: apiClient.database.fn.now()
-        },
-        {
-          key: 'NOTIFY_ON_ERROR',
-          value: 'true',
-          description: '错误通知',
-          group: 'notify',
-          created_at: apiClient.database.fn.now(),
-          updated_at: apiClient.database.fn.now()
-        }
-      ]);
-
-      await trx.commit();
-
-      const resetSettings = await apiClient.database
-        .table('system_settings')
-        .select('*');
-
-      return c.json({
-        message: '系统设置已重置',
-        data: resetSettings
-      });
-    } catch (error) {
-      log.api('重置系统设置失败:', error);
-      return c.json({ error: '重置系统设置失败' }, 500);
-    }
-  });
-
-  return settingsRoutes;
-}

+ 296 - 0
server/routes_system_settings.ts

@@ -0,0 +1,296 @@
+import { Hono } from "hono";
+import debug from "debug";
+import type {
+  SystemSetting,
+  SystemSettingGroupData,
+} from "../client/share/types.ts";
+
+import type { Variables, WithAuth } from "./app.tsx";
+
+const log = {
+  api: debug("api:sys"),
+};
+
+// 创建系统设置路由
+export function createSystemSettingsRoutes(withAuth: WithAuth) {
+  const settingsRoutes = new Hono<{ Variables: Variables }>();
+
+  // 获取所有系统设置(按分组)
+  settingsRoutes.get('/', withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      const settings = await apiClient.database
+        .table('system_settings')
+        .select('*');
+
+      // 按分组整理数据
+      const groupedSettings = settings.reduce((acc: SystemSettingGroupData[], setting) => {
+        const groupIndex = acc.findIndex((g: SystemSettingGroupData) => g.name === setting.group);
+        if (groupIndex === -1) {
+          acc.push({
+            name: setting.group,
+            description: `${setting.group}组设置`,
+            settings: [{
+              id: setting.id,
+              key: setting.key,
+              value: setting.value,
+              description: setting.description,
+              group: setting.group
+            }]
+          });
+        } else {
+          acc[groupIndex].settings.push({
+            id: setting.id,
+            key: setting.key,
+            value: setting.value,
+            description: setting.description,
+            group: setting.group
+          });
+        }
+        return acc;
+      }, []);
+
+      return c.json({
+        message: '获取系统设置成功',
+        data: groupedSettings
+      });
+    } catch (error) {
+      log.api('获取系统设置失败:', error);
+      return c.json({ error: '获取系统设置失败' }, 500);
+    }
+  });
+
+  // 获取指定分组的系统设置
+  settingsRoutes.get('/group/:group', withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      const group = c.req.param('group');
+      
+      const settings = await apiClient.database
+        .table('system_settings')
+        .where('group', group)
+        .select('*');
+
+      return c.json({
+        message: '获取分组设置成功',
+        data: settings
+      });
+    } catch (error) {
+      log.api('获取分组设置失败:', error);
+      return c.json({ error: '获取分组设置失败' }, 500);
+    }
+  });
+
+  // 更新系统设置
+  settingsRoutes.put('/:key', withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      const key = c.req.param('key');
+      const settingData = await c.req.json();
+
+      // 验证设置是否存在
+      const existingSetting = await apiClient.database
+        .table('system_settings')
+        .where('key', key)
+        .first();
+
+      if (!existingSetting) {
+        return c.json({ error: '设置项不存在' }, 404);
+      }
+
+      // 更新设置
+      await apiClient.database
+        .table('system_settings')
+        .where('key', key)
+        .update({
+          value: settingData.value,
+          updated_at: apiClient.database.fn.now()
+        });
+
+      // 获取更新后的设置
+      const updatedSetting = await apiClient.database
+        .table('system_settings')
+        .where('key', key)
+        .first();
+
+      return c.json({
+        message: '系统设置已更新',
+        data: updatedSetting
+      });
+    } catch (error) {
+      log.api('更新系统设置失败:', error);
+      return c.json({ error: '更新系统设置失败' }, 500);
+    }
+  });
+
+  // 批量更新系统设置
+  settingsRoutes.put('/', withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      const settingsData = await c.req.json();
+
+      // 验证数据格式
+      if (!Array.isArray(settingsData)) {
+        return c.json({ error: '无效的请求数据格式,应为数组' }, 400);
+      }
+
+      const trxProvider = apiClient.database.transactionProvider();
+      const trx = await trxProvider();
+
+      for (const setting of settingsData) {
+        if (!setting.key) continue;
+
+        // 验证设置是否存在
+        const existingSetting = await trx.table('system_settings')
+          .where('key', setting.key)
+          .first();
+
+        if (!existingSetting) {
+          throw new Error(`设置项 ${setting.key} 不存在`);
+        }
+
+        // 更新设置
+        await trx.table('system_settings')
+          .where('key', setting.key)
+          .update({
+            value: setting.value,
+            updated_at: trx.fn.now()
+          });
+      }
+
+      await trx.commit();
+
+      // 获取所有更新后的设置
+      const updatedSettings = await apiClient.database
+        .table('system_settings')
+        .whereIn('key', settingsData.map(s => s.key))
+        .select('*');
+
+      return c.json({
+        message: '系统设置已批量更新',
+        data: updatedSettings
+      });
+    } catch (error) {
+      log.api('批量更新系统设置失败:', error);
+      return c.json({ error: '批量更新系统设置失败' }, 500);
+    }
+  });
+
+  // 重置系统设置
+  settingsRoutes.post('/reset', withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+
+      // 重置为迁移文件中定义的初始值
+      const trxProvider = apiClient.database.transactionProvider();
+      const trx = await trxProvider();
+
+      // 清空现有设置
+      await trx.table('system_settings').delete();
+      
+      // 插入默认设置
+      await trx.table('system_settings').insert([
+        // 基础设置组
+        {
+          key: 'SITE_NAME',
+          value: '应用管理系统',
+          description: '站点名称',
+          group: 'basic',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        },
+        {
+          key: 'SITE_DESCRIPTION',
+          value: '一个强大的应用管理系统',
+          description: '站点描述',
+          group: 'basic',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        },
+        {
+          key: 'SITE_KEYWORDS',
+          value: '应用管理,系统管理,后台管理',
+          description: '站点关键词',
+          group: 'basic',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        },
+        // 功能设置组
+        {
+          key: 'ENABLE_REGISTER',
+          value: 'true',
+          description: '是否开启注册',
+          group: 'feature',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        },
+        {
+          key: 'ENABLE_CAPTCHA',
+          value: 'true',
+          description: '是否开启验证码',
+          group: 'feature',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        },
+        {
+          key: 'LOGIN_ATTEMPTS',
+          value: '5',
+          description: '登录尝试次数',
+          group: 'feature',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        },
+        // 上传设置组
+        {
+          key: 'UPLOAD_MAX_SIZE',
+          value: '10',
+          description: '最大上传大小(MB)',
+          group: 'upload',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        },
+        {
+          key: 'ALLOWED_FILE_TYPES',
+          value: 'jpg,jpeg,png,gif,doc,docx,xls,xlsx,pdf',
+          description: '允许的文件类型',
+          group: 'upload',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        },
+        // 通知设置组
+        {
+          key: 'NOTIFY_ON_LOGIN',
+          value: 'true',
+          description: '登录通知',
+          group: 'notify',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        },
+        {
+          key: 'NOTIFY_ON_ERROR',
+          value: 'true',
+          description: '错误通知',
+          group: 'notify',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        }
+      ]);
+
+      await trx.commit();
+
+      const resetSettings = await apiClient.database
+        .table('system_settings')
+        .select('*');
+
+      return c.json({
+        message: '系统设置已重置',
+        data: resetSettings
+      });
+    } catch (error) {
+      log.api('重置系统设置失败:', error);
+      return c.json({ error: '重置系统设置失败' }, 500);
+    }
+  });
+
+  return settingsRoutes;
+}

+ 186 - 0
server/routes_theme.ts

@@ -0,0 +1,186 @@
+import { Hono } from "hono";
+import debug from "debug";
+import type {
+  ThemeSettings,
+} from "../client/share/types.ts";
+
+import {
+  ThemeMode,
+  FontSize,
+  CompactMode,
+} from "../client/share/types.ts";
+
+import type { Variables, WithAuth } from "./app.tsx";
+
+const log = {
+  api: debug("api:sys"),
+};
+
+// 创建主题设置路由
+export function createThemeRoutes(withAuth: WithAuth) {
+  const themeRoutes = new Hono<{ Variables: Variables }>();
+
+  // 获取当前主题设置
+  themeRoutes.get("/", withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      const user = c.get('user');
+
+      if (!user) {
+        return c.json({ error: "未授权访问" }, 401);
+      }
+
+      // 获取用户的主题设置
+      let themeSettings = await apiClient.database
+        .table("theme_settings")
+        .where("user_id", user.id)
+        .first();
+
+      // 如果用户没有主题设置,则创建默认设置
+      if (!themeSettings) {
+        const defaultSettings = {
+          theme_mode: ThemeMode.LIGHT,
+          primary_color: '#1890ff',
+          font_size: FontSize.MEDIUM,
+          is_compact: CompactMode.NORMAL
+        };
+
+        const [id] = await apiClient.database.table("theme_settings").insert({
+          user_id: user.id,
+          settings: defaultSettings,
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now(),
+        });
+
+        themeSettings = await apiClient.database
+          .table("theme_settings")
+          .where("id", id)
+          .first();
+      }
+
+      return c.json({
+        message: "获取主题设置成功",
+        data: themeSettings?.settings,
+      });
+    } catch (error) {
+      log.api("获取主题设置失败:", error);
+      return c.json({ error: "获取主题设置失败" }, 500);
+    }
+  });
+
+  // 更新主题设置
+  themeRoutes.put("/", withAuth, async (c) => {
+    try {
+      const user = c.get('user');
+      const apiClient = c.get('apiClient');
+
+      if (!user) {
+        return c.json({ error: "未授权访问" }, 401);
+      }
+
+      const themeData = (await c.req.json()) as Partial<ThemeSettings>;
+
+      // 检查用户是否已有主题设置
+      const existingTheme = await apiClient.database
+        .table("theme_settings")
+        .where("user_id", user.id)
+        .first();
+
+      if (existingTheme) {
+        // 更新现有设置
+        const currentSettings = existingTheme.settings || {};
+        const updatedSettings = {
+          ...currentSettings,
+          ...themeData
+        };
+
+        await apiClient.database
+          .table("theme_settings")
+          .where("user_id", user.id)
+          .update({
+            settings: JSON.stringify(updatedSettings),
+            updated_at: apiClient.database.fn.now(),
+          });
+      } else {
+        // 创建新设置
+        const defaultSettings = {
+          theme_mode: ThemeMode.LIGHT,
+          primary_color: '#1890ff',
+          font_size: FontSize.MEDIUM,
+          is_compact: CompactMode.NORMAL
+        };
+
+        const updatedSettings = {
+          ...defaultSettings,
+          ...themeData
+        };
+
+        await apiClient.database.table("theme_settings").insert({
+          user_id: user.id,
+          settings: updatedSettings,
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now(),
+        });
+      }
+
+      // 获取更新后的主题设置
+      const updatedTheme = await apiClient.database
+        .table("theme_settings")
+        .where("user_id", user.id)
+        .first();
+
+      return c.json({
+        message: "主题设置更新成功",
+        data: updatedTheme,
+      });
+    } catch (error) {
+      log.api("更新主题设置失败:", error);
+      return c.json({ error: "更新主题设置失败" }, 500);
+    }
+  });
+
+  // 重置主题设置为默认值
+  themeRoutes.post("/reset", withAuth, async (c) => {
+    try {
+      const user = c.get('user');
+      const apiClient = c.get('apiClient');
+
+      if (!user) {
+        return c.json({ error: "未授权访问" }, 401);
+      }
+
+      // 默认主题设置
+      const defaultSettings = {
+        theme_mode: ThemeMode.LIGHT,
+        primary_color: '#1890ff',
+        font_size: FontSize.MEDIUM,
+        is_compact: CompactMode.NORMAL
+      };
+
+      // 更新用户的主题设置
+      await apiClient.database
+        .table("theme_settings")
+        .where("user_id", user.id)
+        .update({
+          settings: JSON.stringify(defaultSettings),
+          updated_at: apiClient.database.fn.now(),
+        });
+
+      // 获取更新后的主题设置
+      const updatedTheme = await apiClient.database
+        .table("theme_settings")
+        .where("user_id", user.id)
+        .first();
+
+      return c.json({
+        message: "主题设置已重置为默认值",
+        data: updatedTheme,
+      });
+    } catch (error) {
+      log.api("重置主题设置失败:", error);
+      return c.json({ error: "重置主题设置失败" }, 500);
+    }
+  });
+
+  return themeRoutes;
+}