1
This commit is contained in:
50
.example.env
Normal file
50
.example.env
Normal file
@ -0,0 +1,50 @@
|
||||
APP_DEBUG=true
|
||||
|
||||
# 后台系统日志开关
|
||||
APP_ADMIN_SYSTEM_LOG=true
|
||||
|
||||
DEFAULT_TIMEZONE=Asia/Shanghai
|
||||
|
||||
DB_TYPE=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_NAME=easyadmin8
|
||||
DB_USER=root
|
||||
DB_PASS=root
|
||||
DB_PORT=3306
|
||||
DB_CHARSET=utf8mb4
|
||||
DB_PREFIX=ea8_
|
||||
|
||||
# 限流器开关 若启动需要配置 Redis 服务
|
||||
RATE_LIMITING_STATUS=false
|
||||
|
||||
# Redis配置
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
REDIS_PREFIX=
|
||||
REDIS_DATABASE=0
|
||||
|
||||
# 后台配置项组
|
||||
[EASYADMIN]
|
||||
|
||||
# 后台地址后缀名称
|
||||
ADMIN=admin
|
||||
|
||||
# 后台登录验证码开关
|
||||
CAPTCHA=false
|
||||
|
||||
# 是否为演示环境
|
||||
IS_DEMO=false
|
||||
|
||||
# CDN配置项组
|
||||
CDN=
|
||||
EXAMPLE=true
|
||||
|
||||
# 是否开启CSRF过滤
|
||||
IS_CSRF=false
|
||||
|
||||
# 静态文件路径前缀
|
||||
STATIC_PATH=/static
|
||||
|
||||
# OSS静态文件路径前缀
|
||||
OSS_STATIC_PREFIX=static_easyadmin
|
||||
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*.js linguist-language=PHP
|
||||
*.css linguist-language=PHP
|
||||
*.html linguist-language=PHP
|
||||
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
*.log
|
||||
.env
|
||||
composer.phar
|
||||
composer.lock
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
/.idea
|
||||
/.vscode
|
||||
/vendor
|
||||
/.settings
|
||||
/.buildpath
|
||||
/.project
|
||||
42
.travis.yml
Normal file
42
.travis.yml
Normal file
@ -0,0 +1,42 @@
|
||||
sudo: false
|
||||
|
||||
language: php
|
||||
|
||||
branches:
|
||||
only:
|
||||
- stable
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache
|
||||
|
||||
before_install:
|
||||
- composer self-update
|
||||
|
||||
install:
|
||||
- composer install --no-dev --no-interaction --ignore-platform-reqs
|
||||
- zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip .
|
||||
- composer require --update-no-dev --no-interaction "topthink/think-image:^1.0"
|
||||
- composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0"
|
||||
- composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0"
|
||||
- composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0"
|
||||
- composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0"
|
||||
- composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0"
|
||||
- composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0"
|
||||
- composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0"
|
||||
- composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0"
|
||||
- zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip .
|
||||
|
||||
script:
|
||||
- php think unit
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw=
|
||||
file:
|
||||
- ThinkPHP_Core.zip
|
||||
- ThinkPHP_Full.zip
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 EasyAdmin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
144
README.md
Normal file
144
README.md
Normal file
@ -0,0 +1,144 @@
|
||||
<div align="center" dir="auto">
|
||||
<img alt="log" src="public/static/common/images/logo-8.png" />
|
||||
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/php-%3E=8.1.0-brightgreen.svg?style=for-the-badge&logo=php&colorB=ff69b4" alt="php">
|
||||
<img src="https://img.shields.io/badge/mysql-%3E=5.7-brightgreen.svg?style=for-the-badge&logo=mysql&colorB=blue" alt="MySQL">
|
||||
<img src="https://img.shields.io/badge/thinkphp-%3E=8.0.0-brightgreen.svg?style=for-the-badge&logo=thinkphp" alt="ThinkPHP">
|
||||
<img src="https://img.shields.io/badge/layui-%3E=2.9.0-brightgreen.svg?style=for-the-badge&logo=layui&colorB=orange" alt="layui">
|
||||
<img src="https://img.shields.io/badge/license-MIT-green?style=for-the-badge&logo=license&colorB=purple" alt="License">
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
## `EasyAdmin8`所有版本 (当前项目为`ThinkPHP`版本)
|
||||
|
||||
| | Github | Gitee |
|
||||
|----------|:----------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|
||||
| ThinkPHP | [EasyAdmin8](https://github.com/EasyAdmin8/EasyAdmin8) | [EasyAdmin8](https://gitee.com/EasyAdmin8/EasyAdmin8) |
|
||||
| Laravel | [EasyAdmin8-Laravel](https://github.com/EasyAdmin8/EasyAdmin8-Laravel) | [EasyAdmin8-Laravel](https://gitee.com/EasyAdmin8/EasyAdmin8-Laravel) |
|
||||
| webman | [EasyAdmin8-webman](https://github.com/EasyAdmin8/EasyAdmin8-webman) | [EasyAdmin8-webman](https://gitee.com/EasyAdmin8/EasyAdmin8-webman) |
|
||||
|
||||
## 项目介绍
|
||||
|
||||
> `EasyAdmin8` 在 [`EasyAdmin`](https://gitee.com/zhongshaofa/easyadmin) 的基础上更新 ThinkPHP 框架到 8.1+ ,PHP 最低版本要求不低于 8.1
|
||||
>
|
||||
> 2025年起 `PHP` 版本要求提升到 `8.1+`, 如果需要 `8.0` 到分支 `v8.0` 中下载
|
||||
>
|
||||
> ThinkPHP v8.1+ 和 Layui v2.9.x 的快速开发的后台管理系统。
|
||||
>
|
||||
> 项目地址:[http://easyadmin8.top](http://easyadmin8.top)
|
||||
>
|
||||
> 演示地址:[http://thinkphp.easyadmin8.top/admin](http://thinkphp.easyadmin8.top/admin)
|
||||
>
|
||||
> 如果您之前已经用过 `FastAdmin` 或者 `EasyAdmin` , 那么入手 `EasyAdmin8` 将会更加轻松
|
||||
>
|
||||
>【如果不能访问,可以自行本地搭建预览或参考下方界面预览图】
|
||||
|
||||
## 大版本更新记录:
|
||||
|
||||
[更新记录](log.md)
|
||||
|
||||
## 安装教程
|
||||
|
||||
> EasyAdmin8 使用 Composer 来管理项目依赖。因此,在使用 EasyAdmin8 之前,请确保你的机器已经安装了 Composer。
|
||||
|
||||
### 通过一键安装命令
|
||||
|
||||
```
|
||||
if [ -f /usr/bin/curl ];then curl -sSO https://easyadmin8.top/auto-install-EasyAdmin8.sh;else wget -O auto-install-EasyAdmin8.sh https://easyadmin8.top/auto-install-EasyAdmin8.sh;fi;bash auto-install-EasyAdmin8.sh
|
||||
```
|
||||
|
||||
### 通过`git`下载安装包,`composer`安装依赖包
|
||||
|
||||
```
|
||||
1.下载安装包
|
||||
|
||||
git clone https://github.com/EasyAdmin8/EasyAdmin8
|
||||
|
||||
或者
|
||||
|
||||
git clone https://gitee.com/EasyAdmin8/EasyAdmin8
|
||||
|
||||
2.安装依赖包(确保 PHP 版本 >= 8.1)
|
||||
|
||||
在根目录下 composer install ,如果有报错信息可以使用命令 composer install --ignore-platform-reqs
|
||||
|
||||
3. 拷贝 .example.env 文件重命名为 .env ,命令 cp .example.env .env ,修改数据库账号密码参数
|
||||
|
||||
4.配置伪静态(以 Nginx 为例)
|
||||
|
||||
location / {
|
||||
if ( !-e $request_filename){
|
||||
rewrite ^/(.*)$ /index.php?s=$1 last;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## CURD命令大全
|
||||
|
||||
> 参考 [CURD命令大全](https://edocs.easyadmin8.top/curd/command.html)
|
||||
|
||||
## 常见问题
|
||||
|
||||
> 参考 [常见问题](https://easyadmin8.top/guide/question.html)
|
||||
|
||||
## 界面预览
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## 交流群
|
||||
|
||||
<center>
|
||||
|
||||

|
||||
|
||||
</center>
|
||||
|
||||
## 相关文档
|
||||
|
||||
* [ThinkPHP 8.1](https://doc.thinkphp.cn)
|
||||
|
||||
* [EasyAdmin](http://easyadmin.99php.cn/docs)
|
||||
|
||||
* [Layui 2.9.x](https://layui.dev/docs)
|
||||
|
||||
* [Layuimini](https://github.com/zhongshaofa/layuimini)
|
||||
|
||||
* [Annotations](https://github.com/doctrine/annotations)
|
||||
|
||||
* [Jquery](https://github.com/jquery/jquery)
|
||||
|
||||
* [RequireJs](https://github.com/requirejs/requirejs)
|
||||
|
||||
* [CKEditor](https://github.com/ckeditor/ckeditor4)
|
||||
|
||||
* [Echarts](https://github.com/apache/incubator-echarts)
|
||||
|
||||
* [UEditorPlus](https://github.com/modstart-lib/ueditor-plus)
|
||||
|
||||
* [wangEditor](https://github.com/wangeditor-team/wangEditor)
|
||||
|
||||
## 免责声明
|
||||
|
||||
> 所有协议遵循 [`EasyAdmin`](https://gitee.com/zhongshaofa/easyadmin)
|
||||
>
|
||||
> 任何用户在使用 `EasyAdmin8` 后台框架前,请您仔细阅读并透彻理解本声明。您可以选择不使用`EasyAdmin8`后台框架,若您一旦使用`EasyAdmin8`后台框架,您的使用行为即被视为对本声明全部内容的认可和接受。
|
||||
|
||||
* `EasyAdmin8`后台框架是一款开源免费的后台快速开发框架 ,主要用于更便捷地开发后台管理;其尊重并保护所有用户的个人隐私权,不窃取任何用户计算机中的信息。更不具备用户数据存储等网络传输功能。
|
||||
|
||||
* 您承诺秉着合法、合理的原则使用`EasyAdmin8`后台框架,不利用`EasyAdmin8`后台框架进行任何违法、侵害他人合法利益等恶意的行为,亦不将`EasyAdmin8`后台框架运用于任何违反我国法律法规的 Web 平台。
|
||||
|
||||
* 任何单位或个人因下载使用`EasyAdmin8`后台框架而产生的任何意外、疏忽、合约毁坏、诽谤、版权或知识产权侵犯及其造成的损失 (包括但不限于直接、间接、附带或衍生的损失等),本开源项目不承担任何法律责任。
|
||||
|
||||
* 用户明确并同意本声明条款列举的全部内容,对使用`EasyAdmin8`后台框架可能存在的风险和相关后果将完全由用户自行承担,本开源项目不承担任何法律责任。
|
||||
|
||||
* 任何单位或个人在阅读本免责声明后,应在《MIT 开源许可证》所允许的范围内进行合法的发布、传播和使用`EasyAdmin8`后台框架等行为,若违反本免责声明条款或违反法律法规所造成的法律责任(包括但不限于民事赔偿和刑事责任),由违约者自行承担。
|
||||
|
||||
* 如果本声明的任何部分被认为无效或不可执行,其余部分仍具有完全效力。不可执行的部分声明,并不构成我们放弃执行该声明的权利。
|
||||
|
||||
* 本开源项目有权随时对本声明条款及附件内容进行单方面的变更,并以消息推送、网页公告等方式予以公布,公布后立即自动生效,无需另行单独通知;若您在本声明内容公告变更后继续使用的,表示您已充分阅读、理解并接受修改后的声明内容。
|
||||
1
app/.htaccess
Normal file
1
app/.htaccess
Normal file
@ -0,0 +1 @@
|
||||
deny from all
|
||||
22
app/AppService.php
Normal file
22
app/AppService.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace app;
|
||||
|
||||
use think\Service;
|
||||
|
||||
/**
|
||||
* 应用服务类
|
||||
*/
|
||||
class AppService extends Service
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
// 服务注册
|
||||
}
|
||||
|
||||
public function boot()
|
||||
{
|
||||
// 服务启动
|
||||
}
|
||||
}
|
||||
94
app/BaseController.php
Normal file
94
app/BaseController.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace app;
|
||||
|
||||
use think\App;
|
||||
use think\exception\ValidateException;
|
||||
use think\Validate;
|
||||
|
||||
/**
|
||||
* 控制器基础类
|
||||
*/
|
||||
abstract class BaseController
|
||||
{
|
||||
/**
|
||||
* Request实例
|
||||
* @var \think\Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* 应用实例
|
||||
* @var \think\App
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
/**
|
||||
* 是否批量验证
|
||||
* @var bool
|
||||
*/
|
||||
protected $batchValidate = false;
|
||||
|
||||
/**
|
||||
* 控制器中间件
|
||||
* @var array
|
||||
*/
|
||||
protected $middleware = [];
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
* @access public
|
||||
* @param App $app 应用对象
|
||||
*/
|
||||
public function __construct(App $app)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->request = $this->app->request;
|
||||
|
||||
// 控制器初始化
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
// 初始化
|
||||
protected function initialize()
|
||||
{}
|
||||
|
||||
/**
|
||||
* 验证数据
|
||||
* @access protected
|
||||
* @param array $data 数据
|
||||
* @param string|array $validate 验证器名或者验证规则数组
|
||||
* @param array $message 提示信息
|
||||
* @param bool $batch 是否批量验证
|
||||
* @return array|string|true
|
||||
* @throws ValidateException
|
||||
*/
|
||||
protected function validate(array $data, string|array $validate, array $message = [], bool $batch = false)
|
||||
{
|
||||
if (is_array($validate)) {
|
||||
$v = new Validate();
|
||||
$v->rule($validate);
|
||||
} else {
|
||||
if (strpos($validate, '.')) {
|
||||
// 支持场景
|
||||
[$validate, $scene] = explode('.', $validate);
|
||||
}
|
||||
$class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate);
|
||||
$v = new $class();
|
||||
if (!empty($scene)) {
|
||||
$v->scene($scene);
|
||||
}
|
||||
}
|
||||
|
||||
$v->message($message);
|
||||
|
||||
// 是否批量验证
|
||||
if ($batch || $this->batchValidate) {
|
||||
$v->batch(true);
|
||||
}
|
||||
|
||||
return $v->failException(true)->check($data);
|
||||
}
|
||||
|
||||
}
|
||||
58
app/ExceptionHandle.php
Normal file
58
app/ExceptionHandle.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
namespace app;
|
||||
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
use think\exception\Handle;
|
||||
use think\exception\HttpException;
|
||||
use think\exception\HttpResponseException;
|
||||
use think\exception\ValidateException;
|
||||
use think\Response;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* 应用异常处理类
|
||||
*/
|
||||
class ExceptionHandle extends Handle
|
||||
{
|
||||
/**
|
||||
* 不需要记录信息(日志)的异常类列表
|
||||
* @var array
|
||||
*/
|
||||
protected $ignoreReport = [
|
||||
HttpException::class,
|
||||
HttpResponseException::class,
|
||||
ModelNotFoundException::class,
|
||||
DataNotFoundException::class,
|
||||
ValidateException::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* 记录异常信息(包括日志或者其它方式记录)
|
||||
*
|
||||
* @access public
|
||||
* @param Throwable $exception
|
||||
* @return void
|
||||
*/
|
||||
public function report(Throwable $exception): void
|
||||
{
|
||||
// 使用内置的方式记录异常日志
|
||||
parent::report($exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an exception into an HTTP response.
|
||||
*
|
||||
* @access public
|
||||
* @param \think\Request $request
|
||||
* @param Throwable $e
|
||||
* @return Response
|
||||
*/
|
||||
public function render($request, Throwable $e): Response
|
||||
{
|
||||
// 添加自定义异常处理机制
|
||||
|
||||
// 其他错误交给系统处理
|
||||
return parent::render($request, $e);
|
||||
}
|
||||
}
|
||||
85
app/LangService.php
Normal file
85
app/LangService.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace app;
|
||||
|
||||
use enums\LangEnum;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class LangService
|
||||
{
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getAllowLang(): array
|
||||
{
|
||||
return array_map(function ($item) {
|
||||
return $item->value;
|
||||
}, array_filter(LangEnum::cases(), function ($item) {
|
||||
return $item->is_open();
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getLangCases(): array
|
||||
{
|
||||
return array_map(function ($item) {
|
||||
return [
|
||||
'value' => $item->value,
|
||||
'lang' => $item->lang(),
|
||||
'label' => $item->label()
|
||||
];
|
||||
}, array_filter(LangEnum::cases(), function ($item) {
|
||||
return $item->is_open();
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getLabelCases(): array
|
||||
{
|
||||
return array_map(function ($item) {
|
||||
return [
|
||||
'value' => $item->value,
|
||||
'label' => $item->label()
|
||||
];
|
||||
}, array_filter(LangEnum::cases(), function ($item) {
|
||||
return $item->is_open();
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getLangExtend(): array
|
||||
{
|
||||
$extend = [];
|
||||
foreach (array_filter(LangEnum::cases(), function ($item) {
|
||||
return $item->is_open();
|
||||
}) as $item) {
|
||||
$extend[$item->value] = $item->extend();
|
||||
}
|
||||
return $extend;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getAcceptLang(): array
|
||||
{
|
||||
$extend = [];
|
||||
foreach (array_filter(LangEnum::cases(), function ($item) {
|
||||
return $item->is_open();
|
||||
}) as $item) {
|
||||
foreach ($item->getLangList() as $langListitem) {
|
||||
$extend[$langListitem] = $item->value;
|
||||
}
|
||||
}
|
||||
return $extend;
|
||||
}
|
||||
}
|
||||
8
app/Request.php
Normal file
8
app/Request.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
namespace app;
|
||||
|
||||
// 应用请求对象类
|
||||
class Request extends \think\Request
|
||||
{
|
||||
|
||||
}
|
||||
36
app/admin/config/admin.php
Normal file
36
app/admin/config/admin.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
// 后台路径地址 默认 admin
|
||||
'alias_name' => env('EASYADMIN.ADMIN'),
|
||||
|
||||
// 不需要验证权限的控制器
|
||||
'no_auth_controller' => [
|
||||
'ajax',
|
||||
'login',
|
||||
'index',
|
||||
],
|
||||
|
||||
// 不需要验证权限的节点
|
||||
'no_auth_node' => [
|
||||
'login/index',
|
||||
'login/out',
|
||||
],
|
||||
|
||||
//上传类型
|
||||
'upload_types' => [
|
||||
'local' => '本地存储',
|
||||
'oss' => '阿里云oss',
|
||||
'cos' => '腾讯云cos',
|
||||
'qnoss' => '七牛云'
|
||||
],
|
||||
|
||||
// 默认编辑器
|
||||
'editor_types' => [
|
||||
'ueditor' => '百度编辑器(不建议使用)',
|
||||
'ckeditor' => 'CK编辑器',
|
||||
'wangEditor' => 'wangEditor(推荐使用)',
|
||||
'EasyMDE' => 'EasyMDE(markdown)',
|
||||
],
|
||||
|
||||
];
|
||||
31
app/admin/config/route.php
Normal file
31
app/admin/config/route.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use app\admin\middleware\CheckInstall;
|
||||
use app\admin\middleware\CheckLogin;
|
||||
use app\admin\middleware\CheckAuth;
|
||||
use app\admin\middleware\SystemLog;
|
||||
use app\admin\middleware\RateLimiting;
|
||||
|
||||
// 你可以在这里继续写你需要的路由
|
||||
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | 这里只是路由的中间件
|
||||
// | 至于为什么要把中间件配置写在这里呢??? Why???
|
||||
// | 因为 ThinkPHP官方最新版本 已经不支持在中间件获取 controller 和 action 了
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
return [
|
||||
'middleware' => [
|
||||
// 限流中间件
|
||||
RateLimiting::class,
|
||||
// 判断是否已经安装后台系统
|
||||
// CheckInstall::class,
|
||||
// 检测是否登录
|
||||
CheckLogin::class,
|
||||
// 操作日志
|
||||
SystemLog::class,
|
||||
// 验证节点权限
|
||||
CheckAuth::class,
|
||||
],
|
||||
];
|
||||
247
app/admin/controller/Ajax.php
Normal file
247
app/admin/controller/Ajax.php
Normal file
@ -0,0 +1,247 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\admin\model\SystemUploadfile;
|
||||
use app\admin\service\UploadService;
|
||||
use app\common\controller\AdminController;
|
||||
use app\common\service\MenuService;
|
||||
use app\Request;
|
||||
use Kaadon\Helper\GdImageHelper;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
use think\db\Query;
|
||||
use think\facade\Cache;
|
||||
use think\file\UploadedFile;
|
||||
use think\response\Json;
|
||||
|
||||
class Ajax extends AdminController
|
||||
{
|
||||
|
||||
/**
|
||||
* 初始化后台接口地址
|
||||
* @return Json
|
||||
* @throws DataNotFoundException
|
||||
* @throws DbException
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
public function initAdmin(): Json
|
||||
{
|
||||
$cacheData = Cache::get('initAdmin_' . $this->adminUid);
|
||||
if (!empty($cacheData)) {
|
||||
return json($cacheData);
|
||||
}
|
||||
$menuService = new MenuService($this->adminUid);
|
||||
$data = [
|
||||
'logoInfo' => [
|
||||
'title' => sysConfig('site', 'logo_title'),
|
||||
'image' => sysConfig('site', 'logo_image'),
|
||||
'href' => __url('index/index'),
|
||||
],
|
||||
'homeInfo' => $menuService->getHomeInfo(),
|
||||
'menuInfo' => $menuService->getMenuTree(),
|
||||
];
|
||||
Cache::tag('initAdmin')->set('initAdmin_' . $this->adminUid, $data);
|
||||
return json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理缓存接口
|
||||
*/
|
||||
public function clearCache(): void
|
||||
{
|
||||
Cache::clear();
|
||||
$this->success('清理缓存成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
* @param Request $request
|
||||
* @return Json|null
|
||||
* @throws \Kaadon\Helper\HelperException
|
||||
*/
|
||||
public function upload(Request $request): Json|null
|
||||
{
|
||||
$this->isDemo && $this->error('演示环境下不允许修改');
|
||||
$this->checkPostRequest();
|
||||
$type = $request->param('type', '');
|
||||
$file = $request->file($type == 'editor' ? 'upload' : 'file');
|
||||
if (config('filesystem.image.to_webp', true) && GdImageHelper::isSupportSuffix($file->extension())) {
|
||||
(new GdImageHelper($file->getRealPath(), $file->extension()))->convertTo($file->getRealPath(),'webp');
|
||||
$file = new UploadedFile($file->getRealPath(), date("YmdHis") . "_" . md5($file->getOriginalName()) . '.webp');
|
||||
$file->setExtension('webp');
|
||||
}
|
||||
$data = [
|
||||
'upload_type' => $request->post('upload_type'),
|
||||
'file' => $file,
|
||||
];
|
||||
$uploadConfig = sysConfig('upload');
|
||||
empty($data['upload_type']) && $data['upload_type'] = $uploadConfig['upload_type'];
|
||||
$rule = [
|
||||
'upload_type|指定上传类型有误' => "in:{$uploadConfig['upload_allow_type']}",
|
||||
'file|文件' => "require|file|fileExt:{$uploadConfig['upload_allow_ext']},webp|fileSize:{$uploadConfig['upload_allow_size']}",
|
||||
];
|
||||
$this->validate($data, $rule);
|
||||
$upload_type = $uploadConfig['upload_type'];
|
||||
try {
|
||||
$upload = UploadService::instance()->setConfig($uploadConfig)->$upload_type($data['file'], $type);
|
||||
}catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
$code = $upload['code'] ?? 0;
|
||||
if ($code == 0) {
|
||||
$this->error($upload['data'] ?? '');
|
||||
}else {
|
||||
if ($type == 'editor') {
|
||||
return json(
|
||||
[
|
||||
'error' => ['message' => '上传成功', 'number' => 201,],
|
||||
'fileName' => '',
|
||||
'uploaded' => 1,
|
||||
'url' => $upload['data']['url'] ?? '',
|
||||
]
|
||||
);
|
||||
}else {
|
||||
$this->success('上传成功', $upload['data'] ?? '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上传文件列表
|
||||
* @param Request $request
|
||||
* @return Json
|
||||
* @throws DataNotFoundException
|
||||
* @throws DbException
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
public function getUploadFiles(Request $request): Json
|
||||
{
|
||||
$get = $request->get();
|
||||
$page = !empty($get['page']) ? $get['page'] : 1;
|
||||
$limit = !empty($get['limit']) ? $get['limit'] : 10;
|
||||
$title = !empty($get['title']) ? $get['title'] : null;
|
||||
$count = SystemUploadfile::where(function(Query $query) use ($title) {
|
||||
!empty($title) && $query->where('original_name', 'like', "%{$title}%");
|
||||
})
|
||||
->count();
|
||||
$list = SystemUploadfile::where(function(Query $query) use ($title) {
|
||||
!empty($title) && $query->where('original_name', 'like', "%{$title}%");
|
||||
})
|
||||
->page($page, $limit)
|
||||
->order($this->sort)
|
||||
->select()->toArray();
|
||||
$data = [
|
||||
'code' => 0,
|
||||
'msg' => '',
|
||||
'count' => $count,
|
||||
'data' => $list,
|
||||
];
|
||||
return json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 百度编辑器上传
|
||||
* @param Request $request
|
||||
* @return Json
|
||||
* @throws DataNotFoundException
|
||||
* @throws DbException
|
||||
* @throws ModelNotFoundException
|
||||
* @throws \Kaadon\Helper\HelperException
|
||||
*/
|
||||
public function uploadUEditor(Request $request): Json
|
||||
{
|
||||
$uploadConfig = sysConfig('upload');
|
||||
$upload_allow_size = $uploadConfig['upload_allow_size'];
|
||||
$_upload_allow_ext = explode(',', $uploadConfig['upload_allow_ext']);
|
||||
$upload_allow_ext = [];
|
||||
array_map(function($value) use (&$upload_allow_ext) {
|
||||
$upload_allow_ext[] = '.' . $value;
|
||||
}, $_upload_allow_ext);
|
||||
$config = [
|
||||
// 上传图片配置项
|
||||
"imageActionName" => "image",
|
||||
"imageFieldName" => "file",
|
||||
"imageMaxSize" => $upload_allow_size,
|
||||
"imageAllowFiles" => $upload_allow_ext,
|
||||
"imageCompressEnable" => true,
|
||||
"imageCompressBorder" => 5000,
|
||||
"imageInsertAlign" => "none",
|
||||
"imageUrlPrefix" => "",
|
||||
// 列出图片
|
||||
"imageManagerActionName" => "listImage",
|
||||
"imageManagerListSize" => 20,
|
||||
"imageManagerUrlPrefix" => "",
|
||||
"imageManagerInsertAlign" => "none",
|
||||
"imageManagerAllowFiles" => $upload_allow_ext,
|
||||
// 上传 video
|
||||
"videoActionName" => "video",
|
||||
"videoFieldName" => "file",
|
||||
"videoUrlPrefix" => "",
|
||||
"videoMaxSize" => $upload_allow_size,
|
||||
"videoAllowFiles" => $upload_allow_ext,
|
||||
// 上传 附件
|
||||
"fileActionName" => "attachment",
|
||||
"fileFieldName" => "file",
|
||||
"fileMaxSize" => $upload_allow_size,
|
||||
"fileAllowFiles" => $upload_allow_ext,
|
||||
];
|
||||
$action = $request->param('action/s', '');
|
||||
$file = $request->file('file');
|
||||
$upload_type = $uploadConfig['upload_type'];
|
||||
switch ($action) {
|
||||
case 'image':
|
||||
if (config('filesystem.image.to_webp', true) && GdImageHelper::isSupportSuffix($file->extension())) {
|
||||
(new GdImageHelper($file->getRealPath(), $file->extension()))->convertTo($file->getRealPath(),'webp');
|
||||
$file = new UploadedFile($file->getRealPath(), date("YmdHis") . "_" . md5($file->getOriginalName()) . '.webp');
|
||||
$file->setExtension('webp');
|
||||
}
|
||||
case 'attachment':
|
||||
case 'video':
|
||||
if ($this->isDemo) return json(['state' => '演示环境下不允许修改']);
|
||||
try {
|
||||
$upload = UploadService::instance()->setConfig($uploadConfig)->$upload_type($file);
|
||||
$code = $upload['code'] ?? 0;
|
||||
if ($code == 0) {
|
||||
return json(['state' => $upload['data'] ?? '上传错误信息']);
|
||||
}else {
|
||||
return json(['state' => 'SUCCESS', 'url' => $upload['data']['url'] ?? '']);
|
||||
}
|
||||
}catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
break;
|
||||
case 'listImage':
|
||||
$list = (new SystemUploadfile())->order($this->sort)->limit(100)->field('url')->select()->toArray();
|
||||
$result = [
|
||||
"state" => "SUCCESS",
|
||||
"list" => $list,
|
||||
"total" => 0,
|
||||
"start" => 0,
|
||||
];
|
||||
return json($result);
|
||||
default:
|
||||
return json($config);
|
||||
}
|
||||
}
|
||||
|
||||
public function composerInfo(): Json
|
||||
{
|
||||
$lockFilePath = root_path() . '/composer.lock';
|
||||
$list = [];
|
||||
if (file_exists($lockFilePath)) {
|
||||
$lockFileContent = file_get_contents($lockFilePath);
|
||||
if ($lockFileContent !== false) {
|
||||
$lockData = json_decode($lockFileContent, true);
|
||||
if (!empty($lockData['packages'])) {
|
||||
foreach ($lockData['packages'] as $package) {
|
||||
$list[] = ['name' => $package['name'], 'version' => $package['version']];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->success('success', $list);
|
||||
}
|
||||
|
||||
}
|
||||
198
app/admin/controller/Index.php
Normal file
198
app/admin/controller/Index.php
Normal file
@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\admin\model\MallOrder;
|
||||
use app\admin\model\SystemAdmin;
|
||||
use app\admin\model\SystemQuick;
|
||||
use app\common\controller\AdminController;
|
||||
use app\Request;
|
||||
use Exception;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
use think\facade\Db;
|
||||
|
||||
class Index extends AdminController
|
||||
{
|
||||
|
||||
/**
|
||||
* 后台主页
|
||||
* @param Request $request
|
||||
* @return string
|
||||
*/
|
||||
public function index(Request $request): string
|
||||
{
|
||||
return $this->fetch('', ['admin' => $request->adminUserInfo,]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台欢迎页
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function welcome(): string
|
||||
{
|
||||
$tpVersion = \think\facade\App::version();
|
||||
$mysqlVersion = Db::query("select version() as version")[0]['version'] ?? '未知';
|
||||
$phpVersion = phpversion();
|
||||
$versions = compact('tpVersion', 'mysqlVersion', 'phpVersion');
|
||||
$quick_list = SystemQuick::field('id,title,icon,href')
|
||||
->where(['status' => 1])->order('sort', 'desc')->limit(50)->select()->toArray();
|
||||
$quicks = array_chunk($quick_list, 8);
|
||||
$this->assign(compact('quicks', 'versions'));
|
||||
$data = [
|
||||
'all_member'=>[
|
||||
"title"=>"今日订单",
|
||||
"data"=>(new MallOrder())->where([['create_time','>',strtotime(date('Y-m-d')." 06:00:00")]])->count(),
|
||||
],
|
||||
'login_member'=>[
|
||||
"title"=>"今日金额",
|
||||
"data"=> (new MallOrder())->where([['create_time','>',strtotime(date('Y-m-d')." 06:00:00")]])->sum('money'),
|
||||
],
|
||||
'recharge_member'=>[
|
||||
"title"=>"今日已支付",
|
||||
"data"=>(new MallOrder())->where([['create_time','>',strtotime(date('Y-m-d')." 06:00:00")],['status','=',2]])->count(),
|
||||
],
|
||||
|
||||
'all_charge'=>[
|
||||
"title"=>"今日已支付金额",
|
||||
"data"=>(new MallOrder())->where([['create_time','>',strtotime(date('Y-m-d')." 06:00:00")],['status','=',2]])->sum('money'),
|
||||
|
||||
],
|
||||
'day_charge'=>[
|
||||
"title"=>"待处理订单",
|
||||
"data"=>(new MallOrder())->where([['create_time','>',strtotime(date('Y-m-d')." 06:00:00")],['status','=',0]])->count(),
|
||||
|
||||
],
|
||||
'recharge_number'=>[
|
||||
"title"=>"总订单数",
|
||||
"data"=>(new MallOrder())->count(),
|
||||
],
|
||||
];
|
||||
$this->assign('data', $data);
|
||||
return $this->fetch();
|
||||
}
|
||||
public function get_order()
|
||||
{
|
||||
$this->success('操作成功', [
|
||||
'count'=>(new MallOrder())->where([['create_time','>',strtotime(date('Y-m-d')." 06:00:00")],['status','=',0]])->count(),
|
||||
'rid'=>(new MallOrder())->where([['create_time','>',strtotime(date('Y-m-d')." 06:00:00")],['status','=',0]])
|
||||
->order('id desc')->value('id')
|
||||
]);
|
||||
}
|
||||
/**
|
||||
* 修改管理员信息
|
||||
* @param Request $request
|
||||
* @return string
|
||||
* @throws DataNotFoundException
|
||||
* @throws DbException
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
public function editAdmin(Request $request): string
|
||||
{
|
||||
$id = $this->adminUid;
|
||||
$row = (new SystemAdmin())
|
||||
->withoutField('password')
|
||||
->find($id);
|
||||
empty($row) && $this->error('用户信息不存在');
|
||||
if ($request->isPost()) {
|
||||
$post = $request->post();
|
||||
$this->isDemo && $this->error('演示环境下不允许修改');
|
||||
$rule = [];
|
||||
$this->validate($post, $rule);
|
||||
try {
|
||||
$login_type = $post['login_type'] ?? 1;
|
||||
if ($login_type == 2) {
|
||||
$ga_secret = (new SystemAdmin())->where('id', $id)->value('ga_secret');
|
||||
if (empty($ga_secret)) $this->error('请先绑定谷歌验证器');
|
||||
}
|
||||
$save = $row->allowField(['head_img', 'phone', 'remark', 'update_time', 'login_type'])->save($post);
|
||||
}catch (\PDOException $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
$save ? $this->success('保存成功') : $this->error('保存失败');
|
||||
}
|
||||
$this->assign('row', $row);
|
||||
$notes = (new SystemAdmin())->notes;
|
||||
$this->assign('notes', $notes);
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @param Request $request
|
||||
* @return string
|
||||
*/
|
||||
public function editPassword(Request $request): string
|
||||
{
|
||||
$id = $this->adminUid;
|
||||
$row = (new SystemAdmin())
|
||||
->withoutField('password')
|
||||
->find($id);
|
||||
if (!$row) {
|
||||
$this->error('用户信息不存在');
|
||||
}
|
||||
if ($request->isPost()) {
|
||||
$post = $request->post();
|
||||
$this->isDemo && $this->error('演示环境下不允许修改');
|
||||
$rule = [
|
||||
'password|登录密码' => 'require',
|
||||
'password_again|确认密码' => 'require',
|
||||
];
|
||||
$this->validate($post, $rule);
|
||||
if ($post['password'] != $post['password_again']) {
|
||||
$this->error('两次密码输入不一致');
|
||||
}
|
||||
|
||||
try {
|
||||
$save = $row->save([
|
||||
'password' => password_hash($post['password'], PASSWORD_DEFAULT),
|
||||
]);
|
||||
}catch (Exception $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
if ($save) {
|
||||
$this->success('保存成功');
|
||||
}else {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
}
|
||||
$this->assign('row', $row);
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置谷歌验证码
|
||||
* @param Request $request
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function set2fa(Request $request): string
|
||||
{
|
||||
$id = $this->adminUid;
|
||||
$row = (new SystemAdmin())->withoutField('password')->find($id);
|
||||
if (!$row) $this->error('用户信息不存在');
|
||||
// You can see: https://gitee.com/wolf-code/authenticator
|
||||
$ga = new \Wolfcode\Authenticator\google\PHPGangstaGoogleAuthenticator();
|
||||
if (!$request->isAjax()) {
|
||||
$old_secret = $row->ga_secret;
|
||||
$secret = $ga->createSecret(32);
|
||||
$ga_title = $this->isDemo ? 'EasyAdmin8演示环境' : '可自定义修改显示标题';
|
||||
$dataUri = $ga->getQRCode($ga_title, $secret);
|
||||
$this->assign(compact('row', 'dataUri', 'old_secret', 'secret'));
|
||||
return $this->fetch();
|
||||
}
|
||||
$this->isDemo && $this->error('演示环境下不允许修改');
|
||||
$post = $request->post();
|
||||
$ga_secret = $post['ga_secret'] ?? '';
|
||||
$ga_code = $post['ga_code'] ?? '';
|
||||
if (empty($ga_code)) $this->error('请输入验证码');
|
||||
if (!$ga->verifyCode($ga_secret, $ga_code)) $this->error('验证码错误');
|
||||
$row->ga_secret = $ga_secret;
|
||||
$row->login_type = 2;
|
||||
$row->save();
|
||||
$this->success('操作成功');
|
||||
}
|
||||
|
||||
}
|
||||
93
app/admin/controller/Login.php
Normal file
93
app/admin/controller/Login.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\admin\model\SystemAdmin;
|
||||
use app\common\controller\AdminController;
|
||||
use app\common\utils\Helper;
|
||||
use think\captcha\facade\Captcha;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
use app\Request;
|
||||
use think\Response;
|
||||
use Wolfcode\RateLimiting\Attributes\RateLimitingMiddleware;
|
||||
|
||||
class Login extends AdminController
|
||||
{
|
||||
|
||||
protected bool $ignoreLogin = true;
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
$action = $this->request->action();
|
||||
if (!empty($this->adminUid) && !in_array($action, ['out'])) {
|
||||
$adminModuleName = config('admin.alias_name');
|
||||
$this->success('已登录,无需再次登录', [], __url("@{$adminModuleName}"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @param Request $request
|
||||
* @return string
|
||||
* @throws DataNotFoundException
|
||||
* @throws DbException
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
#[RateLimitingMiddleware(key: [Helper::class, 'getIp'], seconds: 1, limit: 1, message: '请求过于频繁')]
|
||||
public function index(Request $request): string
|
||||
{
|
||||
$captcha = env('EASYADMIN.CAPTCHA', 1);
|
||||
if (!$request->isPost()) return $this->fetch('', compact('captcha'));
|
||||
$post = $request->post();
|
||||
$rule = [
|
||||
'username|用户名' => 'require',
|
||||
'password|密码' => 'require',
|
||||
'keep_login|是否保持登录' => 'require',
|
||||
];
|
||||
$captcha == 1 && $rule['captcha|验证码'] = 'require|captcha';
|
||||
$this->validate($post, $rule);
|
||||
$admin = SystemAdmin::where(['username' => $post['username']])->find();
|
||||
if (empty($admin)) {
|
||||
$this->error('用户不存在');
|
||||
}
|
||||
if (!password_verify($post['password'], $admin->password)) {
|
||||
$this->error('密码输入有误');
|
||||
}
|
||||
if ($admin->status == 0) {
|
||||
$this->error('账号已被禁用');
|
||||
}
|
||||
if ($admin->login_type == 2) {
|
||||
if (empty($post['ga_code'])) $this->error('请输入谷歌验证码', ['is_ga_code' => true]);
|
||||
$ga = new \Wolfcode\Authenticator\google\PHPGangstaGoogleAuthenticator();
|
||||
if (!$ga->verifyCode($admin->ga_secret, $post['ga_code'])) $this->error('谷歌验证码错误');;
|
||||
}
|
||||
$admin->login_num += 1;
|
||||
$admin->save();
|
||||
$admin = $admin->toArray();
|
||||
unset($admin['password']);
|
||||
$admin['expire_time'] = $post['keep_login'] == 1 ? 0 : time() + 7200;
|
||||
session('admin', $admin);
|
||||
$this->success('登录成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户退出
|
||||
*/
|
||||
public function out(): void
|
||||
{
|
||||
session('admin', null);
|
||||
$this->success('退出登录成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
* @return Response
|
||||
*/
|
||||
public function captcha(): Response
|
||||
{
|
||||
return Captcha::instance()->create();
|
||||
}
|
||||
}
|
||||
21
app/admin/controller/article/Article.php
Normal file
21
app/admin/controller/article/Article.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\article;
|
||||
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\common\model\Articles;
|
||||
use think\App;
|
||||
|
||||
#[ControllerAnnotation(title: '文章管理')]
|
||||
class Article extends AdminController
|
||||
{
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
self::$model = Articles::class;
|
||||
}
|
||||
|
||||
}
|
||||
21
app/admin/controller/article/Cate.php
Normal file
21
app/admin/controller/article/Cate.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\article;
|
||||
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\common\model\ArticleCates;
|
||||
use think\App;
|
||||
|
||||
#[ControllerAnnotation(title: '文章分类管理')]
|
||||
class Cate extends AdminController
|
||||
{
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
self::$model = ArticleCates::class;
|
||||
}
|
||||
|
||||
}
|
||||
51
app/admin/controller/mall/Blackip.php
Normal file
51
app/admin/controller/mall/Blackip.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\mall;
|
||||
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\MiddlewareAnnotation;
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use app\common\controller\AdminController;
|
||||
use app\Request;
|
||||
use think\App;
|
||||
use think\response\Json;
|
||||
|
||||
#[ControllerAnnotation(title: '商城商品管理')]
|
||||
class Blackip extends AdminController
|
||||
{
|
||||
|
||||
#[NodeAnnotation(ignore: ['export'])] // 过滤不需要生成的权限节点 默认 CURD 中会自动生成部分节点 可以在此处过滤
|
||||
protected array $ignoreNode;
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
self::$model = new \app\admin\model\BlackIp();
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '列表', auth: true)]
|
||||
public function index(Request $request): Json|string
|
||||
{
|
||||
if ($request->isAjax()) {
|
||||
if (input('selectFields')) return $this->selectList();
|
||||
list($page, $limit, $where) = $this->buildTableParams();
|
||||
$count = self::$model::where($where)->count();
|
||||
$list = self::$model::where($where)->page($page, $limit)->order($this->sort)->select()->toArray();
|
||||
$data = [
|
||||
'code' => 0,
|
||||
'msg' => '',
|
||||
'count' => $count,
|
||||
'data' => $list,
|
||||
];
|
||||
return json($data);
|
||||
}
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
#[MiddlewareAnnotation(ignore: MiddlewareAnnotation::IGNORE_LOGIN)]
|
||||
public function no_check_login(Request $request): string
|
||||
{
|
||||
return '这里演示方法不需要经过登录验证';
|
||||
}
|
||||
|
||||
}
|
||||
21
app/admin/controller/mall/Cate.php
Normal file
21
app/admin/controller/mall/Cate.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\mall;
|
||||
|
||||
use app\admin\model\MallCate;
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use think\App;
|
||||
|
||||
#[ControllerAnnotation(title: '商品分类管理')]
|
||||
class Cate extends AdminController
|
||||
{
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
self::$model = MallCate::class;
|
||||
}
|
||||
|
||||
}
|
||||
135
app/admin/controller/mall/Goods.php
Normal file
135
app/admin/controller/mall/Goods.php
Normal file
@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\mall;
|
||||
|
||||
use app\admin\model\MallCate;
|
||||
use app\admin\model\MallGoods;
|
||||
use app\admin\service\annotation\MiddlewareAnnotation;
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use app\Request;
|
||||
use think\App;
|
||||
use think\response\Json;
|
||||
use Wolfcode\Ai\Enum\AiType;
|
||||
use Wolfcode\Ai\Service\AiChatService;
|
||||
|
||||
#[ControllerAnnotation(title: '商城商品管理')]
|
||||
class Goods extends AdminController
|
||||
{
|
||||
|
||||
#[NodeAnnotation(ignore: ['export'])] // 过滤不需要生成的权限节点 默认 CURD 中会自动生成部分节点 可以在此处过滤
|
||||
protected array $ignoreNode;
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
self::$model = new MallGoods();
|
||||
$this->assign('cate', MallCate::column('title', 'id'));
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '列表', auth: true)]
|
||||
public function index(Request $request): Json|string
|
||||
{
|
||||
if ($request->isAjax()) {
|
||||
if (input('selectFields')) return $this->selectList();
|
||||
list($page, $limit, $where) = $this->buildTableParams();
|
||||
$count = self::$model::where($where)->count();
|
||||
$list = self::$model::with(['cate'])->where($where)->page($page, $limit)->order($this->sort)->select()->toArray();
|
||||
$data = [
|
||||
'code' => 0,
|
||||
'msg' => '',
|
||||
'count' => $count,
|
||||
'data' => $list,
|
||||
];
|
||||
return json($data);
|
||||
}
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '入库', auth: true)]
|
||||
public function stock(Request $request, $id): string
|
||||
{
|
||||
$row = self::$model::find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
if ($request->isPost()) {
|
||||
$post = $request->post();
|
||||
$rule = [];
|
||||
$this->validate($post, $rule);
|
||||
try {
|
||||
$post['total_stock'] = $row->total_stock + $post['stock'];
|
||||
$post['stock'] = $row->stock + $post['stock'];
|
||||
$save = $row->save($post);
|
||||
}catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
$save ? $this->success('保存成功') : $this->error('保存失败');
|
||||
}
|
||||
$this->assign('row', $row);
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
#[MiddlewareAnnotation(ignore: MiddlewareAnnotation::IGNORE_LOGIN)]
|
||||
public function no_check_login(Request $request): string
|
||||
{
|
||||
return '这里演示方法不需要经过登录验证';
|
||||
}
|
||||
|
||||
|
||||
#[NodeAnnotation(title: 'AI优化', auth: true)]
|
||||
public function aiOptimization(Request $request): void
|
||||
{
|
||||
$message = $request->post('message');
|
||||
if (empty($message)) $this->error('请输入内容');
|
||||
|
||||
// 演示环境下 默认返回的内容
|
||||
if ($this->isDemo) {
|
||||
$content = <<<EOF
|
||||
演示环境中 默认返回的内容
|
||||
|
||||
我来帮你优化这个标题,让它更有吸引力且更符合电商平台的搜索逻辑:
|
||||
|
||||
"商务男士高端定制马克杯 | 办公室精英必备 | 优质陶瓷防烫手柄"
|
||||
|
||||
这个优化后的标题:
|
||||
1. 突出了目标用户群体(商务男士)
|
||||
2. 强调了产品定位(高端定制)
|
||||
3. 点明了使用场景(办公室)
|
||||
4. 添加了材质和功能特点(优质陶瓷、防烫手柄)
|
||||
5. 使用了吸引人的关键词(精英必备)
|
||||
|
||||
这样的标题不仅更具体,也更容易被搜索引擎识别,同时能精准触达目标客户群。您觉得这个版本如何?
|
||||
EOF;
|
||||
$choices = [['message' => [
|
||||
'role' => 'assistant',
|
||||
'content' => $content,
|
||||
]]];
|
||||
$this->success('success', compact('choices'));
|
||||
}
|
||||
|
||||
try {
|
||||
$result = AiChatService::instance()
|
||||
// 当使用推理模型时,可能存在超时的情况,所以需要设置超时时间为 0
|
||||
// ->setTimeLimit(0)
|
||||
// 请替换为您需要的模型类型
|
||||
->setAiType(AiType::QWEN)
|
||||
// 如果需要指定模型的 API 地址,可自行设置
|
||||
// ->setAiUrl('https://xxx.com')
|
||||
// 请替换为您的模型
|
||||
->setAiModel('qwen-plus')
|
||||
// 请替换为您的 API KEY
|
||||
->setAiKey('sk-1234567890')
|
||||
// 此内容会作为系统提示,会影响到回答的内容 当前仅作为测试使用
|
||||
->setSystemContent('你现在是一位资深的海外电商产品经理')
|
||||
->chat($message);
|
||||
$choices = $result['choices'];
|
||||
}catch (\Throwable $exception) {
|
||||
$choices = [['message' => [
|
||||
'role' => 'assistant',
|
||||
'content' => $exception->getMessage(),
|
||||
]]];
|
||||
}
|
||||
$this->success('success', compact('choices'));
|
||||
}
|
||||
|
||||
}
|
||||
120
app/admin/controller/mall/Order.php
Normal file
120
app/admin/controller/mall/Order.php
Normal file
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\mall;
|
||||
|
||||
use app\admin\model\MallOrder;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\MiddlewareAnnotation;
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use app\common\controller\AdminController;
|
||||
use app\Request;
|
||||
use think\App;
|
||||
use think\facade\Db;
|
||||
use think\response\Json;
|
||||
use Wolfcode\Ai\Enum\AiType;
|
||||
use Wolfcode\Ai\Service\AiChatService;
|
||||
|
||||
#[ControllerAnnotation(title: '商城商品管理')]
|
||||
class Order extends AdminController
|
||||
{
|
||||
|
||||
#[NodeAnnotation(ignore: ['export'])] // 过滤不需要生成的权限节点 默认 CURD 中会自动生成部分节点 可以在此处过滤
|
||||
protected array $ignoreNode;
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
self::$model = new MallOrder();
|
||||
}
|
||||
protected array $allowModifyFields = [
|
||||
'status',
|
||||
'sort',
|
||||
'remark',
|
||||
'is_delete',
|
||||
'is_auth',
|
||||
'title',
|
||||
'url',
|
||||
];
|
||||
|
||||
#[NodeAnnotation(title: '列表', auth: true)]
|
||||
public function index(Request $request): Json|string
|
||||
{
|
||||
if ($request->isAjax()) {
|
||||
if (input('selectFields')) return $this->selectList();
|
||||
list($page, $limit, $where) = $this->buildTableParams();
|
||||
$count = self::$model::where($where)->count();
|
||||
$list = self::$model::where($where)->page($page, $limit)->order($this->sort)->select()->toArray();
|
||||
$data = [
|
||||
'code' => 0,
|
||||
'msg' => '',
|
||||
'count' => $count,
|
||||
'data' => $list,
|
||||
];
|
||||
return json($data);
|
||||
}
|
||||
return $this->fetch();
|
||||
}
|
||||
#[NodeAnnotation(title: '确认支付成功', auth: true)]
|
||||
public function recharge(Request $request, $id): string
|
||||
{
|
||||
$row = self::$model::find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
if ($request->isPost()) {
|
||||
$post = $request->post();
|
||||
try {
|
||||
$post['status'] = 2;
|
||||
$save = $row->save($post);
|
||||
}catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
$save ? $this->success('确认支付成功') : $this->error('保存失败');
|
||||
}
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '编辑', auth: true)]
|
||||
public function edit(Request $request, $id = 0): string
|
||||
{
|
||||
$row = self::$model::find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
if ($request->isPost()) {
|
||||
$post = $request->post();
|
||||
$rule = [];
|
||||
$this->validate($post, $rule);
|
||||
try {
|
||||
Db::transaction(function() use ($post, $row, &$save) {
|
||||
$post['status'] = 1;
|
||||
$save = $row->save($post);
|
||||
});
|
||||
}catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
$save ? $this->success('保存成功') : $this->error('保存失败');
|
||||
}
|
||||
$this->assign('row', $row);
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: 'IP拉黑', auth: true)]
|
||||
public function blockip(Request $request, $id): string
|
||||
{
|
||||
$row = self::$model::find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
if ($request->isPost()) {
|
||||
try {
|
||||
$save = (new \app\admin\model\BlackIp())->save([
|
||||
'ip' => $row->ip,
|
||||
]);
|
||||
}catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
$save ? $this->success('IP拉黑成功') : $this->error('保存失败');
|
||||
}
|
||||
}
|
||||
|
||||
#[MiddlewareAnnotation(ignore: MiddlewareAnnotation::IGNORE_LOGIN)]
|
||||
public function no_check_login(Request $request): string
|
||||
{
|
||||
return '这里演示方法不需要经过登录验证';
|
||||
}
|
||||
|
||||
}
|
||||
179
app/admin/controller/system/Admin.php
Normal file
179
app/admin/controller/system/Admin.php
Normal file
@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
use app\admin\model\SystemAdmin;
|
||||
use app\admin\service\TriggerService;
|
||||
use app\common\constants\AdminConstant;
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use app\Request;
|
||||
use think\App;
|
||||
use think\response\Json;
|
||||
|
||||
#[ControllerAnnotation(title: '管理员管理')]
|
||||
class Admin extends AdminController
|
||||
{
|
||||
|
||||
protected array $sort = [
|
||||
'sort' => 'desc',
|
||||
'id' => 'desc',
|
||||
];
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
self::$model = SystemAdmin::class;
|
||||
$this->assign('auth_list', self::$model::getAuthList());
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '列表', auth: true)]
|
||||
public function index(Request $request): Json|string
|
||||
{
|
||||
if ($request->isAjax()) {
|
||||
if (input('selectFields')) {
|
||||
return $this->selectList();
|
||||
}
|
||||
list($page, $limit, $where) = $this->buildTableParams();
|
||||
$count = self::$model::where($where)->count();
|
||||
$list = self::$model::withoutField('password')
|
||||
->where($where)
|
||||
->page($page, $limit)
|
||||
->order($this->sort)
|
||||
->select()->toArray();
|
||||
$data = [
|
||||
'code' => 0,
|
||||
'msg' => '',
|
||||
'count' => $count,
|
||||
'data' => $list,
|
||||
];
|
||||
return json($data);
|
||||
}
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '添加', auth: true)]
|
||||
public function add(Request $request): string
|
||||
{
|
||||
if ($request->isPost()) {
|
||||
$post = $request->post();
|
||||
$authIds = $request->post('auth_ids', []);
|
||||
$post['auth_ids'] = implode(',', array_keys($authIds));
|
||||
$rule = [];
|
||||
$this->validate($post, $rule);
|
||||
if (empty($post['password'])) $post['password'] = '123456';
|
||||
$post['password'] = password_hash($post['password'],PASSWORD_DEFAULT);
|
||||
try {
|
||||
$save = self::$model::create($post);
|
||||
}catch (\Exception $e) {
|
||||
$this->error('保存失败' . $e->getMessage());
|
||||
}
|
||||
$save ? $this->success('保存成功') : $this->error('保存失败');
|
||||
}
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '编辑', auth: true)]
|
||||
public function edit(Request $request, $id = 0): string
|
||||
{
|
||||
$row = self::$model::find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
if ($request->isPost()) {
|
||||
$post = $request->post();
|
||||
$authIds = $request->post('auth_ids', []);
|
||||
$post['auth_ids'] = implode(',', array_keys($authIds));
|
||||
$rule = [];
|
||||
$this->validate($post, $rule);
|
||||
try {
|
||||
$save = $row->save($post);
|
||||
TriggerService::updateMenu($id);
|
||||
}catch (\Exception $e) {
|
||||
$this->error('保存失败' . $e->getMessage());
|
||||
}
|
||||
$save ? $this->success('保存成功') : $this->error('保存失败');
|
||||
}
|
||||
$this->assign('row', $row);
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '设置密码', auth: true)]
|
||||
public function password(Request $request, $id): string
|
||||
{
|
||||
$row = self::$model::find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
if ($request->isAjax()) {
|
||||
$post = $request->post();
|
||||
$rule = [
|
||||
'password|登录密码' => 'require',
|
||||
'password_again|确认密码' => 'require',
|
||||
];
|
||||
$this->validate($post, $rule);
|
||||
if ($post['password'] != $post['password_again']) {
|
||||
$this->error('两次密码输入不一致');
|
||||
}
|
||||
try {
|
||||
$save = $row->save([
|
||||
'password' => password_hash($post['password'],PASSWORD_DEFAULT),
|
||||
]);
|
||||
}catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
$save ? $this->success('保存成功') : $this->error('保存失败');
|
||||
}
|
||||
$this->assign('row', $row);
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '删除', auth: true)]
|
||||
public function delete(Request $request): void
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$id = $request->param('id');
|
||||
$row = self::$model::whereIn('id', $id)->select();
|
||||
$row->isEmpty() && $this->error('数据不存在');
|
||||
$id == AdminConstant::SUPER_ADMIN_ID && $this->error('超级管理员不允许修改');
|
||||
if (is_array($id)) {
|
||||
if (in_array(AdminConstant::SUPER_ADMIN_ID, $id)) {
|
||||
$this->error('超级管理员不允许修改');
|
||||
}
|
||||
}
|
||||
try {
|
||||
$save = $row->delete();
|
||||
}catch (\Exception $e) {
|
||||
$this->error('删除失败');
|
||||
}
|
||||
$save ? $this->success('删除成功') : $this->error('删除失败');
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '属性修改', auth: true)]
|
||||
public function modify(Request $request): void
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$post = $request->post();
|
||||
$rule = [
|
||||
'id|ID' => 'require',
|
||||
'field|字段' => 'require',
|
||||
'value|值' => 'require',
|
||||
];
|
||||
$this->validate($post, $rule);
|
||||
if (!in_array($post['field'], $this->allowModifyFields)) {
|
||||
$this->error('该字段不允许修改:' . $post['field']);
|
||||
}
|
||||
if ($post['id'] == AdminConstant::SUPER_ADMIN_ID && $post['field'] == 'status') {
|
||||
$this->error('超级管理员状态不允许修改');
|
||||
}
|
||||
$row = self::$model::find($post['id']);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
try {
|
||||
$row->save([
|
||||
$post['field'] => $post['value'],
|
||||
]);
|
||||
}catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
$this->success('保存成功');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
71
app/admin/controller/system/Auth.php
Normal file
71
app/admin/controller/system/Auth.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
use app\admin\model\SystemAuth;
|
||||
use app\admin\model\SystemAuthNode;
|
||||
use app\admin\service\TriggerService;
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use app\Request;
|
||||
use think\App;
|
||||
|
||||
#[ControllerAnnotation(title: '角色权限管理', auth: true)]
|
||||
class Auth extends AdminController
|
||||
{
|
||||
|
||||
protected array $sort = [
|
||||
'sort' => 'desc',
|
||||
'id' => 'desc',
|
||||
];
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
self::$model = SystemAuth::class;
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '授权', auth: true)]
|
||||
public function authorize(Request $request, $id): string
|
||||
{
|
||||
$row = self::$model::find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
if ($request->isAjax()) {
|
||||
$list = self::$model::getAuthorizeNodeListByAdminId($id);
|
||||
$this->success('获取成功', $list);
|
||||
}
|
||||
$this->assign('row', $row);
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '授权保存', auth: true)]
|
||||
public function saveAuthorize(Request $request): void
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$id = $request->post('id');
|
||||
$node = $request->post('node', "[]");
|
||||
$node = json_decode($node, true);
|
||||
$row = self::$model::find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
try {
|
||||
$authNode = new SystemAuthNode();
|
||||
$authNode->where('auth_id', $id)->delete();
|
||||
if (!empty($node)) {
|
||||
$saveAll = [];
|
||||
foreach ($node as $vo) {
|
||||
$saveAll[] = [
|
||||
'auth_id' => $id,
|
||||
'node_id' => $vo,
|
||||
];
|
||||
}
|
||||
$authNode->saveAll($saveAll);
|
||||
}
|
||||
TriggerService::updateMenu();
|
||||
}catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
$this->success('保存成功');
|
||||
}
|
||||
|
||||
}
|
||||
70
app/admin/controller/system/Config.php
Normal file
70
app/admin/controller/system/Config.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
use app\admin\model\SystemConfig;
|
||||
use app\admin\service\TriggerService;
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use app\Request;
|
||||
use think\App;
|
||||
use think\facade\Cache;
|
||||
use think\response\Json;
|
||||
|
||||
#[ControllerAnnotation(title: '系统配置管理')]
|
||||
class Config extends AdminController
|
||||
{
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
self::$model = SystemConfig::class;
|
||||
$this->assign('upload_types', config('admin.upload_types'));
|
||||
$this->assign('editor_types', config('admin.editor_types'));
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '列表', auth: true)]
|
||||
public function index(Request $request): Json|string
|
||||
{
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '保存', auth: true)]
|
||||
public function save(Request $request): void
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$post = $request->post();
|
||||
$notAddFields = ['_token', 'file', 'group'];
|
||||
try {
|
||||
$group = $post['group'] ?? '';
|
||||
if (empty($group)) $this->error('保存失败');
|
||||
if ($group == 'upload') {
|
||||
$upload_types = config('admin.upload_types');
|
||||
// 兼容旧版本
|
||||
self::$model::where('name', 'upload_allow_type')->update(['value' => implode(',', array_keys($upload_types))]);
|
||||
}
|
||||
foreach ($post as $key => $val) {
|
||||
if (in_array($key, $notAddFields)) continue;
|
||||
$config_key_data = self::$model::where('name', $key)->find();
|
||||
if (!is_null($config_key_data)) {
|
||||
$config_key_data->save(['value' => $val,]);
|
||||
}else {
|
||||
self::$model::create(
|
||||
[
|
||||
'name' => $key,
|
||||
'value' => $val,
|
||||
'group' => $group,
|
||||
]);
|
||||
}
|
||||
if (Cache::has($key)) Cache::set($key, $val);
|
||||
}
|
||||
TriggerService::updateMenu();
|
||||
TriggerService::updateSysConfig();
|
||||
}catch (\Exception $e) {
|
||||
$this->error('保存失败' . $e->getMessage());
|
||||
}
|
||||
$this->success('保存成功');
|
||||
}
|
||||
|
||||
}
|
||||
155
app/admin/controller/system/CurdGenerate.php
Normal file
155
app/admin/controller/system/CurdGenerate.php
Normal file
@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
use app\admin\service\curd\BuildCurd;
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use app\Request;
|
||||
use think\db\exception\PDOException;
|
||||
use think\exception\FileException;
|
||||
use think\facade\Console;
|
||||
use think\facade\Db;
|
||||
use think\helper\Str;
|
||||
use think\response\Json;
|
||||
|
||||
#[ControllerAnnotation(title: 'CURD可视化管理')]
|
||||
class CurdGenerate extends AdminController
|
||||
{
|
||||
#[NodeAnnotation(title: '列表', auth: true)]
|
||||
public function index(Request $request): Json|string
|
||||
{
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '操作', auth: true)]
|
||||
public function save(Request $request, string $type = ''): ?Json
|
||||
{
|
||||
if (!$request->isAjax()) $this->error();
|
||||
switch ($type) {
|
||||
case "search":
|
||||
$tb_prefix = $request->param('tb_prefix/s', '');
|
||||
$tb_name = $request->param('tb_name/s', '');
|
||||
if (empty($tb_name)) $this->error('参数错误');
|
||||
|
||||
try {
|
||||
$list = Db::query("SHOW FULL COLUMNS FROM {$tb_prefix}{$tb_name}");
|
||||
$data = [];
|
||||
foreach ($list as $value) {
|
||||
$data[] = [
|
||||
'name' => $value['Field'],
|
||||
'type' => $value['Type'],
|
||||
'key' => $value['Key'],
|
||||
'extra' => $value['Extra'],
|
||||
'null' => $value['Null'],
|
||||
'desc' => $value['Comment'],
|
||||
];
|
||||
}
|
||||
$this->success('查询成功', compact('data', 'list'));
|
||||
}catch (PDOException $exception) {
|
||||
$this->error($exception->getMessage());
|
||||
}
|
||||
break;
|
||||
case "add":
|
||||
$tb_prefix = $request->param('tb_prefix/s', '');
|
||||
$tb_name = $request->param('tb_name/s', '');
|
||||
if (empty($tb_name)) $this->error('参数错误');
|
||||
|
||||
$tb_fields = $request->param('tb_fields');
|
||||
$force = $request->post('force/d', 0);
|
||||
try {
|
||||
$build = (new BuildCurd())->setTablePrefix($tb_prefix)->setTable($tb_name);
|
||||
$build->setForce($force); // 强制覆盖
|
||||
// 新增字段类型
|
||||
if ($tb_fields) {
|
||||
foreach ($tb_fields as $tk => $tf) {
|
||||
if (empty($tf)) continue;
|
||||
$tf = array_values($tf);
|
||||
switch ($tk) {
|
||||
case 'ignore':
|
||||
$build->setIgnoreFields($tf, true);
|
||||
break;
|
||||
case 'select':
|
||||
$build->setSelectFields($tf, true);
|
||||
break;
|
||||
case 'radio':
|
||||
$build->setRadioFieldSuffix($tf, true);
|
||||
break;
|
||||
case 'checkbox':
|
||||
$build->setCheckboxFieldSuffix($tf, true);
|
||||
break;
|
||||
case 'image':
|
||||
$build->setImageFieldSuffix($tf, true);
|
||||
break;
|
||||
case 'images':
|
||||
$build->setImagesFieldSuffix($tf, true);
|
||||
break;
|
||||
case 'date':
|
||||
$build->setDateFieldSuffix($tf, true);
|
||||
break;
|
||||
case 'datetime':
|
||||
$build->setDatetimeFieldSuffix($tf, true);
|
||||
break;
|
||||
case 'editor':
|
||||
$build->setEditorFields($tf, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$build = $build->render();
|
||||
$fileList = $build->getFileList();
|
||||
if (empty($fileList)) $this->error('这里什么都没有');
|
||||
$result = $build->create();
|
||||
$_file = $result[0] ?? '';
|
||||
$link = '';
|
||||
if (!empty($_file)) {
|
||||
$_fileExp = explode(DIRECTORY_SEPARATOR, $_file);
|
||||
$_fileExp_last = array_slice($_fileExp, -2);
|
||||
$_fileExp_last_0 = $_fileExp_last[0] . '.';
|
||||
if ($_fileExp_last[0] == 'controller') $_fileExp_last_0 = '';
|
||||
$link = '/' . config('admin.alias_name') . '/' . $_fileExp_last_0 . Str::snake(explode('.php', end($_fileExp_last))[0] ?? '') . '/index';
|
||||
}
|
||||
$this->success('生成成功', compact('result', 'link'));
|
||||
}catch (FileException $exception) {
|
||||
return json(['code' => -1, 'msg' => $exception->getMessage()]);
|
||||
}
|
||||
break;
|
||||
case "delete":
|
||||
$tb_prefix = $request->param('tb_prefix/s', '');
|
||||
$tb_name = $request->param('tb_name/s', '');
|
||||
if (empty($tb_name)) $this->error('参数错误');
|
||||
|
||||
try {
|
||||
$build = (new BuildCurd())->setTablePrefix($tb_prefix)->setTable($tb_name);
|
||||
$build = $build->render();
|
||||
$fileList = $build->getFileList();
|
||||
if (empty($fileList)) $this->error('这里什么都没有');
|
||||
$result = $build->delete();
|
||||
$this->success('删除自动生成CURD文件成功', compact('result'));
|
||||
}catch (FileException $exception) {
|
||||
return json(['code' => -1, 'msg' => $exception->getMessage()]);
|
||||
}
|
||||
break;
|
||||
case 'console':
|
||||
$command = $request->post('command', '');
|
||||
if (empty($command)) $this->error('请输入命令');
|
||||
$commandExp = explode(' ', $command);
|
||||
$commandExp = array_values(array_filter($commandExp));
|
||||
try {
|
||||
|
||||
$output = Console::call('curd', [...$commandExp]);
|
||||
}catch (\Throwable $exception) {
|
||||
$this->error($exception->getMessage() . $exception->getLine());
|
||||
}
|
||||
if (empty($output)) $this->error('设置错误');
|
||||
$this->success($output->fetch());
|
||||
break;
|
||||
default:
|
||||
$this->error('参数错误');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
138
app/admin/controller/system/Log.php
Normal file
138
app/admin/controller/system/Log.php
Normal file
@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
use app\admin\model\SystemLog;
|
||||
use app\admin\service\annotation\MiddlewareAnnotation;
|
||||
use app\admin\service\tool\CommonTool;
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use app\Request;
|
||||
use jianyan\excel\Excel;
|
||||
use think\App;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\PDOException;
|
||||
use think\facade\Db;
|
||||
use think\response\Json;
|
||||
|
||||
#[ControllerAnnotation(title: '操作日志管理')]
|
||||
class Log extends AdminController
|
||||
{
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
self::$model = SystemLog::class;
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '列表', auth: true)]
|
||||
public function index(Request $request): Json|string
|
||||
{
|
||||
if ($request->isAjax()) {
|
||||
if (input('selectFields')) {
|
||||
return $this->selectList();
|
||||
}
|
||||
[$page, $limit, $where, $excludeFields] = $this->buildTableParams(['month']);
|
||||
$month = !empty($excludeFields['month']) ? date('Ym', strtotime($excludeFields['month'])) : date('Ym');
|
||||
$model = (new self::$model)->setSuffix("_$month")->with('admin')->where($where);
|
||||
try {
|
||||
$count = $model->count();
|
||||
$list = $model->page($page, $limit)->order($this->sort)->select();
|
||||
}catch (PDOException|DbException $exception) {
|
||||
$count = 0;
|
||||
$list = [];
|
||||
}
|
||||
$data = [
|
||||
'code' => 0,
|
||||
'msg' => '',
|
||||
'count' => $count,
|
||||
'data' => $list,
|
||||
];
|
||||
return json($data);
|
||||
}
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '导出', auth: true)]
|
||||
public function export()
|
||||
{
|
||||
if (env('EASYADMIN.IS_DEMO', false)) {
|
||||
$this->error('演示环境下不允许操作');
|
||||
}
|
||||
[$page, $limit, $where, $excludeFields] = $this->buildTableParams(['month']);
|
||||
$month = !empty($excludeFields['month']) ? date('Ym', strtotime($excludeFields['month'])) : date('Ym');
|
||||
$tableName = (new self::$model)->setSuffix("_$month")->getName();
|
||||
$tableName = CommonTool::humpToLine(lcfirst($tableName));
|
||||
$prefix = config('database.connections.mysql.prefix');
|
||||
$dbList = Db::query("show full columns from {$prefix}{$tableName}");
|
||||
$header = [];
|
||||
foreach ($dbList as $vo) {
|
||||
$comment = !empty($vo['Comment']) ? $vo['Comment'] : $vo['Field'];
|
||||
if (!in_array($vo['Field'], $this->noExportFields)) {
|
||||
$header[] = [$comment, $vo['Field']];
|
||||
}
|
||||
}
|
||||
$model = (new self::$model)->setSuffix("_$month")->with('admin')->where($where);
|
||||
try {
|
||||
$list = $model
|
||||
->limit(10000)
|
||||
->order('id', 'desc')
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($list as &$vo) {
|
||||
$vo['content'] = json_encode($vo['content'], JSON_UNESCAPED_UNICODE);
|
||||
$vo['response'] = json_encode($vo['response'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
exportExcel($header, $list, '操作日志');
|
||||
}catch (\Throwable $exception) {
|
||||
$this->error($exception->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[NodeAnnotation(title: '删除指定日志', auth: true)]
|
||||
public function deleteMonthLog(Request $request)
|
||||
{
|
||||
if (!$request->isAjax()) {
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
if ($this->isDemo) $this->error('演示环境下不允许操作');
|
||||
|
||||
$monthsAgo = $request->param('month/d', 0);
|
||||
if ($monthsAgo < 1) $this->error('月份错误');
|
||||
|
||||
$currentDate = new \DateTime();
|
||||
$currentDate->modify("-$monthsAgo months");
|
||||
|
||||
$dbPrefix = env('DB_PREFIX');
|
||||
$dbLike = "{$dbPrefix}system_log_";
|
||||
$tables = Db::query("SHOW TABLES LIKE '$dbLike%'");
|
||||
$threshold = date('Ym', strtotime("-$monthsAgo month"));
|
||||
$tableNames = [];
|
||||
try {
|
||||
foreach ($tables as $table) {
|
||||
$tableName = current($table);
|
||||
if (!preg_match("/^$dbLike\d{6}$/", $tableName)) continue;
|
||||
$datePart = substr($tableName, -6);
|
||||
$issetTable = Db::query("SHOW TABLES LIKE '$tableName'");
|
||||
if (!$issetTable) continue;
|
||||
if ($datePart - $threshold <= 0) {
|
||||
Db::execute("DROP TABLE `$tableName`");
|
||||
$tableNames[] = $tableName;
|
||||
}
|
||||
}
|
||||
}catch (PDOException) {
|
||||
}
|
||||
if (empty($tableNames)) $this->error('没有需要删除的表');
|
||||
$this->success('操作成功 - 共删除 ' . count($tableNames) . ' 张表<br/>' . implode('<br>', $tableNames));
|
||||
}
|
||||
|
||||
#[MiddlewareAnnotation(ignore: MiddlewareAnnotation::IGNORE_LOG)]
|
||||
#[NodeAnnotation(title: '框架日志', auth: true, ignore: NodeAnnotation::IGNORE_NODE)]
|
||||
public function record(): Json|string
|
||||
{
|
||||
return (new \Wolfcode\PhpLogviewer\thinkphp\LogViewer())->fetch();
|
||||
}
|
||||
|
||||
}
|
||||
191
app/admin/controller/system/Menu.php
Normal file
191
app/admin/controller/system/Menu.php
Normal file
@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
use app\admin\model\SystemMenu;
|
||||
use app\admin\model\SystemNode;
|
||||
use app\admin\service\TriggerService;
|
||||
use app\common\constants\MenuConstant;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use app\common\controller\AdminController;
|
||||
use app\Request;
|
||||
use think\App;
|
||||
use think\response\Json;
|
||||
|
||||
#[ControllerAnnotation(title: '菜单管理')]
|
||||
class Menu extends AdminController
|
||||
{
|
||||
|
||||
protected array $sort = [
|
||||
'sort' => 'desc',
|
||||
'id' => 'asc',
|
||||
];
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
self::$model = SystemMenu::class;
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '列表', auth: true)]
|
||||
public function index(Request $request): Json|string
|
||||
{
|
||||
if ($request->isAjax()) {
|
||||
if (input('selectFields')) {
|
||||
return $this->selectList();
|
||||
}
|
||||
$count = self::$model::count();
|
||||
$list = self::$model::order($this->sort)->select()->toArray();
|
||||
$data = [
|
||||
'code' => 0,
|
||||
'msg' => '',
|
||||
'count' => $count,
|
||||
'data' => $list,
|
||||
];
|
||||
return json($data);
|
||||
}
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '添加', auth: true)]
|
||||
public function add(Request $request): string
|
||||
{
|
||||
$id = $request->param('id');
|
||||
$homeId = self::$model::where(['pid' => MenuConstant::HOME_PID,])->value('id');
|
||||
if ($id == $homeId) {
|
||||
$this->error('首页不能添加子菜单');
|
||||
}
|
||||
if ($request->isPost()) {
|
||||
$post = $request->post();
|
||||
$rule = [
|
||||
'pid|上级菜单' => 'require',
|
||||
'title|菜单名称' => 'require',
|
||||
'icon|菜单图标' => 'require',
|
||||
];
|
||||
$this->validate($post, $rule);
|
||||
try {
|
||||
$save = self::$model::create($post);
|
||||
}catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
if ($save) {
|
||||
TriggerService::updateMenu();
|
||||
$this->success('保存成功');
|
||||
}else {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
}
|
||||
$pidMenuList = self::$model::getPidMenuList();
|
||||
$this->assign('id', $id);
|
||||
$this->assign('pidMenuList', $pidMenuList);
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '编辑', auth: true)]
|
||||
public function edit(Request $request, $id = 0): string
|
||||
{
|
||||
$row = self::$model::find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
if ($request->isPost()) {
|
||||
$post = $request->post();
|
||||
$rule = [
|
||||
'pid|上级菜单' => 'require',
|
||||
'title|菜单名称' => 'require',
|
||||
'icon|菜单图标' => 'require',
|
||||
];
|
||||
$this->validate($post, $rule);
|
||||
if ($row->pid == MenuConstant::HOME_PID) $post['pid'] = MenuConstant::HOME_PID;
|
||||
try {
|
||||
$save = $row->save($post);
|
||||
}catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
if (!empty($save)) {
|
||||
TriggerService::updateMenu();
|
||||
$this->success('保存成功');
|
||||
}else {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
}
|
||||
$pidMenuList = self::$model::getPidMenuList();
|
||||
$this->assign([
|
||||
'id' => $id,
|
||||
'pidMenuList' => $pidMenuList,
|
||||
'row' => $row,
|
||||
]);
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '删除', auth: true)]
|
||||
public function delete(Request $request): void
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$id = $request->param('id');
|
||||
$row = self::$model::whereIn('id', $id)->select();
|
||||
empty($row) && $this->error('数据不存在');
|
||||
try {
|
||||
$save = $row->delete();
|
||||
}catch (\Exception $e) {
|
||||
$this->error('删除失败');
|
||||
}
|
||||
if ($save) {
|
||||
TriggerService::updateMenu();
|
||||
$this->success('删除成功');
|
||||
}else {
|
||||
$this->error('删除失败');
|
||||
}
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '属性修改', auth: true)]
|
||||
public function modify(Request $request): void
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$post = $request->post();
|
||||
$rule = [
|
||||
'id|ID' => 'require',
|
||||
'field|字段' => 'require',
|
||||
'value|值' => 'require',
|
||||
];
|
||||
$this->validate($post, $rule);
|
||||
$row = self::$model::find($post['id']);
|
||||
if (!$row) {
|
||||
$this->error('数据不存在');
|
||||
}
|
||||
if (!in_array($post['field'], $this->allowModifyFields)) {
|
||||
$this->error('该字段不允许修改:' . $post['field']);
|
||||
}
|
||||
$homeId = self::$model::where([
|
||||
'pid' => MenuConstant::HOME_PID,
|
||||
])
|
||||
->value('id');
|
||||
if ($post['id'] == $homeId && $post['field'] == 'status') {
|
||||
$this->error('首页状态不允许关闭');
|
||||
}
|
||||
try {
|
||||
$row->save([
|
||||
$post['field'] => $post['value'],
|
||||
]);
|
||||
}catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
TriggerService::updateMenu();
|
||||
$this->success('保存成功');
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '添加菜单提示', auth: true)]
|
||||
public function getMenuTips(): Json
|
||||
{
|
||||
$node = input('get.keywords');
|
||||
$list = SystemNode::whereLike('node', "%{$node}%")
|
||||
->field('node,title')
|
||||
->limit(10)
|
||||
->select()->toArray();
|
||||
return json([
|
||||
'code' => 0,
|
||||
'content' => $list,
|
||||
'type' => 'success',
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
125
app/admin/controller/system/Node.php
Normal file
125
app/admin/controller/system/Node.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
use app\admin\model\SystemMenu;
|
||||
use app\admin\model\SystemNode;
|
||||
use app\admin\service\TriggerService;
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use app\admin\service\NodeService;
|
||||
use app\Request;
|
||||
use think\App;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
use think\response\Json;
|
||||
|
||||
#[ControllerAnnotation(title: '系统节点管理')]
|
||||
class Node extends AdminController
|
||||
{
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
self::$model = SystemNode::class;
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '列表', auth: true)]
|
||||
public function index(Request $request): Json|string
|
||||
{
|
||||
if ($request->isAjax()) {
|
||||
if (input('selectFields')) {
|
||||
return $this->selectList();
|
||||
}
|
||||
$count = self::$model::count();
|
||||
$list = self::$model::getNodeTreeList();
|
||||
$data = [
|
||||
'code' => 0,
|
||||
'msg' => '',
|
||||
'count' => $count,
|
||||
'data' => $list,
|
||||
];
|
||||
return json($data);
|
||||
}
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '系统节点更新', auth: true)]
|
||||
public function refreshNode($force = 0): void
|
||||
{
|
||||
|
||||
$this->checkPostRequest();
|
||||
$nodeList = (new NodeService())->getNodeList();
|
||||
empty($nodeList) && $this->error('暂无需要更新的系统节点');
|
||||
|
||||
try {
|
||||
if ($force == 1) {
|
||||
$updateNodeList = self::$model::whereIn('node', array_column($nodeList, 'node'))->select();
|
||||
$formatNodeList = array_format_key($nodeList, 'node');
|
||||
foreach ($updateNodeList as $vo) {
|
||||
isset($formatNodeList[$vo['node']])
|
||||
&& self::$model::where('id', $vo['id'])->update(
|
||||
[
|
||||
'title' => $formatNodeList[$vo['node']]['title'],
|
||||
'is_auth' => $formatNodeList[$vo['node']]['is_auth'],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
$existNodeList = self::$model::field('node,title,type,is_auth')->select();
|
||||
foreach ($nodeList as $key => $vo) {
|
||||
foreach ($existNodeList as $v) {
|
||||
if ($vo['node'] == $v->node) {
|
||||
unset($nodeList[$key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($nodeList)) {
|
||||
(new self::$model)->saveAll($nodeList);
|
||||
TriggerService::updateNode();
|
||||
}
|
||||
}catch (\Exception $e) {
|
||||
$this->error('节点更新失败');
|
||||
}
|
||||
$this->success('节点更新成功');
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '清除失效节点', auth: true)]
|
||||
public function clearNode(): void
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$nodeList = (new NodeService())->getNodeList();
|
||||
try {
|
||||
$existNodeList = self::$model::field('id,node,title,type,is_auth')->select()->toArray();
|
||||
$formatNodeList = array_format_key($nodeList, 'node');
|
||||
foreach ($existNodeList as $vo) {
|
||||
!isset($formatNodeList[$vo['node']]) && self::$model::where('id', $vo['id'])->delete();
|
||||
}
|
||||
TriggerService::updateNode();
|
||||
}catch (\Exception $e) {
|
||||
$this->error('节点更新失败');
|
||||
}
|
||||
$this->success('节点更新成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \ReflectionException
|
||||
* @throws \Doctrine\Common\Annotations\AnnotationException
|
||||
*/
|
||||
#[NodeAnnotation(title: '刷新菜单', auth: true)]
|
||||
public function refreshMenu(): void
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$nodeList = (new NodeService())->getNodeList();
|
||||
empty($nodeList) && $this->error('暂无需要更新的系统节点');
|
||||
try {
|
||||
SystemMenu::refreshMenu($nodeList);
|
||||
}catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
$this->success('菜单刷新成功');
|
||||
}
|
||||
}
|
||||
27
app/admin/controller/system/Quick.php
Normal file
27
app/admin/controller/system/Quick.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
|
||||
use app\admin\model\SystemQuick;
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use think\App;
|
||||
|
||||
#[ControllerAnnotation(title: '快捷入口管理')]
|
||||
class Quick extends AdminController
|
||||
{
|
||||
|
||||
protected array $sort = [
|
||||
'sort' => 'desc',
|
||||
'id' => 'desc',
|
||||
];
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
self::$model = SystemQuick::class;
|
||||
}
|
||||
|
||||
}
|
||||
22
app/admin/controller/system/Uploadfile.php
Normal file
22
app/admin/controller/system/Uploadfile.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
use app\admin\model\SystemUploadfile;
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use think\App;
|
||||
|
||||
#[ControllerAnnotation(title: '上传文件管理')]
|
||||
class Uploadfile extends AdminController
|
||||
{
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
self::$model = SystemUploadfile::class;
|
||||
$this->assign('upload_types', config('admin.upload_types'));
|
||||
}
|
||||
|
||||
}
|
||||
0
app/admin/entity/.keep
Normal file
0
app/admin/entity/.keep
Normal file
12
app/admin/entity/Test.php
Normal file
12
app/admin/entity/Test.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\entity;
|
||||
|
||||
use app\common\entity\BaseEntity;
|
||||
|
||||
/**
|
||||
* ThinkORM 4.0 实体模型案例
|
||||
* 可与 Model 并存 或者 单独使用
|
||||
* @package app\admin\entity
|
||||
*/
|
||||
class Test extends BaseEntity {}
|
||||
5
app/admin/middleware.php
Normal file
5
app/admin/middleware.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
// ...
|
||||
];
|
||||
43
app/admin/middleware/CheckAuth.php
Normal file
43
app/admin/middleware/CheckAuth.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\middleware;
|
||||
|
||||
use app\common\service\AuthService;
|
||||
use app\common\traits\JumpTrait;
|
||||
use app\Request;
|
||||
use Closure;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
|
||||
class CheckAuth
|
||||
{
|
||||
use JumpTrait;
|
||||
|
||||
/**
|
||||
* @throws ModelNotFoundException
|
||||
* @throws DbException
|
||||
* @throws DataNotFoundException
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$adminUserInfo = $request->adminUserInfo;
|
||||
if (empty($adminUserInfo)) return $next($request);
|
||||
$adminConfig = config('admin');
|
||||
$adminId = $adminUserInfo['id'];
|
||||
|
||||
$authService = app(AuthService::class, ['adminId' => $adminId]);
|
||||
$currentNode = $authService->getCurrentNode();
|
||||
$currentController = parse_name($request->controller());
|
||||
|
||||
if (!in_array($currentController, $adminConfig['no_auth_controller']) && !in_array($currentNode, $adminConfig['no_auth_node'])) {
|
||||
$check = $authService->checkNode($currentNode);
|
||||
!$check && $this->error('无权限访问');
|
||||
// 判断是否为演示环境
|
||||
if (env('EASYADMIN.IS_DEMO', false) && $request->isPost()) {
|
||||
if (!in_array($currentNode, ['system.log/record', 'mall.goods/aiOptimization'])) $this->error('演示环境下不允许修改');
|
||||
}
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
21
app/admin/middleware/CheckInstall.php
Normal file
21
app/admin/middleware/CheckInstall.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\middleware;
|
||||
|
||||
use app\common\traits\JumpTrait;
|
||||
use app\Request;
|
||||
use Closure;
|
||||
|
||||
class CheckInstall
|
||||
{
|
||||
use JumpTrait;
|
||||
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$controller = $request->controller();
|
||||
if (!is_file(root_path() . 'config' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'lock' . DIRECTORY_SEPARATOR . 'install.lock')) {
|
||||
if ($controller != 'Install') return redirect('/install');
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
60
app/admin/middleware/CheckLogin.php
Normal file
60
app/admin/middleware/CheckLogin.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\middleware;
|
||||
|
||||
use app\common\traits\JumpTrait;
|
||||
use app\Request;
|
||||
use Closure;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use app\admin\service\annotation\MiddlewareAnnotation;
|
||||
|
||||
class CheckLogin
|
||||
{
|
||||
use JumpTrait;
|
||||
|
||||
/**
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$controller = $request->controller();
|
||||
if (empty($controller)) return $next($request);
|
||||
if (str_contains($controller, '.')) $controller = str_replace('.', '\\', $controller);
|
||||
$action = $request->action();
|
||||
$controllerClass = 'app\\admin\\controller\\' . $controller;
|
||||
$classObj = new ReflectionClass($controllerClass);
|
||||
$properties = $classObj->getDefaultProperties();
|
||||
// 整个控制器是否忽略登录
|
||||
$ignoreLogin = $properties['ignoreLogin'] ?? false;
|
||||
$adminUserInfo = session('admin');
|
||||
if (!$ignoreLogin) {
|
||||
$noNeedCheck = $properties['noNeedCheck'] ?? [];
|
||||
if (in_array($action, $noNeedCheck)) {
|
||||
return $next($request);
|
||||
}
|
||||
try {
|
||||
$reflectionMethod = new \ReflectionMethod($controllerClass, $action);
|
||||
$attributes = $reflectionMethod->getAttributes(MiddlewareAnnotation::class);
|
||||
foreach ($attributes as $attribute) {
|
||||
$annotation = $attribute->newInstance();
|
||||
$_ignore = (array)$annotation->ignore;
|
||||
// 控制器中的某个方法忽略登录
|
||||
if (in_array('LOGIN', $_ignore)) return $next($request);
|
||||
}
|
||||
}catch (\Throwable) {
|
||||
}
|
||||
if (empty($adminUserInfo)) {
|
||||
return redirect(__url('login/index'));
|
||||
}
|
||||
// 判断是否登录过期
|
||||
$expireTime = $adminUserInfo['expire_time'];
|
||||
if ($expireTime !== 0 && time() > $expireTime) {
|
||||
session('admin', null);
|
||||
$this->error('登录已过期,请重新登录', [], __url(env('EASYADMIN.ADMIN') . '/login/index'));
|
||||
}
|
||||
}
|
||||
$request->adminUserInfo = $adminUserInfo ?: [];
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
45
app/admin/middleware/RateLimiting.php
Normal file
45
app/admin/middleware/RateLimiting.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\middleware;
|
||||
|
||||
use app\common\traits\JumpTrait;
|
||||
use app\Request;
|
||||
use Closure;
|
||||
use Wolfcode\RateLimiting\Bootstrap;
|
||||
|
||||
class RateLimiting
|
||||
{
|
||||
use JumpTrait;
|
||||
|
||||
/**
|
||||
* 启用限流器需要开启Redis
|
||||
* @param Request $request
|
||||
* @param Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): mixed
|
||||
{
|
||||
// 是否启用限流器
|
||||
if (!env('RATE_LIMITING_STATUS', false)) return $next($request);
|
||||
if ($request->method() == 'GET') return $next($request);
|
||||
$controller = $request->controller();
|
||||
$module = app('http')->getName();
|
||||
$appNamespace = config('app.app_namespace');
|
||||
$controllerClass = "app\\{$module}\\controller\\{$controller}{$appNamespace}";
|
||||
$controllerClass = str_replace('.', '\\', $controllerClass);
|
||||
$action = $request->action();
|
||||
try {
|
||||
Bootstrap::init($controllerClass, $action, [
|
||||
# Redis 相关配置
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'port' => (int)env('REDIS_PORT', 6379),
|
||||
'password' => env('REDIS_PASSWORD', ''),
|
||||
'prefix' => env('REDIS_PREFIX', ''),
|
||||
'database' => (int)env('REDIS_DATABASE', 0),
|
||||
]);
|
||||
}catch (\Throwable $exception) {
|
||||
$this->error($exception->getMessage());
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
104
app/admin/middleware/SystemLog.php
Normal file
104
app/admin/middleware/SystemLog.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\middleware;
|
||||
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\MiddlewareAnnotation;
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use app\admin\service\SystemLogService;
|
||||
use app\common\traits\JumpTrait;
|
||||
use app\Request;
|
||||
use Closure;
|
||||
use ReflectionException;
|
||||
|
||||
class SystemLog
|
||||
{
|
||||
use JumpTrait;
|
||||
|
||||
/**
|
||||
* 敏感信息字段,日志记录时需要加密
|
||||
* @var array
|
||||
*/
|
||||
protected array $sensitiveParams = [
|
||||
'password',
|
||||
'password_again',
|
||||
'phone',
|
||||
'mobile',
|
||||
];
|
||||
|
||||
/**
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$response = $next($request);
|
||||
if (!env('APP_ADMIN_SYSTEM_LOG', true)) return $response;
|
||||
$params = $request->param();
|
||||
if (isset($params['s'])) unset($params['s']);
|
||||
foreach ($params as $key => $val) {
|
||||
in_array($key, $this->sensitiveParams) && $params[$key] = "***********";
|
||||
}
|
||||
$method = strtolower($request->method());
|
||||
$url = $request->url();
|
||||
|
||||
if (env('APP_DEBUG')) {
|
||||
trace(['url' => $url, 'method' => $method, 'params' => $params,], 'requestDebugInfo');
|
||||
}
|
||||
if ($request->isAjax()) {
|
||||
if (in_array($method, ['post', 'put', 'delete'])) {
|
||||
|
||||
$title = '';
|
||||
try {
|
||||
$pathInfo = $request->pathinfo();
|
||||
$pathInfoExp = explode('/', $pathInfo);
|
||||
$_action = end($pathInfoExp) ?? '';
|
||||
$pathInfoExp = explode('.', $pathInfoExp[0] ?? '');
|
||||
$_name = $pathInfoExp[0] ?? '';
|
||||
$_controller = ucfirst($pathInfoExp[1] ?? '');
|
||||
$className = $_controller ? "app\admin\controller\\{$_name}\\{$_controller}" : "app\admin\controller\\{$_name}";
|
||||
if ($_name && $_action) {
|
||||
$reflectionMethod = new \ReflectionMethod($className, $_action);
|
||||
$attributes = $reflectionMethod->getAttributes(MiddlewareAnnotation::class);
|
||||
foreach ($attributes as $attribute) {
|
||||
$annotation = $attribute->newInstance();
|
||||
$_ignore = (array)$annotation->ignore;
|
||||
if (in_array('log', array_map('strtolower', $_ignore))) return $response;
|
||||
}
|
||||
$controllerTitle = $nodeTitle = '';
|
||||
$controllerAttributes = (new \ReflectionClass($className))->getAttributes(ControllerAnnotation::class);
|
||||
$actionAttributes = $reflectionMethod->getAttributes(NodeAnnotation::class);
|
||||
foreach ($controllerAttributes as $controllerAttribute) {
|
||||
$controllerAnnotation = $controllerAttribute->newInstance();
|
||||
$controllerTitle = $controllerAnnotation->title ?? '';
|
||||
}
|
||||
foreach ($actionAttributes as $actionAttribute) {
|
||||
$actionAnnotation = $actionAttribute->newInstance();
|
||||
$nodeTitle = $actionAnnotation->title ?? '';
|
||||
}
|
||||
$title = $controllerTitle . ' - ' . $nodeTitle;
|
||||
}
|
||||
}catch (\Throwable $exception) {
|
||||
}
|
||||
|
||||
$ip = $request->ip();
|
||||
// 限制记录的响应内容,避免过大
|
||||
$_response = json_encode($response->getData(), JSON_UNESCAPED_UNICODE);
|
||||
$_response = mb_substr($_response, 0, 3000, 'utf-8');
|
||||
|
||||
$data = [
|
||||
'admin_id' => session('admin.id'),
|
||||
'title' => $title,
|
||||
'url' => $url,
|
||||
'method' => $method,
|
||||
'ip' => $ip,
|
||||
'content' => json_encode($params, JSON_UNESCAPED_UNICODE),
|
||||
'response' => $_response,
|
||||
'useragent' => $request->server('HTTP_USER_AGENT'),
|
||||
'create_time' => time(),
|
||||
];
|
||||
SystemLogService::instance()->save($data);
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
25
app/admin/model/BlackIp.php
Normal file
25
app/admin/model/BlackIp.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
class BlackIp extends TimeModel
|
||||
{
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'deleteTime' => 'delete_time',
|
||||
];
|
||||
}
|
||||
|
||||
// * +++++++++++++++++++++++++++
|
||||
// | 以下两种写法适用于 with 关联
|
||||
// * +++++++++++++++++++++++++
|
||||
|
||||
// public function cate(): BelongsTo
|
||||
// {
|
||||
// return $this->belongsTo('app\admin\model\MallCate', 'cate_id', 'id');
|
||||
// }
|
||||
|
||||
}
|
||||
18
app/admin/model/MallCate.php
Normal file
18
app/admin/model/MallCate.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
class MallCate extends TimeModel
|
||||
{
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'deleteTime' => 'delete_time',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
32
app/admin/model/MallGoods.php
Normal file
32
app/admin/model/MallGoods.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
use think\model\relation\BelongsTo;
|
||||
use think\model\relation\HasOne;
|
||||
|
||||
class MallGoods extends TimeModel
|
||||
{
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'deleteTime' => 'delete_time',
|
||||
];
|
||||
}
|
||||
|
||||
// * +++++++++++++++++++++++++++
|
||||
// | 以下两种写法适用于 with 关联
|
||||
// * +++++++++++++++++++++++++
|
||||
|
||||
// public function cate(): BelongsTo
|
||||
// {
|
||||
// return $this->belongsTo('app\admin\model\MallCate', 'cate_id', 'id');
|
||||
// }
|
||||
|
||||
public function cate(): HasOne
|
||||
{
|
||||
return $this->hasOne(MallCate::class, 'id', 'cate_id');
|
||||
}
|
||||
|
||||
}
|
||||
27
app/admin/model/MallOrder.php
Normal file
27
app/admin/model/MallOrder.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
use think\model\relation\HasOne;
|
||||
|
||||
class MallOrder extends TimeModel
|
||||
{
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'deleteTime' => 'delete_time',
|
||||
];
|
||||
}
|
||||
|
||||
// * +++++++++++++++++++++++++++
|
||||
// | 以下两种写法适用于 with 关联
|
||||
// * +++++++++++++++++++++++++
|
||||
|
||||
// public function cate(): BelongsTo
|
||||
// {
|
||||
// return $this->belongsTo('app\admin\model\MallCate', 'cate_id', 'id');
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
36
app/admin/model/SystemAdmin.php
Normal file
36
app/admin/model/SystemAdmin.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
class SystemAdmin extends TimeModel
|
||||
{
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'deleteTime' => 'delete_time',
|
||||
];
|
||||
}
|
||||
|
||||
public array $notes = [
|
||||
'login_type' => [
|
||||
1 => '密码登录',
|
||||
2 => '密码 + 谷歌验证码登录'
|
||||
],
|
||||
];
|
||||
|
||||
public static function getAuthIdsAttr($value): array
|
||||
{
|
||||
if (!$value) return [];
|
||||
return explode(',', $value);
|
||||
}
|
||||
|
||||
public static function getAuthList(): array
|
||||
{
|
||||
return SystemAuth::where('status', 1)->column('title', 'id');
|
||||
}
|
||||
|
||||
}
|
||||
61
app/admin/model/SystemAuth.php
Normal file
61
app/admin/model/SystemAuth.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
|
||||
class SystemAuth extends TimeModel
|
||||
{
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'deleteTime' => 'delete_time',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据角色ID获取授权节点
|
||||
* @param $authId
|
||||
* @return array
|
||||
* @throws DataNotFoundException
|
||||
* @throws DbException
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
public static function getAuthorizeNodeListByAdminId($authId): array
|
||||
{
|
||||
$checkNodeList = (new SystemAuthNode())
|
||||
->where('auth_id', $authId)
|
||||
->column('node_id');
|
||||
$systemNode = new SystemNode();
|
||||
$nodeList = $systemNode
|
||||
->where('is_auth', 1)
|
||||
->field('id,node,title,type,is_auth')
|
||||
->select()
|
||||
->toArray();
|
||||
$newNodeList = [];
|
||||
foreach ($nodeList as $vo) {
|
||||
if ($vo['type'] == 1) {
|
||||
$vo = array_merge($vo, ['field' => 'node', 'spread' => true]);
|
||||
$vo['checked'] = false;
|
||||
$vo['title'] = "{$vo['title']}【{$vo['node']}】";
|
||||
$children = [];
|
||||
foreach ($nodeList as $v) {
|
||||
if ($v['type'] == 2 && strpos($v['node'], $vo['node'] . '/') !== false) {
|
||||
$v = array_merge($v, ['field' => 'node', 'spread' => true]);
|
||||
$v['checked'] = in_array($v['id'], $checkNodeList) ? true : false;
|
||||
$v['title'] = "{$v['title']}【{$v['node']}】";
|
||||
$children[] = $v;
|
||||
}
|
||||
}
|
||||
!empty($children) && $vo['children'] = $children;
|
||||
$newNodeList[] = $vo;
|
||||
}
|
||||
}
|
||||
return $newNodeList;
|
||||
}
|
||||
|
||||
}
|
||||
10
app/admin/model/SystemAuthNode.php
Normal file
10
app/admin/model/SystemAuthNode.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
class SystemAuthNode extends TimeModel
|
||||
{
|
||||
|
||||
}
|
||||
21
app/admin/model/SystemConfig.php
Normal file
21
app/admin/model/SystemConfig.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
use think\Model;
|
||||
|
||||
class SystemConfig extends TimeModel
|
||||
{
|
||||
public static function onBeforeUpdate(Model $model): void
|
||||
{
|
||||
if ($model->getData('name') === 'upload_allow_ext') {
|
||||
//去除 php
|
||||
$model->value = implode(',',array_map(function ($ext) {
|
||||
return trim(strtolower($ext), ' ');
|
||||
}, array_filter(explode(',', $model->getData('value')), function ($ext) {
|
||||
return strtolower(trim($ext)) !== 'php';
|
||||
})));
|
||||
}
|
||||
}
|
||||
}
|
||||
29
app/admin/model/SystemLog.php
Normal file
29
app/admin/model/SystemLog.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use app\admin\service\SystemLogService;
|
||||
use app\common\model\TimeModel;
|
||||
use think\model\relation\BelongsTo;
|
||||
|
||||
class SystemLog extends TimeModel
|
||||
{
|
||||
|
||||
protected array $type = [
|
||||
'content' => 'json',
|
||||
'response' => 'json',
|
||||
];
|
||||
|
||||
protected function init(): void
|
||||
{
|
||||
SystemLogService::instance()->detectTable();
|
||||
}
|
||||
|
||||
|
||||
public function admin(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo('app\admin\model\SystemAdmin', 'admin_id', 'id');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
132
app/admin/model/SystemMenu.php
Normal file
132
app/admin/model/SystemMenu.php
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use app\common\constants\MenuConstant;
|
||||
use app\common\model\TimeModel;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
use think\Model;
|
||||
|
||||
class SystemMenu extends TimeModel
|
||||
{
|
||||
public static array $menuTypeList = [
|
||||
'system' => '系统管理',
|
||||
'mall' => '商城管理',
|
||||
'article' => '文章管理',
|
||||
];
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'deleteTime' => 'delete_time',
|
||||
];
|
||||
}
|
||||
|
||||
public static function onBeforeUpdate(Model $model): void
|
||||
{
|
||||
$model->system = 0; // 系统添加
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ModelNotFoundException
|
||||
* @throws DbException
|
||||
* @throws DataNotFoundException
|
||||
*/
|
||||
public static function getPidMenuList(): array
|
||||
{
|
||||
$list = self::field('id,pid,title')->where([
|
||||
['pid', '<>', MenuConstant::HOME_PID],
|
||||
['status', '=', 1],
|
||||
])->select()->toArray();
|
||||
|
||||
$pidMenuList = self::buildPidMenu(0, $list);
|
||||
return array_merge([[
|
||||
'id' => 0,
|
||||
'pid' => 0,
|
||||
'title' => '顶级菜单',
|
||||
]], $pidMenuList);
|
||||
}
|
||||
|
||||
protected static function buildPidMenu($pid, $list, $level = 0): array
|
||||
{
|
||||
$newList = [];
|
||||
foreach ($list as $vo) {
|
||||
if ($vo['pid'] == $pid) {
|
||||
$level++;
|
||||
foreach ($newList as $v) {
|
||||
if ($vo['pid'] == $v['pid'] && isset($v['level'])) {
|
||||
$level = $v['level'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
$vo['level'] = $level;
|
||||
if ($level > 1) {
|
||||
$repeatString = " ";
|
||||
$markString = str_repeat("{$repeatString}├{$repeatString}", $level - 1);
|
||||
$vo['title'] = $markString . $vo['title'];
|
||||
}
|
||||
$newList[] = $vo;
|
||||
$childList = self::buildPidMenu($vo['id'], $list, $level);
|
||||
!empty($childList) && $newList = array_merge($newList, $childList);
|
||||
}
|
||||
|
||||
}
|
||||
return $newList;
|
||||
}
|
||||
|
||||
public static function refreshMenu($nodeList): void
|
||||
{
|
||||
$nodeList = array_filter($nodeList, function ($item) {
|
||||
return $item['type'] == 1;
|
||||
});
|
||||
$menuList = array_map(function ($item) {
|
||||
return "{$item['node']}/index";
|
||||
}, $nodeList);
|
||||
|
||||
if (!empty($menuList)) {
|
||||
$hasMenu = (new self())->whereIn('href', $menuList)->column('href');
|
||||
$needInsertMenu = array_diff($menuList, $hasMenu);
|
||||
$insertNode = array_filter($nodeList, function ($item) use ($needInsertMenu) {
|
||||
return in_array("{$item['node']}/index", $needInsertMenu);
|
||||
});
|
||||
$data = [];
|
||||
foreach ($insertNode as $vo) {
|
||||
$pidText = explode('.', $vo['node']);
|
||||
if (isset($pidText[0]) && self::$menuTypeList[$pidText[0]]) {
|
||||
$pidMenuId = (new self())->where([
|
||||
'title' => self::$menuTypeList[$pidText[0]],
|
||||
'pid' => 0,
|
||||
])->value('id');
|
||||
if (empty($pidMenuId)) {
|
||||
$pidMenuId = (new self())->insertGetId([
|
||||
'title' => self::$menuTypeList[$pidText[0]],
|
||||
'href' => '',
|
||||
'icon' => 'fa fa-list',
|
||||
'pid' => 0,
|
||||
'status' => 1,
|
||||
'system' => 0, // 系统添加
|
||||
'create_time' => time(),
|
||||
]);
|
||||
}
|
||||
$data[] = [
|
||||
'title' => $vo['title'],
|
||||
'href' => "{$vo['node']}/index",
|
||||
'icon' => 'fa fa-list',
|
||||
'target' => '_self',
|
||||
'pid' => $pidMenuId,
|
||||
'status' => 1,
|
||||
'system' => 1, // 系统添加
|
||||
'create_time' => time(),
|
||||
];
|
||||
}
|
||||
|
||||
};
|
||||
if (count($data) > 0) (new self())->insertAll($data);
|
||||
self::getPidMenuList(); // 刷新菜单缓存
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
35
app/admin/model/SystemNode.php
Normal file
35
app/admin/model/SystemNode.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
class SystemNode extends TimeModel
|
||||
{
|
||||
|
||||
public static function getNodeTreeList(): array
|
||||
{
|
||||
$list = self::select()->toArray();
|
||||
return self::buildNodeTree($list);
|
||||
}
|
||||
|
||||
protected static function buildNodeTree($list): array
|
||||
{
|
||||
$newList = [];
|
||||
$repeatString = " ";
|
||||
foreach ($list as $vo) {
|
||||
if ($vo['type'] == 1) {
|
||||
$newList[] = $vo;
|
||||
foreach ($list as $v) {
|
||||
if ($v['type'] == 2 && str_contains($v['node'], $vo['node'] . '/')) {
|
||||
$v['node'] = "{$repeatString}├{$repeatString}" . $v['node'];
|
||||
$newList[] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $newList;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
17
app/admin/model/SystemQuick.php
Normal file
17
app/admin/model/SystemQuick.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
class SystemQuick extends TimeModel
|
||||
{
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'deleteTime' => 'delete_time',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
10
app/admin/model/SystemUploadfile.php
Normal file
10
app/admin/model/SystemUploadfile.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
class SystemUploadfile extends TimeModel
|
||||
{
|
||||
|
||||
}
|
||||
20
app/admin/service/ConfigService.php
Normal file
20
app/admin/service/ConfigService.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\service;
|
||||
|
||||
use think\facade\Cache;
|
||||
|
||||
class ConfigService
|
||||
{
|
||||
|
||||
public static function getVersion()
|
||||
{
|
||||
$version = cache('site_version');
|
||||
if (empty($version)) {
|
||||
$version = sysConfig('site', 'site_version');
|
||||
cache('site_version', $version);
|
||||
}
|
||||
return $version;
|
||||
}
|
||||
|
||||
}
|
||||
24
app/admin/service/NodeService.php
Normal file
24
app/admin/service/NodeService.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\service;
|
||||
|
||||
|
||||
use app\admin\service\auth\Node;
|
||||
|
||||
class NodeService
|
||||
{
|
||||
|
||||
/**
|
||||
* 获取节点服务
|
||||
* @return array
|
||||
* @throws \Doctrine\Common\Annotations\AnnotationException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function getNodeList()
|
||||
{
|
||||
$basePath = base_path() . 'admin' . DIRECTORY_SEPARATOR . 'controller';
|
||||
$baseNamespace = "app\admin\controller";
|
||||
$nodeList = (new Node($basePath, $baseNamespace))->getNodeList();
|
||||
return $nodeList;
|
||||
}
|
||||
}
|
||||
128
app/admin/service/SystemLogService.php
Normal file
128
app/admin/service/SystemLogService.php
Normal file
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\service;
|
||||
|
||||
use think\facade\Cache;
|
||||
use think\facade\Db;
|
||||
use think\facade\Config;
|
||||
use think\facade\Env;
|
||||
|
||||
/**
|
||||
* 系统日志表
|
||||
* Class SystemLogService
|
||||
* @package app\admin\service
|
||||
*/
|
||||
class SystemLogService
|
||||
{
|
||||
|
||||
protected static ?SystemLogService $instance = null;
|
||||
|
||||
/**
|
||||
* 表前缀
|
||||
* @var string
|
||||
*/
|
||||
protected string $tablePrefix;
|
||||
|
||||
/**
|
||||
* 表后缀
|
||||
* @var string
|
||||
*/
|
||||
protected string $tableSuffix;
|
||||
|
||||
/**
|
||||
* 表名
|
||||
* @var string
|
||||
*/
|
||||
protected string $tableName;
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
* SystemLogService constructor.
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
$this->tablePrefix = Config::get('database.connections.mysql.prefix');
|
||||
$this->tableSuffix = date('Ym', time());
|
||||
$this->tableName = "{$this->tablePrefix}system_log_{$this->tableSuffix}";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实例对象
|
||||
* @return SystemLogService
|
||||
*/
|
||||
public static function instance(): SystemLogService
|
||||
{
|
||||
if (is_null(self::$instance)) {
|
||||
self::$instance = new static();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 保存数据
|
||||
* @param $data
|
||||
* @return bool|string
|
||||
*/
|
||||
public function save($data): bool|string
|
||||
{
|
||||
Db::startTrans();
|
||||
try {
|
||||
$this->detectTable();
|
||||
Db::table($this->tableName)->strict(false)->insert($data);
|
||||
Db::commit();
|
||||
}catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return $e->getMessage();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测数据表
|
||||
* @return bool
|
||||
*/
|
||||
public function detectTable(): bool
|
||||
{
|
||||
$_key = "system_log_{$this->tableName}_table";
|
||||
// 手动删除日志表时候 记得清除缓存
|
||||
$isset = Cache::get($_key);
|
||||
if ($isset) return true;
|
||||
$check = Db::query("show tables like '{$this->tableName}'");
|
||||
if (empty($check)) {
|
||||
$sql = $this->getCreateSql();
|
||||
Db::execute($sql);
|
||||
}
|
||||
Cache::set($_key, !empty($check));
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getAllTableList()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据后缀获取创建表的sql
|
||||
* @return string
|
||||
*/
|
||||
protected function getCreateSql(): string
|
||||
{
|
||||
return <<<EOT
|
||||
CREATE TABLE `{$this->tableName}` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`admin_id` int(10) unsigned DEFAULT '0' COMMENT '管理员ID',
|
||||
`url` varchar(1500) NOT NULL DEFAULT '' COMMENT '操作页面',
|
||||
`method` varchar(50) NOT NULL COMMENT '请求方法',
|
||||
`title` varchar(100) DEFAULT '' COMMENT '日志标题',
|
||||
`content` json NOT NULL COMMENT '请求数据',
|
||||
`response` json DEFAULT NULL COMMENT '回调数据',
|
||||
`ip` varchar(50) NOT NULL DEFAULT '' COMMENT 'IP',
|
||||
`useragent` varchar(255) DEFAULT '' COMMENT 'User-Agent',
|
||||
`create_time` int(10) DEFAULT NULL COMMENT '操作时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='后台操作日志表 - {$this->tableSuffix}';
|
||||
EOT;
|
||||
}
|
||||
|
||||
}
|
||||
50
app/admin/service/TriggerService.php
Normal file
50
app/admin/service/TriggerService.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\service;
|
||||
|
||||
use think\facade\Cache;
|
||||
|
||||
class TriggerService
|
||||
{
|
||||
|
||||
/**
|
||||
* 更新菜单缓存
|
||||
* @param null $adminId
|
||||
* @return bool
|
||||
*/
|
||||
public static function updateMenu($adminId = null)
|
||||
{
|
||||
if(empty($adminId)){
|
||||
Cache::tag('initAdmin')->clear();
|
||||
}else{
|
||||
Cache::delete('initAdmin_' . $adminId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新节点缓存
|
||||
* @param null $adminId
|
||||
* @return bool
|
||||
*/
|
||||
public static function updateNode($adminId = null)
|
||||
{
|
||||
if(empty($adminId)){
|
||||
Cache::tag('authNode')->clear();
|
||||
}else{
|
||||
Cache::delete('allAuthNode_' . $adminId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统设置缓存
|
||||
* @return bool
|
||||
*/
|
||||
public static function updateSysConfig(): bool
|
||||
{
|
||||
Cache::tag('sysConfig')->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
226
app/admin/service/UploadService.php
Normal file
226
app/admin/service/UploadService.php
Normal file
@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\service;
|
||||
|
||||
use app\admin\model\SystemUploadfile;
|
||||
use OSS\Core\OssException;
|
||||
use OSS\Credentials\EnvironmentVariableCredentialsProvider;
|
||||
use OSS\OssClient;
|
||||
use think\facade\Env;
|
||||
use think\file\UploadedFile;
|
||||
use think\helper\Str;
|
||||
use Qcloud\Cos\Client;
|
||||
use Exception;
|
||||
use Qiniu\Storage\UploadManager;
|
||||
use Qiniu\Auth;
|
||||
|
||||
class UploadService
|
||||
{
|
||||
public static ?UploadService $_instance = null;
|
||||
protected array $options = [];
|
||||
private array $saveData;
|
||||
|
||||
public static function instance(): ?UploadService
|
||||
{
|
||||
if (!static::$_instance) static::$_instance = new static();
|
||||
return static::$_instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @return $this
|
||||
*/
|
||||
public function setConfig(array $options = []): UploadService
|
||||
{
|
||||
$this->options = $options;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getConfig(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UploadedFile $file
|
||||
* @param string $base_path
|
||||
* @return string
|
||||
*/
|
||||
protected function setFilePath(UploadedFile $file, string $base_path = ''): string
|
||||
{
|
||||
$path = date('Ymd') . '/' . Str::random(3) . time() . Str::random() . '.' . $file->extension();
|
||||
return $base_path . $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UploadedFile $file
|
||||
* @return UploadService
|
||||
*/
|
||||
protected function setSaveData(UploadedFile $file): static
|
||||
{
|
||||
$options = $this->options;
|
||||
$data = [
|
||||
'upload_type' => $options['upload_type'],
|
||||
'original_name' => $file->getOriginalName(),
|
||||
'mime_type' => $file->getMime(),
|
||||
'file_size' => $file->getSize(),
|
||||
'file_ext' => strtolower($file->extension()),
|
||||
'create_time' => time(),
|
||||
];
|
||||
$this->saveData = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地存储
|
||||
*
|
||||
* @param UploadedFile $file
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
public function local(UploadedFile $file, string $type = ''): array
|
||||
{
|
||||
if ($file->isValid()) {
|
||||
$base_path = '/storage/' . date('Ymd') . '/';
|
||||
// 上传文件的目标文件夹
|
||||
$destinationPath = public_path() . $base_path;
|
||||
$this->setSaveData($file);
|
||||
// 将文件移动到目标文件夹中
|
||||
$move = $file->move($destinationPath, Str::random(3) . time() . Str::random() . session('admin.id') . '.' . $file->extension());
|
||||
$url = $base_path . $move->getFilename();
|
||||
$data = ['url' => $url];
|
||||
$this->save($url);
|
||||
return ['code' => 1, 'data' => $data];
|
||||
}
|
||||
$data = '上传失败';
|
||||
return ['code' => 0, 'data' => $data];
|
||||
}
|
||||
|
||||
/**
|
||||
* 阿里云OSS
|
||||
*
|
||||
* @param UploadedFile $file
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
public function oss(UploadedFile $file, string $type = ''): array
|
||||
{
|
||||
$config = $this->getConfig();
|
||||
$accessKeyId = $config['oss_access_key_id'];
|
||||
$accessKeySecret = $config['oss_access_key_secret'];
|
||||
$endpoint = $config['oss_endpoint'];
|
||||
$bucket = $config['oss_bucket'];
|
||||
// 升级 aliyuncs/oss-sdk-php 到 v2.7.2 以上, 使用签名 v4 版本
|
||||
putenv('OSS_ACCESS_KEY_ID=' . $accessKeyId);
|
||||
putenv('OSS_ACCESS_KEY_SECRET=' . $accessKeySecret);
|
||||
$region = str_replace(['http://oss-', 'https://oss-', 'oss-'], '', explode('.aliyuncs.com', $endpoint)[0] ?? '');
|
||||
$provider = new EnvironmentVariableCredentialsProvider();
|
||||
$args = [
|
||||
"provider" => $provider,
|
||||
"endpoint" => $endpoint,
|
||||
"signatureVersion" => OssClient::OSS_SIGNATURE_VERSION_V4,
|
||||
"region" => $region
|
||||
];
|
||||
if ($file->isValid()) {
|
||||
$object = $this->setFilePath($file, Env::get('EASYADMIN.OSS_STATIC_PREFIX', 'easyadmin8') . '/');
|
||||
try {
|
||||
$ossClient = new OssClient($args);
|
||||
$_rs = $ossClient->putObject($bucket, $object, file_get_contents($file->getRealPath()));
|
||||
$oss_request_url = $_rs['oss-request-url'] ?? '';
|
||||
if (empty($oss_request_url)) return ['code' => 0, 'data' => '上传至OSS失败'];
|
||||
$oss_request_url = str_replace('http://', 'https://', $oss_request_url);
|
||||
$this->setSaveData($file);
|
||||
} catch (OssException $e) {
|
||||
return ['code' => 0, 'data' => $e->getMessage()];
|
||||
}
|
||||
$data = ['url' => $oss_request_url];
|
||||
$this->save($oss_request_url);
|
||||
return ['code' => 1, 'data' => $data];
|
||||
}
|
||||
$data = '上传失败';
|
||||
return ['code' => 0, 'data' => $data];
|
||||
}
|
||||
|
||||
/**
|
||||
* 腾讯云cos
|
||||
*
|
||||
* @param UploadedFile $file
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
public function cos(UploadedFile $file, string $type = ''): array
|
||||
{
|
||||
$config = $this->getConfig();
|
||||
$secretId = $config['cos_secret_id']; //替换为用户的 secretId,请登录访问管理控制台进行查看和管理,https://console.cloud.tencent.com/cam/capi
|
||||
$secretKey = $config['cos_secret_key']; //替换为用户的 secretKey,请登录访问管理控制台进行查看和管理,https://console.cloud.tencent.com/cam/capi
|
||||
$region = $config['cos_region']; //替换为用户的 region,已创建桶归属的region可以在控制台查看,https://console.cloud.tencent.com/cos5/bucket
|
||||
if ($file->isValid()) {
|
||||
$cosClient = new Client(
|
||||
[
|
||||
'region' => $region,
|
||||
'schema' => 'http',
|
||||
'credentials' => ['secretId' => $secretId, 'secretKey' => $secretKey,
|
||||
],
|
||||
]);
|
||||
try {
|
||||
$object = $this->setFilePath($file, Env::get('EASYADMIN.OSS_STATIC_PREFIX', 'easyadmin8') . '/');
|
||||
$result = $cosClient->upload(
|
||||
$config['cos_bucket'], //存储桶名称,由BucketName-Appid 组成,可以在COS控制台查看 https://console.cloud.tencent.com/cos5/bucket
|
||||
$object, //此处的 key 为对象键
|
||||
file_get_contents($file->getRealPath())
|
||||
);
|
||||
$location = $result['Location'] ?? '';
|
||||
if (empty($location)) return ['code' => 0, 'data' => '上传至COS失败'];
|
||||
$location = 'https://' . $location;
|
||||
$this->setSaveData($file);
|
||||
}catch (Exception $e) {
|
||||
return ['code' => 0, 'data' => $e->getMessage()];
|
||||
}
|
||||
$data = ['url' => $location];
|
||||
$this->save($location);
|
||||
return ['code' => 1, 'data' => $data];
|
||||
}
|
||||
$data = '上传失败';
|
||||
return ['code' => 0, 'data' => $data];
|
||||
}
|
||||
|
||||
/**
|
||||
* 七牛云
|
||||
*
|
||||
* @param UploadedFile $file
|
||||
* @param string $type
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function qnoss(UploadedFile $file, string $type = ''): array
|
||||
{
|
||||
if (!$file->isValid()) return ['code' => 1, 'data' => '上传验证失败'];
|
||||
$uploadMgr = new UploadManager();
|
||||
$config = $this->getConfig();
|
||||
$accessKey = $config['qnoss_access_key'];
|
||||
$secretKey = $config['qnoss_secret_key'];
|
||||
$bucket = $config['qnoss_bucket'];
|
||||
$domain = $config['qnoss_domain'];
|
||||
$auth = new Auth($accessKey, $secretKey);
|
||||
$token = $auth->uploadToken($bucket);
|
||||
$object = $this->setFilePath($file, Env::get('EASYADMIN.OSS_STATIC_PREFIX', 'easyadmin8') . '/');
|
||||
list($ret, $error) = $uploadMgr->putFile($token, $object, $file->getRealPath());
|
||||
if (empty($ret)) return ['code' => 0, 'data' => $error->getResponse()->error ?? '上传失败,请检查七牛云相关参数配置'];
|
||||
$url = $domain . "/" . $ret['key'];
|
||||
$data = ['url' => $url];
|
||||
$this->setSaveData($file);
|
||||
$this->save($url);
|
||||
return ['code' => 1, 'data' => $data];
|
||||
}
|
||||
|
||||
protected function save(string $url = ''): bool
|
||||
{
|
||||
$data = $this->saveData;
|
||||
$data['url'] = $url;
|
||||
$data['upload_time'] = time();
|
||||
return (new SystemUploadfile())->save($data);
|
||||
}
|
||||
}
|
||||
21
app/admin/service/annotation/ControllerAnnotation.php
Normal file
21
app/admin/service/annotation/ControllerAnnotation.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\service\annotation;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* controller 节点注解类
|
||||
*/
|
||||
#[Attribute]
|
||||
final class ControllerAnnotation
|
||||
{
|
||||
/**
|
||||
* @param string $title
|
||||
* @param bool $auth 是否需要权限
|
||||
* @param string|array $ignore
|
||||
*/
|
||||
public function __construct(public string $title = '', public bool $auth = true, public string|array $ignore = '')
|
||||
{
|
||||
}
|
||||
}
|
||||
19
app/admin/service/annotation/MiddlewareAnnotation.php
Normal file
19
app/admin/service/annotation/MiddlewareAnnotation.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\service\annotation;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_METHOD)]
|
||||
final class MiddlewareAnnotation
|
||||
{
|
||||
/** 过滤日志 */
|
||||
const IGNORE_LOG = 'LOG';
|
||||
|
||||
/** 免登录 */
|
||||
const IGNORE_LOGIN = 'LOGIN';
|
||||
|
||||
public function __construct(public string $type = '', public string|array $ignore = '')
|
||||
{
|
||||
}
|
||||
}
|
||||
25
app/admin/service/annotation/NodeAnnotation.php
Normal file
25
app/admin/service/annotation/NodeAnnotation.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\service\annotation;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* action 节点注解类
|
||||
*/
|
||||
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_METHOD| Attribute::TARGET_PROPERTY)]
|
||||
final class NodeAnnotation
|
||||
{
|
||||
/** 过滤节点 */
|
||||
const IGNORE_NODE = 'NODE';
|
||||
|
||||
/**
|
||||
* @param string $title
|
||||
* @param bool $auth 是否需要权限
|
||||
* @param string|array $ignore
|
||||
*/
|
||||
public function __construct(public string $title = '', public bool $auth = true, public string|array $ignore = '')
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
155
app/admin/service/auth/Node.php
Normal file
155
app/admin/service/auth/Node.php
Normal file
@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\service\auth;
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationException;
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use Doctrine\Common\Annotations\AnnotationRegistry;
|
||||
use Doctrine\Common\Annotations\DocParser;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use app\admin\service\tool\CommonTool;
|
||||
use ReflectionException;
|
||||
|
||||
/**
|
||||
* 节点处理类
|
||||
* Class Node
|
||||
* @package EasyAdmin\auth
|
||||
*/
|
||||
class Node
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string 当前文件夹
|
||||
*/
|
||||
protected string $basePath;
|
||||
|
||||
/**
|
||||
* @var string 命名空间前缀
|
||||
*/
|
||||
protected string $baseNamespace;
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
* Node constructor.
|
||||
* @param string $basePath 读取的文件夹
|
||||
* @param string $baseNamespace 读取的命名空间前缀
|
||||
*/
|
||||
public function __construct(string $basePath, string $baseNamespace)
|
||||
{
|
||||
$this->basePath = $basePath;
|
||||
$this->baseNamespace = $baseNamespace;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有节点
|
||||
* @return array
|
||||
* @throws AnnotationException
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function getNodeList(): array
|
||||
{
|
||||
list($nodeList, $controllerList) = [[], $this->getControllerList()];
|
||||
|
||||
if (!empty($controllerList)) {
|
||||
AnnotationRegistry::loadAnnotationClass('class_exists');
|
||||
$parser = new DocParser();
|
||||
$parser->setIgnoreNotImportedAnnotations(true);
|
||||
$reader = new AnnotationReader($parser);
|
||||
|
||||
foreach ($controllerList as $controllerFormat => $controller) {
|
||||
|
||||
// 获取类和方法的注释信息
|
||||
$reflectionClass = new \ReflectionClass($controller);
|
||||
$methods = $reflectionClass->getMethods();
|
||||
$actionList = [];
|
||||
|
||||
// 遍历读取所有方法的注释的参数信息
|
||||
foreach ($methods as $method) {
|
||||
|
||||
// 忽略掉不需要的节点
|
||||
$property = $reflectionClass->getProperty('ignoreNode');
|
||||
$propertyAttributes = $property->getAttributes(NodeAnnotation::class);
|
||||
if (!empty($propertyAttributes[0])) {
|
||||
$propertyAttribute = $propertyAttributes[0]->newInstance();
|
||||
if (in_array($method->name, $propertyAttribute->ignore)) continue;
|
||||
}
|
||||
|
||||
$attributes = $reflectionClass->getMethod($method->name)->getAttributes(NodeAnnotation::class);
|
||||
foreach ($attributes as $attribute) {
|
||||
$annotation = $attribute->newInstance();
|
||||
if (!empty($annotation->ignore)) if (strtolower($annotation->ignore) == 'node') continue;
|
||||
$actionList[] = [
|
||||
'node' => $controllerFormat . '/' . $method->name,
|
||||
'title' => $annotation->title ?? null,
|
||||
'is_auth' => $annotation->auth ?? false,
|
||||
'type' => 2,
|
||||
];
|
||||
}
|
||||
}
|
||||
// 方法非空才读取控制器注解
|
||||
if (!empty($actionList)) {
|
||||
// 读取Controller的注解
|
||||
$attributes = $reflectionClass->getAttributes(ControllerAnnotation::class);
|
||||
foreach ($attributes as $attribute) {
|
||||
$controllerAnnotation = $attribute->newInstance();
|
||||
$nodeList[] = [
|
||||
'node' => $controllerFormat,
|
||||
'title' => $controllerAnnotation->title ?? null,
|
||||
'is_auth' => $controllerAnnotation->auth ?? false,
|
||||
'type' => 1,
|
||||
];
|
||||
}
|
||||
$nodeList = array_merge($nodeList, $actionList);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return $nodeList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有控制器
|
||||
* @return array
|
||||
*/
|
||||
public function getControllerList(): array
|
||||
{
|
||||
return $this->readControllerFiles($this->basePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 遍历读取控制器文件
|
||||
* @param $path
|
||||
* @return array
|
||||
*/
|
||||
protected function readControllerFiles($path): array
|
||||
{
|
||||
list($list, $temp_list, $dirExplode) = [[], scandir($path), explode($this->basePath, $path)];
|
||||
$middleDir = !empty($dirExplode[1]) ? str_replace('/', '\\', substr($dirExplode[1], 1)) . "\\" : '';
|
||||
|
||||
foreach ($temp_list as $file) {
|
||||
// 排除根目录和没有开启注解的模块
|
||||
if ($file == ".." || $file == ".") {
|
||||
continue;
|
||||
}
|
||||
if (is_dir($path . DIRECTORY_SEPARATOR . $file)) {
|
||||
// 子文件夹,进行递归
|
||||
$childFiles = $this->readControllerFiles($path . DIRECTORY_SEPARATOR . $file);
|
||||
$list = array_merge($childFiles, $list);
|
||||
}else {
|
||||
// 判断是不是控制器
|
||||
$fileExplodeArray = explode('.', $file);
|
||||
if (count($fileExplodeArray) != 2 || end($fileExplodeArray) != 'php') {
|
||||
continue;
|
||||
}
|
||||
// 根目录下的文件
|
||||
$className = str_replace('.php', '', $file);
|
||||
$controllerFormat = str_replace('\\', '.', $middleDir) . CommonTool::humpToLine(lcfirst($className));
|
||||
$list[$controllerFormat] = "{$this->baseNamespace}\\{$middleDir}" . $className;
|
||||
}
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
}
|
||||
166
app/admin/service/console/CliEcho.php
Normal file
166
app/admin/service/console/CliEcho.php
Normal file
@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\service\console;
|
||||
|
||||
class CliEcho
|
||||
{
|
||||
|
||||
private array $foreground_colors = [];
|
||||
|
||||
private array $background_colors = [];
|
||||
|
||||
private static array $foregroundColors = [
|
||||
'black' => '0;30',
|
||||
'dark_gray' => '1;30',
|
||||
'blue' => '0;34',
|
||||
'light_blue' => '1;34',
|
||||
'green' => '0;32',
|
||||
'light_green' => '1;32',
|
||||
'cyan' => '0;36',
|
||||
'light_cyan' => '1;36',
|
||||
'red' => '0;31',
|
||||
'light_red' => '1;31',
|
||||
'purple' => '0;35',
|
||||
'light_purple' => '1;35',
|
||||
'brown' => '0;33',
|
||||
'yellow' => '1;33',
|
||||
'light_gray' => '0;37',
|
||||
'white' => '1;37',
|
||||
];
|
||||
|
||||
private static $backgroundColors = [
|
||||
'black' => '40',
|
||||
'red' => '41',
|
||||
'green' => '42',
|
||||
'yellow' => '43',
|
||||
'blue' => '44',
|
||||
'magenta' => '45',
|
||||
'cyan' => '46',
|
||||
'light_gray' => '47',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Set up shell colors
|
||||
$this->foreground_colors['black'] = '0;30';
|
||||
$this->foreground_colors['dark_gray'] = '1;30';
|
||||
$this->foreground_colors['blue'] = '0;34';
|
||||
$this->foreground_colors['light_blue'] = '1;34';
|
||||
$this->foreground_colors['green'] = '0;32';
|
||||
$this->foreground_colors['light_green'] = '1;32';
|
||||
$this->foreground_colors['cyan'] = '0;36';
|
||||
$this->foreground_colors['light_cyan'] = '1;36';
|
||||
$this->foreground_colors['red'] = '0;31';
|
||||
$this->foreground_colors['light_red'] = '1;31';
|
||||
$this->foreground_colors['purple'] = '0;35';
|
||||
$this->foreground_colors['light_purple'] = '1;35';
|
||||
$this->foreground_colors['brown'] = '0;33';
|
||||
$this->foreground_colors['yellow'] = '1;33';
|
||||
$this->foreground_colors['light_gray'] = '0;37';
|
||||
$this->foreground_colors['white'] = '1;37';
|
||||
$this->background_colors['black'] = '40';
|
||||
$this->background_colors['red'] = '41';
|
||||
$this->background_colors['green'] = '42';
|
||||
$this->background_colors['yellow'] = '43';
|
||||
$this->background_colors['blue'] = '44';
|
||||
$this->background_colors['magenta'] = '45';
|
||||
$this->background_colors['cyan'] = '46';
|
||||
$this->background_colors['light_gray'] = '47';
|
||||
}
|
||||
|
||||
// Returns colored string
|
||||
public function getColoredString($string, $foreground_color = null, $background_color = null, $new_line = false): string
|
||||
{
|
||||
$colored_string = '';
|
||||
// Check if given foreground color found
|
||||
if (isset($this->foreground_colors[$foreground_color])) {
|
||||
$colored_string .= "\033[" . $this->foreground_colors[$foreground_color] . 'm';
|
||||
}
|
||||
// Check if given background color found
|
||||
if (isset($this->background_colors[$background_color])) {
|
||||
$colored_string .= "\033[" . $this->background_colors[$background_color] . 'm';
|
||||
}
|
||||
// Add string and end coloring
|
||||
$colored_string .= $string . "\033[0m";
|
||||
return $new_line ? $colored_string . PHP_EOL : $colored_string;
|
||||
}
|
||||
|
||||
// Returns all foreground color names
|
||||
public function getForegroundColors(): array
|
||||
{
|
||||
return array_keys($this->foreground_colors);
|
||||
}
|
||||
|
||||
// Returns all background color names
|
||||
public function getBackgroundColors(): array
|
||||
{
|
||||
return array_keys($this->background_colors);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取带颜色的文字.
|
||||
*
|
||||
* @param string $string black|dark_gray|blue|light_blue|green|light_green|cyan|light_cyan|red|light_red|purple|brown|yellow|light_gray|white
|
||||
* @param string|null $foregroundColor 前景颜色 black|red|green|yellow|blue|magenta|cyan|light_gray
|
||||
* @param string|null $backgroundColor 背景颜色 同$foregroundColor
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function initColoredString(
|
||||
string $string,
|
||||
?string $foregroundColor = null,
|
||||
?string $backgroundColor = null
|
||||
): string
|
||||
{
|
||||
$coloredString = '';
|
||||
if (isset(static::$foregroundColors[$foregroundColor])) {
|
||||
$coloredString .= "\033[" . static::$foregroundColors[$foregroundColor] . 'm';
|
||||
}
|
||||
if (isset(static::$backgroundColors[$backgroundColor])) {
|
||||
$coloredString .= "\033[" . static::$backgroundColors[$backgroundColor] . 'm';
|
||||
}
|
||||
$coloredString .= $string . "\033[0m";
|
||||
return $coloredString;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出提示信息.
|
||||
*
|
||||
* @param $msg
|
||||
*/
|
||||
public static function notice($msg): void
|
||||
{
|
||||
fwrite(STDOUT, self::initColoredString($msg, 'light_gray') . PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出错误信息.
|
||||
*
|
||||
* @param $msg
|
||||
*/
|
||||
public static function error($msg): void
|
||||
{
|
||||
fwrite(STDERR, self::initColoredString($msg, 'white', 'red') . PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出警告信息.
|
||||
*
|
||||
* @param $msg
|
||||
*/
|
||||
public static function warn($msg): void
|
||||
{
|
||||
fwrite(STDOUT, self::initColoredString($msg, 'red', 'yellow') . PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出成功信息.
|
||||
*
|
||||
* @param $msg
|
||||
*/
|
||||
public static function success($msg): void
|
||||
{
|
||||
fwrite(STDOUT, self::initColoredString($msg, 'light_cyan') . PHP_EOL);
|
||||
}
|
||||
|
||||
}
|
||||
1575
app/admin/service/curd/BuildCurd.php
Normal file
1575
app/admin/service/curd/BuildCurd.php
Normal file
File diff suppressed because it is too large
Load Diff
8
app/admin/service/curd/exceptions/CurdException.php
Normal file
8
app/admin/service/curd/exceptions/CurdException.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace app\admin\service\curd\exceptions;
|
||||
|
||||
class CurdException extends \Exception
|
||||
{
|
||||
}
|
||||
8
app/admin/service/curd/exceptions/FileException.php
Normal file
8
app/admin/service/curd/exceptions/FileException.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace app\admin\service\curd\exceptions;
|
||||
|
||||
class FileException extends \Exception
|
||||
{
|
||||
}
|
||||
8
app/admin/service/curd/exceptions/TableException.php
Normal file
8
app/admin/service/curd/exceptions/TableException.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace app\admin\service\curd\exceptions;
|
||||
|
||||
class TableException extends \Exception
|
||||
{
|
||||
}
|
||||
28
app/admin/service/curd/templates/controller/controller.code
Normal file
28
app/admin/service/curd/templates/controller/controller.code
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace {{controllerNamespace}};
|
||||
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use think\App;
|
||||
|
||||
#[ControllerAnnotation(title: '{{controllerAnnotation}}')]
|
||||
class {{controllerName}} extends AdminController
|
||||
{
|
||||
|
||||
private array $notes;
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
self::$model = new {{modelFilename}}();
|
||||
$notes = self::$model::$notes;
|
||||
{{constructRelation}}
|
||||
$this->notes =$notes;
|
||||
$this->assign(compact('notes'));
|
||||
}
|
||||
|
||||
{{indexMethod}}
|
||||
|
||||
}
|
||||
21
app/admin/service/curd/templates/controller/indexMethod.code
Normal file
21
app/admin/service/curd/templates/controller/indexMethod.code
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
#[NodeAnnotation(title: '列表', auth: true)]
|
||||
public function index(\app\Request $request): \think\response\Json|string
|
||||
{
|
||||
if ($request->isAjax()) {
|
||||
if (input('selectFields')) {
|
||||
return $this->selectList();
|
||||
}
|
||||
list($page, $limit, $where) = $this->buildTableParams();
|
||||
$count = self::$model::where($where)->{{relationIndexMethod}}->count();
|
||||
$list = self::$model::where($where)->{{relationIndexMethod}}->page($page, $limit)->order($this->sort)->select()->toArray();
|
||||
$data = [
|
||||
'code' => 0,
|
||||
'msg' => '',
|
||||
'count' => $count,
|
||||
'data' => $list,
|
||||
];
|
||||
return json($data);
|
||||
}
|
||||
return $this->fetch();
|
||||
}
|
||||
2
app/admin/service/curd/templates/controller/select.code
Normal file
2
app/admin/service/curd/templates/controller/select.code
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
$this->assign('{{name}}', $this->model->{{name}}());
|
||||
23
app/admin/service/curd/templates/model/model.code
Normal file
23
app/admin/service/curd/templates/model/model.code
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace {{modelNamespace}};
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
class {{modelName}} extends TimeModel
|
||||
{
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'name' => "{{table}}",
|
||||
'table' => "{{prefix_table}}",
|
||||
'deleteTime' => {{deleteTime}},
|
||||
];
|
||||
}
|
||||
|
||||
public static array $notes = {{selectArrays}};
|
||||
|
||||
{{relationList}}
|
||||
|
||||
}
|
||||
5
app/admin/service/curd/templates/model/relation.code
Normal file
5
app/admin/service/curd/templates/model/relation.code
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
public function {{relationMethod}}()
|
||||
{
|
||||
return $this->belongsTo({{relationModel}}, '{{foreignKey}}', '{{primaryKey}}'){{relationFields}};
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
|
||||
public function {{name}}()
|
||||
{
|
||||
return \app\admin\model\{{relation}}::column('{{values}}', 'id');
|
||||
}
|
||||
5
app/admin/service/curd/templates/model/select.code
Normal file
5
app/admin/service/curd/templates/model/select.code
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
public function {{name}}()
|
||||
{
|
||||
return {{values}};
|
||||
}
|
||||
91
app/admin/service/curd/templates/static/js.code
Normal file
91
app/admin/service/curd/templates/static/js.code
Normal file
@ -0,0 +1,91 @@
|
||||
define(["jquery", "easy-admin"], function ($, ea) {
|
||||
|
||||
var init = {
|
||||
table_elem: '#currentTable',
|
||||
table_render_id: 'currentTableRenderId',
|
||||
index_url: '{{controllerUrl}}/index',
|
||||
add_url: '{{controllerUrl}}/add',
|
||||
edit_url: '{{controllerUrl}}/edit',
|
||||
delete_url: '{{controllerUrl}}/delete',
|
||||
export_url: '{{controllerUrl}}/export',
|
||||
modify_url: '{{controllerUrl}}/modify',
|
||||
recycle_url: '{{controllerUrl}}/recycle',
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
index: function () {
|
||||
ea.table.render({
|
||||
init: init,
|
||||
cols: [[
|
||||
{{indexCols}}
|
||||
]],
|
||||
});
|
||||
|
||||
ea.listen();
|
||||
},
|
||||
add: function () {
|
||||
ea.listen();
|
||||
},
|
||||
edit: function () {
|
||||
ea.listen();
|
||||
},
|
||||
recycle: function () {
|
||||
init.index_url = init.recycle_url;
|
||||
ea.table.render({
|
||||
init: init,
|
||||
toolbar: ['refresh',
|
||||
[{
|
||||
class: 'layui-btn layui-btn-sm',
|
||||
method: 'get',
|
||||
field: 'id',
|
||||
icon: 'fa fa-refresh',
|
||||
text: '全部恢复',
|
||||
title: '确定恢复?',
|
||||
auth: 'recycle',
|
||||
url: init.recycle_url + '?type=restore',
|
||||
checkbox: true
|
||||
}, {
|
||||
class: 'layui-btn layui-btn-danger layui-btn-sm',
|
||||
method: 'get',
|
||||
field: 'id',
|
||||
icon: 'fa fa-delete',
|
||||
text: '彻底删除',
|
||||
title: '确定彻底删除?',
|
||||
auth: 'recycle',
|
||||
url: init.recycle_url + '?type=delete',
|
||||
checkbox: true
|
||||
}], 'export',
|
||||
],
|
||||
cols: [[
|
||||
{{recycleCols}}
|
||||
{
|
||||
width: 250,
|
||||
title: '操作',
|
||||
templet: ea.table.tool,
|
||||
operat: [
|
||||
[{
|
||||
title: '确认恢复?',
|
||||
text: '恢复数据',
|
||||
filed: 'id',
|
||||
url: init.recycle_url + '?type=restore',
|
||||
method: 'get',
|
||||
auth: 'recycle',
|
||||
class: 'layui-btn layui-btn-xs layui-btn-success',
|
||||
}, {
|
||||
title: '想好了吗?',
|
||||
text: '彻底删除',
|
||||
filed: 'id',
|
||||
method: 'get',
|
||||
url: init.recycle_url + '?type=delete',
|
||||
auth: 'recycle',
|
||||
class: 'layui-btn layui-btn-xs layui-btn-normal layui-bg-red',
|
||||
}]]
|
||||
}
|
||||
]],
|
||||
});
|
||||
|
||||
ea.listen();
|
||||
},
|
||||
};
|
||||
});
|
||||
10
app/admin/service/curd/templates/view/form.code
Normal file
10
app/admin/service/curd/templates/view/form.code
Normal file
@ -0,0 +1,10 @@
|
||||
<div class="layuimini-container">
|
||||
<form id="app-form" class="layui-form layuimini-form">
|
||||
{{formList}}
|
||||
<div class="hr-line"></div>
|
||||
<div class="layui-form-item text-center">
|
||||
<button type="submit" class="layui-btn layui-btn-normal layui-btn-sm" lay-submit>确认</button>
|
||||
<button type="reset" class="layui-btn layui-btn-primary layui-btn-sm">重置</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
16
app/admin/service/curd/templates/view/index.code
Normal file
16
app/admin/service/curd/templates/view/index.code
Normal file
@ -0,0 +1,16 @@
|
||||
<div class="layuimini-container">
|
||||
<div class="layuimini-main">
|
||||
<table id="currentTable" class="layui-table layui-hide"
|
||||
data-auth-add="{:auth('{{controllerUrl}}/add')}"
|
||||
data-auth-edit="{:auth('{{controllerUrl}}/edit')}"
|
||||
data-auth-delete="{:auth('{{controllerUrl}}/delete')}"
|
||||
data-auth-recycle="{:auth('{{controllerUrl}}/recycle')}"
|
||||
lay-filter="currentTable">
|
||||
<!-- searchTableShow="false" 隐藏搜索框 -->
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
{{notesScript}}
|
||||
</script>
|
||||
@ -0,0 +1,7 @@
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">{{comment}}</label>
|
||||
<div class="layui-input-block">
|
||||
{{define}}
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,3 @@
|
||||
{foreach ${{name}} as $k=>$v}
|
||||
<input type="checkbox" name="{{field}}[]" value="{$k}" lay-skin="primary" title="{$v}" {{select}}>
|
||||
{/foreach}
|
||||
7
app/admin/service/curd/templates/view/module/date.code
Normal file
7
app/admin/service/curd/templates/view/module/date.code
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">{{comment}}</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="{{field}}" data-date="" data-date-type="{{define}}" class="layui-input" {{required}} placeholder="请输入{{comment}}" value="{{value}}">
|
||||
</div>
|
||||
</div>
|
||||
8
app/admin/service/curd/templates/view/module/editor.code
Normal file
8
app/admin/service/curd/templates/view/module/editor.code
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">{{comment}}</label>
|
||||
<div class="layui-input-block">
|
||||
{:editor_textarea({{value}},"{{field}}","{{comment}}")}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
11
app/admin/service/curd/templates/view/module/file.code
Normal file
11
app/admin/service/curd/templates/view/module/file.code
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label required">{{comment}}</label>
|
||||
<div class="layui-input-block layuimini-upload">
|
||||
<input name="{{field}}" class="layui-input layui-col-xs6" {{required}} placeholder="请上传{{comment}}" value="{{value}}">
|
||||
<div class="layuimini-upload-btn">
|
||||
<span><a class="layui-btn" data-upload="{{field}}" data-upload-number="one" data-upload-exts="*" data-upload-icon="file"><i class="fa fa-upload"></i> 上传</a></span>
|
||||
<span><a class="layui-btn layui-btn-normal" id="select_{{field}}" data-upload-select="{{field}}" data-upload-number="one" data-upload-mimetype="*"><i class="fa fa-list"></i> 选择</a></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
11
app/admin/service/curd/templates/view/module/files.code
Normal file
11
app/admin/service/curd/templates/view/module/files.code
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label required">{{comment}}</label>
|
||||
<div class="layui-input-block layuimini-upload">
|
||||
<input name="{{field}}" class="layui-input layui-col-xs6" {{required}} placeholder="请上传{{comment}}" value="{{value}}">
|
||||
<div class="layuimini-upload-btn">
|
||||
<span><a class="layui-btn" data-upload="{{field}}" data-upload-number="more" data-upload-exts="*" data-upload-icon="file"><i class="fa fa-upload" data-upload-sign="{{define}}"></i> 上传</a></span>
|
||||
<span><a class="layui-btn layui-btn-normal" id="select_{{field}}" data-upload-select="{{field}}" data-upload-number="more" data-upload-mimetype="*" data-upload-sign="{{define}}"><i class="fa fa-list"></i> 选择</a></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
11
app/admin/service/curd/templates/view/module/image.code
Normal file
11
app/admin/service/curd/templates/view/module/image.code
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label required">{{comment}}</label>
|
||||
<div class="layui-input-block layuimini-upload">
|
||||
<input name="{{field}}" class="layui-input layui-col-xs6" {{required}} placeholder="请上传{{comment}}" value="{{value}}">
|
||||
<div class="layuimini-upload-btn">
|
||||
<span><a class="layui-btn" data-upload="{{field}}" data-upload-number="one" data-upload-exts="png|jpg|ico|jpeg" data-upload-icon="image"><i class="fa fa-upload"></i> 上传</a></span>
|
||||
<span><a class="layui-btn layui-btn-normal" id="select_{{field}}" data-upload-select="{{field}}" data-upload-number="one" data-upload-mimetype="image/*"><i class="fa fa-list"></i> 选择</a></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
11
app/admin/service/curd/templates/view/module/images.code
Normal file
11
app/admin/service/curd/templates/view/module/images.code
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label required">{{comment}}</label>
|
||||
<div class="layui-input-block layuimini-upload">
|
||||
<input name="{{field}}" class="layui-input layui-col-xs6" {{required}} placeholder="请上传{{comment}}" value="{{value}}">
|
||||
<div class="layuimini-upload-btn">
|
||||
<span><a class="layui-btn" data-upload="{{field}}" data-upload-number="more" data-upload-exts="png|jpg|ico|jpeg" data-upload-icon="image" data-upload-sign="{{define}}"><i class="fa fa-upload"></i> 上传</a></span>
|
||||
<span><a class="layui-btn layui-btn-normal" id="select_{{field}}" data-upload-select="{{field}}" data-upload-number="more" data-upload-mimetype="image/*" data-upload-sign="{{define}}"><i class="fa fa-list"></i> 选择</a></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
7
app/admin/service/curd/templates/view/module/input.code
Normal file
7
app/admin/service/curd/templates/view/module/input.code
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">{{comment}}</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="{{field}}" class="layui-input" {{required}} placeholder="请输入{{comment}}" value="{{value}}">
|
||||
</div>
|
||||
</div>
|
||||
4
app/admin/service/curd/templates/view/module/option.code
Normal file
4
app/admin/service/curd/templates/view/module/option.code
Normal file
@ -0,0 +1,4 @@
|
||||
<option value=''></option>
|
||||
{foreach ${{name}} as $k=>$v}
|
||||
<option value='{$k}' {{select}}>{$v}</option>
|
||||
{/foreach}
|
||||
7
app/admin/service/curd/templates/view/module/radio.code
Normal file
7
app/admin/service/curd/templates/view/module/radio.code
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">{{comment}}</label>
|
||||
<div class="layui-input-block">
|
||||
{{define}}
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,3 @@
|
||||
{foreach ${{name}} as $k=>$v}
|
||||
<input type="radio" name="{{field}}" value="{$k}" title="{$v}" {{select}}>
|
||||
{/foreach}
|
||||
9
app/admin/service/curd/templates/view/module/select.code
Normal file
9
app/admin/service/curd/templates/view/module/select.code
Normal file
@ -0,0 +1,9 @@
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">{{comment}}</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="{{field}}" {{required}}>
|
||||
{{define}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
7
app/admin/service/curd/templates/view/module/sort.code
Normal file
7
app/admin/service/curd/templates/view/module/sort.code
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">{{comment}}</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="number" name="{{field}}" class="layui-input" lay-affix="number" {{required}} placeholder="请输入{{comment}}" value="{{value}}">
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,7 @@
|
||||
|
||||
<div class="layui-form-item layui-form-text">
|
||||
<label class="layui-form-label">{{comment}}</label>
|
||||
<div class="layui-input-block">
|
||||
<textarea name="{{field}}" class="layui-textarea" {{required}} placeholder="请输入{{comment}}">{{value}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
13
app/admin/service/curd/templates/view/recycle.code
Normal file
13
app/admin/service/curd/templates/view/recycle.code
Normal file
@ -0,0 +1,13 @@
|
||||
<div class="layuimini-container">
|
||||
<div class="layuimini-main">
|
||||
<table id="currentTable" class="layui-table layui-hide"
|
||||
data-auth-recycle="{:auth('{{controllerUrl}}/recycle')}"
|
||||
lay-filter="currentTable">
|
||||
<!-- searchTableShow="false" 隐藏搜索框 -->
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
{{notesScript}}
|
||||
</script>
|
||||
108
app/admin/service/tool/CommonTool.php
Normal file
108
app/admin/service/tool/CommonTool.php
Normal file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\service\tool;
|
||||
|
||||
class CommonTool
|
||||
{
|
||||
|
||||
/**
|
||||
* 下划线转驼峰
|
||||
* @param $str
|
||||
* @return null|string|string[]
|
||||
*/
|
||||
public static function lineToHump($str)
|
||||
{
|
||||
$str = preg_replace_callback('/([-_]+([a-z]{1}))/i', function ($matches) {
|
||||
return strtoupper($matches[2]);
|
||||
}, $str);
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* 驼峰转下划线
|
||||
* @param $str
|
||||
* @return null|string|string[]
|
||||
*/
|
||||
public static function humpToLine($str)
|
||||
{
|
||||
$str = preg_replace_callback('/([A-Z]{1})/', function ($matches) {
|
||||
return '_' . strtolower($matches[0]);
|
||||
}, $str);
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取真实IP
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getRealIp()
|
||||
{
|
||||
$ip = $_SERVER['REMOTE_ADDR'];
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && preg_match_all('#\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}#s', $_SERVER['HTTP_X_FORWARDED_FOR'], $matches)) {
|
||||
foreach ($matches[0] as $xip) {
|
||||
if (!preg_match('#^(10|172\.16|192\.168)\.#', $xip)) {
|
||||
$ip = $xip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}elseif (isset($_SERVER['HTTP_CLIENT_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_CLIENT_IP'])) {
|
||||
$ip = $_SERVER['HTTP_CLIENT_IP'];
|
||||
}elseif (isset($_SERVER['HTTP_CF_CONNECTING_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_CF_CONNECTING_IP'])) {
|
||||
$ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
|
||||
}elseif (isset($_SERVER['HTTP_X_REAL_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_X_REAL_IP'])) {
|
||||
$ip = $_SERVER['HTTP_X_REAL_IP'];
|
||||
}
|
||||
return $ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取文件夹下的所有文件
|
||||
* @param $path
|
||||
* @param $basePath
|
||||
* @return array|mixed
|
||||
*/
|
||||
public static function readDirAllFiles($path, $basePath = '')
|
||||
{
|
||||
list($list, $temp_list) = [[], scandir($path)];
|
||||
empty($basePath) && $basePath = $path;
|
||||
foreach ($temp_list as $file) {
|
||||
if ($file != ".." && $file != ".") {
|
||||
if (is_dir($path . DIRECTORY_SEPARATOR . $file)) {
|
||||
$childFiles = self::readDirAllFiles($path . DIRECTORY_SEPARATOR . $file, $basePath);
|
||||
$list = array_merge($childFiles, $list);
|
||||
}else {
|
||||
$filePath = $path . DIRECTORY_SEPARATOR . $file;
|
||||
$fileName = str_replace($basePath . DIRECTORY_SEPARATOR, '', $filePath);
|
||||
$list[$fileName] = $filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模板值替换
|
||||
* @param $string
|
||||
* @param $array
|
||||
* @return mixed
|
||||
*/
|
||||
public static function replaceTemplate($string, $array)
|
||||
{
|
||||
foreach ($array as $key => $val) {
|
||||
if (is_null($val)) $val = '';
|
||||
$string = str_replace("{{" . $key . "}}", $val, $string);
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
|
||||
|
||||
public static function replaceArrayString(?string $arrayString): string
|
||||
{
|
||||
$arrayString = str_replace('array (', '[', $arrayString);
|
||||
$arrayString = str_replace(')', ']', $arrayString);
|
||||
$arrayString = str_replace('=>
|
||||
[', '=> [', $arrayString);
|
||||
return $arrayString;
|
||||
}
|
||||
|
||||
}
|
||||
203
app/admin/traits/Curd.php
Normal file
203
app/admin/traits/Curd.php
Normal file
@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\traits;
|
||||
|
||||
use app\admin\service\annotation\NodeAnnotation;
|
||||
use app\admin\service\tool\CommonTool;
|
||||
use app\Request;
|
||||
use think\db\exception\PDOException;
|
||||
use think\facade\Db;
|
||||
use think\response\Json;
|
||||
|
||||
/**
|
||||
* 后台CURD复用
|
||||
* Trait Curd
|
||||
* @package app\admin\traits
|
||||
*/
|
||||
trait Curd
|
||||
{
|
||||
|
||||
#[NodeAnnotation(title: '列表', auth: true)]
|
||||
public function index(Request $request): Json|string
|
||||
{
|
||||
if ($request->isAjax()) {
|
||||
if (input('selectFields')) {
|
||||
return $this->selectList();
|
||||
}
|
||||
list($page, $limit, $where) = $this->buildTableParams();
|
||||
$count = self::$model::where($where)->count();
|
||||
$list = self::$model::where($where)->page($page, $limit)->order($this->sort)->select()->toArray();
|
||||
$data = [
|
||||
'code' => 0,
|
||||
'msg' => '',
|
||||
'count' => $count,
|
||||
'data' => $list,
|
||||
];
|
||||
return json($data);
|
||||
}
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '添加', auth: true)]
|
||||
public function add(Request $request): string
|
||||
{
|
||||
if ($request->isPost()) {
|
||||
$post = $request->post();
|
||||
$rule = [];
|
||||
$this->validate($post, $rule);
|
||||
try {
|
||||
Db::transaction(function() use ($post, &$save) {
|
||||
$save = self::$model::create($post);
|
||||
});
|
||||
}catch (\Exception $e) {
|
||||
$this->error('新增失败:' . $e->getMessage());
|
||||
}
|
||||
$save ? $this->success('新增成功') : $this->error('新增失败');
|
||||
}
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '编辑', auth: true)]
|
||||
public function edit(Request $request, $id = 0): string
|
||||
{
|
||||
$row = self::$model::find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
if ($request->isPost()) {
|
||||
$post = $request->post();
|
||||
$rule = [];
|
||||
$this->validate($post, $rule);
|
||||
try {
|
||||
Db::transaction(function() use ($post, $row, &$save) {
|
||||
$save = $row->save($post);
|
||||
});
|
||||
}catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
$save ? $this->success('保存成功') : $this->error('保存失败');
|
||||
}
|
||||
$this->assign('row', $row);
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '删除', auth: true)]
|
||||
public function delete(Request $request): void
|
||||
{
|
||||
// 如果不是id作为主键 请在对应的控制器中覆盖重写
|
||||
$id = $request->param('id', []);
|
||||
$this->checkPostRequest();
|
||||
$row = self::$model::whereIn('id', $id)->select();
|
||||
$row->isEmpty() && $this->error('数据不存在');
|
||||
try {
|
||||
$save = $row->delete();
|
||||
}catch (\Exception $e) {
|
||||
$this->error('删除失败');
|
||||
}
|
||||
$save ? $this->success('删除成功') : $this->error('删除失败');
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '导出', auth: true)]
|
||||
public function export()
|
||||
{
|
||||
if (env('EASYADMIN.IS_DEMO', false)) {
|
||||
$this->error('演示环境下不允许操作');
|
||||
}
|
||||
list($page, $limit, $where) = $this->buildTableParams();
|
||||
$tableName = (new self::$model)->getName();
|
||||
$tableName = CommonTool::humpToLine(lcfirst($tableName));
|
||||
$prefix = config('database.connections.mysql.prefix');
|
||||
$dbList = Db::query("show full columns from {$prefix}{$tableName}");
|
||||
$header = [];
|
||||
foreach ($dbList as $vo) {
|
||||
$comment = !empty($vo['Comment']) ? $vo['Comment'] : $vo['Field'];
|
||||
if (!in_array($vo['Field'], $this->noExportFields)) {
|
||||
$header[] = [$comment, $vo['Field']];
|
||||
}
|
||||
}
|
||||
$list = self::$model::where($where)
|
||||
->limit(100000)
|
||||
->order($this->sort)
|
||||
->select()
|
||||
->toArray();
|
||||
try {
|
||||
exportExcel($header, $list);
|
||||
}catch (\Throwable $exception) {
|
||||
$this->error('导出失败: ' . $exception->getMessage() . PHP_EOL . $exception->getFile() . PHP_EOL . $exception->getLine());
|
||||
}
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '属性修改', auth: true)]
|
||||
public function modify(Request $request): void
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$post = $request->post();
|
||||
$rule = [
|
||||
'id|ID' => 'require',
|
||||
'field|字段' => 'require',
|
||||
'value|值' => 'require',
|
||||
];
|
||||
$this->validate($post, $rule);
|
||||
$row = self::$model::find($post['id']);
|
||||
if (!$row) {
|
||||
$this->error('数据不存在');
|
||||
}
|
||||
if (!in_array($post['field'], $this->allowModifyFields)) {
|
||||
$this->error('该字段不允许修改:' . $post['field']);
|
||||
}
|
||||
try {
|
||||
Db::transaction(function() use ($post, $row) {
|
||||
$row->save([
|
||||
$post['field'] => $post['value'],
|
||||
]);
|
||||
});
|
||||
}catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
$this->success('保存成功');
|
||||
}
|
||||
|
||||
#[NodeAnnotation(title: '回收站', auth: true)]
|
||||
public function recycle(Request $request): Json|string
|
||||
{
|
||||
if (!$request->isAjax()) {
|
||||
return $this->fetch();
|
||||
}
|
||||
$id = $request->param('id', []);
|
||||
$type = $request->param('type', '');
|
||||
$deleteTimeField = (new self::$model)->getOption('deleteTime'); // 获取软删除字段
|
||||
$defaultErrorMsg = 'Model 中未设置软删除 deleteTime 对应字段 或 数据表中不存在该字段';
|
||||
if (!$deleteTimeField) $this->success($defaultErrorMsg);
|
||||
switch ($type) {
|
||||
case 'restore':
|
||||
self::$model::withTrashed()->whereIn('id', $id)->strict(false)->update([$deleteTimeField => null, 'update_time' => time()]);
|
||||
$this->success('success');
|
||||
break;
|
||||
case 'delete':
|
||||
self::$model::destroy($id, true);
|
||||
$this->success('success');
|
||||
break;
|
||||
default:
|
||||
list($page, $limit, $where) = $this->buildTableParams();
|
||||
try {
|
||||
$count = self::$model::withTrashed()->where($where)->whereNotNull($deleteTimeField)->count();
|
||||
$list = self::$model::withTrashed()->where($where)->page($page, $limit)->order($this->sort)->whereNotNull($deleteTimeField)->select()->toArray();
|
||||
$data = [
|
||||
'code' => 0,
|
||||
'msg' => '',
|
||||
'count' => $count,
|
||||
'data' => $list,
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
$error = $e->getMessage();
|
||||
if ($e instanceof PDOException) $error .= '<br>' . $defaultErrorMsg;
|
||||
$data = [
|
||||
'code' => -1,
|
||||
'msg' => $error,
|
||||
'count' => 0,
|
||||
'data' => [],
|
||||
];
|
||||
}
|
||||
return json($data);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
73
app/admin/view/article/article/add.html
Normal file
73
app/admin/view/article/article/add.html
Normal file
@ -0,0 +1,73 @@
|
||||
<div class="layuimini-container">
|
||||
<form id="app-form" class="layui-form layuimini-form">
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">文章标题</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="title" class="layui-input" lay-verify="required" placeholder="请输入文章标题" value="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">商品分类</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="cate_id" lay-verify="required" data-select="{:url('article.cate/index')}" data-fields="id,title" data-value="{$row.cate_id|default=''}">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">封面图片</label>
|
||||
<div class="layui-input-block layuimini-upload">
|
||||
<input name="cover" class="layui-input layui-col-xs6" placeholder="请上传封面图片" value="">
|
||||
<div class="layuimini-upload-btn">
|
||||
<span><a class="layui-btn" data-upload="cover" data-upload-number="one" data-upload-exts="png|jpg|ico|jpeg" data-upload-icon="image" data-upload-mimetype="image/*"><i class="fa fa-upload"></i> 上传</a></span>
|
||||
<span><a class="layui-btn layui-btn-normal" id="select_cover" data-upload-select="cover" data-upload-number="one"><i class="fa fa-list"></i> 选择</a></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">文章简介</label>
|
||||
<div class="layui-input-block">
|
||||
<textarea name="summary" class="layui-textarea" placeholder="请输入文章简介"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">文章内容</label>
|
||||
<div class="layui-input-block">
|
||||
<textarea name="content" class="layui-textarea" placeholder="请输入文章内容" style="height: 200px;"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">作者</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="author" class="layui-input" placeholder="请输入作者" value="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">排序</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="number" name="sort" class="layui-input" lay-affix="number" placeholder="请输入排序" value="0">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">状态</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="radio" name="status" value="1" title="启用" checked>
|
||||
<input type="radio" name="status" value="0" title="禁用">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hr-line"></div>
|
||||
<div class="layui-form-item text-center">
|
||||
<button type="submit" class="layui-btn layui-btn-normal layui-btn-sm" lay-submit>确认</button>
|
||||
<button type="reset" class="layui-btn layui-btn-primary layui-btn-sm">重置</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
71
app/admin/view/article/article/edit.html
Normal file
71
app/admin/view/article/article/edit.html
Normal file
@ -0,0 +1,71 @@
|
||||
<div class="layuimini-container">
|
||||
<form id="app-form" class="layui-form layuimini-form">
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">文章标题</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="title" class="layui-input" lay-verify="required" placeholder="请输入文章标题" value="{$row.title|default=''}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">商品分类</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="cate_id" lay-verify="required" data-select="{:url('article.cate/index')}" data-fields="id,title" data-value="{$row.cate_id|default=''}">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">封面图片</label>
|
||||
<div class="layui-input-block layuimini-upload">
|
||||
<input name="cover" class="layui-input layui-col-xs6" placeholder="请上传封面图片" value="{$row.cover|default=''}">
|
||||
<div class="layuimini-upload-btn">
|
||||
<span><a class="layui-btn" data-upload="cover" data-upload-number="one" data-upload-exts="png|jpg|ico|jpeg" data-upload-icon="image" data-upload-mimetype="image/*"><i class="fa fa-upload"></i> 上传</a></span>
|
||||
<span><a class="layui-btn layui-btn-normal" id="select_cover" data-upload-select="cover" data-upload-number="one"><i class="fa fa-list"></i> 选择</a></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">文章简介</label>
|
||||
<div class="layui-input-block">
|
||||
<textarea name="summary" class="layui-textarea" placeholder="请输入文章简介">{$row.summary|default=''}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">文章内容</label>
|
||||
<div class="layui-input-block">
|
||||
<textarea name="content" class="layui-textarea" placeholder="请输入文章内容" style="height: 200px;">{$row.content|default=''}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">作者</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="author" class="layui-input" placeholder="请输入作者" value="{$row.author|default=''}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">排序</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="number" name="sort" class="layui-input" lay-affix="number" placeholder="请输入排序" value="{$row.sort|default=0}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">状态</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="radio" name="status" value="1" title="启用" {if condition="$row.status == 1"}checked{/if}>
|
||||
<input type="radio" name="status" value="0" title="禁用" {if condition="$row.status == 0"}checked{/if}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hr-line"></div>
|
||||
<div class="layui-form-item text-center">
|
||||
<button type="submit" class="layui-btn layui-btn-normal layui-btn-sm" lay-submit>确认</button>
|
||||
<button type="reset" class="layui-btn layui-btn-primary layui-btn-sm">重置</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
10
app/admin/view/article/article/index.html
Normal file
10
app/admin/view/article/article/index.html
Normal file
@ -0,0 +1,10 @@
|
||||
<div class="layuimini-container">
|
||||
<div class="layuimini-main">
|
||||
<table id="currentTable" class="layui-table layui-hide"
|
||||
data-auth-add="{:auth('article.article/add')}"
|
||||
data-auth-edit="{:auth('article.article/edit')}"
|
||||
data-auth-delete="{:auth('article.article/delete')}"
|
||||
lay-filter="currentTable">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
33
app/admin/view/article/cate/add.html
Normal file
33
app/admin/view/article/cate/add.html
Normal file
@ -0,0 +1,33 @@
|
||||
<div class="layuimini-container">
|
||||
<form id="app-form" class="layui-form layuimini-form">
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">分类名称</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="title" class="layui-input" lay-verify="required" placeholder="请输入分类名称" value="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">分类排序</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="number" name="sort" class="layui-input" lay-affix="number" placeholder="请输入分类排序" value="0">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">状态</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="radio" name="status" value="1" title="启用" checked>
|
||||
<input type="radio" name="status" value="0" title="禁用">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hr-line"></div>
|
||||
<div class="layui-form-item text-center">
|
||||
<button type="submit" class="layui-btn layui-btn-normal layui-btn-sm" lay-submit>确认</button>
|
||||
<button type="reset" class="layui-btn layui-btn-primary layui-btn-sm">重置</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
33
app/admin/view/article/cate/edit.html
Normal file
33
app/admin/view/article/cate/edit.html
Normal file
@ -0,0 +1,33 @@
|
||||
<div class="layuimini-container">
|
||||
<form id="app-form" class="layui-form layuimini-form">
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">分类名称</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="title" class="layui-input" lay-verify="required" placeholder="请输入分类名称" value="{$row.title|default=''}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">分类排序</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="number" name="sort" class="layui-input" lay-affix="number" placeholder="请输入分类排序" value="{$row.sort|default=0}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">状态</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="radio" name="status" value="1" title="启用" {if condition="$row.status == 1"}checked{/if}>
|
||||
<input type="radio" name="status" value="0" title="禁用" {if condition="$row.status == 0"}checked{/if}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hr-line"></div>
|
||||
<div class="layui-form-item text-center">
|
||||
<button type="submit" class="layui-btn layui-btn-normal layui-btn-sm" lay-submit>确认</button>
|
||||
<button type="reset" class="layui-btn layui-btn-primary layui-btn-sm">重置</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user