diff --git a/Server/application/superadmin/config/route.php b/Server/application/superadmin/config/route.php
index 5413a593..dfec2b8d 100644
--- a/Server/application/superadmin/config/route.php
+++ b/Server/application/superadmin/config/route.php
@@ -2,4 +2,13 @@
use think\facade\Route;
// 超级管理员认证相关路由
-Route::post('auth/login', 'app\\superadmin\\controller\\Auth@login');
\ No newline at end of file
+Route::post('auth/login', 'app\\superadmin\\controller\\Auth@login');
+
+// 菜单管理相关路由
+Route::group('menu', function () {
+ Route::get('tree', 'app\\superadmin\\controller\\Menu@getMenuTree');
+ Route::get('list', 'app\\superadmin\\controller\\Menu@getMenuList');
+ Route::post('save', 'app\\superadmin\\controller\\Menu@saveMenu');
+ Route::delete('delete/:id', 'app\\superadmin\\controller\\Menu@deleteMenu');
+ Route::post('status', 'app\\superadmin\\controller\\Menu@updateStatus');
+});
\ No newline at end of file
diff --git a/Server/application/superadmin/controller/Menu.php b/Server/application/superadmin/controller/Menu.php
new file mode 100644
index 00000000..d27fdd2e
--- /dev/null
+++ b/Server/application/superadmin/controller/Menu.php
@@ -0,0 +1,151 @@
+request->param('only_enabled', 1);
+ $useCache = $this->request->param('use_cache', 1);
+
+ // 调用模型获取菜单树
+ $menuTree = MenuModel::getMenuTree($onlyEnabled, $useCache);
+
+ return json([
+ 'code' => 200,
+ 'msg' => '获取成功',
+ 'data' => $menuTree
+ ]);
+ }
+
+ /**
+ * 获取所有菜单(平铺结构,便于后台管理)
+ * @return \think\response\Json
+ */
+ public function getMenuList()
+ {
+ // 查询条件
+ $where = [];
+ $status = $this->request->param('status');
+ if ($status !== null && $status !== '') {
+ $where[] = ['status', '=', intval($status)];
+ }
+
+ // 获取所有菜单
+ $menus = MenuModel::where($where)
+ ->order('sort', 'asc')
+ ->select();
+
+ return json([
+ 'code' => 200,
+ 'msg' => '获取成功',
+ 'data' => $menus
+ ]);
+ }
+
+ /**
+ * 添加或更新菜单
+ * @return \think\response\Json
+ */
+ public function saveMenu()
+ {
+ if (!$this->request->isPost()) {
+ return json(['code' => 405, 'msg' => '请求方法不允许']);
+ }
+
+ // 获取参数
+ $data = $this->request->post();
+
+ // 验证参数
+ $validate = $this->validate($data, [
+ 'title|菜单名称' => 'require|max:50',
+ 'path|路由路径' => 'require|max:100',
+ 'parent_id|父菜单ID' => 'require|number',
+ 'status|状态' => 'require|in:0,1',
+ 'sort|排序' => 'require|number',
+ ]);
+
+ if ($validate !== true) {
+ return json(['code' => 400, 'msg' => $validate]);
+ }
+
+ // 保存菜单
+ $result = MenuModel::saveMenu($data);
+
+ if ($result) {
+ return json(['code' => 200, 'msg' => '保存成功']);
+ } else {
+ return json(['code' => 500, 'msg' => '保存失败']);
+ }
+ }
+
+ /**
+ * 删除菜单
+ * @param int $id 菜单ID
+ * @return \think\response\Json
+ */
+ public function deleteMenu($id)
+ {
+ if (!$this->request->isDelete()) {
+ return json(['code' => 405, 'msg' => '请求方法不允许']);
+ }
+
+ if (empty($id) || !is_numeric($id)) {
+ return json(['code' => 400, 'msg' => '参数错误']);
+ }
+
+ $result = MenuModel::deleteMenu($id);
+
+ if ($result) {
+ return json(['code' => 200, 'msg' => '删除成功']);
+ } else {
+ return json(['code' => 500, 'msg' => '删除失败,可能存在子菜单']);
+ }
+ }
+
+ /**
+ * 更新菜单状态
+ * @return \think\response\Json
+ */
+ public function updateStatus()
+ {
+ if (!$this->request->isPost()) {
+ return json(['code' => 405, 'msg' => '请求方法不允许']);
+ }
+
+ $id = $this->request->post('id');
+ $status = $this->request->post('status');
+
+ if (empty($id) || !is_numeric($id) || !in_array($status, [0, 1])) {
+ return json(['code' => 400, 'msg' => '参数错误']);
+ }
+
+ $menu = MenuModel::find($id);
+ if (!$menu) {
+ return json(['code' => 404, 'msg' => '菜单不存在']);
+ }
+
+ $menu->status = $status;
+ $result = $menu->save();
+
+ // 清除缓存
+ MenuModel::clearMenuCache();
+
+ if ($result) {
+ return json(['code' => 200, 'msg' => '状态更新成功']);
+ } else {
+ return json(['code' => 500, 'msg' => '状态更新失败']);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Server/application/superadmin/model/Menu.php b/Server/application/superadmin/model/Menu.php
new file mode 100644
index 00000000..8e116a15
--- /dev/null
+++ b/Server/application/superadmin/model/Menu.php
@@ -0,0 +1,144 @@
+order('sort', 'asc')
+ ->select()
+ ->toArray();
+
+ // 组织成树状结构
+ $menuTree = self::buildMenuTree($allMenus);
+
+ // 缓存结果
+ if ($useCache) {
+ Cache::set($cacheKey, $menuTree, 3600); // 缓存1小时
+ }
+
+ return $menuTree;
+ }
+
+ /**
+ * 构建菜单树
+ * @param array $menus 所有菜单
+ * @param int $parentId 父菜单ID
+ * @return array
+ */
+ private static function buildMenuTree($menus, $parentId = 0)
+ {
+ $tree = [];
+
+ foreach ($menus as $menu) {
+ if ($menu['parent_id'] == $parentId) {
+ $children = self::buildMenuTree($menus, $menu['id']);
+ if (!empty($children)) {
+ $menu['children'] = $children;
+ }
+ $tree[] = $menu;
+ }
+ }
+
+ return $tree;
+ }
+
+ /**
+ * 清除菜单缓存
+ */
+ public static function clearMenuCache()
+ {
+ Cache::delete('superadmin_menu_tree_enabled');
+ Cache::delete('superadmin_menu_tree_all');
+ }
+
+ /**
+ * 添加或更新菜单
+ * @param array $data 菜单数据
+ * @return bool
+ */
+ public static function saveMenu($data)
+ {
+ if (isset($data['id']) && $data['id'] > 0) {
+ // 更新
+ $menu = self::find($data['id']);
+ if (!$menu) {
+ return false;
+ }
+ $result = $menu->save($data);
+ } else {
+ // 新增
+ $menu = new self();
+ $result = $menu->save($data);
+ }
+
+ // 清除缓存
+ self::clearMenuCache();
+
+ return $result !== false;
+ }
+
+ /**
+ * 删除菜单
+ * @param int $id 菜单ID
+ * @return bool
+ */
+ public static function deleteMenu($id)
+ {
+ // 查找子菜单
+ $childCount = self::where('parent_id', $id)->count();
+ if ($childCount > 0) {
+ return false; // 有子菜单不能删除
+ }
+
+ $result = self::destroy($id);
+
+ // 清除缓存
+ self::clearMenuCache();
+
+ return $result !== false;
+ }
+}
\ No newline at end of file
diff --git a/Server/database/create_menu_table.sql b/Server/database/create_menu_table.sql
new file mode 100644
index 00000000..106bf7af
--- /dev/null
+++ b/Server/database/create_menu_table.sql
@@ -0,0 +1,35 @@
+-- 创建菜单表
+CREATE TABLE IF NOT EXISTS `tk_menus` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
+ `title` varchar(50) NOT NULL COMMENT '菜单名称',
+ `path` varchar(100) NOT NULL COMMENT '路由路径',
+ `icon` varchar(50) DEFAULT NULL COMMENT '图标名称',
+ `parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '父菜单ID,0表示顶级菜单',
+ `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态:1启用,0禁用',
+ `sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序,数值越小越靠前',
+ `create_time` int(11) DEFAULT NULL COMMENT '创建时间',
+ `update_time` int(11) DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ KEY `idx_parent_id` (`parent_id`),
+ KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统菜单表';
+
+-- 插入超级管理员顶级菜单
+INSERT INTO `tk_menus` (`title`, `path`, `icon`, `parent_id`, `status`, `sort`, `create_time`, `update_time`) VALUES
+('仪表盘', '/dashboard', 'LayoutDashboard', 0, 1, 10, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
+('项目管理', '/dashboard/projects', 'FolderKanban', 0, 1, 20, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
+('客户池', '/dashboard/customers', 'Users', 0, 1, 30, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
+('管理员权限', '/dashboard/admins', 'Settings', 0, 1, 40, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
+('系统设置', '/settings', 'Cog', 0, 1, 50, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
+
+-- 插入子菜单
+INSERT INTO `tk_menus` (`title`, `path`, `icon`, `parent_id`, `status`, `sort`, `create_time`, `update_time`) VALUES
+('项目列表', '/dashboard/projects', 'List', 2, 1, 21, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
+('新建项目', '/dashboard/projects/new', 'PlusCircle', 2, 1, 22, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
+('客户管理', '/dashboard/customers', 'Users', 3, 1, 31, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
+('客户分析', '/dashboard/customers/analytics', 'BarChart', 3, 1, 32, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
+('管理员列表', '/dashboard/admins', 'UserCog', 4, 1, 41, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
+('角色管理', '/dashboard/admins/roles', 'ShieldCheck', 4, 1, 42, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
+('权限设置', '/dashboard/admins/permissions', 'Lock', 4, 1, 43, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
+('基本设置', '/settings/general', 'Settings', 5, 1, 51, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
+('安全设置', '/settings/security', 'Shield', 5, 1, 52, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
\ No newline at end of file
diff --git a/Server/scripts/init-menu.php b/Server/scripts/init-menu.php
new file mode 100644
index 00000000..d1ed8ad5
--- /dev/null
+++ b/Server/scripts/init-menu.php
@@ -0,0 +1,173 @@
+ Env::get('database.type', 'mysql'),
+ 'hostname' => Env::get('database.hostname', '127.0.0.1'),
+ 'database' => Env::get('database.database', 'database'),
+ 'username' => Env::get('database.username', 'root'),
+ 'password' => Env::get('database.password', 'root'),
+ 'hostport' => Env::get('database.hostport', '3306'),
+ 'charset' => Env::get('database.charset', 'utf8mb4'),
+ 'prefix' => Env::get('database.prefix', 'tk_'),
+];
+
+// 连接数据库
+try {
+ $dsn = "{$dbConfig['type']}:host={$dbConfig['hostname']};port={$dbConfig['hostport']};dbname={$dbConfig['database']};charset={$dbConfig['charset']}";
+ $pdo = new PDO($dsn, $dbConfig['username'], $dbConfig['password']);
+ $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ echo "数据库连接成功!\n";
+} catch (PDOException $e) {
+ die("数据库连接失败: " . $e->getMessage() . "\n");
+}
+
+// 创建菜单表SQL
+$createTableSql = "
+CREATE TABLE IF NOT EXISTS `{$dbConfig['prefix']}menus` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
+ `title` varchar(50) NOT NULL COMMENT '菜单名称',
+ `path` varchar(100) NOT NULL COMMENT '路由路径',
+ `icon` varchar(50) DEFAULT NULL COMMENT '图标名称',
+ `parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '父菜单ID,0表示顶级菜单',
+ `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态:1启用,0禁用',
+ `sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序,数值越小越靠前',
+ `create_time` int(11) DEFAULT NULL COMMENT '创建时间',
+ `update_time` int(11) DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ KEY `idx_parent_id` (`parent_id`),
+ KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统菜单表';
+";
+
+// 执行创建表SQL
+try {
+ $pdo->exec($createTableSql);
+ echo "菜单表创建成功!\n";
+} catch (PDOException $e) {
+ echo "菜单表创建失败: " . $e->getMessage() . "\n";
+}
+
+// 检查表中是否已有数据
+$checkSql = "SELECT COUNT(*) FROM `{$dbConfig['prefix']}menus`";
+try {
+ $count = $pdo->query($checkSql)->fetchColumn();
+ if ($count > 0) {
+ echo "菜单表中已有 {$count} 条数据,跳过数据初始化\n";
+ exit(0);
+ }
+} catch (PDOException $e) {
+ echo "检查数据失败: " . $e->getMessage() . "\n";
+ exit(1);
+}
+
+// 插入顶级菜单数据
+$topMenus = [
+ ['仪表盘', '/dashboard', 'LayoutDashboard', 0, 1, 10],
+ ['项目管理', '/dashboard/projects', 'FolderKanban', 0, 1, 20],
+ ['客户池', '/dashboard/customers', 'Users', 0, 1, 30],
+ ['管理员权限', '/dashboard/admins', 'Settings', 0, 1, 40],
+ ['系统设置', '/settings', 'Cog', 0, 1, 50],
+];
+
+$insertTopMenuSql = "INSERT INTO `{$dbConfig['prefix']}menus`
+ (`title`, `path`, `icon`, `parent_id`, `status`, `sort`, `create_time`, `update_time`)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
+
+$timestamp = time();
+$insertStmt = $pdo->prepare($insertTopMenuSql);
+
+$pdo->beginTransaction();
+try {
+ foreach ($topMenus as $index => $menu) {
+ $insertStmt->execute([
+ $menu[0], // title
+ $menu[1], // path
+ $menu[2], // icon
+ $menu[3], // parent_id
+ $menu[4], // status
+ $menu[5], // sort
+ $timestamp,
+ $timestamp
+ ]);
+ }
+ $pdo->commit();
+ echo "顶级菜单数据插入成功!\n";
+} catch (PDOException $e) {
+ $pdo->rollBack();
+ echo "顶级菜单数据插入失败: " . $e->getMessage() . "\n";
+ exit(1);
+}
+
+// 查询刚插入的顶级菜单ID
+$menuIds = [];
+$queryTopMenuSql = "SELECT id, title FROM `{$dbConfig['prefix']}menus` WHERE parent_id = 0";
+try {
+ $topMenusResult = $pdo->query($queryTopMenuSql)->fetchAll(PDO::FETCH_ASSOC);
+ foreach ($topMenusResult as $menu) {
+ $menuIds[$menu['title']] = $menu['id'];
+ }
+} catch (PDOException $e) {
+ echo "查询顶级菜单失败: " . $e->getMessage() . "\n";
+ exit(1);
+}
+
+// 插入子菜单数据
+$subMenus = [
+ ['项目列表', '/dashboard/projects', 'List', $menuIds['项目管理'], 1, 21],
+ ['新建项目', '/dashboard/projects/new', 'PlusCircle', $menuIds['项目管理'], 1, 22],
+ ['客户管理', '/dashboard/customers', 'Users', $menuIds['客户池'], 1, 31],
+ ['客户分析', '/dashboard/customers/analytics', 'BarChart', $menuIds['客户池'], 1, 32],
+ ['管理员列表', '/dashboard/admins', 'UserCog', $menuIds['管理员权限'], 1, 41],
+ ['角色管理', '/dashboard/admins/roles', 'ShieldCheck', $menuIds['管理员权限'], 1, 42],
+ ['权限设置', '/dashboard/admins/permissions', 'Lock', $menuIds['管理员权限'], 1, 43],
+ ['基本设置', '/settings/general', 'Settings', $menuIds['系统设置'], 1, 51],
+ ['安全设置', '/settings/security', 'Shield', $menuIds['系统设置'], 1, 52],
+];
+
+$insertSubMenuSql = "INSERT INTO `{$dbConfig['prefix']}menus`
+ (`title`, `path`, `icon`, `parent_id`, `status`, `sort`, `create_time`, `update_time`)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
+
+$pdo->beginTransaction();
+try {
+ $insertStmt = $pdo->prepare($insertSubMenuSql);
+ foreach ($subMenus as $menu) {
+ $insertStmt->execute([
+ $menu[0], // title
+ $menu[1], // path
+ $menu[2], // icon
+ $menu[3], // parent_id
+ $menu[4], // status
+ $menu[5], // sort
+ $timestamp,
+ $timestamp
+ ]);
+ }
+ $pdo->commit();
+ echo "子菜单数据插入成功!\n";
+} catch (PDOException $e) {
+ $pdo->rollBack();
+ echo "子菜单数据插入失败: " . $e->getMessage() . "\n";
+ exit(1);
+}
+
+echo "菜单初始化完成!\n";
\ No newline at end of file
diff --git a/SuperAdmin/app/dashboard/layout.tsx b/SuperAdmin/app/dashboard/layout.tsx
index b44afd4e..557729fe 100644
--- a/SuperAdmin/app/dashboard/layout.tsx
+++ b/SuperAdmin/app/dashboard/layout.tsx
@@ -1,12 +1,11 @@
"use client"
import type React from "react"
-
import { useState } from "react"
-import Link from "next/link"
-import { usePathname } from "next/navigation"
import { Button } from "@/components/ui/button"
-import { LayoutDashboard, Users, Settings, LogOut, Menu, X } from "lucide-react"
+import { Menu, X, LogOut } from "lucide-react"
+import { Sidebar } from "@/components/layout/sidebar"
+import { Header } from "@/components/layout/header"
export default function DashboardLayout({
children,
@@ -14,25 +13,6 @@ export default function DashboardLayout({
children: React.ReactNode
}) {
const [sidebarOpen, setSidebarOpen] = useState(true)
- const pathname = usePathname()
-
- const navItems = [
- {
- title: "项目管理",
- href: "/dashboard/projects",
- icon: