修复token验证和左侧表单问题
This commit is contained in:
parent
1ceaeb9214
commit
2d44b8764d
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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'
|
||||
];
|
||||
}
|
||||
@ -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()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,402 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\controller\BaseController;
|
||||
use app\index\model\User\Users as Users;
|
||||
use app\index\model\User\UsersGroup as UsersGroup;
|
||||
|
||||
|
||||
use think\facade\Log;
|
||||
use think\facade\Cache;
|
||||
use think\Response;
|
||||
|
||||
class UserController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 用户登录接口
|
||||
*
|
||||
* @return \think\Response
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
if (!$this->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);
|
||||
}
|
||||
}
|
||||
25
app/api/model/AdminConfig.php
Normal file
25
app/api/model/AdminConfig.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class AdminConfig extends Model
|
||||
{
|
||||
}
|
||||
92
app/api/model/AdminSysMenu.php
Normal file
92
app/api/model/AdminSysMenu.php
Normal file
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class AdminSysMenu extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'admin_sys_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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
25
app/api/model/AdminUser.php
Normal file
25
app/api/model/AdminUser.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class AdminUser extends Model
|
||||
{
|
||||
}
|
||||
25
app/api/model/AdminUserGroup.php
Normal file
25
app/api/model/AdminUserGroup.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class AdminUserGroup extends Model
|
||||
{
|
||||
}
|
||||
25
app/api/model/ApiKey.php
Normal file
25
app/api/model/ApiKey.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class ApiKey extends Model
|
||||
{
|
||||
}
|
||||
25
app/api/model/Article/Articles.php
Normal file
25
app/api/model/Article/Articles.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\model\Article;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class Articles extends Model
|
||||
{
|
||||
}
|
||||
25
app/api/model/Article/ArticlesCategory.php
Normal file
25
app/api/model/Article/ArticlesCategory.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\model\Article;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class ArticlesCategory extends Model
|
||||
{
|
||||
}
|
||||
25
app/api/model/Banner.php
Normal file
25
app/api/model/Banner.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class Banner extends Model
|
||||
{
|
||||
}
|
||||
54
app/api/model/Base.php
Normal file
54
app/api/model/Base.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
/**
|
||||
* 后台管理系统
|
||||
*/
|
||||
namespace app\api\model;
|
||||
use think\Model;
|
||||
use think\facade\App;
|
||||
|
||||
class Base extends Model{
|
||||
public function logs($data=null,$fileName=''){
|
||||
if(is_null($data) || is_null($fileName)){
|
||||
return false;
|
||||
}
|
||||
//获取Runtime路径
|
||||
$path = App::getRuntimePath() . 'logs' . DIRECTORY_SEPARATOR;
|
||||
if(!is_dir($path)){
|
||||
$mkdir_re = mkdir($path,0777,TRUE);
|
||||
if(!$mkdir_re){
|
||||
$this -> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
app/api/model/ContentPush/ContentPush.php
Normal file
34
app/api/model/ContentPush/ContentPush.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\model\ContentPush;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class ContentPush extends Model
|
||||
{
|
||||
protected $name = 'content_push';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'create_time';
|
||||
protected $updateTime = 'update_time';
|
||||
protected $deleteTime = 'delete_time';
|
||||
}
|
||||
34
app/api/model/ContentPush/ContentPushSetting.php
Normal file
34
app/api/model/ContentPush/ContentPushSetting.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\model\ContentPush;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class ContentPushSetting extends Model
|
||||
{
|
||||
protected $name = 'content_push_setting';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'create_time';
|
||||
protected $updateTime = 'update_time';
|
||||
protected $deleteTime = 'delete_time';
|
||||
}
|
||||
25
app/api/model/DailyStats.php
Normal file
25
app/api/model/DailyStats.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class DailyStats extends Model
|
||||
{
|
||||
}
|
||||
25
app/api/model/Log/LogsLogin.php
Normal file
25
app/api/model/Log/LogsLogin.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\model\Log;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class LogsLogin extends Model
|
||||
{
|
||||
}
|
||||
25
app/api/model/Log/LogsOperation.php
Normal file
25
app/api/model/Log/LogsOperation.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\model\Log;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class LogsOperation extends Model
|
||||
{
|
||||
}
|
||||
25
app/api/model/MailConfig.php
Normal file
25
app/api/model/MailConfig.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class MailConfig extends Model
|
||||
{
|
||||
}
|
||||
30
app/api/model/Resource/Resource.php
Normal file
30
app/api/model/Resource/Resource.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\model\Resource;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class Resource extends Model
|
||||
{
|
||||
// 设置当前模型对应的数据表名称(不含前缀)
|
||||
protected $name = 'resources';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
}
|
||||
30
app/api/model/Resource/ResourceCategory.php
Normal file
30
app/api/model/Resource/ResourceCategory.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\model\Resource;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class ResourceCategory extends Model
|
||||
{
|
||||
// 设置当前模型对应的数据表名称(不含前缀)
|
||||
protected $name = 'resources_category';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
}
|
||||
25
app/api/model/User/Users.php
Normal file
25
app/api/model/User/Users.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\model\User;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class Users extends Model
|
||||
{
|
||||
}
|
||||
25
app/api/model/User/UsersGroup.php
Normal file
25
app/api/model/User/UsersGroup.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\model\User;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class UsersGroup extends Model
|
||||
{
|
||||
}
|
||||
68
app/api/model/YzAdminConfig.php
Normal file
68
app/api/model/YzAdminConfig.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
/**
|
||||
* 配置表
|
||||
*/
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class YzAdminConfig extends Model
|
||||
{
|
||||
// 设置当前模型对应的数据表名称(不含前缀)
|
||||
protected $name = 'admin_config';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'config_id';
|
||||
|
||||
/**
|
||||
* 列出全部配置,key对应value
|
||||
*/
|
||||
public function getAll(){
|
||||
$aList = static::where('config_status',1)->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;
|
||||
}
|
||||
}
|
||||
25
app/api/model/ZIconfont.php
Normal file
25
app/api/model/ZIconfont.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* 商业使用授权协议
|
||||
*
|
||||
* Copyright (c) 2025 [云泽网]. 保留所有权利.
|
||||
*
|
||||
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
|
||||
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
|
||||
*
|
||||
* 授权购买请联系: 357099073@qq.com
|
||||
* 官方网站: https://www.yunzer.cn
|
||||
*
|
||||
* 评估用户须知:
|
||||
* 1. 禁止移除版权声明
|
||||
* 2. 禁止用于生产环境
|
||||
* 3. 禁止转售或分发
|
||||
*/
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class ZIconfont extends Model
|
||||
{
|
||||
}
|
||||
7
frontend/components.d.ts
vendored
7
frontend/components.d.ts
vendored
@ -14,6 +14,7 @@ declare module 'vue' {
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
@ -26,8 +27,11 @@ declare module 'vue' {
|
||||
ElMain: typeof import('element-plus/es')['ElMain']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElStatistic: typeof import('element-plus/es')['ElStatistic']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
@ -37,4 +41,7 @@ declare module 'vue' {
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
}
|
||||
export interface GlobalDirectives {
|
||||
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,19 +2,15 @@
|
||||
import { useDark } from '@vueuse/core'
|
||||
import { onMounted } from 'vue'
|
||||
import useColorStore from '@/store/color'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import ENV_CONFIG from '@/config/env'
|
||||
|
||||
useDark()
|
||||
|
||||
const colorStore = useColorStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
onMounted(() => {
|
||||
// 设置页面标题
|
||||
document.title = ENV_CONFIG.APP_TITLE
|
||||
// 初始化用户状态
|
||||
userStore.initUserState()
|
||||
// 初始化主题色
|
||||
colorStore.primaryChange(colorStore.primary)
|
||||
})
|
||||
|
||||
@ -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')
|
||||
}
|
||||
@ -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
|
||||
@ -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,
|
||||
|
||||
@ -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')
|
||||
@ -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: '/' })
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -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<T> {
|
||||
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<MenuItem[]>
|
||||
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<MenuItem[]>
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
234
frontend/src/views/articles/articlelist.vue
Normal file
234
frontend/src/views/articles/articlelist.vue
Normal file
@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<div class="article-list">
|
||||
<el-card>
|
||||
<div class="header">
|
||||
<el-input
|
||||
v-model="search"
|
||||
placeholder="搜索文章标题"
|
||||
clearable
|
||||
class="search-input"
|
||||
@keyup.enter="fetchArticles"
|
||||
/>
|
||||
<el-select
|
||||
v-model="selectedCategory"
|
||||
placeholder="筛选分类"
|
||||
clearable
|
||||
class="category-select"
|
||||
style="width: 150px"
|
||||
@change="fetchArticles"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in categoryOptions"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="fetchArticles">搜索</el-button>
|
||||
<el-button type="success" @click="handlePublish">发布文章</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
:data="articles"
|
||||
style="width: 100%; margin-top: 20px"
|
||||
v-loading="loading"
|
||||
border
|
||||
>
|
||||
<el-table-column prop="id" label="ID" width="80" align="center" />
|
||||
<el-table-column prop="title" label="标题" min-width="200" />
|
||||
<el-table-column prop="author" label="作者" width="120" />
|
||||
<el-table-column prop="category" label="分类" width="120" />
|
||||
<el-table-column prop="create_time" label="创建时间" width="180" />
|
||||
<el-table-column label="操作" width="220">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
||||
<el-button size="small" type="primary" @click="handlePublishSingle(scope.row)">发布</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination">
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next, jumper"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
:current-page="page"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
<!-- 发布文章对话框 -->
|
||||
<el-dialog v-model="publishDialogVisible" title="发布文章" width="500px">
|
||||
<el-form :model="publishForm" label-width="80px">
|
||||
<el-form-item label="标题">
|
||||
<el-input v-model="publishForm.title" />
|
||||
</el-form-item>
|
||||
<el-form-item label="作者">
|
||||
<el-input v-model="publishForm.author" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类">
|
||||
<el-select v-model="publishForm.category" placeholder="请选择分类">
|
||||
<el-option
|
||||
v-for="item in categoryOptions"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="内容">
|
||||
<el-input
|
||||
v-model="publishForm.content"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入文章内容"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="publishDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitPublish">发布</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
// 这里假设有文章API
|
||||
// import { getArticleList } from '@/api/article'
|
||||
|
||||
const articles = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
const search = ref('')
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
const selectedCategory = ref('')
|
||||
const categoryOptions = ref<string[]>([
|
||||
'前端', '后端', '数据库', '架构', '安全', '运维', '测试'
|
||||
])
|
||||
|
||||
// 发布文章相关
|
||||
const publishDialogVisible = ref(false)
|
||||
const publishForm = ref({
|
||||
title: '',
|
||||
author: '',
|
||||
category: '',
|
||||
content: ''
|
||||
})
|
||||
|
||||
function fetchArticles() {
|
||||
loading.value = true
|
||||
// 这里用模拟数据,实际请替换为API请求
|
||||
setTimeout(() => {
|
||||
// 假数据
|
||||
const all = [
|
||||
{ id: 1, title: 'Vue3 入门', author: '张三', category: '前端', create_time: '2024-06-01 10:00' },
|
||||
{ id: 2, title: 'TypeScript 实践', author: '李四', category: '前端', create_time: '2024-06-02 11:00' },
|
||||
{ id: 3, title: 'PHP 高级技巧', author: '王五', category: '后端', create_time: '2024-06-03 12:00' },
|
||||
{ id: 4, title: '数据库优化', author: '赵六', category: '数据库', create_time: '2024-06-04 13:00' },
|
||||
{ id: 5, title: '云原生架构', author: '钱七', category: '架构', create_time: '2024-06-05 14:00' },
|
||||
{ id: 6, title: '安全最佳实践', author: '孙八', category: '安全', create_time: '2024-06-06 15:00' },
|
||||
{ id: 7, title: '性能调优', author: '周九', category: '运维', create_time: '2024-06-07 16:00' },
|
||||
{ id: 8, title: '微服务设计', author: '吴十', category: '架构', create_time: '2024-06-08 17:00' },
|
||||
{ id: 9, title: '前端工程化', author: '郑十一', category: '前端', create_time: '2024-06-09 18:00' },
|
||||
{ id: 10, title: '测试驱动开发', author: '冯十二', category: '测试', create_time: '2024-06-10 19:00' },
|
||||
{ id: 11, title: '持续集成', author: '褚十三', category: '运维', create_time: '2024-06-11 20:00' }
|
||||
]
|
||||
let filtered = all
|
||||
if (search.value) {
|
||||
filtered = filtered.filter(a => a.title.includes(search.value))
|
||||
}
|
||||
if (selectedCategory.value) {
|
||||
filtered = filtered.filter(a => a.category === selectedCategory.value)
|
||||
}
|
||||
total.value = filtered.length
|
||||
const start = (page.value - 1) * pageSize.value
|
||||
articles.value = filtered.slice(start, start + pageSize.value)
|
||||
loading.value = false
|
||||
}, 500)
|
||||
}
|
||||
|
||||
function handleEdit(row: any) {
|
||||
// 编辑文章逻辑
|
||||
alert('编辑文章: ' + row.title)
|
||||
}
|
||||
|
||||
function handleDelete(row: any) {
|
||||
// 删除文章逻辑
|
||||
if (confirm('确定要删除文章 "' + row.title + '" 吗?')) {
|
||||
// 实际应调用API
|
||||
articles.value = articles.value.filter(a => a.id !== row.id)
|
||||
total.value--
|
||||
}
|
||||
}
|
||||
|
||||
function handlePageChange(val: number) {
|
||||
page.value = val
|
||||
fetchArticles()
|
||||
}
|
||||
|
||||
// 点击“发布文章”按钮
|
||||
function handlePublish() {
|
||||
publishForm.value = {
|
||||
title: '',
|
||||
author: '',
|
||||
category: '',
|
||||
content: ''
|
||||
}
|
||||
publishDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 单条发布(模拟,实际应调用API)
|
||||
function handlePublishSingle(row: any) {
|
||||
alert('发布文章: ' + row.title)
|
||||
}
|
||||
|
||||
// 提交发布
|
||||
function submitPublish() {
|
||||
if (!publishForm.value.title || !publishForm.value.author || !publishForm.value.category) {
|
||||
alert('请填写完整信息')
|
||||
return
|
||||
}
|
||||
// 实际应调用API,这里直接添加到表格
|
||||
const newId = Math.max(...articles.value.map(a => a.id), 0) + 1
|
||||
articles.value.unshift({
|
||||
id: newId,
|
||||
title: publishForm.value.title,
|
||||
author: publishForm.value.author,
|
||||
category: publishForm.value.category,
|
||||
create_time: new Date().toISOString().slice(0, 16).replace('T', ' ')
|
||||
})
|
||||
total.value++
|
||||
publishDialogVisible.value = false
|
||||
// 可选:重置分页到第一页
|
||||
page.value = 1
|
||||
fetchArticles()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchArticles()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.article-list {
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.search-input {
|
||||
width: 300px;
|
||||
}
|
||||
.category-select {
|
||||
min-width: 120px;
|
||||
}
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
@ -59,29 +59,29 @@
|
||||
<el-aside :width="menuStore.width">
|
||||
<el-menu
|
||||
router
|
||||
:default-active="route.path"
|
||||
:default-active="route.path === '/' ? '/' : route.path"
|
||||
:collapse="menuStore.collapse"
|
||||
style="height: 100%;"
|
||||
>
|
||||
<template v-for="menu in menus" :key="menu.smid">
|
||||
<el-sub-menu
|
||||
v-if="menu.children && menu.children.length > 0"
|
||||
:index="menu.smid.toString()"
|
||||
:index="menu.src || menu.smid.toString()"
|
||||
>
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<component :is="menu.icon_class"></component>
|
||||
<Setting />
|
||||
</el-icon>
|
||||
<span>{{ menu.label }}</span>
|
||||
</template>
|
||||
<el-menu-item
|
||||
v-for="child in menu.children"
|
||||
:key="child.smid"
|
||||
:index="child.smid.toString()"
|
||||
:index="child.src || child.smid.toString()"
|
||||
@click="handleMenuClick(child)"
|
||||
>
|
||||
<el-icon>
|
||||
<component :is="child.icon_class"></component>
|
||||
<Setting />
|
||||
</el-icon>
|
||||
<span>{{ child.label }}</span>
|
||||
</el-menu-item>
|
||||
@ -90,11 +90,12 @@
|
||||
<el-menu-item
|
||||
v-else
|
||||
:key="menu.smid"
|
||||
:index="menu.smid.toString()"
|
||||
:index="menu.smid === 0 ? '/' : (menu.src || menu.smid.toString())"
|
||||
@click="handleMenuClick(menu)"
|
||||
>
|
||||
<el-icon>
|
||||
<component :is="menu.icon_class"></component>
|
||||
<Monitor v-if="menu.smid === 0" />
|
||||
<Setting v-else />
|
||||
</el-icon>
|
||||
<template #title>{{ menu.label }}</template>
|
||||
</el-menu-item>
|
||||
@ -165,9 +166,19 @@ import useMenuStore from '@/store/modules/menu'
|
||||
import useFastnavStore from '@/store/fastnav'
|
||||
import useMenuCollapseStore from '@/store/menu'
|
||||
import useColorStore from '@/store/color'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import { useRoute, useRouter, RouterView } from 'vue-router'
|
||||
import { useFullscreen, useDark } from '@vueuse/core'
|
||||
import {
|
||||
Setting,
|
||||
User,
|
||||
Document,
|
||||
Folder,
|
||||
Monitor,
|
||||
Grid,
|
||||
Menu,
|
||||
House
|
||||
} from '@element-plus/icons-vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@ -183,6 +194,25 @@ const colorStore = useColorStore()
|
||||
const menus = ref<any[]>([])
|
||||
const collapse = ref(true)
|
||||
|
||||
// 图标映射
|
||||
const iconMap: Record<string, any> = {
|
||||
'layui-icon-senior': Setting,
|
||||
'layui-icon-user': User,
|
||||
'layui-icon-file': Document,
|
||||
'layui-icon-folder': Folder,
|
||||
'layui-icon-monitor': Monitor,
|
||||
'layui-icon-grid': Grid,
|
||||
'layui-icon-menu': Menu,
|
||||
'layui-icon-home': House,
|
||||
// 默认图标
|
||||
'': Setting
|
||||
}
|
||||
|
||||
// 获取图标组件
|
||||
const getIconComponent = (iconClass: string) => {
|
||||
return iconMap[iconClass] || Setting
|
||||
}
|
||||
|
||||
// 新增:控制右键菜单各项的禁用状态
|
||||
const tagMenuReloadDisabled = ref(false)
|
||||
const tagMenuCloseDisabled = ref(false)
|
||||
@ -191,9 +221,26 @@ const tagMenuLeftDisabled = ref(false)
|
||||
const tagMenuRightDisabled = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
// 获取动态菜单数据
|
||||
// 从store获取菜单数据,如果还没有则等待
|
||||
if (menuDataStore.getMenus.length === 0) {
|
||||
await menuDataStore.fetchUserMenus()
|
||||
menus.value = menuDataStore.getMenus
|
||||
}
|
||||
|
||||
// 添加固定的工作台菜单
|
||||
const workbenchMenu = {
|
||||
smid: 0,
|
||||
label: '工作台',
|
||||
type: 1,
|
||||
src: '',
|
||||
parent_id: 0,
|
||||
icon_class: '',
|
||||
sort: '999999',
|
||||
status: 1,
|
||||
children: []
|
||||
}
|
||||
|
||||
// 将工作台菜单放在最前面
|
||||
menus.value = [workbenchMenu, ...menuDataStore.getMenus]
|
||||
})
|
||||
|
||||
// 折叠侧边栏
|
||||
@ -263,12 +310,13 @@ const handleTagCommand = (command: string) => {
|
||||
|
||||
// 菜单点击处理
|
||||
const handleMenuClick = (menu: any) => {
|
||||
if (menu.type === 1 && menu.src) {
|
||||
// 内部跳转
|
||||
const title = menu.label
|
||||
const path = '/' + menu.src.replace('/', '_')
|
||||
// 这里需要根据实际情况处理页面跳转
|
||||
console.log('内部跳转:', title, path)
|
||||
if (menu.smid === 0) {
|
||||
// 工作台菜单
|
||||
router.push('/')
|
||||
} else if (menu.type === 1 && menu.src) {
|
||||
// 内部跳转 - 使用src作为路径
|
||||
const path = '/' + menu.src
|
||||
router.push(path)
|
||||
} else if (menu.type === 2 && menu.src) {
|
||||
// 外部链接
|
||||
window.open(menu.src, '_blank')
|
||||
|
||||
@ -53,9 +53,9 @@ export default defineConfig({
|
||||
},
|
||||
|
||||
// 服务器配置
|
||||
// server: {
|
||||
// port: 3000,
|
||||
// open: true,
|
||||
// cors: true
|
||||
// }
|
||||
server: {
|
||||
port: 5173, // 保持你当前使用的端口
|
||||
open: true,
|
||||
cors: true
|
||||
}
|
||||
})
|
||||
@ -37,4 +37,14 @@ Route::post('index/wechat/reGenerateQrcode', 'index/wechat/reGenerateQrcode');
|
||||
Route::get('index/wechat/testWechat', 'index/wechat/testWechat');
|
||||
Route::post('index/wechat/testWechat', 'index/wechat/testWechat');
|
||||
|
||||
// ... existing code ...
|
||||
// API路由组
|
||||
Route::group('api', function () {
|
||||
// 管理员登录
|
||||
Route::post('admin/login', 'api/Admin/login');
|
||||
|
||||
// 管理员相关接口
|
||||
Route::get('admin/info', 'api/Admin/info');
|
||||
Route::post('admin/logout', 'api/Admin/logout');
|
||||
Route::post('admin/change-password', 'api/Admin/changePassword');
|
||||
Route::get('admin/menus', 'api/Admin/menus');
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user