diff --git a/.gitignore b/.gitignore index 8c952c7..37f6256 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,4 @@ /vendor /runtime *.log -.env +/.env diff --git a/app/command/Test.php b/app/command/Test.php new file mode 100644 index 0000000..ad1f762 --- /dev/null +++ b/app/command/Test.php @@ -0,0 +1,22 @@ +writeln('Hello ' . $this->getName() . ''); + return self::SUCCESS; + } +} diff --git a/app/common/base/BaseController.php b/app/common/base/BaseController.php index a859440..c840604 100644 --- a/app/common/base/BaseController.php +++ b/app/common/base/BaseController.php @@ -24,7 +24,7 @@ class BaseController { return json([ 'code' => $code, - 'message' => $message, + 'msg' => $message, 'data' => $data, ]); } @@ -36,7 +36,7 @@ class BaseController { return json([ 'code' => $code, - 'message' => $message, + 'msg' => $message, 'data' => $data, ]); } @@ -59,5 +59,3 @@ class BaseController return (int) ($request->userId ?? 0); } } - - diff --git a/app/common/constants/CommonStatus.php b/app/common/constants/CommonStatus.php deleted file mode 100644 index 91ed3ab..0000000 --- a/app/common/constants/CommonStatus.php +++ /dev/null @@ -1,22 +0,0 @@ -reloadCache(); + } +} + + diff --git a/app/exceptions/BadRequestException.php b/app/exceptions/BadRequestException.php new file mode 100644 index 0000000..8b0989b --- /dev/null +++ b/app/exceptions/BadRequestException.php @@ -0,0 +1,25 @@ +data($data); + } + } +} + diff --git a/app/exceptions/ForbiddenException.php b/app/exceptions/ForbiddenException.php new file mode 100644 index 0000000..598313d --- /dev/null +++ b/app/exceptions/ForbiddenException.php @@ -0,0 +1,25 @@ +data($data); + } + } +} + diff --git a/app/exceptions/InternalServerException.php b/app/exceptions/InternalServerException.php new file mode 100644 index 0000000..3872068 --- /dev/null +++ b/app/exceptions/InternalServerException.php @@ -0,0 +1,26 @@ +data($data); + } + } +} + diff --git a/app/exceptions/NotFoundException.php b/app/exceptions/NotFoundException.php new file mode 100644 index 0000000..f51c636 --- /dev/null +++ b/app/exceptions/NotFoundException.php @@ -0,0 +1,25 @@ +data($data); + } + } +} + diff --git a/app/exceptions/UnauthorizedException.php b/app/exceptions/UnauthorizedException.php new file mode 100644 index 0000000..b83cd34 --- /dev/null +++ b/app/exceptions/UnauthorizedException.php @@ -0,0 +1,26 @@ +data($data); + } + } +} + diff --git a/app/exceptions/ValidationException.php b/app/exceptions/ValidationException.php index d44f7ca..9c9b8a1 100644 --- a/app/exceptions/ValidationException.php +++ b/app/exceptions/ValidationException.php @@ -3,8 +3,6 @@ namespace app\exceptions; use Webman\Exception\BusinessException; -use Webman\Http\Request; -use Webman\Http\Response; /** * 参数校验异常 @@ -19,6 +17,9 @@ class ValidationException extends BusinessException { public function __construct(string $message = '参数校验失败', int $bizCode = 422, array $data = []) { - parent::__construct($message, $bizCode, $data); + parent::__construct($message, $bizCode); + if (!empty($data)) { + $this->data($data); + } } } diff --git a/app/http/admin/controller/AuthController.php b/app/http/admin/controller/AuthController.php index 4888e91..acd5fe9 100644 --- a/app/http/admin/controller/AuthController.php +++ b/app/http/admin/controller/AuthController.php @@ -27,12 +27,8 @@ class AuthController extends BaseController */ public function captcha(Request $request) { - try { - $data = $this->captchaService->generate(); - return $this->success($data); - } catch (\Throwable $e) { - return $this->fail('验证码生成失败:' . $e->getMessage(), 500); - } + $data = $this->captchaService->generate(); + return $this->success($data); } /** @@ -52,14 +48,8 @@ class AuthController extends BaseController return $this->fail('请填写完整登录信息', 400); } - try { - $data = $this->authService->login($username, $password, $verifyCode, $captchaId); - return $this->success($data); - } catch (\RuntimeException $e) { - return $this->fail($e->getMessage(), $e->getCode() ?: 500); - } catch (\Throwable $e) { - return $this->fail('登录失败:' . $e->getMessage(), 500); - } + $data = $this->authService->login($username, $password, $verifyCode, $captchaId); + return $this->success($data); } } diff --git a/app/http/admin/controller/MenuController.php b/app/http/admin/controller/MenuController.php index 30bcb7d..6009d5d 100644 --- a/app/http/admin/controller/MenuController.php +++ b/app/http/admin/controller/MenuController.php @@ -3,6 +3,7 @@ namespace app\http\admin\controller; use app\common\base\BaseController; +use app\services\MenuService; use support\Request; /** @@ -10,44 +11,15 @@ use support\Request; */ class MenuController extends BaseController { + public function __construct( + protected MenuService $menuService + ) { + } + public function getRouters() { - // 获取菜单数据并转换为树形结构 - $routers = $this->buildMenuTree($this->getSystemMenu()); - + $routers = $this->menuService->getRouters(); return $this->success($routers); } - - /** - * 获取系统菜单数据 - * 从配置文件读取 - */ - private function getSystemMenu(): array - { - return config('menu', []); - } - - /** - * 构建菜单树形结构 - */ - private function buildMenuTree(array $menus, string $parentId = '0'): array - { - $tree = []; - - foreach ($menus as $menu) { - if (($menu['parentId'] ?? '0') === $parentId) { - $children = $this->buildMenuTree($menus, $menu['id']); - $menu['children'] = !empty($children) ? $children : null; - $tree[] = $menu; - } - } - - // 按 sort 排序 - usort($tree, function ($a, $b) { - return ($a['meta']['sort'] ?? 0) <=> ($b['meta']['sort'] ?? 0); - }); - - return $tree; - } } diff --git a/app/http/admin/controller/SystemController.php b/app/http/admin/controller/SystemController.php index a5bb0c5..427724f 100644 --- a/app/http/admin/controller/SystemController.php +++ b/app/http/admin/controller/SystemController.php @@ -3,6 +3,7 @@ namespace app\http\admin\controller; use app\common\base\BaseController; +use app\services\SystemSettingService; use support\Request; /** @@ -10,6 +11,10 @@ use support\Request; */ class SystemController extends BaseController { + public function __construct( + protected SystemSettingService $settingService + ) { + } /** * GET /system/getDict * GET /system/getDict/{code} @@ -24,23 +29,50 @@ class SystemController extends BaseController */ public function getDict(Request $request, string $code = '') { - // 获取所有字典数据 - $allDicts = config('dict', []); - - // 如果指定了 code,则只返回对应的字典 - if (!empty($code)) { - // 将数组转换为以 code 为键的关联数组,便于快速查找 - $dictsByCode = array_column($allDicts, null, 'code'); - $dict = $dictsByCode[$code] ?? null; - - if ($dict === null) { - return $this->fail('未找到指定的字典:' . $code, 404); - } - return $this->success($dict); + $data = $this->settingService->getDict($code); + return $this->success($data); + } + + /** + * GET /system/base-config/tabs + * + * 获取所有Tab配置 + * 由 SystemSettingService 负责读取配置和缓存 + */ + public function getTabsConfig() + { + $tabs = $this->settingService->getTabs(); + return $this->success($tabs); + } + + /** + * GET /system/base-config/form/{tabKey} + * + * 获取指定Tab的表单配置 + * 从 SystemSettingService 获取合并后的配置 + */ + public function getFormConfig(Request $request, string $tabKey) + { + $formConfig = $this->settingService->getFormConfig($tabKey); + return $this->success($formConfig); + } + + /** + * POST /system/base-config/submit/{tabKey} + * + * 提交表单数据 + * 接收表单数据,直接使用字段名(fieldName)作为 config_key 保存到数据库 + */ + public function submitConfig(Request $request, string $tabKey) + { + $formData = $request->post(); + + if (empty($formData)) { + return $this->fail('提交数据不能为空', 400); } - - // 返回所有字典 - return $this->success($allDicts); + + $this->settingService->saveFormConfig($tabKey, $formData); + return $this->success(null, '保存成功'); } } diff --git a/app/http/admin/controller/UserController.php b/app/http/admin/controller/UserController.php index 7255a81..1936989 100644 --- a/app/http/admin/controller/UserController.php +++ b/app/http/admin/controller/UserController.php @@ -33,12 +33,8 @@ class UserController extends BaseController return $this->fail('未获取到用户信息,请先登录', 401); } - try { - $data = $this->userService->getUserInfoById($userId); - return $this->success($data); - } catch (\RuntimeException $e) { - return $this->fail($e->getMessage(), $e->getCode() ?: 500); - } + $data = $this->userService->getUserInfoById($userId); + return $this->success($data); } } diff --git a/app/http/admin/middleware/AuthMiddleware.php b/app/http/admin/middleware/AuthMiddleware.php index f337539..45f8501 100644 --- a/app/http/admin/middleware/AuthMiddleware.php +++ b/app/http/admin/middleware/AuthMiddleware.php @@ -3,9 +3,10 @@ namespace app\http\admin\middleware; use Webman\MiddlewareInterface; -use Webman\Http\Response; use Webman\Http\Request; +use Webman\Http\Response; use app\common\utils\JwtUtil; +use app\exceptions\UnauthorizedException; /** * JWT 认证中间件 @@ -25,7 +26,7 @@ class AuthMiddleware implements MiddlewareInterface // 从请求头中获取 token $auth = $request->header('Authorization', ''); if (!$auth) { - return $this->unauthorized('缺少认证令牌'); + throw new UnauthorizedException('缺少认证令牌'); } // 兼容 "Bearer xxx" 或直接 "xxx" @@ -36,7 +37,7 @@ class AuthMiddleware implements MiddlewareInterface } if (!$token) { - return $this->unauthorized('认证令牌格式错误'); + throw new UnauthorizedException('认证令牌格式错误'); } try { @@ -44,7 +45,7 @@ class AuthMiddleware implements MiddlewareInterface $payload = JwtUtil::parseToken($token); if (empty($payload) || !isset($payload['user_id'])) { - return $this->unauthorized('认证令牌无效'); + throw new UnauthorizedException('认证令牌无效'); } // 将用户信息存储到请求对象中,供控制器使用 @@ -53,29 +54,20 @@ class AuthMiddleware implements MiddlewareInterface // 继续处理请求 return $handler($request); + } catch (UnauthorizedException $e) { + // 重新抛出业务异常,让框架处理 + throw $e; } catch (\Throwable $e) { // 根据异常类型返回不同的错误信息 $message = $e->getMessage(); if (str_contains($message, 'expired') || str_contains($message, 'Expired')) { - return $this->unauthorized('认证令牌已过期', 401); + throw new UnauthorizedException('认证令牌已过期'); } elseif (str_contains($message, 'signature') || str_contains($message, 'Signature')) { - return $this->unauthorized('认证令牌签名无效', 401); + throw new UnauthorizedException('认证令牌签名无效'); } else { - return $this->unauthorized('认证令牌验证失败:' . $message, 401); + throw new UnauthorizedException('认证令牌验证失败:' . $message); } } } - - /** - * 返回未授权响应 - */ - private function unauthorized(string $message, int $code = 401): Response - { - return json([ - 'code' => $code, - 'message' => $message, - 'data' => null, - ], $code); - } } diff --git a/app/models/SystemConfig.php b/app/models/SystemConfig.php new file mode 100644 index 0000000..e0754d0 --- /dev/null +++ b/app/models/SystemConfig.php @@ -0,0 +1,38 @@ +model - ->newQuery() - ->whereIn('type', [1, 2]) // 1目录 2菜单,排除3按钮 - ->where('status', 1) // 只获取启用的菜单 - ->orderBy('sort', 'asc') - ->orderBy('id', 'asc') - ->get() - ->toArray(); - } - - /** - * 根据菜单ID列表获取启用的菜单(仅目录和菜单类型,排除按钮) - */ - public function getMenusByIds(array $menuIds): array - { - if (empty($menuIds)) { - return []; - } - - return $this->model - ->newQuery() - ->whereIn('id', $menuIds) - ->whereIn('type', [1, 2]) // 1目录 2菜单,排除3按钮 - ->where('status', 1) // 只获取启用的菜单 - ->orderBy('sort', 'asc') - ->orderBy('id', 'asc') - ->get() - ->toArray(); - } -} - - diff --git a/app/repositories/RoleRepository.php b/app/repositories/RoleRepository.php deleted file mode 100644 index 7b5a721..0000000 --- a/app/repositories/RoleRepository.php +++ /dev/null @@ -1,19 +0,0 @@ - + */ + protected function loadAllToCache(): array + { + // 优先从 webman/cache 获取 + $cached = Cache::get(self::CACHE_KEY_ALL_CONFIG); + if (is_array($cached)) { + return $cached; + } + + // 缓存不存在时从数据库加载 + $configs = $this->model + ->newQuery() + ->get(['config_key', 'config_value']); + + $result = []; + foreach ($configs as $config) { + $result[$config->config_key] = $config->config_value; + } + + // 写入缓存(不过期,除非显式清理) + Cache::set(self::CACHE_KEY_ALL_CONFIG, $result); + + return $result; + } + + /** + * 清空缓存(供事件调用) + */ + public static function clearCache(): void + { + Cache::delete(self::CACHE_KEY_ALL_CONFIG); + } + + /** + * 重新从数据库加载缓存(供事件调用) + */ + public function reloadCache(): void + { + Cache::delete(self::CACHE_KEY_ALL_CONFIG); + $this->loadAllToCache(); + } + + /** + * 根据配置键名查询配置值 + * + * @param string $configKey + * @return string|null + */ + public function getValueByKey(string $configKey): ?string + { + $all = $this->loadAllToCache(); + + return $all[$configKey] ?? null; + } + + /** + * 根据配置键名数组批量查询配置 + * + * @param array $configKeys + * @return array 返回 ['config_key' => 'config_value'] 格式的数组 + */ + public function getValuesByKeys(array $configKeys): array + { + if (empty($configKeys)) { + return []; + } + + $all = $this->loadAllToCache(); + + $result = []; + foreach ($configKeys as $key) { + if (array_key_exists($key, $all)) { + $result[$key] = $all[$key]; + } + } + + return $result; + } + + /** + * 根据配置键名更新或创建配置 + * + * @param string $configKey + * @param string $configValue + * @return bool + */ + public function updateOrCreate(string $configKey, string $configValue): bool + { + $this->model + ->newQuery() + ->updateOrCreate( + ['config_key' => $configKey], + ['config_value' => $configValue] + ); + + // 通过事件通知重新加载缓存 + Event::emit('system.config.updated', null); + + return true; + } + + /** + * 批量更新或创建配置 + * + * @param array $configs 格式:['config_key' => 'config_value'] + * @return bool + */ + public function batchUpdateOrCreate(array $configs): bool + { + if (empty($configs)) { + return true; + } + + foreach ($configs as $configKey => $configValue) { + $this->model + ->newQuery() + ->updateOrCreate( + ['config_key' => $configKey], + ['config_value' => $configValue] + ); + } + + // 批量更新后只触发一次事件,通知重新加载缓存 + Event::emit('system.config.updated', null); + + return true; + } +} + diff --git a/app/routes/admin.php b/app/routes/admin.php index 7dc71a2..b0f4ab0 100644 --- a/app/routes/admin.php +++ b/app/routes/admin.php @@ -30,5 +30,10 @@ Route::group('/adminapi', function () { // 系统相关(需要JWT验证) Route::get('/system/getDict[/{code}]', [SystemController::class, 'getDict']); + + // 系统配置相关(需要JWT验证) + Route::get('/system/base-config/tabs', [SystemController::class, 'getTabsConfig']); + Route::get('/system/base-config/form/{tabKey}', [SystemController::class, 'getFormConfig']); + Route::post('/system/base-config/submit/{tabKey}', [SystemController::class, 'submitConfig']); })->middleware([AuthMiddleware::class]); })->middleware([Cors::class]); \ No newline at end of file diff --git a/app/routes/mer.php b/app/routes/mer.php new file mode 100644 index 0000000..67b84ef --- /dev/null +++ b/app/routes/mer.php @@ -0,0 +1,8 @@ + string] - * @throws \RuntimeException */ public function login(string $username, string $password, string $verifyCode, string $captchaId): array { // 1. 校验验证码 if (!$this->captchaService->validate($captchaId, $verifyCode)) { - throw new \RuntimeException('验证码错误或已失效', 400); + throw new BadRequestException('验证码错误或已失效'); } // 2. 查询用户 $user = $this->userRepository->findByUserName($username); if (!$user) { - throw new \RuntimeException('账号或密码错误', 401); + throw new UnauthorizedException('账号或密码错误'); } // 3. 校验密码 if (!$this->validatePassword($password, $user->password)) { - throw new \RuntimeException('账号或密码错误', 401); + throw new UnauthorizedException('账号或密码错误'); } // 4. 检查用户状态 if ($user->status !== 1) { - throw new \RuntimeException('账号已被禁用', 403); + throw new ForbiddenException('账号已被禁用'); } // 5. 生成 JWT token(包含用户ID、用户名、昵称等信息) diff --git a/app/services/MenuService.php b/app/services/MenuService.php new file mode 100644 index 0000000..c43b52b --- /dev/null +++ b/app/services/MenuService.php @@ -0,0 +1,89 @@ +getSystemMenu(); + return $this->buildMenuTree($menus); + } + + /** + * 获取系统菜单数据 + * 仅从 JSON 文件 + 缓存中读取 + */ + protected function getSystemMenu(): array + { + $menus = Cache::get(self::CACHE_KEY_MENU); + + if (!is_array($menus)) { + // 优先读取 JSON 文件 + $jsonPath = config_path('system-file/menu.json'); + if (!file_exists($jsonPath)) { + throw new InternalServerException('菜单配置文件不存在'); + } + + $jsonContent = file_get_contents($jsonPath); + $data = json_decode($jsonContent, true); + + if (json_last_error() !== JSON_ERROR_NONE || !is_array($data)) { + throw new InternalServerException('菜单配置文件格式错误:' . json_last_error_msg()); + } + + $menus = $data; + Cache::set(self::CACHE_KEY_MENU, $menus); + } + + return $menus; + } + + /** + * 构建菜单树形结构 + */ + protected function buildMenuTree(array $menus, string $parentId = '0'): array + { + $tree = []; + + foreach ($menus as $menu) { + if (($menu['parentId'] ?? '0') === $parentId) { + $children = $this->buildMenuTree($menus, $menu['id']); + $menu['children'] = !empty($children) ? $children : null; + $tree[] = $menu; + } + } + + // 按 sort 排序 + usort($tree, function ($a, $b) { + return ($a['meta']['sort'] ?? 0) <=> ($b['meta']['sort'] ?? 0); + }); + + return $tree; + } +} + + diff --git a/app/services/SystemConfigService.php b/app/services/SystemConfigService.php new file mode 100644 index 0000000..223231b --- /dev/null +++ b/app/services/SystemConfigService.php @@ -0,0 +1,82 @@ +configRepository->getValueByKey($configKey); + return $value !== null ? $value : $default; + } + + /** + * 根据配置键名数组批量获取配置值 + * + * @param array $configKeys + * @return array 返回 ['config_key' => 'config_value'] 格式的数组 + */ + public function getValues(array $configKeys): array + { + return $this->configRepository->getValuesByKeys($configKeys); + } + + /** + * 保存配置值 + * + * @param string $configKey + * @param mixed $configValue + * @return bool + */ + public function setValue(string $configKey, $configValue): bool + { + // 如果是数组或对象,转换为JSON字符串 + if (is_array($configValue) || is_object($configValue)) { + $configValue = json_encode($configValue, JSON_UNESCAPED_UNICODE); + } else { + $configValue = (string) $configValue; + } + + return $this->configRepository->updateOrCreate($configKey, $configValue); + } + + /** + * 批量保存配置值 + * + * @param array $configs 格式:['config_key' => 'config_value'] + * @return bool + */ + public function setValues(array $configs): bool + { + // 处理数组和对象类型的值 + $processedConfigs = []; + foreach ($configs as $key => $value) { + if (is_array($value) || is_object($value)) { + $processedConfigs[$key] = json_encode($value, JSON_UNESCAPED_UNICODE); + } else { + $processedConfigs[$key] = (string) $value; + } + } + + return $this->configRepository->batchUpdateOrCreate($processedConfigs); + } +} + diff --git a/app/services/SystemSettingService.php b/app/services/SystemSettingService.php new file mode 100644 index 0000000..6ddd50a --- /dev/null +++ b/app/services/SystemSettingService.php @@ -0,0 +1,211 @@ + $sortB; + }); + + Cache::set(self::CACHE_KEY_TABS, $tabs); + + return $tabs; + } + + /** + * 获取指定 Tab 的表单配置(合并数据库值) + * + * @param string $tabKey + * @return array + */ + public function getFormConfig(string $tabKey): array + { + $cacheKey = self::CACHE_KEY_FORM_PREFIX . $tabKey; + + $formConfig = Cache::get($cacheKey); + if (!is_array($formConfig)) { + $configPath = config_path("base-config/{$tabKey}.json"); + + if (!file_exists($configPath)) { + throw new NotFoundException("表单配置文件不存在:{$tabKey}"); + } + + $jsonContent = file_get_contents($configPath); + $formConfig = json_decode($jsonContent, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new InternalServerException('表单配置文件格式错误:' . json_last_error_msg()); + } + + Cache::set($cacheKey, $formConfig); + } + + // 合并数据库配置值 + if (isset($formConfig['rules']) && is_array($formConfig['rules'])) { + $fieldNames = []; + foreach ($formConfig['rules'] as $rule) { + if (isset($rule['field'])) { + $fieldNames[] = $rule['field']; + } + } + + if (!empty($fieldNames)) { + $dbValues = $this->configService->getValues($fieldNames); + + foreach ($formConfig['rules'] as &$rule) { + if (isset($rule['field']) && isset($dbValues[$rule['field']])) { + $value = $dbValues[$rule['field']]; + + $decoded = json_decode($value, true); + if (json_last_error() === JSON_ERROR_NONE) { + $rule['value'] = $decoded; + } else { + if (isset($rule['type'])) { + switch ($rule['type']) { + case 'inputNumber': + $rule['value'] = is_numeric($value) ? (float) $value : ($rule['value'] ?? 0); + break; + case 'switch': + $rule['value'] = in_array(strtolower($value), ['1', 'true', 'yes', 'on'], true); + break; + default: + $rule['value'] = $value; + } + } else { + $rule['value'] = $value; + } + } + } + } + unset($rule); + } + } + + Cache::set($cacheKey, $formConfig); + + return $formConfig; + } + + /** + * 保存表单配置 + * + * @param string $tabKey + * @param array $formData + * @return void + */ + public function saveFormConfig(string $tabKey, array $formData): void + { + $result = $this->configService->setValues($formData); + + if (!$result) { + throw new InternalServerException('保存失败'); + } + + // 清理对应表单缓存 + $cacheKey = self::CACHE_KEY_FORM_PREFIX . $tabKey; + Cache::delete($cacheKey); + } +} + + diff --git a/app/services/UserService.php b/app/services/UserService.php index e5e04de..23be6ea 100644 --- a/app/services/UserService.php +++ b/app/services/UserService.php @@ -4,6 +4,7 @@ namespace app\services; use app\common\base\BaseService; use app\common\constants\RoleCode; +use app\exceptions\NotFoundException; use app\repositories\UserRepository; /** @@ -29,7 +30,7 @@ class UserService extends BaseService { $user = $this->users->find($id); if (!$user) { - throw new \RuntimeException('用户不存在', 404); + throw new NotFoundException('用户不存在'); } $userArray = $user->toArray(); diff --git a/app/validation/SystemConfigValidator.php b/app/validation/SystemConfigValidator.php new file mode 100644 index 0000000..b73e820 --- /dev/null +++ b/app/validation/SystemConfigValidator.php @@ -0,0 +1,23 @@ + 'string|max:100', + 'config_value' => 'nullable|string', + ]; + + protected array $messages = []; + + protected array $attributes = [ + 'config_key' => '配置项键名', + 'config_value' => '配置项值', + ]; + + protected array $scenes = []; +} diff --git a/composer.json b/composer.json index 2ab515c..a8c9ed7 100644 --- a/composer.json +++ b/composer.json @@ -32,14 +32,14 @@ "webman/redis": "^2.1", "illuminate/events": "^12.49", "webman/cache": "^2.1", - "webman/console": "^2.1", - "topthink/think-validate": "^3.0", + "webman/console": "^2.2", "webman/captcha": "^1.0", "webman/event": "^1.0", "vlucas/phpdotenv": "^5.6", "workerman/crontab": "^1.0", "webman/redis-queue": "^2.1", - "firebase/php-jwt": "^7.0" + "firebase/php-jwt": "^7.0", + "webman/validation": "^2.2" }, "suggest": { "ext-event": "For better performance. " diff --git a/composer.lock b/composer.lock index aa3208a..f6ae6c0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ea45057d8c2734266a897db21c27e75c", + "content-hash": "c9b20b999efb47e639901ded2ade0fdb", "packages": [ { "name": "brick/math", @@ -225,6 +225,150 @@ ], "time": "2025-08-10T19:31:58+00:00" }, + { + "name": "doctrine/lexer", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/3.0.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2024-02-05T11:56:58+00:00" + }, + { + "name": "egulias/email-validator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" + }, + "require-dev": { + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2025-03-06T22:45:56+00:00" + }, { "name": "firebase/php-jwt", "version": "v7.0.2", @@ -1697,6 +1841,120 @@ }, "time": "2026-02-04T15:14:59+00:00" }, + { + "name": "illuminate/translation", + "version": "v12.52.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/translation.git", + "reference": "18aa24aba6f2ab2447b9b903ae7360725fe5bdd0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/translation/zipball/18aa24aba6f2ab2447b9b903ae7360725fe5bdd0", + "reference": "18aa24aba6f2ab2447b9b903ae7360725fe5bdd0", + "shasum": "" + }, + "require": { + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/filesystem": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", + "php": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Translation package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2026-02-06T12:12:31+00:00" + }, + { + "name": "illuminate/validation", + "version": "v12.52.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/validation.git", + "reference": "195b7dd66548a6a82dd93bf6280926bf9039903c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/validation/zipball/195b7dd66548a6a82dd93bf6280926bf9039903c", + "reference": "195b7dd66548a6a82dd93bf6280926bf9039903c", + "shasum": "" + }, + "require": { + "brick/math": "^0.11|^0.12|^0.13|^0.14", + "egulias/email-validator": "^3.2.5|^4.0", + "ext-filter": "*", + "ext-mbstring": "*", + "illuminate/collections": "^12.0", + "illuminate/container": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", + "illuminate/translation": "^12.0", + "php": "^8.2", + "symfony/http-foundation": "^7.2", + "symfony/mime": "^7.2", + "symfony/polyfill-php83": "^1.33" + }, + "suggest": { + "illuminate/database": "Required to use the database presence verifier (^12.0).", + "ramsey/uuid": "Required to use Validator::validateUuid() (^4.7)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Validation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Validation package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2026-02-14T23:03:41+00:00" + }, { "name": "laravel/serializable-closure", "version": "v1.3.7", @@ -5019,142 +5277,6 @@ ], "time": "2025-09-11T10:15:23+00:00" }, - { - "name": "topthink/think-container", - "version": "v3.0.2", - "source": { - "type": "git", - "url": "https://github.com/top-think/think-container.git", - "reference": "b2df244be1e7399ad4c8be1ccc40ed57868f730a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/top-think/think-container/zipball/b2df244be1e7399ad4c8be1ccc40ed57868f730a", - "reference": "b2df244be1e7399ad4c8be1ccc40ed57868f730a", - "shasum": "" - }, - "require": { - "php": ">=8.0", - "psr/container": "^2.0", - "topthink/think-helper": "^3.1" - }, - "require-dev": { - "phpunit/phpunit": "^9.5" - }, - "type": "library", - "autoload": { - "files": [], - "psr-4": { - "think\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "liu21st", - "email": "liu21st@gmail.com" - } - ], - "description": "PHP Container & Facade Manager", - "support": { - "issues": "https://github.com/top-think/think-container/issues", - "source": "https://github.com/top-think/think-container/tree/v3.0.2" - }, - "time": "2025-04-07T03:21:51+00:00" - }, - { - "name": "topthink/think-helper", - "version": "v3.1.12", - "source": { - "type": "git", - "url": "https://github.com/top-think/think-helper.git", - "reference": "fe277121112a8f1c872e169a733ca80bb11c4acb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/top-think/think-helper/zipball/fe277121112a8f1c872e169a733ca80bb11c4acb", - "reference": "fe277121112a8f1c872e169a733ca80bb11c4acb", - "shasum": "" - }, - "require": { - "php": ">=7.1.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/helper.php" - ], - "psr-4": { - "think\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "yunwuxin", - "email": "448901948@qq.com" - } - ], - "description": "The ThinkPHP6 Helper Package", - "support": { - "issues": "https://github.com/top-think/think-helper/issues", - "source": "https://github.com/top-think/think-helper/tree/v3.1.12" - }, - "time": "2025-12-26T09:58:29+00:00" - }, - { - "name": "topthink/think-validate", - "version": "v3.0.7", - "source": { - "type": "git", - "url": "https://github.com/top-think/think-validate.git", - "reference": "85063f6d4ef8ed122f17a36179dc3e0949b30988" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/top-think/think-validate/zipball/85063f6d4ef8ed122f17a36179dc3e0949b30988", - "reference": "85063f6d4ef8ed122f17a36179dc3e0949b30988", - "shasum": "" - }, - "require": { - "php": ">=8.0", - "topthink/think-container": ">=3.0" - }, - "type": "library", - "autoload": { - "files": [ - "src/helper.php" - ], - "psr-4": { - "think\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "liu21st", - "email": "liu21st@gmail.com" - } - ], - "description": "think validate", - "support": { - "issues": "https://github.com/top-think/think-validate/issues", - "source": "https://github.com/top-think/think-validate/tree/v3.0.7" - }, - "time": "2025-06-11T05:51:40+00:00" - }, { "name": "vlucas/phpdotenv", "version": "v5.6.3", @@ -5410,16 +5532,16 @@ }, { "name": "webman/console", - "version": "v2.1.12", + "version": "v2.2.1", "source": { "type": "git", "url": "https://github.com/webman-php/console.git", - "reference": "7f1288ace2c7b6326e5a756dba067c74bd7cd2bc" + "reference": "3c1a50296f7b3b3eff3a8fcda8906276dae3a18f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webman-php/console/zipball/7f1288ace2c7b6326e5a756dba067c74bd7cd2bc", - "reference": "7f1288ace2c7b6326e5a756dba067c74bd7cd2bc", + "url": "https://api.github.com/repos/webman-php/console/zipball/3c1a50296f7b3b3eff3a8fcda8906276dae3a18f", + "reference": "3c1a50296f7b3b3eff3a8fcda8906276dae3a18f", "shasum": "" }, "require": { @@ -5460,7 +5582,7 @@ "source": "https://github.com/webman-php/console", "wiki": "http://www.workerman.net/doc/webman" }, - "time": "2026-01-20T15:17:31+00:00" + "time": "2026-02-23T02:46:47+00:00" }, { "name": "webman/database", @@ -5601,6 +5723,46 @@ }, "time": "2025-11-14T07:12:52+00:00" }, + { + "name": "webman/validation", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/webman-php/validation.git", + "reference": "afe9c66bf612d969d5657c83617bdb8d92e62ee3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/validation/zipball/afe9c66bf612d969d5657c83617bdb8d92e62ee3", + "reference": "afe9c66bf612d969d5657c83617bdb8d92e62ee3", + "shasum": "" + }, + "require": { + "illuminate/translation": "*", + "illuminate/validation": "*", + "workerman/webman-framework": "^2.1 || dev-master" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webman\\Validation\\": "src", + "support\\validation\\": "src/support/validation" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Webman plugin webman/validation", + "support": { + "issues": "https://github.com/webman-php/validation/issues", + "source": "https://github.com/webman-php/validation/tree/v2.2.0" + }, + "time": "2026-02-21T08:22:43+00:00" + }, { "name": "workerman/coroutine", "version": "v1.1.4", diff --git a/config/base-config/basic.json b/config/base-config/basic.json new file mode 100644 index 0000000..a646743 --- /dev/null +++ b/config/base-config/basic.json @@ -0,0 +1,84 @@ +{ + "formId": "basic-config", + "title": "基础设置", + "submitText": "保存设置", + "submitUrl": "/adminapi/system/base-config/submit/basic", + "cacheKey": "basic_config_cache", + "refreshAfterSubmit": true, + "rules": [ + { + "type": "input", + "field": "site_name", + "title": "站点名称", + "value": "", + "props": { + "placeholder": "请输入站点名称" + }, + "validate": [ + { + "required": true, + "message": "站点名称不能为空" + } + ] + }, + { + "type": "textarea", + "field": "site_description", + "title": "站点描述", + "value": "", + "props": { + "placeholder": "请输入站点描述", + "autoSize": { + "minRows": 3, + "maxRows": 6 + } + } + }, + { + "type": "input", + "field": "site_logo", + "title": "站点Logo", + "value": "", + "props": { + "placeholder": "请输入Logo地址或上传Logo" + } + }, + { + "type": "input", + "field": "icp_number", + "title": "备案号", + "value": "", + "props": { + "placeholder": "请输入ICP备案号" + } + }, + { + "type": "switch", + "field": "site_status", + "title": "站点状态", + "value": true, + "props": { + "checkedText": "开启", + "uncheckedText": "关闭" + } + }, + { + "type": "inputNumber", + "field": "page_size", + "title": "每页显示数量", + "value": 10, + "props": { + "min": 1, + "max": 100, + "precision": 0 + }, + "validate": [ + { + "required": true, + "message": "每页显示数量不能为空" + } + ] + } + ] +} + diff --git a/config/base-config/email.json b/config/base-config/email.json new file mode 100644 index 0000000..0db2994 --- /dev/null +++ b/config/base-config/email.json @@ -0,0 +1,112 @@ +{ + "formId": "email-config", + "title": "邮件设置", + "submitText": "保存设置", + "submitUrl": "/adminapi/system/base-config/submit/email", + "cacheKey": "email_config_cache", + "refreshAfterSubmit": true, + "rules": [ + { + "type": "input", + "field": "smtp_host", + "title": "SMTP服务器", + "value": "", + "props": { + "placeholder": "例如:smtp.qq.com" + }, + "validate": [ + { + "required": true, + "message": "SMTP服务器不能为空" + } + ] + }, + { + "type": "inputNumber", + "field": "smtp_port", + "title": "SMTP端口", + "value": 465, + "props": { + "min": 1, + "max": 65535, + "precision": 0 + }, + "validate": [ + { + "required": true, + "message": "SMTP端口不能为空" + } + ] + }, + { + "type": "switch", + "field": "smtp_ssl", + "title": "启用SSL", + "value": true, + "props": { + "checkedText": "是", + "uncheckedText": "否" + } + }, + { + "type": "input", + "field": "smtp_username", + "title": "SMTP用户名", + "value": "", + "props": { + "placeholder": "请输入SMTP用户名" + }, + "validate": [ + { + "required": true, + "message": "SMTP用户名不能为空" + } + ] + }, + { + "type": "input", + "field": "smtp_password", + "title": "SMTP密码", + "value": "", + "props": { + "type": "password", + "placeholder": "请输入SMTP密码或授权码" + }, + "validate": [ + { + "required": true, + "message": "SMTP密码不能为空" + } + ] + }, + { + "type": "input", + "field": "from_email", + "title": "发件人邮箱", + "value": "", + "props": { + "placeholder": "请输入发件人邮箱地址" + }, + "validate": [ + { + "required": true, + "message": "发件人邮箱不能为空" + }, + { + "type": "email", + "message": "请输入正确的邮箱地址" + } + ] + }, + { + "type": "input", + "field": "from_name", + "title": "发件人名称", + "value": "", + "props": { + "placeholder": "请输入发件人名称" + } + } + ] +} + diff --git a/config/base-config/permission.json b/config/base-config/permission.json new file mode 100644 index 0000000..ac9f49e --- /dev/null +++ b/config/base-config/permission.json @@ -0,0 +1,99 @@ +{ + "formId": "permission-config", + "title": "权限设置", + "submitText": "保存设置", + "submitUrl": "/adminapi/system/base-config/submit/permission", + "cacheKey": "permission_config_cache", + "refreshAfterSubmit": true, + "rules": [ + { + "type": "switch", + "field": "enable_permission", + "title": "启用权限控制", + "value": true, + "props": { + "checkedText": "启用", + "uncheckedText": "禁用" + } + }, + { + "type": "inputNumber", + "field": "session_timeout", + "title": "会话超时时间(分钟)", + "value": 30, + "props": { + "min": 1, + "max": 1440, + "precision": 0 + }, + "validate": [ + { + "required": true, + "message": "会话超时时间不能为空" + } + ] + }, + { + "type": "inputNumber", + "field": "password_min_length", + "title": "密码最小长度", + "value": 6, + "props": { + "min": 4, + "max": 32, + "precision": 0 + }, + "validate": [ + { + "required": true, + "message": "密码最小长度不能为空" + } + ] + }, + { + "type": "switch", + "field": "require_strong_password", + "title": "要求强密码", + "value": false, + "props": { + "checkedText": "是", + "uncheckedText": "否" + } + }, + { + "type": "inputNumber", + "field": "max_login_attempts", + "title": "最大登录尝试次数", + "value": 5, + "props": { + "min": 1, + "max": 10, + "precision": 0 + }, + "validate": [ + { + "required": true, + "message": "最大登录尝试次数不能为空" + } + ] + }, + { + "type": "inputNumber", + "field": "lockout_duration", + "title": "账户锁定时长(分钟)", + "value": 30, + "props": { + "min": 1, + "max": 1440, + "precision": 0 + }, + "validate": [ + { + "required": true, + "message": "账户锁定时长不能为空" + } + ] + } + ] +} + diff --git a/config/base-config/tabs.json b/config/base-config/tabs.json new file mode 100644 index 0000000..4144994 --- /dev/null +++ b/config/base-config/tabs.json @@ -0,0 +1,27 @@ +[ + { + "key": "basic", + "title": "基础设置", + "icon": "settings", + "description": "配置系统基础信息,包括站点名称、Logo、备案号等", + "sort": 1, + "disabled": false + }, + { + "key": "email", + "title": "邮件设置", + "icon": "email", + "description": "配置邮件服务器相关参数,包括SMTP服务器、端口、账号密码等", + "sort": 2, + "disabled": false + }, + { + "key": "permission", + "title": "权限设置", + "icon": "lock", + "description": "配置系统权限相关参数,预留权限控制功能", + "sort": 3, + "disabled": false + } +] + diff --git a/config/dict.php b/config/dict.php deleted file mode 100644 index 64f2ba2..0000000 --- a/config/dict.php +++ /dev/null @@ -1,48 +0,0 @@ - '性别', - 'code' => 'gender', - 'description' => '这是一个性别字典', - 'list' => [ - ['name' => '女', 'value' => 0], - ['name' => '男', 'value' => 1], - ['name' => '其它', 'value' => 2], - ], - ], - [ - 'name' => '状态', - 'code' => 'status', - 'description' => '状态字段可以用这个', - 'list' => [ - ['name' => '禁用', 'value' => 0], - ['name' => '启用', 'value' => 1], - ], - ], - [ - 'name' => '岗位', - 'code' => 'post', - 'description' => '岗位字段', - 'list' => [ - ['name' => '总经理', 'value' => 1], - ['name' => '总监', 'value' => 2], - ['name' => '人事主管', 'value' => 3], - ['name' => '开发部主管', 'value' => 4], - ['name' => '普通职员', 'value' => 5], - ['name' => '其它', 'value' => 999], - ], - ], - [ - 'name' => '任务状态', - 'code' => 'taskStatus', - 'description' => '任务状态字段可以用它', - 'list' => [ - ['name' => '失败', 'value' => 0], - ['name' => '成功', 'value' => 1], - ], - ], -]; diff --git a/config/event.php b/config/event.php index 28a3b2c..fe3e7dd 100644 --- a/config/event.php +++ b/config/event.php @@ -1,5 +1,8 @@ [ + [app\events\SystemConfig::class, 'reload'], + ], ]; diff --git a/config/plugin/webman/validation/app.php b/config/plugin/webman/validation/app.php new file mode 100644 index 0000000..64c2382 --- /dev/null +++ b/config/plugin/webman/validation/app.php @@ -0,0 +1,8 @@ + true, + 'exception' => ValidationException::class, +]; \ No newline at end of file diff --git a/config/plugin/webman/validation/command.php b/config/plugin/webman/validation/command.php new file mode 100644 index 0000000..643c36f --- /dev/null +++ b/config/plugin/webman/validation/command.php @@ -0,0 +1,7 @@ + [ + Middleware::class, + ], +]; \ No newline at end of file diff --git a/config/system-file/dict.json b/config/system-file/dict.json new file mode 100644 index 0000000..21ad541 --- /dev/null +++ b/config/system-file/dict.json @@ -0,0 +1,45 @@ +[ + { + "name": "性别", + "code": "gender", + "description": "这是一个性别字典", + "list": [ + { "name": "女", "value": 0 }, + { "name": "男", "value": 1 }, + { "name": "其它", "value": 2 } + ] + }, + { + "name": "状态", + "code": "status", + "description": "状态字段可以用这个", + "list": [ + { "name": "禁用", "value": 0 }, + { "name": "启用", "value": 1 } + ] + }, + { + "name": "岗位", + "code": "post", + "description": "岗位字段", + "list": [ + { "name": "总经理", "value": 1 }, + { "name": "总监", "value": 2 }, + { "name": "人事主管", "value": 3 }, + { "name": "开发部主管", "value": 4 }, + { "name": "普通职员", "value": 5 }, + { "name": "其它", "value": 999 } + ] + }, + { + "name": "任务状态", + "code": "taskStatus", + "description": "任务状态字段可以用它", + "list": [ + { "name": "失败", "value": 0 }, + { "name": "成功", "value": 1 } + ] + } +] + + diff --git a/config/system-file/menu.json b/config/system-file/menu.json new file mode 100644 index 0000000..99c3290 --- /dev/null +++ b/config/system-file/menu.json @@ -0,0 +1,1059 @@ +[ + { + "id": "01", + "parentId": "0", + "path": "/home", + "name": "home", + "component": "home/home", + "meta": { + "title": "平台首页", + "hide": false, + "disable": false, + "keepAlive": false, + "affix": true, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin", "common"], + "svgIcon": "home", + "icon": "", + "sort": 1, + "type": 2 + } + }, + { + "id": "02", + "parentId": "0", + "path": "/order", + "name": "order", + "redirect": "/order/order-list", + "meta": { + "title": "收款订单", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin", "common"], + "svgIcon": "order", + "icon": "", + "sort": 2, + "type": 1 + } + }, + { + "id": "0201", + "parentId": "02", + "path": "/order/order-list", + "name": "order-list", + "component": "order/order-list/index", + "meta": { + "title": "订单管理", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin", "common"], + "icon": "icon-file", + "sort": 1, + "type": 2 + } + }, + { + "id": "0202", + "parentId": "02", + "path": "/order/refund", + "name": "refund", + "component": "order/refund/index", + "meta": { + "title": "退款管理", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin", "common"], + "icon": "icon-loop", + "sort": 2, + "type": 2 + } + }, + { + "id": "0203", + "parentId": "02", + "path": "/order/exception", + "name": "exception", + "component": "order/exception/index", + "meta": { + "title": "异常订单", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin", "common"], + "icon": "icon-bug", + "sort": 3, + "type": 2 + } + }, + { + "id": "0204", + "parentId": "02", + "path": "/order/order-log", + "name": "order-log", + "component": "order/order-log/index", + "meta": { + "title": "订单日志", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-book", + "sort": 4, + "type": 2 + } + }, + { + "id": "0205", + "parentId": "02", + "path": "/order/export", + "name": "export", + "component": "order/export/index", + "meta": { + "title": "导出订单", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-printer", + "sort": 5, + "type": 2 + } + }, + { + "id": "0206", + "parentId": "02", + "path": "/order/user-statistics", + "name": "user-statistics", + "component": "order/user-statistics/index", + "meta": { + "title": "支付用户统计", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-user", + "sort": 6, + "type": 2 + } + }, + { + "id": "0207", + "parentId": "02", + "path": "/order/blacklist", + "name": "blacklist", + "component": "order/blacklist/index", + "meta": { + "title": "黑名单管理", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-lock", + "sort": 7, + "type": 2 + } + }, + { + "id": "03", + "parentId": "0", + "path": "/merchant", + "name": "merchant", + "redirect": "/merchant/merchant-list", + "meta": { + "title": "商户管理", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin", "common"], + "svgIcon": "merchant", + "icon": "", + "sort": 3, + "type": 1 + } + }, + { + "id": "0301", + "parentId": "03", + "path": "/merchant/merchant-list", + "name": "merchant-list", + "component": "merchant/merchant-list/index", + "meta": { + "title": "商户列表", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin", "common"], + "icon": "icon-user-group", + "sort": 1, + "type": 2 + } + }, + { + "id": "0302", + "parentId": "03", + "path": "/merchant/audit", + "name": "audit", + "component": "merchant/audit/index", + "meta": { + "title": "商户入驻审核", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-idcard", + "sort": 2, + "type": 2 + } + }, + { + "id": "0303", + "parentId": "03", + "path": "/merchant/config", + "name": "config", + "component": "merchant/config/index", + "meta": { + "title": "商户配置", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-tool", + "sort": 3, + "type": 2 + } + }, + { + "id": "0304", + "parentId": "03", + "path": "/merchant/group", + "name": "group", + "component": "merchant/group/index", + "meta": { + "title": "商户分组", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-folder", + "sort": 4, + "type": 2 + } + }, + { + "id": "0305", + "parentId": "03", + "path": "/merchant/funds", + "name": "funds", + "component": "merchant/funds/index", + "meta": { + "title": "商户资金管理", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-bar-chart", + "sort": 5, + "type": 2 + } + }, + { + "id": "0306", + "parentId": "03", + "path": "/merchant/package", + "name": "package", + "component": "merchant/package/index", + "meta": { + "title": "商户套餐管理", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-gift", + "sort": 6, + "type": 2 + } + }, + { + "id": "0307", + "parentId": "03", + "path": "/merchant/statistics", + "name": "statistics", + "component": "merchant/statistics/index", + "meta": { + "title": "商户统计", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-bar-chart", + "sort": 7, + "type": 2 + } + }, + { + "id": "04", + "parentId": "0", + "path": "/finance", + "name": "finance", + "redirect": "/finance/settlement", + "meta": { + "title": "财务中心", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "svgIcon": "financial", + "icon": "", + "sort": 4, + "type": 1 + } + }, + { + "id": "0401", + "parentId": "04", + "path": "/finance/settlement", + "name": "settlement", + "component": "finance/settlement/index", + "meta": { + "title": "结算管理", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-calendar", + "sort": 1, + "type": 2 + } + }, + { + "id": "0402", + "parentId": "04", + "path": "/finance/batch-settlement", + "name": "batch-settlement", + "component": "finance/batch-settlement/index", + "meta": { + "title": "批量结算", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-calendar-clock", + "sort": 2, + "type": 2 + } + }, + { + "id": "0403", + "parentId": "04", + "path": "/finance/settlement-record", + "name": "settlement-record", + "component": "finance/settlement-record/index", + "meta": { + "title": "结算记录", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-book", + "sort": 3, + "type": 2 + } + }, + { + "id": "0404", + "parentId": "04", + "path": "/finance/split", + "name": "split", + "component": "finance/split/index", + "meta": { + "title": "分账管理", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-branch", + "sort": 4, + "type": 2 + } + }, + { + "id": "0405", + "parentId": "04", + "path": "/finance/fee", + "name": "fee", + "component": "finance/fee/index", + "meta": { + "title": "手续费管理", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-tag", + "sort": 5, + "type": 2 + } + }, + { + "id": "0406", + "parentId": "04", + "path": "/finance/reconciliation", + "name": "reconciliation", + "component": "finance/reconciliation/index", + "meta": { + "title": "财务对账", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-file", + "sort": 6, + "type": 2 + } + }, + { + "id": "0407", + "parentId": "04", + "path": "/finance/invoice", + "name": "invoice", + "component": "finance/invoice/index", + "meta": { + "title": "发票管理", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-file-pdf", + "sort": 7, + "type": 2 + } + }, + { + "id": "05", + "parentId": "0", + "path": "/channel", + "name": "channel", + "redirect": "/channel/channel-list", + "meta": { + "title": "支付通道", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "svgIcon": "channel", + "icon": "", + "sort": 5, + "type": 1 + } + }, + { + "id": "0501", + "parentId": "05", + "path": "/channel/channel-list", + "name": "channel-list", + "component": "channel/channel-list/index", + "meta": { + "title": "通道管理", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-cloud", + "sort": 1, + "type": 2 + } + }, + { + "id": "0502", + "parentId": "05", + "path": "/channel/channel-config", + "name": "channel-config", + "component": "channel/channel-config/index", + "meta": { + "title": "通道配置", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-tool", + "sort": 2, + "type": 2 + } + }, + { + "id": "0503", + "parentId": "05", + "path": "/channel/payment-method", + "name": "payment-method", + "component": "channel/payment-method/index", + "meta": { + "title": "支付方式", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-relation", + "sort": 3, + "type": 2 + } + }, + { + "id": "0504", + "parentId": "05", + "path": "/channel/plugin", + "name": "plugin", + "component": "channel/plugin/index", + "meta": { + "title": "支付插件", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-tool", + "sort": 4, + "type": 2 + } + }, + { + "id": "0505", + "parentId": "05", + "path": "/channel/polling", + "name": "polling", + "component": "channel/polling/index", + "meta": { + "title": "通道轮询与容灾", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-loop", + "sort": 5, + "type": 2 + } + }, + { + "id": "0506", + "parentId": "05", + "path": "/channel/monitor", + "name": "monitor", + "component": "channel/monitor/index", + "meta": { + "title": "通道监控", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-desktop", + "sort": 6, + "type": 2 + } + }, + { + "id": "06", + "parentId": "0", + "path": "/risk", + "name": "risk", + "redirect": "/risk/rule", + "meta": { + "title": "风控与安全", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "svgIcon": "risk", + "icon": "", + "sort": 6, + "type": 1 + } + }, + { + "id": "0601", + "parentId": "06", + "path": "/risk/rule", + "name": "rule", + "component": "risk/rule/index", + "meta": { + "title": "风控规则", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-safe", + "sort": 1, + "type": 2 + } + }, + { + "id": "0602", + "parentId": "06", + "path": "/risk/warning", + "name": "warning", + "component": "risk/warning/index", + "meta": { + "title": "风控预警", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-notification", + "sort": 2, + "type": 2 + } + }, + { + "id": "0603", + "parentId": "06", + "path": "/risk/disposal", + "name": "disposal", + "component": "risk/disposal/index", + "meta": { + "title": "风险处置", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-tool", + "sort": 3, + "type": 2 + } + }, + { + "id": "0604", + "parentId": "06", + "path": "/risk/report", + "name": "risk-report", + "component": "risk/report/index", + "meta": { + "title": "风险报告", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-file", + "sort": 4, + "type": 2 + } + }, + { + "id": "0605", + "parentId": "06", + "path": "/risk/security", + "name": "security", + "component": "risk/security/index", + "meta": { + "title": "安全配置", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-lock", + "sort": 5, + "type": 2 + } + }, + { + "id": "08", + "parentId": "0", + "path": "/analysis", + "name": "analysis", + "redirect": "/analysis/transaction", + "meta": { + "title": "运营分析", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "svgIcon": "analysis", + "icon": "", + "sort": 7, + "type": 1 + } + }, + { + "id": "0801", + "parentId": "08", + "path": "/analysis/transaction", + "name": "transaction", + "component": "analysis/transaction/index", + "meta": { + "title": "交易分析", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-bar-chart", + "sort": 1, + "type": 2 + } + }, + { + "id": "0802", + "parentId": "08", + "path": "/analysis/merchant-analysis", + "name": "merchant-analysis", + "component": "analysis/merchant-analysis/index", + "meta": { + "title": "商户分析", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-bar-chart", + "sort": 2, + "type": 2 + } + }, + { + "id": "0803", + "parentId": "08", + "path": "/analysis/finance-analysis", + "name": "finance-analysis", + "component": "analysis/finance-analysis/index", + "meta": { + "title": "财务分析", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-bar-chart", + "sort": 3, + "type": 2 + } + }, + { + "id": "0804", + "parentId": "08", + "path": "/analysis/report", + "name": "analysis-report", + "component": "analysis/report/index", + "meta": { + "title": "报表中心", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-file", + "sort": 4, + "type": 2 + } + }, + { + "id": "07", + "parentId": "0", + "path": "/system", + "name": "system", + "redirect": "/system/base-config", + "meta": { + "title": "系统设置", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "svgIcon": "config", + "sort": 8, + "type": 1 + } + }, + { + "id": "0701", + "parentId": "07", + "path": "/system/base-config", + "name": "base-config", + "component": "system/base-config/index", + "meta": { + "title": "基础配置", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-tool", + "sort": 1, + "type": 2 + } + }, + { + "id": "0702", + "parentId": "07", + "path": "/system/notice-config", + "name": "notice-config", + "component": "system/notice-config/index", + "meta": { + "title": "通知配置", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-notification", + "sort": 2, + "type": 2 + } + }, + { + "id": "0703", + "parentId": "07", + "path": "/system/log", + "name": "log", + "component": "system/log/index", + "meta": { + "title": "日志管理", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-book", + "sort": 3, + "type": 2 + } + }, + { + "id": "0704", + "parentId": "07", + "path": "/system/api", + "name": "api", + "component": "system/api/index", + "meta": { + "title": "API管理", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "icon": "icon-tool", + "sort": 4, + "type": 2 + } + }, + { + "id": "0705", + "parentId": "07", + "path": "/system/userinfo", + "name": "userinfo", + "component": "system/userinfo/userinfo", + "meta": { + "title": "个人信息", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin", "common"], + "icon": "icon-user", + "sort": 10, + "type": 2 + } + }, + { + "id": "09", + "parentId": "0", + "path": "/about", + "name": "about", + "component": "about/about", + "meta": { + "title": "关于项目", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin"], + "svgIcon": "about", + "sort": 9, + "type": 2 + } + } + ] \ No newline at end of file diff --git a/config/system-file/menu.md b/config/system-file/menu.md new file mode 100644 index 0000000..4b21f81 --- /dev/null +++ b/config/system-file/menu.md @@ -0,0 +1,137 @@ +# 系统菜单配置 + +## 配置说明 +系统菜单基于路由配置实现,以下是完整的菜单路由配置项说明,所有配置项均为 JSON 格式,可直接用于前端路由解析。 + +## 核心配置结构 +```json +{ + "id": "01", + "parentId": "0", + "path": "/home", + "name": "home", + "component": "home/home", + "meta": { + "title": "home", + "hide": false, + "disable": false, + "keepAlive": false, + "affix": true, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin", "common"], + "svgIcon": "home", + "icon": "", + "sort": 1, + "type": 2 + }, + "children": null +} +``` + +## 字段详细说明 +| 一级字段 | 类型 | 必填 | 说明 | +|----------|--------|------|----------------------------------------------------------------------| +| `id` | string | 是 | 路由唯一标识,建议按「层级+序号」命名(如 01=首页,0201=订单管理)| +| `parentId` | string | 是 | 父路由ID,顶层路由固定为 `0`,子路由对应父路由的 `id` | +| `path` | string | 是 | 路由访问路径(如 `/home`、`/order/order-list`)| +| `name` | string | 是 | 路由名称,需与组件名/路径名保持一致,用于路由跳转标识 | +| `component` | string | 否 | 组件文件路径(基于 `src/views` 目录),如 `home/home` 对应 `src/views/home/home.vue`;目录级路由可省略 | +| `meta` | object | 是 | 路由元信息,包含菜单展示、权限、样式等核心配置 | +| `children` | array | 否 | 子路由列表,目录级路由(`type:1`)需配置,菜单级路由(`type:2`)默认为 `null` | + +### `meta` 子字段说明 +| 子字段 | 类型 | 必填 | 默认值 | 说明 | +|-----------|---------|------|--------|----------------------------------------------------------------------| +| `title` | string | 是 | - | 菜单显示标题:
1. 填写国际化key(如 `home`),自动匹配多语言;
2. 无对应key则直接展示文字 | +| `hide` | boolean | 否 | false | 是否隐藏菜单:
✅ true = 不显示在侧边栏,但可正常访问;
❌ false = 正常显示 | +| `disable` | boolean | 否 | false | 是否停用路由:
✅ true = 不显示+不可访问;
❌ false = 正常可用 | +| `keepAlive` | boolean | 否 | false | 是否缓存组件:
✅ true = 切换路由不销毁组件;
❌ false = 切换后销毁 | +| `affix` | boolean | 否 | false | 是否固定在标签栏:
✅ true = 标签栏无关闭按钮;
❌ false = 可关闭 | +| `link` | string | 否 | "" | 外链地址,填写后路由跳转至该地址(优先级高于 `component`)| +| `iframe` | boolean | 否 | false | 是否内嵌外链:
✅ true = 在页面内以iframe展示 `link` 地址;
❌ false = 跳转新页面 | +| `isFull` | boolean | 否 | false | 是否全屏显示:
✅ true = 菜单页面占满整个视口;
❌ false = 保留侧边栏/头部 | +| `roles` | array | 否 | [] | 路由权限角色:
如 `["admin", "common"]`,仅对应角色可访问该菜单 | +| `svgIcon` | string | 否 | "" | SVG菜单图标:
优先级高于 `icon`,取值为 `src/assets/svgs` 目录下的SVG文件名 | +| `icon` | string | 否 | "" | 普通图标:
默认使用 Arco Design 图标库,填写图标名(如 `icon-file`)即可 | +| `sort` | number | 否 | 0 | 菜单排序:数值越小,展示越靠前 | +| `type` | number | 是 | - | 路由类型:
1 = 目录(仅作为父级,无组件);
2 = 菜单(可访问的页面);
3 = 按钮(权限控制用) | + +## 配置示例 +### 1. 顶层菜单(首页) +```json +{ + "id": "01", + "parentId": "0", + "path": "/home", + "name": "home", + "component": "home/home", + "meta": { + "title": "平台首页", + "hide": false, + "disable": false, + "keepAlive": false, + "affix": true, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin", "common"], + "svgIcon": "home", + "icon": "", + "sort": 1, + "type": 2 + }, + "children": null +} +``` + +### 2. 目录级路由(收款订单) +```json +{ + "id": "02", + "parentId": "0", + "path": "/order", + "name": "order", + "redirect": "/order/order-list", + "meta": { + "title": "收款订单", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin", "common"], + "svgIcon": "order", + "icon": "", + "sort": 2, + "type": 1 + }, + "children": [ + { + "id": "0201", + "parentId": "02", + "path": "/order/order-list", + "name": "order-list", + "component": "order/order-list/index", + "meta": { + "title": "订单管理", + "hide": false, + "disable": false, + "keepAlive": true, + "affix": false, + "link": "", + "iframe": false, + "isFull": false, + "roles": ["admin", "common"], + "icon": "icon-file", + "sort": 1, + "type": 2 + }, + "children": null + } + ] +} +``` diff --git a/database/ma_system_config.sql b/database/ma_system_config.sql new file mode 100644 index 0000000..29ed468 --- /dev/null +++ b/database/ma_system_config.sql @@ -0,0 +1,11 @@ +-- 系统配置表 +CREATE TABLE IF NOT EXISTS `ma_system_config` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `config_key` varchar(100) NOT NULL DEFAULT '' COMMENT '配置项键名(唯一标识,直接使用字段名)', + `config_value` text COMMENT '配置项值(支持字符串、数字、JSON等)', + `created_at` datetime DEFAULT NULL COMMENT '创建时间', + `updated_at` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_config_key` (`config_key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统配置表'; + diff --git a/doc/event.md b/doc/event.md new file mode 100644 index 0000000..897d6d2 --- /dev/null +++ b/doc/event.md @@ -0,0 +1,112 @@ +event事件处理 +webman/event 提供一种精巧的事件机制,可实现在不侵入代码的情况下执行一些业务逻辑,实现业务模块之间的解耦。典型的场景如一个新用户注册成功时,只要发布一个自定义事件如user.register,各个模块遍能收到该事件执行相应的业务逻辑。 + +安装 +composer require webman/event + +订阅事件 +订阅事件统一通过文件config/event.php来配置 + + [ + [app\event\User::class, 'register'], + // ...其它事件处理函数... + ], + 'user.logout' => [ + [app\event\User::class, 'logout'], + // ...其它事件处理函数... + ] +]; +说明: + +user.register user.logout 等是事件名称,字符串类型,建议小写单词并以点(.)分割 +一个事件可以对应多个事件处理函数,调用顺序为配置的顺序 +事件处理函数 +事件处理函数可以是任意的类方法、函数、闭包函数等。 +例如创建事件处理类 app/event/User.php (目录不存在请自行创建) + + 'webman', + 'age' => 2 + ]; + Event::dispatch('user.register', $user); + } +} +发布事件有两个函数,Event::dispatch($event_name, $data); 和 Event::emit($event_name, $data); 二者参数一样。 +区别是emit内部会自动捕获异常,也就是说如果一个事件有多个处理函数,某个处理函数发生异常不会影响其它处理函数的执行。 +而dispatch则内部不会自动捕获异常,当前事件的任何一个处理函数发生异常,则停止执行下一个处理函数并直接向上抛出异常。 + +提示 +参数$data可以是任意的数据,例如数组、类实例、字符串等 + +通配符事件监听 +通配符注册监听允许您在同一个监听器上处理多个事件,例如config/event.php里配置 + + [ + [app\event\User::class, 'deal'] + ], +]; +我们可以通过事件处理函数第二个参数$event_data获得具体的事件名 + + [ + function($user){ + var_dump($user); + } + ] +]; +查看事件及监听器 +使用命令 php webman event:list 查看项目配置的所有事件及监听器 + +支持范围 +除了主项目基础插件和应用插件同样支持event.php配置。 +基础插件配置文件 config/plugin/插件厂商/插件名/event.php +应用插件配置文件 plugin/插件名/config/event.php + +注意事项 +event事件处理并不是异步的,event不适合处理慢业务,慢业务应该用消息队列处理 \ No newline at end of file diff --git a/doc/exception.md b/doc/exception.md new file mode 100644 index 0000000..c295218 --- /dev/null +++ b/doc/exception.md @@ -0,0 +1,114 @@ +异常处理 +配置 +config/exception.php + +return [ + // 这里配置异常处理类 + '' => support\exception\Handler::class, +]; +多应用模式时,你可以为每个应用单独配置异常处理类,参见多应用 + +默认异常处理类 +webman中异常默认由 support\exception\Handler 类来处理。可修改配置文件config/exception.php来更改默认异常处理类。异常处理类必须实现Webman\Exception\ExceptionHandlerInterface 接口。 + +interface ExceptionHandlerInterface +{ + /** + * 记录日志 + * @param Throwable $e + * @return mixed + */ + public function report(Throwable $e); + + /** + * 渲染返回 + * @param Request $request + * @param Throwable $e + * @return Response + */ + public function render(Request $request, Throwable $e) : Response; +} +渲染响应 +异常处理类中的render方法是用来渲染响应的。 + +如果配置文件config/app.php中debug值为true(以下简称app.debug=true),将返回详细的异常信息,否则将返回简略的异常信息。 + +如果请求期待是json返回,则返回的异常信息将以json格式返回,类似 + +{ + "code": "500", + "msg": "异常信息" +} +如果app.debug=true,json数据里会额外增加一个trace字段返回详细的调用栈。 + +你可以编写自己的异常处理类来更改默认异常处理逻辑。 + +业务异常 BusinessException +有时候我们想在某个嵌套函数里终止请求并返回一个错误信息给客户端,这时可以通过抛出BusinessException来做到这点。 +例如: + +checkInput($request->post()); + return response('hello index'); + } + + protected function checkInput($input) + { + if (!isset($input['token'])) { + throw new BusinessException('参数错误', 3000); + } + } +} +以上示例会返回一个 + +{"code": 3000, "msg": "参数错误"} +注意 +业务异常BusinessException不需要业务try捕获,框架会自动捕获并根据请求类型返回合适的输出。 + +自定义业务异常 +如果以上响应不符合你的需求,例如想把msg要改为message,可以自定义一个MyBusinessException + +新建 app/exception/MyBusinessException.php 内容如下 + +expectsJson()) { + return json(['code' => $this->getCode() ?: 500, 'message' => $this->getMessage()]); + } + // 非json请求则返回一个页面 + return new Response(200, [], $this->getMessage()); + } +} +这样当业务调用 + +use app\exception\MyBusinessException; + +throw new MyBusinessException('参数错误', 3000); +json请求将收到一个类似如下的json返回 + +{"code": 3000, "message": "参数错误"} +提示 +因为BusinessException异常属于业务异常(例如用户输入参数错误),它是可预知的,所以框架并不会认为它是致命错误,并不会记录日志。 + +总结 +在任何想中断当前请求并返回信息给客户端的时候可以考虑使用BusinessException异常。 \ No newline at end of file diff --git a/doc/skill.md b/doc/skill.md new file mode 100644 index 0000000..f2f369a --- /dev/null +++ b/doc/skill.md @@ -0,0 +1,323 @@ +# MPAY V2 项目技术栈与结构文档 + +## 1. 项目概述 + +MPAY V2 是一个基于 Webman 后端框架和 Vue 3 前端框架的支付管理系统,提供完整的支付业务管理功能,包括用户认证、菜单管理、系统配置、财务管理、渠道管理和数据分析等核心模块。 + +## 2. 技术架构 + +### 2.1 后端技术栈 + +| 类别 | 技术/框架 | 版本 | 用途 | 来源 | +|------|-----------|------|------|------| +| 基础框架 | Webman | ^2.1 | 高性能HTTP服务框架 | composer.json:28 | +| PHP版本 | PHP | >=8.1 | 开发语言 | composer.json:27 | +| 数据库 | webman/database | ^2.1 | 数据库操作 | composer.json:31 | +| 缓存 | Redis | ^2.1 | 缓存存储 | composer.json:32 | +| 缓存 | webman/cache | ^2.1 | 缓存管理 | composer.json:34 | +| 认证 | JWT | ^7.0 | 用户认证 | composer.json:42 | +| 验证码 | webman/captcha | ^1.0 | 登录验证码 | composer.json:37 | +| 事件系统 | webman/event | ^1.0 | 事件管理 | composer.json:38 | +| 配置管理 | vlucas/phpdotenv | ^5.6 | 环境变量 | composer.json:39 | +| 定时任务 | workerman/crontab | ^1.0 | 定时任务 | composer.json:40 | +| 队列 | webman/redis-queue | ^2.1 | 消息队列 | composer.json:41 | +| 验证 | topthink/think-validate | ^3.0 | 数据验证 | composer.json:36 | +| 容器 | php-di/php-di | 7.0 | 依赖注入 | composer.json:30 | +| 日志 | monolog/monolog | ^2.0 | 日志管理 | composer.json:29 | +| 控制台 | webman/console | ^2.1 | 命令行工具 | composer.json:35 | + +### 2.2 前端技术栈 + +| 类别 | 技术/框架 | 版本 | 用途 | 来源 | +|------|-----------|------|------|------| +| 基础框架 | Vue | ^3.5.15 | 前端框架 | package.json:61 | +| 语言 | TypeScript | ^5.2.2 | 开发语言 | package.json:103 | +| 构建工具 | Vite | ^6.3.5 | 构建工具 | package.json:107 | +| UI框架 | Arco Design | ^2.57.0 | 界面组件库 | package.json:72 | +| 状态管理 | Pinia | ^2.3.0 | 状态管理 | package.json:53 | +| 路由 | Vue Router | ^4.3.0 | 前端路由 | package.json:66 | +| HTTP客户端 | Axios | ^1.6.8 | API调用 | package.json:47 | +| 表单生成 | @form-create/arco-design | ^3.2.37 | 动态表单 | package.json:41 | +| 图表 | @visactor/vchart | ^1.11.0 | 数据可视化 | package.json:42 | +| 代码编辑器 | CodeMirror | ^6.0.1 | 代码编辑 | package.json:48 | +| 富文本编辑器 | @wangeditor/editor | ^5.1.23 | 内容编辑 | package.json:45 | +| 国际化 | vue-i18n | 10.0.0-alpha.3 | 多语言支持 | package.json:64 | +| 工具库 | @vueuse/core | ^12.4.0 | 实用工具 | package.json:44 | +| 指纹识别 | @fingerprintjs/fingerprintjs | ^4.6.2 | 设备识别 | package.json:40 | +| 二维码 | qrcode | ^1.5.4 | 二维码生成 | package.json:57 | +| 条码 | jsbarcode | ^3.11.6 | 条码生成 | package.json:51 | +| 打印 | print-js | ^1.6.0 | 页面打印 | package.json:56 | +| 进度条 | nprogress | ^0.2.0 | 加载进度 | package.json:52 | +| 中文转拼音 | pinyin-pro | ^3.26.0 | 拼音转换 | package.json:55 | +| 引导 | driver.js | ^1.3.1 | 功能引导 | package.json:49 | + +## 3. 项目结构 + +### 3.1 后端目录结构 + +``` +d:\phpstudy_pro\WWW\mpay\mpay_v2_webman\ +├── app/ # 应用代码 +│ ├── common/ # 通用代码 +│ │ ├── base/ # 基础类 +│ │ │ ├── BaseController.php +│ │ │ ├── BaseModel.php +│ │ │ ├── BaseRepository.php +│ │ │ └── BaseService.php +│ │ ├── constants/ # 常量 +│ │ │ └── YesNo.php +│ │ ├── enums/ # 枚举 +│ │ │ └── MenuType.php +│ │ ├── middleware/ # 中间件 +│ │ │ ├── Cors.php +│ │ │ └── StaticFile.php +│ │ └── utils/ # 工具类 +│ │ └── JwtUtil.php +│ ├── events/ # 事件 +│ │ └── SystemConfig.php +│ ├── exceptions/ # 异常处理 +│ │ └── ValidationException.php +│ ├── http/ # HTTP相关 +│ │ ├── admin/ # 后台管理 +│ │ │ ├── controller/ # 控制器 +│ │ │ │ ├── AuthController.php +│ │ │ │ ├── MenuController.php +│ │ │ │ ├── SystemController.php +│ │ │ │ └── UserController.php +│ │ │ └── middleware/ # 中间件 +│ │ │ └── AuthMiddleware.php +│ ├── models/ # 数据模型 +│ │ ├── SystemConfig.php +│ │ └── User.php +│ ├── process/ # 进程管理 +│ │ ├── Http.php +│ │ └── Monitor.php +│ ├── repositories/ # 数据仓库 +│ │ ├── SystemConfigRepository.php +│ │ └── UserRepository.php +│ ├── routes/ # 路由配置 +│ │ ├── admin.php +│ │ ├── api.php +│ │ └── mer.php +│ ├── services/ # 业务逻辑 +│ │ ├── AuthService.php +│ │ ├── CaptchaService.php +│ │ ├── MenuService.php +│ │ ├── SystemConfigService.php +│ │ ├── SystemSettingService.php +│ │ └── UserService.php +│ └── validation/ # 数据验证 +│ └── SystemConfigValidator.php +├── config/ # 配置文件 +│ ├── base-config/ # 基础配置 +│ │ ├── basic.json +│ │ ├── email.json +│ │ ├── permission.json +│ │ └── tabs.json +│ ├── plugin/ # 插件配置 +│ │ ├── webman/ +│ │ │ ├── console/ +│ │ │ ├── event/ +│ │ │ ├── redis-queue/ +│ │ │ └── validation/ +│ ├── system-file/ # 系统文件 +│ │ ├── dict.json +│ │ ├── menu.json +│ │ └── menu.md +│ ├── app.php +│ ├── autoload.php +│ ├── bootstrap.php +│ ├── cache.php +│ ├── container.php +│ ├── database.php +│ ├── dependence.php +│ ├── event.php +│ ├── exception.php +│ ├── jwt.php +│ ├── log.php +│ ├── menu.php +│ ├── middleware.php +│ ├── process.php +│ ├── redis.php +│ ├── route.php +│ ├── server.php +│ ├── session.php +│ ├── static.php +│ ├── translation.php +│ └── view.php +├── database/ # 数据库文件 +│ └── ma_system_config.sql +├── doc/ # 文档 +│ ├── event.md +│ └── exception.md +├── public/ # 静态资源 +│ └── favicon.ico +├── resource/ # 资源文件 +│ └── mpay_v2_admin/ # 前端项目 +├── .env # 环境变量 +├── composer.json # PHP依赖 +└── composer.lock # 依赖锁定 +``` + +### 3.2 前端目录结构 + +``` +d:\phpstudy_pro\WWW\mpay\mpay_v2_webman\resource\mpay_v2_admin\ +├── src/ # 源代码 +│ ├── api/ # API调用 +│ ├── assets/ # 静态资源 +│ ├── components/ # 组件 +│ ├── config/ # 配置 +│ ├── directives/ # 指令 +│ ├── hooks/ # 钩子 +│ ├── lang/ # 国际化 +│ ├── layout/ # 布局 +│ ├── mock/ # 模拟数据 +│ ├── router/ # 路由 +│ ├── store/ # 状态管理 +│ ├── style/ # 样式 +│ ├── typings/ # 类型定义 +│ ├── utils/ # 工具函数 +│ ├── views/ # 页面 +│ ├── App.vue # 根组件 +│ ├── auto-import.d.ts # 自动导入 +│ ├── components.d.ts # 组件声明 +│ ├── main.ts # 入口文件 +│ └── style.css # 全局样式 +├── build/ # 构建配置 +│ ├── optimize.ts +│ └── vite-plugin.ts +├── .env # 环境变量 +├── .env.development # 开发环境变量 +├── .env.production # 生产环境变量 +├── .env.test # 测试环境变量 +├── eslint.config.js # ESLint配置 +├── index.html # HTML模板 +├── package.json # 前端依赖 +└── vite.config.ts # Vite配置 +``` + +## 4. 核心功能模块 + +### 4.1 后端核心模块 + +| 模块 | 主要功能 | 文件位置 | 来源 | +|------|----------|----------|------| +| 认证模块 | 用户登录、验证码生成 | app/http/admin/controller/AuthController.php | app/routes/admin.php:20-21 | +| 用户模块 | 获取用户信息 | app/http/admin/controller/UserController.php | app/routes/admin.php:26 | +| 菜单模块 | 获取路由菜单 | app/http/admin/controller/MenuController.php | app/routes/admin.php:29 | +| 系统模块 | 字典管理、配置管理 | app/http/admin/controller/SystemController.php | app/routes/admin.php:32-37 | + +### 4.2 前端核心模块 + +| 模块 | 主要功能 | 文件位置 | 来源 | +|------|----------|----------|------| +| 布局模块 | 系统整体布局 | src/layout/ | resource/mpay_v2_admin/src/layout/ | +| 认证模块 | 登录、权限控制 | src/views/login/ | resource/mpay_v2_admin/src/views/ | +| 首页模块 | 数据概览 | src/views/home/ | resource/mpay_v2_admin/src/views/home/ | +| 财务管理 | 结算、对账、发票 | src/views/finance/ | resource/mpay_v2_admin/src/views/finance/ | +| 渠道管理 | 通道配置、支付方式 | src/views/channel/ | resource/mpay_v2_admin/src/views/channel/ | +| 数据分析 | 交易分析、商户分析 | src/views/analysis/ | resource/mpay_v2_admin/src/views/analysis/ | +| 系统设置 | 系统配置、字典管理 | src/views/system/ | resource/mpay_v2_admin/src/views/ | + +## 5. API接口设计 + +### 5.1 认证接口 + +| 路径 | 方法 | 模块/文件 | 功能 | 权限 | 来源 | +|------|------|-----------|------|------|------| +| /adminapi/captcha | GET | AuthController | 获取验证码 | 无 | app/routes/admin.php:20 | +| /adminapi/login | POST | AuthController | 用户登录 | 无 | app/routes/admin.php:21 | + +### 5.2 用户接口 + +| 路径 | 方法 | 模块/文件 | 功能 | 权限 | 来源 | +|------|------|-----------|------|------|------| +| /adminapi/user/getUserInfo | GET | UserController | 获取用户信息 | JWT | app/routes/admin.php:26 | + +### 5.3 菜单接口 + +| 路径 | 方法 | 模块/文件 | 功能 | 权限 | 来源 | +|------|------|-----------|------|------|------| +| /adminapi/menu/getRouters | GET | MenuController | 获取路由菜单 | JWT | app/routes/admin.php:29 | + +### 5.4 系统接口 + +| 路径 | 方法 | 模块/文件 | 功能 | 权限 | 来源 | +|------|------|-----------|------|------|------| +| /adminapi/system/getDict[/{code}] | GET | SystemController | 获取字典数据 | JWT | app/routes/admin.php:32 | +| /adminapi/system/base-config/tabs | GET | SystemController | 获取配置标签 | JWT | app/routes/admin.php:35 | +| /adminapi/system/base-config/form/{tabKey} | GET | SystemController | 获取表单配置 | JWT | app/routes/admin.php:36 | +| /adminapi/system/base-config/submit/{tabKey} | POST | SystemController | 提交配置 | JWT | app/routes/admin.php:37 | + +## 6. 技术特点 + +### 6.1 后端特点 + +1. **高性能架构**:基于 Webman 框架,使用 Workerman 作为底层,支持高并发处理 +2. **模块化设计**:采用分层架构,清晰分离控制器、服务、仓库和模型 +3. **JWT认证**:使用 JSON Web Token 实现无状态认证 +4. **中间件机制**:通过中间件实现请求拦截和权限控制 +5. **Redis集成**:使用 Redis 作为缓存和队列存储 +6. **事件系统**:支持事件驱动架构 +7. **定时任务**:内置定时任务管理功能 +8. **数据验证**:使用 think-validate 进行数据验证 +9. **依赖注入**:使用 PHP-DI 实现依赖注入 +10. **日志管理**:使用 Monolog 进行日志管理 + +### 6.2 前端特点 + +1. **Vue 3 + TypeScript**:使用最新的 Vue 3 组合式 API 和 TypeScript 提供类型安全 +2. **Arco Design**:采用字节跳动开源的 Arco Design UI 组件库,提供美观的界面 +3. **Pinia 状态管理**:使用 Pinia 替代 Vuex,提供更简洁的状态管理方案 +4. **Vite 构建工具**:使用 Vite 提供快速的开发体验和优化的构建输出 +5. **国际化支持**:内置多语言支持,可轻松切换语言 +6. **响应式设计**:适配不同屏幕尺寸的设备 +7. **丰富的功能组件**:集成多种实用组件,如二维码生成、条码生成、富文本编辑等 +8. **权限控制**:基于指令的权限控制机制 +9. **Mock 数据**:内置 Mock 数据,方便开发和测试 + +## 7. 开发流程 + +### 7.1 后端开发 + +1. **环境准备**:PHP 8.1+,Composer,MySQL,Redis +2. **依赖安装**:`composer install` +3. **配置环境**:复制 `.env.example` 为 `.env` 并配置相关参数 +4. **启动服务**:`php start.php start` +5. **代码结构**:遵循 Webman 框架规范,按模块组织代码 + +### 7.2 前端开发 + +1. **环境准备**:Node.js 18.12+,PNPM 8.7+ +2. **依赖安装**:`pnpm install` +3. **开发模式**:`pnpm dev` +4. **构建部署**:`pnpm build:prod` +5. **代码结构**:遵循 Vue 3 项目规范,按功能模块组织代码 + +## 8. 部署与配置 + +### 8.1 后端部署 + +1. **服务器要求**:Linux/Unix 系统,PHP 8.1+,MySQL 5.7+,Redis 5.0+ +2. **Nginx 配置**:配置反向代理指向 Webman 服务 +3. **启动方式**: + - 开发环境:`php start.php start` + - 生产环境:`php start.php start -d` +4. **监控管理**:可使用 Supervisor 管理进程 + +### 8.2 前端部署 + +1. **构建**:`pnpm build:prod` +2. **部署**:将 `dist` 目录部署到 Web 服务器 +3. **Nginx 配置**:配置静态文件服务和路由重写 + +## 9. 总结 + +MPAY V2 项目采用现代化的技术栈和架构设计,后端使用 Webman 框架提供高性能的 API 服务,前端使用 Vue 3 + TypeScript + Arco Design 提供美观、响应式的用户界面。项目结构清晰,模块化程度高,便于维护和扩展。 + +核心功能覆盖了支付管理系统的主要业务场景,包括用户认证、菜单管理、系统配置、财务管理、渠道管理和数据分析等模块,为支付业务的运营和管理提供了完整的解决方案。 + +技术特点包括高性能架构、模块化设计、JWT认证、Redis集成、Vue 3组合式API、TypeScript类型安全、Arco Design UI组件库、Pinia状态管理、Vite构建工具等,确保了系统的稳定性、安全性和可扩展性。 + +该项目适合作为支付管理系统的基础框架,可根据具体业务需求进行定制和扩展。 \ No newline at end of file diff --git a/support/helpers.php b/support/helpers.php index 5ad4b10..7cd1c16 100644 --- a/support/helpers.php +++ b/support/helpers.php @@ -1,2 +1,38 @@