115 Commits

Author SHA1 Message Date
wuyanwen
fd35d91f1c 修复角色名不显示 2020-01-06 09:28:14 +08:00
wuyanwen
f6e3ee1225 修改数据库字段 2019-12-30 17:07:30 +08:00
yanwenwu
7b782e916a 修改readme 2019-12-21 15:15:12 +08:00
yanwenwu
cf1dedabd4 冲突 2019-12-20 22:22:32 +08:00
yanwenwu
fc885476ef 修改 2019-12-20 22:15:45 +08:00
wuyanwen
aefa972804 修改用户管理 2019-12-19 07:23:32 +08:00
wuyanwen
c02f762537 修改权限管理 2019-12-19 07:23:18 +08:00
wuyanwen
122b624580 新增模块创建命令 2019-12-19 07:19:15 +08:00
wuyanwen
7e304cacf0 修改基类 2019-12-19 07:18:39 +08:00
wuyanwen
11854ec33e 修改服务 2019-12-19 07:18:18 +08:00
wuyanwen
a36603f7c2 修改系统管理 2019-12-19 07:17:51 +08:00
wuyanwen
6e0a405f09 修改首页 2019-12-19 07:17:33 +08:00
wuyanwen
2f71ba5380 Merge branch 'master' of https://gitee.com/jaguarjack/catchAdmin 2019-12-18 18:54:07 +08:00
wuyanwen
57f6597e77 添加初始化账号 2019-12-18 18:54:01 +08:00
zhangfayuan
300e536053 Apache环境下出现 No input file specified. 2019-12-18 14:34:45 +08:00
wuyanwen
3d8b31b2dc 修改composer镜像 2019-12-17 13:57:03 +08:00
wuyanwen
4e8aa4ae31 修改error页面 2019-12-17 12:35:25 +08:00
wuyanwen
daf6d1f760 修改响应 2019-12-17 12:35:12 +08:00
wuyanwen
7be9b78ac4 修改readme 2019-12-17 12:21:57 +08:00
wuyanwen
b4a411f343 新增解压包命令 2019-12-17 09:03:32 +08:00
wuyanwen
46a41c86ea 新增layout 2019-12-17 09:03:04 +08:00
wuyanwen
2c349e0832 优化 2019-12-17 09:02:49 +08:00
wuyanwen
5d1da432c7 修改服务 2019-12-17 09:02:29 +08:00
wuyanwen
3e91f16f27 修改继承 2019-12-17 09:02:16 +08:00
wuyanwen
efd0dd8e8f 修改继承 2019-12-17 09:02:09 +08:00
wuyanwen
f1c9f3db53 修改继承 2019-12-17 09:02:00 +08:00
wuyanwen
6f33180f9f 修改 js 2019-12-16 17:28:26 +08:00
wuyanwen
6e73f78bd3 修改 js 2019-12-16 17:25:40 +08:00
wuyanwen
438582ad6a 修改配置 2019-12-15 18:55:41 +08:00
wuyanwen
6caafbf4ca 修改配置 2019-12-15 18:55:14 +08:00
wuyanwen
0bc1b3c30c 修改 2019-12-15 17:47:30 +08:00
wuyanwen
a1c135f5d0 修改 2019-12-15 17:45:47 +08:00
wuyanwen
bfeb14b597 新增子域名设置 2019-12-15 17:27:01 +08:00
wuyanwen
27b30a8704 新增域名路由 2019-12-15 17:26:40 +08:00
wuyanwen
aea9eb6a17 修改路由加载 2019-12-15 15:51:00 +08:00
wuyanwen
0d05e4ecbd 修改exception 2019-12-15 15:50:31 +08:00
wuyanwen
f1ca73e05f 新增清空日志 2019-12-15 15:50:10 +08:00
wuyanwen
b365ec1db8 修改logo地址 2019-12-15 15:13:37 +08:00
wuyanwen
cd16d4314e 替换logo 2019-12-15 15:13:15 +08:00
wuyanwen
aba362dbad 新增基类异常 2019-12-15 13:38:47 +08:00
wuyanwen
79863215b3 修改request 2019-12-15 13:38:25 +08:00
wuyanwen
6c12887f8d 修复登录异常 2019-12-15 13:38:02 +08:00
wuyanwen
a018373013 修改验证码 2019-12-14 23:09:03 +08:00
wuyanwen
a9a857389a 修改登录 2019-12-14 22:59:38 +08:00
wuyanwen
5fec0b01b7 修改基类 2019-12-14 22:55:25 +08:00
wuyanwen
a62b1d54d6 修改服务 2019-12-14 22:55:05 +08:00
wuyanwen
4bfc021cdf 修改首页 2019-12-14 22:54:55 +08:00
wuyanwen
e3ececc277 修改权限路由 2019-12-14 22:54:41 +08:00
wuyanwen
2d6d074d94 修改登录 2019-12-14 22:54:24 +08:00
wuyanwen
6ac4b8784e 新增体验地址 2019-12-14 18:29:08 +08:00
wuyanwen
34d75c3f46 修改路由加载 2019-12-14 18:21:29 +08:00
wuyanwen
d21126882b 修改权限 2019-12-14 18:17:22 +08:00
wuyanwen
d7f0e6c949 新增拦截 2019-12-14 18:16:52 +08:00
wuyanwen
dc5ffd6ab1 修改权限 2019-12-14 18:16:44 +08:00
wuyanwen
d1d497076b 修改 2019-12-14 18:02:09 +08:00
wuyanwen
e6d1517429 修改 2019-12-14 17:59:11 +08:00
wuyanwen
eb2fd14f07 修改备份地址 2019-12-14 17:54:06 +08:00
wuyanwen
1bcc0f217e 修改备份地址 2019-12-14 17:53:40 +08:00
wuyanwen
6724da9e8f 修改基类 2019-12-14 17:50:54 +08:00
wuyanwen
d3e0c64b58 修改权限路由 2019-12-14 17:37:45 +08:00
wuyanwen
722b8cfa7f 修改路由缓存 2019-12-14 17:37:00 +08:00
wuyanwen
0d0abf3ea0 tree-table 2019-12-14 17:08:21 +08:00
wuyanwen
485432acfb 修改路由 2019-12-14 16:56:59 +08:00
wuyanwen
3ec10515fb 修改权限 2019-12-14 16:54:56 +08:00
wuyanwen
d3dfe38d60 发布 2019-12-14 16:02:09 +08:00
wuyanwen
74841b0717 修改readme 2019-12-14 15:57:55 +08:00
wuyanwen
5bcfe3c52d 安装新增数据填充 2019-12-14 15:57:32 +08:00
wuyanwen
6ac0562470 新增用户seed 2019-12-14 15:57:15 +08:00
wuyanwen
d27b7442d8 新增seed 2019-12-14 15:56:59 +08:00
wuyanwen
8479522970 安装修改 2019-12-14 15:03:39 +08:00
wuyanwen
1ce4c7122a 修改安装命令 2019-12-14 14:58:08 +08:00
wuyanwen
e35a76a80e 新增备份 2019-12-13 17:26:54 +08:00
wuyanwen
dacf005117 新增助手函数 2019-12-13 17:26:40 +08:00
wuyanwen
fad0f9770d 新增助手函数 2019-12-13 17:26:19 +08:00
wuyanwen
b5e90dd14f 修改权限管理 2019-12-13 17:26:09 +08:00
wuyanwen
17d30f1cc3 修改系统管理 2019-12-13 17:25:47 +08:00
wuyanwen
a4a03857ac 修改用户管理 2019-12-13 17:25:34 +08:00
wuyanwen
0d0cc6eeb6 修改服务 2019-12-13 17:25:22 +08:00
wuyanwen
d40deeff60 修改app 2019-12-13 17:25:05 +08:00
wuyanwen
6f7e0d71c2 更新公共库 2019-12-12 22:34:27 +08:00
wuyanwen
86a198d66a 注册事件 2019-12-12 22:34:08 +08:00
wuyanwen
5976ebb235 增加操作事件 2019-12-12 22:33:58 +08:00
wuyanwen
02b973d0b5 增加登录事件 2019-12-12 22:33:45 +08:00
wuyanwen
bdbf812941 修改菜单 2019-12-12 22:33:28 +08:00
wuyanwen
15be66ec26 新增系统管理 2019-12-12 22:33:12 +08:00
wuyanwen
d53b0bae73 新增后台基础公共库 2019-12-12 18:54:07 +08:00
wuyanwen
4a5993160f 修改session时间 2019-12-12 18:53:34 +08:00
wuyanwen
67e79bfa19 增加后台配置 2019-12-12 18:53:25 +08:00
wuyanwen
c8eedead00 注册中间件 2019-12-12 18:53:10 +08:00
wuyanwen
109cb7279a 修改登录 2019-12-12 18:52:56 +08:00
wuyanwen
a0a62b5640 增加默认错误页面 2019-12-12 18:52:44 +08:00
wuyanwen
5c7c976869 权限管理 2019-12-12 18:52:33 +08:00
wuyanwen
6c423e5fc5 用户管理 2019-12-12 18:52:24 +08:00
wuyanwen
5f6a7cf24e 增加基础 2019-12-12 18:52:11 +08:00
wuyanwen
ca4272d7a6 公共库 2019-12-12 09:14:08 +08:00
wuyanwen
d154f3e1ac 用户模块 2019-12-12 09:13:53 +08:00
wuyanwen
66e72c6537 登录模块 2019-12-12 09:13:44 +08:00
wuyanwen
7a6628a95f 权限管理 2019-12-12 09:13:29 +08:00
wuyanwen
ecae3e90f1 Merge branch 'master' of https://gitee.com/jaguarjack/catchAdmin 2019-12-11 21:00:27 +08:00
wuyanwen
c48d9154ad 首页 2019-12-11 21:00:24 +08:00
wuyanwen
6624a0cc6b 权限管理 2019-12-11 21:00:14 +08:00
wuyanwen
22064c6178 用户管理 2019-12-11 20:59:59 +08:00
zhangfayuan
7ac37e235b catch-seed:run 2019-12-10 14:03:28 +08:00
wuyanwen
ae7fd47a5d 新增theme 2019-12-09 16:48:09 +08:00
wuyanwen
dfa045726b 修改 2019-12-09 16:22:00 +08:00
wuyanwen
e6b443043a 修改忽略文件 2019-12-09 09:58:52 +08:00
wuyanwen
330a19e8c3 用户管理 2019-12-07 17:31:38 +08:00
wuyanwen
fa4837487b 增加分页响应 2019-12-06 14:44:41 +08:00
wuyanwen
5c7765c97f 扩展功能 2019-12-06 09:17:40 +08:00
wuyanwen
397c8bb7f7 基础功能 2019-12-06 08:24:07 +08:00
wuyanwen
6b4dd70752 用户管理模块 2019-12-03 21:43:37 +08:00
wuyanwen
2651ed6305 修改 2019-12-03 08:15:31 +08:00
wuyanwen
849db90b32 Merge branch 'master' of https://gitee.com/jaguarjack/catchAdmin 2019-12-02 23:05:47 +08:00
wuyanwen
40676f8b14 first commit 2019-12-02 23:04:43 +08:00
wuyanwen
38bcb43e70 删除 2019-12-02 22:58:11 +08:00
962 changed files with 68586 additions and 40523 deletions

View File

@@ -1,15 +0,0 @@
app_debug=true
app_trace=true
db_connection=mysql
db_host=localhost
db_database=thinking
db_username=user
db_port=3306
db_password=password
redis_host=
redis_port=
redis_password=

1
.example.env Normal file
View File

@@ -0,0 +1 @@
APP_DEBUG = false

3
.gitignore vendored
View File

@@ -1,7 +1,6 @@
/.idea /.idea
/.vscode /.vscode
/vendor /vendor
/database
*.log *.log
thinkphp
.env .env
.DS_Store

20
LICENSE
View File

@@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2018 JaguarJack
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.

32
LICENSE.txt Normal file
View File

@@ -0,0 +1,32 @@
ThinkPHP遵循Apache2开源协议发布并提供免费使用。
版权所有Copyright © 2006-2016 by ThinkPHP (http://thinkphp.cn)
All rights reserved。
ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
Apache Licence是著名的非盈利开源组织Apache采用的协议。
该协议和BSD类似鼓励代码共享和尊重原作者的著作权
允许代码修改,再作为开源或商业软件发布。需要满足
的条件:
1 需要给代码的用户一份Apache Licence
2 如果你修改了代码,需要在被修改的文件中说明;
3 在延伸的代码中(修改和有源代码衍生的代码中)需要
带有原来代码中的协议,商标,专利声明和其他原来作者规
定需要包含的说明;
4 如果再发布的产品中包含一个Notice文件则在Notice文
件中需要带有本协议内容。你可以在Notice中增加自己的
许可但不可以表现为对Apache Licence构成更改。
具体的协议参考http://www.apache.org/licenses/LICENSE-2.0
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,81 +1,47 @@
# think-admin ## CatchAdmin
# ENV
- php >= 7.1.3
- mysql >= 5.5
# install ## 5.1 版本的请使用 tag1.0 版本
## 新版后台在开发中 请不要使用
### 环境要求
- php7.1+ (需以下扩展)
- mbstring
- json
- openssl
- xml
- pdo
- nginx
- mysql
### install
- curl -sS http://install.phpcomposer.com/installer | php - curl -sS http://install.phpcomposer.com/installer | php
- composer config -g repo.packagist composer https://packagist.laravel-china.org - composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
- composer update - composer update
- 修改根目录下 .env.emp .env - php think catch:install
- .env 配置数据库信息
- php think migrate:run
- php think seed:run
# Use ### Use
- 配置虚拟域名 OR 在根目录下执行 php think run - 配置虚拟域名 OR 在根目录下执行 php think run
- yourUrl/login - yourUrl/login
- 默认用户名 admin 密码 admin - 默认用户名 admin@gmail.com 密码 admin
# nginx 配置 ### Problem
```
server {
listen 端口;
server_name 域名;
access_log logs/wenwen.access.log;
root 项目目录/public;
index index.php index.html index.htm;
location / {
index index.php index.html index.htm;
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?s=$1 last;
break;
}
}
#error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location ~ \.php$ {
root 项目目录/public;
fastcgi_pass phpfastcgi;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ^~ /data {
deny all;
}
}
```
# Problem
> SQLSTATE[42000]: Syntax error or access violation: 1067 Invalid default value for 'updated_at' > SQLSTATE[42000]: Syntax error or access violation: 1067 Invalid default value for 'updated_at'
设置 sql_mode; > 设置 sql_mode;
``` ```
show variables like 'sql_mode' ; show variables like 'sql_mode' ;
remove 'NO_ZERO_IN_DATE,NO_ZERO_DATE'
``` ```
> remove 'NO_ZERO_IN_DATE,NO_ZERO_DATE' > SET GLOBAL sql_mode='STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
```
SET GLOBAL sql_mode='STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
```
# Test Address
<a href="http://tp5.whwww.net" target="__BLANK">测试地址</a>
- 账号admin
- 密码: 123456
# Talking ### Talking
- 可以提 ISSUE请按照 issue 模板提问 - 可以提 ISSUE请按照 issue 模板提问
- 欢迎进入 Q 群,可以及时反馈一些问题。 - 欢迎进入 Q 群,可以及时反馈一些问题。
- ![输入图片说明](https://images.gitee.com/uploads/images/2018/1219/110300_0257b6c0_810218.jpeg "微信图片_20181219105915.jpg") - ![输入图片说明](https://images.gitee.com/uploads/images/2018/1219/110300_0257b6c0_810218.jpeg "微信图片_20181219105915.jpg")
仅供学习 仅供学习
## 体验地址
[体验地址](http://demo.catchadmin.com/login)
- 账号: test@catch.com
- 密码: 123456

96
app/BaseController.php Normal file
View File

@@ -0,0 +1,96 @@
<?php
declare (strict_types = 1);
namespace app;
use catcher\CatchAdmin;
use think\App;
use think\exception\ValidateException;
use think\facade\View;
use think\helper\Str;
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, $validate, array $message = [], bool $batch = false)
{
if (is_array($validate)) {
$v = new Validate();
$v->rule($validate);
} else {
if (strpos($validate, '.')) {
// 支持场景
list($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);
}
}

65
app/ExceptionHandle.php Normal file
View File

@@ -0,0 +1,65 @@
<?php
namespace app;
use catcher\CatchResponse;
use catcher\exceptions\CatchException;
use catcher\exceptions\FailedException;
use catcher\exceptions\LoginFailedException;
use catcher\exceptions\PermissionForbiddenException;
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
* @throws \Exception
*/
public function render($request, Throwable $e): Response
{
// if ($e instanceof CatchException){
return CatchResponse::fail($e->getMessage(), $e->getCode());
// }
// 其他错误交给系统处理
//return parent::render($request, $e);
}
}

14
app/Request.php Normal file
View File

@@ -0,0 +1,14 @@
<?php
namespace app;
// 应用请求对象类
use catchAdmin\user\Auth;
class Request extends \think\Request
{
public function user()
{
return Auth::user();
}
}

2
app/common.php Normal file
View File

@@ -0,0 +1,2 @@
<?php
// 应用公共文件

18
app/event.php Normal file
View File

@@ -0,0 +1,18 @@
<?php
// 事件定义文件
return [
'bind' => [],
'listen' => [
'AppInit' => [],
'HttpRun' => [],
'HttpEnd' => [],
'LogLevel' => [],
'LogWrite' => [],
'RouteLoaded' => [],
],
'subscribe' => [
],
];

10
app/middleware.php Normal file
View File

@@ -0,0 +1,10 @@
<?php
// 全局中间件定义文件
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
// \think\middleware\LoadLangPack::class,
// Session初始化
\think\middleware\SessionInit::class
];

9
app/provider.php Normal file
View File

@@ -0,0 +1,9 @@
<?php
use app\ExceptionHandle;
use app\Request;
// 容器Provider定义文件
return [
'think\Request' => Request::class,
'think\exception\Handle' => ExceptionHandle::class,
];

5
app/service.php Normal file
View File

@@ -0,0 +1,5 @@
<?php
return [
\jaguarjack\think\module\ThinkModuleService::class,
\catchAdmin\CatchAdminService::class,
];

View File

@@ -1,48 +0,0 @@
<?php
namespace app\admin\controller;
use think\Controller;
use app\traits\ControllerTrait;
abstract class Base extends Controller
{
use ControllerTrait;
protected $limit = 10;
protected $page = 1;
protected $middleware = ['checkLogin', 'auth', 'logRecord'];
/**
* 过滤参数
*
* @time at 2018年11月15日
* @param $params
* @return void
*/
protected function checkParams(&$params)
{
$this->limit = $params['limit'] ?? $this->limit;
$this->page = $params['page'] ?? $this->page;
foreach ($params as $key => $param) {
if (!$param || $key == 'limit' || $key == 'page') {
unset($params[$key]);
}
}
$this->start = $this->start();
}
/**
* Table ID Start
*
* @time at 2018年11月16日
* @return float|int
*/
protected function start()
{
return (int)$this->limit * ((int)$this->page - 1) + 1;
}
}

View File

@@ -1,63 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2019/1/18
* Time: 10:36
*/
namespace app\admin\controller;
use think\Db;
class Database extends Base
{
/**
* 数据字典列表
*
* @time at 2019年01月18日
* @return mixed
*/
public function index()
{
$this->tables = Db::query('SHOW TABLE STATUS');
return $this->fetch();
}
/**
* 优化表
*
* @time at 2019年01月18日
* @return void
*/
public function optimize()
{
$table = $this->request->post('table');
if (!$table) {
$this->error('参数错误, 未指定表');
}
Db::query(sprintf('optimize table %s', $table)) ? $this->success('优化成功') : $this->error('优化失败');
}
/**
*
*
* @time at 2019年01月18日
* @return void
*/
public function view()
{
$table = $this->request->param('table');
if (!$table) {
$this->error('参数错误', '未指定表');
}
$this->table = Db::query('show full columns from ' . $table);
return $this->fetch();
}
}

View File

@@ -1,43 +0,0 @@
<?php
namespace app\admin\controller;
use think\permissions\facade\Permissions;
use think\permissions\facade\Roles;
use app\service\MenuService;
class Index extends Base
{
protected $middleware = [ 'checkLogin' ];
/**
* 首页
*
* @time at 2018年11月15日
* @return mixed|string
*/
public function index(MenuService $menuService)
{
$loginUser = $this->getLoginUser();
$userHasRoles = $loginUser->getRoles();
$permissionIds = [];
$userHasRoles->each(function ($role, $key) use (&$permissionIds) {
$permissionIds = array_merge($permissionIds, Roles::getRoleBy($role->id)->getPermissions(false));
});
$permissions = Permissions::whereIn('id', $permissionIds)->where('is_show', 1)->select();
$this->permissions = $menuService->tree($permissions);
$this->loginUser = $loginUser;
return $this->fetch();
}
/**
* main
*
* @time at 2018年11月16日
* @return mixed|string
*/
public function main()
{
return $this->fetch();
}
}

View File

@@ -1,30 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2019/1/18
* Time: 9:01
*/
namespace app\admin\controller;
use app\model\LogRecordModel;
class Log extends Base
{
/**
* 日志列表
*
* @time at 2019年01月18日
* @param LogRecordModel $logRecordModel
* @return mixed
*/
public function index(LogRecordModel $logRecordModel)
{
$params = $this->request->param();
$this->checkParams($params);
$this->list = $logRecordModel->getAll($params, $this->limit);
return $this->fetch();
}
}

View File

@@ -1,55 +0,0 @@
<?php
namespace app\admin\controller;
use app\traits\Auth;
use think\Controller;
class Login extends Controller
{
use Auth;
protected $redirect = '/index';
/**
* Login Page
*
* @return mixed
*/
public function login()
{
// 登录逻辑
if ($this->request->isPost()) {
$this->authLogin($this->request);
}
return $this->fetch('/index/login');
}
/**
* 登出
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\think\response\Redirect
*/
public function logout()
{
$this->authLogout();
return redirect(url('login'));
}
/**
* 验证规则
*
* @time at 2018年11月13日
* @return array
*/
protected function rule()
{
return [
$this->name() => 'require',
'password|密码' => 'require',
'captcha|验证码' => 'require|captcha'
];
}
}

View File

@@ -1,78 +0,0 @@
<?php
namespace app\admin\controller;
use think\Collection;
use think\permissions\facade\Permissions;
use app\admin\request\PermissionRequest;
use app\service\MenuService;
class Permission extends Base
{
public function index(MenuService $menuService)
{
$this->permissions = new Collection($menuService->sort(Permissions::select()));
return $this->fetch();
}
/**
* Create Data
*
* @time at 2018年11月13日
* @return mixed|string
*/
public function create(PermissionRequest $request, MenuService $menuService)
{
if ($request->isPost()) {
$data = $request->post();
Permissions::store($data) ? $this->success('添加成功', url('permission/index')) : $this->error('添加失败');
}
$this->permissions = $menuService->sort(Permissions::select());
$this->permissionId = $this->request->param('id') ?? 0;
return $this->fetch();
}
/**
* Edit Data
*
* @time at 2018年11月13日
* @return mixed|string
*/
public function edit(PermissionRequest $request, MenuService $menuService)
{
if ($request->isPost()) {
$data = $request->post();
Permissions::updateBy($data['id'], $data) !== false ? $this->success('编辑成功', url('permission/index')) : $this->error('');
}
$permissionId = $this->request->param('id');
if (!$permissionId) {
$this->error('不存在的数据');
}
$this->permissions = $menuService->sort(Permissions::select());
$this->permission = Permissions::getPermissionBy($permissionId);
return $this->fetch();
}
/**
* Delete Data
*
* @time at 2018年11月13日
* @return void
*/
public function delete()
{
$permissionId = $this->request->post('id');
if (!$permissionId) {
$this->error('不存在数据');
}
if (Permissions::where('pid', $permissionId)->find()) {
$this->error('请先删除子菜单');
}
// 删除权限关联的角色信息
Permissions::detachRole($permissionId);
if (Permissions::deleteBy($permissionId)) {
$this->success('删除成功', url('permission/index'));
}
$this->error('删除失败');
}
}

View File

@@ -1,115 +0,0 @@
<?php
namespace app\admin\controller;
use think\permissions\facade\Roles;
use app\admin\request\RoleRequest;
use think\permissions\facade\Permissions;
use app\service\MenuService;
class Role extends Base
{
public function index()
{
$this->roles = Roles::paginate(10);
return $this->fetch();
}
/**
* create Data
*
* @time at 2018年11月13日
* @return mixed|string
*/
public function create(RoleRequest $request)
{
if ($request->isPost()) {
Roles::store($request->post()) ? $this->success('创建成功', url('role/index')) : $this->error('创建失败');
}
return $this->fetch();
}
/**
* Edit Data
*
* @time at 2018年11月13日
* @return mixed|string
*/
public function edit(RoleRequest $request)
{
if ($this->request->isPost()) {
Roles::updateBy($request->post('id'), $request->post()) !== false ? $this->success('编辑成功', url('role/index')) : $this->error('编辑失败');
}
$this->role = Roles::getRoleBy($this->request->param('id'));
return $this->fetch();
}
/**
* Delete Data
*
* @time at 2018年11月13日
* @return void
*/
public function delete()
{
$roleId = $this->request->post('id');
if (!$roleId) {
$this->error('角色信息不存在');
}
// 删除角色相关的用户
Roles::detachUsers($roleId);
// 删除角色相关的权限
Roles::detachPermissions($roleId);
if (Roles::deleteBy($roleId)) {
$this->success('删除成功', url('role/index'));
}
$this->error('删除失败');
}
/**
* 获取角色权限
*
* @time at 2018年09月21日
* @return void
*/
public function getPermissionsOfRole(MenuService $menuService)
{
$field = ['name', 'id', 'pid'];
$roleId = $this->request->post('role_id');
$permissions = Permissions::field($field)->all();
$roleHasPermissions = Roles::getRoleBy($roleId)->getPermissions(false);
$permissions = $permissions->each(function ($item, $key) use ($roleHasPermissions){
if (!$item->pid) {
$item->open = true;
}
$item->checked = in_array($item->id, $roleHasPermissions) ? true : false;
return $item;
});
header('content-Type: application/json');
exit(json_encode($menuService->sort($permissions)));
}
/**
* 分配权限
*
* @time at 2018年11月15日
* @return mixed|string
*/
public function givePermissions()
{
if ($this->request->isPost()) {
$postData = $this->request->post();
$roleId = $postData['role_id'];
if (!isset($postData['permissions'])) {
Roles::detachPermissions($roleId);
$this->success('分配成功', url('role/index'));
}
$permissions = $postData['permissions'];
Roles::detachPermissions($roleId);
Roles::attachPermissions($roleId, $permissions) ? $this->success('分配成功', url('role/index')) : $this->error('分配失败');
}
$this->role_id = $this->request->param('id');
return $this->fetch('role/givePermissions');
}
}

View File

@@ -1,126 +0,0 @@
<?php
namespace app\admin\controller;
use app\model\UserModel;
use app\admin\request\UserRequest;
use think\permissions\facade\Roles;
class User extends Base
{
/**
* User List
*
* @time at 2018年11月12日
* @return mixed|string
*/
public function index(UserModel $userModel)
{
$params = $this->request->param();
$this->checkParams($params);
$this->users = $userModel->getList($params, $this->limit);
return $this->fetch();
}
/**
* create Data
*
* @time at 2018年11月12日
* @return mixed|string
*/
public function create(UserModel $userModel, UserRequest $request)
{
if ($request->isPost()) {
$data = $request->post();
$data['password'] = generatePassword($data['password']);
if ($userId = $userModel->store($data)) {
// 分配角色
$this->giveRoles($userModel, $userId, $data);
$this->success('添加成功', url('user/index'));
}
$this->error('添加失败');
}
$this->roles = Roles::all();
return $this->fetch();
}
/**
* Edit Data
*
* @time at 2018年11月12日
* @return mixed|string
*/
public function edit(UserModel $userModel, UserRequest $request)
{
if ($request->isPost()) {
$data = $request->post();
$this->giveRoles($userModel, $data['id'], $data);
$data['password'] = generatePassword($data['password']);
$userModel->updateBy($data['id'], $data) ? $this->success('修改成功', url('user/index')) : $this->error('修改失败');
}
$id = $this->request->param('id');
if (!$id) {
$this->error('数据不存在');
}
$user = $userModel->findBy($id);
$userHasRoles = $user->getRoles(false);
$roles = Roles::all()->each(function($item, $key) use ($userHasRoles){
$item->checked = in_array($item->id, $userHasRoles) ? true : false;
return $item;
});
$this->user = $user;
$this->roles = $roles;
return $this->fetch();
}
/**
* Delete Data
*
* @time at 2018年11月12日
* @return void
*/
public function delete(UserModel $userModel)
{
$id = $this->request->post('id');
if (!$id) {
$this->error('不存在的数据');
}
// 删除用户相关的角色
$userModel->detachRoles($id);
if ($userModel->deleteBy($id)) {
$this->success('删除成功', url('user/index'));
}
$this->error('删除失败');
}
/**
* 分配角色
*
* @time at 2018年11月15日
* @param \app\model\UserModel $userModel
* @param int $userId
* @param $data
* @return bool
*/
protected function giveRoles(UserModel $userModel, int $userId, &$data)
{
if (isset($data['roles'])) {
$rolesIds = $data['roles'];
if (!is_array($rolesIds)) {
$rolesIds = [$rolesIds];
}
$userModel->detachRoles($userId);
$userModel->attachRoles($userId, $rolesIds);
unset($data['roles']);
return true;
}
$userModel->detachRoles($userId);
return true;
}
}

View File

@@ -1,30 +0,0 @@
<?php
/**
* UserRequest.php
* Created by wuyanwen <wuyanwen1992@gmail.com>
* Date: 2018/11/29 0029 21:56
*/
namespace app\admin\request;
use think\exception\HttpResponseException;
use think\Request;
abstract class FormRequest extends Request
{
/**
* FormRequest constructor.
*/
public function __construct()
{
parent::__construct();
if ($this->withServer($_SERVER)->isAjax(true) && $err = $this->validate()) {
throw new HttpResponseException(json([
'code' => 0,
'msg' => $err,
'wait' => 3,
]));
}
}
}

View File

@@ -1,17 +0,0 @@
<?php
/**
* UserRequest.php
* Created by wuyanwen <wuyanwen1992@gmail.com>
* Date: 2018/11/29 0029 21:56
*/
namespace app\admin\request;
use app\admin\validates\PermissionValidate;
class PermissionRequest extends FormRequest
{
public function validate()
{
return (new PermissionValidate())->getErrors($this->post());
}
}

View File

@@ -1,17 +0,0 @@
<?php
/**
* UserRequest.php
* Created by wuyanwen <wuyanwen1992@gmail.com>
* Date: 2018/11/29 0029 21:56
*/
namespace app\admin\request;
use app\admin\validates\RoleValidate;
class RoleRequest extends FormRequest
{
public function validate()
{
return (new RoleValidate())->getErrors($this->post());
}
}

View File

@@ -1,17 +0,0 @@
<?php
/**
* UserRequest.php
* Created by wuyanwen <wuyanwen1992@gmail.com>
* Date: 2018/11/29 0029 21:56
*/
namespace app\admin\request;
use app\admin\validates\UserValidate;
class UserRequest extends FormRequest
{
public function validate()
{
return (new UserValidate())->getErrors($this->post());
}
}

View File

@@ -1,35 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/11/12 0012
* Time: 下午 16:31
*/
namespace app\admin\validates;;
use think\Validate;
abstract class AbstractValidate extends Validate
{
/**
* Get Validate Errors
*
* @time at 2018年11月12日
* @param $data
* @return array
*/
public function getErrors($data)
{
$this->check($data);
return $this->getError();
}
public function __set($name, $value)
{
// TODO: Implement __set() method.
$this->rule[$name] = $value;
}
}

View File

@@ -1,19 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/11/14 0014
* Time: 下午 18:21
*/
namespace app\admin\validates;
class PermissionValidate extends AbstractValidate
{
protected $rule = [
'name|菜单名称' => 'require|min:2|max:10|chs|unique:permissions',
'module|模块名称' => 'require|min:2|max:10|alpha',
'controller|控制器名称' => 'require|min:2|max:50|alpha',
'action|方法名称' => 'require|min:2|max:50|alpha',
];
}

View File

@@ -1,15 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/11/14 0014
* Time: 下午 17:42
*/
namespace app\admin\validates;
class RoleValidate extends AbstractValidate
{
protected $rule = [
'name|角色名' => 'require|min:3|max:15|chs|unique:roles',
];
}

View File

@@ -1,18 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/11/12 0012
* Time: 下午 16:38
*/
namespace app\admin\validates;
class UserValidate extends AbstractValidate
{
protected $rule = [
'name|用户名' => 'require|min:3|max:15|alphaNum|unique:users',
'email|邮箱' => 'email|unique:users',
'password|密码' => 'confirm|min:6|max:20|alphaDash',
];
}

View File

@@ -1,20 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/11/13 0013
* Time: 上午 9:33
*/
namespace app\behavior;
class LoginRecord
{
public function run($params)
{
$user = $params['user'];
## 登录记录
$user->login_at = date('Y-m-d h:i:s', time());
$user->login_ip = request()->ip();
$user->save();
}
}

View File

@@ -1,15 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
return [
'make:curd' => app\command\MakeCurd::class,
'rbac:publish' => think\permissions\command\PermissionPublish::class,
];

View File

@@ -1,154 +0,0 @@
<?php
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\DB;
class MakeCurd extends Command
{
protected $appPath;
protected $stubPath;
// view 默认的三个模板
protected $views = ['index', 'create', 'edit'];
public function __construct()
{
parent::__construct();
$this->appPath = env('app_path');
$this->stubPath = $this->appPath . 'command' . DIRECTORY_SEPARATOR . 'stub' .DIRECTORY_SEPARATOR;
}
protected function configure()
{
$this->setName('make:curd')
->addArgument('controller', Argument::OPTIONAL, "controller name")
->addArgument('model', Argument::OPTIONAL, "model name")
->addOption('module', null, Option::VALUE_REQUIRED, 'module name')
->setDescription('Create curd option controller model --module?');
}
protected function execute(Input $input, Output $output)
{
// 首先获取默认模块
$moduleName = config('app.default_module');
$controllerName = trim($input->getArgument('controller'));
if (!$controllerName) {
$output->writeln('Controller Name Must Set');exit;
}
$modelName = trim($input->getArgument('model'));
if (!$modelName) {
$output->writeln('Model Name Must Set');exit;
}
if ($input->hasOption('module')) {
$moduleName = $input->getOption('module');
}
$this->makeController($controllerName, $moduleName);
$output->writeln($controllerName . ' controller create success');
$this->makeModel($modelName, $moduleName);
$output->writeln($modelName . ' model create success');
$this->makeView($controllerName, $moduleName);
$output->writeln($controllerName . ' view create success');
}
// 创建控制器文件
protected function makeController($controllerName, $moduleName)
{
$controllerStub = $this->stubPath . 'Controller.stub';
$controllerStub = str_replace(['$controller', '$module'], [ucfirst($controllerName), strtolower($moduleName)], file_get_contents($controllerStub));
$controllerPath = $this->appPath . $moduleName . DIRECTORY_SEPARATOR . 'controller' . DIRECTORY_SEPARATOR;
if (!is_dir($controllerPath)) {
mkdir($controllerPath, 0777, true);
}
return file_put_contents( $controllerPath . $controllerName . '.php', $controllerStub);
}
// 创建模型文件
public function makeModel($modelName, $moduleName)
{
$modelPath = $this->appPath . DIRECTORY_SEPARATOR . 'model';
if (!is_dir($modelPath)) {
mkdir($modelPath, 0777, true);
}
$modelContents = "<?php \r\n \r\n";
$modelContents .= "namespace app\model;\r\n \r\n";
$modelContents .= 'class $modelModel extends BaseModel';
$modelContents .= "\r\n { \r\n \t";
$modelContents .= 'protected $table = \'' . config('database.prefix') . '$_table\';';
$modelContents = $this->writeField($modelContents, $modelName);
$modelContents = str_replace('$model', ucfirst($modelName), $modelContents);
$modelContents = str_replace('$_table', $this->unCamelize($modelName), $modelContents);
$modelContents .= "\r\n }";
return file_put_contents($modelPath . DIRECTORY_SEPARATOR . $modelName . 'Model.php', $modelContents);
}
private function writeField($modelContents, $modelName)
{
$info = Db::query('show full columns from ' . config('database.prefix') . $this->unCamelize($modelName));
foreach ($info as $value) {
$modelContents .= sprintf("\r\n %s \t protected $%s = '%s'; \r\n", $this->fieldComment($value['Comment']), $this->combine($value['Field']), $value['Field']);
}
return $modelContents;
}
// 创建模板
public function makeView($controllerName, $moduleName)
{
$viewStub = $this->stubPath . 'View.stub';
$viewPath = (config('template.view_base') ? config('template.view_base') . $moduleName . DIRECTORY_SEPARATOR : env('app_path') . $moduleName . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR) . strtolower($controllerName);
if (!is_dir($viewPath)) {
mkdir($viewPath, 0777, true);
}
$stub = explode('||', file_get_contents($viewStub));
foreach ($this->views as $view) {
if ($view == 'index') {
file_put_contents($viewPath . DIRECTORY_SEPARATOR . $view .'.html', trim($stub[0]));
} else {
file_put_contents($viewPath . DIRECTORY_SEPARATOR . $view .'.html', trim($stub[1]));
}
}
}
/**
* 字符注释
*
* @time at 2019年01月08日
* @param $comment
* @return string
*/
private function fieldComment($comment)
{
return sprintf("\t /** \r\n \t * @var string \r\n \t * @desc %s \r\n \t */ \r\n", $comment);
}
/**
* 驼峰分割
*
* @time at 2019年01月02日
* @param string $camelCaps
* @param string $separator
* @return string
*/
private function unCamelize(string $string, string $separator = '_')
{
return strtolower(preg_replace('/(?<=[a-z])([A-Z])/', $separator . '$1', $string));
}
private function combine(string $string)
{
$s = explode('_', $string);
array_walk($s, function (&$value, $key) {
if ($key) {
$value = ucfirst($value);
}
});
return implode($s, '');
}
}

View File

@@ -1,12 +0,0 @@
{extend name="public:base" /}
{block name="menu"}{/block}
{block name="search"}{/block}
{block name="button-create"}{/block}
{block name="table-head"}{/block}
{block name="table-body"}{/block}
{block name="paginate"}{/block}
||
{extend name="public:form" /}
{block name="menu"}{/block}
{block name='action'}{/block}
{block name="form"}{/block}

View File

@@ -1,67 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 流年 <liu21st@gmail.com>
// +----------------------------------------------------------------------
// 应用公共文件
/**
* 钩子行为
*/
if (!function_exists('hook')) {
function hook($behavior, $params) {
\think\facade\Hook::exec($behavior, $params);
}
}
/**
* 编辑按钮
*/
if (!function_exists('editButton')) {
function editButton(string $url, string $name = '编辑') {
return sprintf('<a href="%s"><button class="btn btn-info btn-xs edit" type="button"><i class="fa fa-paste"></i> %s</button></a>', $url, $name);
}
}
/**
* 增加按钮
*/
if (!function_exists('createButton')) {
function createButton(string $url, string $name, $isBig = true) {
return $isBig ? sprintf('<a href="%s"> <button type="button" class="btn btn-w-m btn-primary"><i class="fa fa-check-square-o"></i> %s</button></a>', $url, $name) :
sprintf('<a href="%s"> <button type="button" class="btn btn-xs btn-primary"><i class="fa fa-check-square-o"></i> %s</button></a>', $url, $name);
}
}
/**
* 删除按钮
*/
if (!function_exists('deleteButton')) {
function deleteButton(string $url, int $id, string $name="删除") {
return sprintf('<button class="btn btn-danger btn-xs delete" data-url="%s" data=%d type="button"><i class="fa fa-trash"></i> %s</button>', $url, $id, $name);
}
}
/**
* 搜索按钮
*/
if (!function_exists('searchButton')) {
function searchButton(string $name="搜索") {
return sprintf('<button class="btn btn-white" type="submit"><i class="fa fa-search"></i> %s</button>', $name);
}
}
/**
* 生成密码
*/
if (!function_exists('generatePassword')) {
function generatePassword(string $password, int $algo = PASSWORD_DEFAULT) {
return password_hash($password, $algo);
}
}

View File

@@ -1,60 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/11/16 0016
* Time: 下午 14:51
*/
namespace app\component\upload;
use think\exception\ThrowableError;
use think\facade\Request;
use app\exceptions\UploadException;
class LocalUpload implements UploadInterface
{
protected $name = null;
/**
* Upload File
*
* @time at 2018年11月16日
* @return string
*/
public function file(){}
/**
* Upload Image
*
* @time at 2018年11月16日
* @return string
*/
public function image()
{
try {
$file = Request::file($this->name);
if (!$this->name) {
throw new UploadException('请选择上传的图片');
}
$info = $file->validate(config('admin.image'))->move(config('admin.local_upload_path'));
if (!$info) {
throw new UploadException($file->getError());
}
return $info->getSaveName();
} catch (UploadException $exception) {
return $exception->getMessage();
}
}
/**
* Set Image Name
*
* @time at 2018年11月16日
* @param $name
* @return $this
*/
public function name($name)
{
$this->name = $name;
return $this;
}
}

View File

@@ -1,15 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/11/16 0016
* Time: 下午 14:50
*/
namespace app\component\upload;
interface UploadInterface
{
public function file();
public function image();
}

View File

@@ -1,13 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/11/13 0013
* Time: 上午 10:49
*/
namespace app\exceptions;
class AppException extends \Exception
{
}

View File

@@ -1,14 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/11/16 0016
* Time: 下午 15:03
*/
namespace app\exceptions;
class UploadException extends \Exception
{
}

View File

@@ -1,15 +0,0 @@
<?php
namespace app\http\middleware;
class CheckLogin
{
public function handle($request, \Closure $next)
{
if (!$request->session('user')) {
return redirect(url('login'));
}
return $next($request);
}
}

View File

@@ -1,16 +0,0 @@
<?php
namespace app\http\middleware;
use app\service\LogService;
class LogRecord
{
public function handle($request, \Closure $next)
{
(new LogService())->record($request);
return $next($request);
}
}

View File

@@ -1,64 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/11/12 0012
* Time: 上午 11:05
*/
namespace app\model;
use think\Model;
class BaseModel extends Model
{
const LIMIT = 10;
/**
* Store Data
*
* @time at 2018年11月12日
* @param array $data
* @return bool
*/
public function store(array $data)
{
return $this->save($data) ? $this->id : false;
}
/**
* Find By ID
*
* @time at 2018年11月12日
* @param int $id
* @return array|false|\PDOStatement|string|\think\Model
*/
public function findBy(int $id)
{
return $this->where('id', $id)->find();
}
/**
* Update By ID && Data
*
* @time at 2018年11月12日
* @param int $id
* @param array $data
* @return bool
*/
public function updateBy(int $id, array $data)
{
return $this->save($data, ['id' => $id]);
}
/**
* Delete By ID
*
* @time at 2018年11月12日
* @param int $id
* @return bool|null
*/
public function deleteBy(int $id)
{
return $this->where('id', $id)->delete();
}
}

View File

@@ -1,36 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2019/1/17
* Time: 18:09
*/
namespace app\model;
use http\Env\Request;
class LogRecordModel extends BaseModel
{
protected $name = 'option_log';
/**
* 日志列表
*
* @time at 2019年01月18日
* @param array $params
* @param int $limit
* @return mixed
*/
public function getAll(array $params, $limit = self::LIMIT)
{
if (!count($params)) {
return $this->order('created_at', 'desc')->paginate($limit, false, ['query' => request()->param()]);
}
if (isset($params['name'])) {
$list = $this->whereLike('user_name', '%'.$params['name'].'%');
}
return $list->order('created_at', 'desc')->paginate($limit, false, ['query' => request()->param()]);
}
}

View File

@@ -1,37 +0,0 @@
<?php
namespace app\model;
use think\permissions\traits\hasRoles;
class UserModel extends BaseModel
{
use hasRoles;
protected $name = 'users';
/**
* Users List
*
* @time at 2018年11月14日
* @param $params
* @return \think\Paginator
*/
public function getList($params, $limit = self::LIMIT)
{
if (!count($params)) {
return $this->paginate($limit);
}
if (isset($params['name'])) {
$user = $this->whereLike('name', '%'.$params['name'].'%');
}
if (isset($params['email'])) {
$user = $this->whereLike('email', '%'.$params['email'].'%');
}
return $user->paginate($limit, false, ['query' => request()->param()]);
}
}

View File

@@ -1,35 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2019/1/17
* Time: 18:06
*/
namespace app\service;
use think\permissions\facade\Permissions;
use think\Request;
use app\model\LogRecordModel;
class LogService
{
public function record(Request $request)
{
$module = $request->module();
$controller = $request->controller();
$action = $request->action();
$user = $request->session('user');
$permission = Permissions::getPermissionByModuleAnd($module, $controller, $action);
(new LogRecordModel())->store([
'user_id' => $user->id,
'user_name' => $user->name,
'module' => $module,
'controller' => $controller,
'action' => $action,
'option' => $permission->name,
'method' => $request->method(),
]);
}
}

View File

@@ -1,55 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/11/13 0013
* Time: 上午 10:50
*/
namespace app\service;
use think\Collection;
class MenuService
{
/**
* 树形结构
*
* @time at 2018年11月13日
* @param $menu
* @return Collection
*/
public function tree(Collection $menus, int $pid = 0)
{
$collection = new Collection();
$menus->each(function ($item, $key) use ($pid, $menus, $collection){
if ($item->pid == $pid) {
$collection[$key] = $item;
$collection[$key][$item->id] = $this->tree($menus, $item->id);
}
});
return $collection;
}
/**
* 顺序结构
*
* @time at 2018年11月13日
* @param $menu
* @return Collection
*/
public function sort(Collection $menus, int $pid = 0, int $level = 0)
{
$collection = [];
foreach ($menus as $menu) {
if ($menu->pid == $pid) {
$menu->level = $level;
$collection[] = $menu;
$collection = array_merge($collection, $this->sort($menus, $menu->id, $level+1));
}
}
return $collection;
}
}

View File

@@ -1,63 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/11/16 0016
* Time: 上午 11:01
*/
namespace app\service;
use think\paginator\driver\Bootstrap;
class PaginateService extends Bootstrap
{
/**
* 渲染分页html
* @return mixed
*/
public function render()
{
if ($this->hasPages()) {
if ($this->simple) {
return sprintf(
'<ul class="pager">%s %s</ul>',
$this->getPreviousButton(),
$this->getNextButton()
);
} else {
return sprintf(
'<ul class="pagination">%s %s %s %s</ul>',
$this->getPreviousButton(),
$this->getLinks(),
$this->getNextButton(),
$this->changeLimit()
);
}
}
}
protected function changeLimit()
{
$query = $this->options['query'];
$html = '&nbsp;<li class="project_page">';
$pageLimit = config('admin.page_limit');
$html .= '<select class="page-form-control limit" name="limit">';
foreach ($pageLimit as $limit) {
if (isset($query['limit']) && $query['limit'] == $limit) {
$html .= sprintf('<option value="%s" selected>%s条/页</option>', $limit, $limit);
} else {
$html .= sprintf('<option value="%s">%s条/页</option>', $limit, $limit);
}
}
$html .= '</select></li>&nbsp;<li>';
$html .= sprintf('<input name="page" class="page-form-control-input" value="%s"> 页 ', $query['page'] ?? 1);
$html .='</li>';
$html .= '<li><button class="btn btn-primary btn-xs hrefTo"><i class="fa fa-location-arrow"></i> 跳转</button></li>';
return $html;
}
}

View File

@@ -1,28 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
// 应用行为扩展定义文件
return [
// 应用初始化
'app_init' => [],
// 应用开始
'app_begin' => [],
// 模块初始化
'module_init' => [],
// 操作开始执行
'action_begin' => [],
// 视图内容过滤
'view_filter' => [],
// 日志写入
'log_write' => [],
// 应用结束
'app_end' => [],
];

View File

@@ -1,183 +0,0 @@
<?php
namespace app\traits;
use think\Request;
use think\Validate;
use think\facade\Session;
use think\facade\Cookie;
use app\model\UserModel as User;
use app\behavior\LoginRecord;
trait Auth
{
protected $loginUserKey = 'user';
public function authLogin(Request $request)
{
$err = $this->validateLogin($request);
if ($err) {
$this->error($err);
}
// 正常输入登录
$userModel = new User();
$field = explode('|', $this->name());
$user = $userModel::where($field[0], $request->param($field[0]))->find();
if (!$user) {
$this->error('登录失败');
}
if (password_verify($request->param('password'), $user->password)) {
Session::set($this->loginUserKey, $user);
# 记住登录
$this->LoginRemember($user, $request);
# 登录记录
hook(LoginRecord::class, ['user' => $user]);
$this->success('登录成功', url($this->redirect));
}
$this->error('登录失败');
}
/**
* 记住登录
* @return bool
*/
public function rememberLogin()
{
// 如果记住登录
if (!Session::get($this->loginUserKey) && Cookie::get('remember_token') && $this->checkRememberToken()) {
return true;
}
return false;
}
/**
* 退出
* @return void
*/
public function authLogout()
{
$user = Session::get($this->loginUserKey);
$this->deleteToken($user);
Session::delete($this->loginUserKey);
}
protected function deleteToken($user)
{
if ($user->remember_token) {
$user->remember_token = null;
$user->save();
Cookie::delete('remember_token');
}
}
/**
* 验证
* @param Request $request
* @return array|bool
*/
protected function validateLogin(Request $request)
{
$validate = new Validate($this->rule());
if (!$validate->check($request->except(['remember']))) {
return $validate->getError();
}
return false;
}
/**
* 登录验证规则
* @return array
*/
protected function rule()
{
return [
$this->name() => 'require|token|alphaDash',
'password|密码' => 'require|alphaDash',
'captcha|验证码' => 'require|captcha'
];
}
/**
* 设置登录字段
*
* @return string
*/
protected function name()
{
return 'name|用户名';
}
/**
* Remember Token
*
* @return string
*/
public function generateRememberToken()
{
return uniqid(md5(time()+rand(10000, 99999)));
}
/**
* 加密 TOKEN
*
* @param $user_id
* @param $remember_token
* @return string
*/
protected function secretRememberToken($user_id, $remember_token)
{
list($key, $method, $iv) = $this->getSecret();
return base64_encode(openssl_encrypt($user_id . ':' . $remember_token, $method, $key, OPENSSL_RAW_DATA, $iv));
}
/**
* 检查remember token 是否正确
*
* @return bool
*/
protected function checkRememberToken()
{
if (!Cookie::has('remember_token')) {
return false;
}
$rememberToken = Cookie::get('remember_token');
// 解密
list($key, $method, $iv) = $this->getSecret();
list($userID) = explode(':', (openssl_decrypt(base64_decode($rememberToken), $method, $key, OPENSSL_RAW_DATA, $iv)));
// 校验
$user = (new User())->findBy($userID);
Session::set('user', $user);
return $user->remember_token == $rememberToken;
}
/**
* 加密
*
* @return array
*/
protected function getSecret()
{
return ['admin_auth', 'AES-128-CBC', '1234567890123412'];
}
/**
* 记住
*
* @param $user
* @return void
*/
protected function LoginRemember($user, Request $request)
{
if ($request->has('remember')) {
$rememberToken = $this->secretRememberToken($user->id, $this->generateRememberToken());
$user->remember_token = $rememberToken;
Cookie::forever('remember_token', $rememberToken);
}
}
}

View File

@@ -1,80 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/11/12 0012
* Time: 上午 11:43
*/
namespace app\traits;
use think\facade\Session;
use app\component\upload\UploadInterface;
use app\component\upload\LocalUpload;
trait ControllerTrait
{
protected $vars = [];
/**
* 绑定实现
*
* @time at 2018年11月16日
* @return void
*/
public function initialize()
{
bind(UploadInterface::class, LocalUpload::class);
}
/**
* 是否登录
*
* @time at 2018年11月15日
* @return bool
*/
protected function isLogin()
{
return $this->getLoginUser() ? true : false;
}
/**
* 获取登录用户
*
* @time at 2018年11月15日
* @return mixed
*/
protected function getLoginUser()
{
return Session::get('user');
}
/**
* fetch 重写
*
* @time at 2018年11月15日
* @param string $template
* @param array $vars
* @param array $config
* @return mixed
*/
protected function fetch($template = '', $vars = [], $config = [])
{
$vars = array_merge($this->vars, $vars);
return $this->view->fetch($template, $vars, $config);
}
/**
* Set Template Vars
*
* @time at 2018年11月12日
* @param $name
* @param $value
* @return void
*/
public function __set($name, $value)
{
// TODO: Implement __set() method.
$this->vars[$name] = $value;
}
}

View File

@@ -1,26 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
return [
// 生成应用公共文件
'__file__' => ['common.php'],
// 定义demo模块的自动生成 (按照实际定义的文件名生成)
'demo' => [
'__file__' => ['common.php'],
'__dir__' => ['behavior', 'controller', 'model', 'view'],
'controller' => ['Index', 'Test', 'UserType'],
'model' => ['User', 'UserType'],
'view' => ['index/index'],
],
// 其他更多的模块定义
];

View File

@@ -0,0 +1,120 @@
<?php
namespace catchAdmin;
use catchAdmin\login\LoginLogListener;
use catchAdmin\permissions\OperateLogListener;
use catchAdmin\permissions\PermissionsMiddleware;
use catchAdmin\system\event\LoginLogEvent;
use catchAdmin\system\event\OperateLogEvent;
use catchAdmin\user\Auth;
use catcher\command\BackupCommand;
use catcher\command\CompressPackageCommand;
use catcher\command\CreateModuleCommand;
use catcher\command\InstallCommand;
use catcher\command\MigrateRunCommand;
use catcher\command\ModelGeneratorCommand;
use catcher\command\ModuleCacheCommand;
use catcher\command\SeedRunCommand;
use catcher\event\LoadModuleRoutes;
use catcher\validates\Sometimes;
use think\facade\Validate;
use think\Service;
class CatchAdminService extends Service
{
/**
*
* @time 2019年11月29日
* @return void
*/
public function boot()
{
$this->registerCommands();
$this->registerValidates();
$this->registerMiddleWares();
$this->registerEvents();
$this->registerListeners();
}
/**
*
* @time 2019年12月13日
* @return void
*/
protected function registerCommands(): void
{
$this->commands([
InstallCommand::class,
ModuleCacheCommand::class,
MigrateRunCommand::class,
ModelGeneratorCommand::class,
SeedRunCommand::class,
BackupCommand::class,
CompressPackageCommand::class,
CreateModuleCommand::class,
]);
}
/**
*
* @time 2019年12月07日
* @return void
*/
protected function registerValidates(): void
{
$validates = [
new Sometimes(),
];
Validate::maker(function($validate) use ($validates){
foreach ($validates as $vali) {
$validate->extend($vali->type(), [$vali, 'verify'], $vali->message());
}
});
}
/**
*
* @time 2019年12月12日
* @return void
*/
protected function registerMiddleWares(): void
{
$this->app->middleware->import([
'catch_check_permission' => PermissionsMiddleware::class,
], 'route');
}
/**
*
* @time 2019年12月12日
* @return void
*/
protected function registerEvents(): void
{
$this->app->event->bind([
'loginLog' => LoginLogEvent::class,
'operateLog' => OperateLogEvent::class,
]);
}
/**
* 注册监听者
*
* @time 2019年12月12日
* @return void
*/
protected function registerListeners(): void
{
$this->app->event->listenEvents([
'loginLog' => [
LoginLogListener::class,
],
'operateLog' => [
OperateLogListener::class,
],
'RouteLoaded' => [
LoadModuleRoutes::class
]
]);
}
}

23
catchAdmin/helper.php Normal file
View File

@@ -0,0 +1,23 @@
<?php
function editButton($name = '修改', $event = 'edit')
{
return sprintf(
'<a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="%s">%s</a>',
$event, $name);
}
function deleteButton($name = '删除', $event = 'del')
{
return sprintf(
'<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="%s">%s</a>',
$event, $name);
}
function addButton($name = '新增', $event = 'add')
{
return sprintf(
'<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="%s">%s</a>',
$event, $name);
}

View File

@@ -0,0 +1,59 @@
<?php
namespace catchAdmin\index\controller;
use catchAdmin\permissions\model\Permissions;
use catchAdmin\user\Auth;
use catcher\base\CatchController;
use catcher\Tree;
use think\facade\Db;
class Index extends CatchController
{
/**
*
* @time 2019年12月11日
* @return string
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\db\exception\DataNotFoundException
*/
public function index(): string
{
$permissionIds = Auth::user()->getPermissionsBy();
$menus = Permissions::whereIn('id', $permissionIds)
->where('type', Permissions::MENU_TYPE)
->field(['id', 'parent_id', 'permission_name', 'route'])
->select()->toArray();
return $this->fetch([
'menus' => Tree::done($menus),
'username' => Auth::user()->username,
]);
}
/**
*
* @time 2019年12月11日
* @throws \Exception
* @return string
*/
public function theme(): string
{
return $this->fetch();
}
/**
*
* @time 2019年12月12日
* @throws \Exception
* @return string
*/
public function dashboard(): string
{
$mysqlVersion = Db::query('select version() as version');
return $this->fetch([
'mysql_version' => $mysqlVersion['0']['version'],
]);
}
}

View File

@@ -0,0 +1,13 @@
{
"name": "首页管理",
"alias": "index",
"description": "",
"keywords": [],
"order": 0,
"services": [
"catchAdmin\\index\\IndexService"
],
"aliases": {},
"files": [],
"requires": []
}

View File

@@ -0,0 +1,5 @@
<?php
$router->get('/', '\catchAdmin\index\controller\Index@index');
$router->get('theme', '\catchAdmin\index\controller\Index@theme');
$router->get('dashboard', '\catchAdmin\index\controller\Index@dashboard');

View File

@@ -0,0 +1,221 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>CatchAdmin 后台开发框架</title>
<link rel="stylesheet" href="__CATCH_ADMIN_LIBS__/layui/css/layui.css"/>
<link rel="stylesheet" href="__CATCH_ADMIN_MODULE__/admin.css?v=315"/>
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<style>
/** 卡片轮播图样式 */
.admin-carousel .layui-carousel-ind {
position: absolute;
top: -41px;
text-align: right;
}
.admin-carousel .layui-carousel-ind ul {
background: 0 0;
}
.admin-carousel .layui-carousel-ind li {
background-color: #e2e2e2;
}
.admin-carousel .layui-carousel-ind li.layui-this {
background-color: #999;
}
/** 广告位轮播图 */
.admin-news .layui-carousel-ind {
height: 45px;
}
.admin-news a {
display: block;
line-height: 60px;
text-align: center;
}
</style>
</head>
<body>
<!-- 加载动画 -->
<div class="page-loading">
<div class="ball-loader">
<span></span><span></span><span></span><span></span>
</div>
</div>
<!-- 正文开始 -->
<div class="layui-fluid">
<div class="layui-row layui-col-space15">
<div class="layui-col-xs12 layui-col-sm6 layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">
访问量<span class="layui-badge layui-bg-blue pull-right"></span>
</div>
<div class="layui-card-body">
<p class="lay-big-font">99,666</p>
<p>总计访问量<span class="pull-right">88万 <i class="layui-icon layui-icon-flag"></i></span></p>
</div>
</div>
</div>
<div class="layui-col-xs12 layui-col-sm6 layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">
下载<span class="layui-badge layui-bg-black pull-right"></span>
</div>
<div class="layui-card-body">
<p class="lay-big-font">33,555</p>
<p>新下载<span class="pull-right">10% <i class="layui-icon">&#xe601</i></span></p>
</div>
</div>
</div>
<div class="layui-col-xs12 layui-col-sm6 layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">
Start<span class="layui-badge layui-bg-green pull-right"></span>
</div>
<div class="layui-card-body">
<p class="lay-big-font">99,666</p>
<p>总Start数<span class="pull-right">88万 <i class="layui-icon layui-icon-rate"></i></span></p>
</div>
</div>
</div>
<div class="layui-col-xs12 layui-col-sm6 layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">
活跃用户<span class="layui-badge layui-bg-orange pull-right"></span>
</div>
<div class="layui-card-body">
<p class="lay-big-font">66,666</p>
<p>最近一个月<span class="pull-right">15% <i class="layui-icon layui-icon-user"></i></span></p>
</div>
</div>
</div>
</div>
<div class="layui-row layui-col-space15">
<div class="layui-col-lg8 layui-col-md7">
<div class="layui-card">
<div class="layui-card-header">更新日志</div>
<div class="layui-card-body">
<ul class="layui-timeline">
<li class="layui-timeline-item">
<i class="layui-icon layui-timeline-axis">&#xe63f;</i>
<div class="layui-timeline-content layui-text">
<h3 class="layui-timeline-title">
V1.0
<small>catchAdmin 后台框架发布</small>&emsp;
<span class="layui-badge-rim">2019-12-15</span>
</h3>
<ul>
<li>基于 Thinkphp6 & layui 开发</li>
<li>完整的权限管理</li>
<li>模块化开发方式</li>
<li>...</li>
</ul>
</div>
</li>
<li class="layui-timeline-item">
<i class="layui-icon layui-timeline-axis">&#xe63f;</i>
<div class="layui-timeline-content layui-text">
<div class="layui-timeline-title">
CatchAdmin 开发中...&emsp;<span class="layui-badge-rim">更早</span>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
<div class="layui-col-lg4 layui-col-md5">
<div class="layui-card">
<div class="layui-card-header">后台框架</div>
<div class="layui-card-body">
<table class="layui-table layui-text">
<colgroup>
<col width="100">
<col>
</colgroup>
<tbody>
<tr>
<td>PHP 版本</td>
<td>{$Think.PHP_VERSION}</td>
</tr>
<tr>
<td>MYSQL 版本</td>
<td>{$mysql_version}</td>
</tr>
<tr>
<td>WEB 服务器</td>
<td>{$_SERVER['SERVER_SOFTWARE']}</td>
</tr>
<tr>
<td>操作系统</td>
<td>{$Think.PHP_OS}</td>
</tr>
<tr>
<td>opcache (建议开启)</td>
{if condition="function_exists('opcache_get_configuration')"}
<td>{:opcache_get_configuration()['directives']['opcache.enable'] ? '开启' : '关闭' }</td>
{else/}
<td>未开启</td>
{/if}
</tr>
<tr>
<td>最大执行时间</td>
<td>{:get_cfg_var("max_execution_time")} s</td>
</tr>
<tr>
<td>上传限制大小(M)</td>
<td>{:get_cfg_var ("upload_max_filesize")}</td>
</tr>
<tr>
<td>当前时间</td>
<td>{:date("Y-m-d H:i:s")}</td>
</tr>
<tr>
<td>核心框架</td>
<td>Thinkphp v6</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- js部分 -->
<script type="text/javascript" src="__CATCH_ADMIN_LIBS__/layui/layui.js"></script>
<script type="text/javascript" src="__CATCH_ADMIN_JS__/common.js?v=315"></script>
<script>
layui.use(['layer', 'carousel'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var carousel = layui.carousel;
var device = layui.device;
// 渲染轮播
carousel.render({
elem: '.layui-carousel',
width: '100%',
height: '60px',
arrow: 'none',
autoplay: true,
trigger: device.ios || device.android ? 'click' : 'hover',
anim: 'fade'
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>403</title>
<link rel="stylesheet" href="__CATCH_ADMIN_LIBS__/layui/css/layui.css"/>
<link rel="stylesheet" href="__CATCH_ADMIN_MODULE__/admin.css?v=315"/>
<link rel="stylesheet" href="__CATCH_ADMIN_CSS__/error-page.css"/>
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<!-- 正文开始 -->
<div class="error-page">
<img class="error-page-img" src="__CATCH_ADMIN_IMAGES__/ic_403.png">
<div class="error-page-info">
<h1>403</h1>
<div class="error-page-info-desc">{$msg}</div>
<div>
{if ($code == 10006)}
<a href="{:url('login')}" class="layui-btn">返回登录</a>
{else/}
<button onclick="history(-1)" class="layui-btn">返回上一页</button>
{/if}
</div>
</div>
</div>
<!-- js部分 -->
<script type="text/javascript" src="__CATCH_ADMIN_LIBS__/layui/layui.js"></script>
<script type="text/javascript" src="__CATCH_ADMIN_JS__/common.js?v=315"></script>
</body>
</html>

View File

@@ -0,0 +1,142 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link href="__CATCH_ADMIN_IMAGES__/favicon.ico" rel="icon">
<title>CatchAdmin 后台管理系统</title>
<link rel="stylesheet" href="__CATCH_ADMIN_LIBS__/layui/css/layui.css"/>
<link rel="stylesheet" href="__CATCH_ADMIN_MODULE__/admin.css?v=315"/>
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body class="layui-layout-body">
<div class="layui-layout layui-layout-admin">
<!-- 头部 -->
<div class="layui-header">
<div class="layui-logo">
<img src="__CATCH_ADMIN_IMAGES__/logo.png"/>
<cite>CatchAdmin</cite>
</div>
<ul class="layui-nav layui-layout-left">
<li class="layui-nav-item" lay-unselect>
<a ew-event="flexible" title="侧边伸缩"><i class="layui-icon layui-icon-shrink-right"></i></a>
</li>
<li class="layui-nav-item" lay-unselect>
<a ew-event="refresh" title="刷新"><i class="layui-icon layui-icon-refresh-3"></i></a>
</li>
</ul>
<ul class="layui-nav layui-layout-right">
<!-- <li class="layui-nav-item" lay-unselect>
<a ew-event="message" title="消息">
<i class="layui-icon layui-icon-notice"></i>
<span class="layui-badge-dot"></span>
</a>
</li>
<li class="layui-nav-item" lay-unselect>
<a ew-event="note" title="便签"><i class="layui-icon layui-icon-note"></i></a>
</li>-->
<li class="layui-nav-item layui-hide-xs" lay-unselect>
<a ew-event="fullScreen" title="全屏"><i class="layui-icon layui-icon-screen-full"></i></a>
</li>
<li class="layui-nav-item" lay-unselect>
<a>
<img src="__CATCH_ADMIN_IMAGES__/head.png" class="layui-nav-img">
<cite>{$username}</cite>
</a>
<dl class="layui-nav-child">
<!--<dd lay-unselect>
<a ew-href="javascript:;">个人中心</a>
</dd>
<dd lay-unselect>
<a ew-event="psw">修改密码</a>
</dd>
<hr>-->
<dd lay-unselect>
<a id="logout">退出</a>
</dd>
</dl>
</li>
<li class="layui-nav-item" lay-unselect>
<a ew-event="theme" title="主题"><i class="layui-icon layui-icon-more-vertical"></i></a>
</li>
</ul>
</div>
<!-- 侧边栏 -->
<div class="layui-side">
<div class="layui-side-scroll">
<ul class="layui-nav layui-nav-tree arrow2" lay-filter="admin-side-nav" lay-accordion="true"
style="margin-top: 15px;">
{foreach $menus as $key => $menu }
<li class="layui-nav-item">
{if (!empty($menu['children']))}
<a><i class="layui-icon layui-icon-home"></i>&emsp;<cite>{$menu['permission_name']}</cite></a>
<dl class="layui-nav-child">
{foreach $menu['children'] as $m}
{if (!empty($m['children']))}
<a><i class="layui-icon layui-icon-home"></i>&emsp;<cite>{$m['permission_name']}</cite></a>
<dl class="layui-nav-child">
{foreach $m['children'] as $_m}
<dd><a lay-href="{:url($_m['route'])}">{$_m['permission_name']}</a></dd>
{/foreach}
</dl>
{else/}
<dd><a lay-href="{:url($m['route'])}">{$m['permission_name']}</a></dd>
{/if}
{/foreach}
</dl>
{else/}
<a lay-href="{:url($menu['route'])}"><i class="layui-icon layui-icon-unlink"></i>&emsp;<cite>{$menu['permission_name']}</cite></a>
{/if}
</li>
{/foreach}
</ul>
</div>
</div>
<!-- 主体部分 -->
<div class="layui-body"></div>
<!-- 底部 -->
<div class="layui-footer">
copyright 2017 ~ © {:date('Y', time())} <a href="https://catchadmin.com" target="_blank">catchadmin.com</a> all rights reserved.
<span class="pull-right">Version 2.0</span>
</div>
</div>
<!-- 加载动画 -->
<div class="page-loading">
<div class="ball-loader">
<span></span><span></span><span></span><span></span>
</div>
</div>
<!-- js部分 -->
<script type="text/javascript" src="__CATCH_ADMIN_LIBS__/layui/layui.js"></script>
<script type="text/javascript" src="__CATCH_ADMIN_JS__/common.js?v=315"></script>
<script>
layui.use(['index', 'admin'], function () {
var $ = layui.jquery;
var index = layui.index;
var admin = layui.admin;
// 默认加载主页
index.loadHome({
menuPath: '{:url("dashboard")}',
menuName: '<i class="layui-icon layui-icon-home"></i>'
});
$(document).on('click','#logout',function(){
admin.req('{:url("logout")}', {}, function (response) {
if (response.code === 10000) {
window.location.href = "{:url('login')}";
}
}, 'post')
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,259 @@
<!DOCTYPE html>
<html lang="en" class="bg-white">
<head>
<title>设置</title>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="__CATCH_ADMIN_LIBS__/layui/css/layui.css"/>
<link rel="stylesheet" href="__CATCH_ADMIN_MODULE__/admin.css?v=315"/>
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<style>
body {
overflow-x: hidden;
}
.layui-card-body {
padding: 0;
}
.theme-div {
padding-left: 15px;
padding-top: 20px;
margin-bottom: 10px;
}
.btnTheme {
display: inline-block;
margin: 0 6px 15px 0;
padding: 4px;
border: 1px solid #fff;
}
.btnTheme img {
width: 80px;
height: 50px;
border: 1px solid #f2f2f2;
background: #F2F2F2;
cursor: pointer;
}
.btnTheme:hover, .btnTheme.active {
border-color: #5FB878;
}
.more-menu-item {
display: block;
height: 50px;
line-height: 50px;
font-size: 16px;
border-bottom: 1px solid #e8e8e8;
color: #333;
padding: 0px 25px;
font-style: normal;
}
.more-menu-item:first-child {
border-top: 1px solid #e8e8e8;
}
.more-menu-item:hover {
background: #F2F2F2;
color: #333;
}
.more-menu-item .layui-icon {
padding-right: 10px;
font-size: 18px;
}
.more-menu-item:after {
content: "\e602";
font-family: layui-icon !important;
position: absolute;
right: 16px;
color: #999;
}
.more-menu-item.no-icon:after {
content: "";
}
/** 设置表单样式 */
.set-item-label {
display: inline-block;
height: 38px;
line-height: 38px;
padding-left: 20px;
color: #333333;
}
.set-item-ctrl {
display: inline-block;
height: 38px;
line-height: 38px;
}
.set-item-ctrl > * {
margin: 0;
}
</style>
</head>
<body>
<!-- 加载动画 -->
<div class="page-loading">
<div class="ball-loader">
<span></span><span></span><span></span><span></span>
</div>
</div>
<div class="layui-card-header">设置主题</div>
<div class="layui-card-body">
<!-- 主题列表 -->
<div class="theme-div"></div>
<!-- 导航 -->
<div class="more-menu">
<a class="more-menu-item" href="https://gitee.com/jaguarjack/catchAdmin" target="_blank">
<i class="layui-icon layui-icon-read" style="font-size: 19px;"></i> Git 地址
</a>
<a class="more-menu-item" href="https://catchadmin.com" target="_blank">
<i class="layui-icon layui-icon-tabs" style="font-size: 16px;"></i> &nbsp;官网
</a>
<!--<a class="more-menu-item" href="https://demo.easyweb.vip/theme" target="_blank">
<i class="layui-icon layui-icon-theme"></i> 主题生成器
</a>-->
</div>
<!-- 控制开关 -->
<div class="layui-form" style="margin: 25px 0;">
<div class="layui-form-item">
<label class="set-item-label">&emsp;脚:</label>
<div class="set-item-ctrl">
<input id="setFooter" lay-filter="setFooter" type="checkbox" lay-skin="switch" lay-text="开启|关闭">
</div>
<label class="set-item-label"> Tab&nbsp;记忆:</label>
<div class="set-item-ctrl">
<input id="setTab" lay-filter="setTab" type="checkbox" lay-skin="switch" lay-text="开启|关闭">
</div>
</div>
<div class="layui-form-item">
<label class="set-item-label">多标签:</label>
<div class="set-item-ctrl">
<input id="setMoreTab" lay-filter="setMoreTab" type="checkbox" lay-skin="switch" lay-text="开启|关闭">
</div>
<label class="set-item-label">切换刷新:</label>
<div class="set-item-ctrl">
<input id="setRefresh" lay-filter="setRefresh" type="checkbox" lay-skin="switch" lay-text="开启|关闭">
</div>
</div>
<div class="layui-form-item">
<label class="set-item-label">导航箭头:</label>
<div class="set-item-ctrl">
<input lay-filter="navArrow" type="radio" value="" title="默认" name="navArrow">
<input lay-filter="navArrow" type="radio" value="arrow2" title="箭头" name="navArrow">
<input lay-filter="navArrow" type="radio" value="arrow3" title="加号" name="navArrow">
</div>
</div>
</div>
</div>
<script type="text/javascript" src="__CATCH_ADMIN_LIBS__/layui/layui.js"></script>
<script type="text/javascript" src="__CATCH_ADMIN_JS__/common.js?v=315"></script>
<script>
layui.use(['layer', 'form', 'admin'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var admin = layui.admin;
var leftNav = '.layui-layout-admin>.layui-side>.layui-side-scroll>.layui-nav';
var mainTab = '.layui-body>.layui-tab[lay-filter="admin-pagetabs"]';
var themes = [
{title: '黑白主题', theme: 'admin'},
{title: '黑色主题', theme: 'black'},
{title: '蓝色主题', theme: 'blue'},
{title: '藏青主题', theme: 'cyan'},
{title: '黄色主题', theme: 'yellow'},
{title: '绿色主题', theme: 'green'},
{title: '粉红主题', theme: 'pink'},
{title: '紫白主题', theme: 'purple-white'},
{title: '紫色主题', theme: 'purple'},
{title: '白色主题', theme: 'white'},
{title: '红白主题', theme: 'red-white'},
{title: '红色主题', theme: 'red'}
];
for (var i = 0; i < themes.length; i++) {
var str = '<div class="btnTheme" theme="theme-' + themes[i].theme + '" title="' + themes[i].title + '">';
str += ' <img src="__CATCH_ADMIN_MODULE__/theme/img/theme-' + themes[i].theme + '.png">';
str += ' </div>';
$('.theme-div').append(str)
}
// 切换主题
var mTheme = layui.data(admin.tableName).theme;
$('.btnTheme[theme=' + (mTheme ? mTheme : admin.defaultTheme) + ']').addClass('active');
$('.btnTheme').click(function () {
$('.btnTheme').removeClass('active');
$(this).addClass('active');
admin.changeTheme($(this).attr('theme'));
});
// 关闭/开启页脚
var openFooter = layui.data(admin.tableName).openFooter;
$('#setFooter').prop('checked', openFooter == undefined ? true : openFooter);
form.on('switch(setFooter)', function (data) {
var checked = data.elem.checked;
layui.data(admin.tableName, {key: 'openFooter', value: checked});
checked ? top.layui.jquery('body.layui-layout-body').removeClass('close-footer') : top.layui.jquery('body.layui-layout-body').addClass('close-footer');
});
// 关闭/开启Tab记忆功能
$('#setTab').prop('checked', top.layui.index.cacheTab);
form.on('switch(setTab)', function (data) {
top.layui.index.setTabCache(data.elem.checked);
});
// 切换Tab自动刷新
var tabAutoRefresh = layui.data(admin.tableName).tabAutoRefresh;
$('#setRefresh').prop('checked', tabAutoRefresh == undefined ? false : tabAutoRefresh);
form.on('switch(setRefresh)', function (data) {
var checked = data.elem.checked;
layui.data(admin.tableName, {key: 'tabAutoRefresh', value: checked});
checked ? top.layui.jquery(mainTab).attr('lay-autoRefresh', 'true') : top.layui.jquery(mainTab).removeAttr('lay-autoRefresh');
});
// 关闭/开启多标签
var openTab = layui.data(admin.tableName).openTab;
$('#setMoreTab').prop('checked', openTab == undefined ? top.layui.index.pageTabs : openTab);
form.on('switch(setMoreTab)', function (data) {
var checked = data.elem.checked;
layui.data(admin.tableName, {key: 'openTab', value: checked});
admin.putTempData('indexTabs', undefined); // 清除缓存的Tab
top.location.reload();
});
// 导航小三角
var navArrow = layui.data(admin.tableName).navArrow;
if (navArrow == undefined) {
var $sideNav = top.layui.jquery('.layui-side .layui-nav-tree');
navArrow = $sideNav.hasClass('arrow2') ? 'arrow2' : $sideNav.hasClass('arrow3') ? 'arrow3' : '';
}
$('[name="navArrow"][value="' + (navArrow ? navArrow : '') + '"]').prop('checked', 'true');
form.on('radio(navArrow)', function (data) {
layui.data(admin.tableName, {key: 'navArrow', value: data.value});
top.layui.jquery(leftNav).removeClass('arrow2 arrow3');
data.value && top.layui.jquery(leftNav).addClass(data.value);
});
form.render('checkbox');
form.render('radio');
});
</script>
</body>
</html>

View File

@@ -0,0 +1,81 @@
<?php
namespace catchAdmin\login;
use catchAdmin\user\model\Users;
use think\facade\Db;
class LoginLogListener
{
public function handle($params)
{
$agent = request()->header('user-agent');
$username = Users::where('email', $params['email'])->value('username');
Db::name('login_log')->insert([
'login_name' => $username ? : $params['email'],
'login_ip' => request()->ip(),
'browser' => $this->getBrowser($agent),
'os' => $this->getOs($agent),
'login_at' => time(),
'status' => $params['success'] ? 1 : 2,
]);
}
/**
*
* @time 2019年12月12日
* @param $agent
* @return string
*/
private function getOs($agent): string
{
if (false !== stripos($agent, 'win') && preg_match('/nt 6.1/i', $agent)) {
return 'Windows 7';
}
if (false !== stripos($agent, 'win') && preg_match('/nt 6.2/i', $agent)) {
return 'Windows 8';
}
if(false !== stripos($agent, 'win') && preg_match('/nt 10.0/i', $agent)) {
return 'Windows 10';#添加win10判断
}
if (false !== stripos($agent, 'win') && preg_match('/nt 5.1/i', $agent)) {
return 'Windows XP';
}
if (false !== stripos($agent, 'linux')) {
return 'Linux';
}
if (false !== stripos($agent, 'mac')) {
return 'mac';
}
return '未知';
}
/**
*
* @time 2019年12月12日
* @param $agent
* @return string
*/
private function getBrowser($agent): string
{
if (false !== stripos($agent, "MSIE")) {
return 'MSIE';
}
if (false !== stripos($agent, "Firefox")) {
return 'Firefox';
}
if (false !== stripos($agent, "Chrome")) {
return 'Chrome';
}
if (false !== stripos($agent, "Safari")) {
return 'Safari';
}
if (false !== stripos($agent, "Opera")) {
return 'Opera';
}
return '未知';
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace catchAdmin\login\controller;
use app\exceptions\LoginFailedException;
use catchAdmin\user\Auth;
use catchAdmin\login\request\LoginRequest;
use catcher\base\CatchController;
use catcher\CatchResponse;
use think\captcha\Captcha;
class Index extends CatchController
{
/**
* 登录
*
* @time 2019年11月30日
* @throws \Exception
* @return string
*/
public function index(): string
{
return $this->fetch();
}
/**
* 登陆
*
* @time 2019年11月28日
* @param LoginRequest $request
* @return bool|string
* @throws \catcher\exceptions\LoginFailedException
* @throws \cather\exceptions\LoginFailedException
* @throws LoginFailedException
*/
public function login(LoginRequest $request)
{
$params = $request->param();
$isSucceed = Auth::login($params);
// 登录事件
$params['success'] = $isSucceed;
event('loginLog', $params);
return $isSucceed ? CatchResponse::success('', '登录成功') :
CatchResponse::success('', '登录失败');
}
/**
* 登出
*
* @time 2019年11月28日
* @return \think\response\Json
* @throws \Exception
*/
public function logout(): \think\response\Json
{
if (Auth::logout()) {
return CatchResponse::success();
}
return CatchResponse::fail('登出失败');
}
/**
*
* @time 2019年12月12日
* @param Captcha $captcha
* @param null $config
* @return \think\Response
*/
public function captcha(Captcha $captcha, $config = null): \think\Response
{
return $captcha->create($config);
}
}

View File

@@ -0,0 +1,13 @@
{
"name": "登陆",
"alias": "login",
"description": "",
"keywords": [],
"order": 1,
"services": [
"catchAdmin\\login\\LoginService"
],
"aliases": {},
"files": [],
"requires": []
}

View File

@@ -0,0 +1,23 @@
<?php
namespace catchAdmin\login\request;
use catcher\base\CatchRequest;
class LoginRequest extends CatchRequest
{
protected function rules(): array
{
// TODO: Implement rules() method.
return [
'email|用户名' => 'email',
'password|密码' => 'require',
'captcha|验证码' => 'require|captcha'
];
}
protected function message(): array
{
// TODO: Implement message() method.
return [];
}
}

View File

@@ -0,0 +1,11 @@
<?php
# 登陆页面
$router->get('login', '\catchAdmin\login\controller\Index@index');
# 登入
$router->post('login', '\catchAdmin\login\controller\Index@login');
# 登出
$router->post('logout', '\catchAdmin\login\controller\Index@logout');
# 验证码
$router->get('catch/captcha/[:config]','\catchAdmin\login\controller\Index@captcha');

View File

@@ -0,0 +1,130 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>登录</title>
<link rel="stylesheet" href="__CATCH_ADMIN_LIBS__/layui/css/layui.css"/>
<link rel="stylesheet" href="__CATCH_ADMIN_CSS__/login.css?v=315">
<link rel="stylesheet" href="__CATCH_ADMIN_MODULE__/admin.css?v=315">
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<script>
if (window != top) {
top.location.replace(location.href);
}
</script>
<style>
.captcha {
width: 114px;
height: 38px;
}
</style>
</head>
<body>
<div class="login-wrapper">
<div class="login-header">
<img src="__CATCH_ADMIN_IMAGES__/logo.png"> CatchAdmin 后台管理系统
</div>
<div class="login-body">
<div class="layui-card">
<div class="layui-card-header">
<i class="layui-icon layui-icon-engine"></i>&nbsp;&nbsp;用户登录
</div>
<form class="layui-card-body layui-form layui-form-pane">
<div class="layui-form-item">
<label class="layui-form-label"><i class="layui-icon layui-icon-username"></i></label>
<div class="layui-input-block">
<input name="email" type="text" placeholder="邮箱" class="layui-input"
lay-verType="tips" lay-verify="required|email" required/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"><i class="layui-icon layui-icon-password"></i></label>
<div class="layui-input-block">
<input name="password" type="password" placeholder="密码" class="layui-input"
lay-verType="tips" lay-verify="required" required/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"><i class="layui-icon layui-icon-vercode"></i></label>
<div class="layui-input-block">
<div class="layui-row inline-block">
<div class="layui-col-xs7">
<input name="captcha" type="text" placeholder="验证码" class="layui-input"
autocomplete="off" lay-verType="tips" lay-verify="required" required/>
</div>
<div class="layui-col-xs5" style="padding-left: 6px;">
<img src="{:url('catch/captcha')}" alt="captcha" class="captcha" onclick="this.src = this.src + '?t=' + (new Date).getTime();"/>
</div>
</div>
</div>
</div>
<div class="layui-form-item">
<!--<a href="javascript:;" class="layui-link">帐号注册</a>-->
<!--<a href="javascript:;" class="layui-link pull-right">忘记密码?</a>-->
</div>
<div class="layui-form-item">
<button lay-filter="login-submit" class="layui-btn layui-btn-fluid" lay-submit>登 录</button>
</div>
<!--<div class="layui-form-item login-other">
<label>第三方登录</label>
<a href="javascript:;"><i class="layui-icon layui-icon-login-qq"></i></a>
<a href="javascript:;"><i class="layui-icon layui-icon-login-wechat"></i></a>
<a href="javascript:;"><i class="layui-icon layui-icon-login-weibo"></i></a>
</div>-->
</form>
</div>
</div>
<div class="login-footer">
<p>© 2015 ~ {:date('Y', time())} @catchAdmin 版权所有</p>
<!--<p>
<span><a href="https://easyweb.vip" target="_blank">获取授权</a></span>
<span><a href="https://easyweb.vip/doc/" target="_blank">开发文档</a></span>
<span><a href="https://demo.easyweb.vip/spa/" target="_blank">单页面版</a></span>
</p>-->
</div>
</div>
<!-- js部分 -->
<script type="text/javascript" src="__CATCH_ADMIN_LIBS__/layui/layui.js"></script>
<script type="text/javascript" src="__CATCH_ADMIN_JS__/common.js?v=315"></script>
<script>
layui.use(['layer', 'form'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
// 表单提交
form.on('submit(login-submit)', function (obj) {
$.ajax({
url: "{:url('login')}",
type: 'post',
data: obj.field,
success: function(response) {
if (response.code === 10000) {
layer.msg(response.msg, {
icon: 1,
time: 2000 //2秒关闭如果不配置默认是3秒
}, function () {
//do something
window.location.href = '/';
})
} else {
layer.msg(response.msg, {
icon: 2,
time: 2000 //2秒关闭如果不配置默认是3秒
})
}
}
});
return false;
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,28 @@
<?php
namespace catchAdmin\permissions;
use catchAdmin\permissions\model\Permissions;
use catcher\CatchAdmin;
use think\facade\Db;
class OperateLogListener
{
public function handle($params)
{
$request = $params['request'];
$permission = $params['permission'];
$parentPermission = Permissions::where('id', $permission->parent_id)->value('permission_name');
Db::name('operate_log')->insert([
'creator_id' => $request->user()->id,
'module' => $parentPermission ? : '',
'method' => $request->method(),
'operate' => $permission->permission_name,
'route' => $permission->route,
'params' => json_encode($request->param()),
'created_at' => time(),
'ip' => $request->ip(),
]);
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace catchAdmin\permissions;
use app\Request;
use catchAdmin\permissions\model\Permissions;
use catcher\exceptions\PermissionForbiddenException;
use think\helper\Str;
class PermissionsMiddleware
{
/**
*
* @time 2019年12月12日
* @param Request $request
* @param \Closure $next
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @throws PermissionForbiddenException
*/
public function handle(Request $request, \Closure $next)
{
$rule = $rule = $request->rule()->getName();
if (!$rule) {
return $next($request);
}
[$module, $controller, $action] = $this->parseRule($rule);
if (in_array($module, $this->ignoreModule())) {
return $next($request);
}
if (!$request->user()) {
throw new PermissionForbiddenException('Login is invalid', 10006);
}
// toad
if (($permission = $this->getPermission($module, $controller, $action, $request))
&& !in_array($permission->id, $request->user()->getPermissionsBy())) {
throw new PermissionForbiddenException();
}
return $next($request);
}
protected function parseRule($rule)
{
[$controller, $action] = explode(Str::contains($rule, '@') ? '@' : '/', $rule);
$controller = explode('\\', $controller);
$controllerName = strtolower(array_pop($controller));
array_pop($controller);
$module = array_pop($controller);
return [$module, $controllerName, $action];
}
/**
*
* @time 2019年12月14日
* @param $module
* @param $controllerName
* @param $action
* @param $request
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @return array|bool|\think\Model|null
*/
protected function getPermission($module, $controllerName, $action, $request)
{
$permissionMark = sprintf('%s:%s', $controllerName, $action);
$permission = Permissions::where('module', $module)->where('permission_mark', $permissionMark)->find();
if (!$permission) {
return false;
}
event('operateLog', [
'request' => $request,
'permission' => $permission,
]);
return $permission;
}
protected function ignoreModule()
{
return ['login'];
}
}

View File

@@ -0,0 +1,166 @@
<?php
namespace catchAdmin\permissions\controller;
use app\Request;
use catcher\base\CatchController;
use catcher\CatchAdmin;
use catcher\CatchForm;
use catcher\CatchResponse;
use catcher\exceptions\FailedException;
use catcher\Tree;
use catchAdmin\permissions\model\Permissions as Permissions;
class Permission extends CatchController
{
protected $permissions;
public function __construct(Permissions $permissions)
{
$this->permissions = $permissions;
}
/**
*
* @time 2019年12月11日
* @throws \Exception
* @return string
*/
public function index()
{
return $this->fetch();
}
/**
*
* @time 2019年12月11日
* @param Request $request
* @return \think\response\Json
*/
public function list(Request $request)
{
return CatchResponse::success(Tree::done($this->permissions->getList($request->param())));
}
/**
*
* @time 2019年12月11日
* @throws \Exception
* @return string
*/
public function create()
{
$form = new CatchForm();
$form->formId('permission');
$form->text('permission_name', '菜单名称', true)->verify('required')->placeholder('请输入菜单名称');
$form->hidden('parent_id')->default(\request()->param('id') ?? 0);
$form->select('module', '模块', true)->verify('required')->options(CatchAdmin::getModulesInfo());
$form->text('route', '路由')->placeholder('请输入路由');
$form->radio('method', '请求方法', true)->default(Permissions::GET)->options([
['value' => Permissions::GET, 'title' => 'get'],
['value' => Permissions::POST, 'title' => 'post'],
['value' => Permissions::PUT, 'title' => 'put'],
['value' => Permissions::DELETE, 'title' => 'delete'],
]);
$form->text('permission_mark', '权限标识', true)->verify('required')->placeholder('请输入权限标识controller:action');
$form->radio('type', '类型', true)->default(Permissions::BTN_TYPE)->options([
['value' => Permissions::MENU_TYPE, 'title' => '菜单'],
['value' => Permissions::BTN_TYPE, 'title' => '按钮'],
]);
$form->text('sort', '排序')->verify('numberX')->default(1)->placeholder('倒叙排序');
$form->formBtn('submitPermission');
return $this->fetch(['form' => $form->render()]);
}
/**
*
* @time 2019年12月11日
* @param Request $request
* @return \think\response\Json
*/
public function save(Request $request)
{
return CatchResponse::success($this->permissions->storeBy($request->param()));
}
public function read()
{}
/**
*
* @time 2019年12月11日
* @param $id
* @throws \Exception
* @return string
*/
public function edit($id)
{
$permission = $this->permissions->findBy($id);
$form = new CatchForm();
$form->formId('permission');
$form->text('permission_name', '菜单名称', true)
->default($permission->permission_name)
->verify('required')
->placeholder('请输入菜单名称');
$form->hidden('parent_id')->default($permission->parent_id);
$form->select('module', '模块', true)->default($permission->module)->options(CatchAdmin::getModulesInfo());
$form->text('route', '路由')->default($permission->route)->placeholder('请输入路由');
$form->radio('method', '请求方法', true)->verify('required')->default($permission->method)->options([
['value' => Permissions::GET, 'title' => 'get'],
['value' => Permissions::POST, 'title' => 'post'],
['value' => Permissions::PUT, 'title' => 'put'],
['value' => Permissions::DELETE, 'title' => 'delete'],
]);
$form->text('permission_mark', '权限标识', true)
->default($permission->permission_mark)
->verify('required')->placeholder('请输入权限标识controller:action');
$form->radio('type', '类型', true)->default($permission->type)->options([
['value' => Permissions::MENU_TYPE, 'title' => '菜单'],
['value' => Permissions::BTN_TYPE, 'title' => '按钮'],
]);
$form->text('sort', '排序')->verify('numberX')->default($permission->sort)->placeholder('倒叙排序');
$form->formBtn('submitPermission');
return $this->fetch([
'form' => $form->render(),
'permission_id' => $permission->id,
]);
}
/**
*
* @time 2019年12月11日
* @param $id
* @param Request $request
* @return \think\response\Json
*/
public function update($id, Request $request)
{
return CatchResponse::success($this->permissions->updateBy($id, $request->param()));
}
/**
*
* @time 2019年12月11日
* @param $id
* @throws FailedException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @return \think\response\Json
*/
public function delete($id)
{
if ($this->permissions->where('parent_id', $id)->find()) {
throw new FailedException('存在子菜单,无法删除');
}
$this->permissions->findBy($id)->roles()->detach();
return CatchResponse::success($this->permissions->deleteBy($id));
}
}

View File

@@ -0,0 +1,197 @@
<?php
namespace catchAdmin\permissions\controller;
use app\Request;
use catcher\base\CatchController;
use catcher\CatchForm;
use catcher\CatchResponse;
use catcher\exceptions\FailedException;
use catcher\Tree;
use think\response\Json;
class Role extends CatchController
{
protected $role;
public function __construct(\catchAdmin\permissions\model\Roles $role)
{
$this->role = $role;
}
/**
*
* @time 2019年12月09日
* @throws \Exception
* @return string
*/
public function index()
{
return $this->fetch();
}
/**
*
* @time 2019年12月11日
* @throws \Exception
* @return string
*/
public function create()
{
$form = new CatchForm();
$form->formId('role');
$form->text('role_name', '角色名称', true)->verify('required')->placeholder('请输入角色名称');
$form->hidden('parent_id')->default(\request()->param('id') ?? 0);
$form->textarea('description', '角色描述')->placeholder('请输入角色描述');
$form->dom('<div id="permissions"></div>', '权限');
$form->formBtn('submitRole');
return $this->fetch([
'form' => $form->render(),
'parent_id' => \request()->param('id') ?? 0,
]);
}
/**
*
* @time 2019年12月11日
* @param Request $request
* @return Json
* @throws \think\db\exception\DbException
*/
public function save(Request $request)
{
$this->role->storeBy($request->param());
if (!empty($request->param('permissionids'))) {
$this->role->attach($request->param('permissionids'));
}
// 添加角色
return CatchResponse::success();
}
public function read($id)
{
}
/**
*
* @time 2019年12月11日
* @param $id
* @throws \Exception
* @return string
*/
public function edit($id)
{
$role = $this->role->findBy($id);
$form = new CatchForm();
$form->formId('role');
$form->hidden('parent_id')->default($role->parent_id);
$form->text('role_name', '角色名称', true)->default($role->role_name)->verify('required')->placeholder('请输入角色名称');
$form->textarea('description', '角色描述')->default($role->description)->placeholder('请输入角色描述');
$form->dom('<div id="permissions"></div>', '权限');
$form->formBtn('submitRole');
return $this->fetch([
'form' => $form->render(),
'role_id' => $role->id,
'parent_id' => $role->parent_id
]);
}
/**
*
* @time 2019年12月11日
* @param $id
* @param Request $request
* @return Json
* @throws \think\db\exception\DbException
*/
public function update($id, Request $request)
{
$this->role->updateBy($id, $request->param());
$role = $this->role->findBy($id);
$role->detach();
if (!empty($request->param('permissionids'))) {
$role->attach($request->param('permissionids'));
}
return CatchResponse::success();
}
/**
*
* @time 2019年12月11日
* @param $id
* @throws FailedException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @return Json
*/
public function delete($id)
{
if ($this->role->where('parent_id', $id)->find()) {
throw new FailedException('存在子角色,无法删除');
}
$role = $this->role->findBy($id);
// 删除权限
$role->detach();
// 删除用户关联
$role->users()->detach();
// 删除
$this->role->deleteBy($id);
return CatchResponse::success();
}
/**
*
* @time 2019年12月11日
* @param Request $request
* @return Json
*/
public function list(Request $request)
{
return CatchResponse::success(Tree::done($this->role->getList($request->param())));
}
/**
*
* @time 2019年12月11日
* @param Request $request
* @param \catchAdmin\permissions\model\Permissions $permission
* @return Json
*/
public function getPermissions(Request $request, \catchAdmin\permissions\model\Permissions $permission): Json
{
$parentRoleHasPermissionIds = null;
if ($request->param('parent_id')) {
$permissions = $this->role->findBy($request->param('parent_id'))->getPermissions();
foreach ($permissions as $_permission) {
$parentRoleHasPermissionIds[] = $_permission->pivot->permission_id;
}
}
$permissions = Tree::done($permission->getList([
'permission_ids' => $parentRoleHasPermissionIds
]));
$permissionIds = [];
if ($request->param('role_id')) {
$roleHasPermissions = $this->role->findBy($request->param('role_id'))->getPermissions();
foreach ($roleHasPermissions as $_permission) {
$permissionIds[] = $_permission->pivot->permission_id;
}
}
return CatchResponse::success([
'permissions' => $permissions,
'hasPermissions' => $permissionIds,
]);
}
}

View File

@@ -0,0 +1,40 @@
<?php
use think\migration\Migrator;
use think\migration\db\Column;
class Roles extends Migrator
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
*
* The following commands can be used in this method and Phinx will
* automatically reverse them when rolling back:
*
* createTable
* renameTable
* addColumn
* renameColumn
* addIndex
* addForeignKey
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change()
{
$table = $this->table('roles',['engine'=>'Innodb', 'comment' => '角色表', 'signed' => false]);
$table->addColumn('role_name', 'string',['limit' => 15,'default'=>'','comment'=>'角色名'])
->addColumn('parent_id', 'integer',['default'=>0,'comment'=>'父级ID', 'signed' => false])
->addColumn('description', 'string',['default'=> '','comment'=>'角色备注'])
->addColumn('created_at', 'integer', array('default'=>0,'comment'=>'创建时间', 'signed' => false ))
->addColumn('updated_at', 'integer', array('default'=>0,'comment'=>'更新时间', 'signed' => false))
->addColumn('deleted_at', 'integer', array('default'=>0,'comment'=>'删除状态0未删除 >0 已删除', 'signed' => false))
->create();
}
}

View File

@@ -0,0 +1,46 @@
<?php
use think\migration\Migrator;
use think\migration\db\Column;
class Permissions extends Migrator
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
*
* The following commands can be used in this method and Phinx will
* automatically reverse them when rolling back:
*
* createTable
* renameTable
* addColumn
* renameColumn
* addIndex
* addForeignKey
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change()
{
$table = $this->table('permissions',['engine'=>'Innodb', 'comment' => '菜单表', 'signed' => false]);
$table->addColumn('permission_name', 'string',['limit' => 15,'default'=>'','comment'=>'菜单名称'])
->addColumn('parent_id', 'integer',['default'=>0,'comment'=>'父级ID', 'signed' => false])
->addColumn('route', 'string', ['default' => '', 'comment' => '路由', 'limit' => 50])
->addColumn('module', 'string', ['default' => '', 'comment' => '模块', 'limit' => 20])
->addColumn('method', 'string', ['default' => 'get', 'comment' => '路由请求方法', 'limit' => 15])
->addColumn('permission_mark', 'string', ['null' => false, 'comment' => '权限标识', 'limit' => 50])
->addColumn('type', 'integer',['limit' => \Phinx\Db\Adapter\MysqlAdapter::INT_TINY,'default'=> 1,'comment'=>'1 菜单 2 按钮'])
->addColumn('sort', 'integer',['default'=> 0,'comment'=>'排序字段'])
->addColumn('created_at', 'integer', array('default'=>0,'comment'=>'创建时间', 'signed' => false ))
->addColumn('updated_at', 'integer', array('default'=>0,'comment'=>'更新时间', 'signed' => false))
->addColumn('deleted_at', 'integer', array('default'=>0,'comment'=>'删除状态null 未删除 timestamp 已删除', 'signed' => false))
->create();
}
}

View File

@@ -0,0 +1,36 @@
<?php
use think\migration\Migrator;
use think\migration\db\Column;
class UserHasRoles extends Migrator
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
*
* The following commands can be used in this method and Phinx will
* automatically reverse them when rolling back:
*
* createTable
* renameTable
* addColumn
* renameColumn
* addIndex
* addForeignKey
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change()
{
$table = $this->table('user_has_roles',['engine'=>'Innodb', 'comment' => '用户角色表', 'signed' => false]);
$table->addColumn('uid', 'integer',['comment'=>'用户ID', 'signed' => false])
->addColumn('role_id', 'integer', ['comment'=>'角色ID', 'signed' => false])
->create();
}
}

View File

@@ -0,0 +1,36 @@
<?php
use think\migration\Migrator;
use think\migration\db\Column;
class RoleHasPermissions extends Migrator
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
*
* The following commands can be used in this method and Phinx will
* automatically reverse them when rolling back:
*
* createTable
* renameTable
* addColumn
* renameColumn
* addIndex
* addForeignKey
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change()
{
$table = $this->table('role_has_permissions',['engine'=>'Innodb', 'comment' => '角色权限表', 'signed' => false]);
$table->addColumn('role_id', 'integer',['comment'=>'角色ID', 'signed' => false])
->addColumn('permission_id', 'integer', ['comment'=>'权限ID', 'signed' => false])
->create();
}
}

View File

@@ -0,0 +1,603 @@
<?php
use think\migration\Seeder;
class PermissionSeed extends Seeder
{
/**
* Run Method.
*
* Write your database seeder using this method.
*
* More information on writing seeders is available here:
* http://docs.phinx.org/en/latest/seeding.html
*/
public function run()
{
$this->roles();
$this->createPermissions();
}
protected function roles()
{
\catchAdmin\permissions\model\Roles::create([
'role_name' => '超级管理员',
'description' => 'super user',
]);
\think\facade\Db::name('user_has_roles')->insert([
'role_id' => 1,
'uid' => 1,
]);
\think\facade\Db::name('role_has_permissions')->insertAll(array (
0 =>
array (
'role_id' => 1,
'permission_id' => 4,
),
1 =>
array (
'role_id' => 1,
'permission_id' => 6,
),
2 =>
array (
'role_id' => 1,
'permission_id' => 7,
),
3 =>
array (
'role_id' => 1,
'permission_id' => 8,
),
4 =>
array (
'role_id' => 1,
'permission_id' => 9,
),
5 =>
array (
'role_id' => 1,
'permission_id' => 10,
),
6 =>
array (
'role_id' => 1,
'permission_id' => 11,
),
7 =>
array (
'role_id' => 1,
'permission_id' => 12,
),
8 =>
array (
'role_id' => 1,
'permission_id' => 13,
),
9 =>
array (
'role_id' => 1,
'permission_id' => 14,
),
10 =>
array (
'role_id' => 1,
'permission_id' => 15,
),
11 =>
array (
'role_id' => 1,
'permission_id' => 16,
),
12 =>
array (
'role_id' => 1,
'permission_id' => 17,
),
13 =>
array (
'role_id' => 1,
'permission_id' => 18,
),
14 =>
array (
'role_id' => 1,
'permission_id' => 19,
),
15 =>
array (
'role_id' => 1,
'permission_id' => 20,
),
16 =>
array (
'role_id' => 1,
'permission_id' => 21,
),
17 =>
array (
'role_id' => 1,
'permission_id' => 22,
),
18 =>
array (
'role_id' => 1,
'permission_id' => 23,
),
19 =>
array (
'role_id' => 1,
'permission_id' => 24,
),
20 =>
array (
'role_id' => 1,
'permission_id' => 25,
),
21 =>
array (
'role_id' => 1,
'permission_id' => 26,
),
22 =>
array (
'role_id' => 1,
'permission_id' => 27,
),
23 =>
array (
'role_id' => 1,
'permission_id' => 28,
),
24 =>
array (
'role_id' => 1,
'permission_id' => 29,
),
25 =>
array (
'role_id' => 1,
'permission_id' => 30,
),
26 =>
array (
'role_id' => 1,
'permission_id' => 31,
),
27 =>
array (
'role_id' => 1,
'permission_id' => 32,
),
28 =>
array (
'role_id' => 1,
'permission_id' => 33,
),
29 =>
array (
'role_id' => 1,
'permission_id' => 34,
),
30 =>
array (
'role_id' => 1,
'permission_id' => 35,
),
31 =>
array (
'role_id' => 1,
'permission_id' => 36,
),
));
}
protected function createPermissions()
{
foreach (array (
0 =>
array (
'id' => 4,
'permission_name' => 'Dashboard',
'parent_id' => 0,
'module' => 'index',
'route' => 'dashboard',
'method' => 'get',
'permission_mark' => 'index:dashboard',
'type' => 1,
'sort' => 10000,
),
1 =>
array (
'id' => 5,
'permission_name' => '主题',
'parent_id' => 4,
'module' => '',
'route' => 'themes',
'method' => 'get',
'permission_mark' => 'index:theme',
'type' => 2,
'sort' => 0,
),
2 =>
array (
'id' => 6,
'permission_name' => '用户管理',
'parent_id' => 16,
'module' => 'user',
'route' => 'user',
'method' => 'get',
'permission_mark' => 'user:index',
'type' => 1,
'sort' => 9999,
),
3 =>
array (
'id' => 7,
'permission_name' => '创建',
'parent_id' => 6,
'module' => 'user',
'route' => 'user',
'method' => 'get',
'permission_mark' => 'user:create',
'type' => 2,
'sort' => 0,
),
4 =>
array (
'id' => 8,
'permission_name' => '保存',
'parent_id' => 6,
'module' => 'user',
'route' => 'user',
'method' => 'post',
'permission_mark' => 'user:save',
'type' => 2,
'sort' => 0,
),
5 =>
array (
'id' => 9,
'permission_name' => '查看',
'parent_id' => 6,
'module' => 'user',
'route' => 'user/<id>/edit',
'method' => 'get',
'permission_mark' => 'user:edit',
'type' => 2,
'sort' => 0,
),
6 =>
array (
'id' => 10,
'permission_name' => '编辑',
'parent_id' => 6,
'module' => 'user',
'route' => 'user/<id>',
'method' => 'put',
'permission_mark' => 'user:update',
'type' => 2,
'sort' => 0,
),
7 =>
array (
'id' => 11,
'permission_name' => '删除',
'parent_id' => 6,
'module' => 'user',
'route' => 'user/<id>',
'method' => 'delete',
'permission_mark' => 'user:delete',
'type' => 2,
'sort' => 0,
),
8 =>
array (
'id' => 12,
'permission_name' => '列表',
'parent_id' => 6,
'module' => 'user',
'route' => 'users',
'method' => 'get',
'permission_mark' => 'user:list',
'type' => 2,
'sort' => 0,
),
9 =>
array (
'id' => 13,
'permission_name' => '禁用/启用',
'parent_id' => 6,
'module' => 'user',
'route' => 'user/switch/status/<id>',
'method' => 'put',
'permission_mark' => 'user:switchStatus',
'type' => 2,
'sort' => 0,
),
10 =>
array (
'id' => 14,
'permission_name' => '恢复',
'parent_id' => 6,
'module' => 'user',
'route' => 'user/recover/<id>',
'method' => 'put',
'permission_mark' => 'user:recover',
'type' => 2,
'sort' => 0,
),
11 =>
array (
'id' => 15,
'permission_name' => '角色管理',
'parent_id' => 16,
'module' => 'permissions',
'route' => 'role',
'method' => 'get',
'permission_mark' => 'role:index',
'type' => 1,
'sort' => 1000,
),
12 =>
array (
'id' => 16,
'permission_name' => '权限管理',
'parent_id' => 0,
'module' => 'permissions',
'route' => '',
'method' => 'get',
'permission_mark' => '@:@',
'type' => 1,
'sort' => 1,
),
13 =>
array (
'id' => 17,
'permission_name' => '菜单管理',
'parent_id' => 16,
'module' => 'permissions',
'route' => 'permission',
'method' => 'get',
'permission_mark' => 'permission:index',
'type' => 1,
'sort' => 1,
),
14 =>
array (
'id' => 18,
'permission_name' => '创建',
'parent_id' => 15,
'module' => 'permissions',
'route' => 'role/create',
'method' => 'get',
'permission_mark' => 'role:create',
'type' => 2,
'sort' => 1,
),
15 =>
array (
'id' => 19,
'permission_name' => '保存',
'parent_id' => 15,
'module' => 'permissions',
'route' => 'role',
'method' => 'post',
'permission_mark' => 'role:save',
'type' => 2,
'sort' => 1,
),
16 =>
array (
'id' => 20,
'permission_name' => '查看',
'parent_id' => 15,
'module' => 'permissions',
'route' => 'role/<id>/edit',
'method' => 'get',
'permission_mark' => 'role:edit',
'type' => 2,
'sort' => 1,
),
17 =>
array (
'id' => 21,
'permission_name' => '更新',
'parent_id' => 15,
'module' => 'permissions',
'route' => 'role/<id>',
'method' => 'put',
'permission_mark' => 'role:update',
'type' => 2,
'sort' => 1,
),
18 =>
array (
'id' => 22,
'permission_name' => '删除',
'parent_id' => 15,
'module' => 'permissions',
'route' => 'role/<id>',
'method' => 'delete',
'permission_mark' => 'role:delete',
'type' => 2,
'sort' => 1,
),
19 =>
array (
'id' => 23,
'permission_name' => '列表',
'parent_id' => 15,
'module' => 'permissions',
'route' => 'roles',
'method' => 'get',
'permission_mark' => 'role:list',
'type' => 2,
'sort' => 1,
),
20 =>
array (
'id' => 24,
'permission_name' => '获取权限',
'parent_id' => 15,
'module' => 'permissions',
'route' => 'role/get/permissions',
'method' => 'get',
'permission_mark' => 'role:getPermissions',
'type' => 2,
'sort' => 1,
),
21 =>
array (
'id' => 25,
'permission_name' => '删除',
'parent_id' => 17,
'module' => 'permissions',
'route' => 'permission/<id>',
'method' => 'delete',
'permission_mark' => 'permissions:delete',
'type' => 2,
'sort' => 1,
),
22 =>
array (
'id' => 26,
'permission_name' => '更新',
'parent_id' => 17,
'module' => 'permissions',
'route' => 'permission/<id>',
'method' => 'put',
'permission_mark' => 'permission:update',
'type' => 2,
'sort' => 1,
),
23 =>
array (
'id' => 27,
'permission_name' => '查看',
'parent_id' => 17,
'module' => 'permissions',
'route' => 'permissions/<id>/edit',
'method' => 'get',
'permission_mark' => 'permission:edit',
'type' => 2,
'sort' => 1,
),
24 =>
array (
'id' => 28,
'permission_name' => '创建',
'parent_id' => 17,
'module' => 'permissions',
'route' => 'permission/create',
'method' => 'get',
'permission_mark' => 'permission:create',
'type' => 2,
'sort' => 1,
),
25 =>
array (
'id' => 29,
'permission_name' => '保存',
'parent_id' => 17,
'module' => 'permissions',
'route' => 'permission',
'method' => 'post',
'permission_mark' => 'permission',
'type' => 2,
'sort' => 1,
),
26 =>
array (
'id' => 30,
'permission_name' => '列表',
'parent_id' => 17,
'module' => 'permissions',
'route' => 'permissions',
'method' => 'get',
'permission_mark' => 'permission:list',
'type' => 2,
'sort' => 1,
),
27 =>
array (
'id' => 31,
'permission_name' => '系统管理',
'parent_id' => 0,
'module' => 'system',
'route' => '',
'method' => 'get',
'permission_mark' => 's:s',
'type' => 1,
'sort' => 1,
),
28 =>
array (
'id' => 32,
'permission_name' => '日志管理',
'parent_id' => 31,
'module' => 'system',
'route' => '',
'method' => 'get',
'permission_mark' => 'log:log',
'type' => 1,
'sort' => 1,
),
29 =>
array (
'id' => 33,
'permission_name' => '登录日志',
'parent_id' => 32,
'module' => 'system',
'route' => 'loginLog/index',
'method' => 'get',
'permission_mark' => 'operateLog:index',
'type' => 1,
'sort' => 1,
),
30 =>
array (
'id' => 34,
'permission_name' => '操作日志',
'parent_id' => 32,
'module' => 'index',
'route' => 'operateLog/index',
'method' => 'get',
'permission_mark' => 'loginLog:index',
'type' => 1,
'sort' => 1,
),
31 =>
array (
'id' => 35,
'permission_name' => '数据字典',
'parent_id' => 31,
'module' => 'system',
'route' => 'data/dictionary',
'method' => 'get',
'permission_mark' => 'datadictory:index',
'type' => 1,
'sort' => 1,
),
32 =>
array (
'id' => 36,
'permission_name' => '查看表',
'parent_id' => 35,
'module' => 'system',
'route' => 'table/view/<table>',
'method' => 'get',
'permission_mark' => 'datadictionary:view',
'type' => 2,
'sort' => 1,
),
) as $permission) {
\catchAdmin\permissions\model\Permissions::create($permission);
}
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace catchAdmin\permissions\model;
trait HasRolesTrait
{
/**
*
* @time 2019年12月08日
* @return mixed
*/
public function roles()
{
return $this->belongsToMany(Roles::class, 'user_has_roles', 'role_id', 'uid');
}
/**
*
* @time 2019年12月08日
* @return mixed
*/
public function getRoles()
{
return $this->roles()->select();
}
/**
*
* @time 2019年12月08日
* @param array $roles
* @return mixed
*/
public function attach(array $roles)
{
if (empty($roles)) {
return true;
}
sort($roles);
return $this->roles()->attach($roles);
}
/**
*
* @time 2019年12月08日
* @param array $roles
* @return mixed
*/
public function detach(array $roles = [])
{
if (empty($roles)) {
return $this->roles()->detach();
}
return $this->roles()->detach($roles);
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace catchAdmin\permissions\model;
use catcher\base\CatchModel;
class Permissions extends CatchModel
{
protected $name = 'permissions';
protected $field = [
'id', //
'permission_name', // 菜单名称
'parent_id', // 父级ID
'module', // 模块
'route', // 路由
'method', // 请求方法
'permission_mark', // 权限标识
'type', // 1 菜单 2 按钮
'sort', // 排序字段
'created_at', // 创建时间
'updated_at', // 更新时间
'deleted_at', // 删除状态null 未删除 timestamp 已删除
];
public const MENU_TYPE = 1;
public const BTN_TYPE = 2;
public const GET = 'get';
public const POST = 'post';
public const PUT = 'put';
public const DELETE = 'delete';
public function getList($search = [])
{
return $this->when($search['name'] ?? false, function ($query) use ($search){
$query->whereLike('name', $search['name']);
})
->when($search['id'] ?? false, function ($query) use ($search){
$query->where('parent_id', $search['id'])
->whereOr('id', $search['id']);
})
->when($search['permission_ids'] ?? false, function ($query) use ($search){
$query->whereIn('id', $search['permission_ids']);
})
->order('sort', 'desc')
->order('id', 'desc')
->select()
->toArray();
}
public function roles(): \think\model\relation\BelongsToMany
{
return $this->belongsToMany(Roles::class, 'role_has_permissions', 'role_id', 'permission_id');
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace catchAdmin\permissions\model;
use catchAdmin\user\model\Users;
use catcher\base\CatchModel;
class Roles extends CatchModel
{
protected $name = 'roles';
protected $field = [
'id', //
'role_name', // 角色名
'parent_id', // 父级ID
'description', // 角色备注
'created_at', // 创建时间
'updated_at', // 更新时间
'deleted_at', // 删除状态0未删除 >0 已删除
];
public function getList($search = [])
{
return $this->when($search['name'] ?? false, function ($query) use ($search){
$query->whereLike('name', $search['name']);
})
->when($search['id'] ?? false, function ($query) use ($search){
$query->where('parent_id', $search['id'])
->whereOr('id', $search['id']);
})
->order('id', 'desc')
->select()
->toArray();
}
/**
*
* @time 2019年12月08日
* @return \think\model\relation\BelongsToMany
*/
public function users(): \think\model\relation\BelongsToMany
{
return $this->belongsToMany(Users::class, 'user_has_roles', 'uid', 'role_id');
}
/**
*
* @time 2019年12月09日
* @return \think\model\relation\BelongsToMany
*/
public function permissions(): \think\model\relation\BelongsToMany
{
return $this->belongsToMany(Permissions::class, 'role_has_permissions', 'permission_id', 'role_id');
}
/**
*
* @time 2019年12月08日
* @return mixed
*/
public function getPermissions()
{
return $this->permissions()->select();
}
/**
*
* @time 2019年12月08日
* @param array $permissions
* @return mixed
* @throws \think\db\exception\DbException
*/
public function attach(array $permissions)
{
if (empty($permissions)) {
return true;
}
sort($permissions);
return $this->permissions()->attach($permissions);
}
/**
*
* @time 2019年12月08日
* @param array $roles
* @return mixed
*/
public function detach(array $roles = [])
{
if (empty($roles)) {
return $this->permissions()->detach();
}
return $this->permissions()->detach($roles);
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "权限管理",
"alias": "permissions",
"description": "",
"keywords": [],
"order": 2,
"services": [],
"aliases": {},
"files": [],
"requires": []
}

View File

@@ -0,0 +1,11 @@
<?php
// 角色
$router->resource('role', '\catchAdmin\permissions\controller\Role');
// 角色列表
$router->get('roles', '\catchAdmin\permissions\controller\Role@list');
$router->get('/role/get/permissions', '\catchAdmin\permissions\controller\Role@getPermissions');
// 权限
$router->resource('permission', '\catchAdmin\permissions\controller\Permission');
// 权限列表
$router->get('permissions', '\catchAdmin\permissions\controller\Permission@list');

View File

@@ -0,0 +1,22 @@
{$form|raw}
<script>
layui.use(['layer', 'form', 'admin', 'formX'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var admin = layui.admin;
var mUser = admin.getLayerData('#permission'); // 列表页面传递的数据,#modelUserForm这个只要写弹窗内任意一个元素的id即可
form.render();
// 回显数据
form.val('role', mUser);
// 表单提交事件
form.on('submit(submitPermission)', function (data) {
admin.req('{:url("permission")}', data.field, function (response) {
layer.msg(response.msg, {icon: 1});
admin.putLayerData('formOk', true, '#permission'); // 设置操作成功的标识,#modelUserForm这个只要写弹窗内任意一个元素的id即可
admin.closeDialog('#permission'); // 关闭页面层弹窗
}, 'post');
return false;
});
});
</script>

View File

@@ -0,0 +1,22 @@
{$form|raw}
<script>
layui.use(['layer', 'form', 'admin', 'formX'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var admin = layui.admin;
var mUser = admin.getLayerData('#permission'); // 列表页面传递的数据,#modelUserForm这个只要写弹窗内任意一个元素的id即可
form.render();
// 回显数据
form.val('permission', mUser);
// 表单提交事件
form.on('submit(submitPermission)', function (data) {
admin.req('permission/'+ "{$permission_id}", data.field, function (response) {
layer.msg(response.msg, {icon: 1});
admin.putLayerData('formOk', true, '#permission'); // 设置操作成功的标识,#modelUserForm这个只要写弹窗内任意一个元素的id即可
admin.closeDialog('#permission'); // 关闭页面层弹窗
}, 'put');
return false;
});
});
</script>

View File

@@ -0,0 +1,200 @@
{extend name="$layout"}
{block name="title"}角色管理{/block}
{block name="search"}
<div class="layui-form toolbar">
<div class="layui-form-item">
<!--<div class="layui-inline">
<div class="layui-input-inline mr0">
<input id="edtSearchAuth" class="layui-input" type="text" placeholder="输入角色名称"/>
</div>
</div>
<div class="layui-inline">
<button id="btnSearchAuth" class="layui-btn icon-btn"><i class="layui-icon">&#xe615;</i>搜索
</button>-->
<button id="btnAddAuth" class="layui-btn icon-btn"><i class="layui-icon">&#xe654;</i>添加</button>
<!--</div>-->
</div>
</div>
{/block}
{block name="table"}
<table class="layui-table" id="tablePermission" lay-filter="tablePermission"></table>
<!-- 表格操作列 -->
<script type="text/html" id="tableBarAuth">
{:editButton()}
{:addButton('新增子菜单')}
{:deleteButton()}
</script>
{/block}
{block name="script"}
<script>
layui.use(['layer', 'form', 'table', 'admin', 'util', 'treeTable'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var table = layui.table;
var util = layui.util;
var admin = layui.admin;
var treeTable = layui.treeTable;
var treeTb = treeTable.render({
tree: {
arrowType: 'arrow2',
iconIndex: 1, // 折叠图标显示在第几列
idName: 'id', // 自定义id字段的名称
childName: 'children', // 自定义标识是否还有子节点的字段名称
pidName: 'parent_id',
isPidData: true,
getIcon: function(d) { // 自定义图标
// d是当前行的数据
if (d.children.length) { // 判断是否有子集
return '<i class="ew-tree-icon ew-tree-icon-folder"></i>';
} else {
return '<i class="ew-tree-icon ew-tree-icon-file"></i>';
}
}
},
elem: '#tablePermission',
cellMinWidth: 100,
cols: [
{type: 'numbers', title: '#'},
{field: 'permission_name', title: '菜单名称', minWidth: 200},
{field: 'route', title: '菜单路由', templet: function (d) {
return escapeChars(d.route);
}
},
{field: 'method', title: '请求方法', width: 90},
{field: 'permission_mark', title: '权限标识'},
{field: 'type', title: '菜单类型', templet: function (d) {
return d.type == 1 ? '<button class="layui-btn layui-btn-primary layui-btn-xs">菜单</button>' :
'<button class="layui-btn layui-btn-primary layui-btn-xs">按钮</button>'
}, width: 90, align: 'center'
},
{field: 'sort', title: '排序', width:60, align: 'center'},
{
field: 'created_at', sort: true, templet: function (d) {
return util.toDateString(d.created_at);
}, title: '创建时间', maxWidth: 100
},
{templet: '#tableBarAuth', title: '操作', align: 'center', minWidth: 150}
],
reqData: function(data, callback) {
// 在这里写ajax请求通过callback方法回调数据
$.get('{:url("permissions")}', function (res) {
callback(res.data); // 参数是数组类型
});
}
});
// 添加按钮点击事件
$('#btnAddAuth').click(function () {
showEditModel();
});
function escapeChars(str) {
str = str.replace(/&/g, '&amp;');
str = str.replace(/</g, '&lt;');
str = str.replace(/>/g, '&gt;');
str = str.replace(/'/g, '&acute;');
str = str.replace(/"/g, '&quot;');
str = str.replace(/\|/g, '&brvbar;');
return str;
}
// 工具条点击事件
treeTable.on('tool(tablePermission)', function (obj) {
var data = obj.data;
var layEvent = obj.event;
if (layEvent === 'edit') { // 修改
showEditModel(data);
} else if (layEvent === 'del') { // 删除
doDel(obj);
} else {
showEditModel(data, true);
}
});
treeTable.on('row(tablePermission)', function(obj){
console.log(obj.value); //得到修改后的值
console.log(obj.field); //当前编辑的字段名
console.log(obj.data); //所在行的所有相关数据
});
// 删除
function doDel(obj) {
layer.confirm('确定要删除“' + obj.data.permission_name + '”吗?', {
skin: 'layui-layer-admin',
shade: .1
}, function (index) {
layer.close(index);
admin.req('/permission/'+ obj.data.id, {}, function (response) {
if (response.code === 10000) {
layer.msg(response.msg, {icon: 1});
obj.del()
} else {
layer.msg(response.msg, {icon: 2});
}
}, 'delete');
});
}
// 显示表单弹窗
// 显示表单弹窗
function showEditModel(permission, addPermission= false) {
var layIndex = admin.open({
title: addPermission ? '新增子菜单' : ((permission ? '修改' : '添加') + '菜单'),
url: addPermission ? '/permission/create' + '?id='+permission.id : (permission ? '/permission/'+permission.id + '/edit': '/permission/create'),
data: addPermission ? '' : permission, // 传递数据到表单页面
end: function () {
if (admin.getLayerData(layIndex, 'formOk')) { // 判断表单操作成功标识
if (addPermission) {
treeTb.reload();
setTimeout(function () {
treeTb.expand(permission.id)
}, 200)
} else {
if (permission) {
treeTb.reload();
setTimeout(function () {
treeTb.expand(permission.id)
}, 200)
} else {
treeTb.reload(); // 成功刷新表格
}
}
}
},
success: function (layero, dIndex) {
// 弹窗超出范围不出现滚动条
$(layero).children('.layui-layer-content').css('overflow', 'visible');
}
});
}
// 搜索按钮点击事件
$('#btnSearchAuth').click(function () {
$('#edtSearchAuth').removeClass('layui-form-danger');
var keyword = $('#edtSearchAuth').val();
var $tds = $('#tableAuth').next('.treeTable').find('.layui-table-body tbody tr td');
$tds.css('background-color', 'transparent');
if (!keyword) {
layer.tips('请输入关键字', '#edtSearchAuth', {tips: [1, '#ff4c4c']});
$('#edtSearchAuth').addClass('layui-form-danger');
$('#edtSearchAuth').focus();
return;
}
var searchCount = 0;
$tds.each(function () {
if ($(this).text().indexOf(keyword) >= 0) {
$(this).css('background-color', '#FAE6A0');
if (searchCount == 0) {
$('body,html').stop(true);
$('body,html').animate({scrollTop: $(this).offset().top - 150}, 500);
}
searchCount++;
}
});
if (searchCount == 0) {
layer.msg("没有匹配结果", {icon: 5, anim: 6});
} else {
treetable.expandAll('#tableAuth');
}
});
});
</script>
{/block}

View File

@@ -0,0 +1,35 @@
{$form|raw}
<script>
layui.use(['layer', 'form', 'admin', 'formX', 'authtree'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var admin = layui.admin;
var authtree = layui.authtree;
var mUser = admin.getLayerData('#role'); // 列表页面传递的数据,#modelUserForm这个只要写弹窗内任意一个元素的id即可
// 回显数据
form.val('role', mUser);
// 表单提交事件
form.on('submit(submitRole)', function (data) {
admin.req('{:url("role")}', data.field, function (response) {
layer.msg(response.msg, {icon: 1});
admin.putLayerData('formOk', true, '#role'); // 设置操作成功的标识,#modelUserForm这个只要写弹窗内任意一个元素的id即可
admin.closeDialog('#role'); // 关闭页面层弹窗
}, 'post');
return false;
});
admin.req('{:url("/role/get/permissions")}',{parent_id:"{$parent_id}"}, function (response) {
authtree.render('#permissions', response.data.permissions,{
inputname: 'permissionids[]',
layfilter: 'lay-check-auth',
autowidth: true,
nameKey: 'permission_name',
valueKey: 'id',
childKey: 'children',
collapseLeafNode: true,
theme: 'auth-skin-default',
});
})
});
</script>

View File

@@ -0,0 +1,36 @@
{$form|raw}
<script>
layui.use(['layer', 'form', 'admin', 'formX', 'authtree'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var admin = layui.admin;
var mUser = admin.getLayerData('#role'); // 列表页面传递的数据,#modelUserForm这个只要写弹窗内任意一个元素的id即可
var authtree = layui.authtree;
// 回显数据
form.val('role', mUser);
admin.req('{:url("/role/get/permissions")}',{role_id:"{$role_id}", parent_id:"{$parent_id}"}, function (response) {
authtree.render('#permissions', response.data.permissions,{
inputname: 'permissionids[]',
layfilter: 'lay-check-auth',
autowidth: true,
nameKey: 'permission_name',
valueKey: 'id',
childKey: 'children',
collapseLeafNode: true,
theme: 'auth-skin-default',
checkedKey: response.data.hasPermissions
});
})
// 表单提交事件
form.on('submit(submitRole)', function (data) {
admin.req('role/'+ "{$role_id}", data.field, function (response) {
layer.msg(response.msg, {icon: 1});
admin.putLayerData('formOk', true, '#role'); // 设置操作成功的标识,#modelUserForm这个只要写弹窗内任意一个元素的id即可
admin.closeDialog('#role'); // 关闭页面层弹窗
}, 'put');
return false;
});
});
</script>

View File

@@ -0,0 +1,178 @@
{extend name="$layout"}
{block name="title"}角色管理{/block}
{block name="search"}
<div class="layui-form toolbar">
<div class="layui-form-item">
<!--<div class="layui-inline">
<div class="layui-input-inline mr0">
<input id="edtSearchAuth" class="layui-input" type="text" placeholder="输入角色名称"/>
</div>
</div>
<div class="layui-inline">
<button id="btnSearchAuth" class="layui-btn icon-btn"><i class="layui-icon">&#xe615;</i>搜索
</button>-->
<button id="btnAddAuth" class="layui-btn icon-btn"><i class="layui-icon">&#xe654;</i>添加</button>
<!--</div>-->
</div>
</div>
{/block}
{block name="table"}
<table class="layui-table" id="tableRole"></table>
<!-- 表格操作列 -->
<script type="text/html" id="tableBarAuth">
{:editButton()}
{:addButton('新增子角色')}
{:deleteButton()}
</script>
{/block}
{block name="script"}
<script>
layui.use(['layer', 'form', 'table', 'admin', 'util', 'treeTable'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var table = layui.table;
var util = layui.util;
var admin = layui.admin;
var treeTable = layui.treeTable;
var treeTb = treeTable.render({
tree: {
arrowType: 'arrow2',
iconIndex: 1, // 折叠图标显示在第几列
idName: 'id', // 自定义id字段的名称
childName: 'children', // 自定义标识是否还有子节点的字段名称
pidName: 'parent_id',
isPidData: true,
getIcon: function(d) { // 自定义图标
// d是当前行的数据
if (d.children.length) { // 判断是否有子集
return '<i class="ew-tree-icon ew-tree-icon-folder"></i>';
} else {
return '<i class="ew-tree-icon ew-tree-icon-file"></i>';
}
}
},
elem: '#tableRole',
cellMinWidth: 100,
cols: [
{type: 'numbers', title: '#'},
{field: 'role_name', title: '角色名称', minWidth: 100},
{field: 'description', title: '角色描述'},
{
field: 'created_at', sort: true, templet: function (d) {
return util.toDateString(d.created_at);
}, title: '创建时间', maxWidth: 100
},
{templet: '#tableBarAuth', title: '操作', align: 'center', minWidth: 120}
],
reqData: function(data, callback) {
// 在这里写ajax请求通过callback方法回调数据
$.get('{:url("roles")}', function (res) {
callback(res.data); // 参数是数组类型
});
}
});
// 添加按钮点击事件
$('#btnAddAuth').click(function () {
showEditModel();
});
// 工具条点击事件
treeTable.on('tool(tableRole)', function (obj) {
var data = obj.data;
var layEvent = obj.event;
if (layEvent === 'edit') { // 修改
showEditModel(data);
} else if (layEvent === 'del') { // 删除
doDel(obj);
} else {
showEditModel(data, true);
}
});
// 删除
function doDel(obj) {
layer.confirm('确定要删除“' + obj.data.role_name + '”吗?', {
skin: 'layui-layer-admin',
shade: .1
}, function (index) {
layer.close(index);
admin.req('/role/'+ obj.data.id, {}, function (response) {
if (response.code === 10000) {
layer.msg(response.msg, {icon: 1});
obj.del()
} else {
layer.msg(response.msg, {icon: 2});
}
}, 'delete');
});
}
// 显示表单弹窗
// 显示表单弹窗
function showEditModel(mRole, addRole = false) {
var layIndex = admin.open({
title: addRole ? '新增子角色' : ((mRole ? '修改' : '添加') + '角色'),
url: addRole ? '/role/create' + '?id='+mRole.id : (mRole ? '/role/'+mRole.id + '/edit': '/role/create'),
data: addRole ? '' : mRole, // 传递数据到表单页面
area: '700px',
end: function () {
if (admin.getLayerData(layIndex, 'formOk')) { // 判断表单操作成功标识
if (addRole) {
treeTb.reload();
setTimeout(function () {
treeTb.expand(mRole.id)
}, 200)
} else {
if (mRole) {
treeTb.reload();
setTimeout(function () {
treeTb.expand(mRole.id)
}, 200)
} else {
treeTb.reload(); // 成功刷新表格
}
}
}
},
success: function (layero, dIndex) {
// 弹窗超出范围不出现滚动条
$(layero).children('.layui-layer-content').css('overflow', 'visible');
}
});
}
// 搜索按钮点击事件
$('#btnSearchAuth').click(function () {
$('#edtSearchAuth').removeClass('layui-form-danger');
var keyword = $('#edtSearchAuth').val();
var $tds = $('#tableAuth').next('.treeTable').find('.layui-table-body tbody tr td');
$tds.css('background-color', 'transparent');
if (!keyword) {
layer.tips('请输入关键字', '#edtSearchAuth', {tips: [1, '#ff4c4c']});
$('#edtSearchAuth').addClass('layui-form-danger');
$('#edtSearchAuth').focus();
return;
}
var searchCount = 0;
$tds.each(function () {
if ($(this).text().indexOf(keyword) >= 0) {
$(this).css('background-color', '#FAE6A0');
if (searchCount == 0) {
$('body,html').stop(true);
$('body,html').animate({scrollTop: $(this).offset().top - 150}, 500);
}
searchCount++;
}
});
if (searchCount == 0) {
layer.msg("没有匹配结果", {icon: 5, anim: 6});
} else {
treetable.expandAll('#tableAuth');
}
});
});
</script>
{/block}

View File

@@ -0,0 +1,84 @@
<?php
namespace catchAdmin\system\controller;
use app\Request;
use catcher\base\CatchController;
use catcher\CatchResponse;
use catcher\exceptions\FailedException;
use think\facade\Console;
use think\facade\Db;
use think\Paginator;
class DataDictionary extends CatchController
{
/**
*
* @time 2019年12月13日
* @throws \Exception
* @return string
*/
public function index(): string
{
return $this->fetch();
}
/**
*
* @time 2019年12月13日
* @param Request $request
* @return \think\response\Json
*/
public function tables(Request $request): \think\response\Json
{
$tables = Db::query('show table status');
return CatchResponse::paginate(Paginator::make($tables, $request->get('limit') ?? 10, $request->get('page'), count($tables), false, []));
}
/**
*
* @time 2019年12月13日
* @param $table
* @throws \Exception
* @return string
*/
public function view($table): string
{
$this->table = Db::query('show full columns from ' . $table);
return $this->fetch();
}
/**
*
* @time 2019年12月13日
* @return \think\response\Json
*/
public function optimize(): \think\response\Json
{
$tables = \request()->post('data');
foreach ($tables as $table) {
Db::query(sprintf('optimize table %s', $table));
}
return CatchResponse::success([], '优化成功');
}
/**
*
* @time 2019年12月13日
* @throws FailedException
* @return \think\response\Json
*/
public function backup(): \think\response\Json
{
try {
Console::call('backup:data', [trim(implode(',', \request()->post('data')), ',')]);
}catch (\Exception $e) {
throw new FailedException($e->getMessage());
}
return CatchResponse::success([], '备份成功');
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace catchAdmin\system\controller;
use catcher\base\CatchController;
use catcher\CatchResponse;
use think\facade\Db;
class LoginLog extends CatchController
{
public function index()
{
return $this->fetch();
}
public function list()
{
return CatchResponse::paginate(Db::name('login_log')->paginate(10));
}
public function empty()
{
return CatchResponse::success(Db::name('login_log')->delete(true), '清空成功');
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace catchAdmin\system\controller;
use catcher\base\CatchController;
use catcher\CatchResponse;
use think\facade\Db;
class OperateLog extends CatchController
{
public function index()
{
return $this->fetch();
}
public function list()
{
return CatchResponse::paginate(
Db::name('operate_log')
->field(['operate_log.*', 'users.username as creator'])
->join('users','users.id = operate_log.creator_id')
->order('id', 'desc')
->paginate(10)
);
}
public function empty()
{
return CatchResponse::success(Db::name('operate_log')->delete(true), '清空成功');
}
}

View File

@@ -0,0 +1,40 @@
<?php
use think\migration\Migrator;
use think\migration\db\Column;
class LoginLog extends Migrator
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
*
* The following commands can be used in this method and Phinx will
* automatically reverse them when rolling back:
*
* createTable
* renameTable
* addColumn
* renameColumn
* addIndex
* addForeignKey
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change()
{
$table = $this->table('login_log',['engine'=>'Myisam', 'comment' => '登录日志', 'signed' => false]);
$table->addColumn('login_name', 'string',['limit' => 50,'default'=>'','comment'=>'用户名'])
->addColumn('login_ip', 'string',['default'=>0, 'limit' => 20, 'comment'=>'登录地点ip', 'signed' => false])
->addColumn('browser', 'string',['default'=> '','comment'=>'浏览器'])
->addColumn('os', 'string',['default'=> '','comment'=>'操作系统'])
->addColumn('login_at', 'integer', array('default'=>0,'comment'=>'登录时间', 'signed' => false ))
->addColumn('status', 'integer',['limit' => \Phinx\Db\Adapter\MysqlAdapter::INT_TINY,'default'=> 1,'comment'=>'1 成功 2 失败'])
->create();
}
}

View File

@@ -0,0 +1,42 @@
<?php
use think\migration\Migrator;
use think\migration\db\Column;
class OperateLog extends Migrator
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
*
* The following commands can be used in this method and Phinx will
* automatically reverse them when rolling back:
*
* createTable
* renameTable
* addColumn
* renameColumn
* addIndex
* addForeignKey
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change()
{
$table = $this->table('operate_log',['engine'=>'Myisam', 'comment' => '操作日志', 'signed' => false]);
$table->addColumn('module', 'string',['limit' => 50,'default'=>'','comment'=>'模块名称'])
->addColumn('operate', 'string',['default'=> '', 'limit' => 100, 'comment'=>'操作模块'])
->addColumn('route', 'string',['default'=> '','limit' => 20, 'comment'=>'路由'])
->addColumn('params', 'string',['default'=> '','limit' => 1000, 'comment'=>'参数'])
->addColumn('ip', 'string',['default'=>'', 'limit' => 20,'comment'=>'ip', 'signed' => false])
->addColumn('creator_id', 'integer',['default'=> 0,'comment'=>'创建人ID', 'signed' => false])
->addColumn('method', 'string',['default'=> '','comment'=>'请求方法'])
->addColumn('created_at', 'integer', array('default'=>0,'comment'=>'登录时间', 'signed' => false ))
->create();
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace catchAdmin\system\event;
class LoginLogEvent
{
protected $params;
public function __construct(array $params)
{
$this->params = $params;
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace catchAdmin\system\event;
class OperateLogEvent
{
protected $params;
public function __construct(array $params)
{
$this->params = $params;
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "系统管理",
"alias": "system",
"description": "",
"keywords": [],
"order": 2,
"services": [],
"aliases": {},
"files": [],
"requires": []
}

View File

@@ -0,0 +1,18 @@
<?php
// 登录日志
$router->get('log/login', '\catchAdmin\system\controller\LoginLog@list');
$router->get('loginLog/index', '\catchAdmin\system\controller\LoginLog@index');
$router->delete('loginLog/empty', '\catchAdmin\system\controller\LoginLog@empty');
// 操作日志
$router->get('log/operate', '\catchAdmin\system\controller\OperateLog@list');
$router->get('operateLog/index', '\catchAdmin\system\controller\OperateLog@index');
$router->delete('operateLog/empty', '\catchAdmin\system\controller\OperateLog@empty');
// 数据字典
$router->get('data/dictionary', '\catchAdmin\system\controller\DataDictionary@index');
$router->get('tables', '\catchAdmin\system\controller\DataDictionary@tables');
$router->get('table/view/<table>', '\catchAdmin\system\controller\DataDictionary@view');
$router->post('table/optimize', '\catchAdmin\system\controller\DataDictionary@optimize');
$router->post('table/backup', '\catchAdmin\system\controller\DataDictionary@backup');

View File

@@ -0,0 +1,113 @@
{extend name="$layout"}
{block name="title"}登录日志{/block}
{block name="search"}
<div class="layui-form toolbar">
<div class="layui-form-item">
<!--<div class="layui-inline">
<div class="layui-input-inline mr0">
<input id="edtSearchAuth" class="layui-input" type="text" placeholder="输入角色名称"/>
</div>
</div>
<div class="layui-inline">
<button id="btnSearchAuth" class="layui-btn icon-btn"><i class="layui-icon">&#xe615;</i>搜索
</button>-->
<button id="optimize" class="layui-btn icon-btn"><i class="layui-icon">&#xe631;</i>优化</button>
<button id="backup" class="layui-btn layui-btn-normal icon-btn"><i class="layui-icon">&#xe620;</i>备份</button>
<!--</div>-->
</div>
</div>
{/block}
{block name="table"}
<table class="layui-table" id="database" lay-filter="database"></table>
<!-- 表格操作列 -->cl
<script type="text/html" id="operate">
{:editButton('查看', 'view')}
</script>
{/block}
{block name="script"}
<script>
layui.use(['layer', 'table', 'util', 'admin'], function () {
var $ = layui.jquery;
var table = layui.table;
var admin = layui.admin;
table.render({
elem: '#database',
url: '{:url("tables")}',
page: true,
response: {
statusCode: 10000,
},
// toolbar: true,
cellMinWidth: 100,
cols: [[
{type: 'checkbox'},
{field: 'Name', title: '表名'},
{field: 'Engine', title: '表引擎'},
{field: 'Collation', title: '数据集'},
{field: 'Rows',title: '数据行数'},
{field: 'Index_length', title: '索引大小', templet: function (d) {
if (d.Index_length < (1024 * 1024)) {
return Math.round(d.Index_length / 1024) + 'KB'
} else {
return Math.round(d.Index_length/(1024 * 1024)) + 'MB'
}
}
},
{field: 'Data_length', title: '数据大小', templet: function (d) {
if (d.Data_length < (1024 * 1024)) {
return Math.round(d.Data_length / 1024) + 'KB'
} else {
return Math.round(d.Data_length/(1024 * 1024)) + 'MB'
}
}
},
{field: 'Create_time', title: '创建时间'},
{field: 'Comment', title: '表注释'},
{align: 'center', toolbar: '#operate', title: '操作'}
]],
});
// 工具条点击事件
table.on('tool(database)', function (obj) {
if (obj.event === 'view') { // 查看
admin.open({
title: '查看表结构',
url: '/table/view/'+obj.data.Name,
area: '900px',
});
}
});
$('#optimize').click(function () {
var checkRows = table.checkStatus('database');
if (checkRows.data.length == 0) {
layer.msg('选择需要优化的表', {icon: 2});
} else {
var name = [];
checkRows.data.map(function(item,index){
name.push(item.Name)
});
admin.req("{:url('table/optimize')}", {data:name},function (response) {
layer.msg(response.msg, {icon: 1})
}, 'post')
}
})
$('#backup').click(function () {
var checkRows = table.checkStatus('database');
if (checkRows.data.length == 0) {
layer.msg('选择需要备份的表', {icon: 2});
} else {
var name = [];
checkRows.data.map(function(item,index){
name.push(item.Name)
});
admin.req("{:url('table/backup')}", {data:name},function (response) {
layer.msg(response.msg, {icon: response.code === 10000 ? 1 : 2})
}, 'post')
}
})
});
</script>
{/block}

View File

@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div style="padding: 10px">
<table class="layui-table">
<thead>
<tr>
<th>字段</th>
<th>类型</th>
<th>字符集</th>
<th>Null</th>
<th>索引</th>
<th>默认值</th>
<th>权限</th>
<th>注释</th>
</tr>
</thead>
<tbody>
{foreach $table as $field}
<tr>
<td>{$field['Field']}</td>
<td>{$field['Type']}</td>
<td>{$field['Collation']}</td>
<td>{$field['Null']}</td>
<td>{$field['Key']}</td>
<td>{$field['Default']}</td>
<td>{$field['Privileges']}</td>
<td>{$field['Comment']}</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
</body>
</html>

View File

@@ -0,0 +1,68 @@
{extend name="$layout"}
{block name="title"}登录日志{/block}
{block name="search"}
<div class="layui-form toolbar">
<div class="layui-form-item">
<div class="layui-inline">
<div class="layui-input-inline mr0">
<input id="edtSearchAuth" class="layui-input" type="text" placeholder="输入角色名称"/>
</div>
</div>
<div class="layui-inline">
<button id="btnSearchAuth" class="layui-btn icon-btn"><i class="layui-icon">&#xe615;</i>搜索
</button>
<button id="empty" class="layui-btn icon-btn"><i class="layui-icon">&#xe640;</i>清空</button>
</div>
</div>
</div>
{/block}
{block name="table"}
<table class="layui-table" id="log"></table>
{/block}
{block name="script"}
<script>
layui.use(['layer', 'form', 'table', 'util', 'admin'], function () {
var $ = layui.jquery;
var table = layui.table;
var util = layui.util;
var admin = layui.admin;
var t = table.render({
elem: '#log',
url: '{:url("log/login")}',
page: true,
response: {
statusCode: 10000,
},
// toolbar: true,
cellMinWidth: 100,
cols: [[
{type: 'id', title: '序号', field: 'id'},
{field: 'login_name', title: '登录名'},
{field: 'login_ip', title: '登录IP'},
{field: 'browser', title: '浏览器'},
{field: 'os', title: '操作系统'},
{field: 'status', title: '状态', templet: function (d) {
return d.status === 1 ?
'<button class="layui-btn layui-btn-xs">成功</button>' :
'<button class="layui-btn layui-btn-xs layui-btn-danger">失败</button>'
}
},
{
field: 'login_at', templet: function (d) {
return util.toDateString(d.login_at);
}, title: '登录时间'
},
]],
});
$('#empty').click(function () {
layer.confirm('确定清空日志吗?', function () {
admin.req("{:url('loginLog/empty')}", {}, function (response) {
layer.msg(response.msg, {icon:1});
t.reload();
}, 'delete')
})
})
});
</script>
{/block}

View File

@@ -0,0 +1,63 @@
{extend name="$layout"}
{block name="title"}操作日志{/block}
{block name="search"}
<div class="layui-form toolbar">
<div class="layui-form-item">
<!--<div class="layui-inline">
<div class="layui-input-inline mr0">
<input id="edtSearchAuth" class="layui-input" type="text" placeholder="输入角色名称"/>
</div>
</div>
<div class="layui-inline">
<button id="btnSearchAuth" class="layui-btn icon-btn"><i class="layui-icon">&#xe615;</i>搜索
</button>-->
<button id="empty" class="layui-btn icon-btn"><i class="layui-icon">&#xe640;</i>清空</button>
<!--</div>-->
</div>
</div>
{/block}
{block name="table"}
<table class="layui-table" id="log"></table>
{/block}
{block name="script"}
<script>
layui.use(['layer', 'form', 'table', 'util', 'admin'], function () {
var $ = layui.jquery;
var table = layui.table;
var util = layui.util;
var admin = layui.admin;
var t = table.render({
elem: '#log',
url: '{:url("log/operate")}',
page: true,
response: {
statusCode: 10000,
},
// toolbar: true,
cellMinWidth: 100,
cols: [[
{type: 'id', title: '序号', field: 'id'},
{field: 'module', title: '模块'},
{field: 'ip', title: 'IP'},
{field: 'operate', title: 'operate'},
{field: 'creator', title: '操作者'},
{field: 'method', title: '请求方法'},
{
field: 'created_at', sort: true, templet: function (d) {
return util.toDateString(d.created_at);
}, title: '创建时间'
},
]],
});
$('#empty').click(function () {
layer.confirm('确定清空日志吗?', function () {
admin.req("{:url('operateLog/empty')}", {}, function (response) {
layer.msg(response.msg, {icon:1});
t.reload();
}, 'delete')
})
})
});
</script>
{/block}

Some files were not shown because too many files have changed in this diff Show More