[
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 saithink\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img src=\"https://saithink.top/images/logo.png\" width=\"120\" />\n</p>\n<p align=\"center\">\n  <img src=\"https://svg.hamm.cn/badge.svg?key=License&value=MIT\" />\n  <img src=\"https://svg.hamm.cn/badge.svg?key=Version&value=6.x\" />\n</p>\n\n<div style=\"padding:18px;max-width: 1024px;margin:0 auto;\">\n<h1>SaiAdmin 6.x</h1>\n\n基于<a href=\"https://www.workerman.net/doc/webman/\" target=\"_blank\">webman</a>(高性能HTTP服务框架)开箱即用的高质量中后台管理系统\n\n<h1>安装</h1>\n\ncomposer环境的安装命令如下\n\n```bash\ncomposer require saithink/saiadmin\n```\n\n启动方式请查看webman官方文档\n\n<h1>功能列表</h1>\n\n• 用户管理，用户添加、修改、删除，支持不同用户登录后台看到不同的首页\n\n• 部门管理，部门组织机构（公司、部门、小组），树结构方式展现适应各种结构\n\n• 岗位管理，可以给用户配置所担任职务\n\n• 角色管理，树结构设计，支持角色菜单和按钮权限分配，支持角色数据权限分配、强大的角色管理体系\n\n• 菜单管理，配置系统菜单和按钮等\n\n• 字典管理，对系统中经常使用并且固定的数据可以重复使用和维护\n\n• 系统配置，系统的一些常用设置管理\n\n• 操作日志，用户对系统的一些正常操作的查询\n\n• 登录日志，用户登录系统的记录查询\n\n• 服务监控，查看当前服务器状态和PHP环境等信息\n\n• 附件管理，管理当前系统上传的文件及图片等信息\n\n• 数据表维护，对系统的数据表可以进行清理碎片和优化\n\n• 定时任务，在线（添加、修改、删除)任务调度包含执行结果日志\n\n• 代码生成，前后端代码的生成（php、vue、js、sql），支持一键下载和一键生成到项目中\n\n• 邮件服务，内置邮件发送服务\n\n• 文件上传，支持本地、七牛云、阿里云、腾讯云上、S3上传\n\n• 应用市场，生态丰富，官网上架官方和开发人员的插件应用\n\n• 兼容性强，基于webman插件形式开发，对webman无侵入，和webman各种应用插件共存\n\n<h1>学习</h1>\n\n<ul>\n  <li>\n    <a href=\"https://saithink.top\" target=\"_blank\">主页 / Home page</a>\n  </li>\n</ul>\n\n\n<h1>演示地址</h1>\n<p>演示地址： <a href=\"http://v6.saithink.top\">http://v6.saithink.top</a></p>\n<p>演示账号：admin</p>\n<p>演示密码：123456</p>\n\n<h1>共同交流</h1>\n\n<table>\n  <tbody>\n    <tr>\n      <td align=\"center\" valign=\"middle\">\n        <img src=\"https://saithink.top/images/me.png\" class=\"no-zoom\" width=\"180px\">\n        <p>saiadmin交流群(添加我微信备注\"saiadmin\")</p>\n      </td>\n    </tr>\n  </tbody>\n</table>\n\n<h1>支持项目</h1>\n\n如果您正在使用这个项目并感觉良好，或者是想支持我继续开发，您可以通过如下`任意`方式支持我：\n\n谢谢！ ❤️\n\n\n|                                       微信                                       |                                      支付宝                                      |\n| :------------------------------------------------------------------------------: | :------------------------------------------------------------------------------: |\n| <img src=\"https://saithink.top/images/wechat.png\" alt=\"Wechat QRcode\" width=180> | <img src=\"https://saithink.top/images/alipay.png\" alt=\"Alipay QRcode\" width=180> |\n\n<div style=\"clear: both\">\n<h1>LICENSE</h1>\nThis project is open-sourced software licensed under the MIT.\n</div>\n\n</div>\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"saithink/saiadmin\",\n    \"description\": \"webman plugin\",\n    \"type\": \"library\",\n    \"license\": \"MIT\",\n    \"require\": {\n        \"php\": \">=8.1\",\n        \"webman/think-orm\": \"^2.1\",\n        \"webman/think-cache\": \"^2.1\",\n        \"webman/cache\": \"^2.1\",\n        \"webman/redis\": \"^2.1\",\n        \"webman/captcha\": \"^1.0\",\n        \"webman/event\": \"^1.0\",\n        \"webman/channel\": \"^2.1\",\n        \"webman/console\": \"^2.1\",\n        \"webman/database\": \"^2.1\",\n        \"illuminate/pagination\": \"^12.43\",\n        \"illuminate/events\": \"^12.43\",\n        \"workerman/crontab\": \"^1.0\",\n        \"tinywan/jwt\": \"^1.11\",\n        \"tinywan/storage\": \"^1.1\",\n        \"guzzlehttp/guzzle\": \"^7.9\",\n        \"topthink/think-validate\": \"^3.0\",\n        \"topthink/think-orm\": \"^2.0.53 || ^3.0.0\",\n        \"ramsey/uuid\": \"^4.0\",\n        \"zoujingli/ip2region\": \"^2.0\",\n        \"openspout/openspout\": \"^4.0\",\n        \"phpmailer/phpmailer\": \"^6.9\",\n        \"godruoyi/php-snowflake\": \"^3.0.0\",\n        \"vlucas/phpdotenv\": \"^5.6\",\n        \"twig/twig\": \"^3.20\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"Webman\\\\saiadmin\\\\\": \"src/\"\n        }\n    },\n    \"authors\": [\n        {\n            \"name\": \"saithink\",\n            \"email\": \"1430792918@qq.com\"\n        }\n    ]\n}\n"
  },
  {
    "path": "src/Install.php",
    "content": "<?php\nnamespace Webman\\saiadmin;\n\nclass Install\n{\n    const WEBMAN_PLUGIN = true;\n\n    /**\n     * @var array\n     */\n    protected static $pathRelation = [\n        'plugin/saiadmin' => 'plugin/saiadmin'\n    ];\n\n    /**\n     * Install\n     * @return void\n     */\n    public static function install()\n    {\n        static::installByRelation();\n        static::addMethod();\n    }\n\n    /**\n     * Uninstall\n     * @return void\n     */\n    public static function uninstall()\n    {\n        self::uninstallByRelation();\n    }\n\n    /**\n     * installByRelation\n     * @return void\n     */\n    public static function installByRelation()\n    {\n        foreach (static::$pathRelation as $source => $dest) {\n            if ($pos = strrpos($dest, '/')) {\n                $parent_dir = base_path().'/'.substr($dest, 0, $pos);\n                if (!is_dir($parent_dir)) {\n                    mkdir($parent_dir, 0777, true);\n                }\n            }\n            //symlink(__DIR__ . \"/$source\", base_path().\"/$dest\");\n            copy_dir(__DIR__ . \"/$source\", base_path().\"/$dest\");\n            echo \"Create $dest\";\n        }\n    }\n\n    /**\n     * uninstallByRelation\n     * @return void\n     */\n    public static function uninstallByRelation()\n    {\n        foreach (static::$pathRelation as $source => $dest) {\n            $path = base_path().\"/$dest\";\n            if (!is_dir($path) && !is_file($path)) {\n                continue;\n            }\n            echo \"Remove $dest\";\n            if (is_file($path) || is_link($path)) {\n                unlink($path);\n                continue;\n            }\n            remove_dir($path);\n        }\n    }\n\n    /**\n     * addMethod\n     * @return void\n     */\n    public static function addMethod()\n    {\n        $path = base_path() . '/support/Request.php';\n        // 如果Request中没有more方法，则自动添加一个\n        $content = file_get_contents($path);\n        if ($content !== false) {\n            if (stripos($content, \"public function more\") !== false) {\n                echo \" Request类，已有more方法\\n\";\n                return;\n            }\n            \n            $pattern = '/class\\s+(\\w+)\\s+extends\\s+(\\\\\\\\?(?:\\w+\\\\\\\\)*\\w+)\\s*\\{[\\s\\S]*?\\}/';\n            if (preg_match($pattern, $content, $matches, PREG_OFFSET_CAPTURE)) {\n                $classEnd = $matches[0][1] + strlen($matches[0][0]) - 1;\n                // 构造新方法\n                $newMethod = <<<EOF\n    /**\n     * 获取参数增强方法\n     * @param array \\$params\n     * @return array\n     */\n    public function more(array \\$params): array\n    {\n        \\$p = [];\n        foreach (\\$params as \\$param) {\n            if (!is_array(\\$param)) {\n                \\$p[\\$param] = \\$this->input(\\$param);\n            } else {\n                if (!isset(\\$param[1])) \\$param[1] = '';\n                if (is_array(\\$param[0])) {\n                    \\$name = \\$param[0][0] . '/' . \\$param[0][1];\n                    \\$keyName = \\$param[0][0];\n                } else {\n                    \\$name =  \\$param[0];\n                    \\$keyName = \\$param[0];\n                }\n                \\$p[\\$keyName] = \\$this->input(\\$name, \\$param[1]);\n            }\n        }\n        return \\$p;\n    }\n\n\nEOF;\n                // 在类的末尾插入新方法\n                $newContent = substr_replace($content, $newMethod, $classEnd, 0);\n\n                // 将修改后的内容写回文件\n                if (file_put_contents($path, $newContent) === false) {\n                   echo \" 无法写入文件：$path\";\n                }\n\n                echo \" Request类添加more方法成功\\n\";\n            } else {\n                echo \" Request类添加方法失败，请手动添加more方法\";\n            }\n        }\n    }\n    \n}\n"
  },
  {
    "path": "src/orm/eloquent/app/logic/system/DatabaseLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse support\\Db;\n\n/**\n * 数据表维护逻辑层\n */\nclass DatabaseLogic extends BaseLogic\n{\n    /**\n     * 获取数据源\n     * @return array\n     */\n    public function getDbSource(): array\n    {\n        $data = config('database.connections');\n        $list = [];\n        foreach ($data as $k => $v) {\n            $list[] = $k;\n        }\n        return $list;\n    }\n\n    /**\n     * 数据列表\n     * @param $query\n     * @return mixed\n     */\n    public function getList($query): mixed\n    {\n        $request = request();\n        $page = $request ? ($request->input('page') ?: 1) : 1;\n        $limit = $request ? ($request->input('limit') ?: 10) : 10;\n\n        return self::getTableList($query, $page, $limit);\n    }\n\n    /**\n     * 获取数据库表数据\n     */\n    public function getTableList($query, $current_page = 1, $per_page = 10): array\n    {\n        if (!empty($query['source'])) {\n            if (!empty($query['name'])) {\n                $sql = 'show table status where name=:name ';\n                $list = Db::connection($query['source'])->select($sql, ['name' => $query['name']]);\n            } else {\n                $list = Db::connection($query['source'])->select('show table status');\n            }\n        } else {\n            if (!empty($query['name'])) {\n                $sql = 'show table status where name=:name ';\n                $list = Db::select($sql, ['name' => $query['name']]);\n            } else {\n                $list = Db::select('show table status');\n            }\n        }\n\n        $data = [];\n        foreach ($list as $item) {\n            $data[] = [\n                'name' => $item->Name,\n                'engine' => $item->Engine,\n                'rows' => $item->Rows,\n                'data_free' => $item->Data_free,\n                'data_length' => $item->Data_length,\n                'index_length' => $item->Index_length,\n                'collation' => $item->Collation,\n                'create_time' => $item->Create_time,\n                'update_time' => $item->Update_time,\n                'comment' => $item->Comment,\n            ];\n        }\n        $total = count($data);\n        $last_page = ceil($total / $per_page);\n        $startIndex = ($current_page - 1) * $per_page;\n        $pageData = array_slice($data, $startIndex, $per_page);\n        return [\n            'data' => $pageData,\n            'total' => $total,\n            'current_page' => $current_page,\n            'per_page' => $per_page,\n            'last_page' => $last_page,\n        ];\n    }\n\n    /**\n     * 获取列信息\n     */\n    public function getColumnList($table, $source): array\n    {\n        $columnList = [];\n        if (preg_match(\"/^[a-zA-Z0-9_]+$/\", $table)) {\n            if (!empty($source)) {\n                $list = Db::connection($source)->select('SHOW FULL COLUMNS FROM `' . $table . '`');\n            } else {\n                $list = Db::select('SHOW FULL COLUMNS FROM `' . $table . '`');\n            }\n            foreach ($list as $column) {\n                preg_match('/^\\w+/', $column->Type, $matches);\n                $columnList[] = [\n                    'column_key' => $column->Key,\n                    'column_name' => $column->Field,\n                    'column_type' => $matches[0],\n                    'column_comment' => trim(preg_replace(\"/\\([^()]*\\)/\", \"\", $column->Comment)),\n                    'extra' => $column->Extra,\n                    'default_value' => $column->Default,\n                    'is_nullable' => $column->Null,\n                ];\n            }\n        }\n        return $columnList;\n    }\n\n    /**\n     * 优化表\n     */\n    public function optimizeTable($tables)\n    {\n        foreach ($tables as $table) {\n            if (preg_match(\"/^[a-zA-Z0-9_]+$/\", $table)) {\n                Db::statement('OPTIMIZE TABLE `' . $table . '`');\n            }\n        }\n    }\n\n    /**\n     * 清理表碎片\n     */\n    public function fragmentTable($tables)\n    {\n        foreach ($tables as $table) {\n            if (preg_match(\"/^[a-zA-Z0-9_]+$/\", $table)) {\n                Db::statement('ANALYZE TABLE `' . $table . '`');\n            }\n        }\n    }\n\n    /**\n     * 获取回收站数据\n     */\n    public function recycleData($table)\n    {\n        if (preg_match(\"/^[a-zA-Z0-9_]+$/\", $table)) {\n            // 查询表字段\n            $sql = 'SHOW COLUMNS FROM `' . $table . '` where Field = \"delete_time\"';\n            $columns = Db::select($sql);\n            $isDeleteTime = false;\n            if (count($columns) > 0) {\n                $isDeleteTime = true;\n            }\n            if (!$isDeleteTime) {\n                throw new ApiException('当前表不支持回收站功能');\n            }\n            // 查询软删除数据\n            $request = request();\n            $page = $request ? ($request->input('page') ?: 1) : 1;\n            $limit = $request ? ($request->input('limit') ?: 10) : 10;\n            $list = Db::table($table)->whereNotNull('delete_time')\n                ->orderBy('delete_time', 'desc')\n                ->paginate($limit, ['*'], 'page', $page);\n            return [\n                'current_page' => $list->currentPage(),\n                'per_page' => $list->perPage(),\n                'last_page' => $list->lastPage(),\n                'has_more' => $list->hasMorePages(),\n                'total' => $list->total(),\n                'data' => $list->items(),\n            ];\n        } else {\n            return [];\n        }\n    }\n\n    /**\n     * 删除数据\n     * @param $table\n     * @param $ids\n     * @return bool\n     */\n    public function delete($table, $ids)\n    {\n        if (preg_match(\"/^[a-zA-Z0-9_]+$/\", $table)) {\n            $count = Db::table($table)->whereIn('id', $ids)->delete();\n            return $count > 0;\n        } else {\n            return false;\n        }\n    }\n\n    /**\n     * 恢复数据\n     * @param $table\n     * @param $ids\n     * @return bool\n     */\n    public function recovery($table, $ids)\n    {\n        if (preg_match(\"/^[a-zA-Z0-9_]+$/\", $table)) {\n            $count = Db::table($table)\n                ->whereIn('id', $ids)\n                ->update(['delete_time' => null]);\n            return $count > 0;\n        } else {\n            return false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/orm/eloquent/app/logic/system/SystemAttachmentLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse Exception;\nuse plugin\\saiadmin\\app\\model\\system\\SystemAttachment;\nuse plugin\\saiadmin\\app\\model\\system\\SystemCategory;\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\service\\storage\\ChunkUploadService;\nuse plugin\\saiadmin\\service\\storage\\UploadService;\nuse plugin\\saiadmin\\utils\\Arr;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 附件逻辑层\n */\nclass SystemAttachmentLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemAttachment();\n    }\n\n    /**\n     * @param $category_id\n     * @param $ids\n     * @return mixed\n     */\n    public function move($category_id, $ids): mixed\n    {\n        $category = SystemCategory::where('id', $category_id)->first();\n        if (!$category) {\n            throw new ApiException('目标分类不存在');\n        }\n        return $this->model->whereIn('id', $ids)->update(['category_id' => $category_id]);\n    }\n\n    /**\n     * 保存网络图片\n     * @param $url\n     * @param $config\n     * @return array\n     * @throws ApiException|Exception\n     */\n    public function saveNetworkImage($url, $config): array\n    {\n        $image_data = file_get_contents($url);\n        if ($image_data === false) {\n            throw new ApiException('获取文件资源失败');\n        }\n        $image_resource = imagecreatefromstring($image_data);\n        if (!$image_resource) {\n            throw new ApiException('创建图片资源失败');\n        }\n        $filename = basename($url);\n        $file_extension = pathinfo($filename, PATHINFO_EXTENSION);\n        $full_dir = runtime_path() . '/resource/';\n        if (!is_dir($full_dir)) {\n            mkdir($full_dir, 0777, true);\n        }\n        $save_path = $full_dir . $filename;\n        $mime_type = 'image/';\n        switch ($file_extension) {\n            case 'jpg':\n            case 'jpeg':\n                $mime_type = 'image/jpeg';\n                $result = imagejpeg($image_resource, $save_path);\n                break;\n            case 'png':\n                $mime_type = 'image/png';\n                $result = imagepng($image_resource, $save_path);\n                break;\n            case 'gif':\n                $mime_type = 'image/gif';\n                $result = imagegif($image_resource, $save_path);\n                break;\n            default:\n                imagedestroy($image_resource);\n                throw new ApiException('文件格式错误');\n        }\n        imagedestroy($image_resource);\n        if (!$result) {\n            throw new ApiException('文件保存失败');\n        }\n\n        $hash = md5_file($save_path);\n        $size = filesize($save_path);\n\n        $model = $this->model->where('hash', $hash)->find();\n        if ($model) {\n            unlink($save_path);\n            return $model->toArray();\n        } else {\n\n            $logic = new SystemConfigLogic();\n            $uploadConfig = $logic->getGroup('upload_config');\n\n            $root = Arr::getConfigValue($uploadConfig, 'local_root');\n\n            $folder = date('Ymd');\n            $full_dir = base_path() . DIRECTORY_SEPARATOR . $root . $folder . DIRECTORY_SEPARATOR;\n            if (!is_dir($full_dir)) {\n                mkdir($full_dir, 0777, true);\n            }\n            $object_name = bin2hex(pack('Nn', time(), random_int(1, 65535))) . \".$file_extension\";\n            $newPath = $full_dir . $object_name;\n\n            copy($save_path, $newPath);\n            unlink($save_path);\n            $domain = Arr::getConfigValue($uploadConfig, 'local_domain');\n            $uri = Arr::getConfigValue($uploadConfig, 'local_uri');\n            $baseUrl = $domain . $uri . $folder . '/';\n\n            $info['storage_mode'] = 1;\n            $info['category_id'] = request()->input('category_id', 1);\n            $info['origin_name'] = $filename;\n            $info['object_name'] = $object_name;\n            $info['hash'] = $hash;\n            $info['mime_type'] = $mime_type;\n            $info['storage_path'] = $root . $folder . '/' . $object_name;\n            $info['suffix'] = $file_extension;\n            $info['size_byte'] = $size;\n            $info['size_info'] = formatBytes($size);\n            $info['url'] = $baseUrl . $object_name;\n            $this->model->create($info);\n            return $info;\n        }\n    }\n\n    /**\n     * 文件上传\n     * @param string $upload\n     * @param bool $local\n     * @return array\n     */\n    public function uploadBase(string $upload = 'image', bool $local = false): array\n    {\n        $logic = new SystemConfigLogic();\n        $uploadConfig = $logic->getGroup('upload_config');\n        $type = Arr::getConfigValue($uploadConfig, 'upload_mode');\n        if ($local === true) {\n            $type = 1;\n        }\n        $result = UploadService::disk($type, $upload)->uploadFile();\n        $data = $result[0];\n        $hash = $data['unique_id'];\n        $hash_check = config('plugin.saiadmin.saithink.file_hash', false);\n        if ($hash_check) {\n            $model = $this->model->where('hash', $hash)->first();\n            if ($model) {\n                return $model->toArray();\n            }\n        }\n        $url = str_replace('\\\\', '/', $data['url']);\n        $savePath = str_replace('\\\\', '/', $data['save_path']);\n        $info['storage_mode'] = $type;\n        $info['category_id'] = request()->input('category_id', 1);\n        $info['origin_name'] = $data['origin_name'];\n        $info['object_name'] = $data['save_name'];\n        $info['hash'] = $data['unique_id'];\n        $info['mime_type'] = $data['mime_type'];\n        $info['storage_path'] = $savePath;\n        $info['suffix'] = $data['extension'];\n        $info['size_byte'] = $data['size'];\n        $info['size_info'] = formatBytes($data['size']);\n        $info['url'] = $url;\n        $this->model->create($info);\n        return $info;\n    }\n\n    /**\n     * 切片上传\n     * @param $data\n     * @return array\n     */\n    public function chunkUpload($data): array\n    {\n        $chunkService = new ChunkUploadService();\n        if ($data['index'] == 0) {\n            $model = $this->model->where('hash', $data['hash'])->first();\n            if ($model) {\n                return $model->toArray();\n            } else {\n                return $chunkService->checkChunk($data);\n            }\n        } else {\n            return $chunkService->uploadChunk($data);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/orm/eloquent/app/logic/system/SystemCategoryLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\app\\model\\system\\SystemCategory;\nuse plugin\\saiadmin\\utils\\Helper;\nuse plugin\\saiadmin\\utils\\Arr;\n\n/**\n * 附件分类逻辑层\n */\nclass SystemCategoryLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemCategory();\n    }\n\n    /**\n     * 添加数据\n     */\n    public function add($data): mixed\n    {\n        $data = $this->handleData($data);\n        return $this->model->create($data);\n    }\n\n    /**\n     * 修改数据\n     */\n    public function edit($id, $data): bool\n    {\n        $data = $this->handleData($data);\n        if ($data['parent_id'] == $id) {\n            throw new ApiException('上级分类和当前分类不能相同');\n        }\n        if (in_array($id, explode(',', $data['level']))) {\n            throw new ApiException('不能将上级分类设置为当前分类的子分类');\n        }\n        $model = $this->model->find($id);\n        if (!$model) {\n            throw new ApiException('数据不存在');\n        }\n        return $model->update($data);\n    }\n\n    /**\n     * 数据删除\n     */\n    public function destroy($ids): bool\n    {\n        $num = $this->model->whereIn('parent_id', $ids)->count();\n        if ($num > 0) {\n            throw new ApiException('该部门下存在子分类，请先删除子分类');\n        } else {\n            return $this->model->destroy($ids);\n        }\n    }\n\n    /**\n     * 数据处理\n     */\n    protected function handleData($data)\n    {\n        if (empty($data['parent_id']) || $data['parent_id'] == 0) {\n            $data['level'] = '0';\n            $data['parent_id'] = 0;\n        } else {\n            $parentMenu = SystemCategory::find($data['parent_id']);\n            $data['level'] = $parentMenu['level'] . $parentMenu['id'] . ',';\n        }\n        return $data;\n    }\n\n    /**\n     * 数据树形化\n     * @param array $where\n     * @return array\n     */\n    public function tree(array $where = []): array\n    {\n        $query = $this->search($where);\n        $request = request();\n        if ($request && $request->input('tree', 'false') === 'true') {\n            $query->select('id', 'id as value', 'category_name as label', 'parent_id', 'category_name', 'sort');\n        }\n        $query->orderBy('sort', 'desc');\n        $data = $this->getAll($query);\n        return Helper::makeTree($data);\n    }\n\n}\n"
  },
  {
    "path": "src/orm/eloquent/app/logic/system/SystemConfigGroupLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\cache\\ConfigCache;\nuse plugin\\saiadmin\\app\\model\\system\\SystemConfigGroup;\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\app\\model\\system\\SystemConfig;\nuse support\\think\\Db;\n\n/**\n * 参数配置分组逻辑层\n */\nclass SystemConfigGroupLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemConfigGroup();\n    }\n\n    /**\n     * 删除配置信息\n     */\n    public function destroy($ids): bool\n    {\n        $id = $ids[0];\n        $model = $this->model->find($id);\n        if (!$model) {\n            throw new ApiException('配置数据未找到');\n        }\n        if (in_array(intval($id), [1, 2, 3])) {\n            throw new ApiException('系统默认分组，无法删除');\n        }\n        Db::startTrans();\n        try {\n            // 删除配置组\n            $model->delete();\n            // 删除配置组数据\n            $typeIds = SystemConfig::where('group_id', $id)->pluck('id')->toArray();\n            SystemConfig::destroy($typeIds);\n            ConfigCache::clearConfig($model->code);\n            Db::commit();\n            return true;\n        } catch (\\Exception $e) {\n            Db::rollback();\n            throw new ApiException('删除数据异常，请检查');\n        }\n    }\n}\n"
  },
  {
    "path": "src/orm/eloquent/app/logic/system/SystemConfigLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\cache\\ConfigCache;\nuse plugin\\saiadmin\\app\\model\\system\\SystemConfig;\nuse plugin\\saiadmin\\app\\model\\system\\SystemConfigGroup;\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 参数配置逻辑层\n */\nclass SystemConfigLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemConfig();\n    }\n\n    /**\n     * 添加数据\n     * @param mixed $data\n     * @return mixed\n     */\n    public function add($data): mixed\n    {\n        $result = $this->model->create($data);\n        $group = SystemConfigGroup::find($data['group_id']);\n        ConfigCache::clearConfig($group->code);\n        return $result;\n    }\n\n    /**\n     * 编辑数据\n     * @param mixed $id\n     * @param mixed $data\n     * @return bool\n     */\n    public function edit($id, $data): bool\n    {\n        $result = parent::edit($id, $data);\n        $group = SystemConfigGroup::find($data['group_id']);\n        ConfigCache::clearConfig($group->code);\n        return $result;\n    }\n\n    /**\n     * 批量更新\n     * @param mixed $group_id\n     * @param mixed $config\n     * @return bool\n     */\n    public function batchUpdate($group_id, $config): bool\n    {\n        $group = SystemConfigGroup::find($group_id);\n        if (!$group) {\n            throw new ApiException('配置组未找到');\n        }\n        $saveData = [];\n        foreach ($config as $key => $value) {\n            $saveData[] = [\n                'id' => $value['id'],\n                'group_id' => $group_id,\n                'name' => $value['name'],\n                'key' => $value['key'],\n                'value' => $value['value']\n            ];\n        }\n        // upsert: 根据 id 更新，如果不存在则插入\n        $this->model->upsert($saveData, ['id'], ['value']);\n        ConfigCache::clearConfig($group->code);\n        return true;\n    }\n\n    /**\n     * 获取配置数据\n     * @param mixed $code\n     * @return array\n     */\n    public function getData($code): array\n    {\n        $group = SystemConfigGroup::where('code', $code)->first();\n        if (empty($group)) {\n            return [];\n        }\n        $config = SystemConfig::where('group_id', $group['id'])->get()->toArray();\n        return $config;\n    }\n\n    /**\n     * 获取配置组\n     */\n    public function getGroup($config): array\n    {\n        return ConfigCache::getConfig($config);\n    }\n\n}\n"
  },
  {
    "path": "src/orm/eloquent/app/logic/system/SystemDeptLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDept;\nuse plugin\\saiadmin\\app\\model\\system\\SystemUser;\nuse plugin\\saiadmin\\utils\\Helper;\nuse plugin\\saiadmin\\utils\\Arr;\nuse support\\Db;\n\n/**\n * 部门逻辑层\n */\nclass SystemDeptLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemDept();\n    }\n\n    /**\n     * 添加数据\n     */\n    public function add($data): mixed\n    {\n        $data = $this->handleData($data);\n        return $this->model->create($data);\n    }\n\n    /**\n     * 修改数据\n     */\n    public function edit($id, $data): mixed\n    {\n        $oldLevel = $data['level'] . $id . ',';\n        $data = $this->handleData($data);\n        if ($data['parent_id'] == $id) {\n            throw new ApiException('上级部门和当前部门不能相同');\n        }\n        if (in_array($id, explode(',', $data['level']))) {\n            throw new ApiException('不能将上级部门设置为当前部门的子部门');\n        }\n        $newLevel = $data['level'] . $id . ',';\n        $deptIds = $this->model->where('level', 'like', $oldLevel . '%')->pluck('id')->toArray();\n\n        return $this->transaction(function () use ($deptIds, $oldLevel, $newLevel, $data, $id) {\n            $this->model->whereIn('id', $deptIds)->update([\n                'level' => Db::raw(\"REPLACE(level, '$oldLevel', '$newLevel')\")\n            ]);\n            return $this->model->where('id', $id)->update($data);\n        });\n    }\n\n    /**\n     * 数据删除\n     */\n    public function destroy($ids): bool\n    {\n        $num = $this->model->whereIn('parent_id', $ids)->count();\n        if ($num > 0) {\n            throw new ApiException('该部门下存在子部门，请先删除子部门');\n        } else {\n            $count = SystemUser::whereIn('dept_id', $ids)->count();\n            if ($count > 0) {\n                throw new ApiException('该部门下存在用户，请先删除或者转移用户');\n            }\n            return $this->model->destroy($ids);\n        }\n    }\n\n    /**\n     * 数据处理\n     */\n    protected function handleData($data)\n    {\n        // 处理上级部门\n        if (empty($data['parent_id']) || $data['parent_id'] == 0) {\n            $data['level'] = '0';\n            $data['parent_id'] = 0;\n        } else {\n            $parentMenu = SystemDept::find($data['parent_id']);\n            $data['level'] = $parentMenu['level'] . $parentMenu['id'] . ',';\n        }\n        return $data;\n    }\n\n    /**\n     * 数据树形化\n     * @param array $where\n     * @return array\n     */\n    public function tree(array $where = []): array\n    {\n        $query = $this->search($where);\n        $request = request();\n        if ($request && $request->input('tree', 'false') === 'true') {\n            $query->select('id', 'id as value', 'name as label', 'parent_id');\n        }\n        $query->orderBy('sort', 'desc');\n        $query->with(['leader']);\n        $data = $this->getAll($query);\n        return Helper::makeTree($data);\n    }\n\n    /**\n     * 可操作部门\n     * @param array $where\n     * @return array\n     */\n    public function accessDept(array $where = []): array\n    {\n        $query = $this->search($where);\n        $query->auth($this->adminInfo['deptList']);\n        $query->select('id', 'id as value', 'name as label', 'parent_id');\n        $query->orderBy('sort', 'desc');\n        $data = $this->getAll($query);\n        return Helper::makeTree($data);\n    }\n\n}\n"
  },
  {
    "path": "src/orm/eloquent/app/logic/system/SystemDictDataLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDictData;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDictType;\nuse plugin\\saiadmin\\app\\cache\\DictCache;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 字典类型逻辑层\n */\nclass SystemDictDataLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemDictData();\n    }\n\n    /**\n     * 添加数据\n     * @param $data\n     * @return mixed\n     */\n    public function add($data): mixed\n    {\n        $type = SystemDictType::find($data['type_id']);\n        if (!$type) {\n            throw new ApiException('字典类型不存在');\n        }\n        $data['code'] = $type->code;\n        $model = $this->model->create($data);\n        DictCache::clear();\n        return $model->getKey();\n    }\n\n}\n"
  },
  {
    "path": "src/orm/eloquent/app/logic/system/SystemDictTypeLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDictType;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDictData;\nuse support\\think\\Db;\n\n/**\n * 字典类型逻辑层\n */\nclass SystemDictTypeLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemDictType();\n    }\n\n    /**\n     * 数据更新\n     */\n    public function edit($id, $data): mixed\n    {\n        Db::startTrans();\n        try {\n            // 修改数据字典类型\n            $result = $this->model->where('id', $id)->update($data);\n            // 更新数据字典数据\n            SystemDictData::where('type_id', $id)->update(['code' => $data['code']]);\n            Db::commit();\n            return $result;\n        } catch (\\Exception $e) {\n            Db::rollback();\n            throw new ApiException('修改数据异常，请检查');\n        }\n    }\n\n    /**\n     * 数据删除\n     */\n    public function destroy($ids): bool\n    {\n        Db::startTrans();\n        try {\n            // 删除数据字典类型\n            $result = $this->model->destroy($ids);\n            // 删除数据字典数据\n            $typeIds = SystemDictData::whereIn('type_id', $ids)->pluck('id')->toArray();\n            SystemDictData::destroy($typeIds);\n            Db::commit();\n            return $result;\n        } catch (\\Exception $e) {\n            Db::rollback();\n            throw new ApiException('删除数据异常，请检查');\n        }\n    }\n\n    /**\n     * 获取全部字典\n     * @return array\n     */\n    public function getDictAll(): array\n    {\n        $data = $this->model->where('status', 1)->select('id', 'name', 'code', 'remark')\n            ->with([\n                'dicts' => function ($query) {\n                    $query->where('status', 1)->select('id', 'type_id', 'label', 'value', 'color', 'code', 'sort')->orderBy('sort', 'desc');\n                }\n            ])->get()->toArray();\n        return $this->packageDict($data, 'code');\n    }\n\n    /**\n     * 组合数据\n     * @param $array\n     * @param $field\n     * @return array\n     */\n    private function packageDict($array, $field): array\n    {\n        $result = [];\n        foreach ($array as $item) {\n            if (isset($item[$field])) {\n                if (isset($result[$item[$field]])) {\n                    $result[$item[$field]] = [($result[$item[$field]])];\n                    $result[$item[$field]][] = $item['dicts'];\n                } else {\n                    $result[$item[$field]] = $item['dicts'];\n                }\n            }\n        }\n        return $result;\n    }\n\n}\n"
  },
  {
    "path": "src/orm/eloquent/app/logic/system/SystemLoginLogLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\model\\system\\SystemLoginLog;\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\nuse plugin\\saiadmin\\utils\\Helper;\nuse support\\Db;\n\n/**\n * 登录日志逻辑层\n */\nclass SystemLoginLogLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemLoginLog();\n    }\n\n    /**\n     * 登录统计图表\n     * @return array\n     */\n    public function loginChart(): array\n    {\n        $sql = \"\n            SELECT\n                d.date AS login_date,\n                COUNT(l.login_time) AS login_count\n            FROM\n                (SELECT CURDATE() - INTERVAL (a.N) DAY AS date\n                 FROM (SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n                       UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n                       UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a\n                 ) d\n            LEFT JOIN sa_system_login_log l\n                ON DATE(l.login_time) = d.date\n            GROUP BY d.date\n            ORDER BY d.date ASC;\n        \";\n        $data = Db::select($sql);\n        return [\n            'login_count' => array_column($data, 'login_count'),\n            'login_date' => array_column($data, 'login_date'),\n        ];\n    }\n\n    /**\n     * 登录统计图表\n     * @return array\n     */\n    public function loginBarChart(): array\n    {\n        $sql = \"\n            SELECT\n                -- 拼接成 YYYY-MM 格式，例如 2023-01\n                CONCAT(LPAD(m.month_num, 2, '0'), '月') AS login_month,\n                COUNT(l.login_time) AS login_count\n            FROM\n                -- 生成 1 到 12 的月份数字\n                (SELECT 1 AS month_num UNION ALL SELECT 2 UNION ALL SELECT 3\n                 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n                 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n                 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12) m\n            LEFT JOIN sa_system_login_log l\n                -- 关联条件：年份等于今年 且 月份等于生成的数字\n                ON YEAR(l.login_time) = YEAR(CURDATE())\n                AND MONTH(l.login_time) = m.month_num\n            GROUP BY\n                m.month_num\n            ORDER BY\n                m.month_num ASC;\n        \";\n        $data = Db::select($sql);\n        return [\n            'login_count' => array_column($data, 'login_count'),\n            'login_month' => array_column($data, 'login_month'),\n        ];\n    }\n\n}\n"
  },
  {
    "path": "src/orm/eloquent/app/logic/system/SystemMailLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\model\\system\\SystemMail;\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 邮件模型逻辑层\n */\nclass SystemMailLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemMail();\n    }\n\n}\n"
  },
  {
    "path": "src/orm/eloquent/app/logic/system/SystemMenuLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\model\\system\\SystemMenu;\nuse plugin\\saiadmin\\app\\model\\system\\SystemRoleMenu;\nuse plugin\\saiadmin\\app\\model\\system\\SystemUserRole;\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\utils\\Arr;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 菜单逻辑层\n */\nclass SystemMenuLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemMenu();\n    }\n\n    /**\n     * 数据添加\n     */\n    public function add($data): mixed\n    {\n        $data = $this->handleData($data);\n        return $this->model->create($data);\n    }\n\n    /**\n     * 数据修改\n     */\n    public function edit($id, $data): mixed\n    {\n        $data = $this->handleData($data);\n        if ($data['parent_id'] == $id) {\n            throw new ApiException('不能设置父级为自身');\n        }\n        return $this->model->where('id', $id)->update($data);\n    }\n\n    /**\n     * 数据删除\n     */\n    public function destroy($ids): bool\n    {\n        $num = $this->model->whereIn('parent_id', $ids)->count();\n        if ($num > 0) {\n            throw new ApiException('该菜单下存在子菜单，请先删除子菜单');\n        } else {\n            return $this->model->destroy($ids);\n        }\n    }\n\n    /**\n     * 数据处理\n     */\n    protected function handleData($data)\n    {\n        // 处理上级菜单\n        if (empty($data['parent_id']) || $data['parent_id'] == 0) {\n            $data['parent_id'] = 0;\n        }\n        return $data;\n    }\n\n    /**\n     * 数据树形化\n     * @param $where\n     * @return array\n     */\n    public function tree($where = []): array\n    {\n        $query = $this->search($where);\n        $request = request();\n        if ($request && $request->input('tree', 'false') === 'true') {\n            $query->select('id', 'id as value', 'name as label', 'parent_id', 'type');\n        }\n        $query->orderBy('sort', 'desc');\n        $data = $this->getAll($query);\n        return Helper::makeTree($data);\n    }\n\n    /**\n     * 权限菜单\n     * @return array\n     */\n    public function auth(): array\n    {\n        $roleLogic = new SystemRoleLogic();\n        $role_ids = Arr::getArrayColumn($this->adminInfo['roleList'], 'id');\n        $roles = $roleLogic->getMenuIdsByRoleIds($role_ids);\n        $ids = $this->filterMenuIds($roles);\n        $query = $this->model\n            ->select('id', 'id as value', 'name as label', 'parent_id', 'type')\n            ->where('status', 1)\n            ->whereIn('id', $ids)\n            ->orderBy('sort', 'desc');\n        $data = $this->getAll($query);\n        return Helper::makeTree($data);\n    }\n\n    /**\n     * 获取全部菜单\n     */\n    public function getAllMenus(): array\n    {\n        $query = $this->search(['status' => 1, 'type' => [1, 2, 4]])->orderBy('sort', 'desc');\n        $data = $this->getAll($query);\n        return Helper::makeArtdMenus($data);\n    }\n\n    /**\n     * 获取全部权限\n     * @return array\n     */\n    public function getAllAuth(): array\n    {\n        return SystemMenu::where('type', 3)\n            ->where('status', 1)\n            ->pluck('slug')\n            ->toArray();\n    }\n\n    /**\n     * 根据角色获取权限\n     * @param $roleIds\n     * @return array\n     */\n    public function getAuthByRole($roleIds): array\n    {\n        $menuId = SystemRoleMenu::whereIn('role_id', $roleIds)->pluck('menu_id')->toArray();\n\n        return SystemMenu::distinct()\n            ->where('type', 3)\n            ->where('status', 1)\n            ->whereIn('id', array_unique($menuId))\n            ->pluck('slug')\n            ->toArray();\n    }\n\n    /**\n     * 根据角色获取菜单\n     * @param $roleIds\n     * @return array\n     */\n    public function getMenuByRole($roleIds): array\n    {\n        $menuId = SystemRoleMenu::whereIn('role_id', $roleIds)->pluck('menu_id')->toArray();\n\n        $data = SystemMenu::distinct()\n            ->where('status', 1)\n            ->whereIn('type', [1, 2, 4])\n            ->whereIn('id', array_unique($menuId))\n            ->orderBy('sort', 'desc')\n            ->get()\n            ->toArray();\n        return Helper::makeArtdMenus($data);\n    }\n\n    /**\n     * 过滤通过角色查询出来的菜单id列表，并去重\n     * @param array $roleData\n     * @return array\n     */\n    public function filterMenuIds(array &$roleData): array\n    {\n        $ids = [];\n        foreach ($roleData as $val) {\n            foreach ($val['menus'] as $menu) {\n                $ids[] = $menu['id'];\n            }\n        }\n        unset($roleData);\n        return array_unique($ids);\n    }\n\n}"
  },
  {
    "path": "src/orm/eloquent/app/logic/system/SystemOperLogLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\model\\system\\SystemOperLog;\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 操作日志逻辑层\n */\nclass SystemOperLogLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemOperLog();\n    }\n\n    /**\n     * 获取自己的操作日志\n     * @param mixed $where\n     * @return array\n     */\n    public function getOwnOperLogList($where): array\n    {\n        $query = $this->search($where);\n        $query->select('id', 'username', 'method', 'router', 'service_name', 'ip', 'ip_location', 'create_time');\n        return $this->getList($query);\n    }\n\n}\n"
  },
  {
    "path": "src/orm/eloquent/app/logic/system/SystemPostLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\model\\system\\SystemPost;\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\service\\OpenSpoutWriter;\nuse OpenSpout\\Reader\\XLSX\\Reader;\n\n/**\n * 岗位管理逻辑层\n */\nclass SystemPostLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemPost();\n    }\n\n    /**\n     * 可操作岗位\n     * @param array $where\n     * @return array\n     */\n    public function accessPost(array $where = []): array\n    {\n        $query = $this->search($where);\n        $query->select('id', 'id as value', 'name as label', 'name', 'code');\n        return $this->getAll($query);\n    }\n\n    /**\n     * 导入数据\n     */\n    public function import($file)\n    {\n        $path = $this->getImport($file);\n        $reader = new Reader();\n        try {\n            $reader->open($path);\n            $data = [];\n            foreach ($reader->getSheetIterator() as $sheet) {\n                $isHeader = true;\n                foreach ($sheet->getRowIterator() as $row) {\n                    if ($isHeader) {\n                        $isHeader = false;\n                        continue;\n                    }\n                    $cells = $row->getCells();\n                    $data[] = [\n                        'name' => $cells[0]->getValue(),\n                        'code' => $cells[1]->getValue(),\n                        'sort' => $cells[2]->getValue(),\n                        'status' => $cells[3]->getValue(),\n                    ];\n                }\n            }\n            $this->saveAll($data);\n        } catch (\\Exception $e) {\n            throw new ApiException('导入文件错误，请上传正确的文件格式xlsx');\n        }\n    }\n\n    /**\n     * 导出数据\n     */\n    public function export($where = [])\n    {\n        $query = $this->search($where)->pluck('id', 'name', 'code', 'sort', 'status', 'create_time');\n        $data = $this->getAll($query);\n        $file_name = '岗位数据.xlsx';\n        $header = ['编号', '岗位名称', '岗位标识', '排序', '状态', '创建时间'];\n        $filter = [\n            'status' => [\n                ['value' => 1, 'label' => '正常'],\n                ['value' => 2, 'label' => '禁用']\n            ]\n        ];\n        $writer = new OpenSpoutWriter($file_name);\n        $writer->setWidth([15, 15, 20, 15, 15, 25]);\n        $writer->setHeader($header);\n        $writer->setData($data, null, $filter);\n        $file_path = $writer->returnFile();\n        return response()->download($file_path, urlencode($file_name));\n    }\n\n}\n"
  },
  {
    "path": "src/orm/eloquent/app/logic/system/SystemRoleLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\cache\\UserMenuCache;\nuse plugin\\saiadmin\\app\\model\\system\\SystemRole;\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\utils\\Helper;\nuse support\\think\\Cache;\nuse support\\think\\Db;\n\n/**\n * 角色逻辑层\n */\nclass SystemRoleLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemRole();\n    }\n\n    /**\n     * 添加数据\n     */\n    public function add($data): mixed\n    {\n        $data = $this->handleData($data);\n        return $this->model->create($data);\n    }\n\n    /**\n     * 修改数据\n     */\n    public function edit($id, $data): bool\n    {\n        $model = $this->model->find($id);\n        if (!$model) {\n            throw new ApiException('数据不存在');\n        }\n        $data = $this->handleData($data);\n        return $model->update($data);\n    }\n\n    /**\n     * 删除数据\n     */\n    public function destroy($ids): bool\n    {\n        // 越权保护\n        $levelArr = array_column($this->adminInfo['roleList'], 'level');\n        $maxLevel = max($levelArr);\n\n        $num = SystemRole::where('level', '>=', $maxLevel)->whereIn('id', $ids)->count();\n        if ($num > 0) {\n            throw new ApiException('不能操作比当前账户职级高的角色');\n        } else {\n            return $this->model->destroy($ids);\n        }\n    }\n\n    /**\n     * 数据处理\n     */\n    protected function handleData($data)\n    {\n        // 越权保护\n        $levelArr = array_column($this->adminInfo['roleList'], 'level');\n        $maxLevel = max($levelArr);\n        if ($data['level'] >= $maxLevel) {\n            throw new ApiException('不能操作比当前账户职级高的角色');\n        }\n        return $data;\n    }\n\n    /**\n     * 可操作角色\n     * @param array $where\n     * @return array\n     */\n    public function accessRole(array $where = []): array\n    {\n        $query = $this->search($where);\n        // 越权保护\n        $levelArr = array_column($this->adminInfo['roleList'], 'level');\n        $maxLevel = max($levelArr);\n        $query->where('level', '<', $maxLevel);\n        $query->orderBy('sort', 'desc');\n        return $this->getAll($query);\n    }\n\n    /**\n     * 根据角色数组获取菜单\n     * @param $ids\n     * @return array\n     */\n    public function getMenuIdsByRoleIds($ids): array\n    {\n        if (empty($ids))\n            return [];\n        return $this->model->whereIn('id', $ids)->with([\n            'menus' => function ($query) {\n                $query->where('status', 1)->orderBy('sort', 'desc');\n            }\n        ])->get()->toArray();\n\n    }\n\n    /**\n     * 根据角色获取菜单\n     * @param $id\n     * @return array\n     */\n    public function getMenuByRole($id): array\n    {\n        $role = $this->model->find($id);\n        $menus = $role->menus ?: [];\n        return [\n            'id' => $id,\n            'menus' => $menus\n        ];\n    }\n\n    /**\n     * 保存菜单权限\n     * @param $id\n     * @param $menu_ids\n     * @return mixed\n     */\n    public function saveMenuPermission($id, $menu_ids): mixed\n    {\n        return $this->transaction(function () use ($id, $menu_ids) {\n            $role = $this->model->find($id);\n            if ($role) {\n                $role->menus()->sync($menu_ids);\n            }\n            $cache = config('plugin.saiadmin.saithink.button_cache');\n            $tag = $cache['role'] . $id;\n            Cache::tag($tag)->clear();       // 清理权限缓存-角色TAG\n            UserMenuCache::clearMenuCache(); // 清理菜单缓存\n            return true;\n        });\n    }\n\n}\n"
  },
  {
    "path": "src/orm/eloquent/app/logic/system/SystemUserLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\cache\\UserAuthCache;\nuse plugin\\saiadmin\\app\\cache\\UserInfoCache;\nuse plugin\\saiadmin\\app\\cache\\UserMenuCache;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDept;\nuse plugin\\saiadmin\\app\\model\\system\\SystemRole;\nuse plugin\\saiadmin\\app\\model\\system\\SystemUser;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\nuse Webman\\Event\\Event;\nuse Tinywan\\Jwt\\JwtToken;\nuse Illuminate\\Support\\Arr;\n\n/**\n * 用户信息逻辑层\n */\nclass SystemUserLogic extends BaseLogic\n{\n\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemUser();\n    }\n\n    /**\n     * 分页数据列表\n     * @param mixed $where\n     * @return array\n     */\n    public function indexList($where): array\n    {\n        $query = $this->search($where);\n        $query->with(['depts']);\n        $query->auth($this->adminInfo['deptList']);\n        return $this->getList($query);\n    }\n\n    /**\n     * 用户列表数据\n     * @param mixed $where\n     * @return array\n     */\n    public function openUserList($where): array\n    {\n        $query = $this->search($where);\n        $query->select('id', 'username', 'realname', 'avatar', 'phone', 'email');\n        return $this->getList($query);\n    }\n\n    /**\n     * 读取用户信息\n     * @param mixed $id\n     * @return array\n     */\n    public function getUser($id): array\n    {\n        $admin = $this->model->find($id);\n        $data = $admin->makeHidden(['password'])->toArray();\n        $data['roleList'] = $admin->roles->toArray() ?: [];\n        $data['postList'] = $admin->posts->toArray() ?: [];\n        $data['deptList'] = $admin->depts ? $admin->depts->toArray() : [];\n        return $data;\n    }\n\n    /**\n     * 读取数据\n     * @param $id\n     * @return array\n     */\n    public function read($id): array\n    {\n        $data = $this->getUser($id);\n        if ($this->adminInfo['id'] > 1) {\n            // 部门保护\n            if (!$this->deptProtect($this->adminInfo['deptList'], $data['dept_id'])) {\n                throw new ApiException('没有权限操作该部门数据');\n            }\n        }\n        return $data;\n    }\n\n    /**\n     * 添加数据\n     * @param $data\n     * @return mixed\n     */\n    public function add($data): mixed\n    {\n        $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);\n        return $this->transaction(function () use ($data) {\n            $role_ids = $data['role_ids'] ?? [];\n            $post_ids = $data['post_ids'] ?? [];\n            if ($this->adminInfo['id'] > 1) {\n                // 部门保护\n                if (!$this->deptProtect($this->adminInfo['deptList'], $data['dept_id'])) {\n                    throw new ApiException('没有权限操作该部门数据');\n                }\n                // 越权保护\n                if (!$this->roleProtect($this->adminInfo['roleList'], $role_ids)) {\n                    throw new ApiException('没有权限操作该角色数据');\n                }\n            }\n            unset($data['password_confirm']);\n            unset($data['role_ids']);\n            unset($data['post_ids']);\n            $user = SystemUser::create($data);\n            $user->roles()->sync($role_ids);\n            $user->posts()->sync($post_ids);\n            return $user;\n        });\n    }\n\n    /**\n     * 修改数据\n     * @param $id\n     * @param $data\n     * @return mixed\n     */\n    public function edit($id, $data): mixed\n    {\n        unset($data['password']);\n        return $this->transaction(function () use ($data, $id) {\n            $role_ids = $data['role_ids'] ?? [];\n            $post_ids = $data['post_ids'] ?? [];\n            // 仅可修改当前部门和子部门的用户\n            $query = $this->model->where('id', $id);\n            $query->auth($this->adminInfo['deptList']);\n            $user = $query->first();\n            if (!$user) {\n                throw new ApiException('没有权限操作该数据');\n            }\n            if ($this->adminInfo['id'] > 1) {\n                // 部门保护\n                if (!$this->deptProtect($this->adminInfo['deptList'], $data['dept_id'])) {\n                    throw new ApiException('没有权限操作该部门数据');\n                }\n                // 越权保护\n                if (!$this->roleProtect($this->adminInfo['roleList'], $role_ids)) {\n                    throw new ApiException('没有权限操作该角色数据');\n                }\n            }\n            unset($data['password_confirm']);\n            unset($data['role_ids']);\n            unset($data['post_ids']);\n            $result = parent::edit($id, $data);\n            if ($result) {\n                $user->roles()->sync($role_ids);\n                $user->posts()->sync($post_ids);\n                UserInfoCache::clearUserInfo($id);\n                UserAuthCache::clearUserAuth($id);\n                UserMenuCache::clearUserMenu($id);\n            }\n            return $result;\n        });\n    }\n\n    /**\n     * 删除数据\n     * @param $ids\n     * @return bool\n     */\n    public function destroy($ids): bool\n    {\n        if (is_array($ids)) {\n            if (count($ids) > 1) {\n                throw new ApiException('禁止批量删除操作');\n            }\n            $ids = $ids[0];\n        }\n        if ($ids == 1) {\n            throw new ApiException('超级管理员禁止删除');\n        }\n        $query = $this->model->where('id', $ids);\n        $query->auth($this->adminInfo['deptList']);\n        $user = $query->first();\n        if (!$user) {\n            throw new ApiException('没有权限操作该数据');\n        }\n        if ($this->adminInfo['id'] > 1) {\n            $role_ids = $user->roles->toArray() ?: [];\n            if (!empty($role_ids)) {\n                // 越权保护\n                if (!$this->roleProtect($this->adminInfo['roleList'], array_column($role_ids, 'id'))) {\n                    throw new ApiException('没有权限操作该角色数据');\n                }\n            }\n        }\n        UserInfoCache::clearUserInfo($ids);\n        UserAuthCache::clearUserAuth($ids);\n        UserMenuCache::clearUserMenu($ids);\n        return parent::destroy($ids);\n    }\n\n    /**\n     * 用户登录\n     * @param string $username\n     * @param string $password\n     * @param string $type\n     * @return array\n     */\n    public function login(string $username, string $password, string $type): array\n    {\n        $adminInfo = $this->model->where('username', $username)->first();\n        $status = 1;\n        $message = '登录成功';\n        if (!$adminInfo) {\n            $message = '账号或密码错误，请重新输入!';\n            throw new ApiException($message);\n        }\n        if ($adminInfo->status === 2) {\n            $status = 0;\n            $message = '您已被禁止登录!';\n        }\n        if (!password_verify($password, $adminInfo->password)) {\n            $status = 0;\n            $message = '账号或密码错误，请重新输入!';\n        }\n        if ($status === 0) {\n            // 登录事件\n            Event::emit('user.login', compact('username', 'status', 'message'));\n            throw new ApiException($message);\n        }\n        $adminInfo->login_time = date('Y-m-d H:i:s');\n        $adminInfo->login_ip = request()->getRealIp();\n        $adminInfo->save();\n\n        $access_exp = config('plugin.saiadmin.saithink.access_exp', 3 * 3600);\n        $token = JwtToken::generateToken([\n            'access_exp' => $access_exp,\n            'id' => $adminInfo->id,\n            'username' => $adminInfo->username,\n            'type' => $type,\n            'plat' => 'saiadmin',\n        ]);\n        // 登录事件\n        $admin_id = $adminInfo->id;\n        Event::emit('user.login', compact('username', 'status', 'message', 'admin_id'));\n        return $token;\n    }\n\n    /**\n     * 更新资料\n     * @param mixed $id\n     * @param mixed $data\n     * @return bool\n     */\n    public function updateInfo($id, $data): bool\n    {\n        $allowFields = ['realname', 'gender', 'phone', 'email', 'avatar', 'signed'];\n        $updateData = Arr::only($data, $allowFields);\n        $this->model->where('id', $id)->update($updateData);\n        return true;\n    }\n\n    /**\n     * 密码修改\n     * @param $adminId\n     * @param $oldPassword\n     * @param $newPassword\n     * @return bool\n     */\n    public function modifyPassword($adminId, $oldPassword, $newPassword): bool\n    {\n        $model = $this->model->find($adminId);\n        if (password_verify($oldPassword, $model->password)) {\n            $model->password = password_hash($newPassword, PASSWORD_DEFAULT);\n            return $model->save();\n        } else {\n            throw new ApiException('原密码错误');\n        }\n    }\n\n    /**\n     * 修改数据\n     */\n    public function authEdit($id, $data)\n    {\n        if ($this->adminInfo['id'] > 1) {\n            // 判断用户是否可以操作\n            $query = SystemUser::where('id', $id);\n            $query->auth($this->adminInfo['deptList']);\n            $user = $query->first();\n            if (!$user) {\n                throw new ApiException('没有权限操作该数据');\n            }\n        }\n        parent::edit($id, $data);\n    }\n\n    /**\n     * 部门保护\n     * @param $dept\n     * @param $dept_id\n     * @return bool\n     */\n    public function deptProtect($dept, $dept_id): bool\n    {\n        // 部门保护\n        $deptIds = [$dept['id']];\n        $deptLevel = $dept['level'] . $dept['id'] . ',';\n        $dept_ids = SystemDept::where('level', 'like', $deptLevel . '%')->pluck('id')->toArray();\n        $deptIds = array_merge($deptIds, $dept_ids);\n        if (!in_array($dept_id, $deptIds)) {\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * 越权保护\n     * @param $roleList\n     * @param $role_ids\n     * @return bool\n     */\n    public function roleProtect($roleList, $role_ids): bool\n    {\n        // 越权保护\n        $levelArr = array_column($roleList, 'level');\n        $maxLevel = max($levelArr);\n        $currentLevel = SystemRole::whereIn('id', $role_ids)->max('level');\n        if ($currentLevel >= $maxLevel) {\n            return false;\n        }\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "src/orm/eloquent/app/logic/tool/CrontabLogLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\tool;\n\nuse plugin\\saiadmin\\app\\model\\tool\\CrontabLog;\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\n\n/**\n * 定时任务日志逻辑层\n */\nclass CrontabLogLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new CrontabLog();\n    }\n\n}\n"
  },
  {
    "path": "src/orm/eloquent/app/logic/tool/CrontabLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\tool;\n\nuse Exception;\nuse GuzzleHttp\\Client;\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Webman\\Channel\\Client as ChannelClient;\nuse plugin\\saiadmin\\app\\model\\tool\\Crontab;\nuse plugin\\saiadmin\\app\\model\\tool\\CrontabLog;\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\n\n/**\n * 定时任务逻辑层\n */\nclass CrontabLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new Crontab();\n    }\n\n    /**\n     * 添加任务\n     */\n    public function add($data): bool\n    {\n        $second = $data['second'];\n        $minute = $data['minute'];\n        $hour = $data['hour'];\n        $week = $data['week'];\n        $day = $data['day'];\n        $month = $data['month'];\n\n        // 规则处理\n        $rule = match ($data['task_style']) {\n            1 => \"0 {$minute} {$hour} * * *\",\n            2 => \"0 {$minute} * * * *\",\n            3 => \"0 {$minute} */{$hour} * * *\",\n            4 => \"0 */{$minute} * * * *\",\n            5 => \"*/{$second} * * * * *\",\n            6 => \"0 {$minute} {$hour} * * {$week}\",\n            7 => \"0 {$minute} {$hour} {$day} * *\",\n            8 => \"0 {$minute} {$hour} {$day} {$month} *\",\n            default => throw new ApiException(\"任务类型异常\"),\n        };\n\n        // 定时任务模型新增\n        $model = Crontab::create([\n            'name' => $data['name'],\n            'type' => $data['type'],\n            'task_style' => $data['task_style'],\n            'rule' => $rule,\n            'target' => $data['target'],\n            'parameter' => $data['parameter'],\n            'status' => $data['status'],\n            'remark' => $data['remark'],\n        ]);\n\n        $id = $model->getKey();\n        // 连接到Channel服务\n        ChannelClient::connect();\n        ChannelClient::publish('crontab', ['args' => $id]);\n\n        return true;\n    }\n\n    /**\n     * 修改任务\n     */\n    public function edit($id, $data): bool\n    {\n        $second = $data['second'];\n        $minute = $data['minute'];\n        $hour = $data['hour'];\n        $week = $data['week'];\n        $day = $data['day'];\n        $month = $data['month'];\n\n        // 规则处理\n        $rule = match ($data['task_style']) {\n            1 => \"0 {$minute} {$hour} * * *\",\n            2 => \"0 {$minute} * * * *\",\n            3 => \"0 {$minute} */{$hour} * * *\",\n            4 => \"0 */{$minute} * * * *\",\n            5 => \"*/{$second} * * * * *\",\n            6 => \"0 {$minute} {$hour} * * {$week}\",\n            7 => \"0 {$minute} {$hour} {$day} * *\",\n            8 => \"0 {$minute} {$hour} {$day} {$month} *\",\n            default => throw new ApiException(\"任务类型异常\"),\n        };\n\n        // 查询任务数据\n        $model = $this->model->find($id);\n        if (!$model) {\n            throw new ApiException('数据不存在');\n        }\n\n        $result = $model->update([\n            'name' => $data['name'],\n            'type' => $data['type'],\n            'task_style' => $data['task_style'],\n            'rule' => $rule,\n            'target' => $data['target'],\n            'parameter' => $data['parameter'],\n            'status' => $data['status'],\n            'remark' => $data['remark'],\n        ]);\n        if ($result) {\n            // 连接到Channel服务\n            ChannelClient::connect();\n            ChannelClient::publish('crontab', ['args' => $id]);\n        }\n\n        // 修改任务数据\n        return $result;\n    }\n\n    /**\n     * 删除定时任务\n     * @param $ids\n     * @return bool\n     * @throws Exception\n     */\n    public function destroy($ids): bool\n    {\n        if (is_array($ids)) {\n            if (count($ids) > 1) {\n                throw new ApiException('禁止批量删除操作');\n            }\n            $ids = $ids[0];\n        }\n        $result = parent::destroy($ids);\n        if ($result) {\n            // 连接到Channel服务\n            ChannelClient::connect();\n            ChannelClient::publish('crontab', ['args' => $ids]);\n        }\n        return $result;\n    }\n\n    /**\n     * 修改状态\n     * @param $id\n     * @param $status\n     * @return bool\n     */\n    public function changeStatus($id, $status): bool\n    {\n        $model = $this->model->find($id);\n        if (!$model) {\n            throw new ApiException('数据不存在');\n        }\n        $result = $model->update(['status' => $status]);\n        if ($result) {\n            // 连接到Channel服务\n            ChannelClient::connect();\n            ChannelClient::publish('crontab', ['args' => $id]);\n        }\n        return $result;\n    }\n\n    /**\n     * 执行定时任务\n     * @param $id\n     * @return bool\n     */\n    public function run($id): bool\n    {\n        $info = $this->model->where('status', 1)->find($id);\n        if (!$info) {\n            return false;\n        }\n        $data['crontab_id'] = $info->id;\n        $data['name'] = $info->name;\n        $data['target'] = $info->target;\n        $data['parameter'] = $info->parameter;\n        switch ($info->type) {\n            case 1:\n                // URL任务GET\n                $httpClient = new Client([\n                    'timeout' => 5,\n                    'verify' => false,\n                ]);\n                try {\n                    $httpClient->request('GET', $info->target);\n                    $data['status'] = 1;\n                    CrontabLog::create($data);\n                    return true;\n                } catch (GuzzleException $e) {\n                    $data['status'] = 2;\n                    $data['exception_info'] = $e->getMessage();\n                    CrontabLog::create($data);\n                    return false;\n                }\n            case 2:\n                // URL任务POST\n                $httpClient = new Client([\n                    'timeout' => 5,\n                    'verify' => false,\n                ]);\n                try {\n                    $res = $httpClient->request('POST', $info->target, [\n                        'form_params' => json_decode($info->parameter ?? '', true)\n                    ]);\n                    $data['status'] = 1;\n                    $data['exception_info'] = $res->getBody();\n                    CrontabLog::create($data);\n                    return true;\n                } catch (GuzzleException $e) {\n                    $data['status'] = 2;\n                    $data['exception_info'] = $e->getMessage();\n                    CrontabLog::create($data);\n                    return false;\n                }\n            case 3:\n                // 类任务\n                $class_name = $info->target;\n                $method_name = 'run';\n                $class = new $class_name;\n                if (method_exists($class, $method_name)) {\n                    $return = $class->$method_name($info->parameter);\n                    $data['status'] = 1;\n                    $data['exception_info'] = $return;\n                    CrontabLog::create($data);\n                    return true;\n                } else {\n                    $data['status'] = 2;\n                    $data['exception_info'] = '类:' . $class_name . ',方法:run,未找到';\n                    CrontabLog::create($data);\n                    return false;\n\n                }\n            default:\n                return false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/orm/eloquent/app/logic/tool/GenerateColumnsLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\tool;\n\nuse plugin\\saiadmin\\app\\model\\tool\\GenerateColumns;\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 代码生成业务字段逻辑层\n */\nclass GenerateColumnsLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new GenerateColumns();\n    }\n\n    public function saveExtra($data)\n    {\n        $default_column = ['create_time', 'update_time', 'created_by', 'updated_by', 'delete_time', 'remark'];\n        // 组装数据\n        foreach ($data as $k => $item) {\n\n            if ($item['column_name'] == 'delete_time') {\n                continue;\n            }\n\n            $column = [\n                'table_id' => $item['table_id'],\n                'column_name' => $item['column_name'],\n                'column_comment' => $item['column_comment'],\n                'column_type' => $item['column_type'],\n                'default_value' => $item['default_value'],\n                'is_pk' => ($item['column_key'] == 'PRI') ? 2 : 1,\n                'is_required' => $item['is_nullable'] == 'NO' ? 2 : 1,\n                'query_type' => 'eq',\n                'view_type' => 'input',\n                'sort' => count($data) - $k,\n                'options' => $item['options'] ?? null\n            ];\n\n            // 设置默认选项\n            if (!in_array($item['column_name'], $default_column) && empty($item['column_key'])) {\n                $column = array_merge(\n                    $column,\n                    [\n                        'is_insert' => 2,\n                        'is_edit' => 2,\n                        'is_list' => 2,\n                        'is_query' => 1,\n                        'is_sort' => 1,\n                    ]\n                );\n            }\n            $keyList = [\n                'column_comment',\n                'column_type',\n                'default_value',\n                'is_pk',\n                'is_required',\n                'is_insert',\n                'is_edit',\n                'is_list',\n                'is_query',\n                'is_sort',\n                'query_type',\n                'view_type',\n                'dict_type',\n                'options',\n                'sort',\n                'is_cover'\n            ];\n            foreach ($keyList as $key) {\n                if (isset($item[$key]))\n                    $column[$key] = $item[$key];\n            }\n            GenerateColumns::create($this->fieldDispose($column));\n        }\n    }\n\n    public function update($data, $where)\n    {\n        $data['is_insert'] = $data['is_insert'] ? 2 : 1;\n        $data['is_edit'] = $data['is_edit'] ? 2 : 1;\n        $data['is_list'] = $data['is_list'] ? 2 : 1;\n        $data['is_query'] = $data['is_query'] ? 2 : 1;\n        $data['is_sort'] = $data['is_sort'] ? 2 : 1;\n        $data['is_required'] = $data['is_required'] ? 2 : 1;\n        $this->model->where($where)->update($data);\n    }\n\n    private function fieldDispose(array $column): array\n    {\n        $object = new class {\n            public function viewTypeDispose(&$column): void\n            {\n                switch ($column['column_type']) {\n                    case 'varchar':\n                        $column['view_type'] = 'input';\n                        break;\n                    // 富文本\n                    case 'text':\n                    case 'longtext':\n                        $column['is_list'] = 1;\n                        $column['is_query'] = 1;\n                        $column['view_type'] = 'editor';\n                        $options = [\n                            'height' => 400,\n                        ];\n                        $column['options'] = $options;\n                        break;\n                    // 日期字段\n                    case 'datetime':\n                        $column['view_type'] = 'date';\n                        $options = [\n                            'mode' => 'datetime'\n                        ];\n                        $column['options'] = $options;\n                        $column['query_type'] = 'between';\n                        break;\n                    case 'date':\n                        $column['view_type'] = 'date';\n                        $options = [\n                            'mode' => 'date'\n                        ];\n                        $column['options'] = $options;\n                        $column['query_type'] = 'between';\n                        break;\n                }\n            }\n\n            public function columnName(&$column): void\n            {\n                if (stristr($column['column_name'], 'name')) {\n                    $column['is_query'] = 2;\n                    $column['is_required'] = 2;\n                    $column['query_type'] = 'like';\n                }\n\n                if (stristr($column['column_name'], 'title')) {\n                    $column['is_query'] = 2;\n                    $column['is_required'] = 2;\n                    $column['query_type'] = 'like';\n                }\n\n                if (stristr($column['column_name'], 'type')) {\n                    $column['is_query'] = 2;\n                    $column['is_required'] = 2;\n                    $column['query_type'] = 'eq';\n                }\n\n                if (stristr($column['column_name'], 'image')) {\n                    $column['is_query'] = 1;\n                    $column['view_type'] = 'uploadImage';\n                    $options = [\n                        'multiple' => false,\n                        'limit' => 1,\n                    ];\n                    $column['options'] = $options;\n                }\n\n                if (stristr($column['column_name'], 'file')) {\n                    $column['is_query'] = 1;\n                    $column['view_type'] = 'uploadFile';\n                    $options = [\n                        'multiple' => false,\n                        'limit' => 1,\n                    ];\n                    $column['options'] = $options;\n                }\n\n                if (stristr($column['column_name'], 'attach')) {\n                    $column['is_query'] = 1;\n                    $column['view_type'] = 'uploadFile';\n                    $options = [\n                        'multiple' => false,\n                        'limit' => 1,\n                    ];\n                    $column['options'] = $options;\n                }\n\n                if ($column['column_name'] === 'sort') {\n                    $column['view_type'] = 'inputNumber';\n                }\n\n                if ($column['column_name'] === 'status') {\n                    $column['view_type'] = 'radio';\n                    $column['dict_type'] = 'data_status';\n                }\n\n                if (stristr($column['column_name'], 'is_')) {\n                    $column['view_type'] = 'radio';\n                    $column['dict_type'] = 'yes_or_no';\n                }\n            }\n        };\n\n        if (!$column['is_cover']) {\n            $object->viewTypeDispose($column);\n            $object->columnName($column);\n        }\n        $column['options'] = json_encode($column['options'], JSON_UNESCAPED_UNICODE);\n        return $column;\n    }\n}\n"
  },
  {
    "path": "src/orm/eloquent/app/logic/tool/GenerateTablesLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\tool;\n\nuse plugin\\saiadmin\\app\\cache\\UserMenuCache;\nuse plugin\\saiadmin\\app\\logic\\system\\DatabaseLogic;\nuse plugin\\saiadmin\\app\\model\\system\\SystemMenu;\nuse plugin\\saiadmin\\app\\model\\tool\\GenerateTables;\nuse plugin\\saiadmin\\app\\model\\tool\\GenerateColumns;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\nuse plugin\\saiadmin\\utils\\Helper;\nuse plugin\\saiadmin\\utils\\code\\CodeZip;\nuse plugin\\saiadmin\\utils\\code\\CodeEngine;\n\n/**\n * 代码生成业务逻辑层\n */\nclass GenerateTablesLogic extends BaseLogic\n{\n    protected $columnLogic = null;\n\n    protected $dataLogic = null;\n\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new GenerateTables();\n        $this->columnLogic = new GenerateColumnsLogic();\n        $this->dataLogic = new DatabaseLogic();\n    }\n\n    public function read($id): GenerateTables\n    {\n        $data = parent::read($id);\n        $data['options'] = $data['options'] ? json_decode($data['options'], true) : [];\n        return $data;\n    }\n\n    /**\n     * 删除表和字段信息\n     * @param $ids\n     * @return bool\n     */\n    public function destroy($ids): bool\n    {\n        return $this->transaction(function () use ($ids) {\n            parent::destroy($ids);\n            GenerateColumns::whereIn('table_id', $ids)->delete();\n            return true;\n        });\n    }\n\n    /**\n     * 装载表信息\n     * @param $names\n     * @param $source\n     * @return void\n     */\n    public function loadTable($names, $source): void\n    {\n        $data = config('database.connections');\n        $config = $data[$source];\n        if (!$config) {\n            throw new ApiException('数据库配置读取失败');\n        }\n\n        $prefix = $config['prefix'] ?? '';\n        foreach ($names as $item) {\n            $class_name = $item['name'];\n            if (!empty($prefix)) {\n                $class_name = Helper::str_replace_once($prefix, '', $class_name);\n            }\n            $class_name = Helper::camel($class_name);\n            $tableInfo = [\n                'table_name' => $item['name'],\n                'table_comment' => $item['comment'],\n                'class_name' => $class_name,\n                'business_name' => Helper::get_business($item['name']),\n                'belong_menu_id' => 80,\n                'menu_name' => $item['comment'],\n                'tpl_category' => 'single',\n                'template' => 'app',\n                'stub' => 'think',\n                'namespace' => '',\n                'package_name' => '',\n                'source' => $source,\n                'generate_menus' => 'index,save,update,read,destroy',\n            ];\n            $model = GenerateTables::create($tableInfo);\n            $columns = $this->dataLogic->getColumnList($item['name'], $source);\n            foreach ($columns as &$column) {\n                $column['table_id'] = $model->id;\n                $column['is_cover'] = false;\n            }\n            $this->columnLogic->saveExtra($columns);\n        }\n    }\n\n    /**\n     * 同步表字段信息\n     * @param $id\n     * @return void\n     */\n    public function sync($id)\n    {\n        $model = $this->model->find($id);\n        // 拉取已有数据表信息\n        $queryModel = $this->columnLogic->model->where('table_id', $id);\n        $columnLogicData = $this->columnLogic->getAll($queryModel);\n        $columnLogicList = [];\n        foreach ($columnLogicData as $item) {\n            $columnLogicList[$item['column_name']] = $item;\n        }\n        $this->columnLogic->where('table_id', $id)->delete();\n        $columns = $this->dataLogic->getColumnList($model->table_name, $model->source ?? '');\n        foreach ($columns as &$column) {\n            $column['table_id'] = $model->id;\n            $column['is_cover'] = false;\n            if (isset($columnLogicList[$column['column_name']])) {\n                // 存在历史信息的情况\n                $getcolumnLogicItem = $columnLogicList[$column['column_name']];\n                if ($getcolumnLogicItem['column_type'] == $column['column_type']) {\n                    $column['is_cover'] = true;\n                    foreach ($getcolumnLogicItem as $key => $item) {\n                        $array = [\n                            'column_comment',\n                            'column_type',\n                            'default_value',\n                            'is_pk',\n                            'is_required',\n                            'is_insert',\n                            'is_edit',\n                            'is_list',\n                            'is_query',\n                            'is_sort',\n                            'query_type',\n                            'view_type',\n                            'dict_type',\n                            'options',\n                            'sort',\n                            'is_cover'\n                        ];\n                        if (in_array($key, $array)) {\n                            if ($key == 'options') {\n                                $column[$key] = json_decode($item, true);\n                            } else {\n                                $column[$key] = $item;\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        $this->columnLogic->saveExtra($columns);\n    }\n\n    /**\n     * 代码预览\n     * @param $id\n     * @return array\n     */\n    public function preview($id): array\n    {\n        $data = $this->renderData($id);\n\n        $codeEngine = new CodeEngine($data);\n        $controllerContent = $codeEngine->renderContent('php', 'controller.stub');\n        $logicContent = $codeEngine->renderContent('php', 'logic.stub');\n        $modelContent = $codeEngine->renderContent('php', 'model.stub');\n        $validateContent = $codeEngine->renderContent('php', 'validate.stub');\n        $sqlContent = $codeEngine->renderContent('sql', 'sql.stub');\n        $indexContent = $codeEngine->renderContent('vue', 'index.stub');\n        $editContent = $codeEngine->renderContent('vue', 'edit-dialog.stub');\n        $searchContent = $codeEngine->renderContent('vue', 'table-search.stub');\n        $apiContent = $codeEngine->renderContent('ts', 'api.stub');\n\n        // 返回生成内容\n        return [\n            [\n                'tab_name' => 'controller.php',\n                'name' => 'controller',\n                'lang' => 'php',\n                'code' => $controllerContent\n            ],\n            [\n                'tab_name' => 'logic.php',\n                'name' => 'logic',\n                'lang' => 'php',\n                'code' => $logicContent\n            ],\n            [\n                'tab_name' => 'model.php',\n                'name' => 'model',\n                'lang' => 'php',\n                'code' => $modelContent\n            ],\n            [\n                'tab_name' => 'validate.php',\n                'name' => 'validate',\n                'lang' => 'php',\n                'code' => $validateContent\n            ],\n            [\n                'tab_name' => 'sql.sql',\n                'name' => 'sql',\n                'lang' => 'sql',\n                'code' => $sqlContent\n            ],\n            [\n                'tab_name' => 'index.vue',\n                'name' => 'index',\n                'lang' => 'html',\n                'code' => $indexContent\n            ],\n            [\n                'tab_name' => 'edit-dialog.vue',\n                'name' => 'edit-dialog',\n                'lang' => 'html',\n                'code' => $editContent\n            ],\n            [\n                'tab_name' => 'table-search.vue',\n                'name' => 'table-search',\n                'lang' => 'html',\n                'code' => $searchContent\n            ],\n            [\n                'tab_name' => 'api.ts',\n                'name' => 'api',\n                'lang' => 'javascript',\n                'code' => $apiContent\n            ]\n        ];\n    }\n\n    /**\n     * 生成到模块\n     * @param $id\n     */\n    public function genModule($id)\n    {\n        $data = $this->renderData($id);\n\n        // 生成文件到模块\n        $codeEngine = new CodeEngine($data);\n        $codeEngine->generateBackend('controller', $codeEngine->renderContent('php', 'controller.stub'));\n        $codeEngine->generateBackend('logic', $codeEngine->renderContent('php', 'logic.stub'));\n        $codeEngine->generateBackend('model', $codeEngine->renderContent('php', 'model.stub'));\n        $codeEngine->generateBackend('validate', $codeEngine->renderContent('php', 'validate.stub'));\n        $codeEngine->generateFrontend('index', $codeEngine->renderContent('vue', 'index.stub'));\n        $codeEngine->generateFrontend('edit-dialog', $codeEngine->renderContent('vue', 'edit-dialog.stub'));\n        $codeEngine->generateFrontend('table-search', $codeEngine->renderContent('vue', 'table-search.stub'));\n        $codeEngine->generateFrontend('api', $codeEngine->renderContent('ts', 'api.stub'));\n    }\n\n    /**\n     * 处理数据\n     * @param $id\n     * @return array\n     */\n    protected function renderData($id): array\n    {\n        $table = $this->model->find($id);\n        if (!in_array($table['template'], [\"plugin\", \"app\"])) {\n            throw new ApiException('应用类型必须为plugin或者app');\n        }\n        if (empty($table['namespace'])) {\n            throw new ApiException('请先设置应用名称');\n        }\n\n        $columns = $this->columnLogic->where('table_id', $id)\n            ->orderBy('sort', 'desc')\n            ->get()\n            ->toArray();\n\n        $pk = 'id';\n        foreach ($columns as &$column) {\n            if ($column['is_pk'] == 2) {\n                $pk = $column['column_name'];\n            }\n            if ($column['column_name'] == 'delete_time') {\n                unset($column['column_name']);\n            }\n            $column['options'] = json_decode($column['options'], true);\n        }\n\n        // 处理特殊变量\n        if ($table['template'] == 'plugin') {\n            $namespace_start = \"plugin\\\\\" . $table['namespace'] . \"\\\\app\\\\admin\\\\\";\n            $namespace_start_model = \"plugin\\\\\" . $table['namespace'] . \"\\\\app\\\\\";\n            $namespace_end = \"\\\\\" . $table['package_name'];\n            $url_path = 'app/' . $table['namespace'] . '/admin/' . $table['package_name'] . '/' . $table['class_name'];\n            $route = 'app/';\n        } else {\n            $namespace_start = \"app\\\\\" . $table['namespace'] . \"\\\\\";\n            $namespace_start_model = \"app\\\\\" . $table['namespace'] . \"\\\\\";\n            $namespace_end = \"\\\\\" . $table['package_name'];\n            $url_path = $table['namespace'] . '/' . $table['package_name'] . '/' . $table['class_name'];\n            $route = '';\n        }\n\n        $config = config('database');\n\n        $data = $table->toArray();\n        $data['pk'] = $pk;\n        $data['namespace_start'] = $namespace_start;\n        $data['namespace_start_model'] = $namespace_start_model;\n        $data['namespace_end'] = $namespace_end;\n        $data['url_path'] = $url_path;\n        $data['route'] = $route;\n        $data['tables'] = [$data];\n        $data['columns'] = $columns;\n        $data['db_source'] = $config['default'] ?? 'mysql';\n\n        $data['options'] = $data['options'] ? json_decode($data['options'], true) : [];\n\n        return $data;\n    }\n\n    /**\n     * 生成到模块\n     */\n    public function generateFile($id)\n    {\n        $table = $this->model->find($id);\n        if (!$table) {\n            throw new ApiException('请选择要生成的表');\n        }\n        $debug = config('app.debug', true);\n        if (!$debug) {\n            throw new ApiException('非调试模式下，不允许生成文件');\n        }\n        $this->updateMenu($table);\n        $this->genModule($id);\n        UserMenuCache::clearMenuCache();\n    }\n\n    /**\n     * 代码生成下载\n     */\n    public function generate($idsArr): array\n    {\n        $zip = new CodeZip();\n        $tables = $this->model->whereIn('id', $idsArr)->get()->toArray();\n        foreach ($idsArr as $table_id) {\n            $data = $this->renderData($table_id);\n            $data['tables'] = $tables;\n            $codeEngine = new CodeEngine($data);\n            $codeEngine->generateTemp();\n        }\n\n        $filename = 'saiadmin.zip';\n        $download = $zip->compress();\n\n        return compact('filename', 'download');\n    }\n\n    /**\n     * 处理菜单列表\n     * @param $tables\n     */\n    public function updateMenu($tables)\n    {\n        /*不存在的情况下进行新建操作*/\n        $url_path = $tables['namespace'] . \":\" . $tables['package_name'] . ':' . $tables['business_name'];\n        $code = $tables['namespace'] . \"/\" . $tables['package_name'] . '/' . $tables['business_name'];\n        $path = $tables['package_name'] . '/' . $tables['business_name'];\n        $component = $tables['namespace'] . \"/\" . $tables['package_name'] . '/' . $tables['business_name'];\n\n        /*先获取一下已有的路由中是否包含当前ID的路由的核心信息*/\n        $model = new SystemMenu();\n        $tableMenu = $model->where('generate_id', $tables['id'])->first();\n        $fistMenu = [\n            'parent_id' => $tables['belong_menu_id'],\n            'name' => $tables['menu_name'],\n            'code' => $code,\n            'path' => $path,\n            'icon' => 'ri:home-2-line',\n            'component' => \"/plugin/$component/index\",\n            'type' => 2,\n            'sort' => 100,\n            'is_iframe' => 2,\n            'is_keep_alive' => 2,\n            'is_hidden' => 2,\n            'is_fixed_tab' => 2,\n            'is_full_page' => 2,\n            'generate_id' => $tables['id']\n        ];\n        if (!$tableMenu) {\n            $temp = SystemMenu::create($fistMenu);\n            $fistMenuId = $temp->id;\n        } else {\n            $fistMenu['id'] = $tableMenu['id'];\n            $tableMenu->update($fistMenu);\n            $fistMenuId = $tableMenu['id'];\n        }\n        /*开始进行子权限的判定操作*/\n        $childNodes = [\n            ['name' => '列表', 'key' => 'index'],\n            ['name' => '保存', 'key' => 'save'],\n            ['name' => '更新', 'key' => 'update'],\n            ['name' => '读取', 'key' => 'read'],\n            ['name' => '删除', 'key' => 'destroy'],\n        ];\n\n        foreach ($childNodes as $node) {\n            $nodeData = $model->where('parent_id', $fistMenuId)->where('generate_key', $node['key'])->first();\n            $childNodeData = [\n                'parent_id' => $fistMenuId,\n                'name' => $node['name'],\n                'slug' => \"$url_path:{$node['key']}\",\n                'type' => 3,\n                'sort' => 100,\n                'is_iframe' => 2,\n                'is_keep_alive' => 2,\n                'is_hidden' => 2,\n                'is_fixed_tab' => 2,\n                'is_full_page' => 2,\n                'generate_key' => $node['key']\n            ];\n            if ($nodeData) {\n                $childNodeData['id'] = $nodeData['id'];\n                $nodeData->update($childNodeData);\n            } else {\n                SystemMenu::create($childNodeData);\n            }\n        }\n    }\n\n    /**\n     * 获取数据表字段信息\n     * @param $table_id\n     * @return mixed\n     */\n    public function getTableColumns($table_id): mixed\n    {\n        $query = $this->columnLogic->where('table_id', $table_id);\n        $data = $this->columnLogic->getAll($query);\n        foreach ($data as $key => $value) {\n            $data[$key]['options'] = json_decode($value['options'], true);\n        }\n        return $data;\n    }\n\n    /**\n     * 编辑数据\n     * @param $id\n     * @param $data\n     * @return mixed\n     */\n    public function edit($id, $data): mixed\n    {\n        $columns = $data['columns'];\n\n        unset($data['columns']);\n\n        if (!empty($data['belong_menu_id'])) {\n            $data['belong_menu_id'] = is_array($data['belong_menu_id']) ? array_pop($data['belong_menu_id']) : $data['belong_menu_id'];\n        } else {\n            $data['belong_menu_id'] = 0;\n        }\n\n        $data['generate_menus'] = implode(',', $data['generate_menus']);\n\n        if (empty($data['options'])) {\n            unset($data['options']);\n        }\n\n        $data['options'] = json_encode($data['options']);\n\n        // 更新业务表\n        $this->model->where('id', $id)->update($data);\n\n        // 更新业务字段表（批量）\n        $updateData = [];\n        foreach ($columns as $column) {\n            $column['is_required'] = $column['is_required'] ? 2 : 1;\n            $column['is_insert'] = $column['is_insert'] ? 2 : 1;\n            $column['is_edit'] = $column['is_edit'] ? 2 : 1;\n            $column['is_list'] = $column['is_list'] ? 2 : 1;\n            $column['is_query'] = $column['is_query'] ? 2 : 1;\n            $column['is_sort'] = $column['is_sort'] ? 2 : 1;\n            $column['options'] = json_encode($column['options']);\n            $updateData[] = $column;\n        }\n        GenerateColumns::upsert($updateData, ['id']);\n\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "src/orm/eloquent/app/model/system/SystemAttachment.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseModel;\n\n/**\n * 附件模型\n *\n * sa_system_attachment 附件信息表\n *\n * @property  $id 主键\n * @property  $category_id 文件分类\n * @property  $storage_mode 存储模式\n * @property  $origin_name 原文件名\n * @property  $object_name 新文件名\n * @property  $hash 文件hash\n * @property  $mime_type 资源类型\n * @property  $storage_path 存储目录\n * @property  $suffix 文件后缀\n * @property  $size_byte 字节数\n * @property  $size_info 文件大小\n * @property  $url url地址\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemAttachment extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $primaryKey = 'id';\n\n    protected $table = 'sa_system_attachment';\n\n    /**\n     * 文件名搜索\n     */\n    public function searchOriginNameAttr($query, $value)\n    {\n        $query->where('origin_name', 'like', '%' . $value . '%');\n    }\n\n    /**\n     * 文件类型搜索\n     */\n    public function searchMimeTypeAttr($query, $value)\n    {\n        $query->where('mime_type', 'like', $value . '/%');\n    }\n\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/system/SystemCategory.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseModel;\n\n/**\n * 附件分类模型\n *\n * sa_system_category 附件分类表\n *\n * @property  $id 分类ID\n * @property  $parent_id 父id\n * @property  $level 组集关系\n * @property  $category_name 分类名称\n * @property  $sort 排序\n * @property  $status 状态\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemCategory extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $primaryKey = 'id';\n\n    protected $table = 'sa_system_category';\n\n    /**\n     * 分类名称搜索\n     */\n    public function searchCategoryNameAttr($query, $value)\n    {\n        $query->where('category_name', 'like', '%' . $value . '%');\n    }\n\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/system/SystemConfig.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseModel;\n\n/**\n * 参数配置模型\n *\n * sa_system_config 参数配置信息表\n *\n * @property  $id 编号\n * @property  $group_id 组id\n * @property  $key 配置键名\n * @property  $value 配置值\n * @property  $name 配置名称\n * @property  $input_type 数据输入类型\n * @property  $config_select_data 配置选项数据\n * @property  $sort 排序\n * @property  $remark 备注\n * @property  $created_by 创建人\n * @property  $updated_by 更新人\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemConfig extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $primaryKey = 'id';\n\n    protected $table = 'sa_system_config';\n\n    protected function casts(): array\n    {\n        return array_merge(parent::casts(), [\n            'config_select_data' => 'array',\n        ]);\n    }\n\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/system/SystemConfigGroup.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseModel;\n\n/**\n * 参数配置分组模型\n * \n * sa_system_config_group 参数配置分组表\n *\n * @property  $id 主键\n * @property  $name 字典名称\n * @property  $code 字典标示\n * @property  $remark 备注\n * @property  $created_by 创建人\n * @property  $updated_by 更新人\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemConfigGroup extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $primaryKey = 'id';\n\n    protected $table = 'sa_system_config_group';\n\n    /**\n     * 关联配置列表\n     */\n    public function configs()\n    {\n        return $this->hasMany(SystemConfig::class, 'group_id', 'id');\n    }\n\n    /**\n     * 名称搜索\n     */\n    public function searchNameAttr($query, $value)\n    {\n        return $query->where('name', 'like', '%' . $value . '%');\n    }\n\n    /**\n     * 编码搜索\n     */\n    public function searchCodeAttr($query, $value)\n    {\n        return $query->where('code', 'like', '%' . $value . '%');\n    }\n\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/system/SystemDept.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseModel;\n\n/**\n * 部门模型\n *\n * sa_system_dept 部门表\n *\n * @property  $id 编号\n * @property  $parent_id 父级ID，0为根节点\n * @property  $name 部门名称\n * @property  $code 部门编码\n * @property  $leader_id 部门负责人ID\n * @property  $level 祖级列表，格式: 0,1,5,\n * @property  $sort 排序，数字越小越靠前\n * @property  $status 状态: 1启用, 0禁用\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemDept extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $primaryKey = 'id';\n\n    protected $table = 'sa_system_dept';\n\n    /**\n     * 权限范围\n     */\n    public function scopeAuth($query, $value)\n    {\n        if (!empty($value)) {\n            $deptIds = [$value['id']];\n            $deptLevel = $value['level'] . $value['id'] . ',';\n            $ids = static::where('level', 'like', $deptLevel . '%')->pluck('id')->toArray();\n            $deptIds = array_merge($deptIds, $ids);\n            $query->whereIn('id', $deptIds);\n        }\n    }\n\n    /**\n     * 部门领导\n     */\n    public function leader()\n    {\n        return $this->hasOne(SystemUser::class, 'id', 'leader_id');\n    }\n\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/system/SystemDictData.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseModel;\n\n/**\n * 字典数据模型\n *\n * sa_system_dict_data 字典数据表\n *\n * @property  $id 主键\n * @property  $type_id 字典类型ID\n * @property  $label 字典标签\n * @property  $value 字典值\n * @property  $color 字典颜色\n * @property  $code 字典标示\n * @property  $sort 排序\n * @property  $status 状态\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemDictData extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $primaryKey = 'id';\n\n    protected $table = 'sa_system_dict_data';\n\n    /**\n     * 关键字搜索\n     */\n    public function searchKeywordsAttr($query, $value)\n    {\n        $query->where('label|code', 'LIKE', \"%$value%\");\n    }\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/system/SystemDictType.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseModel;\n\n/**\n * 字典类型模型\n *\n * sa_system_dict_type 字典类型表\n *\n * @property  $id 主键\n * @property  $name 字典名称\n * @property  $code 字典标示\n * @property  $status 状态\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemDictType extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $primaryKey = 'id';\n\n    protected $table = 'sa_system_dict_type';\n\n    /**\n     * 关联字典数据\n     */\n    public function dicts()\n    {\n        return $this->hasMany(SystemDictData::class, 'type_id', 'id');\n    }\n\n    /**\n     * 名称搜索\n     */\n    public function searchNameAttr($query, $value)\n    {\n        return $query->where('name', 'like', '%' . $value . '%');\n    }\n\n    /**\n     * 编码搜索\n     */\n    public function searchCodeAttr($query, $value)\n    {\n        return $query->where('code', 'like', '%' . $value . '%');\n    }\n\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/system/SystemLoginLog.php",
    "content": "<?php\n\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseModel;\n\n/**\n * 登录日志模型\n *\n * sa_system_login_log 登录日志表\n *\n * @property  $id 主键\n * @property  $username 用户名\n * @property  $ip 登录IP地址\n * @property  $ip_location IP所属地\n * @property  $os 操作系统\n * @property  $browser 浏览器\n * @property  $status 登录状态\n * @property  $message 提示消息\n * @property  $login_time 登录时间\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 更新时间\n */\nclass SystemLoginLog extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $primaryKey = 'id';\n\n    protected $table = 'sa_system_login_log';\n\n    /**\n     * 时间范围搜索\n     */\n    public function searchLoginTimeAttr($query, $value)\n    {\n        $query->whereTime('login_time', 'between', $value);\n    }\n\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/system/SystemMail.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseModel;\n\n/**\n * 邮件记录模型\n *\n * sa_system_mail 邮件记录\n *\n * @property  $id 编号\n * @property  $gateway 网关\n * @property  $from 发送人\n * @property  $email 接收人\n * @property  $code 验证码\n * @property  $content 邮箱内容\n * @property  $status 发送状态\n * @property  $response 返回结果\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemMail extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $primaryKey = 'id';\n\n    protected $table = 'sa_system_mail';\n\n    /**\n     * 发送人搜索\n     */\n    public function searchFromAttr($query, $value)\n    {\n        $query->where('from', 'like', '%' . $value . '%');\n    }\n\n    /**\n     * 接收人搜索\n     */\n    public function searchEmailAttr($query, $value)\n    {\n        $query->where('email', 'like', '%' . $value . '%');\n    }\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/system/SystemMenu.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseModel;\n\n/**\n * 菜单模型\n *\n * sa_system_menu 菜单权限表\n *\n * @property  $id \n * @property  $parent_id 父级ID\n * @property  $name 菜单名称\n * @property  $code 组件名称\n * @property  $slug 权限标识，如 user:list, user:add\n * @property  $type 类型: 1目录, 2菜单, 3按钮/API\n * @property  $path 路由地址或API路径\n * @property  $component 前端组件路径，如 layout/User\n * @property  $method 请求方式\n * @property  $icon 图标\n * @property  $sort 排序\n * @property  $link_url 外部链接\n * @property  $is_iframe 是否iframe\n * @property  $is_keep_alive 是否缓存\n * @property  $is_hidden 是否隐藏\n * @property  $is_fixed_tab 是否固定标签页\n * @property  $is_full_page 是否全屏\n * @property  $generate_id 生成id\n * @property  $generate_key 生成key\n * @property  $status 状态\n * @property  $remark \n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemMenu extends BaseModel\n{\n    // 完整数据库表名称\n    protected $table = 'sa_system_menu';\n    // 主键\n    protected $primaryKey = 'id';\n\n    /**\n     * Id搜索\n     */\n    public function searchIdAttr($query, $value)\n    {\n        $query->whereIn('id', $value);\n    }\n\n    /**\n     * 名称搜索\n     */\n    public function searchNameAttr($query, $value)\n    {\n        $query->where('name', 'like', '%' . $value . '%');\n    }\n\n    /**\n     * 路径搜索\n     */\n    public function searchPathAttr($query, $value)\n    {\n        $query->where('path', 'like', '%' . $value . '%');\n    }\n\n    /**\n     * 菜单搜索\n     */\n    public function searchMenuAttr($query, $value)\n    {\n        if (!empty($value)) {\n            $query->whereIn('type', [1, 2]);\n        }\n    }\n\n    /**\n     * 类型搜索\n     */\n    public function searchTypeAttr($query, $value)\n    {\n        if (is_array($value)) {\n            $query->whereIn('type', $value);\n        } else {\n            $query->where('type', $value);\n        }\n    }\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/system/SystemOperLog.php",
    "content": "<?php\n\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseModel;\n\n/**\n * 操作日志模型\n *\n * sa_system_oper_log 操作日志表\n *\n * @property  $id 主键\n * @property  $username 用户名\n * @property  $app 应用名称\n * @property  $method 请求方式\n * @property  $router 请求路由\n * @property  $service_name 业务名称\n * @property  $ip 请求IP地址\n * @property  $ip_location IP所属地\n * @property  $request_data 请求数据\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 更新时间\n */\nclass SystemOperLog extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $primaryKey = 'id';\n\n    protected $table = 'sa_system_oper_log';\n\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/system/SystemPost.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseModel;\n\n/**\n * 岗位模型\n *\n * sa_system_post 岗位信息表\n *\n * @property  $id 主键\n * @property  $name 岗位名称\n * @property  $code 岗位代码\n * @property  $sort 排序\n * @property  $status 状态\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemPost extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $primaryKey = 'id';\n\n    protected $table = 'sa_system_post';\n\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/system/SystemRole.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseModel;\n\n/**\n * 角色模型\n *\n * sa_system_role 角色表\n *\n * @property  $id \n * @property  $name 角色名称\n * @property  $code 角色标识，如: hr_manager\n * @property  $level 角色级别：用于行政控制，不可操作级别大于自己的角色\n * @property  $data_scope 数据范围: 1全部, 2本部门及下属, 3本部门, 4仅本人, 5自定义\n * @property  $remark 备注\n * @property  $sort \n * @property  $status 状态: 1启用, 0禁用\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemRole extends BaseModel\n{\n\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $primaryKey = 'id';\n\n    /**\n     * 数据表完整名称\n     * @var string\n     */\n    protected $table = 'sa_system_role';\n\n    /**\n     * 权限范围\n     */\n    public function scopeAuth($query, $value)\n    {\n        $id = $value['id'];\n        $roles = $value['roles'];\n        if ($id > 1) {\n            $ids = [];\n            foreach ($roles as $item) {\n                $ids[] = $item['id'];\n                $temp = static::whereRaw('FIND_IN_SET(\"' . $item['id'] . '\", level) > 0')->pluck('id')->toArray();\n                $ids = array_merge($ids, $temp);\n            }\n            $query->where('id', 'in', array_unique($ids));\n        }\n    }\n\n    /**\n     * 通过中间表获取菜单\n     */\n    public function menus()\n    {\n        return $this->belongsToMany(SystemMenu::class, SystemRoleMenu::class, 'role_id', 'menu_id');\n    }\n\n    /**\n     * 通过中间表获取部门\n     */\n    public function depts()\n    {\n        return $this->belongsToMany(SystemDept::class, SystemRoleDept::class, 'role_id', 'dept_id');\n    }\n\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/system/SystemRoleDept.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\Pivot;\n\n/**\n * 角色部门关联模型\n *\n * sa_system_role_dept 角色-自定义数据权限关联\n *\n * @property  $id \n * @property  $role_id \n * @property  $dept_id \n */\nclass SystemRoleDept extends Pivot\n{\n    protected $primaryKey = 'id';\n\n    protected $table = 'sa_system_role_dept';\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/system/SystemRoleMenu.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\Pivot;\n\n/**\n * 角色菜单关联模型\n *\n * sa_system_role_menu 角色权限关联\n *\n * @property  $id \n * @property  $role_id \n * @property  $menu_id \n */\nclass SystemRoleMenu extends Pivot\n{\n    protected $primaryKey = 'id';\n\n    protected $table = 'sa_system_role_menu';\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/system/SystemUser.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseModel;\n\n/**\n * 用户信息模型\n *\n * sa_system_user 用户表\n *\n * @property  $id \n * @property  $username 登录账号\n * @property  $password 加密密码\n * @property  $realname 真实姓名\n * @property  $gender 性别\n * @property  $avatar 头像\n * @property  $email 邮箱\n * @property  $phone 手机号\n * @property  $signed 个性签名\n * @property  $dashboard 工作台\n * @property  $dept_id 主归属部门\n * @property  $is_super 是否超级管理员: 1是\n * @property  $status 状态: 1启用, 2禁用\n * @property  $remark 备注\n * @property  $login_time 最后登录时间\n * @property  $login_ip 最后登录IP\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemUser extends BaseModel\n{\n\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $primaryKey = 'id';\n\n    /**\n     * 数据表完整名称\n     * @var string\n     */\n    protected $table = 'sa_system_user';\n\n    /**\n     * 关键字搜索\n     */\n    public function searchKeywordAttr($query, $value)\n    {\n        if ($value) {\n            $query->whereAny(['username', 'realname', 'phone'], 'like', '%' . $value . '%');\n        }\n    }\n\n    /**\n     * 权限范围 - 过滤部门用户\n     */\n    public function scopeAuth($query, $value)\n    {\n        if (!empty($value)) {\n            $deptIds = [$value['id']];\n            $deptLevel = $value['level'] . $value['id'] . ',';\n            $dept_ids = SystemDept::where('level', 'like', $deptLevel . '%')->pluck('id')->toArray();\n            $deptIds = array_merge($deptIds, $dept_ids);\n            $query->whereIn('dept_id', $deptIds);\n        }\n    }\n\n    /**\n     * 通过中间表关联角色\n     */\n    public function roles()\n    {\n        return $this->belongsToMany(SystemRole::class, SystemUserRole::class, 'user_id', 'role_id');\n    }\n\n    /**\n     * 通过中间表关联岗位\n     */\n    public function posts()\n    {\n        return $this->belongsToMany(SystemPost::class, SystemUserPost::class, 'user_id', 'post_id');\n    }\n\n    /**\n     * 通过中间表关联部门\n     */\n    public function depts()\n    {\n        return $this->belongsTo(SystemDept::class, 'dept_id', 'id');\n    }\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/system/SystemUserPost.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\Pivot;\n\n/**\n * 用户岗位关联模型\n *\n * sa_system_user_post 用户与岗位关联表\n *\n * @property  $id 主键\n * @property  $user_id 用户主键\n * @property  $post_id 岗位主键\n */\nclass SystemUserPost extends Pivot\n{\n    protected $primaryKey = 'id';\n\n    protected $table = 'sa_system_user_post';\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/system/SystemUserRole.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\Pivot;\n\n/**\n * 用户角色关联模型\n *\n * sa_system_user_role 用户角色关联\n *\n * @property  $id \n * @property  $user_id \n * @property  $role_id \n */\nclass SystemUserRole extends Pivot\n{\n    protected $primaryKey = 'id';\n    protected $table = 'sa_system_user_role';\n\n    /**\n     * 获取角色id\n     * @param mixed $user_id\n     * @return array\n     */\n    public static function getRoleIds($user_id): array\n    {\n        return static::where('user_id', $user_id)->pluck('role_id')->toArray();\n    }\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/tool/Crontab.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\tool;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseModel;\n\n/**\n * 定时任务模型\n *\n * sa_tool_crontab 定时任务信息表\n *\n * @property  $id 主键\n * @property  $name 任务名称\n * @property  $type 任务类型\n * @property  $target 调用任务字符串\n * @property  $parameter 调用任务参数\n * @property  $task_style 执行类型\n * @property  $rule 任务执行表达式\n * @property  $status 状态\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass Crontab extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_tool_crontab';\n\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/tool/CrontabLog.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\tool;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseModel;\n\n/**\n * 定时任务日志模型\n *\n * sa_tool_crontab_log 定时任务执行日志表\n *\n * @property  $id 主键\n * @property  $crontab_id 任务ID\n * @property  $name 任务名称\n * @property  $target 任务调用目标字符串\n * @property  $parameter 任务调用参数\n * @property  $exception_info 异常信息\n * @property  $status 执行状态\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass CrontabLog extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_tool_crontab_log';\n\n    protected $guarded = ['created_by', 'updated_by'];\n\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/tool/GenerateColumns.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\tool;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseModel;\n\n/**\n * 代码生成业务字段模型\n */\nclass GenerateColumns extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_tool_generate_columns';\n\n    protected $guarded = ['is_cover'];\n\n}"
  },
  {
    "path": "src/orm/eloquent/app/model/tool/GenerateTables.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\tool;\n\nuse plugin\\saiadmin\\basic\\eloquent\\BaseModel;\n\n/**\n * 代码生成业务模型\n */\nclass GenerateTables extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_tool_generate_tables';\n\n}"
  },
  {
    "path": "src/orm/think/app/logic/system/DatabaseLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse support\\think\\Db;\n\n/**\n * 数据表维护逻辑层\n */\nclass DatabaseLogic extends BaseLogic\n{\n    /**\n     * 获取数据源\n     * @return array\n     */\n    public function getDbSource(): array\n    {\n        $data = config('think-orm.connections');\n        $list = [];\n        foreach ($data as $k => $v) {\n            $list[] = $k;\n        }\n        return $list;\n    }\n\n    /**\n     * 数据列表\n     * @param $query\n     * @return mixed\n     */\n    public function getList($query): mixed\n    {\n        $request = request();\n        $page = $request ? ($request->input('page') ?: 1) : 1;\n        $limit = $request ? ($request->input('limit') ?: 10) : 10;\n\n        return self::getTableList($query, $page, $limit);\n    }\n\n    /**\n     * 获取数据库表数据\n     */\n    public function getTableList($query, $current_page = 1, $per_page = 10): array\n    {\n        if (!empty($query['source'])) {\n            if (!empty($query['name'])) {\n                $sql = 'show table status where name=:name ';\n                $list = Db::connect($query['source'])->query($sql, ['name' => $query['name']]);\n            } else {\n                $list = Db::connect($query['source'])->query('show table status');\n            }\n        } else {\n            if (!empty($query['name'])) {\n                $sql = 'show table status where name=:name ';\n                $list = Db::query($sql, ['name' => $query['name']]);\n            } else {\n                $list = Db::query('show table status');\n            }\n        }\n\n        $data = [];\n        foreach ($list as $item) {\n            $data[] = [\n                'name' => $item['Name'],\n                'engine' => $item['Engine'],\n                'rows' => $item['Rows'],\n                'data_free' => $item['Data_free'],\n                'data_length' => $item['Data_length'],\n                'index_length' => $item['Index_length'],\n                'collation' => $item['Collation'],\n                'create_time' => $item['Create_time'],\n                'update_time' => $item['Update_time'],\n                'comment' => $item['Comment'],\n            ];\n        }\n        $total = count($data);\n        $last_page = ceil($total / $per_page);\n        $startIndex = ($current_page - 1) * $per_page;\n        $pageData = array_slice($data, $startIndex, $per_page);\n        return [\n            'data' => $pageData,\n            'total' => $total,\n            'current_page' => $current_page,\n            'per_page' => $per_page,\n            'last_page' => $last_page,\n        ];\n    }\n\n    /**\n     * 获取列信息\n     */\n    public function getColumnList($table, $source): array\n    {\n        $columnList = [];\n        if (preg_match(\"/^[a-zA-Z0-9_]+$/\", $table)) {\n            if (!empty($source)) {\n                $list = Db::connect($source)->query('SHOW FULL COLUMNS FROM `' . $table . '`');\n            } else {\n                $list = Db::query('SHOW FULL COLUMNS FROM `' . $table . '`');\n            }\n            foreach ($list as $column) {\n                preg_match('/^\\w+/', $column['Type'], $matches);\n                $columnList[] = [\n                    'column_key' => $column['Key'],\n                    'column_name' => $column['Field'],\n                    'column_type' => $matches[0],\n                    'column_comment' => trim(preg_replace(\"/\\([^()]*\\)/\", \"\", $column['Comment'])),\n                    'extra' => $column['Extra'],\n                    'default_value' => $column['Default'],\n                    'is_nullable' => $column['Null'],\n                ];\n            }\n        }\n        return $columnList;\n    }\n\n    /**\n     * 优化表\n     */\n    public function optimizeTable($tables)\n    {\n        foreach ($tables as $table) {\n            if (preg_match(\"/^[a-zA-Z0-9_]+$/\", $table)) {\n                Db::execute('OPTIMIZE TABLE `' . $table . '`');\n            }\n        }\n    }\n\n    /**\n     * 清理表碎片\n     */\n    public function fragmentTable($tables)\n    {\n        foreach ($tables as $table) {\n            if (preg_match(\"/^[a-zA-Z0-9_]+$/\", $table)) {\n                Db::execute('ANALYZE TABLE `' . $table . '`');\n            }\n        }\n    }\n\n    /**\n     * 获取回收站数据\n     */\n    public function recycleData($table)\n    {\n        if (preg_match(\"/^[a-zA-Z0-9_]+$/\", $table)) {\n            // 查询表字段\n            $sql = 'SHOW COLUMNS FROM `' . $table . '` where Field = \"delete_time\"';\n            $columns = Db::query($sql);\n            $isDeleteTime = false;\n            if (count($columns) > 0) {\n                $isDeleteTime = true;\n            }\n            if (!$isDeleteTime) {\n                throw new ApiException('当前表不支持回收站功能');\n            }\n            // 查询软删除数据\n            $request = request();\n            $limit = $request ? ($request->input('limit') ?: 10) : 10;\n            return Db::table($table)->whereNotNull('delete_time')\n                ->order('delete_time', 'desc')\n                ->paginate($limit)\n                ->toArray();\n        } else {\n            return [];\n        }\n    }\n\n    /**\n     * 删除数据\n     * @param $table\n     * @param $ids\n     * @return bool\n     */\n    public function delete($table, $ids)\n    {\n        if (preg_match(\"/^[a-zA-Z0-9_]+$/\", $table)) {\n            $count = Db::table($table)->whereIn('id', $ids)->delete($ids);\n            return $count > 0;\n        } else {\n            return false;\n        }\n    }\n\n    /**\n     * 恢复数据\n     * @param $table\n     * @param $ids\n     * @return bool\n     */\n    public function recovery($table, $ids)\n    {\n        if (preg_match(\"/^[a-zA-Z0-9_]+$/\", $table)) {\n            $count = Db::table($table)\n                ->where('id', 'in', $ids)\n                ->update(['delete_time' => null]);\n            return $count > 0;\n        } else {\n            return false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/orm/think/app/logic/system/SystemAttachmentLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse Exception;\nuse plugin\\saiadmin\\app\\model\\system\\SystemAttachment;\nuse plugin\\saiadmin\\app\\model\\system\\SystemCategory;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\service\\storage\\ChunkUploadService;\nuse plugin\\saiadmin\\service\\storage\\UploadService;\nuse plugin\\saiadmin\\utils\\Arr;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 附件逻辑层\n */\nclass SystemAttachmentLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemAttachment();\n    }\n\n    /**\n     * @param $category_id\n     * @param $ids\n     * @return mixed\n     */\n    public function move($category_id, $ids): mixed\n    {\n        $category = SystemCategory::where('id', $category_id)->findOrEmpty();\n        if ($category->isEmpty()) {\n            throw new ApiException('目标分类不存在');\n        }\n        return $this->model->whereIn('id', $ids)->update(['category_id' => $category_id]);\n    }\n\n    /**\n     * 保存网络图片\n     * @param $url\n     * @param $config\n     * @return array\n     * @throws ApiException|Exception\n     */\n    public function saveNetworkImage($url, $config): array\n    {\n        $image_data = file_get_contents($url);\n        if ($image_data === false) {\n            throw new ApiException('获取文件资源失败');\n        }\n        $image_resource = imagecreatefromstring($image_data);\n        if (!$image_resource) {\n            throw new ApiException('创建图片资源失败');\n        }\n        $filename = basename($url);\n        $file_extension = pathinfo($filename, PATHINFO_EXTENSION);\n        $full_dir = runtime_path() . '/resource/';\n        if (!is_dir($full_dir)) {\n            mkdir($full_dir, 0777, true);\n        }\n        $save_path = $full_dir . $filename;\n        $mime_type = 'image/';\n        switch ($file_extension) {\n            case 'jpg':\n            case 'jpeg':\n                $mime_type = 'image/jpeg';\n                $result = imagejpeg($image_resource, $save_path);\n                break;\n            case 'png':\n                $mime_type = 'image/png';\n                $result = imagepng($image_resource, $save_path);\n                break;\n            case 'gif':\n                $mime_type = 'image/gif';\n                $result = imagegif($image_resource, $save_path);\n                break;\n            default:\n                imagedestroy($image_resource);\n                throw new ApiException('文件格式错误');\n        }\n        imagedestroy($image_resource);\n        if (!$result) {\n            throw new ApiException('文件保存失败');\n        }\n\n        $hash = md5_file($save_path);\n        $size = filesize($save_path);\n\n        $model = $this->model->where('hash', $hash)->find();\n        if ($model) {\n            unlink($save_path);\n            return $model->toArray();\n        } else {\n\n            $logic = new SystemConfigLogic();\n            $uploadConfig = $logic->getGroup('upload_config');\n\n            $root = Arr::getConfigValue($uploadConfig, 'local_root');\n\n            $folder = date('Ymd');\n            $full_dir = base_path() . DIRECTORY_SEPARATOR . $root . $folder . DIRECTORY_SEPARATOR;\n            if (!is_dir($full_dir)) {\n                mkdir($full_dir, 0777, true);\n            }\n            $object_name = bin2hex(pack('Nn', time(), random_int(1, 65535))) . \".$file_extension\";\n            $newPath = $full_dir . $object_name;\n\n            copy($save_path, $newPath);\n            unlink($save_path);\n            $domain = Arr::getConfigValue($uploadConfig, 'local_domain');\n            $uri = Arr::getConfigValue($uploadConfig, 'local_uri');\n            $baseUrl = $domain . $uri . $folder . '/';\n\n            $info['storage_mode'] = 1;\n            $info['category_id'] = request()->input('category_id', 1);\n            $info['origin_name'] = $filename;\n            $info['object_name'] = $object_name;\n            $info['hash'] = $hash;\n            $info['mime_type'] = $mime_type;\n            $info['storage_path'] = $root . $folder . '/' . $object_name;\n            $info['suffix'] = $file_extension;\n            $info['size_byte'] = $size;\n            $info['size_info'] = formatBytes($size);\n            $info['url'] = $baseUrl . $object_name;\n            $this->model->save($info);\n            return $info;\n        }\n    }\n\n    /**\n     * 文件上传\n     * @param string $upload\n     * @param bool $local\n     * @return array\n     */\n    public function uploadBase(string $upload = 'image', bool $local = false): array\n    {\n        $logic = new SystemConfigLogic();\n        $uploadConfig = $logic->getGroup('upload_config');\n        $type = Arr::getConfigValue($uploadConfig, 'upload_mode');\n        if ($local === true) {\n            $type = 1;\n        }\n        $result = UploadService::disk($type, $upload)->uploadFile();\n        $data = $result[0];\n        $hash = $data['unique_id'];\n        $hash_check = config('plugin.saiadmin.saithink.file_hash', false);\n        if ($hash_check) {\n            $model = $this->model->where('hash', $hash)->findOrEmpty();\n            if (!$model->isEmpty()) {\n                return $model->toArray();\n            }\n        }\n        $url = str_replace('\\\\', '/', $data['url']);\n        $savePath = str_replace('\\\\', '/', $data['save_path']);\n        $info['storage_mode'] = $type;\n        $info['category_id'] = request()->input('category_id', 1);\n        $info['origin_name'] = $data['origin_name'];\n        $info['object_name'] = $data['save_name'];\n        $info['hash'] = $data['unique_id'];\n        $info['mime_type'] = $data['mime_type'];\n        $info['storage_path'] = $savePath;\n        $info['suffix'] = $data['extension'];\n        $info['size_byte'] = $data['size'];\n        $info['size_info'] = formatBytes($data['size']);\n        $info['url'] = $url;\n        $this->model->save($info);\n        return $info;\n    }\n\n    /**\n     * 切片上传\n     * @param $data\n     * @return array\n     */\n    public function chunkUpload($data): array\n    {\n        $chunkService = new ChunkUploadService();\n        if ($data['index'] == 0) {\n            $model = $this->model->where('hash', $data['hash'])->findOrEmpty();\n            if (!$model->isEmpty()) {\n                return $model->toArray();\n            } else {\n                return $chunkService->checkChunk($data);\n            }\n        } else {\n            return $chunkService->uploadChunk($data);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/orm/think/app/logic/system/SystemCategoryLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\app\\model\\system\\SystemCategory;\nuse plugin\\saiadmin\\utils\\Helper;\nuse plugin\\saiadmin\\utils\\Arr;\n\n/**\n * 附件分类逻辑层\n */\nclass SystemCategoryLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemCategory();\n    }\n\n    /**\n     * 添加数据\n     */\n    public function add($data): bool\n    {\n        $data = $this->handleData($data);\n        return $this->model->save($data);\n    }\n\n    /**\n     * 修改数据\n     */\n    public function edit($id, $data): bool\n    {\n        $data = $this->handleData($data);\n        if ($data['parent_id'] == $id) {\n            throw new ApiException('上级分类和当前分类不能相同');\n        }\n        if (in_array($id, explode(',', $data['level']))) {\n            throw new ApiException('不能将上级分类设置为当前分类的子分类');\n        }\n        $model = $this->model->findOrEmpty($id);\n        if ($model->isEmpty()) {\n            throw new ApiException('数据不存在');\n        }\n        return $model->save($data);\n    }\n\n    /**\n     * 数据删除\n     */\n    public function destroy($ids): bool\n    {\n        $num = $this->model->where('parent_id', 'in', $ids)->count();\n        if ($num > 0) {\n            throw new ApiException('该部门下存在子分类，请先删除子分类');\n        } else {\n            return $this->model->destroy($ids);\n        }\n    }\n\n    /**\n     * 数据处理\n     */\n    protected function handleData($data)\n    {\n        if (empty($data['parent_id']) || $data['parent_id'] == 0) {\n            $data['level'] = '0';\n            $data['parent_id'] = 0;\n        } else {\n            $parentMenu = SystemCategory::findOrEmpty($data['parent_id']);\n            $data['level'] = $parentMenu['level'] . $parentMenu['id'] . ',';\n        }\n        return $data;\n    }\n\n    /**\n     * 数据树形化\n     * @param array $where\n     * @return array\n     */\n    public function tree(array $where = []): array\n    {\n        $query = $this->search($where);\n        $request = request();\n        if ($request && $request->input('tree', 'false') === 'true') {\n            $query->field('id, id as value, category_name as label, parent_id, category_name, sort');\n        }\n        $query->order('sort', 'desc');\n        $data = $this->getAll($query);\n        return Helper::makeTree($data);\n    }\n\n}\n"
  },
  {
    "path": "src/orm/think/app/logic/system/SystemConfigGroupLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\cache\\ConfigCache;\nuse plugin\\saiadmin\\app\\model\\system\\SystemConfigGroup;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\app\\model\\system\\SystemConfig;\nuse support\\think\\Db;\n\n/**\n * 参数配置分组逻辑层\n */\nclass SystemConfigGroupLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemConfigGroup();\n    }\n\n    /**\n     * 删除配置信息\n     */\n    public function destroy($ids): bool\n    {\n        $id = $ids[0];\n        $model = $this->model->where('id', $id)->findOrEmpty();\n        if ($model->isEmpty()) {\n            throw new ApiException('配置数据未找到');\n        }\n        if (in_array(intval($id), [1, 2, 3])) {\n            throw new ApiException('系统默认分组，无法删除');\n        }\n        Db::startTrans();\n        try {\n            // 删除配置组\n            $model->delete();\n            // 删除配置组数据\n            $typeIds = SystemConfig::where('group_id', $id)->column('id');\n            SystemConfig::destroy($typeIds);\n            ConfigCache::clearConfig($model->code);\n            Db::commit();\n            return true;\n        } catch (\\Exception $e) {\n            Db::rollback();\n            throw new ApiException('删除数据异常，请检查');\n        }\n    }\n}\n"
  },
  {
    "path": "src/orm/think/app/logic/system/SystemConfigLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\cache\\ConfigCache;\nuse plugin\\saiadmin\\app\\model\\system\\SystemConfig;\nuse plugin\\saiadmin\\app\\model\\system\\SystemConfigGroup;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 参数配置逻辑层\n */\nclass SystemConfigLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemConfig();\n    }\n\n    /**\n     * 添加数据\n     * @param mixed $data\n     * @return mixed\n     */\n    public function add($data): mixed\n    {\n        $result = $this->model->create($data);\n        $group = SystemConfigGroup::find($data['group_id']);\n        ConfigCache::clearConfig($group->code);\n        return $result;\n    }\n\n    /**\n     * 编辑数据\n     * @param mixed $id\n     * @param mixed $data\n     * @return bool\n     */\n    public function edit($id, $data): bool\n    {\n        $result = parent::edit($id, $data);\n        $group = SystemConfigGroup::find($data['group_id']);\n        ConfigCache::clearConfig($group->code);\n        return $result;\n    }\n\n    /**\n     * 批量更新\n     * @param mixed $group_id\n     * @param mixed $config\n     * @return bool\n     */\n    public function batchUpdate($group_id, $config): bool\n    {\n        $group = SystemConfigGroup::find($group_id);\n        if (!$group) {\n            throw new ApiException('配置组未找到');\n        }\n        $saveData = [];\n        foreach ($config as $key => $value) {\n            $saveData[] = [\n                'id' => $value['id'],\n                'group_id' => $group_id,\n                'name' => $value['name'],\n                'key' => $value['key'],\n                'value' => $value['value']\n            ];\n        }\n        // upsert: 根据 id 更新，如果不存在则插入\n        $this->model->saveAll($saveData);\n        ConfigCache::clearConfig($group->code);\n        return true;\n    }\n\n    /**\n     * 获取配置数据\n     * @param mixed $code\n     * @return array\n     */\n    public function getData($code): array\n    {\n        $group = SystemConfigGroup::where('code', $code)->findOrEmpty();\n        if (empty($group)) {\n            return [];\n        }\n        $config = SystemConfig::where('group_id', $group['id'])->select()->toArray();\n        return $config;\n    }\n\n    /**\n     * 获取配置组\n     */\n    public function getGroup($config): array\n    {\n        return ConfigCache::getConfig($config);\n    }\n\n}\n"
  },
  {
    "path": "src/orm/think/app/logic/system/SystemDeptLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDept;\nuse plugin\\saiadmin\\app\\model\\system\\SystemUser;\nuse plugin\\saiadmin\\utils\\Helper;\nuse plugin\\saiadmin\\utils\\Arr;\n\n/**\n * 部门逻辑层\n */\nclass SystemDeptLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemDept();\n    }\n\n    /**\n     * 添加数据\n     */\n    public function add($data): mixed\n    {\n        $data = $this->handleData($data);\n        $this->model->save($data);\n        return $this->model->getKey();\n    }\n\n    /**\n     * 修改数据\n     */\n    public function edit($id, $data): mixed\n    {\n        $oldLevel = $data['level'] . $id . ',';\n        $data = $this->handleData($data);\n        if ($data['parent_id'] == $id) {\n            throw new ApiException('上级部门和当前部门不能相同');\n        }\n        if (in_array($id, explode(',', $data['level']))) {\n            throw new ApiException('不能将上级部门设置为当前部门的子部门');\n        }\n        $newLevel = $data['level'] . $id . ',';\n        $deptIds = $this->model->where('level', 'like', $oldLevel . '%')->column('id');\n\n        return $this->transaction(function () use ($deptIds, $oldLevel, $newLevel, $data, $id) {\n            $this->model->whereIn('id', $deptIds)->exp('level', \"REPLACE(level, '$oldLevel', '$newLevel')\")->update([]);\n            return $this->model->update($data, ['id' => $id]);\n        });\n    }\n\n    /**\n     * 数据删除\n     */\n    public function destroy($ids): bool\n    {\n        $num = $this->model->where('parent_id', 'in', $ids)->count();\n        if ($num > 0) {\n            throw new ApiException('该部门下存在子部门，请先删除子部门');\n        } else {\n            $count = SystemUser::where('dept_id', 'in', $ids)->count();\n            if ($count > 0) {\n                throw new ApiException('该部门下存在用户，请先删除或者转移用户');\n            }\n            return $this->model->destroy($ids);\n        }\n    }\n\n    /**\n     * 数据处理\n     */\n    protected function handleData($data)\n    {\n        // 处理上级部门\n        if (empty($data['parent_id']) || $data['parent_id'] == 0) {\n            $data['level'] = '0';\n            $data['parent_id'] = 0;\n        } else {\n            $parentMenu = SystemDept::findOrEmpty($data['parent_id']);\n            $data['level'] = $parentMenu['level'] . $parentMenu['id'] . ',';\n        }\n        return $data;\n    }\n\n    /**\n     * 数据树形化\n     * @param array $where\n     * @return array\n     */\n    public function tree(array $where = []): array\n    {\n        $query = $this->search($where);\n        $request = request();\n        if ($request && $request->input('tree', 'false') === 'true') {\n            $query->field('id, id as value, name as label, parent_id');\n        }\n        $query->order('sort', 'desc');\n        $query->with(['leader']);\n        $data = $this->getAll($query);\n        return Helper::makeTree($data);\n    }\n\n    /**\n     * 可操作部门\n     * @param array $where\n     * @return array\n     */\n    public function accessDept(array $where = []): array\n    {\n        $query = $this->search($where);\n        $query->auth($this->adminInfo['deptList']);\n        $query->field('id, id as value, name as label, parent_id');\n        $query->order('sort', 'desc');\n        $data = $this->getAll($query);\n        return Helper::makeTree($data);\n    }\n\n}\n"
  },
  {
    "path": "src/orm/think/app/logic/system/SystemDictDataLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDictData;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDictType;\nuse plugin\\saiadmin\\app\\cache\\DictCache;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 字典类型逻辑层\n */\nclass SystemDictDataLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemDictData();\n    }\n\n    /**\n     * 添加数据\n     * @param $data\n     * @return mixed\n     */\n    public function add($data): mixed\n    {\n        $type = SystemDictType::where('id', $data['type_id'])->findOrEmpty();\n        if ($type->isEmpty()) {\n            throw new ApiException('字典类型不存在');\n        }\n        $data['code'] = $type->code;\n        $model = $this->model->create($data);\n        DictCache::clear();\n        return $model->getKey();\n    }\n\n}\n"
  },
  {
    "path": "src/orm/think/app/logic/system/SystemDictTypeLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDictType;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDictData;\nuse support\\think\\Db;\n\n/**\n * 字典类型逻辑层\n */\nclass SystemDictTypeLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemDictType();\n    }\n\n    /**\n     * 添加数据\n     */\n    public function add($data): mixed\n    {\n        $model = $this->model->where('code', $data['code'])->findOrEmpty();\n        if (!$model->isEmpty()) {\n            throw new ApiException('该字典标识已存在');\n        }\n        return $this->model->save($data);\n    }\n\n    /**\n     * 数据更新\n     */\n    public function edit($id, $data): mixed\n    {\n        Db::startTrans();\n        try {\n            // 修改数据字典类型\n            $result = $this->model->update($data, ['id' => $id]);\n            // 更新数据字典数据\n            SystemDictData::update(['code' => $data['code']], ['type_id' => $id]);\n            Db::commit();\n            return $result;\n        } catch (\\Exception $e) {\n            Db::rollback();\n            throw new ApiException('修改数据异常，请检查');\n        }\n    }\n\n    /**\n     * 数据删除\n     */\n    public function destroy($ids): bool\n    {\n        Db::startTrans();\n        try {\n            // 删除数据字典类型\n            $result = $this->model->destroy($ids);\n            // 删除数据字典数据\n            $typeIds = SystemDictData::where('type_id', 'in', $ids)->column('id');\n            SystemDictData::destroy($typeIds);\n            Db::commit();\n            return $result;\n        } catch (\\Exception $e) {\n            Db::rollback();\n            throw new ApiException('删除数据异常，请检查');\n        }\n    }\n\n    /**\n     * 获取全部字典\n     * @return array\n     */\n    public function getDictAll(): array\n    {\n        $data = $this->model->where('status', 1)->field('id, name, code, remark')\n            ->with([\n                'dicts' => function ($query) {\n                    $query->where('status', 1)->field('id, type_id, label, value, color, code, sort')->order('sort', 'desc');\n                }\n            ])->select()->toArray();\n        return $this->packageDict($data, 'code');\n    }\n\n    /**\n     * 组合数据\n     * @param $array\n     * @param $field\n     * @return array\n     */\n    private function packageDict($array, $field): array\n    {\n        $result = [];\n        foreach ($array as $item) {\n            if (isset($item[$field])) {\n                if (isset($result[$item[$field]])) {\n                    $result[$item[$field]] = [($result[$item[$field]])];\n                    $result[$item[$field]][] = $item['dicts'];\n                } else {\n                    $result[$item[$field]] = $item['dicts'];\n                }\n            }\n        }\n        return $result;\n    }\n\n}\n"
  },
  {
    "path": "src/orm/think/app/logic/system/SystemLoginLogLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\model\\system\\SystemLoginLog;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\utils\\Helper;\nuse support\\think\\Db;\n\n/**\n * 登录日志逻辑层\n */\nclass SystemLoginLogLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemLoginLog();\n    }\n\n    /**\n     * 登录统计图表\n     * @return array\n     */\n    public function loginChart(): array\n    {\n        $sql = \"\n            SELECT\n                d.date AS login_date,\n                COUNT(l.login_time) AS login_count\n            FROM\n                (SELECT CURDATE() - INTERVAL (a.N) DAY AS date\n                 FROM (SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n                       UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n                       UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a\n                 ) d\n            LEFT JOIN sa_system_login_log l\n                ON DATE(l.login_time) = d.date\n            GROUP BY d.date\n            ORDER BY d.date ASC;\n        \";\n        $data = Db::query($sql);\n        return [\n            'login_count' => array_column($data, 'login_count'),\n            'login_date' => array_column($data, 'login_date'),\n        ];\n    }\n\n    /**\n     * 登录统计图表\n     * @return array\n     */\n    public function loginBarChart(): array\n    {\n        $sql = \"\n            SELECT\n                -- 拼接成 YYYY-MM 格式，例如 2023-01\n                CONCAT(LPAD(m.month_num, 2, '0'), '月') AS login_month,\n                COUNT(l.login_time) AS login_count\n            FROM\n                -- 生成 1 到 12 的月份数字\n                (SELECT 1 AS month_num UNION ALL SELECT 2 UNION ALL SELECT 3\n                 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n                 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n                 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12) m\n            LEFT JOIN sa_system_login_log l\n                -- 关联条件：年份等于今年 且 月份等于生成的数字\n                ON YEAR(l.login_time) = YEAR(CURDATE())\n                AND MONTH(l.login_time) = m.month_num\n            GROUP BY\n                m.month_num\n            ORDER BY\n                m.month_num ASC;\n        \";\n        $data = Db::query($sql);\n        return [\n            'login_count' => array_column($data, 'login_count'),\n            'login_month' => array_column($data, 'login_month'),\n        ];\n    }\n\n}\n"
  },
  {
    "path": "src/orm/think/app/logic/system/SystemMailLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\model\\system\\SystemMail;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 邮件模型逻辑层\n */\nclass SystemMailLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemMail();\n    }\n\n}\n"
  },
  {
    "path": "src/orm/think/app/logic/system/SystemMenuLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\model\\system\\SystemMenu;\nuse plugin\\saiadmin\\app\\model\\system\\SystemRoleMenu;\nuse plugin\\saiadmin\\app\\model\\system\\SystemUserRole;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\utils\\Arr;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 菜单逻辑层\n */\nclass SystemMenuLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemMenu();\n    }\n\n    /**\n     * 数据添加\n     */\n    public function add($data): mixed\n    {\n        $data = $this->handleData($data);\n        return $this->model->save($data);\n    }\n\n    /**\n     * 数据修改\n     */\n    public function edit($id, $data): mixed\n    {\n        $data = $this->handleData($data);\n        if ($data['parent_id'] == $id) {\n            throw new ApiException('不能设置父级为自身');\n        }\n        return $this->model->update($data, ['id' => $id]);\n    }\n\n    /**\n     * 数据删除\n     */\n    public function destroy($ids): bool\n    {\n        $num = $this->model->where('parent_id', 'in', $ids)->count();\n        if ($num > 0) {\n            throw new ApiException('该菜单下存在子菜单，请先删除子菜单');\n        } else {\n            return $this->model->destroy($ids);\n        }\n    }\n\n    /**\n     * 数据处理\n     */\n    protected function handleData($data)\n    {\n        // 处理上级菜单\n        if (empty($data['parent_id']) || $data['parent_id'] == 0) {\n            $data['level'] = '0';\n            $data['parent_id'] = 0;\n        } else {\n            $parentMenu = $this->model->findOrEmpty($data['parent_id']);\n            $data['level'] = $parentMenu['level'] . $parentMenu['id'] . ',';\n        }\n        return $data;\n    }\n\n    /**\n     * 数据树形化\n     * @param $where\n     * @return array\n     */\n    public function tree($where = []): array\n    {\n        $query = $this->search($where);\n        $request = request();\n        if ($request && $request->input('tree', 'false') === 'true') {\n            $query->field('id, id as value, name as label, parent_id, type');\n        }\n        $query->order('sort', 'desc');\n        $data = $this->getAll($query);\n        return Helper::makeTree($data);\n    }\n\n    /**\n     * 权限菜单\n     * @return array\n     */\n    public function auth(): array\n    {\n        $roleLogic = new SystemRoleLogic();\n        $role_ids = Arr::getArrayColumn($this->adminInfo['roleList'], 'id');\n        $roles = $roleLogic->getMenuIdsByRoleIds($role_ids);\n        $ids = $this->filterMenuIds($roles);\n        $query = $this->model\n            ->field('id, id as value, name as label, parent_id, type')\n            ->where('status', 1)\n            ->where('id', 'in', $ids)\n            ->order('sort', 'desc');\n        $data = $this->getAll($query);\n        return Helper::makeTree($data);\n    }\n\n    /**\n     * 获取全部菜单\n     */\n    public function getAllMenus(): array\n    {\n        $query = $this->search(['status' => 1, 'type' => [1, 2, 4]])->order('sort', 'desc');\n        $data = $this->getAll($query);\n        return Helper::makeArtdMenus($data);\n    }\n\n    /**\n     * 获取全部权限\n     * @return array\n     */\n    public function getAllAuth(): array\n    {\n        return SystemMenu::where('type', 3)\n            ->where('status', 1)\n            ->column('slug');\n    }\n\n    /**\n     * 根据角色获取权限\n     * @param $roleIds\n     * @return array\n     */\n    public function getAuthByRole($roleIds): array\n    {\n        $menuId = SystemRoleMenu::whereIn('role_id', $roleIds)->column('menu_id');\n\n        return SystemMenu::distinct(true)\n            ->where('type', 3)\n            ->where('status', 1)\n            ->where('id', 'in', array_unique($menuId))\n            ->column('slug');\n    }\n\n    /**\n     * 根据角色获取菜单\n     * @param $roleIds\n     * @return array\n     */\n    public function getMenuByRole($roleIds): array\n    {\n        $menuId = SystemRoleMenu::whereIn('role_id', $roleIds)->column('menu_id');\n\n        $data = SystemMenu::distinct(true)\n            ->where('status', 1)\n            ->where('type', 'in', [1, 2, 4])\n            ->where('id', 'in', array_unique($menuId))\n            ->order('sort', 'desc')\n            ->select()\n            ->toArray();\n        return Helper::makeArtdMenus($data);\n    }\n\n    /**\n     * 过滤通过角色查询出来的菜单id列表，并去重\n     * @param array $roleData\n     * @return array\n     */\n    public function filterMenuIds(array &$roleData): array\n    {\n        $ids = [];\n        foreach ($roleData as $val) {\n            foreach ($val['menus'] as $menu) {\n                $ids[] = $menu['id'];\n            }\n        }\n        unset($roleData);\n        return array_unique($ids);\n    }\n\n}"
  },
  {
    "path": "src/orm/think/app/logic/system/SystemOperLogLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\model\\system\\SystemOperLog;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 操作日志逻辑层\n */\nclass SystemOperLogLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemOperLog();\n    }\n\n    /**\n     * 获取自己的操作日志\n     * @param mixed $where\n     * @return array\n     */\n    public function getOwnOperLogList($where): array\n    {\n        $query = $this->search($where);\n        $query->field('id, username, method, router, service_name, ip, ip_location, create_time');\n        return $this->getList($query);\n    }\n\n}\n"
  },
  {
    "path": "src/orm/think/app/logic/system/SystemPostLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\model\\system\\SystemPost;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\service\\OpenSpoutWriter;\nuse OpenSpout\\Reader\\XLSX\\Reader;\n\n/**\n * 岗位管理逻辑层\n */\nclass SystemPostLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemPost();\n    }\n\n    /**\n     * 可操作岗位\n     * @param array $where\n     * @return array\n     */\n    public function accessPost(array $where = []): array\n    {\n        $query = $this->search($where);\n        $query->field('id, id as value, name as label, name, code');\n        return $this->getAll($query);\n    }\n\n    /**\n     * 导入数据\n     */\n    public function import($file)\n    {\n        $path = $this->getImport($file);\n        $reader = new Reader();\n        try {\n            $reader->open($path);\n            $data = [];\n            foreach ($reader->getSheetIterator() as $sheet) {\n                $isHeader = true;\n                foreach ($sheet->getRowIterator() as $row) {\n                    if ($isHeader) {\n                        $isHeader = false;\n                        continue;\n                    }\n                    $cells = $row->getCells();\n                    $data[] = [\n                        'name' => $cells[0]->getValue(),\n                        'code' => $cells[1]->getValue(),\n                        'sort' => $cells[2]->getValue(),\n                        'status' => $cells[3]->getValue(),\n                    ];\n                }\n            }\n            $this->saveAll($data);\n        } catch (\\Exception $e) {\n            throw new ApiException('导入文件错误，请上传正确的文件格式xlsx');\n        }\n    }\n\n    /**\n     * 导出数据\n     */\n    public function export($where = [])\n    {\n        $query = $this->search($where)->field('id,name,code,sort,status,create_time');\n        $data = $this->getAll($query);\n        $file_name = '岗位数据.xlsx';\n        $header = ['编号', '岗位名称', '岗位标识', '排序', '状态', '创建时间'];\n        $filter = [\n            'status' => [\n                ['value' => 1, 'label' => '正常'],\n                ['value' => 2, 'label' => '禁用']\n            ]\n        ];\n        $writer = new OpenSpoutWriter($file_name);\n        $writer->setWidth([15, 15, 20, 15, 15, 25]);\n        $writer->setHeader($header);\n        $writer->setData($data, null, $filter);\n        $file_path = $writer->returnFile();\n        return response()->download($file_path, urlencode($file_name));\n    }\n\n}\n"
  },
  {
    "path": "src/orm/think/app/logic/system/SystemRoleLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\cache\\UserMenuCache;\nuse plugin\\saiadmin\\app\\model\\system\\SystemRole;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\utils\\Helper;\nuse support\\think\\Cache;\nuse support\\think\\Db;\n\n/**\n * 角色逻辑层\n */\nclass SystemRoleLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemRole();\n    }\n\n    /**\n     * 添加数据\n     */\n    public function add($data): bool\n    {\n        $data = $this->handleData($data);\n        return $this->model->save($data);\n    }\n\n    /**\n     * 修改数据\n     */\n    public function edit($id, $data): bool\n    {\n        $model = $this->model->findOrEmpty($id);\n        if ($model->isEmpty()) {\n            throw new ApiException('数据不存在');\n        }\n        $data = $this->handleData($data);\n        return $model->save($data);\n    }\n\n    /**\n     * 删除数据\n     */\n    public function destroy($ids): bool\n    {\n        // 越权保护\n        $levelArr = array_column($this->adminInfo['roleList'], 'level');\n        $maxLevel = max($levelArr);\n\n        $num = SystemRole::where('level', '>=', $maxLevel)->whereIn('id', $ids)->count();\n        if ($num > 0) {\n            throw new ApiException('不能操作比当前账户职级高的角色');\n        } else {\n            return $this->model->destroy($ids);\n        }\n    }\n\n    /**\n     * 数据处理\n     */\n    protected function handleData($data)\n    {\n        // 越权保护\n        $levelArr = array_column($this->adminInfo['roleList'], 'level');\n        $maxLevel = max($levelArr);\n        if ($data['level'] >= $maxLevel) {\n            throw new ApiException('不能操作比当前账户职级高的角色');\n        }\n        return $data;\n    }\n\n    /**\n     * 可操作角色\n     * @param array $where\n     * @return array\n     */\n    public function accessRole(array $where = []): array\n    {\n        $query = $this->search($where);\n        // 越权保护\n        $levelArr = array_column($this->adminInfo['roleList'], 'level');\n        $maxLevel = max($levelArr);\n        $query->where('level', '<', $maxLevel);\n        $query->order('sort', 'desc');\n        return $this->getAll($query);\n    }\n\n    /**\n     * 根据角色数组获取菜单\n     * @param $ids\n     * @return array\n     */\n    public function getMenuIdsByRoleIds($ids): array\n    {\n        if (empty($ids))\n            return [];\n        return $this->model->where('id', 'in', $ids)->with([\n            'menus' => function ($query) {\n                $query->where('status', 1)->order('sort', 'desc');\n            }\n        ])->select()->toArray();\n\n    }\n\n    /**\n     * 根据角色获取菜单\n     * @param $id\n     * @return array\n     */\n    public function getMenuByRole($id): array\n    {\n        $role = $this->model->findOrEmpty($id);\n        $menus = $role->menus ?: [];\n        return [\n            'id' => $id,\n            'menus' => $menus\n        ];\n    }\n\n    /**\n     * 保存菜单权限\n     * @param $id\n     * @param $menu_ids\n     * @return mixed\n     */\n    public function saveMenuPermission($id, $menu_ids): mixed\n    {\n        return $this->transaction(function () use ($id, $menu_ids) {\n            $role = $this->model->findOrEmpty($id);\n            if ($role) {\n                $role->menus()->detach();\n                $data = array_map(function ($menu_id) use ($id) {\n                    return ['menu_id' => $menu_id, 'role_id' => $id];\n                }, $menu_ids);\n                Db::name('sa_system_role_menu')->limit(100)->insertAll($data);\n            }\n            $cache = config('plugin.saiadmin.saithink.button_cache');\n            $tag = $cache['role'] . $id;\n            Cache::tag($tag)->clear();       // 清理权限缓存-角色TAG\n            UserMenuCache::clearMenuCache(); // 清理菜单缓存\n            return true;\n        });\n    }\n\n}\n"
  },
  {
    "path": "src/orm/think/app/logic/system/SystemUserLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\cache\\UserAuthCache;\nuse plugin\\saiadmin\\app\\cache\\UserInfoCache;\nuse plugin\\saiadmin\\app\\cache\\UserMenuCache;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDept;\nuse plugin\\saiadmin\\app\\model\\system\\SystemRole;\nuse plugin\\saiadmin\\app\\model\\system\\SystemUser;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse Webman\\Event\\Event;\nuse Tinywan\\Jwt\\JwtToken;\n\n/**\n * 用户信息逻辑层\n */\nclass SystemUserLogic extends BaseLogic\n{\n\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemUser();\n    }\n\n    /**\n     * 分页数据列表\n     * @param mixed $where\n     * @return array\n     */\n    public function indexList($where): array\n    {\n        $query = $this->search($where);\n        $query->with(['depts']);\n        $query->auth($this->adminInfo['deptList']);\n        return $this->getList($query);\n    }\n\n    /**\n     * 用户列表数据\n     * @param mixed $where\n     * @return array\n     */\n    public function openUserList($where): array\n    {\n        $query = $this->search($where);\n        $query->field('id, username, realname, avatar, phone, email');\n        return $this->getList($query);\n    }\n\n    /**\n     * 读取用户信息\n     * @param mixed $id\n     * @return array\n     */\n    public function getUser($id): array\n    {\n        $admin = $this->model->findOrEmpty($id);\n        $data = $admin->hidden(['password'])->toArray();\n        $data['roleList'] = $admin->roles->toArray() ?: [];\n        $data['postList'] = $admin->posts->toArray() ?: [];\n        $data['deptList'] = $admin->depts ? $admin->depts->toArray() : [];\n        return $data;\n    }\n\n    /**\n     * 读取数据\n     * @param $id\n     * @return array\n     */\n    public function read($id): array\n    {\n        $data = $this->getUser($id);\n        if ($this->adminInfo['id'] > 1) {\n            // 部门保护\n            if (!$this->deptProtect($this->adminInfo['deptList'], $data['dept_id'])) {\n                throw new ApiException('没有权限操作该部门数据');\n            }\n        }\n        return $data;\n    }\n\n    /**\n     * 添加数据\n     * @param $data\n     * @return mixed\n     */\n    public function add($data): mixed\n    {\n        $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);\n        return $this->transaction(function () use ($data) {\n            $role_ids = $data['role_ids'] ?? [];\n            $post_ids = $data['post_ids'] ?? [];\n            if ($this->adminInfo['id'] > 1) {\n                // 部门保护\n                if (!$this->deptProtect($this->adminInfo['deptList'], $data['dept_id'])) {\n                    throw new ApiException('没有权限操作该部门数据');\n                }\n                // 越权保护\n                if (!$this->roleProtect($this->adminInfo['roleList'], $role_ids)) {\n                    throw new ApiException('没有权限操作该角色数据');\n                }\n            }\n            $user = SystemUser::create($data);\n            $user->roles()->detach();\n            $user->posts()->detach();\n            $user->roles()->saveAll($role_ids);\n            if (!empty($post_ids)) {\n                $user->posts()->save($post_ids);\n            }\n            return $user;\n        });\n    }\n\n    /**\n     * 修改数据\n     * @param $id\n     * @param $data\n     * @return mixed\n     */\n    public function edit($id, $data): mixed\n    {\n        unset($data['password']);\n        return $this->transaction(function () use ($data, $id) {\n            $role_ids = $data['role_ids'] ?? [];\n            $post_ids = $data['post_ids'] ?? [];\n            // 仅可修改当前部门和子部门的用户\n            $query = $this->model->where('id', $id);\n            $query->auth($this->adminInfo['deptList']);\n            $user = $query->findOrEmpty();\n            if ($user->isEmpty()) {\n                throw new ApiException('没有权限操作该数据');\n            }\n            if ($this->adminInfo['id'] > 1) {\n                // 部门保护\n                if (!$this->deptProtect($this->adminInfo['deptList'], $data['dept_id'])) {\n                    throw new ApiException('没有权限操作该部门数据');\n                }\n                // 越权保护\n                if (!$this->roleProtect($this->adminInfo['roleList'], $role_ids)) {\n                    throw new ApiException('没有权限操作该角色数据');\n                }\n            }\n            $result = parent::edit($id, $data);\n            if ($result) {\n                $user->roles()->detach();\n                $user->posts()->detach();\n                $user->roles()->saveAll($role_ids);\n                if (!empty($post_ids)) {\n                    $user->posts()->save($post_ids);\n                }\n                UserInfoCache::clearUserInfo($id);\n                UserAuthCache::clearUserAuth($id);\n                UserMenuCache::clearUserMenu($id);\n            }\n            return $result;\n        });\n    }\n\n    /**\n     * 删除数据\n     * @param $ids\n     * @return bool\n     */\n    public function destroy($ids): bool\n    {\n        if (is_array($ids)) {\n            if (count($ids) > 1) {\n                throw new ApiException('禁止批量删除操作');\n            }\n            $ids = $ids[0];\n        }\n        if ($ids == 1) {\n            throw new ApiException('超级管理员禁止删除');\n        }\n        $query = $this->model->where('id', $ids);\n        $query->auth($this->adminInfo['deptList']);\n        $user = $query->findOrEmpty();\n        if ($user->isEmpty()) {\n            throw new ApiException('没有权限操作该数据');\n        }\n        if ($this->adminInfo['id'] > 1) {\n            $role_ids = $user->roles->toArray() ?: [];\n            if (!empty($role_ids)) {\n                // 越权保护\n                if (!$this->roleProtect($this->adminInfo['roleList'], array_column($role_ids, 'id'))) {\n                    throw new ApiException('没有权限操作该角色数据');\n                }\n            }\n        }\n        UserInfoCache::clearUserInfo($ids);\n        UserAuthCache::clearUserAuth($ids);\n        UserMenuCache::clearUserMenu($ids);\n        return parent::destroy($ids);\n    }\n\n    /**\n     * 用户登录\n     * @param string $username\n     * @param string $password\n     * @param string $type\n     * @return array\n     */\n    public function login(string $username, string $password, string $type): array\n    {\n        $adminInfo = $this->model->where('username', $username)->findOrEmpty();\n        $status = 1;\n        $message = '登录成功';\n        if ($adminInfo->isEmpty()) {\n            $message = '账号或密码错误，请重新输入!';\n            throw new ApiException($message);\n        }\n        if ($adminInfo->status === 2) {\n            $status = 0;\n            $message = '您已被禁止登录!';\n        }\n        if (!password_verify($password, $adminInfo->password)) {\n            $status = 0;\n            $message = '账号或密码错误，请重新输入!';\n        }\n        if ($status === 0) {\n            // 登录事件\n            Event::emit('user.login', compact('username', 'status', 'message'));\n            throw new ApiException($message);\n        }\n        $adminInfo->login_time = date('Y-m-d H:i:s');\n        $adminInfo->login_ip = request()->getRealIp();\n        $adminInfo->save();\n\n        $access_exp = config('plugin.saiadmin.saithink.access_exp', 3 * 3600);\n        $token = JwtToken::generateToken([\n            'access_exp' => $access_exp,\n            'id' => $adminInfo->id,\n            'username' => $adminInfo->username,\n            'type' => $type,\n            'plat' => 'saiadmin',\n        ]);\n        // 登录事件\n        $admin_id = $adminInfo->id;\n        Event::emit('user.login', compact('username', 'status', 'message', 'admin_id'));\n        return $token;\n    }\n\n    /**\n     * 更新资料\n     * @param mixed $id\n     * @param mixed $data\n     * @return bool\n     */\n    public function updateInfo($id, $data): bool\n    {\n        $this->model->update($data, ['id' => $id], ['realname', 'gender', 'phone', 'email', 'avatar', 'signed']);\n        return true;\n    }\n\n    /**\n     * 密码修改\n     * @param $adminId\n     * @param $oldPassword\n     * @param $newPassword\n     * @return bool\n     */\n    public function modifyPassword($adminId, $oldPassword, $newPassword): bool\n    {\n        $model = $this->model->findOrEmpty($adminId);\n        if (password_verify($oldPassword, $model->password)) {\n            $model->password = password_hash($newPassword, PASSWORD_DEFAULT);\n            return $model->save();\n        } else {\n            throw new ApiException('原密码错误');\n        }\n    }\n\n    /**\n     * 修改数据\n     */\n    public function authEdit($id, $data)\n    {\n        if ($this->adminInfo['id'] > 1) {\n            // 判断用户是否可以操作\n            $query = SystemUser::where('id', $id);\n            $query->auth($this->adminInfo['deptList']);\n            $user = $query->findOrEmpty();\n            if ($user->isEmpty()) {\n                throw new ApiException('没有权限操作该数据');\n            }\n        }\n        parent::edit($id, $data);\n    }\n\n    /**\n     * 部门保护\n     * @param $dept\n     * @param $dept_id\n     * @return bool\n     */\n    public function deptProtect($dept, $dept_id): bool\n    {\n        // 部门保护\n        $deptIds = [$dept['id']];\n        $deptLevel = $dept['level'] . $dept['id'] . ',';\n        $dept_ids = SystemDept::whereLike('level', $deptLevel . '%')->column('id');\n        $deptIds = array_merge($deptIds, $dept_ids);\n        if (!in_array($dept_id, $deptIds)) {\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * 越权保护\n     * @param $roleList\n     * @param $role_ids\n     * @return bool\n     */\n    public function roleProtect($roleList, $role_ids): bool\n    {\n        // 越权保护\n        $levelArr = array_column($roleList, 'level');\n        $maxLevel = max($levelArr);\n        $currentLevel = SystemRole::whereIn('id', $role_ids)->max('level');\n        if ($currentLevel >= $maxLevel) {\n            return false;\n        }\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "src/orm/think/app/logic/tool/CrontabLogLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\tool;\n\nuse plugin\\saiadmin\\app\\model\\tool\\CrontabLog;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\n\n/**\n * 定时任务日志逻辑层\n */\nclass CrontabLogLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new CrontabLog();\n    }\n\n}\n"
  },
  {
    "path": "src/orm/think/app/logic/tool/CrontabLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\tool;\n\nuse Exception;\nuse GuzzleHttp\\Client;\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Webman\\Channel\\Client as ChannelClient;\nuse plugin\\saiadmin\\app\\model\\tool\\Crontab;\nuse plugin\\saiadmin\\app\\model\\tool\\CrontabLog;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\n\n/**\n * 定时任务逻辑层\n */\nclass CrontabLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new Crontab();\n    }\n\n    /**\n     * 添加任务\n     */\n    public function add($data): bool\n    {\n        $second = $data['second'];\n        $minute = $data['minute'];\n        $hour = $data['hour'];\n        $week = $data['week'];\n        $day = $data['day'];\n        $month = $data['month'];\n\n        // 规则处理\n        $rule = match ($data['task_style']) {\n            1 => \"0 {$minute} {$hour} * * *\",\n            2 => \"0 {$minute} * * * *\",\n            3 => \"0 {$minute} */{$hour} * * *\",\n            4 => \"0 */{$minute} * * * *\",\n            5 => \"*/{$second} * * * * *\",\n            6 => \"0 {$minute} {$hour} * * {$week}\",\n            7 => \"0 {$minute} {$hour} {$day} * *\",\n            8 => \"0 {$minute} {$hour} {$day} {$month} *\",\n            default => throw new ApiException(\"任务类型异常\"),\n        };\n\n        // 定时任务模型新增\n        $model = Crontab::create([\n            'name' => $data['name'],\n            'type' => $data['type'],\n            'task_style' => $data['task_style'],\n            'rule' => $rule,\n            'target' => $data['target'],\n            'parameter' => $data['parameter'],\n            'status' => $data['status'],\n            'remark' => $data['remark'],\n        ]);\n\n        $id = $model->getKey();\n        // 连接到Channel服务\n        ChannelClient::connect();\n        ChannelClient::publish('crontab', ['args' => $id]);\n\n        return true;\n    }\n\n    /**\n     * 修改任务\n     */\n    public function edit($id, $data): bool\n    {\n        $second = $data['second'];\n        $minute = $data['minute'];\n        $hour = $data['hour'];\n        $week = $data['week'];\n        $day = $data['day'];\n        $month = $data['month'];\n\n        // 规则处理\n        $rule = match ($data['task_style']) {\n            1 => \"0 {$minute} {$hour} * * *\",\n            2 => \"0 {$minute} * * * *\",\n            3 => \"0 {$minute} */{$hour} * * *\",\n            4 => \"0 */{$minute} * * * *\",\n            5 => \"*/{$second} * * * * *\",\n            6 => \"0 {$minute} {$hour} * * {$week}\",\n            7 => \"0 {$minute} {$hour} {$day} * *\",\n            8 => \"0 {$minute} {$hour} {$day} {$month} *\",\n            default => throw new ApiException(\"任务类型异常\"),\n        };\n\n        // 查询任务数据\n        $model = $this->model->findOrEmpty($id);\n        if ($model->isEmpty()) {\n            throw new ApiException('数据不存在');\n        }\n\n        $result = $model->save([\n            'name' => $data['name'],\n            'type' => $data['type'],\n            'task_style' => $data['task_style'],\n            'rule' => $rule,\n            'target' => $data['target'],\n            'parameter' => $data['parameter'],\n            'status' => $data['status'],\n            'remark' => $data['remark'],\n        ]);\n        if ($result) {\n            // 连接到Channel服务\n            ChannelClient::connect();\n            ChannelClient::publish('crontab', ['args' => $id]);\n        }\n\n        // 修改任务数据\n        return $result;\n    }\n\n    /**\n     * 删除定时任务\n     * @param $ids\n     * @return bool\n     * @throws Exception\n     */\n    public function destroy($ids): bool\n    {\n        if (is_array($ids)) {\n            if (count($ids) > 1) {\n                throw new ApiException('禁止批量删除操作');\n            }\n            $ids = $ids[0];\n        }\n        $result = parent::destroy($ids);\n        if ($result) {\n            // 连接到Channel服务\n            ChannelClient::connect();\n            ChannelClient::publish('crontab', ['args' => $ids]);\n        }\n        return $result;\n    }\n\n    /**\n     * 修改状态\n     * @param $id\n     * @param $status\n     * @return bool\n     */\n    public function changeStatus($id, $status): bool\n    {\n        $model = $this->model->findOrEmpty($id);\n        if ($model->isEmpty()) {\n            throw new ApiException('数据不存在');\n        }\n        $result = $model->save(['status' => $status]);\n        if ($result) {\n            // 连接到Channel服务\n            ChannelClient::connect();\n            ChannelClient::publish('crontab', ['args' => $id]);\n        }\n        return $result;\n    }\n\n    /**\n     * 执行定时任务\n     * @param $id\n     * @return bool\n     */\n    public function run($id): bool\n    {\n        $info = $this->model->where('status', 1)->findOrEmpty($id);\n        if ($info->isEmpty()) {\n            return false;\n        }\n        $data['crontab_id'] = $info->id;\n        $data['name'] = $info->name;\n        $data['target'] = $info->target;\n        $data['parameter'] = $info->parameter;\n        switch ($info->type) {\n            case 1:\n                // URL任务GET\n                $httpClient = new Client([\n                    'timeout' => 5,\n                    'verify' => false,\n                ]);\n                try {\n                    $httpClient->request('GET', $info->target);\n                    $data['status'] = 1;\n                    CrontabLog::create($data);\n                    return true;\n                } catch (GuzzleException $e) {\n                    $data['status'] = 2;\n                    $data['exception_info'] = $e->getMessage();\n                    CrontabLog::create($data);\n                    return false;\n                }\n            case 2:\n                // URL任务POST\n                $httpClient = new Client([\n                    'timeout' => 5,\n                    'verify' => false,\n                ]);\n                try {\n                    $res = $httpClient->request('POST', $info->target, [\n                        'form_params' => json_decode($info->parameter ?? '', true)\n                    ]);\n                    $data['status'] = 1;\n                    $data['exception_info'] = $res->getBody();\n                    CrontabLog::create($data);\n                    return true;\n                } catch (GuzzleException $e) {\n                    $data['status'] = 2;\n                    $data['exception_info'] = $e->getMessage();\n                    CrontabLog::create($data);\n                    return false;\n                }\n            case 3:\n                // 类任务\n                $class_name = $info->target;\n                $method_name = 'run';\n                $class = new $class_name;\n                if (method_exists($class, $method_name)) {\n                    $return = $class->$method_name($info->parameter);\n                    $data['status'] = 1;\n                    $data['exception_info'] = $return;\n                    CrontabLog::create($data);\n                    return true;\n                } else {\n                    $data['status'] = 2;\n                    $data['exception_info'] = '类:' . $class_name . ',方法:run,未找到';\n                    CrontabLog::create($data);\n                    return false;\n\n                }\n            default:\n                return false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/orm/think/app/logic/tool/GenerateColumnsLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\tool;\n\nuse plugin\\saiadmin\\app\\model\\tool\\GenerateColumns;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 代码生成业务字段逻辑层\n */\nclass GenerateColumnsLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new GenerateColumns();\n    }\n\n    public function saveExtra($data)\n    {\n        $default_column = ['create_time', 'update_time', 'created_by', 'updated_by', 'delete_time', 'remark'];\n        // 组装数据\n        foreach ($data as $k => $item) {\n\n            if ($item['column_name'] == 'delete_time') {\n                continue;\n            }\n\n            $column = [\n                'table_id' => $item['table_id'],\n                'column_name' => $item['column_name'],\n                'column_comment' => $item['column_comment'],\n                'column_type' => $item['column_type'],\n                'default_value' => $item['default_value'],\n                'is_pk' => ($item['column_key'] == 'PRI') ? 2 : 1,\n                'is_required' => $item['is_nullable'] == 'NO' ? 2 : 1,\n                'query_type' => 'eq',\n                'view_type' => 'input',\n                'sort' => count($data) - $k,\n                'options' => $item['options'] ?? null\n            ];\n\n            // 设置默认选项\n            if (!in_array($item['column_name'], $default_column) && empty($item['column_key'])) {\n                $column = array_merge(\n                    $column,\n                    [\n                        'is_insert' => 2,\n                        'is_edit' => 2,\n                        'is_list' => 2,\n                        'is_query' => 1,\n                        'is_sort' => 1,\n                    ]\n                );\n            }\n            $keyList = [\n                'column_comment',\n                'column_type',\n                'default_value',\n                'is_pk',\n                'is_required',\n                'is_insert',\n                'is_edit',\n                'is_list',\n                'is_query',\n                'is_sort',\n                'query_type',\n                'view_type',\n                'dict_type',\n                'options',\n                'sort',\n                'is_cover'\n            ];\n            foreach ($keyList as $key) {\n                if (isset($item[$key]))\n                    $column[$key] = $item[$key];\n            }\n            GenerateColumns::create($this->fieldDispose($column));\n        }\n    }\n\n    public function update($data, $where)\n    {\n        $data['is_insert'] = $data['is_insert'] ? 2 : 1;\n        $data['is_edit'] = $data['is_edit'] ? 2 : 1;\n        $data['is_list'] = $data['is_list'] ? 2 : 1;\n        $data['is_query'] = $data['is_query'] ? 2 : 1;\n        $data['is_sort'] = $data['is_sort'] ? 2 : 1;\n        $data['is_required'] = $data['is_required'] ? 2 : 1;\n        $this->model->update($data, $where);\n    }\n\n    private function fieldDispose(array $column): array\n    {\n        $object = new class {\n            public function viewTypeDispose(&$column): void\n            {\n                switch ($column['column_type']) {\n                    case 'varchar':\n                        $column['view_type'] = 'input';\n                        break;\n                    // 富文本\n                    case 'text':\n                    case 'longtext':\n                        $column['is_list'] = 1;\n                        $column['is_query'] = 1;\n                        $column['view_type'] = 'editor';\n                        $options = [\n                            'height' => 400,\n                        ];\n                        $column['options'] = $options;\n                        break;\n                    // 日期字段\n                    case 'datetime':\n                        $column['view_type'] = 'date';\n                        $options = [\n                            'mode' => 'datetime'\n                        ];\n                        $column['options'] = $options;\n                        $column['query_type'] = 'between';\n                        break;\n                    case 'date':\n                        $column['view_type'] = 'date';\n                        $options = [\n                            'mode' => 'date'\n                        ];\n                        $column['options'] = $options;\n                        $column['query_type'] = 'between';\n                        break;\n                }\n            }\n\n            public function columnName(&$column): void\n            {\n                if (stristr($column['column_name'], 'name')) {\n                    $column['is_query'] = 2;\n                    $column['is_required'] = 2;\n                    $column['query_type'] = 'like';\n                }\n\n                if (stristr($column['column_name'], 'title')) {\n                    $column['is_query'] = 2;\n                    $column['is_required'] = 2;\n                    $column['query_type'] = 'like';\n                }\n\n                if (stristr($column['column_name'], 'type')) {\n                    $column['is_query'] = 2;\n                    $column['is_required'] = 2;\n                    $column['query_type'] = 'eq';\n                }\n\n                if (stristr($column['column_name'], 'image')) {\n                    $column['is_query'] = 1;\n                    $column['view_type'] = 'uploadImage';\n                    $options = [\n                        'multiple' => false,\n                        'limit' => 1,\n                    ];\n                    $column['options'] = $options;\n                }\n\n                if (stristr($column['column_name'], 'file')) {\n                    $column['is_query'] = 1;\n                    $column['view_type'] = 'uploadFile';\n                    $options = [\n                        'multiple' => false,\n                        'limit' => 1,\n                    ];\n                    $column['options'] = $options;\n                }\n\n                if (stristr($column['column_name'], 'attach')) {\n                    $column['is_query'] = 1;\n                    $column['view_type'] = 'uploadFile';\n                    $options = [\n                        'multiple' => false,\n                        'limit' => 1,\n                    ];\n                    $column['options'] = $options;\n                }\n\n                if ($column['column_name'] === 'sort') {\n                    $column['view_type'] = 'inputNumber';\n                }\n\n                if ($column['column_name'] === 'status') {\n                    $column['view_type'] = 'radio';\n                    $column['dict_type'] = 'data_status';\n                }\n\n                if (stristr($column['column_name'], 'is_')) {\n                    $column['view_type'] = 'radio';\n                    $column['dict_type'] = 'yes_or_no';\n                }\n            }\n        };\n\n        if (!$column['is_cover']) {\n            $object->viewTypeDispose($column);\n            $object->columnName($column);\n        }\n        $column['options'] = json_encode($column['options'], JSON_UNESCAPED_UNICODE);\n        return $column;\n    }\n}\n"
  },
  {
    "path": "src/orm/think/app/logic/tool/GenerateTablesLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\tool;\n\nuse plugin\\saiadmin\\app\\cache\\UserMenuCache;\nuse plugin\\saiadmin\\app\\logic\\system\\DatabaseLogic;\nuse plugin\\saiadmin\\app\\model\\system\\SystemMenu;\nuse plugin\\saiadmin\\app\\model\\tool\\GenerateTables;\nuse plugin\\saiadmin\\app\\model\\tool\\GenerateColumns;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\utils\\Helper;\nuse plugin\\saiadmin\\utils\\code\\CodeZip;\nuse plugin\\saiadmin\\utils\\code\\CodeEngine;\n\n/**\n * 代码生成业务逻辑层\n */\nclass GenerateTablesLogic extends BaseLogic\n{\n    protected $columnLogic = null;\n\n    protected $dataLogic = null;\n\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new GenerateTables();\n        $this->columnLogic = new GenerateColumnsLogic();\n        $this->dataLogic = new DatabaseLogic();\n    }\n\n    /**\n     * 删除表和字段信息\n     * @param $ids\n     * @return bool\n     */\n    public function destroy($ids): bool\n    {\n        return $this->transaction(function () use ($ids) {\n            parent::destroy($ids);\n            GenerateColumns::destroy(function ($query) use ($ids) {\n                $query->where('table_id', 'in', $ids);\n            });\n            return true;\n        });\n    }\n\n    /**\n     * 装载表信息\n     * @param $names\n     * @param $source\n     * @return void\n     */\n    public function loadTable($names, $source): void\n    {\n        $data = config('think-orm.connections');\n        $config = $data[$source];\n        if (!$config) {\n            throw new ApiException('数据库配置读取失败');\n        }\n\n        $prefix = $config['prefix'] ?? '';\n        foreach ($names as $item) {\n            $class_name = $item['name'];\n            if (!empty($prefix)) {\n                $class_name = Helper::str_replace_once($prefix, '', $class_name);\n            }\n            $class_name = Helper::camel($class_name);\n            $tableInfo = [\n                'table_name' => $item['name'],\n                'table_comment' => $item['comment'],\n                'class_name' => $class_name,\n                'business_name' => Helper::get_business($item['name']),\n                'belong_menu_id' => 80,\n                'menu_name' => $item['comment'],\n                'tpl_category' => 'single',\n                'template' => 'app',\n                'stub' => 'think',\n                'namespace' => '',\n                'package_name' => '',\n                'source' => $source,\n                'generate_menus' => 'index,save,update,read,destroy',\n            ];\n            $model = GenerateTables::create($tableInfo);\n            $columns = $this->dataLogic->getColumnList($item['name'], $source);\n            foreach ($columns as &$column) {\n                $column['table_id'] = $model->id;\n                $column['is_cover'] = false;\n            }\n            $this->columnLogic->saveExtra($columns);\n        }\n    }\n\n    /**\n     * 同步表字段信息\n     * @param $id\n     * @return void\n     */\n    public function sync($id)\n    {\n        $model = $this->model->findOrEmpty($id);\n        // 拉取已有数据表信息\n        $queryModel = $this->columnLogic->model->where('table_id', $id);\n        $columnLogicData = $this->columnLogic->getAll($queryModel);\n        $columnLogicList = [];\n        foreach ($columnLogicData as $item) {\n            $columnLogicList[$item['column_name']] = $item;\n        }\n        GenerateColumns::destroy(function ($query) use ($id) {\n            $query->where('table_id', $id);\n        });\n        $columns = $this->dataLogic->getColumnList($model->table_name, $model->source ?? '');\n        foreach ($columns as &$column) {\n            $column['table_id'] = $model->id;\n            $column['is_cover'] = false;\n            if (isset($columnLogicList[$column['column_name']])) {\n                // 存在历史信息的情况\n                $getcolumnLogicItem = $columnLogicList[$column['column_name']];\n                if ($getcolumnLogicItem['column_type'] == $column['column_type']) {\n                    $column['is_cover'] = true;\n                    foreach ($getcolumnLogicItem as $key => $item) {\n                        $array = [\n                            'column_comment',\n                            'column_type',\n                            'default_value',\n                            'is_pk',\n                            'is_required',\n                            'is_insert',\n                            'is_edit',\n                            'is_list',\n                            'is_query',\n                            'is_sort',\n                            'query_type',\n                            'view_type',\n                            'dict_type',\n                            'options',\n                            'sort',\n                            'is_cover'\n                        ];\n                        if (in_array($key, $array)) {\n                            $column[$key] = $item;\n                        }\n                    }\n                }\n            }\n        }\n        $this->columnLogic->saveExtra($columns);\n    }\n\n    /**\n     * 代码预览\n     * @param $id\n     * @return array\n     */\n    public function preview($id): array\n    {\n        $data = $this->renderData($id);\n\n        $codeEngine = new CodeEngine($data);\n        $controllerContent = $codeEngine->renderContent('php', 'controller.stub');\n        $logicContent = $codeEngine->renderContent('php', 'logic.stub');\n        $modelContent = $codeEngine->renderContent('php', 'model.stub');\n        $validateContent = $codeEngine->renderContent('php', 'validate.stub');\n        $sqlContent = $codeEngine->renderContent('sql', 'sql.stub');\n        $indexContent = $codeEngine->renderContent('vue', 'index.stub');\n        $editContent = $codeEngine->renderContent('vue', 'edit-dialog.stub');\n        $searchContent = $codeEngine->renderContent('vue', 'table-search.stub');\n        $apiContent = $codeEngine->renderContent('ts', 'api.stub');\n\n        // 返回生成内容\n        return [\n            [\n                'tab_name' => 'controller.php',\n                'name' => 'controller',\n                'lang' => 'php',\n                'code' => $controllerContent\n            ],\n            [\n                'tab_name' => 'logic.php',\n                'name' => 'logic',\n                'lang' => 'php',\n                'code' => $logicContent\n            ],\n            [\n                'tab_name' => 'model.php',\n                'name' => 'model',\n                'lang' => 'php',\n                'code' => $modelContent\n            ],\n            [\n                'tab_name' => 'validate.php',\n                'name' => 'validate',\n                'lang' => 'php',\n                'code' => $validateContent\n            ],\n            [\n                'tab_name' => 'sql.sql',\n                'name' => 'sql',\n                'lang' => 'sql',\n                'code' => $sqlContent\n            ],\n            [\n                'tab_name' => 'index.vue',\n                'name' => 'index',\n                'lang' => 'html',\n                'code' => $indexContent\n            ],\n            [\n                'tab_name' => 'edit-dialog.vue',\n                'name' => 'edit-dialog',\n                'lang' => 'html',\n                'code' => $editContent\n            ],\n            [\n                'tab_name' => 'table-search.vue',\n                'name' => 'table-search',\n                'lang' => 'html',\n                'code' => $searchContent\n            ],\n            [\n                'tab_name' => 'api.ts',\n                'name' => 'api',\n                'lang' => 'javascript',\n                'code' => $apiContent\n            ]\n        ];\n    }\n\n    /**\n     * 生成到模块\n     * @param $id\n     */\n    public function genModule($id)\n    {\n        $data = $this->renderData($id);\n\n        // 生成文件到模块\n        $codeEngine = new CodeEngine($data);\n        $codeEngine->generateBackend('controller', $codeEngine->renderContent('php', 'controller.stub'));\n        $codeEngine->generateBackend('logic', $codeEngine->renderContent('php', 'logic.stub'));\n        $codeEngine->generateBackend('model', $codeEngine->renderContent('php', 'model.stub'));\n        $codeEngine->generateBackend('validate', $codeEngine->renderContent('php', 'validate.stub'));\n        $codeEngine->generateFrontend('index', $codeEngine->renderContent('vue', 'index.stub'));\n        $codeEngine->generateFrontend('edit-dialog', $codeEngine->renderContent('vue', 'edit-dialog.stub'));\n        $codeEngine->generateFrontend('table-search', $codeEngine->renderContent('vue', 'table-search.stub'));\n        $codeEngine->generateFrontend('api', $codeEngine->renderContent('ts', 'api.stub'));\n    }\n\n    /**\n     * 处理数据\n     * @param $id\n     * @return array\n     */\n    protected function renderData($id): array\n    {\n        $table = $this->model->findOrEmpty($id);\n        if (!in_array($table['template'], [\"plugin\", \"app\"])) {\n            throw new ApiException('应用类型必须为plugin或者app');\n        }\n        if (empty($table['namespace'])) {\n            throw new ApiException('请先设置应用名称');\n        }\n\n        $columns = $this->columnLogic->where('table_id', $id)\n            ->order('sort', 'desc')\n            ->select()\n            ->toArray();\n        $pk = 'id';\n        foreach ($columns as &$column) {\n            if ($column['is_pk'] == 2) {\n                $pk = $column['column_name'];\n            }\n            if ($column['column_name'] == 'delete_time') {\n                unset($column['column_name']);\n            }\n        }\n\n        // 处理特殊变量\n        if ($table['template'] == 'plugin') {\n            $namespace_start = \"plugin\\\\\" . $table['namespace'] . \"\\\\app\\\\admin\\\\\";\n            $namespace_start_model = \"plugin\\\\\" . $table['namespace'] . \"\\\\app\\\\\";\n            $namespace_end = \"\\\\\" . $table['package_name'];\n            $url_path = 'app/' . $table['namespace'] . '/admin/' . $table['package_name'] . '/' . $table['class_name'];\n            $route = 'app/';\n        } else {\n            $namespace_start = \"app\\\\\" . $table['namespace'] . \"\\\\\";\n            $namespace_start_model = \"app\\\\\" . $table['namespace'] . \"\\\\\";\n            $namespace_end = \"\\\\\" . $table['package_name'];\n            $url_path = $table['namespace'] . '/' . $table['package_name'] . '/' . $table['class_name'];\n            $route = '';\n        }\n\n        $config = config('think-orm');\n\n        $data = $table->toArray();\n        $data['pk'] = $pk;\n        $data['namespace_start'] = $namespace_start;\n        $data['namespace_start_model'] = $namespace_start_model;\n        $data['namespace_end'] = $namespace_end;\n        $data['url_path'] = $url_path;\n        $data['route'] = $route;\n        $data['tables'] = [$data];\n        $data['columns'] = $columns;\n        $data['db_source'] = $config['default'] ?? 'mysql';\n\n        return $data;\n    }\n\n    /**\n     * 生成到模块\n     */\n    public function generateFile($id)\n    {\n        $table = $this->model->where('id', $id)->findOrEmpty();\n        if ($table->isEmpty()) {\n            throw new ApiException('请选择要生成的表');\n        }\n        $debug = config('app.debug', true);\n        if (!$debug) {\n            throw new ApiException('非调试模式下，不允许生成文件');\n        }\n        $this->updateMenu($table);\n        $this->genModule($id);\n        UserMenuCache::clearMenuCache();\n    }\n\n    /**\n     * 代码生成下载\n     */\n    public function generate($idsArr): array\n    {\n        $zip = new CodeZip();\n        $tables = $this->model->where('id', 'in', $idsArr)->select()->toArray();\n        foreach ($idsArr as $table_id) {\n            $data = $this->renderData($table_id);\n            $data['tables'] = $tables;\n            $codeEngine = new CodeEngine($data);\n            $codeEngine->generateTemp();\n        }\n\n        $filename = 'saiadmin.zip';\n        $download = $zip->compress();\n\n        return compact('filename', 'download');\n    }\n\n    /**\n     * 处理菜单列表\n     * @param $tables\n     */\n    public function updateMenu($tables)\n    {\n        /*不存在的情况下进行新建操作*/\n        $url_path = $tables['namespace'] . \":\" . $tables['package_name'] . ':' . $tables['business_name'];\n        $code = $tables['namespace'] . \"/\" . $tables['package_name'] . '/' . $tables['business_name'];\n        $path = $tables['package_name'] . '/' . $tables['business_name'];\n        $component = $tables['namespace'] . \"/\" . $tables['package_name'] . '/' . $tables['business_name'];\n\n        /*先获取一下已有的路由中是否包含当前ID的路由的核心信息*/\n        $model = new SystemMenu();\n        $tableMenu = $model->where('generate_id', $tables['id'])->findOrEmpty();\n        $fistMenu = [\n            'parent_id' => $tables['belong_menu_id'],\n            'name' => $tables['menu_name'],\n            'code' => $code,\n            'path' => $path,\n            'icon' => 'ri:home-2-line',\n            'component' => \"/plugin/$component/index\",\n            'type' => 2,\n            'sort' => 100,\n            'is_iframe' => 2,\n            'is_keep_alive' => 2,\n            'is_hidden' => 2,\n            'is_fixed_tab' => 2,\n            'is_full_page' => 2,\n            'generate_id' => $tables['id']\n        ];\n        if ($tableMenu->isEmpty()) {\n            $temp = SystemMenu::create($fistMenu);\n            $fistMenuId = $temp->id;\n        } else {\n            $fistMenu['id'] = $tableMenu['id'];\n            $tableMenu->save($fistMenu);\n            $fistMenuId = $tableMenu['id'];\n        }\n        /*开始进行子权限的判定操作*/\n        $childNodes = [\n            ['name' => '列表', 'key' => 'index'],\n            ['name' => '保存', 'key' => 'save'],\n            ['name' => '更新', 'key' => 'update'],\n            ['name' => '读取', 'key' => 'read'],\n            ['name' => '删除', 'key' => 'destroy'],\n        ];\n\n        foreach ($childNodes as $node) {\n            $nodeData = $model->where('parent_id', $fistMenuId)->where('generate_key', $node['key'])->findOrEmpty();\n            $childNodeData = [\n                'parent_id' => $fistMenuId,\n                'name' => $node['name'],\n                'slug' => \"$url_path:{$node['key']}\",\n                'type' => 3,\n                'sort' => 100,\n                'is_iframe' => 2,\n                'is_keep_alive' => 2,\n                'is_hidden' => 2,\n                'is_fixed_tab' => 2,\n                'is_full_page' => 2,\n                'generate_key' => $node['key']\n            ];\n            if (!empty($nodeData)) {\n                $childNodeData['id'] = $nodeData['id'];\n                $nodeData->save($childNodeData);\n            } else {\n                $menuModel = new SystemMenu();\n                $menuModel->save($childNodeData);\n            }\n        }\n    }\n\n    /**\n     * 获取数据表字段信息\n     * @param $table_id\n     * @return mixed\n     */\n    public function getTableColumns($table_id): mixed\n    {\n        $query = $this->columnLogic->where('table_id', $table_id);\n        return $this->columnLogic->getAll($query);\n    }\n\n    /**\n     * 编辑数据\n     * @param $id\n     * @param $data\n     * @return mixed\n     */\n    public function edit($id, $data): mixed\n    {\n        $columns = $data['columns'];\n\n        unset($data['columns']);\n\n        if (!empty($data['belong_menu_id'])) {\n            $data['belong_menu_id'] = is_array($data['belong_menu_id']) ? array_pop($data['belong_menu_id']) : $data['belong_menu_id'];\n        } else {\n            $data['belong_menu_id'] = 0;\n        }\n\n        $data['generate_menus'] = implode(',', $data['generate_menus']);\n\n        if (empty($data['options'])) {\n            unset($data['options']);\n        }\n\n        $data['options'] = json_encode($data['options'], JSON_UNESCAPED_UNICODE);\n\n        // 更新业务表\n        $this->update($data, ['id' => $id]);\n\n        // 更新业务字段表\n        foreach ($columns as $column) {\n            if ($column['options']) {\n                $column['options'] = json_encode($column['options'], JSON_NUMERIC_CHECK);\n            }\n            $this->columnLogic->update($column, ['id' => $column['id']]);\n        }\n\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "src/orm/think/app/model/system/SystemAttachment.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 附件模型\n *\n * sa_system_attachment 附件信息表\n *\n * @property  $id 主键\n * @property  $category_id 文件分类\n * @property  $storage_mode 存储模式\n * @property  $origin_name 原文件名\n * @property  $object_name 新文件名\n * @property  $hash 文件hash\n * @property  $mime_type 资源类型\n * @property  $storage_path 存储目录\n * @property  $suffix 文件后缀\n * @property  $size_byte 字节数\n * @property  $size_info 文件大小\n * @property  $url url地址\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemAttachment extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_attachment';\n\n    /**\n     * 原文件名搜索\n     */\n    public function searchOriginNameAttr($query, $value)\n    {\n        $query->where('origin_name', 'like', '%' . $value . '%');\n    }\n\n    /**\n     * 文件类型搜索\n     */\n    public function searchMimeTypeAttr($query, $value)\n    {\n        $query->where('mime_type', 'like', $value . '/%');\n    }\n\n}"
  },
  {
    "path": "src/orm/think/app/model/system/SystemCategory.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 附件分类模型\n *\n * sa_system_category 附件分类表\n *\n * @property  $id 分类ID\n * @property  $parent_id 父id\n * @property  $level 组集关系\n * @property  $category_name 分类名称\n * @property  $sort 排序\n * @property  $status 状态\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemCategory extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_category';\n\n    /**\n     * 分类名称搜索\n     */\n    public function searchCategoryNameAttr($query, $value)\n    {\n        $query->where('category_name', 'like', '%' . $value . '%');\n    }\n\n}"
  },
  {
    "path": "src/orm/think/app/model/system/SystemConfig.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 参数配置模型\n *\n * sa_system_config 参数配置信息表\n *\n * @property  $id 编号\n * @property  $group_id 组id\n * @property  $key 配置键名\n * @property  $value 配置值\n * @property  $name 配置名称\n * @property  $input_type 数据输入类型\n * @property  $config_select_data 配置选项数据\n * @property  $sort 排序\n * @property  $remark 备注\n * @property  $created_by 创建人\n * @property  $updated_by 更新人\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemConfig extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_config';\n\n    public function getConfigSelectDataAttr($value)\n    {\n        return json_decode($value ?? '', true);\n    }\n\n}"
  },
  {
    "path": "src/orm/think/app/model/system/SystemConfigGroup.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 参数配置分组模型\n * \n * sa_system_config_group 参数配置分组表\n *\n * @property  $id 主键\n * @property  $name 字典名称\n * @property  $code 字典标示\n * @property  $remark 备注\n * @property  $created_by 创建人\n * @property  $updated_by 更新人\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemConfigGroup extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_config_group';\n\n    /**\n     * 关联配置列表\n     */\n    public function configs()\n    {\n        return $this->hasMany(SystemConfig::class, 'group_id', 'id');\n    }\n\n    /**\n     * 名称搜索\n     */\n    public function searchNameAttr($query, $value)\n    {\n        return $query->where('name', 'like', '%' . $value . '%');\n    }\n\n    /**\n     * 编码搜索\n     */\n    public function searchCodeAttr($query, $value)\n    {\n        return $query->where('code', 'like', '%' . $value . '%');\n    }\n\n}"
  },
  {
    "path": "src/orm/think/app/model/system/SystemDept.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 部门模型\n *\n * sa_system_dept 部门表\n *\n * @property  $id 编号\n * @property  $parent_id 父级ID，0为根节点\n * @property  $name 部门名称\n * @property  $code 部门编码\n * @property  $leader_id 部门负责人ID\n * @property  $level 祖级列表，格式: 0,1,5,\n * @property  $sort 排序，数字越小越靠前\n * @property  $status 状态: 1启用, 0禁用\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemDept extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_dept';\n\n    /**\n     * 权限范围\n     */\n    public function scopeAuth($query, $value)\n    {\n        if (!empty($value)) {\n            $deptIds = [$value['id']];\n            $deptLevel = $value['level'] . $value['id'] . ',';\n            $ids = static::whereLike('level', $deptLevel . '%')->column('id');\n            $deptIds = array_merge($deptIds, $ids);\n            $query->whereIn('id', $deptIds);\n        }\n    }\n\n    /**\n     * 部门领导\n     */\n    public function leader()\n    {\n        return $this->hasOne(SystemUser::class, 'id', 'leader_id');\n    }\n\n}"
  },
  {
    "path": "src/orm/think/app/model/system/SystemDictData.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 字典数据模型\n *\n * sa_system_dict_data 字典数据表\n *\n * @property  $id 主键\n * @property  $type_id 字典类型ID\n * @property  $label 字典标签\n * @property  $value 字典值\n * @property  $color 字典颜色\n * @property  $code 字典标示\n * @property  $sort 排序\n * @property  $status 状态\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemDictData extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_dict_data';\n\n    /**\n     * 关键字搜索\n     */\n    public function searchKeywordsAttr($query, $value)\n    {\n        $query->where('label|code', 'LIKE', \"%$value%\");\n    }\n}"
  },
  {
    "path": "src/orm/think/app/model/system/SystemDictType.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 字典类型模型\n *\n * sa_system_dict_type 字典类型表\n *\n * @property  $id 主键\n * @property  $name 字典名称\n * @property  $code 字典标示\n * @property  $status 状态\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemDictType extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_dict_type';\n\n    /**\n     * 关联字典数据\n     */\n    public function dicts()\n    {\n        return $this->hasMany(SystemDictData::class, 'type_id', 'id');\n    }\n\n}"
  },
  {
    "path": "src/orm/think/app/model/system/SystemLoginLog.php",
    "content": "<?php\n\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 登录日志模型\n *\n * sa_system_login_log 登录日志表\n *\n * @property  $id 主键\n * @property  $username 用户名\n * @property  $ip 登录IP地址\n * @property  $ip_location IP所属地\n * @property  $os 操作系统\n * @property  $browser 浏览器\n * @property  $status 登录状态\n * @property  $message 提示消息\n * @property  $login_time 登录时间\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 更新时间\n */\nclass SystemLoginLog extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_login_log';\n\n    /**\n     * 时间范围搜索\n     */\n    public function searchLoginTimeAttr($query, $value)\n    {\n        $query->whereTime('login_time', 'between', $value);\n    }\n\n}"
  },
  {
    "path": "src/orm/think/app/model/system/SystemMail.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 邮件记录模型\n *\n * sa_system_mail 邮件记录\n *\n * @property  $id 编号\n * @property  $gateway 网关\n * @property  $from 发送人\n * @property  $email 接收人\n * @property  $code 验证码\n * @property  $content 邮箱内容\n * @property  $status 发送状态\n * @property  $response 返回结果\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemMail extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_mail';\n\n    /**\n     * 发送人搜索\n     */\n    public function searchFromAttr($query, $value)\n    {\n        $query->where('from', 'like', '%' . $value . '%');\n    }\n\n    /**\n     * 接收人搜索\n     */\n    public function searchEmailAttr($query, $value)\n    {\n        $query->where('email', 'like', '%' . $value . '%');\n    }\n}"
  },
  {
    "path": "src/orm/think/app/model/system/SystemMenu.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 菜单模型\n *\n * sa_system_menu 菜单权限表\n *\n * @property  $id \n * @property  $parent_id 父级ID\n * @property  $name 菜单名称\n * @property  $code 组件名称\n * @property  $slug 权限标识，如 user:list, user:add\n * @property  $type 类型: 1目录, 2菜单, 3按钮/API\n * @property  $path 路由地址或API路径\n * @property  $component 前端组件路径，如 layout/User\n * @property  $method 请求方式\n * @property  $icon 图标\n * @property  $sort 排序\n * @property  $link_url 外部链接\n * @property  $is_iframe 是否iframe\n * @property  $is_keep_alive 是否缓存\n * @property  $is_hidden 是否隐藏\n * @property  $is_fixed_tab 是否固定标签页\n * @property  $is_full_page 是否全屏\n * @property  $generate_id 生成id\n * @property  $generate_key 生成key\n * @property  $status 状态\n * @property  $remark \n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemMenu extends BaseModel\n{\n    // 完整数据库表名称\n    protected $table = 'sa_system_menu';\n    // 主键\n    protected $pk = 'id';\n\n    /**\n     * Id搜索\n     */\n    public function searchIdAttr($query, $value)\n    {\n        $query->whereIn('id', $value);\n    }\n\n    public function searchNameAttr($query, $value)\n    {\n        $query->where('name', 'like', '%' . $value . '%');\n    }\n\n    public function searchPathAttr($query, $value)\n    {\n        $query->where('path', 'like', '%' . $value . '%');\n    }\n\n    public function searchMenuAttr($query, $value)\n    {\n        if (!empty($value)) {\n            $query->whereIn('type', [1, 2]);\n        }\n    }\n\n    /**\n     * Type搜索\n     */\n    public function searchTypeAttr($query, $value)\n    {\n        if (is_array($value)) {\n            $query->whereIn('type', $value);\n        } else {\n            $query->where('type', $value);\n        }\n    }\n\n}"
  },
  {
    "path": "src/orm/think/app/model/system/SystemOperLog.php",
    "content": "<?php\n\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 操作日志模型\n *\n * sa_system_oper_log 操作日志表\n *\n * @property  $id 主键\n * @property  $username 用户名\n * @property  $app 应用名称\n * @property  $method 请求方式\n * @property  $router 请求路由\n * @property  $service_name 业务名称\n * @property  $ip 请求IP地址\n * @property  $ip_location IP所属地\n * @property  $request_data 请求数据\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 更新时间\n */\nclass SystemOperLog extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_oper_log';\n\n}"
  },
  {
    "path": "src/orm/think/app/model/system/SystemPost.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 岗位模型\n *\n * sa_system_post 岗位信息表\n *\n * @property  $id 主键\n * @property  $name 岗位名称\n * @property  $code 岗位代码\n * @property  $sort 排序\n * @property  $status 状态\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemPost extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_post';\n\n}"
  },
  {
    "path": "src/orm/think/app/model/system/SystemRole.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 角色模型\n *\n * sa_system_role 角色表\n *\n * @property  $id \n * @property  $name 角色名称\n * @property  $code 角色标识，如: hr_manager\n * @property  $level 角色级别：用于行政控制，不可操作级别大于自己的角色\n * @property  $data_scope 数据范围: 1全部, 2本部门及下属, 3本部门, 4仅本人, 5自定义\n * @property  $remark 备注\n * @property  $sort \n * @property  $status 状态: 1启用, 0禁用\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemRole extends BaseModel\n{\n\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    /**\n     * 数据表完整名称\n     * @var string\n     */\n    protected $table = 'sa_system_role';\n\n    /**\n     * 权限范围\n     */\n    public function scopeAuth($query, $value)\n    {\n        $id = $value['id'];\n        $roles = $value['roles'];\n        if ($id > 1) {\n            $ids = [];\n            foreach ($roles as $item) {\n                $ids[] = $item['id'];\n                $temp = static::whereRaw('FIND_IN_SET(\"' . $item['id'] . '\", level) > 0')->column('id');\n                $ids = array_merge($ids, $temp);\n            }\n            $query->where('id', 'in', array_unique($ids));\n        }\n    }\n\n    /**\n     * 通过中间表获取菜单\n     */\n    public function menus()\n    {\n        return $this->belongsToMany(SystemMenu::class, SystemRoleMenu::class, 'menu_id', 'role_id');\n    }\n\n    /**\n     * 通过中间表获取部门\n     */\n    public function depts()\n    {\n        return $this->belongsToMany(SystemDept::class, SystemRoleDept::class, 'dept_id', 'role_id');\n    }\n\n}"
  },
  {
    "path": "src/orm/think/app/model/system/SystemRoleDept.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse think\\model\\Pivot;\n\n/**\n * 角色部门关联模型\n *\n * sa_system_role_dept 角色-自定义数据权限关联\n *\n * @property  $id \n * @property  $role_id \n * @property  $dept_id \n */\nclass SystemRoleDept extends Pivot\n{\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_role_dept';\n}"
  },
  {
    "path": "src/orm/think/app/model/system/SystemRoleMenu.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse think\\model\\Pivot;\n\n/**\n * 角色菜单关联模型\n *\n * sa_system_role_menu 角色权限关联\n *\n * @property  $id \n * @property  $role_id \n * @property  $menu_id \n */\nclass SystemRoleMenu extends Pivot\n{\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_role_menu';\n}"
  },
  {
    "path": "src/orm/think/app/model/system/SystemUser.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 用户信息模型\n *\n * sa_system_user 用户表\n *\n * @property  $id \n * @property  $username 登录账号\n * @property  $password 加密密码\n * @property  $realname 真实姓名\n * @property  $gender 性别\n * @property  $avatar 头像\n * @property  $email 邮箱\n * @property  $phone 手机号\n * @property  $signed 个性签名\n * @property  $dashboard 工作台\n * @property  $dept_id 主归属部门\n * @property  $is_super 是否超级管理员: 1是\n * @property  $status 状态: 1启用, 2禁用\n * @property  $remark 备注\n * @property  $login_time 最后登录时间\n * @property  $login_ip 最后登录IP\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemUser extends BaseModel\n{\n\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    /**\n     * 数据表完整名称\n     * @var string\n     */\n    protected $table = 'sa_system_user';\n\n    public function searchKeywordAttr($query, $value)\n    {\n        if ($value) {\n            $query->where('username|realname|phone', 'like', '%' . $value . '%');\n        }\n    }\n\n    /**\n     * 权限范围 - 过滤部门用户\n     */\n    public function scopeAuth($query, $value)\n    {\n        if (!empty($value)) {\n            $deptIds = [$value['id']];\n            $deptLevel = $value['level'] . $value['id'] . ',';\n            $dept_ids = SystemDept::whereLike('level', $deptLevel . '%')->column('id');\n            $deptIds = array_merge($deptIds, $dept_ids);\n            $query->whereIn('dept_id', $deptIds);\n        }\n    }\n\n    /**\n     * 通过中间表关联角色\n     */\n    public function roles()\n    {\n        return $this->belongsToMany(SystemRole::class, SystemUserRole::class, 'role_id', 'user_id');\n    }\n\n    /**\n     * 通过中间表关联岗位\n     */\n    public function posts()\n    {\n        return $this->belongsToMany(SystemPost::class, SystemUserPost::class, 'post_id', 'user_id');\n    }\n\n    /**\n     * 通过中间表关联部门\n     */\n    public function depts()\n    {\n        return $this->belongsTo(SystemDept::class, 'dept_id', 'id');\n    }\n}"
  },
  {
    "path": "src/orm/think/app/model/system/SystemUserPost.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse think\\model\\Pivot;\n\n/**\n * 用户岗位关联模型\n *\n * sa_system_user_post 用户与岗位关联表\n *\n * @property  $id 主键\n * @property  $user_id 用户主键\n * @property  $post_id 岗位主键\n */\nclass SystemUserPost extends Pivot\n{\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_user_post';\n}"
  },
  {
    "path": "src/orm/think/app/model/system/SystemUserRole.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse think\\model\\Pivot;\n\n/**\n * 用户角色关联模型\n *\n * sa_system_user_role 用户角色关联\n *\n * @property  $id \n * @property  $user_id \n * @property  $role_id \n */\nclass SystemUserRole extends Pivot\n{\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_user_role';\n\n    /**\n     * 获取角色id\n     * @param mixed $user_id\n     * @return array\n     */\n    public static function getRoleIds($user_id): array\n    {\n        return static::where('user_id', $user_id)->column('role_id');\n    }\n}"
  },
  {
    "path": "src/orm/think/app/model/tool/Crontab.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\tool;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 定时任务模型\n *\n * sa_tool_crontab 定时任务信息表\n *\n * @property  $id 主键\n * @property  $name 任务名称\n * @property  $type 任务类型\n * @property  $target 调用任务字符串\n * @property  $parameter 调用任务参数\n * @property  $task_style 执行类型\n * @property  $rule 任务执行表达式\n * @property  $status 状态\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass Crontab extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_tool_crontab';\n\n}"
  },
  {
    "path": "src/orm/think/app/model/tool/CrontabLog.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\tool;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 定时任务日志模型\n *\n * sa_tool_crontab_log 定时任务执行日志表\n *\n * @property  $id 主键\n * @property  $crontab_id 任务ID\n * @property  $name 任务名称\n * @property  $target 任务调用目标字符串\n * @property  $parameter 任务调用参数\n * @property  $exception_info 异常信息\n * @property  $status 执行状态\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass CrontabLog extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_tool_crontab_log';\n\n}"
  },
  {
    "path": "src/orm/think/app/model/tool/GenerateColumns.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\tool;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 代码生成业务字段模型\n */\nclass GenerateColumns extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_tool_generate_columns';\n\n    public function getOptionsAttr($value)\n    {\n        return json_decode($value ?? '', true);\n    }\n\n}"
  },
  {
    "path": "src/orm/think/app/model/tool/GenerateTables.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\tool;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 代码生成业务模型\n */\nclass GenerateTables extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_tool_generate_tables';\n\n    public function getOptionsAttr($value)\n    {\n        return json_decode($value ?? '', true);\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/cache/ConfigCache.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\ndeclare(strict_types=1);\n\nnamespace plugin\\saiadmin\\app\\cache;\n\nuse plugin\\saiadmin\\app\\logic\\system\\SystemConfigLogic;\nuse support\\think\\Cache;\n\n/**\n * 配置缓存\n */\nclass ConfigCache\n{\n    /**\n     * 读取缓存配置\n     * @return array\n     */\n    public static function cacheConfig(): array\n    {\n        return config('plugin.saiadmin.saithink.config_cache', [\n            'expire' => 60 * 60 * 24 * 365,\n            'prefix' => 'saiadmin:config_cache:config_',\n            'tag' => 'saiadmin:config_cache'\n        ]);\n    }\n\n    /**\n     * 获取配置信息\n     */\n    public static function getConfig(string $code = ''): array\n    {\n        if (empty($code)) {\n            return [];\n        }\n        $cache = static::cacheConfig();\n        // 直接从缓存获取\n        $config = Cache::get($cache['prefix'] . md5($code));\n        if ($config) {\n            return $config;\n        }\n\n        // 设置配置并获取\n        $config = static::setConfig($code);\n        if ($config) {\n            return $config;\n        }\n\n        return [];\n    }\n\n    /**\n     * 设置配置数据\n     */\n    public static function setConfig(string $code): array\n    {\n        $cache = static::cacheConfig();\n\n        $data = (new SystemConfigLogic())->getData($code);\n        if (empty($data)) {\n            return [];\n        }\n\n        $tag = [];\n        $tag[] = $cache['tag'];\n\n        // 保存到缓存\n        Cache::tag($tag)->set($cache['prefix'] . md5($code), $data, $cache['expire']);\n        return $data;\n    }\n\n    /**\n     * 清理单个配置缓存\n     */\n    public static function clearConfig(string $code): bool\n    {\n        $cache = static::cacheConfig();\n        return Cache::delete($cache['prefix'] . md5($code));\n    }\n\n    /**\n     * 清理全部配置缓存\n     * @return bool\n     */\n    public static function clear(): bool\n    {\n        $cache = static::cacheConfig();\n        return Cache::tag($cache['tag'])->clear();\n    }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/cache/DictCache.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\ndeclare(strict_types=1);\n\nnamespace plugin\\saiadmin\\app\\cache;\n\nuse plugin\\saiadmin\\app\\logic\\system\\SystemDictTypeLogic;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDictType;\nuse support\\think\\Cache;\n\n/**\n * 字典信息缓存\n */\nclass DictCache\n{\n    /**\n     * 读取缓存配置\n     * @return array\n     */\n    public static function cacheConfig(): array\n    {\n        return config('plugin.saiadmin.saithink.dict_cache', [\n            'expire' => 60 * 60 * 24 * 365,\n            'tag' => 'saiadmin:dict_cache',\n        ]);\n    }\n\n    /**\n     * 获取全部字典\n     */\n    public static function getDictAll(): array\n    {\n        $cache = static::cacheConfig();\n        // 直接从缓存获取\n        $data = Cache::get($cache['tag']);\n        if ($data) {\n            return $data;\n        }\n\n        // 获取信息并返回\n        $data = static::setDictAll();\n        if ($data) {\n            return $data;\n        }\n\n        return [];\n    }\n\n    /**\n     * 获取单个字典\n     */\n    public static function getDict($code): array\n    {\n        $data = static::getDictAll();\n        if (isset($data[$code])) {\n            return $data[$code];\n        } else {\n            return [];\n        }\n    }\n\n    /**\n     * 设置全部字典\n     */\n    public static function setDictAll(): array\n    {\n        $cache = static::cacheConfig();\n        $data = (new SystemDictTypeLogic)->getDictAll();\n\n        Cache::set($cache['tag'], $data, $cache['expire']);\n        return $data;\n    }\n\n    /**\n     * 清除全部字典信息\n     */\n    public static function clear(): bool\n    {\n        $cache = static::cacheConfig();\n        return Cache::delete($cache['tag']);\n    }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/cache/ReflectionCache.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\ndeclare(strict_types=1);\n\nnamespace plugin\\saiadmin\\app\\cache;\n\nuse ReflectionClass;\nuse ReflectionMethod;\nuse plugin\\saiadmin\\service\\Permission;\nuse support\\think\\Cache;\n\n/**\n * 反射文件缓存\n */\nclass ReflectionCache\n{\n    /**\n     * 读取缓存配置\n     * @return array\n     */\n    public static function cacheConfig(): array\n    {\n        return config('plugin.saiadmin.saithink.reflection_cache', [\n            'tag' => 'saiadmin:reflection',\n            'expire' => 60 * 60 * 24 * 365,\n            'no_need' => 'saiadmin:reflection_cache:no_need_',\n            'attr' => 'saiadmin:reflection_cache:attr_',\n        ]);\n    }\n\n    /**\n     * 获取控制器中无需登录的方法列表\n     */\n    public static function getNoNeedLogin(string $controller): array\n    {\n        $cache = static::cacheConfig();\n        $tag = [];\n        $tag[] = $cache['tag'];\n        $key = $cache['no_need'] . md5($controller);\n\n        $data = Cache::get($key);\n        if ($data !== null) {\n            return $data;\n        }\n\n        // 反射逻辑\n        if (class_exists($controller)) {\n            $ref = new ReflectionClass($controller);\n            $data = $ref->getDefaultProperties()['noNeedLogin'] ?? [];\n        } else {\n            $data = [];\n        }\n\n        Cache::tag($tag)->set($key, $data, $cache['expire']);\n        return $data;\n    }\n\n    /**\n     * 获取方法上的权限属性\n     */\n    public static function getPermissionAttributes(string $controller, string $action): array\n    {\n        $cache = static::cacheConfig();\n        $tag = [];\n        $tag[] = $cache['tag'];\n        $key = $cache['attr'] . md5($controller . '::' . $action);\n\n        $data = Cache::get($key);\n        if ($data) {\n            return $data;\n        }\n\n        $data = [];\n        if (method_exists($controller, $action)) {\n            $refMethod = new ReflectionMethod($controller, $action);\n            $attributes = $refMethod->getAttributes(Permission::class);\n            if (!empty($attributes)) {\n                $attr = $attributes[0]->newInstance();\n                $data = [\n                    'title' => $attr->getTitle(),\n                    'slug'  => $attr->getSlug(),\n                ];\n            }\n        }\n\n        Cache::tag($tag)->set($key, $data, $cache['expire']);\n        return $data;\n    }\n\n    /**\n     * 清理所有反射缓存\n     * @return bool\n     */\n    public static function clear(): bool\n    {\n        $cache = static::cacheConfig();\n        return Cache::tag($cache['tag'])->clear();\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/cache/UserAuthCache.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\ndeclare(strict_types=1);\n\nnamespace plugin\\saiadmin\\app\\cache;\n\nuse plugin\\saiadmin\\app\\logic\\system\\SystemMenuLogic;\nuse plugin\\saiadmin\\app\\model\\system\\SystemUserRole;\nuse support\\think\\Cache;\n\n/**\n * 用户权限缓存\n */\nclass UserAuthCache\n{\n    /**\n     * 读取缓存配置\n     * @return array\n     */\n    public static function cacheConfig(): array\n    {\n        return config('plugin.saiadmin.saithink.button_cache', [\n            'prefix' => 'saiadmin:button_cache:user_',\n            'expire' => 60 * 60 * 2,\n            'all' => 'saiadmin:button_cache:all',\n            'role' => 'saiadmin:button_cache:role_',\n            'tag' => 'saiadmin:button_cache',\n        ]);\n    }\n\n    /**\n     * 获取用户的权限\n     */\n    public static function getUserAuth($uid): array\n    {\n        if (empty($uid)) {\n            return [];\n        }\n        $cache = static::cacheConfig();\n        // 直接从缓存获取\n        $auth = Cache::get($cache['prefix'] . $uid);\n        if ($auth) {\n            return $auth;\n        }\n\n        // 设置权限并返回\n        $auth = static::setUserAuth($uid);\n        if ($auth) {\n            return $auth;\n        }\n\n        return [];\n    }\n\n    /**\n     * 设置用户的权限\n     */\n    public static function setUserAuth($uid): array\n    {\n        // 从缓存获取，直接返回\n        $roleIds = SystemUserRole::getRoleIds($uid);\n\n        // 获取角色关联的菜单权限\n        $data = (new SystemMenuLogic())->getAuthByRole($roleIds);\n        if (empty($data)) {\n            return [];\n        }\n\n        $cache = static::cacheConfig();\n\n        $tag = [];\n        $tag[] = $cache['tag'];\n        if (!empty($roleIds)) {\n            foreach ($roleIds as $role) {\n                $tag[] = $cache['role'] . $role;\n            }\n        }\n\n        // 保存到缓存\n        Cache::tag($tag)->set($cache['prefix'] . $uid, $data, $cache['expire']);\n        return $data;\n    }\n\n    /**\n     * 获取全部权限\n     */\n    public static function getAllAuth(): array\n    {\n        $cache = static::cacheConfig();\n        // 直接从缓存获取\n        $auth = Cache::get($cache['all']);\n        if ($auth) {\n            return $auth;\n        }\n\n        $all = (new SystemMenuLogic())->getAllAuth();\n\n        // 设置权限并返回\n        Cache::tag($cache['tag'])->set($cache['all'], $all, $cache['expire']);\n\n        return $all;\n    }\n\n    /**\n     * 清理缓存\n     */\n    public static function clearUserAuth($uid): bool\n    {\n        $cache = static::cacheConfig();\n        return Cache::delete($cache['prefix'] . $uid);\n    }\n\n    /**\n     * 清理角色缓存\n     */\n    public static function clearUserAuthByRoleId($role_id): bool\n    {\n        $cache = static::cacheConfig();\n        if (is_array($role_id)) {\n            $tags = [];\n            foreach ($role_id as $id) {\n                $tags[] = $cache['role'] . $id;\n            }\n        } else {\n            $tags = $cache['role'] . $role_id;\n        }\n        return Cache::tag($tags)->clear();\n    }\n\n    /**\n     * 清理所有用户缓存\n     * @return bool\n     */\n    public static function clear(): bool\n    {\n        $cache = static::cacheConfig();\n        return Cache::tag($cache['tag'])->clear();\n    }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/cache/UserInfoCache.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\ndeclare(strict_types=1);\n\nnamespace plugin\\saiadmin\\app\\cache;\n\nuse plugin\\saiadmin\\app\\logic\\system\\SystemUserLogic;\nuse support\\think\\Cache;\n\n/**\n * 用户信息缓存\n */\nclass UserInfoCache\n{\n    /**\n     * 读取缓存配置\n     * @return array\n     */\n    public static function cacheConfig(): array\n    {\n        return config('plugin.saiadmin.saithink.user_cache', [\n            'prefix' => 'saiadmin:user_cache:info_',\n            'expire' => 60 * 60 * 4,\n            'dept' => 'saiadmin:user_cache:dept_',\n            'role' => 'saiadmin:user_cache:role_',\n            'post' => 'saiadmin:user_cache:post_',\n        ]);\n    }\n\n    /**\n     * 通过id获取缓存管理员信息\n     */\n    public static function getUserInfo($uid): array\n    {\n        if (empty($uid)) {\n            return [];\n        }\n        $cache = static::cacheConfig();\n        // 直接从缓存获取\n        $adminInfo = Cache::get($cache['prefix'] . $uid);\n\n        if ($adminInfo) {\n            return $adminInfo;\n        }\n\n        // 获取缓存信息并返回\n        $adminInfo = static::setUserInfo($uid);\n        if ($adminInfo) {\n            return $adminInfo;\n        }\n\n        return [];\n    }\n\n    /**\n     * 设置管理员信息\n     */\n    public static function setUserInfo($uid): array\n    {\n        $data = (new SystemUserLogic())->getUser($uid);\n        $cache = static::cacheConfig();\n\n        $tags = [];\n        if (!empty($data['deptList'])) {\n            $tags[] = $cache['dept'] . $data['deptList']['id'];\n        }\n        if (!empty($data['roleList'])) {\n            foreach ($data['roleList'] as $role) {\n                $tags[] = $cache['role'] . $role['id'];\n            }\n        }\n        if (!empty($data['postList'])) {\n            foreach ($data['postList'] as $post) {\n                $tags[] = $cache['post'] . $post['id'];\n            }\n        }\n        Cache::tag($tags)->set($cache['prefix'] . $uid, $data, $cache['expire']);\n        return $data;\n    }\n\n    /**\n     * 清理管理员信息缓存\n     */\n    public static function clearUserInfo($uid): bool\n    {\n        $cache = static::cacheConfig();\n        return Cache::delete($cache['prefix'] . $uid);\n    }\n\n    /**\n     * 清理部门下所有用户缓存\n     */\n    public static function clearUserInfoByDeptId($dept_id): bool\n    {\n        $cache = static::cacheConfig();\n        if (is_array($dept_id)) {\n            $tags = [];\n            foreach ($dept_id as $id) {\n                $tags[] = $cache['dept'] . $id;\n            }\n        } else {\n            $tags = $cache['dept'] . $dept_id;\n        }\n        return Cache::tag($tags)->clear();\n    }\n\n    /**\n     * 清理角色下所有用户缓存\n     */\n    public static function clearUserInfoByRoleId($role_id): bool\n    {\n        $cache = static::cacheConfig();\n        if (is_array($role_id)) {\n            $tags = [];\n            foreach ($role_id as $id) {\n                $tags[] = $cache['role'] . $id;\n            }\n        } else {\n            $tags = $cache['role'] . $role_id;\n        }\n        return Cache::tag($tags)->clear();\n    }\n\n    /**\n     * 清理岗位下所有用户缓存\n     */\n    public static function clearUserInfoByPostId($post_id): bool\n    {\n        $cache = static::cacheConfig();\n        if (is_array($post_id)) {\n            $tags = [];\n            foreach ($post_id as $id) {\n                $tags[] = $cache['post'] . $id;\n            }\n        } else {\n            $tags = $cache['post'] . $post_id;\n        }\n        return Cache::tag($tags)->clear();\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/cache/UserMenuCache.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\ndeclare(strict_types=1);\n\nnamespace plugin\\saiadmin\\app\\cache;\n\nuse plugin\\saiadmin\\app\\logic\\system\\SystemMenuLogic;\nuse plugin\\saiadmin\\app\\model\\system\\SystemUserRole;\nuse support\\think\\Cache;\n\n/**\n * 用户菜单缓存\n */\nclass UserMenuCache\n{\n    /**\n     * 读取缓存配置\n     * @return array\n     */\n    public static function cacheConfig(): array\n    {\n        return config('plugin.saiadmin.saithink.menu_cache', [\n            'prefix' => 'saiadmin:menu_cache:user_',\n            'expire' => 60 * 60 * 24 * 7,\n            'tag' => 'saiadmin:menu_cache',\n        ]);\n    }\n\n    /**\n     * 获取用户的菜单\n     */\n    public static function getUserMenu($uid): array\n    {\n        if (empty($uid)) {\n            return [];\n        }\n        $cache = static::cacheConfig();\n        // 直接从缓存获取\n        $menu = Cache::get($cache['prefix'] . $uid);\n        if ($menu) {\n            return $menu;\n        }\n\n        // 设置用户菜单并获取\n        $menu = static::setUserMenu($uid);\n        if ($menu) {\n            return $menu;\n        }\n\n        return [];\n    }\n\n    /**\n     * 设置用户菜单\n     */\n    public static function setUserMenu($uid): array\n    {\n        $cache = static::cacheConfig();\n        $tag = [];\n        $tag[] = $cache['tag'];\n        $logic = new SystemMenuLogic();\n        if ($uid == 1) {\n            $data = $logic->getAllMenus();\n        } else {\n            $roleIds = SystemUserRole::getRoleIds($uid);\n            $data = $logic->getMenuByRole($roleIds);\n            if (empty($data)) {\n                return [];\n            }\n        }\n\n        // 保存到缓存\n        Cache::tag($tag)->set($cache['prefix'] . $uid, $data, $cache['expire']);\n        return $data;\n    }\n\n    /**\n     * 清理用户缓存\n     */\n    public static function clearUserMenu($uid): bool\n    {\n        $cache = static::cacheConfig();\n        return Cache::delete($cache['prefix'] . $uid);\n    }\n\n    /**\n     * 清理所有菜单缓存\n     * @return bool\n     */\n    public static function clearMenuCache(): bool\n    {\n        $cache = static::cacheConfig();\n        return Cache::tag($cache['tag'])->clear();\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/InstallController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller;\n\nuse Throwable;\nuse support\\Request;\nuse support\\Response;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\basic\\OpenController;\n\n/**\n * 安装控制器\n */\nclass InstallController extends OpenController\n{\n    /**\n     * 不需要登录的方法\n     */\n    protected array $noNeedLogin = ['index', 'install'];\n\n    /**\n     * 应用名称\n     * @var string\n     */\n    protected string $app = 'saiadmin';\n\n    protected string $version = '6.0.0';\n\n    /**\n     * 安装首页\n     */\n    public function index()\n    {\n        $data['app'] = $this->app;\n        $data['version'] = config('plugin.saiadmin.app.version', $this->version);\n\n        $env = base_path() . DIRECTORY_SEPARATOR . '.env';\n\n        clearstatcache();\n        if (is_file($env)) {\n            $data['error'] = '程序已经安装';\n            return view('install/error', $data);\n        }\n\n        if (!is_writable(base_path() . DIRECTORY_SEPARATOR . 'config')) {\n            $data['error'] = '权限认证失败';\n            return view('install/error', $data);\n        }\n\n        return view('install/index', $data);\n    }\n\n    /**\n     * 执行安装\n     */\n    public function install(Request $request)\n    {\n        $env = base_path() . DIRECTORY_SEPARATOR . '.env';\n\n        clearstatcache();\n        if (is_file($env)) {\n            return $this->fail('管理后台已经安装！如需重新安装，请删除根目录env配置文件并重启');\n        }\n\n        $user = $request->post('username');\n        $password = $request->post('password');\n        $database = $request->post('database');\n        $host = $request->post('host');\n        $port = (int) $request->post('port') ?: 3306;\n        $dataType = $request->post('dataType', 'demo');\n\n        try {\n            $db = $this->getPdo($host, $user, $password, $port);\n            $smt = $db->query(\"show databases like '$database'\");\n            if (empty($smt->fetchAll())) {\n                $db->exec(\"create database `$database` CHARSET utf8mb4 COLLATE utf8mb4_general_ci\");\n            }\n        } catch (\\Throwable $e) {\n            $message = $e->getMessage();\n            if (stripos($message, 'Access denied for user')) {\n                return $this->fail('数据库用户名或密码错误');\n            }\n            if (stripos($message, 'Connection refused')) {\n                return $this->fail('Connection refused. 请确认数据库IP端口是否正确，数据库已经启动');\n            }\n            if (stripos($message, 'timed out')) {\n                return $this->fail('数据库连接超时，请确认数据库IP端口是否正确，安全组及防火墙已经放行端口');\n            }\n            throw $e;\n        }\n\n        $db->exec(\"use `$database`\");\n\n        $smt = $db->query(\"show tables like 'sa_system_menu';\");\n        $tables = $smt->fetchAll();\n        if (count($tables) > 0) {\n            return $this->fail('数据库已经安装，请勿重复安装');\n        }\n\n        if ($dataType == 'demo') {\n            $sql_file = base_path() . '/plugin/saiadmin/db/saiadmin-6.0.sql';\n        } else {\n            $sql_file = base_path() . '/plugin/saiadmin/db/saiadmin-pure.sql';\n        }\n\n        if (!is_file($sql_file)) {\n            return $this->fail('数据库SQL文件不存在');\n        }\n\n        $sql_query = file_get_contents($sql_file);\n\n        $db->exec($sql_query);\n\n        $this->generateConfig();\n\n        $env_config = <<<EOF\n# 数据库配置\nDB_TYPE = mysql\nDB_HOST = $host\nDB_PORT = $port\nDB_NAME = $database\nDB_USER = $user\nDB_PASSWORD = $password\nDB_PREFIX = \n\n# 缓存方式\nCACHE_MODE = file\n\n# Redis配置\nREDIS_HOST = 127.0.0.1\nREDIS_PORT = 6379\nREDIS_PASSWORD = ''\nREDIS_DB = 0\nDB_CHARSET = utf8mb4\n\n# 验证码配置\nCAPTCHA_MODE = cache\n\n#前端目录\nFRONTEND_DIR = saiadmin-artd\nEOF;\n        file_put_contents(base_path() . DIRECTORY_SEPARATOR . '.env', $env_config);\n\n        // 尝试reload\n        if (function_exists('posix_kill')) {\n            set_error_handler(function () {});\n            posix_kill(posix_getppid(), SIGUSR1);\n            restore_error_handler();\n        }\n\n        return $this->success('安装成功');\n    }\n\n    /**\n     * 生成配置文件 \n     */\n    protected function generateConfig()\n    {\n        // 1、think-orm配置文件\n        $think_orm_config = <<<EOF\n<?php\n\nreturn [\n    'default' => 'mysql',\n    'connections' => [\n        'mysql' => [\n            // 数据库类型\n            'type' => env('DB_TYPE', 'mysql'),\n            // 服务器地址\n            'hostname' => env('DB_HOST', '127.0.0.1'),\n            // 数据库名\n            'database' => env('DB_NAME', 'saiadmin'),\n            // 数据库用户名\n            'username' => env('DB_USER', 'root'),\n            // 数据库密码\n            'password' => env('DB_PASSWORD', '123456'),\n            // 数据库连接端口\n            'hostport' => env('DB_PORT', 3306),\n            // 数据库连接参数\n            'params' => [\n                // 连接超时3秒\n                \\PDO::ATTR_TIMEOUT => 3,\n            ],\n            // 数据库编码默认采用utf8\n            'charset' => env('DB_CHARSET', 'utf8mb4'),\n            // 数据库表前缀\n            'prefix' => env('DB_PREFIX', ''),\n            // 断线重连\n            'break_reconnect' => true,\n            // 自定义分页类\n            'bootstrap' =>  '',\n            // 连接池配置\n            'pool' => [\n                'max_connections' => 5, // 最大连接数\n                'min_connections' => 1, // 最小连接数\n                'wait_timeout' => 3,    // 从连接池获取连接等待超时时间\n                'idle_timeout' => 60,   // 连接最大空闲时间，超过该时间会被回收\n                'heartbeat_interval' => 50, // 心跳检测间隔，需要小于60秒\n            ],\n        ],\n    ],\n];\nEOF;\n        file_put_contents(base_path() . '/config/think-orm.php', $think_orm_config);\n\n        // 2、chache配置文件\n        $cache_config = <<<EOF\n<?php\n\nreturn [\n    'default' => env('CACHE_MODE', 'file'),\n    'stores' => [\n        'file' => [\n            'driver' => 'file',\n            'path' => runtime_path('cache')\n        ],\n        'redis' => [\n            'driver' => 'redis',\n            'connection' => 'default'\n        ],\n        'array' => [\n            'driver' => 'array'\n        ]\n    ]\n];\nEOF;\n        file_put_contents(base_path() . '/config/cache.php', $cache_config);\n\n        // 3、redis配置文件\n        $redis_config = <<<EOF\n<?php\n\nreturn [\n    'default' => [\n        'password' => env('REDIS_PASSWORD', ''),\n        'host' => env('REDIS_HOST', '127.0.0.1'),\n        'port' => env('REDIS_PORT', 6379),\n        'database' => env('REDIS_DB', 0),\n        'pool' => [\n            'max_connections' => 5,\n            'min_connections' => 1,\n            'wait_timeout' => 3,\n            'idle_timeout' => 60,\n            'heartbeat_interval' => 50,\n        ],\n    ]\n];\nEOF;\n        file_put_contents(base_path() . '/config/redis.php', $redis_config);\n\n        // 4、think-cache配置文件\n        $think_cache_config = <<<EOF\n<?php\nreturn [\n    // 默认缓存驱动\n    'default' => env('CACHE_MODE', 'file'),\n    // 缓存连接方式配置\n    'stores'  => [\n        // redis缓存\n        'redis' => [\n            // 驱动方式\n            'type' => 'redis',\n            // 服务器地址\n            'host' => env('REDIS_HOST', '127.0.0.1'),\n            // 服务器端口\n            'port' => env('REDIS_PORT', 6379),\n            // 服务器密码\n            'password' => env('REDIS_PASSWORD', ''),\n            // 数据库\n            'select' => env('REDIS_DB', 0),\n            // 缓存前缀\n            'prefix' => 'cache:',\n            // 默认缓存有效期 0表示永久缓存\n            'expire'     => 0,\n            // Thinkphp官方没有这个参数，由于生成的tag键默认不过期，如果tag键数量很大，避免长时间占用内存，可以设置一个超过其他缓存的过期时间，0为不设置\n            'tag_expire' => 86400 * 30,\n            // 缓存标签前缀\n            'tag_prefix' => 'tag:',\n            // 连接池配置\n            'pool' => [\n                'max_connections' => 5, // 最大连接数\n                'min_connections' => 1, // 最小连接数\n                'wait_timeout' => 3,    // 从连接池获取连接等待超时时间\n                'idle_timeout' => 60,   // 连接最大空闲时间，超过该时间会被回收\n                'heartbeat_interval' => 50, // 心跳检测间隔，需要小于60秒\n            ],\n        ],\n        // 文件缓存\n        'file' => [\n            // 驱动方式\n            'type' => 'file',\n            // 设置不同的缓存保存目录\n            'path' => runtime_path() . '/file/',\n        ],\n    ],\n];\nEOF;\n        file_put_contents(base_path() . '/config/think-cache.php', $think_cache_config);\n\n        // 5、database配置文件\n        $database = <<<EOF\n<?php\nreturn [\n    'default' => 'mysql',\n    'connections' => [\n        'mysql' => [\n            'driver' => env('DB_TYPE', 'mysql'),\n            'host' => env('DB_HOST', '127.0.0.1'),\n            'port' => env('DB_PORT', 3306),\n            'database' => env('DB_NAME', 'saiadmin'),\n            'username' => env('DB_USER', 'root'),\n            'password' => env('DB_PASSWORD', '123456'),\n            'charset' => env('DB_CHARSET', 'utf8mb4'),\n            'collation' => env('DB_COLLATION', 'utf8mb4_general_ci'),\n            'prefix' => env('DB_PREFIX', ''),\n            'strict' => true,\n            'engine' => null,\n            'options' => [\n                PDO::ATTR_EMULATE_PREPARES => false, // Must be false for Swoole and Swow drivers.\n            ],\n            'pool' => [\n                'max_connections' => 5,\n                'min_connections' => 1,\n                'wait_timeout' => 3,\n                'idle_timeout' => 60,\n                'heartbeat_interval' => 50,\n            ],\n        ],\n    ],\n];\nEOF;\n        file_put_contents(base_path() . '/config/database.php', $database);\n\n    }\n\n    /**\n     * 获取pdo连接\n     * @param $host\n     * @param $username\n     * @param $password\n     * @param $port\n     * @param $database\n     * @return \\PDO\n     */\n    protected function getPdo($host, $username, $password, $port, $database = null): \\PDO\n    {\n        $dsn = \"mysql:host=$host;port=$port;\";\n        if ($database) {\n            $dsn .= \"dbname=$database\";\n        }\n        $params = [\n            \\PDO::MYSQL_ATTR_INIT_COMMAND => \"set names utf8mb4\",\n            \\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,\n            \\PDO::ATTR_EMULATE_PREPARES => false,\n            \\PDO::ATTR_TIMEOUT => 5,\n            \\PDO::ATTR_ERRMODE => \\PDO::ERRMODE_EXCEPTION,\n        ];\n        return new \\PDO($dsn, $username, $password, $params);\n    }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/LoginController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller;\n\nuse support\\Request;\nuse support\\Response;\nuse plugin\\saiadmin\\utils\\Captcha;\nuse plugin\\saiadmin\\basic\\BaseController;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemUserLogic;\n\n/**\n * 登录控制器\n */\nclass LoginController extends BaseController\n{\n\n    /**\n     * 不需要登录的方法\n     */\n    protected array $noNeedLogin = ['captcha', 'login'];\n\n    /**\n     * 获取验证码\n     */\n    public function captcha() : Response\n    {\n        $captcha = new Captcha();\n        $result = $captcha->imageCaptcha();\n        if ($result['result'] !== 1) {\n            return $this->fail($result['message']);\n        }\n        return $this->success($result);\n    }\n\n    /**\n     * 登录\n     * @param Request $request\n     * @return Response\n     */\n    public function login(Request $request): Response\n    {\n        $username = $request->post('username', '');\n        $password = $request->post('password', '');\n        $type = $request->post('type', 'pc');\n\n        $code = $request->post('code', '');\n        $uuid = $request->post('uuid', '');\n        $captcha = new Captcha();\n        if (!$captcha->checkCaptcha($uuid, $code)) {\n            return $this->fail('验证码错误');\n        }\n        $logic = new SystemUserLogic();\n        $data = $logic->login($username, $password, $type);\n        return $this->success($data);\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/SystemController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller;\n\nuse plugin\\saiadmin\\app\\cache\\DictCache;\nuse plugin\\saiadmin\\app\\cache\\UserAuthCache;\nuse plugin\\saiadmin\\app\\cache\\UserInfoCache;\nuse plugin\\saiadmin\\app\\cache\\UserMenuCache;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemCategoryLogic;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemLoginLogLogic;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemOperLogLogic;\nuse plugin\\saiadmin\\basic\\BaseController;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemUserLogic;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemAttachmentLogic;\nuse plugin\\saiadmin\\service\\Permission;\nuse support\\Request;\nuse support\\Response;\nuse plugin\\saiadmin\\utils\\Arr;\nuse Tinywan\\Storage\\Storage;\n\n/**\n * 系统控制器\n */\nclass SystemController extends BaseController\n{\n\n    /**\n     * 用户信息\n     */\n    public function userInfo(): Response\n    {\n        $info['user'] = $this->adminInfo;\n        $info = [];\n        $info['id'] = $this->adminInfo['id'];\n        $info['username'] = $this->adminInfo['username'];\n        $info['dashboard'] = $this->adminInfo['dashboard'];\n        $info['avatar'] = $this->adminInfo['avatar'];\n        $info['email'] = $this->adminInfo['email'];\n        $info['phone'] = $this->adminInfo['phone'];\n        $info['gender'] = $this->adminInfo['gender'];\n        $info['signed'] = $this->adminInfo['signed'];\n        $info['realname'] = $this->adminInfo['realname'];\n        $info['department'] = $this->adminInfo['deptList'];\n        if ($this->adminInfo['id'] === 1) {\n            $info['buttons'] = ['*'];\n            $info['roles'] = ['super_admin'];\n        } else {\n            $info['buttons'] = UserAuthCache::getUserAuth($this->adminInfo['id']);\n            $info['roles'] = Arr::getArrayColumn($this->adminInfo['roleList'], 'code');\n        }\n        return $this->success($info);\n    }\n\n    /**\n     * 全部字典数据\n     */\n    public function dictAll(): Response\n    {\n        $dict = DictCache::getDictAll();\n        return $this->success($dict);\n    }\n\n    /**\n     * 菜单数据\n     * @return Response\n     */\n    public function menu(): Response\n    {\n        $data = UserMenuCache::getUserMenu($this->adminInfo['id']);\n        return $this->success($data);\n    }\n\n    /**\n     * 获取资源列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('附件列表读取', 'core:system:resource')]\n    public function getResourceCategory(Request $request): Response\n    {\n        $logic = new SystemCategoryLogic();\n        $data = $logic->tree([]);\n        return $this->success($data);\n    }\n\n    /**\n     * 获取资源列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('附件列表读取', 'core:system:resource')]\n    public function getResourceList(Request $request): Response\n    {\n        $logic = new SystemAttachmentLogic();\n        $where = $request->more([\n            ['origin_name', ''],\n            ['category_id', ''],\n        ]);\n        $query = $logic->search($where);\n        $query->whereIn('mime_type', ['image/jpeg', 'image/png', 'image/gif', 'image/webp']);\n        $data = $logic->getList($query);\n        return $this->success($data);\n    }\n\n    /**\n     * 获取用户列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('用户列表读取', 'core:system:user')]\n    public function getUserList(Request $request): Response\n    {\n        $logic = new SystemUserLogic();\n        $where = $request->more([\n            ['keyword', ''],\n            ['dept_id', ''],\n        ]);\n        $data = $logic->openUserList($where);\n        return $this->success($data);\n    }\n\n    /**\n     * 下载网络图片\n     */\n    #[Permission('上传网络图片', 'core:system:uploadImage')]\n    public function saveNetworkImage(Request $request): Response\n    {\n        $url = $request->input('url', '');\n        $config = Storage::getConfig('local');\n        $logic = new SystemAttachmentLogic();\n        $data = $logic->saveNetworkImage($url, $config);\n        return $this->success($data, '操作成功');\n    }\n\n    /**\n     * 上传图片\n     */\n    #[Permission('上传图片', 'core:system:uploadImage')]\n    public function uploadImage(Request $request): Response\n    {\n        $logic = new SystemAttachmentLogic();\n        $type = $request->input('mode', 'system');\n        if ($type == 'local') {\n            return $this->success($logic->uploadBase('image', true));\n        }\n        return $this->success($logic->uploadBase('image'));\n    }\n\n    /**\n     * 上传文件\n     */\n    #[Permission('上传文件', 'core:system:uploadFile')]\n    public function uploadFile(Request $request): Response\n    {\n        $logic = new SystemAttachmentLogic();\n        $type = $request->input('mode', 'system');\n        if ($type == 'local') {\n            return $this->success($logic->uploadBase('file', true));\n        }\n        return $this->success($logic->uploadBase('file'));\n    }\n\n    /**\n     * 切片上传\n     */\n    #[Permission('上传文件', 'core:system:chunkUpload')]\n    public function chunkUpload(Request $request): Response\n    {\n        $logic = new SystemAttachmentLogic();\n        $data = $request->post();\n        $result = $logic->chunkUpload($data);\n        return $this->success($result);\n    }\n\n    /**\n     * 获取登录日志\n     * @return Response\n     */\n    public function getLoginLogList(): Response\n    {\n        $logic = new SystemLoginLogLogic();\n        $logic->init($this->adminInfo);\n        $query = $logic->search(['username' => $this->adminName]);\n        $data = $logic->getList($query);\n        return $this->success($data);\n    }\n\n    /**\n     * 获取操作日志\n     * @return Response\n     */\n    public function getOperationLogList(): Response\n    {\n        $logic = new SystemOperLogLogic();\n        $logic->init($this->adminInfo);\n        $data = $logic->getOwnOperLogList(['username' => $this->adminName]);\n        return $this->success($data);\n    }\n\n    /**\n     * 清除缓存\n     * @return Response\n     */\n    public function clearAllCache(): Response\n    {\n        UserInfoCache::clearUserInfo($this->adminId);\n        UserAuthCache::clearUserAuth($this->adminId);\n        UserMenuCache::clearUserMenu($this->adminId);\n        return $this->success([], '清除缓存成功!');\n    }\n\n    /**\n     * 基本统计\n     * @return Response\n     */\n    #[Permission('工作台数据统计', 'core:console:list')]\n    public function statistics(): Response\n    {\n        $userLogic = new SystemUserLogic();\n        $userCount = $userLogic->count('id');\n        $uploadLogic = new SystemAttachmentLogic();\n        $attachCount = $uploadLogic->count('id');\n        $loginLogic = new SystemLoginLogLogic();\n        $loginCount = $loginLogic->count('id');\n        $operLogic = new SystemOperLogLogic();\n        $operCount = $operLogic->count('id');\n        return $this->success([\n            'user' => $userCount,\n            'attach' => $attachCount,\n            'login' => $loginCount,\n            'operate' => $operCount,\n        ]);\n    }\n\n    /**\n     * 登录统计曲线图\n     * @return Response\n     */\n    #[Permission('工作台数据统计', 'core:console:list')]\n    public function loginChart(): Response\n    {\n        $logic = new SystemLoginLogLogic();\n        $data = $logic->loginChart();\n        return $this->success($data);\n    }\n\n    /**\n     * 登录统计柱状图\n     * @return Response\n     */\n    #[Permission('工作台数据统计', 'core:console:list')]\n    public function loginBarChart(): Response\n    {\n        $logic = new SystemLoginLogLogic();\n        $data = $logic->loginBarChart();\n        return $this->success($data);\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/system/DataBaseController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller\\system;\n\nuse plugin\\saiadmin\\app\\logic\\system\\DatabaseLogic;\nuse plugin\\saiadmin\\basic\\BaseController;\nuse plugin\\saiadmin\\service\\Permission;\nuse support\\Request;\nuse support\\Response;\n\n/**\n * 数据表维护控制器\n */\nclass DataBaseController extends BaseController\n{\n    /**\n     * 构造\n     */\n    public function __construct()\n    {\n        $this->logic = new DatabaseLogic();\n        parent::__construct();\n    }\n\n    /**\n     * 数据源列表\n     * @return Response\n     */\n    public function source(): Response\n    {\n        $data = $this->logic->getDbSource();\n        return $this->success($data);\n    }\n\n    /**\n     * 数据列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('数据表列表', 'core:database:index')]\n    public function index(Request $request): Response\n    {\n        $where = $request->more([\n            ['name', ''],\n            ['source', ''],\n        ]);\n        $data = $this->logic->getList($where);\n        return $this->success($data);\n    }\n\n    /**\n     * 回收站数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('回收站数据', 'core:recycle:index')]\n    public function recycle(Request $request): Response\n    {\n        $table = $request->input('table', '');\n        $data = $this->logic->recycleData($table);\n        return $this->success($data);\n    }\n\n    /**\n     * 销毁数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('回收站销毁', 'core:recycle:edit')]\n    public function delete(Request $request): Response\n    {\n        $table = $request->input('table', '');\n        $ids = $request->input('ids', '');\n        if (!empty($ids)) {\n            $result = $this->logic->delete($table, $ids);\n            if (!$result) {\n                return $this->fail('操作失败');\n            }\n            return $this->success('操作成功');\n        } else {\n            return $this->fail('参数错误，请检查');\n        }\n    }\n\n    /**\n     * 恢复数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('回收站恢复', 'core:recycle:edit')]\n    public function recovery(Request $request): Response\n    {\n        $table = $request->input('table', '');\n        $ids = $request->input('ids', '');\n        if (!empty($ids)) {\n            $result = $this->logic->recovery($table, $ids);\n            if (!$result) {\n                return $this->fail('操作失败');\n            }\n            return $this->success('操作成功');\n        } else {\n            return $this->fail('参数错误，请检查');\n        }\n    }\n\n    /**\n     * 获取表字段信息\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('数据表字段', 'core:database:index')]\n    public function detailed(Request $request): Response\n    {\n        $table = $request->input('table', '');\n        $data = $this->logic->getColumnList($table, '');\n        return $this->success($data);\n    }\n\n    /**\n     * 优化表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('数据表优化表', 'core:database:edit')]\n    public function optimize(Request $request): Response\n    {\n        $tables = $request->input('tables', []);\n        $this->logic->optimizeTable($tables);\n        return $this->success('优化成功');\n    }\n\n    /**\n     * 清理表碎片\n     */\n    #[Permission('数据表清理碎片', 'core:database:edit')]\n    public function fragment(Request $request): Response\n    {\n        $tables = $request->input('tables', []);\n        $this->logic->fragmentTable($tables);\n        return $this->success('清理成功');\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/system/SystemAttachmentController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller\\system;\n\nuse plugin\\saiadmin\\basic\\BaseController;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemAttachmentLogic;\nuse plugin\\saiadmin\\service\\Permission;\nuse support\\Request;\nuse support\\Response;\n\n/**\n * 附件管理控制器\n */\nclass SystemAttachmentController extends BaseController\n{\n    /**\n     * 构造\n     */\n    public function __construct()\n    {\n        $this->logic = new SystemAttachmentLogic();\n        parent::__construct();\n    }\n\n    /**\n     * 数据列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('附件数据列表', 'core:attachment:index')]\n    public function index(Request $request) : Response\n    {\n        $where = $request->more([\n            ['origin_name', ''],\n            ['category_id', ''],\n            ['storage_mode', ''],\n            ['mime_type', ''],\n            ['create_time', ''],\n        ]);\n        $query = $this->logic->search($where);\n        $data = $this->logic->getList($query);\n        return $this->success($data);\n    }\n\n    /**\n     * 更新数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('附件数据修改', 'core:attachment:edit')]\n    public function update(Request $request): Response\n    {\n        $data = $request->post();\n        $result = $this->logic->edit($data['id'], ['origin_name' => $data['origin_name']]);\n        if ($result) {\n            return $this->success('修改成功');\n        } else {\n            return $this->fail('修改失败');\n        }\n    }\n\n    /**\n     * 删除数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('附件数据删除', 'core:attachment:edit')]\n    public function destroy(Request $request) : Response\n    {\n        $ids = $request->post('ids', '');\n        if (empty($ids)) {\n            return $this->fail('请选择要删除的数据');\n        }\n        $result = $this->logic->destroy($ids);\n        if ($result) {\n            return $this->success('删除成功');\n        } else {\n            return $this->fail('删除失败');\n        }\n    }\n\n    /**\n     * 移动分类\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('附件移动分类', 'core:attachment:edit')]\n    public function move(Request $request) : Response\n    {\n        $category_id = $request->post('category_id', '');\n        $ids = $request->post('ids', '');\n        if (empty($ids) || empty($category_id)) {\n            return $this->fail('参数错误，请检查参数');\n        }\n        $result = $this->logic->move($category_id, $ids);\n        if ($result) {\n            return $this->success('删除成功');\n        } else {\n            return $this->fail('删除失败');\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/system/SystemCategoryController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller\\system;\n\nuse plugin\\saiadmin\\app\\validate\\system\\SystemCategoryValidate;\nuse plugin\\saiadmin\\basic\\BaseController;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemCategoryLogic;\nuse plugin\\saiadmin\\service\\Permission;\nuse support\\Request;\nuse support\\Response;\n\n/**\n * 附件分类控制器\n */\nclass SystemCategoryController extends BaseController\n{\n    /**\n     * 构造\n     */\n    public function __construct()\n    {\n        $this->logic = new SystemCategoryLogic();\n        $this->validate = new SystemCategoryValidate;\n        parent::__construct();\n    }\n\n    /**\n     * 数据列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('附件分类列表', 'core:attachment:index')]\n    public function index(Request $request) : Response\n    {\n        $where = $request->more([\n            ['category_name', ''],\n        ]);\n        $data = $this->logic->tree($where);\n        return $this->success($data);\n    }\n\n    /**\n     * 读取数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('附件分类读取', 'core:attachment:index')]\n    public function read(Request $request) : Response\n    {\n        $id = $request->input('id', '');\n        $model = $this->logic->read($id);\n        if ($model) {\n            $data = is_array($model) ? $model : $model->toArray();\n            return $this->success($data);\n        } else {\n            return $this->fail('未查找到信息');\n        }\n    }\n\n    /**\n     * 保存数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('附件分类添加', 'core:attachment:edit')]\n    public function save(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('save', $data);\n        $result = $this->logic->add($data);\n        if ($result) {\n            return $this->success('添加成功');\n        } else {\n            return $this->fail('添加失败');\n        }\n    }\n\n    /**\n     * 更新数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('附件分类修改', 'core:attachment:edit')]\n    public function update(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('update', $data);\n        $result = $this->logic->edit($data['id'], $data);\n        if ($result) {\n            return $this->success('修改成功');\n        } else {\n            return $this->fail('修改失败');\n        }\n    }\n\n    /**\n     * 删除数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('附件分类删除', 'core:attachment:edit')]\n    public function destroy(Request $request) : Response\n    {\n        $ids = $request->post('ids', '');\n        if (empty($ids)) {\n            return $this->fail('请选择要删除的数据');\n        }\n        $result = $this->logic->destroy($ids);\n        if ($result) {\n            return $this->success('删除成功');\n        } else {\n            return $this->fail('删除失败');\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/system/SystemConfigController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller\\system;\n\nuse plugin\\saiadmin\\app\\cache\\ConfigCache;\nuse plugin\\saiadmin\\basic\\BaseController;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemConfigLogic;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemConfigGroupLogic;\nuse plugin\\saiadmin\\app\\validate\\system\\SystemConfigValidate;\nuse plugin\\saiadmin\\service\\Permission;\nuse support\\Request;\nuse support\\Response;\n\n/**\n * 配置项数据控制器\n */\nclass SystemConfigController extends BaseController\n{\n    /**\n     * 构造\n     */\n    public function __construct()\n    {\n        $this->logic = new SystemConfigLogic();\n        $this->validate = new SystemConfigValidate;\n        parent::__construct();\n    }\n\n    /**\n     * 数据列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('系统设置列表', 'core:config:index')]\n    public function index(Request $request): Response\n    {\n        $where = $request->more([\n            ['group_id', ''],\n            ['name', ''],\n            ['key', ''],\n        ]);\n        $this->logic->setOrderField('sort');\n        $this->logic->setOrderType('desc');\n        $query = $this->logic->search($where);\n        $data = $this->logic->getList($query);\n        return $this->success($data);\n    }\n\n    /**\n     * 保存数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('系统设置管理', 'core:config:edit')]\n    public function save(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('save', $data);\n        $result = $this->logic->add($data);\n        if ($result) {\n            return $this->success('添加成功');\n        } else {\n            return $this->fail('添加失败');\n        }\n    }\n\n    /**\n     * 更新数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('系统设置管理', 'core:config:edit')]\n    public function update(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('update', $data);\n        $result = $this->logic->edit($data['id'], $data);\n        if ($result) {\n            return $this->success('修改成功');\n        } else {\n            return $this->fail('修改失败');\n        }\n    }\n\n    /**\n     * 删除数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('系统设置管理', 'core:config:edit')]\n    public function destroy(Request $request): Response\n    {\n        $ids = $request->post('ids', '');\n        if (empty($ids)) {\n            return $this->fail('请选择要删除的数据');\n        }\n        $result = $this->logic->destroy($ids);\n        if ($result) {\n            return $this->success('删除成功');\n        } else {\n            return $this->fail('删除失败');\n        }\n    }\n\n    /**\n     * 修改配置内容\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('系统设置修改', 'core:config:update')]\n    public function batchUpdate(Request $request): Response\n    {\n        $group_id = $request->post('group_id');\n        $config = $request->post('config');\n        if (empty($group_id) || empty($config)) {\n            return $this->fail('参数错误');\n        }\n        $this->logic->batchUpdate($group_id, $config);\n        return $this->success('操作成功');\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/system/SystemConfigGroupController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller\\system;\n\nuse plugin\\saiadmin\\app\\cache\\ConfigCache;\nuse plugin\\saiadmin\\basic\\BaseController;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemConfigGroupLogic;\nuse plugin\\saiadmin\\app\\validate\\system\\SystemConfigGroupValidate;\nuse plugin\\saiadmin\\service\\Permission;\nuse plugin\\saiadmin\\utils\\Arr;\nuse support\\Request;\nuse support\\Response;\nuse plugin\\saiadmin\\service\\EmailService;\nuse plugin\\saiadmin\\app\\model\\system\\SystemMail;\n\n/**\n * 配置控制器\n */\nclass SystemConfigGroupController extends BaseController\n{\n    /**\n     * 构造\n     */\n    public function __construct()\n    {\n        $this->logic = new SystemConfigGroupLogic();\n        $this->validate = new SystemConfigGroupValidate;\n        parent::__construct();\n    }\n\n    /**\n     * 数据列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('系统设置列表', 'core:config:index')]\n    public function index(Request $request) : Response\n    {\n        $where = $request->more([\n            ['name', ''],\n            ['code', ''],\n        ]);\n        $query = $this->logic->search($where);\n        $data = $this->logic->getAll($query);\n        return $this->success($data);\n    }\n\n    /**\n     * 保存数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('系统设置管理', 'core:config:edit')]\n    public function save(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('save', $data);\n        $result = $this->logic->add($data);\n        if ($result) {\n            return $this->success('添加成功');\n        } else {\n            return $this->fail('添加失败');\n        }\n    }\n\n    /**\n     * 更新数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('系统设置管理', 'core:config:edit')]\n    public function update(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('update', $data);\n        $result = $this->logic->edit($data['id'], $data);\n        if ($result) {\n            ConfigCache::clearConfig($data['code']);\n            return $this->success('修改成功');\n        } else {\n            return $this->fail('修改失败');\n        }\n    }\n\n    /**\n     * 删除数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('系统设置管理', 'core:config:edit')]\n    public function destroy(Request $request) : Response\n    {\n        $ids = $request->post('ids', '');\n        if (empty($ids)) {\n            return $this->fail('请选择要删除的数据');\n        }\n        $result = $this->logic->destroy($ids);\n        if ($result) {\n            return $this->success('删除成功');\n        } else {\n            return $this->fail('删除失败');\n        }\n    }\n\n    /**\n     * 邮件测试\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('系统设置修改', 'core:config:update')]\n    public function email(Request $request) : Response\n    {\n        $email = $request->input('email', '');\n        if (empty($email)) {\n            return $this->fail('请输入邮箱');\n        }\n        $subject = \"测试邮件\";\n        $code = \"9527\";\n        $content = \"<h1>验证码：{code}</h1><p>这是一封测试邮件,请忽略</p>\";\n        $template = [\n            'code' => $code\n        ];\n        $config = EmailService::getConfig();\n        $model = SystemMail::create([\n            'gateway' => Arr::getConfigValue($config,'Host'),\n            'from' => Arr::getConfigValue($config,'From'),\n            'email' => $email,\n            'code' => $code,\n        ]);\n        try {\n            $result = EmailService::sendByTemplate($email, $subject, $content, $template);\n            if (!empty($result)) {\n                $model->status = 'failure';\n                $model->response = $result;\n                $model->save();\n                return $this->fail('发送失败，请查看日志');\n            } else {\n                $model->status = 'success';\n                $model->save();\n                return $this->success([], '发送成功');\n            }\n        } catch (\\Exception $e) {\n            $model->status = 'failure';\n            $model->response = $e->getMessage();\n            $model->save();\n            return $this->fail($e->getMessage());\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/system/SystemDeptController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller\\system;\n\nuse plugin\\saiadmin\\app\\validate\\system\\SystemDeptValidate;\nuse plugin\\saiadmin\\basic\\BaseController;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemDeptLogic;\nuse plugin\\saiadmin\\service\\Permission;\nuse support\\Request;\nuse support\\Response;\n\n/**\n * 部门控制器\n */\nclass SystemDeptController extends BaseController\n{\n    /**\n     * 构造\n     */\n    public function __construct()\n    {\n        $this->logic = new SystemDeptLogic();\n        $this->validate = new SystemDeptValidate;\n        parent::__construct();\n    }\n\n    /**\n     * 数据列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('部门数据列表', 'core:dept:index')]\n    public function index(Request $request) : Response\n    {\n        $where = $request->more([\n            ['name', ''],\n            ['code', ''],\n            ['status', ''],\n        ]);\n        $data = $this->logic->tree($where);\n        return $this->success($data);\n    }\n\n    /**\n     * 读取数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('部门数据读取', 'core:dept:read')]\n    public function read(Request $request) : Response\n    {\n        $id = $request->input('id', '');\n        $model = $this->logic->read($id);\n        if ($model) {\n            $data = is_array($model) ? $model : $model->toArray();\n            return $this->success($data);\n        } else {\n            return $this->fail('未查找到信息');\n        }\n    }\n\n    /**\n     * 保存数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('部门数据添加', 'core:dept:save')]\n    public function save(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('save', $data);\n        $result = $this->logic->add($data);\n        if ($result) {\n            return $this->success('添加成功');\n        } else {\n            return $this->fail('添加失败');\n        }\n    }\n\n    /**\n     * 更新数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('部门数据修改','core:dept:update')]\n    public function update(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('update', $data);\n        $result = $this->logic->edit($data['id'], $data);\n        if ($result) {\n            return $this->success('修改成功');\n        } else {\n            return $this->fail('修改失败');\n        }\n    }\n\n    /**\n     * 删除数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('部门数据删除','core:dept:destroy')]\n    public function destroy(Request $request) : Response\n    {\n        $ids = $request->post('ids', '');\n        if (empty($ids)) {\n            return $this->fail('请选择要删除的数据');\n        }\n        $result = $this->logic->destroy($ids);\n        if ($result) {\n            return $this->success('删除成功');\n        } else {\n            return $this->fail('删除失败');\n        }\n    }\n\n    /**\n     * 可操作部门\n     * @param Request $request\n     * @return Response\n     */\n    public function accessDept(Request $request) : Response\n    {\n        $where = ['status' => 1];\n        $data = $this->logic->accessDept($where);\n        return $this->success($data);\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/system/SystemDictDataController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller\\system;\n\nuse plugin\\saiadmin\\app\\cache\\DictCache;\nuse plugin\\saiadmin\\basic\\BaseController;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemDictDataLogic;\nuse plugin\\saiadmin\\app\\validate\\system\\SystemDictDataValidate;\nuse plugin\\saiadmin\\service\\Permission;\nuse support\\Request;\nuse support\\Response;\n\n/**\n * 字典数据控制器\n */\nclass SystemDictDataController extends BaseController\n{\n    /**\n     * 构造\n     */\n    public function __construct()\n    {\n        $this->logic = new SystemDictDataLogic();\n        $this->validate = new SystemDictDataValidate;\n        parent::__construct();\n    }\n\n    /**\n     * 数据列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('数据字典列表', 'core:dict:index')]\n    public function index(Request $request): Response\n    {\n        $where = $request->more([\n            ['label', ''],\n            ['value', ''],\n            ['type_id', ''],\n            ['status', ''],\n        ]);\n        $this->logic->setOrderField('sort');\n        $this->logic->setOrderType('desc');\n        $query = $this->logic->search($where);\n        $data = $this->logic->getList($query);\n        return $this->success($data);\n    }\n\n    /**\n     * 保存数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('数据字典管理', 'core:dict:edit')]\n    public function save(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('save', $data);\n        $result = $this->logic->add($data);\n        if ($result) {\n            DictCache::clear();\n            return $this->success('添加成功');\n        } else {\n            return $this->fail('添加失败');\n        }\n    }\n\n    /**\n     * 更新数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('数据字典管理', 'core:dict:edit')]\n    public function update(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('update', $data);\n        $result = $this->logic->edit($data['id'], $data);\n        if ($result) {\n            DictCache::clear();\n            return $this->success('修改成功');\n        } else {\n            return $this->fail('修改失败');\n        }\n    }\n\n    /**\n     * 删除数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('数据字典管理', 'core:dict:edit')]\n    public function destroy(Request $request): Response\n    {\n        $ids = $request->post('ids', '');\n        if (empty($ids)) {\n            return $this->fail('请选择要删除的数据');\n        }\n        $result = $this->logic->destroy($ids);\n        if ($result) {\n            DictCache::clear();\n            return $this->success('删除成功');\n        } else {\n            return $this->fail('删除失败');\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/system/SystemDictTypeController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller\\system;\n\nuse plugin\\saiadmin\\app\\cache\\DictCache;\nuse plugin\\saiadmin\\basic\\BaseController;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemDictTypeLogic;\nuse plugin\\saiadmin\\app\\validate\\system\\SystemDictTypeValidate;\nuse plugin\\saiadmin\\service\\Permission;\nuse support\\Cache;\nuse support\\Request;\nuse support\\Response;\n\n/**\n * 字典类型控制器\n */\nclass SystemDictTypeController extends BaseController\n{\n    /**\n     * 构造\n     */\n    public function __construct()\n    {\n        $this->logic = new SystemDictTypeLogic();\n        $this->validate = new SystemDictTypeValidate;\n        parent::__construct();\n    }\n\n    /**\n     * 数据列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('数据字典列表', 'core:dict:index')]\n    public function index(Request $request) : Response\n    {\n        $where = $request->more([\n            ['name', ''],\n            ['code', ''],\n            ['status', ''],\n        ]);\n        $query = $this->logic->search($where);\n        $data = $this->logic->getList($query);\n        return $this->success($data);\n    }\n\n    /**\n     * 保存数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('数据字典管理', 'core:dict:edit')]\n    public function save(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('save', $data);\n        $result = $this->logic->add($data);\n        if ($result) {\n            DictCache::clear();\n            return $this->success('添加成功');\n        } else {\n            return $this->fail('添加失败');\n        }\n    }\n\n    /**\n     * 更新数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('数据字典管理', 'core:dict:edit')]\n    public function update(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('update', $data);\n        $result = $this->logic->edit($data['id'], $data);\n        if ($result) {\n            DictCache::clear();\n            return $this->success('修改成功');\n        } else {\n            return $this->fail('修改失败');\n        }\n    }\n\n    /**\n     * 删除数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('数据字典管理', 'core:dict:edit')]\n    public function destroy(Request $request) : Response\n    {\n        $ids = $request->post('ids', '');\n        if (empty($ids)) {\n            return $this->fail('请选择要删除的数据');\n        }\n        $result = $this->logic->destroy($ids);\n        if ($result) {\n            DictCache::clear();\n            return $this->success('删除成功');\n        } else {\n            return $this->fail('删除失败');\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/system/SystemLogController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller\\system;\n\nuse plugin\\saiadmin\\basic\\BaseController;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemLoginLogLogic;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemOperLogLogic;\nuse plugin\\saiadmin\\service\\Permission;\nuse support\\Request;\nuse support\\Response;\n\n/**\n * 日志控制器\n */\nclass SystemLogController extends BaseController\n{\n\n    /**\n     * 登录日志列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('登录日志列表', 'core:logs:login')]\n    public function getLoginLogPageList(Request $request) : Response\n    {\n        $where = $request->more([\n            ['login_time', ''],\n            ['username', ''],\n            ['status', ''],\n            ['ip', ''],\n        ]);\n        $logic = new SystemLoginLogLogic();\n        $query = $logic->search($where);\n        $data = $logic->getList($query);\n        return $this->success($data);\n    }\n\n    /**\n     * 删除登录日志\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('登录日志删除', 'core:logs:deleteLogin')]\n    public function deleteLoginLog(Request $request) : Response\n    {\n        $ids = $request->input('ids', '');\n        $logic = new SystemLoginLogLogic();\n        if (!empty($ids)) {\n            $logic->destroy($ids);\n            return $this->success('删除成功');\n        } else {\n            return $this->fail('参数错误，请检查');\n        }\n    }\n\n    /**\n     * 操作日志列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('操作日志列表', 'core:logs:Oper')]\n    public function getOperLogPageList(Request $request) : Response\n    {\n        $where = $request->more([\n            ['create_time', ''],\n            ['username', ''],\n            ['service_name', ''],\n            ['router', ''],\n            ['ip', ''],\n        ]);\n        $logic = new SystemOperLogLogic();\n        $logic->init($this->adminInfo);\n        $query = $logic->search($where);\n        $data = $logic->getList($query);\n        return $this->success($data);\n    }\n\n    /**\n     * 删除操作日志\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('操作日志删除', 'core:logs:deleteOper')]\n    public function deleteOperLog(Request $request) : Response\n    {\n        $ids = $request->input('ids', '');\n        $logic = new SystemOperLogLogic();\n        if (!empty($ids)) {\n            $logic->destroy($ids);\n            return $this->success('删除成功');\n        } else {\n            return $this->fail('参数错误，请检查');\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/system/SystemMailController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller\\system;\n\nuse plugin\\saiadmin\\service\\Permission;\nuse plugin\\saiadmin\\basic\\BaseController;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemMailLogic;\nuse plugin\\saiadmin\\app\\validate\\system\\SystemMailValidate;\nuse support\\Request;\nuse support\\Response;\n\n/**\n * 邮件记录控制器\n */\nclass SystemMailController extends BaseController\n{\n    /**\n     * 构造\n     */\n    public function __construct()\n    {\n        $this->logic = new SystemMailLogic();\n        $this->validate = new SystemMailValidate;\n        parent::__construct();\n    }\n\n    /**\n     * 数据列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('邮件日志列表', 'core:email:index')]\n    public function index(Request $request): Response\n    {\n        $where = $request->more([\n            ['gateway', ''],\n            ['from', ''],\n            ['code', ''],\n            ['email', ''],\n            ['status', ''],\n            ['create_time', ''],\n        ]);\n        $query = $this->logic->search($where);\n        $data = $this->logic->getList($query);\n        return $this->success($data);\n    }\n\n    /**\n     * 删除数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('邮件日志删除', 'core:email:destroy')]\n    public function destroy(Request $request) : Response\n    {\n        $ids = $request->post('ids', '');\n        if (empty($ids)) {\n            return $this->fail('请选择要删除的数据');\n        }\n        $result = $this->logic->destroy($ids);\n        if ($result) {\n            return $this->success('删除成功');\n        } else {\n            return $this->fail('删除失败');\n        }\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/system/SystemMenuController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller\\system;\n\nuse plugin\\saiadmin\\app\\cache\\UserMenuCache;\nuse plugin\\saiadmin\\basic\\BaseController;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemMenuLogic;\nuse plugin\\saiadmin\\app\\validate\\system\\SystemMenuValidate;\nuse plugin\\saiadmin\\service\\Permission;\nuse support\\Request;\nuse support\\Response;\n\n/**\n * 菜单控制器\n */\nclass SystemMenuController extends BaseController\n{\n    /**\n     * 构造\n     */\n    public function __construct()\n    {\n        $this->logic = new SystemMenuLogic();\n        $this->validate = new SystemMenuValidate;\n        parent::__construct();\n    }\n\n    /**\n     * 数据列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('菜单数据列表', 'core:menu:index')]\n    public function index(Request $request): Response\n    {\n        $where = $request->more([\n            ['name', ''],\n            ['path', ''],\n            ['menu', ''],\n            ['status', ''],\n        ]);\n        $data = $this->logic->tree($where);\n        return $this->success($data);\n    }\n\n    /**\n     * 读取数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('菜单数据读取', 'core:menu:read')]\n    public function read(Request $request): Response\n    {\n        $id = $request->input('id', '');\n        $model = $this->logic->read($id);\n        if ($model) {\n            $data = is_array($model) ? $model : $model->toArray();\n            return $this->success($data);\n        } else {\n            return $this->fail('未查找到信息');\n        }\n    }\n\n    /**\n     * 保存数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('菜单数据添加', 'core:menu:save')]\n    public function save(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('save', $data);\n        $result = $this->logic->add($data);\n        if ($result) {\n            UserMenuCache::clearMenuCache();\n            return $this->success('添加成功');\n        } else {\n            return $this->fail('添加失败');\n        }\n    }\n\n    /**\n     * 更新数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('菜单数据修改', 'core:menu:update')]\n    public function update(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('update', $data);\n        $result = $this->logic->edit($data['id'], $data);\n        if ($result) {\n            UserMenuCache::clearMenuCache();\n            return $this->success('修改成功');\n        } else {\n            return $this->fail('修改失败');\n        }\n    }\n\n    /**\n     * 删除数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('菜单数据删除', 'core:menu:destroy')]\n    public function destroy(Request $request): Response\n    {\n        $ids = $request->post('ids', '');\n        if (empty($ids)) {\n            return $this->fail('请选择要删除的数据');\n        }\n        $result = $this->logic->destroy($ids);\n        if ($result) {\n            UserMenuCache::clearMenuCache();\n            return $this->success('删除成功');\n        } else {\n            return $this->fail('删除失败');\n        }\n    }\n\n    /**\n     * 可操作菜单\n     * @param Request $request\n     * @return Response\n     */\n    public function accessMenu(Request $request): Response\n    {\n        $where = [];\n        if ($this->adminId > 1) {\n            $data = $this->logic->auth();\n        } else {\n            $data = $this->logic->tree($where);\n        }\n        return $this->success($data);\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/system/SystemPostController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller\\system;\n\nuse plugin\\saiadmin\\basic\\BaseController;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemPostLogic;\nuse plugin\\saiadmin\\app\\validate\\system\\SystemPostValidate;\nuse plugin\\saiadmin\\service\\Permission;\nuse support\\Request;\nuse support\\Response;\n\n/**\n * 岗位信息控制器\n */\nclass SystemPostController extends BaseController\n{\n    /**\n     * 构造\n     */\n    public function __construct()\n    {\n        $this->logic = new SystemPostLogic();\n        $this->validate = new SystemPostValidate;\n        parent::__construct();\n    }\n\n    /**\n     * 数据列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('岗位数据列表', 'core:post:index')]\n    public function index(Request $request): Response\n    {\n        $where = $request->more([\n            ['name', ''],\n            ['code', ''],\n            ['status', ''],\n        ]);\n        $query = $this->logic->search($where);\n        $data = $this->logic->getList($query);\n        return $this->success($data);\n    }\n\n    /**\n     * 读取数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('岗位数据读取', 'core:post:read')]\n    public function read(Request $request): Response\n    {\n        $id = $request->input('id', '');\n        $model = $this->logic->read($id);\n        if ($model) {\n            $data = is_array($model) ? $model : $model->toArray();\n            return $this->success($data);\n        } else {\n            return $this->fail('未查找到信息');\n        }\n    }\n\n    /**\n     * 保存数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('岗位数据添加', 'core:post:save')]\n    public function save(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('save', $data);\n        $result = $this->logic->add($data);\n        if ($result) {\n            return $this->success('添加成功');\n        } else {\n            return $this->fail('添加失败');\n        }\n    }\n\n    /**\n     * 更新数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('岗位数据修改', 'core:post:update')]\n    public function update(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('update', $data);\n        $result = $this->logic->edit($data['id'], $data);\n        if ($result) {\n            return $this->success('修改成功');\n        } else {\n            return $this->fail('修改失败');\n        }\n    }\n\n    /**\n     * 删除数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('岗位数据删除', 'core:post:destroy')]\n    public function destroy(Request $request): Response\n    {\n        $ids = $request->post('ids', '');\n        if (empty($ids)) {\n            return $this->fail('请选择要删除的数据');\n        }\n        $result = $this->logic->destroy($ids);\n        if ($result) {\n            return $this->success('删除成功');\n        } else {\n            return $this->fail('删除失败');\n        }\n    }\n\n    /**\n     * 导入数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('岗位数据导入', 'core:post:import')]\n    public function import(Request $request): Response\n    {\n        $file = current($request->file());\n        if (!$file || !$file->isValid()) {\n            return $this->fail('未找到上传文件');\n        }\n        $this->logic->import($file);\n        return $this->success('导入成功');\n    }\n\n    /**\n     * 导出数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('岗位数据导出', 'core:post:export')]\n    public function export(Request $request): Response\n    {\n        $where = $request->more([\n            ['name', ''],\n            ['code', ''],\n            ['status', ''],\n        ]);\n        return $this->logic->export($where);\n    }\n\n    /**\n     * 下载导入模板\n     * @return Response\n     */\n    public function downloadTemplate(): Response\n    {\n        $file_name = \"template.xlsx\";\n        return downloadFile($file_name);\n    }\n\n    /**\n     * 可操作岗位\n     * @param Request $request\n     * @return Response\n     */\n    public function accessPost(Request $request): Response\n    {\n        $where = ['status' => 1];\n        $data = $this->logic->accessPost($where);\n        return $this->success($data);\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/system/SystemRoleController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller\\system;\n\nuse plugin\\saiadmin\\app\\model\\system\\SystemUserRole;\nuse plugin\\saiadmin\\basic\\BaseController;\nuse plugin\\saiadmin\\app\\cache\\UserInfoCache;\nuse plugin\\saiadmin\\app\\model\\system\\SystemUser;\nuse plugin\\saiadmin\\app\\validate\\system\\SystemRoleValidate;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemRoleLogic;\nuse plugin\\saiadmin\\service\\Permission;\nuse support\\Request;\nuse support\\Response;\n\n/**\n * 角色控制器\n */\nclass SystemRoleController extends BaseController\n{\n    /**\n     * 构造\n     */\n    public function __construct()\n    {\n        $this->logic = new SystemRoleLogic();\n        $this->validate = new SystemRoleValidate;\n        parent::__construct();\n    }\n\n    /**\n     * 数据列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('角色数据列表', 'core:role:index')]\n    public function index(Request $request): Response\n    {\n        $where = $request->more([\n            ['name', ''],\n            ['code', ''],\n            ['status', ''],\n        ]);\n        $query = $this->logic->search($where);\n        $levelArr = array_column($this->adminInfo['roleList'], 'level');\n        $maxLevel = max($levelArr);\n        $query->where('level', '<', $maxLevel);\n        $data = $this->logic->getList($query);\n        return $this->success($data);\n    }\n\n    /**\n     * 读取数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('角色数据读取', 'core:role:read')]\n    public function read(Request $request): Response\n    {\n        $id = $request->input('id', '');\n        $model = $this->logic->read($id);\n        if ($model) {\n            $data = is_array($model) ? $model : $model->toArray();\n            return $this->success($data);\n        } else {\n            return $this->fail('未查找到信息');\n        }\n    }\n\n    /**\n     * 保存数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('角色数据添加', 'core:role:save')]\n    public function save(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('save', $data);\n        $result = $this->logic->add($data);\n        if ($result) {\n            return $this->success('添加成功');\n        } else {\n            return $this->fail('添加失败');\n        }\n    }\n\n    /**\n     * 更新数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('角色数据修改', 'core:role:update')]\n    public function update(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('update', $data);\n        $result = $this->logic->edit($data['id'], $data);\n        if ($result) {\n            return $this->success('修改成功');\n        } else {\n            return $this->fail('修改失败');\n        }\n    }\n\n    /**\n     * 删除数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('角色数据删除', 'core:role:destroy')]\n    public function destroy(Request $request): Response\n    {\n        $ids = $request->post('ids', '');\n        if (empty($ids)) {\n            return $this->fail('请选择要删除的数据');\n        }\n        $result = $this->logic->destroy($ids);\n        if ($result) {\n            return $this->success('删除成功');\n        } else {\n            return $this->fail('删除失败');\n        }\n    }\n\n    /**\n     * 根据角色获取菜单\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('角色数据列表', 'core:role:index')]\n    public function getMenuByRole(Request $request): Response\n    {\n        $id = $request->get('id');\n        $data = $this->logic->getMenuByRole($id);\n        return $this->success($data);\n    }\n\n    /**\n     * 菜单权限\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('角色菜单权限', 'core:role:menu')]\n    public function menuPermission(Request $request): Response\n    {\n        $id = $request->post('id');\n        $menu_ids = $request->post('menu_ids');\n        $this->logic->saveMenuPermission($id, $menu_ids);\n        return $this->success('操作成功');\n    }\n\n    /**\n     * 可操作角色\n     * @param Request $request\n     * @return Response\n     */\n    public function accessRole(Request $request): Response\n    {\n        $where = ['status' => 1];\n        $data = $this->logic->accessRole($where);\n        return $this->success($data);\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/system/SystemServerController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller\\system;\n\nuse plugin\\saiadmin\\service\\Permission;\nuse plugin\\saiadmin\\basic\\BaseController;\nuse plugin\\saiadmin\\utils\\ServerMonitor;\nuse support\\think\\Cache;\nuse support\\Request;\nuse support\\Response;\n\n/**\n * 邮件记录控制器\n */\nclass SystemServerController extends BaseController\n{\n    /**\n     * 构造\n     */\n    public function __construct()\n    {\n        parent::__construct();\n    }\n\n    /**\n     * 数据列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('服务监控', 'core:server:monitor')]\n    public function monitor(Request $request): Response\n    {\n        $service = new ServerMonitor();\n        return $this->success([\n            'memory' => $service->getMemoryInfo(),\n            'disk' => $service->getDiskInfo(),\n            'phpEnv' => $service->getPhpAndEnvInfo(),\n        ]);\n    }\n\n    /**\n     * 数据列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('缓存信息', 'core:server:cache')]\n    public function cache(Request $request): Response\n    {\n        $menu_cache = config('plugin.saiadmin.saithink.menu_cache', []);\n        $button_cache = config('plugin.saiadmin.saithink.button_cache', []);\n        $config_cache = config('plugin.saiadmin.saithink.config_cache', []);\n        $dict_cache = config('plugin.saiadmin.saithink.dict_cache', []);\n        $reflection_cache = config('plugin.saiadmin.saithink.reflection_cache', []);\n\n        return $this->success([\n            'menu_cache' => $menu_cache,\n            'button_cache' => $button_cache,\n            'config_cache' => $config_cache,\n            'dict_cache' => $dict_cache,\n            'reflection_cache' => $reflection_cache\n        ]);\n    }\n\n    /**\n     * 删除数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('缓存数据清理', 'core:server:clear')]\n    public function clear(Request $request) : Response\n    {\n        $tag = $request->input('tag', '');\n        if (empty($tag)) {\n            return $this->fail('请选择要删除的缓存');\n        }\n        Cache::tag($tag)->clear();\n        Cache::delete($tag);\n        return $this->success('删除成功');\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/system/SystemUserController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller\\system;\n\nuse plugin\\saiadmin\\app\\cache\\UserAuthCache;\nuse plugin\\saiadmin\\app\\cache\\UserMenuCache;\nuse plugin\\saiadmin\\basic\\BaseController;\nuse plugin\\saiadmin\\app\\cache\\UserInfoCache;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemUserLogic;\nuse plugin\\saiadmin\\app\\validate\\system\\SystemUserValidate;\nuse plugin\\saiadmin\\service\\Permission;\nuse support\\Request;\nuse support\\Response;\n\n/**\n * 用户信息控制器\n */\nclass SystemUserController extends BaseController\n{\n    /**\n     * 构造\n     */\n    public function __construct()\n    {\n        $this->logic = new SystemUserLogic();\n        $this->validate = new SystemUserValidate;\n        parent::__construct();\n    }\n\n    /**\n     * 数据列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('用户数据列表', 'core:user:index')]\n    public function index(Request $request): Response\n    {\n        $where = $request->more([\n            ['username', ''],\n            ['phone', ''],\n            ['email', ''],\n            ['status', ''],\n            ['dept_id', ''],\n            ['create_time', ''],\n        ]);\n        $data = $this->logic->indexList($where);\n        return $this->success($data);\n    }\n\n    /**\n     * 读取数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('用户数据读取', 'core:user:read')]\n    public function read(Request $request): Response\n    {\n        $id = $request->input('id', '');\n        $model = $this->logic->read($id);\n        if ($model) {\n            $data = is_array($model) ? $model : $model->toArray();\n            return $this->success($data);\n        } else {\n            return $this->fail('未查找到信息');\n        }\n    }\n\n    /**\n     * 保存数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('用户数据保存', 'core:user:save')]\n    public function save(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('save', $data);\n        $result = $this->logic->add($data);\n        if ($result) {\n            return $this->success('添加成功');\n        } else {\n            return $this->fail('添加失败');\n        }\n    }\n\n    /**\n     * 更新数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('用户数据更新', 'core:user:update')]\n    public function update(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('update', $data);\n        $result = $this->logic->edit($data['id'], $data);\n        if ($result) {\n            return $this->success('修改成功');\n        } else {\n            return $this->fail('修改失败');\n        }\n    }\n\n    /**\n     * 删除数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('用户数据删除', 'core:user:destroy')]\n    public function destroy(Request $request): Response\n    {\n        $ids = $request->input('ids', '');\n        if (!empty($ids)) {\n            $this->logic->destroy($ids);\n            return $this->success('操作成功');\n        } else {\n            return $this->fail('参数错误，请检查');\n        }\n    }\n\n    /**\n     * 清理用户缓存\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('清理用户缓存', 'core:user:cache')]\n    public function clearCache(Request $request): Response\n    {\n        $id = $request->post('id', '');\n        UserInfoCache::clearUserInfo($id);\n        UserAuthCache::clearUserAuth($id);\n        UserMenuCache::clearUserMenu($id);\n        return $this->success('操作成功');\n    }\n\n    /**\n     * 修改用户密码\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('修改用户密码', 'core:user:password')]\n    public function initUserPassword(Request $request): Response\n    {\n        $id = $request->post('id', '');\n        $password = $request->post('password', '');\n        if ($id == 1) {\n            return $this->fail('超级管理员不允许重置密码');\n        }\n        $data = ['password' => password_hash($password, PASSWORD_DEFAULT)];\n        $this->logic->authEdit($id, $data);\n        UserInfoCache::clearUserInfo($id);\n        return $this->success('操作成功');\n    }\n\n    /**\n     * 设置用户首页\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('设置用户首页', 'core:user:home')]\n    public function setHomePage(Request $request): Response\n    {\n        $id = $request->post('id', '');\n        $dashboard = $request->post('dashboard', '');\n        $data = ['dashboard' => $dashboard];\n        $this->logic->authEdit($id, $data);\n        UserInfoCache::clearUserInfo($id);\n        return $this->success('操作成功');\n    }\n\n    /**\n     * 更新资料\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('用户修改资料')]\n    public function updateInfo(Request $request): Response\n    {\n        $data = $request->post();\n        unset($data['deptList']);\n        unset($data['postList']);\n        unset($data['roleList']);\n        $result = $this->logic->updateInfo($this->adminId, $data);\n        if ($result) {\n            UserInfoCache::clearUserInfo($this->adminId);\n            return $this->success('操作成功');\n        } else {\n            return $this->fail('操作失败');\n        }\n    }\n\n    /**\n     * 修改密码\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('用户修改密码')]\n    public function modifyPassword(Request $request): Response\n    {\n        $oldPassword = $request->input('oldPassword');\n        $newPassword = $request->input('newPassword');\n        $this->logic->modifyPassword($this->adminId, $oldPassword, $newPassword);\n        UserInfoCache::clearUserInfo($this->adminId);\n        return $this->success('修改成功');\n    }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/tool/CrontabController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller\\tool;\n\nuse plugin\\saiadmin\\app\\logic\\tool\\CrontabLogic;\nuse plugin\\saiadmin\\app\\logic\\tool\\CrontabLogLogic;\nuse plugin\\saiadmin\\app\\validate\\tool\\CrontabValidate;\nuse plugin\\saiadmin\\basic\\BaseController;\nuse plugin\\saiadmin\\service\\Permission;\nuse Webman\\Channel\\Client;\nuse support\\Request;\nuse support\\Response;\n\n/**\n * 定时任务控制器\n */\nclass CrontabController extends BaseController\n{\n    /**\n     * 构造\n     */\n    public function __construct()\n    {\n        $this->logic = new CrontabLogic();\n        $this->validate = new CrontabValidate;\n        parent::__construct();\n    }\n\n    /**\n     * 数据列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('定时任务列表', 'tool:crontab:index')]\n    public function index(Request $request) : Response\n    {\n        $where = $request->more([\n            ['name', ''],\n            ['type', ''],\n            ['status', ''],\n            ['create_time', ''],\n        ]);\n        $query = $this->logic->search($where);\n        $data = $this->logic->getList($query);\n        return $this->success($data);\n    }\n\n    /**\n     * 保存数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('定时任务添加', 'tool:crontab:edit')]\n    public function save(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('save', $data);\n        $result = $this->logic->add($data);\n        if ($result) {\n            return $this->success('添加成功');\n        } else {\n            return $this->fail('添加失败');\n        }\n    }\n\n    /**\n     * 更新数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('定时任务修改', 'tool:crontab:edit')]\n    public function update(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('update', $data);\n        $result = $this->logic->edit($data['id'], $data);\n        if ($result) {\n            return $this->success('修改成功');\n        } else {\n            return $this->fail('修改失败');\n        }\n    }\n\n    /**\n     * 删除数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('定时任务删除', 'tool:crontab:edit')]\n    public function destroy(Request $request) : Response\n    {\n        $ids = $request->post('ids', '');\n        if (empty($ids)) {\n            return $this->fail('请选择要删除的数据');\n        }\n        $result = $this->logic->destroy($ids);\n        if ($result) {\n            return $this->success('删除成功');\n        } else {\n            return $this->fail('删除失败');\n        }\n    }\n\n    /**\n     * 修改状态\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('定时任务状态修改', 'tool:crontab:edit')]\n    public function changeStatus(Request $request) : Response\n    {\n        $id = $request->input('id', '');\n        $status = $request->input('status', 1);\n        if (empty($id)) {\n            return $this->fail('参数错误，请检查');\n        }\n        $result = $this->logic->changeStatus($id, $status);\n        if ($result) {\n            return $this->success('操作成功');\n        } else {\n            return $this->fail('操作失败');\n        }\n    }\n\n    /**\n     * 执行定时任务\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('定时任务执行', 'tool:crontab:run')]\n    public function run(Request $request) : Response\n    {\n        $id = $request->input('id', '');\n        $result = $this->logic->run($id);\n        if ($result) {\n            return $this->success('执行成功');\n        } else {\n            return $this->fail('执行失败');\n        }\n    }\n\n    /**\n     * 定时任务日志\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('定时任务日志', 'tool:crontab:index')]\n    public function logPageList(Request $request) : Response\n    {\n        $where = $request->more([\n            ['crontab_id', ''],\n            ['create_time', []]\n        ]);\n        $logic = new CrontabLogLogic();\n        $query = $logic->search($where);\n        $data = $logic->getList($query);\n        return $this->success($data);\n    }\n\n    /**\n     * 定时任务日志删除\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('定时任务日志删除', 'tool:crontab:edit')]\n    public function deleteCrontabLog(Request $request) : Response\n    {\n        $ids = $request->input('ids', '');\n        if (!empty($ids)) {\n            $logic = new CrontabLogLogic();\n            $logic->destroy($ids);\n            return $this->success('操作成功');\n        } else {\n            return $this->fail('参数错误，请检查');\n        }\n    }\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/controller/tool/GenerateTablesController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\controller\\tool;\n\nuse plugin\\saiadmin\\basic\\BaseController;\nuse plugin\\saiadmin\\app\\logic\\tool\\GenerateTablesLogic;\nuse plugin\\saiadmin\\app\\validate\\tool\\GenerateTablesValidate;\nuse plugin\\saiadmin\\app\\cache\\UserMenuCache;\nuse plugin\\saiadmin\\service\\Permission;\nuse support\\Request;\nuse support\\Response;\n\n/**\n * 代码生成控制器\n */\nclass GenerateTablesController extends BaseController\n{\n    /**\n     * 构造\n     */\n    public function __construct()\n    {\n        $this->logic = new GenerateTablesLogic();\n        $this->validate = new GenerateTablesValidate;\n        parent::__construct();\n    }\n\n    /**\n     * 数据列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('代码生成列表', 'tool:code:index')]\n    public function index(Request $request): Response\n    {\n        $where = $request->more([\n            ['table_name', ''],\n        ]);\n        $query = $this->logic->search($where);\n        $data = $this->logic->getList($query);\n        return $this->success($data);\n    }\n\n    /**\n     * 读取数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('代码生成列表', 'tool:code:index')]\n    public function read(Request $request): Response\n    {\n        $id = $request->input('id', '');\n        $model = $this->logic->read($id);\n        if ($model) {\n            $data = is_array($model) ? $model : $model->toArray();\n            return $this->success($data);\n        } else {\n            return $this->fail('未查找到信息');\n        }\n    }\n\n    /**\n     * 修改数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('代码生成修改', 'tool:code:edit')]\n    public function update(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('update', $data);\n        $result = $this->logic->edit($data['id'], $data);\n        if ($result) {\n            return $this->success('修改成功');\n        } else {\n            return $this->fail('修改失败');\n        }\n    }\n\n    /**\n     * 删除数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('代码生成删除', 'tool:code:edit')]\n    public function destroy(Request $request): Response\n    {\n        $ids = $request->post('ids', '');\n        if (empty($ids)) {\n            return $this->fail('请选择要删除的数据');\n        }\n        $result = $this->logic->destroy($ids);\n        if ($result) {\n            return $this->success('删除成功');\n        } else {\n            return $this->fail('删除失败');\n        }\n    }\n\n    /**\n     * 装载数据表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('代码生成装载', 'tool:code:edit')]\n    public function loadTable(Request $request): Response\n    {\n        $names = $request->input('names', []);\n        $source = $request->input('source', '');\n        $this->logic->loadTable($names, $source);\n        return $this->success('操作成功');\n    }\n\n    /**\n     * 同步数据表字段信息\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('代码生成同步表结构', 'tool:code:edit')]\n    public function sync(Request $request): Response\n    {\n        $id = $request->input('id', '');\n        $this->logic->sync($id);\n        return $this->success('操作成功');\n    }\n\n    /**\n     * 代码预览\n     */\n    #[Permission('代码生成预览', 'tool:code:edit')]\n    public function preview(Request $request): Response\n    {\n        $id = $request->input('id', '');\n        $data = $this->logic->preview($id);\n        return $this->success($data);\n    }\n\n    /**\n     * 代码生成\n     */\n    #[Permission('代码生成文件', 'tool:code:edit')]\n    public function generate(Request $request): Response\n    {\n        $ids = $request->input('ids', '');\n        $data = $this->logic->generate($ids);\n        return response()->download($data['download'], $data['filename']);\n    }\n\n    /**\n     * 生成到模块\n     */\n    #[Permission('代码生成到模块', 'tool:code:edit')]\n    public function generateFile(Request $request): Response\n    {\n        $id = $request->input('id', '');\n        $this->logic->generateFile($id);\n        UserMenuCache::clearMenuCache();\n        return $this->success('操作成功');\n    }\n\n    /**\n     * 获取数据表字段信息\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('代码生成读取表字段', 'tool:code:index')]\n    public function getTableColumns(Request $request): Response\n    {\n        $table_id = $request->input('table_id', '');\n        $data = $this->logic->getTableColumns($table_id);\n        return $this->success($data);\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/event/SystemUser.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\event;\n\nuse plugin\\saiadmin\\app\\cache\\ReflectionCache;\nuse plugin\\saiadmin\\app\\model\\system\\SystemLoginLog;\nuse plugin\\saiadmin\\app\\model\\system\\SystemOperLog;\n\nclass SystemUser\n{\n    /**\n     * 登录日志\n     * @param $item\n     */\n    public function login($item)\n    {\n        $request = request();\n        $ip = $request ? $request->getRealIp() : '127.0.0.1';\n        $http_user_agent = $request ? $request->header('user-agent') : '';\n        $data['username'] = $item['username'];\n        $data['ip'] = $ip;\n        $data['ip_location'] = self::getIpLocation($ip);\n        $data['os'] = self::getOs($http_user_agent);\n        $data['browser'] = self::getBrowser($http_user_agent);\n        $data['status'] = $item['status'];\n        $data['message'] = $item['message'];\n        $data['login_time'] = date('Y-m-d H:i:s');\n        if (isset($item['admin_id'])) {\n            $data['created_by'] = $item['admin_id'];\n            $data['updated_by'] = $item['admin_id'];\n        }\n        SystemLoginLog::create($data);\n    }\n\n    /**\n     * 记录操作日志\n     */\n    public function operateLog(): bool\n    {\n        $request = request();\n        if (!$request) {\n            return false;\n        }\n        if ($request->method() === 'GET') {\n            return false;\n        }\n        $info = getCurrentInfo();\n        $ip = $request->getRealIp();\n        $module = $request->plugin;\n        $rule = trim($request->uri());\n        $data['username'] = $info['username'];\n        $data['method'] = $request->method();\n        $data['router'] = $rule;\n        $data['service_name'] = self::getServiceName();\n        $data['app'] = $module;\n        $data['ip'] = $ip;\n        $data['ip_location'] = self::getIpLocation($ip);\n        $data['request_data'] = $this->filterParams($request->all());\n        SystemOperLog::create($data);\n        return true;\n    }\n\n    /**\n     * 获取业务名称\n     */\n    protected function getServiceName(): string\n    {\n        $request = request();\n        if (!$request) {\n            return '未命名业务';\n        }\n        $permissions = ReflectionCache::getPermissionAttributes($request->controller, $request->action);\n        if (!empty($permissions)) {\n            return $permissions['title'] ?? '未命名业务';\n        } else {\n            return '未命名业务';\n        }\n    }\n\n    /**\n     * 过滤字段\n     */\n    protected function filterParams($params): string\n    {\n        $blackList = ['password', 'oldPassword', 'newPassword'];\n        foreach ($params as $key => $value) {\n            if (in_array($key, $blackList)) {\n                $params[$key] = '******';\n            }\n        }\n        return json_encode($params, JSON_UNESCAPED_UNICODE);\n    }\n\n    /**\n     * 获取IP地理位置\n     */\n    protected function getIpLocation($ip): string\n    {\n        $ip2region = new \\Ip2Region();\n        try {\n            $region = $ip2region->memorySearch($ip);\n        } catch (\\Exception $e) {\n            return '未知';\n        }\n        list($country, $province, $city, $network) = explode('|', $region['region']);\n        if ($network === '内网IP') {\n            return $network;\n        }\n        if ($country == '中国') {\n            return $province . '-' . $city . ':' . $network;\n        } else if ($country == '0') {\n            return '未知';\n        } else {\n            return $country;\n        }\n    }\n\n    /**\n     * 获取浏览器信息\n     */\n    protected function getBrowser($user_agent): string\n    {\n        $br = 'Unknown';\n        if (preg_match('/MSIE/i', $user_agent)) {\n            $br = 'MSIE';\n        } elseif (preg_match('/Firefox/i', $user_agent)) {\n            $br = 'Firefox';\n        } elseif (preg_match('/Chrome/i', $user_agent)) {\n            $br = 'Chrome';\n        } elseif (preg_match('/Safari/i', $user_agent)) {\n            $br = 'Safari';\n        } elseif (preg_match('/Opera/i', $user_agent)) {\n            $br = 'Opera';\n        } else {\n            $br = 'Other';\n        }\n        return $br;\n    }\n\n    /**\n     * 获取操作系统信息\n     */\n    protected function getOs($user_agent): string\n    {\n        $os = 'Unknown';\n        if (preg_match('/win/i', $user_agent)) {\n            $os = 'Win';\n        } elseif (preg_match('/mac/i', $user_agent)) {\n            $os = 'Mac';\n        } elseif (preg_match('/linux/i', $user_agent)) {\n            $os = 'Linux';\n        } else {\n            $os = 'Other';\n        }\n        return $os;\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/exception/Handler.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\exception;\n\nuse Throwable;\nuse Webman\\Http\\Request;\nuse Webman\\Http\\Response;\nuse Webman\\Exception\\ExceptionHandler;\nuse plugin\\saiadmin\\exception\\ApiException;\n\n/**\n * 异常处理类\n */\nclass Handler extends ExceptionHandler\n{\n    public $dontReport = [\n        ApiException::class,\n    ];\n\n    public function report(Throwable $exception)\n    {\n        if ($this->shouldntReport($exception)) {\n            return;\n        }\n        $logs = '';\n        if ($request = \\request()) {\n            $user = getCurrentInfo();\n            $logs .= $request->method() . ' ' . $request->uri();\n            $logs .= PHP_EOL . '[request_param]: ' . json_encode($request->all());\n            $logs .= PHP_EOL . '[timestamp]: ' . date('Y-m-d H:i:s');\n            $logs .= PHP_EOL . '[client_ip]: ' . $request->getRealIp();\n            $logs .= PHP_EOL . '[action_user]: ' . var_export($user, true);\n            $logs .= PHP_EOL . '[exception_handle]: ' . get_class($exception);\n            $logs .= PHP_EOL . '[exception_info]: ' . PHP_EOL . $exception;\n        }\n        $this->logger->error($logs);\n    }\n\n    public function render(Request $request, Throwable $exception): Response\n    {\n        $debug = config('app.debug', true);\n        $code = $exception->getCode();\n        $json = [\n            'code' => $code ? $code : 500,\n            'message' => $code !== 500 ? $exception->getMessage() : 'Server internal error',\n            'type' => 'failed'\n        ];\n        if ($debug) {\n            $json['request_url'] = $request->method() . ' ' . $request->uri();\n            $json['timestamp'] = date('Y-m-d H:i:s');\n            $json['client_ip'] = $request->getRealIp();\n            $json['request_param'] = $request->all();\n            $json['exception_handle'] = get_class($exception);\n            $json['exception_info'] = [\n                'code' => $exception->getCode(),\n                'message' => $exception->getMessage(),\n                'file' => $exception->getFile(),\n                'line' => $exception->getLine(),\n                'trace' => explode(\"\\n\", $exception->getTraceAsString())\n            ];\n        }\n        return new Response(200, ['Content-Type' => 'application/json;charset=utf-8'], json_encode($json));\n    }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/functions.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nuse Webman\\Route;\nuse support\\Response;\nuse Tinywan\\Jwt\\JwtToken;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\app\\cache\\ConfigCache;\nuse plugin\\saiadmin\\app\\cache\\DictCache;\n\nif (!function_exists('getCurrentInfo')) {\n    /**\n     * 获取当前登录用户\n     */\n    function getCurrentInfo(): bool|array\n    {\n        if (!request()) {\n            return false;\n        }\n        try {\n            $token = JwtToken::getExtend();\n        } catch (\\Throwable $e) {\n            return false;\n        }\n        return $token;\n    }\n}\n\nif (!function_exists('fastRoute')) {\n    /**\n     * 快速注册路由[index|save|update|read|destroy|import|export]\n     * @param string $name\n     * @param string $controller\n     * @return void\n     */\n    function fastRoute(string $name, string $controller): void\n    {\n        $name = trim($name, '/');\n        if (method_exists($controller, 'index'))\n            Route::get(\"/$name/index\", [$controller, 'index']);\n        if (method_exists($controller, 'save'))\n            Route::post(\"/$name/save\", [$controller, 'save']);\n        if (method_exists($controller, 'update'))\n            Route::put(\"/$name/update\", [$controller, 'update']);\n        if (method_exists($controller, 'read'))\n            Route::get(\"/$name/read\", [$controller, 'read']);\n        if (method_exists($controller, 'destroy'))\n            Route::delete(\"/$name/destroy\", [$controller, 'destroy']);\n        if (method_exists($controller, 'import'))\n            Route::post(\"/$name/import\", [$controller, 'import']);\n        if (method_exists($controller, 'export'))\n            Route::post(\"/$name/export\", [$controller, 'export']);\n    }\n}\n\nif (!function_exists('downloadFile')) {\n    /**\n     * 下载模板\n     * @param $file_name\n     * @return Response\n     */\n    function downloadFile($file_name): Response\n    {\n        $base_dir = config('plugin.saiadmin.saithink.template', base_path() . '/public/template');\n        if (file_exists($base_dir . DIRECTORY_SEPARATOR . $file_name)) {\n            return response()->download($base_dir . DIRECTORY_SEPARATOR . $file_name, urlencode($file_name));\n        } else {\n            throw new ApiException('模板不存在');\n        }\n    }\n}\n\nif (!function_exists('formatBytes')) {\n    /**\n     * 根据字节计算大小\n     * @param $bytes\n     * @return string\n     */\n    function formatBytes($bytes): string\n    {\n        $units = ['B', 'KB', 'MB', 'GB', 'TB'];\n        for ($i = 0; $bytes > 1024; $i++) {\n            $bytes /= 1024;\n        }\n        return round($bytes, 2) . ' ' . $units[$i];\n    }\n}\n\nif (!function_exists('getConfigGroup')) {\n    /**\n     * 读取配置组\n     * @param $group\n     * @return array\n     */\n    function getConfigGroup($group): array\n    {\n        return ConfigCache::getConfig($group);\n    }\n}\n\nif (!function_exists('dictDataList')) {\n    /**\n     * 根据字典编码获取字典列表\n     * @param string $code 字典编码\n     * @return array\n     */\n    function dictDataList(string $code): array\n    {\n        return DictCache::getDict($code);\n    }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/logic/system/DatabaseLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse support\\think\\Db;\n\n/**\n * 数据表维护逻辑层\n */\nclass DatabaseLogic extends BaseLogic\n{\n    /**\n     * 获取数据源\n     * @return array\n     */\n    public function getDbSource(): array\n    {\n        $data = config('think-orm.connections');\n        $list = [];\n        foreach ($data as $k => $v) {\n            $list[] = $k;\n        }\n        return $list;\n    }\n\n    /**\n     * 数据列表\n     * @param $query\n     * @return mixed\n     */\n    public function getList($query): mixed\n    {\n        $request = request();\n        $page = $request ? ($request->input('page') ?: 1) : 1;\n        $limit = $request ? ($request->input('limit') ?: 10) : 10;\n\n        return self::getTableList($query, $page, $limit);\n    }\n\n    /**\n     * 获取数据库表数据\n     */\n    public function getTableList($query, $current_page = 1, $per_page = 10): array\n    {\n        if (!empty($query['source'])) {\n            if (!empty($query['name'])) {\n                $sql = 'show table status where name=:name ';\n                $list = Db::connect($query['source'])->query($sql, ['name' => $query['name']]);\n            } else {\n                $list = Db::connect($query['source'])->query('show table status');\n            }\n        } else {\n            if (!empty($query['name'])) {\n                $sql = 'show table status where name=:name ';\n                $list = Db::query($sql, ['name' => $query['name']]);\n            } else {\n                $list = Db::query('show table status');\n            }\n        }\n\n        $data = [];\n        foreach ($list as $item) {\n            $data[] = [\n                'name' => $item['Name'],\n                'engine' => $item['Engine'],\n                'rows' => $item['Rows'],\n                'data_free' => $item['Data_free'],\n                'data_length' => $item['Data_length'],\n                'index_length' => $item['Index_length'],\n                'collation' => $item['Collation'],\n                'create_time' => $item['Create_time'],\n                'update_time' => $item['Update_time'],\n                'comment' => $item['Comment'],\n            ];\n        }\n        $total = count($data);\n        $last_page = ceil($total / $per_page);\n        $startIndex = ($current_page - 1) * $per_page;\n        $pageData = array_slice($data, $startIndex, $per_page);\n        return [\n            'data' => $pageData,\n            'total' => $total,\n            'current_page' => $current_page,\n            'per_page' => $per_page,\n            'last_page' => $last_page,\n        ];\n    }\n\n    /**\n     * 获取列信息\n     */\n    public function getColumnList($table, $source): array\n    {\n        $columnList = [];\n        if (preg_match(\"/^[a-zA-Z0-9_]+$/\", $table)) {\n            if (!empty($source)) {\n                $list = Db::connect($source)->query('SHOW FULL COLUMNS FROM `' . $table . '`');\n            } else {\n                $list = Db::query('SHOW FULL COLUMNS FROM `' . $table . '`');\n            }\n            foreach ($list as $column) {\n                preg_match('/^\\w+/', $column['Type'], $matches);\n                $columnList[] = [\n                    'column_key' => $column['Key'],\n                    'column_name' => $column['Field'],\n                    'column_type' => $matches[0],\n                    'column_comment' => trim(preg_replace(\"/\\([^()]*\\)/\", \"\", $column['Comment'])),\n                    'extra' => $column['Extra'],\n                    'default_value' => $column['Default'],\n                    'is_nullable' => $column['Null'],\n                ];\n            }\n        }\n        return $columnList;\n    }\n\n    /**\n     * 优化表\n     */\n    public function optimizeTable($tables)\n    {\n        foreach ($tables as $table) {\n            if (preg_match(\"/^[a-zA-Z0-9_]+$/\", $table)) {\n                Db::execute('OPTIMIZE TABLE `' . $table . '`');\n            }\n        }\n    }\n\n    /**\n     * 清理表碎片\n     */\n    public function fragmentTable($tables)\n    {\n        foreach ($tables as $table) {\n            if (preg_match(\"/^[a-zA-Z0-9_]+$/\", $table)) {\n                Db::execute('ANALYZE TABLE `' . $table . '`');\n            }\n        }\n    }\n\n    /**\n     * 获取回收站数据\n     */\n    public function recycleData($table)\n    {\n        if (preg_match(\"/^[a-zA-Z0-9_]+$/\", $table)) {\n            // 查询表字段\n            $sql = 'SHOW COLUMNS FROM `' . $table . '` where Field = \"delete_time\"';\n            $columns = Db::query($sql);\n            $isDeleteTime = false;\n            if (count($columns) > 0) {\n                $isDeleteTime = true;\n            }\n            if (!$isDeleteTime) {\n                throw new ApiException('当前表不支持回收站功能');\n            }\n            // 查询软删除数据\n            $request = request();\n            $limit = $request ? ($request->input('limit') ?: 10) : 10;\n            return Db::table($table)->whereNotNull('delete_time')\n                ->order('delete_time', 'desc')\n                ->paginate($limit)\n                ->toArray();\n        } else {\n            return [];\n        }\n    }\n\n    /**\n     * 删除数据\n     * @param $table\n     * @param $ids\n     * @return bool\n     */\n    public function delete($table, $ids)\n    {\n        if (preg_match(\"/^[a-zA-Z0-9_]+$/\", $table)) {\n            $count = Db::table($table)->whereIn('id', $ids)->delete($ids);\n            return $count > 0;\n        } else {\n            return false;\n        }\n    }\n\n    /**\n     * 恢复数据\n     * @param $table\n     * @param $ids\n     * @return bool\n     */\n    public function recovery($table, $ids)\n    {\n        if (preg_match(\"/^[a-zA-Z0-9_]+$/\", $table)) {\n            $count = Db::table($table)\n                ->where('id', 'in', $ids)\n                ->update(['delete_time' => null]);\n            return $count > 0;\n        } else {\n            return false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/logic/system/SystemAttachmentLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse Exception;\nuse plugin\\saiadmin\\app\\model\\system\\SystemAttachment;\nuse plugin\\saiadmin\\app\\model\\system\\SystemCategory;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\service\\storage\\ChunkUploadService;\nuse plugin\\saiadmin\\service\\storage\\UploadService;\nuse plugin\\saiadmin\\utils\\Arr;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 附件逻辑层\n */\nclass SystemAttachmentLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemAttachment();\n    }\n\n    /**\n     * @param $category_id\n     * @param $ids\n     * @return mixed\n     */\n    public function move($category_id, $ids): mixed\n    {\n        $category = SystemCategory::where('id', $category_id)->findOrEmpty();\n        if ($category->isEmpty()) {\n            throw new ApiException('目标分类不存在');\n        }\n        return $this->model->whereIn('id', $ids)->update(['category_id' => $category_id]);\n    }\n\n    /**\n     * 保存网络图片\n     * @param $url\n     * @param $config\n     * @return array\n     * @throws ApiException|Exception\n     */\n    public function saveNetworkImage($url, $config): array\n    {\n        $image_data = file_get_contents($url);\n        if ($image_data === false) {\n            throw new ApiException('获取文件资源失败');\n        }\n        $image_resource = imagecreatefromstring($image_data);\n        if (!$image_resource) {\n            throw new ApiException('创建图片资源失败');\n        }\n        $filename = basename($url);\n        $file_extension = pathinfo($filename, PATHINFO_EXTENSION);\n        $full_dir = runtime_path() . '/resource/';\n        if (!is_dir($full_dir)) {\n            mkdir($full_dir, 0777, true);\n        }\n        $save_path = $full_dir . $filename;\n        $mime_type = 'image/';\n        switch ($file_extension) {\n            case 'jpg':\n            case 'jpeg':\n                $mime_type = 'image/jpeg';\n                $result = imagejpeg($image_resource, $save_path);\n                break;\n            case 'png':\n                $mime_type = 'image/png';\n                $result = imagepng($image_resource, $save_path);\n                break;\n            case 'gif':\n                $mime_type = 'image/gif';\n                $result = imagegif($image_resource, $save_path);\n                break;\n            default:\n                imagedestroy($image_resource);\n                throw new ApiException('文件格式错误');\n        }\n        imagedestroy($image_resource);\n        if (!$result) {\n            throw new ApiException('文件保存失败');\n        }\n\n        $hash = md5_file($save_path);\n        $size = filesize($save_path);\n\n        $model = $this->model->where('hash', $hash)->find();\n        if ($model) {\n            unlink($save_path);\n            return $model->toArray();\n        } else {\n\n            $logic = new SystemConfigLogic();\n            $uploadConfig = $logic->getGroup('upload_config');\n\n            $root = Arr::getConfigValue($uploadConfig, 'local_root');\n\n            $folder = date('Ymd');\n            $full_dir = base_path() . DIRECTORY_SEPARATOR . $root . $folder . DIRECTORY_SEPARATOR;\n            if (!is_dir($full_dir)) {\n                mkdir($full_dir, 0777, true);\n            }\n            $object_name = bin2hex(pack('Nn', time(), random_int(1, 65535))) . \".$file_extension\";\n            $newPath = $full_dir . $object_name;\n\n            copy($save_path, $newPath);\n            unlink($save_path);\n            $domain = Arr::getConfigValue($uploadConfig, 'local_domain');\n            $uri = Arr::getConfigValue($uploadConfig, 'local_uri');\n            $baseUrl = $domain . $uri . $folder . '/';\n\n            $info['storage_mode'] = 1;\n            $info['category_id'] = request()->input('category_id', 1);\n            $info['origin_name'] = $filename;\n            $info['object_name'] = $object_name;\n            $info['hash'] = $hash;\n            $info['mime_type'] = $mime_type;\n            $info['storage_path'] = $root . $folder . '/' . $object_name;\n            $info['suffix'] = $file_extension;\n            $info['size_byte'] = $size;\n            $info['size_info'] = formatBytes($size);\n            $info['url'] = $baseUrl . $object_name;\n            $this->model->save($info);\n            return $info;\n        }\n    }\n\n    /**\n     * 文件上传\n     * @param string $upload\n     * @param bool $local\n     * @return array\n     */\n    public function uploadBase(string $upload = 'image', bool $local = false): array\n    {\n        $logic = new SystemConfigLogic();\n        $uploadConfig = $logic->getGroup('upload_config');\n        $type = Arr::getConfigValue($uploadConfig, 'upload_mode');\n        if ($local === true) {\n            $type = 1;\n        }\n        $result = UploadService::disk($type, $upload)->uploadFile();\n        $data = $result[0];\n        $hash = $data['unique_id'];\n        $hash_check = config('plugin.saiadmin.saithink.file_hash', false);\n        if ($hash_check) {\n            $model = $this->model->where('hash', $hash)->findOrEmpty();\n            if (!$model->isEmpty()) {\n                return $model->toArray();\n            }\n        }\n        $url = str_replace('\\\\', '/', $data['url']);\n        $savePath = str_replace('\\\\', '/', $data['save_path']);\n        $info['storage_mode'] = $type;\n        $info['category_id'] = request()->input('category_id', 1);\n        $info['origin_name'] = $data['origin_name'];\n        $info['object_name'] = $data['save_name'];\n        $info['hash'] = $data['unique_id'];\n        $info['mime_type'] = $data['mime_type'];\n        $info['storage_path'] = $savePath;\n        $info['suffix'] = $data['extension'];\n        $info['size_byte'] = $data['size'];\n        $info['size_info'] = formatBytes($data['size']);\n        $info['url'] = $url;\n        $this->model->save($info);\n        return $info;\n    }\n\n    /**\n     * 切片上传\n     * @param $data\n     * @return array\n     */\n    public function chunkUpload($data): array\n    {\n        $chunkService = new ChunkUploadService();\n        if ($data['index'] == 0) {\n            $model = $this->model->where('hash', $data['hash'])->findOrEmpty();\n            if (!$model->isEmpty()) {\n                return $model->toArray();\n            } else {\n                return $chunkService->checkChunk($data);\n            }\n        } else {\n            return $chunkService->uploadChunk($data);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/logic/system/SystemCategoryLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\app\\model\\system\\SystemCategory;\nuse plugin\\saiadmin\\utils\\Helper;\nuse plugin\\saiadmin\\utils\\Arr;\n\n/**\n * 附件分类逻辑层\n */\nclass SystemCategoryLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemCategory();\n    }\n\n    /**\n     * 添加数据\n     */\n    public function add($data): bool\n    {\n        $data = $this->handleData($data);\n        return $this->model->save($data);\n    }\n\n    /**\n     * 修改数据\n     */\n    public function edit($id, $data): bool\n    {\n        $data = $this->handleData($data);\n        if ($data['parent_id'] == $id) {\n            throw new ApiException('上级分类和当前分类不能相同');\n        }\n        if (in_array($id, explode(',', $data['level']))) {\n            throw new ApiException('不能将上级分类设置为当前分类的子分类');\n        }\n        $model = $this->model->findOrEmpty($id);\n        if ($model->isEmpty()) {\n            throw new ApiException('数据不存在');\n        }\n        return $model->save($data);\n    }\n\n    /**\n     * 数据删除\n     */\n    public function destroy($ids): bool\n    {\n        $num = $this->model->where('parent_id', 'in', $ids)->count();\n        if ($num > 0) {\n            throw new ApiException('该部门下存在子分类，请先删除子分类');\n        } else {\n            return $this->model->destroy($ids);\n        }\n    }\n\n    /**\n     * 数据处理\n     */\n    protected function handleData($data)\n    {\n        if (empty($data['parent_id']) || $data['parent_id'] == 0) {\n            $data['level'] = '0';\n            $data['parent_id'] = 0;\n        } else {\n            $parentMenu = SystemCategory::findOrEmpty($data['parent_id']);\n            $data['level'] = $parentMenu['level'] . $parentMenu['id'] . ',';\n        }\n        return $data;\n    }\n\n    /**\n     * 数据树形化\n     * @param array $where\n     * @return array\n     */\n    public function tree(array $where = []): array\n    {\n        $query = $this->search($where);\n        $request = request();\n        if ($request && $request->input('tree', 'false') === 'true') {\n            $query->field('id, id as value, category_name as label, parent_id, category_name, sort');\n        }\n        $query->order('sort', 'desc');\n        $data = $this->getAll($query);\n        return Helper::makeTree($data);\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/logic/system/SystemConfigGroupLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\cache\\ConfigCache;\nuse plugin\\saiadmin\\app\\model\\system\\SystemConfigGroup;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\app\\model\\system\\SystemConfig;\nuse support\\think\\Db;\n\n/**\n * 参数配置分组逻辑层\n */\nclass SystemConfigGroupLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemConfigGroup();\n    }\n\n    /**\n     * 删除配置信息\n     */\n    public function destroy($ids): bool\n    {\n        $id = $ids[0];\n        $model = $this->model->where('id', $id)->findOrEmpty();\n        if ($model->isEmpty()) {\n            throw new ApiException('配置数据未找到');\n        }\n        if (in_array(intval($id), [1, 2, 3])) {\n            throw new ApiException('系统默认分组，无法删除');\n        }\n        Db::startTrans();\n        try {\n            // 删除配置组\n            $model->delete();\n            // 删除配置组数据\n            $typeIds = SystemConfig::where('group_id', $id)->column('id');\n            SystemConfig::destroy($typeIds);\n            ConfigCache::clearConfig($model->code);\n            Db::commit();\n            return true;\n        } catch (\\Exception $e) {\n            Db::rollback();\n            throw new ApiException('删除数据异常，请检查');\n        }\n    }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/logic/system/SystemConfigLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\cache\\ConfigCache;\nuse plugin\\saiadmin\\app\\model\\system\\SystemConfig;\nuse plugin\\saiadmin\\app\\model\\system\\SystemConfigGroup;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 参数配置逻辑层\n */\nclass SystemConfigLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemConfig();\n    }\n\n    /**\n     * 添加数据\n     * @param mixed $data\n     * @return mixed\n     */\n    public function add($data): mixed\n    {\n        $result = $this->model->create($data);\n        $group = SystemConfigGroup::find($data['group_id']);\n        ConfigCache::clearConfig($group->code);\n        return $result;\n    }\n\n    /**\n     * 编辑数据\n     * @param mixed $id\n     * @param mixed $data\n     * @return bool\n     */\n    public function edit($id, $data): bool\n    {\n        $result = parent::edit($id, $data);\n        $group = SystemConfigGroup::find($data['group_id']);\n        ConfigCache::clearConfig($group->code);\n        return $result;\n    }\n\n    /**\n     * 批量更新\n     * @param mixed $group_id\n     * @param mixed $config\n     * @return bool\n     */\n    public function batchUpdate($group_id, $config): bool\n    {\n        $group = SystemConfigGroup::find($group_id);\n        if (!$group) {\n            throw new ApiException('配置组未找到');\n        }\n        $saveData = [];\n        foreach ($config as $key => $value) {\n            $saveData[] = [\n                'id' => $value['id'],\n                'group_id' => $group_id,\n                'name' => $value['name'],\n                'key' => $value['key'],\n                'value' => $value['value']\n            ];\n        }\n        // upsert: 根据 id 更新，如果不存在则插入\n        $this->model->saveAll($saveData);\n        ConfigCache::clearConfig($group->code);\n        return true;\n    }\n\n    /**\n     * 获取配置数据\n     * @param mixed $code\n     * @return array\n     */\n    public function getData($code): array\n    {\n        $group = SystemConfigGroup::where('code', $code)->findOrEmpty();\n        if (empty($group)) {\n            return [];\n        }\n        $config = SystemConfig::where('group_id', $group['id'])->select()->toArray();\n        return $config;\n    }\n\n    /**\n     * 获取配置组\n     */\n    public function getGroup($config): array\n    {\n        return ConfigCache::getConfig($config);\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/logic/system/SystemDeptLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDept;\nuse plugin\\saiadmin\\app\\model\\system\\SystemUser;\nuse plugin\\saiadmin\\utils\\Helper;\nuse plugin\\saiadmin\\utils\\Arr;\n\n/**\n * 部门逻辑层\n */\nclass SystemDeptLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemDept();\n    }\n\n    /**\n     * 添加数据\n     */\n    public function add($data): mixed\n    {\n        $data = $this->handleData($data);\n        $this->model->save($data);\n        return $this->model->getKey();\n    }\n\n    /**\n     * 修改数据\n     */\n    public function edit($id, $data): mixed\n    {\n        $oldLevel = $data['level'] . $id . ',';\n        $data = $this->handleData($data);\n        if ($data['parent_id'] == $id) {\n            throw new ApiException('上级部门和当前部门不能相同');\n        }\n        if (in_array($id, explode(',', $data['level']))) {\n            throw new ApiException('不能将上级部门设置为当前部门的子部门');\n        }\n        $newLevel = $data['level'] . $id . ',';\n        $deptIds = $this->model->where('level', 'like', $oldLevel . '%')->column('id');\n\n        return $this->transaction(function () use ($deptIds, $oldLevel, $newLevel, $data, $id) {\n            $this->model->whereIn('id', $deptIds)->exp('level', \"REPLACE(level, '$oldLevel', '$newLevel')\")->update([]);\n            return $this->model->update($data, ['id' => $id]);\n        });\n    }\n\n    /**\n     * 数据删除\n     */\n    public function destroy($ids): bool\n    {\n        $num = $this->model->where('parent_id', 'in', $ids)->count();\n        if ($num > 0) {\n            throw new ApiException('该部门下存在子部门，请先删除子部门');\n        } else {\n            $count = SystemUser::where('dept_id', 'in', $ids)->count();\n            if ($count > 0) {\n                throw new ApiException('该部门下存在用户，请先删除或者转移用户');\n            }\n            return $this->model->destroy($ids);\n        }\n    }\n\n    /**\n     * 数据处理\n     */\n    protected function handleData($data)\n    {\n        // 处理上级部门\n        if (empty($data['parent_id']) || $data['parent_id'] == 0) {\n            $data['level'] = '0';\n            $data['parent_id'] = 0;\n        } else {\n            $parentMenu = SystemDept::findOrEmpty($data['parent_id']);\n            $data['level'] = $parentMenu['level'] . $parentMenu['id'] . ',';\n        }\n        return $data;\n    }\n\n    /**\n     * 数据树形化\n     * @param array $where\n     * @return array\n     */\n    public function tree(array $where = []): array\n    {\n        $query = $this->search($where);\n        $request = request();\n        if ($request && $request->input('tree', 'false') === 'true') {\n            $query->field('id, id as value, name as label, parent_id');\n        }\n        $query->order('sort', 'desc');\n        $query->with(['leader']);\n        $data = $this->getAll($query);\n        return Helper::makeTree($data);\n    }\n\n    /**\n     * 可操作部门\n     * @param array $where\n     * @return array\n     */\n    public function accessDept(array $where = []): array\n    {\n        $query = $this->search($where);\n        $query->auth($this->adminInfo['deptList']);\n        $query->field('id, id as value, name as label, parent_id');\n        $query->order('sort', 'desc');\n        $data = $this->getAll($query);\n        return Helper::makeTree($data);\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/logic/system/SystemDictDataLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDictData;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDictType;\nuse plugin\\saiadmin\\app\\cache\\DictCache;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 字典类型逻辑层\n */\nclass SystemDictDataLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemDictData();\n    }\n\n    /**\n     * 添加数据\n     * @param $data\n     * @return mixed\n     */\n    public function add($data): mixed\n    {\n        $type = SystemDictType::where('id', $data['type_id'])->findOrEmpty();\n        if ($type->isEmpty()) {\n            throw new ApiException('字典类型不存在');\n        }\n        $data['code'] = $type->code;\n        $model = $this->model->create($data);\n        DictCache::clear();\n        return $model->getKey();\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/logic/system/SystemDictTypeLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDictType;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDictData;\nuse support\\think\\Db;\n\n/**\n * 字典类型逻辑层\n */\nclass SystemDictTypeLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemDictType();\n    }\n\n    /**\n     * 添加数据\n     */\n    public function add($data): mixed\n    {\n        $model = $this->model->where('code', $data['code'])->findOrEmpty();\n        if (!$model->isEmpty()) {\n            throw new ApiException('该字典标识已存在');\n        }\n        return $this->model->save($data);\n    }\n\n    /**\n     * 数据更新\n     */\n    public function edit($id, $data): mixed\n    {\n        Db::startTrans();\n        try {\n            // 修改数据字典类型\n            $result = $this->model->update($data, ['id' => $id]);\n            // 更新数据字典数据\n            SystemDictData::update(['code' => $data['code']], ['type_id' => $id]);\n            Db::commit();\n            return $result;\n        } catch (\\Exception $e) {\n            Db::rollback();\n            throw new ApiException('修改数据异常，请检查');\n        }\n    }\n\n    /**\n     * 数据删除\n     */\n    public function destroy($ids): bool\n    {\n        Db::startTrans();\n        try {\n            // 删除数据字典类型\n            $result = $this->model->destroy($ids);\n            // 删除数据字典数据\n            $typeIds = SystemDictData::where('type_id', 'in', $ids)->column('id');\n            SystemDictData::destroy($typeIds);\n            Db::commit();\n            return $result;\n        } catch (\\Exception $e) {\n            Db::rollback();\n            throw new ApiException('删除数据异常，请检查');\n        }\n    }\n\n    /**\n     * 获取全部字典\n     * @return array\n     */\n    public function getDictAll(): array\n    {\n        $data = $this->model->where('status', 1)->field('id, name, code, remark')\n            ->with([\n                'dicts' => function ($query) {\n                    $query->where('status', 1)->field('id, type_id, label, value, color, code, sort')->order('sort', 'desc');\n                }\n            ])->select()->toArray();\n        return $this->packageDict($data, 'code');\n    }\n\n    /**\n     * 组合数据\n     * @param $array\n     * @param $field\n     * @return array\n     */\n    private function packageDict($array, $field): array\n    {\n        $result = [];\n        foreach ($array as $item) {\n            if (isset($item[$field])) {\n                if (isset($result[$item[$field]])) {\n                    $result[$item[$field]] = [($result[$item[$field]])];\n                    $result[$item[$field]][] = $item['dicts'];\n                } else {\n                    $result[$item[$field]] = $item['dicts'];\n                }\n            }\n        }\n        return $result;\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/logic/system/SystemLoginLogLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\model\\system\\SystemLoginLog;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\utils\\Helper;\nuse support\\think\\Db;\n\n/**\n * 登录日志逻辑层\n */\nclass SystemLoginLogLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemLoginLog();\n    }\n\n    /**\n     * 登录统计图表\n     * @return array\n     */\n    public function loginChart(): array\n    {\n        $sql = \"\n            SELECT\n                d.date AS login_date,\n                COUNT(l.login_time) AS login_count\n            FROM\n                (SELECT CURDATE() - INTERVAL (a.N) DAY AS date\n                 FROM (SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n                       UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n                       UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a\n                 ) d\n            LEFT JOIN sa_system_login_log l\n                ON DATE(l.login_time) = d.date\n            GROUP BY d.date\n            ORDER BY d.date ASC;\n        \";\n        $data = Db::query($sql);\n        return [\n            'login_count' => array_column($data, 'login_count'),\n            'login_date' => array_column($data, 'login_date'),\n        ];\n    }\n\n    /**\n     * 登录统计图表\n     * @return array\n     */\n    public function loginBarChart(): array\n    {\n        $sql = \"\n            SELECT\n                -- 拼接成 YYYY-MM 格式，例如 2023-01\n                CONCAT(LPAD(m.month_num, 2, '0'), '月') AS login_month,\n                COUNT(l.login_time) AS login_count\n            FROM\n                -- 生成 1 到 12 的月份数字\n                (SELECT 1 AS month_num UNION ALL SELECT 2 UNION ALL SELECT 3\n                 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n                 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n                 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12) m\n            LEFT JOIN sa_system_login_log l\n                -- 关联条件：年份等于今年 且 月份等于生成的数字\n                ON YEAR(l.login_time) = YEAR(CURDATE())\n                AND MONTH(l.login_time) = m.month_num\n            GROUP BY\n                m.month_num\n            ORDER BY\n                m.month_num ASC;\n        \";\n        $data = Db::query($sql);\n        return [\n            'login_count' => array_column($data, 'login_count'),\n            'login_month' => array_column($data, 'login_month'),\n        ];\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/logic/system/SystemMailLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\model\\system\\SystemMail;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 邮件模型逻辑层\n */\nclass SystemMailLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemMail();\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/logic/system/SystemMenuLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\model\\system\\SystemMenu;\nuse plugin\\saiadmin\\app\\model\\system\\SystemRoleMenu;\nuse plugin\\saiadmin\\app\\model\\system\\SystemUserRole;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\utils\\Arr;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 菜单逻辑层\n */\nclass SystemMenuLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemMenu();\n    }\n\n    /**\n     * 数据添加\n     */\n    public function add($data): mixed\n    {\n        $data = $this->handleData($data);\n        return $this->model->save($data);\n    }\n\n    /**\n     * 数据修改\n     */\n    public function edit($id, $data): mixed\n    {\n        $data = $this->handleData($data);\n        if ($data['parent_id'] == $id) {\n            throw new ApiException('不能设置父级为自身');\n        }\n        return $this->model->update($data, ['id' => $id]);\n    }\n\n    /**\n     * 数据删除\n     */\n    public function destroy($ids): bool\n    {\n        $num = $this->model->where('parent_id', 'in', $ids)->count();\n        if ($num > 0) {\n            throw new ApiException('该菜单下存在子菜单，请先删除子菜单');\n        } else {\n            return $this->model->destroy($ids);\n        }\n    }\n\n    /**\n     * 数据处理\n     */\n    protected function handleData($data)\n    {\n        // 处理上级菜单\n        if (empty($data['parent_id']) || $data['parent_id'] == 0) {\n            $data['level'] = '0';\n            $data['parent_id'] = 0;\n        } else {\n            $parentMenu = $this->model->findOrEmpty($data['parent_id']);\n            $data['level'] = $parentMenu['level'] . $parentMenu['id'] . ',';\n        }\n        return $data;\n    }\n\n    /**\n     * 数据树形化\n     * @param $where\n     * @return array\n     */\n    public function tree($where = []): array\n    {\n        $query = $this->search($where);\n        $request = request();\n        if ($request && $request->input('tree', 'false') === 'true') {\n            $query->field('id, id as value, name as label, parent_id, type');\n        }\n        $query->order('sort', 'desc');\n        $data = $this->getAll($query);\n        return Helper::makeTree($data);\n    }\n\n    /**\n     * 权限菜单\n     * @return array\n     */\n    public function auth(): array\n    {\n        $roleLogic = new SystemRoleLogic();\n        $role_ids = Arr::getArrayColumn($this->adminInfo['roleList'], 'id');\n        $roles = $roleLogic->getMenuIdsByRoleIds($role_ids);\n        $ids = $this->filterMenuIds($roles);\n        $query = $this->model\n            ->field('id, id as value, name as label, parent_id, type')\n            ->where('status', 1)\n            ->where('id', 'in', $ids)\n            ->order('sort', 'desc');\n        $data = $this->getAll($query);\n        return Helper::makeTree($data);\n    }\n\n    /**\n     * 获取全部菜单\n     */\n    public function getAllMenus(): array\n    {\n        $query = $this->search(['status' => 1, 'type' => [1, 2, 4]])->order('sort', 'desc');\n        $data = $this->getAll($query);\n        return Helper::makeArtdMenus($data);\n    }\n\n    /**\n     * 获取全部权限\n     * @return array\n     */\n    public function getAllAuth(): array\n    {\n        return SystemMenu::where('type', 3)\n            ->where('status', 1)\n            ->column('slug');\n    }\n\n    /**\n     * 根据角色获取权限\n     * @param $roleIds\n     * @return array\n     */\n    public function getAuthByRole($roleIds): array\n    {\n        $menuId = SystemRoleMenu::whereIn('role_id', $roleIds)->column('menu_id');\n\n        return SystemMenu::distinct(true)\n            ->where('type', 3)\n            ->where('status', 1)\n            ->where('id', 'in', array_unique($menuId))\n            ->column('slug');\n    }\n\n    /**\n     * 根据角色获取菜单\n     * @param $roleIds\n     * @return array\n     */\n    public function getMenuByRole($roleIds): array\n    {\n        $menuId = SystemRoleMenu::whereIn('role_id', $roleIds)->column('menu_id');\n\n        $data = SystemMenu::distinct(true)\n            ->where('status', 1)\n            ->where('type', 'in', [1, 2, 4])\n            ->where('id', 'in', array_unique($menuId))\n            ->order('sort', 'desc')\n            ->select()\n            ->toArray();\n        return Helper::makeArtdMenus($data);\n    }\n\n    /**\n     * 过滤通过角色查询出来的菜单id列表，并去重\n     * @param array $roleData\n     * @return array\n     */\n    public function filterMenuIds(array &$roleData): array\n    {\n        $ids = [];\n        foreach ($roleData as $val) {\n            foreach ($val['menus'] as $menu) {\n                $ids[] = $menu['id'];\n            }\n        }\n        unset($roleData);\n        return array_unique($ids);\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/logic/system/SystemOperLogLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\model\\system\\SystemOperLog;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 操作日志逻辑层\n */\nclass SystemOperLogLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemOperLog();\n    }\n\n    /**\n     * 获取自己的操作日志\n     * @param mixed $where\n     * @return array\n     */\n    public function getOwnOperLogList($where): array\n    {\n        $query = $this->search($where);\n        $query->field('id, username, method, router, service_name, ip, ip_location, create_time');\n        return $this->getList($query);\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/logic/system/SystemPostLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\model\\system\\SystemPost;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\service\\OpenSpoutWriter;\nuse OpenSpout\\Reader\\XLSX\\Reader;\n\n/**\n * 岗位管理逻辑层\n */\nclass SystemPostLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemPost();\n    }\n\n    /**\n     * 可操作岗位\n     * @param array $where\n     * @return array\n     */\n    public function accessPost(array $where = []): array\n    {\n        $query = $this->search($where);\n        $query->field('id, id as value, name as label, name, code');\n        return $this->getAll($query);\n    }\n\n    /**\n     * 导入数据\n     */\n    public function import($file)\n    {\n        $path = $this->getImport($file);\n        $reader = new Reader();\n        try {\n            $reader->open($path);\n            $data = [];\n            foreach ($reader->getSheetIterator() as $sheet) {\n                $isHeader = true;\n                foreach ($sheet->getRowIterator() as $row) {\n                    if ($isHeader) {\n                        $isHeader = false;\n                        continue;\n                    }\n                    $cells = $row->getCells();\n                    $data[] = [\n                        'name' => $cells[0]->getValue(),\n                        'code' => $cells[1]->getValue(),\n                        'sort' => $cells[2]->getValue(),\n                        'status' => $cells[3]->getValue(),\n                    ];\n                }\n            }\n            $this->saveAll($data);\n        } catch (\\Exception $e) {\n            throw new ApiException('导入文件错误，请上传正确的文件格式xlsx');\n        }\n    }\n\n    /**\n     * 导出数据\n     */\n    public function export($where = [])\n    {\n        $query = $this->search($where)->field('id,name,code,sort,status,create_time');\n        $data = $this->getAll($query);\n        $file_name = '岗位数据.xlsx';\n        $header = ['编号', '岗位名称', '岗位标识', '排序', '状态', '创建时间'];\n        $filter = [\n            'status' => [\n                ['value' => 1, 'label' => '正常'],\n                ['value' => 2, 'label' => '禁用']\n            ]\n        ];\n        $writer = new OpenSpoutWriter($file_name);\n        $writer->setWidth([15, 15, 20, 15, 15, 25]);\n        $writer->setHeader($header);\n        $writer->setData($data, null, $filter);\n        $file_path = $writer->returnFile();\n        return response()->download($file_path, urlencode($file_name));\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/logic/system/SystemRoleLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\cache\\UserMenuCache;\nuse plugin\\saiadmin\\app\\model\\system\\SystemRole;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\utils\\Helper;\nuse support\\think\\Cache;\nuse support\\think\\Db;\n\n/**\n * 角色逻辑层\n */\nclass SystemRoleLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemRole();\n    }\n\n    /**\n     * 添加数据\n     */\n    public function add($data): bool\n    {\n        $data = $this->handleData($data);\n        return $this->model->save($data);\n    }\n\n    /**\n     * 修改数据\n     */\n    public function edit($id, $data): bool\n    {\n        $model = $this->model->findOrEmpty($id);\n        if ($model->isEmpty()) {\n            throw new ApiException('数据不存在');\n        }\n        $data = $this->handleData($data);\n        return $model->save($data);\n    }\n\n    /**\n     * 删除数据\n     */\n    public function destroy($ids): bool\n    {\n        // 越权保护\n        $levelArr = array_column($this->adminInfo['roleList'], 'level');\n        $maxLevel = max($levelArr);\n\n        $num = SystemRole::where('level', '>=', $maxLevel)->whereIn('id', $ids)->count();\n        if ($num > 0) {\n            throw new ApiException('不能操作比当前账户职级高的角色');\n        } else {\n            return $this->model->destroy($ids);\n        }\n    }\n\n    /**\n     * 数据处理\n     */\n    protected function handleData($data)\n    {\n        // 越权保护\n        $levelArr = array_column($this->adminInfo['roleList'], 'level');\n        $maxLevel = max($levelArr);\n        if ($data['level'] >= $maxLevel) {\n            throw new ApiException('不能操作比当前账户职级高的角色');\n        }\n        return $data;\n    }\n\n    /**\n     * 可操作角色\n     * @param array $where\n     * @return array\n     */\n    public function accessRole(array $where = []): array\n    {\n        $query = $this->search($where);\n        // 越权保护\n        $levelArr = array_column($this->adminInfo['roleList'], 'level');\n        $maxLevel = max($levelArr);\n        $query->where('level', '<', $maxLevel);\n        $query->order('sort', 'desc');\n        return $this->getAll($query);\n    }\n\n    /**\n     * 根据角色数组获取菜单\n     * @param $ids\n     * @return array\n     */\n    public function getMenuIdsByRoleIds($ids): array\n    {\n        if (empty($ids))\n            return [];\n        return $this->model->where('id', 'in', $ids)->with([\n            'menus' => function ($query) {\n                $query->where('status', 1)->order('sort', 'desc');\n            }\n        ])->select()->toArray();\n\n    }\n\n    /**\n     * 根据角色获取菜单\n     * @param $id\n     * @return array\n     */\n    public function getMenuByRole($id): array\n    {\n        $role = $this->model->findOrEmpty($id);\n        $menus = $role->menus ?: [];\n        return [\n            'id' => $id,\n            'menus' => $menus\n        ];\n    }\n\n    /**\n     * 保存菜单权限\n     * @param $id\n     * @param $menu_ids\n     * @return mixed\n     */\n    public function saveMenuPermission($id, $menu_ids): mixed\n    {\n        return $this->transaction(function () use ($id, $menu_ids) {\n            $role = $this->model->findOrEmpty($id);\n            if ($role) {\n                $role->menus()->detach();\n                $data = array_map(function ($menu_id) use ($id) {\n                    return ['menu_id' => $menu_id, 'role_id' => $id];\n                }, $menu_ids);\n                Db::name('sa_system_role_menu')->limit(100)->insertAll($data);\n            }\n            $cache = config('plugin.saiadmin.saithink.button_cache');\n            $tag = $cache['role'] . $id;\n            Cache::tag($tag)->clear();       // 清理权限缓存-角色TAG\n            UserMenuCache::clearMenuCache(); // 清理菜单缓存\n            return true;\n        });\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/logic/system/SystemUserLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\system;\n\nuse plugin\\saiadmin\\app\\cache\\UserAuthCache;\nuse plugin\\saiadmin\\app\\cache\\UserInfoCache;\nuse plugin\\saiadmin\\app\\cache\\UserMenuCache;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDept;\nuse plugin\\saiadmin\\app\\model\\system\\SystemRole;\nuse plugin\\saiadmin\\app\\model\\system\\SystemUser;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse Webman\\Event\\Event;\nuse Tinywan\\Jwt\\JwtToken;\n\n/**\n * 用户信息逻辑层\n */\nclass SystemUserLogic extends BaseLogic\n{\n\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new SystemUser();\n    }\n\n    /**\n     * 分页数据列表\n     * @param mixed $where\n     * @return array\n     */\n    public function indexList($where): array\n    {\n        $query = $this->search($where);\n        $query->with(['depts']);\n        $query->auth($this->adminInfo['deptList']);\n        return $this->getList($query);\n    }\n\n    /**\n     * 用户列表数据\n     * @param mixed $where\n     * @return array\n     */\n    public function openUserList($where): array\n    {\n        $query = $this->search($where);\n        $query->field('id, username, realname, avatar, phone, email');\n        return $this->getList($query);\n    }\n\n    /**\n     * 读取用户信息\n     * @param mixed $id\n     * @return array\n     */\n    public function getUser($id): array\n    {\n        $admin = $this->model->findOrEmpty($id);\n        $data = $admin->hidden(['password'])->toArray();\n        $data['roleList'] = $admin->roles->toArray() ?: [];\n        $data['postList'] = $admin->posts->toArray() ?: [];\n        $data['deptList'] = $admin->depts ? $admin->depts->toArray() : [];\n        return $data;\n    }\n\n    /**\n     * 读取数据\n     * @param $id\n     * @return array\n     */\n    public function read($id): array\n    {\n        $data = $this->getUser($id);\n        if ($this->adminInfo['id'] > 1) {\n            // 部门保护\n            if (!$this->deptProtect($this->adminInfo['deptList'], $data['dept_id'])) {\n                throw new ApiException('没有权限操作该部门数据');\n            }\n        }\n        return $data;\n    }\n\n    /**\n     * 添加数据\n     * @param $data\n     * @return mixed\n     */\n    public function add($data): mixed\n    {\n        $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);\n        return $this->transaction(function () use ($data) {\n            $role_ids = $data['role_ids'] ?? [];\n            $post_ids = $data['post_ids'] ?? [];\n            if ($this->adminInfo['id'] > 1) {\n                // 部门保护\n                if (!$this->deptProtect($this->adminInfo['deptList'], $data['dept_id'])) {\n                    throw new ApiException('没有权限操作该部门数据');\n                }\n                // 越权保护\n                if (!$this->roleProtect($this->adminInfo['roleList'], $role_ids)) {\n                    throw new ApiException('没有权限操作该角色数据');\n                }\n            }\n            $user = SystemUser::create($data);\n            $user->roles()->detach();\n            $user->posts()->detach();\n            $user->roles()->saveAll($role_ids);\n            if (!empty($post_ids)) {\n                $user->posts()->save($post_ids);\n            }\n            return $user;\n        });\n    }\n\n    /**\n     * 修改数据\n     * @param $id\n     * @param $data\n     * @return mixed\n     */\n    public function edit($id, $data): mixed\n    {\n        unset($data['password']);\n        return $this->transaction(function () use ($data, $id) {\n            $role_ids = $data['role_ids'] ?? [];\n            $post_ids = $data['post_ids'] ?? [];\n            // 仅可修改当前部门和子部门的用户\n            $query = $this->model->where('id', $id);\n            $query->auth($this->adminInfo['deptList']);\n            $user = $query->findOrEmpty();\n            if ($user->isEmpty()) {\n                throw new ApiException('没有权限操作该数据');\n            }\n            if ($this->adminInfo['id'] > 1) {\n                // 部门保护\n                if (!$this->deptProtect($this->adminInfo['deptList'], $data['dept_id'])) {\n                    throw new ApiException('没有权限操作该部门数据');\n                }\n                // 越权保护\n                if (!$this->roleProtect($this->adminInfo['roleList'], $role_ids)) {\n                    throw new ApiException('没有权限操作该角色数据');\n                }\n            }\n            $result = parent::edit($id, $data);\n            if ($result) {\n                $user->roles()->detach();\n                $user->posts()->detach();\n                $user->roles()->saveAll($role_ids);\n                if (!empty($post_ids)) {\n                    $user->posts()->save($post_ids);\n                }\n                UserInfoCache::clearUserInfo($id);\n                UserAuthCache::clearUserAuth($id);\n                UserMenuCache::clearUserMenu($id);\n            }\n            return $result;\n        });\n    }\n\n    /**\n     * 删除数据\n     * @param $ids\n     * @return bool\n     */\n    public function destroy($ids): bool\n    {\n        if (is_array($ids)) {\n            if (count($ids) > 1) {\n                throw new ApiException('禁止批量删除操作');\n            }\n            $ids = $ids[0];\n        }\n        if ($ids == 1) {\n            throw new ApiException('超级管理员禁止删除');\n        }\n        $query = $this->model->where('id', $ids);\n        $query->auth($this->adminInfo['deptList']);\n        $user = $query->findOrEmpty();\n        if ($user->isEmpty()) {\n            throw new ApiException('没有权限操作该数据');\n        }\n        if ($this->adminInfo['id'] > 1) {\n            $role_ids = $user->roles->toArray() ?: [];\n            if (!empty($role_ids)) {\n                // 越权保护\n                if (!$this->roleProtect($this->adminInfo['roleList'], array_column($role_ids, 'id'))) {\n                    throw new ApiException('没有权限操作该角色数据');\n                }\n            }\n        }\n        UserInfoCache::clearUserInfo($ids);\n        UserAuthCache::clearUserAuth($ids);\n        UserMenuCache::clearUserMenu($ids);\n        return parent::destroy($ids);\n    }\n\n    /**\n     * 用户登录\n     * @param string $username\n     * @param string $password\n     * @param string $type\n     * @return array\n     */\n    public function login(string $username, string $password, string $type): array\n    {\n        $adminInfo = $this->model->where('username', $username)->findOrEmpty();\n        $status = 1;\n        $message = '登录成功';\n        if ($adminInfo->isEmpty()) {\n            $message = '账号或密码错误，请重新输入!';\n            throw new ApiException($message);\n        }\n        if ($adminInfo->status === 2) {\n            $status = 0;\n            $message = '您已被禁止登录!';\n        }\n        if (!password_verify($password, $adminInfo->password)) {\n            $status = 0;\n            $message = '账号或密码错误，请重新输入!';\n        }\n        if ($status === 0) {\n            // 登录事件\n            Event::emit('user.login', compact('username', 'status', 'message'));\n            throw new ApiException($message);\n        }\n        $adminInfo->login_time = date('Y-m-d H:i:s');\n        $adminInfo->login_ip = request()->getRealIp();\n        $adminInfo->save();\n\n        $access_exp = config('plugin.saiadmin.saithink.access_exp', 3 * 3600);\n        $token = JwtToken::generateToken([\n            'access_exp' => $access_exp,\n            'id' => $adminInfo->id,\n            'username' => $adminInfo->username,\n            'type' => $type,\n            'plat' => 'saiadmin',\n        ]);\n        // 登录事件\n        $admin_id = $adminInfo->id;\n        Event::emit('user.login', compact('username', 'status', 'message', 'admin_id'));\n        return $token;\n    }\n\n    /**\n     * 更新资料\n     * @param mixed $id\n     * @param mixed $data\n     * @return bool\n     */\n    public function updateInfo($id, $data): bool\n    {\n        $this->model->update($data, ['id' => $id], ['realname', 'gender', 'phone', 'email', 'avatar', 'signed']);\n        return true;\n    }\n\n    /**\n     * 密码修改\n     * @param $adminId\n     * @param $oldPassword\n     * @param $newPassword\n     * @return bool\n     */\n    public function modifyPassword($adminId, $oldPassword, $newPassword): bool\n    {\n        $model = $this->model->findOrEmpty($adminId);\n        if (password_verify($oldPassword, $model->password)) {\n            $model->password = password_hash($newPassword, PASSWORD_DEFAULT);\n            return $model->save();\n        } else {\n            throw new ApiException('原密码错误');\n        }\n    }\n\n    /**\n     * 修改数据\n     */\n    public function authEdit($id, $data)\n    {\n        if ($this->adminInfo['id'] > 1) {\n            // 判断用户是否可以操作\n            $query = SystemUser::where('id', $id);\n            $query->auth($this->adminInfo['deptList']);\n            $user = $query->findOrEmpty();\n            if ($user->isEmpty()) {\n                throw new ApiException('没有权限操作该数据');\n            }\n        }\n        parent::edit($id, $data);\n    }\n\n    /**\n     * 部门保护\n     * @param $dept\n     * @param $dept_id\n     * @return bool\n     */\n    public function deptProtect($dept, $dept_id): bool\n    {\n        // 部门保护\n        $deptIds = [$dept['id']];\n        $deptLevel = $dept['level'] . $dept['id'] . ',';\n        $dept_ids = SystemDept::whereLike('level', $deptLevel . '%')->column('id');\n        $deptIds = array_merge($deptIds, $dept_ids);\n        if (!in_array($dept_id, $deptIds)) {\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * 越权保护\n     * @param $roleList\n     * @param $role_ids\n     * @return bool\n     */\n    public function roleProtect($roleList, $role_ids): bool\n    {\n        // 越权保护\n        $levelArr = array_column($roleList, 'level');\n        $maxLevel = max($levelArr);\n        $currentLevel = SystemRole::whereIn('id', $role_ids)->max('level');\n        if ($currentLevel >= $maxLevel) {\n            return false;\n        }\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/logic/tool/CrontabLogLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\tool;\n\nuse plugin\\saiadmin\\app\\model\\tool\\CrontabLog;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\n\n/**\n * 定时任务日志逻辑层\n */\nclass CrontabLogLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new CrontabLog();\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/logic/tool/CrontabLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\tool;\n\nuse Exception;\nuse GuzzleHttp\\Client;\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Webman\\Channel\\Client as ChannelClient;\nuse plugin\\saiadmin\\app\\model\\tool\\Crontab;\nuse plugin\\saiadmin\\app\\model\\tool\\CrontabLog;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\n\n/**\n * 定时任务逻辑层\n */\nclass CrontabLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new Crontab();\n    }\n\n    /**\n     * 添加任务\n     */\n    public function add($data): bool\n    {\n        $second = $data['second'];\n        $minute = $data['minute'];\n        $hour = $data['hour'];\n        $week = $data['week'];\n        $day = $data['day'];\n        $month = $data['month'];\n\n        // 规则处理\n        $rule = match ($data['task_style']) {\n            1 => \"0 {$minute} {$hour} * * *\",\n            2 => \"0 {$minute} * * * *\",\n            3 => \"0 {$minute} */{$hour} * * *\",\n            4 => \"0 */{$minute} * * * *\",\n            5 => \"*/{$second} * * * * *\",\n            6 => \"0 {$minute} {$hour} * * {$week}\",\n            7 => \"0 {$minute} {$hour} {$day} * *\",\n            8 => \"0 {$minute} {$hour} {$day} {$month} *\",\n            default => throw new ApiException(\"任务类型异常\"),\n        };\n\n        // 定时任务模型新增\n        $model = Crontab::create([\n            'name' => $data['name'],\n            'type' => $data['type'],\n            'task_style' => $data['task_style'],\n            'rule' => $rule,\n            'target' => $data['target'],\n            'parameter' => $data['parameter'],\n            'status' => $data['status'],\n            'remark' => $data['remark'],\n        ]);\n\n        $id = $model->getKey();\n        // 连接到Channel服务\n        ChannelClient::connect();\n        ChannelClient::publish('crontab', ['args' => $id]);\n\n        return true;\n    }\n\n    /**\n     * 修改任务\n     */\n    public function edit($id, $data): bool\n    {\n        $second = $data['second'];\n        $minute = $data['minute'];\n        $hour = $data['hour'];\n        $week = $data['week'];\n        $day = $data['day'];\n        $month = $data['month'];\n\n        // 规则处理\n        $rule = match ($data['task_style']) {\n            1 => \"0 {$minute} {$hour} * * *\",\n            2 => \"0 {$minute} * * * *\",\n            3 => \"0 {$minute} */{$hour} * * *\",\n            4 => \"0 */{$minute} * * * *\",\n            5 => \"*/{$second} * * * * *\",\n            6 => \"0 {$minute} {$hour} * * {$week}\",\n            7 => \"0 {$minute} {$hour} {$day} * *\",\n            8 => \"0 {$minute} {$hour} {$day} {$month} *\",\n            default => throw new ApiException(\"任务类型异常\"),\n        };\n\n        // 查询任务数据\n        $model = $this->model->findOrEmpty($id);\n        if ($model->isEmpty()) {\n            throw new ApiException('数据不存在');\n        }\n\n        $result = $model->save([\n            'name' => $data['name'],\n            'type' => $data['type'],\n            'task_style' => $data['task_style'],\n            'rule' => $rule,\n            'target' => $data['target'],\n            'parameter' => $data['parameter'],\n            'status' => $data['status'],\n            'remark' => $data['remark'],\n        ]);\n        if ($result) {\n            // 连接到Channel服务\n            ChannelClient::connect();\n            ChannelClient::publish('crontab', ['args' => $id]);\n        }\n\n        // 修改任务数据\n        return $result;\n    }\n\n    /**\n     * 删除定时任务\n     * @param $ids\n     * @return bool\n     * @throws Exception\n     */\n    public function destroy($ids): bool\n    {\n        if (is_array($ids)) {\n            if (count($ids) > 1) {\n                throw new ApiException('禁止批量删除操作');\n            }\n            $ids = $ids[0];\n        }\n        $result = parent::destroy($ids);\n        if ($result) {\n            // 连接到Channel服务\n            ChannelClient::connect();\n            ChannelClient::publish('crontab', ['args' => $ids]);\n        }\n        return $result;\n    }\n\n    /**\n     * 修改状态\n     * @param $id\n     * @param $status\n     * @return bool\n     */\n    public function changeStatus($id, $status): bool\n    {\n        $model = $this->model->findOrEmpty($id);\n        if ($model->isEmpty()) {\n            throw new ApiException('数据不存在');\n        }\n        $result = $model->save(['status' => $status]);\n        if ($result) {\n            // 连接到Channel服务\n            ChannelClient::connect();\n            ChannelClient::publish('crontab', ['args' => $id]);\n        }\n        return $result;\n    }\n\n    /**\n     * 执行定时任务\n     * @param $id\n     * @return bool\n     */\n    public function run($id): bool\n    {\n        $info = $this->model->where('status', 1)->findOrEmpty($id);\n        if ($info->isEmpty()) {\n            return false;\n        }\n        $data['crontab_id'] = $info->id;\n        $data['name'] = $info->name;\n        $data['target'] = $info->target;\n        $data['parameter'] = $info->parameter;\n        switch ($info->type) {\n            case 1:\n                // URL任务GET\n                $httpClient = new Client([\n                    'timeout' => 5,\n                    'verify' => false,\n                ]);\n                try {\n                    $httpClient->request('GET', $info->target);\n                    $data['status'] = 1;\n                    CrontabLog::create($data);\n                    return true;\n                } catch (GuzzleException $e) {\n                    $data['status'] = 2;\n                    $data['exception_info'] = $e->getMessage();\n                    CrontabLog::create($data);\n                    return false;\n                }\n            case 2:\n                // URL任务POST\n                $httpClient = new Client([\n                    'timeout' => 5,\n                    'verify' => false,\n                ]);\n                try {\n                    $res = $httpClient->request('POST', $info->target, [\n                        'form_params' => json_decode($info->parameter ?? '', true)\n                    ]);\n                    $data['status'] = 1;\n                    $data['exception_info'] = $res->getBody();\n                    CrontabLog::create($data);\n                    return true;\n                } catch (GuzzleException $e) {\n                    $data['status'] = 2;\n                    $data['exception_info'] = $e->getMessage();\n                    CrontabLog::create($data);\n                    return false;\n                }\n            case 3:\n                // 类任务\n                $class_name = $info->target;\n                $method_name = 'run';\n                $class = new $class_name;\n                if (method_exists($class, $method_name)) {\n                    $return = $class->$method_name($info->parameter);\n                    $data['status'] = 1;\n                    $data['exception_info'] = $return;\n                    CrontabLog::create($data);\n                    return true;\n                } else {\n                    $data['status'] = 2;\n                    $data['exception_info'] = '类:' . $class_name . ',方法:run,未找到';\n                    CrontabLog::create($data);\n                    return false;\n\n                }\n            default:\n                return false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/logic/tool/GenerateColumnsLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\tool;\n\nuse plugin\\saiadmin\\app\\model\\tool\\GenerateColumns;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\utils\\Helper;\n\n/**\n * 代码生成业务字段逻辑层\n */\nclass GenerateColumnsLogic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new GenerateColumns();\n    }\n\n    public function saveExtra($data)\n    {\n        $default_column = ['create_time', 'update_time', 'created_by', 'updated_by', 'delete_time', 'remark'];\n        // 组装数据\n        foreach ($data as $k => $item) {\n\n            if ($item['column_name'] == 'delete_time') {\n                continue;\n            }\n\n            $column = [\n                'table_id' => $item['table_id'],\n                'column_name' => $item['column_name'],\n                'column_comment' => $item['column_comment'],\n                'column_type' => $item['column_type'],\n                'default_value' => $item['default_value'],\n                'is_pk' => ($item['column_key'] == 'PRI') ? 2 : 1,\n                'is_required' => $item['is_nullable'] == 'NO' ? 2 : 1,\n                'query_type' => 'eq',\n                'view_type' => 'input',\n                'sort' => count($data) - $k,\n                'options' => $item['options'] ?? null\n            ];\n\n            // 设置默认选项\n            if (!in_array($item['column_name'], $default_column) && empty($item['column_key'])) {\n                $column = array_merge(\n                    $column,\n                    [\n                        'is_insert' => 2,\n                        'is_edit' => 2,\n                        'is_list' => 2,\n                        'is_query' => 1,\n                        'is_sort' => 1,\n                    ]\n                );\n            }\n            $keyList = [\n                'column_comment',\n                'column_type',\n                'default_value',\n                'is_pk',\n                'is_required',\n                'is_insert',\n                'is_edit',\n                'is_list',\n                'is_query',\n                'is_sort',\n                'query_type',\n                'view_type',\n                'dict_type',\n                'options',\n                'sort',\n                'is_cover'\n            ];\n            foreach ($keyList as $key) {\n                if (isset($item[$key]))\n                    $column[$key] = $item[$key];\n            }\n            GenerateColumns::create($this->fieldDispose($column));\n        }\n    }\n\n    public function update($data, $where)\n    {\n        $data['is_insert'] = $data['is_insert'] ? 2 : 1;\n        $data['is_edit'] = $data['is_edit'] ? 2 : 1;\n        $data['is_list'] = $data['is_list'] ? 2 : 1;\n        $data['is_query'] = $data['is_query'] ? 2 : 1;\n        $data['is_sort'] = $data['is_sort'] ? 2 : 1;\n        $data['is_required'] = $data['is_required'] ? 2 : 1;\n        $this->model->update($data, $where);\n    }\n\n    private function fieldDispose(array $column): array\n    {\n        $object = new class {\n            public function viewTypeDispose(&$column): void\n            {\n                switch ($column['column_type']) {\n                    case 'varchar':\n                        $column['view_type'] = 'input';\n                        break;\n                    // 富文本\n                    case 'text':\n                    case 'longtext':\n                        $column['is_list'] = 1;\n                        $column['is_query'] = 1;\n                        $column['view_type'] = 'editor';\n                        $options = [\n                            'height' => 400,\n                        ];\n                        $column['options'] = $options;\n                        break;\n                    // 日期字段\n                    case 'datetime':\n                        $column['view_type'] = 'date';\n                        $options = [\n                            'mode' => 'datetime',\n                            'value_format' => 'YYYY-MM-DD HH:mm:ss'\n                        ];\n                        $column['options'] = $options;\n                        $column['query_type'] = 'between';\n                        break;\n                    case 'date':\n                        $column['view_type'] = 'date';\n                        $options = [\n                            'mode' => 'date',\n                            'value_format' => 'YYYY-MM-DD'\n                        ];\n                        $column['options'] = $options;\n                        $column['query_type'] = 'between';\n                        break;\n                }\n            }\n\n            public function columnName(&$column): void\n            {\n                if (stristr($column['column_name'], 'name')) {\n                    $column['is_query'] = 2;\n                    $column['is_required'] = 2;\n                    $column['query_type'] = 'like';\n                }\n\n                if (stristr($column['column_name'], 'title')) {\n                    $column['is_query'] = 2;\n                    $column['is_required'] = 2;\n                    $column['query_type'] = 'like';\n                }\n\n                if (stristr($column['column_name'], 'type')) {\n                    $column['is_query'] = 2;\n                    $column['is_required'] = 2;\n                    $column['query_type'] = 'eq';\n                }\n\n                if (stristr($column['column_name'], 'image')) {\n                    $column['is_query'] = 1;\n                    $column['view_type'] = 'uploadImage';\n                    $options = [\n                        'multiple' => false,\n                        'limit' => 1,\n                    ];\n                    $column['options'] = $options;\n                }\n\n                if (stristr($column['column_name'], 'file')) {\n                    $column['is_query'] = 1;\n                    $column['view_type'] = 'uploadFile';\n                    $options = [\n                        'multiple' => false,\n                        'limit' => 1,\n                    ];\n                    $column['options'] = $options;\n                }\n\n                if (stristr($column['column_name'], 'attach')) {\n                    $column['is_query'] = 1;\n                    $column['view_type'] = 'uploadFile';\n                    $options = [\n                        'multiple' => false,\n                        'limit' => 1,\n                    ];\n                    $column['options'] = $options;\n                }\n\n                if ($column['column_name'] === 'sort') {\n                    $column['view_type'] = 'inputNumber';\n                }\n\n                if ($column['column_name'] === 'status') {\n                    $column['view_type'] = 'radio';\n                    $column['dict_type'] = 'data_status';\n                }\n\n                if (stristr($column['column_name'], 'is_')) {\n                    $column['view_type'] = 'radio';\n                    $column['dict_type'] = 'yes_or_no';\n                }\n            }\n        };\n\n        if (!$column['is_cover']) {\n            $object->viewTypeDispose($column);\n            $object->columnName($column);\n        }\n        $column['options'] = json_encode($column['options'], JSON_UNESCAPED_UNICODE);\n        return $column;\n    }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/logic/tool/GenerateTablesLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\logic\\tool;\n\nuse plugin\\saiadmin\\app\\cache\\UserMenuCache;\nuse plugin\\saiadmin\\app\\logic\\system\\DatabaseLogic;\nuse plugin\\saiadmin\\app\\model\\system\\SystemMenu;\nuse plugin\\saiadmin\\app\\model\\tool\\GenerateTables;\nuse plugin\\saiadmin\\app\\model\\tool\\GenerateColumns;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\nuse plugin\\saiadmin\\utils\\Helper;\nuse plugin\\saiadmin\\utils\\code\\CodeZip;\nuse plugin\\saiadmin\\utils\\code\\CodeEngine;\n\n/**\n * 代码生成业务逻辑层\n */\nclass GenerateTablesLogic extends BaseLogic\n{\n    protected $columnLogic = null;\n\n    protected $dataLogic = null;\n\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new GenerateTables();\n        $this->columnLogic = new GenerateColumnsLogic();\n        $this->dataLogic = new DatabaseLogic();\n    }\n\n    /**\n     * 删除表和字段信息\n     * @param $ids\n     * @return bool\n     */\n    public function destroy($ids): bool\n    {\n        return $this->transaction(function () use ($ids) {\n            parent::destroy($ids);\n            GenerateColumns::destroy(function ($query) use ($ids) {\n                $query->where('table_id', 'in', $ids);\n            });\n            return true;\n        });\n    }\n\n    /**\n     * 装载表信息\n     * @param $names\n     * @param $source\n     * @return void\n     */\n    public function loadTable($names, $source): void\n    {\n        $data = config('think-orm.connections');\n        $config = $data[$source];\n        if (!$config) {\n            throw new ApiException('数据库配置读取失败');\n        }\n\n        $prefix = $config['prefix'] ?? '';\n        foreach ($names as $item) {\n            $class_name = $item['name'];\n            if (!empty($prefix)) {\n                $class_name = Helper::str_replace_once($prefix, '', $class_name);\n            }\n            $class_name = Helper::camel($class_name);\n            $tableInfo = [\n                'table_name' => $item['name'],\n                'table_comment' => $item['comment'],\n                'class_name' => $class_name,\n                'business_name' => Helper::get_business($item['name']),\n                'belong_menu_id' => 80,\n                'menu_name' => $item['comment'],\n                'tpl_category' => 'single',\n                'template' => 'app',\n                'stub' => 'think',\n                'namespace' => '',\n                'package_name' => '',\n                'source' => $source,\n                'generate_menus' => 'index,save,update,read,destroy',\n            ];\n            $model = GenerateTables::create($tableInfo);\n            $columns = $this->dataLogic->getColumnList($item['name'], $source);\n            foreach ($columns as &$column) {\n                $column['table_id'] = $model->id;\n                $column['is_cover'] = false;\n            }\n            $this->columnLogic->saveExtra($columns);\n        }\n    }\n\n    /**\n     * 同步表字段信息\n     * @param $id\n     * @return void\n     */\n    public function sync($id)\n    {\n        $model = $this->model->findOrEmpty($id);\n        // 拉取已有数据表信息\n        $queryModel = $this->columnLogic->model->where('table_id', $id);\n        $columnLogicData = $this->columnLogic->getAll($queryModel);\n        $columnLogicList = [];\n        foreach ($columnLogicData as $item) {\n            $columnLogicList[$item['column_name']] = $item;\n        }\n        GenerateColumns::destroy(function ($query) use ($id) {\n            $query->where('table_id', $id);\n        });\n        $columns = $this->dataLogic->getColumnList($model->table_name, $model->source ?? '');\n        foreach ($columns as &$column) {\n            $column['table_id'] = $model->id;\n            $column['is_cover'] = false;\n            if (isset($columnLogicList[$column['column_name']])) {\n                // 存在历史信息的情况\n                $getcolumnLogicItem = $columnLogicList[$column['column_name']];\n                if ($getcolumnLogicItem['column_type'] == $column['column_type']) {\n                    $column['is_cover'] = true;\n                    foreach ($getcolumnLogicItem as $key => $item) {\n                        $array = [\n                            'column_comment',\n                            'column_type',\n                            'is_pk',\n                            'is_required',\n                            'is_insert',\n                            'is_edit',\n                            'is_list',\n                            'is_query',\n                            'is_sort',\n                            'query_type',\n                            'view_type',\n                            'dict_type',\n                            'options',\n                            'sort',\n                            'is_cover'\n                        ];\n                        if (in_array($key, $array)) {\n                            $column[$key] = $item;\n                        }\n                    }\n                }\n            }\n        }\n        $this->columnLogic->saveExtra($columns);\n    }\n\n    /**\n     * 代码预览\n     * @param $id\n     * @return array\n     */\n    public function preview($id): array\n    {\n        $data = $this->renderData($id);\n\n        $codeEngine = new CodeEngine($data);\n        $controllerContent = $codeEngine->renderContent('php', 'controller.stub');\n        $logicContent = $codeEngine->renderContent('php', 'logic.stub');\n        $modelContent = $codeEngine->renderContent('php', 'model.stub');\n        $validateContent = $codeEngine->renderContent('php', 'validate.stub');\n        $sqlContent = $codeEngine->renderContent('sql', 'sql.stub');\n        $indexContent = $codeEngine->renderContent('vue', 'index.stub');\n        $editContent = $codeEngine->renderContent('vue', 'edit-dialog.stub');\n        $searchContent = $codeEngine->renderContent('vue', 'table-search.stub');\n        $apiContent = $codeEngine->renderContent('ts', 'api.stub');\n\n        // 返回生成内容\n        return [\n            [\n                'tab_name' => 'controller.php',\n                'name' => 'controller',\n                'lang' => 'php',\n                'code' => $controllerContent\n            ],\n            [\n                'tab_name' => 'logic.php',\n                'name' => 'logic',\n                'lang' => 'php',\n                'code' => $logicContent\n            ],\n            [\n                'tab_name' => 'model.php',\n                'name' => 'model',\n                'lang' => 'php',\n                'code' => $modelContent\n            ],\n            [\n                'tab_name' => 'validate.php',\n                'name' => 'validate',\n                'lang' => 'php',\n                'code' => $validateContent\n            ],\n            [\n                'tab_name' => 'sql.sql',\n                'name' => 'sql',\n                'lang' => 'sql',\n                'code' => $sqlContent\n            ],\n            [\n                'tab_name' => 'index.vue',\n                'name' => 'index',\n                'lang' => 'html',\n                'code' => $indexContent\n            ],\n            [\n                'tab_name' => 'edit-dialog.vue',\n                'name' => 'edit-dialog',\n                'lang' => 'html',\n                'code' => $editContent\n            ],\n            [\n                'tab_name' => 'table-search.vue',\n                'name' => 'table-search',\n                'lang' => 'html',\n                'code' => $searchContent\n            ],\n            [\n                'tab_name' => 'api.ts',\n                'name' => 'api',\n                'lang' => 'javascript',\n                'code' => $apiContent\n            ]\n        ];\n    }\n\n    /**\n     * 生成到模块\n     * @param $id\n     */\n    public function genModule($id)\n    {\n        $data = $this->renderData($id);\n\n        // 生成文件到模块\n        $codeEngine = new CodeEngine($data);\n        $codeEngine->generateBackend('controller', $codeEngine->renderContent('php', 'controller.stub'));\n        $codeEngine->generateBackend('logic', $codeEngine->renderContent('php', 'logic.stub'));\n        $codeEngine->generateBackend('model', $codeEngine->renderContent('php', 'model.stub'));\n        $codeEngine->generateBackend('validate', $codeEngine->renderContent('php', 'validate.stub'));\n        $codeEngine->generateFrontend('index', $codeEngine->renderContent('vue', 'index.stub'));\n        $codeEngine->generateFrontend('edit-dialog', $codeEngine->renderContent('vue', 'edit-dialog.stub'));\n        $codeEngine->generateFrontend('table-search', $codeEngine->renderContent('vue', 'table-search.stub'));\n        $codeEngine->generateFrontend('api', $codeEngine->renderContent('ts', 'api.stub'));\n    }\n\n    /**\n     * 处理数据\n     * @param $id\n     * @return array\n     */\n    protected function renderData($id): array\n    {\n        $table = $this->model->findOrEmpty($id);\n        if (!in_array($table['template'], [\"plugin\", \"app\"])) {\n            throw new ApiException('应用类型必须为plugin或者app');\n        }\n        if (empty($table['namespace'])) {\n            throw new ApiException('请先设置应用名称');\n        }\n\n        $columns = $this->columnLogic->where('table_id', $id)\n            ->order('sort', 'desc')\n            ->select()\n            ->toArray();\n        $pk = 'id';\n        foreach ($columns as &$column) {\n            if ($column['is_pk'] == 2) {\n                $pk = $column['column_name'];\n            }\n            if ($column['column_name'] == 'delete_time') {\n                unset($column['column_name']);\n            }\n        }\n\n        // 处理特殊变量\n        if ($table['template'] == 'plugin') {\n            $namespace_start = \"plugin\\\\\" . $table['namespace'] . \"\\\\app\\\\admin\\\\\";\n            $namespace_start_model = \"plugin\\\\\" . $table['namespace'] . \"\\\\app\\\\\";\n            $namespace_end = \"\\\\\" . $table['package_name'];\n            $url_path = 'app/' . $table['namespace'] . '/admin/' . $table['package_name'] . '/' . $table['class_name'];\n            $route = 'app/';\n        } else {\n            $namespace_start = \"app\\\\\" . $table['namespace'] . \"\\\\\";\n            $namespace_start_model = \"app\\\\\" . $table['namespace'] . \"\\\\\";\n            $namespace_end = \"\\\\\" . $table['package_name'];\n            $url_path = $table['namespace'] . '/' . $table['package_name'] . '/' . $table['class_name'];\n            $route = '';\n        }\n\n        $config = config('think-orm');\n\n        $data = $table->toArray();\n        $data['pk'] = $pk;\n        $data['namespace_start'] = $namespace_start;\n        $data['namespace_start_model'] = $namespace_start_model;\n        $data['namespace_end'] = $namespace_end;\n        $data['url_path'] = $url_path;\n        $data['route'] = $route;\n        $data['tables'] = [$data];\n        $data['columns'] = $columns;\n        $data['db_source'] = $config['default'] ?? 'mysql';\n\n        return $data;\n    }\n\n    /**\n     * 生成到模块\n     */\n    public function generateFile($id)\n    {\n        $table = $this->model->where('id', $id)->findOrEmpty();\n        if ($table->isEmpty()) {\n            throw new ApiException('请选择要生成的表');\n        }\n        $debug = config('app.debug', true);\n        if (!$debug) {\n            throw new ApiException('非调试模式下，不允许生成文件');\n        }\n        $this->updateMenu($table);\n        $this->genModule($id);\n        UserMenuCache::clearMenuCache();\n    }\n\n    /**\n     * 代码生成下载\n     */\n    public function generate($idsArr): array\n    {\n        $zip = new CodeZip();\n        $tables = $this->model->where('id', 'in', $idsArr)->select()->toArray();\n        foreach ($idsArr as $table_id) {\n            $data = $this->renderData($table_id);\n            $data['tables'] = $tables;\n            $codeEngine = new CodeEngine($data);\n            $codeEngine->generateTemp();\n        }\n\n        $filename = 'saiadmin.zip';\n        $download = $zip->compress();\n\n        return compact('filename', 'download');\n    }\n\n    /**\n     * 处理菜单列表\n     * @param $tables\n     */\n    public function updateMenu($tables)\n    {\n        /*不存在的情况下进行新建操作*/\n        $url_path = $tables['namespace'] . \":\" . $tables['package_name'] . ':' . $tables['business_name'];\n        $code = $tables['namespace'] . \"/\" . $tables['package_name'] . '/' . $tables['business_name'];\n        $path = $tables['package_name'] . '/' . $tables['business_name'];\n        $component = $tables['namespace'] . \"/\" . $tables['package_name'] . '/' . $tables['business_name'];\n\n        /*先获取一下已有的路由中是否包含当前ID的路由的核心信息*/\n        $model = new SystemMenu();\n        $tableMenu = $model->where('generate_id', $tables['id'])->findOrEmpty();\n        $fistMenu = [\n            'parent_id' => $tables['belong_menu_id'],\n            'name' => $tables['menu_name'],\n            'code' => $code,\n            'path' => $path,\n            'icon' => 'ri:home-2-line',\n            'component' => \"/plugin/$component/index\",\n            'type' => 2,\n            'sort' => 100,\n            'is_iframe' => 2,\n            'is_keep_alive' => 2,\n            'is_hidden' => 2,\n            'is_fixed_tab' => 2,\n            'is_full_page' => 2,\n            'generate_id' => $tables['id']\n        ];\n        if ($tableMenu->isEmpty()) {\n            $temp = SystemMenu::create($fistMenu);\n            $fistMenuId = $temp->id;\n        } else {\n            $fistMenu['id'] = $tableMenu['id'];\n            $tableMenu->save($fistMenu);\n            $fistMenuId = $tableMenu['id'];\n        }\n        /*开始进行子权限的判定操作*/\n        $childNodes = [\n            ['name' => '列表', 'key' => 'index'],\n            ['name' => '保存', 'key' => 'save'],\n            ['name' => '更新', 'key' => 'update'],\n            ['name' => '读取', 'key' => 'read'],\n            ['name' => '删除', 'key' => 'destroy'],\n        ];\n\n        foreach ($childNodes as $node) {\n            $nodeData = $model->where('parent_id', $fistMenuId)->where('generate_key', $node['key'])->findOrEmpty();\n            $childNodeData = [\n                'parent_id' => $fistMenuId,\n                'name' => $node['name'],\n                'slug' => \"$url_path:{$node['key']}\",\n                'type' => 3,\n                'sort' => 100,\n                'is_iframe' => 2,\n                'is_keep_alive' => 2,\n                'is_hidden' => 2,\n                'is_fixed_tab' => 2,\n                'is_full_page' => 2,\n                'generate_key' => $node['key']\n            ];\n            if (!empty($nodeData)) {\n                $childNodeData['id'] = $nodeData['id'];\n                $nodeData->save($childNodeData);\n            } else {\n                $menuModel = new SystemMenu();\n                $menuModel->save($childNodeData);\n            }\n        }\n    }\n\n    /**\n     * 获取数据表字段信息\n     * @param $table_id\n     * @return mixed\n     */\n    public function getTableColumns($table_id): mixed\n    {\n        $query = $this->columnLogic->where('table_id', $table_id);\n        return $this->columnLogic->getAll($query);\n    }\n\n    /**\n     * 编辑数据\n     * @param $id\n     * @param $data\n     * @return mixed\n     */\n    public function edit($id, $data): mixed\n    {\n        $columns = $data['columns'];\n\n        unset($data['columns']);\n\n        if (!empty($data['belong_menu_id'])) {\n            $data['belong_menu_id'] = is_array($data['belong_menu_id']) ? array_pop($data['belong_menu_id']) : $data['belong_menu_id'];\n        } else {\n            $data['belong_menu_id'] = 0;\n        }\n\n        $data['generate_menus'] = implode(',', $data['generate_menus']);\n\n        if (empty($data['options'])) {\n            unset($data['options']);\n        }\n\n        $data['options'] = json_encode($data['options'], JSON_UNESCAPED_UNICODE);\n\n        // 更新业务表\n        $this->update($data, ['id' => $id]);\n\n        // 更新业务字段表\n        foreach ($columns as $column) {\n            if ($column['options']) {\n                $column['options'] = json_encode($column['options'], JSON_NUMERIC_CHECK);\n            }\n            $this->columnLogic->update($column, ['id' => $column['id']]);\n        }\n\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/middleware/CheckAuth.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\middleware;\n\nuse Webman\\Http\\Request;\nuse Webman\\Http\\Response;\nuse Webman\\MiddlewareInterface;\nuse plugin\\saiadmin\\app\\cache\\UserAuthCache;\nuse plugin\\saiadmin\\app\\cache\\ReflectionCache;\nuse plugin\\saiadmin\\exception\\SystemException;\n\n/**\n * 权限检查中间件\n */\nclass CheckAuth implements MiddlewareInterface\n{\n\n    public function process(Request $request, callable $handler) : Response\n    {\n        $controller = $request->controller;\n        $action = $request->action;\n\n        // 通过反射获取控制器哪些方法不需要登录\n        $noNeedLogin = ReflectionCache::getNoNeedLogin($controller);\n\n        // 不登录访问，无需权限验证\n        if (in_array($action, $noNeedLogin)) {\n            return $handler($request);\n        }\n\n        // 登录信息\n        $token = getCurrentInfo();\n        if ($token === false) {\n            throw new SystemException('用户信息读取失败，无法访问或操作');\n        }\n\n        // 系统默认超级管理员，无需权限验证\n        if ($token['id'] === 1) {\n            return $handler($request);\n        }\n\n        // 2. 获取接口权限属性 (使用缓存类)\n        $permissions = ReflectionCache::getPermissionAttributes($controller, $action);\n\n        if (!empty($permissions) && !empty($permissions['slug'])) {\n            // 用户权限缓存\n            $auth = UserAuthCache::getUserAuth($token['id']);\n\n            if (!$this->checkPermissions($permissions, $auth)) {\n                throw new SystemException('权限不足，无法访问或操作');\n            }\n        }\n\n        return $handler($request);\n    }\n\n    /**\n     * 检查权限\n     */\n    private function checkPermissions(array $attr, array $userPermissions): bool\n    {\n        // 直接对比 slug\n        return in_array($attr['slug'], $userPermissions);\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/middleware/CheckLogin.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\middleware;\n\nuse Webman\\Http\\Request;\nuse Webman\\Http\\Response;\nuse Webman\\MiddlewareInterface;\nuse Tinywan\\Jwt\\JwtToken;\nuse plugin\\saiadmin\\app\\cache\\ReflectionCache;\nuse plugin\\saiadmin\\exception\\ApiException;\n\n/**\n * 登录检查中间件\n */\nclass CheckLogin implements MiddlewareInterface\n{\n    public function process(Request $request, callable $handler): Response\n    {\n        // 通过反射获取控制器哪些方法不需要登录\n        $noNeedLogin = ReflectionCache::getNoNeedLogin($request->controller);\n        // 访问的方法需要登录\n        if (!in_array($request->action, $noNeedLogin)) {\n            try {\n                $token = JwtToken::getExtend();\n            } catch (\\Throwable $e) {\n                throw new ApiException('您的登录凭证错误或者已过期，请重新登录', 401);\n            }\n            if ($token['plat'] !== 'saiadmin') {\n                throw new ApiException('登录凭证校验失败');\n            }\n            $request->setHeader('check_login', true);\n            $request->setHeader('check_admin', $token);\n        }\n        return $handler($request);\n    }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/middleware/CrossDomain.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\middleware;\n\nuse Webman\\Http\\Request;\nuse Webman\\Http\\Response;\nuse Webman\\MiddlewareInterface;\n\n/**\n * 跨域中间件\n */\nclass CrossDomain implements MiddlewareInterface\n{\n    public function process(Request $request, callable $handler) : Response\n    {\n        // 如果是options请求则返回一个空响应，否则继续向洋葱芯穿越，并得到一个响应\n        $response = $request->method() == 'OPTIONS' ? response('') : $handler($request);\n\n        // 给响应添加跨域相关的http头\n        $response->withHeaders([\n            'Access-Control-Allow-Credentials' => 'true',\n            'Access-Control-Allow-Origin' => $request->header('origin', '*'),\n            'Access-Control-Allow-Methods' => $request->header('access-control-request-method', '*'),\n            'Access-Control-Allow-Headers' => $request->header('access-control-request-headers', '*'),\n        ]);\n\n        return $response;\n    }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/middleware/SystemLog.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\middleware;\n\nuse Webman\\Event\\Event;\nuse Webman\\Http\\Request;\nuse Webman\\Http\\Response;\nuse Webman\\MiddlewareInterface;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\app\\cache\\ReflectionCache;\n\nclass SystemLog implements MiddlewareInterface\n{\n    /**\n     * @param Request $request\n     * @param callable $handler\n     * @return Response\n     */\n    public function process(Request $request, callable $handler): Response\n    {\n        // 通过反射获取控制器哪些方法不需要登录\n        $noNeedLogin = ReflectionCache::getNoNeedLogin($request->controller);\n        // 访问的方法需要登录\n        if (!in_array($request->action, $noNeedLogin)) {\n            try {\n                // 记录日志\n                Event::emit('user.operateLog', true);\n            } catch (\\Throwable $e) {\n                throw new ApiException('登录凭获取失败，请检查');\n            }\n        }\n        return $handler($request);\n    }\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/system/SystemAttachment.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 附件模型\n *\n * sa_system_attachment 附件信息表\n *\n * @property  $id 主键\n * @property  $category_id 文件分类\n * @property  $storage_mode 存储模式\n * @property  $origin_name 原文件名\n * @property  $object_name 新文件名\n * @property  $hash 文件hash\n * @property  $mime_type 资源类型\n * @property  $storage_path 存储目录\n * @property  $suffix 文件后缀\n * @property  $size_byte 字节数\n * @property  $size_info 文件大小\n * @property  $url url地址\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemAttachment extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_attachment';\n\n    /**\n     * 原文件名搜索\n     */\n    public function searchOriginNameAttr($query, $value)\n    {\n        $query->where('origin_name', 'like', '%' . $value . '%');\n    }\n\n    /**\n     * 文件类型搜索\n     */\n    public function searchMimeTypeAttr($query, $value)\n    {\n        $query->where('mime_type', 'like', $value . '/%');\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/system/SystemCategory.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 附件分类模型\n *\n * sa_system_category 附件分类表\n *\n * @property  $id 分类ID\n * @property  $parent_id 父id\n * @property  $level 组集关系\n * @property  $category_name 分类名称\n * @property  $sort 排序\n * @property  $status 状态\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemCategory extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_category';\n\n    /**\n     * 分类名称搜索\n     */\n    public function searchCategoryNameAttr($query, $value)\n    {\n        $query->where('category_name', 'like', '%' . $value . '%');\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/system/SystemConfig.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 参数配置模型\n *\n * sa_system_config 参数配置信息表\n *\n * @property  $id 编号\n * @property  $group_id 组id\n * @property  $key 配置键名\n * @property  $value 配置值\n * @property  $name 配置名称\n * @property  $input_type 数据输入类型\n * @property  $config_select_data 配置选项数据\n * @property  $sort 排序\n * @property  $remark 备注\n * @property  $created_by 创建人\n * @property  $updated_by 更新人\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemConfig extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_config';\n\n    public function getConfigSelectDataAttr($value)\n    {\n        return json_decode($value ?? '', true);\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/system/SystemConfigGroup.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 参数配置分组模型\n * \n * sa_system_config_group 参数配置分组表\n *\n * @property  $id 主键\n * @property  $name 字典名称\n * @property  $code 字典标示\n * @property  $remark 备注\n * @property  $created_by 创建人\n * @property  $updated_by 更新人\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemConfigGroup extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_config_group';\n\n    /**\n     * 关联配置列表\n     */\n    public function configs()\n    {\n        return $this->hasMany(SystemConfig::class, 'group_id', 'id');\n    }\n\n    /**\n     * 名称搜索\n     */\n    public function searchNameAttr($query, $value)\n    {\n        return $query->where('name', 'like', '%' . $value . '%');\n    }\n\n    /**\n     * 编码搜索\n     */\n    public function searchCodeAttr($query, $value)\n    {\n        return $query->where('code', 'like', '%' . $value . '%');\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/system/SystemDept.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 部门模型\n *\n * sa_system_dept 部门表\n *\n * @property  $id 编号\n * @property  $parent_id 父级ID，0为根节点\n * @property  $name 部门名称\n * @property  $code 部门编码\n * @property  $leader_id 部门负责人ID\n * @property  $level 祖级列表，格式: 0,1,5,\n * @property  $sort 排序，数字越小越靠前\n * @property  $status 状态: 1启用, 0禁用\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemDept extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_dept';\n\n    /**\n     * 权限范围\n     */\n    public function scopeAuth($query, $value)\n    {\n        if (!empty($value)) {\n            $deptIds = [$value['id']];\n            $deptLevel = $value['level'] . $value['id'] . ',';\n            $ids = static::whereLike('level', $deptLevel . '%')->column('id');\n            $deptIds = array_merge($deptIds, $ids);\n            $query->whereIn('id', $deptIds);\n        }\n    }\n\n    /**\n     * 部门领导\n     */\n    public function leader()\n    {\n        return $this->hasOne(SystemUser::class, 'id', 'leader_id');\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/system/SystemDictData.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 字典数据模型\n *\n * sa_system_dict_data 字典数据表\n *\n * @property  $id 主键\n * @property  $type_id 字典类型ID\n * @property  $label 字典标签\n * @property  $value 字典值\n * @property  $color 字典颜色\n * @property  $code 字典标示\n * @property  $sort 排序\n * @property  $status 状态\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemDictData extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_dict_data';\n\n    /**\n     * 关键字搜索\n     */\n    public function searchKeywordsAttr($query, $value)\n    {\n        $query->where('label|code', 'LIKE', \"%$value%\");\n    }\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/system/SystemDictType.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 字典类型模型\n *\n * sa_system_dict_type 字典类型表\n *\n * @property  $id 主键\n * @property  $name 字典名称\n * @property  $code 字典标示\n * @property  $status 状态\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemDictType extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_dict_type';\n\n    /**\n     * 关联字典数据\n     */\n    public function dicts()\n    {\n        return $this->hasMany(SystemDictData::class, 'type_id', 'id');\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/system/SystemLoginLog.php",
    "content": "<?php\n\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 登录日志模型\n *\n * sa_system_login_log 登录日志表\n *\n * @property  $id 主键\n * @property  $username 用户名\n * @property  $ip 登录IP地址\n * @property  $ip_location IP所属地\n * @property  $os 操作系统\n * @property  $browser 浏览器\n * @property  $status 登录状态\n * @property  $message 提示消息\n * @property  $login_time 登录时间\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 更新时间\n */\nclass SystemLoginLog extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_login_log';\n\n    /**\n     * 时间范围搜索\n     */\n    public function searchLoginTimeAttr($query, $value)\n    {\n        $query->whereTime('login_time', 'between', $value);\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/system/SystemMail.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 邮件记录模型\n *\n * sa_system_mail 邮件记录\n *\n * @property  $id 编号\n * @property  $gateway 网关\n * @property  $from 发送人\n * @property  $email 接收人\n * @property  $code 验证码\n * @property  $content 邮箱内容\n * @property  $status 发送状态\n * @property  $response 返回结果\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemMail extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_mail';\n\n    /**\n     * 发送人搜索\n     */\n    public function searchFromAttr($query, $value)\n    {\n        $query->where('from', 'like', '%' . $value . '%');\n    }\n\n    /**\n     * 接收人搜索\n     */\n    public function searchEmailAttr($query, $value)\n    {\n        $query->where('email', 'like', '%' . $value . '%');\n    }\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/system/SystemMenu.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 菜单模型\n *\n * sa_system_menu 菜单权限表\n *\n * @property  $id \n * @property  $parent_id 父级ID\n * @property  $name 菜单名称\n * @property  $code 组件名称\n * @property  $slug 权限标识，如 user:list, user:add\n * @property  $type 类型: 1目录, 2菜单, 3按钮/API\n * @property  $path 路由地址或API路径\n * @property  $component 前端组件路径，如 layout/User\n * @property  $method 请求方式\n * @property  $icon 图标\n * @property  $sort 排序\n * @property  $link_url 外部链接\n * @property  $is_iframe 是否iframe\n * @property  $is_keep_alive 是否缓存\n * @property  $is_hidden 是否隐藏\n * @property  $is_fixed_tab 是否固定标签页\n * @property  $is_full_page 是否全屏\n * @property  $generate_id 生成id\n * @property  $generate_key 生成key\n * @property  $status 状态\n * @property  $remark \n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemMenu extends BaseModel\n{\n    // 完整数据库表名称\n    protected $table = 'sa_system_menu';\n    // 主键\n    protected $pk = 'id';\n\n    /**\n     * Id搜索\n     */\n    public function searchIdAttr($query, $value)\n    {\n        $query->whereIn('id', $value);\n    }\n\n    public function searchNameAttr($query, $value)\n    {\n        $query->where('name', 'like', '%' . $value . '%');\n    }\n\n    public function searchPathAttr($query, $value)\n    {\n        $query->where('path', 'like', '%' . $value . '%');\n    }\n\n    public function searchMenuAttr($query, $value)\n    {\n        if (!empty($value)) {\n            $query->whereIn('type', [1, 2]);\n        }\n    }\n\n    /**\n     * Type搜索\n     */\n    public function searchTypeAttr($query, $value)\n    {\n        if (is_array($value)) {\n            $query->whereIn('type', $value);\n        } else {\n            $query->where('type', $value);\n        }\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/system/SystemOperLog.php",
    "content": "<?php\n\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 操作日志模型\n *\n * sa_system_oper_log 操作日志表\n *\n * @property  $id 主键\n * @property  $username 用户名\n * @property  $app 应用名称\n * @property  $method 请求方式\n * @property  $router 请求路由\n * @property  $service_name 业务名称\n * @property  $ip 请求IP地址\n * @property  $ip_location IP所属地\n * @property  $request_data 请求数据\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 更新时间\n */\nclass SystemOperLog extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_oper_log';\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/system/SystemPost.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 岗位模型\n *\n * sa_system_post 岗位信息表\n *\n * @property  $id 主键\n * @property  $name 岗位名称\n * @property  $code 岗位代码\n * @property  $sort 排序\n * @property  $status 状态\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemPost extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_post';\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/system/SystemRole.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 角色模型\n *\n * sa_system_role 角色表\n *\n * @property  $id \n * @property  $name 角色名称\n * @property  $code 角色标识，如: hr_manager\n * @property  $level 角色级别：用于行政控制，不可操作级别大于自己的角色\n * @property  $data_scope 数据范围: 1全部, 2本部门及下属, 3本部门, 4仅本人, 5自定义\n * @property  $remark 备注\n * @property  $sort \n * @property  $status 状态: 1启用, 0禁用\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemRole extends BaseModel\n{\n\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    /**\n     * 数据表完整名称\n     * @var string\n     */\n    protected $table = 'sa_system_role';\n\n    /**\n     * 权限范围\n     */\n    public function scopeAuth($query, $value)\n    {\n        $id = $value['id'];\n        $roles = $value['roles'];\n        if ($id > 1) {\n            $ids = [];\n            foreach ($roles as $item) {\n                $ids[] = $item['id'];\n                $temp = static::whereRaw('FIND_IN_SET(\"' . $item['id'] . '\", level) > 0')->column('id');\n                $ids = array_merge($ids, $temp);\n            }\n            $query->where('id', 'in', array_unique($ids));\n        }\n    }\n\n    /**\n     * 通过中间表获取菜单\n     */\n    public function menus()\n    {\n        return $this->belongsToMany(SystemMenu::class, SystemRoleMenu::class, 'menu_id', 'role_id');\n    }\n\n    /**\n     * 通过中间表获取部门\n     */\n    public function depts()\n    {\n        return $this->belongsToMany(SystemDept::class, SystemRoleDept::class, 'dept_id', 'role_id');\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/system/SystemRoleDept.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse think\\model\\Pivot;\n\n/**\n * 角色部门关联模型\n *\n * sa_system_role_dept 角色-自定义数据权限关联\n *\n * @property  $id \n * @property  $role_id \n * @property  $dept_id \n */\nclass SystemRoleDept extends Pivot\n{\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_role_dept';\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/system/SystemRoleMenu.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse think\\model\\Pivot;\n\n/**\n * 角色菜单关联模型\n *\n * sa_system_role_menu 角色权限关联\n *\n * @property  $id \n * @property  $role_id \n * @property  $menu_id \n */\nclass SystemRoleMenu extends Pivot\n{\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_role_menu';\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/system/SystemUser.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 用户信息模型\n *\n * sa_system_user 用户表\n *\n * @property  $id \n * @property  $username 登录账号\n * @property  $password 加密密码\n * @property  $realname 真实姓名\n * @property  $gender 性别\n * @property  $avatar 头像\n * @property  $email 邮箱\n * @property  $phone 手机号\n * @property  $signed 个性签名\n * @property  $dashboard 工作台\n * @property  $dept_id 主归属部门\n * @property  $is_super 是否超级管理员: 1是\n * @property  $status 状态: 1启用, 2禁用\n * @property  $remark 备注\n * @property  $login_time 最后登录时间\n * @property  $login_ip 最后登录IP\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass SystemUser extends BaseModel\n{\n\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    /**\n     * 数据表完整名称\n     * @var string\n     */\n    protected $table = 'sa_system_user';\n\n    public function searchKeywordAttr($query, $value)\n    {\n        if ($value) {\n            $query->where('username|realname|phone', 'like', '%' . $value . '%');\n        }\n    }\n\n    /**\n     * 权限范围 - 过滤部门用户\n     */\n    public function scopeAuth($query, $value)\n    {\n        if (!empty($value)) {\n            $deptIds = [$value['id']];\n            $deptLevel = $value['level'] . $value['id'] . ',';\n            $dept_ids = SystemDept::whereLike('level', $deptLevel . '%')->column('id');\n            $deptIds = array_merge($deptIds, $dept_ids);\n            $query->whereIn('dept_id', $deptIds);\n        }\n    }\n\n    /**\n     * 通过中间表关联角色\n     */\n    public function roles()\n    {\n        return $this->belongsToMany(SystemRole::class, SystemUserRole::class, 'role_id', 'user_id');\n    }\n\n    /**\n     * 通过中间表关联岗位\n     */\n    public function posts()\n    {\n        return $this->belongsToMany(SystemPost::class, SystemUserPost::class, 'post_id', 'user_id');\n    }\n\n    /**\n     * 通过中间表关联部门\n     */\n    public function depts()\n    {\n        return $this->belongsTo(SystemDept::class, 'dept_id', 'id');\n    }\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/system/SystemUserPost.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse think\\model\\Pivot;\n\n/**\n * 用户岗位关联模型\n *\n * sa_system_user_post 用户与岗位关联表\n *\n * @property  $id 主键\n * @property  $user_id 用户主键\n * @property  $post_id 岗位主键\n */\nclass SystemUserPost extends Pivot\n{\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_user_post';\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/system/SystemUserRole.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\system;\n\nuse think\\model\\Pivot;\n\n/**\n * 用户角色关联模型\n *\n * sa_system_user_role 用户角色关联\n *\n * @property  $id \n * @property  $user_id \n * @property  $role_id \n */\nclass SystemUserRole extends Pivot\n{\n    protected $pk = 'id';\n\n    protected $table = 'sa_system_user_role';\n\n    /**\n     * 获取角色id\n     * @param mixed $user_id\n     * @return array\n     */\n    public static function getRoleIds($user_id): array\n    {\n        return static::where('user_id', $user_id)->column('role_id');\n    }\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/tool/Crontab.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\tool;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 定时任务模型\n *\n * sa_tool_crontab 定时任务信息表\n *\n * @property  $id 主键\n * @property  $name 任务名称\n * @property  $type 任务类型\n * @property  $target 调用任务字符串\n * @property  $parameter 调用任务参数\n * @property  $task_style 执行类型\n * @property  $rule 任务执行表达式\n * @property  $status 状态\n * @property  $remark 备注\n * @property  $created_by 创建者\n * @property  $updated_by 更新者\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass Crontab extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_tool_crontab';\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/tool/CrontabLog.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\tool;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 定时任务日志模型\n *\n * sa_tool_crontab_log 定时任务执行日志表\n *\n * @property  $id 主键\n * @property  $crontab_id 任务ID\n * @property  $name 任务名称\n * @property  $target 任务调用目标字符串\n * @property  $parameter 任务调用参数\n * @property  $exception_info 异常信息\n * @property  $status 执行状态\n * @property  $create_time 创建时间\n * @property  $update_time 修改时间\n */\nclass CrontabLog extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_tool_crontab_log';\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/tool/GenerateColumns.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\tool;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 代码生成业务字段模型\n */\nclass GenerateColumns extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_tool_generate_columns';\n\n    public function getOptionsAttr($value)\n    {\n        return json_decode($value ?? '', true);\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/model/tool/GenerateTables.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\model\\tool;\n\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n\n/**\n * 代码生成业务模型\n */\nclass GenerateTables extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n    protected $pk = 'id';\n\n    protected $table = 'sa_tool_generate_tables';\n\n    public function getOptionsAttr($value)\n    {\n        return json_decode($value ?? '', true);\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/app/validate/system/SystemCategoryValidate.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\validate\\system;\n\nuse plugin\\saiadmin\\basic\\BaseValidate;\n\n/**\n * 附件分类验证器\n */\nclass SystemCategoryValidate extends BaseValidate\n{\n    /**\n     * 定义验证规则\n     */\n    protected $rule = [\n        'category_name' => 'require',\n    ];\n\n    /**\n     * 定义错误信息\n     */\n    protected $message = [\n        'category_name' => '分类名称必须填写',\n    ];\n\n    /**\n     * 定义场景\n     */\n    protected $scene = [\n        'add' => [\n            'category_name',\n        ],\n        'edit' => [\n            'category_name',\n        ],\n    ];\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/validate/system/SystemConfigGroupValidate.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\validate\\system;\n\nuse plugin\\saiadmin\\basic\\BaseValidate;\nuse plugin\\saiadmin\\app\\model\\system\\SystemConfigGroup;\n\n/**\n * 字典类型验证器\n */\nclass SystemConfigGroupValidate extends BaseValidate\n{\n    /**\n     * 定义验证规则\n     */\n    protected $rule = [\n        'name' => 'require|max:16',\n        'code' => 'require|alphaDash|unique:' . SystemConfigGroup::class,\n    ];\n\n    /**\n     * 定义错误信息\n     */\n    protected $message = [\n        'name.require' => '组名称必须填写',\n        'name.max' => '组名称最多不能超过16个字符',\n        'name.chs' => '组名称必须是中文',\n        'code.require' => '组标识必须填写',\n        'code.alphaDash' => '组标识只能由英文字母组成',\n        'code.unique' => '配置组标识不能重复',\n    ];\n\n    /**\n     * 定义场景\n     */\n    protected $scene = [\n        'save' => [\n            'name',\n            'code',\n        ],\n        'update' => [\n            'name',\n            'code',\n        ],\n    ];\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/validate/system/SystemConfigValidate.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\validate\\system;\n\nuse plugin\\saiadmin\\basic\\BaseValidate;\n\n/**\n * 字典类型验证器\n */\nclass SystemConfigValidate extends BaseValidate\n{\n    /**\n     * 定义验证规则\n     */\n    protected $rule = [\n        'name' => 'require',\n        'key' => 'require',\n        'group_id' => 'require',\n        'input_type' => 'require',\n    ];\n\n    /**\n     * 定义错误信息\n     */\n    protected $message = [\n        'name' => '配置标题必须填写',\n        'key' => '配置标识必须填写',\n        'group_id' => '所属组必须填写',\n        'input_type' => '输入组件必须填写',\n    ];\n\n    /**\n     * 定义场景\n     */\n    protected $scene = [\n        'save' => [\n            'name',\n            'key',\n            'group_id',\n            'input_type',\n        ],\n        'update' => [\n            'name',\n            'key',\n            'group_id',\n            'input_type',\n        ],\n    ];\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/validate/system/SystemCrontabValidate.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saithink [ saithink快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\validate\\system;\n\nuse plugin\\saiadmin\\basic\\BaseValidate;\n\n/**\n * 字典类型验证器\n */\nclass SystemCrontabValidate extends BaseValidate\n{\n    /**\n     * 定义验证规则\n     */\n    protected $rule = [\n        'name' => 'require',\n        'type' => 'require',\n        'rule' => 'require',\n        'target' => 'require',\n        'status' => 'require',\n    ];\n\n    /**\n     * 定义错误信息\n     */\n    protected $message = [\n        'name' => '任务名称必须填写',\n        'type' => '任务类型必须填写',\n        'rule' => '任务规则必须填写',\n        'target' => '调用目标必须填写',\n        'status' => '状态必须填写',\n    ];\n\n    /**\n     * 定义场景\n     */\n    protected $scene = [\n        'save' => [\n            'name',\n            'type',\n            'rule',\n            'target',\n            'status',\n        ],\n        'update' => [\n            'name',\n            'type',\n            'rule',\n            'target',\n            'status',\n        ],\n    ];\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/validate/system/SystemDeptValidate.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\validate\\system;\n\nuse plugin\\saiadmin\\basic\\BaseValidate;\n\n/**\n * 部门验证器\n */\nclass SystemDeptValidate extends BaseValidate\n{\n    /**\n     * 定义验证规则\n     */\n    protected $rule = [\n        'name' => 'require',\n        'status' => 'require',\n    ];\n\n    /**\n     * 定义错误信息\n     */\n    protected $message = [\n        'name' => '部门名称必须填写',\n        'status' => '状态必须填写',\n    ];\n\n    /**\n     * 定义场景\n     */\n    protected $scene = [\n        'add' => [\n            'name',\n            'status',\n        ],\n        'edit' => [\n            'name',\n            'status',\n        ],\n    ];\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/validate/system/SystemDictDataValidate.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\validate\\system;\n\nuse plugin\\saiadmin\\basic\\BaseValidate;\n\n/**\n * 字典数据验证器\n */\nclass SystemDictDataValidate extends BaseValidate\n{\n    /**\n     * 定义验证规则\n     */\n    protected $rule = [\n        'label' => 'require',\n        'value' => 'require',\n        'status' => 'require',\n        'type_id' => 'require',\n        'code' => 'require',\n    ];\n\n    /**\n     * 定义错误信息\n     */\n    protected $message = [\n        'label' => '字典标签必须填写',\n        'value' => '字典键值必须填写',\n        'status' => '状态必须填写',\n        'type_id' => '字典类型必须填写',\n        'code' => '字典标识必须填写',\n    ];\n\n    /**\n     * 定义场景\n     */\n    protected $scene = [\n        'save' => [\n            'label',\n            'value',\n            'status',\n            'type_id',\n        ],\n        'update' => [\n            'label',\n            'value',\n            'status',\n        ],\n    ];\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/validate/system/SystemDictTypeValidate.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\validate\\system;\n\nuse plugin\\saiadmin\\basic\\BaseValidate;\nuse plugin\\saiadmin\\app\\model\\system\\SystemDictType;\n\n/**\n * 字典类型验证器\n */\nclass SystemDictTypeValidate extends BaseValidate\n{\n    /**\n     * 定义验证规则\n     */\n    protected $rule = [\n        'name' => 'require|max:16',\n        'code' => 'require|alphaDash|unique:' . SystemDictType::class,\n        'status' => 'require',\n    ];\n\n    /**\n     * 定义错误信息\n     */\n    protected $message = [\n        'name.require' => '字典名称必须填写',\n        'name.max' => '字典名称最多不能超过16个字符',\n        'code.require' => '字典标识必须填写',\n        'code.alphaDash' => '字典标识只能由英文字母组成',\n        'code.unique' => '字典标识已存在',\n        'status' => '状态必须填写',\n    ];\n\n    /**\n     * 定义场景\n     */\n    protected $scene = [\n        'save' => [\n            'name',\n            'code',\n            'status',\n        ],\n        'update' => [\n            'name',\n            'code',\n            'status',\n        ],\n    ];\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/validate/system/SystemMailValidate.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\validate\\system;\n\nuse plugin\\saiadmin\\basic\\BaseValidate;\n\n/**\n * 邮件验证器\n */\nclass SystemMailValidate extends BaseValidate\n{\n    /**\n     * 定义验证规则\n     */\n    protected $rule = [\n        'gateway' => 'require',\n        'from' => 'require',\n        'email' => 'require',\n    ];\n\n    /**\n     * 定义错误信息\n     */\n    protected $message = [\n        'gateway' => '网关必须填写',\n        'from' => '发件人必须填写',\n        'email' => '接收人必须填写',\n    ];\n\n    /**\n     * 定义场景\n     */\n    protected $scene = [\n        'save' => [\n            'gateway',\n            'from',\n            'email',\n        ],\n        'update' => [\n            'gateway',\n            'from',\n            'email',\n        ],\n    ];\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/validate/system/SystemMenuValidate.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\validate\\system;\n\nuse plugin\\saiadmin\\basic\\BaseValidate;\n\n/**\n * 菜单验证器\n */\nclass SystemMenuValidate extends BaseValidate\n{\n    /**\n     * 定义验证规则\n     */\n    protected $rule = [\n        'name' => 'require|max:16',\n        'status' => 'require',\n    ];\n\n    /**\n     * 定义错误信息\n     */\n    protected $message = [\n        'name.require' => '菜单名称必须填写',\n        'name.max' => '菜单名称最多不能超过16个字符',\n        'status' => '状态必须填写',\n    ];\n\n    /**\n     * 定义场景\n     */\n    protected $scene = [\n        'save' => [\n            'name',\n            'status',\n        ],\n        'update' => [\n            'name',\n            'status',\n        ],\n    ];\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/validate/system/SystemNoticeValidate.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\validate\\system;\n\nuse plugin\\saiadmin\\basic\\BaseValidate;\n\n/**\n * 系统公告验证器\n */\nclass SystemNoticeValidate extends BaseValidate\n{\n    /**\n     * 定义验证规则\n     */\n    protected $rule = [\n        'title' => 'require|min:4',\n        'content' => 'require',\n        'type' => 'require',\n    ];\n\n    /**\n     * 定义错误信息\n     */\n    protected $message = [\n        'title.require' => '公告标题必须填写',\n        'title.min' => '公告标题必须大于4个字符',\n        'content' => '公告内容必须填写',\n        'type' => '公告类型必须填写',\n    ];\n\n    /**\n     * 定义场景\n     */\n    protected $scene = [\n        'save' => [\n            'title',\n            'content',\n            'type',\n        ],\n        'update' => [\n            'title',\n            'content',\n            'type',\n        ],\n    ];\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/validate/system/SystemPostValidate.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\validate\\system;\n\nuse plugin\\saiadmin\\basic\\BaseValidate;\n\n/**\n * 用户角色验证器\n */\nclass SystemPostValidate extends BaseValidate\n{\n    /**\n     * 定义验证规则\n     */\n    protected $rule = [\n        'name' => 'require',\n        'code' => 'require',\n        'status' => 'require',\n    ];\n\n    /**\n     * 定义错误信息\n     */\n    protected $message = [\n        'name' => '岗位名称必须填写',\n        'code' => '岗位标识必须填写',\n        'status' => '状态必须填写',\n    ];\n\n    /**\n     * 定义场景\n     */\n    protected $scene = [\n        'save' => [\n            'name',\n            'code',\n            'status',\n        ],\n        'update' => [\n            'name',\n            'code',\n            'status',\n        ],\n    ];\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/validate/system/SystemRoleValidate.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\validate\\system;\n\nuse plugin\\saiadmin\\basic\\BaseValidate;\nuse plugin\\saiadmin\\app\\model\\system\\SystemRole;\n\n/**\n * 用户角色验证器\n */\nclass SystemRoleValidate extends BaseValidate\n{\n    /**\n     * 定义验证规则\n     */\n    protected $rule = [\n        'name' => 'require|max:16',\n        'code' => 'require|alphaDash|unique:' . SystemRole::class,\n        'status' => 'require',\n    ];\n\n    /**\n     * 定义错误信息\n     */\n    protected $message = [\n        'name.require' => '角色名称必须填写',\n        'name.max' => '角色名称最多不能超过16个字符',\n        'code.require' => '角色标识必须填写',\n        'code.alphaDash' => '角色标识只能由英文字母组成',\n        'code.unique' => '角色标识不能重复',\n        'status' => '状态必须填写',\n    ];\n\n    /**\n     * 定义场景\n     */\n    protected $scene = [\n        'add' => [\n            'name',\n            'code',\n            'status',\n        ],\n        'edit' => [\n            'name',\n            'code',\n            'status',\n        ],\n    ];\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/validate/system/SystemUserValidate.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\validate\\system;\n\nuse plugin\\saiadmin\\basic\\BaseValidate;\nuse plugin\\saiadmin\\app\\model\\system\\SystemUser;\n\n/**\n * 用户信息验证器\n */\nclass SystemUserValidate extends BaseValidate\n{\n    /**\n     * 定义验证规则\n     */\n    protected $rule = [\n        'username' => 'require|max:16|unique:' . SystemUser::class,\n        'password' => 'require|min:6|max:16',\n        'role_ids' => 'require',\n    ];\n\n    /**\n     * 定义错误信息\n     */\n    protected $message = [\n        'username.require' => '用户名必须填写',\n        'username.max' => '用户名最多不能超过16个字符',\n        'username.unique' => '用户名不能重复',\n        'password.require' => '密码必须填写',\n        'password.min' => '密码最少为6位',\n        'password.max' => '密码长度不能超过16位',\n        'role_ids' => '角色必须填写',\n    ];\n\n    /**\n     * 定义场景\n     */\n    protected $scene = [\n        'save' => [\n            'username',\n            'password',\n            'role_ids',\n        ],\n        'update' => [\n            'username',\n            'role_ids',\n        ],\n    ];\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/validate/tool/CrontabValidate.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\validate\\tool;\n\nuse plugin\\saiadmin\\basic\\BaseValidate;\n\n/**\n * 字典类型验证器\n */\nclass CrontabValidate extends BaseValidate\n{\n    /**\n     * 定义验证规则\n     */\n    protected $rule = [\n        'name' => 'require',\n        'type' => 'require',\n        'target' => 'require',\n        'status' => 'require',\n    ];\n\n    /**\n     * 定义错误信息\n     */\n    protected $message = [\n        'name' => '任务名称必须填写',\n        'type' => '任务类型必须填写',\n        'target' => '调用目标必须填写',\n        'status' => '状态必须填写',\n    ];\n\n    /**\n     * 定义场景\n     */\n    protected $scene = [\n        'save' => [\n            'name',\n            'type',\n            'target',\n            'status',\n        ],\n        'update' => [\n            'name',\n            'type',\n            'target',\n            'status',\n        ],\n    ];\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/validate/tool/GenerateTablesValidate.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\app\\validate\\tool;\n\nuse plugin\\saiadmin\\basic\\BaseValidate;\n\n/**\n * 用户角色验证器\n */\nclass GenerateTablesValidate extends BaseValidate\n{\n    /**\n     * 定义验证规则\n     */\n    protected $rule = [\n        'table_name' => 'require',\n        'table_comment' => 'require',\n        'class_name' => 'require|alphaDash',\n        'business_name' => 'require|alphaDash',\n        'template' => 'require',\n        'namespace' => 'require',\n        'menu_name' => 'require',\n    ];\n\n    /**\n     * 定义错误信息\n     */\n    protected $message = [\n        'table_name' => '表名称必须填写',\n        'table_comment' => '表描述必须填写',\n        'class_name.require' => '实体类必须填写',\n        'class_name.alphaDash' => '实体类必须是英文',\n        'business_name.require' => '实体别名必须填写',\n        'business_name.alphaDash' => '实体别名必须是英文',\n        'template' => '模板必须填写',\n        'namespace' => '命名空间必须填写',\n        'menu_name' => '菜单名称必须填写',\n    ];\n\n    /**\n     * 定义场景\n     */\n    protected $scene = [\n        'save' => [\n            'table_name',\n            'table_comment',\n            'class_name',\n            'business_name',\n            'template',\n            'namespace',\n            'menu_name',\n        ],\n        'update' => [\n            'table_name',\n            'table_comment',\n            'class_name',\n            'business_name',\n            'template',\n            'namespace',\n            'menu_name',\n        ]\n    ];\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/app/view/install/error.html",
    "content": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>安装错误</title>\n    <link href=\"/app/saiadmin/assets/bootstrap.min.css\" rel=\"stylesheet\">\n    <style>\n        :root {\n            --primary-gradient: linear-gradient(135deg, #7166F0 0%, #8F85F3 100%);\n            --error-gradient: linear-gradient(135deg, #7166F0 0%, #8F85F3 100%);\n            --bg-gradient: linear-gradient(135deg, #F8FAFC 0%, #F1F5F9 100%);\n            --card-bg: rgba(255, 255, 255, 0.95);\n            --text-primary: #1E293B;\n            --text-secondary: #475569;\n            --border-color: rgba(113, 102, 240, 0.1);\n        }\n\n        body {\n            background: var(--bg-gradient);\n            min-height: 100vh;\n            margin: 0;\n            padding: 0;\n            font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n            display: flex;\n            flex-direction: column;\n            color: var(--text-primary);\n        }\n\n        .logo-section {\n            text-align: left;\n            padding: 1rem 2rem;\n            background: var(--card-bg);\n            box-shadow: 0 2px 15px rgba(0, 0, 0, 0.05);\n            backdrop-filter: blur(10px);\n            border-bottom: 1px solid var(--border-color);\n            position: fixed;\n            top: 0;\n            left: 0;\n            right: 0;\n            z-index: 1000;\n            display: flex;\n            align-items: center;\n            gap: 1rem;\n        }\n\n        .logo-icon {\n            animation: pulse 2s infinite;\n            flex-shrink: 0;\n        }\n\n        .logo-icon img {\n            width: 40px;\n            height: 40px;\n            border-radius: 10px;\n            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);\n            transition: transform 0.3s ease;\n        }\n\n        .logo-icon img:hover {\n            transform: scale(1.05);\n        }\n\n        .logo-section h1 {\n            font-size: 1.4rem;\n            background: var(--primary-gradient);\n            -webkit-background-clip: text;\n            -webkit-text-fill-color: transparent;\n            margin: 0;\n            font-weight: 700;\n            letter-spacing: 0.5px;\n            white-space: nowrap;\n            overflow: hidden;\n            text-overflow: ellipsis;\n        }\n\n        .main-content {\n            flex: 1;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            padding: 2rem;\n            margin-top: 70px;\n        }\n\n        .install-wrapper {\n            width: 100%;\n            max-width: 800px;\n            animation: fadeIn 0.8s ease-out;\n        }\n\n        .error-container {\n            text-align: center;\n            padding: 3rem;\n            background: var(--card-bg);\n            border-radius: 20px;\n            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05);\n            backdrop-filter: blur(10px);\n            border: 1px solid var(--border-color);\n            transform: translateY(0);\n            transition: all 0.3s ease;\n        }\n\n        .error-container:hover {\n            transform: translateY(-5px);\n            box-shadow: 0 15px 35px rgba(0, 0, 0, 0.08);\n        }\n\n        .error-icon {\n            font-size: 5rem;\n            margin-bottom: 1.5rem;\n            animation: shake 0.5s ease-in-out;\n            color: #7166F0;\n        }\n\n        .error-icon svg {\n            filter: drop-shadow(0 5px 15px rgba(113, 102, 240, 0.2));\n        }\n\n        .error-title {\n            font-size: 2rem;\n            font-weight: 700;\n            background: var(--error-gradient);\n            -webkit-background-clip: text;\n            -webkit-text-fill-color: transparent;\n            margin-bottom: 1rem;\n        }\n\n        .error-message {\n            color: var(--text-secondary);\n            font-size: 1.1rem;\n            margin-bottom: 2.5rem;\n            line-height: 1.6;\n            background: rgba(113, 102, 240, 0.05);\n            padding: 1rem;\n            border-radius: 10px;\n            border: 1px solid rgba(113, 102, 240, 0.1);\n        }\n\n        .btn-custom {\n            padding: 1rem 2.5rem;\n            font-size: 1.1rem;\n            font-weight: 600;\n            border-radius: 50px;\n            background: var(--primary-gradient);\n            border: none;\n            color: white;\n            transition: all 0.3s ease;\n            box-shadow: 0 5px 15px rgba(113, 102, 240, 0.3);\n            text-decoration: none;\n            display: inline-block;\n            letter-spacing: 0.5px;\n            position: relative;\n            overflow: hidden;\n        }\n\n        .btn-custom:hover {\n            transform: translateY(-2px);\n            box-shadow: 0 8px 25px rgba(113, 102, 240, 0.4);\n            color: white;\n        }\n\n        .btn-custom:active {\n            transform: translateY(0);\n        }\n\n        .btn-custom::after {\n            content: '';\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            background: linear-gradient(45deg, rgba(255,255,255,0.1), rgba(255,255,255,0));\n            transform: translateX(-100%);\n            transition: transform 0.6s ease;\n        }\n\n        .btn-custom:hover::after {\n            transform: translateX(100%);\n        }\n\n        @keyframes fadeIn {\n            from { opacity: 0; transform: translateY(20px); }\n            to { opacity: 1; transform: translateY(0); }\n        }\n\n        @keyframes pulse {\n            0% { transform: scale(1); }\n            50% { transform: scale(1.05); }\n            100% { transform: scale(1); }\n        }\n\n        @keyframes shake {\n            0%, 100% { transform: translateX(0); }\n            25% { transform: translateX(-5px); }\n            75% { transform: translateX(5px); }\n        }\n\n        @media (max-width: 576px) {\n            .logo-section {\n                padding: 0.8rem 1rem;\n            }\n            .logo-section h1 {\n                font-size: 1rem;\n            }\n            .logo-icon img {\n                width: 32px;\n                height: 32px;\n            }\n            .main-content {\n                margin-top: 60px;\n                padding: 1rem;\n            }\n            .error-container {\n                padding: 2rem;\n            }\n            .error-title {\n                font-size: 1.6rem;\n            }\n            .btn-custom {\n                padding: 0.8rem 2rem;\n                font-size: 1rem;\n            }\n        }\n    </style>\n</head>\n<body>\n    <!-- Logo Section -->\n    <div class=\"logo-section\">\n        <div class=\"logo-icon\">\n            <img src=\"https://saithink.top/images/logo.png\">\n        </div>\n        <h1>【{{app}}-{{version}}】安装配置向导</h1>\n    </div>\n\n    <div class=\"main-content\">\n        <div class=\"install-wrapper\">\n            <div class=\"error-container\">\n                <div class=\"error-icon\">\n                    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" fill=\"currentColor\" class=\"bi bi-exclamation-circle\" viewBox=\"0 0 16 16\">\n                        <path d=\"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z\"/>\n                        <path d=\"M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z\"/>\n                    </svg>\n                </div>\n                <h2 class=\"error-title\">安装失败</h2>\n                <p class=\"error-message\">错误提示：{{error}}</p>\n                <a href=\"/core/install\" class=\"btn btn-custom\">重新安装</a>\n            </div>\n        </div>\n    </div>\n</body>\n</html>\n"
  },
  {
    "path": "src/plugin/saiadmin/app/view/install/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>应用安装向导</title>\n    <!-- Bootstrap CSS -->\n    <link href=\"/app/saiadmin/assets/bootstrap.min.css\" rel=\"stylesheet\">\n    <!-- Font Awesome -->\n    <link href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css\" rel=\"stylesheet\">\n    <!-- Custom CSS -->\n    <style>\n        :root {\n            --primary: #7166F0;\n            --primary-light: #8B7FF7;\n            --primary-dark: #5B4FD9;\n            --success: #10B981;\n            --warning: #F59E0B;\n            --dark: #1F2937;\n            --light: #F8FAFC;\n            --border-radius: 10px;\n            --box-shadow: 0 10px 30px rgba(113, 102, 240, 0.1);\n            --transition: all 0.3s ease;\n            --primary-gradient: linear-gradient(135deg, #7166F0 0%, #8B7FF7 100%);\n            --card-bg: rgba(255, 255, 255, 0.95);\n            --border-color: rgba(113, 102, 240, 0.1);\n        }\n\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body {\n            background: linear-gradient(135deg, #F5F3FF 0%, #EDE9FE 100%);\n            min-height: 100vh;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            padding: 30px 15px;\n            color: var(--dark);\n        }\n\n        .install-wrapper {\n            width: 100%;\n            max-width: 1000px;\n        }\n\n        .logo-section {\n            text-align: left;\n            padding: 1rem 2rem;\n            background: var(--card-bg);\n            box-shadow: 0 2px 15px rgba(0, 0, 0, 0.05);\n            backdrop-filter: blur(10px);\n            border-bottom: 1px solid var(--border-color);\n            position: fixed;\n            top: 0;\n            left: 0;\n            right: 0;\n            z-index: 1000;\n            display: flex;\n            align-items: center;\n            gap: 1rem;\n        }\n\n        .logo-icon {\n            animation: pulse 2s infinite;\n            flex-shrink: 0;\n        }\n\n        .logo-icon img {\n            width: 40px;\n            height: 40px;\n            border-radius: 10px;\n            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);\n            transition: transform 0.3s ease;\n        }\n\n        .logo-icon img:hover {\n            transform: scale(1.05);\n        }\n\n        .logo-section h1 {\n            font-size: 1.4rem;\n            background: var(--primary-gradient);\n            -webkit-background-clip: text;\n            -webkit-text-fill-color: transparent;\n            margin: 0;\n            font-weight: 700;\n            letter-spacing: 0.5px;\n            white-space: nowrap;\n            overflow: hidden;\n            text-overflow: ellipsis;\n        }\n\n        .main-content {\n            flex: 1;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            padding: 2rem;\n            margin-top: 70px;\n        }\n\n        .install-container {\n            background: var(--card-bg);\n            border-radius: var(--border-radius);\n            box-shadow: var(--box-shadow);\n            overflow: hidden;\n            width: 100%;\n            backdrop-filter: blur(10px);\n            border: 1px solid var(--border-color);\n        }\n\n        .progress-container {\n            padding: 25px 30px 0;\n        }\n\n        .progress {\n            height: 8px;\n            border-radius: 50px;\n            background-color: #F5F3FF;\n            margin-bottom: 25px;\n            overflow: visible;\n            box-shadow: inset 0 1px 3px rgba(113, 102, 240, 0.1);\n        }\n\n        .progress-bar {\n            background: var(--primary-gradient);\n            border-radius: 50px;\n            position: relative;\n            transition: var(--transition);\n        }\n\n        .progress-bar::after {\n            content: '';\n            position: absolute;\n            right: -4px;\n            top: -4px;\n            width: 16px;\n            height: 16px;\n            border-radius: 50%;\n            background: white;\n            border: 3px solid var(--primary);\n            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);\n        }\n\n        .steps-container {\n            display: flex;\n            justify-content: space-between;\n            position: relative;\n            margin-bottom: 30px;\n        }\n\n        .step-item {\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            position: relative;\n            z-index: 1;\n            flex: 1;\n        }\n\n        .step-icon {\n            width: 50px;\n            height: 50px;\n            border-radius: 50%;\n            background: white;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            margin-bottom: 10px;\n            border: 2px solid #F5F3FF;\n            color: #94A3B8;\n            transition: var(--transition);\n            position: relative;\n        }\n\n        .step-icon i {\n            font-size: 20px;\n        }\n\n        .step-text {\n            font-size: 14px;\n            font-weight: 600;\n            color: #94A3B8;\n            transition: var(--transition);\n        }\n\n        .step-item.active .step-icon,\n        .step-item.completed .step-icon {\n            border-color: var(--primary);\n            color: white;\n            background: var(--primary-gradient);\n        }\n\n        .step-item.active .step-text,\n        .step-item.completed .step-text {\n            color: var(--primary);\n        }\n\n        .step-item.completed .step-icon::after {\n            content: '\\f00c';\n            font-family: 'Font Awesome 6 Free';\n            font-weight: 900;\n            position: absolute;\n        }\n\n        .content-section {\n            padding: 30px;\n            width: 100%;\n        }\n\n        .step-content {\n            display: none;\n            animation: fadeIn 0.5s ease;\n            width: 100%;\n        }\n\n        .step-content.active {\n            display: block;\n            width: 100%;\n        }\n\n        @keyframes fadeIn {\n            from { opacity: 0; transform: translateY(20px); }\n            to { opacity: 1; transform: translateY(0); }\n        }\n\n        .step-title {\n            font-size: 24px;\n            font-weight: 700;\n            margin-bottom: 25px;\n            color: var(--primary);\n            width: 100%;\n            text-align: left;\n        }\n\n        .form-group {\n            margin-bottom: 25px;\n            position: relative;\n        }\n\n        .form-label {\n            font-weight: 600;\n            font-size: 14px;\n            color: var(--dark);\n            margin-bottom: 10px;\n            display: block;\n            transition: var(--transition);\n        }\n\n        .form-control {\n            height: 50px;\n            border-radius: var(--border-radius);\n            border: 2px solid #F5F3FF;\n            padding: 10px 15px 10px 50px;\n            font-size: 15px;\n            transition: var(--transition);\n            background-color: #F8FAFC;\n            color: var(--dark);\n        }\n\n        .form-control:focus {\n            border-color: var(--primary);\n            background-color: #fff;\n            box-shadow: 0 0 0 4px rgba(113, 102, 240, 0.1);\n            outline: none;\n        }\n\n        .form-control::placeholder {\n            color: #94A3B8;\n        }\n\n        /* Custom Input Group Styles */\n        .custom-input-group {\n            position: relative;\n            display: flex;\n            align-items: center;\n        }\n\n        .custom-input-icon {\n            position: absolute;\n            left: 0;\n            top: 0;\n            width: 50px;\n            height: 50px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            background-color: transparent;\n            color: #94A3B8;\n            z-index: 5;\n            pointer-events: none;\n            transition: var(--transition);\n        }\n\n        .custom-input-group:hover .custom-input-icon {\n            color: var(--primary);\n        }\n\n        .custom-input-group .form-control:focus + .custom-input-icon {\n            color: var(--primary);\n        }\n\n        /* 添加输入框动画效果 */\n        .form-control:focus + .custom-input-icon {\n            transform: scale(1.1);\n        }\n\n        /* 添加输入框hover效果 */\n        .form-control:hover {\n            border-color: #ced4da;\n            background-color: #fff;\n        }\n\n        /* 添加密码输入框特殊样式 */\n        input[type=\"password\"].form-control {\n            letter-spacing: 2px;\n        }\n\n        /* 添加数字输入框特殊样式 */\n        input[type=\"number\"].form-control {\n            -moz-appearance: textfield;\n        }\n\n        input[type=\"number\"].form-control::-webkit-outer-spin-button,\n        input[type=\"number\"].form-control::-webkit-inner-spin-button {\n            -webkit-appearance: none;\n            margin: 0;\n        }\n\n        /* 添加输入框聚焦时的标签动画 */\n        .form-group:focus-within .form-label {\n            color: var(--primary);\n            transform: translateY(-2px);\n        }\n\n        /* 添加输入框验证状态样式 */\n        .form-control.is-valid {\n            border-color: var(--success);\n            background-image: none;\n        }\n\n        .form-control.is-invalid {\n            border-color: var(--warning);\n            background-image: none;\n        }\n\n        /* 添加输入框加载状态样式 */\n        .form-control.is-loading {\n            background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%234361ee' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 12a9 9 0 1 1-6.219-8.56'/%3E%3C/svg%3E\");\n            background-repeat: no-repeat;\n            background-position: right 10px center;\n            background-size: 20px;\n            padding-right: 40px;\n        }\n\n        .btn {\n            height: 50px;\n            border-radius: var(--border-radius);\n            padding: 0 25px;\n            font-weight: 600;\n            font-size: 15px;\n            transition: var(--transition);\n            display: inline-flex;\n            align-items: center;\n            justify-content: center;\n            gap: 10px;\n        }\n\n        .btn-primary {\n            background: var(--primary-gradient);\n            border: none;\n            color: white;\n            transition: var(--transition);\n            box-shadow: 0 5px 15px rgba(113, 102, 240, 0.3);\n        }\n\n        .btn-primary:hover {\n            transform: translateY(-2px);\n            box-shadow: 0 8px 25px rgba(113, 102, 240, 0.4);\n        }\n\n        .btn-primary:active {\n            transform: translateY(0);\n        }\n\n        .btn-outline-primary {\n            color: var(--primary);\n            border-color: var(--primary);\n        }\n\n        .btn-outline-primary:hover {\n            background: var(--primary);\n            border-color: var(--primary);\n        }\n\n        .log-container {\n            background: #F8FAFC;\n            border-radius: var(--border-radius);\n            border: 2px solid #F5F3FF;\n            height: 300px;\n            overflow-y: auto;\n            padding: 20px;\n            margin-top: 20px;\n            width: 100%;\n        }\n\n        .log-item {\n            padding: 15px;\n            border-radius: var(--border-radius);\n            background: white;\n            margin-bottom: 15px;\n            box-shadow: 0 2px 5px rgba(113, 102, 240, 0.05);\n            display: flex;\n            align-items: flex-start;\n            gap: 15px;\n            animation: slideIn 0.3s ease;\n            border: 1px solid #F5F3FF;\n            transition: var(--transition);\n        }\n\n        .log-item:hover {\n            transform: translateY(-2px);\n            box-shadow: 0 5px 15px rgba(113, 102, 240, 0.1);\n        }\n\n        .log-icon {\n            width: 40px;\n            height: 40px;\n            border-radius: 50%;\n            background: rgba(113, 102, 240, 0.1);\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            color: var(--primary);\n            flex-shrink: 0;\n            transition: var(--transition);\n        }\n\n        .log-item:hover .log-icon {\n            transform: scale(1.1);\n            background: rgba(113, 102, 240, 0.2);\n        }\n\n        .log-content {\n            flex-grow: 1;\n        }\n\n        .log-text {\n            font-size: 14px;\n            line-height: 1.6;\n            color: var(--dark);\n            margin-bottom: 5px;\n        }\n\n        .log-time {\n            font-size: 12px;\n            color: #94A3B8;\n        }\n\n        .completed-message {\n            text-align: center;\n            padding: 40px 20px;\n            animation: fadeIn 0.5s ease;\n            width: 100%;\n            max-width: 100%;\n        }\n\n        .completed-icon {\n            width: 100px;\n            height: 100px;\n            border-radius: 50%;\n            background: rgba(113, 102, 240, 0.1);\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            margin: 0 auto 30px;\n            color: var(--primary);\n            font-size: 40px;\n            animation: scaleIn 0.5s ease;\n            transition: var(--transition);\n        }\n\n        .completed-icon:hover {\n            transform: scale(1.1);\n            background: rgba(113, 102, 240, 0.2);\n        }\n\n        .completed-title {\n            font-size: 28px;\n            font-weight: 700;\n            margin-bottom: 15px;\n            color: var(--primary);\n            background: var(--primary-gradient);\n            -webkit-background-clip: text;\n            -webkit-text-fill-color: transparent;\n        }\n\n        .completed-subtitle {\n            font-size: 16px;\n            color: #6c757d;\n            margin-bottom: 30px;\n            line-height: 1.6;\n            max-width: 100%;\n            padding: 0 20px;\n        }\n\n        .action-buttons {\n            display: flex;\n            justify-content: center;\n            gap: 15px;\n            width: 100%;\n            padding: 0 20px;\n        }\n\n        .action-buttons .btn {\n            padding: 12px 30px;\n            font-size: 16px;\n            border-radius: 50px;\n            background: var(--primary-gradient);\n            border: none;\n            color: white;\n            transition: var(--transition);\n            box-shadow: 0 5px 15px rgba(113, 102, 240, 0.3);\n        }\n\n        .action-buttons .btn:hover {\n            transform: translateY(-2px);\n            box-shadow: 0 8px 25px rgba(113, 102, 240, 0.4);\n        }\n\n        .action-buttons .btn:active {\n            transform: translateY(0);\n        }\n\n        @keyframes scaleIn {\n            from { transform: scale(0); opacity: 0; }\n            to { transform: scale(1); opacity: 1; }\n        }\n\n        @keyframes slideIn {\n            from { transform: translateY(10px); opacity: 0; }\n            to { transform: translateY(0); opacity: 1; }\n        }\n\n        /* Responsive adjustments */\n        @media (max-width: 768px) {\n            .content-section {\n                padding: 20px;\n            }\n\n            .step-title {\n                font-size: 20px;\n            }\n\n            .btn {\n                height: 45px;\n                padding: 0 20px;\n                font-size: 14px;\n            }\n\n            .form-control {\n                height: 45px;\n            }\n\n            .custom-input-icon {\n                height: 45px;\n                width: 45px;\n            }\n        }\n\n        @media (max-width: 576px) {\n            .steps-container {\n                flex-direction: column;\n                align-items: flex-start;\n                gap: 15px;\n            }\n\n            .step-item {\n                flex-direction: row;\n                width: 100%;\n                justify-content: flex-start;\n                gap: 15px;\n            }\n\n            .step-icon {\n                margin-bottom: 0;\n            }\n\n            .log-container {\n                height: 250px;\n            }\n\n            .completed-icon {\n                width: 80px;\n                height: 80px;\n                font-size: 32px;\n            }\n\n            .completed-title {\n                font-size: 24px;\n            }\n        }\n\n        /* Step 2: Installation Process Styles */\n        #step2 {\n            width: 100%;\n        }\n\n        #step2 .progress {\n            width: 100%;\n        }\n\n        #step2 .log-container {\n            width: 100%;\n            margin: 20px 0;\n        }\n\n        /* Step 3: Installation Complete Styles */\n        #step3 {\n            width: 100%;\n        }\n\n        #step3 .completed-message {\n            width: 100%;\n            padding: 40px 0;\n        }\n\n        #step3 .completed-subtitle {\n            width: 100%;\n            padding: 0;\n            margin: 0 auto 30px;\n        }\n\n        #step3 .action-buttons {\n            width: 100%;\n            padding: 0;\n        }\n\n        /* 确保所有步骤内容区域宽度一致 */\n        .row {\n            width: 100%;\n            margin: 0;\n        }\n\n        .col-md-6 {\n            width: 100%;\n            padding: 0;\n        }\n\n        @media (min-width: 768px) {\n            .col-md-6 {\n                width: 50%;\n                padding: 0 15px;\n            }\n        }\n    </style>\n</head>\n<body>\n    <div class=\"install-wrapper\">\n        <!-- Logo Section -->\n        <div class=\"logo-section\">\n            <div class=\"logo-icon\">\n                <img src=\"https://saithink.top/images/logo.png\" alt=\"Logo\">\n            </div>\n            <h1>【{{app}}-{{version}}】安装配置向导</h1>\n        </div>\n\n        <!-- Main Install Container -->\n        <div class=\"main-content\">\n            <div class=\"install-container\">\n                <!-- Progress Section -->\n                <div class=\"progress-container\">\n                    <div class=\"progress\">\n                        <div class=\"progress-bar\" role=\"progressbar\" style=\"width: 0%\"></div>\n                    </div>\n                    <div class=\"steps-container\">\n                        <div class=\"step-item active\" data-step=\"1\">\n                            <div class=\"step-icon\">\n                                <i class=\"fas fa-database\"></i>\n                            </div>\n                            <div class=\"step-text\">数据库配置</div>\n                        </div>\n                        <div class=\"step-item\" data-step=\"2\">\n                            <div class=\"step-icon\">\n                                <i class=\"fas fa-cogs\"></i>\n                            </div>\n                            <div class=\"step-text\">执行安装</div>\n                        </div>\n                        <div class=\"step-item\" data-step=\"3\">\n                            <div class=\"step-icon\">\n                                <i class=\"fas fa-check\"></i>\n                            </div>\n                            <div class=\"step-text\">安装完成</div>\n                        </div>\n                    </div>\n                </div>\n\n                <!-- Content Section -->\n                <div class=\"content-section\">\n                    <!-- Step 1: Database Config -->\n                    <div class=\"step-content active\" id=\"step1\">\n                        <h2 class=\"step-title\">数据库配置</h2>\n                        <form id=\"dbConfigForm\">\n                            <div class=\"row g-4\">\n                                <div class=\"col-md-6\">\n                                    <div class=\"form-group\">\n                                        <label class=\"form-label\">数据库数据</label>\n                                        <div class=\"radio-group\" style=\"display: flex; gap: 20px;\">\n                                            <label class=\"radio-label\" style=\"display: flex; align-items: center; cursor: pointer; padding: 10px 15px; border: 2px solid #F5F3FF; border-radius: 10px; background: #F8FAFC; transition: all 0.3s ease;\">\n                                                <input type=\"radio\" name=\"dataType\" id=\"dataTypeDemo\" value=\"demo\" checked style=\"width: 18px; height: 18px; margin-right: 10px; accent-color: #7166f0;\">\n                                                <span style=\"display: flex; flex-direction: column;\">\n                                                    <strong style=\"color: #1e293b; font-size: 14px;\">附带演示数据</strong>\n                                                </span>\n                                            </label>\n                                            <label class=\"radio-label\" style=\"display: flex; align-items: center; cursor: pointer; padding: 10px 15px; border: 2px solid #F5F3FF; border-radius: 10px; background: #F8FAFC; transition: all 0.3s ease;\">\n                                                <input type=\"radio\" name=\"dataType\" id=\"dataTypePure\" value=\"pure\" style=\"width: 18px; height: 18px; margin-right: 10px; accent-color: #7166f0;\">\n                                                <span style=\"display: flex; flex-direction: column;\">\n                                                    <strong style=\"color: #1e293b; font-size: 14px;\">纯净数据</strong>\n                                                </span>\n                                            </label>\n                                        </div>\n                                    </div>\n                                </div>\n                                <div class=\"col-md-6\">\n                                    <div class=\"form-group\">\n                                        <label for=\"dbHost\" class=\"form-label\">数据库主机</label>\n                                        <div class=\"custom-input-group\">\n                                            <input type=\"text\" class=\"form-control\" id=\"dbHost\" value=\"127.0.0.1\" required>\n                                            <div class=\"custom-input-icon\">\n                                                <i class=\"fas fa-server\"></i>\n                                            </div>\n                                        </div>\n                                    </div>\n                                </div>\n                                <div class=\"col-md-6\">\n                                    <div class=\"form-group\">\n                                        <label for=\"dbPort\" class=\"form-label\">端口</label>\n                                        <div class=\"custom-input-group\">\n                                            <input type=\"number\" class=\"form-control\" id=\"dbPort\" value=\"3306\" required>\n                                            <div class=\"custom-input-icon\">\n                                                <i class=\"fas fa-plug\"></i>\n                                            </div>\n                                        </div>\n                                    </div>\n                                </div>\n                                <div class=\"col-md-6\">\n                                    <div class=\"form-group\">\n                                        <label for=\"dbName\" class=\"form-label\">数据库名</label>\n                                        <div class=\"custom-input-group\">\n                                            <input type=\"text\" class=\"form-control\" id=\"dbName\" required>\n                                            <div class=\"custom-input-icon\">\n                                                <i class=\"fas fa-database\"></i>\n                                            </div>\n                                        </div>\n                                    </div>\n                                </div>\n                                <div class=\"col-md-6\">\n                                    <div class=\"form-group\">\n                                        <label for=\"dbUser\" class=\"form-label\">用户名</label>\n                                        <div class=\"custom-input-group\">\n                                            <input type=\"text\" class=\"form-control\" id=\"dbUser\" required>\n                                            <div class=\"custom-input-icon\">\n                                                <i class=\"fas fa-user\"></i>\n                                            </div>\n                                        </div>\n                                    </div>\n                                </div>\n                                <div class=\"col-md-6\">\n                                    <div class=\"form-group\">\n                                        <label for=\"dbPassword\" class=\"form-label\">密码</label>\n                                        <div class=\"custom-input-group\">\n                                            <input type=\"password\" class=\"form-control\" id=\"dbPassword\" required>\n                                            <div class=\"custom-input-icon\">\n                                                <i class=\"fas fa-key\"></i>\n                                            </div>\n                                        </div>\n                                    </div>\n                                </div>\n                            </div>\n                            <div class=\"d-flex justify-content-end mt-4\">\n                                <button type=\"button\" class=\"btn btn-primary\" onclick=\"nextStep(1)\">\n                                    <span>下一步</span>\n                                    <i class=\"fas fa-arrow-right\"></i>\n                                </button>\n                            </div>\n                        </form>\n                    </div>\n\n                    <!-- Step 2: Installation Process -->\n                    <div class=\"step-content\" id=\"step2\">\n                        <h2 class=\"step-title\">执行安装</h2>\n                        <div class=\"progress mb-4\">\n                            <div class=\"progress-bar progress-bar-striped progress-bar-animated\" role=\"progressbar\" style=\"width: 0%\"></div>\n                        </div>\n                        <div class=\"log-container\" id=\"installLog\">\n                            <div class=\"log-item\">\n                                <div class=\"log-icon\">\n                                    <i class=\"fas fa-info\"></i>\n                                </div>\n                                <div class=\"log-content\">\n                                    <div class=\"log-text\">准备开始安装...</div>\n                                    <div class=\"log-time\">刚刚</div>\n                                </div>\n                            </div>\n                        </div>\n                    </div>\n\n                    <!-- Step 3: Installation Complete -->\n                    <div class=\"step-content\" id=\"step3\">\n                        <div class=\"completed-message\">\n                            <div class=\"completed-icon\">\n                                <i class=\"fas fa-check\"></i>\n                            </div>\n                            <h2 class=\"completed-title\">安装成功</h2>\n                            <p class=\"completed-subtitle\">恭喜！已成功安装系统，请重启webman，启动前端项目进行访问。</p>\n                            <div class=\"action-buttons\">\n                                <a href=\"http://localhost:3006\" class=\"btn btn-primary\">\n                                    <i class=\"fas fa-tachometer-alt\"></i>\n                                    <span>进入管理后台</span>\n                                </a>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n\n    <!-- jQuery -->\n    <script src=\"/app/saiadmin/assets/jquery.min.js\"></script>\n    <script>\n\n        // 显示通知\n        function showNotification(type, message) {\n            const icon = type === 'success' ? 'check-circle' : 'exclamation-circle';\n            const color = type === 'success' ? '#4cc9f0' : '#f72585';\n            \n            const notification = $(`\n                <div class=\"notification\" style=\"\n                    position: fixed;\n                    top: 20px;\n                    right: 20px;\n                    background: white;\n                    padding: 15px 20px;\n                    border-radius: 10px;\n                    box-shadow: 0 5px 15px rgba(0,0,0,0.1);\n                    display: flex;\n                    align-items: center;\n                    gap: 15px;\n                    z-index: 1000;\n                    transform: translateX(120%);\n                    transition: transform 0.3s ease;\n                \">\n                    <div style=\"\n                        width: 30px;\n                        height: 30px;\n                        background: ${color};\n                        color: white;\n                        border-radius: 50%;\n                        display: flex;\n                        align-items: center;\n                        justify-content: center;\n                    \">\n                        <i class=\"fas fa-${icon}\"></i>\n                    </div>\n                    <div>${message}</div>\n                </div>\n            `);\n            \n            $('body').append(notification);\n            \n            setTimeout(() => {\n                notification.css('transform', 'translateX(0)');\n            }, 100);\n            \n            setTimeout(() => {\n                notification.css('transform', 'translateX(120%)');\n                setTimeout(() => {\n                    notification.remove();\n                }, 300);\n            }, 3000);\n        }\n\n        // 切换步骤\n        function nextStep(currentStep) {\n            if (currentStep === 1) {\n                // 验证数据库配置\n                if (!validateDbConfig()) {\n                    return;\n                }\n                // 开始安装过程\n                startInstallation();\n            } else {\n                // 隐藏当前步骤，显示下一步\n                $('.step-content').removeClass('active');\n                $(`#step${currentStep + 1}`).addClass('active');\n            \n                // 更新步骤指示器\n                $('.step-item').removeClass('active');\n                $(`.step-item[data-step=\"${currentStep + 1}\"]`).addClass('active');\n            \n                // 将之前的步骤标记为已完成\n                for (let i = 1; i <= currentStep; i++) {\n                    $(`.step-item[data-step=\"${i}\"]`).addClass('completed');\n                }\n            \n                // 更新进度条\n                const progress = ((currentStep + 1) / 3) * 100;\n                $('.progress-bar').css('width', progress + '%');\n            }\n            \n            \n        }\n\n        // 验证数据库配置\n        function validateDbConfig() {\n            const required = ['dbHost', 'dbName', 'dbUser', 'dbPassword'];\n            for (const field of required) {\n                if (!$(`#${field}`).val()) {\n                    showNotification('error', '请填写所有必填字段');\n                    return false;\n                }\n            }\n            return true;\n        }\n\n        // 开始安装过程\n        function startInstallation() {\n            const dbConfig = {\n                host: $('#dbHost').val(),\n                port: $('#dbPort').val(),\n                database: $('#dbName').val(),\n                username: $('#dbUser').val(),\n                password: $('#dbPassword').val(),\n                prefix: $('#dbPrefix').val(),\n                dataType: $('input[name=\"dataType\"]:checked').val()\n            };\n\n            // 模拟安装过程\n            const steps = [\n                { icon: 'database', text: '正在创建数据库表...' },\n                { icon: 'file-import', text: '正在导入基础数据...' },\n                { icon: 'cogs', text: '正在配置系统参数...' },\n                { icon: 'tachometer-alt', text: '正在优化系统性能...' }\n            ];\n\n            let currentStep = 0;\n            const installLog = $('#installLog');\n            const progressBar = $('.progress-bar');\n\n            function runStep() {\n                if (currentStep < steps.length) {\n                    // 获取当前时间\n                    const now = new Date();\n                    const timeString = now.getHours().toString().padStart(2, '0') + ':' + \n                                       now.getMinutes().toString().padStart(2, '0') + ':' + \n                                       now.getSeconds().toString().padStart(2, '0');\n                    \n                    // 添加日志\n                    const step = steps[currentStep];\n                    installLog.append(`\n                        <div class=\"log-item\">\n                            <div class=\"log-icon\">\n                                <i class=\"fas fa-${step.icon}\"></i>\n                            </div>\n                            <div class=\"log-content\">\n                                <div class=\"log-text\">${step.text}</div>\n                                <div class=\"log-time\">${timeString}</div>\n                            </div>\n                        </div>\n                    `);\n                    installLog.scrollTop(installLog[0].scrollHeight);\n                    \n                    // 更新进度\n                    const progress = ((currentStep + 1) / steps.length) * 100;\n                    progressBar.css('width', progress + '%');\n\n                    // 模拟异步操作\n                    setTimeout(() => {\n                        currentStep++;\n                        runStep();\n                    }, 1000);\n                } else {\n                    // 安装完成\n                    setTimeout(() => {\n                        nextStep(2);\n                    }, 500);\n                }\n            }\n\n            $.ajax({\n                url: '/core/install/install',\n                method: 'POST',\n                data: dbConfig,\n                success: function(response) {\n                    if (response.code == 200) {\n\n                        // 隐藏当前步骤，显示下一步\n                        $('.step-content').removeClass('active');\n                        $(`#step2`).addClass('active');\n                    \n                        // 更新步骤指示器\n                        $('.step-item').removeClass('active');\n                        $(`.step-item[data-step=2]`).addClass('active');\n\n                        runStep()\n                    } else {\n                        showNotification('error', '数据库连接失败：' + response.message);\n                    }\n                },\n                error: function() {\n                    showNotification('error', '请求失败，请检查网络连接');\n                }\n            });\n           \n        }\n    </script>\n</body>\n</html>\n"
  },
  {
    "path": "src/plugin/saiadmin/basic/AbstractLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\basic;\n\nuse plugin\\saiadmin\\basic\\contracts\\LogicInterface;\n\n/**\n * 抽象逻辑层基类\n * 定义通用属性和方法签名，具体实现由各 ORM 驱动完成\n */\nabstract class AbstractLogic implements LogicInterface\n{\n    /**\n     * 模型注入\n     * @var object\n     */\n    protected $model;\n\n    /**\n     * 管理员信息\n     * @var array\n     */\n    protected array $adminInfo;\n\n    /**\n     * 排序字段\n     * @var string\n     */\n    protected string $orderField = '';\n\n    /**\n     * 排序方式\n     * @var string\n     */\n    protected string $orderType = 'ASC';\n\n    /**\n     * 初始化\n     * @param $user\n     * @return void\n     */\n    public function init($user): void\n    {\n        $this->adminInfo = $user;\n    }\n\n    /**\n     * 设置排序字段\n     * @param string $field\n     * @return static\n     */\n    public function setOrderField(string $field): static\n    {\n        $this->orderField = $field;\n        return $this;\n    }\n\n    /**\n     * 设置排序方式\n     * @param string $type\n     * @return static\n     */\n    public function setOrderType(string $type): static\n    {\n        $this->orderType = $type;\n        return $this;\n    }\n\n    /**\n     * 获取模型实例\n     * @return object\n     */\n    public function getModel(): object\n    {\n        return $this->model;\n    }\n\n    /**\n     * 获取上传的导入文件\n     * @param $file\n     * @return string\n     */\n    public function getImport($file): string\n    {\n        $full_dir = runtime_path() . '/resource/';\n        if (!is_dir($full_dir)) {\n            mkdir($full_dir, 0777, true);\n        }\n        $ext = $file->getUploadExtension() ?: null;\n        $full_path = $full_dir . md5(time()) . '.' . $ext;\n        $file->move($full_path);\n        return $full_path;\n    }\n\n    /**\n     * 方法调用代理到模型\n     * @param string $name\n     * @param array $arguments\n     * @return mixed\n     */\n    public function __call(string $name, array $arguments): mixed\n    {\n        return call_user_func_array([$this->model, $name], $arguments);\n    }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/basic/BaseController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\basic;\n\nuse plugin\\saiadmin\\app\\cache\\UserInfoCache;\nuse plugin\\saiadmin\\exception\\ApiException;\n\n/**\n * 基类 控制器继承此类\n */\nclass BaseController extends OpenController\n{\n\n    /**\n     * 当前登陆管理员信息\n     */\n    protected $adminInfo;\n\n    /**\n     * 当前登陆管理员ID\n     */\n    protected int $adminId;\n\n    /**\n     * 当前登陆管理员账号\n     */\n    protected string $adminName;\n\n    /**\n     * 逻辑层注入\n     */\n    protected $logic;\n\n    /**\n     * 验证器注入\n     */\n    protected $validate;\n\n    /**\n     * 初始化\n     */\n    protected function init(): void\n    {\n        // 登录模式赋值\n        $isLogin = request()->header('check_login', false);\n        if ($isLogin) {\n            $result = request()->header('check_admin');\n            $this->adminId = $result['id'];\n            $this->adminName = $result['username'];\n            $this->adminInfo = UserInfoCache::getUserInfo($result['id']);\n\n            // 用户数据传递给逻辑层\n            $this->logic && $this->logic->init($this->adminInfo);\n        }\n    }\n\n    /**\n     * 验证器调用\n     */\n    protected function validate(string $scene, $data): bool\n    {\n        if ($this->validate) {\n            if (!$this->validate->scene($scene)->check($data)) {\n                throw new ApiException($this->validate->getError());\n            }\n        }\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/basic/BaseValidate.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\basic;\n\nuse think\\Validate;\n\n/**\n * 验证器基类\n */\nclass BaseValidate extends Validate\n{\n\n    /**\n     * 验证是否唯一\n     * @access public\n     * @param mixed  $value 字段值\n     * @param mixed  $rule  验证规则 格式：数据表,字段名,排除ID,主键名\n     * @param array  $data  数据\n     * @param string $field 验证字段名\n     * @return bool\n     */\n    public function unique($value, $rule, array $data = [], string $field = ''): bool\n    {\n        if (is_string($rule)) {\n            $rule = explode(',', $rule);\n        }\n\n        if (str_contains($rule[0], '\\\\')) {\n            // 指定模型类\n            $db = new $rule[0];\n        } else {\n            return false;\n        }\n\n        $key = $rule[1] ?? $field;\n        $map = [];\n\n        if (str_contains($key, '^')) {\n            // 支持多个字段验证\n            $fields = explode('^', $key);\n            foreach ($fields as $key) {\n                if (isset($data[$key])) {\n                    $map[] = [$key, '=', $data[$key]];\n                }\n            }\n        } elseif (strpos($key, '=')) {\n            // 支持复杂验证\n            parse_str($key, $array);\n            foreach ($array as $k => $val) {\n                $map[] = [$k, '=', $data[$k] ?? $val];\n            }\n        } elseif (isset($data[$field])) {\n            $map[] = [$key, '=', $data[$field]];\n        }\n\n        $pk = !empty($rule[3]) ? $rule[3] : $db->getPrimaryKeyName();\n\n        if (is_string($pk)) {\n            if (isset($rule[2])) {\n                $map[] = [$pk, '<>', $rule[2]];\n            } elseif (isset($data[$pk])) {\n                $map[] = [$pk, '<>', $data[$pk]];\n            }\n        }\n\n        if ($db->where($map)->count() > 0) {\n            return false;\n        }\n\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/basic/OpenController.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\basic;\n\nuse support\\Request;\nuse support\\Response;\n\n/**\n * 基类 控制器继承此类\n */\nclass OpenController\n{\n    /**\n     * 构造方法\n     * @access public\n     */\n    public function __construct()\n    {\n        // 控制器初始化\n        $this->init();\n    }\n\n    /**\n     * 成功返回json内容\n     * @param array|string $data\n     * @param string $msg\n     * @param int $option\n     * @return Response\n     */\n    public function success(array | string $data = [], string $msg = 'success', int $option = JSON_UNESCAPED_UNICODE): Response\n    {\n        if (is_string($data)) {\n            $msg = $data;\n        }\n        return json(['code' => 200, 'message' => $msg, 'data' => $data], $option);\n    }\n\n    /**\n     * 失败返回json内容\n     * @param string $msg\n     * @return Response\n     */\n    public function fail(string $msg = 'fail'): Response\n    {\n        return json(['code' => 400, 'message' => $msg]);\n    }\n\n    /**\n     * 初始化\n     */\n    protected function init(): void\n    {\n        // TODO\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/basic/contracts/LogicInterface.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\basic\\contracts;\n\n/**\n * Logic 接口定义\n * 所有 Logic 基类必须实现此接口\n */\ninterface LogicInterface\n{\n    /**\n     * 初始化\n     * @param mixed $user 用户信息\n     * @return void\n     */\n    public function init($user): void;\n\n    /**\n     * 添加数据\n     * @param array $data\n     * @return mixed\n     */\n    public function add(array $data): mixed;\n\n    /**\n     * 修改数据\n     * @param mixed $id\n     * @param array $data\n     * @return mixed\n     */\n    public function edit($id, array $data): mixed;\n\n    /**\n     * 读取数据\n     * @param mixed $id\n     * @return mixed\n     */\n    public function read($id): mixed;\n\n    /**\n     * 删除数据\n     * @param mixed $ids\n     * @return bool\n     */\n    public function destroy($ids): bool;\n\n    /**\n     * 搜索器搜索\n     * @param array $searchWhere\n     * @return mixed\n     */\n    public function search(array $searchWhere = []): mixed;\n\n    /**\n     * 分页查询数据\n     * @param mixed $query\n     * @return mixed\n     */\n    public function getList($query): mixed;\n\n    /**\n     * 获取全部数据\n     * @param mixed $query\n     * @return mixed\n     */\n    public function getAll($query): mixed;\n\n    /**\n     * 数据库事务操作\n     * @param callable $closure\n     * @param bool $isTran\n     * @return mixed\n     */\n    public function transaction(callable $closure, bool $isTran = true): mixed;\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/basic/contracts/ModelInterface.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\basic\\contracts;\n\n/**\n * Model 接口定义\n * 所有 Model 基类必须实现此接口\n */\ninterface ModelInterface\n{\n    /**\n     * 获取表名\n     * @return string\n     */\n    public function getTableName(): string;\n\n    /**\n     * 获取主键名\n     * @return string\n     */\n    public function getPrimaryKeyName(): string;\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/basic/eloquent/BaseLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\basic\\eloquent;\n\nuse support\\Db;\nuse plugin\\saiadmin\\basic\\AbstractLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\n\n/**\n * Laravel Eloquent 逻辑层基类\n */\nclass BaseLogic extends AbstractLogic\n{\n    /**\n     * 数据库事务操作\n     * @param callable $closure\n     * @param bool $isTran\n     * @return mixed\n     */\n    public function transaction(callable $closure, bool $isTran = true): mixed\n    {\n        return $isTran ? Db::transaction($closure) : $closure();\n    }\n\n    /**\n     * 添加数据\n     * @param array $data\n     * @return mixed\n     */\n    public function add(array $data): mixed\n    {\n        $model = $this->model->create($data);\n        return $model->getKey();\n    }\n\n    /**\n     * 修改数据\n     * @param mixed $id\n     * @param array $data\n     * @return mixed\n     */\n    public function edit($id, array $data): mixed\n    {\n        $model = $this->model->find($id);\n        if (!$model) {\n            throw new ApiException('数据不存在');\n        }\n        return $model->update($data);\n    }\n\n    /**\n     * 读取数据\n     * @param mixed $id\n     * @return mixed\n     */\n    public function read($id): mixed\n    {\n        $model = $this->model->find($id);\n        if (!$model) {\n            throw new ApiException('数据不存在');\n        }\n        return $model;\n    }\n\n    /**\n     * 删除数据\n     * @param mixed $ids\n     * @return bool\n     */\n    public function destroy($ids): bool\n    {\n        return $this->model->destroy($ids);\n    }\n\n    /**\n     * 搜索器搜索\n     * @param array $searchWhere\n     * @return mixed\n     */\n    public function search(array $searchWhere = []): mixed\n    {\n        $withSearch = array_keys($searchWhere);\n        $data = [];\n        foreach ($searchWhere as $key => $value) {\n            if ($value !== '' && $value !== null && $value !== []) {\n                $data[$key] = $value;\n            }\n        }\n        $withSearch = array_keys($data);\n        return $this->model->withSearch($withSearch, $data);\n    }\n\n    /**\n     * 分页查询数据\n     * @param mixed $query\n     * @return mixed\n     */\n    public function getList($query): mixed\n    {\n        $request = request();\n        $saiType = $request ? $request->input('saiType', 'list') : 'list';\n        $page = $request ? $request->input('page', 1) : 1;\n        $limit = $request ? $request->input('limit', 10) : 10;\n        $orderField = $request ? $request->input('orderField', '') : '';\n        $orderType = $request ? $request->input('orderType', $this->orderType) : $this->orderType;\n\n        if (empty($orderField)) {\n            $orderField = $this->orderField !== '' ? $this->orderField : $this->model->getKeyName();\n        }\n\n        $query->orderBy($orderField, $orderType);\n\n        if ($saiType === 'all') {\n            return $query->get()->toArray();\n        }\n\n        $list = $query->paginate($limit, ['*'], 'page', $page);\n\n        return [\n            'current_page' => $list->currentPage(),\n            'per_page' => $list->perPage(),\n            'last_page' => $list->lastPage(),\n            'has_more' => $list->hasMorePages(),\n            'total' => $list->total(),\n            'data' => $list->items(),\n        ];\n    }\n\n    /**\n     * 获取全部数据\n     * @param mixed $query\n     * @return mixed\n     */\n    public function getAll($query): mixed\n    {\n        $request = request();\n        $orderField = $request ? $request->input('orderField', '') : '';\n        $orderType = $request ? $request->input('orderType', $this->orderType) : $this->orderType;\n\n        if (empty($orderField)) {\n            $orderField = $this->orderField !== '' ? $this->orderField : $this->model->getKeyName();\n        }\n\n        $query->orderBy($orderField, $orderType);\n        return $query->get()->toArray();\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/basic/eloquent/BaseModel.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\basic\\eloquent;\n\nuse support\\Model;\nuse Illuminate\\Database\\Eloquent\\SoftDeletes;\nuse plugin\\saiadmin\\basic\\contracts\\ModelInterface;\n\n/**\n * Laravel Eloquent 模型基类\n */\nclass BaseModel extends Model implements ModelInterface\n{\n    use SoftDeletes;\n\n    /**\n     * 创建时间字段\n     */\n    const CREATED_AT = 'create_time';\n\n    /**\n     * 更新时间字段\n     */\n    const UPDATED_AT = 'update_time';\n\n    /**\n     * 删除时间字段\n     */\n    const DELETED_AT = 'delete_time';\n\n    /**\n     * 隐藏字段\n     * @var array\n     */\n    protected $hidden = ['delete_time'];\n\n    /**\n     * 不可批量赋值的属性 (为空表示全部可赋值)\n     * @var array\n     */\n    protected $guarded = [];\n\n    /**\n     * 类型转换\n     * @return array\n     */\n    protected function casts(): array\n    {\n        return [\n            'create_time' => 'datetime:Y-m-d H:i:s',\n            'update_time' => 'datetime:Y-m-d H:i:s',\n        ];\n    }\n\n    /**\n     * 处理时区问题\n     * @param \\DateTimeInterface $date\n     * @return string\n     */\n    protected function serializeDate(\\DateTimeInterface $date): string\n    {\n        return $date->format($this->dateFormat ?: 'Y-m-d H:i:s');\n    }\n\n    /**\n     * 获取表名\n     * @return string\n     */\n    public function getTableName(): string\n    {\n        return $this->getTable();\n    }\n\n    /**\n     * 获取主键名\n     * @return string\n     */\n    public function getPrimaryKeyName(): string\n    {\n        return $this->getKeyName();\n    }\n\n    /**\n     * 搜索器搜索\n     * @param array $fields\n     * @param array $data\n     * @return mixed\n     */\n    public function withSearch(array $fields, array $data): mixed\n    {\n        $query = $this->newQuery();\n        foreach ($fields as $field) {\n            $method = 'search' . ucfirst($this->toCamelCase($field)) . 'Attr';\n            if (method_exists($this, $method) && isset($data[$field]) && $data[$field] !== '') {\n                $this->$method($query, $data[$field]);\n            } else {\n                $query->where($field, $data[$field]);\n            }\n        }\n        return $query;\n    }\n\n    /**\n     * 将下划线命名转换为驼峰命名\n     * @param string $str\n     * @return string\n     */\n    protected function toCamelCase(string $str): string\n    {\n        return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $str))));\n    }\n\n    /**\n     * 添加时间范围搜索\n     * @param $query\n     * @param $value\n     */\n    public function searchCreateTimeAttr($query, $value)\n    {\n        if (is_array($value)) {\n            $query->whereBetween('create_time', $value);\n        } else {\n            $query->where('create_time', '=', $value);\n        }\n    }\n\n    /**\n     * 更新时间范围搜索\n     * @param mixed $query\n     * @param mixed $value\n     */\n    public function searchUpdateTimeAttr($query, $value)\n    {\n        if (is_array($value)) {\n            $query->whereBetween('update_time', $value);\n        } else {\n            $query->where('update_time', '=', $value);\n        }\n    }\n\n    /**\n     * 模型启动事件\n     * @return void\n     */\n    protected static function boot(): void\n    {\n        parent::boot();\n\n        // 创建前事件\n        static::creating(function ($model) {\n            $info = getCurrentInfo();\n            $schema = $model->getConnection()->getSchemaBuilder();\n            if ($info && $schema->hasColumn($model->getTable(), 'created_by')) {\n                $model->created_by = $info['id'];\n            }\n        });\n\n        // 保存前事件\n        static::saving(function ($model) {\n            $info = getCurrentInfo();\n            $schema = $model->getConnection()->getSchemaBuilder();\n            if ($info && $schema->hasColumn($model->getTable(), 'updated_by')) {\n                $model->updated_by = $info['id'];\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/basic/think/BaseLogic.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\basic\\think;\n\nuse support\\think\\Db;\nuse plugin\\saiadmin\\basic\\AbstractLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\n\n/**\n * ThinkORM 逻辑层基类\n */\nclass BaseLogic extends AbstractLogic\n{\n    /**\n     * 数据库事务操作\n     * @param callable $closure\n     * @param bool $isTran\n     * @return mixed\n     */\n    public function transaction(callable $closure, bool $isTran = true): mixed\n    {\n        return $isTran ? Db::transaction($closure) : $closure();\n    }\n\n    /**\n     * 添加数据\n     * @param array $data\n     * @return mixed\n     */\n    public function add(array $data): mixed\n    {\n        $model = $this->model->create($data);\n        return $model->getKey();\n    }\n\n    /**\n     * 修改数据\n     * @param mixed $id\n     * @param array $data\n     * @return mixed\n     */\n    public function edit($id, array $data): mixed\n    {\n        $model = $this->model->findOrEmpty($id);\n        if ($model->isEmpty()) {\n            throw new ApiException('数据不存在');\n        }\n        return $model->save($data);\n    }\n\n    /**\n     * 读取数据\n     * @param mixed $id\n     * @return mixed\n     */\n    public function read($id): mixed\n    {\n        $model = $this->model->findOrEmpty($id);\n        if ($model->isEmpty()) {\n            throw new ApiException('数据不存在');\n        }\n        return $model;\n    }\n\n    /**\n     * 删除数据\n     * @param mixed $ids\n     * @return bool\n     */\n    public function destroy($ids): bool\n    {\n        return $this->model->destroy($ids);\n    }\n\n    /**\n     * 搜索器搜索\n     * @param array $searchWhere\n     * @return mixed\n     */\n    public function search(array $searchWhere = []): mixed\n    {\n        $withSearch = array_keys($searchWhere);\n        $data = [];\n        foreach ($searchWhere as $key => $value) {\n            if ($value !== '' && $value !== null && $value !== []) {\n                $data[$key] = $value;\n            }\n        }\n        $withSearch = array_keys($data);\n        return $this->model->withSearch($withSearch, $data);\n    }\n\n    /**\n     * 分页查询数据\n     * @param mixed $query\n     * @return mixed\n     */\n    public function getList($query): mixed\n    {\n        $request = request();\n        $saiType = $request ? $request->input('saiType', 'list') : 'list';\n        $page = $request ? $request->input('page', 1) : 1;\n        $limit = $request ? $request->input('limit', 10) : 10;\n        $orderField = $request ? $request->input('orderField', '') : '';\n        $orderType = $request ? $request->input('orderType', $this->orderType) : $this->orderType;\n\n        if (empty($orderField)) {\n            $orderField = $this->orderField !== '' ? $this->orderField : $this->model->getPk();\n        }\n\n        $query->order($orderField, $orderType);\n\n        if ($saiType === 'all') {\n            return $query->select()->toArray();\n        }\n\n        return $query->paginate($limit, false, ['page' => $page])->toArray();\n    }\n\n    /**\n     * 获取全部数据\n     * @param mixed $query\n     * @return mixed\n     */\n    public function getAll($query): mixed\n    {\n        $request = request();\n        $orderField = $request ? $request->input('orderField', '') : '';\n        $orderType = $request ? $request->input('orderType', $this->orderType) : $this->orderType;\n\n        if (empty($orderField)) {\n            $orderField = $this->orderField !== '' ? $this->orderField : $this->model->getPk();\n        }\n\n        $query->order($orderField, $orderType);\n        return $query->select()->toArray();\n    }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/basic/think/BaseModel.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\basic\\think;\n\nuse support\\think\\Model;\nuse think\\model\\concern\\SoftDelete;\nuse plugin\\saiadmin\\basic\\contracts\\ModelInterface;\n\n/**\n * ThinkORM 模型基类\n */\nclass BaseModel extends Model implements ModelInterface\n{\n    use SoftDelete;\n\n    /**\n     * 删除时间字段\n     * @var string\n     */\n    protected $deleteTime = 'delete_time';\n\n    /**\n     * 创建时间字段\n     * @var string\n     */\n    protected $createTime = 'create_time';\n\n    /**\n     * 更新时间字段\n     * @var string\n     */\n    protected $updateTime = 'update_time';\n\n    /**\n     * 隐藏字段\n     * @var array\n     */\n    protected $hidden = ['delete_time'];\n\n    /**\n     * 只读字段\n     * @var array\n     */\n    protected $readonly = ['created_by', 'create_time'];\n\n    /**\n     * 获取表名\n     * @return string\n     */\n    public function getTableName(): string\n    {\n        return $this->getTable();\n    }\n\n    /**\n     * 获取主键名\n     * @return string\n     */\n    public function getPrimaryKeyName(): string\n    {\n        return $this->getPk();\n    }\n\n    /**\n     * 添加时间范围搜索\n     * @param $query\n     * @param $value\n     */\n    public function searchCreateTimeAttr($query, $value)\n    {\n        if (is_array($value)) {\n            $query->whereBetween('create_time', $value);\n        } else {\n            $query->where('create_time', '=', $value);\n        }\n    }\n\n    /**\n     * 更新时间范围搜索\n     * @param mixed $query\n     * @param mixed $value\n     */\n    public function searchUpdateTimeAttr($query, $value)\n    {\n        if (is_array($value)) {\n            $query->whereBetween('update_time', $value);\n        } else {\n            $query->where('update_time', '=', $value);\n        }\n    }\n\n    /**\n     * 新增前事件\n     * @param Model $model\n     * @return void\n     */\n    public static function onBeforeInsert($model): void\n    {\n        $info = getCurrentInfo();\n        $info && $model->setAttr('created_by', $info['id']);\n    }\n\n    /**\n     * 写入前事件\n     * @param Model $model\n     * @return void\n     */\n    public static function onBeforeWrite($model): void\n    {\n        $info = getCurrentInfo();\n        $info && $model->setAttr('updated_by', $info['id']);\n    }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/command/SaiOrm.php",
    "content": "<?php\n\nnamespace plugin\\saiadmin\\command;\n\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Symfony\\Component\\Console\\Style\\SymfonyStyle;\nuse Symfony\\Component\\Console\\Question\\ChoiceQuestion;\n\n/**\n * SaiAdmin ORM 切换命令\n * 用于切换 saiadmin 插件的 ORM 实现 (think / eloquent)\n */\nclass SaiOrm extends Command\n{\n    protected static $defaultName = 'sai:orm';\n    protected static $defaultDescription = '切换 SaiAdmin 使用的 ORM';\n\n    /**\n     * ORM 源文件目录\n     */\n    protected string $ormSourcePath;\n\n    /**\n     * 目标插件目录\n     */\n    protected string $pluginAppPath;\n\n    /**\n     * ORM 选项配置\n     */\n    protected array $ormOptions = [\n        'think' => '1. ThinkORM (TopThink)',\n        'eloquent' => '2. Eloquent ORM (Laravel)',\n        'exit' => '3. 退出，什么也不做',\n    ];\n\n    protected function configure(): void\n    {\n        $this->setName('sai:orm')\n            ->setDescription('切换 SaiAdmin 使用的 ORM');\n    }\n\n    protected function execute(InputInterface $input, OutputInterface $output): int\n    {\n        $io = new SymfonyStyle($input, $output);\n\n        $io->title('SaiAdmin ORM 切换工具');\n        $io->text('此命令只切换 saiadmin 框架核心使用的 ORM, 不影响其他模块功能, 多种 ORM 可以同时使用!');\n        $io->newLine();\n\n        // 创建选择问题（编号从1开始）\n        $helper = $this->getHelper('question');\n        $choices = [\n            1 => '1. ThinkORM (TopThink)',\n            2 => '2. Eloquent ORM (Laravel)',\n            3 => '3. 退出，什么也不做',\n        ];\n        $question = new ChoiceQuestion(\n            '请选择要使用的 ORM 框架:',\n            $choices,\n            1 // 默认选中第一个\n        );\n        $question->setErrorMessage('选项 %s 无效');\n\n        // 获取用户选择\n        $selected = $helper->ask($input, $output, $question);\n\n        // 根据选择的文本反查 key\n        $selectedKey = array_search($selected, $choices);\n\n        // 如果选择退出\n        if ($selectedKey == 3) {\n            $io->newLine();\n            $io->info('已退出，什么也没做。');\n            return Command::SUCCESS;\n        }\n\n        // 映射选项到 ORM 类型\n        $ormMap = [1 => 'think', 2 => 'eloquent'];\n        $orm = $ormMap[$selectedKey];\n\n        $io->newLine();\n        $io->section(\"您选择了: {$selected}\");\n\n        // 确认操作\n        if (!$io->confirm('确定要切换吗？这将覆盖 saiadmin 核心代码文件', true)) {\n            $io->warning('操作已取消');\n            return Command::SUCCESS;\n        }\n\n        // 设置路径\n        $this->ormSourcePath = BASE_PATH . '/vendor/saithink/saiadmin/src/orm/' . $orm . '/app';\n        $this->pluginAppPath = BASE_PATH . '/plugin/saiadmin/app';\n\n        // 检查源目录是否存在\n        if (!is_dir($this->ormSourcePath)) {\n            $io->error(\"ORM 源目录不存在: {$this->ormSourcePath}\");\n            return Command::FAILURE;\n        }\n\n        $io->section('开始复制文件...');\n\n        try {\n            $copiedFiles = $this->copyDirectory($this->ormSourcePath, $this->pluginAppPath, $io);\n\n            $io->newLine();\n            $io->success([\n                \"ORM 切换成功！\",\n                \"已切换到: {$selected}\",\n                \"复制文件数: {$copiedFiles}\"\n            ]);\n\n            $io->note([\n                '请重启 webman 服务使更改生效',\n                '命令: php webman restart 或 php windows.php'\n            ]);\n\n            return Command::SUCCESS;\n        } catch (\\Exception $e) {\n            $io->error(\"切换失败: \" . $e->getMessage());\n            return Command::FAILURE;\n        }\n    }\n\n    /**\n     * 递归复制目录\n     * @param string $source 源目录\n     * @param string $dest 目标目录\n     * @param SymfonyStyle $io 输出接口\n     * @return int 复制的文件数量\n     */\n    protected function copyDirectory(string $source, string $dest, SymfonyStyle $io): int\n    {\n        $count = 0;\n\n        if (!is_dir($dest)) {\n            mkdir($dest, 0755, true);\n        }\n\n        $iterator = new \\RecursiveIteratorIterator(\n            new \\RecursiveDirectoryIterator($source, \\RecursiveDirectoryIterator::SKIP_DOTS),\n            \\RecursiveIteratorIterator::SELF_FIRST\n        );\n\n        // 先计算文件总数用于进度条\n        $files = [];\n        foreach ($iterator as $item) {\n            if (!$item->isDir()) {\n                $files[] = $item;\n            }\n        }\n\n        // 创建进度条\n        $io->progressStart(count($files));\n\n        // 重新遍历并复制\n        $iterator = new \\RecursiveIteratorIterator(\n            new \\RecursiveDirectoryIterator($source, \\RecursiveDirectoryIterator::SKIP_DOTS),\n            \\RecursiveIteratorIterator::SELF_FIRST\n        );\n\n        foreach ($iterator as $item) {\n            $destPath = $dest . DIRECTORY_SEPARATOR . $iterator->getSubPathName();\n\n            if ($item->isDir()) {\n                if (!is_dir($destPath)) {\n                    mkdir($destPath, 0755, true);\n                }\n            } else {\n                copy($item->getPathname(), $destPath);\n                $count++;\n                $io->progressAdvance();\n            }\n        }\n\n        $io->progressFinish();\n\n        return $count;\n    }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/command/SaiPlugin.php",
    "content": "<?php\n\nnamespace plugin\\saiadmin\\command;\n\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Symfony\\Component\\Console\\Style\\SymfonyStyle;\n\n/**\n * SaiAdmin 插件创建命令\n * 用于创建 saiadmin 插件\n */\nclass SaiPlugin extends Command\n{\n    protected static $defaultName = 'sai:plugin';\n    protected static $defaultDescription = '创建 SaiAdmin 插件';\n\n    /**\n     * @return void\n     */\n    protected function configure()\n    {\n        $this->addArgument('name', InputArgument::REQUIRED, 'App plugin name');\n    }\n\n    /**\n     * @param InputInterface $input\n     * @param OutputInterface $output\n     * @return int\n     */\n    protected function execute(InputInterface $input, OutputInterface $output): int\n    {\n        $io = new SymfonyStyle($input, $output);\n        $io->title('SaiAdmin 插件创建工具');\n        $io->text('此命令用于创建基于webman的 saiadmin 插件, 用于扩展 saiadmin 框架功能!');\n        $io->newLine();\n\n        $name = $input->getArgument('name');\n        $io->text(\"创建 SaiAdmin 插件 $name\");\n\n        if (strpos($name, '/') !== false) {\n            $io->error('名称错误,名称必须不包含字符 \\'/\\'');\n            return self::FAILURE;\n        }\n\n        // Create dir config/plugin/$name\n        if (is_dir($plugin_config_path = base_path() . \"/plugin/$name\")) {\n            $io->error(\"目录 $plugin_config_path 已存在\");\n            return self::FAILURE;\n        }\n\n        $this->createAll($name);\n\n        $io->newLine();\n        $io->success(\"SaiAdmin 插件创建成功！\");\n\n        return self::SUCCESS;\n    }\n\n    /**\n     * @param $name\n     * @return void\n     */\n    protected function createAll($name)\n    {\n        $base_path = base_path();\n        $this->mkdir(\"$base_path/plugin/$name/app\", 0777, true);\n        $this->mkdir(\"$base_path/plugin/$name/app/admin/controller\", 0777, true);\n        $this->mkdir(\"$base_path/plugin/$name/app/admin/logic\", 0777, true);\n        $this->mkdir(\"$base_path/plugin/$name/app/api/controller\", 0777, true);\n        $this->mkdir(\"$base_path/plugin/$name/app/api/logic\", 0777, true);\n        $this->mkdir(\"$base_path/plugin/$name/app/cache\", 0777, true);\n        $this->mkdir(\"$base_path/plugin/$name/app/event\", 0777, true);\n        $this->mkdir(\"$base_path/plugin/$name/app/model\", 0777, true);\n        $this->mkdir(\"$base_path/plugin/$name/app/middleware\", 0777, true);\n        $this->mkdir(\"$base_path/plugin/$name/config\", 0777, true);\n        $this->createControllerFile(\"$base_path/plugin/$name/app/api/controller/IndexController.php\", $name);\n        $this->createFunctionsFile(\"$base_path/plugin/$name/app/functions.php\");\n        $this->createConfigFiles(\"$base_path/plugin/$name/config\", $name);\n    }\n\n    /**\n     * @param $path\n     * @return void\n     */\n    protected function mkdir($path, $mode = 0777, $recursive = false)\n    {\n        if (is_dir($path)) {\n            return;\n        }\n        echo \"Create $path\\r\\n\";\n        mkdir($path, $mode, $recursive);\n    }\n\n    /**\n     * @param $path\n     * @param $name\n     * @return void\n     */\n    protected function createControllerFile($path, $name)\n    {\n        $content = <<<EOF\n<?php\n\nnamespace plugin\\\\$name\\\\app\\\\api\\\\controller;\n\nuse plugin\\\\saiadmin\\\\basic\\\\OpenController;\n\nclass IndexController extends OpenController\n{\n\n    public function index()\n    {\n        return \\$this->success([\n            'app' => '$name',\n            'version' => '1.0.0',\n        ]);\n    }\n\n}\n\n\nEOF;\n        file_put_contents($path, $content);\n\n    }\n\n    /**\n     * @param $file\n     * @return void\n     */\n    protected function createFunctionsFile($file)\n    {\n        $content = <<<EOF\n<?php\n/**\n * Here is your custom functions.\n */\n\n\n\nEOF;\n        file_put_contents($file, $content);\n    }\n\n    /**\n     * @param $base\n     * @param $name\n     * @return void\n     */\n    protected function createConfigFiles($base, $name)\n    {\n        // app.php\n        $content = <<<EOF\n<?php\n\nuse support\\\\Request;\n\nreturn [\n    'debug' => true,\n    'controller_suffix' => 'Controller',\n    'controller_reuse' => false,\n    'version' => '1.0.0'\n];\n\nEOF;\n        file_put_contents(\"$base/app.php\", $content);\n\n        // autoload.php\n        $content = <<<EOF\n<?php\nreturn [\n    'files' => [\n        base_path() . '/plugin/$name/app/functions.php',\n    ]\n];\nEOF;\n        file_put_contents(\"$base/autoload.php\", $content);\n\n        // container.php\n        $content = <<<EOF\n<?php\nreturn new Webman\\\\Container;\n\nEOF;\n        file_put_contents(\"$base/container.php\", $content);\n\n\n        // exception.php\n        $content = <<<EOF\n<?php\n\nreturn [\n    '' => \\\\plugin\\\\saiadmin\\\\app\\\\exception\\\\Handler::class,\n];\n\nEOF;\n        file_put_contents(\"$base/exception.php\", $content);\n\n        // log.php\n        $content = <<<EOF\n<?php\n\nreturn [\n    'default' => [\n        'handlers' => [\n            [\n                'class' => Monolog\\\\Handler\\\\RotatingFileHandler::class,\n                'constructor' => [\n                    runtime_path() . '/logs/$name.log',\n                    7,\n                    Monolog\\\\Logger::DEBUG,\n                ],\n                'formatter' => [\n                    'class' => Monolog\\\\Formatter\\\\LineFormatter::class,\n                    'constructor' => [null, 'Y-m-d H:i:s', true],\n                ],\n            ]\n        ],\n    ],\n];\n\nEOF;\n        file_put_contents(\"$base/log.php\", $content);\n\n        // middleware.php\n        $content = <<<EOF\n<?php\n\nuse plugin\\saiadmin\\app\\middleware\\SystemLog;\nuse plugin\\saiadmin\\app\\middleware\\CheckLogin;\nuse plugin\\saiadmin\\app\\middleware\\CheckAuth;\n\nreturn [\n    'admin' => [\n        CheckLogin::class,\n        CheckAuth::class,\n        SystemLog::class,\n    ],\n    'api' => [\n    ]\n];\n\nEOF;\n        file_put_contents(\"$base/middleware.php\", $content);\n\n        // process.php\n        $content = <<<EOF\n<?php\nreturn [];\n\nEOF;\n        file_put_contents(\"$base/process.php\", $content);\n\n        // route.php\n        $content = <<<EOF\n<?php\n\nuse Webman\\\\Route;\n\n\nEOF;\n        file_put_contents(\"$base/route.php\", $content);\n\n        // static.php\n        $content = <<<EOF\n<?php\n\nreturn [\n    'enable' => true,\n    'middleware' => [],    // Static file Middleware\n];\n\nEOF;\n        file_put_contents(\"$base/static.php\", $content);\n\n        // translation.php\n        $content = <<<EOF\n<?php\n\nreturn [\n    // Default language\n    'locale' => 'zh_CN',\n    // Fallback language\n    'fallback_locale' => ['zh_CN', 'en'],\n    // Folder where language files are stored\n    'path' => base_path() . \"/plugin/$name/resource/translations\",\n];\n\nEOF;\n        file_put_contents(\"$base/translation.php\", $content);\n\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/command/SaiUpgrade.php",
    "content": "<?php\n\nnamespace plugin\\saiadmin\\command;\n\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Symfony\\Component\\Console\\Style\\SymfonyStyle;\n\n/**\n * SaiAdmin 升级命令\n * 用于从 vendor 目录升级 saiadmin 插件到最新版本\n */\nclass SaiUpgrade extends Command\n{\n    protected static $defaultName = 'sai:upgrade';\n    protected static $defaultDescription = '升级 SaiAdmin 插件到最新版本';\n\n    /**\n     * 升级源目录\n     */\n    protected string $sourcePath;\n\n    /**\n     * 目标插件目录\n     */\n    protected string $targetPath;\n\n    protected function configure(): void\n    {\n        $this->setName('sai:upgrade')\n            ->setDescription('升级 SaiAdmin 插件到最新版本');\n    }\n\n    protected function execute(InputInterface $input, OutputInterface $output): int\n    {\n        $io = new SymfonyStyle($input, $output);\n\n        $io->title('SaiAdmin 升级工具');\n        $io->text([\n            '此命令将从 vendor 目录复制最新版本的 saiadmin 插件文件到 plugin 目录',\n            '源目录: vendor/saithink/saiadmin/src/plugin/saiadmin',\n            '目标目录: plugin/saiadmin',\n        ]);\n        $io->newLine();\n\n        // 设置路径\n        $this->sourcePath = BASE_PATH . '/vendor/saithink/saiadmin/src/plugin/saiadmin';\n        $this->targetPath = BASE_PATH . '/plugin/saiadmin';\n\n        // 检查源目录是否存在\n        if (!is_dir($this->sourcePath)) {\n            $io->error([\n                \"升级源目录不存在: {$this->sourcePath}\",\n                \"请确保已通过 composer 安装了 saithink/saiadmin 包\",\n            ]);\n            return Command::FAILURE;\n        }\n\n        // 获取版本信息\n        $currentVersion = $this->getVersion($this->targetPath);\n        $latestVersion = $this->getVersion($this->sourcePath);\n\n        // 显示版本信息\n        $io->section('版本信息');\n        $io->table(\n            ['项目', '版本'],\n            [\n                ['当前版本', $currentVersion ?: '未知'],\n                ['最新版本', $latestVersion ?: '未知'],\n            ]\n        );\n\n        // 版本对比提示\n        if ($currentVersion && $latestVersion) {\n            if (version_compare($currentVersion, $latestVersion, '>=')) {\n                $io->success('当前已是最新版本！');\n                if (!$io->confirm('是否仍要继续覆盖安装？', false)) {\n                    $io->info('操作已取消');\n                    return Command::SUCCESS;\n                }\n            } else {\n                $io->info(\"发现新版本: {$currentVersion} → {$latestVersion}\");\n            }\n        }\n\n        // 警告信息\n        $io->warning([\n            \"注意：此操作将覆盖 {$this->targetPath} 目录的现有文件！\",\n            \"建议在执行前备份您的自定义修改。\",\n        ]);\n\n        // 确认操作\n        if (!$io->confirm('确定要执行升级操作吗？', false)) {\n            $io->info('操作已取消');\n            return Command::SUCCESS;\n        }\n\n        $io->section('开始升级...');\n\n        try {\n            $copiedFiles = $this->copyDirectory($this->sourcePath, $this->targetPath, $io);\n\n            $io->newLine();\n            $io->success([\n                \"SaiAdmin 升级成功！\",\n                \"复制文件数: {$copiedFiles}\",\n            ]);\n\n            $io->note([\n                '请重启 webman 服务使更改生效',\n                '命令: php webman restart 或 php windows.php',\n            ]);\n\n            return Command::SUCCESS;\n        } catch (\\Exception $e) {\n            $io->error(\"升级失败: \" . $e->getMessage());\n            return Command::FAILURE;\n        }\n    }\n\n    /**\n     * 递归复制目录\n     * @param string $source 源目录\n     * @param string $dest 目标目录\n     * @param SymfonyStyle $io 输出接口\n     * @return int 复制的文件数量\n     */\n    protected function copyDirectory(string $source, string $dest, SymfonyStyle $io): int\n    {\n        $count = 0;\n\n        if (!is_dir($dest)) {\n            mkdir($dest, 0755, true);\n        }\n\n        $iterator = new \\RecursiveIteratorIterator(\n            new \\RecursiveDirectoryIterator($source, \\RecursiveDirectoryIterator::SKIP_DOTS),\n            \\RecursiveIteratorIterator::SELF_FIRST\n        );\n\n        // 先计算文件总数用于进度条\n        $files = [];\n        foreach ($iterator as $item) {\n            if (!$item->isDir()) {\n                $files[] = $item;\n            }\n        }\n\n        // 创建进度条\n        $io->progressStart(count($files));\n\n        // 重新遍历并复制\n        $iterator = new \\RecursiveIteratorIterator(\n            new \\RecursiveDirectoryIterator($source, \\RecursiveDirectoryIterator::SKIP_DOTS),\n            \\RecursiveIteratorIterator::SELF_FIRST\n        );\n\n        foreach ($iterator as $item) {\n            $destPath = $dest . DIRECTORY_SEPARATOR . $iterator->getSubPathName();\n\n            if ($item->isDir()) {\n                if (!is_dir($destPath)) {\n                    mkdir($destPath, 0755, true);\n                }\n            } else {\n                copy($item->getPathname(), $destPath);\n                $count++;\n                $io->progressAdvance();\n            }\n        }\n\n        $io->progressFinish();\n\n        return $count;\n    }\n\n    /**\n     * 从目录中获取版本号\n     * @param string $path 插件目录路径\n     * @return string|null 版本号\n     */\n    protected function getVersion(string $path): ?string\n    {\n        $configFile = $path . '/config/app.php';\n\n        if (!file_exists($configFile)) {\n            return null;\n        }\n\n        try {\n            $config = include $configFile;\n            return $config['version'] ?? null;\n        } catch (\\Exception $e) {\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/config/app.php",
    "content": "<?php\n\nuse support\\Request;\n\nreturn [\n    'debug' => true,\n    'controller_suffix' => 'Controller',\n    'controller_reuse' => false,\n    'version' => '6.0.8'\n];\n"
  },
  {
    "path": "src/plugin/saiadmin/config/autoload.php",
    "content": "<?php\nreturn [\n    'files' => [\n        base_path() . '/plugin/saiadmin/app/functions.php',\n    ]\n];"
  },
  {
    "path": "src/plugin/saiadmin/config/container.php",
    "content": "<?php\nreturn new Webman\\Container;\n"
  },
  {
    "path": "src/plugin/saiadmin/config/database.php",
    "content": "<?php\nreturn  [];"
  },
  {
    "path": "src/plugin/saiadmin/config/event.php",
    "content": "<?php\nreturn [\n    'user.login' => [\n        [plugin\\saiadmin\\app\\event\\SystemUser::class, 'login'],\n    ],\n    'user.operateLog' => [\n        [plugin\\saiadmin\\app\\event\\SystemUser::class, 'operateLog'],\n    ]\n];"
  },
  {
    "path": "src/plugin/saiadmin/config/exception.php",
    "content": "<?php\n\nreturn [\n    '' => \\plugin\\saiadmin\\app\\exception\\Handler::class,\n];\n"
  },
  {
    "path": "src/plugin/saiadmin/config/log.php",
    "content": "<?php\n\nreturn [\n    'default' => [\n        'handlers' => [\n            [\n                'class' => Monolog\\Handler\\RotatingFileHandler::class,\n                'constructor' => [\n                    runtime_path() . '/logs/saiadmin.log',\n                    7,\n                    Monolog\\Logger::DEBUG,\n                ],\n                'formatter' => [\n                    'class' => Monolog\\Formatter\\LineFormatter::class,\n                    'constructor' => [null, 'Y-m-d H:i:s', true],\n                ],\n            ]\n        ],\n    ],\n];\n"
  },
  {
    "path": "src/plugin/saiadmin/config/middleware.php",
    "content": "<?php\n\nuse plugin\\saiadmin\\app\\middleware\\SystemLog;\nuse plugin\\saiadmin\\app\\middleware\\CheckLogin;\nuse plugin\\saiadmin\\app\\middleware\\CheckAuth;\n\nreturn [\n    '' => [\n        CheckLogin::class,\n        CheckAuth::class,\n        SystemLog::class,\n    ]\n];\n"
  },
  {
    "path": "src/plugin/saiadmin/config/process.php",
    "content": "<?php\nreturn [\n    'task'  => [\n        'handler'  => plugin\\saiadmin\\process\\Task::class\n    ]\n];\n"
  },
  {
    "path": "src/plugin/saiadmin/config/route.php",
    "content": "<?php\n\nuse Webman\\Route;\n\nRoute::group('/core', function () {\n\n    Route::get('/install', [plugin\\saiadmin\\app\\controller\\InstallController::class, 'index']);\n    Route::post('/install/install', [plugin\\saiadmin\\app\\controller\\InstallController::class, 'install']);\n\n    Route::get('/captcha', [plugin\\saiadmin\\app\\controller\\LoginController::class, 'captcha']);\n    Route::post('/login', [plugin\\saiadmin\\app\\controller\\LoginController::class, 'login']);\n\n    Route::get('/system/user', [plugin\\saiadmin\\app\\controller\\SystemController::class, 'userInfo']);\n    Route::get(\"/system/dictAll\", [plugin\\saiadmin\\app\\controller\\SystemController::class, 'dictAll']);\n    Route::get('/system/menu', [plugin\\saiadmin\\app\\controller\\SystemController::class, 'menu']);\n\n    Route::get('/system/statistics', [plugin\\saiadmin\\app\\controller\\SystemController::class, 'statistics']);\n    Route::get('/system/loginChart', [plugin\\saiadmin\\app\\controller\\SystemController::class, 'loginChart']);\n    Route::get('/system/loginBarChart', [plugin\\saiadmin\\app\\controller\\SystemController::class, 'loginBarChart']);\n    Route::get('/system/clearAllCache', [plugin\\saiadmin\\app\\controller\\SystemController::class, 'clearAllCache']);\n\n    Route::get(\"/system/getResourceCategory\", [plugin\\saiadmin\\app\\controller\\SystemController::class, 'getResourceCategory']);\n    Route::get(\"/system/getResourceList\", [plugin\\saiadmin\\app\\controller\\SystemController::class, 'getResourceList']);\n    Route::post(\"/system/saveNetworkImage\", [plugin\\saiadmin\\app\\controller\\SystemController::class, 'saveNetworkImage']);\n    Route::post(\"/system/uploadImage\", [plugin\\saiadmin\\app\\controller\\SystemController::class, 'uploadImage']);\n    Route::post(\"/system/uploadFile\", [plugin\\saiadmin\\app\\controller\\SystemController::class, 'uploadFile']);\n    Route::post(\"/system/chunkUpload\", [plugin\\saiadmin\\app\\controller\\SystemController::class, 'chunkUpload']);\n    Route::get(\"/system/getUserList\", [plugin\\saiadmin\\app\\controller\\SystemController::class, 'getUserList']);\n    Route::get(\"/system/getLoginLogList\", [plugin\\saiadmin\\app\\controller\\SystemController::class, 'getLoginLogList']);\n    Route::get(\"/system/getOperationLogList\", [plugin\\saiadmin\\app\\controller\\SystemController::class, 'getOperationLogList']);\n\n    // 用户管理\n    fastRoute(\"user\", \\plugin\\saiadmin\\app\\controller\\system\\SystemUserController::class);\n    Route::post(\"/user/updateInfo\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemUserController::class, 'updateInfo']);\n    Route::post(\"/user/modifyPassword\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemUserController::class, 'modifyPassword']);\n    Route::post(\"/user/clearCache\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemUserController::class, 'clearCache']);\n    Route::post(\"/user/initUserPassword\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemUserController::class, 'initUserPassword']);\n    Route::post(\"/user/setHomePage\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemUserController::class, 'setHomePage']);\n\n    // 角色管理\n    fastRoute('role', \\plugin\\saiadmin\\app\\controller\\system\\SystemRoleController::class);\n    Route::get(\"/role/accessRole\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemRoleController::class, 'accessRole']);\n    Route::get(\"/role/getMenuByRole\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemRoleController::class, 'getMenuByRole']);\n    Route::post(\"/role/menuPermission\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemRoleController::class, 'menuPermission']);\n\n    // 部门管理\n    fastRoute(\"dept\", \\plugin\\saiadmin\\app\\controller\\system\\SystemDeptController::class);\n    Route::get(\"/dept/accessDept\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemDeptController::class, 'accessDept']);\n\n    // 岗位管理\n    fastRoute('post', \\plugin\\saiadmin\\app\\controller\\system\\SystemPostController::class);\n    Route::get(\"/post/accessPost\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemPostController::class, 'accessPost']);\n    Route::post(\"/post/downloadTemplate\", [plugin\\saiadmin\\app\\controller\\system\\SystemPostController::class, 'downloadTemplate']);\n\n    // 菜单管理\n    fastRoute('menu', \\plugin\\saiadmin\\app\\controller\\system\\SystemMenuController::class);\n    Route::get(\"/menu/accessMenu\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemMenuController::class, 'accessMenu']);\n    // 字典类型管理\n    fastRoute('dictType', \\plugin\\saiadmin\\app\\controller\\system\\SystemDictTypeController::class);\n    // 字典数据管理\n    fastRoute('dictData', \\plugin\\saiadmin\\app\\controller\\system\\SystemDictDataController::class);\n    // 附件管理\n    fastRoute('attachment', \\plugin\\saiadmin\\app\\controller\\system\\SystemAttachmentController::class);\n    Route::post(\"/attachment/move\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemAttachmentController::class, 'move']);\n    // 附件分类\n    fastRoute('category', \\plugin\\saiadmin\\app\\controller\\system\\SystemCategoryController::class);\n    // 系统设置\n    fastRoute('configGroup', \\plugin\\saiadmin\\app\\controller\\system\\SystemConfigGroupController::class);\n    Route::post(\"/configGroup/email\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemConfigGroupController::class, 'email']);\n    fastRoute('config', \\plugin\\saiadmin\\app\\controller\\system\\SystemConfigController::class);\n    Route::post(\"/config/batchUpdate\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemConfigController::class, 'batchUpdate']);\n\n    // 日志管理\n    Route::get(\"/logs/getLoginLogPageList\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemLogController::class, 'getLoginLogPageList']);\n    Route::delete(\"/logs/deleteLoginLog\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemLogController::class, 'deleteLoginLog']);\n    Route::get(\"/logs/getOperLogPageList\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemLogController::class, 'getOperLogPageList']);\n    Route::delete(\"/logs/deleteOperLog\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemLogController::class, 'deleteOperLog']);\n    fastRoute(\"email\", \\plugin\\saiadmin\\app\\controller\\system\\SystemMailController::class);\n\n    // 服务管理\n    Route::get(\"/server/monitor\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemServerController::class, 'monitor']);\n    Route::get(\"/server/cache\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemServerController::class, 'cache']);\n    Route::post(\"/server/clear\", [\\plugin\\saiadmin\\app\\controller\\system\\SystemServerController::class, 'clear']);\n\n    // 数据表维护\n    Route::get(\"/database/index\", [\\plugin\\saiadmin\\app\\controller\\system\\DataBaseController::class, 'index']);\n    Route::get(\"/database/recycle\", [\\plugin\\saiadmin\\app\\controller\\system\\DataBaseController::class, 'recycle']);\n    Route::delete(\"/database/delete\", [\\plugin\\saiadmin\\app\\controller\\system\\DataBaseController::class, 'delete']);\n    Route::post(\"/database/recovery\", [\\plugin\\saiadmin\\app\\controller\\system\\DataBaseController::class, 'recovery']);\n    Route::get(\"/database/dataSource\", [\\plugin\\saiadmin\\app\\controller\\system\\DataBaseController::class, 'source']);\n    Route::get(\"/database/detailed\", [\\plugin\\saiadmin\\app\\controller\\system\\DataBaseController::class, 'detailed']);\n    Route::post(\"/database/optimize\", [\\plugin\\saiadmin\\app\\controller\\system\\DataBaseController::class, 'optimize']);\n    Route::post(\"/database/fragment\", [\\plugin\\saiadmin\\app\\controller\\system\\DataBaseController::class, 'fragment']);\n\n});\n\nRoute::group('/tool', function () {\n\n    // 定时任务\n    fastRoute('crontab', \\plugin\\saiadmin\\app\\controller\\tool\\CrontabController::class);\n    Route::post(\"/crontab/run\", [\\plugin\\saiadmin\\app\\controller\\tool\\CrontabController::class, 'run']);\n    Route::get(\"/crontab/logPageList\", [\\plugin\\saiadmin\\app\\controller\\tool\\CrontabController::class, 'logPageList']);\n    Route::delete('/crontab/deleteCrontabLog', [\\plugin\\saiadmin\\app\\controller\\tool\\CrontabController::class, 'deleteCrontabLog']);\n\n    // 代码生成\n    fastRoute('code', \\plugin\\saiadmin\\app\\controller\\tool\\GenerateTablesController::class);\n    Route::get(\"/code/getTableColumns\", [\\plugin\\saiadmin\\app\\controller\\tool\\GenerateTablesController::class, 'getTableColumns']);\n    Route::get(\"/code/preview\", [\\plugin\\saiadmin\\app\\controller\\tool\\GenerateTablesController::class, 'preview']);\n    Route::post(\"/code/loadTable\", [\\plugin\\saiadmin\\app\\controller\\tool\\GenerateTablesController::class, 'loadTable']);\n    Route::post(\"/code/generate\", [\\plugin\\saiadmin\\app\\controller\\tool\\GenerateTablesController::class, 'generate']);\n    Route::post(\"/code/generateFile\", [\\plugin\\saiadmin\\app\\controller\\tool\\GenerateTablesController::class, 'generateFile']);\n    Route::post(\"/code/sync\", [\\plugin\\saiadmin\\app\\controller\\tool\\GenerateTablesController::class, 'sync']);\n});\n\nRoute::disableDefaultRoute('saiadmin');"
  },
  {
    "path": "src/plugin/saiadmin/config/saithink.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nreturn [\n\n    'access_exp' => 8 * 60 * 60, // 登录token有效期，默认8小时\n\n\t// 验证码存储模式\n    'captcha' => [\n        // 验证码存储模式 session或者cache\n        'mode' => getenv('CAPTCHA_MODE'),\n        // 验证码过期时间 (秒)\n        'expire' => 300,\n    ],\n\n    // excel模板下载路径\n    'template' => base_path(). '/plugin/saiadmin/public/template',\n\n    // excel导出文件路径\n    'export_path' => base_path() . '/plugin/saiadmin/public/export/',\n\n    // 文件开启hash验证，开启后上传文件将会判断数据库中是否存在，如果存在直接获取\n    'file_hash' => false,\n\n    // 用户信息缓存\n    'user_cache' => [\n        'prefix' => 'saiadmin:user_cache:info_',\n        'expire' => 60 * 60 * 4,\n        'dept' => 'saiadmin:user_cache:dept_',\n        'role' => 'saiadmin:user_cache:role_',\n        'post' => 'saiadmin:user_cache:post_',\n    ],\n\n    // 用户权限缓存\n    'button_cache' => [\n        'prefix' => 'saiadmin:button_cache:user_',\n        'expire' => 60 * 60 * 2,\n        'all' => 'saiadmin:button_cache:all',\n        'role' => 'saiadmin:button_cache:role_',\n        'tag' => 'saiadmin:button_cache',\n    ],\n\n    // 用户菜单缓存\n    'menu_cache' => [\n        'prefix' => 'saiadmin:menu_cache:user_',\n        'expire' => 60 * 60 * 24 * 7,\n        'tag' => 'saiadmin:menu_cache',\n    ],\n\n    // 字典缓存\n    'dict_cache' => [\n        'expire' => 60 * 60 * 24 * 365,\n        'tag' => 'saiadmin:dict_cache',\n    ],\n\n    // 配置数据缓存\n    'config_cache' => [\n        'expire' => 60 * 60 * 24 * 365,\n        'prefix' => 'saiadmin:config_cache:config_',\n        'tag' => 'saiadmin:config_cache'\n    ],\n\n    // 反射缓存\n    'reflection_cache' => [\n        'tag' => 'saiadmin:reflection',\n        'expire' => 60 * 60 * 24 * 365,\n        'no_need' => 'saiadmin:reflection_cache:no_need_',\n        'attr' => 'saiadmin:reflection_cache:attr_',\n    ],\n\n];\n"
  },
  {
    "path": "src/plugin/saiadmin/config/static.php",
    "content": "<?php\n\nreturn [\n    'enable' => true,\n    'middleware' => [],    // Static file Middleware\n];\n"
  },
  {
    "path": "src/plugin/saiadmin/config/translation.php",
    "content": "<?php\n\nreturn [\n    // Default language\n    'locale' => 'zh_CN',\n    // Fallback language\n    'fallback_locale' => ['zh_CN', 'en'],\n    // Folder where language files are stored\n    'path' => base_path() . \"/plugin/saiadmin/resource/translations\",\n];\n"
  },
  {
    "path": "src/plugin/saiadmin/config/view.php",
    "content": "<?php\n\nuse support\\view\\Raw;\nuse support\\view\\Twig;\nuse support\\view\\Blade;\nuse support\\view\\ThinkPHP;\n\nreturn [\n    'handler' => Twig::class\n];\n"
  },
  {
    "path": "src/plugin/saiadmin/db/plugin.sql",
    "content": "-- ----------------------------\n-- Records of sa_system_menu\n-- ----------------------------\nINSERT INTO `sa_system_menu` SELECT NULL, 0, '插件市场', 'Plugin', '', 2, '/plugin', '/plugin/saipackage/install/index', NULL, 'ri:apps-2-ai-line', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sa_system_menu` WHERE `code` = 'Plugin' AND `create_time` = '2026-01-01 00:00:00' AND ISNULL(`delete_time`));\n"
  },
  {
    "path": "src/plugin/saiadmin/db/saiadmin-6.0.sql",
    "content": "\nSET NAMES utf8mb4;\nSET FOREIGN_KEY_CHECKS = 0;\n\n-- ----------------------------\n-- Table structure for sa_system_attachment\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_attachment`;\nCREATE TABLE `sa_system_attachment`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `category_id` int(11) NULL DEFAULT 0 COMMENT '文件分类',\n  `storage_mode` smallint(6) NULL DEFAULT 1 COMMENT '存储模式 (1 本地 2 阿里云 3 七牛云 4 腾讯云)',\n  `origin_name` varchar(255) NULL DEFAULT NULL COMMENT '原文件名',\n  `object_name` varchar(50) NULL DEFAULT NULL COMMENT '新文件名',\n  `hash` varchar(64) NULL DEFAULT NULL COMMENT '文件hash',\n  `mime_type` varchar(255) NULL DEFAULT NULL COMMENT '资源类型',\n  `storage_path` varchar(100) NULL DEFAULT NULL COMMENT '存储目录',\n  `suffix` varchar(10) NULL DEFAULT NULL COMMENT '文件后缀',\n  `size_byte` bigint(20) NULL DEFAULT NULL COMMENT '字节数',\n  `size_info` varchar(50) NULL DEFAULT NULL COMMENT '文件大小',\n  `url` varchar(255) NULL DEFAULT NULL COMMENT 'url地址',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `hash`(`hash`) USING BTREE,\n  INDEX `idx_url`(`url`) USING BTREE,\n  INDEX `idx_create_time`(`create_time`) USING BTREE,\n  INDEX `idx_category_id`(`category_id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '附件信息表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_attachment\n-- ----------------------------\n\n-- ----------------------------\n-- Table structure for sa_system_category\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_category`;\nCREATE TABLE `sa_system_category`  (\n  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '分类ID',\n  `parent_id` int(11) NOT NULL DEFAULT 0 COMMENT '父id',\n  `level` varchar(255) NULL DEFAULT NULL COMMENT '组集关系',\n  `category_name` varchar(100) NOT NULL DEFAULT '' COMMENT '分类名称',\n  `sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序',\n  `status` tinyint(1) NULL DEFAULT 1 COMMENT '状态',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `pid`(`parent_id`) USING BTREE,\n  INDEX `sort`(`sort`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 6 COMMENT = '附件分类表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_category\n-- ----------------------------\nINSERT INTO `sa_system_category` VALUES (1, 0, '0,', '全部分类', 100, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_category` VALUES (2, 1, '0,1,', '图片分类', 100, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_category` VALUES (3, 1, '0,1,', '文件分类', 100, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_category` VALUES (4, 1, '0,1,', '系统图片', 100, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_category` VALUES (5, 1, '0,1,', '其他分类', 100, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_system_config\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_config`;\nCREATE TABLE `sa_system_config`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '编号',\n  `group_id` int(11) NULL DEFAULT NULL COMMENT '组id',\n  `key` varchar(32) NOT NULL COMMENT '配置键名',\n  `value` text NULL COMMENT '配置值',\n  `name` varchar(255) NULL DEFAULT NULL COMMENT '配置名称',\n  `input_type` varchar(32) NULL DEFAULT NULL COMMENT '数据输入类型',\n  `config_select_data` varchar(500) NULL DEFAULT NULL COMMENT '配置选项数据',\n  `sort` smallint(5) UNSIGNED NULL DEFAULT 0 COMMENT '排序',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建人',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新人',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`, `key`) USING BTREE,\n  INDEX `group_id`(`group_id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 302 COMMENT = '参数配置信息表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_config\n-- ----------------------------\nINSERT INTO `sa_system_config` VALUES (1, 1, 'site_copyright', 'Copyright © 2024 saithink', '版权信息', 'textarea', NULL, 96, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (2, 1, 'site_desc', '基于vue3 + webman 的极速开发框架', '网站描述', 'textarea', NULL, 97, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (3, 1, 'site_keywords', '后台管理系统', '网站关键字', 'input', NULL, 98, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (4, 1, 'site_name', 'SaiAdmin', '网站名称', 'input', NULL, 99, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (5, 1, 'site_record_number', '9527', '网站备案号', 'input', NULL, 95, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (6, 2, 'upload_allow_file', 'txt,doc,docx,xls,xlsx,ppt,pptx,rar,zip,7z,gz,pdf,wps,md,jpg,png,jpeg,mp4,pem,crt', '文件类型', 'input', NULL, 0, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (7, 2, 'upload_allow_image', 'jpg,jpeg,png,gif,svg,bmp', '图片类型', 'input', NULL, 0, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (8, 2, 'upload_mode', '1', '上传模式', 'select', '[{\\\"label\\\":\\\"本地上传\\\",\\\"value\\\":\\\"1\\\"},{\\\"label\\\":\\\"阿里云OSS\\\",\\\"value\\\":\\\"2\\\"},{\\\"label\\\":\\\"七牛云\\\",\\\"value\\\":\\\"3\\\"},{\\\"label\\\":\\\"腾讯云COS\\\",\\\"value\\\":\\\"4\\\"},{\\\"label\\\":\\\"亚马逊S3\\\",\\\"value\\\":\\\"5\\\"}]', 99, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (10, 2, 'upload_size', '52428800', '上传大小', 'input', NULL, 88, '单位Byte,1MB=1024*1024Byte', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (11, 2, 'local_root', 'public/storage/', '本地存储路径', 'input', NULL, 0, '本地存储文件路径', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (12, 2, 'local_domain', 'http://127.0.0.1:8787', '本地存储域名', 'input', NULL, 0, 'http://127.0.0.1:8787', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (13, 2, 'local_uri', '/storage/', '本地访问路径', 'input', NULL, 0, '访问是通过domain + uri', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (14, 2, 'qiniu_accessKey', '', '七牛key', 'input', NULL, 0, '七牛云存储secretId', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (15, 2, 'qiniu_secretKey', '', '七牛secret', 'input', NULL, 0, '七牛云存储secretKey', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (16, 2, 'qiniu_bucket', '', '七牛bucket', 'input', NULL, 0, '七牛云存储bucket', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (17, 2, 'qiniu_dirname', '', '七牛dirname', 'input', NULL, 0, '七牛云存储dirname', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (18, 2, 'qiniu_domain', '', '七牛domain', 'input', NULL, 0, '七牛云存储domain', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (19, 2, 'cos_secretId', '', '腾讯Id', 'input', NULL, 0, '腾讯云存储secretId', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (20, 2, 'cos_secretKey', '', '腾讯key', 'input', NULL, 0, '腾讯云secretKey', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (21, 2, 'cos_bucket', '', '腾讯bucket', 'input', NULL, 0, '腾讯云存储bucket', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (22, 2, 'cos_dirname', '', '腾讯dirname', 'input', NULL, 0, '腾讯云存储dirname', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (23, 2, 'cos_domain', '', '腾讯domain', 'input', NULL, 0, '腾讯云存储domain', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (24, 2, 'cos_region', '', '腾讯region', 'input', NULL, 0, '腾讯云存储region', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (25, 2, 'oss_accessKeyId', '', '阿里Id', 'input', NULL, 0, '阿里云存储accessKeyId', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (26, 2, 'oss_accessKeySecret', '', '阿里Secret', 'input', NULL, 0, '阿里云存储accessKeySecret', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (27, 2, 'oss_bucket', '', '阿里bucket', 'input', NULL, 0, '阿里云存储bucket', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (28, 2, 'oss_dirname', '', '阿里dirname', 'input', NULL, 0, '阿里云存储dirname', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (29, 2, 'oss_domain', '', '阿里domain', 'input', NULL, 0, '阿里云存储domain', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (30, 2, 'oss_endpoint', '', '阿里endpoint', 'input', NULL, 0, '阿里云存储endpoint', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (31, 3, 'Host', 'smtp.qq.com', 'SMTP服务器', 'input', '', 100, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (32, 3, 'Port', '465', 'SMTP端口', 'input', '', 100, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (33, 3, 'Username', '', 'SMTP用户名', 'input', '', 100, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (34, 3, 'Password', '', 'SMTP密码', 'input', '', 100, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (35, 3, 'SMTPSecure', 'ssl', 'SMTP验证方式', 'radio', '[\\r\\n    {\\\"label\\\":\\\"ssl\\\",\\\"value\\\":\\\"ssl\\\"},\\r\\n    {\\\"label\\\":\\\"tsl\\\",\\\"value\\\":\\\"tsl\\\"}\\r\\n]', 100, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (36, 3, 'From', '', '默认发件人', 'input', '', 100, '默认发件的邮箱地址', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (37, 3, 'FromName', '账户注册', '默认发件名称', 'input', '', 100, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (38, 3, 'CharSet', 'UTF-8', '编码', 'input', '', 100, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (39, 3, 'SMTPDebug', '0', '调试模式', 'radio', '[\\r\\n    {\\\"label\\\":\\\"关闭\\\",\\\"value\\\":\\\"0\\\"},\\r\\n    {\\\"label\\\":\\\"client\\\",\\\"value\\\":\\\"1\\\"},\\r\\n    {\\\"label\\\":\\\"server\\\",\\\"value\\\":\\\"2\\\"}\\r\\n]', 100, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (40, 2, 's3_key', '', 'key', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (41, 2, 's3_secret', '', 'secret', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (42, 2, 's3_bucket', '', 'bucket', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (43, 2, 's3_dirname', '', 'dirname', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (44, 2, 's3_domain', '', 'domain', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (45, 2, 's3_region', '', 'region', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (46, 2, 's3_version', '', 'version', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (47, 2, 's3_use_path_style_endpoint', '', 'path_style_endpoint', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (48, 2, 's3_endpoint', '', 'endpoint', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (49, 2, 's3_acl', '', 'acl', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_system_config_group\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_config_group`;\nCREATE TABLE `sa_system_config_group`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `name` varchar(50) NULL DEFAULT NULL COMMENT '字典名称',\n  `code` varchar(100) NULL DEFAULT NULL COMMENT '字典标示',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建人',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新人',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 4 COMMENT = '参数配置分组表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_config_group\n-- ----------------------------\nINSERT INTO `sa_system_config_group` VALUES (1, '站点配置', 'site_config', '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config_group` VALUES (2, '上传配置', 'upload_config', NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config_group` VALUES (3, '邮件服务', 'email_config', NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_system_dept\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_dept`;\nCREATE TABLE `sa_system_dept`  (\n  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,\n  `parent_id` bigint(20) UNSIGNED NULL DEFAULT 0 COMMENT '父级ID，0为根节点',\n  `name` varchar(64) NOT NULL COMMENT '部门名称',\n  `code` varchar(64) NULL DEFAULT NULL COMMENT '部门编码',\n  `leader_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '部门负责人ID',\n  `level` varchar(255) NULL DEFAULT '' COMMENT '祖级列表，格式: 0,1,5, (便于查询子孙节点)',\n  `sort` int(11) NULL DEFAULT 0 COMMENT '排序，数字越小越靠前',\n  `status` tinyint(1) NULL DEFAULT 1 COMMENT '状态: 1启用, 0禁用',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `idx_parent_id`(`parent_id`) USING BTREE,\n  INDEX `idx_path`(`level`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1114 COMMENT = '部门表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_dept\n-- ----------------------------\nINSERT INTO `sa_system_dept` VALUES (1, 0, '腾讯集团', 'GROUP', 1, '0,', 100, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dept` VALUES (2, 1, '总办', 'GMO', NULL, '0,1,', 100, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dept` VALUES (10, 1, '微信事业群', 'WXG', NULL, '0,1,', 200, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dept` VALUES (11, 1, '互动娱乐事业群', 'IEG', NULL, '0,1,', 300, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dept` VALUES (12, 1, '云与智慧产业事业群', 'CSIG', NULL, '0,1,', 400, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dept` VALUES (101, 10, '微信基础产品部', 'WX_BASE', NULL, '0,1,10,', 100, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dept` VALUES (102, 10, '微信支付线', 'WX_PAY', NULL, '0,1,10,', 200, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dept` VALUES (111, 11, '天美工作室群', 'TIMI', NULL, '0,1,11,', 100, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dept` VALUES (112, 11, '光子工作室群', 'LIGHT', NULL, '0,1,11,', 200, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dept` VALUES (121, 12, '腾讯云事业部', 'CLOUD', NULL, '0,1,12,', 100, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dept` VALUES (1111, 111, '王者荣耀项目组', 'HOK', NULL, '0,1,11,111,', 100, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dept` VALUES (1112, 111, 'QQ飞车项目组', 'QQ_SPEED', NULL, '0,1,11,111,', 200, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_system_dict_data\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_dict_data`;\nCREATE TABLE `sa_system_dict_data`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `type_id` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '字典类型ID',\n  `label` varchar(50) NULL DEFAULT NULL COMMENT '字典标签',\n  `value` varchar(100) NULL DEFAULT NULL COMMENT '字典值',\n  `color` varchar(50) NULL DEFAULT NULL COMMENT '字典颜色',\n  `code` varchar(100) NULL DEFAULT NULL COMMENT '字典标示',\n  `sort` smallint(5) UNSIGNED NULL DEFAULT 0 COMMENT '排序',\n  `status` smallint(6) NULL DEFAULT 1 COMMENT '状态 (1正常 2停用)',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `type_id`(`type_id`) USING BTREE,\n  INDEX `idx_code`(`code`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 50 COMMENT = '字典数据表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_dict_data\n-- ----------------------------\nINSERT INTO `sa_system_dict_data` VALUES (2, 2, '本地存储', '1', '#5d87ff', 'upload_mode', 99, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (3, 2, '阿里云OSS', '2', '#f9901f', 'upload_mode', 98, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (4, 2, '七牛云', '3', '#00ced1', 'upload_mode', 97, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (5, 2, '腾讯云COS', '4', '#1d84ff', 'upload_mode', 96, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (6, 2, '亚马逊S3', '5', '#ff80c8', 'upload_mode', 95, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (7, 3, '正常', '1', '#13deb9', 'data_status', 0, 1, '1为正常', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (8, 3, '停用', '2', '#ff4d4f', 'data_status', 0, 1, '2为停用', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (9, 4, '统计页面', 'statistics', '#00ced1', 'dashboard', 100, 1, '管理员用', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (10, 4, '工作台', 'work', '#ff8c00', 'dashboard', 50, 1, '员工使用', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (11, 5, '男', '1', '#5d87ff', 'gender', 0, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (12, 5, '女', '2', '#ff4500', 'gender', 0, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (13, 5, '未知', '3', '#b48df3', 'gender', 0, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (16, 12, '图片', 'image', '#60c041', 'attachment_type', 10, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (17, 12, '文档', 'text', '#1d84ff', 'attachment_type', 9, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (18, 12, '音频', 'audio', '#00ced1', 'attachment_type', 8, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (19, 12, '视频', 'video', '#ff4500', 'attachment_type', 7, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (20, 12, '应用程序', 'application', '#ff8c00', 'attachment_type', 6, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (21, 13, '目录', '1', '#909399', 'menu_type', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (22, 13, '菜单', '2', '#1e90ff', 'menu_type', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (23, 13, '按钮', '3', '#ff4500', 'menu_type', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (24, 13, '外链', '4', '#00ced1', 'menu_type', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (25, 14, '是', '1', '#60c041', 'yes_or_no', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (26, 14, '否', '2', '#ff4500', 'yes_or_no', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (47, 20, 'URL任务GET', '1', '#5d87ff', 'crontab_task_type', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (48, 20, 'URL任务POST', '2', '#00ced1', 'crontab_task_type', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (49, 20, '类任务', '3', '#ff8c00', 'crontab_task_type', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_system_dict_type\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_dict_type`;\nCREATE TABLE `sa_system_dict_type`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `name` varchar(50) NULL DEFAULT NULL COMMENT '字典名称',\n  `code` varchar(100) NULL DEFAULT NULL COMMENT '字典标示',\n  `status` smallint(6) NULL DEFAULT 1 COMMENT '状态 (1正常 2停用)',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `idx_code`(`code`) USING BTREE,\n  INDEX `idx_name`(`name`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 24 COMMENT = '字典类型表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_dict_type\n-- ----------------------------\nINSERT INTO `sa_system_dict_type` VALUES (2, '存储模式', 'upload_mode', 1, '上传文件存储模式', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_type` VALUES (3, '数据状态', 'data_status', 1, '通用数据状态', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_type` VALUES (4, '后台首页', 'dashboard', 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_type` VALUES (5, '性别', 'gender', 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_type` VALUES (12, '附件类型', 'attachment_type', 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_type` VALUES (13, '菜单类型', 'menu_type', 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_type` VALUES (14, '是否', 'yes_or_no', 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_type` VALUES (20, '定时任务类型', 'crontab_task_type', 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_system_login_log\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_login_log`;\nCREATE TABLE `sa_system_login_log`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `username` varchar(20) NULL DEFAULT NULL COMMENT '用户名',\n  `ip` varchar(45) NULL DEFAULT NULL COMMENT '登录IP地址',\n  `ip_location` varchar(255) NULL DEFAULT NULL COMMENT 'IP所属地',\n  `os` varchar(50) NULL DEFAULT NULL COMMENT '操作系统',\n  `browser` varchar(50) NULL DEFAULT NULL COMMENT '浏览器',\n  `status` smallint(6) NULL DEFAULT 1 COMMENT '登录状态 (1成功 2失败)',\n  `message` varchar(50) NULL DEFAULT NULL COMMENT '提示消息',\n  `login_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '登录时间',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `username`(`username`) USING BTREE,\n  INDEX `idx_create_time`(`create_time`) USING BTREE,\n  INDEX `idx_login_time`(`login_time`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '登录日志表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_login_log\n-- ----------------------------\n\n-- ----------------------------\n-- Table structure for sa_system_mail\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_mail`;\nCREATE TABLE `sa_system_mail`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '编号',\n  `gateway` varchar(50) NULL DEFAULT NULL COMMENT '网关',\n  `from` varchar(50) NULL DEFAULT NULL COMMENT '发送人',\n  `email` varchar(50) NULL DEFAULT NULL COMMENT '接收人',\n  `code` varchar(20) NULL DEFAULT NULL COMMENT '验证码',\n  `content` varchar(500) NULL DEFAULT NULL COMMENT '邮箱内容',\n  `status` varchar(20) NULL DEFAULT NULL COMMENT '发送状态',\n  `response` varchar(500) NULL DEFAULT NULL COMMENT '返回结果',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `idx_create_time`(`create_time`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '邮件记录' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_mail\n-- ----------------------------\n\n-- ----------------------------\n-- Table structure for sa_system_menu\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_menu`;\nCREATE TABLE `sa_system_menu`  (\n  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,\n  `parent_id` bigint(20) UNSIGNED NULL DEFAULT 0 COMMENT '父级ID',\n  `name` varchar(64) NOT NULL COMMENT '菜单名称',\n  `code` varchar(64) NULL DEFAULT NULL COMMENT '组件名称',\n  `slug` varchar(100) NULL DEFAULT NULL COMMENT '权限标识，如 user:list, user:add',\n  `type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '类型: 1目录, 2菜单, 3按钮/API',\n  `path` varchar(255) NULL DEFAULT NULL COMMENT '路由地址(前端)或API路径(后端)',\n  `component` varchar(255) NULL DEFAULT NULL COMMENT '前端组件路径，如 layout/User',\n  `method` varchar(10) NULL DEFAULT NULL COMMENT '请求方式',\n  `icon` varchar(64) NULL DEFAULT NULL COMMENT '图标',\n  `sort` int(11) NULL DEFAULT 100 COMMENT '排序',\n  `link_url` varchar(255) NULL DEFAULT NULL COMMENT '外部链接',\n  `is_iframe` tinyint(1) NULL DEFAULT 2 COMMENT '是否iframe',\n  `is_keep_alive` tinyint(1) NULL DEFAULT 2 COMMENT '是否缓存',\n  `is_hidden` tinyint(1) NULL DEFAULT 2 COMMENT '是否隐藏',\n  `is_fixed_tab` tinyint(1) NULL DEFAULT 2 COMMENT '是否固定标签页',\n  `is_full_page` tinyint(1) NULL DEFAULT 2 COMMENT '是否全屏',\n  `generate_id` int(11) NULL DEFAULT 0 COMMENT '生成id',\n  `generate_key` varchar(255) NULL DEFAULT NULL COMMENT '生成key',\n  `status` tinyint(1) NULL DEFAULT 1 COMMENT '状态',\n  `remark` varchar(255) NULL DEFAULT NULL,\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `idx_parent_id`(`parent_id`) USING BTREE,\n  INDEX `idx_slug`(`slug`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1000 COMMENT = '菜单权限表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_menu\n-- ----------------------------\nINSERT INTO `sa_system_menu` VALUES (1, 0, '仪表盘', 'Dashboard', NULL, 1, '/dashboard', NULL, NULL, 'ri:pie-chart-line', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (2, 1, '工作台', 'Console', NULL, 2, 'console', '/dashboard/console', NULL, 'ri:home-smile-2-line', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (3, 0, '系统管理', 'System', NULL, 1, '/system', NULL, NULL, 'ri:user-3-line', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (4, 3, '用户管理', 'User', NULL, 2, 'user', '/system/user', NULL, 'ri:user-line', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (5, 3, '部门管理', 'Dept', NULL, 2, 'dept', '/system/dept', NULL, 'ri:node-tree', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (6, 3, '角色管理', 'Role', NULL, 2, 'role', '/system/role', NULL, 'ri:admin-line', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (7, 3, '岗位管理', 'Post', '', 2, 'post', '/system/post', NULL, 'ri:signpost-line', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (8, 3, '菜单管理', 'Menu', NULL, 2, 'menu', '/system/menu', NULL, 'ri:menu-line', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (10, 0, '运维管理', 'Safeguard', NULL, 1, '/safeguard', '', NULL, 'ri:shield-check-line', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (11, 10, '缓存管理', 'Cache', '', 2, 'cache', '/safeguard/cache', NULL, 'ri:keyboard-box-line', 80, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (12, 10, '数据字典', 'Dict', NULL, 2, 'dict', '/safeguard/dict', NULL, 'ri:database-2-line', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (13, 10, '附件管理', 'Attachment', '', 2, 'attachment', '/safeguard/attachment', NULL, 'ri:file-cloud-line', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (14, 10, '数据表维护', 'Database', '', 2, 'database', '/safeguard/database', NULL, 'ri:database-line', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (15, 10, '登录日志', 'LoginLog', '', 2, 'login-log', '/safeguard/login-log', NULL, 'ri:login-circle-line', 50, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (16, 10, '操作日志', 'OperLog', '', 2, 'oper-log', '/safeguard/oper-log', NULL, 'ri:shield-keyhole-line', 50, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (17, 10, '邮件日志', 'EmailLog', '', 2, 'email-log', '/safeguard/email-log', NULL, 'ri:mail-line', 50, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (18, 3, '系统设置', 'Config', NULL, 2, 'config', '/system/config', NULL, 'ri:settings-4-line', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (19, 0, '官方文档', 'Document', '', 4, '', '', NULL, 'ri:file-copy-2-fill', 90, 'https://saithink.top', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (20, 4, '数据列表', '', 'core:user:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (21, 1, '个人中心', 'UserCenter', '', 2, 'user-center', '/dashboard/user-center/index', NULL, 'ri:user-2-line', 100, '', 2, 2, 1, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (22, 4, '添加', '', 'core:user:save', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (23, 4, '修改', '', 'core:user:update', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (24, 4, '读取', '', 'core:user:read', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (25, 4, '删除', '', 'core:user:destroy', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (26, 4, '重置密码', '', 'core:user:password', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (27, 4, '清理缓存', '', 'core:user:cache', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (28, 4, '设置工作台', '', 'core:user:home', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (29, 5, '数据列表', '', 'core:dept:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (30, 5, '添加', '', 'core:dept:save', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (31, 5, '修改', '', 'core:dept:update', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (32, 5, '读取', '', 'core:dept:read', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (33, 5, '删除', '', 'core:dept:destroy', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (34, 6, '添加', '', 'core:role:save', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (35, 6, '数据列表', '', 'core:role:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (36, 6, '修改', '', 'core:role:update', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (37, 6, '读取', '', 'core:role:read', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (38, 6, '删除', '', 'core:role:destroy', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (39, 6, '菜单权限', '', 'core:role:menu', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (41, 7, '数据列表', '', 'core:post:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (42, 7, '添加', '', 'core:post:save', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (43, 7, '修改', '', 'core:post:update', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (44, 7, '读取', '', 'core:post:read', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (45, 7, '删除', '', 'core:post:destroy', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (46, 7, '导入', '', 'core:post:import', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (47, 7, '导出', '', 'core:post:export', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (48, 8, '数据列表', '', 'core:menu:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (49, 8, '读取', '', 'core:menu:read', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (50, 8, '添加', '', 'core:menu:save', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (51, 8, '修改', '', 'core:menu:update', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (52, 8, '删除', '', 'core:menu:destroy', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (53, 18, '数据列表', '', 'core:config:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (54, 18, '管理', '', 'core:config:edit', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (55, 18, '修改', '', 'core:config:update', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (56, 12, '数据列表', '', 'core:dict:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (57, 12, '管理', '', 'core:dict:edit', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (58, 13, '数据列表', '', 'core:attachment:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (59, 13, '管理', '', 'core:attachment:edit', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (60, 14, '数据表列表', '', 'core:database:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (61, 14, '数据表维护', '', 'core:database:edit', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (62, 14, '回收站数据', '', 'core:recycle:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (63, 14, '回收站管理', '', 'core:recycle:edit', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (64, 15, '数据列表', '', 'core:logs:login', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (65, 15, '删除', '', 'core:logs:deleteLogin', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (66, 16, '数据列表', '', 'core:logs:Oper', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (67, 16, '删除', '', 'core:logs:deleteOper', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (68, 17, '数据列表', '', 'core:email:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (69, 17, '删除', '', 'core:email:destroy', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (70, 10, '服务监控', 'Server', '', 2, 'server', '/safeguard/server', NULL, 'ri:server-line', 90, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (71, 70, '数据列表', '', 'core:server:monitor', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (72, 11, '数据列表', '', 'core:server:cache', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (73, 11, '缓存清理', '', 'core:server:clear', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (74, 2, '登录数据统计', '', 'core:console:list', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (75, 0, '附加权限', 'Permission', '', 1, 'permission', '', NULL, 'ri:apps-2-ai-line', 100, '', 2, 2, 1, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (76, 75, '上传图片', '', 'core:system:uploadImage', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (77, 75, '上传文件', '', 'core:system:uploadFile', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (78, 75, '附件列表', '', 'core:system:resource', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (79, 75, '用户列表', '', 'core:system:user', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (80, 0, '工具', 'Tool', '', 1, '/tool', '', NULL, 'ri:tools-line', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (81, 80, '代码生成', 'Code', '', 2, 'code', '/tool/code', NULL, 'ri:code-s-slash-line', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (82, 80, '定时任务', 'Crontab', '', 2, 'crontab', '/tool/crontab', NULL, 'ri:time-line', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (83, 82, '数据列表', '', 'tool:crontab:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (84, 82, '管理', '', 'tool:crontab:edit', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (85, 82, '运行任务', '', 'tool:crontab:run', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (86, 81, '数据列表', '', 'tool:code:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (87, 81, '管理', '', 'tool:code:edit', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (88, 0, '插件市场', 'Plugin', '', 2, '/plugin', '/plugin/saipackage/install/index', NULL, 'ri:apps-2-ai-line', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_system_oper_log\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_oper_log`;\nCREATE TABLE `sa_system_oper_log`  (\n  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `username` varchar(20) NULL DEFAULT NULL COMMENT '用户名',\n  `app` varchar(50) NULL DEFAULT NULL COMMENT '应用名称',\n  `method` varchar(20) NULL DEFAULT NULL COMMENT '请求方式',\n  `router` varchar(500) NULL DEFAULT NULL COMMENT '请求路由',\n  `service_name` varchar(30) NULL DEFAULT NULL COMMENT '业务名称',\n  `ip` varchar(45) NULL DEFAULT NULL COMMENT '请求IP地址',\n  `ip_location` varchar(255) NULL DEFAULT NULL COMMENT 'IP所属地',\n  `request_data` text NULL COMMENT '请求数据',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `username`(`username`) USING BTREE,\n  INDEX `idx_create_time`(`create_time`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '操作日志表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_oper_log\n-- ----------------------------\n\n-- ----------------------------\n-- Table structure for sa_system_post\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_post`;\nCREATE TABLE `sa_system_post`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `name` varchar(50) NULL DEFAULT NULL COMMENT '岗位名称',\n  `code` varchar(100) NULL DEFAULT NULL COMMENT '岗位代码',\n  `sort` smallint(5) UNSIGNED NULL DEFAULT 0 COMMENT '排序',\n  `status` smallint(6) NULL DEFAULT 1 COMMENT '状态 (1正常 2停用)',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 87 COMMENT = '岗位信息表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_post\n-- ----------------------------\nINSERT INTO `sa_system_post` VALUES (1, '司机岗', 'driver', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_post` VALUES (2, '保安岗', 'security', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_system_role\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_role`;\nCREATE TABLE `sa_system_role`  (\n  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,\n  `name` varchar(64) NOT NULL COMMENT '角色名称',\n  `code` varchar(64) NOT NULL COMMENT '角色标识(英文唯一)，如: hr_manager',\n  `level` int(11) NULL DEFAULT 1 COMMENT '角色级别(1-100)：用于行政控制，不可操作级别>=自己的角色',\n  `data_scope` tinyint(4) NULL DEFAULT 1 COMMENT '数据范围: 1全部, 2本部门及下属, 3本部门, 4仅本人, 5自定义',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `sort` int(11) NULL DEFAULT 100,\n  `status` tinyint(1) NULL DEFAULT 1 COMMENT '状态: 1启用, 0禁用',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  UNIQUE INDEX `uk_slug`(`code`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 17 COMMENT = '角色表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_role\n-- ----------------------------\nINSERT INTO `sa_system_role` VALUES (1, '超级管理员', 'super_admin', 100, 1, '系统维护者，拥有所有权限', 100, 1, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_role` VALUES (2, '集团总裁', 'ceo', 90, 1, '查看全集团数据', 100, 1, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_role` VALUES (3, 'BG总裁', 'bg_president', 80, 2, '', 100, 1, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_role` VALUES (4, '部门总经理', 'gm', 60, 2, '', 100, 1, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_role` VALUES (5, '组长', 'team_leader', 30, 3, '', 100, 1, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_role` VALUES (6, '普通员工', 'staff', 10, 4, '', 100, 1, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_system_role_dept\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_role_dept`;\nCREATE TABLE `sa_system_role_dept`  (\n  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,\n  `role_id` bigint(20) UNSIGNED NOT NULL,\n  `dept_id` bigint(20) UNSIGNED NOT NULL,\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `idx_role_id`(`role_id`) USING BTREE,\n  INDEX `idx_dept_id`(`dept_id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '角色-自定义数据权限关联' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_role_dept\n-- ----------------------------\n\n-- ----------------------------\n-- Table structure for sa_system_role_menu\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_role_menu`;\nCREATE TABLE `sa_system_role_menu`  (\n  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,\n  `role_id` bigint(20) UNSIGNED NOT NULL,\n  `menu_id` bigint(20) UNSIGNED NOT NULL,\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `idx_menu_id`(`menu_id`) USING BTREE,\n  INDEX `idx_role_id`(`role_id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '角色权限关联' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_role_menu\n-- ----------------------------\n\n-- ----------------------------\n-- Table structure for sa_system_user\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_user`;\nCREATE TABLE `sa_system_user`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,\n  `username` varchar(64) NOT NULL COMMENT '登录账号',\n  `password` varchar(255) NOT NULL COMMENT '加密密码',\n  `realname` varchar(64) NULL DEFAULT NULL COMMENT '真实姓名',\n  `gender` varchar(10) NULL DEFAULT NULL COMMENT '性别',\n  `avatar` varchar(255) NULL DEFAULT NULL COMMENT '头像',\n  `email` varchar(128) NULL DEFAULT NULL COMMENT '邮箱',\n  `phone` varchar(20) NULL DEFAULT NULL COMMENT '手机号',\n  `signed` varchar(255) NULL DEFAULT NULL COMMENT '个性签名',\n  `dashboard` varchar(255) NULL DEFAULT 'work' COMMENT '工作台',\n  `dept_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '主归属部门',\n  `is_super` tinyint(1) NULL DEFAULT 0 COMMENT '是否超级管理员: 1是(跳过权限检查), 0否',\n  `status` tinyint(1) NULL DEFAULT 1 COMMENT '状态: 1启用, 0禁用',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `login_time` timestamp(0) NULL DEFAULT NULL COMMENT '最后登录时间',\n  `login_ip` varchar(45) NULL DEFAULT NULL COMMENT '最后登录IP',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  UNIQUE INDEX `uk_username`(`username`) USING BTREE,\n  INDEX `idx_dept_id`(`dept_id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 110 COMMENT = '用户表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_user\n-- ----------------------------\nINSERT INTO `sa_system_user` VALUES (1, 'admin', '$2y$10$wnixh48uDnaW/6D9EygDd.OHJK0vQY/4nHaTjMKBCVDBP2NiTatqS', '祭道之上', '2', 'https://image.saithink.top/saiadmin/avatar.jpg', 'saiadmin@admin.com', '15888888888', 'SaiAdmin是兼具设计美学与高效开发的后台系统!', 'statistics', 1, 1, 1, NULL, NULL, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_user` VALUES (2, 'martin', '$2y$10$J3EkwRH8rNkveaanx1.j.ebRiBpnnVUGWa.i2MS3aNpb9ydAOolmm', '刘炽平', '2', 'https://image.saithink.top/saiadmin/avatar.jpg', 'martin@163.com', '15888888888', NULL, 'work', 1, 0, 1, '', NULL, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_user` VALUES (3, 'allen', '$2y$10$H8d7riOjOiwPSopguEQ1fuKZz.fA0A54OvuzTqgJlbG1N3uOxEwM.', '张小龙', '', 'https://image.saithink.top/saiadmin/avatar.jpg', '', '15888888888', NULL, 'work', 10, 0, 1, '', NULL, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_user` VALUES (4, 'mark', '$2y$10$sY/4StKVV.N/8Ock8J8kdeIOK4jS4tAUoYjkzvB8Tzy0fLh.wA2KS', '任宇昕', NULL, 'https://image.saithink.top/saiadmin/avatar.jpg', NULL, '15888888888', NULL, 'work', 11, 0, 1, NULL, NULL, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_user` VALUES (5, 'dowson', '$2y$10$sY/4StKVV.N/8Ock8J8kdeIOK4jS4tAUoYjkzvB8Tzy0fLh.wA2KS', '汤道生', NULL, 'https://image.saithink.top/saiadmin/avatar.jpg', NULL, '15888888888', NULL, 'work', 12, 0, 1, NULL, NULL, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_user` VALUES (10, 'timi_boss', '$2y$10$sY/4StKVV.N/8Ock8J8kdeIOK4jS4tAUoYjkzvB8Tzy0fLh.wA2KS', '姚晓光', NULL, 'https://image.saithink.top/saiadmin/avatar.jpg', '', '15888888888', NULL, 'work', 111, 0, 1, '', NULL, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_user` VALUES (100, 'dev_wang', '$2y$10$sY/4StKVV.N/8Ock8J8kdeIOK4jS4tAUoYjkzvB8Tzy0fLh.wA2KS', '王程序员', NULL, 'https://image.saithink.top/saiadmin/avatar.jpg', NULL, '15888888888', NULL, 'work', 1111, 0, 1, NULL, NULL, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_user` VALUES (101, 'dev_li', '$2y$10$sY/4StKVV.N/8Ock8J8kdeIOK4jS4tAUoYjkzvB8Tzy0fLh.wA2KS', '李策划', NULL, 'https://image.saithink.top/saiadmin/avatar.jpg', NULL, '15888888888', NULL, 'work', 1111, 0, 1, NULL, NULL, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_system_user_post\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_user_post`;\nCREATE TABLE `sa_system_user_post`  (\n  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `user_id` bigint(20) UNSIGNED NOT NULL COMMENT '用户主键',\n  `post_id` bigint(20) UNSIGNED NOT NULL COMMENT '岗位主键',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `idx_user_id`(`user_id`) USING BTREE,\n  INDEX `idx_post_id`(`post_id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '用户与岗位关联表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_user_post\n-- ----------------------------\n\n-- ----------------------------\n-- Table structure for sa_system_user_role\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_user_role`;\nCREATE TABLE `sa_system_user_role`  (\n  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,\n  `user_id` bigint(20) UNSIGNED NOT NULL,\n  `role_id` bigint(20) UNSIGNED NOT NULL,\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `idx_role_id`(`role_id`) USING BTREE,\n  INDEX `idx_user_id`(`user_id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 55 COMMENT = '用户角色关联' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_user_role\n-- ----------------------------\nINSERT INTO `sa_system_user_role` VALUES (1, 1, 1);\n\n-- ----------------------------\n-- Table structure for sa_tool_crontab\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_tool_crontab`;\nCREATE TABLE `sa_tool_crontab`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `name` varchar(100) NULL DEFAULT NULL COMMENT '任务名称',\n  `type` smallint(6) NULL DEFAULT 4 COMMENT '任务类型',\n  `target` varchar(500) NULL DEFAULT NULL COMMENT '调用任务字符串',\n  `parameter` varchar(1000) NULL DEFAULT NULL COMMENT '调用任务参数',\n  `task_style` tinyint(1) NULL DEFAULT NULL COMMENT '执行类型',\n  `rule` varchar(32) NULL DEFAULT NULL COMMENT '任务执行表达式',\n  `singleton` smallint(6) NULL DEFAULT 1 COMMENT '是否单次执行 (1 是 2 不是)',\n  `status` smallint(6) NULL DEFAULT 1 COMMENT '状态 (1正常 2停用)',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 9 COMMENT = '定时任务信息表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_tool_crontab\n-- ----------------------------\nINSERT INTO `sa_tool_crontab` VALUES (1, '访问官网', 1, 'https://saithink.top', '', 1, '0 0 8 * * *', 2, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_tool_crontab` VALUES (2, '登录gitee', 2, 'https://gitee.com/check_user_login', '{\\\"user_login\\\": \\\"saiadmin\\\"}', 1, '0 0 10 * * *', 2, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_tool_crontab` VALUES (3, '定时执行任务', 3, '\\\\plugin\\\\saiadmin\\\\process\\\\Test', '{\\\"type\\\":\\\"1\\\"}', 5, '0 0 */12 * * *', 2, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_tool_crontab_log\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_tool_crontab_log`;\nCREATE TABLE `sa_tool_crontab_log`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `crontab_id` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '任务ID',\n  `name` varchar(255) NULL DEFAULT NULL COMMENT '任务名称',\n  `target` varchar(500) NULL DEFAULT NULL COMMENT '任务调用目标字符串',\n  `parameter` varchar(1000) NULL DEFAULT NULL COMMENT '任务调用参数',\n  `exception_info` varchar(2000) NULL DEFAULT NULL COMMENT '异常信息',\n  `status` smallint(6) NULL DEFAULT 1 COMMENT '执行状态 (1成功 2失败)',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '定时任务执行日志表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_tool_crontab_log\n-- ----------------------------\n\n-- ----------------------------\n-- Table structure for sa_tool_generate_columns\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_tool_generate_columns`;\nCREATE TABLE `sa_tool_generate_columns`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `table_id` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '所属表ID',\n  `column_name` varchar(200) NULL DEFAULT NULL COMMENT '字段名称',\n  `column_comment` varchar(255) NULL DEFAULT NULL COMMENT '字段注释',\n  `column_type` varchar(50) NULL DEFAULT NULL COMMENT '字段类型',\n  `default_value` varchar(50) NULL DEFAULT NULL COMMENT '默认值',\n  `is_pk` smallint(6) NULL DEFAULT 1 COMMENT '1 非主键 2 主键',\n  `is_required` smallint(6) NULL DEFAULT 1 COMMENT '1 非必填 2 必填',\n  `is_insert` smallint(6) NULL DEFAULT 1 COMMENT '1 非插入字段 2 插入字段',\n  `is_edit` smallint(6) NULL DEFAULT 1 COMMENT '1 非编辑字段 2 编辑字段',\n  `is_list` smallint(6) NULL DEFAULT 1 COMMENT '1 非列表显示字段 2 列表显示字段',\n  `is_query` smallint(6) NULL DEFAULT 1 COMMENT '1 非查询字段 2 查询字段',\n  `is_sort` smallint(6) NULL DEFAULT 1 COMMENT '1 非排序 2 排序',\n  `query_type` varchar(100) NULL DEFAULT 'eq' COMMENT '查询方式 eq 等于, neq 不等于, gt 大于, lt 小于, like 范围',\n  `view_type` varchar(100) NULL DEFAULT 'text' COMMENT '页面控件,text, textarea, password, select, checkbox, radio, date, upload, ma-upload(封装的上传控件)',\n  `dict_type` varchar(200) NULL DEFAULT NULL COMMENT '字典类型',\n  `allow_roles` varchar(255) NULL DEFAULT NULL COMMENT '允许查看该字段的角色',\n  `options` varchar(1000) NULL DEFAULT NULL COMMENT '字段其他设置',\n  `sort` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT '排序',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '代码生成业务字段表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_tool_generate_columns\n-- ----------------------------\n\n-- ----------------------------\n-- Table structure for sa_tool_generate_tables\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_tool_generate_tables`;\nCREATE TABLE `sa_tool_generate_tables`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `table_name` varchar(200) NULL DEFAULT NULL COMMENT '表名称',\n  `table_comment` varchar(500) NULL DEFAULT NULL COMMENT '表注释',\n  `stub` varchar(50) NULL DEFAULT NULL COMMENT 'stub类型',\n  `template` varchar(50) NULL DEFAULT NULL COMMENT '模板名称',\n  `namespace` varchar(255) NULL DEFAULT NULL COMMENT '命名空间',\n  `package_name` varchar(100) NULL DEFAULT NULL COMMENT '控制器包名',\n  `business_name` varchar(50) NULL DEFAULT NULL COMMENT '业务名称',\n  `class_name` varchar(50) NULL DEFAULT NULL COMMENT '类名称',\n  `menu_name` varchar(100) NULL DEFAULT NULL COMMENT '生成菜单名',\n  `belong_menu_id` int(11) NULL DEFAULT NULL COMMENT '所属菜单',\n  `tpl_category` varchar(100) NULL DEFAULT NULL COMMENT '生成类型,single 单表CRUD,tree 树表CRUD,parent_sub父子表CRUD',\n  `generate_type` smallint(6) NULL DEFAULT 1 COMMENT '1 压缩包下载 2 生成到模块',\n  `generate_path` varchar(100) NULL DEFAULT 'saiadmin-artd' COMMENT '前端根目录',\n  `generate_model` smallint(6) NULL DEFAULT 1 COMMENT '1 软删除 2 非软删除',\n  `generate_menus` varchar(255) NULL DEFAULT NULL COMMENT '生成菜单列表',\n  `build_menu` smallint(6) NULL DEFAULT 1 COMMENT '是否构建菜单',\n  `component_type` smallint(6) NULL DEFAULT 1 COMMENT '组件显示方式',\n  `options` varchar(1500) NULL DEFAULT NULL COMMENT '其他业务选项',\n  `form_width` int(11) NULL DEFAULT 800 COMMENT '表单宽度',\n  `is_full` tinyint(1) NULL DEFAULT 1 COMMENT '是否全屏',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `source` varchar(255) NULL DEFAULT NULL COMMENT '数据源',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '代码生成业务表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_tool_generate_tables\n-- ----------------------------\n\n-- ----------------------------\n-- Table structure for sa_article\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_article`;\nCREATE TABLE `sa_article`  (\n  `id` int(10) NOT NULL AUTO_INCREMENT COMMENT '编号',\n  `category_id` int(10) NOT NULL COMMENT '分类id',\n  `title` varchar(255) NOT NULL DEFAULT '' COMMENT '文章标题',\n  `author` varchar(255) NULL DEFAULT NULL COMMENT '文章作者',\n  `image` varchar(1000) NULL DEFAULT '' COMMENT '文章图片',\n  `describe` varchar(1000) NOT NULL COMMENT '文章简介',\n  `content` text NOT NULL COMMENT '文章内容',\n  `views` int(11) NULL DEFAULT 0 COMMENT '浏览次数',\n  `sort` int(10) UNSIGNED NULL DEFAULT 100 COMMENT '排序',\n  `status` tinyint(1) UNSIGNED NULL DEFAULT 1 COMMENT '状态',\n  `is_link` tinyint(1) NULL DEFAULT 2 COMMENT '是否外链',\n  `link_url` varchar(255) NULL DEFAULT NULL COMMENT '链接地址',\n  `is_hot` tinyint(1) UNSIGNED NULL DEFAULT 2 COMMENT '是否热门',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `idx_category_id`(`category_id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 9 COMMENT = '文章表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_article\n-- ----------------------------\nINSERT INTO `sa_article` VALUES (1, 1, '科技为农业强国建设插上腾飞之翼', '新华网', 'https://www.news.cn/tech/20251203/51066a5dc41545fa849d49423770ad70/2025120351066a5dc41545fa849d49423770ad70_202512037a03214ec26c4f029d6e1599d07c3779.png', '“十四五”规划提出，完善农业科技创新体系，创新农技推广服务方式，建设智慧农业。5年来，在科技创新的强劲支撑下，14亿人的饭碗端得更牢、农业现代化水平显著提升、产业新动能持续增强，农业强国建设迈上新台阶。', '<p style=\\\"text-align: justify;\\\"> &nbsp; &nbsp; &nbsp; &nbsp;“平均亩产1209.1公斤，这标志着全国首个两百万亩玉米‘吨粮田’成功创建。”金秋时节，新疆伊犁哈萨克自治州传来喜讯。这一纪录的诞生，离不开中国农业科学院研发的“玉米密植高产精准调控技术”支撑。依托该技术，位于伊犁的200余万亩玉米高产田亩保苗株数从传统的不足5000株提升到7000—8000株，玉米收获穗数大幅提升。</p><p style=\\\"text-align: justify;\\\">　　这只是我国科技强农、粮食增产增收的一个缩影。“十四五”以来，我国粮食总产量始终保持在1.3万亿斤以上。2024年粮食总产量更是首次突破1.4万亿斤，比2020年增产740亿斤。</p><p style=\\\"text-align: justify;\\\">　　习近平总书记强调，发展现代农业，建设农业强国，必须依靠科技进步，让科技为农业现代化插上腾飞的翅膀。</p><p style=\\\"text-align: justify;\\\">　　“十四五”规划提出，完善农业科技创新体系，创新农技推广服务方式，建设智慧农业。5年来，在科技创新的强劲支撑下，14亿人的饭碗端得更牢、农业现代化水平显著提升、产业新动能持续增强，农业强国建设迈上新台阶。</p><p style=\\\"text-align: justify;\\\">　　科技铸“芯”，夯实大国粮仓之基</p><p style=\\\"text-align: justify;\\\">　　国以农为本，农以种为先，种子被誉为农业的“芯片”。前不久，四川省富顺县水稻百亩超高产攻关片进行实割实测，再生稻亩产达到494.81公斤，加上此前测产中稻亩产807.13公斤，合计亩产突破1300公斤。取得这一成绩的背后，是“甬优4949”等高产突破性品种的选育和“中稻+再生稻”生产模式的推广。</p><p style=\\\"text-align: justify;\\\">　　水稻是我国第一大口粮。“十四五”时期全国多地选育出一批水稻突破性品种：安徽农业大学水稻栽培团队推广自育水稻品种，帮助当地农户水稻亩产增至800公斤；湖南杂交水稻研究中心选育出“西子3号”，推动解决部分受重金属污染地区“镉大米”问题；国家耐盐碱水稻技术创新中心培育出“箐两优3261”，填补了我国华南滨海盐碱区暂无强耐盐、多抗、优质杂交稻品种的空白……</p><p style=\\\"text-align: justify;\\\">　　习近平总书记指出，中国人的饭碗要牢牢端在自己手中，就必须把种子牢牢攥在自己手里。</p><p style=\\\"text-align: justify;\\\">　　作为我国另一大口粮，小麦育种的创新步伐也不断提速。2025年，西北农林科技大学一次性通过国家审定12个新品种，覆盖半冬性、冬性、春性类型，在抗倒伏等方面实现全面突破。这些为不同生态区“量身定制”的品种，在丰富我国小麦品种的同时，也大幅提升了小麦产能潜力。截至目前，西农小麦系列品种累计推广面积已达18亿亩，为保障国家粮食安全提供了坚实的种源支撑。</p><p style=\\\"text-align: justify;\\\">　　“十四五”以来，我国深入实施种业振兴行动，育成了一批生产急需的重大品种，选育出优质高产水稻、节水抗病小麦、机收籽粒玉米、高油高产大豆等急需品种，农作物自主选育品种面积占比超过了95%，做到了“中国粮”主要用“中国种”。</p><p style=\\\"text-align: justify;\\\">　　“去年全国粮食亩产394.7公斤，比‘十三五’末提高了12.5公斤，单产提升对我国粮食产量增长的贡献超过60%，有些年份会超过80%。”农业农村部党组书记、部长韩俊表示，“十四五”以来，农业农村部深入实施国家粮食安全战略，“以我为主、立足国内、确保产能、适度进口、科技支撑”，坚持产量产能、生产生态、增产增收一起抓，强化藏粮于地、藏粮于技，全方位夯实粮食安全根基。</p><p style=\\\"text-align: justify;\\\">　　智慧提“效”，驱动耕作方式变革</p><p style=\\\"text-align: justify;\\\">　　气象墒情传感器、智能虫情测报站等设备如同“千里眼”，与空中无人机巡航、地面机器狗巡检形成立体监测网络。这是日前科技日报记者在北京市昌平区的天汇园果园见到的一幕。</p><p style=\\\"text-align: justify;\\\">　　“目前，该果园环境和土壤墒情覆盖10余项指标，虫情识别准确率达90%，种植生产信息化率超过95%，同时土壤成分快检技术能在30分钟内完成土壤成分‘体检’，辅助实现果园虫情和灾情等早预警、早干预。”北京市智慧农业创新团队岗位专家吴建伟介绍，该果园管理从“经验驱动”转向“数据驱动”，为果树生长提供了全天候守护。</p><p style=\\\"text-align: justify;\\\">　　在四川省成都市新都区稻菜现代农业园区，当地自主研发的农业巡检机器人已代替人工开展巡检工作；在浙江省衢州市龙游县田间地头，一架植保无人机3小时就能完成300亩农田的喷药流程，相当于40多个人整整一天的工作量……</p><p style=\\\"text-align: justify;\\\">　　“十四五”以来，类似的农业新场景新模式不断涌现，现代农业设施装备持续普及应用。我国先后支持建设国家智慧农业创新应用项目116个，深入开展国产化智慧农业技术的中试熟化、推广应用，探索形成了一批信息技术与农机农艺相融合的节本增产增效技术模式。</p><p style=\\\"text-align: justify;\\\">　　习近平总书记指出，农业科技创新要着力提升创新体系整体效能，农业科技工作要突出应用导向，把论文写在大地上。</p><p style=\\\"text-align: justify;\\\">　　5年来，我国农业科技创新体系整体效能显著提升。我国充分利用物联网、大数据、人工智能等现代信息技术发展智慧农业，并研制出一批先进智能适用的农机装备。</p><p style=\\\"text-align: justify;\\\">　　“随着智能农机加快推广，全国安装北斗终端的农机约200万台，植保无人机年作业面积超过4.1亿亩。人工智能、农业机器人等新技术与农业生产经营加速融合，精准播种、变量施肥、智慧灌溉、精准饲喂、环境控制等逐渐普及。”农业农村部市场与信息化司司长雷刘功介绍。</p><p style=\\\"text-align: justify;\\\">　　这些前沿技术的落地应用，正是农业科技现代化推动农业现代化的生动实践。“十四五”以来，我国坚持用现代设施装备武装农业，用现代科学技术服务农业，推动农业现代化水平不断提高。2024年底，农业科技进步贡献率已经达到了63.2%，农作物耕种收综合机械化率超过75%。</p><p style=\\\"text-align: justify;\\\">　　创新延“链”，拓宽食物供给版图</p><p style=\\\"text-align: justify;\\\">　　近日，蒙牛集团携多款产品参加第八届中国国际进口博览会，展示其发展新质生产力的最新成果。“我们打造的全球液态奶行业首座‘灯塔工厂’，已成为全球乳业最高人效比的新标杆，是中国乳业抢占全球智能制造新高地的生动写照。”中粮集团副总经理、蒙牛乳业董事长庆立军介绍。这座“灯塔工厂”通过实施30多项第四次工业革命技术，实现了“百人百亿”的极致人效比——100名员工，年产能达百万吨，创造产值百亿元。</p><p style=\\\"text-align: justify;\\\">　　今天，科研创新已成为发展现代化海洋牧场的强大引擎。南方海洋实验室研发“珠海琴”等多功能融合的新型组合式结构加强型养殖平台，为海洋养殖带来新变革；珠海市海洋集团形成海工型养殖装备设计、建造、施工和运维等全产业链条，成功研发“格盛一号”养殖平台，订单水体总量相当于新开拓28.25万亩耕地。</p><p style=\\\"text-align: justify;\\\">　　习近平总书记指出，要树立大农业观、大食物观，农林牧渔并举，构建多元化食物供给体系。</p><p style=\\\"text-align: justify;\\\">　　“十四五”以来，我国突出科技支撑，强化要素保障，努力向森林要食物，向草原要食物，向江河湖海要食物，向设施农业要食物，向植物动物微生物要热量、要蛋白，多元化食物供给体系加快构建。</p><p style=\\\"text-align: justify;\\\">　　一组数据表明，农业科技创新正通过看得见的方式，让老百姓的餐桌品类变得愈发丰富——2024年，我国肉蛋奶等畜产品总量达到1.75亿吨，比2020年增加2778万吨，增长18.8%；水产品总产量达到7358万吨，比2020年增长12.3%，水产品总产量持续36年居全球第一。</p><p style=\\\"text-align: justify;\\\">　　党的二十届四中全会审议通过的《中共中央关于制定国民经济和社会发展第十五个五年规划的建议》提出，“统筹发展科技农业、绿色农业、质量农业、品牌农业，把农业建成现代化大产业”。科技创新能够催生新产业、新模式、新动能，是发展新质生产力的核心要素。韩俊表示，加快建设农业强国，必须清醒认识到农业科技国际竞争新形势，把农业科技创新放在更加突出的位置，紧盯世界农业科技前沿，加快突破农业关键核心技术，努力抢占农业科技创新制高点，塑造农业农村发展新动能新优势，培育壮大农业新质生产力。</p>', 5, 100, 1, 2, '', 2, 1, 1, '2024-06-02 22:55:25', '2026-01-10 11:13:25', NULL);\nINSERT INTO `sa_article` VALUES (2, 1, '商业航天稳步快跑 “太空旅游”渐行渐近', '新华网', 'https://www.news.cn/tech/20251124/c7cb9d4e405c4c82b78a8f861889cb22/20251124c7cb9d4e405c4c82b78a8f861889cb22_20251124044f95bbab864da2b0c30861aa41279b.png', '业界普遍认为，以可复用火箭为代表的核心技术突破是商业航天提速的关键支撑。据统计，2025年底至2026年初，我国可复用火箭技术将进入密集首飞期，包括蓝箭航天“朱雀三号”、中科宇航“力箭二号”、星际荣耀“双曲线三号”和星河动力“智神星一号”在内的多款可复用火箭将迎来首飞。', '<p style=\\\"text-align: justify;\\\"> &nbsp; &nbsp; &nbsp; &nbsp;可搭载7名乘客穿越卡门线，体验约4分钟失重体验……记者从11月22日在京开幕的第四届中国空间科学大会上了解到我国太空旅游的最新进展。与会专家学者认为，随着产业链条不断完善、核心技术持续突破，我国商业航天已迈入稳步快跑的发展新阶段，曾经遥不可及的“太空旅游”正加速走进现实。</p><p style=\\\"text-align: justify;\\\">  记者在第四届中国空间科学大会同期举行的“航天新技术、新成果展”上看到，我国首型面向太空旅游的可重复使用飞行器力鸿二号的模型吸引了众多参观者。中科宇航展台工作人员告诉记者，力鸿二号将采用“箭船分离”的方式将乘客送上太空：飞到既定高度之后，载人舱与火箭分离，继续飞越100公里的卡门线，开始约4分钟的失重段，之后返回地面，以伞降的方式着陆，火箭也将垂直着陆回收。“我们的目标是让力鸿二号可重复使用超30次，这样就能把飞行成本降下来，让更多的人体验太空旅游。”</p><p style=\\\"text-align: justify;\\\">  我国商业航天的快速发展让太空旅游渐行渐近。业界普遍认为，以可复用火箭为代表的核心技术突破是商业航天提速的关键支撑。据统计，2025年底至2026年初，我国可复用火箭技术将进入密集首飞期，包括蓝箭航天“朱雀三号”、中科宇航“力箭二号”、星际荣耀“双曲线三号”和星河动力“智神星一号”在内的多款可复用火箭将迎来首飞。</p><p style=\\\"text-align: justify;\\\">  不仅火箭研制加速突破，卫星应用也在不断拓展。此次展会上，微纳星空等卫星企业也带来了最新的研发成果。微纳星空品牌总监刘晓光介绍，即将发射的“全天候卫士”MN200S-2（01B）星是公司自主研制的商业X波段相控阵雷达成像领域的技术标杆型卫星，可广泛应用于应急救灾、海洋维权、国土安全、生态监测、智慧城市建设等场景，并可实现多星高密度堆叠发射，为后续卫星规模化组网编队提供关键技术验证与工程实践依据。“随着国家低轨卫星互联网的能力建设牵引，微纳星空已经开启批量化、低成本的卫星制造。”</p><p style=\\\"text-align: justify;\\\">  业界认为，目前我国已形成覆盖火箭研制、卫星制造、发射服务、地面应用的完整商业航天产业链，产业集群效应逐步显现。在北京，“南箭北星”的产业格局已显露雏形：亦庄新城正在打造全国首个商业航天共性科研生产基地——火箭大街，海淀区作为“北星”的核心承载区，已集聚涵盖商业卫星制造、测运控、运营及数据应用的近200家相关企业。“在此基础上，海淀正全力推进卫星小镇‘两区一平台’的建设：先导区目前已有40余家商业航天企业聚集；紧邻航天城的卫星小镇核心区54万平方米空间预计2026年6月竣备，将重点引入卫星上下游企业；同时，卫星小镇拟建公共服务平台，提供卫星整星及组部件的力学、热真空、抗辐射等多种测试服务。”卫星小镇核心区对接人段叶叶介绍。</p><p style=\\\"text-align: justify;\\\">  “我国发展商业航天的优势是人多、力量大、竞争强，技术和产品能够快速迭代，紧跟国际趋势。”中国科学院微小卫星创新研究院副院长张永合在接受记者专访时表示，但目前我国商业航天企业和人才大多集中在制造领域，“还需要更多能创造任务的人，有非常前沿的想法，有改变当前航天模式的颠覆性路径。”</p><p style=\\\"text-align: justify;\\\">  张永合认为，商业航天关键是要创造需求，“比如太空旅游就是商业航天创造的需求，将人们日常生活中的旅游延伸到太空中去，在产业上就属于增量。”未来，低空经济、空间互联网等也将打开想象空间。“有了坚实的技术底座，新的产业形态就会自然而然生长出来。”</p><p style=\\\"text-align: justify;\\\">  不过，业内专家也指出，我国商业航天发展仍面临体制机制创新不足、部分核心技术有待突破等挑战。从政策层面来看，近年来国家持续加大对商业航天的支持力度，相关扶持政策和行业规范正在逐步完善，旨在优化市场环境、加大核心技术研发支持，为商业航天高质量发展营造良好生态，推动太空旅游等新业态逐步走向成熟。</p><p style=\\\"text-align: justify;\\\">  业内普遍认为，商业航天已成为航天强国建设的重要增长点。从运载火箭重复使用技术突破到卫星应用场景拓展，随着技术持续成熟、产业链不断完善和政策环境优化，未来“上太空”有望从专业探索逐步走向大众体验，中国商业航天也将在全球太空经济格局中占据重要地位。</p><p><br></p>', 1, 100, 1, 2, '', 2, 1, 1, '2024-06-02 22:56:47', '2026-01-10 11:13:47', NULL);\nINSERT INTO `sa_article` VALUES (3, 2, '以数字经济为引擎加快推进中国式现代化', '新华网', 'https://www.news.cn/tech/20251023/0cb8f0bcb7874992b8d431abdd7331a9/202510230cb8f0bcb7874992b8d431abdd7331a9_2025102332abb363b12744eb9f725ce395f16e4a.png', 'The Athletic报道，阿森纳理疗师乔丹-里斯即将加盟曼联，成为红魔的首席理疗师。曼联首席理疗师罗宾-萨德勒已于今年一月离开俱乐部', '<p style=\\\"text-align: justify;\\\"> &nbsp; &nbsp; &nbsp; &nbsp;随着中国式现代化不断向前推进，中国迎来了数字经济发展的新机遇。在数字经济快速发展的背景下，中国式现代化的内涵得以拓展，现代化动力得以重塑，现代化新动能得以培育，现代化新优势得以形成。数字技术创新、实体经济与数字经济融合、产业数字化、数字产业化成为推进中国式现代化的重要驱动力量。</p><p style=\\\"text-align: justify;\\\">  在数字经济推动下，现代化由工业经济时代的现代化向数字经济时代的现代化转变，在这一大背景下需要在理论上研究数字经济赋能中国式现代化的逻辑和机制，需要深入探讨中国式现代化如何紧紧抓住数字经济发展带来的新机遇，以数字化的知识和信息作为关键生产要素，以数字技术为核心驱动力，在数据要素和数字技术的双轮驱动下推动中国式现代化走上新征程。</p><p style=\\\"text-align: justify;\\\">  南京大学数字经济与管理学院任保平教授的专著《数字经济赋能中国式现代化》于2025年在江苏人民出版社出版，全书共17章，35.8万字。该书立足世界范围内数字化浪潮下的经济现代化背景，从理论与实践两个方面研究了数字经济发展对中国式现代化的赋能作用。</p><p style=\\\"text-align: justify;\\\">  在理论层面，该书研究了数字经济发展对中国式现代化的影响、数字经济与中国式现代化的有机衔接，数字经济背景下中国式现代化目标的重塑、数字经济与中国式现代化深度融合的逻辑机制，数字经济背景下中国式现代化的延伸和拓展。在实践层面，从中国式现代化的不同方面具体研究了数字经济的赋能作用，具体包括数字经济赋能中国式新型工业化、新型城镇化、科技现代化、农业农村现代化、产业现代化和科技现代化。</p><p style=\\\"text-align: justify;\\\">  该书的核心观点主要有以下方面。一是，中国式现代化战略在数字化转型背景下发生的一系列拓展。促进工业化与信息化的融合发展，以数字化带动工业化发展，加大数字技术研发力度，大力发展数字产业。以数字化带动农业现代化，补足中国式现代化短板。协同匹配数字经济时代的创新供求，提升产业技术创新能力。促进企业数字化转型，引领数字经济发展。协调产业数字化与数字产业化，推进产业基础现代化。加快新型基础设施建设，提升基础设施支撑能力。构建数字平台体系，打造现代化经济新形态。</p><p style=\\\"text-align: justify;\\\">  二是，以数字经济发展培育中国式现代化新优势。针对数字经济带来的现代化新变化，研究了数字经济对中国式现代化的引擎作用，认为目前中国式现代化正处于数字经济蓬勃发展带来无数新机遇的时代，我们要抓住数字经济发展带来的新机遇，以数字经济推动中国式现代化的新发展。</p><p style=\\\"text-align: justify;\\\">  三是，阐释数字经济赋能中国式现代化的逻辑。在理论上深刻阐释数字经济如何成为中国式现代化的新引擎，数字经济作为新引擎对中国式现代化赋能的驱动机制和路径，论证数字经济发展赋能中国式现代化在目标、路径和战略上的延伸和拓展，为数字经济赋能中国式现代化提供了一个理论框架。</p><p style=\\\"text-align: justify;\\\">  四是，研究数字经济全面赋能中国式现代化的机制。中国式经济现代化涉及多方面内容，包括科技现代化、工业现代化、农业现代化、服务业现代化、产业链现代化、城市现代化、区域现代化、城市现代化、生态现代化、企业现代化、人的现代化和治理现代化，数字经济应该从上述方面赋能中国式现代化。</p><p style=\\\"text-align: justify;\\\">  五是，提出了以数字经济培育中国式现代化新优势的路径。数字经济培育中国式现代化的新优势包括需求端的动力新优势、供给端的效率新优势等。需要从数字化转型的创新能力、基础设施的供给能力、数字化转型的战略支撑能力，数字化转型的保障能力等方面研究数字经济发展培育中国式现代化新优势的实现路径。而且，需要从效率变革机制、动力变革机制和质量变革机制等方面研究数字经济赋能中国式现代化新优势培育的机制，从数字产业化、产业数字化、产学研协同创新、劳动力质量和相关配套制度等方面实现数字经济培育中国式现代化的新优势，全面展示数字经济赋能中国式现代化中的应用场景。</p>', 2, 100, 1, 2, '', 2, 1, 1, '2024-06-02 22:58:41', '2026-01-10 11:13:01', NULL);\nINSERT INTO `sa_article` VALUES (4, 2, '2025腾讯全球数字生态大会在深圳举行', '新华网', 'https://www.news.cn/tech/20250918/a8a0f6e1a6d740188db7752e247518bb/20250918a8a0f6e1a6d740188db7752e247518bb_202509184f78f2904fa2456db9537d878cb89166.jpg', '5月26日晚上18：00，中超第14轮，深圳新鹏城主场迎战上海申花，上半场马莱莱补射斩获赛季第6球，半场战罢，申花暂1-0新鹏城', '<p><br></p><div data-w-e-type=\\\"video\\\" data-w-e-is-void>\\n<video poster=\\\"\\\" controls=\\\"true\\\" width=\\\"auto\\\" height=\\\"auto\\\"><source src=\\\"https://vodpub6.v.news.cn/yqfbzx-original/20250918/20250918a8a0f6e1a6d740188db7752e247518bb_XxjfceC000090_20250917_CBVFN0A001.mp4\\\" type=\\\"video/mp4\\\"/></video>\\n</div><p><span style=\\\"color: rgb(0, 0, 0);\\\"> &nbsp; &nbsp; &nbsp; &nbsp;9月16日，2025腾讯全球数字生态大会在深圳举行，会上公布多项AI技术和产品最新进展，并宣布全面开放腾讯AI落地能力及优势场景，助力“好用的AI”在千行百业中加速落地。</span></p><p><br></p>', 3, 100, 1, 2, '', 2, 1, 1, '2024-06-02 22:59:41', '2026-01-10 13:42:34', NULL);\nINSERT INTO `sa_article` VALUES (5, 3, '秀我中国丨中国小机器人“勇闯”美国CES', '新华网', 'https://www.news.cn/tech/20260109/b2c43e2b0d1e43a98840c33e37fbbc73/20260109896bd0b56c18435987243f0f5dc01d67_202601099d0953f9999949a9b55e9d212d7bf773.jpg', '2026年美国拉斯维加斯消费电子展（CES）6日至9日举行，首次亮相海外展会的中国小机器人“启元Q1”刚一登场就成为焦点，凭借其出色表现“圈粉”海外。', '<p><br></p><div data-w-e-type=\\\"video\\\" data-w-e-is-void>\\n<video poster=\\\"https://vodpub6.v.news.cn/yqfbzx-original/20260109/image/2ff2c0d5-4060-400d-8640-b41a0da5af1f.jpg\\\" controls=\\\"true\\\" width=\\\"360\\\" height=\\\"640\\\"><source src=\\\"https://vodpub6.v.news.cn/yqfbzx-original/20260109/20260109896bd0b56c18435987243f0f5dc01d67_XxjfceC000165_20260109_CBVFN0A001.mp4\\\" type=\\\"video/mp4\\\"/></video>\\n</div><p style=\\\"text-align: left;\\\"><span style=\\\"color: rgb(0, 0, 0);\\\"> &nbsp; &nbsp; &nbsp; &nbsp;2026年美国拉斯维加斯消费电子展（CES）6日至9日举行，首次亮相海外展会的中国小机器人“启元Q1”刚一登场就成为焦点，凭借其出色表现“圈粉”海外。</span></p>', 3, 100, 1, 2, '', 2, 1, 1, '2024-06-02 23:01:17', '2026-01-10 13:42:24', NULL);\nINSERT INTO `sa_article` VALUES (6, 3, 'AI助力药物虚拟筛选提速百万倍 开启后AlphaFold时代创新药', '新华网', 'https://www.news.cn/tech/20260109/2e0f65d6733a4e2588a97dfe96593a09/202601092e0f65d6733a4e2588a97dfe96593a09_202601090012b088f5604e22a77ae70f8656f466.jpg', '团队与清华大学闫创业教授团队合作，在去甲肾上腺素转运体（NET）的临床相关靶点上开展了系列生物实验验证。', '<p><span style=\\\"color: rgb(0, 0, 0);\\\"> &nbsp; &nbsp; &nbsp; &nbsp;1月9日，清华大学智能产业研究院（AIR）联合清华大学生命学院、清华大学化学系在《科学》杂志发表论文《深度对比学习实现基因组级别药物虚拟筛选》。该论文研发了一个AI驱动的超高通量药物虚拟筛选平台DrugCLIP, 筛选速度对比传统方法实现百万倍提升，同时在预测准确率上也取得显著突破。依托该平台，团队打通了从AlphaFold结构预测到药物发现的关键通道，首次完成了覆盖人类基因组规模的药物虚拟筛选，为后AlphaFold时代的创新药物发现带来新可能性。</span></p><p><img src=\\\"https://www.news.cn/tech/20260109/2e0f65d6733a4e2588a97dfe96593a09/202601092e0f65d6733a4e2588a97dfe96593a09_2026010932fb993ce4734583aa3e4e861e536cff.png\\\" alt=\\\"\\\" data-href=\\\"\\\" style=\\\"\\\"/></p><p style=\\\"text-align: justify;\\\"> &nbsp; &nbsp;长期以来，药物研发面临“高风险、高投入、低成功率”的难题，在靶点发现与先导化合物筛选阶段，受限于传统工具的计算能力，绝大多数潜在靶点和化合物仍未被充分探索。如何在广阔的生物与化学空间中精准高效地发现活性化合物，是当前创新药物研发面临的核心挑战。</p><p style=\\\"text-align: justify;\\\">  据了解，为突破虚拟筛选规模瓶颈，DrugCLIP创新性地构建了蛋白口袋与小分子的“向量化结合空间”，将传统基于物理对接的筛选流程转化为高效的向量检索问题。该模型结合对比学习、3D结构预训练与多模态编码技术，能在三维结构层面精准建模蛋白-配体间的相互作用。训练后的高潜力分子将自然聚集于目标蛋白口袋的向量邻域，能够有效支撑快速的大规模虚拟筛选。依托这一机制，DrugCLIP在128核CPU+8张GPU的计算节点上，能实现毫秒级打分与万亿级日吞吐能力，筛选100万个候选分子仅需0.02秒，日处理能力达31万亿次，对比传统方法实现了百万倍提升。</p><p style=\\\"text-align: justify;\\\"><img src=\\\"https://www.news.cn/tech/20260109/2e0f65d6733a4e2588a97dfe96593a09/202601092e0f65d6733a4e2588a97dfe96593a09_2026010902fd55e1493f4741a2f10b4480ee398e.png\\\" alt=\\\"\\\" data-href=\\\"\\\" style=\\\"\\\"></p><p style=\\\"text-align: justify;\\\"> &nbsp; &nbsp;团队与清华大学闫创业教授团队合作，在去甲肾上腺素转运体（NET）的临床相关靶点上开展了系列生物实验验证。团队使用DrugCLIP模型从160万个候选分子中筛选出约100个高评分分子，同位素配体转运实验检测显示，其中15%为有效抑制剂，其中12个分子结合能力优于现有抗抑郁药物安非他酮。相关复合物结构已通过冷冻电镜解析，进一步验证了DrugCLIP筛选结果的生物学可信度。</p><p style=\\\"text-align: justify;\\\">  值得关注的是，DrugCLIP支持对AlphaFold预测的蛋白结构和apo（无配体）状态下的蛋白口袋进行筛选，扩大了其在真实药物发现场景中的适用性。团队和清华大学刘磊教授团队合作，针对E3泛素连接酶TRIP12（thyroid hormone receptor interactor 12）进行了虚拟筛选与实验验证。过往研究发现，TRIP12是多种肿瘤、帕金森综合征的潜在靶点，但是TRIP12缺少已知的小分子配体和复合物结构。团队使用DrugCLIP模型，从160万个候选分子中高通量筛选出约50个高评分分子，SPR实验证实，其中10个分子与TRIP12有结合能力，两个亲和力较高的分子也对TRIP12的泛素连接酶活性有一定的抑制活性。</p><p style=\\\"text-align: justify;\\\">  此外，依托DrugCLIP，团队首次完成了人类基因组规模的虚拟筛选项目，覆盖约1万个蛋白靶点、2万个结合口袋，分析超过5亿个小分子，富集出200万余个高潜力活性分子，构建了目前已知最大规模的蛋白-配体筛选数据库。该数据库已面向全球科研社区开放，为基础研究与早期药物发现提供了强大数据支持。</p><p style=\\\"text-align: justify;\\\">  DrugCLIP平台现已免费开放，用户无需本地部署，通过网页上传蛋白结构即可启动筛选任务。平台集成口袋/分子编码、向量检索、可视化与结果分析等功能，支持多种分子库调用与自定义上传，广泛适用于科研机构与企业用户。</p><p style=\\\"text-align: justify;\\\">  未来，DrugCLIP将与科研产业生态合作伙伴深度合作，在抗癌、传染病、罕见病等方向加速新靶点与First-in-class药物的发现。团队将持续优化引擎性能、拓展支持模态，助力构建一个更智能、高效与普惠的全球药物创新生态。</p>', 4, 100, 1, 2, '', 2, 1, 1, '2024-06-02 23:02:40', '2026-01-10 13:38:51', NULL);\nINSERT INTO `sa_article` VALUES (7, 4, '高度重视低空经济为哪般', '新华网', 'https://www.news.cn/tech/20250312/c0453593a495424780c5424c054a1d4d/20250312c0453593a495424780c5424c054a1d4d_2025031215d8945b560d4d169997f7745d0ef56f.jpg', '当前，我国低空经济正处于市场培育初期，关键技术的实用性和商业价值仅得到初步验证，但已彰显出广阔的增长空间', '<p style=\\\"text-align: justify;\\\"> &nbsp; &nbsp; &nbsp; &nbsp;近年来，低空经济成为全球发达经济体角逐的重要方向。虽然世界范围内低空经济还处于培育初期阶段，但是美国、日本、欧盟等国家和地区已经重点围绕场景开发应用、交通管理能力、运行技术验证、系统标准体系等方面积极出台和完善相关政策，加快发展低空经济。</p><p style=\\\"text-align: justify;\\\">  低空经济是依托低空飞行活动牵引串联的一系列相互关联的产业经济活动，不仅包括上游生产制造飞行器所必需的材料、零部件及分系统的行业企业，还包括中下游低空飞行器组装集成制造和测试试飞、设施配套及低空服务等领域。低空经济产业链条长、产业关联性强、应用场景丰富，具有战略引领性、高增长潜力等显著特征，既可以推动现代农牧业、先进制造业、现代服务业深度融合发展，又能够扩大有效投资、提振消费需求、提升创新能力。世界主要国家高度重视低空经济发展，就是因为看好其发展前景。</p><p style=\\\"text-align: justify;\\\">  当前，我国低空经济正处于市场培育初期，关键技术的实用性和商业价值仅得到初步验证，但已彰显出广阔的增长空间。未来随着技术迭代升级和商业模式逐步成熟，低空经济的高增长潜力将会进一步释放，更容易实现相关产业企业的群体性爆发成长，有望成为拉动经济增长的新引擎。</p><p style=\\\"text-align: justify;\\\">  一方面，低空飞行器的产业规模体量加快增长、产业生态持续完善。目前，我国无人机制造国际竞争力逐步增强，消费级无人机世界领先优势突出。截至2023年底，我国民用无人机研制企业已超过2300家，量产的无人机产品超过1000款。2023年，我国民用无人机产业规模达到1174.3亿元，同比增长32%。同时，新一代信息技术、新材料、新能源加速与航空科学技术融合发展，推动低空飞行器动力装备及系统、传感器、飞控系统等相关技术加速迭代，绿色高效、安全低噪的飞行器设计、制造与验证技术也持续更新升级。</p><p style=\\\"text-align: justify;\\\">  另一方面，体量巨大、类型多样的应用场景持续涌现，牵引低空服务快速释放动能。运营航空器大幅增加，《2023—2024中国民用无人驾驶航空发展报告》显示，截至2024年8月底，我国无人机实名登记数达198.7万架，比2023年底增加72万架；共颁发无人机驾驶员执照22万本，比2023年底增加13.9%。随着影视航拍、航空运动、空中观光游览等低空文旅应用场景快速发展，低空经济能为满足人民群众美好生活需求提供新供给。2023年，横店“航空＋影视＋旅游”交旅融合案例入选第一批交通运输与旅游融合发展十佳案例；2024年，敦煌“飞天”通用航空项目等航空旅游产品案例入选第二批交通运输与旅游融合发展示范案例。低空旅游市场潜力开始显现。</p><p style=\\\"text-align: justify;\\\">  同时，低空经济在农业植保、现代物流等行业领域的发展应用不断深入。随着无人机应用技术不断成熟和应用场景持续丰富，“农林牧副渔”多场景作业不断拓展，农业无人机服务市场规模呈蓬勃发展态势。2024年，全国植保无人机的保有量达到25.1万架，作业面积更是高达26.7亿亩次，同比增长近25%。从全球看，上世纪80年代以来，美国农业植保无人机作业渗透率超过50％，日本60％的稻田采用无人机进行植保作业。相较而言，我国农业无人机作业渗透率还比较低，有很大发展空间。在低空物流领域，以无人机为载运工具的无人化配送成为优化城市物流的重要方向，这能有效解决传统物流配送模式面临的劳动力成本、运输成本大幅攀升以及物资配送流通效率低下等诸多问题。在“低空+”领域，低空经济赋能社会治理成效突出，促进巡检、应急救援、城市管理、森林防火、医疗救护等公共服务快速发展。实践中，北京延庆、湖北武汉等地已采用电力线路无人机智能巡检，有效降低了巡检成本，提升了巡检效率。</p><p style=\\\"text-align: justify;\\\">  但也要看到，我国低空经济发展还存在一些问题，如统筹发展和安全有短板、产业融合化发展不足、空域管理协同机制尚不健全、基础设施建设相对滞后等。对此，要从突出集群融合、强化科技创新、加强设施建设等方面综合施策，将低空经济的发展潜力充分释放出来。</p><p style=\\\"text-align: justify;\\\">  一是突出集群融合，加快培育壮大低空经济产业集群，以市场需求为牵引、以科技创新为驱动，积极完善产业生态、谋划应用场景，推进低空制造业集群化发展。二是强化科技创新，聚焦低空经济创新链薄弱环节，加大科技创新投入，加快提升低空技术支撑能力。三是加强设施建设，构建低空经济基础设施综合保障体系，坚持绿色发展、节约集约，统筹推进通用机场、电动垂直起降飞行器起降场、固定运营基地、飞行服务站等地面配套基础设施建设，推进低空飞行通信、导航、气象监测等信息基础设施建设，加速低空经济智联网络设施建设。此外，还要统筹发展和安全，加强低空飞行器监控防护，强化低空安全技术攻关，提升空域精细化管理能力。坚持包容审慎的安全风险管控理念，建设监管服务体系，建立灵活调配、动态高效的低空空域管理使用机制，增强管理的协同性与联动性。</p>', 11, 100, 1, 2, '', 2, 1, 1, '2024-06-02 23:04:23', '2026-01-10 13:43:44', NULL);\nINSERT INTO `sa_article` VALUES (8, 4, '国家发改委成立低空经济发展司', '新华网', 'https://www.news.cn/tech/20241231/3f5396024a9749ee863292c04c7119dc/202412313f5396024a9749ee863292c04c7119dc_2024123101c42d384b83467f835ffd286af095d4.jpg', '近日，低空经济发展司召开推动低空基础设施建设座谈会和推动低空智能网联系统建设专题座谈会', '<p style=\\\"text-align: justify;\\\"> &nbsp; &nbsp; &nbsp; 记者从国家发展和改革委员会官方网站获悉，低空经济发展司已正式成立。</p><p style=\\\"text-align: justify;\\\">　　低空经济发展司的具体职责是拟订并组织实施低空经济发展战略、中长期发展规划，提出有关政策建议，协调有关重大问题等。</p><p style=\\\"text-align: justify;\\\">　　近日，低空经济发展司召开推动低空基础设施建设座谈会和推动低空智能网联系统建设专题座谈会。</p><p style=\\\"text-align: justify;\\\">　　在推动低空基础设施建设座谈会上，低空经济发展司负责同志同自然资源部、生态环境部等部委和有关中央企业进行座谈，了解相关领域低空经济典型场景应用和相关基础设施建设发展情况，并就推动低空基础设施有序规划建设进行交流。</p><p style=\\\"text-align: justify;\\\">　　在推动低空智能网联系统建设专题座谈会上，低空经济发展司负责同志与通信、导航方面有关专家进行座谈，就低空智能网联系统建设进行交流。</p>', 6, 100, 1, 2, '', 2, 1, 1, '2024-06-02 23:04:23', '2026-01-10 13:42:32', NULL);\n\n-- ----------------------------\n-- Table structure for sa_article_banner\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_article_banner`;\nCREATE TABLE `sa_article_banner`  (\n  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',\n  `banner_type` int(11) NULL DEFAULT NULL COMMENT '类型',\n  `image` varchar(1000) NULL DEFAULT NULL COMMENT '图片地址',\n  `is_href` tinyint(1) NULL DEFAULT 1 COMMENT '是否链接',\n  `url` varchar(255) NULL DEFAULT NULL COMMENT '链接地址',\n  `title` varchar(255) NULL DEFAULT NULL COMMENT '标题',\n  `status` tinyint(1) NULL DEFAULT 1 COMMENT '状态',\n  `sort` int(11) NULL DEFAULT 0 COMMENT '排序',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '描述',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 4 COMMENT = '文章轮播图' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_article_banner\n-- ----------------------------\nINSERT INTO `sa_article_banner` VALUES (1, 1, 'https://picsum.photos/id/490/640/360', 1, '/blog/1', '探索亚洲的烹饪奇迹', 1, 100, '有一系列名为“新加坡传统烹饪”的食谱，探索了新加坡的美食和文化。它包括新加坡华人、马来人、印度人、欧亚人和土生华人（海峡华人）的美食', 1, 1, '2024-06-02 23:06:37', '2026-01-09 21:51:50', NULL);\nINSERT INTO `sa_article_banner` VALUES (2, 1, 'https://picsum.photos/id/29/640/360', 1, '/blog/2', '探索雄伟的山峰', 1, 100, '攀登这座风景如画的山峰的最佳方式是乘坐御在所索道，乘坐15 分钟即可将游客带入空中，欣赏周围一览无余的景观', 1, 1, '2024-06-02 23:06:49', '2026-01-09 21:51:54', NULL);\nINSERT INTO `sa_article_banner` VALUES (3, 1, 'https://picsum.photos/id/903/640/360', 1, '/blog/3', '揭秘奇迹', 1, 100, '极光是地球磁场与太阳风相互作用的产物，当太阳风中的带电粒子与地球高层大气中的原子、分子碰撞时，会产生发光现象，形成美丽的极光', 1, 1, '2024-06-02 23:06:56', '2026-01-09 21:53:32', NULL);\n\n-- ----------------------------\n-- Table structure for sa_article_category\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_article_category`;\nCREATE TABLE `sa_article_category`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '编号',\n  `parent_id` int(11) NOT NULL DEFAULT 0 COMMENT '父级ID',\n  `category_name` varchar(255) NOT NULL COMMENT '分类标题',\n  `describe` varchar(255) NULL DEFAULT NULL COMMENT '分类简介',\n  `image` varchar(255) NULL DEFAULT NULL COMMENT '分类图片',\n  `sort` int(10) UNSIGNED NULL DEFAULT 100 COMMENT '排序',\n  `status` tinyint(1) UNSIGNED NULL DEFAULT 1 COMMENT '状态',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 5 COMMENT = '文章分类表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_article_category\n-- ----------------------------\nINSERT INTO `sa_article_category` VALUES (1, 0, '大国科技', '', NULL, 100, 1, 1, 1, '2024-06-02 22:50:51', '2026-01-06 18:03:07', NULL);\nINSERT INTO `sa_article_category` VALUES (2, 0, '数字经济', '', NULL, 100, 1, 1, 1, '2024-06-02 22:50:56', '2026-01-09 16:54:05', NULL);\nINSERT INTO `sa_article_category` VALUES (3, 0, '科技快讯', '', NULL, 100, 1, 1, 1, '2024-06-02 22:51:01', '2026-01-07 01:03:37', NULL);\nINSERT INTO `sa_article_category` VALUES (4, 0, '低空经济', '', NULL, 100, 1, 1, 1, '2024-06-02 22:51:16', '2026-01-06 18:03:14', NULL);\n\nSET FOREIGN_KEY_CHECKS = 1;\n"
  },
  {
    "path": "src/plugin/saiadmin/db/saiadmin-pure.sql",
    "content": "\nSET NAMES utf8mb4;\nSET FOREIGN_KEY_CHECKS = 0;\n\n-- ----------------------------\n-- Table structure for sa_system_attachment\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_attachment`;\nCREATE TABLE `sa_system_attachment`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `category_id` int(11) NULL DEFAULT 0 COMMENT '文件分类',\n  `storage_mode` smallint(6) NULL DEFAULT 1 COMMENT '存储模式 (1 本地 2 阿里云 3 七牛云 4 腾讯云)',\n  `origin_name` varchar(255) NULL DEFAULT NULL COMMENT '原文件名',\n  `object_name` varchar(50) NULL DEFAULT NULL COMMENT '新文件名',\n  `hash` varchar(64) NULL DEFAULT NULL COMMENT '文件hash',\n  `mime_type` varchar(255) NULL DEFAULT NULL COMMENT '资源类型',\n  `storage_path` varchar(100) NULL DEFAULT NULL COMMENT '存储目录',\n  `suffix` varchar(10) NULL DEFAULT NULL COMMENT '文件后缀',\n  `size_byte` bigint(20) NULL DEFAULT NULL COMMENT '字节数',\n  `size_info` varchar(50) NULL DEFAULT NULL COMMENT '文件大小',\n  `url` varchar(255) NULL DEFAULT NULL COMMENT 'url地址',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `hash`(`hash`) USING BTREE,\n  INDEX `idx_url`(`url`) USING BTREE,\n  INDEX `idx_create_time`(`create_time`) USING BTREE,\n  INDEX `idx_category_id`(`category_id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '附件信息表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_attachment\n-- ----------------------------\n\n-- ----------------------------\n-- Table structure for sa_system_category\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_category`;\nCREATE TABLE `sa_system_category`  (\n  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '分类ID',\n  `parent_id` int(11) NOT NULL DEFAULT 0 COMMENT '父id',\n  `level` varchar(255) NULL DEFAULT NULL COMMENT '组集关系',\n  `category_name` varchar(100) NOT NULL DEFAULT '' COMMENT '分类名称',\n  `sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序',\n  `status` tinyint(1) NULL DEFAULT 1 COMMENT '状态',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `pid`(`parent_id`) USING BTREE,\n  INDEX `sort`(`sort`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 6 COMMENT = '附件分类表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_category\n-- ----------------------------\nINSERT INTO `sa_system_category` VALUES (1, 0, '0,', '全部分类', 100, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_category` VALUES (2, 1, '0,1,', '图片分类', 100, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_category` VALUES (3, 1, '0,1,', '文件分类', 100, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_category` VALUES (4, 1, '0,1,', '系统图片', 100, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_category` VALUES (5, 1, '0,1,', '其他分类', 100, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_system_config\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_config`;\nCREATE TABLE `sa_system_config`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '编号',\n  `group_id` int(11) NULL DEFAULT NULL COMMENT '组id',\n  `key` varchar(32) NOT NULL COMMENT '配置键名',\n  `value` text NULL COMMENT '配置值',\n  `name` varchar(255) NULL DEFAULT NULL COMMENT '配置名称',\n  `input_type` varchar(32) NULL DEFAULT NULL COMMENT '数据输入类型',\n  `config_select_data` varchar(500) NULL DEFAULT NULL COMMENT '配置选项数据',\n  `sort` smallint(5) UNSIGNED NULL DEFAULT 0 COMMENT '排序',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建人',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新人',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`, `key`) USING BTREE,\n  INDEX `group_id`(`group_id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 302 COMMENT = '参数配置信息表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_config\n-- ----------------------------\nINSERT INTO `sa_system_config` VALUES (1, 1, 'site_copyright', 'Copyright © 2024 saithink', '版权信息', 'textarea', NULL, 96, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (2, 1, 'site_desc', '基于vue3 + webman 的极速开发框架', '网站描述', 'textarea', NULL, 97, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (3, 1, 'site_keywords', '后台管理系统', '网站关键字', 'input', NULL, 98, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (4, 1, 'site_name', 'SaiAdmin', '网站名称', 'input', NULL, 99, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (5, 1, 'site_record_number', '9527', '网站备案号', 'input', NULL, 95, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (6, 2, 'upload_allow_file', 'txt,doc,docx,xls,xlsx,ppt,pptx,rar,zip,7z,gz,pdf,wps,md,jpg,png,jpeg,mp4,pem,crt', '文件类型', 'input', NULL, 0, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (7, 2, 'upload_allow_image', 'jpg,jpeg,png,gif,svg,bmp', '图片类型', 'input', NULL, 0, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (8, 2, 'upload_mode', '1', '上传模式', 'select', '[{\\\"label\\\":\\\"本地上传\\\",\\\"value\\\":\\\"1\\\"},{\\\"label\\\":\\\"阿里云OSS\\\",\\\"value\\\":\\\"2\\\"},{\\\"label\\\":\\\"七牛云\\\",\\\"value\\\":\\\"3\\\"},{\\\"label\\\":\\\"腾讯云COS\\\",\\\"value\\\":\\\"4\\\"},{\\\"label\\\":\\\"亚马逊S3\\\",\\\"value\\\":\\\"5\\\"}]', 99, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (10, 2, 'upload_size', '52428800', '上传大小', 'input', NULL, 88, '单位Byte,1MB=1024*1024Byte', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (11, 2, 'local_root', 'public/storage/', '本地存储路径', 'input', NULL, 0, '本地存储文件路径', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (12, 2, 'local_domain', 'http://127.0.0.1:8787', '本地存储域名', 'input', NULL, 0, 'http://127.0.0.1:8787', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (13, 2, 'local_uri', '/storage/', '本地访问路径', 'input', NULL, 0, '访问是通过domain + uri', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (14, 2, 'qiniu_accessKey', '', '七牛key', 'input', NULL, 0, '七牛云存储secretId', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (15, 2, 'qiniu_secretKey', '', '七牛secret', 'input', NULL, 0, '七牛云存储secretKey', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (16, 2, 'qiniu_bucket', '', '七牛bucket', 'input', NULL, 0, '七牛云存储bucket', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (17, 2, 'qiniu_dirname', '', '七牛dirname', 'input', NULL, 0, '七牛云存储dirname', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (18, 2, 'qiniu_domain', '', '七牛domain', 'input', NULL, 0, '七牛云存储domain', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (19, 2, 'cos_secretId', '', '腾讯Id', 'input', NULL, 0, '腾讯云存储secretId', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (20, 2, 'cos_secretKey', '', '腾讯key', 'input', NULL, 0, '腾讯云secretKey', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (21, 2, 'cos_bucket', '', '腾讯bucket', 'input', NULL, 0, '腾讯云存储bucket', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (22, 2, 'cos_dirname', '', '腾讯dirname', 'input', NULL, 0, '腾讯云存储dirname', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (23, 2, 'cos_domain', '', '腾讯domain', 'input', NULL, 0, '腾讯云存储domain', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (24, 2, 'cos_region', '', '腾讯region', 'input', NULL, 0, '腾讯云存储region', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (25, 2, 'oss_accessKeyId', '', '阿里Id', 'input', NULL, 0, '阿里云存储accessKeyId', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (26, 2, 'oss_accessKeySecret', '', '阿里Secret', 'input', NULL, 0, '阿里云存储accessKeySecret', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (27, 2, 'oss_bucket', '', '阿里bucket', 'input', NULL, 0, '阿里云存储bucket', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (28, 2, 'oss_dirname', '', '阿里dirname', 'input', NULL, 0, '阿里云存储dirname', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (29, 2, 'oss_domain', '', '阿里domain', 'input', NULL, 0, '阿里云存储domain', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (30, 2, 'oss_endpoint', '', '阿里endpoint', 'input', NULL, 0, '阿里云存储endpoint', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (31, 3, 'Host', 'smtp.qq.com', 'SMTP服务器', 'input', '', 100, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (32, 3, 'Port', '465', 'SMTP端口', 'input', '', 100, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (33, 3, 'Username', '', 'SMTP用户名', 'input', '', 100, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (34, 3, 'Password', '', 'SMTP密码', 'input', '', 100, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (35, 3, 'SMTPSecure', 'ssl', 'SMTP验证方式', 'radio', '[\\r\\n    {\\\"label\\\":\\\"ssl\\\",\\\"value\\\":\\\"ssl\\\"},\\r\\n    {\\\"label\\\":\\\"tsl\\\",\\\"value\\\":\\\"tsl\\\"}\\r\\n]', 100, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (36, 3, 'From', '', '默认发件人', 'input', '', 100, '默认发件的邮箱地址', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (37, 3, 'FromName', '账户注册', '默认发件名称', 'input', '', 100, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (38, 3, 'CharSet', 'UTF-8', '编码', 'input', '', 100, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (39, 3, 'SMTPDebug', '0', '调试模式', 'radio', '[\\r\\n    {\\\"label\\\":\\\"关闭\\\",\\\"value\\\":\\\"0\\\"},\\r\\n    {\\\"label\\\":\\\"client\\\",\\\"value\\\":\\\"1\\\"},\\r\\n    {\\\"label\\\":\\\"server\\\",\\\"value\\\":\\\"2\\\"}\\r\\n]', 100, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (40, 2, 's3_key', '', 'key', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (41, 2, 's3_secret', '', 'secret', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (42, 2, 's3_bucket', '', 'bucket', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (43, 2, 's3_dirname', '', 'dirname', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (44, 2, 's3_domain', '', 'domain', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (45, 2, 's3_region', '', 'region', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (46, 2, 's3_version', '', 'version', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (47, 2, 's3_use_path_style_endpoint', '', 'path_style_endpoint', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (48, 2, 's3_endpoint', '', 'endpoint', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config` VALUES (49, 2, 's3_acl', '', 'acl', 'input', '', 0, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_system_config_group\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_config_group`;\nCREATE TABLE `sa_system_config_group`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `name` varchar(50) NULL DEFAULT NULL COMMENT '字典名称',\n  `code` varchar(100) NULL DEFAULT NULL COMMENT '字典标示',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建人',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新人',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 4 COMMENT = '参数配置分组表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_config_group\n-- ----------------------------\nINSERT INTO `sa_system_config_group` VALUES (1, '站点配置', 'site_config', '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config_group` VALUES (2, '上传配置', 'upload_config', NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_config_group` VALUES (3, '邮件服务', 'email_config', NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_system_dept\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_dept`;\nCREATE TABLE `sa_system_dept`  (\n  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,\n  `parent_id` bigint(20) UNSIGNED NULL DEFAULT 0 COMMENT '父级ID，0为根节点',\n  `name` varchar(64) NOT NULL COMMENT '部门名称',\n  `code` varchar(64) NULL DEFAULT NULL COMMENT '部门编码',\n  `leader_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '部门负责人ID',\n  `level` varchar(255) NULL DEFAULT '' COMMENT '祖级列表，格式: 0,1,5, (便于查询子孙节点)',\n  `sort` int(11) NULL DEFAULT 0 COMMENT '排序，数字越小越靠前',\n  `status` tinyint(1) NULL DEFAULT 1 COMMENT '状态: 1启用, 0禁用',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `idx_parent_id`(`parent_id`) USING BTREE,\n  INDEX `idx_path`(`level`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1114 COMMENT = '部门表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_dept\n-- ----------------------------\nINSERT INTO `sa_system_dept` VALUES (1, 0, '腾讯集团', 'GROUP', 1, '0,', 100, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_system_dict_data\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_dict_data`;\nCREATE TABLE `sa_system_dict_data`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `type_id` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '字典类型ID',\n  `label` varchar(50) NULL DEFAULT NULL COMMENT '字典标签',\n  `value` varchar(100) NULL DEFAULT NULL COMMENT '字典值',\n  `color` varchar(50) NULL DEFAULT NULL COMMENT '字典颜色',\n  `code` varchar(100) NULL DEFAULT NULL COMMENT '字典标示',\n  `sort` smallint(5) UNSIGNED NULL DEFAULT 0 COMMENT '排序',\n  `status` smallint(6) NULL DEFAULT 1 COMMENT '状态 (1正常 2停用)',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `type_id`(`type_id`) USING BTREE,\n  INDEX `idx_code`(`code`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 50 COMMENT = '字典数据表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_dict_data\n-- ----------------------------\nINSERT INTO `sa_system_dict_data` VALUES (2, 2, '本地存储', '1', '#5d87ff', 'upload_mode', 99, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (3, 2, '阿里云OSS', '2', '#f9901f', 'upload_mode', 98, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (4, 2, '七牛云', '3', '#00ced1', 'upload_mode', 97, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (5, 2, '腾讯云COS', '4', '#1d84ff', 'upload_mode', 96, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (6, 2, '亚马逊S3', '5', '#ff80c8', 'upload_mode', 95, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (7, 3, '正常', '1', '#13deb9', 'data_status', 0, 1, '1为正常', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (8, 3, '停用', '2', '#ff4d4f', 'data_status', 0, 1, '2为停用', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (9, 4, '统计页面', 'statistics', '#00ced1', 'dashboard', 100, 1, '管理员用', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (10, 4, '工作台', 'work', '#ff8c00', 'dashboard', 50, 1, '员工使用', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (11, 5, '男', '1', '#5d87ff', 'gender', 0, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (12, 5, '女', '2', '#ff4500', 'gender', 0, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (13, 5, '未知', '3', '#b48df3', 'gender', 0, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (16, 12, '图片', 'image', '#60c041', 'attachment_type', 10, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (17, 12, '文档', 'text', '#1d84ff', 'attachment_type', 9, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (18, 12, '音频', 'audio', '#00ced1', 'attachment_type', 8, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (19, 12, '视频', 'video', '#ff4500', 'attachment_type', 7, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (20, 12, '应用程序', 'application', '#ff8c00', 'attachment_type', 6, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (21, 13, '目录', '1', '#909399', 'menu_type', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (22, 13, '菜单', '2', '#1e90ff', 'menu_type', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (23, 13, '按钮', '3', '#ff4500', 'menu_type', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (24, 13, '外链', '4', '#00ced1', 'menu_type', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (25, 14, '是', '1', '#60c041', 'yes_or_no', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (26, 14, '否', '2', '#ff4500', 'yes_or_no', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (47, 20, 'URL任务GET', '1', '#5d87ff', 'crontab_task_type', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (48, 20, 'URL任务POST', '2', '#00ced1', 'crontab_task_type', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_data` VALUES (49, 20, '类任务', '3', '#ff8c00', 'crontab_task_type', 100, 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_system_dict_type\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_dict_type`;\nCREATE TABLE `sa_system_dict_type`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `name` varchar(50) NULL DEFAULT NULL COMMENT '字典名称',\n  `code` varchar(100) NULL DEFAULT NULL COMMENT '字典标示',\n  `status` smallint(6) NULL DEFAULT 1 COMMENT '状态 (1正常 2停用)',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `idx_code`(`code`) USING BTREE,\n  INDEX `idx_name`(`name`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 24 COMMENT = '字典类型表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_dict_type\n-- ----------------------------\nINSERT INTO `sa_system_dict_type` VALUES (2, '存储模式', 'upload_mode', 1, '上传文件存储模式', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_type` VALUES (3, '数据状态', 'data_status', 1, '通用数据状态', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_type` VALUES (4, '后台首页', 'dashboard', 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_type` VALUES (5, '性别', 'gender', 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_type` VALUES (12, '附件类型', 'attachment_type', 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_type` VALUES (13, '菜单类型', 'menu_type', 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_type` VALUES (14, '是否', 'yes_or_no', 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_dict_type` VALUES (20, '定时任务类型', 'crontab_task_type', 1, '', 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_system_login_log\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_login_log`;\nCREATE TABLE `sa_system_login_log`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `username` varchar(20) NULL DEFAULT NULL COMMENT '用户名',\n  `ip` varchar(45) NULL DEFAULT NULL COMMENT '登录IP地址',\n  `ip_location` varchar(255) NULL DEFAULT NULL COMMENT 'IP所属地',\n  `os` varchar(50) NULL DEFAULT NULL COMMENT '操作系统',\n  `browser` varchar(50) NULL DEFAULT NULL COMMENT '浏览器',\n  `status` smallint(6) NULL DEFAULT 1 COMMENT '登录状态 (1成功 2失败)',\n  `message` varchar(50) NULL DEFAULT NULL COMMENT '提示消息',\n  `login_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '登录时间',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `username`(`username`) USING BTREE,\n  INDEX `idx_create_time`(`create_time`) USING BTREE,\n  INDEX `idx_login_time`(`login_time`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '登录日志表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_login_log\n-- ----------------------------\n\n-- ----------------------------\n-- Table structure for sa_system_mail\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_mail`;\nCREATE TABLE `sa_system_mail`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '编号',\n  `gateway` varchar(50) NULL DEFAULT NULL COMMENT '网关',\n  `from` varchar(50) NULL DEFAULT NULL COMMENT '发送人',\n  `email` varchar(50) NULL DEFAULT NULL COMMENT '接收人',\n  `code` varchar(20) NULL DEFAULT NULL COMMENT '验证码',\n  `content` varchar(500) NULL DEFAULT NULL COMMENT '邮箱内容',\n  `status` varchar(20) NULL DEFAULT NULL COMMENT '发送状态',\n  `response` varchar(500) NULL DEFAULT NULL COMMENT '返回结果',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `idx_create_time`(`create_time`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '邮件记录' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_mail\n-- ----------------------------\n\n-- ----------------------------\n-- Table structure for sa_system_menu\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_menu`;\nCREATE TABLE `sa_system_menu`  (\n  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,\n  `parent_id` bigint(20) UNSIGNED NULL DEFAULT 0 COMMENT '父级ID',\n  `name` varchar(64) NOT NULL COMMENT '菜单名称',\n  `code` varchar(64) NULL DEFAULT NULL COMMENT '组件名称',\n  `slug` varchar(100) NULL DEFAULT NULL COMMENT '权限标识，如 user:list, user:add',\n  `type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '类型: 1目录, 2菜单, 3按钮/API',\n  `path` varchar(255) NULL DEFAULT NULL COMMENT '路由地址(前端)或API路径(后端)',\n  `component` varchar(255) NULL DEFAULT NULL COMMENT '前端组件路径，如 layout/User',\n  `method` varchar(10) NULL DEFAULT NULL COMMENT '请求方式',\n  `icon` varchar(64) NULL DEFAULT NULL COMMENT '图标',\n  `sort` int(11) NULL DEFAULT 100 COMMENT '排序',\n  `link_url` varchar(255) NULL DEFAULT NULL COMMENT '外部链接',\n  `is_iframe` tinyint(1) NULL DEFAULT 2 COMMENT '是否iframe',\n  `is_keep_alive` tinyint(1) NULL DEFAULT 2 COMMENT '是否缓存',\n  `is_hidden` tinyint(1) NULL DEFAULT 2 COMMENT '是否隐藏',\n  `is_fixed_tab` tinyint(1) NULL DEFAULT 2 COMMENT '是否固定标签页',\n  `is_full_page` tinyint(1) NULL DEFAULT 2 COMMENT '是否全屏',\n  `generate_id` int(11) NULL DEFAULT 0 COMMENT '生成id',\n  `generate_key` varchar(255) NULL DEFAULT NULL COMMENT '生成key',\n  `status` tinyint(1) NULL DEFAULT 1 COMMENT '状态',\n  `remark` varchar(255) NULL DEFAULT NULL,\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `idx_parent_id`(`parent_id`) USING BTREE,\n  INDEX `idx_slug`(`slug`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1000 COMMENT = '菜单权限表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_menu\n-- ----------------------------\nINSERT INTO `sa_system_menu` VALUES (1, 0, '仪表盘', 'Dashboard', NULL, 1, '/dashboard', NULL, NULL, 'ri:pie-chart-line', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (2, 1, '工作台', 'Console', NULL, 2, 'console', '/dashboard/console', NULL, 'ri:home-smile-2-line', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (3, 0, '系统管理', 'System', NULL, 1, '/system', NULL, NULL, 'ri:user-3-line', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (4, 3, '用户管理', 'User', NULL, 2, 'user', '/system/user', NULL, 'ri:user-line', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (5, 3, '部门管理', 'Dept', NULL, 2, 'dept', '/system/dept', NULL, 'ri:node-tree', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (6, 3, '角色管理', 'Role', NULL, 2, 'role', '/system/role', NULL, 'ri:admin-line', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (7, 3, '岗位管理', 'Post', '', 2, 'post', '/system/post', NULL, 'ri:signpost-line', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (8, 3, '菜单管理', 'Menu', NULL, 2, 'menu', '/system/menu', NULL, 'ri:menu-line', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (10, 0, '运维管理', 'Safeguard', NULL, 1, '/safeguard', '', NULL, 'ri:shield-check-line', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (11, 10, '缓存管理', 'Cache', '', 2, 'cache', '/safeguard/cache', NULL, 'ri:keyboard-box-line', 80, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (12, 10, '数据字典', 'Dict', NULL, 2, 'dict', '/safeguard/dict', NULL, 'ri:database-2-line', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (13, 10, '附件管理', 'Attachment', '', 2, 'attachment', '/safeguard/attachment', NULL, 'ri:file-cloud-line', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (14, 10, '数据表维护', 'Database', '', 2, 'database', '/safeguard/database', NULL, 'ri:database-line', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (15, 10, '登录日志', 'LoginLog', '', 2, 'login-log', '/safeguard/login-log', NULL, 'ri:login-circle-line', 50, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (16, 10, '操作日志', 'OperLog', '', 2, 'oper-log', '/safeguard/oper-log', NULL, 'ri:shield-keyhole-line', 50, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (17, 10, '邮件日志', 'EmailLog', '', 2, 'email-log', '/safeguard/email-log', NULL, 'ri:mail-line', 50, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (18, 3, '系统设置', 'Config', NULL, 2, 'config', '/system/config', NULL, 'ri:settings-4-line', 100, NULL, 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (19, 0, '官方文档', 'Document', '', 4, '', '', NULL, 'ri:file-copy-2-fill', 90, 'https://saithink.top', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (20, 4, '数据列表', '', 'core:user:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (21, 1, '个人中心', 'UserCenter', '', 2, 'user-center', '/dashboard/user-center/index', NULL, 'ri:user-2-line', 100, '', 2, 2, 1, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (22, 4, '添加', '', 'core:user:save', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (23, 4, '修改', '', 'core:user:update', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (24, 4, '读取', '', 'core:user:read', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (25, 4, '删除', '', 'core:user:destroy', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (26, 4, '重置密码', '', 'core:user:password', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (27, 4, '清理缓存', '', 'core:user:cache', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (28, 4, '设置工作台', '', 'core:user:home', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (29, 5, '数据列表', '', 'core:dept:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (30, 5, '添加', '', 'core:dept:save', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (31, 5, '修改', '', 'core:dept:update', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (32, 5, '读取', '', 'core:dept:read', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (33, 5, '删除', '', 'core:dept:destroy', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (34, 6, '添加', '', 'core:role:save', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (35, 6, '数据列表', '', 'core:role:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (36, 6, '修改', '', 'core:role:update', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (37, 6, '读取', '', 'core:role:read', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (38, 6, '删除', '', 'core:role:destroy', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (39, 6, '菜单权限', '', 'core:role:menu', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (41, 7, '数据列表', '', 'core:post:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (42, 7, '添加', '', 'core:post:save', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (43, 7, '修改', '', 'core:post:update', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (44, 7, '读取', '', 'core:post:read', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (45, 7, '删除', '', 'core:post:destroy', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (46, 7, '导入', '', 'core:post:import', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (47, 7, '导出', '', 'core:post:export', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (48, 8, '数据列表', '', 'core:menu:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (49, 8, '读取', '', 'core:menu:read', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (50, 8, '添加', '', 'core:menu:save', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (51, 8, '修改', '', 'core:menu:update', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (52, 8, '删除', '', 'core:menu:destroy', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (53, 18, '数据列表', '', 'core:config:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (54, 18, '管理', '', 'core:config:edit', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (55, 18, '修改', '', 'core:config:update', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (56, 12, '数据列表', '', 'core:dict:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (57, 12, '管理', '', 'core:dict:edit', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (58, 13, '数据列表', '', 'core:attachment:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (59, 13, '管理', '', 'core:attachment:edit', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (60, 14, '数据表列表', '', 'core:database:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (61, 14, '数据表维护', '', 'core:database:edit', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (62, 14, '回收站数据', '', 'core:recycle:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (63, 14, '回收站管理', '', 'core:recycle:edit', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (64, 15, '数据列表', '', 'core:logs:login', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (65, 15, '删除', '', 'core:logs:deleteLogin', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (66, 16, '数据列表', '', 'core:logs:Oper', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (67, 16, '删除', '', 'core:logs:deleteOper', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (68, 17, '数据列表', '', 'core:email:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (69, 17, '删除', '', 'core:email:destroy', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (70, 10, '服务监控', 'Server', '', 2, 'server', '/safeguard/server', NULL, 'ri:server-line', 90, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (71, 70, '数据列表', '', 'core:server:monitor', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (72, 11, '数据列表', '', 'core:server:cache', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (73, 11, '缓存清理', '', 'core:server:clear', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (74, 2, '登录数据统计', '', 'core:console:list', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (75, 0, '附加权限', 'Permission', '', 1, 'permission', '', NULL, 'ri:apps-2-ai-line', 100, '', 2, 2, 1, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (76, 75, '上传图片', '', 'core:system:uploadImage', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (77, 75, '上传文件', '', 'core:system:uploadFile', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (78, 75, '附件列表', '', 'core:system:resource', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (79, 75, '用户列表', '', 'core:system:user', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (80, 0, '工具', 'Tool', '', 1, '/tool', '', NULL, 'ri:tools-line', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (81, 80, '代码生成', 'Code', '', 2, 'code', '/tool/code', NULL, 'ri:code-s-slash-line', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (82, 80, '定时任务', 'Crontab', '', 2, 'crontab', '/tool/crontab', NULL, 'ri:time-line', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (83, 82, '数据列表', '', 'tool:crontab:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (84, 82, '管理', '', 'tool:crontab:edit', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (85, 82, '运行任务', '', 'tool:crontab:run', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (86, 81, '数据列表', '', 'tool:code:index', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (87, 81, '管理', '', 'tool:code:edit', 3, '', '', NULL, '', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\nINSERT INTO `sa_system_menu` VALUES (88, 0, '插件市场', 'Plugin', '', 2, '/plugin', '/plugin/saipackage/install/index', NULL, 'ri:apps-2-ai-line', 100, '', 2, 2, 2, 2, 2, 0, NULL, 1, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_system_oper_log\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_oper_log`;\nCREATE TABLE `sa_system_oper_log`  (\n  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `username` varchar(20) NULL DEFAULT NULL COMMENT '用户名',\n  `app` varchar(50) NULL DEFAULT NULL COMMENT '应用名称',\n  `method` varchar(20) NULL DEFAULT NULL COMMENT '请求方式',\n  `router` varchar(500) NULL DEFAULT NULL COMMENT '请求路由',\n  `service_name` varchar(30) NULL DEFAULT NULL COMMENT '业务名称',\n  `ip` varchar(45) NULL DEFAULT NULL COMMENT '请求IP地址',\n  `ip_location` varchar(255) NULL DEFAULT NULL COMMENT 'IP所属地',\n  `request_data` text NULL COMMENT '请求数据',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `username`(`username`) USING BTREE,\n  INDEX `idx_create_time`(`create_time`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '操作日志表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_oper_log\n-- ----------------------------\n\n-- ----------------------------\n-- Table structure for sa_system_post\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_post`;\nCREATE TABLE `sa_system_post`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `name` varchar(50) NULL DEFAULT NULL COMMENT '岗位名称',\n  `code` varchar(100) NULL DEFAULT NULL COMMENT '岗位代码',\n  `sort` smallint(5) UNSIGNED NULL DEFAULT 0 COMMENT '排序',\n  `status` smallint(6) NULL DEFAULT 1 COMMENT '状态 (1正常 2停用)',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 87 COMMENT = '岗位信息表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Table structure for sa_system_role\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_role`;\nCREATE TABLE `sa_system_role`  (\n  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,\n  `name` varchar(64) NOT NULL COMMENT '角色名称',\n  `code` varchar(64) NOT NULL COMMENT '角色标识(英文唯一)，如: hr_manager',\n  `level` int(11) NULL DEFAULT 1 COMMENT '角色级别(1-100)：用于行政控制，不可操作级别>=自己的角色',\n  `data_scope` tinyint(4) NULL DEFAULT 1 COMMENT '数据范围: 1全部, 2本部门及下属, 3本部门, 4仅本人, 5自定义',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `sort` int(11) NULL DEFAULT 100,\n  `status` tinyint(1) NULL DEFAULT 1 COMMENT '状态: 1启用, 0禁用',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  UNIQUE INDEX `uk_slug`(`code`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 17 COMMENT = '角色表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_role\n-- ----------------------------\nINSERT INTO `sa_system_role` VALUES (1, '超级管理员', 'super_admin', 100, 1, '系统维护者，拥有所有权限', 100, 1, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_system_role_dept\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_role_dept`;\nCREATE TABLE `sa_system_role_dept`  (\n  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,\n  `role_id` bigint(20) UNSIGNED NOT NULL,\n  `dept_id` bigint(20) UNSIGNED NOT NULL,\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `idx_role_id`(`role_id`) USING BTREE,\n  INDEX `idx_dept_id`(`dept_id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '角色-自定义数据权限关联' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_role_dept\n-- ----------------------------\n\n-- ----------------------------\n-- Table structure for sa_system_role_menu\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_role_menu`;\nCREATE TABLE `sa_system_role_menu`  (\n  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,\n  `role_id` bigint(20) UNSIGNED NOT NULL,\n  `menu_id` bigint(20) UNSIGNED NOT NULL,\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `idx_menu_id`(`menu_id`) USING BTREE,\n  INDEX `idx_role_id`(`role_id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '角色权限关联' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_role_menu\n-- ----------------------------\n\n-- ----------------------------\n-- Table structure for sa_system_user\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_user`;\nCREATE TABLE `sa_system_user`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,\n  `username` varchar(64) NOT NULL COMMENT '登录账号',\n  `password` varchar(255) NOT NULL COMMENT '加密密码',\n  `realname` varchar(64) NULL DEFAULT NULL COMMENT '真实姓名',\n  `gender` varchar(10) NULL DEFAULT NULL COMMENT '性别',\n  `avatar` varchar(255) NULL DEFAULT NULL COMMENT '头像',\n  `email` varchar(128) NULL DEFAULT NULL COMMENT '邮箱',\n  `phone` varchar(20) NULL DEFAULT NULL COMMENT '手机号',\n  `signed` varchar(255) NULL DEFAULT NULL COMMENT '个性签名',\n  `dashboard` varchar(255) NULL DEFAULT 'work' COMMENT '工作台',\n  `dept_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '主归属部门',\n  `is_super` tinyint(1) NULL DEFAULT 0 COMMENT '是否超级管理员: 1是(跳过权限检查), 0否',\n  `status` tinyint(1) NULL DEFAULT 1 COMMENT '状态: 1启用, 0禁用',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `login_time` timestamp(0) NULL DEFAULT NULL COMMENT '最后登录时间',\n  `login_ip` varchar(45) NULL DEFAULT NULL COMMENT '最后登录IP',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE,\n  UNIQUE INDEX `uk_username`(`username`) USING BTREE,\n  INDEX `idx_dept_id`(`dept_id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 110 COMMENT = '用户表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_user\n-- ----------------------------\nINSERT INTO `sa_system_user` VALUES (1, 'admin', '$2y$10$wnixh48uDnaW/6D9EygDd.OHJK0vQY/4nHaTjMKBCVDBP2NiTatqS', '祭道之上', '2', 'https://image.saithink.top/saiadmin/avatar.jpg', 'saiadmin@admin.com', '15888888888', 'SaiAdmin是兼具设计美学与高效开发的后台系统!', 'statistics', 1, 1, 1, NULL, NULL, NULL, 1, 1, '2026-01-01 00:00:00', '2026-01-01 00:00:00', NULL);\n\n-- ----------------------------\n-- Table structure for sa_system_user_post\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_user_post`;\nCREATE TABLE `sa_system_user_post`  (\n  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `user_id` bigint(20) UNSIGNED NOT NULL COMMENT '用户主键',\n  `post_id` bigint(20) UNSIGNED NOT NULL COMMENT '岗位主键',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `idx_user_id`(`user_id`) USING BTREE,\n  INDEX `idx_post_id`(`post_id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '用户与岗位关联表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_user_post\n-- ----------------------------\n\n-- ----------------------------\n-- Table structure for sa_system_user_role\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_system_user_role`;\nCREATE TABLE `sa_system_user_role`  (\n  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,\n  `user_id` bigint(20) UNSIGNED NOT NULL,\n  `role_id` bigint(20) UNSIGNED NOT NULL,\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `idx_role_id`(`role_id`) USING BTREE,\n  INDEX `idx_user_id`(`user_id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 55 COMMENT = '用户角色关联' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_system_user_role\n-- ----------------------------\nINSERT INTO `sa_system_user_role` VALUES (1, 1, 1);\n\n-- ----------------------------\n-- Table structure for sa_tool_crontab\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_tool_crontab`;\nCREATE TABLE `sa_tool_crontab`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `name` varchar(100) NULL DEFAULT NULL COMMENT '任务名称',\n  `type` smallint(6) NULL DEFAULT 4 COMMENT '任务类型',\n  `target` varchar(500) NULL DEFAULT NULL COMMENT '调用任务字符串',\n  `parameter` varchar(1000) NULL DEFAULT NULL COMMENT '调用任务参数',\n  `task_style` tinyint(1) NULL DEFAULT NULL COMMENT '执行类型',\n  `rule` varchar(32) NULL DEFAULT NULL COMMENT '任务执行表达式',\n  `singleton` smallint(6) NULL DEFAULT 1 COMMENT '是否单次执行 (1 是 2 不是)',\n  `status` smallint(6) NULL DEFAULT 1 COMMENT '状态 (1正常 2停用)',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '定时任务信息表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Table structure for sa_tool_crontab_log\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_tool_crontab_log`;\nCREATE TABLE `sa_tool_crontab_log`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `crontab_id` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '任务ID',\n  `name` varchar(255) NULL DEFAULT NULL COMMENT '任务名称',\n  `target` varchar(500) NULL DEFAULT NULL COMMENT '任务调用目标字符串',\n  `parameter` varchar(1000) NULL DEFAULT NULL COMMENT '任务调用参数',\n  `exception_info` varchar(2000) NULL DEFAULT NULL COMMENT '异常信息',\n  `status` smallint(6) NULL DEFAULT 1 COMMENT '执行状态 (1成功 2失败)',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '定时任务执行日志表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_tool_crontab_log\n-- ----------------------------\n\n-- ----------------------------\n-- Table structure for sa_tool_generate_columns\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_tool_generate_columns`;\nCREATE TABLE `sa_tool_generate_columns`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `table_id` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '所属表ID',\n  `column_name` varchar(200) NULL DEFAULT NULL COMMENT '字段名称',\n  `column_comment` varchar(255) NULL DEFAULT NULL COMMENT '字段注释',\n  `column_type` varchar(50) NULL DEFAULT NULL COMMENT '字段类型',\n  `default_value` varchar(50) NULL DEFAULT NULL COMMENT '默认值',\n  `is_pk` smallint(6) NULL DEFAULT 1 COMMENT '1 非主键 2 主键',\n  `is_required` smallint(6) NULL DEFAULT 1 COMMENT '1 非必填 2 必填',\n  `is_insert` smallint(6) NULL DEFAULT 1 COMMENT '1 非插入字段 2 插入字段',\n  `is_edit` smallint(6) NULL DEFAULT 1 COMMENT '1 非编辑字段 2 编辑字段',\n  `is_list` smallint(6) NULL DEFAULT 1 COMMENT '1 非列表显示字段 2 列表显示字段',\n  `is_query` smallint(6) NULL DEFAULT 1 COMMENT '1 非查询字段 2 查询字段',\n  `is_sort` smallint(6) NULL DEFAULT 1 COMMENT '1 非排序 2 排序',\n  `query_type` varchar(100) NULL DEFAULT 'eq' COMMENT '查询方式 eq 等于, neq 不等于, gt 大于, lt 小于, like 范围',\n  `view_type` varchar(100) NULL DEFAULT 'text' COMMENT '页面控件,text, textarea, password, select, checkbox, radio, date, upload, ma-upload(封装的上传控件)',\n  `dict_type` varchar(200) NULL DEFAULT NULL COMMENT '字典类型',\n  `allow_roles` varchar(255) NULL DEFAULT NULL COMMENT '允许查看该字段的角色',\n  `options` varchar(1000) NULL DEFAULT NULL COMMENT '字段其他设置',\n  `sort` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT '排序',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '代码生成业务字段表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_tool_generate_columns\n-- ----------------------------\n\n-- ----------------------------\n-- Table structure for sa_tool_generate_tables\n-- ----------------------------\nDROP TABLE IF EXISTS `sa_tool_generate_tables`;\nCREATE TABLE `sa_tool_generate_tables`  (\n  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `table_name` varchar(200) NULL DEFAULT NULL COMMENT '表名称',\n  `table_comment` varchar(500) NULL DEFAULT NULL COMMENT '表注释',\n  `stub` varchar(50) NULL DEFAULT NULL COMMENT 'stub类型',\n  `template` varchar(50) NULL DEFAULT NULL COMMENT '模板名称',\n  `namespace` varchar(255) NULL DEFAULT NULL COMMENT '命名空间',\n  `package_name` varchar(100) NULL DEFAULT NULL COMMENT '控制器包名',\n  `business_name` varchar(50) NULL DEFAULT NULL COMMENT '业务名称',\n  `class_name` varchar(50) NULL DEFAULT NULL COMMENT '类名称',\n  `menu_name` varchar(100) NULL DEFAULT NULL COMMENT '生成菜单名',\n  `belong_menu_id` int(11) NULL DEFAULT NULL COMMENT '所属菜单',\n  `tpl_category` varchar(100) NULL DEFAULT NULL COMMENT '生成类型,single 单表CRUD,tree 树表CRUD,parent_sub父子表CRUD',\n  `generate_type` smallint(6) NULL DEFAULT 1 COMMENT '1 压缩包下载 2 生成到模块',\n  `generate_path` varchar(100) NULL DEFAULT 'saiadmin-artd' COMMENT '前端根目录',\n  `generate_model` smallint(6) NULL DEFAULT 1 COMMENT '1 软删除 2 非软删除',\n  `generate_menus` varchar(255) NULL DEFAULT NULL COMMENT '生成菜单列表',\n  `build_menu` smallint(6) NULL DEFAULT 1 COMMENT '是否构建菜单',\n  `component_type` smallint(6) NULL DEFAULT 1 COMMENT '组件显示方式',\n  `options` varchar(1500) NULL DEFAULT NULL COMMENT '其他业务选项',\n  `form_width` int(11) NULL DEFAULT 800 COMMENT '表单宽度',\n  `is_full` tinyint(1) NULL DEFAULT 1 COMMENT '是否全屏',\n  `remark` varchar(255) NULL DEFAULT NULL COMMENT '备注',\n  `source` varchar(255) NULL DEFAULT NULL COMMENT '数据源',\n  `created_by` int(11) NULL DEFAULT NULL COMMENT '创建者',\n  `updated_by` int(11) NULL DEFAULT NULL COMMENT '更新者',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',\n  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '代码生成业务表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of sa_tool_generate_tables\n-- ----------------------------\n\nSET FOREIGN_KEY_CHECKS = 1;\n"
  },
  {
    "path": "src/plugin/saiadmin/exception/ApiException.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\exception;\n\nuse Webman\\Http\\Request;\nuse Webman\\Http\\Response;\nuse support\\exception\\BusinessException;\n\n/**\n * 常规操作异常-只返回json数据,不记录异常日志\n */\nclass ApiException extends BusinessException\n{\n    public function render(Request $request): ?Response\n    {\n        return json(['code' => $this->getCode() ?: 500, 'message' => $this->getMessage()]);\n    }\n}"
  },
  {
    "path": "src/plugin/saiadmin/exception/SystemException.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\exception;\n\nuse Throwable;\n\n/**\n * 系统接口错误-返回json数据,并且记录异常日志\n */\nclass SystemException extends \\RuntimeException\n{\n    public function __construct($message, $code = 400, Throwable $previous = null)\n    {\n        parent::__construct($message, $code, $previous);\n    }\n}"
  },
  {
    "path": "src/plugin/saiadmin/process/Task.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\process;\n\nuse plugin\\saiadmin\\app\\logic\\tool\\CrontabLogic;\nuse Webman\\Channel\\Client;\nuse Workerman\\Crontab\\Crontab;\n\nclass Task\n{\n    protected $logic; //login对象\n    public $crontabIds = []; //定时任务表主键id => Crontab对象id\n\n    public function __construct()\n    {\n        $dbName = env('DB_NAME');\n        if (!empty($dbName)) {\n            $this->logic = new CrontabLogic();\n            // 连接webman channel服务\n            Client::connect();\n            // 订阅某个自定义事件并注册回调，收到事件后会自动触发此回调\n            Client::on('crontab', function ($data) {\n                $this->reload($data);\n            });\n        }\n    }\n    public function onWorkerStart()\n    {\n        $dbName = env('DB_NAME');\n        if (!empty($dbName)) {\n            $this->initStart();\n        }\n    }\n\n    public function initStart()\n    {\n        $logic = new CrontabLogic();\n        $taskList = $logic->getAll($logic->search(['status' => 1]));\n        foreach ($taskList as $item) {\n            $crontab = new Crontab($item['rule'], function () use ($item) {\n                $this->logic->run($item['id']);\n            });\n            $this->crontabIds[intval($item['id'])] = $crontab->getId(); //存储定时任务表主键id => Crontab对象id\n            echo PHP_EOL . date('Y-m-d H:i:s') . \" => 定时任务[\" . $item['id'] . \"][\" . $item['name'] . \"]:启动成功\" . PHP_EOL;\n        }\n    }\n\n    public function reload($data)\n    {\n        $id = intval($data['args'] ?? 0); //定时任务表主键id\n        if (isset($this->crontabIds[$id])) {\n            Crontab::remove($this->crontabIds[$id]);\n            unset($this->crontabIds[$id]); //删除定时任务表主键id => Crontab对象id\n            echo PHP_EOL . date('Y-m-d H:i:s') . \" => 定时任务[\" . $id . \"]:移除成功\" . PHP_EOL;\n        }\n        $item = $this->logic->read($id);// 查询定时任务表数据\n        if ($item && $item['status'] == 1) {\n            $crontab = new Crontab($item['rule'], function () use ($item) {\n                $this->logic->run($item['id']);\n            });\n            $this->crontabIds[$id] = $crontab->getId(); //存储定时任务表主键id => Crontab对象id\n            echo PHP_EOL . date('Y-m-d H:i:s') . \" => 定时任务[\" . $item['id'] . \"][\" . $item['name'] . \"]:启动成功\" . PHP_EOL;\n        }\n    }\n}"
  },
  {
    "path": "src/plugin/saiadmin/process/Test.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\process;\n\nclass Test\n{\n    public function run($args): void\n    {\n        echo '任务[Test]调用：' . date('Y-m-d H:i:s') . \"\\n\";\n    }\n}"
  },
  {
    "path": "src/plugin/saiadmin/service/EmailService.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\service;\n\nuse PHPMailer\\PHPMailer\\Exception;\nuse PHPMailer\\PHPMailer\\PHPMailer;\nuse plugin\\saiadmin\\app\\logic\\system\\SystemConfigLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\utils\\Arr;\n\n/**\n * 邮件服务类\n */\nclass EmailService\n{\n    /**\n     * 读取配置\n     * @return array\n     */\n    public static function getConfig(): array\n    {\n        $logic = new SystemConfigLogic();\n        $config = $logic->getGroup('email_config');\n        if (!$config) {\n            throw new ApiException('未设置邮件配置');\n        }\n        return $config;\n    }\n\n    /**\n     * Get Mailer\n     * @return PHPMailer\n     */\n    public static function getMailer(): PHPMailer\n    {\n        if (!class_exists(PHPMailer::class)) {\n            throw new ApiException('请执行 composer require phpmailer/phpmailer 并重启');\n        }\n        $config = static::getConfig();\n        $mailer = new PHPMailer();\n        $mailer->SMTPDebug = intval(Arr::getConfigValue($config,'SMTPDebug'));\n        $mailer->isSMTP();\n        $mailer->Host = Arr::getConfigValue($config,'Host');\n        $mailer->SMTPAuth = true;\n        $mailer->CharSet = Arr::getConfigValue($config,'CharSet');\n        $mailer->Username = Arr::getConfigValue($config,'Username');\n        $mailer->Password = Arr::getConfigValue($config,'Password');\n        $mailer->SMTPSecure = Arr::getConfigValue($config,'SMTPSecure');\n        $mailer->Port = Arr::getConfigValue($config,'Port');\n        return $mailer;\n    }\n\n    /**\n     * 发送邮件\n     * @param $from\n     * @param $to\n     * @param $subject\n     * @param $content\n     * @return string\n     * @throws Exception\n     */\n    public static function send($from, $to, $subject, $content): string\n    {\n        $mailer = static::getMailer();\n        call_user_func_array([$mailer, 'setFrom'], (array)$from);\n        call_user_func_array([$mailer, 'addAddress'], (array)$to);\n        $mailer->Subject = $subject;\n        $mailer->isHTML(true);\n        $mailer->Body = $content;\n        $mailer->send();\n        return $mailer->ErrorInfo;\n    }\n\n    /**\n     * 按照模版发送\n     * @param string|array $to\n     * @param $subject\n     * @param $content\n     * @param array $templateData\n     * @return string\n     * @throws Exception\n     */\n    public static function sendByTemplate($to, $subject, $content, array $templateData = []): string\n    {\n        if ($templateData) {\n            $search = [];\n            foreach ($templateData as $key => $value) {\n                $search[] = '{' . $key . '}';\n            }\n            $content = str_replace($search, array_values($templateData), $content);\n        }\n        $config = static::getConfig();\n        return static::send([Arr::getConfigValue($config,'From'), Arr::getConfigValue($config,'FromName')], $to, $subject, $content);\n    }\n    \n}"
  },
  {
    "path": "src/plugin/saiadmin/service/OpenSpoutWriter.php",
    "content": "<?php\nnamespace plugin\\saiadmin\\service;\n\nuse OpenSpout\\Common\\Entity\\Style\\Border;\nuse OpenSpout\\Common\\Entity\\Style\\BorderPart;\nuse OpenSpout\\Writer\\XLSX\\Writer;\nuse OpenSpout\\Common\\Entity\\Row;\nuse OpenSpout\\Common\\Entity\\Style\\Style;\n\n/**\n * Excel写入类\n * OpenSpout\n */\nclass OpenSpoutWriter\n{\n    /**\n     * 操作实例\n     * @var Writer\n     */\n    protected $instance;\n\n    /**\n     * 文件路径\n     * @var string\n     */\n    protected $filepath;\n\n    /**\n     * 初始化\n     * @param string $fileName 文件名称\n     */\n    public function __construct(string $fileName)\n    {\n        $this->filepath = $this->getFileName($fileName);\n        $this->instance = new Writer();\n        $this->instance->openToFile($this->filepath);\n    }\n\n    /**\n     * 获取完整的文件路径\n     * @param string $fileName\n     * @return string\n     */\n    public function getFileName(string $fileName): string\n    {\n        $path = config('plugin.saiadmin.saithink.export_path',base_path() . '/plugin/saiadmin/public/export/');\n        @mkdir($path, 0777, true);\n        return $path . $fileName;\n    }\n\n    /**\n     * 设置表格宽度\n     * @param array $width 宽度数组\n     * @return void\n     */\n    public function setWidth(array $width = [])\n    {\n        if (empty($width)) {\n            return;\n        }\n        $sheet = $this->instance->getCurrentSheet();\n        foreach ($width as $key => $value) {\n            $sheet->setColumnWidth($value, $key + 1);\n        }\n    }\n\n    /**\n     * 设置表头\n     * @param array $header 表头数组\n     * @param $style\n     * @return void\n     */\n    public function setHeader(array $header = [], $style = null): void\n    {\n        if (empty($style)) {\n            $border = new Border(\n                new BorderPart(\"top\", \"black\", \"thin\"),\n                new BorderPart(\"right\", \"black\", \"thin\"),\n                new BorderPart(\"bottom\", \"black\", \"thin\"),\n                new BorderPart(\"left\", \"black\", \"thin\"),\n            );\n            $style = new Style();\n            $style->setFontBold();\n            $style->setCellAlignment(\"center\");\n            $style->setBorder($border);\n        }\n        $rowFromValues = Row::fromValues($header, $style);\n        $this->instance->addRow($rowFromValues);\n    }\n\n    /**\n     * 设置数据\n     * @param array $data 数据数组\n     * @param $style\n     * @return void\n     */\n    public function setData(array $data = [], $style = null, array $filter = []): void\n    {\n        if (empty($style)) {\n            $border = new Border(\n                new BorderPart(\"top\", \"black\", \"thin\"),\n                new BorderPart(\"right\", \"black\", \"thin\"),\n                new BorderPart(\"bottom\", \"black\", \"thin\"),\n                new BorderPart(\"left\", \"black\", \"thin\"),\n            );\n            $style = new Style();\n            $style->setCellAlignment(\"center\");\n            $style->setBorder($border);\n        }\n        foreach($data as $row) {\n            if (!empty($filter)) {\n                foreach ($filter as $key => $value) {\n                    foreach ($value  as $item) {\n                        if ($item['value'] == $row[$key]) {\n                            $row[$key] = $item['label'];\n                            break;\n                        }\n                    }\n                }\n            }\n            $rowFromValues = Row::fromValues($row, $style);\n            $this->instance->addRow($rowFromValues);\n        }\n    }\n\n    /**\n     * 获取文件\n     * @return string\n     */\n    public function returnFile(): string\n    {\n        $this->instance->close();\n        return $this->filepath;\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/service/Permission.php",
    "content": "<?php\n\nnamespace plugin\\saiadmin\\service;\n\n/**\n * 权限注解\n */\n#[\\Attribute(\\Attribute::TARGET_METHOD | \\Attribute::TARGET_CLASS)]\nclass Permission\n{\n    /**\n     * 权限标题/名称\n     */\n    public string $title;\n\n    /**\n     * 权限标识（唯一，格式如：module:controller:action）\n     */\n    public ?string $slug = null;\n\n    /**\n     * 构造函数 #[Permission(title:'标题', slug:'标识')]\n     * @param string|null $title\n     * @param string|null $slug\n     */\n    public function __construct(\n        ?string $title = null,\n        ?string $slug = null,\n    )\n    {\n        $this->title = $title ?? '';\n        $this->slug = $slug;\n    }\n\n    /**\n     * 获取权限标题\n     */\n    public function getTitle(): string\n    {\n        return $this->title;\n    }\n\n    /**\n     * 获取权限标识\n     */\n    public function getSlug(): ?string\n    {\n        return $this->slug;\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/service/storage/ChunkUploadService.php",
    "content": "<?php\nnamespace plugin\\saiadmin\\service\\storage;\n\nuse plugin\\saiadmin\\app\\logic\\system\\SystemConfigLogic;\nuse plugin\\saiadmin\\app\\model\\system\\SystemAttachment;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\utils\\Arr;\n\n/**\n * 切片上传服务\n */\nclass ChunkUploadService\n{\n    /**\n     * 基础配置\n     */\n    protected $config;\n\n    protected $path;\n\n    protected $folder = \"chunk\";\n\n    /**\n     * 初始化切片上传服务\n     */\n    public function __construct($folder = \"chunk\")\n    {\n        $logic = new SystemConfigLogic();\n        $this->folder = $folder;\n        $this->config = $logic->getGroup('upload_config');\n        $this->path = $this->checkPath();\n    }\n\n    /**\n     * 检查并创建上传路径\n     * @return string\n     */\n    public function checkPath(): string\n    {\n        $root = Arr::getConfigValue($this->config, 'local_root');\n        $path = base_path() . DIRECTORY_SEPARATOR . $root . $this->folder . DIRECTORY_SEPARATOR;\n        if (!is_dir($path)) {\n            mkdir($path, 0777, true);\n        }\n        return $path;\n    }\n\n    /**\n     * 检查切片文件上传状态\n     * @param $data\n     * @return array\n     */\n    public function checkChunk($data): array\n    {\n        $allow_file = Arr::getConfigValue($this->config, 'upload_allow_file');\n        if (!in_array($data['ext'], explode(',', $allow_file))) {\n            throw new ApiException('不支持该格式的文件上传');\n        }\n        // 检查已经上传的分片文件\n        for ($i = 0; $i < $data['total']; ++$i) {\n            $chunkFile = $this->path . \"{$data['hash']}_{$data['total']}_{$i}.chunk\";\n            if (!file_exists($chunkFile)) {\n                if ($i == 0) {\n                    return $this->uploadChunk($data);\n                } else {\n                    return ['chunk' => $i, 'status' => 'resume'];\n                }\n            }\n        }\n        // 分片文件已经全部上传\n        return ['chunk' => $i, 'status' => 'success'];\n    }\n\n    /**\n     * 上传切片\n     * @param $data\n     * @return array\n     */\n    public function uploadChunk($data): array\n    {\n        $allow_file = Arr::getConfigValue($this->config, 'upload_allow_file');\n        if (!in_array($data['ext'], explode(',', $allow_file))) {\n            throw new ApiException('不支持该格式的文件上传');\n        }\n        $request = request();\n        if (!$request) {\n            throw new ApiException('切片上传服务必须在 HTTP 请求环境下调用');\n        }\n        $uploadFile = current($request->file());\n        $chunkName = $this->path . \"{$data['hash']}_{$data['total']}_{$data['index']}.chunk\";\n        $uploadFile->move($chunkName);\n        if (($data['index'] + 1) == $data['total']) {\n            return $this->mergeChunk($data);\n        }\n        return ['chunk' => $data['index'], 'status' => 'success'];\n    }\n\n    /**\n     * 合并切片文件\n     * @param $data\n     * @return array\n     */\n    public function mergeChunk($data): array\n    {\n        $filePath = $this->path . $data['hash'] . '.' . $data['ext'];\n        $fileHandle = fopen($filePath, 'w');\n        for ($i = 0; $i < $data['total']; ++$i) {\n            $chunkFile = $this->path . \"{$data['hash']}_{$data['total']}_{$i}.chunk\";\n            if (!file_exists($chunkFile)) {\n                throw new ApiException('切片文件查找失败，请重新上传');\n            }\n            fwrite($fileHandle, file_get_contents($chunkFile));\n            unlink($chunkFile);\n        }\n\n        $domain = Arr::getConfigValue($this->config, 'local_domain');\n        $uri = Arr::getConfigValue($this->config, 'local_uri');\n        $baseUrl = $domain . $uri . $this->folder . '/';\n\n        $save_path = Arr::getConfigValue($this->config, 'local_root') . $this->folder . '/';\n        $object_name = $data['hash'] . '.' . $data['ext'];\n\n        $info['storage_mode'] = 1;\n        $info['category_id'] = 1;\n        $info['origin_name'] = $data['name'];\n        $info['object_name'] = $object_name;\n        $info['hash'] = $data['hash'];\n        $info['mime_type'] = $data['type'];\n        $info['storage_path'] = $save_path . $object_name;\n        $info['suffix'] = $data['ext'];\n        $info['size_byte'] = $data['size'];\n        $info['size_info'] = formatBytes($data['size']);\n        $info['url'] = $baseUrl . $object_name;\n        SystemAttachment::create($info);\n        return $info;\n    }\n}"
  },
  {
    "path": "src/plugin/saiadmin/service/storage/UploadService.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\service\\storage;\n\nuse plugin\\saiadmin\\app\\logic\\system\\SystemConfigLogic;\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\utils\\Arr;\n\n/**\n * 文件上传服务\n * @method static array uploadFile(array $config = [])  上传文件\n * @method static array uploadBase64(string $base64, string $extension = 'png') 上传Base64文件\n * @method static array uploadServerFile(string $file_path)  上传服务端文件\n */\nclass UploadService\n{\n    /**\n     * @desc 存储磁盘\n     * @param int $type\n     * @param string $upload\n     * @param bool $_is_file_upload\n     * @return mixed\n     */\n    public static function disk(int $type = 1, string $upload = 'image', bool $_is_file_upload = true)\n    {\n        $logic = new SystemConfigLogic();\n        $uploadConfig = $logic->getGroup('upload_config');\n\n        $file = current(request()->file());\n        $ext = $file->getUploadExtension() ?: null;\n        $file_size = $file->getSize();\n        if ($file_size > Arr::getConfigValue($uploadConfig, 'upload_size')) {\n            throw new ApiException('文件大小超过限制');\n        }\n        $allow_file = Arr::getConfigValue($uploadConfig, 'upload_allow_file');\n        $allow_image = Arr::getConfigValue($uploadConfig, 'upload_allow_image');\n        if ($upload == 'image') {\n            if (!in_array($ext, explode(',', $allow_image))) {\n                throw new ApiException('不支持该格式的文件上传');\n            }\n        } else {\n            if (!in_array($ext, explode(',', $allow_file))) {\n                throw new ApiException('不支持该格式的文件上传');\n            }\n        }\n        switch ($type) {\n            case 1:\n                // 本地\n                $config = [\n                    'adapter' => \\Tinywan\\Storage\\Adapter\\LocalAdapter::class,\n                    'root' => Arr::getConfigValue($uploadConfig, 'local_root'),\n                    'dirname' => function () {\n                        return date('Ymd');\n                    },\n                    'domain' => Arr::getConfigValue($uploadConfig, 'local_domain'),\n                    'uri' => Arr::getConfigValue($uploadConfig, 'local_uri'),\n                    'algo' => 'sha1',\n                ];\n                break;\n            case 2:\n                // 阿里云\n                $config = [\n                    'adapter' => \\Tinywan\\Storage\\Adapter\\OssAdapter::class,\n                    'accessKeyId' => Arr::getConfigValue($uploadConfig, 'oss_accessKeyId'),\n                    'accessKeySecret' => Arr::getConfigValue($uploadConfig, 'oss_accessKeySecret'),\n                    'bucket' => Arr::getConfigValue($uploadConfig, 'oss_bucket'),\n                    'dirname' => Arr::getConfigValue($uploadConfig, 'oss_dirname'),\n                    'domain' => Arr::getConfigValue($uploadConfig, 'oss_domain'),\n                    'endpoint' => Arr::getConfigValue($uploadConfig, 'oss_endpoint'),\n                    'algo' => 'sha1',\n                ];\n                break;\n            case 3:\n                // 七牛\n                $config = [\n                    'adapter' => \\Tinywan\\Storage\\Adapter\\QiniuAdapter::class,\n                    'accessKey' => Arr::getConfigValue($uploadConfig, 'qiniu_accessKey'),\n                    'secretKey' => Arr::getConfigValue($uploadConfig, 'qiniu_secretKey'),\n                    'bucket' => Arr::getConfigValue($uploadConfig, 'qiniu_bucket'),\n                    'dirname' => Arr::getConfigValue($uploadConfig, 'qiniu_dirname'),\n                    'domain' => Arr::getConfigValue($uploadConfig, 'qiniu_domain'),\n                ];\n                break;\n            case 4:\n                // 腾讯云\n                $config = [\n                    'adapter' => \\Tinywan\\Storage\\Adapter\\CosAdapter::class,\n                    'secretId' => Arr::getConfigValue($uploadConfig, 'cos_secretId'),\n                    'secretKey' => Arr::getConfigValue($uploadConfig, 'cos_secretKey'),\n                    'bucket' => Arr::getConfigValue($uploadConfig, 'cos_bucket'),\n                    'dirname' => Arr::getConfigValue($uploadConfig, 'cos_dirname'),\n                    'domain' => Arr::getConfigValue($uploadConfig, 'cos_domain'),\n                    'region' => Arr::getConfigValue($uploadConfig, 'cos_region'),\n                ];\n                break;\n            case 5:\n                // s3 亚马逊\n                $config = [\n                    'adapter' => \\Tinywan\\Storage\\Adapter\\S3Adapter::class,\n                    'key' => Arr::getConfigValue($uploadConfig, 's3_key'),\n                    'secret' => Arr::getConfigValue($uploadConfig, 's3_secret'),\n                    'bucket' => Arr::getConfigValue($uploadConfig, 's3_bucket'),\n                    'dirname' => Arr::getConfigValue($uploadConfig, 's3_dirname'),\n                    'domain' => Arr::getConfigValue($uploadConfig, 's3_domain'),\n                    'region' => Arr::getConfigValue($uploadConfig, 's3_region'),\n                    'version' => Arr::getConfigValue($uploadConfig, 's3_version'),\n                    // 'use_path_style_endpoint' => Arr::getConfigValue($uploadConfig,'s3_use_path_style_endpoint'),\n                    'use_path_style_endpoint' => filter_var(Arr::getConfigValue($uploadConfig, 's3_use_path_style_endpoint'), FILTER_VALIDATE_BOOLEAN),\n                    'endpoint' => Arr::getConfigValue($uploadConfig, 's3_endpoint'),\n                    'acl' => Arr::getConfigValue($uploadConfig, 's3_acl'),\n                ];\n                break;\n            default:\n                throw new ApiException('该上传模式不存在');\n        }\n        return new $config['adapter'](array_merge(\n            $config,\n            ['_is_file_upload' => $_is_file_upload]\n        ));\n    }\n\n    /**\n     * @param $name\n     * @param $arguments\n     * @return mixed\n     * @author Tinywan(ShaoBo Wan)\n     */\n    public static function __callStatic($name, $arguments)\n    {\n        return static::disk()->{$name}(...$arguments);\n    }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/utils/Arr.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\utils;\n\n/**\n * Array操作类\n * Class Arr\n */\nclass Arr\n{\n    /**\n     * 获取数组中指定的列\n     * @param array $source\n     * @param string $column\n     * @return array\n     */\n    public static function getArrayColumn($source, $column): array\n    {\n        $columnArr = [];\n        foreach ($source as $item) {\n            $columnArr[] = $item[$column];\n        }\n        return $columnArr;\n    }\n\n    /**\n     * 批量获取数组中指定的列\n     * @param array $source\n     * @param array $column\n     * @return array\n     */\n    public static function getArrayColumns($source, $columns): array\n    {\n        $columnArr = [];\n        foreach ($source as $item) {\n            $tempArr = [];\n            foreach ($columns as $key) {\n                $temp = explode('.', $key);\n                if (count($temp) > 1) {\n                    $tempArr[$key] = $item[$temp[0]][$temp[1]];\n                } else {\n                    $tempArr[$key] = $item[$key];\n                }\n            }\n            $columnArr[] = $tempArr;\n        }\n        return $columnArr;\n    }\n\n    /**\n     * 把二维数组中某列设置为key返回\n     * @param array $array 输入数组\n     * @param string $field 要作为键的字段名\n     * @param bool $unique 要做键的字段是否唯一(该字段与记录是否一一对应)\n     * @return array\n     */\n    public static function fieldAsKey($array, $field, $unique = false) {\n        $result = [];\n        foreach ($array as $item) {\n            if (isset($item[$field])) {\n                if (!$unique && isset($result[$item[$field]])) {\n                    $unique = true;\n                    $result[$item[$field]] = [($result[$item[$field]])];\n                    $result[$item[$field]][] = $item;\n                } elseif ($unique) {\n                    $result[$item[$field]][] = $item;\n                } else {\n                    $result[$item[$field]] = $item;\n                }\n            }\n        }\n        return $result;\n    }\n\n    /**\n     * 数组转字符串去重复\n     * @param array $data\n     * @return false|string[]\n     */\n    public static function unique(array $data)\n    {\n        return array_unique(explode(',', implode(',', $data)));\n    }\n\n    /**\n     * 获取数组中去重复过后的指定key值\n     * @param array $list\n     * @param string $key\n     * @return array\n     */\n    public static function getUniqueKey(array $list, string $key)\n    {\n        return array_unique(array_column($list, $key));\n    }\n\n    /**\n     * 合并二维数组，并且指定key去重, 第一个覆盖第二个\n     * @param array $arr1\n     * @param array $arr2\n     * @param string $key\n     * @return array\n     */\n    public static function mergeArray(array $arr1, array $arr2, string $key)\n    {\n        $arr = array_merge($arr1,$arr2);\n        $tmp_arr = [];\n        foreach($arr as $k => $v) {\n            if(in_array($v[$key], $tmp_arr)) {\n                unset($arr[$k]);\n            } else {\n                $tmp_arr[] = $v[$key];\n            }\n        }\n        return $arr;\n    }\n\n    /**\n     * 相同键值的合并作为键生成新数组\n     * @param array $data\n     * @param string $field\n     * @return array\n     */\n    public static function groupSameField(array $data, string $field)\n    {\n        $result= [];\n        foreach ($data as $key => $info) {\n            $result[$info[$field]][] = $info;\n        }\n        return $result;\n    }\n\n    /**\n     * 生成无限级树算法\n     * @param  array  $arr                输入数组\n     * @param  number $pid                根级的pid\n     * @param  string $column_name        列名,id|pid父id的名字|children子数组的键名\n     * @return array  $ret\n     */\n    public static function makeTree($arr, $pid = 0, $column_name = 'id|pid|children') {\n        list($idname, $pidname, $cldname) = explode('|', $column_name);\n        $ret = array();\n        foreach ($arr as $k => $v) {\n            if ($v [$pidname] == $pid) {\n                $tmp = $arr [$k];\n                unset($arr [$k]);\n                $tmp [$cldname] = self::makeTree($arr, $v [$idname], $column_name);\n                $ret [] = $tmp;\n            }\n        }\n        return $ret;\n    }\n\n    /**\n     * 二位数组按某个键值排序\n     * @param array $arr\n     * @param string $key\n     * @param int $sort\n     * @return array\n     */\n    public static function sortArray($arr, $key, $sort = SORT_ASC)\n    {\n        array_multisort(array_column($arr,$key),$sort,$arr);\n        return $arr;\n    }\n\n    /**\n     * 数组中根据某一列中某个字段的值来查询这一列数据\n     * @param $array\n     * @param $column\n     * @param $value\n     * @return array\n     */\n    public static function getArrayByColumn($array, $column, $value): array\n    {\n        $result = [];\n        foreach ($array as $key => $item) {\n            if ($item[$column] == $value) {\n                $result = $item;\n            }\n        }\n        return $result;\n    }\n\n    /**\n     * 数组中根据key值获取value\n     * @param $array\n     * @param $key\n     * @return mixed|string\n     */\n    public static function getConfigValue($array, $key)\n    {\n        foreach ($array as $item) {\n            if ($item['key'] === $key) {\n                return $item['value'];\n            }\n        }\n        return '';\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/utils/Captcha.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\utils;\n\nuse support\\think\\Cache;\nuse Ramsey\\Uuid\\Uuid;\nuse Webman\\Captcha\\CaptchaBuilder;\nuse Webman\\Captcha\\PhraseBuilder;\nuse plugin\\saiadmin\\exception\\ApiException;\n\n/**\n * 验证码工具类\n */\nclass Captcha\n{\n    /**\n     * 图形验证码\n     * @return array\n     */\n    public static function imageCaptcha(): array\n    {\n        $builder = new PhraseBuilder(4, 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ');\n        $captcha = new CaptchaBuilder(null, $builder);\n        $captcha->setBackgroundColor(242, 243, 245);\n        $captcha->build(120, 36);\n\n        $uuid = Uuid::uuid4();\n        $key = $uuid->toString();\n        $mode = config('plugin.saiadmin.saithink.captcha.mode', 'session');\n        $expire = config('plugin.saiadmin.saithink.captcha.expire', 300);\n        $code = strtolower($captcha->getPhrase());\n        if ($mode === 'cache') {\n            try {\n                Cache::set($key, $code, $expire);\n            } catch (\\Exception $e) {\n                return [\n                    'result' => -1,\n                    'message' => '验证码获取失败，请检查缓存配置'\n                ];\n            }\n        } else {\n            $request = request();\n            if ($request) {\n                $request->session()->set($key, $code);\n            }\n        }\n        $img_content = $captcha->get();\n        return [\n            'result' => 1,\n            'uuid' => $key,\n            'image' => 'data:image/png;base64,' . base64_encode($img_content)\n        ];\n    }\n\n    /**\n     * 数字验证码\n     * @param string $key\n     * @param int $length\n     * @return array\n     */\n    public static function numberCaptcha(string $key, int $length = 4): array\n    {\n        $code = str_pad(rand(0, 999999), $length, '0', STR_PAD_LEFT);\n        $mode = config('plugin.saiadmin.saithink.captcha.mode', 'session');\n        $expire = config('plugin.saiadmin.saithink.captcha.expire', 300);\n        if ($mode === 'cache') {\n            try {\n                Cache::set($key, $code, $expire);\n            } catch (\\Exception $e) {\n                return [\n                    'result' => -1,\n                    'message' => '验证码获取失败，请检查缓存配置'\n                ];\n            }\n        } else {\n            $request = request();\n            if ($request) {\n                $request->session()->set($key, $code);\n            }\n        }\n        return [\n            'result' => 1,\n            'uuid' => $key,\n            'code' => $code,\n        ];\n    }\n\n    /**\n     * 验证码验证\n     * @param string $uuid\n     * @param string|int $captcha\n     * @return bool\n     */\n    public static function checkCaptcha(string $uuid, string|int $captcha): bool\n    {\n        $mode = config('plugin.saiadmin.saithink.captcha.mode', 'session');\n        if ($mode === 'cache') {\n            try {\n                $code = Cache::get($uuid);\n                Cache::delete($uuid);\n            } catch (\\Exception $e) {\n                throw new ApiException($e->getMessage());\n            }\n        } else {\n            try {\n                $code = session($uuid);\n                session()->forget($uuid);\n            } catch (\\Exception $e) {\n                throw new ApiException($e->getMessage());\n            }\n        }\n        if (strtolower($captcha) !== $code) {\n            return false;\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/utils/Helper.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\utils;\n\n/**\n * 帮助类\n */\nclass Helper\n{\n    /**\n     * 数据树形化\n     * @param array $data 数据\n     * @param string $childrenname 子数据名\n     * @param string $keyName 数据key名\n     * @param string $pidName 数据上级key名\n     * @return array\n     */\n    public static function makeTree(array $data, string $childrenname = 'children', string $keyName = 'id', string $pidName = 'parent_id')\n    {\n        $list = [];\n        foreach ($data as $value) {\n            $list[$value[$keyName]] = $value;\n        }\n        $tree = []; //格式化好的树\n        foreach ($list as $item) {\n            if (isset($list[$item[$pidName]])) {\n                $list[$item[$pidName]][$childrenname][] = &$list[$item[$keyName]];\n            } else {\n                $tree[] = &$list[$item[$keyName]];\n            }\n        }\n        return $tree;\n    }\n\n    /**\n     * 生成Arco菜单\n     * @param array $data 数据\n     * @param string $childrenname 子数据名\n     * @param string $keyName 数据key名\n     * @param string $pidName 数据上级key名\n     * @return array\n     */\n    public static function makeArcoMenus(array $data, string $childrenname = 'children', string $keyName = 'id', string $pidName = 'parent_id')\n    {\n        $list = [];\n        foreach ($data as $value) {\n            if ($value['type'] === 'M'){\n                $path = '/'.$value['route'];\n                $layout = isset($value['is_layout']) ? $value['is_layout'] : 1;\n                $temp = [\n                    $keyName => $value[$keyName],\n                    $pidName => $value[$pidName],\n                    'name' => $value['route'],\n                    'path' => $path,\n                    'component' => $value['component'],\n                    'redirect' => $value['redirect'],\n                    'meta' => [\n                        'title' => $value['name'],\n                        'type' => $value['type'],\n                        'hidden' => $value['is_hidden'] === 1,\n                        'layout' => $layout === 1,\n                        'hiddenBreadcrumb' => false,\n                        'icon' => $value['icon'],\n                    ],\n                ];\n                $list[$value[$keyName]] = $temp;\n            }\n\t\t\tif ($value['type'] === 'I' || $value['type'] === 'L'){\n                $temp = [\n                    $keyName => $value[$keyName],\n                    $pidName => $value[$pidName],\n                    'name' => $value['code'],\n                    'path' => $value['route'],\n                    'meta' => [\n                        'title' => $value['name'],\n                        'type' => $value['type'],\n                        'hidden' => $value['is_hidden'] === 1,\n                        'hiddenBreadcrumb' => false,\n                        'icon' => $value['icon'],\n                    ],\n                ];\n                $list[$value[$keyName]] = $temp;\n            }\n        }\n        $tree = []; //格式化好的树\n        foreach ($list as $item) {\n            if (isset($list[$item[$pidName]])) {\n                $list[$item[$pidName]][$childrenname][] = &$list[$item[$keyName]];\n            } else {\n                $tree[] = &$list[$item[$keyName]];\n            }\n        }\n        return $tree;\n    }\n\n\n    /**\n     * 生成Artd菜单\n     * @param array $data\n     * @param string $childrenname\n     * @param string $keyName\n     * @param string $pidName\n     * @return array\n     */\n    public static function makeArtdMenus(array $data, string $childrenname = 'children', string $keyName = 'id', string $pidName = 'parent_id')\n    {\n        $list = [];\n        foreach ($data as $value) {\n            $component = '';\n            if ($value['type'] === 1) {\n                $component = '/index/index';\n            }\n            if ($value['type'] === 2) {\n                $component = $value['component'];\n            }\n            $temp = [\n                $keyName => $value[$keyName],\n                $pidName => $value[$pidName],\n                'name' => $value['code'],\n                'path' => $value['path'],\n                'component' => $component,\n                'meta' => [\n                    'title' => $value['name'],\n                    'icon' => $value['icon'],\n                    'isIframe' => $value['is_iframe'] === 1,\n                    'keepAlive' => $value['is_keep_alive'] === 1,\n                    'isHide' => $value['is_hidden'] === 1,\n                    'fixedTab' => $value['is_fixed_tab'] === 1,\n                    'isFullPage' => $value['is_full_page'] === 1,\n                ],\n            ];\n            if ($value['type'] === 4) {\n                $temp['path'] = '/outside/Iframe';\n                $temp['meta']['link'] = $value['link_url'];\n            }\n            $list[$value[$keyName]] = $temp;\n        }\n        $tree = [];\n        foreach ($list as $item) {\n            if (isset($list[$item[$pidName]])) {\n                $list[$item[$pidName]][$childrenname][] = &$list[$item[$keyName]];\n            } else {\n                $tree[] = &$list[$item[$keyName]];\n            }\n        }\n        return $tree;\n    }\n\n    /**\n     * 下划线转驼峰\n     */\n    public static function camelize($uncamelized_words,$separator='_')\n    {\n        $uncamelized_words = $separator. str_replace($separator, \" \", strtolower($uncamelized_words));\n        return ltrim(str_replace(\" \", \"\", ucwords($uncamelized_words)), $separator );\n    }\n\n    /**\n     * 驼峰命名转下划线命名\n     */\n    public static function uncamelize($camelCaps,$separator='_')\n    {\n        return strtolower(preg_replace('/([a-z])([A-Z])/', \"$1\" . $separator . \"$2\", $camelCaps));\n    }\n\n    /**\n     * 转换为驼峰\n     * @param string $value\n     * @return string\n     */\n    public static function camel(string $value): string\n    {\n        static $cache = [];\n        $key = $value;\n\n        if (isset($cache[$key])) {\n            return $cache[$key];\n        }\n\n        $value = ucwords(str_replace(['-', '_'], ' ', $value));\n\n        return $cache[$key] = str_replace(' ', '', $value);\n    }\n\n    /**\n     * 获取业务名称\n     * @param string $tableName\n     * @return mixed\n     */\n    public static function get_business(string $tableName)\n    {\n        $start = strrpos($tableName,'_');\n        if ($start !== false) {\n            $result = substr($tableName, $start + 1);\n        } else {\n            $result = $tableName;\n        }\n        return static::camelize($result);\n    }\n\n    /**\n     * 获取业务名称\n     * @param string $tableName\n     * @return mixed\n     */\n    public static function get_big_business(string $tableName)\n    {\n        $start = strrpos($tableName,'_');\n        $result = substr($tableName, $start + 1);\n        return static::camel($result);\n    }\n\n    /**\n     * 只替换一次字符串\n     * @param $needle\n     * @param $replace\n     * @param $haystack\n     * @return array|mixed|string|string[]\n     */\n    public static function str_replace_once($needle, $replace, $haystack)\n    {\n        $pos = strpos($haystack, $needle);\n        if ($pos === false) {\n            return $haystack;\n        }\n        return substr_replace($haystack, $replace, $pos, strlen($needle));\n    }\n\n    /**\n     * 遍历目录\n     * @param $template_name\n     * @return array\n     */\n    public static function get_dir($template_name)\n    {\n        $dir = base_path($template_name);\n        $fileDir = [];\n        if (is_dir($dir)){\n            if ($dh = opendir($dir)){\n                while (($file = readdir($dh)) !== false){\n                    if($file != \".\" && $file != \"..\"){\n                        array_push($fileDir, $file);\n                    }\n                }\n                closedir($dh);\n            }\n        }\n        return $fileDir;\n    }\n}"
  },
  {
    "path": "src/plugin/saiadmin/utils/ServerMonitor.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\utils;\n\n/**\n * 服务器监控信息\n */\nclass ServerMonitor\n{\n    /**\n     * 获取内存信息\n     * @return array\n     */\n    public function getMemoryInfo(): array\n    {\n        $totalMem = 0; // 总内存 (Bytes)\n        $freeMem  = 0; // 可用/剩余内存 (Bytes)\n\n        if (stristr(PHP_OS, 'WIN')) {\n            // Windows 系统\n            // 一次性获取 总可见内存 和 空闲物理内存 (单位都是 KB)\n            // TotalVisibleMemorySize: 操作系统可识别的内存总数 (比物理内存条总数略少，更准确反映可用上限)\n            // FreePhysicalMemory: 当前可用物理内存\n            $cmd = 'wmic OS get FreePhysicalMemory,TotalVisibleMemorySize /format:csv';\n            $output = shell_exec($cmd);\n            $output = mb_convert_encoding($output ?? '', 'UTF-8', 'GBK, UTF-8, ASCII');\n            $lines  = explode(\"\\n\", trim($output));\n\n            foreach ($lines as $line) {\n                $line = trim($line);\n                if (empty($line)) continue;\n\n                // CSV 格式: Node,FreePhysicalMemory,TotalVisibleMemorySize\n                $parts = str_getcsv($line);\n\n                // 确保解析正确且排除标题行 (通常索引1是Free, 2是Total)\n                if (count($parts) >= 3 && is_numeric($parts[1])) {\n                    $freeMem  = floatval($parts[1]) * 1024; // KB -> Bytes\n                    $totalMem = floatval($parts[2]) * 1024; // KB -> Bytes\n                    break;\n                }\n            }\n        } else {\n            // Linux 系统\n            // 读取 /proc/meminfo，效率远高于 shell_exec('cat ...')\n            $memInfo = @file_get_contents('/proc/meminfo');\n            if ($memInfo) {\n                // 使用正则提取 MemTotal 和 MemAvailable (单位 kB)\n                // MemAvailable 是较新的内核指标，比单纯的 MemFree 更准确（包含可回收的缓存）\n                if (preg_match('/^MemTotal:\\s+(\\d+)\\s+kB/m', $memInfo, $matches)) {\n                    $totalMem = floatval($matches[1]) * 1024;\n                }\n\n                if (preg_match('/^MemAvailable:\\s+(\\d+)\\s+kB/m', $memInfo, $matches)) {\n                    $freeMem = floatval($matches[1]) * 1024;\n                } else {\n                    // 如果内核太老没有 MemAvailable，退化使用 MemFree\n                    if (preg_match('/^MemFree:\\s+(\\d+)\\s+kB/m', $memInfo, $matches)) {\n                        $freeMem = floatval($matches[1]) * 1024;\n                    }\n                }\n            }\n        }\n\n        // 计算已用内存\n        $usedMem = $totalMem - $freeMem;\n\n        // 避免除以0\n        $rate = ($totalMem > 0) ? ($usedMem / $totalMem) * 100 : 0;\n\n        // PHP 自身占用\n        $phpMem = memory_get_usage(true);\n\n        return [\n            // 人类可读格式 (String)\n            'total'     => $this->formatBytes($totalMem),\n            'free'      => $this->formatBytes($freeMem),\n            'used'      => $this->formatBytes($usedMem),\n            'php'       => $this->formatBytes($phpMem),\n            'rate'      => sprintf('%.2f', $rate) . '%',\n\n            // 原始数值 (Float/Int)，方便前端图表使用或逻辑判断，统一单位 Bytes\n            'raw' => [\n                'total' => $totalMem,\n                'free'  => $freeMem,\n                'used'  => $usedMem,\n                'php'   => $phpMem,\n                'rate'  => round($rate, 2)\n            ]\n        ];\n    }\n\n    /**\n     * 获取PHP及环境信息\n     * @return array\n     */\n    public function getPhpAndEnvInfo(): array\n    {\n        return [\n            'php_version'         => PHP_VERSION,\n            'os'                  => PHP_OS,\n            'project_path'        => BASE_PATH,\n            'memory_limit'        => ini_get('memory_limit'),\n            'max_execution_time'  => ini_get('max_execution_time'),\n            'error_reporting'     => ini_get('error_reporting'),\n            'display_errors'      => ini_get('display_errors'),\n            'upload_max_filesize' => ini_get('upload_max_filesize'),\n            'post_max_size'       => ini_get('post_max_size'),\n            'extension_dir'       => ini_get('extension_dir'),\n            'loaded_extensions'   => implode(', ', get_loaded_extensions()),\n        ];\n    }\n\n    /**\n     * 获取磁盘信息\n     * @return array\n     */\n    public function getDiskInfo(): array\n    {\n        $disk = [];\n\n        if (stristr(PHP_OS, 'WIN')) {\n            // Windows 系统\n            // 使用 CSV 格式输出，避免空格解析错误；SkipTop=1 跳过空行\n            // LogicalDisk 包含: Caption(盘符), FreeSpace(剩余字节), Size(总字节)\n            $cmd = 'wmic logicaldisk get Caption,FreeSpace,Size /format:csv';\n            $output = shell_exec($cmd);\n\n            // 转换编码，防止中文乱码（视服务器环境而定，通常 Windows CMD 输出为 GBK）\n            $output = mb_convert_encoding($output ?? '', 'UTF-8', 'GBK, UTF-8, ASCII');\n            $lines = explode(\"\\n\", trim($output));\n\n            foreach ($lines as $line) {\n                $line = trim($line);\n                if (empty($line)) continue;\n\n                // CSV 格式: Node,Caption,FreeSpace,Size\n                $parts = str_getcsv($line);\n\n                // 确保数据列足够且是一个盘符 (例如 \"C:\")\n                // 索引通常是: 1=>Caption, 2=>FreeSpace, 3=>Size (索引0通常是计算机名)\n                if (count($parts) >= 4 && preg_match('/^[A-Z]:$/', $parts[1])) {\n                    $caption   = $parts[1];\n                    $freeSpace = floatval($parts[2]);\n                    $totalSize = floatval($parts[3]);\n\n                    // 避免除以 0 错误（如光驱未放入光盘时 Size 可能为 0 或 null）\n                    if ($totalSize <= 0) continue;\n\n                    $usedSpace = $totalSize - $freeSpace;\n\n                    $disk[] = [\n                        'filesystem'     => $caption,\n                        'mounted_on'     => $caption,\n                        'size'           => $this->formatBytes($totalSize),\n                        'available'      => $this->formatBytes($freeSpace),\n                        'used'           => $this->formatBytes($usedSpace),\n                        'use_percentage' => sprintf('%.2f', ($usedSpace / $totalSize) * 100) . '%',\n                        'raw'            => [ // 保留原始数据以便前端或其他逻辑使用\n                            'size'      => $totalSize,\n                            'available' => $freeSpace,\n                            'used'      => $usedSpace\n                        ]\n                    ];\n                }\n            }\n\n        } else {\n            // Linux 系统\n            // -P: POSIX 输出格式（强制在一行显示，防止长挂载点换行）\n            // -T: 显示文件系统类型\n            // 默认单位是 1K-blocks (1024字节)\n            $output = shell_exec('df -TP 2>/dev/null');\n            $lines = explode(\"\\n\", trim($output ?? ''));\n\n            // 过滤表头\n            array_shift($lines);\n\n            foreach ($lines as $line) {\n                $line = trim($line);\n                if (empty($line)) continue;\n\n                // 限制分割数量，防止挂载点名称中有空格导致解析错位（虽然 -P 很大程度避免了这个问题，但仍需谨慎）\n                $parts = preg_split('/\\s+/', $line);\n\n                // df -TP 输出列: Filesystem(0), Type(1), 1024-blocks(2), Used(3), Available(4), Capacity(5), Mounted on(6)\n                if (count($parts) >= 7) {\n                    $filesystem = $parts[0];\n                    $type       = $parts[1];\n                    $totalKB    = floatval($parts[2]); // 单位是 KB\n                    $usedKB     = floatval($parts[3]);\n                    $availKB    = floatval($parts[4]);\n                    $mountedOn  = $parts[6];\n\n                    // 过滤逻辑：只显示物理硬盘或特定挂载点\n                    // 通常过滤掉 tmpfs, devtmpfs, overlay, squashfs(snap) 等\n                    // 如果你只想看 /dev/ 开头的物理盘，保留原来的正则即可\n                    if (!preg_match('/^\\/dev\\//', $filesystem)) {\n                        // continue; // 根据需求决定是否取消注释此行\n                    }\n                    // 过滤掉 Docker overlay 或 kubelet 等产生的繁杂挂载\n                    if (strpos($filesystem, 'overlay') !== false) continue;\n\n                    // 转换为字节\n                    $totalSize = $totalKB * 1024;\n                    $usedSize  = $usedKB * 1024;\n                    $freeSize  = $availKB * 1024;\n\n                    if ($totalSize <= 0) continue;\n\n                    $disk[] = [\n                        'filesystem'     => $filesystem,\n                        'type'           => $type,\n                        'mounted_on'     => $mountedOn,\n                        'size'           => $this->formatBytes($totalSize),\n                        'available'      => $this->formatBytes($freeSize),\n                        'used'           => $this->formatBytes($usedSize),\n                        'use_percentage' => sprintf('%.2f', ($usedSize / $totalSize) * 100) . '%',\n                        'raw'            => [\n                            'size'      => $totalSize,\n                            'available' => $freeSize,\n                            'used'      => $usedSize\n                        ]\n                    ];\n                }\n            }\n        }\n        return $disk;\n    }\n\n    /**\n     * 格式化字节为可读格式 (B, KB, MB, GB, TB...)\n     * @param int|float $bytes 字节数\n     * @param int $precision 小数点后保留位数\n     * @return string\n     */\n    private function formatBytes($bytes, int $precision = 2): string\n    {\n        if ($bytes <= 0) {\n            return '0 B';\n        }\n        $base = log($bytes, 1024);\n        $suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'];\n        // 确保不会数组越界\n        $class = min((int)floor($base), count($suffixes) - 1);\n        return sprintf(\"%.\" . $precision . \"f\", $bytes / pow(1024, $class)) . ' ' . $suffixes[$class];\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/utils/code/CodeEngine.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\utils\\code;\n\nuse Twig\\TwigFilter;\nuse Twig\\Environment;\nuse Twig\\Loader\\FilesystemLoader;\nuse plugin\\saiadmin\\exception\\ApiException;\n\n// 定义目录分隔符常量\ndefined('DS') or define('DS', DIRECTORY_SEPARATOR);\n\n/**\n * 代码生成引擎\n */\nclass CodeEngine\n{\n    /**\n     * @var array 值栈\n     */\n    private array $value = [];\n\n    /**\n     * 模板名称\n     * @var string\n     */\n    private string $stub = 'saiadmin';\n\n    /**\n     * 获取配置文件\n     * @return string[]\n     */\n    private static function _getConfig(): array\n    {\n        return [\n            'template_path' => base_path() . DS . 'plugin' . DS . 'saiadmin' . DS . 'utils' . DS . 'code' . DS . 'stub',\n            'generate_path' => runtime_path() . DS . 'code_engine' . DS . 'saiadmin',\n        ];\n    }\n\n    /**\n     * 初始化\n     * @param array $data 数据\n     */\n    public function __construct(array $data)\n    {\n        // 读取配置文件\n        $config = self::_getConfig();\n\n        // 判断模板是否存在\n        if (!is_dir($config['template_path'])) {\n            throw new ApiException('模板目录不存在！');\n        }\n        // 判断文件生成目录是否存在\n        if (!is_dir($config['generate_path'])) {\n            mkdir($config['generate_path'], 0770, true);\n        }\n        // 赋值\n        $this->value = $data;\n    }\n\n    /**\n     * 设置模板名称\n     * @param $stub\n     * @return void\n     */\n    public function setStub($stub): void\n    {\n        $this->stub = $stub;\n    }\n\n    /**\n     * 渲染文件内容\n     */\n    public function renderContent($path, $filename): string\n    {\n        $config = self::_getConfig();\n\n        $path = $config['template_path'] . DS . $this->stub . DS . $path;\n\n        $loader = new FilesystemLoader($path);\n        $twig = new Environment($loader);\n        $camelFilter = new TwigFilter('camel', function ($value) {\n            static $cache = [];\n            $key = $value;\n            if (isset($cache[$key])) {\n                return $cache[$key];\n            }\n            $value = ucwords(str_replace(['-', '_'], ' ', $value));\n            return $cache[$key] = str_replace(' ', '', $value);\n        });\n        $boolFilter = new TwigFilter('bool', function ($value) {\n            if ($value == 1) {\n                return 'true';\n            } else {\n                return 'false';\n            }\n        });\n        $formatFilter = new TwigFilter('formatNumber', function ($value) {\n            if (ctype_digit((string) $value)) {\n                return $value;\n            } else {\n                return '1';\n            }\n        });\n        $defaultFilter = new TwigFilter('parseNumber', function ($value) {\n            if (\\is_numeric($value)) {\n                return $value;\n            } else {\n                return 'null';\n            }\n        });\n        $containsFilter = new TwigFilter('str_contains', function ($haystack, $needle) {\n            return str_contains($haystack ?? '', $needle ?? '');\n        });\n\n        $twig->addFilter($camelFilter);\n        $twig->addFilter($boolFilter);\n        $twig->addFilter($containsFilter);\n        $twig->addFilter($formatFilter);\n        $twig->addFilter($defaultFilter);\n\n        return $twig->render($filename, $this->value);\n    }\n\n    /**\n     * 生成后端文件\n     */\n    public function generateBackend($action, $content): void\n    {\n        $outPath = '';\n        if ($this->value['template'] == 'app') {\n            $rootPath = base_path() . DS . 'app' . DS . $this->value['namespace'];\n            $adminPath = '';\n\n        } else {\n            $rootPath = base_path() . DS . 'plugin' . DS . $this->value['namespace'] . DS . 'app';\n            $adminPath = DS . 'admin';\n        }\n        $subPath = DS . $this->value['package_name'];\n        switch ($action) {\n            case 'controller':\n                $outPath = $rootPath . $adminPath . DS . 'controller' . $subPath . DS . $this->value['class_name'] . 'Controller.php';\n                break;\n            case 'logic':\n                $outPath = $rootPath . $adminPath . DS . 'logic' . $subPath . DS . $this->value['class_name'] . 'Logic.php';\n                break;\n            case 'validate':\n                $outPath = $rootPath . $adminPath . DS . 'validate' . $subPath . DS . $this->value['class_name'] . 'Validate.php';\n                break;\n            case 'model':\n                $outPath = $rootPath . DS . 'model' . $subPath . DS . $this->value['class_name'] . '.php';\n                break;\n            default:\n                break;\n        }\n\n        if (empty($outPath)) {\n            throw new ApiException('文件类型异常，无法生成指定文件！');\n        }\n        if (!is_dir(dirname($outPath))) {\n            mkdir(dirname($outPath), 0777, true);\n        }\n\n        file_put_contents($outPath, $content);\n    }\n\n    /**\n     * 生成前端文件\n     */\n    public function generateFrontend($action, $content): void\n    {\n        $rootPath = dirname(base_path()) . DS . $this->value['generate_path'];\n        if (!is_dir($rootPath)) {\n            throw new ApiException('前端目录查找失败，必须与后端目录为同级目录！');\n        }\n\n        $rootPath = $rootPath . DS . 'src' . DS . 'views' . DS . 'plugin' . DS . $this->value['namespace'];\n        $subPath = DS . $this->value['package_name'];\n        switch ($action) {\n            case 'index':\n                $outPath = $rootPath . $subPath . DS . $this->value['business_name'] . DS . 'index.vue';\n                break;\n            case 'edit-dialog':\n                $outPath = $rootPath . $subPath . DS . $this->value['business_name'] . DS . 'modules' . DS . 'edit-dialog.vue';\n                break;\n            case 'table-search':\n                $outPath = $rootPath . $subPath . DS . $this->value['business_name'] . DS . 'modules' . DS . 'table-search.vue';\n                break;\n            case 'api':\n                $outPath = $rootPath . DS . 'api' . $subPath . DS . $this->value['business_name'] . '.ts';\n                break;\n            default:\n                break;\n        }\n\n        if (empty($outPath)) {\n            throw new ApiException('文件类型异常，无法生成指定文件！');\n        }\n        if (!is_dir(dirname($outPath))) {\n            mkdir(dirname($outPath), 0777, true);\n        }\n\n        file_put_contents($outPath, $content);\n    }\n\n    /**\n     * 生成临时文件\n     */\n    public function generateTemp(): void\n    {\n        $config = self::_getConfig();\n        $rootPath = $config['generate_path'];\n\n        $vuePath = $rootPath . DS . 'vue' . DS . 'src' . DS . 'views' . DS . 'plugin' . DS . $this->value['namespace'];\n        $phpPath = $rootPath . DS . 'php';\n        $sqlPath = $rootPath . DS . 'sql';\n        if ($this->value['template'] == 'app') {\n            $phpPath = $phpPath . DS . 'app' . DS . $this->value['namespace'];\n            $adminPath = '';\n        } else {\n            $phpPath = $phpPath . DS . 'plugin' . DS . $this->value['namespace'] . DS . 'app';\n            $adminPath = DS . 'admin';\n        }\n        $subPath = DS . $this->value['package_name'];\n\n        $indexOutPath = $vuePath . $subPath . DS . $this->value['business_name'] . DS . 'index.vue';\n        $this->checkPath($indexOutPath);\n        $indexContent = $this->renderContent('vue', 'index.stub');\n        file_put_contents($indexOutPath, $indexContent);\n\n        $editOutPath = $vuePath . $subPath . DS . $this->value['business_name'] . DS . 'modules' . DS . 'edit-dialog.vue';\n        $this->checkPath($editOutPath);\n        $editContent = $this->renderContent('vue', 'edit-dialog.stub');\n        file_put_contents($editOutPath, $editContent);\n\n        $searchOutPath = $vuePath . $subPath . DS . $this->value['business_name'] . DS . 'modules' . DS . 'table-search.vue';\n        $this->checkPath($searchOutPath);\n        $searchContent = $this->renderContent('vue', 'table-search.stub');\n        file_put_contents($searchOutPath, $searchContent);\n\n        $viewOutPath = $vuePath . DS . 'api' . $subPath . DS . $this->value['business_name'] . '.ts';\n        $this->checkPath($viewOutPath);\n        $viewContent = $this->renderContent('ts', 'api.stub');\n        file_put_contents($viewOutPath, $viewContent);\n\n        $controllerOutPath = $phpPath . $adminPath . DS . 'controller' . $subPath . DS . $this->value['class_name'] . 'Controller.php';\n        $this->checkPath($controllerOutPath);\n        $controllerContent = $this->renderContent('php', 'controller.stub');\n        file_put_contents($controllerOutPath, $controllerContent);\n\n        $logicOutPath = $phpPath . $adminPath . DS . 'logic' . $subPath . DS . $this->value['class_name'] . 'Logic.php';\n        $this->checkPath($logicOutPath);\n        $logicContent = $this->renderContent('php', 'logic.stub');\n        file_put_contents($logicOutPath, $logicContent);\n\n        $validateOutPath = $phpPath . $adminPath . DS . 'validate' . $subPath . DS . $this->value['class_name'] . 'Validate.php';\n        $this->checkPath($validateOutPath);\n        $validateContent = $this->renderContent('php', 'validate.stub');\n        file_put_contents($validateOutPath, $validateContent);\n\n        $modelOutPath = $phpPath . DS . 'model' . $subPath . DS . $this->value['class_name'] . '.php';\n        $this->checkPath($modelOutPath);\n        $modelContent = $this->renderContent('php', 'model.stub');\n        file_put_contents($modelOutPath, $modelContent);\n\n        $sqlOutPath = $sqlPath . DS . 'sql.sql';\n        $this->checkPath($sqlOutPath);\n        $sqlContent = $this->renderContent('sql', 'sql.stub');\n        file_put_contents($sqlOutPath, $sqlContent);\n    }\n\n    /**\n     * 检查并生成路径\n     * @param $path\n     * @return void\n     */\n    protected function checkPath($path): void\n    {\n        if (!is_dir(dirname($path))) {\n            mkdir(dirname($path), 0777, true);\n        }\n    }\n\n}"
  },
  {
    "path": "src/plugin/saiadmin/utils/code/CodeZip.php",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: sai <1430792918@qq.com>\n// +----------------------------------------------------------------------\nnamespace plugin\\saiadmin\\utils\\code;\n\nuse plugin\\saiadmin\\exception\\ApiException;\n\n/**\n * 代码构建 压缩类\n */\nclass CodeZip\n{\n\n    /**\n     * 获取配置文件\n     * @return string[]\n     */\n    private static function _getConfig(): array\n    {\n        return [\n            'template_path' => base_path().DIRECTORY_SEPARATOR.'plugin'.DIRECTORY_SEPARATOR.'saiadmin'.DIRECTORY_SEPARATOR.'utils'.DIRECTORY_SEPARATOR.'code'.DIRECTORY_SEPARATOR.'stub',\n            'generate_path' => runtime_path().DIRECTORY_SEPARATOR.'code_engine'.DIRECTORY_SEPARATOR.'saiadmin',\n        ];\n    }\n\n    /**\n     * 构造器\n     */\n    public function __construct()\n    {\n        // 读取配置文件\n        $config = self::_getConfig();\n\n        // 清理源目录\n        if (is_dir($config['generate_path'])) {\n            $this->recursiveDelete($config['generate_path']);\n        }\n\n        // 清理压缩文件\n        $zipName = $config['generate_path'].'.zip';\n        if (is_file($zipName)) {\n            unlink($zipName);\n        }\n    }\n\n    /**\n     * 文件压缩\n     */\n    public function compress(bool $isDownload = false)\n    {\n        // 读取配置文件\n        $config = self::_getConfig();\n        $zipArc = new \\ZipArchive;\n        $zipName = $config['generate_path'].'.zip';\n        $dirPath = $config['generate_path'];\n        if ($zipArc->open($zipName, \\ZipArchive::OVERWRITE | \\ZipArchive::CREATE) !== true) {\n            throw new ApiException('无法打开文件，或者文件创建失败');\n        }\n        $this->addFileToZip($dirPath, $zipArc);\n        $zipArc->close();\n        // 是否下载\n        if ($isDownload) {\n            $this->toBinary($zipName);\n        } else {\n            return $zipName;\n        }\n    }\n\n    /**\n     * 文件解压\n     */\n    public function deCompress(string $file, string $dirName)\n    {\n        if (!file_exists($file)) {\n            return false;\n        }\n        // zip实例化对象\n        $zipArc = new \\ZipArchive();\n        // 打开文件\n        if (!$zipArc->open($file)) {\n            return false;\n        }\n        // 解压文件\n        if (!$zipArc->extractTo($dirName)) {\n            // 关闭\n            $zipArc->close();\n            return false;\n        }\n        return $zipArc->close();\n    }\n\n    /**\n     * 将文件加入到压缩包\n     */\n    public function addFileToZip($rootPath, $zip)\n    {\n        $files = new \\RecursiveIteratorIterator(\n            new \\RecursiveDirectoryIterator($rootPath),\n            \\RecursiveIteratorIterator::LEAVES_ONLY\n        );\n        foreach ($files as $name => $file)\n        {\n            // Skip directories (they would be added automatically)\n            if (!$file->isDir())\n            {\n                // Get real and relative path for current file\n                $filePath = $file->getRealPath();\n                $relativePath = substr($filePath, strlen($rootPath) + 1);\n\n                // Add current file to archive\n                $zip->addFile($filePath, $relativePath);\n            }\n        }\n    }\n\n    /**\n     * 递归删除目录下所有文件和文件夹\n     */\n    public function recursiveDelete($dir)\n    {\n        // 打开指定目录\n        if ($handle = @opendir($dir)) {\n            while (($file = readdir($handle)) !== false) {\n                if (($file == \".\") || ($file == \"..\")) {\n                    continue;\n                }\n                if (is_dir($dir . '/' . $file)) {\n                    // 递归\n                    self::recursiveDelete($dir . '/' . $file);\n                } else {\n                    unlink($dir . '/' . $file); // 删除文件\n                }\n            }\n            @closedir($handle);\n        }\n        rmdir($dir);\n    }\n\n    /**\n     * 下载二进制流文件\n     */\n    public function toBinary(string $fileName)\n    {\n        try {\n            header(\"Cache-Control: public\");\n            header(\"Content-Description: File Transfer\");\n            header('Content-disposition: attachment; filename=' . basename($fileName)); //文件名\n            header(\"Content-Type: application/zip\"); //zip格式的\n            header(\"Content-Transfer-Encoding: binary\"); //告诉浏览器，这是二进制文件\n            header('Content-Length: ' . filesize($fileName)); //告诉浏览器，文件大小\n            if(ob_get_length() > 0) {\n                ob_clean();\n            }\n            flush();\n            @readfile($fileName);\n            @unlink($fileName);\n        } catch (\\Throwable $th) {\n            throw new ApiException('系统生成文件错误');\n        }\n    }\n}"
  },
  {
    "path": "src/plugin/saiadmin/utils/code/stub/saiadmin/php/controller.stub",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: your name\n// +----------------------------------------------------------------------\nnamespace {{namespace_start}}controller{{namespace_end}};\n\nuse plugin\\saiadmin\\basic\\BaseController;\nuse {{namespace_start}}logic{{namespace_end}}\\{{class_name}}Logic;\nuse {{namespace_start}}validate{{namespace_end}}\\{{class_name}}Validate;\nuse plugin\\saiadmin\\service\\Permission;\nuse support\\Request;\nuse support\\Response;\n\n/**\n * {{menu_name}}控制器\n */\nclass {{class_name}}Controller extends BaseController\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->logic = new {{class_name}}Logic();\n        $this->validate = new {{class_name}}Validate;\n        parent::__construct();\n    }\n\n    /**\n     * 数据列表\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('{{menu_name}}列表', '{{namespace}}:{{package_name}}:{{business_name}}:index')]\n    public function index(Request $request): Response\n    {\n        $where = $request->more([\n{% for column in columns %}\n{% if column.is_query == '2' %}\n            ['{{column.column_name}}', ''],\n{% endif %}\n{% endfor %}\n        ]);\n{% if tpl_category == 'single' %}\n        $query = $this->logic->search($where);\n{% if options.relations != null %}\n        $query->with([\n{% for item in options.relations %}\n            '{{item.name}}',\n{% endfor %}\n        ]);\n{% endif %}\n        $data = $this->logic->getList($query);\n{% endif %}\n{% if tpl_category == 'tree' %}\n        $data = $this->logic->tree($where);\n{% endif %}\n        return $this->success($data);\n    }\n\n    /**\n     * 读取数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('{{menu_name}}读取', '{{namespace}}:{{package_name}}:{{business_name}}:read')]\n    public function read(Request $request): Response\n    {\n        $id = $request->input('id', '');\n        $model = $this->logic->read($id);\n        if ($model) {\n            $data = is_array($model) ? $model : $model->toArray();\n            return $this->success($data);\n        } else {\n            return $this->fail('未查找到信息');\n        }\n    }\n\n    /**\n     * 保存数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('{{menu_name}}添加', '{{namespace}}:{{package_name}}:{{business_name}}:save')]\n    public function save(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('save', $data);\n        $result = $this->logic->add($data);\n        if ($result) {\n            return $this->success('添加成功');\n        } else {\n            return $this->fail('添加失败');\n        }\n    }\n\n    /**\n     * 更新数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('{{menu_name}}修改', '{{namespace}}:{{package_name}}:{{business_name}}:update')]\n    public function update(Request $request): Response\n    {\n        $data = $request->post();\n        $this->validate('update', $data);\n        $result = $this->logic->edit($data['id'], $data);\n        if ($result) {\n            return $this->success('修改成功');\n        } else {\n            return $this->fail('修改失败');\n        }\n    }\n\n    /**\n     * 删除数据\n     * @param Request $request\n     * @return Response\n     */\n    #[Permission('{{menu_name}}删除', '{{namespace}}:{{package_name}}:{{business_name}}:destroy')]\n    public function destroy(Request $request): Response\n    {\n        $ids = $request->post('ids', '');\n        if (empty($ids)) {\n            return $this->fail('请选择要删除的数据');\n        }\n        $result = $this->logic->destroy($ids);\n        if ($result) {\n            return $this->success('删除成功');\n        } else {\n            return $this->fail('删除失败');\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/utils/code/stub/saiadmin/php/logic.stub",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: your name\n// +----------------------------------------------------------------------\nnamespace {{namespace_start}}logic{{namespace_end}};\n\n{% if stub == 'eloquent' %}\nuse plugin\\saiadmin\\basic\\eloquent\\BaseLogic;\n{% else %}\nuse plugin\\saiadmin\\basic\\think\\BaseLogic;\n{% endif %}\nuse plugin\\saiadmin\\exception\\ApiException;\nuse plugin\\saiadmin\\utils\\Helper;\nuse {{namespace_start_model}}model{{namespace_end}}\\{{class_name}};\n\n/**\n * {{menu_name}}逻辑层\n */\nclass {{class_name}}Logic extends BaseLogic\n{\n    /**\n     * 构造函数\n     */\n    public function __construct()\n    {\n        $this->model = new {{class_name}}();\n    }\n\n{% if tpl_category == 'tree' %}\n    /**\n     * 修改数据\n     * @param $id\n     * @param $data\n     * @return mixed\n     */\n    public function edit($id, $data): mixed\n    {\n        if (!isset($data['{{options.tree_parent_id}}'])) {\n            $data['{{options.tree_parent_id}}'] = 0;\n        }\n        if ($data['{{options.tree_parent_id}}'] == $data['{{options.tree_id}}']) {\n            throw new ApiException('不能设置父级为自身');\n        }\n        return parent::edit($id, $data);\n    }\n\n    /**\n     * 删除数据\n     * @param $ids\n     */\n    public function destroy($ids): bool\n    {\n        $num = $this->model->whereIn('{{options.tree_parent_id}}', $ids)->count();\n        if ($num > 0) {\n            throw new ApiException('该分类下存在子分类，请先删除子分类');\n        } else {\n            return parent::destroy($ids);\n        }\n    }\n\n    /**\n     * 树形数据\n     */\n    public function tree($where)\n    {\n        $query = $this->search($where);\n        $request = request();\n        if ($request && $request->input('tree', 'false') === 'true') {\n{% if stub == 'eloquent' %}\n            $query->select('{{options.tree_id}}', '{{options.tree_id}} as value', '{{options.tree_name}} as label', '{{options.tree_parent_id}}');\n{% else %}\n            $query->field('{{options.tree_id}}, {{options.tree_id}} as value, {{options.tree_name}} as label, {{options.tree_parent_id}}');\n{% endif %}\n        }\n{% if options.relations != null %}\n        $query->with([\n{% for item in options.relations %}\n            '{{item.name}}',\n{% endfor %}\n        ]);\n{% endif %}\n        $data = $this->getAll($query);\n        return Helper::makeTree($data);\n    }\n\n{% endif %}\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/utils/code/stub/saiadmin/php/model.stub",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: your name\n// +----------------------------------------------------------------------\nnamespace {{namespace_start_model}}model{{namespace_end}};\n\n{% if stub == 'eloquent' %}\nuse plugin\\saiadmin\\basic\\eloquent\\BaseModel;\n{% else %}\nuse plugin\\saiadmin\\basic\\think\\BaseModel;\n{% endif %}\n\n/**\n * {{menu_name}}模型\n *\n * {{table_name}} {{table_comment}}\n *\n{% for column in columns %}\n * @property {{column.php_type}} ${{column.column_name}} {{column.column_comment}}\n{% endfor %}\n */\nclass {{class_name}} extends BaseModel\n{\n    /**\n     * 数据表主键\n     * @var string\n     */\n{% if stub == 'eloquent' %}\n    protected $primaryKey = '{{pk}}';\n{% else %}\n    protected $pk = '{{pk}}';\n{% endif %}\n\n    /**\n     * 数据库表名称\n     * @var string\n     */\n    protected $table = '{{table_name}}';\n\n{% if source != db_source and source != '' %}\n    /**\n     * 数据库连接\n     * @var string\n     */\n    protected $connection = '{{source}}';\n\n{% endif %}\n{% if stub == 'eloquent' %}\n    /**\n     * 属性转换\n     */\n    protected function casts(): array\n    {\n        return array_merge(parent::casts(), [\n{% for column in columns %}\n{% if column.view_type == 'inputTag' or column.view_type == 'checkbox' %}\n            '{{column.column_name}}' => 'array',\n{% endif %}\n{% if column.view_type == 'uploadImage' and column.options.limit > 1 %}\n            '{{column.column_name}}' => 'array',\n{% endif %}\n{% if column.view_type == 'imagePicker' and column.options.limit > 1 %}\n            '{{column.column_name}}' => 'array',\n{% endif %}\n{% if column.view_type == 'uploadFile' and column.options.limit > 1 %}\n            '{{column.column_name}}' => 'array',\n{% endif %}\n{% if column.view_type == 'chunkUpload' and column.options.limit > 1 %}\n            '{{column.column_name}}' => 'array',\n{% endif %}\n{% endfor %}\n        ]);\n    }\n{% else %}\n{% for column in columns %}\n{% if column.view_type == 'inputTag' or column.view_type == 'checkbox' %}\n    /**\n     * {{column.column_comment}} 保存数组转换\n     */\n    public function set{{column.column_name | camel}}Attr($value)\n    {\n        return json_encode($value, JSON_UNESCAPED_UNICODE);\n    }\n\n    /**\n     * {{column.column_comment}} 读取数组转换\n     */\n    public function get{{column.column_name | camel}}Attr($value)\n    {\n        return json_decode($value ?? '', true);\n    }\n\n{% endif %}\n{% if column.view_type == 'uploadImage' and column.options.limit > 1 %}\n    /**\n     * {{column.column_comment}} 保存数组转换\n     */\n    public function set{{column.column_name | camel}}Attr($value)\n    {\n        return json_encode($value, JSON_UNESCAPED_UNICODE);\n    }\n\n    /**\n     * {{column.column_comment}} 读取数组转换\n     */\n    public function get{{column.column_name | camel}}Attr($value)\n    {\n        return json_decode($value ?? '', true);\n    }\n\n{% endif %}\n{% if column.view_type == 'imagePicker' and column.options.limit > 1 %}\n    /**\n     * {{column.column_comment}} 保存数组转换\n     */\n    public function set{{column.column_name | camel}}Attr($value)\n    {\n        return json_encode($value, JSON_UNESCAPED_UNICODE);\n    }\n\n    /**\n     * {{column.column_comment}} 读取数组转换\n     */\n    public function get{{column.column_name | camel}}Attr($value)\n    {\n        return json_decode($value ?? '', true);\n    }\n\n{% endif %}\n{% if column.view_type == 'uploadFile' and column.options.limit > 1 %}\n    /**\n     * {{column.column_comment}} 保存数组转换\n     */\n    public function set{{column.column_name | camel}}Attr($value)\n    {\n        return json_encode($value, JSON_UNESCAPED_UNICODE);\n    }\n\n    /**\n     * {{column.column_comment}} 读取数组转换\n     */\n    public function get{{column.column_name | camel}}Attr($value)\n    {\n        return json_decode($value ?? '', true);\n    }\n\n{% endif %}\n{% if column.view_type == 'chunkUpload' and column.options.limit > 1 %}\n    /**\n     * {{column.column_comment}} 保存数组转换\n     */\n    public function set{{column.column_name | camel}}Attr($value)\n    {\n        return json_encode($value, JSON_UNESCAPED_UNICODE);\n    }\n\n    /**\n     * {{column.column_comment}} 读取数组转换\n     */\n    public function get{{column.column_name | camel}}Attr($value)\n    {\n        return json_decode($value ?? '', true);\n    }\n\n{% endif %}\n{% endfor %}\n{% endif %}\n{% for column in columns %}\n{% if column.is_query == 2 and column.query_type == 'neq' %}\n    /**\n     * {{column.column_comment}} 搜索\n     */\n    public function search{{column.column_name | camel}}Attr($query, $value)\n    {\n        $query->where('{{column.column_name}}', '<>', $value);\n    }\n\n{% endif %}\n{% if column.is_query == 2 and column.query_type == 'gt' %}\n    /**\n     * {{column.column_comment}} 搜索\n     */\n    public function search{{column.column_name | camel}}Attr($query, $value)\n    {\n        $query->where('{{column.column_name}}', '>', $value);\n    }\n\n{% endif %}\n{% if column.is_query == 2 and column.query_type == 'gte' %}\n    /**\n     * {{column.column_comment}} 搜索\n     */\n    public function search{{column.column_name | camel}}Attr($query, $value)\n    {\n        $query->where('{{column.column_name}}', '>=', $value);\n    }\n\n{% endif %}\n{% if column.is_query == 2 and column.query_type == 'lt' %}\n    /**\n     * {{column.column_comment}} 搜索\n     */\n    public function search{{column.column_name | camel}}Attr($query, $value)\n    {\n        $query->where('{{column.column_name}}', '<', $value);\n    }\n\n{% endif %}\n{% if column.is_query == 2 and column.query_type == 'lte' %}\n    /**\n     * {{column.column_comment}} 搜索\n     */\n    public function search{{column.column_name | camel}}Attr($query, $value)\n    {\n        $query->where('{{column.column_name}}', '<=', $value);\n    }\n\n{% endif %}\n{% if column.is_query == 2 and column.query_type == 'like' %}\n    /**\n     * {{column.column_comment}} 搜索\n     */\n    public function search{{column.column_name | camel}}Attr($query, $value)\n    {\n        $query->where('{{column.column_name}}', 'like', '%'.$value.'%');\n    }\n\n{% endif %}\n{% if column.is_query == 2 and column.query_type == 'in' %}\n    /**\n     * {{column.column_comment}} 搜索\n     */\n    public function search{{column.column_name | camel}}Attr($query, $value)\n    {\n        $query->whereIn('{{column.column_name}}', $value);\n    }\n\n{% endif %}\n{% if column.is_query == 2 and column.query_type == 'notin' %}\n    /**\n     * {{column.column_comment}} 搜索\n     */\n    public function search{{column.column_name | camel}}Attr($query, $value)\n    {\n        $query->whereNotIn('{{column.column_name}}', $value);\n    }\n\n{% endif %}\n{% if column.is_query == 2 and column.query_type == 'between' %}\n    /**\n     * {{column.column_comment}} 搜索\n     */\n    public function search{{column.column_name | camel}}Attr($query, $value)\n    {\n        $query->whereBetween('{{column.column_name}}', $value);\n    }\n\n{% endif %}\n{% endfor %}\n{% for item in options.relations %}\n{% if item.type == 'belongsTo' %}\n    /**\n     * 关联模型 {{item.name}}\n     */\n    public function {{item.name}}()\n    {\n        return $this->{{item.type}}({{item.model}}::class, '{{item.localKey}}', '{{item.foreignKey}}');\n    }\n\n{% endif %}\n{% if item.type == 'hasOne' or item.type == 'hasMany' %}\n    /**\n     * 关联模型 {{item.name}}\n     */\n    public function {{item.name}}()\n    {\n        return $this->{{item.type}}({{item.model}}::class, '{{item.localKey}}', '{{item.foreignKey}}');\n    }\n\n{% endif %}\n{% if item.type == 'belongsToMany' and stub == 'think' %}\n    /**\n     * 关联模型 {{item.name}}\n     */\n    public function {{item.name}}()\n    {\n        return $this->{{item.type}}({{item.model}}::class, {{item.table}}::class, '{{item.localKey}}', '{{item.foreignKey}}');\n    }\n\n{% endif %}\n{% if item.type == 'belongsToMany' and stub == 'eloquent' %}\n    /**\n     * 关联模型 {{item.name}}\n     */\n    public function {{item.name}}()\n    {\n        return $this->{{item.type}}({{item.model}}::class, {{item.table}}::class, '{{item.foreignKey}}', '{{item.localKey}}');\n    }\n\n{% endif %}\n{% endfor %}\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/utils/code/stub/saiadmin/php/validate.stub",
    "content": "<?php\n// +----------------------------------------------------------------------\n// | saiadmin [ saiadmin快速开发框架 ]\n// +----------------------------------------------------------------------\n// | Author: your name\n// +----------------------------------------------------------------------\nnamespace {{namespace_start}}validate{{namespace_end}};\n\nuse plugin\\saiadmin\\basic\\BaseValidate;\n\n/**\n * {{menu_name}}验证器\n */\nclass {{class_name}}Validate extends BaseValidate\n{\n    /**\n     * 定义验证规则\n     */\n    protected $rule =   [\n{% for column in columns %}\n{% if column.is_required == 2 and column.is_pk != 2 %}\n        '{{column.column_name}}' => 'require',\n{% endif %}\n{% endfor %}\n    ];\n\n    /**\n     * 定义错误信息\n     */\n    protected $message  =   [\n{% for column in columns %}\n{% if column.is_required == 2 and column.is_pk != 2 %}\n        '{{column.column_name}}' => '{{column.column_comment}}必须填写',\n{% endif %}\n{% endfor %}\n    ];\n\n    /**\n     * 定义场景\n     */\n    protected $scene = [\n        'save' => [\n{% for column in columns %}\n{% if column.is_required == 2 and column.is_pk != 2 %}\n            '{{column.column_name}}',\n{% endif %}\n{% endfor %}\n        ],\n        'update' => [\n{% for column in columns %}\n{% if column.is_required == 2 and column.is_pk != 2 %}\n            '{{column.column_name}}',\n{% endif %}\n{% endfor %}\n        ],\n    ];\n\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/utils/code/stub/saiadmin/sql/sql.stub",
    "content": "-- 数据库语句--\n\n{% for column in tables %}\n-- 菜单[{{column.menu_name}}] SQL\nINSERT INTO `sa_system_menu`(`parent_id`, `name`, `code`, `slug`, `type`, `path`, `component`, `icon`, `sort`, `is_iframe`, `is_keep_alive`, `is_hidden`, `is_fixed_tab`, `is_full_page`, `create_time`, `update_time`) VALUES ({{column.belong_menu_id}}, '{{column.menu_name}}', '{{column.namespace}}/{{column.package_name}}/{{column.business_name}}', '', 2, '{{column.package_name}}/{{column.business_name}}', '/plugin/{{column.namespace}}/{{column.package_name}}/{{column.business_name}}/index', 'ri:home-2-line', 100, 2, 2, 2, 2, 2, now(), now());\n\nSET @id := LAST_INSERT_ID();\n\nINSERT INTO `sa_system_menu`(`parent_id`, `name`, `slug`, `type`, `sort`, `is_iframe`, `is_keep_alive`, `is_hidden`, `is_fixed_tab`, `is_full_page`, `create_time`, `update_time`) VALUES (@id, '列表', '{{column.namespace}}:{{column.package_name}}:{{column.business_name}}:index', 3, 100, 2, 2, 2, 2, 2, now(), now());\nINSERT INTO `sa_system_menu`(`parent_id`, `name`, `slug`, `type`, `sort`, `is_iframe`, `is_keep_alive`, `is_hidden`, `is_fixed_tab`, `is_full_page`, `create_time`, `update_time`) VALUES (@id, '保存', '{{column.namespace}}:{{column.package_name}}:{{column.business_name}}:save', 3, 100, 2, 2, 2, 2, 2, now(), now());\nINSERT INTO `sa_system_menu`(`parent_id`, `name`, `slug`, `type`, `sort`, `is_iframe`, `is_keep_alive`, `is_hidden`, `is_fixed_tab`, `is_full_page`, `create_time`, `update_time`) VALUES (@id, '更新', '{{column.namespace}}:{{column.package_name}}:{{column.business_name}}:update', 3, 100, 2, 2, 2, 2, 2, now(), now());\nINSERT INTO `sa_system_menu`(`parent_id`, `name`, `slug`, `type`, `sort`, `is_iframe`, `is_keep_alive`, `is_hidden`, `is_fixed_tab`, `is_full_page`, `create_time`, `update_time`) VALUES (@id, '读取', '{{column.namespace}}:{{column.package_name}}:{{column.business_name}}:read', 3, 100, 2, 2, 2, 2, 2, now(), now());\nINSERT INTO `sa_system_menu`(`parent_id`, `name`, `slug`, `type`, `sort`, `is_iframe`, `is_keep_alive`, `is_hidden`, `is_fixed_tab`, `is_full_page`, `create_time`, `update_time`) VALUES (@id, '删除', '{{column.namespace}}:{{column.package_name}}:{{column.business_name}}:destroy', 3, 100, 2, 2, 2, 2, 2, now(), now());\n{% endfor %}\n"
  },
  {
    "path": "src/plugin/saiadmin/utils/code/stub/saiadmin/ts/api.stub",
    "content": "import request from '@/utils/http'\n\n/**\n * {{menu_name}} API接口\n */\nexport default {\n  /**\n   * 获取数据列表\n   * @param params 搜索参数\n   * @returns 数据列表\n   */\n  list(params: Record<string, any>) {\n{% if tpl_category == 'tree' %}\n    return request.get<Api.Common.ApiData[]>({\n{% else %}\n    return request.get<Api.Common.ApiPage>({\n{% endif %}\n      url: '/{{url_path}}/index',\n      params\n    })\n  },\n\n  /**\n   * 读取数据\n   * @param id 数据ID\n   * @returns 数据详情\n   */\n  read(id: number | string) {\n    return request.get<Api.Common.ApiData>({\n      url: '/{{url_path}}/read?id=' + id\n    })\n  },\n\n  /**\n   * 创建数据\n   * @param params 数据参数\n   * @returns 执行结果\n   */\n  save(params: Record<string, any>) {\n    return request.post<any>({\n      url: '/{{url_path}}/save',\n      data: params\n    })\n  },\n\n  /**\n   * 更新数据\n   * @param params 数据参数\n   * @returns 执行结果\n   */\n  update(params: Record<string, any>) {\n    return request.put<any>({\n      url: '/{{url_path}}/update',\n      data: params\n    })\n  },\n\n  /**\n   * 删除数据\n   * @param id 数据ID\n   * @returns 执行结果\n   */\n  delete(params: Record<string, any>) {\n    return request.del<any>({\n      url: '/{{url_path}}/destroy',\n      data: params\n    })\n  }\n}\n"
  },
  {
    "path": "src/plugin/saiadmin/utils/code/stub/saiadmin/vue/edit-dialog.stub",
    "content": "<template>\n{% if component_type == 1 %}\n  <el-dialog\n{% else %}\n  <el-drawer\n{% endif %}\n    v-model=\"visible\"\n    :title=\"dialogType === 'add' ? '新增{{menu_name}}' : '编辑{{menu_name}}'\"\n{% if is_full == 2 %}\n{% if component_type == 1 %}\n    :fullscreen=\"true\"\n{% else %}\n    size=\"100%\"\n{% endif %}\n{% else %}\n{% if component_type == 1 %}\n    width=\"{{form_width}}px\"\n{% else %}\n    :size=\"{{form_width}}\"\n{% endif %}\n{% endif %}\n    align-center\n    :close-on-click-modal=\"false\"\n    @close=\"handleClose\"\n  >\n    <el-form ref=\"formRef\" :model=\"formData\" :rules=\"rules\" label-width=\"120px\">\n{% for column in columns %}\n{% if column.is_insert == 2 %}\n{% if column.view_type == 'input' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <el-input v-model=\"formData.{{column.column_name}}\" placeholder=\"请输入{{column.column_comment}}\" />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'password' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <el-input v-model=\"formData.{{column.column_name}}\" type=\"password\" placeholder=\"请输入{{column.column_comment}}\" show-password />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'textarea' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <el-input v-model=\"formData.{{column.column_name}}\" type=\"textarea\" :rows=\"2\" placeholder=\"请输入{{column.column_comment}}\" />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'inputNumber' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <el-input-number v-model=\"formData.{{column.column_name}}\" placeholder=\"请输入{{column.column_comment}}\" />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'inputTag' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <el-input-tag v-model=\"formData.{{column.column_name}}\" placeholder=\"请输入{{column.column_comment}}\" clearable />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'switch' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <sa-switch v-model=\"formData.{{column.column_name}}\" />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'slider' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <el-slider v-model=\"formData.{{column.column_name}}\" placeholder=\"请输入{{column.column_comment}}\" />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'select' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <el-select v-model=\"formData.{{column.column_name}}\" :options=\"[]\" placeholder=\"请选择{{column.column_comment}}\" clearable />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'saSelect' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <sa-select v-model=\"formData.{{column.column_name}}\" dict=\"{{column.dict_type}}\" placeholder=\"请选择{{column.column_comment}}\" clearable />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'treeSelect' and column.column_name == options.tree_parent_id %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <el-tree-select\n          v-model=\"formData.{{column.column_name}}\"\n          :data=\"optionData.treeData\"\n          placeholder=\"请选择{{column.column_comment}}\"\n          check-strictly\n          clearable\n        />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'treeSelect' and column.column_name != options.tree_parent_id %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <el-tree-select v-model=\"formData.{{column.column_name}}\" :data=\"[]\" placeholder=\"请选择{{column.column_comment}}\" clearable />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'radio' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <sa-radio v-model=\"formData.{{column.column_name}}\" dict=\"{{column.dict_type}}\" />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'checkbox' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <sa-checkbox v-model=\"formData.{{column.column_name}}\" dict=\"{{column.dict_type}}\" />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'date' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <el-date-picker v-model=\"formData.{{column.column_name}}\" type=\"{{column.options.mode}}\" value-format=\"{{column.options.value_format}}\" placeholder=\"请选择{{column.column_comment}}\" />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'time' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <el-time-picker v-model=\"formData.{{column.column_name}}\" placeholder=\"请选择{{column.column_comment}}\" />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'rate' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <el-rate v-model=\"formData.{{column.column_name}}\" />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'cascader' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <el-cascader v-model=\"formData.{{column.column_name}}\" :options=\"[]\" placeholder=\"请选择{{column.column_comment}}\" allow-clear />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'userSelect' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <sa-user v-model=\"formData.{{column.column_name}}\" :multiple=\"{{column.options.multiple|bool}}\" />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'uploadImage' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <sa-image-upload v-model=\"formData.{{column.column_name}}\" :limit=\"{{column.options.limit|formatNumber}}\" :multiple=\"{{column.options.multiple|bool}}\" />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'imagePicker' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <sa-image-picker v-model=\"formData.{{column.column_name}}\" :limit=\"{{column.options.limit|formatNumber}}\" :multiple=\"{{column.options.multiple|bool}}\" />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'uploadFile' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <sa-file-upload v-model=\"formData.{{column.column_name}}\" :limit=\"{{column.options.limit|formatNumber}}\" :multiple=\"{{column.options.multiple|bool}}\" />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'chunkUpload' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <sa-chunk-upload v-model=\"formData.{{column.column_name}}\" :limit=\"{{column.options.limit|formatNumber}}\" :multiple=\"{{column.options.multiple|bool}}\" />\n      </el-form-item>\n{% endif %}\n{% if column.view_type == 'editor' %}\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <sa-editor v-model=\"formData.{{column.column_name}}\" height=\"{{column.options.height}}px\" />\n      </el-form-item>\n{% endif %}\n{% endif %}\n{% endfor %}\n    </el-form>\n    <template #footer>\n      <el-button @click=\"handleClose\">取消</el-button>\n      <el-button type=\"primary\" @click=\"handleSubmit\">提交</el-button>\n    </template>\n{% if component_type == 1 %}\n  </el-dialog>\n{% else %}\n  </el-drawer>\n{% endif %}\n</template>\n\n<script setup lang=\"ts\">\n  import api from '../../../api/{{package_name}}/{{business_name}}'\n  import { ElMessage } from 'element-plus'\n  import type { FormInstance, FormRules } from 'element-plus'\n\n  interface Props {\n    modelValue: boolean\n    dialogType: string\n    data?: Record<string, any>\n  }\n\n  interface Emits {\n    (e: 'update:modelValue', value: boolean): void\n    (e: 'success'): void\n  }\n\n  const props = withDefaults(defineProps<Props>(), {\n    modelValue: false,\n    dialogType: 'add',\n    data: undefined\n  })\n\n  const emit = defineEmits<Emits>()\n\n  const formRef = ref<FormInstance>()\n{% if tpl_category == 'tree' %}\n  const optionData = reactive({\n    treeData: <any[]>[]\n  })\n{% endif %}\n\n  /**\n   * 弹窗显示状态双向绑定\n   */\n  const visible = computed({\n    get: () => props.modelValue,\n    set: (value) => emit('update:modelValue', value)\n  })\n\n  /**\n   * 表单验证规则\n   */\n  const rules = reactive<FormRules>({\n{% for column in columns %}\n{% if column.is_required == 2 and column.is_pk == 1  %}\n    {{column.column_name}}: [{ required: true, message: '{{column.column_comment}}必需填写', trigger: 'blur' }],\n{% endif %}\n{% endfor %}\n  })\n\n  /**\n   * 初始数据\n   */\n  const initialFormData = {\n{% for column in columns %}\n{% if column.is_pk == 2 %}\n    {{column.column_name}}: null,\n{% elseif column.is_insert == 2 %}\n{% if column.column_type == 'int' or column.column_type == 'smallint' or column.column_type == 'tinyint' %}\n    {{column.column_name}}: {{column.default_value | parseNumber}},\n{% elseif column.view_type == 'inputTag' or column.view_type == 'checkbox' or column.options.limit > 1 %}\n    {{column.column_name}}: [],\n{% elseif column.view_type == 'userSelect' or column.view_type == 'date' %}\n    {{column.column_name}}: null,\n{% else %}\n    {{column.column_name}}: '{{column.default_value}}',\n{% endif %}\n{% endif %}\n{% endfor %}\n  }\n\n  /**\n   * 表单数据\n   */\n  const formData = reactive({ ...initialFormData })\n\n  /**\n   * 监听弹窗打开，初始化表单数据\n   */\n  watch(\n    () => props.modelValue,\n    (newVal) => {\n      if (newVal) {\n        initPage()\n      }\n    }\n  )\n\n  /**\n   * 初始化页面数据\n   */\n  const initPage = async () => {\n    // 先重置为初始值\n    Object.assign(formData, initialFormData)\n{% if tpl_category == 'tree' %}\n    // 初始化树形数据\n    const data = await api.list({ tree: true })\n    optionData.treeData = [\n      {\n        id: 0,\n        value: 0,\n        label: '无上级分类',\n        children: data\n      }\n    ]\n{% endif %}\n    // 如果有数据，则填充数据\n    if (props.data) {\n      await nextTick()\n      initForm()\n    }\n  }\n\n  /**\n   * 初始化表单数据\n   */\n  const initForm = () => {\n    if (props.data) {\n      for (const key in formData) {\n        if (props.data[key] != null && props.data[key] != undefined) {\n          ;(formData as any)[key] = props.data[key]\n        }\n      }\n    }\n  }\n\n  /**\n   * 关闭弹窗并重置表单\n   */\n  const handleClose = () => {\n    visible.value = false\n    formRef.value?.resetFields()\n  }\n\n  /**\n   * 提交表单\n   */\n  const handleSubmit = async () => {\n    if (!formRef.value) return\n    try {\n      await formRef.value.validate()\n      if (props.dialogType === 'add') {\n        await api.save(formData)\n        ElMessage.success('新增成功')\n      } else {\n        await api.update(formData)\n        ElMessage.success('修改成功')\n      }\n      emit('success')\n      handleClose()\n    } catch (error) {\n      console.log('表单验证失败:', error)\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/plugin/saiadmin/utils/code/stub/saiadmin/vue/index.stub",
    "content": "<template>\n  <div class=\"art-full-height\">\n    <!-- 搜索面板 -->\n    <TableSearch v-model=\"searchForm\" @search=\"handleSearch\" @reset=\"resetSearchParams\" />\n\n    <ElCard class=\"art-table-card\" shadow=\"never\">\n      <!-- 表格头部 -->\n      <ArtTableHeader v-model:columns=\"columnChecks\" :loading=\"loading\" @refresh=\"refreshData\">\n        <template #left>\n          <ElSpace wrap>\n            <ElButton v-permission=\"'{{namespace}}:{{package_name}}:{{business_name}}:save'\" @click=\"showDialog('add')\" v-ripple>\n              <template #icon>\n                <ArtSvgIcon icon=\"ri:add-fill\" />\n              </template>\n              新增\n            </ElButton>\n            <ElButton\n              v-permission=\"'{{namespace}}:{{package_name}}:{{business_name}}:destroy'\"\n              :disabled=\"selectedRows.length === 0\"\n              @click=\"deleteSelectedRows(api.delete, refreshData)\"\n              v-ripple\n            >\n              <template #icon>\n                <ArtSvgIcon icon=\"ri:delete-bin-5-line\" />\n              </template>\n              删除\n            </ElButton>\n{% if tpl_category == 'tree' %}\n            <ElButton @click=\"toggleExpand\" v-ripple>\n              <template #icon>\n                <ArtSvgIcon v-if=\"isExpanded\" icon=\"ri:collapse-diagonal-line\" />\n                <ArtSvgIcon v-else icon=\"ri:expand-diagonal-line\" />\n              </template>\n              {{ isExpanded ? '收起' : '展开' }}\n            </ElButton>\n{% endif %}\n          </ElSpace>\n        </template>\n      </ArtTableHeader>\n\n      <!-- 表格 -->\n      <ArtTable\n        ref=\"tableRef\"\n        rowKey=\"id\"\n        :loading=\"loading\"\n        :data=\"data\"\n        :columns=\"columns\"\n        :pagination=\"pagination\"\n        @sort-change=\"handleSortChange\"\n        @selection-change=\"handleSelectionChange\"\n        @pagination:size-change=\"handleSizeChange\"\n        @pagination:current-change=\"handleCurrentChange\"\n      >\n        <!-- 操作列 -->\n        <template #operation=\"{ row }\">\n          <div class=\"flex gap-2\">\n            <SaButton\n              v-permission=\"'{{namespace}}:{{package_name}}:{{business_name}}:update'\"\n              type=\"secondary\"\n              @click=\"showDialog('edit', row)\"\n            />\n            <SaButton\n              v-permission=\"'{{namespace}}:{{package_name}}:{{business_name}}:destroy'\"\n              type=\"error\"\n              @click=\"deleteRow(row, api.delete, refreshData)\"\n            />\n          </div>\n        </template>\n      </ArtTable>\n    </ElCard>\n\n    <!-- 编辑弹窗 -->\n    <EditDialog\n      v-model=\"dialogVisible\"\n      :dialog-type=\"dialogType\"\n      :data=\"dialogData\"\n      @success=\"refreshData\"\n    />\n  </div>\n</template>\n\n<script setup lang=\"ts\">\n  import { useTable } from '@/hooks/core/useTable'\n  import { useSaiAdmin } from '@/composables/useSaiAdmin'\n  import api from '../../api/{{package_name}}/{{business_name}}'\n  import TableSearch from './modules/table-search.vue'\n  import EditDialog from './modules/edit-dialog.vue'\n\n{% if tpl_category == 'tree' %}\n  // 状态管理\n  const isExpanded = ref(false)\n  const tableRef = ref()\n{% endif %}\n\n  // 搜索表单\n  const searchForm = ref({\n{% for column in columns %}\n{% if column.is_query == 2 and column.query_type != 'between' %}\n    {{column.column_name}}: undefined,\n{% endif %}\n{% if column.is_query == 2 and column.query_type == 'between' %}\n    {{column.column_name}}: [],\n{% endif %}\n{% endfor %}\n  })\n\n  // 搜索处理\n  const handleSearch = (params: Record<string, any>) => {\n    Object.assign(searchParams, params)\n    getData()\n  }\n\n  // 表格配置\n  const {\n    columns,\n    columnChecks,\n    data,\n    loading,\n    getData,\n    searchParams,\n    pagination,\n    resetSearchParams,\n    handleSortChange,\n    handleSizeChange,\n    handleCurrentChange,\n    refreshData\n  } = useTable({\n    core: {\n      apiFn: api.list,\n      columnsFactory: () => [\n        { type: 'selection' },\n{% for column in columns %}\n{% if column.is_list == 2 %}\n{% if column.view_type == 'uploadImage' or column.view_type == 'imagePicker' %}\n        { prop: '{{column.column_name}}', label: '{{column.column_comment}}', saiType: 'image' },\n{% else %}\n{% if column.is_sort == 2 %}\n{% if column.view_type == 'saSelect' or column.view_type == 'radio' or column.view_type == 'checkbox' %}\n        { prop: '{{column.column_name}}', label: '{{column.column_comment}}', saiType: 'dict', saiDict: '{{column.dict_type}}', sortable: true },\n{% else %}\n        { prop: '{{column.column_name}}', label: '{{column.column_comment}}', sortable: true },\n{% endif %}\n{% else %}\n{% if column.view_type == 'saSelect' or column.view_type == 'radio' or column.view_type == 'checkbox' %}\n        { prop: '{{column.column_name}}', label: '{{column.column_comment}}', saiType: 'dict', saiDict: '{{column.dict_type}}' },\n{% else %}\n        { prop: '{{column.column_name}}', label: '{{column.column_comment}}' },\n{% endif %}\n{% endif %}\n{% endif %}\n{% endif %}\n{% endfor %}\n        { prop: 'operation', label: '操作', width: 100, fixed: 'right', useSlot: true }\n      ]\n    }\n  })\n\n  // 编辑配置\n  const {\n    dialogType,\n    dialogVisible,\n    dialogData,\n    showDialog,\n    deleteRow,\n    deleteSelectedRows,\n    handleSelectionChange,\n    selectedRows\n  } = useSaiAdmin()\n\n{% if tpl_category == 'tree' %}\n  // 切换展开/收起所有菜单\n  const toggleExpand = (): void => {\n    isExpanded.value = !isExpanded.value\n    nextTick(() => {\n      if (tableRef.value?.elTableRef && data.value) {\n        const processRows = (rows: any[]) => {\n          rows.forEach((row) => {\n            if (row.children?.length) {\n              tableRef.value.elTableRef.toggleRowExpansion(row, isExpanded.value)\n              processRows(row.children)\n            }\n          })\n        }\n        processRows(data.value)\n      }\n    })\n  }\n{% endif %}\n</script>\n"
  },
  {
    "path": "src/plugin/saiadmin/utils/code/stub/saiadmin/vue/table-search.stub",
    "content": "<template>\n  <sa-search-bar\n    ref=\"searchBarRef\"\n    v-model=\"formData\"\n    label-width=\"100px\"\n    :showExpand=\"false\"\n    @reset=\"handleReset\"\n    @search=\"handleSearch\"\n    @expand=\"handleExpand\"\n  >\n{% for column in columns %}\n{% if column.is_query == 2 %}\n{% if column.view_type == 'select' %}\n    <el-col v-bind=\"setSpan(6)\">\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <el-select v-model=\"formData.{{column.column_name}}\" :options=\"[]\" placeholder=\"请选择{{column.column_comment}}\" clearable />\n      </el-form-item>\n    </el-col>\n{% endif %}\n{% if column.view_type == 'saSelect' or column.view_type == 'radio' %}\n    <el-col v-bind=\"setSpan(6)\">\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <sa-select v-model=\"formData.{{column.column_name}}\" dict=\"{{column.dict_type}}\" placeholder=\"请选择{{column.column_comment}}\" clearable />\n      </el-form-item>\n    </el-col>\n{% endif %}\n{% if column.view_type == 'cascader' %}\n    <el-col v-bind=\"setSpan(6)\">\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <el-cascader v-model=\"formData.{{column.column_name}}\" :options=\"[]\" placeholder=\"请选择{{column.column_comment}}\" clearable />\n      </el-form-item>\n    </el-col>\n{% endif %}\n{% if column.view_type == 'date' and column.options.mode == 'date' and column.query_type == 'between' %}\n    <el-col v-bind=\"setSpan(6)\">\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <el-date-picker v-model=\"formData.{{column.column_name}}\" type=\"daterange\" start-placeholder=\"开始日期\" end-placeholder=\"结束日期\" value-format=\"YYYY-MM-DD\" clearable />\n      </el-form-item>\n    </el-col>\n{% endif %}\n{% if column.view_type == 'date' and column.options.mode == 'datetime' and column.query_type == 'between' %}\n    <el-col v-bind=\"setSpan(12)\">\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <el-date-picker v-model=\"formData.{{column.column_name}}\" type=\"datetimerange\" start-placeholder=\"开始日期\" end-placeholder=\"结束日期\" value-format=\"YYYY-MM-DD HH:mm:ss\" clearable />\n      </el-form-item>\n    </el-col>\n{% endif %}\n{% if column.view_type == 'date' and column.query_type != 'between' %}\n    <el-col v-bind=\"setSpan(6)\">\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <el-date-picker v-model=\"formData.{{column.column_name}}\" type=\"{{column.options.mode}}\" placeholder=\"请选择{{column.column_comment}}\" value-format=\"YYYY-MM-DD HH:mm:ss\" clearable />\n      </el-form-item>\n    </el-col>\n{% endif %}\n{% if column.view_type != 'select' and column.view_type != 'radio' and column.view_type != 'saSelect' and column.view_type != 'cascader' and column.view_type != 'date'  %}\n    <el-col v-bind=\"setSpan(6)\">\n      <el-form-item label=\"{{column.column_comment}}\" prop=\"{{column.column_name}}\">\n        <el-input v-model=\"formData.{{column.column_name}}\" placeholder=\"请输入{{column.column_comment}}\" clearable />\n      </el-form-item>\n    </el-col>\n{% endif %}\n{% endif %}\n{% endfor %}\n  </sa-search-bar>\n</template>\n\n<script setup lang=\"ts\">\n  interface Props {\n    modelValue: Record<string, any>\n  }\n  interface Emits {\n    (e: 'update:modelValue', value: Record<string, any>): void\n    (e: 'search', params: Record<string, any>): void\n    (e: 'reset'): void\n  }\n  const props = defineProps<Props>()\n  const emit = defineEmits<Emits>()\n  // 展开/收起\n  const isExpanded = ref<boolean>(false)\n\n  // 表单数据双向绑定\n  const searchBarRef = ref()\n  const formData = computed({\n    get: () => props.modelValue,\n    set: (val) => emit('update:modelValue', val)\n  })\n\n  // 重置\n  function handleReset() {\n    searchBarRef.value?.ref.resetFields()\n    emit('reset')\n  }\n\n  // 搜索\n  async function handleSearch() {\n    emit('search', formData.value)\n  }\n\n  // 展开/收起\n  function handleExpand(expanded: boolean) {\n    isExpanded.value = expanded\n  }\n\n  // 栅格占据的列数\n  const setSpan = (span: number) => {\n    return {\n      span: span,\n      xs: 24, // 手机：满宽显示\n      sm: span >= 12 ? span : 12, // 平板：大于等于12保持，否则用半宽\n      md: span >= 8 ? span : 8, // 中等屏幕：大于等于8保持，否则用三分之一宽\n      lg: span,\n      xl: span\n    }\n  }\n</script>\n"
  }
]