修复token验证和左侧表单问题
This commit is contained in:
parent
1ceaeb9214
commit
2d44b8764d
@ -22,6 +22,33 @@ use think\Model;
|
|||||||
|
|
||||||
class AdminSysMenu extends 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
|
* @return array
|
||||||
@ -30,28 +57,36 @@ class AdminSysMenu extends Model
|
|||||||
{
|
{
|
||||||
// 获取所有启用的菜单
|
// 获取所有启用的菜单
|
||||||
$menus = self::where('status', 1)
|
$menus = self::where('status', 1)
|
||||||
->order('type', 'asc')
|
|
||||||
->order('sort', 'desc')
|
->order('sort', 'desc')
|
||||||
|
->order('smid', 'asc')
|
||||||
->select()
|
->select()
|
||||||
->toArray();
|
->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) {
|
foreach ($menus as $menu) {
|
||||||
if ($menu['parent_id'] == 0) {
|
if ($menu['parent_id'] == $parent_id) {
|
||||||
$menuTree[$menu['smid']] = $menu;
|
$children = self::buildTree($menus, $menu['smid']);
|
||||||
$menuTree[$menu['smid']]['children'] = [];
|
if ($children) {
|
||||||
|
$menu['children'] = $children;
|
||||||
|
} else {
|
||||||
|
$menu['children'] = [];
|
||||||
|
}
|
||||||
|
$tree[] = $menu;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 再处理子菜单
|
return $tree;
|
||||||
foreach ($menus as $menu) {
|
|
||||||
if ($menu['parent_id'] != 0 && isset($menuTree[$menu['parent_id']])) {
|
|
||||||
$menuTree[$menu['parent_id']]['children'][] = $menu;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_values($menuTree);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,4 +22,28 @@ use think\Model;
|
|||||||
|
|
||||||
class AdminUser extends 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;
|
namespace app\api\controller;
|
||||||
|
|
||||||
use app\api\controller\BaseController;
|
use app\api\controller\BaseController;
|
||||||
use app\admin\model\AdminUser;
|
use app\api\model\AdminUser;
|
||||||
use app\index\model\AdminUserGroup;
|
use app\api\model\AdminSysMenu;
|
||||||
|
|
||||||
use think\facade\Log;
|
use think\facade\Log;
|
||||||
use think\facade\Cache;
|
use think\facade\Cache;
|
||||||
@ -12,9 +12,9 @@ use think\Response;
|
|||||||
class AdminController extends BaseController
|
class AdminController extends BaseController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* 生成用户token
|
* 生成管理员token
|
||||||
*
|
*
|
||||||
* @param int $userId 用户ID
|
* @param int $userId 管理员ID
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
private function generateToken($userId)
|
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
|
* @return \think\Response
|
||||||
*/
|
*/
|
||||||
@ -58,10 +78,10 @@ class AdminController extends BaseController
|
|||||||
return json(['code' => 1, 'msg' => $validate->getError()]);
|
return json(['code' => 1, 'msg' => $validate->getError()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询用户
|
// 查询管理员
|
||||||
$user = AdminUser::where('account', $data['account'])->find();
|
$user = AdminUser::where('account', $data['account'])->find();
|
||||||
if (!$user) {
|
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' => '密码错误']);
|
return json(['code' => 1, 'msg' => '密码错误']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成JWT token(这里使用简单的token,实际项目中建议使用JWT)
|
// 生成token
|
||||||
$token = $this->generateToken($user->id);
|
$token = $this->generateToken($user->uid);
|
||||||
|
|
||||||
// 将token存储到缓存中,设置过期时间
|
// 将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([
|
return json([
|
||||||
'code' => 0,
|
'code' => 0,
|
||||||
'msg' => '登录成功',
|
'msg' => '登录成功',
|
||||||
'data' => [
|
'data' => [
|
||||||
'token' => $token,
|
'token' => $token,
|
||||||
'user_info' => [
|
'user_info' => [
|
||||||
'id' => $user->id,
|
'id' => $user->uid,
|
||||||
'account' => $user->account,
|
'account' => $user->account,
|
||||||
'name' => $user->name,
|
'name' => $user->name,
|
||||||
'avatar' => $user->avatar ?? '/static/images/avatar.png',
|
'avatar' => $user->avatar ?? '/static/images/avatar.png',
|
||||||
@ -99,8 +119,201 @@ class AdminController extends BaseController
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::record('登录失败:' . $e->getMessage(), 'error');
|
Log::record('管理员登录失败:' . $e->getMessage(), 'error');
|
||||||
return json(['code' => 1, 'msg' => '登录失败:' . $e->getMessage()]);
|
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']
|
ElCol: typeof import('element-plus/es')['ElCol']
|
||||||
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
||||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||||
|
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||||
@ -26,8 +27,11 @@ declare module 'vue' {
|
|||||||
ElMain: typeof import('element-plus/es')['ElMain']
|
ElMain: typeof import('element-plus/es')['ElMain']
|
||||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
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']
|
ElRow: typeof import('element-plus/es')['ElRow']
|
||||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||||
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||||
ElStatistic: typeof import('element-plus/es')['ElStatistic']
|
ElStatistic: typeof import('element-plus/es')['ElStatistic']
|
||||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||||
@ -37,4 +41,7 @@ declare module 'vue' {
|
|||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
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 { useDark } from '@vueuse/core'
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
import useColorStore from '@/store/color'
|
import useColorStore from '@/store/color'
|
||||||
import useUserStore from '@/store/modules/user'
|
|
||||||
import ENV_CONFIG from '@/config/env'
|
import ENV_CONFIG from '@/config/env'
|
||||||
|
|
||||||
useDark()
|
useDark()
|
||||||
|
|
||||||
const colorStore = useColorStore()
|
const colorStore = useColorStore()
|
||||||
const userStore = useUserStore()
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 设置页面标题
|
// 设置页面标题
|
||||||
document.title = ENV_CONFIG.APP_TITLE
|
document.title = ENV_CONFIG.APP_TITLE
|
||||||
// 初始化用户状态
|
|
||||||
userStore.initUserState()
|
|
||||||
// 初始化主题色
|
// 初始化主题色
|
||||||
colorStore.primaryChange(colorStore.primary)
|
colorStore.primaryChange(colorStore.primary)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -4,48 +4,16 @@ import api from './user'
|
|||||||
export interface MenuItem {
|
export interface MenuItem {
|
||||||
smid: number
|
smid: number
|
||||||
label: string
|
label: string
|
||||||
icon_class: string
|
|
||||||
type: number
|
type: number
|
||||||
src: string
|
src: string
|
||||||
sort: number
|
|
||||||
status: number
|
|
||||||
parent_id: number
|
parent_id: number
|
||||||
|
icon_class: string
|
||||||
|
sort: string
|
||||||
|
status: number
|
||||||
children?: MenuItem[]
|
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 = () => {
|
export const getUserMenus = () => {
|
||||||
return api.get('/menu/userMenus')
|
return api.get('/admin/menus')
|
||||||
}
|
|
||||||
|
|
||||||
// 获取角色列表
|
|
||||||
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')
|
|
||||||
}
|
}
|
||||||
@ -54,19 +54,19 @@ export const login = (data: { account: string; password: string }) => {
|
|||||||
return api.post('/admin/login', data)
|
return api.post('/admin/login', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户信息接口
|
// 获取管理员信息接口
|
||||||
export const getUserInfo = () => {
|
export const getUserInfo = () => {
|
||||||
return api.get('/user/info')
|
return api.get('/admin/info')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户登出接口
|
// 管理员登出接口
|
||||||
export const logout = () => {
|
export const logout = () => {
|
||||||
return api.post('/user/logout')
|
return api.post('/admin/logout')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修改密码接口
|
// 修改密码接口
|
||||||
export const changePassword = (data: { oldPassword: string; newPassword: string }) => {
|
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
|
export default api
|
||||||
@ -9,8 +9,8 @@ const ENV_CONFIG = {
|
|||||||
APP_TITLE: import.meta.env.VITE_APP_TITLE || '项目管理系统',
|
APP_TITLE: import.meta.env.VITE_APP_TITLE || '项目管理系统',
|
||||||
|
|
||||||
// 本地存储key
|
// 本地存储key
|
||||||
TOKEN_KEY: 'token',
|
TOKEN_KEY: 'admin_token',
|
||||||
USER_INFO_KEY: 'userInfo',
|
USER_INFO_KEY: 'admin_user_info',
|
||||||
|
|
||||||
// 是否为开发环境
|
// 是否为开发环境
|
||||||
IS_DEV: import.meta.env.DEV,
|
IS_DEV: import.meta.env.DEV,
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import 'element-plus/theme-chalk/dark/css-vars.css'
|
|||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||||
import useUserStore from '@/store/modules/user'
|
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
@ -21,8 +20,4 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
|||||||
|
|
||||||
app.use(pinia).use(ElementPlus).use(router)
|
app.use(pinia).use(ElementPlus).use(router)
|
||||||
|
|
||||||
// 初始化用户状态
|
|
||||||
const userStore = useUserStore()
|
|
||||||
userStore.initUserState()
|
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import { RouteRecordRaw, createRouter, createWebHashHistory } from 'vue-router'
|
import { RouteRecordRaw, createRouter, createWebHashHistory } from 'vue-router'
|
||||||
|
|
||||||
import * as MenuUtil from '@/util/menu'
|
|
||||||
import useFastnavStore from '@/store/fastnav'
|
import useFastnavStore from '@/store/fastnav'
|
||||||
import useUserStore from '@/store/modules/user'
|
import useUserStore from '@/store/modules/user'
|
||||||
|
import useMenuStore from '@/store/modules/menu'
|
||||||
|
|
||||||
// 只保留非菜单相关的静态路由
|
// 只保留非菜单相关的静态路由
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
@ -15,7 +15,16 @@ const routes: RouteRecordRaw[] = [
|
|||||||
path: '/',
|
path: '/',
|
||||||
name: 'Main',
|
name: 'Main',
|
||||||
component: () => import('@/views/main/index.vue'),
|
component: () => import('@/views/main/index.vue'),
|
||||||
children: [], // 预定义空的子路由数组
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'Home',
|
||||||
|
component: () => import('@/views/home/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '工作台'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/:catchAll(.*)',
|
path: '/:catchAll(.*)',
|
||||||
@ -31,8 +40,93 @@ const router = createRouter({
|
|||||||
let isAddRoute = false
|
let isAddRoute = false
|
||||||
let isUserStateInitialized = 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) => {
|
router.beforeEach(async (to, _from, next) => {
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
const menuStore = useMenuStore()
|
||||||
|
|
||||||
// 等待用户状态初始化完成
|
// 等待用户状态初始化完成
|
||||||
if (!isUserStateInitialized) {
|
if (!isUserStateInitialized) {
|
||||||
@ -59,9 +153,9 @@ router.beforeEach(async (to, _from, next) => {
|
|||||||
|
|
||||||
// 已经添加过动态路由
|
// 已经添加过动态路由
|
||||||
if (isAddRoute) {
|
if (isAddRoute) {
|
||||||
if (to.meta.desc) {
|
if (to.meta.title) {
|
||||||
const fastnavStore = useFastnavStore()
|
const fastnavStore = useFastnavStore()
|
||||||
fastnavStore.addData(to.meta.desc as string, to.path)
|
fastnavStore.addData(to.meta.title as string, to.path)
|
||||||
}
|
}
|
||||||
next()
|
next()
|
||||||
return
|
return
|
||||||
@ -70,16 +164,24 @@ router.beforeEach(async (to, _from, next) => {
|
|||||||
// 添加主页子路由
|
// 添加主页子路由
|
||||||
isAddRoute = true
|
isAddRoute = true
|
||||||
|
|
||||||
|
// 获取菜单数据
|
||||||
|
await menuStore.fetchUserMenus()
|
||||||
|
|
||||||
// 找到主路由
|
// 找到主路由
|
||||||
const mainRoute = router.options.routes.find((v) => v.path == '/')!
|
const mainRoute = router.options.routes.find((v) => v.path == '/')!
|
||||||
|
|
||||||
// 根据用户类型生成菜单和路由(默认使用管理员类型)
|
// 根据菜单数据生成路由
|
||||||
const [_genMenus, genRoutes] = MenuUtil.gen(1)
|
const genRoutes = generateRoutesFromMenus(menuStore.getMenus)
|
||||||
|
|
||||||
// 设置主路由的重定向和子路由
|
// 设置主路由的重定向和子路由
|
||||||
if (genRoutes.length > 0) {
|
if (genRoutes.length > 0) {
|
||||||
mainRoute.redirect = genRoutes[0].path
|
// 保留默认的Home页面路由,添加动态路由
|
||||||
mainRoute.children = genRoutes
|
const existingChildren = mainRoute.children || []
|
||||||
|
const homeRoute = existingChildren.find(route => route.name === 'Home')
|
||||||
|
|
||||||
|
mainRoute.children = homeRoute ? [homeRoute, ...genRoutes] : genRoutes
|
||||||
|
// 始终重定向到工作台
|
||||||
|
mainRoute.redirect = '/'
|
||||||
|
|
||||||
// 添加主路由(包含子路由)
|
// 添加主路由(包含子路由)
|
||||||
router.addRoute(mainRoute)
|
router.addRoute(mainRoute)
|
||||||
@ -87,8 +189,8 @@ router.beforeEach(async (to, _from, next) => {
|
|||||||
// 重新导航到目标路由
|
// 重新导航到目标路由
|
||||||
next({ ...to, replace: true })
|
next({ ...to, replace: true })
|
||||||
} else {
|
} else {
|
||||||
// 如果没有生成路由,至少重定向到登录页
|
// 如果没有生成路由,重定向到工作台
|
||||||
next({ path: '/login' })
|
next({ path: '/' })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { defineStore } from 'pinia'
|
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'
|
import ENV_CONFIG from '@/config/env'
|
||||||
|
|
||||||
interface MenuState {
|
interface MenuState {
|
||||||
@ -8,12 +8,6 @@ interface MenuState {
|
|||||||
error: string | null
|
error: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ApiResponse<T> {
|
|
||||||
code: number
|
|
||||||
message: string
|
|
||||||
data: T
|
|
||||||
}
|
|
||||||
|
|
||||||
const useMenuStore = defineStore('menu', {
|
const useMenuStore = defineStore('menu', {
|
||||||
state: (): MenuState => ({
|
state: (): MenuState => ({
|
||||||
menus: [],
|
menus: [],
|
||||||
@ -33,43 +27,22 @@ const useMenuStore = defineStore('menu', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
// 获取用户菜单(使用临时接口)
|
// 获取管理员菜单
|
||||||
async fetchUserMenus() {
|
async fetchUserMenus() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.error = null
|
this.error = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await getTempUserMenus() as unknown as ApiResponse<MenuItem[]>
|
const response = await getUserMenus() as unknown as MenuItem[]
|
||||||
|
|
||||||
if (response.code === 200 && response.data) {
|
if (response && Array.isArray(response)) {
|
||||||
this.menus = response.data
|
this.menus = response
|
||||||
} else {
|
} else {
|
||||||
throw new Error(response.message || '获取菜单失败')
|
throw new Error('获取菜单失败')
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.error = error.message || '获取菜单失败'
|
this.error = error.message || '获取菜单失败'
|
||||||
console.error('获取用户菜单失败:', error)
|
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)
|
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -103,6 +103,7 @@ const useUserStore = defineStore('user', {
|
|||||||
try {
|
try {
|
||||||
// 验证token是否有效
|
// 验证token是否有效
|
||||||
const response = await getUserInfoApi() as unknown as UserInfo
|
const response = await getUserInfoApi() as unknown as UserInfo
|
||||||
|
|
||||||
if (response && response.id) {
|
if (response && response.id) {
|
||||||
this.token = token
|
this.token = token
|
||||||
this.userInfo = response
|
this.userInfo = response
|
||||||
@ -113,10 +114,21 @@ const useUserStore = defineStore('user', {
|
|||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('获取用户信息失败:', error)
|
console.error('获取用户信息失败:', error)
|
||||||
// 网络错误时,不清除本地状态,保持用户登录状态
|
// 如果有本地token和用户信息,先保持登录状态
|
||||||
// 只有在明确知道token无效时才清除
|
// 只有在明确知道token无效时才清除
|
||||||
if (error.response?.status === 401) {
|
if (error.response?.status === 401) {
|
||||||
this.clearUserState()
|
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-aside :width="menuStore.width">
|
||||||
<el-menu
|
<el-menu
|
||||||
router
|
router
|
||||||
:default-active="route.path"
|
:default-active="route.path === '/' ? '/' : route.path"
|
||||||
:collapse="menuStore.collapse"
|
:collapse="menuStore.collapse"
|
||||||
style="height: 100%;"
|
style="height: 100%;"
|
||||||
>
|
>
|
||||||
<template v-for="menu in menus" :key="menu.smid">
|
<template v-for="menu in menus" :key="menu.smid">
|
||||||
<el-sub-menu
|
<el-sub-menu
|
||||||
v-if="menu.children && menu.children.length > 0"
|
v-if="menu.children && menu.children.length > 0"
|
||||||
:index="menu.smid.toString()"
|
:index="menu.src || menu.smid.toString()"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<component :is="menu.icon_class"></component>
|
<Setting />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<span>{{ menu.label }}</span>
|
<span>{{ menu.label }}</span>
|
||||||
</template>
|
</template>
|
||||||
<el-menu-item
|
<el-menu-item
|
||||||
v-for="child in menu.children"
|
v-for="child in menu.children"
|
||||||
:key="child.smid"
|
:key="child.smid"
|
||||||
:index="child.smid.toString()"
|
:index="child.src || child.smid.toString()"
|
||||||
@click="handleMenuClick(child)"
|
@click="handleMenuClick(child)"
|
||||||
>
|
>
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<component :is="child.icon_class"></component>
|
<Setting />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<span>{{ child.label }}</span>
|
<span>{{ child.label }}</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
@ -90,11 +90,12 @@
|
|||||||
<el-menu-item
|
<el-menu-item
|
||||||
v-else
|
v-else
|
||||||
:key="menu.smid"
|
:key="menu.smid"
|
||||||
:index="menu.smid.toString()"
|
:index="menu.smid === 0 ? '/' : (menu.src || menu.smid.toString())"
|
||||||
@click="handleMenuClick(menu)"
|
@click="handleMenuClick(menu)"
|
||||||
>
|
>
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<component :is="menu.icon_class"></component>
|
<Monitor v-if="menu.smid === 0" />
|
||||||
|
<Setting v-else />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<template #title>{{ menu.label }}</template>
|
<template #title>{{ menu.label }}</template>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
@ -165,9 +166,19 @@ import useMenuStore from '@/store/modules/menu'
|
|||||||
import useFastnavStore from '@/store/fastnav'
|
import useFastnavStore from '@/store/fastnav'
|
||||||
import useMenuCollapseStore from '@/store/menu'
|
import useMenuCollapseStore from '@/store/menu'
|
||||||
import useColorStore from '@/store/color'
|
import useColorStore from '@/store/color'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref, computed } from 'vue'
|
||||||
import { useRoute, useRouter, RouterView } from 'vue-router'
|
import { useRoute, useRouter, RouterView } from 'vue-router'
|
||||||
import { useFullscreen, useDark } from '@vueuse/core'
|
import { useFullscreen, useDark } from '@vueuse/core'
|
||||||
|
import {
|
||||||
|
Setting,
|
||||||
|
User,
|
||||||
|
Document,
|
||||||
|
Folder,
|
||||||
|
Monitor,
|
||||||
|
Grid,
|
||||||
|
Menu,
|
||||||
|
House
|
||||||
|
} from '@element-plus/icons-vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -183,6 +194,25 @@ const colorStore = useColorStore()
|
|||||||
const menus = ref<any[]>([])
|
const menus = ref<any[]>([])
|
||||||
const collapse = ref(true)
|
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 tagMenuReloadDisabled = ref(false)
|
||||||
const tagMenuCloseDisabled = ref(false)
|
const tagMenuCloseDisabled = ref(false)
|
||||||
@ -191,9 +221,26 @@ const tagMenuLeftDisabled = ref(false)
|
|||||||
const tagMenuRightDisabled = ref(false)
|
const tagMenuRightDisabled = ref(false)
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 获取动态菜单数据
|
// 从store获取菜单数据,如果还没有则等待
|
||||||
|
if (menuDataStore.getMenus.length === 0) {
|
||||||
await menuDataStore.fetchUserMenus()
|
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) => {
|
const handleMenuClick = (menu: any) => {
|
||||||
if (menu.type === 1 && menu.src) {
|
if (menu.smid === 0) {
|
||||||
// 内部跳转
|
// 工作台菜单
|
||||||
const title = menu.label
|
router.push('/')
|
||||||
const path = '/' + menu.src.replace('/', '_')
|
} else if (menu.type === 1 && menu.src) {
|
||||||
// 这里需要根据实际情况处理页面跳转
|
// 内部跳转 - 使用src作为路径
|
||||||
console.log('内部跳转:', title, path)
|
const path = '/' + menu.src
|
||||||
|
router.push(path)
|
||||||
} else if (menu.type === 2 && menu.src) {
|
} else if (menu.type === 2 && menu.src) {
|
||||||
// 外部链接
|
// 外部链接
|
||||||
window.open(menu.src, '_blank')
|
window.open(menu.src, '_blank')
|
||||||
|
|||||||
@ -53,9 +53,9 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 服务器配置
|
// 服务器配置
|
||||||
// server: {
|
server: {
|
||||||
// port: 3000,
|
port: 5173, // 保持你当前使用的端口
|
||||||
// open: true,
|
open: true,
|
||||||
// cors: true
|
cors: true
|
||||||
// }
|
}
|
||||||
})
|
})
|
||||||
@ -37,4 +37,14 @@ Route::post('index/wechat/reGenerateQrcode', 'index/wechat/reGenerateQrcode');
|
|||||||
Route::get('index/wechat/testWechat', 'index/wechat/testWechat');
|
Route::get('index/wechat/testWechat', 'index/wechat/testWechat');
|
||||||
Route::post('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