diff --git a/app/admin/model/AdminSysMenu.php b/app/admin/model/AdminSysMenu.php index bc7bc1b..3bc5cf9 100644 --- a/app/admin/model/AdminSysMenu.php +++ b/app/admin/model/AdminSysMenu.php @@ -22,6 +22,33 @@ use think\Model; class AdminSysMenu extends Model { + // 设置表名 + protected $name = 'menu'; + + // 设置主键 + protected $pk = 'smid'; + + // 自动时间戳 + protected $autoWriteTimestamp = true; + protected $createTime = 'create_time'; + protected $updateTime = 'update_time'; + + // 字段类型转换 + protected $type = [ + 'smid' => 'integer', + 'parent_id' => 'integer', + 'type' => 'integer', + 'sort' => 'integer', + 'status' => 'integer', + 'create_time' => 'integer', + 'update_time' => 'integer' + ]; + + // 允许写入的字段 + protected $allowField = [ + 'parent_id', 'type', 'label', 'icon_class', 'sort', 'src', 'status' + ]; + /** * 获取菜单树形结构 * @return array @@ -30,28 +57,36 @@ class AdminSysMenu extends Model { // 获取所有启用的菜单 $menus = self::where('status', 1) - ->order('type', 'asc') ->order('sort', 'desc') + ->order('smid', 'asc') ->select() ->toArray(); - $menuTree = []; + return self::buildTree($menus); + } - // 先处理所有父菜单 + /** + * 构建树形结构 + * @param array $menus 菜单数组 + * @param int $parent_id 父级ID + * @return array + */ + public static function buildTree($menus, $parent_id = 0) + { + $tree = []; + foreach ($menus as $menu) { - if ($menu['parent_id'] == 0) { - $menuTree[$menu['smid']] = $menu; - $menuTree[$menu['smid']]['children'] = []; + if ($menu['parent_id'] == $parent_id) { + $children = self::buildTree($menus, $menu['smid']); + if ($children) { + $menu['children'] = $children; + } else { + $menu['children'] = []; + } + $tree[] = $menu; } } - - // 再处理子菜单 - foreach ($menus as $menu) { - if ($menu['parent_id'] != 0 && isset($menuTree[$menu['parent_id']])) { - $menuTree[$menu['parent_id']]['children'][] = $menu; - } - } - - return array_values($menuTree); + + return $tree; } } diff --git a/app/admin/model/AdminUser.php b/app/admin/model/AdminUser.php index dc1d6aa..3de8112 100644 --- a/app/admin/model/AdminUser.php +++ b/app/admin/model/AdminUser.php @@ -22,4 +22,28 @@ use think\Model; class AdminUser extends Model { + // 设置表名 + protected $name = 'admin_user'; + + // 设置主键 + protected $pk = 'uid'; + + // 自动时间戳 + protected $autoWriteTimestamp = true; + protected $createTime = 'create_time'; + protected $updateTime = 'update_time'; + + // 字段类型转换 + protected $type = [ + 'uid' => 'integer', + 'sex' => 'integer', + 'status' => 'integer', + 'create_time' => 'integer', + 'update_time' => 'integer' + ]; + + // 允许写入的字段 + protected $allowField = [ + 'account', 'password', 'name', 'avatar', 'phone', 'sex', 'qq', 'wechat', 'status' + ]; } \ No newline at end of file diff --git a/app/api/controller/AdminController.php b/app/api/controller/AdminController.php index 767da33..5f4c8a7 100644 --- a/app/api/controller/AdminController.php +++ b/app/api/controller/AdminController.php @@ -2,8 +2,8 @@ namespace app\api\controller; use app\api\controller\BaseController; -use app\admin\model\AdminUser; -use app\index\model\AdminUserGroup; +use app\api\model\AdminUser; +use app\api\model\AdminSysMenu; use think\facade\Log; use think\facade\Cache; @@ -12,9 +12,9 @@ use think\Response; class AdminController extends BaseController { /** - * 生成用户token + * 生成管理员token * - * @param int $userId 用户ID + * @param int $userId 管理员ID * @return string */ private function generateToken($userId) @@ -31,7 +31,27 @@ class AdminController extends BaseController } /** - * 用户登录接口 + * 从token中获取管理员ID + * + * @param string $token + * @return int|null + */ + private function getUserIdFromToken($token) + { + try { + $data = json_decode(base64_decode($token), true); + if ($data && isset($data['user_id'])) { + return $data['user_id']; + } + } catch (\Exception $e) { + return null; + } + + return null; + } + + /** + * 管理员登录接口 * * @return \think\Response */ @@ -58,10 +78,10 @@ class AdminController extends BaseController return json(['code' => 1, 'msg' => $validate->getError()]); } - // 查询用户 + // 查询管理员 $user = AdminUser::where('account', $data['account'])->find(); if (!$user) { - return json(['code' => 1, 'msg' => '用户不存在']); + return json(['code' => 1, 'msg' => '管理员不存在']); } // 验证密码 @@ -69,23 +89,23 @@ class AdminController extends BaseController return json(['code' => 1, 'msg' => '密码错误']); } - // 生成JWT token(这里使用简单的token,实际项目中建议使用JWT) - $token = $this->generateToken($user->id); + // 生成token + $token = $this->generateToken($user->uid); // 将token存储到缓存中,设置过期时间 - Cache::set('user_token_' . $user->id, $token, 7 * 24 * 3600); + Cache::set('admin_token_' . $user->uid, $token, 7 * 24 * 3600); // 记录登录日志 - Log::record('用户登录成功:' . $user->account, 'info'); + Log::record('管理员登录成功:' . $user->account, 'info'); - // 返回用户信息和token + // 返回管理员信息和token return json([ 'code' => 0, 'msg' => '登录成功', 'data' => [ 'token' => $token, 'user_info' => [ - 'id' => $user->id, + 'id' => $user->uid, 'account' => $user->account, 'name' => $user->name, 'avatar' => $user->avatar ?? '/static/images/avatar.png', @@ -99,8 +119,201 @@ class AdminController extends BaseController ]); } catch (\Exception $e) { - Log::record('登录失败:' . $e->getMessage(), 'error'); + Log::record('管理员登录失败:' . $e->getMessage(), 'error'); return json(['code' => 1, 'msg' => '登录失败:' . $e->getMessage()]); } } + + /** + * 退出登录接口 + * + * @return \think\Response + */ + public function logout() + { + try { + $token = $this->request->header('Authorization'); + if ($token) { + // 去掉Bearer前缀 + if (strpos($token, 'Bearer ') === 0) { + $token = substr($token, 7); + } + + // 从token中获取管理员ID + $userId = $this->getUserIdFromToken($token); + if ($userId) { + // 清除token缓存 + Cache::delete('admin_token_' . $userId); + } + } + + Log::record('管理员退出登录', 'info'); + // 增加前端刷新指示 + return json([ + 'code' => 0, + 'msg' => '退出成功', + 'refresh' => true // 前端可根据此字段判断是否需要刷新 + ]); + + } catch (\Exception $e) { + Log::record('退出登录失败:' . $e->getMessage(), 'error'); + return json(['code' => 1, 'msg' => '退出失败:' . $e->getMessage()]); + } + } + + /** + * 获取管理员信息接口 + * + * @return \think\Response + */ + public function info() + { + try { + $token = $this->request->header('Authorization'); + + if (!$token) { + return json(['code' => 1, 'msg' => '请先登录']); + } + + // 去掉Bearer前缀 + if (strpos($token, 'Bearer ') === 0) { + $token = substr($token, 7); + } + + $userId = $this->getUserIdFromToken($token); + + if (!$userId) { + return json(['code' => 1, 'msg' => 'token无效']); + } + + // 验证token是否在缓存中 + $cachedToken = Cache::get('admin_token_' . $userId); + + if (!$cachedToken || $cachedToken !== $token) { + return json(['code' => 1, 'msg' => 'token已过期']); + } + + // 获取管理员信息 + $user = AdminUser::where('uid', $userId)->find(); + if (!$user) { + return json(['code' => 1, 'msg' => '管理员不存在']); + } + + return json([ + 'code' => 0, + 'msg' => '获取成功', + 'data' => [ + 'id' => $user->uid, + 'account' => $user->account, + 'name' => $user->name, + 'avatar' => $user->avatar ?? '/static/images/avatar.png', + 'phone' => $user->phone ?? '', + 'sex' => $user->sex ?? 0, + 'qq' => $user->qq ?? '', + 'wechat' => $user->wechat ?? '', + 'create_time' => $user->create_time + ] + ]); + + } catch (\Exception $e) { + Log::record('获取管理员信息失败:' . $e->getMessage(), 'error'); + return json(['code' => 1, 'msg' => '获取管理员信息失败:' . $e->getMessage()]); + } + } + + /** + * 修改密码接口 + * + * @return \think\Response + */ + public function changePassword() + { + if (!$this->request->isPost()) { + return json(['code' => 1, 'msg' => '请求方法错误']); + } + + try { + $token = $this->request->header('Authorization'); + if (!$token) { + return json(['code' => 1, 'msg' => '请先登录']); + } + + // 去掉Bearer前缀 + if (strpos($token, 'Bearer ') === 0) { + $token = substr($token, 7); + } + + $userId = $this->getUserIdFromToken($token); + if (!$userId) { + return json(['code' => 1, 'msg' => 'token无效']); + } + + // 验证token是否在缓存中 + $cachedToken = Cache::get('admin_token_' . $userId); + if (!$cachedToken || $cachedToken !== $token) { + return json(['code' => 1, 'msg' => 'token已过期']); + } + + $data = $this->request->post(); + + // 验证数据 + $validate = validate([ + 'oldPassword' => 'require', + 'newPassword' => 'require|min:6' + ], [ + 'oldPassword.require' => '原密码不能为空', + 'newPassword.require' => '新密码不能为空', + 'newPassword.min' => '新密码长度不能少于6位' + ]); + + if (!$validate->check($data)) { + return json(['code' => 1, 'msg' => $validate->getError()]); + } + + // 获取管理员信息 + $user = AdminUser::where('uid', $userId)->find(); + if (!$user) { + return json(['code' => 1, 'msg' => '管理员不存在']); + } + + // 验证原密码 + if ($user->password !== md5($data['oldPassword'])) { + return json(['code' => 1, 'msg' => '原密码错误']); + } + + // 更新密码 + $user->password = md5($data['newPassword']); + $user->save(); + + Log::record('管理员修改密码成功:' . $user->account, 'info'); + + return json(['code' => 0, 'msg' => '密码修改成功']); + + } catch (\Exception $e) { + Log::record('修改密码失败:' . $e->getMessage(), 'error'); + return json(['code' => 1, 'msg' => '修改密码失败:' . $e->getMessage()]); + } + } + + /** + * 获取管理员菜单接口 + * + * @return \think\Response + */ + public function menus() + { + try { + // 取消token验证,直接获取菜单数据 + $menus = AdminSysMenu::getMenuTree(); + + return json([ + 'code' => 0, + 'msg' => '获取成功', + 'data' => $menus + ]); + } catch (\Exception $e) { + Log::record('获取菜单失败:' . $e->getMessage(), 'error'); + return json(['code' => 1, 'msg' => '获取菜单失败:' . $e->getMessage()]); + } + } } diff --git a/app/api/controller/UserController.php b/app/api/controller/UserController.php deleted file mode 100644 index bf55f3d..0000000 --- a/app/api/controller/UserController.php +++ /dev/null @@ -1,402 +0,0 @@ -request->isPost()) { - return json(['code' => 1, 'msg' => '请求方法错误']); - } - - $data = $this->request->post(); - - try { - // 验证数据 - $validate = validate([ - 'account' => 'require|email', - 'password' => 'require' - ], [ - 'account.require' => '账户不能为空', - 'account.email' => '邮箱格式不正确', - 'password.require' => '密码不能为空' - ]); - - if (!$validate->check($data)) { - return json(['code' => 1, 'msg' => $validate->getError()]); - } - - // 查询用户 - $user = Users::where('account', $data['account'])->find(); - if (!$user) { - return json(['code' => 1, 'msg' => '用户不存在']); - } - - // 验证密码 - if ($user->password !== md5($data['password'])) { - return json(['code' => 1, 'msg' => '密码错误']); - } - - // 生成JWT token(这里使用简单的token,实际项目中建议使用JWT) - $token = $this->generateToken($user->id); - - // 将token存储到缓存中,设置过期时间 - Cache::set('user_token_' . $user->id, $token, 7 * 24 * 3600); - - // 记录登录日志 - Log::record('用户登录成功:' . $user->account, 'info'); - - // 返回用户信息和token - return json([ - 'code' => 0, - 'msg' => '登录成功', - 'data' => [ - 'token' => $token, - 'user_info' => [ - 'id' => $user->id, - 'account' => $user->account, - 'name' => $user->name, - 'avatar' => $user->avatar ?? '/static/images/avatar.png', - 'phone' => $user->phone ?? '', - 'sex' => $user->sex ?? 0, - 'qq' => $user->qq ?? '', - 'wechat' => $user->wechat ?? '', - 'create_time' => $user->create_time - ] - ] - ]); - - } catch (\Exception $e) { - Log::record('登录失败:' . $e->getMessage(), 'error'); - return json(['code' => 1, 'msg' => '登录失败:' . $e->getMessage()]); - } - } - - /** - * 用户注册接口 - * - * @return \think\Response - */ - public function register() - { - if (!$this->request->isPost()) { - return json(['code' => 1, 'msg' => '请求方法错误']); - } - - $data = $this->request->post(); - - try { - // 验证数据 - $validate = validate([ - 'account' => 'require|email|unique:users', - 'code' => 'require|number|length:6', - 'password' => 'require|min:6|max:20', - 'repassword' => 'require|confirm:password' - ], [ - 'account.require' => '账户不能为空', - 'account.email' => '邮箱格式不正确', - 'account.unique' => '该邮箱已注册', - 'code.require' => '验证码不能为空', - 'code.number' => '验证码必须为数字', - 'code.length' => '验证码长度必须为6位', - 'password.require' => '密码不能为空', - 'password.min' => '密码长度不能小于6个字符', - 'password.max' => '密码长度不能超过20个字符', - 'repassword.require' => '确认密码不能为空', - 'repassword.confirm' => '两次输入的密码不一致' - ]); - - if (!$validate->check($data)) { - return json(['code' => 1, 'msg' => $validate->getError()]); - } - - // 验证邮箱验证码 - $emailCode = Cache::get('email_code_' . $data['account']); - if (!$emailCode || $emailCode != $data['code']) { - return json(['code' => 1, 'msg' => '验证码错误或已过期']); - } - - // 创建用户 - $user = new Users; - $user->account = $data['account']; - $user->password = md5($data['password']); - $user->name = $this->generateRandomName(); - $user->create_time = time(); - $user->save(); - - // 清除验证码缓存 - Cache::delete('email_code_' . $data['account']); - - return json(['code' => 0, 'msg' => '注册成功']); - - } catch (\Exception $e) { - return json(['code' => 1, 'msg' => '注册失败:' . $e->getMessage()]); - } - } - - /** - * 退出登录接口 - * - * @return \think\Response - */ - public function logout() - { - try { - $token = $this->request->header('Authorization'); - if ($token) { - // 从token中获取用户ID - $userId = $this->getUserIdFromToken($token); - if ($userId) { - // 清除token缓存 - Cache::delete('user_token_' . $userId); - } - } - - Log::record('用户退出登录', 'info'); - return json(['code' => 0, 'msg' => '退出成功']); - - } catch (\Exception $e) { - Log::record('退出登录失败:' . $e->getMessage(), 'error'); - return json(['code' => 1, 'msg' => '退出失败:' . $e->getMessage()]); - } - } - - /** - * 获取用户信息接口 - * - * @return \think\Response - */ - public function getUserInfo() - { - try { - $token = $this->request->header('Authorization'); - if (!$token) { - return json(['code' => 1, 'msg' => '请先登录']); - } - - $userId = $this->getUserIdFromToken($token); - if (!$userId) { - return json(['code' => 1, 'msg' => 'token无效']); - } - - // 验证token是否在缓存中 - $cachedToken = Cache::get('user_token_' . $userId); - if (!$cachedToken || $cachedToken !== $token) { - return json(['code' => 1, 'msg' => 'token已过期']); - } - - // 获取用户信息 - $user = Users::find($userId); - if (!$user) { - return json(['code' => 1, 'msg' => '用户不存在']); - } - - return json([ - 'code' => 0, - 'msg' => '获取成功', - 'data' => [ - 'id' => $user->id, - 'account' => $user->account, - 'name' => $user->name, - 'avatar' => $user->avatar ?? '/static/images/avatar.png', - 'phone' => $user->phone ?? '', - 'sex' => $user->sex ?? 0, - 'qq' => $user->qq ?? '', - 'wechat' => $user->wechat ?? '', - 'create_time' => $user->create_time - ] - ]); - - } catch (\Exception $e) { - return json(['code' => 1, 'msg' => '获取用户信息失败:' . $e->getMessage()]); - } - } - - /** - * 发送邮箱验证码接口 - * - * @return \think\Response - */ - public function sendEmailCode() - { - if (!$this->request->isPost()) { - return json(['code' => 1, 'msg' => '请求方法错误']); - } - - $email = $this->request->post('account'); - if (empty($email)) { - return json(['code' => 1, 'msg' => '邮箱不能为空']); - } - - // 验证邮箱格式 - if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { - return json(['code' => 1, 'msg' => '邮箱格式不正确']); - } - - // 检查邮箱是否已注册 - $exists = Users::where('account', $email)->find(); - if ($exists) { - return json(['code' => 1, 'msg' => '该邮箱已注册']); - } - - // 生成6位随机验证码 - $code = mt_rand(100000, 999999); - - // 这里应该调用邮件发送服务 - // 为了演示,我们直接返回成功 - // 实际项目中需要实现邮件发送逻辑 - - // 将验证码存入缓存,有效期5分钟 - Cache::set('email_code_' . $email, $code, 300); - - return json(['code' => 0, 'msg' => '验证码已发送']); - } - - /** - * 修改密码接口 - * - * @return \think\Response - */ - public function updatePassword() - { - if (!$this->request->isPost()) { - return json(['code' => 1, 'msg' => '请求方法错误']); - } - - try { - $token = $this->request->header('Authorization'); - if (!$token) { - return json(['code' => 1, 'msg' => '请先登录']); - } - - $userId = $this->getUserIdFromToken($token); - if (!$userId) { - return json(['code' => 1, 'msg' => 'token无效']); - } - - $data = $this->request->post(); - - // 验证数据 - $validate = validate([ - 'old_password' => 'require', - 'new_password' => 'require|min:6|max:20', - 'confirm_password' => 'require|confirm:new_password' - ], [ - 'old_password.require' => '旧密码不能为空', - 'new_password.require' => '新密码不能为空', - 'new_password.min' => '新密码长度不能小于6个字符', - 'new_password.max' => '新密码长度不能超过20个字符', - 'confirm_password.require' => '确认密码不能为空', - 'confirm_password.confirm' => '两次输入的密码不一致' - ]); - - if (!$validate->check($data)) { - return json(['code' => 1, 'msg' => $validate->getError()]); - } - - // 获取用户信息 - $user = Users::find($userId); - if (!$user) { - return json(['code' => 1, 'msg' => '用户不存在']); - } - - // 验证旧密码 - if ($user->password !== md5($data['old_password'])) { - return json(['code' => 1, 'msg' => '旧密码错误']); - } - - // 更新密码 - $user->password = md5($data['new_password']); - $user->update_time = time(); - - if ($user->save()) { - // 清除token,要求重新登录 - Cache::delete('user_token_' . $userId); - return json(['code' => 0, 'msg' => '密码修改成功,请重新登录']); - } else { - return json(['code' => 1, 'msg' => '密码修改失败']); - } - - } catch (\Exception $e) { - return json(['code' => 1, 'msg' => '密码修改失败:' . $e->getMessage()]); - } - } - - /** - * 生成简单的token - * - * @param int $userId - * @return string - */ - private function generateToken($userId) - { - $data = [ - 'user_id' => $userId, - 'timestamp' => time(), - 'random' => mt_rand(100000, 999999) - ]; - - return base64_encode(json_encode($data)); - } - - /** - * 从token中获取用户ID - * - * @param string $token - * @return int|null - */ - private function getUserIdFromToken($token) - { - try { - $data = json_decode(base64_decode($token), true); - if ($data && isset($data['user_id'])) { - return $data['user_id']; - } - } catch (\Exception $e) { - return null; - } - - return null; - } - - /** - * 生成随机用户名 - * - * @return string - */ - private function generateRandomName() - { - return '云朵_' . mt_rand(100000, 999999); - } -} \ No newline at end of file diff --git a/app/api/model/AdminConfig.php b/app/api/model/AdminConfig.php new file mode 100644 index 0000000..f643ffc --- /dev/null +++ b/app/api/model/AdminConfig.php @@ -0,0 +1,25 @@ + 'integer', + 'parent_id' => 'integer', + 'type' => 'integer', + 'sort' => 'integer', + 'status' => 'integer', + 'create_time' => 'integer', + 'update_time' => 'integer' + ]; + + // 允许写入的字段 + protected $allowField = [ + 'parent_id', 'type', 'label', 'icon_class', 'sort', 'src', 'status' + ]; + + /** + * 获取菜单树形结构 + * @return array + */ + public static function getMenuTree() + { + // 获取所有启用的菜单 + $menus = self::where('status', 1) + ->order('sort', 'desc') + ->order('smid', 'asc') + ->select() + ->toArray(); + + return self::buildTree($menus); + } + + /** + * 构建树形结构 + * @param array $menus 菜单数组 + * @param int $parent_id 父级ID + * @return array + */ + public static function buildTree($menus, $parent_id = 0) + { + $tree = []; + + foreach ($menus as $menu) { + if ($menu['parent_id'] == $parent_id) { + $children = self::buildTree($menus, $menu['smid']); + if ($children) { + $menu['children'] = $children; + } else { + $menu['children'] = []; + } + $tree[] = $menu; + } + } + + return $tree; + } +} diff --git a/app/api/model/AdminUser.php b/app/api/model/AdminUser.php new file mode 100644 index 0000000..951674c --- /dev/null +++ b/app/api/model/AdminUser.php @@ -0,0 +1,25 @@ + logs($data,$fileName); + } + } + $info = ['data'=>$data]; + $content = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL; + if(empty($fileName)){ + $filePath = $path . "/" . date("Ymd",time()).'.info.log'; + }else{ + $filePath = $path . "/" . $fileName . '.info.log'; + } + $time = "[".date("Y-m-d H:i:s",time())."]"; + $re = file_put_contents($filePath, $time." ".$content , FILE_APPEND); + if(!$re){ + $this -> logs($data,$fileName); + }else{ + return true; + } + } +} \ No newline at end of file diff --git a/app/api/model/ContentPush/ContentPush.php b/app/api/model/ContentPush/ContentPush.php new file mode 100644 index 0000000..04b5fc5 --- /dev/null +++ b/app/api/model/ContentPush/ContentPush.php @@ -0,0 +1,34 @@ +order('config_sort DESC')->select()->toArray(); + if(empty($aList)){ + return []; + }else{ + $return = []; + foreach($aList as $k=>$v){ + $return[$v['config_name']] = $v['config_value']; + } + } + return $return; + } + + /** + * 多条数据更新 + */ + public function updateAll($data){ + $lists = static::order('config_sort DESC,config_id')->select()->toArray(); + if(empty($lists)){ + return false; + }else{ + foreach($lists as &$lists_v){ + $lists_v['config_value'] = $data[$lists_v['config_name']]; + } + $save = static::saveAll($lists); + if(empty($save)){ + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/app/api/model/ZIconfont.php b/app/api/model/ZIconfont.php new file mode 100644 index 0000000..033b7af --- /dev/null +++ b/app/api/model/ZIconfont.php @@ -0,0 +1,25 @@ + { // 设置页面标题 document.title = ENV_CONFIG.APP_TITLE - // 初始化用户状态 - userStore.initUserState() // 初始化主题色 colorStore.primaryChange(colorStore.primary) }) diff --git a/frontend/src/api/menu.ts b/frontend/src/api/menu.ts index 8b554a8..56c55e1 100644 --- a/frontend/src/api/menu.ts +++ b/frontend/src/api/menu.ts @@ -4,48 +4,16 @@ import api from './user' export interface MenuItem { smid: number label: string - icon_class: string type: number src: string - sort: number - status: number parent_id: number + icon_class: string + sort: string + status: number children?: MenuItem[] } -export interface RoleItem { - group_id: number - group_name: string - status: number - create_time: string -} - -// 获取所有菜单列表 -export const getMenuList = () => { - return api.get('/menu/list') -} - -// 根据用户角色获取菜单 +// 获取管理员菜单接口 export const getUserMenus = () => { - return api.get('/menu/userMenus') -} - -// 获取角色列表 -export const getRoleList = () => { - return api.get('/menu/roles') -} - -// 获取菜单详情 -export const getMenuDetail = (id: number) => { - return api.get('/menu/detail', { params: { id } }) -} - -// 临时菜单接口(使用用户控制器) -export const getTempUserMenus = () => { - return api.get('/user/menus') -} - -// 或者直接使用完整路径(如果baseURL有问题) -export const getTempUserMenusDirect = () => { - return api.get('https://www.yunzer.cn/api/user/menus') + return api.get('/admin/menus') } \ No newline at end of file diff --git a/frontend/src/api/user.ts b/frontend/src/api/user.ts index c2339c3..68e3d38 100644 --- a/frontend/src/api/user.ts +++ b/frontend/src/api/user.ts @@ -54,19 +54,19 @@ export const login = (data: { account: string; password: string }) => { return api.post('/admin/login', data) } -// 获取用户信息接口 +// 获取管理员信息接口 export const getUserInfo = () => { - return api.get('/user/info') + return api.get('/admin/info') } -// 用户登出接口 +// 管理员登出接口 export const logout = () => { - return api.post('/user/logout') + return api.post('/admin/logout') } // 修改密码接口 export const changePassword = (data: { oldPassword: string; newPassword: string }) => { - return api.post('/user/change-password', data) + return api.post('/admin/change-password', data) } export default api \ No newline at end of file diff --git a/frontend/src/config/env.ts b/frontend/src/config/env.ts index 7d94785..048de9b 100644 --- a/frontend/src/config/env.ts +++ b/frontend/src/config/env.ts @@ -9,8 +9,8 @@ const ENV_CONFIG = { APP_TITLE: import.meta.env.VITE_APP_TITLE || '项目管理系统', // 本地存储key - TOKEN_KEY: 'token', - USER_INFO_KEY: 'userInfo', + TOKEN_KEY: 'admin_token', + USER_INFO_KEY: 'admin_user_info', // 是否为开发环境 IS_DEV: import.meta.env.DEV, diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 518f573..e81f9a2 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -8,7 +8,6 @@ import 'element-plus/theme-chalk/dark/css-vars.css' import { createPinia } from 'pinia' import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' import * as ElementPlusIconsVue from '@element-plus/icons-vue' -import useUserStore from '@/store/modules/user' const app = createApp(App) @@ -21,8 +20,4 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.use(pinia).use(ElementPlus).use(router) -// 初始化用户状态 -const userStore = useUserStore() -userStore.initUserState() - app.mount('#app') \ No newline at end of file diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 38b2785..2f85937 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -1,8 +1,8 @@ import { RouteRecordRaw, createRouter, createWebHashHistory } from 'vue-router' -import * as MenuUtil from '@/util/menu' import useFastnavStore from '@/store/fastnav' import useUserStore from '@/store/modules/user' +import useMenuStore from '@/store/modules/menu' // 只保留非菜单相关的静态路由 const routes: RouteRecordRaw[] = [ @@ -15,7 +15,16 @@ const routes: RouteRecordRaw[] = [ path: '/', name: 'Main', component: () => import('@/views/main/index.vue'), - children: [], // 预定义空的子路由数组 + children: [ + { + path: '', + name: 'Home', + component: () => import('@/views/home/index.vue'), + meta: { + title: '工作台' + } + } + ], }, { path: '/:catchAll(.*)', @@ -31,8 +40,93 @@ const router = createRouter({ let isAddRoute = false let isUserStateInitialized = false +// 使用相对路径,vite 的 import.meta.glob 会返回 /src/views/... 格式的key +const views = import.meta.glob('@/views/**/*.vue') + +// 创建一个映射,将 @/views/... 格式的路径映射到实际的组件 +const viewsMap = new Map() +for (const [key, component] of Object.entries(views)) { + // 将 /src/views/... 转换为 @/views/... 格式作为key + const mappedKey = key.replace('/src/views/', '@/views/') + viewsMap.set(mappedKey, component) + // 同时保留原始key + viewsMap.set(key, component) +} + +// 智能查找组件文件 +function findComponent(componentName: string, routePath: string): any { + // 处理 routePath,去除开头的斜杠 + let cleanPath = routePath.startsWith('/') ? routePath.slice(1) : routePath + + // 只保留第一个斜杠后的路径(去掉多余的斜杠) + cleanPath = cleanPath.replace(/\/+/g, '/') + + // 可能的路径组合 + const possiblePaths = [ + // 1. 直接匹配:/src/views/路径.vue + `/src/views/${cleanPath}.vue`, + // 2. index.vue 结尾:/src/views/路径/index.vue + `/src/views/${cleanPath}/index.vue`, + // 3. 小写路径匹配 + `/src/views/${cleanPath.toLowerCase()}.vue`, + `/src/views/${cleanPath.toLowerCase()}/index.vue`, + ].filter(Boolean) + + // 精确匹配 - 使用映射查找 + for (const path of possiblePaths) { + if (viewsMap.get(path)) { + return viewsMap.get(path) + } + } + + // 模糊匹配 - 改进逻辑 + const searchTerms = [componentName.toLowerCase()] + + // 优先匹配更精确的路径 + for (const [path, component] of Object.entries(views)) { + // 检查路径是否包含组件名(不区分大小写) + if (path.toLowerCase().includes(componentName.toLowerCase())) { + return component + } + } + + return null +} + +// 从菜单数据生成路由 +function generateRoutesFromMenus(menus: any[]): RouteRecordRaw[] { + const routes: RouteRecordRaw[] = [] + + for (const menu of menus) { + // 只处理有src字段的菜单项(type为1表示页面) + if (menu.type === 1 && menu.src) { + const route: RouteRecordRaw = { + path: `/${menu.src}`, + name: menu.label, + component: findComponent(menu.label, menu.src), + meta: { + title: menu.label + } + } + + if (route.component) { + routes.push(route) + } + } + + // 递归处理子菜单 + if (menu.children && menu.children.length > 0) { + const childRoutes = generateRoutesFromMenus(menu.children) + routes.push(...childRoutes) + } + } + + return routes +} + router.beforeEach(async (to, _from, next) => { const userStore = useUserStore() + const menuStore = useMenuStore() // 等待用户状态初始化完成 if (!isUserStateInitialized) { @@ -59,9 +153,9 @@ router.beforeEach(async (to, _from, next) => { // 已经添加过动态路由 if (isAddRoute) { - if (to.meta.desc) { + if (to.meta.title) { const fastnavStore = useFastnavStore() - fastnavStore.addData(to.meta.desc as string, to.path) + fastnavStore.addData(to.meta.title as string, to.path) } next() return @@ -70,16 +164,24 @@ router.beforeEach(async (to, _from, next) => { // 添加主页子路由 isAddRoute = true + // 获取菜单数据 + await menuStore.fetchUserMenus() + // 找到主路由 const mainRoute = router.options.routes.find((v) => v.path == '/')! - // 根据用户类型生成菜单和路由(默认使用管理员类型) - const [_genMenus, genRoutes] = MenuUtil.gen(1) + // 根据菜单数据生成路由 + const genRoutes = generateRoutesFromMenus(menuStore.getMenus) // 设置主路由的重定向和子路由 if (genRoutes.length > 0) { - mainRoute.redirect = genRoutes[0].path - mainRoute.children = genRoutes + // 保留默认的Home页面路由,添加动态路由 + const existingChildren = mainRoute.children || [] + const homeRoute = existingChildren.find(route => route.name === 'Home') + + mainRoute.children = homeRoute ? [homeRoute, ...genRoutes] : genRoutes + // 始终重定向到工作台 + mainRoute.redirect = '/' // 添加主路由(包含子路由) router.addRoute(mainRoute) @@ -87,8 +189,8 @@ router.beforeEach(async (to, _from, next) => { // 重新导航到目标路由 next({ ...to, replace: true }) } else { - // 如果没有生成路由,至少重定向到登录页 - next({ path: '/login' }) + // 如果没有生成路由,重定向到工作台 + next({ path: '/' }) } }) diff --git a/frontend/src/store/modules/menu.ts b/frontend/src/store/modules/menu.ts index fbf89dc..341cddf 100644 --- a/frontend/src/store/modules/menu.ts +++ b/frontend/src/store/modules/menu.ts @@ -1,5 +1,5 @@ import { defineStore } from 'pinia' -import { getUserMenus, getMenuList, getTempUserMenus, type MenuItem } from '@/api/menu' +import { getUserMenus, type MenuItem } from '@/api/menu' import ENV_CONFIG from '@/config/env' interface MenuState { @@ -8,12 +8,6 @@ interface MenuState { error: string | null } -interface ApiResponse { - code: number - message: string - data: T -} - const useMenuStore = defineStore('menu', { state: (): MenuState => ({ menus: [], @@ -33,43 +27,22 @@ const useMenuStore = defineStore('menu', { }, actions: { - // 获取用户菜单(使用临时接口) + // 获取管理员菜单 async fetchUserMenus() { this.loading = true this.error = null try { - const response = await getTempUserMenus() as unknown as ApiResponse + const response = await getUserMenus() as unknown as MenuItem[] - if (response.code === 200 && response.data) { - this.menus = response.data + if (response && Array.isArray(response)) { + this.menus = response } else { - throw new Error(response.message || '获取菜单失败') + throw new Error('获取菜单失败') } } catch (error: any) { this.error = error.message || '获取菜单失败' - console.error('获取用户菜单失败:', error) - } finally { - this.loading = false - } - }, - - // 获取所有菜单(管理员用) - async fetchAllMenus() { - this.loading = true - this.error = null - - try { - const response = await getMenuList() as unknown as ApiResponse - - if (response.code === 200 && response.data) { - this.menus = response.data - } else { - throw new Error(response.message || '获取菜单失败') - } - } catch (error: any) { - this.error = error.message || '获取菜单失败' - console.error('获取所有菜单失败:', error) + console.error('获取管理员菜单失败:', error) } finally { this.loading = false } diff --git a/frontend/src/store/modules/user.ts b/frontend/src/store/modules/user.ts index 458db2a..9414796 100644 --- a/frontend/src/store/modules/user.ts +++ b/frontend/src/store/modules/user.ts @@ -103,6 +103,7 @@ const useUserStore = defineStore('user', { try { // 验证token是否有效 const response = await getUserInfoApi() as unknown as UserInfo + if (response && response.id) { this.token = token this.userInfo = response @@ -113,10 +114,21 @@ const useUserStore = defineStore('user', { } } catch (error: any) { console.error('获取用户信息失败:', error) - // 网络错误时,不清除本地状态,保持用户登录状态 + // 如果有本地token和用户信息,先保持登录状态 // 只有在明确知道token无效时才清除 if (error.response?.status === 401) { this.clearUserState() + } else { + // 网络错误或其他错误,使用本地存储的数据 + try { + const userInfo = JSON.parse(userInfoStr) + this.token = token + this.userInfo = userInfo + this.isLogin = true + } catch (parseError) { + // 本地数据解析失败,清除状态 + this.clearUserState() + } } } } diff --git a/frontend/src/views/articles/articlelist.vue b/frontend/src/views/articles/articlelist.vue new file mode 100644 index 0000000..c3142cc --- /dev/null +++ b/frontend/src/views/articles/articlelist.vue @@ -0,0 +1,234 @@ + + + + + diff --git a/frontend/src/views/main/index.vue b/frontend/src/views/main/index.vue index 8960c39..7198bb6 100644 --- a/frontend/src/views/main/index.vue +++ b/frontend/src/views/main/index.vue @@ -59,29 +59,29 @@