This commit is contained in:
mkm 2023-06-06 10:47:49 +08:00
commit 8957cf818d
1047 changed files with 345035 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
/.idea
/.vscode
/vendor
*.log
.env
/tests/tmp
/tests/.phpunit.result.cache

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 walkor<walkor@workerman.net> and contributors (see https://github.com/walkor/webman/contributors)
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.

20
README.md Normal file
View File

@ -0,0 +1,20 @@
# webman
High performance HTTP Service Framework for PHP based on [Workerman](https://github.com/walkor/workerman).
# Manual (文档)
https://www.workerman.net/doc/webman
# Home page (主页)
https://www.workerman.net/webman
# Benchmarks (压测)
https://www.techempower.com/benchmarks/#section=test&runid=9716e3cd-9e53-433c-b6c5-d2c48c9593c1&hw=ph&test=db&l=zg24n3-1r&a=2
![image](https://user-images.githubusercontent.com/6073368/96447814-120fc980-1245-11eb-938d-6ea408716c72.png)
## LICENSE
MIT

View File

@ -0,0 +1,59 @@
<?php
namespace app\api\controller;
use support\Request;
use plugin\admin\app\model\Admin;
use plugin\admin\app\controller\Base;
use plugin\admin\app\common\Util;
class IndexController extends Base
{
public function login(Request $request)
{
$username = $request->post('username', '');
$password = $request->post('password', '');
if (!$username) {
return $this->json(1, '用户名不能为空');
}
$admin = Admin::where('username', $username)->first();
if (!$admin || !Util::passwordVerify($password, $admin->password)) {
return $this->json(1, '账户不存在或密码错误');
}
if ($admin->status != 0) {
return $this->json(1, '当前账户暂时无法登录');
}
$admin->login_at = date('Y-m-d H:i:s');
$admin->save();
$admin = $admin->toArray();
$session = $request->session();
unset($admin['password']);
$session->set('admin', $admin);
return $this->json(200, '登录成功', [
"userinfo" => [
"id" => 1,
"username" => $admin['username'],
"nickname" => $admin['nickname'],
"avatar" => $admin['avatar'],
"password" => ''
],
'token' => [
"tokenName" => 'satoken',
"tokenValue" => $request->sessionId(),
"isLogin" => "true",
"loginId" => "1",
"loginType" => "login",
"tokenTimeout" => 2592000,
"sessionTimeout" => 2592000,
"tokenSessionTimeout" => 2591893,
"tokenActivityTimeout" => -1,
"loginDevice" => "default-device",
"tag" => null
]
]);
}
public function getOssInfo(Request $request){
return $this->json(200,'ok',['bucketURL'=>'http://127.0.0.1:8787']);
}
}

View File

@ -0,0 +1,109 @@
<?php
namespace app\api\controller;
use Illuminate\Support\Env;
use support\Request;
use plugin\admin\app\model\Admin;
use plugin\admin\app\controller\Base;
use support\exception\BusinessException;
use plugin\admin\app\common\Util;
use plugin\admin\app\model\Project as ProjectModel;
use plugin\admin\app\model\Projectdata as ProjectdataModel;
use think\facade\Db;
class Project extends Base
{
public function list(Request $request){
$select=ProjectModel::paginate();
return json(['code'=>200,'msg'=>'获取成功','count'=>$select->total(),'data'=>$select->items()]);
}
public function edit(Request $request){
Db::name('Projects')->where('id', $request->post('id'))->update(['indexImage'=>$request->post('indexImage')]);
return json(['code'=>200,'msg'=>'更新成功']);
}
public function create(Request $request){
$data = $request->post();
$id= ProjectModel::insertGetId($data);
$res=ProjectModel::where('id',$id)->first();
$res['CreateTime']=$res['created_at'];
$res['CreateUserId']=1;
$res['id']=$res['Id'];
return $this->json(200,'ok',$res->toArray());
}
public function publish(Request $request){
$data = $request->post();
$find=ProjectModel::find($data['id']);
$find['state']=$data['state'];
$find->save();
if($find){
return $this->json(200,'操作成功');
}else{
return $this->json(500,'操作失败');
}
}
public function getData(Request $request){
$data = $request->get();
$find=ProjectModel::find($data['projectId']);
$ProjectdataModel=ProjectdataModel::where('projectId',$data['projectId'])->first();
if($ProjectdataModel){
$find['content']=$ProjectdataModel['content'];
}else{
$find['content']='';
}
return $this->json(200,'ok',$find->toArray());
}
public function data(Request $request){
$data = $request->post();
$find=Db::name('projectdatas')->where('projectId', $data['projectId'])->find();
if($find){
Db::name('projectdatas')->where('projectId',$data['projectId'])->update(['content'=>$data['content']]);
}else{
$id=Db::name('projectdatas')->insertGetId(['projectId'=>$data['projectId'],'content'=>$data['content']]);
$find=Db::name('projectdatas')->where('id', $id)->find();
}
return $this->json(200,'ok',$find);
}
public function upload(Request $request){
$file = current($request->file());
if (!$file || !$file->isValid()) {
return $this->json(1, '未找到文件');
}
$relative_dir='/upload/img/'.date('Ymd');
$relative_dir = ltrim($relative_dir, '/');
$file = current($request->file());
if (!$file || !$file->isValid()) {
throw new BusinessException('未找到上传文件', 400);
}
$base_dir = base_path() . '/plugin/admin/public/';
$full_dir = $base_dir . $relative_dir;
if (!is_dir($full_dir)) {
mkdir($full_dir, 0777, true);
}
$ext = strtolower($file->getUploadExtension());
$ext_forbidden_map = ['php', 'php3', 'php5', 'css', 'js', 'html', 'htm', 'asp', 'jsp'];
if (in_array($ext, $ext_forbidden_map)) {
throw new BusinessException('不支持该格式的文件上传', 400);
}
$relative_path = $relative_dir . '/' . bin2hex(pack('Nn',time(), random_int(1, 65535))) . ".$ext";
$full_path = $base_dir . $relative_path;
var_dump($full_path);
$file_size = $file->getSize();
$file_name = $file->getUploadName();
$file->move($full_path);
return $this->json(200, '上传成功', [
'fileName' => "/app/admin/$relative_path",
'name' => $file_name,
'size' => $file_size,
]);
}
}

View File

@ -0,0 +1,42 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace app\api\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
/**
* Class StaticFile
* @package app\middleware
*/
class StaticFile implements MiddlewareInterface
{
public function process(Request $request, callable $next): Response
{
// Access to files beginning with. Is prohibited
if (strpos($request->path(), '/.') !== false) {
return response('<h1>403 forbidden</h1>', 403);
}
/** @var Response $response */
$response = $next($request);
// Add cross domain HTTP header
/*$response->withHeaders([
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Credentials' => 'true',
]);*/
return $response;
}
}

29
app/api/model/Test.php Normal file
View File

@ -0,0 +1,29 @@
<?php
namespace app\api\model;
use support\Model;
class Test extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'test';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = false;
}

View File

@ -0,0 +1,14 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="/favicon.ico"/>
<title>webman</title>
</head>
<body>
hello <?=htmlspecialchars($name)?>
</body>
</html>

4
app/functions.php Normal file
View File

@ -0,0 +1,4 @@
<?php
/**
* Here is your custom functions.
*/

58
composer.json Normal file
View File

@ -0,0 +1,58 @@
{
"name": "workerman/webman",
"type": "project",
"keywords": [
"high performance",
"http service"
],
"homepage": "https://www.workerman.net",
"license": "MIT",
"description": "High performance HTTP Service Framework.",
"authors": [
{
"name": "walkor",
"email": "walkor@workerman.net",
"homepage": "https://www.workerman.net",
"role": "Developer"
}
],
"support": {
"email": "walkor@workerman.net",
"issues": "https://github.com/walkor/webman/issues",
"forum": "https://wenda.workerman.net/",
"wiki": "https://workerman.net/doc/webman",
"source": "https://github.com/walkor/webman"
},
"require": {
"php": ">=7.2",
"workerman/webman-framework": "^1.5.0",
"monolog/monolog": "^2.0",
"webman/admin": "^0.6.13",
"webman/think-orm": "^1.1"
},
"suggest": {
"ext-event": "For better performance. "
},
"autoload": {
"psr-4": {
"": "./",
"app\\": "./app",
"App\\": "./app",
"app\\View\\Components\\": "./app/view/components"
},
"files": [
"./support/helpers.php"
]
},
"scripts": {
"post-package-install": [
"support\\Plugin::install"
],
"post-package-update": [
"support\\Plugin::install"
],
"pre-package-uninstall": [
"support\\Plugin::uninstall"
]
}
}

3135
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

26
config/app.php Normal file
View File

@ -0,0 +1,26 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use support\Request;
return [
'debug' => true,
'error_reporting' => E_ALL,
'default_timezone' => 'Asia/Shanghai',
'request_class' => Request::class,
'public_path' => base_path() . DIRECTORY_SEPARATOR . 'public',
'runtime_path' => base_path(false) . DIRECTORY_SEPARATOR . 'runtime',
'controller_suffix' => 'Controller',
'controller_reuse' => false,
];

21
config/autoload.php Normal file
View File

@ -0,0 +1,21 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
'files' => [
base_path() . '/app/functions.php',
base_path() . '/support/Request.php',
base_path() . '/support/Response.php',
]
];

19
config/bootstrap.php Normal file
View File

@ -0,0 +1,19 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
support\bootstrap\Session::class,
support\bootstrap\LaravelDb::class,
Webman\ThinkOrm\ThinkOrm::class,
];

15
config/container.php Normal file
View File

@ -0,0 +1,15 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return new Webman\Container;

15
config/database.php Normal file
View File

@ -0,0 +1,15 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [];

15
config/dependence.php Normal file
View File

@ -0,0 +1,15 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [];

5
config/event.php Normal file
View File

@ -0,0 +1,5 @@
<?php
return [
];

17
config/exception.php Normal file
View File

@ -0,0 +1,17 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
'' => support\exception\Handler::class,
];

32
config/log.php Normal file
View File

@ -0,0 +1,32 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
'default' => [
'handlers' => [
[
'class' => Monolog\Handler\RotatingFileHandler::class,
'constructor' => [
runtime_path() . '/logs/webman.log',
7, //$maxFiles
Monolog\Logger::DEBUG,
],
'formatter' => [
'class' => Monolog\Formatter\LineFormatter::class,
'constructor' => [null, 'Y-m-d H:i:s', true],
],
]
],
],
];

15
config/middleware.php Normal file
View File

@ -0,0 +1,15 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [];

View File

@ -0,0 +1,4 @@
<?php
return [
'enable' => true,
];

View File

@ -0,0 +1,17 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
Webman\Event\BootStrap::class,
];

View File

@ -0,0 +1,7 @@
<?php
use Webman\Event\EventListCommand;
return [
EventListCommand::class
];

42
config/process.php Normal file
View File

@ -0,0 +1,42 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use Workerman\Worker;
return [
// File update detection and automatic reload
'monitor' => [
'handler' => process\Monitor::class,
'reloadable' => false,
'constructor' => [
// Monitor these directories
'monitorDir' => array_merge([
app_path(),
config_path(),
base_path() . '/process',
base_path() . '/support',
base_path() . '/resource',
base_path() . '/.env',
], glob(base_path() . '/plugin/*/app'), glob(base_path() . '/plugin/*/config'), glob(base_path() . '/plugin/*/api')),
// Files with these suffixes will be monitored
'monitorExtensions' => [
'php', 'html', 'htm', 'env'
],
'options' => [
'enable_file_monitor' => !Worker::$daemonize && DIRECTORY_SEPARATOR === '/',
'enable_memory_monitor' => DIRECTORY_SEPARATOR === '/',
]
]
]
];

22
config/redis.php Normal file
View File

@ -0,0 +1,22 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
'default' => [
'host' => '127.0.0.1',
'password' => null,
'port' => 6379,
'database' => 0,
],
];

37
config/route.php Normal file
View File

@ -0,0 +1,37 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use Webman\Route;
Route::group('/api', function () {
Route::group('/goview', function () {
Route::group('/sys', function () {
Route::post('/login',[app\api\controller\IndexController::class,'login']);
Route::get('/getOssInfo',[app\api\controller\IndexController::class,'getOssInfo']);
});
Route::group('/project', function () {
Route::post('/create',[app\api\controller\Project::class,'create']);
Route::post('/edit',[app\api\controller\Project::class,'edit']);
Route::post('/upload',[app\api\controller\Project::class,'upload']);
Route::post('/save/data',[app\api\controller\Project::class,'data']);
Route::put('/publish',[app\api\controller\Project::class,'publish']);
Route::get('/list',[app\api\controller\Project::class,'list']);
Route::get('/getData',[app\api\controller\Project::class,'getData']);
});
});
});

31
config/server.php Normal file
View File

@ -0,0 +1,31 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
'listen' => 'http://0.0.0.0:8787',
'transport' => 'tcp',
'context' => [],
'name' => 'webman',
'count' => cpu_count() * 4,
'user' => '',
'group' => '',
'reusePort' => false,
'event_loop' => '',
'stop_timeout' => 2,
'pid_file' => runtime_path() . '/webman.pid',
'status_file' => runtime_path() . '/webman.status',
'stdout_file' => runtime_path() . '/logs/stdout.log',
'log_file' => runtime_path() . '/logs/workerman.log',
'max_package_size' => 10 * 1024 * 1024
];

65
config/session.php Normal file
View File

@ -0,0 +1,65 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use Webman\Session\FileSessionHandler;
use Webman\Session\RedisSessionHandler;
use Webman\Session\RedisClusterSessionHandler;
return [
'type' => 'file', // or redis or redis_cluster
'handler' => FileSessionHandler::class,
'config' => [
'file' => [
'save_path' => runtime_path() . '/sessions',
],
'redis' => [
'host' => '127.0.0.1',
'port' => 6379,
'auth' => '',
'timeout' => 2,
'database' => '',
'prefix' => 'redis_session_',
],
'redis_cluster' => [
'host' => ['127.0.0.1:7000', '127.0.0.1:7001', '127.0.0.1:7001'],
'timeout' => 2,
'auth' => '',
'prefix' => 'redis_session_',
]
],
'session_name' => 'PHPSID',
'auto_update_timestamp' => false,
'lifetime' => 7*24*60*60,
'cookie_lifetime' => 365*24*60*60,
'cookie_path' => '/',
'domain' => '',
'http_only' => true,
'secure' => false,
'same_site' => '',
'gc_probability' => [1, 1000],
];

23
config/static.php Normal file
View File

@ -0,0 +1,23 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
/**
* Static file settings
*/
return [
'enable' => true,
'middleware' => [ // Static file Middleware
//app\middleware\StaticFile::class,
],
];

36
config/thinkorm.php Normal file
View File

@ -0,0 +1,36 @@
<?php
return [
'default' => 'mysql',
'connections' => [
'mysql' => [
// 数据库类型
'type' => 'mysql',
// 服务器地址
'hostname' => '127.0.0.1',
// 数据库名
'database' => 'webman_admin',
// 数据库用户名
'username' => 'root',
// 数据库密码
'password' => '123456',
// 数据库连接端口
'hostport' => '3306',
// 数据库连接参数
'params' => [
// 连接超时3秒
\PDO::ATTR_TIMEOUT => 3,
],
// 数据库编码默认采用utf8
'charset' => 'utf8',
// 数据库表前缀
'prefix' => 'wa_',
// 断线重连
'break_reconnect' => true,
// 关闭SQL监听日志
'trigger_sql' => false,
// 自定义分页类
'bootstrap' => ''
],
],
];

25
config/translation.php Normal file
View File

@ -0,0 +1,25 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
/**
* Multilingual configuration
*/
return [
// Default language
'locale' => 'zh_CN',
// Fallback language
'fallback_locale' => ['zh_CN', 'en'],
// Folder where language files are stored
'path' => base_path() . '/resource/translations',
];

22
config/view.php Normal file
View File

@ -0,0 +1,22 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use support\view\Raw;
use support\view\Twig;
use support\view\Blade;
use support\view\ThinkPHP;
return [
'handler' => Raw::class
];

127
plugin/admin/api/Auth.php Normal file
View File

@ -0,0 +1,127 @@
<?php
namespace plugin\admin\api;
use plugin\admin\app\model\Role;
use plugin\admin\app\model\Rule;
use support\exception\BusinessException;
use function admin;
/**
* 对外提供的鉴权接口
*/
class Auth
{
/**
* 判断权限
* 如果没有权限则抛出异常
* @param string $controller
* @param string $action
* @return void
* @throws \ReflectionException|BusinessException
*/
public static function access(string $controller, string $action)
{
$code = 0;
$msg = '';
if (!static::canAccess($controller, $action, $code, $msg)) {
throw new BusinessException($msg, $code);
}
}
/**
* 判断是否有权限
* @param string $controller
* @param string $action
* @param int $code
* @param string $msg
* @return bool
* @throws \ReflectionException|BusinessException
*/
public static function canAccess(string $controller, string $action, int &$code = 0, string &$msg = ''): bool
{
// 无控制器信息说明是函数调用,函数不属于任何控制器,鉴权操作应该在函数内部完成。
if (!$controller) {
return true;
}
// 获取控制器鉴权信息
$class = new \ReflectionClass($controller);
$properties = $class->getDefaultProperties();
$noNeedLogin = $properties['noNeedLogin'] ?? [];
$noNeedAuth = $properties['noNeedAuth'] ?? [];
// 不需要登录
if (in_array($action, $noNeedLogin)) {
return true;
}
// 获取登录信息
$admin = admin();
if (!$admin) {
$msg = '请登录';
// 401是未登录固定的返回码
$code = 401;
return false;
}
// 不需要鉴权
if (in_array($action, $noNeedAuth)) {
return true;
}
// 当前管理员无角色
$roles = $admin['roles'];
if (!$roles) {
$msg = '无权限';
$code = 2;
return false;
}
// 角色没有规则
$rules = Role::whereIn('id', $roles)->pluck('rules');
$rule_ids = [];
foreach ($rules as $rule_string) {
if (!$rule_string) {
continue;
}
$rule_ids = array_merge($rule_ids, explode(',', $rule_string));
}
if (!$rule_ids) {
$msg = '无权限';
$code = 2;
return false;
}
// 超级管理员
if (in_array('*', $rule_ids)){
return true;
}
// 如果action为index规则里有任意一个以$controller开头的权限即可
if (strtolower($action) === 'index') {
$rule = Rule::where(function ($query) use ($controller, $action) {
$controller_like = str_replace('\\', '\\\\', $controller);
$query->where('key', 'like', "$controller_like@%")->orWhere('key', $controller);
})->whereIn('id', $rule_ids)->first();
if ($rule) {
return true;
}
$msg = '无权限';
$code = 2;
return false;
}
// 查询是否有当前控制器的规则
$rule = Rule::where(function ($query) use ($controller, $action) {
$query->where('key', "$controller@$action")->orWhere('key', $controller);
})->whereIn('id', $rule_ids)->first();
if (!$rule) {
$msg = '无权限';
$code = 2;
return false;
}
return true;
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace plugin\admin\api;
class Install
{
/**
* 安装
*
* @param $version
* @return void
*/
public static function install($version)
{
// 导入菜单
Menu::import(static::getMenus());
}
/**
* 卸载
*
* @param $version
* @return void
*/
public static function uninstall($version)
{
// 删除菜单
foreach (static::getMenus() as $menu) {
Menu::delete($menu['name']);
}
}
/**
* 更新
*
* @param $from_version
* @param $to_version
* @param $context
* @return void
*/
public static function update($from_version, $to_version, $context = null)
{
// 删除不用的菜单
if (isset($context['previous_menus'])) {
static::removeUnnecessaryMenus($context['previous_menus']);
}
// 导入新菜单
Menu::import(static::getMenus());
}
/**
* 更新前数据收集等
*
* @param $from_version
* @param $to_version
* @return array|array[]
*/
public static function beforeUpdate($from_version, $to_version)
{
// 在更新之前获得老菜单通过context传递给 update
return ['previous_menus' => static::getMenus()];
}
/**
* 获取菜单
*
* @return array|mixed
*/
public static function getMenus()
{
clearstatcache();
if (is_file($menu_file = __DIR__ . '/../config/menu.php')) {
$menus = include $menu_file;
return $menus ?: [];
}
return [];
}
/**
* 删除不需要的菜单
*
* @param $previous_menus
* @return void
*/
public static function removeUnnecessaryMenus($previous_menus)
{
$menus_to_remove = array_diff(Menu::column($previous_menus, 'name'), Menu::column(static::getMenus(), 'name'));
foreach ($menus_to_remove as $name) {
Menu::delete($name);
}
}
}

150
plugin/admin/api/Menu.php Normal file
View File

@ -0,0 +1,150 @@
<?php
namespace plugin\admin\api;
use plugin\admin\app\model\Role;
use plugin\admin\app\model\Rule;
use support\exception\BusinessException;
use function admin;
/**
* 对外提供的菜单接口
*/
class Menu
{
/**
* 根据key获取菜单
* @param $key
* @return array
*/
public static function get($key)
{
$menu = Rule::where('key', $key)->first();
return $menu ? $menu->toArray() : null;
}
/**
* 根据id获得菜单
* @param $id
* @return array
*/
public static function find($id): array
{
return Rule::find($id)->toArray();
}
/**
* 添加菜单
* @param array $menu
* @return int
*/
public static function add(array $menu)
{
$item = new Rule;
foreach ($menu as $key => $value) {
$item->$key = $value;
}
$item->save();
return $item->id;
}
/**
* 导入菜单
* @param array $menu_tree
* @return void
*/
public static function import(array $menu_tree)
{
if (is_numeric(key($menu_tree)) && !isset($menu_tree['key'])) {
foreach ($menu_tree as $item) {
static::import($item);
}
return;
}
$children = $menu_tree['children'] ?? [];
unset($menu_tree['children']);
if ($old_menu = Menu::get($menu_tree['key'])) {
$pid = $old_menu['id'];
Rule::where('key', $menu_tree['key'])->update($menu_tree);
} else {
$pid = static::add($menu_tree);
}
foreach ($children as $menu) {
$menu['pid'] = $pid;
static::import($menu);
}
}
/**
* 删除菜单
* @param $key
* @return void
*/
public static function delete($key)
{
$item = Rule::where('key', $key)->first();
if (!$item) {
return;
}
// 子规则一起删除
$delete_ids = $children_ids = [$item['id']];
while($children_ids) {
$children_ids = Rule::whereIn('pid', $children_ids)->pluck('id')->toArray();
$delete_ids = array_merge($delete_ids, $children_ids);
}
Rule::whereIn('id', $delete_ids)->delete();
}
/**
* 获取菜单中某个()字段的值
* @param $menu
* @param null $column
* @param null $index
* @return array|mixed
*/
public static function column($menu, $column = null, $index = null)
{
$values = [];
if (is_numeric(key($menu)) && !isset($menu['key'])) {
foreach ($menu as $item) {
$values = array_merge($values, static::column($item, $column, $index));
}
return $values;
}
$children = $menu['children'] ?? [];
unset($menu['children']);
if ($column === null) {
if ($index) {
$values[$menu[$index]] = $menu;
} else {
$values[] = $menu;
}
} else {
if (is_array($column)) {
$item = [];
foreach ($column as $f) {
$item[$f] = $menu[$f] ?? null;
}
if ($index) {
$values[$menu[$index]] = $item;
} else {
$values[] = $item;
}
} else {
$value = $menu[$column] ?? null;
if ($index) {
$values[$menu[$index]] = $value;
} else {
$values[] = $value;
}
}
}
foreach ($children as $child) {
$values = array_merge($values, static::column($child, $column, $index));
}
return $values;
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace plugin\admin\api;
use ReflectionException;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
use support\exception\BusinessException;
/**
* 对外提供的鉴权中间件
*/
class Middleware implements MiddlewareInterface
{
/**
* 鉴权
* @param Request $request
* @param callable $handler
* @return Response
* @throws ReflectionException
* @throws BusinessException
*/
public function process(Request $request, callable $handler): Response
{
$controller = $request->controller;
$action = $request->action;
$code = 0;
$msg = '';
if (!Auth::canAccess($controller, $action, $code, $msg)) {
if ($request->expectsJson()) {
$response = json(['code' => $code, 'msg' => $msg, 'type' => 'error']);
} else {
if ($code === 401) {
$response = response(<<<EOF
<script>
if (self !== top) {
parent.location.reload();
}
</script>
EOF
);
} else {
$request->app = '';
$request->plugin = 'admin';
$response = view('common/error/403')->withStatus(403);
}
}
} else {
$response = $request->method() == 'OPTIONS' ? response('') : $handler($request);
}
return $response;
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace plugin\admin\app\common;
use plugin\admin\app\model\Admin;
use plugin\admin\app\model\AdminRole;
use plugin\admin\app\model\Role;
use plugin\admin\app\model\Rule;
class Auth
{
/**
* 获取权限范围内的所有角色id
* @param bool $with_self
* @return array
*/
public static function getScopeRoleIds(bool $with_self = false): array
{
if (!$admin = admin()) {
return [];
}
$role_ids = $admin['roles'];
$rules = Role::whereIn('id', $role_ids)->pluck('rules')->toArray();
if ($rules && in_array('*', $rules)) {
return Role::pluck('id')->toArray();
}
$roles = Role::get();
$tree = new Tree($roles);
$descendants = $tree->getDescendant($role_ids, $with_self);
return array_column($descendants, 'id');
}
/**
* 获取权限范围内的所有管理员id
* @param bool $with_self
* @return array
*/
public static function getScopeAdminIds(bool $with_self = false): array
{
$role_ids = static::getScopeRoleIds($with_self);
return AdminRole::whereIn('role_id', $role_ids)->pluck('admin_id')->toArray();
}
/**
* 是否是超级管理员
* @param int $admin_id
* @return bool
*/
public static function isSupperAdmin(int $admin_id = 0): bool
{
if (!$admin_id) {
if (!$roles = admin('roles')) {
return false;
}
} else {
$roles = AdminRole::where('admin_id', $admin_id)->pluck('role_id');
}
$rules = Role::whereIn('id', $roles)->pluck('rules');
return $rules && in_array('*', $rules->toArray());
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,187 @@
<?php
namespace plugin\admin\app\common;
class Tree
{
/**
* 获取完整的树结构,包含祖先节点
*/
const INCLUDE_ANCESTORS = 1;
/**
* 获取部分树,不包含祖先节点
*/
const EXCLUDE_ANCESTORS = 0;
/**
* 数据
* @var array
*/
protected $data = [];
/**
* 哈希树
* @var array
*/
protected $hashTree = [];
/**
* 父级字段名
* @var string
*/
protected $pidName = 'pid';
/**
* @param $data
* @param string $pid_name
*/
public function __construct($data, string $pid_name = 'pid')
{
$this->pidName = $pid_name;
if (is_object($data) && method_exists($data, 'toArray')) {
$this->data = $data->toArray();
} else {
$this->data = (array)$data;
}
$this->hashTree = $this->getHashTree();
}
/**
* 获取子孙节点
* @param array $include
* @param bool $with_self
* @return array
*/
public function getDescendant(array $include, bool $with_self = false): array
{
$items = [];
foreach ($include as $id) {
if (!isset($this->hashTree[$id])) {
return [];
}
if ($with_self) {
$item = $this->hashTree[$id];
unset($item['children']);
$items[$item['id']] = $item;
}
foreach ($this->hashTree[$id]['children'] ?? [] as $item) {
unset($item['children']);
$items[$item['id']] = $item;
foreach ($this->getDescendant([$item['id']]) as $it) {
$items[$it['id']] = $it;
}
}
}
return array_values($items);
}
/**
* 获取哈希树
* @param array $data
* @return array
*/
protected function getHashTree(array $data = []): array
{
$data = $data ?: $this->data;
$hash_tree = [];
foreach ($data as $item) {
$hash_tree[$item['id']] = $item;
}
foreach ($hash_tree as $index => $item) {
if ($item[$this->pidName] && isset($hash_tree[$item[$this->pidName]])) {
$hash_tree[$item[$this->pidName]]['children'][$hash_tree[$index]['id']] = &$hash_tree[$index];
}
}
return $hash_tree;
}
/**
* 获取树
* @param array $include
* @param int $type
* @return array|null
*/
public function getTree(array $include = [], int $type = 1): ?array
{
// $type === static::EXCLUDE_ANCESTORS
if ($type === static::EXCLUDE_ANCESTORS) {
$items = [];
$include = array_unique($include);
foreach ($include as $id) {
if (!isset($this->hashTree[$id])) {
return [];
}
$items[] = $this->hashTree[$id];
}
return static::arrayValues($items);
}
// $type === static::INCLUDE_ANCESTORS
$hash_tree = $this->hashTree;
$items = [];
if ($include) {
$map = [];
foreach ($include as $id) {
if (!isset($hash_tree[$id])) {
continue;
}
$item = $hash_tree[$id];
$max_depth = 100;
while ($max_depth-- > 0 && $item[$this->pidName] && isset($hash_tree[$item[$this->pidName]])) {
$last_item = $item;
$pid = $item[$this->pidName];
$item = $hash_tree[$pid];
$item_id = $item['id'];
if (empty($map[$item_id])) {
$map[$item_id] = 1;
$hash_tree[$pid]['children'] = [];
}
$hash_tree[$pid]['children'][$last_item['id']] = $last_item;
$item = $hash_tree[$pid];
}
$items[$item['id']] = $item;
}
} else {
$items = $hash_tree;
}
$formatted_items = [];
foreach ($items as $item) {
if (!$item[$this->pidName] || !isset($hash_tree[$item[$this->pidName]])) {
$formatted_items[] = $item;
}
}
return static::arrayValues($formatted_items);
}
/**
* 递归重建数组下标
* @param $array
* @return array
*/
public static function arrayValues($array): array
{
if (!$array) {
return [];
}
if (!isset($array['children'])) {
$current = current($array);
if (!is_array($current) || !isset($current['children'])) {
return $array;
}
$tree = array_values($array);
foreach ($tree as $index => $item) {
$tree[$index] = static::arrayValues($item);
}
return $tree;
}
$array['children'] = array_values($array['children']);
foreach ($array['children'] as $index => $child) {
$array['children'][$index] = static::arrayValues($child);
}
return $array;
}
}

View File

@ -0,0 +1,567 @@
<?php
namespace plugin\admin\app\common;
use process\Monitor;
use Throwable;
use Illuminate\Database\Connection;
use Illuminate\Database\Schema\Builder;
use plugin\admin\app\model\Option;
use support\exception\BusinessException;
use support\Db;
use Workerman\Timer;
use Workerman\Worker;
class Util
{
/**
* 密码哈希
* @param $password
* @param string $algo
* @return false|string|null
*/
public static function passwordHash($password, string $algo = PASSWORD_DEFAULT)
{
return password_hash($password, $algo);
}
/**
* 验证密码哈希
* @param $password
* @param $hash
* @return bool
*/
public static function passwordVerify(string $password, string $hash): bool
{
return password_verify($password, $hash);
}
/**
* 获取webman-admin数据库连接
* @return Connection
*/
public static function db(): Connection
{
return Db::connection('plugin.admin.mysql');
}
/**
* 获取SchemaBuilder
* @return Builder
*/
public static function schema(): Builder
{
return Db::schema('plugin.admin.mysql');
}
/**
* 获取语义化时间
* @param $time
* @return false|string
*/
public static function humanDate($time)
{
$timestamp = is_numeric($time) ? $time : strtotime($time);
$dur = time() - $timestamp;
if ($dur < 0) {
return date('Y-m-d', $timestamp);
} else {
if ($dur < 60) {
return $dur . '秒前';
} else {
if ($dur < 3600) {
return floor($dur / 60) . '分钟前';
} else {
if ($dur < 86400) {
return floor($dur / 3600) . '小时前';
} else {
if ($dur < 2592000) { // 30天内
return floor($dur / 86400) . '天前';
} else {
return date('Y-m-d', $timestamp);;
}
}
}
}
}
return date('Y-m-d', $timestamp);
}
/**
* 格式化文件大小
* @param $file_size
* @return string
*/
public static function formatBytes($file_size): string
{
$size = sprintf("%u", $file_size);
if($size == 0) {
return("0 Bytes");
}
$size_name = array(" Bytes", " KB", " MB", " GB", " TB", " PB", " EB", " ZB", " YB");
return round($size/pow(1024, ($i = floor(log($size, 1024)))), 2) . $size_name[$i];
}
/**
* 数据库字符串转义
* @param $var
* @return false|string
*/
public static function pdoQuote($var)
{
return Util::db()->getPdo()->quote($var, \PDO::PARAM_STR);
}
/**
* 检查表名是否合法
* @param string $table
* @return string
* @throws BusinessException
*/
public static function checkTableName(string $table): string
{
if (!preg_match('/^[a-zA-Z_0-9]+$/', $table)) {
throw new BusinessException('表名不合法');
}
return $table;
}
/**
* 变量或数组中的元素只能是字母数字下划线组合
* @param $var
* @return mixed
* @throws BusinessException
*/
public static function filterAlphaNum($var)
{
$vars = (array)$var;
array_walk_recursive($vars, function ($item) {
if (is_string($item) && !preg_match('/^[a-zA-Z_0-9]+$/', $item)) {
throw new BusinessException('参数不合法');
}
});
return $var;
}
/**
* 变量或数组中的元素只能是字母数字
* @param $var
* @return mixed
* @throws BusinessException
*/
public static function filterNum($var)
{
$vars = (array)$var;
array_walk_recursive($vars, function ($item) {
if (is_string($item) && !preg_match('/^[0-9]+$/', $item)) {
throw new BusinessException('参数不合法');
}
});
return $var;
}
/**
* 检测是否是合法URL Path
* @param $var
* @return string
* @throws BusinessException
*/
public static function filterUrlPath($var): string
{
if (!is_string($var) || !preg_match('/^[a-zA-Z0-9_\-\/&?.]+$/', $var)) {
throw new BusinessException('参数不合法');
}
return $var;
}
/**
* 检测是否是合法Path
* @param $var
* @return string
* @throws BusinessException
*/
public static function filterPath($var): string
{
if (!is_string($var) || !preg_match('/^[a-zA-Z0-9_\-\/]+$/', $var)) {
throw new BusinessException('参数不合法');
}
return $var;
}
/**
* 类转换为url path
* @param $controller_class
* @return false|string
*/
static function controllerToUrlPath($controller_class)
{
$key = strtolower($controller_class);
$action = '';
if (strpos($key, '@')) {
[$key, $action] = explode( '@', $key, 2);
}
$prefix = 'plugin';
$paths = explode('\\', $key);
if (count($paths) < 2) {
return false;
}
$base = '';
if (strpos($key, "$prefix\\") === 0) {
if (count($paths) < 4) {
return false;
}
array_shift($paths);
$plugin = array_shift($paths);
$base = "/app/$plugin/";
}
array_shift($paths);
foreach ($paths as $index => $path) {
if ($path === 'controller') {
unset($paths[$index]);
}
}
$suffix = 'controller';
$code = $base . implode('/', $paths);
if (substr($code, -strlen($suffix)) === $suffix) {
$code = substr($code, 0, -strlen($suffix));
}
return $action ? "$code/$action" : $code;
}
/**
* 转换为驼峰
* @param string $value
* @return string
*/
public static function camel(string $value): string
{
static $cache = [];
$key = $value;
if (isset($cache[$key])) {
return $cache[$key];
}
$value = ucwords(str_replace(['-', '_'], ' ', $value));
return $cache[$key] = str_replace(' ', '', $value);
}
/**
* 转换为小驼峰
* @param $value
* @return string
*/
public static function smCamel($value): string
{
return lcfirst(static::camel($value));
}
/**
* 获取注释中第一行
* @param $comment
* @return false|mixed|string
*/
public static function getCommentFirstLine($comment)
{
if ($comment === false) {
return false;
}
foreach (explode("\n", $comment) as $str) {
if ($s = trim($str, "*/\ \t\n\r\0\x0B")) {
return $s;
}
}
return $comment;
}
/**
* 表单类型到插件的映射
* @return \string[][]
*/
public static function methodControlMap(): array
{
return [
//method=>[控件]
'integer' => ['InputNumber'],
'string' => ['Input'],
'text' => ['TextArea'],
'date' => ['DatePicker'],
'enum' => ['Select'],
'float' => ['Input'],
'tinyInteger' => ['InputNumber'],
'smallInteger' => ['InputNumber'],
'mediumInteger' => ['InputNumber'],
'bigInteger' => ['InputNumber'],
'unsignedInteger' => ['InputNumber'],
'unsignedTinyInteger' => ['InputNumber'],
'unsignedSmallInteger' => ['InputNumber'],
'unsignedMediumInteger' => ['InputNumber'],
'unsignedBigInteger' => ['InputNumber'],
'decimal' => ['Input'],
'double' => ['Input'],
'mediumText' => ['TextArea'],
'longText' => ['TextArea'],
'dateTime' => ['DateTimePicker'],
'time' => ['DateTimePicker'],
'timestamp' => ['DateTimePicker'],
'char' => ['Input'],
'binary' => ['Input'],
'json' => ['input']
];
}
/**
* 数据库类型到插件的转换
* @param $type
* @return string
*/
public static function typeToControl($type): string
{
if (stripos($type, 'int') !== false) {
return 'inputNumber';
}
if (stripos($type, 'time') !== false || stripos($type, 'date') !== false) {
return 'dateTimePicker';
}
if (stripos($type, 'text') !== false) {
return 'textArea';
}
if ($type === 'enum') {
return 'select';
}
return 'input';
}
/**
* 数据库类型到表单类型的转换
* @param $type
* @param $unsigned
* @return string
*/
public static function typeToMethod($type, $unsigned = false)
{
if (stripos($type, 'int') !== false) {
$type = str_replace('int', 'Integer', $type);
return $unsigned ? "unsigned" . ucfirst($type) : lcfirst($type);
}
$map = [
'int' => 'integer',
'varchar' => 'string',
'mediumtext' => 'mediumText',
'longtext' => 'longText',
'datetime' => 'dateTime',
];
return $map[$type] ?? $type;
}
/**
* 按表获取摘要
* @param $table
* @param null $section
* @return array|mixed
* @throws BusinessException
*/
public static function getSchema($table, $section = null)
{
Util::checkTableName($table);
$database = config('database.connections')['plugin.admin.mysql']['database'];
$schema_raw = $section !== 'table' ? Util::db()->select("select * from information_schema.COLUMNS where TABLE_SCHEMA = '$database' and table_name = '$table' order by ORDINAL_POSITION") : [];
$forms = [];
$columns = [];
foreach ($schema_raw as $item) {
$field = $item->COLUMN_NAME;
$columns[$field] = [
'field' => $field,
'type' => Util::typeToMethod($item->DATA_TYPE, (bool)strpos($item->COLUMN_TYPE, 'unsigned')),
'comment' => $item->COLUMN_COMMENT,
'default' => $item->COLUMN_DEFAULT,
'length' => static::getLengthValue($item),
'nullable' => $item->IS_NULLABLE !== 'NO',
'primary_key' => $item->COLUMN_KEY === 'PRI',
'auto_increment' => strpos($item->EXTRA, 'auto_increment') !== false
];
$forms[$field] = [
'field' => $field,
'comment' => $item->COLUMN_COMMENT,
'control' => static::typeToControl($item->DATA_TYPE),
'form_show' => $item->COLUMN_KEY !== 'PRI',
'list_show' => true,
'enable_sort' => false,
'searchable' => false,
'search_type' => 'normal',
'control_args' => '',
];
}
$table_schema = $section == 'table' || !$section ? Util::db()->select("SELECT TABLE_COMMENT FROM information_schema.`TABLES` WHERE TABLE_SCHEMA='$database' and TABLE_NAME='$table'") : [];
$indexes = !$section || in_array($section, ['keys', 'table']) ? Util::db()->select("SHOW INDEX FROM `$table`") : [];
$keys = [];
$primary_key = [];
foreach ($indexes as $index) {
$key_name = $index->Key_name;
if ($key_name == 'PRIMARY') {
$primary_key[] = $index->Column_name;
continue;
}
if (!isset($keys[$key_name])) {
$keys[$key_name] = [
'name' => $key_name,
'columns' => [],
'type' => $index->Non_unique == 0 ? 'unique' : 'normal'
];
}
$keys[$key_name]['columns'][] = $index->Column_name;
}
$data = [
'table' => ['name' => $table, 'comment' => $table_schema[0]->TABLE_COMMENT ?? '', 'primary_key' => $primary_key],
'columns' => $columns,
'forms' => $forms,
'keys' => array_reverse($keys, true)
];
$schema = Option::where('name', "table_form_schema_$table")->value('value');
$form_schema_map = $schema ? json_decode($schema, true) : [];
foreach ($data['forms'] as $field => $item) {
if (isset($form_schema_map[$field])) {
$data['forms'][$field] = $form_schema_map[$field];
}
}
return $section ? $data[$section] : $data;
}
/**
* 获取字段长度或默认值
* @param $schema
* @return mixed|string
*/
public static function getLengthValue($schema)
{
$type = $schema->DATA_TYPE;
if (in_array($type, ['float', 'decimal', 'double'])) {
return "{$schema->NUMERIC_PRECISION},{$schema->NUMERIC_SCALE}";
}
if ($type === 'enum') {
return implode(',', array_map(function($item){
return trim($item, "'");
}, explode(',', substr($schema->COLUMN_TYPE, 5, -1))));
}
if (in_array($type, ['varchar', 'text', 'char'])) {
return $schema->CHARACTER_MAXIMUM_LENGTH;
}
if (in_array($type, ['time', 'datetime', 'timestamp'])) {
return $schema->CHARACTER_MAXIMUM_LENGTH;
}
return '';
}
/**
* 获取控件参数
* @param $control
* @param $control_args
* @return array
*/
public static function getControlProps($control, $control_args): array
{
if (!$control_args) {
return [];
}
$control = strtolower($control);
$props = [];
$split = explode(';', $control_args);
foreach ($split as $item) {
$pos = strpos($item, ':');
if ($pos === false) {
continue;
}
$name = trim(substr($item, 0, $pos));
$values = trim(substr($item, $pos + 1));
// values = a:v,c:d
$pos = strpos($values, ':');
if ($pos !== false) {
$options = explode(',', $values);
$values = [];
foreach ($options as $option) {
[$v, $n] = explode(':', $option);
if (in_array($control, ['select', 'selectmulti', 'treeselect', 'treemultiselect']) && $name == 'data') {
$values[] = ['value' => $v, 'name' => $n];
} else {
$values[$v] = $n;
}
}
}
$props[$name] = $values;
}
return $props;
}
/**
* 获取某个composer包的版本
* @param string $package
* @return mixed|string
*/
public static function getPackageVersion(string $package)
{
$installed_php = base_path('vendor/composer/installed.php');
if (is_file($installed_php)) {
$packages = include $installed_php;
}
return substr($packages['versions'][$package]['version'] ?? 'unknown ', 0, -2);
}
/**
* Reload webman
* @return bool
*/
public static function reloadWebman()
{
if (function_exists('posix_kill')) {
try {
posix_kill(posix_getppid(), SIGUSR1);
return true;
} catch (Throwable $e) {}
} else {
Timer::add(1, function () {
Worker::stopAll();
});
}
return false;
}
/**
* Pause file monitor
* @return void
*/
public static function pauseFileMonitor()
{
if (method_exists(Monitor::class, 'pause')) {
Monitor::pause();
}
}
/**
* Resume file monitor
* @return void
*/
public static function resumeFileMonitor()
{
if (method_exists(Monitor::class, 'resume')) {
Monitor::resume();
}
}
}

View File

@ -0,0 +1,258 @@
<?php
namespace plugin\admin\app\controller;
use plugin\admin\app\common\Auth;
use plugin\admin\app\common\Util;
use plugin\admin\app\model\Admin;
use support\exception\BusinessException;
use support\Request;
use support\Response;
use Webman\Captcha\CaptchaBuilder;
use Webman\Captcha\PhraseBuilder;
/**
* 管理员账户
*/
class AccountController extends Crud
{
/**
* 不需要登录的方法
* @var string[]
*/
protected $noNeedLogin = ['login', 'logout', 'captcha'];
/**
* 不需要鉴权的方法
* @var string[]
*/
protected $noNeedAuth = ['info'];
/**
* @var Admin
*/
protected $model = null;
/**
* 构造函数
*/
public function __construct()
{
$this->model = new Admin;
}
/**
* 账户设置
* @return Response
*/
public function index()
{
return view('account/index');
}
/**
* 登录
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function login(Request $request): Response
{
$this->checkDatabaseAvailable();
$captcha = $request->post('captcha');
if (strtolower($captcha) !== session('captcha-login')) {
return $this->json(1, '验证码错误');
}
$request->session()->forget('captcha-login');
$username = $request->post('username', '');
$password = $request->post('password', '');
if (!$username) {
return $this->json(1, '用户名不能为空');
}
$this->checkLoginLimit($username);
$admin = Admin::where('username', $username)->first();
if (!$admin || !Util::passwordVerify($password, $admin->password)) {
return $this->json(1, '账户不存在或密码错误');
}
if ($admin->status != 0) {
return $this->json(1, '当前账户暂时无法登录');
}
$admin->login_at = date('Y-m-d H:i:s');
$admin->save();
$this->removeLoginLimit($username);
$admin = $admin->toArray();
$session = $request->session();
unset($admin['password']);
$session->set('admin', $admin);
return $this->json(0, '登录成功', [
'nickname' => $admin['nickname'],
'token' => $request->sessionId(),
]);
}
/**
* 退出
* @param Request $request
* @return Response
*/
public function logout(Request $request): Response
{
$request->session()->delete('admin');
return $this->json(0);
}
/**
* 获取登录信息
* @param Request $request
* @return Response
*/
public function info(Request $request): Response
{
$admin = admin();
if (!$admin) {
return $this->json(1);
}
$info = [
'id' => $admin['id'],
'username' => $admin['username'],
'nickname' => $admin['nickname'],
'avatar' => $admin['avatar'],
'email' => $admin['email'],
'mobile' => $admin['mobile'],
'isSupperAdmin' => Auth::isSupperAdmin(),
'token' => $request->sessionId(),
];
return $this->json(0, 'ok', $info);
}
/**
* 更新
* @param Request $request
* @return Response
*/
public function update(Request $request): Response
{
$allow_column = [
'nickname' => 'nickname',
'avatar' => 'avatar',
'email' => 'email',
'mobile' => 'mobile',
];
$data = $request->post();
$update_data = [];
foreach ($allow_column as $key => $column) {
if (isset($data[$key])) {
$update_data[$column] = $data[$key];
}
}
if (isset($update_data['password'])) {
$update_data['password'] = Util::passwordHash($update_data['password']);
}
Admin::where('id', admin_id())->update($update_data);
$admin = admin();
unset($update_data['password']);
foreach ($update_data as $key => $value) {
$admin[$key] = $value;
}
$request->session()->set('admin', $admin);
return $this->json(0);
}
/**
* 修改密码
* @param Request $request
* @return Response
*/
public function password(Request $request): Response
{
$hash = Admin::find(admin_id())['password'];
$password = $request->post('password');
if (!$password) {
return $this->json(2, '密码不能为空');
}
if ($request->post('password_confirm') !== $password) {
return $this->json(3, '两次密码输入不一致');
}
if (!Util::passwordVerify($request->post('old_password'), $hash)) {
return $this->json(1, '原始密码不正确');
}
$update_data = [
'password' => Util::passwordHash($password)
];
Admin::where('id', admin_id())->update($update_data);
return $this->json(0);
}
/**
* 验证码
* @param Request $request
* @param string $type
* @return Response
*/
public function captcha(Request $request, string $type = 'login'): Response
{
$builder = new PhraseBuilder(4, 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ');
$captcha = new CaptchaBuilder(null, $builder);
$captcha->build(120);
$request->session()->set("captcha-$type", strtolower($captcha->getPhrase()));
$img_content = $captcha->get();
return response($img_content, 200, ['Content-Type' => 'image/jpeg']);
}
/**
* 检查登录频率限制
* @param $username
* @return void
* @throws BusinessException
*/
protected function checkLoginLimit($username)
{
$limit_log_path = runtime_path() . '/login';
if (!is_dir($limit_log_path)) {
mkdir($limit_log_path, 0777, true);
}
$limit_file = $limit_log_path . '/' . md5($username) . '.limit';
$time = date('YmdH') . ceil(date('i')/5);
$limit_info = [];
if (is_file($limit_file)) {
$json_str = file_get_contents($limit_file);
$limit_info = json_decode($json_str, true);
}
if (!$limit_info || $limit_info['time'] != $time) {
$limit_info = [
'username' => $username,
'count' => 0,
'time' => $time
];
}
$limit_info['count']++;
file_put_contents($limit_file, json_encode($limit_info));
if ($limit_info['count'] >= 5) {
throw new BusinessException('登录失败次数过多请5分钟后再试');
}
}
/**
* 解除登录频率限制
* @param $username
* @return void
*/
protected function removeLoginLimit($username)
{
$limit_log_path = runtime_path() . '/login';
$limit_file = $limit_log_path . '/' . md5($username) . '.limit';
if (is_file($limit_file)) {
unlink($limit_file);
}
}
protected function checkDatabaseAvailable()
{
if (!config('plugin.admin.database')) {
throw new BusinessException('请重启webman');
}
}
}

View File

@ -0,0 +1,219 @@
<?php
namespace plugin\admin\app\controller;
use plugin\admin\app\common\Auth;
use plugin\admin\app\model\Admin;
use plugin\admin\app\model\AdminRole;
use support\exception\BusinessException;
use support\Request;
use support\Response;
/**
* 管理员列表
*/
class AdminController extends Crud
{
/**
* 不需要鉴权的方法
* @var array
*/
protected $noNeedAuth = ['select'];
/**
* @var Admin
*/
protected $model = null;
/**
* 开启auth数据限制
* @var string
*/
protected $dataLimit = 'auth';
/**
* 以id为数据限制字段
* @var string
*/
protected $dataLimitField = 'id';
/**
* 构造函数
* @return void
*/
public function __construct()
{
$this->model = new Admin;
}
/**
* 浏览
* @return Response
*/
public function index(): Response
{
return view('admin/index');
}
/**
* 查询
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function select(Request $request): Response
{
[$where, $format, $limit, $field, $order] = $this->selectInput($request);
$query = $this->doSelect($where, $field, $order);
if ($format === 'select') {
return $this->formatSelect($query->get());
}
$paginator = $query->paginate($limit);
$items = $paginator->items();
$admin_ids = array_column($items, 'id');
$roles = AdminRole::whereIn('admin_id', $admin_ids)->get();
$roles_map = [];
foreach ($roles as $role) {
$roles_map[$role['admin_id']][] = $role['role_id'];
}
$login_admin_id = admin_id();
foreach ($items as $index => $item) {
$admin_id = $item['id'];
$items[$index]['roles'] = isset($roles_map[$admin_id]) ? implode(',', $roles_map[$admin_id]) : '';
$items[$index]['show_toolbar'] = $admin_id != $login_admin_id;
}
return json(['code' => 0, 'msg' => 'ok', 'count' => $paginator->total(), 'data' => $items]);
}
/**
* 插入
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function insert(Request $request): Response
{
if ($request->method() === 'POST') {
$data = $this->insertInput($request);
$admin_id = $this->doInsert($data);
$role_ids = $request->post('roles');
$role_ids = $role_ids ? explode(',', $role_ids) : [];
if (!$role_ids) {
return $this->json(1, '至少选择一个角色组');
}
if (!Auth::isSupperAdmin() && array_diff($role_ids, Auth::getScopeRoleIds())) {
return $this->json(1, '角色超出权限范围');
}
AdminRole::where('admin_id', $admin_id)->delete();
foreach ($role_ids as $id) {
$admin_role = new AdminRole;
$admin_role->admin_id = $admin_id;
$admin_role->role_id = $id;
$admin_role->save();
}
return $this->json(0, 'ok', ['id' => $admin_id]);
}
return view('admin/insert');
}
/**
* 更新
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function update(Request $request): Response
{
if ($request->method() === 'POST') {
[$id, $data] = $this->updateInput($request);
$admin_id = $request->post('id');
if (!$admin_id) {
return $this->json(1, '缺少参数');
}
// 不能禁用自己
if (isset($data['status']) && $data['status'] == 1 && $id == admin_id()) {
return $this->json(1, '不能禁用自己');
}
// 需要更新角色
$role_ids = $request->post('roles');
if ($role_ids !== null) {
if (!$role_ids) {
return $this->json(1, '至少选择一个角色组');
}
$role_ids = explode(',', $role_ids);
$is_supper_admin = Auth::isSupperAdmin();
$exist_role_ids = AdminRole::where('admin_id', $admin_id)->pluck('role_id')->toArray();
$scope_role_ids = Auth::getScopeRoleIds();
if (!$is_supper_admin && !array_intersect($exist_role_ids, $scope_role_ids)) {
return $this->json(1, '无权限更改该记录');
}
if (!$is_supper_admin && array_diff($role_ids, $scope_role_ids)) {
return $this->json(1, '角色超出权限范围');
}
// 删除账户角色
$delete_ids = array_diff($exist_role_ids, $role_ids);
AdminRole::whereIn('role_id', $delete_ids)->where('admin_id', $admin_id)->delete();
// 添加账户角色
$add_ids = array_diff($role_ids, $exist_role_ids);
foreach ($add_ids as $role_id) {
$admin_role = new AdminRole;
$admin_role->admin_id = $admin_id;
$admin_role->role_id = $role_id;
$admin_role->save();
}
}
$this->doUpdate($id, $data);
return $this->json(0);
}
return view('admin/update');
}
/**
* 删除
* @param Request $request
* @return Response
*/
public function delete(Request $request): Response
{
$primary_key = $this->model->getKeyName();
$ids = $request->post($primary_key);
if (!$ids) {
return $this->json(0);
}
$ids = (array)$ids;
if (in_array(admin_id(), $ids)) {
return $this->json(1, '不能删除自己');
}
if (!Auth::isSupperAdmin() && array_diff($ids, Auth::getScopeAdminIds())) {
return $this->json(1, '无数据权限');
}
$this->model->whereIn($primary_key, $ids)->delete();
AdminRole::whereIn('admin_id', $ids)->delete();
return $this->json(0);
}
/**
* 格式化下拉列表
* @param $items
* @return Response
*/
protected function formatSelect($items): Response
{
$formatted_items = [];
foreach ($items as $item) {
$formatted_items[] = [
'name' => $item->nickname,
'value' => $item->id
];
}
return $this->json(0, 'ok', $formatted_items);
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace plugin\admin\app\controller;
use support\Model;
use support\Response;
/**
* 基础控制器
*/
class Base
{
/**
* @var Model
*/
protected $model = null;
/**
* 无需登录及鉴权的方法
* @var array
*/
protected $noNeedLogin = [];
/**
* 需要登录无需鉴权的方法
* @var array
*/
protected $noNeedAuth = [];
/**
* 数据限制
* 例如当$dataLimit='personal'时将只返回当前管理员的数据
* @var string
*/
protected $dataLimit = null;
/**
* 数据限制字段
*/
protected $dataLimitField = 'admin_id';
/**
* 返回格式化json数据
*
* @param int $code
* @param string $msg
* @param array $data
* @return Response
*/
protected function json(int $code, string $msg = 'ok', array $data = []): Response
{
return json(['code' => $code, 'data' => $data, 'msg' => $msg]);
}
}

View File

@ -0,0 +1,143 @@
<?php
namespace plugin\admin\app\controller;
use plugin\admin\app\common\Util;
use plugin\admin\app\model\Option;
use support\exception\BusinessException;
use support\Request;
use support\Response;
/**
* 系统设置
*/
class ConfigController extends Base
{
/**
* 不需要验证权限的方法
* @var string[]
*/
protected $noNeedAuth = ['get'];
/**
* 账户设置
* @return Response
*/
public function index(): Response
{
return view('config/index');
}
/**
* 获取配置
* @return Response
*/
public function get(): Response
{
return json($this->getByDefault());
}
/**
* 基于配置文件获取默认权限
* @return mixed
*/
protected function getByDefault()
{
$name = 'system_config';
$config = Option::where('name', $name)->value('value');
if (empty($config)) {
$config = file_get_contents(base_path('plugin/admin/public/config/pear.config.json'));
if ($config) {
$option = new Option();
$option->name = $name;
$option->value = $config;
$option->save();
}
}
return json_decode($config, true);
}
/**
* 更改
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function update(Request $request): Response
{
$post = $request->post();
$config = $this->getByDefault();
$data = [];
foreach ($post as $section => $items) {
if (!isset($config[$section])) {
continue;
}
switch ($section) {
case 'logo':
$data[$section]['title'] = htmlspecialchars($items['title'] ?? '');
$data[$section]['image'] = Util::filterUrlPath($items['image'] ?? '');
break;
case 'menu':
$data[$section]['data'] = Util::filterUrlPath($items['data'] ?? '');
$data[$section]['accordion'] = !empty($items['accordion']);
$data[$section]['collapse'] = !empty($items['collapse']);
$data[$section]['control'] = !empty($items['control']);
$data[$section]['controlWidth'] = (int)$items['controlWidth'] ?? 500;
$data[$section]['select'] = (int)$items['select'] ?? 0;
$data[$section]['async'] = true;
break;
case 'tab':
$data[$section]['enable'] = true;
$data[$section]['keepState'] = !empty($items['keepState']);
$data[$section]['preload'] = !empty($items['preload']);
$data[$section]['session'] = !empty($items['session']);
$data[$section]['max'] = Util::filterNum($items['max'] ?? '30');
$data[$section]['index']['id'] = Util::filterNum($items['index']['id'] ?? '0');
$data[$section]['index']['href'] = Util::filterUrlPath($items['index']['href'] ?? '');
$data[$section]['index']['title'] = htmlspecialchars($items['index']['title'] ?? '首页');
break;
case 'theme':
$data[$section]['defaultColor'] = Util::filterNum($items['defaultColor'] ?? '2');
$data[$section]['defaultMenu'] = $items['defaultMenu'] ?? '' == 'dark-theme' ? 'dark-theme' : 'light-theme';
$data[$section]['defaultHeader'] = $items['defaultHeader'] ?? '' == 'dark-theme' ? 'dark-theme' : 'light-theme';
$data[$section]['allowCustom'] = !empty($items['allowCustom']);
$data[$section]['banner'] = !empty($items['banner']);
break;
case 'colors':
foreach ($config['colors'] as $index => $item) {
if (!isset($items[$index])) {
$config['colors'][$index] = $item;
continue;
}
$data_item = $items[$index];
$data[$section][$index]['id'] = $index + 1;
$data[$section][$index]['color'] = $this->filterColor($data_item['color'] ?? '');
$data[$section][$index]['second'] = $this->filterColor($data_item['second'] ?? '');
}
break;
}
}
$config = array_merge($config, $data);
$name = 'system_config';
Option::where('name', $name)->update([
'value' => json_encode($config)
]);
return $this->json(0);
}
/**
* 颜色检查
* @param string $color
* @return string
* @throws BusinessException
*/
protected function filterColor(string $color): string
{
if (!preg_match('/\#[a-zA-Z]6/', $color)) {
throw new BusinessException('参数错误');
}
return $color;
}
}

View File

@ -0,0 +1,401 @@
<?php
namespace plugin\admin\app\controller;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder as QueryBuilder;
use plugin\admin\app\common\Auth;
use plugin\admin\app\common\Tree;
use plugin\admin\app\common\Util;
use support\exception\BusinessException;
use support\Model;
use support\Request;
use support\Response;
class Crud extends Base
{
/**
* @var Model
*/
protected $model = null;
/**
* 查询
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function select(Request $request): Response
{
[$where, $format, $limit, $field, $order] = $this->selectInput($request);
$query = $this->doSelect($where, $field, $order);
return $this->doFormat($query, $format, $limit);
}
/**
* 添加
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function insert(Request $request): Response
{
$data = $this->insertInput($request);
$id = $this->doInsert($data);
return $this->json(0, 'ok', ['id' => $id]);
}
/**
* 更新
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function update(Request $request): Response
{
[$id, $data] = $this->updateInput($request);
$this->doUpdate($id, $data);
return $this->json(0);
}
/**
* 删除
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function delete(Request $request): Response
{
$ids = $this->deleteInput($request);
$this->doDelete($ids);
return $this->json(0);
}
/**
* 查询前置
* @param Request $request
* @return array
* @throws BusinessException
*/
protected function selectInput(Request $request): array
{
$field = $request->get('field');
$order = $request->get('order', 'asc');
$format = $request->get('format', 'normal');
$limit = (int)$request->get('limit', $format === 'tree' ? 1000 : 10);
$limit = $limit <= 0 ? 10 : $limit;
$order = $order === 'asc' ? 'asc' : 'desc';
$where = $request->get();
$page = (int)$request->get('page');
$page = $page > 0 ? $page : 1;
$table = config('plugin.admin.database.connections.mysql.prefix') . $this->model->getTable();
$allow_column = Util::db()->select("desc `$table`");
if (!$allow_column) {
throw new BusinessException('表不存在');
}
$allow_column = array_column($allow_column, 'Field', 'Field');
if (!in_array($field, $allow_column)) {
$field = null;
}
foreach ($where as $column => $value) {
if ($value === '' || !isset($allow_column[$column]) ||
(is_array($value) && (in_array($value[0], ['', 'undefined']) || in_array($value[1], ['', 'undefined'])))) {
unset($where[$column]);
}
}
// 按照数据限制字段返回数据
if ($this->dataLimit === 'personal') {
$where[$this->dataLimitField] = admin_id();
} elseif ($this->dataLimit === 'auth') {
$primary_key = $this->model->getKeyName();
if (!Auth::isSupperAdmin() && (!isset($where[$primary_key]) || $this->dataLimitField != $primary_key)) {
$where[$this->dataLimitField] = ['in', Auth::getScopeAdminIds(true)];
}
}
return [$where, $format, $limit, $field, $order, $page];
}
/**
* 执行查询
* @param array $where
* @param string|null $field
* @param string $order
* @return EloquentBuilder|QueryBuilder|Model
*/
protected function doSelect(array $where, string $field = null, string $order= 'desc')
{
$model = $this->model;
foreach ($where as $column => $value) {
if (is_array($value)) {
if ($value[0] === 'like') {
$model = $model->where($column, 'like', "%$value[1]%");
} elseif (in_array($value[0], ['>', '=', '<', '<>', 'not like'])) {
$model = $model->where($column, $value[0], $value[1]);
} elseif ($value[0] == 'in') {
$model = $model->whereIn($column, $value[1]);
} elseif ($value[0] == 'not in') {
$model = $model->whereNotIn($column, $value[1]);
} elseif ($value[0] == 'null') {
$model = $model->whereNull($column, $value[1]);
} elseif ($value[0] == 'not null') {
$model = $model->whereNotNull($column, $value[1]);
} elseif ($value[0] !== '' || $value[1] !== '') {
$model = $model->whereBetween($column, $value);
}
} else {
$model = $model->where($column, $value);
}
}
if ($field) {
$model = $model->orderBy($field, $order);
}
return $model;
}
/**
* 格式化数据
* @param $query
* @param $format
* @param $limit
* @return Response
*/
protected function doFormat($query, $format, $limit): Response
{
$methods = [
'select' => 'formatSelect',
'tree' => 'formatTree',
'table_tree' => 'formatTableTree',
'normal' => 'formatNormal',
];
$paginator = $query->paginate($limit);
$total = $paginator->total();
$items = $paginator->items();
$format_function = $methods[$format] ?? 'formatNormal';
return call_user_func([$this, $format_function], $items, $total);
}
/**
* 插入前置方法
* @param Request $request
* @return array
* @throws BusinessException
*/
protected function insertInput(Request $request): array
{
$data = $this->inputFilter($request->post());
$password_filed = 'password';
if (isset($data[$password_filed])) {
$data[$password_filed] = Util::passwordHash($data[$password_filed]);
}
if (!Auth::isSupperAdmin() && $this->dataLimit) {
if (!empty($data[$this->dataLimitField])) {
$admin_id = $data[$this->dataLimitField];
if (!in_array($admin_id, Auth::getScopeAdminIds(true))) {
throw new BusinessException('无数据权限');
}
}
}
return $data;
}
/**
* 执行插入
* @param array $data
* @return mixed|null
*/
protected function doInsert(array $data)
{
$primary_key = $this->model->getKeyName();
$model_class = get_class($this->model);
$model = new $model_class;
foreach ($data as $key => $val) {
$model->{$key} = $val;
}
$model->save();
return $primary_key ? $model->$primary_key : null;
}
/**
* 更新前置方法
* @param Request $request
* @return array
* @throws BusinessException
*/
protected function updateInput(Request $request): array
{
$primary_key = $this->model->getKeyName();
$id = $request->post($primary_key);
$data = $this->inputFilter($request->post());
if (!Auth::isSupperAdmin() && $this->dataLimit && !empty($data[$this->dataLimitField])) {
$admin_id = $data[$this->dataLimitField];
if (!in_array($admin_id, Auth::getScopeAdminIds(true))) {
throw new BusinessException('无数据权限');
}
}
$password_filed = 'password';
if (isset($data[$password_filed])) {
// 密码为空,则不更新密码
if ($data[$password_filed] === '') {
unset($data[$password_filed]);
} else {
$data[$password_filed] = Util::passwordHash($data[$password_filed]);
}
}
unset($data[$primary_key]);
return [$id, $data];
}
/**
* 执行更新
* @param $id
* @param $data
* @return void
* @throws BusinessException
*/
protected function doUpdate($id, $data)
{
$model = $this->model->find($id);
if (!$model) {
throw new BusinessException('记录不存在', 2);
}
foreach ($data as $key => $val) {
$model->{$key} = $val;
}
$model->save();
}
/**
* 对用户输入表单过滤
* @param array $data
* @return array
* @throws BusinessException
*/
protected function inputFilter(array $data): array
{
$table = config('plugin.admin.database.connections.mysql.prefix') . $this->model->getTable();
$allow_column = $this->model->getConnection()->select("desc `$table`");
if (!$allow_column) {
throw new BusinessException('表不存在', 2);
}
$columns = array_column($allow_column, 'Type', 'Field');
foreach ($data as $col => $item) {
if (!isset($columns[$col])) {
unset($data[$col]);
continue;
}
// 非字符串类型传空则为null
if ($item === '' && strpos(strtolower($columns[$col]), 'varchar') === false && strpos(strtolower($columns[$col]), 'text') === false) {
$data[$col] = null;
}
if (is_array($item)) {
$data[$col] = implode(',', $item);
}
}
if (empty($data['created_at'])) {
unset($data['created_at']);
}
if (empty($data['updated_at'])) {
unset($data['updated_at']);
}
return $data;
}
/**
* 删除前置方法
* @param Request $request
* @return array
* @throws BusinessException
*/
protected function deleteInput(Request $request): array
{
$primary_key = $this->model->getKeyName();
if (!$primary_key) {
throw new BusinessException('该表无主键,不支持删除');
}
$ids = (array)$request->post($primary_key, []);
if (!Auth::isSupperAdmin() && $this->dataLimit) {
$admin_ids = $this->model->where($primary_key, $ids)->pluck($this->dataLimitField)->toArray();
if (array_diff($admin_ids, Auth::getScopeAdminIds(true))) {
throw new BusinessException('无数据权限');
}
}
return $ids;
}
/**
* 执行删除
* @param array $ids
* @return void
*/
protected function doDelete(array $ids)
{
if (!$ids) {
return;
}
$primary_key = $this->model->getKeyName();
$this->model->whereIn($primary_key, $ids)->delete();
}
/**
* 格式化树
* @param $items
* @return Response
*/
protected function formatTree($items): Response
{
$format_items = [];
foreach ($items as $item) {
$format_items[] = [
'name' => $item->title ?? $item->name ?? $item->id,
'value' => (string)$item->id,
'id' => $item->id,
'pid' => $item->pid,
];
}
$tree = new Tree($format_items);
return $this->json(0, 'ok', $tree->getTree());
}
/**
* 格式化表格树
* @param $items
* @return Response
*/
protected function formatTableTree($items): Response
{
$tree = new Tree($items);
return $this->json(0, 'ok', $tree->getTree());
}
/**
* 格式化下拉列表
* @param $items
* @return Response
*/
protected function formatSelect($items): Response
{
$formatted_items = [];
foreach ($items as $item) {
$formatted_items[] = [
'name' => $item->title ?? $item->name ?? $item->id,
'value' => $item->id
];
}
return $this->json(0, 'ok', $formatted_items);
}
/**
* 通用格式化
* @param $items
* @param $total
* @return Response
*/
protected function formatNormal($items, $total): Response
{
return json(['code' => 0, 'msg' => 'ok', 'count' => $total, 'data' => $items]);
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace plugin\admin\app\controller;
use support\Request;
use support\Response;
/**
* 开发辅助相关
*/
class DevController
{
/**
* 表单构建
* @return Response
*/
public function formBuild()
{
return view('dev/form-build');
}
}

View File

@ -0,0 +1,109 @@
<?php
namespace plugin\admin\app\controller;
use plugin\admin\app\model\Dict;
use plugin\admin\app\model\Option;
use support\exception\BusinessException;
use support\Request;
use support\Response;
/**
* 字典管理
*/
class DictController extends Base
{
/**
* 不需要授权的方法
*/
protected $noNeedAuth = ['get'];
/**
* 浏览
* @return Response
*/
public function index(): Response
{
return view('dict/index');
}
/**
* 查询
* @param Request $request
* @return Response
*/
public function select(Request $request): Response
{
$name = $request->get('name', '');
if ($name && is_string($name)) {
$items = Option::where('name', 'like', "dict_$name%")->get()->toArray();
} else {
$items = Option::where('name', 'like', 'dict_%')->get()->toArray();
}
foreach ($items as &$item) {
$item['name'] = Dict::optionNameTodictName($item['name']);
}
return $this->json(0, 'ok', $items);
}
/**
* 插入
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function insert(Request $request): Response
{
if ($request->method() === 'POST') {
$name = $request->post('name');
if (Dict::get($name)) {
return $this->json(1, '字典已经存在');
}
$values = (array)$request->post('value', []);
Dict::save($name, $values);
}
return view('dict/insert');
}
/**
* 更新
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function update(Request $request): Response
{
if ($request->method() === 'POST') {
$name = $request->post('name');
if (!Dict::get($name)) {
return $this->json(1, '字典不存在');
}
Dict::save($name, $request->post('value'));
}
return view('dict/update');
}
/**
* 删除
* @param Request $request
* @return Response
*/
public function delete(Request $request): Response
{
$names = (array)$request->post('name');
Dict::delete($names);
return $this->json(0);
}
/**
* 获取
* @param Request $request
* @param $name
* @return Response
*/
public function get(Request $request, $name): Response
{
return $this->json(0, 'ok', Dict::get($name));
}
}

View File

@ -0,0 +1,89 @@
<?php
namespace plugin\admin\app\controller;
use plugin\admin\app\common\Util;
use plugin\admin\app\model\User;
use support\exception\BusinessException;
use support\Request;
use support\Response;
use think\db\Where;
use Workerman\Worker;
class IndexController
{
/**
* 无需登录的方法
* @var string[]
*/
protected $noNeedLogin = ['index'];
/**
* 不需要鉴权的方法
* @var string[]
*/
protected $noNeedAuth = ['dashboard'];
/**
* 后台主页
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function index(Request $request): Response
{
clearstatcache();
if (!is_file(base_path('plugin/admin/config/database.php'))) {
return view('index/install');
}
$admin = admin();
if (!$admin) {
return view('account/login');
}
return view('index/index');
}
/**
* 仪表板
* @param Request $request
* @return Response
*/
public function dashboard(Request $request): Response
{
// 今日新增用户数
$today_user_count = User::where('created_at', '>', date('Y-m-d') . ' 00:00:00')->count();
// 7天内新增用户数
$day7_user_count = User::where('created_at', '>', date('Y-m-d H:i:s', time() - 7 * 24 * 60 * 60))->count();
// 30天内新增用户数
$day30_user_count = User::where('created_at', '>', date('Y-m-d H:i:s', time() - 30 * 24 * 60 * 60))->count();
// 总用户数
$user_count = User::count();
// mysql版本
$version = Util::db()->select('select VERSION() as version');
$mysql_version = $version[0]->version ?? 'unknown';
$day7_detail = [];
$now = time();
for ($i = 0; $i < 7; $i++) {
$date = date('Y-m-d', $now - 24 * 60 * 60 * $i);
$day7_detail[substr($date, 5)] = User::where('created_at', '>', "$date 00:00:00")
->where('created_at', '<', "$date 23:59:59")->count();
}
return view('index/dashboard', [
'today_user_count' => $today_user_count,
'day7_user_count' => $day7_user_count,
'day30_user_count' => $day30_user_count,
'user_count' => $user_count,
'php_version' => PHP_VERSION,
'workerman_version' => Worker::VERSION,
'webman_version' => Util::getPackageVersion('workerman/webman-framework'),
'admin_version' => config('plugin.admin.app.version'),
'mysql_version' => $mysql_version,
'os' => PHP_OS,
'day7_detail' => array_reverse($day7_detail),
]);
}
}

View File

@ -0,0 +1,392 @@
<?php
namespace plugin\admin\app\controller;
use Illuminate\Database\Capsule\Manager;
use plugin\admin\app\common\Util;
use plugin\admin\app\model\Admin;
use support\exception\BusinessException;
use support\Request;
use support\Response;
use Webman\Captcha\CaptchaBuilder;
/**
* 安装
*/
class InstallController extends Base
{
/**
* 不需要登录的方法
* @var string[]
*/
protected $noNeedLogin = ['step1', 'step2'];
/**
* 设置数据库
* @param Request $request
* @return Response
* @throws BusinessException|\Throwable
*/
public function step1(Request $request): Response
{
$database_config_file = base_path() . '/plugin/admin/config/database.php';
clearstatcache();
if (is_file($database_config_file)) {
return $this->json(1, '管理后台已经安装!如需重新安装,请删除该插件数据库配置文件并重启');
}
if (!class_exists(CaptchaBuilder::class) || !class_exists(Manager::class)) {
return $this->json(1, '请先restart重启webman后再进行此页面的设置');
}
$user = $request->post('user');
$password = $request->post('password');
$database = $request->post('database');
$host = $request->post('host');
$port = (int)$request->post('port') ?: 3306;
$overwrite = $request->post('overwrite');
try {
$db = $this->getPdo($host, $user, $password, $port);
$smt = $db->query("show databases like '$database'");
if (empty($smt->fetchAll())) {
$db->exec("create database $database");
}
$db->exec("use $database");
$smt = $db->query("show tables");
$tables = $smt->fetchAll();
} catch (\Throwable $e) {
if (stripos($e, 'Access denied for user')) {
return $this->json(1, '数据库用户名或密码错误');
}
if (stripos($e, 'Connection refused')) {
return $this->json(1, 'Connection refused. 请确认数据库IP端口是否正确数据库已经启动');
}
if (stripos($e, 'timed out')) {
return $this->json(1, '数据库连接超时请确认数据库IP端口是否正确安全组及防火墙已经放行端口');
}
throw $e;
}
$tables_to_install = [
'wa_admins',
'wa_admin_roles',
'wa_roles',
'wa_rules',
'wa_options',
'wa_users',
'wa_uploads',
];
$tables_exist = [];
foreach ($tables as $table) {
$tables_exist[] = current($table);
}
$tables_conflict = array_intersect($tables_to_install, $tables_exist);
if (!$overwrite) {
if ($tables_conflict) {
return $this->json(1, '以下表' . implode(',', $tables_conflict) . '已经存在,如需覆盖请选择强制覆盖');
}
} else {
foreach ($tables_conflict as $table) {
$db->exec("DROP TABLE `$table`");
}
}
$sql_file = base_path() . '/plugin/admin/install.sql';
if (!is_file($sql_file)) {
return $this->json(1, '数据库SQL文件不存在');
}
$sql_query = file_get_contents($sql_file);
$sql_query = $this->removeComments($sql_query);
$sql_query = $this->splitSqlFile($sql_query, ';');
foreach ($sql_query as $sql) {
$db->exec($sql);
}
// 导入菜单
$menus = include base_path() . '/plugin/admin/config/menu.php';
// 安装过程中没有数据库配置无法使用api\Menu::import()方法
$this->importMenu($menus, $db);
$config_content = <<<EOF
<?php
return [
'default' => 'mysql',
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => '$host',
'port' => '$port',
'database' => '$database',
'username' => '$user',
'password' => '$password',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_general_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
],
];
EOF;
file_put_contents($database_config_file, $config_content);
$think_orm_config = <<<EOF
<?php
return [
'default' => 'mysql',
'connections' => [
'mysql' => [
// 数据库类型
'type' => 'mysql',
// 服务器地址
'hostname' => '$host',
// 数据库名
'database' => '$database',
// 数据库用户名
'username' => '$user',
// 数据库密码
'password' => '$password',
// 数据库连接端口
'hostport' => $port,
// 数据库连接参数
'params' => [
// 连接超时3秒
\PDO::ATTR_TIMEOUT => 3,
],
// 数据库编码默认采用utf8
'charset' => 'utf8mb4',
// 数据库表前缀
'prefix' => '',
// 断线重连
'break_reconnect' => true,
// 关闭SQL监听日志
'trigger_sql' => true,
// 自定义分页类
'bootstrap' => ''
],
],
];
EOF;
file_put_contents(base_path() . '/plugin/admin/config/thinkorm.php', $think_orm_config);
// 尝试reload
if (function_exists('posix_kill')) {
set_error_handler(function () {});
posix_kill(posix_getppid(), SIGUSR1);
restore_error_handler();
}
return $this->json(0);
}
/**
* 设置管理员
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function step2(Request $request): Response
{
$username = $request->post('username');
$password = $request->post('password');
$password_confirm = $request->post('password_confirm');
if ($password != $password_confirm) {
return $this->json(1, '两次密码不一致');
}
if (!is_file($config_file = base_path() . '/plugin/admin/config/database.php')) {
return $this->json(1, '请先完成第一步数据库配置');
}
$config = include $config_file;
$connection = $config['connections']['mysql'];
$pdo = $this->getPdo($connection['host'], $connection['username'], $connection['password'], $connection['port'], $connection['database']);
if ($pdo->query('select * from `wa_admins`')->fetchAll()) {
return $this->json(1, '后台已经安装完毕,无法通过此页面创建管理员');
}
$smt = $pdo->prepare("insert into `wa_admins` (`username`, `password`, `nickname`, `created_at`, `updated_at`) values (:username, :password, :nickname, :created_at, :updated_at)");
$time = date('Y-m-d H:i:s');
$data = [
'username' => $username,
'password' => Util::passwordHash($password),
'nickname' => '超级管理员',
'created_at' => $time,
'updated_at' => $time
];
foreach ($data as $key => $value) {
$smt->bindValue($key, $value);
}
$smt->execute();
$admin_id = $pdo->lastInsertId();
$smt = $pdo->prepare("insert into `wa_admin_roles` (`role_id`, `admin_id`) values (:role_id, :admin_id)");
$smt->bindValue('role_id', 1);
$smt->bindValue('admin_id', $admin_id);
$smt->execute();
$request->session()->flush();
return $this->json(0);
}
/**
* 添加菜单
* @param array $menu
* @param \PDO $pdo
* @return int
*/
protected function addMenu(array $menu, \PDO $pdo): int
{
$allow_columns = ['title', 'key', 'icon', 'href', 'pid', 'weight', 'type'];
$data = [];
foreach ($allow_columns as $column) {
if (isset($menu[$column])) {
$data[$column] = $menu[$column];
}
}
$time = date('Y-m-d H:i:s');
$data['created_at'] = $data['updated_at'] = $time;
$values = [];
foreach ($data as $k => $v) {
$values[] = ":$k";
}
$columns = array_keys($data);
foreach ($columns as $k => $column) {
$columns[$k] = "`$column`";
}
$sql = "insert into wa_rules (" .implode(',', $columns). ") values (" . implode(',', $values) . ")";
$smt = $pdo->prepare($sql);
foreach ($data as $key => $value) {
$smt->bindValue($key, $value);
}
$smt->execute();
return $pdo->lastInsertId();
}
/**
* 导入菜单
* @param array $menu_tree
* @param \PDO $pdo
* @return void
*/
protected function importMenu(array $menu_tree, \PDO $pdo)
{
if (is_numeric(key($menu_tree)) && !isset($menu_tree['key'])) {
foreach ($menu_tree as $item) {
$this->importMenu($item, $pdo);
}
return;
}
$children = $menu_tree['children'] ?? [];
unset($menu_tree['children']);
$smt = $pdo->prepare("select * from wa_rules where `key`=:key limit 1");
$smt->execute(['key' => $menu_tree['key']]);
$old_menu = $smt->fetch();
if ($old_menu) {
$pid = $old_menu['id'];
$params = [
'title' => $menu_tree['title'],
'icon' => $menu_tree['icon'] ?? '',
'key' => $menu_tree['key'],
];
$sql = "update wa_rules set title=:title, icon=:icon where `key`=:key";
$smt = $pdo->prepare($sql);
$smt->execute($params);
} else {
$pid = $this->addMenu($menu_tree, $pdo);
}
foreach ($children as $menu) {
$menu['pid'] = $pid;
$this->importMenu($menu, $pdo);
}
}
/**
* 去除sql文件中的注释
* @param $sql
* @return string
*/
protected function removeComments($sql): string
{
return preg_replace("/(\n--[^\n]*)/","", $sql);
}
/**
* 分割sql文件
* @param $sql
* @param $delimiter
* @return array
*/
function splitSqlFile($sql, $delimiter): array
{
$tokens = explode($delimiter, $sql);
$output = array();
$matches = array();
$token_count = count($tokens);
for ($i = 0; $i < $token_count; $i++) {
if (($i != ($token_count - 1)) || (strlen($tokens[$i] > 0))) {
$total_quotes = preg_match_all("/'/", $tokens[$i], $matches);
$escaped_quotes = preg_match_all("/(?<!\\\\)(\\\\\\\\)*\\\\'/", $tokens[$i], $matches);
$unescaped_quotes = $total_quotes - $escaped_quotes;
if (($unescaped_quotes % 2) == 0) {
$output[] = $tokens[$i];
$tokens[$i] = "";
} else {
$temp = $tokens[$i] . $delimiter;
$tokens[$i] = "";
$complete_stmt = false;
for ($j = $i + 1; (!$complete_stmt && ($j < $token_count)); $j++) {
$total_quotes = preg_match_all("/'/", $tokens[$j], $matches);
$escaped_quotes = preg_match_all("/(?<!\\\\)(\\\\\\\\)*\\\\'/", $tokens[$j], $matches);
$unescaped_quotes = $total_quotes - $escaped_quotes;
if (($unescaped_quotes % 2) == 1) {
$output[] = $temp . $tokens[$j];
$tokens[$j] = "";
$temp = "";
$complete_stmt = true;
$i = $j;
} else {
$temp .= $tokens[$j] . $delimiter;
$tokens[$j] = "";
}
}
}
}
}
return $output;
}
/**
* 获取pdo连接
* @param $host
* @param $username
* @param $password
* @param $port
* @param $database
* @return \PDO
*/
protected function getPdo($host, $username, $password, $port, $database = null): \PDO
{
$dsn = "mysql:host=$host;port=$port;";
if ($database) {
$dsn .= "dbname=$database";
}
$params = [
\PDO::MYSQL_ATTR_INIT_COMMAND => "set names utf8mb4",
\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
\PDO::ATTR_EMULATE_PREPARES => false,
\PDO::ATTR_TIMEOUT => 5,
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
];
return new \PDO($dsn, $username, $password, $params);
}
}

View File

@ -0,0 +1,549 @@
<?php
namespace plugin\admin\app\controller;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use plugin\admin\app\common\Util;
use plugin\admin\app\controller\Base;
use process\Monitor;
use support\exception\BusinessException;
use support\Log;
use support\Request;
use support\Response;
use ZIPARCHIVE;
use function array_diff;
use function ini_get;
use function scandir;
use const DIRECTORY_SEPARATOR;
use const PATH_SEPARATOR;
class PluginController extends Base
{
/**
* 不需要鉴权的方法
* @var string[]
*/
protected $noNeedAuth = ['schema', 'captcha'];
/**
* @param Request $request
* @return string
* @throws GuzzleException
*/
public function index(Request $request)
{
$client = $this->httpClient();
$response = $client->get("/webman-admin/apps");
return (string)$response->getBody();
}
/**
* 列表
* @param Request $request
* @return Response
* @throws GuzzleException
*/
public function list(Request $request): Response
{
$installed = $this->getLocalPlugins();
$client = $this->httpClient();
$query = $request->get();
$query['version'] = $this->getAdminVersion();
$response = $client->get('/api/app/list', ['query' => $query]);
$content = $response->getBody()->getContents();
$data = json_decode($content, true);
if (!$data) {
$msg = "/api/app/list return $content";
echo "msg\r\n";
Log::error($msg);
return $this->json(1, '获取数据出错');
}
$disabled = is_phar();
foreach ($data['data']['items'] as $key => $item) {
$name = $item['name'];
$data['data']['items'][$key]['installed'] = $installed[$name] ?? 0;
$data['data']['items'][$key]['disabled'] = $disabled;
}
$items = $data['data']['items'];
$count = $data['data']['total'];
return json(['code' => 0, 'msg' => 'ok', 'data' => $items, 'count' => $count]);
}
/**
* 安装
* @param Request $request
* @return Response
* @throws GuzzleException|BusinessException
*/
public function install(Request $request): Response
{
$name = $request->post('name');
$version = $request->post('version');
$installed_version = $this->getPluginVersion($name);
if (!$name || !$version) {
return $this->json(1, '缺少参数');
}
$user = session('app-plugin-user');
if (!$user) {
return $this->json(-1, '请登录');
}
// 获取下载zip文件url
$data = $this->getDownloadUrl($name, $version);
if ($data['code'] != 0) {
return $this->json($data['code'], $data['msg'], $data['data'] ?? []);
}
// 下载zip文件
$base_path = base_path() . "/plugin/$name";
$zip_file = "$base_path.zip";
$extract_to = base_path() . '/plugin/';
$this->downloadZipFile($data['data']['url'], $zip_file);
$has_zip_archive = class_exists(ZipArchive::class, false);
if (!$has_zip_archive) {
$cmd = $this->getUnzipCmd($zip_file, $extract_to);
if (!$cmd) {
throw new BusinessException('请给php安装zip模块或者给系统安装unzip命令');
}
if (!function_exists('proc_open')) {
throw new BusinessException('请解除proc_open函数的禁用或者给php安装zip模块');
}
}
Util::pauseFileMonitor();
try {
// 解压zip到plugin目录
if ($has_zip_archive) {
$zip = new ZipArchive;
$zip->open($zip_file, ZIPARCHIVE::CHECKCONS);
}
$context = null;
$install_class = "\\plugin\\$name\\api\\Install";
if ($installed_version) {
// 执行beforeUpdate
if (class_exists($install_class) && method_exists($install_class, 'beforeUpdate')) {
$context = call_user_func([$install_class, 'beforeUpdate'], $installed_version, $version);
}
}
if (!empty($zip)) {
$zip->extractTo(base_path() . '/plugin/');
unset($zip);
} else {
$this->unzipWithCmd($cmd);
}
unlink($zip_file);
if ($installed_version) {
// 执行update更新
if (class_exists($install_class) && method_exists($install_class, 'update')) {
call_user_func([$install_class, 'update'], $installed_version, $version, $context);
}
} else {
// 执行install安装
if (class_exists($install_class) && method_exists($install_class, 'install')) {
call_user_func([$install_class, 'install'], $version);
}
}
} finally {
Util::resumeFileMonitor();
}
Util::reloadWebman();
return $this->json(0);
}
/**
* 卸载
* @param Request $request
* @return Response
*/
public function uninstall(Request $request): Response
{
$name = $request->post('name');
$version = $request->post('version');
if (!$name || !preg_match('/^[a-zA-Z0-9_]+$/', $name)) {
return $this->json(1, '参数错误');
}
// 获得插件路径
clearstatcache();
$path = get_realpath(base_path() . "/plugin/$name");
if (!$path || !is_dir($path)) {
return $this->json(1, '已经删除');
}
// 执行uninstall卸载
$install_class = "\\plugin\\$name\\api\\Install";
if (class_exists($install_class) && method_exists($install_class, 'uninstall')) {
call_user_func([$install_class, 'uninstall'], $version);
}
// 删除目录
clearstatcache();
if (is_dir($path)) {
$monitor_support_pause = method_exists(Monitor::class, 'pause');
if ($monitor_support_pause) {
Monitor::pause();
}
try {
$this->rmDir($path);
} finally {
if ($monitor_support_pause) {
Monitor::resume();
}
}
}
clearstatcache();
Util::reloadWebman();
return $this->json(0);
}
/**
* 支付
* @param Request $request
* @return string|Response
* @throws GuzzleException
*/
public function pay(Request $request)
{
$app = $request->get('app');
if (!$app) {
return response('app not found');
}
$token = session('app-plugin-token');
if (!$token) {
return 'Please login workerman.net';
}
$client = $this->httpClient();
$response = $client->get("/payment/app/$app/$token");
return (string)$response->getBody();
}
/**
* 登录验证码
* @param Request $request
* @return Response
* @throws GuzzleException
*/
public function captcha(Request $request): Response
{
$client = $this->httpClient();
$response = $client->get('/user/captcha?type=login');
$sid_str = $response->getHeaderLine('Set-Cookie');
if (preg_match('/PHPSID=([a-zA-Z_0-9]+?);/', $sid_str, $match)) {
$sid = $match[1];
session()->set('app-plugin-token', $sid);
}
return response($response->getBody()->getContents())->withHeader('Content-Type', 'image/jpeg');
}
/**
* 登录官网
* @param Request $request
* @return Response|string
* @throws GuzzleException
*/
public function login(Request $request)
{
$client = $this->httpClient();
if ($request->method() === 'GET') {
$response = $client->get("/webman-admin/login");
return (string)$response->getBody();
}
$response = $client->post('/api/user/login', [
'form_params' => [
'email' => $request->post('username'),
'password' => $request->post('password'),
'captcha' => $request->post('captcha')
]
]);
$content = $response->getBody()->getContents();
$data = json_decode($content, true);
if (!$data) {
$msg = "/api/user/login return $content";
echo "msg\r\n";
Log::error($msg);
return $this->json(1, '发生错误');
}
if ($data['code'] != 0) {
return $this->json($data['code'], $data['msg']);
}
session()->set('app-plugin-user', [
'uid' => $data['data']['uid']
]);
return $this->json(0);
}
/**
* 获取zip下载url
* @param $name
* @param $version
* @return mixed
* @throws BusinessException
* @throws GuzzleException
*/
protected function getDownloadUrl($name, $version)
{
$client = $this->httpClient();
$response = $client->get("/app/download/$name?version=$version");
$content = $response->getBody()->getContents();
$data = json_decode($content, true);
if (!$data) {
$msg = "/api/app/download return $content";
Log::error($msg);
throw new BusinessException('访问官方接口失败 ' . $response->getStatusCode() . ' ' . $response->getReasonPhrase());
}
if ($data['code'] && $data['code'] != -1 && $data['code'] != -2) {
throw new BusinessException($data['msg']);
}
if ($data['code'] == 0 && !isset($data['data']['url'])) {
throw new BusinessException('官方接口返回数据错误');
}
return $data;
}
/**
* 下载zip
* @param $url
* @param $file
* @return void
* @throws BusinessException
* @throws GuzzleException
*/
protected function downloadZipFile($url, $file)
{
$client = $this->downloadClient();
$response = $client->get($url);
$body = $response->getBody();
$status = $response->getStatusCode();
if ($status == 404) {
throw new BusinessException('安装包不存在');
}
$zip_content = $body->getContents();
if (empty($zip_content)) {
throw new BusinessException('安装包不存在');
}
file_put_contents($file, $zip_content);
}
/**
* 获取系统支持的解压命令
* @param $zip_file
* @param $extract_to
* @return mixed|string|null
*/
protected function getUnzipCmd($zip_file, $extract_to)
{
if ($cmd = $this->findCmd('unzip')) {
$cmd = "$cmd -o -qq $zip_file -d $extract_to";
} else if ($cmd = $this->findCmd('7z')) {
$cmd = "$cmd x -bb0 -y $zip_file -o$extract_to";
} else if ($cmd = $this->findCmd('7zz')) {
$cmd = "$cmd x -bb0 -y $zip_file -o$extract_to";
}
return $cmd;
}
/**
* 使用解压命令解压
* @param $cmd
* @return void
* @throws BusinessException
*/
protected function unzipWithCmd($cmd)
{
$desc = [
0 => ["pipe", "r"],
1 => ["pipe", "w"],
2 => ["pipe", "w"],
];
$handler = proc_open($cmd, $desc, $pipes);
if (!is_resource($handler)) {
throw new BusinessException("解压zip时出错:proc_open调用失败");
}
$err = fread($pipes[2], 1024);
fclose($pipes[2]);
proc_close($handler);
if ($err) {
throw new BusinessException("解压zip时出错:$err");
}
}
/**
* 获取已安装的插件列表
* @return array
*/
protected function getLocalPlugins(): array
{
clearstatcache();
$installed = [];
$plugin_names = array_diff(scandir(base_path() . '/plugin/'), array('.', '..')) ?: [];
foreach ($plugin_names as $plugin_name) {
if (is_dir(base_path() . "/plugin/$plugin_name") && $version = $this->getPluginVersion($plugin_name)) {
$installed[$plugin_name] = $version;
}
}
return $installed;
}
/**
* 获取已安装的插件列表
* @param Request $request
* @return Response
*/
public function getInstalledPlugins(Request $request): Response
{
return $this->json(0, 'ok', $this->getLocalPlugins());
}
/**
* 获取本地插件版本
* @param $name
* @return array|mixed|null
*/
protected function getPluginVersion($name)
{
if (!is_file($file = base_path() . "/plugin/$name/config/app.php")) {
return null;
}
$config = include $file;
return $config['version'] ?? null;
}
/**
* 获取webman/admin版本
* @return string
*/
protected function getAdminVersion(): string
{
return config('plugin.admin.app.version', '');
}
/**
* 删除目录
* @param $src
* @return void
*/
protected function rmDir($src)
{
$dir = opendir($src);
while (false !== ($file = readdir($dir))) {
if (($file != '.') && ($file != '..')) {
$full = $src . '/' . $file;
if (is_dir($full)) {
$this->rmDir($full);
} else {
unlink($full);
}
}
}
closedir($dir);
rmdir($src);
}
/**
* 获取httpclient
* @return Client
*/
protected function httpClient(): Client
{
// 下载zip
$options = [
'base_uri' => config('plugin.admin.app.plugin_market_host'),
'timeout' => 30,
'connect_timeout' => 5,
'verify' => false,
'http_errors' => false,
'headers' => [
'Referer' => \request()->fullUrl(),
'User-Agent' => 'webman-app-plugin',
'Accept' => 'application/json;charset=UTF-8',
]
];
if ($token = session('app-plugin-token')) {
$options['headers']['Cookie'] = "PHPSID=$token;";
}
return new Client($options);
}
/**
* 获取下载httpclient
* @return Client
*/
protected function downloadClient(): Client
{
// 下载zip
$options = [
'timeout' => 30,
'connect_timeout' => 5,
'verify' => false,
'http_errors' => false,
'headers' => [
'Referer' => \request()->fullUrl(),
'User-Agent' => 'webman-app-plugin',
]
];
if ($token = session('app-plugin-token')) {
$options['headers']['Cookie'] = "PHPSID=$token;";
}
return new Client($options);
}
/**
* 查找系统命令
* @param string $name
* @param string|null $default
* @param array $extraDirs
* @return mixed|string|null
*/
protected function findCmd(string $name, string $default = null, array $extraDirs = [])
{
if (ini_get('open_basedir')) {
$searchPath = array_merge(explode(PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs);
$dirs = [];
foreach ($searchPath as $path) {
if (@is_dir($path)) {
$dirs[] = $path;
} else {
if (basename($path) == $name && @is_executable($path)) {
return $path;
}
}
}
} else {
$dirs = array_merge(
explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
$extraDirs
);
}
$suffixes = [''];
if ('\\' === DIRECTORY_SEPARATOR) {
$pathExt = getenv('PATHEXT');
$suffixes = array_merge($pathExt ? explode(PATH_SEPARATOR, $pathExt) : ['.exe', '.bat', '.cmd', '.com'], $suffixes);
}
foreach ($suffixes as $suffix) {
foreach ($dirs as $dir) {
if (@is_file($file = $dir . DIRECTORY_SEPARATOR . $name . $suffix) && ('\\' === DIRECTORY_SEPARATOR || @is_executable($file))) {
return $file;
}
}
}
return $default;
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace plugin\admin\app\controller;
use support\Request;
use support\Response;
use plugin\admin\app\model\Project;
use plugin\admin\app\controller\Crud;
use support\exception\BusinessException;
/**
* 项目
*/
class ProjectController extends Crud
{
/**
* @var Project
*/
protected $model = null;
/**
* 构造函数
* @return void
*/
public function __construct()
{
$this->model = new Project;
}
/**
* 浏览
* @return Response
*/
public function index(): Response
{
return view('project/index');
}
/**
* 插入
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function insert(Request $request): Response
{
if ($request->method() === 'POST') {
return parent::insert($request);
}
return view('project/insert');
}
/**
* 更新
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function update(Request $request): Response
{
if ($request->method() === 'POST') {
return parent::update($request);
}
return view('project/update');
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace plugin\admin\app\controller;
use support\Request;
use support\Response;
use plugin\admin\app\model\Projectdata;
use plugin\admin\app\controller\Crud;
use support\exception\BusinessException;
/**
* 项目内容
*/
class ProjectdataController extends Crud
{
/**
* @var Projectdata
*/
protected $model = null;
/**
* 构造函数
* @return void
*/
public function __construct()
{
$this->model = new Projectdata;
}
/**
* 浏览
* @return Response
*/
public function index(): Response
{
return view('projectdata/index');
}
/**
* 插入
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function insert(Request $request): Response
{
if ($request->method() === 'POST') {
return parent::insert($request);
}
return view('projectdata/insert');
}
/**
* 更新
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function update(Request $request): Response
{
if ($request->method() === 'POST') {
return parent::update($request);
}
return view('projectdata/update');
}
}

View File

@ -0,0 +1,252 @@
<?php
namespace plugin\admin\app\controller;
use plugin\admin\app\common\Auth;
use plugin\admin\app\common\Tree;
use plugin\admin\app\model\Role;
use plugin\admin\app\model\Rule;
use support\exception\BusinessException;
use support\Request;
use support\Response;
/**
* 角色管理
*/
class RoleController extends Crud
{
/**
* 不需要鉴权的方法
* @var array
*/
protected $noNeedAuth = ['select'];
/**
* @var Role
*/
protected $model = null;
/**
* 构造函数
*/
public function __construct()
{
$this->model = new Role;
}
/**
* 浏览
* @return Response
*/
public function index(): Response
{
return view('role/index');
}
/**
* 查询
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function select(Request $request): Response
{
$id = $request->get('id');
[$where, $format, $limit, $field, $order] = $this->selectInput($request);
$role_ids = Auth::getScopeRoleIds(true);
if (!$id) {
$where['id'] = ['in', $role_ids];
} elseif (!in_array($id, $role_ids)) {
throw new BusinessException('无权限');
}
$query = $this->doSelect($where, $field, $order);
return $this->doFormat($query, $format, $limit);
}
/**
* 插入
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function insert(Request $request): Response
{
if ($request->method() === 'POST') {
$data = $this->insertInput($request);
$pid = $data['pid'] ?? null;
if (!$pid) {
return $this->json(1, '请选择父级角色组');
}
if (!Auth::isSupperAdmin() && !in_array($pid, Auth::getScopeRoleIds(true))) {
return $this->json(1, '父级角色组超出权限范围');
}
$this->checkRules($pid, $data['rules'] ?? '');
$id = $this->doInsert($data);
return $this->json(0, 'ok', ['id' => $id]);
}
return view('role/insert');
}
/**
* 更新
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function update(Request $request): Response
{
if ($request->method() === 'GET') {
return view('role/update');
}
[$id, $data] = $this->updateInput($request);
$is_supper_admin = Auth::isSupperAdmin();
$descendant_role_ids = Auth::getScopeRoleIds();
if (!$is_supper_admin && !in_array($id, $descendant_role_ids)) {
return $this->json(1, '无数据权限');
}
$role = Role::find($id);
if (!$role) {
return $this->json(1, '数据不存在');
}
$is_supper_role = $role->rules === '*';
// 超级角色组不允许更改rules pid 字段
if ($is_supper_role) {
unset($data['rules'], $data['pid']);
}
if (key_exists('pid', $data)) {
$pid = $data['pid'];
if (!$pid) {
return $this->json(1, '请选择父级角色组');
}
if ($pid == $id) {
return $this->json(1, '父级不能是自己');
}
if (!$is_supper_admin && !in_array($pid, Auth::getScopeRoleIds(true))) {
return $this->json(1, '父级超出权限范围');
}
} else {
$pid = $role->pid;
}
if (!$is_supper_role) {
$this->checkRules($pid, $data['rules'] ?? '');
}
$this->doUpdate($id, $data);
// 删除所有子角色组中已经不存在的权限
if (!$is_supper_role) {
$tree = new Tree(Role::select(['id', 'pid'])->get());
$descendant_roles = $tree->getDescendant([$id]);
$descendant_role_ids = array_column($descendant_roles, 'id');
$rule_ids = $data['rules'] ? explode(',', $data['rules']) : [];
foreach ($descendant_role_ids as $role_id) {
$tmp_role = Role::find($role_id);
$tmp_rule_ids = $role->getRuleIds();
$tmp_rule_ids = array_intersect($rule_ids, $tmp_rule_ids);
$tmp_role->rules = implode(',', $tmp_rule_ids);
$tmp_role->save();
}
}
return $this->json(0);
}
/**
* 删除
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function delete(Request $request): Response
{
$ids = $this->deleteInput($request);
if (in_array(1, $ids)) {
return $this->json(1, '无法删除超级管理员角色');
}
if (!Auth::isSupperAdmin() && array_diff($ids, Auth::getScopeRoleIds())) {
return $this->json(1, '无删除权限');
}
$tree = new Tree(Role::get());
$descendants = $tree->getDescendant($ids);
if ($descendants) {
$ids = array_merge($ids, array_column($descendants, 'id'));
}
$this->doDelete($ids);
return $this->json(0);
}
/**
* 获取角色权限
* @param Request $request
* @return Response
*/
public function rules(Request $request): Response
{
$role_id = $request->get('id');
if (empty($role_id)) {
return $this->json(0, 'ok', []);
}
if (!Auth::isSupperAdmin() && !in_array($role_id, Auth::getScopeRoleIds(true))) {
return $this->json(1, '角色组超出权限范围');
}
$rule_id_string = Role::where('id', $role_id)->value('rules');
if ($rule_id_string === '') {
return $this->json(0, 'ok', []);
}
$rules = Rule::get();
$include = [];
if ($rule_id_string !== '*') {
$include = explode(',', $rule_id_string);
}
$items = [];
foreach ($rules as $item) {
$items[] = [
'name' => $item->title ?? $item->name ?? $item->id,
'value' => (string)$item->id,
'id' => $item->id,
'pid' => $item->pid,
];
}
$tree = new Tree($items);
return $this->json(0, 'ok', $tree->getTree($include));
}
/**
* 检查权限字典是否合法
* @param int $role_id
* @param $rule_ids
* @return void
* @throws BusinessException
*/
protected function checkRules(int $role_id, $rule_ids)
{
if ($rule_ids) {
$rule_ids = explode(',', $rule_ids);
if (in_array('*', $rule_ids)) {
throw new BusinessException('非法数据');
}
$rule_exists = Rule::whereIn('id', $rule_ids)->pluck('id');
if (count($rule_exists) != count($rule_ids)) {
throw new BusinessException('权限不存在');
}
$rule_id_string = Role::where('id', $role_id)->value('rules');
if ($rule_id_string === '') {
throw new BusinessException('数据超出权限范围');
}
if ($rule_id_string === '*') {
return;
}
$legal_rule_ids = explode(',', $rule_id_string);
if (array_diff($rule_ids, $legal_rule_ids)) {
throw new BusinessException('数据超出权限范围');
}
}
}
}

View File

@ -0,0 +1,329 @@
<?php
namespace plugin\admin\app\controller;
use plugin\admin\app\common\Tree;
use plugin\admin\app\common\Util;
use plugin\admin\app\model\Role;
use plugin\admin\app\model\Rule;
use support\exception\BusinessException;
use support\Request;
use support\Response;
/**
* 权限菜单
*/
class RuleController extends Crud
{
/**
* 不需要权限的方法
*
* @var string[]
*/
protected $noNeedAuth = ['get', 'permission'];
/**
* @var Rule
*/
protected $model = null;
/**
* 构造函数
*/
public function __construct()
{
$this->model = new Rule;
}
/**
* 浏览
* @return Response
*/
public function index(): Response
{
return view('rule/index');
}
/**
* 查询
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function select(Request $request): Response
{
$this->syncRules();
return parent::select($request);
}
/**
* 获取菜单
* @param Request $request
* @return Response
*/
function get(Request $request): Response
{
$rules = $this->getRules(admin('roles'));
$types = $request->get('type', '0,1');
$types = is_string($types) ? explode(',', $types) : [0, 1];
$items = Rule::orderBy('weight', 'desc')->get()->toArray();
$formatted_items = [];
foreach ($items as $item) {
$item['pid'] = (int)$item['pid'];
$item['name'] = $item['title'];
$item['value'] = $item['id'];
$item['icon'] = $item['icon'] ? "layui-icon {$item['icon']}" : '';
$formatted_items[] = $item;
}
$tree = new Tree($formatted_items);
$tree_items = $tree->getTree();
// 超级管理员权限为 *
if (!in_array('*', $rules)) {
$this->removeNotContain($tree_items, 'id', $rules);
}
$this->removeNotContain($tree_items, 'type', $types);
return $this->json(0, 'ok', Tree::arrayValues($tree_items));
}
/**
* 获取权限
* @param Request $request
* @return Response
*/
public function permission(Request $request): Response
{
$rules = $this->getRules(admin('roles'));
// 超级管理员
if (in_array('*', $rules)) {
return $this->json(0, 'ok', ['*']);
}
$keys = Rule::whereIn('id', $rules)->pluck('key');
$permissions = [];
foreach ($keys as $key) {
if (!$key = Util::controllerToUrlPath($key)) {
continue;
}
$code = str_replace('/', '.', trim($key, '/'));
$permissions[] = $code;
}
return $this->json(0, 'ok', $permissions);
}
/**
* 根据类同步规则到数据库
* @return void
*/
protected function syncRules()
{
$items = $this->model->where('key', 'like', '%\\\\%')->get()->keyBy('key');
$methods_in_db = [];
$methods_in_files = [];
foreach ($items as $item) {
$class = $item->key;
if (strpos($class, '@')) {
$methods_in_db[$class] = $class;
continue;
}
if (class_exists($class)) {
$reflection = new \ReflectionClass($class);
$properties = $reflection->getDefaultProperties();
$no_need_auth = array_merge($properties['noNeedLogin'] ?? [], $properties['noNeedAuth'] ?? []);
$class = $reflection->getName();
$pid = $item->id;
$methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);
foreach ($methods as $method) {
$method_name = $method->getName();
if (strtolower($method_name) === 'index' || strpos($method_name, '__') === 0 || in_array($method_name, $no_need_auth)) {
continue;
}
$name = "$class@$method_name";
$methods_in_files[$name] = $name;
$title = Util::getCommentFirstLine($method->getDocComment()) ?: $method_name;
$menu = $items[$name] ?? [];
if ($menu) {
if ($menu->title != $title) {
Rule::where('key', $name)->update(['title' => $title]);
}
continue;
}
$menu = new Rule;
$menu->pid = $pid;
$menu->key = $name;
$menu->title = $title;
$menu->type = 2;
$menu->save();
}
}
}
// 从数据库中删除已经不存在的方法
$menu_names_to_del = array_diff($methods_in_db, $methods_in_files);
if ($menu_names_to_del) {
//Rule::whereIn('key', $menu_names_to_del)->delete();
}
}
/**
* 查询前置方法
* @param Request $request
* @return array
* @throws BusinessException
*/
protected function selectInput(Request $request): array
{
[$where, $format, $limit, $field, $order] = parent::selectInput($request);
// 允许通过type=0,1格式传递菜单类型
$types = $request->get('type');
if ($types && is_string($types)) {
$where['type'] = ['in', explode(',', $types)];
}
// 默认weight排序
if (!$field) {
$field = 'weight';
$order = 'desc';
}
return [$where, $format, $limit, $field, $order];
}
/**
* 添加
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function insert(Request $request): Response
{
if ($request->method() === 'GET') {
return view('rule/insert');
}
$data = $this->insertInput($request);
if (empty($data['type'])) {
$data['type'] = strpos($data['key'], '\\') ? 1 : 0;
}
$data['key'] = str_replace('\\\\', '\\', $data['key']);
$key = $data['key'] ?? '';
if ($this->model->where('key', $key)->first()) {
return $this->json(1, "菜单标识 $key 已经存在");
}
$data['pid'] = empty($data['pid']) ? 0 : $data['pid'];
$this->doInsert($data);
return $this->json(0);
}
/**
* 更新
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function update(Request $request): Response
{
if ($request->method() === 'GET') {
return view('rule/update');
}
[$id, $data] = $this->updateInput($request);
if (!$row = $this->model->find($id)) {
return $this->json(2, '记录不存在');
}
if (isset($data['pid'])) {
$data['pid'] = $data['pid'] ?: 0;
if ($data['pid'] == $row['id']) {
return $this->json(2, '不能将自己设置为上级菜单');
}
}
if (isset($data['key'])) {
$data['key'] = str_replace('\\\\', '\\', $data['key']);
}
$this->doUpdate($id, $data);
return $this->json(0);
}
/**
* 删除
* @param Request $request
* @return Response
*/
public function delete(Request $request): Response
{
$ids = $this->deleteInput($request);
// 子规则一起删除
$delete_ids = $children_ids = $ids;
while($children_ids) {
$children_ids = $this->model->whereIn('pid', $children_ids)->pluck('id')->toArray();
$delete_ids = array_merge($delete_ids, $children_ids);
}
$this->doDelete($delete_ids);
return $this->json(0);
}
/**
* 移除不包含某些数据的数组
* @param $array
* @param $key
* @param $values
* @return void
*/
protected function removeNotContain(&$array, $key, $values)
{
foreach ($array as $k => &$item) {
if (!is_array($item)) {
continue;
}
if (!$this->arrayContain($item, $key, $values)) {
unset($array[$k]);
} else {
if (!isset($item['children'])) {
continue;
}
$this->removeNotContain($item['children'], $key, $values);
}
}
}
/**
* 判断数组是否包含某些数据
* @param $array
* @param $key
* @param $values
* @return bool
*/
protected function arrayContain(&$array, $key, $values): bool
{
if (!is_array($array)) {
return false;
}
if (isset($array[$key]) && in_array($array[$key], $values)) {
return true;
}
if (!isset($array['children'])) {
return false;
}
foreach ($array['children'] as $item) {
if ($this->arrayContain($item, $key, $values)) {
return true;
}
}
return false;
}
/**
* 获取权限规则
* @param $roles
* @return array
*/
protected function getRules($roles): array
{
$rules_strings = $roles ? Role::whereIn('id', $roles)->pluck('rules') : [];
$rules = [];
foreach ($rules_strings as $rule_string) {
if (!$rule_string) {
continue;
}
$rules = array_merge($rules, explode(',', $rule_string));
}
return $rules;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,309 @@
<?php
namespace plugin\admin\app\controller;
use Exception;
use Intervention\Image\ImageManagerStatic as Image;
use plugin\admin\app\controller\Base;
use plugin\admin\app\controller\Crud;
use plugin\admin\app\model\Upload;
use support\exception\BusinessException;
use support\Request;
use support\Response;
/**
* 附件管理
*/
class UploadController extends Crud
{
/**
* @var Upload
*/
protected $model = null;
/**
* 只返回当前管理员数据
* @var string
*/
protected $dataLimit = 'personal';
/**
* 构造函数
* @return void
*/
public function __construct()
{
$this->model = new Upload;
}
/**
* 浏览
* @return Response
*/
public function index(): Response
{
return view('upload/index');
}
/**
* 浏览附件
* @return Response
*/
public function attachment(): Response
{
return view('upload/attachment');
}
/**
* 查询附件
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function select(Request $request): Response
{
[$where, $format, $limit, $field, $order] = $this->selectInput($request);
if (!empty($where['ext']) && is_string($where['ext'])) {
$where['ext'] = ['in', explode(',', $where['ext'])];
}
if (!empty($where['name']) && is_string($where['name'])) {
$where['name'] = ['like', "%{$where['name']}%"];
}
$query = $this->doSelect($where, $field, $order);
return $this->doFormat($query, $format, $limit);
}
/**
* 更新附件
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function update(Request $request): Response
{
if ($request->method() === 'GET') {
return view('upload/update');
}
return parent::update($request);
}
/**
* 添加附件
* @param Request $request
* @return Response
* @throws Exception
*/
public function insert(Request $request): Response
{
if ($request->method() === 'GET') {
return view('upload/insert');
}
$file = current($request->file());
if (!$file || !$file->isValid()) {
return $this->json(1, '未找到文件');
}
$data = $this->base($request, '/upload/files/'.date('Ymd'));
$upload = new Upload;
$upload->admin_id = admin_id();
$upload->name = $data['name'];
[
$upload->url,
$upload->name,
$_,
$upload->file_size,
$upload->mime_type,
$upload->image_width,
$upload->image_height,
$upload->ext
] = array_values($data);
$upload->category = $request->post('category');
$upload->save();
return $this->json(0, '上传成功', [
'url' => $data['url'],
'name' => $data['name'],
'size' => $data['size'],
]);
}
/**
* 上传文件
* @param Request $request
* @return Response
* @throws Exception
*/
public function file(Request $request): Response
{
$file = current($request->file());
if (!$file || !$file->isValid()) {
return $this->json(1, '未找到文件');
}
$img_exts = [
'jpg',
'jpeg',
'png',
'gif'
];
if (in_array($file->getUploadExtension(), $img_exts)) {
return $this->image($request);
}
$data = $this->base($request, '/upload/files/'.date('Ymd'));
return $this->json(0, '上传成功', [
'url' => $data['url'],
'name' => $data['name'],
'size' => $data['size'],
]);
}
/**
* 上传图片
* @param Request $request
* @return Response
* @throws Exception
*/
public function image(Request $request): Response
{
$data = $this->base($request, '/upload/img/'.date('Ymd'));
$realpath = $data['realpath'];
try {
$img = Image::make($realpath);
$max_height = 1170;
$max_width = 1170;
$width = $img->width();
$height = $img->height();
$ratio = 1;
if ($height > $max_height || $width > $max_width) {
$ratio = $width > $height ? $max_width / $width : $max_height / $height;
}
$img->resize($width*$ratio, $height*$ratio)->save($realpath);
} catch (Exception $e) {
unlink($realpath);
return json( [
'code' => 500,
'msg' => '处理图片发生错误'
]);
}
return json( [
'code' => 0,
'msg' => '上传成功',
'data' => [
'url' => $data['url'],
'name' => $data['name'],
'size' => $data['size'],
]
]);
}
/**
* 上传头像
* @param Request $request
* @return Response
* @throws Exception
*/
public function avatar(Request $request): Response
{
$file = current($request->file());
if ($file && $file->isValid()) {
$ext = strtolower($file->getUploadExtension());
if (!in_array($ext, ['jpg', 'jpeg', 'gif', 'png'])) {
return json(['code' => 2, 'msg' => '仅支持 jpg jpeg gif png格式']);
}
$image = Image::make($file);
$width = $image->width();
$height = $image->height();
$size = min($width, $height);
$relative_path = 'upload/avatar/' . date('Ym');
$real_path = base_path() . "/plugin/admin/public/$relative_path";
if (!is_dir($real_path)) {
mkdir($real_path, 0777, true);
}
$name = bin2hex(pack('Nn',time(), random_int(1, 65535)));
$ext = $file->getUploadExtension();
$image->crop($size, $size)->resize(300, 300);
$path = base_path() . "/plugin/admin/public/$relative_path/$name.lg.$ext";
$image->save($path);
$image->resize(120, 120);
$path = base_path() . "/plugin/admin/public/$relative_path/$name.md.$ext";
$image->save($path);
$image->resize(60, 60);
$path = base_path() . "/plugin/admin/public/$relative_path/$name.$ext";
$image->save($path);
$image->resize(30, 30);
$path = base_path() . "/plugin/admin/public/$relative_path/$name.sm.$ext";
$image->save($path);
return json([
'code' => 0,
'msg' => '上传成功',
'data' => [
'url' => "/app/admin/$relative_path/$name.md.$ext"
]
]);
}
return json(['code' => 1, 'msg' => 'file not found']);
}
/**
* 删除附件
* @param Request $request
* @return Response
*/
public function delete(Request $request): Response
{
return parent::delete($request);
}
/**
* 获取上传数据
* @param Request $request
* @param $relative_dir
* @return array
* @throws BusinessException
*/
protected function base(Request $request, $relative_dir): array
{
$relative_dir = ltrim($relative_dir, '/');
$file = current($request->file());
if (!$file || !$file->isValid()) {
throw new BusinessException('未找到上传文件', 400);
}
$base_dir = base_path() . '/plugin/admin/public/';
$full_dir = $base_dir . $relative_dir;
if (!is_dir($full_dir)) {
mkdir($full_dir, 0777, true);
}
$ext = strtolower($file->getUploadExtension());
$ext_forbidden_map = ['php', 'php3', 'php5', 'css', 'js', 'html', 'htm', 'asp', 'jsp'];
if (in_array($ext, $ext_forbidden_map)) {
throw new BusinessException('不支持该格式的文件上传', 400);
}
$relative_path = $relative_dir . '/' . bin2hex(pack('Nn',time(), random_int(1, 65535))) . ".$ext";
$full_path = $base_dir . $relative_path;
$file_size = $file->getSize();
$file_name = $file->getUploadName();
$mime_type = $file->getUploadMimeType();
$file->move($full_path);
$image_with = $image_height = 0;
if ($img_info = getimagesize($full_path)) {
[$image_with, $image_height] = $img_info;
$mime_type = $img_info['mime'];
}
return [
'url' => "/app/admin/$relative_path",
'name' => $file_name,
'realpath' => $full_path,
'size' => $file_size,
'mime_type' => $mime_type,
'image_with' => $image_with,
'image_height' => $image_height,
'ext' => $ext,
];
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace plugin\admin\app\controller;
use plugin\admin\app\controller\Base;
use plugin\admin\app\controller\Crud;
use plugin\admin\app\model\User;
use support\exception\BusinessException;
use support\Request;
use support\Response;
/**
* 用户管理
*/
class UserController extends Crud
{
/**
* @var User
*/
protected $model = null;
/**
* 构造函数
* @return void
*/
public function __construct()
{
$this->model = new User;
}
/**
* 浏览
* @return Response
*/
public function index(): Response
{
return view('user/index');
}
/**
* 插入
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function insert(Request $request): Response
{
if ($request->method() === 'POST') {
return parent::insert($request);
}
return view('user/insert');
}
/**
* 更新
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function update(Request $request): Response
{
if ($request->method() === 'POST') {
return parent::update($request);
}
return view('user/update');
}
}

View File

@ -0,0 +1,40 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace plugin\admin\app\exception;
use Throwable;
use Webman\Http\Request;
use Webman\Http\Response;
/**
* Class Handler
* @package support\exception
*/
class Handler extends \support\exception\Handler
{
public function render(Request $request, Throwable $exception): Response
{
$code = $exception->getCode();
$debug = $this->_debug ?? $this->debug;
if ($request->expectsJson()) {
$json = ['code' => $code ?: 500, 'msg' => $debug ? $exception->getMessage() : 'Server internal error', 'type' => 'failed'];
$debug && $json['traces'] = (string)$exception;
return new Response(200, ['Content-Type' => 'application/json'],
\json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
$error = $debug ? \nl2br((string)$exception) : 'Server internal error';
return new Response(500, [], $error);
}
}

View File

@ -0,0 +1,139 @@
<?php
/**
* Here is your custom functions.
*/
use plugin\admin\app\model\User;
use plugin\admin\app\model\Admin;
use plugin\admin\app\model\AdminRole;
/**
* 当前管理员id
* @return integer|null
*/
function admin_id(): ?int
{
return session('admin.id');
}
/**
* 当前管理员
* @param null|array|string $fields
* @return array|mixed|null
*/
function admin($fields = null)
{
refresh_admin_session();
if (!$admin = session('admin')) {
return null;
}
if ($fields === null) {
return $admin;
}
if (is_array($fields)) {
$results = [];
foreach ($fields as $field) {
$results[$field] = $admin[$field] ?? null;
}
return $results;
}
return $admin[$fields] ?? null;
}
/**
* 当前登录用户id
* @return integer|null
*/
function user_id(): ?int
{
return session('user.id');
}
/**
* 当前登录用户
* @param null|array|string $fields
* @return array|mixed|null
*/
function user($fields = null)
{
refresh_user_session();
if (!$user = session('user')) {
return null;
}
if ($fields === null) {
return $user;
}
if (is_array($fields)) {
$results = [];
foreach ($fields as $field) {
$results[$field] = $user[$field] ?? null;
}
return $results;
}
return $user[$fields] ?? null;
}
/**
* 刷新当前管理员session
* @param bool $force
* @return void
*/
function refresh_admin_session(bool $force = false)
{
if (!$admin_id = admin_id()) {
return null;
}
$time_now = time();
// session在2秒内不刷新
$session_ttl = 2;
$session_last_update_time = session('admin.session_last_update_time', 0);
if (!$force && $time_now - $session_last_update_time < $session_ttl) {
return null;
}
$session = request()->session();
$admin = Admin::find($admin_id);
if (!$admin) {
$session->forget('admin');
return null;
}
$admin = $admin->toArray();
unset($admin['password']);
// 账户被禁用
if ($admin['status'] != 0) {
$session->forget('admin');
return;
}
$admin['roles'] = AdminRole::where('admin_id', $admin_id)->pluck('role_id')->toArray();
$admin['session_last_update_time'] = $time_now;
$session->set('admin', $admin);
}
/**
* 刷新当前用户session
* @param bool $force
* @return void
*/
function refresh_user_session(bool $force = false)
{
if (!$user_id = user_id()) {
return null;
}
$time_now = time();
// session在2秒内不刷新
$session_ttl = 2;
$session_last_update_time = session('user.session_last_update_time', 0);
if (!$force && $time_now - $session_last_update_time < $session_ttl) {
return null;
}
$session = request()->session();
$user = User::find($user_id);
if (!$user) {
$session->forget('user');
return null;
}
$user = $user->toArray();
unset($user['password']);
$user['session_last_update_time'] = $time_now;
$session->set('user', $user);
}

View File

@ -0,0 +1,54 @@
<?php
namespace plugin\admin\app\middleware;
use plugin\admin\api\Auth;
use ReflectionException;
use support\exception\BusinessException;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
class AccessControl implements MiddlewareInterface
{
/**
* @param Request $request
* @param callable $handler
* @return Response
* @throws ReflectionException|BusinessException
*/
public function process(Request $request, callable $handler): Response
{
$controller = $request->controller;
$action = $request->action;
$code = 0;
$msg = '';
if (!Auth::canAccess($controller, $action, $code, $msg)) {
if ($request->expectsJson()) {
$response = json(['code' => $code, 'msg' => $msg, 'data' => []]);
} else {
if ($code === 401) {
$response = response(<<<EOF
<script>
if (self !== top) {
parent.location.reload();
}
</script>
EOF
);
} else {
$request->app = '';
$request->plugin = 'admin';
$response = view('common/error/403')->withStatus(403);
}
}
} else {
$response = $request->method() == 'OPTIONS' ? response('') : $handler($request);
}
return $response;
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace plugin\admin\app\model;
use plugin\admin\app\model\Base;
/**
* @property integer $id ID(主键)
* @property string $username 用户名
* @property string $nickname 昵称
* @property string $password 密码
* @property string $avatar 头像
* @property string $email 邮箱
* @property string $mobile 手机
* @property string $created_at 创建时间
* @property string $updated_at 更新时间
* @property string $login_at 登录时间
* @property string $roles 角色
* @property integer $status 状态 0正常 1禁用
*/
class Admin extends Base
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'wa_admins';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
}

View File

@ -0,0 +1,31 @@
<?php
namespace plugin\admin\app\model;
use plugin\admin\app\model\Base;
/**
* @property integer $id ID(主键)
* @property string $admin_id 管理员id
* @property string $role_id 角色id
*/
class AdminRole extends Base
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'wa_admin_roles';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
public $timestamps = false;
}

View File

@ -0,0 +1,26 @@
<?php
namespace plugin\admin\app\model;
use DateTimeInterface;
use support\Model;
class Base extends Model
{
/**
* @var string
*/
protected $connection = 'plugin.admin.mysql';
/**
* 格式化日期
*
* @param DateTimeInterface $date
* @return string
*/
protected function serializeDate(DateTimeInterface $date)
{
return $date->format('Y-m-d H:i:s');
}
}

View File

@ -0,0 +1,96 @@
<?php
namespace plugin\admin\app\model;
use support\exception\BusinessException;
/**
* 字典相关
*/
class Dict
{
/**
* 获取字典
* @param $name
* @return mixed|null
*/
public static function get($name)
{
$value = Option::where('name', static::dictNameToOptionName($name))->value('value');
return $value ? json_decode($value, true) : null;
}
/**
* 保存字典
* @param $name
* @param $values
* @return void
* @throws BusinessException
*/
public static function save($name, $values)
{
if (!preg_match('/[a-zA-Z]/', $name)) {
throw new BusinessException('字典名只能包含字母');
}
$option_name = static::dictNameToOptionName($name);
if (!$option = Option::where('name', $option_name)->first()) {
$option = new Option;
}
$format_values = static::filterValue($values);
$option->name = $option_name;
$option->value = json_encode($format_values, JSON_UNESCAPED_UNICODE);
$option->save();
}
/**
* 删除字典
* @param array $names
* @return void
*/
public static function delete(array $names)
{
foreach ($names as $index => $name) {
$names[$index] = static::dictNameToOptionName($name);
}
Option::whereIn('name', $names)->delete();
}
/**
* 字典名到option名转换
* @param string $name
* @return string
*/
public static function dictNameToOptionName(string $name): string
{
return "dict_$name";
}
/**
* option名到字典名转换
* @param string $name
* @return string
*/
public static function optionNameToDictName(string $name): string
{
return substr($name, 5);
}
/**
* 过滤值
* @param array $values
* @return array
* @throws BusinessException
*/
public static function filterValue(array $values): array
{
$format_values = [];
foreach ($values as $item) {
if (!isset($item['value']) || !isset($item['name'])) {
throw new BusinessException('字典格式错误', 1);
}
$format_values[] = ['value' => $item['value'], 'name' => $item['name']];
}
return $format_values;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace plugin\admin\app\model;
/**
* @property integer $id (主键)
* @property string $name
* @property mixed $value
* @property string $created_at 创建时间
* @property string $updated_at 更新时间
*/
class Option extends Base
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'wa_options';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
}

View File

@ -0,0 +1,37 @@
<?php
namespace plugin\admin\app\model;
use plugin\admin\app\model\Base;
/**
* @property integer $Id (主键)
* @property mixed $ProjectName
* @property integer $State
* @property string $CreateTime
* @property integer $CreateUserId
* @property integer $IsDelete
* @property mixed $IndexImage
* @property mixed $Remarks
* @property string $created_at 创建时间
* @property string $updated_at 更新时间
*/
class Project extends Base
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'wa_projects';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'Id';
}

View File

@ -0,0 +1,34 @@
<?php
namespace plugin\admin\app\model;
use plugin\admin\app\model\Base;
/**
* @property integer $Id (主键)
* @property integer $ProjectId
* @property string $CreateTime
* @property integer $CreateUserId
* @property mixed $ContentData
* @property string $created_at 创建时间
* @property string $updated_at 更新时间
*/
class Projectdata extends Base
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'wa_projectdatas';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'Id';
}

View File

@ -0,0 +1,38 @@
<?php
namespace plugin\admin\app\model;
/**
* @property integer $id 主键(主键)
* @property string $name 角色名
* @property string $rules 权限
* @property string $created_at 创建时间
* @property string $updated_at 更新时间
* @property integer $pid 上级id
*/
class Role extends Base
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'wa_roles';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* @return mixed
*/
public function getRuleIds()
{
return $this->rules ? explode(',', $this->rules) : [];
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace plugin\admin\app\model;
use plugin\admin\app\model\Base;
/**
* @property integer $id 主键(主键)
* @property string $title 标题
* @property string $icon 图标
* @property string $key 标识
* @property integer $pid 上级菜单
* @property string $created_at 创建时间
* @property string $updated_at 更新时间
* @property string $href url
* @property integer $type 类型
* @property integer $weight 排序
*/
class Rule extends Base
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'wa_rules';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
}

View File

@ -0,0 +1,42 @@
<?php
namespace plugin\admin\app\model;
use plugin\admin\app\model\Base;
/**
* @property integer $id 主键(主键)
* @property string $name 名称
* @property string $url url
* @property integer $admin_id 管理员
* @property integer $user_id 用户
* @property integer $file_size 文件大小
* @property string $mime_type mime类型
* @property integer $image_width 图片宽度
* @property integer $image_height 图片高度
* @property string $ext 扩展名
* @property string $storage 存储位置
* @property string $created_at 上传时间
* @property string $category 类别
* @property string $updated_at 更新时间
*/
class Upload extends Base
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'wa_uploads';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
}

View File

@ -0,0 +1,49 @@
<?php
namespace plugin\admin\app\model;
use plugin\admin\app\model\Base;
/**
* @property integer $id 主键(主键)
* @property string $username 用户名
* @property string $nickname 昵称
* @property string $password 密码
* @property string $sex 性别
* @property string $avatar 头像
* @property string $email 邮箱
* @property string $mobile 手机
* @property integer $level 等级
* @property string $birthday 生日
* @property integer $money 余额
* @property integer $score 积分
* @property string $last_time 登录时间
* @property string $last_ip 登录ip
* @property string $join_time 注册时间
* @property string $join_ip 注册ip
* @property string $token token
* @property string $created_at 创建时间
* @property string $updated_at 更新时间
* @property integer $role 角色
* @property integer $status 禁用
*/
class User extends Base
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'wa_users';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
}

View File

@ -0,0 +1,158 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="/app/admin/component/layui/css/layui.css" />
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body class="pear-container">
<style>
.layui-input-block input {
width: 300px;
}
</style>
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-tab layui-tab-brief">
<ul class="layui-tab-title">
<li class="layui-this">基本信息</li>
<li>安全设置</li>
</ul>
<div class="layui-tab-content">
<!-- 基本信息 -->
<div class="layui-tab-item layui-show">
<form class="layui-form" lay-filter="baseInfo">
<div class="layui-form-item">
<label class="layui-form-label">昵称</label>
<div class="layui-input-block">
<input type="text" name="nickname" required lay-verify="required" placeholder="请输入昵称" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">邮箱</label>
<div class="layui-input-block">
<input type="text" name="email" placeholder="请输入邮箱" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">联系电话</label>
<div class="layui-input-block">
<input type="text" name="mobile" placeholder="请输入联系电话" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit="" lay-filter="saveBaseInfo">
提交
</button>
<button type="reset" class="pear-btn pear-btn-md">
重置
</button>
</div>
</div>
</form>
</div>
<div class="layui-tab-item">
<form class="layui-form" action="">
<div class="layui-form-item">
<label class="layui-form-label">原始密码</label>
<div class="layui-input-block">
<input type="password" name="old_password" required lay-verify="required" placeholder="请输入原始密码" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">新密码</label>
<div class="layui-input-block">
<input type="password" name="password" required lay-verify="required" placeholder="请输入新密码" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">确认新密码</label>
<div class="layui-input-block">
<input type="password" name="password_confirm" required lay-verify="required" placeholder="请再次输入新密码" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit="" lay-filter="savePassword">
提交
</button>
<button type="reset" class="pear-btn pear-btn-md">
重置
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
layui.use(["form", "popup"], function () {
let form = layui.form;
let $ = layui.$;
$.ajax({
url: "/app/admin/account/info",
dataType: "json",
success: function (res) {
form.val("baseInfo", res.data);
}
});
form.on("submit(saveBaseInfo)", function(data){
$.ajax({
url: "/app/admin/account/update",
dataType: "json",
type: "POST",
data: data.field,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功");
}
});
return false;
});
form.on("submit(savePassword)", function(data){
$.ajax({
url: "/app/admin/account/password",
dataType: "json",
type: "POST",
data: data.field,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功");
}
});
return false;
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,81 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>登录</title>
<!-- 样 式 文 件 -->
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/pages/login.css" />
</head>
<!-- 代 码 结 构 -->
<body background="/app/admin/admin/images/background.svg" style="background-size: cover;">
<form class="layui-form">
<div class="layui-form-item">
<img class="logo" src="/app/admin/admin/images/logo.png" />
<div class="title pear-text">webman admin</div>
</div>
<div class="layui-form-item">
<input lay-verify="required" hover class="layui-input" type="text" name="username" value="" placeholder="用户名" />
</div>
<div class="layui-form-item">
<input lay-verify="required" hover class="layui-input" type="password" name="password" value="" placeholder="密码" />
</div>
<div class="layui-form-item">
<input hover lay-verify="required" class="code layui-input layui-input-inline" name="captcha" placeholder="验证码" />
<img class="codeImage" width="120px"/>
</div>
<div class="layui-form-item">
<button type="submit" class="pear-btn pear-btn-primary login" lay-submit lay-filter="login">
登 入
</button>
</div>
</form>
<script>
var color = localStorage.getItem("theme-color-color");
var second = localStorage.getItem("theme-color-second");
if (!color || !second) {
localStorage.setItem("theme-color-color", "#2d8cf0");
localStorage.setItem("theme-color-second", "#ecf5ff");
}
</script>
<!-- 资 源 引 入 -->
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script>
layui.use(['form', 'button', 'popup', 'layer', 'theme', 'admin'], function() {
var $ = layui.$, layer = layui.layer, form = layui.form;
function switchCaptcha() {
$('.codeImage').attr("src", "/app/admin/account/captcha/login?v=" + new Date().getTime());
}
switchCaptcha();
// 登 录 提 交
form.on('submit(login)', function (data) {
layer.load();
$.ajax({
url: '/app/admin/account/login',
type: "POST",
data: data.field,
success: function (res) {
layer.closeAll('loading');
if (!res.code) {
layui.popup.success('登录成功', function () {
location.reload();
})
} else {
layui.popup.failure(res.msg, function () {
switchCaptcha();
})
}
}
});
return false;
});
$('.codeImage').on('click', function () {
switchCaptcha();
});
})
</script>
</body>
</html>

View File

@ -0,0 +1,404 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>浏览页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body class="pear-container">
<!-- 顶部查询表单 -->
<div class="layui-card">
<div class="layui-card-body">
<form class="layui-form top-search-from">
<div class="layui-form-item">
<label class="layui-form-label">用户名</label>
<div class="layui-input-block">
<input type="text" name="username" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">昵称</label>
<div class="layui-input-block">
<input type="text" name="nickname" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">邮箱</label>
<div class="layui-input-block">
<input type="text" name="email" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">手机</label>
<div class="layui-input-block">
<input type="text" name="mobile" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">创建时间</label>
<div class="layui-input-block">
<div class="layui-input-block" id="created_at">
<input type="text" autocomplete="off" name="created_at[]" id="created_at-date-start" class="layui-input inline-block" placeholder="开始时间">
-
<input type="text" autocomplete="off" name="created_at[]" id="created_at-date-end" class="layui-input inline-block" placeholder="结束时间">
</div>
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label"></label>
<button class="pear-btn pear-btn-md pear-btn-primary" lay-submit lay-filter="table-query">
<i class="layui-icon layui-icon-search"></i>查询
</button>
<button type="reset" class="pear-btn pear-btn-md" lay-submit lay-filter="table-reset">
<i class="layui-icon layui-icon-refresh"></i>重置
</button>
</div>
<div class="toggle-btn">
<a class="layui-hide">展开<i class="layui-icon layui-icon-down"></i></a>
<a class="layui-hide">收起<i class="layui-icon layui-icon-up"></i></a>
</div>
</form>
</div>
</div>
<!-- 数据表格 -->
<div class="layui-card">
<div class="layui-card-body">
<table id="data-table" lay-filter="data-table"></table>
</div>
</div>
<!-- 表格顶部工具栏 -->
<script type="text/html" id="table-toolbar">
<button class="pear-btn pear-btn-primary pear-btn-md" lay-event="add" permission="app.admin.admin.insert">
<i class="layui-icon layui-icon-add-1"></i>新增
</button>
<button class="pear-btn pear-btn-danger pear-btn-md" lay-event="batchRemove" permission="app.admin.admin.delete">
<i class="layui-icon layui-icon-delete"></i>删除
</button>
</script>
<!-- 表格行工具栏 -->
<script type="text/html" id="table-bar">
{{# if(d.show_toolbar){ }}
<button class="pear-btn pear-btn-xs tool-btn" lay-event="edit" permission="app.admin.admin.update">编辑</button>
<button class="pear-btn pear-btn-xs tool-btn" lay-event="remove" permission="app.admin.admin.delete">删除</button>
{{# } }}
</script>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script src="/app/admin/admin/js/common.js"></script>
<script>
// 相关常量
const PRIMARY_KEY = "id";
const SELECT_API = "/app/admin/admin/select";
const UPDATE_API = "/app/admin/admin/update";
const DELETE_API = "/app/admin/admin/delete";
const INSERT_URL = "/app/admin/admin/insert";
const UPDATE_URL = "/app/admin/admin/update";
// 字段 创建时间 created_at
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#created_at",
range: ["#created_at-date-start", "#created_at-date-end"],
});
})
// 表格渲染
layui.use(["table", "form", "common", "popup", "util"], function() {
let table = layui.table;
let form = layui.form;
let $ = layui.$;
let common = layui.common;
let util = layui.util;
// 表头参数
let cols = [
{
type: "checkbox"
},{
title: "ID",
field: "id",
width: 100,
sort: true,
},{
title: "用户名",
field: "username",
},{
title: "昵称",
field: "nickname",
},{
title: "密码",
field: "password",
hide: true,
},{
title: "头像",
field: "avatar",
templet: function (d) {
return '<img src="'+encodeURI(d['avatar'])+'" style="max-width:32px;max-height:32px;" alt="" />'
},
width: 90,
},{
title: "邮箱",
field: "email",
hide: true,
},{
title: "手机",
field: "mobile",
hide: true,
},{
title: "创建时间",
field: "created_at",
hide: true,
},{
title: "更新时间",
field: "updated_at",
hide: true,
},{
title: "登录时间",
field: "login_at",
},{
title: "角色",
field: "roles",
templet: function (d) {
let field = "roles";
if (typeof d[field] == "undefined") return "";
let items = [];
layui.each((d[field] + "").split(","), function (k , v) {
items.push(apiResults[field][v] || v);
});
return util.escape(items.join(","));
}
},{
title: "禁用",
field: "status",
templet: function (d) {
let field = "status";
form.on("switch("+field+")", function (data) {
let load = layer.load();
let postData = {};
postData[field] = data.elem.checked ? 1 : 0;
postData[PRIMARY_KEY] = this.value;
$.post(UPDATE_API, postData, function (res) {
layer.close(load);
if (res.code) {
return layui.popup.failure(res.msg, function () {
data.elem.checked = !data.elem.checked;
form.render();
});
}
return layui.popup.success("操作成功");
})
});
let checked = d[field] === 1 ? "checked" : "";
if (parent.Admin.Account.id === d.id) return '';
return '<input type="checkbox" value="'+util.escape(d[PRIMARY_KEY])+'" lay-filter="'+util.escape(field)+'" lay-skin="switch" lay-text="'+util.escape('')+'" '+checked+'/>';
},
width: 90,
},{
title: "操作",
toolbar: "#table-bar",
align: "center",
fixed: "right",
width: 130,
}
];
// 渲染表格
function render()
{
table.render({
elem: "#data-table",
url: SELECT_API,
page: true,
cols: [cols],
skin: "line",
size: "lg",
toolbar: "#table-toolbar",
autoSort: false,
defaultToolbar: [{
title: "刷新",
layEvent: "refresh",
icon: "layui-icon-refresh",
}, "filter", "print", "exports"],
done: function () {
layer.photos({photos: 'div[lay-id="data-table"]', anim: 5});
}
});
}
// 获取表格中下拉或树形组件数据
let apis = [];
apis.push(["roles", "/app/admin/role/select?format=select"]);
let apiResults = {};
apiResults["roles"] = [];
let count = apis.length;
layui.each(apis, function (k, item) {
let [field, url] = item;
$.ajax({
url: url,
dateType: "json",
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
function travel(items) {
for (let k in items) {
let item = items[k];
apiResults[field][item.value] = item.name;
if (item.children) {
travel(item.children);
}
}
}
travel(res.data);
},
complete: function () {
if (--count === 0) {
render();
}
}
});
});
if (!count) {
render();
}
// 编辑或删除行事件
table.on("tool(data-table)", function(obj) {
if (obj.event === "remove") {
remove(obj);
} else if (obj.event === "edit") {
edit(obj);
}
});
// 表格顶部工具栏事件
table.on("toolbar(data-table)", function(obj) {
if (obj.event === "add") {
add();
} else if (obj.event === "refresh") {
refreshTable();
} else if (obj.event === "batchRemove") {
batchRemove(obj);
}
});
// 表格顶部搜索事件
form.on("submit(table-query)", function(data) {
table.reload("data-table", {
page: {
curr: 1
},
where: data.field
})
return false;
});
// 表格顶部搜索重置事件
form.on("submit(table-reset)", function(data) {
table.reload("data-table", {
where: []
})
});
// 表格排序事件
table.on("sort(data-table)", function(obj){
table.reload("data-table", {
initSort: obj,
scrollPos: "fixed",
where: {
field: obj.field,
order: obj.type
}
});
});
// 表格新增数据
let add = function() {
layer.open({
type: 2,
title: "新增",
shade: 0.1,
area: [common.isModile()?"100%":"500px", common.isModile()?"100%":"450px"],
content: INSERT_URL
});
}
// 表格编辑数据
let edit = function(obj) {
let value = obj.data[PRIMARY_KEY];
layer.open({
type: 2,
title: "修改",
shade: 0.1,
area: [common.isModile()?"100%":"500px", common.isModile()?"100%":"450px"],
content: UPDATE_URL + "?" + PRIMARY_KEY + "=" + value
});
}
// 删除一行
let remove = function(obj) {
return doRemove(obj.data[PRIMARY_KEY]);
}
// 删除多行
let batchRemove = function(obj) {
let checkIds = common.checkField(obj, PRIMARY_KEY);
if (checkIds === "") {
layui.popup.warning("未选中数据");
return false;
}
doRemove(checkIds.split(","));
}
// 执行删除
let doRemove = function (ids) {
let data = {};
data[PRIMARY_KEY] = ids;
layer.confirm("确定删除?", {
icon: 3,
title: "提示"
}, function(index) {
layer.close(index);
let loading = layer.load();
$.ajax({
url: DELETE_API,
data: data,
dataType: "json",
type: "post",
success: function(res) {
layer.close(loading);
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", refreshTable);
}
})
});
}
// 刷新表格数据
window.refreshTable = function(param) {
table.reloadData("data-table", {
scrollPos: "fixed"
});
}
})
</script>
</body>
</html>

View File

@ -0,0 +1,181 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>新增页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body>
<form class="layui-form" action="">
<div class="mainBox">
<div class="main-container mr-5">
<div class="layui-form-item">
<label class="layui-form-label required">角色</label>
<div class="layui-input-block">
<div name="roles" id="roles" value=""></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">用户名</label>
<div class="layui-input-block">
<input type="text" name="username" value="" required lay-verify="required" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">昵称</label>
<div class="layui-input-block">
<input type="text" name="nickname" value="" required lay-verify="required" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">密码</label>
<div class="layui-input-block">
<input type="text" name="password" value="" required lay-verify="required" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">头像</label>
<div class="layui-input-block">
<img class="img-3" src=""/>
<input type="text" style="display:none" name="avatar" value="/app/admin/avatar.png" />
<button type="button" class="pear-btn pear-btn-primary pear-btn-sm" id="avatar">
<i class="layui-icon layui-icon-upload"></i>上传图片
</button>
<button type="button" class="pear-btn pear-btn-primary pear-btn-sm" id="attachment-choose-avatar">
<i class="layui-icon layui-icon-align-left"></i>选择图片
</button>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">邮箱</label>
<div class="layui-input-block">
<input type="text" name="email" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">手机</label>
<div class="layui-input-block">
<input type="text" name="mobile" value="" class="layui-input">
</div>
</div>
</div>
</div>
<div class="bottom">
<div class="button-container">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit=""
lay-filter="save">
提交
</button>
<button type="reset" class="pear-btn pear-btn-md">
重置
</button>
</div>
</div>
</form>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
// 相关接口
const INSERT_API = "/app/admin/admin/insert";
// 字段 头像 avatar
layui.use(["upload", "layer"], function() {
let input = layui.$("#avatar").prev();
input.prev().attr("src", input.val());
layui.$("#attachment-choose-avatar").on("click", function() {
parent.layer.open({
type: 2,
title: "选择附件",
content: "/app/admin/upload/attachment?ext=jpg,jpeg,png,gif,bmp",
area: ["95%", "90%"],
success: function (layero, index) {
parent.layui.$("#layui-layer" + index).data("callback", function (data) {
input.val(data.url).prev().attr("src", data.url);
});
}
});
});
layui.upload.render({
elem: "#avatar",
url: "/app/admin/upload/avatar",
value: "/app/admin/avatar.png",
acceptMime: "image/gif,image/jpeg,image/jpg,image/png",
field: "__file__",
done: function (res) {
if (res.code > 0) return layui.layer.msg(res.msg);
this.item.prev().val(res.data.url).prev().attr("src", res.data.url);
}
});
});
// 字段 角色 roles
layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({
url: "/app/admin/role/select?format=tree",
dataType: "json",
success: function (res) {
let value = layui.$("#roles").attr("value");
let initValue = value ? value.split(",") : [];
if (!top.Admin.Account.isSupperAdmin) {
layui.each(res.data, function (k, v) {
v.disabled = true;
});
}
layui.xmSelect.render({
el: "#roles",
name: "roles",
initValue: initValue,
data: res.data,
layVerify: "required",
tree: {"show":true, expandedKeys:true, strict:false},
toolbar: {show:true, list:["ALL","CLEAR","REVERSE"]},
});
if (res.code) {
layui.popup.failure(res.msg);
}
}
});
});
//提交事件
layui.use(["form", "popup"], function () {
layui.form.on("submit(save)", function (data) {
layui.$.ajax({
url: INSERT_API,
type: "POST",
dateType: "json",
data: data.field,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", function () {
parent.refreshTable();
parent.layer.close(parent.layer.getFrameIndex(window.name));
});
}
});
return false;
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,215 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>更新页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body>
<form class="layui-form">
<div class="mainBox">
<div class="main-container mr-5">
<div class="layui-form-item">
<label class="layui-form-label required">角色</label>
<div class="layui-input-block">
<div name="roles" id="roles" value="" ></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">用户名</label>
<div class="layui-input-block">
<input type="text" name="username" value="" required lay-verify="required" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">昵称</label>
<div class="layui-input-block">
<input type="text" name="nickname" value="" required lay-verify="required" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">密码</label>
<div class="layui-input-block">
<input type="text" name="password" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">头像</label>
<div class="layui-input-block">
<img class="img-3" src=""/>
<input type="text" style="display:none" name="avatar" value="" />
<button type="button" class="pear-btn pear-btn-primary pear-btn-sm" id="avatar" permission="app.admin.upload.avatar">
<i class="layui-icon layui-icon-upload"></i>上传图片
</button>
<button type="button" class="pear-btn pear-btn-primary pear-btn-sm" id="attachment-choose-avatar" permission="app.admin.upload.attachment">
<i class="layui-icon layui-icon-align-left"></i>选择图片
</button>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">邮箱</label>
<div class="layui-input-block">
<input type="text" name="email" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">手机</label>
<div class="layui-input-block">
<input type="text" name="mobile" value="" class="layui-input">
</div>
</div>
</div>
</div>
<div class="bottom">
<div class="button-container">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit="" lay-filter="save">
提交
</button>
<button type="reset" class="pear-btn pear-btn-md">
重置
</button>
</div>
</div>
</form>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
// 相关接口
const PRIMARY_KEY = "id";
const SELECT_API = "/app/admin/admin/select" + location.search;
const UPDATE_API = "/app/admin/admin/update";
// 获取数据库记录
layui.use(["form", "util", "popup"], function () {
let $ = layui.$;
$.ajax({
url: SELECT_API,
dataType: "json",
success: function (res) {
// 给表单初始化数据
layui.each(res.data[0], function (key, value) {
let obj = $('*[name="'+key+'"]');
if (key === "password") {
obj.attr("placeholder", "不更新密码请留空");
return;
}
if (typeof obj[0] === "undefined" || !obj[0].nodeName) return;
if (obj[0].nodeName.toLowerCase() === "textarea") {
obj.val(layui.util.escape(value));
} else {
obj.attr("value", value);
}
});
// 字段 头像 avatar
layui.use(["upload", "layer"], function() {
let input = layui.$("#avatar").prev();
input.prev().attr("src", input.val());
layui.$("#attachment-choose-avatar").on("click", function() {
parent.layer.open({
type: 2,
title: "选择附件",
content: "/app/admin/upload/attachment?ext=jpg,jpeg,png,gif,bmp",
area: ["95%", "90%"],
success: function (layero, index) {
parent.layui.$("#layui-layer" + index).data("callback", function (data) {
input.val(data.url).prev().attr("src", data.url);
});
}
});
});
layui.upload.render({
elem: "#avatar",
url: "/app/admin/upload/avatar",
acceptMime: "image/gif,image/jpeg,image/jpg,image/png",
field: "__file__",
done: function (res) {
if (res.code > 0) return layui.layer.msg(res.msg);
this.item.prev().val(res.data.url).prev().attr("src", res.data.url);
}
});
});
// 字段 角色 roles
layui.use(["jquery", "xmSelect"], function() {
layui.$.ajax({
url: "/app/admin/role/select?format=tree",
dataType: "json",
success: function (res) {
let value = layui.$("#roles").attr("value");
let initValue = value ? value.split(",") : [];
if (!top.Admin.Account.isSupperAdmin) {
layui.each(res.data, function (k, v) {
v.disabled = true;
});
}
layui.xmSelect.render({
el: "#roles",
name: "roles",
initValue: initValue,
data: res.data,
layVerify: "required",
tree: {show: true, expandedKeys: true, strict: false},
toolbar: {show: true, list: ["ALL","CLEAR","REVERSE"]},
})
if (res.code) {
layui.popup.failure(res.msg);
}
}
});
});
// ajax产生错误
if (res.code) {
layui.popup.failure(res.msg);
}
}
});
});
//提交事件
layui.use(["form", "popup"], function () {
layui.form.on("submit(save)", function (data) {
data.field[PRIMARY_KEY] = layui.url().search[PRIMARY_KEY];
layui.$.ajax({
url: UPDATE_API,
type: "POST",
dateType: "json",
data: data.field,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", function () {
parent.refreshTable();
parent.layer.close(parent.layer.getFrameIndex(window.name));
});
}
});
return false;
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<link href="/app/admin/component/pear/css/pear.css" rel="stylesheet" />
<link href="/app/admin/admin/css/pages/error.css" rel="stylesheet" />
</head>
<body>
<div class="content">
<img src="/app/admin/admin/images/403.svg" alt="">
<div class="content-r">
<h1>403</h1>
<p>抱歉,你无权访问该页面</p>
</div>
</div>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
</body>
</html>

View File

@ -0,0 +1,314 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="/app/admin/component/layui/css/layui.css" />
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body class="pear-container">
<style>
.layui-input-block input {
width: 300px;
}
</style>
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-tab layui-tab-brief">
<ul class="layui-tab-title">
<li class="layui-this">基本信息</li>
<li>菜单配置</li>
<li>页面配置</li>
</ul>
<div class="layui-tab-content">
<!-- 基本信息 -->
<div class="layui-tab-item layui-show">
<form class="layui-form" lay-filter="baseInfo">
<div class="layui-form-item">
<label class="layui-form-label">网站名称</label>
<div class="layui-input-block">
<input type="text" name="title" required lay-verify="required" placeholder="请输入网站名称" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">网站Logo</label>
<div class="layui-input-block">
<img class="img-3" src=""/>
<input type="text" style="display:none" name="image" value="/app/admin/admin/avatar.png" />
<button type="button" class="pear-btn pear-btn-primary pear-btn-sm" id="image" permission="app.admin.upload.avatar">
<i class="layui-icon layui-icon-upload"></i>上传图片
</button>
<button type="button" class="pear-btn pear-btn-primary pear-btn-sm" id="attachment-choose-image" permission="app.admin.upload.attachment">
<i class="layui-icon layui-icon-align-left"></i>选择图片
</button>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit="" lay-filter="saveBaseInfo">
提交
</button>
</div>
</div>
</form>
</div>
<!-- 菜单设置 -->
<div class="layui-tab-item">
<form class="layui-form" lay-filter="menuInfo">
<div class="layui-form-item">
<label class="layui-form-label">菜单url</label>
<div class="layui-input-block">
<input type="text" name="data" required lay-verify="required" placeholder="请输入菜单url" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">菜单宽度</label>
<div class="layui-input-block">
<input type="number" name="controlWidth" required lay-verify="required" placeholder="请输入宽度" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">默认菜单ID</label>
<div class="layui-input-block">
<input type="number" name="select" required lay-verify="required" placeholder="请输入默认菜单ID" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">开启手风琴</label>
<div class="layui-input-block">
<input type="checkbox" name="accordion" id="accordion" lay-skin="switch" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">折叠菜单</label>
<div class="layui-input-block">
<input type="checkbox" id="collapse" name="collapse" lay-skin="switch" />
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit="" lay-filter="saveMenuInfo">
提交
</button>
</div>
</div>
</form>
</div>
<!-- tab设置 -->
<div class="layui-tab-item">
<form class="layui-form" lay-filter="tabInfo">
<div class="layui-form-item">
<label class="layui-form-label">保持标签</label>
<div class="layui-input-block">
<input type="checkbox" name="keepState" lay-skin="switch" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">记住标签</label>
<div class="layui-input-block">
<input type="checkbox" name="session" lay-skin="switch" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">预加载标签</label>
<div class="layui-input-block">
<input type="checkbox" name="preload" lay-skin="switch" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">最大标签数</label>
<div class="layui-input-block">
<input type="number" name="max" required lay-verify="required" placeholder="请输入最大标签数" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">主标签标题</label>
<div class="layui-input-block">
<input type="text" name="title" required lay-verify="required" placeholder="请输入主页标签标题" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">主标签URL</label>
<div class="layui-input-block">
<input type="text" name="href" required lay-verify="required" placeholder="请输入菜单url" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">主标签ID</label>
<div class="layui-input-block">
<input type="number" name="id" required lay-verify="required" placeholder="请输入主页标签ID" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit="" lay-filter="saveTabInfo">
提交
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
// 基础设置
layui.use(["upload", "layer", "popup"], function() {
let $ = layui.$;
let form = layui.form;
// image
let input = $("#image").prev();
input.prev().attr("src", input.val());
layui.$("#attachment-choose-image").on("click", function() {
parent.layer.open({
type: 2,
title: "选择附件",
content: "/app/admin/upload/attachment?ext=jpg,jpeg,png,gif,bmp",
area: ["95%", "90%"],
success: function (layero, index) {
parent.layui.$("#layui-layer" + index).data("callback", function (data) {
input.val(data.url).prev().attr("src", data.url);
});
}
});
});
layui.upload.render({
elem: "#image",
url: "/app/admin/upload/avatar",
acceptMime: "image/gif,image/jpeg,image/jpg,image/png",
done: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
this.item.prev().val(res.data.url).prev().attr("src", res.data.url);
}
});
// 提交
form.on("submit(saveBaseInfo)", function(data){
$.ajax({
url: "/app/admin/config/update",
dataType: "json",
type: "POST",
data: {logo: data.field},
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功");
}
});
return false;
});
});
// 菜单设置
layui.use(["upload", "layer", "popup"], function() {
let $ = layui.$;
let form = layui.form;
// 提交
form.on("submit(saveMenuInfo)", function(data){
$.ajax({
url: "/app/admin/config/update",
dataType: "json",
type: "POST",
data: {menu: data.field},
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功");
}
});
return false;
});
});
// 标签设置
layui.use(["upload", "layer", "popup"], function() {
let $ = layui.$;
let form = layui.form;
// 提交
form.on("submit(saveTabInfo)", function(data){
let field = data.field;
field.index = {
id: field.id,
href: field.href,
title: field.title,
};
delete data.field;
$.ajax({
url: "/app/admin/config/update",
dataType: "json",
type: "POST",
data: {tab: field},
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功");
}
});
// 删除sessionStorage缓存
sessionStorage.clear();
return false;
});
});
layui.use(["form"], function () {
let form = layui.form;
let $ = layui.$;
$.ajax({
url: "/app/admin/config/get",
dataType: "json",
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
form.val("baseInfo", res.logo);
$("#image").prev().val(res.logo.image).prev().attr("src", res.logo.image);
form.val("menuInfo", res.menu);
let tab = res.tab;
let index = tab.index;
delete tab.index;
tab.id = index.id;
tab.title = index.title;
tab.href= index.href;
form.val("tabInfo", res.tab);
}
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,113 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>表单生成器</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<style>
html,body{background-color: whitesmoke}
.layui-fluid{margin-top: 15px;}
.content{min-height: 1200px;}
.nav{text-align: center;}
.nav button{margin-bottom: 3px;width: 100%;margin-top: 3px;margin-bottom: 3px;border-radius: 1px;}
.nav button:hover{background-color: #5FB878;border: 1px solid #5FB878;color: white;}
.layui-card-body .layui-btn+.layui-btn{margin-left: 0px;}
.code-show{min-height: 700px;}
.js-show{min-height: 360px;}
.layui-card-body {padding: 10px;}
.button{line-height: 100% !important;}
.content .pear-btn-md, .content .pear-btn-sm, .content .pear-btn-xs, .content .pear-btn {
line-height: 100%;
letter-spacing: 2px;
padding: 0 15px;
font-weight: 400;
font-size: 14px;
}
</style>
</head>
<body>
<div class="layui-fluid">
<div class="layui-row layui-col-space10">
<div class="layui-col-md1">
<div class="layui-card nav click-but">
<div class="layui-card-header"></div>
<div class="layui-card-body">
<button class="pear-btn pear-btn-sm" plain data-size="block" data-type="text">输入框</button>
<button class="pear-btn pear-btn-sm" plain data-size="block" data-type="password">密码框</button>
<button class="pear-btn pear-btn-sm" plain data-size="block" data-type="select">选择框</button>
<button class="pear-btn pear-btn-sm" plain data-size="block" data-type="checkbox_a">复选框</button>
<button class="pear-btn pear-btn-sm" plain data-size="block" data-type="checkbox_b">开关</button>
<button class="pear-btn pear-btn-sm" plain data-size="block" data-type="radio">单选框</button>
<button class="pear-btn pear-btn-sm" plain data-size="block" data-type="textarea">文本域</button>
<button class="pear-btn pear-btn-sm" plain data-size="block" data-type="icon">图标</button>
<button class="pear-btn pear-btn-sm" plain data-size="block" data-type="multiSelect">下拉多选</button>
<button class="pear-btn pear-btn-sm" plain data-size="block" data-type="treeSelectOne">树单选</button>
<button class="pear-btn pear-btn-sm" plain data-size="block" data-type="tree">树多选</button>
<button class="pear-btn pear-btn-sm" plain data-size="block" data-type="upload">上传文件</button>
<button class="pear-btn pear-btn-sm" plain data-size="block" data-type="uploadImg">上传图片</button>
<button class="pear-btn pear-btn-sm" plain data-size="block" data-type="submit">提交</button>
</div>
</div>
<div class="layui-card nav">
<div class="layui-card-header"></div>
<div class="layui-card-body">
<button class="pear-btn pear-btn-sm" plain data-size="inline" data-type="text">输入框</button>
<button class="pear-btn pear-btn-sm" plain data-size="inline" data-type="password">密码框</button>
<button class="pear-btn pear-btn-sm" plain data-size="inline" data-type="select">选择框</button>
<button class="pear-btn pear-btn-sm" plain data-size="inline" data-type="checkbox_a">复选框</button>
<button class="pear-btn pear-btn-sm" plain data-size="inline" data-type="checkbox_b">开关</button>
<button class="pear-btn pear-btn-sm" plain data-size="inline" data-type="radio">单选框</button>
<button class="pear-btn pear-btn-sm" plain data-size="inline" data-type="textarea">文本域</button>
<button class="pear-btn pear-btn-sm" plain data-size="inline" data-type="icon">图标</button>
<button class="pear-btn pear-btn-sm" plain data-size="inline" data-type="multiSelect">下拉多选</button>
<button class="pear-btn pear-btn-sm" plain data-size="inline" data-type="treeSelectOne">树单选</button>
<button class="pear-btn pear-btn-sm" plain data-size="inline" data-type="tree">树多选</button>
<button class="pear-btn pear-btn-sm" plain data-size="inline" data-type="upload">上传文件</button>
<button class="pear-btn pear-btn-sm" plain data-size="inline" data-type="uploadImg">上传图片</button>
<button class="pear-btn pear-btn-sm" plain data-size="block" data-type="submit">提交</button>
</div>
</div>
<div class="layui-card nav">
<div class="layui-card-body">
<button class="pear-btn pear-btn-danger pear-btn-sm del-form" data-type="del"> <i class="layui-icon layui-font-16">&#xe640;</i></button>
</div>
</div>
</div>
<div class="layui-col-md5">
<div class="layui-card content">
<div class="layui-card-header">
view
</div>
<div class="layui-card-body code">
<form class="layui-form" action="" onsubmit="return false">
</form>
</div>
</div>
</div>
<div class="layui-col-md6">
<div class="layui-card r-code-html">
<div class="layui-card-header">html</div>
<div class="layui-card-body">
<textarea name="" class="layui-textarea code-show"></textarea>
</div>
</div>
<div class="layui-card r-code-js">
<div class="layui-card-header">code</div>
<div class="layui-card-body">
<textarea name="" class="layui-textarea js-show"></textarea>
</div>
</div>
</div>
</div>
</div>
</body>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
layui.use("design");
</script>
</html>

View File

@ -0,0 +1,260 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>浏览页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body class="pear-container">
<!-- 顶部查询表单 -->
<div class="layui-card">
<div class="layui-card-body">
<form class="layui-form top-search-from">
<div class="layui-form-item">
<label class="layui-form-label">名称</label>
<div class="layui-input-block">
<input type="text" name="name" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label"></label>
<button class="pear-btn pear-btn-md pear-btn-primary" lay-submit lay-filter="table-query">
<i class="layui-icon layui-icon-search"></i>查询
</button>
<button type="reset" class="pear-btn pear-btn-md" lay-submit lay-filter="table-reset">
<i class="layui-icon layui-icon-refresh"></i>重置
</button>
</div>
<div class="toggle-btn">
<a class="layui-hide">展开<i class="layui-icon layui-icon-down"></i></a>
<a class="layui-hide">收起<i class="layui-icon layui-icon-up"></i></a>
</div>
</form>
</div>
</div>
<!-- 数据表格 -->
<div class="layui-card">
<div class="layui-card-body">
<table id="data-table" lay-filter="data-table"></table>
</div>
</div>
<!-- 表格顶部工具栏 -->
<script type="text/html" id="table-toolbar">
<button class="pear-btn pear-btn-primary pear-btn-md" lay-event="add" permission="app.admin.dict.insert">
<i class="layui-icon layui-icon-add-1"></i>新增
</button>
<button class="pear-btn pear-btn-danger pear-btn-md" lay-event="batchRemove" permission="app.admin.dict.delete">
<i class="layui-icon layui-icon-delete"></i>删除
</button>
</script>
<!-- 表格行工具栏 -->
<script type="text/html" id="table-bar">
<button class="pear-btn pear-btn-xs tool-btn" lay-event="edit" permission="app.admin.dict.update">编辑</button>
<button class="pear-btn pear-btn-xs tool-btn" lay-event="remove" permission="app.admin.dict.delete">删除</button>
</script>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script src="/app/admin/admin/js/common.js"></script>
<script>
// 相关接口
const SELECT_API = "/app/admin/dict/select";
const UPDATE_API = "/app/admin/dict/update";
const DELETE_API = "/app/admin/dict/delete";
const INSERT_URL = "/app/admin/dict/insert";
const UPDATE_URL = "/app/admin/dict/update";
// 表格渲染
layui.use(["table", "form", "common", "popup", "util"], function() {
let table = layui.table;
let form = layui.form;
let $ = layui.$;
let common = layui.common;
let util = layui.util;
// 表头参数
let cols = [
{
type: "checkbox"
},{
title: "名称",
field: "name",
width: 160,
},{
title: "值",
field: "value",
},{
title: "创建时间",
field: "created_at",
width: 180,
hide: true,
},{
title: "更新时间",
field: "updated_at",
width: 180,
hide: true,
},{
title: "操作",
toolbar: "#table-bar",
align: "center",
width: 130,
fixed: "right",
}
];
// 渲染表格
function render()
{
table.render({
elem: "#data-table",
url: SELECT_API,
page: true,
cols: [cols],
skin: "line",
size: "lg",
toolbar: "#table-toolbar",
autoSort: false,
defaultToolbar: [{
title: "刷新",
layEvent: "refresh",
icon: "layui-icon-refresh",
}, "filter", "print", "exports"]
});
}
render();
// 编辑或删除行事件
table.on("tool(data-table)", function(obj) {
if (obj.event === "remove") {
remove(obj);
} else if (obj.event === "edit") {
edit(obj);
}
});
// 表格顶部工具栏事件
table.on("toolbar(data-table)", function(obj) {
if (obj.event === "add") {
add();
} else if (obj.event === "refresh") {
refreshTable();
} else if (obj.event === "batchRemove") {
batchRemove(obj);
}
});
// 表格顶部搜索事件
form.on("submit(table-query)", function(data) {
table.reload("data-table", {
page: {
curr: 1
},
where: data.field
})
return false;
});
// 表格顶部搜索重置事件
form.on("submit(table-reset)", function(data) {
table.reload("data-table", {
where: []
})
});
// 表格排序事件
table.on("sort(data-table)", function(obj){
table.reload("data-table", {
initSort: obj,
scrollPos: "fixed",
where: {
field: obj.field,
order: obj.type
}
});
});
// 表格新增数据
let add = function() {
layer.open({
type: 2,
title: "新增",
shade: 0.1,
area: [common.isModile()?"100%":"500px", common.isModile()?"100%":"500px"],
content: INSERT_URL
});
}
// 表格编辑数据
let edit = function(obj) {
let value = obj.data["name"];
layer.open({
type: 2,
title: "修改",
shade: 0.1,
area: [common.isModile()?"100%":"500px", common.isModile()?"100%":"500px"],
content: UPDATE_URL + "?name=" + value
});
}
// 删除一行
let remove = function(obj) {
return doRemove(obj.data["name"]);
}
// 删除多行
let batchRemove = function(obj) {
let checkIds = common.checkField(obj, "name");
if (checkIds === "") {
layui.popup.warning("未选中数据");
return false;
}
doRemove(checkIds.split(","));
}
// 执行删除
let doRemove = function (ids) {
let data = {};
data["name"] = ids;
layer.confirm("确定删除?", {
icon: 3,
title: "提示"
}, function(index) {
layer.close(index);
let loading = layer.load();
$.ajax({
url: DELETE_API,
data: data,
dataType: "json",
type: "post",
success: function(res) {
layer.close(loading);
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", refreshTable);
}
})
});
}
// 刷新表格数据
window.refreshTable = function(param) {
table.reloadData("data-table", {
scrollPos: "fixed"
});
}
})
</script>
</body>
</html>

View File

@ -0,0 +1,224 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>新增字典</title>
<link rel="stylesheet" href="/app/admin/component/layui/css/layui.css" />
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body>
<style>
.mainBox {
width: auto !important;
}
.layui-tab .layui-table-cell {
overflow:visible !important;
}
.layui-table-body ,.layui-table-box{
overflow:visible !important;
}
.layui-tab .layui-form-select dl {
max-height: 190px;
}
.layui-table-body .layui-table-col-special:last-child {
width: 100% !important;
border-right: 1px solid #eee !important;
}
xm-select {
min-height: 38px;
line-height: 38px;
}
xm-select .xm-body .xm-option .xm-option-icon {
font-size: 18px !important;
}
</style>
<form class="layui-form" action="" lay-filter="create-dict-form">
<div class="mainBox">
<div class="main-container mr-5">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label required" style="width:auto">字典名</label>
<div class="layui-input-inline">
<input type="text" name="name" required lay-verify="required" autocomplete="off" class="layui-input" placeholder="请输入英文字母组合">
</div>
</div>
</div>
<div>
<!-- 字段属性 -->
<table id="column-table" lay-filter="column-table"></table>
<script type="text/html" id="column-toolbar">
<button type="button" class="pear-btn pear-btn-primary pear-btn-md" lay-event="add">
<i class="layui-icon layui-icon-add-1"></i>新增
</button>
<button type="button" class="pear-btn pear-btn-danger pear-btn-md" lay-event="batchRemove">
<i class="layui-icon layui-icon-delete"></i>删除
</button>
</script>
<script type="text/html" id="col-value">
<input type="text" name="value[{{ d.LAY_INDEX-1 }}][value]" placeholder="值" autocomplete="off" class="layui-input" value="{{ d.value }}">
<input type="hidden" name="value[{{ d.LAY_INDEX-1 }}][_field_id]" value="{{ d._field_id }}">
</script>
<script type="text/html" id="col-name">
<input type="text" name="value[{{ d.LAY_INDEX-1 }}][name]" placeholder="标题" autocomplete="off" class="layui-input" value="{{ d.name }}">
</script>
</div>
</div>
</div>
<div class="bottom">
<div class="button-container">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit=""
lay-filter="save">
提交
</button>
<button type="reset" class="pear-btn pear-btn-md">
重置
</button>
</div>
</div>
</form>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
const INSERT_API = "/app/admin/dict/insert";
// 字段设置
layui.use(["table", "common", "popup"], function () {
let table = layui.table;
let common = layui.common;
let cols = [
{
type: "checkbox",
width: 50,
},
{
title: "值",
field: "value",
templet: "#col-value"
},
{
title: "标题",
field: "name",
templet: "#col-name",
}
];
window._field_id = 0;
let data = [{
_field_id: _field_id++,
value : "",
name: "",
}];
table.render({
elem: "#column-table",
cols: [cols],
data: data,
cellMinWidth: 40,
skin: "line",
size: "lg",
limit: 10000,
page: false,
toolbar: "#column-toolbar",
defaultToolbar: [],
});
table.on("toolbar(column-table)", function(obj) {
if (obj.event === "add") {
add();
} else if (obj.event === "batchRemove") {
batchRemove(obj);
}
});
let add = function() {
syncTableData();
let options = table.getData("column-table");
options.push({
_field_id: _field_id++,
value : "",
name: "",
});
table.reloadData("column-table", {data:options});
}
let batchRemove = function(obj) {
var checkIds = common.checkField(obj,"_field_id");
if (checkIds === "") return layui.popup.warning("未选中数据");
let data = table.getData("column-table");
let newData = [];
let deleteIds = checkIds.split(",");
layui.each(data, function (index, item) {
if (deleteIds.indexOf(item._field_id + "") === -1) {
newData.push(item);
}
});
table.reloadData("column-table", {data: newData})
}
});
layui.use(["form", "popup"], function () {
//提交事件
layui.form.on("submit(save)", function () {
let data = layui.form.val("create-dict-form");
layui.$.ajax({
url: INSERT_API,
type: "POST",
dateType: "json",
data: data,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", function () {
parent.refreshTable();
parent.layer.close(parent.layer.getFrameIndex(window.name));
});
}
});
return false;
});
});
window.syncTableData = function () {
let tableData = layui.form.val("create-dict-form");
let columnTableData = [];
let len = Object.keys(tableData).length;
let id = 0;
window._key_id = 0;
while (id < len) {
// column data
if (typeof tableData["value[" + id + "][_field_id]"] !== "undefined") {
columnTableData.push({
_field_id: tableData["value[" + id + "][_field_id]"],
name : tableData["value[" + id + "][name]"],
value: tableData["value[" + id + "][value]"],
});
}
_key_id++;
id++;
}
layui.table.reloadData("column-table", {data: columnTableData});
}
</script>
</body>
</html>

View File

@ -0,0 +1,240 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>更新字典</title>
<link rel="stylesheet" href="/app/admin/component/layui/css/layui.css" />
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body>
<style>
.mainBox {
width: auto !important;
}
.layui-tab .layui-table-cell {
overflow:visible !important;
}
.layui-table-body ,.layui-table-box{
overflow:visible !important;
}
.layui-tab .layui-form-select dl {
max-height: 190px;
}
.layui-table-body .layui-table-col-special:last-child {
width: 100% !important;
border-right: 1px solid #eee !important;
}
xm-select {
min-height: 38px;
line-height: 38px;
}
xm-select .xm-body .xm-option .xm-option-icon {
font-size: 18px !important;
}
</style>
<form class="layui-form" action="" lay-filter="create-dict-form">
<div class="mainBox">
<div class="main-container mr-5">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label required" style="width:auto">字典名</label>
<div class="layui-input-inline">
<input type="text" name="name" required lay-verify="required" autocomplete="off" class="layui-input" placeholder="请输入英文字母组合">
</div>
</div>
</div>
<div>
<!-- 字段属性 -->
<table id="column-table" lay-filter="column-table"></table>
<script type="text/html" id="column-toolbar">
<button type="button" class="pear-btn pear-btn-primary pear-btn-md" lay-event="add">
<i class="layui-icon layui-icon-add-1"></i>新增
</button>
<button type="button" class="pear-btn pear-btn-danger pear-btn-md" lay-event="batchRemove">
<i class="layui-icon layui-icon-delete"></i>删除
</button>
</script>
<script type="text/html" id="col-value">
<input type="text" name="value[{{ d.LAY_INDEX-1 }}][value]" placeholder="值" autocomplete="off" class="layui-input" value="{{ d.value }}">
<input type="hidden" name="value[{{ d.LAY_INDEX-1 }}][_field_id]" value="{{ d._field_id }}">
</script>
<script type="text/html" id="col-name">
<input type="text" name="value[{{ d.LAY_INDEX-1 }}][name]" placeholder="标题" autocomplete="off" class="layui-input" value="{{ d.name }}">
</script>
</div>
</div>
</div>
<div class="bottom">
<div class="button-container">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit=""
lay-filter="save">
提交
</button>
<button type="reset" class="pear-btn pear-btn-md">
重置
</button>
</div>
</div>
</form>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
const DICT_NAME = layui.url().search.name;
const UPDATE_API = "/app/admin/dict/update";
const SELECT_API = "/app/admin/dict/get/" + DICT_NAME;
// 字段设置
layui.use(["table", "common", "popup"], function () {
let table = layui.table;
let common = layui.common;
let $ = layui.$;
let cols = [
{
type: "checkbox",
width: 50,
},
{
title: "值",
field: "value",
templet: "#col-value"
},
{
title: "标题",
field: "name",
templet: "#col-name",
}
];
$('input[name="name"]').val(DICT_NAME);
window._field_id = 0;
let data = [];
$.ajax({
url: SELECT_API,
dataType: "json",
async: false,
success: function (res) {
data = res.data;
layui.each(data, function (k, v) {
data[k]["_field_id"] = _field_id++;
})
// ajax产生错误
if (res.code) {
layui.popup.failure(res.msg);
}
}
});
table.render({
elem: "#column-table",
cols: [cols],
data: data,
cellMinWidth: 40,
skin: "line",
size: "lg",
limit: 10000,
page: false,
toolbar: "#column-toolbar",
defaultToolbar: [],
});
table.on("toolbar(column-table)", function(obj) {
if (obj.event === "add") {
add();
} else if (obj.event === "batchRemove") {
batchRemove(obj);
}
});
let add = function() {
syncTableData();
let options = table.getData("column-table");
options.push({
_field_id: _field_id++,
value : "",
name: "",
});
table.reloadData("column-table", {data:options});
}
let batchRemove = function(obj) {
var checkIds = common.checkField(obj,"_field_id");
if (checkIds === "") return layui.popup.warning("未选中数据");
let data = table.getData("column-table");
let newData = [];
let deleteIds = checkIds.split(",");
layui.each(data, function (index, item) {
if (deleteIds.indexOf(item._field_id + "") === -1) {
newData.push(item);
}
});
table.reloadData("column-table", {data: newData})
}
});
layui.use(["form", "popup"], function () {
//提交事件
layui.form.on("submit(save)", function () {
let data = layui.form.val("create-dict-form");
layui.$.ajax({
url: UPDATE_API,
type: "POST",
dateType: "json",
data: data,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", function () {
parent.refreshTable();
parent.layer.close(parent.layer.getFrameIndex(window.name));
});
}
});
return false;
});
});
window.syncTableData = function () {
let tableData = layui.form.val("create-dict-form");
let columnTableData = [];
let len = Object.keys(tableData).length;
let id = 0;
window._key_id = 0;
while (id < len) {
// column data
if (typeof tableData["value[" + id + "][_field_id]"] !== "undefined") {
columnTableData.push({
_field_id: tableData["value[" + id + "][_field_id]"],
name : tableData["value[" + id + "][name]"],
value: tableData["value[" + id + "][value]"],
});
}
_key_id++;
id++;
}
layui.table.reloadData("column-table", {data: columnTableData});
}
</script>
</body>
</html>

View File

@ -0,0 +1,358 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>控制后台</title>
<meta name="renderer" content="webkit">
<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="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/demos/css/console1.css" />
</head>
<body class="pear-container">
<div>
<div class="layui-row layui-col-space10">
<div class="layui-col-xs6 layui-col-md3">
<div class="layui-card top-panel">
<div class="layui-card-header">今日注册</div>
<div class="layui-card-body">
<div class="layui-row layui-col-space5">
<div class="layui-col-xs8 layui-col-md8 top-panel-number" style="color: #28333E;" id="value1">
0
</div>
<div class="layui-col-xs4 layui-col-md4 top-panel-tips">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 1024 1024" width="200" height="200" t="1591462258798"
p-id="942" version="1.1">
<path fill="#fcc66f" d="M 262.7 835 c -15.3 0 -28.1 -11.4 -29.8 -26.6 L 174.1 291 c -0.6 -5.1 1 -10.2 4.5 -14 s 8.3 -6 13.4 -6 h 640 c 5.1 0 10 2.2 13.4 6 s 5 8.9 4.5 14 l -58.8 517.4 c -1.7 15.2 -14.5 26.6 -29.8 26.6 H 262.7 Z"
p-id="943" />
<path fill="#ffd79c" d="M 802 289 l -58.8 517.4 c -0.7 6.1 -5.8 10.6 -11.9 10.6 h 30 c 6.1 0 11.2 -4.6 11.9 -10.6 L 832 289 h -30 Z"
p-id="944" />
<path fill="#f56e73" d="M 164 307 c -16.5 0 -30 -13.5 -30 -30 v -58 c 0 -16.5 13.5 -30 30 -30 h 696 c 16.5 0 30 13.5 30 30 v 58 c 0 16.5 -13.5 30 -30 30 H 164 Z"
p-id="945" />
<path fill="#ffa1a8" d="M 860 207 h -30 c 6.6 0 12 5.4 12 12 v 58 c 0 6.6 -5.4 12 -12 12 h 30 c 6.6 0 12 -5.4 12 -12 v -58 c 0 -6.6 -5.4 -12 -12 -12 Z"
p-id="946" />
<path fill="#65c8ff" d="M 190.9 651.5 c -31.4 0 -56.9 -25.5 -56.9 -56.9 V 219 c 0 -16.5 13.5 -30 30 -30 h 466.2 c 9.9 0 18 8.1 18 18 v 301.1 c 0 34.7 -28.2 62.9 -62.9 62.9 s -62.9 -28.2 -62.9 -62.9 V 393.5 c 0 -23.2 -18.8 -42 -42 -42 s -42 18.8 -42 42 v 68.1 c 0 29.4 -23.9 53.4 -53.4 53.4 s -53.4 -23.9 -53.4 -53.4 v -68.1 c 0 -23.2 -18.8 -42 -42 -42 s -42 18.8 -42 42 v 201.1 c 0.1 31.4 -25.4 56.9 -56.7 56.9 Z"
p-id="947" />
<path fill="#b3eaff" d="M 277.8 321.5 c -33.1 0 -60 26.9 -60 60 v 201.1 c 0 21.5 -17.4 38.9 -38.9 38.9 c -7.7 0 -14.8 -2.2 -20.8 -6.1 c 6.9 10.9 19 18.1 32.8 18.1 c 21.5 0 38.9 -17.4 38.9 -38.9 V 393.5 c 0 -33.1 26.9 -60 60 -60 c 13.5 0 25.9 4.5 36 12 c -11 -14.5 -28.4 -24 -48 -24 Z M 618.3 207 v 289.1 c 0 24.8 -20.1 44.9 -44.9 44.9 c -9.3 0 -18 -2.8 -25.2 -7.7 c 8.1 11.9 21.7 19.7 37.2 19.7 c 24.8 0 44.9 -20.1 44.9 -44.9 V 207 h -12 Z M 468.5 321.5 c -33.1 0 -60 26.9 -60 60 v 68.1 c 0 19.5 -15.8 35.4 -35.4 35.4 c -6.7 0 -12.9 -1.9 -18.3 -5.1 c 6.2 10.2 17.4 17.1 30.3 17.1 c 19.5 0 35.4 -15.8 35.4 -35.4 v -68.1 c 0 -33.1 26.9 -60 60 -60 c 13.5 0 25.9 4.5 36 12 c -11 -14.5 -28.4 -24 -48 -24 Z"
p-id="948" />
<path fill="#453b56" d="M 698 729.4 m -18 0 a 18 18 0 1 0 36 0 a 18 18 0 1 0 -36 0 Z" p-id="949" />
<path fill="#453b56" d="M 860 171 H 632.5 v 0.1 c -0.7 0 -1.5 -0.1 -2.2 -0.1 H 164 c -26.5 0 -48 21.5 -48 48 v 375.6 c 0 41.3 33.6 74.9 74.9 74.9 c 2.7 0 5.4 -0.2 8.1 -0.5 l 16 141.4 c 2.8 24.3 23.3 42.6 47.7 42.6 h 498.6 c 24.4 0 44.9 -18.3 47.7 -42.6 l 55.2 -485.6 c 24.5 -2.1 43.8 -22.7 43.8 -47.8 v -58 c 0 -26.5 -21.5 -48 -48 -48 Z M 190.9 633.5 c -21.5 0 -38.9 -17.4 -38.9 -38.9 V 219 c 0 -6.6 5.4 -12 12 -12 h 466.3 v 301.1 c 0 24.8 -20.1 44.9 -44.9 44.9 c -24.8 0 -44.9 -20.1 -44.9 -44.9 V 393.5 c 0 -33.1 -26.9 -60 -60 -60 s -60 26.9 -60 60 v 68.1 c 0 19.5 -15.8 35.4 -35.4 35.4 c -19.5 0 -35.4 -15.8 -35.4 -35.4 v -68.1 c 0 -33.1 -26.9 -60 -60 -60 s -60 26.9 -60 60 v 201.1 c 0.1 21.5 -17.4 38.9 -38.8 38.9 Z m 582.3 172.9 c -0.7 6.1 -5.8 10.6 -11.9 10.6 H 262.7 c -6.1 0 -11.2 -4.6 -11.9 -10.6 l -6.7 -59 h 396.6 c 9.9 0 18 -8.1 18 -18 s -8.1 -18 -18 -18 H 240 l -6.3 -55.4 c 19.3 -13.6 32.1 -36 32.1 -61.3 V 393.5 c 0 -13.2 10.8 -24 24 -24 s 24 10.8 24 24 v 68.1 c 0 39.4 32 71.4 71.4 71.4 s 71.4 -32 71.4 -71.4 v -68.1 c 0 -13.2 10.8 -24 24 -24 s 24 10.8 24 24 v 114.6 c 0 44.6 36.3 80.9 80.9 80.9 c 44.6 0 80.9 -36.3 80.9 -80.9 V 325 h 161.7 l -54.9 481.4 Z M 872 277 c 0 6.6 -5.4 12 -12 12 H 666.3 v -82 H 860 c 6.6 0 12 5.4 12 12 v 58 Z"
p-id="950" /></svg>
</div>
</div>
</div>
</div>
</div>
<div class="layui-col-xs6 layui-col-md3">
<div class="layui-card top-panel">
<div class="layui-card-header">7日内注册</div>
<div class="layui-card-body">
<div class="layui-row layui-col-space5">
<div class="layui-col-xs8 layui-col-md8 top-panel-number" style="color: #28333E;" id="value2">
0
</div>
<div class="layui-col-xs4 layui-col-md4 top-panel-tips">
<svg t="1591462430908" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="3170" width="200" height="200">
<path d="M532 784.2c0 24.4-19.8 44.3-44.3 44.3s-44.3-19.8-44.3-44.3c0-24.4 44.3-80.3 44.3-80.3s44.3 55.8 44.3 80.3zM766 784.2c0 24.4 19.8 44.3 44.3 44.3 24.4 0 44.3-19.8 44.3-44.3 0-24.4-44.3-80.3-44.3-80.3S766 759.7 766 784.2z"
fill="#97DCFF" p-id="3171"></path>
<path d="M123.5 471.3c-9.9 0-18-8.1-18-18v-302c0-9.9 8.1-18 18-18h58c9.9 0 18 8.1 18 18v302c0 9.9-8.1 18-18 18h-58z"
fill="#FCC66F" p-id="3172"></path>
<path d="M181.5 151.3v302h-58v-302h58m0-36h-58c-19.9 0-36 16.1-36 36v302c0 19.9 16.1 36 36 36h58c19.9 0 36-16.1 36-36v-302c0-19.8-16.1-36-36-36z"
fill="#453B56" p-id="3173"></path>
<path d="M266.4 210.7m-18 0a18 18 0 1 0 36 0 18 18 0 1 0-36 0Z" fill="#453B56" p-id="3174"></path>
<path d="M430.8 641.1c-9.9 0-18-8.1-18-18v-21.6c0-130.3 106-236.3 236.3-236.3s236.3 106 236.3 236.3v21.6c0 9.9-8.1 18-18 18H430.8z"
fill="#FCC66F" p-id="3175"></path>
<path d="M649 383.2c-5 0-10 0.2-15 0.6 113.5 7.7 203.3 102.2 203.3 217.7v21.6h30v-21.6c0-120.6-97.7-218.3-218.3-218.3z"
fill="#FFD79C" p-id="3176"></path>
<path d="M419.6 694.4c-22.1 0-40.1-18-40.1-40.1s18-40.1 40.1-40.1h458.8c22.1 0 40.1 18 40.1 40.1s-18 40.1-40.1 40.1H419.6z"
fill="#F56E73" p-id="3177"></path>
<path d="M878.4 632.3h-30c12.2 0 22.1 9.9 22.1 22.1s-9.9 22.1-22.1 22.1h30c12.2 0 22.1-9.9 22.1-22.1s-9.9-22.1-22.1-22.1z"
fill="#FFA1A8" p-id="3178"></path>
<path d="M693.3 846.4c0 24.4-19.8 44.3-44.3 44.3-24.4 0-44.3-19.8-44.3-44.3s44.3-80.3 44.3-80.3 44.3 55.9 44.3 80.3z"
fill="#97DCFF" p-id="3179"></path>
<path d="M649 908.7c-34.3 0-62.3-27.9-62.3-62.3 0-28.5 36.9-77.2 48.1-91.4 3.4-4.3 8.6-6.8 14.1-6.8s10.7 2.5 14.1 6.8c11.3 14.2 48.1 62.9 48.1 91.4 0.2 34.3-27.8 62.3-62.1 62.3z m0-112.3c-14.1 20.4-26.3 41.9-26.3 50 0 14.5 11.8 26.3 26.3 26.3s26.3-11.8 26.3-26.3c0-8.1-12.1-29.6-26.3-50z"
fill="#453B56" p-id="3180"></path>
<path d="M903.3 601.9v-0.5c0-134.1-104.4-244.3-236.3-253.6v-30.7c0-68.7-55.9-124.6-124.6-124.6H326.5c-9.9 0-18 8.1-18 18s8.1 18 18 18h215.9c48.8 0 88.6 39.7 88.6 88.6v30.7c-131.8 9.3-236.3 119.4-236.3 253.6v0.5c-19.6 9.3-33.2 29.3-33.2 52.4 0 32 26 58.1 58.1 58.1H459c-14.8 21-33.5 51.5-33.5 71.8 0 34.3 27.9 62.3 62.3 62.3 34.3 0 62.2-27.9 62.2-62.3 0-20.3-18.6-50.7-33.5-71.8h264.9c-14.8 21-33.5 51.5-33.5 71.8 0 34.3 27.9 62.3 62.3 62.3 34.3 0 62.3-27.9 62.3-62.3 0-20.3-18.6-50.7-33.5-71.8h39.4c32 0 58.1-26 58.1-58.1 0-23.1-13.6-43-33.2-52.4zM487.8 810.4c-14.5 0-26.3-11.8-26.3-26.3 0-8.1 12.1-29.6 26.3-50 14.1 20.4 26.2 41.9 26.2 50 0 14.5-11.8 26.3-26.2 26.3z m322.5 0c-14.5 0-26.3-11.8-26.3-26.3 0-8.1 12.1-29.6 26.3-50 14.1 20.4 26.3 41.9 26.3 50-0.1 14.5-11.9 26.3-26.3 26.3zM649 383.2c118.8 0 215.4 94.9 218.1 213.1H430.9c2.8-118.1 99.3-213.1 218.1-213.1z m251.5 271.1c0 12.2-9.9 22.1-22.1 22.1H419.6c-12.2 0-22.1-9.9-22.1-22.1 0-12.2 9.9-22.1 22.1-22.1h458.8c12.2 0.1 22.1 10 22.1 22.1z"
fill="#453B56" p-id="3181"></path>
</svg>
</div>
</div>
</div>
</div>
</div>
<div class="layui-col-xs6 layui-col-md3">
<div class="layui-card top-panel">
<div class="layui-card-header">30日内注册</div>
<div class="layui-card-body">
<div class="layui-row layui-col-space5">
<div class="layui-col-xs8 layui-col-md8 top-panel-number" style="color: #28333E;" id="value3">
0
</div>
<div class="layui-col-xs4 layui-col-md4 top-panel-tips">
<svg t="1591462464512" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="3311" width="200" height="200">
<path d="M750.4 216.5h-130v-15.3c0-32.9-26.8-59.7-59.7-59.7h-97.3c-32.9 0-59.7 26.8-59.7 59.7v15.3h-130c-30.7 0-55.6 25-55.6 55.6v72.4c0 9.9 8.1 18 18 18h31.5v478c0 23.2 18.8 42 42 42h405c23.2 0 42-18.8 42-42v-478H788c9.9 0 18-8.1 18-18v-72.4c0-30.6-25-55.6-55.6-55.6z"
fill="#FCC66F" p-id="3312"></path>
<path d="M708.5 344.5v496c0 13.3-10.7 24-24 24h30c13.3 0 24-10.7 24-24v-496h-30z" fill="#FFD79C" p-id="3313"></path>
<path d="M309.5 882.5c-23.2 0-42-18.8-42-42V596c0-9.9 8.1-18 18-18h36.8c30.2 0 54.8 24.6 54.8 54.8v231.7c0 9.9-8.1 18-18 18h-49.6zM664.9 882.5c-9.9 0-18-8.1-18-18V632.8c0-30.2 24.6-54.8 54.8-54.8h36.8c9.9 0 18 8.1 18 18v244.5c0 23.2-18.8 42-42 42h-49.6z"
fill="#F56E73" p-id="3314"></path>
<path d="M708.5 596v244.5c0 13.3-10.7 24-24 24h30c13.3 0 24-10.7 24-24V596h-30z" fill="#FFA1A8" p-id="3315"></path>
<path d="M475.2 882.5c-9.9 0-18-8.1-18-18V632.8c0-30.2 24.6-54.8 54.8-54.8 30.2 0 54.8 24.6 54.8 54.8v231.7c0 9.9-8.1 18-18 18h-73.6z"
fill="#F56E73" p-id="3316"></path>
<path d="M560.7 159.5h-18c23 0 41.7 18.7 41.7 41.7V221h18v-19.8c-0.1-23-18.7-41.7-41.7-41.7zM750.4 234.5h-30c20.8 0 37.6 16.8 37.6 37.6v72.4h30v-72.4c0-20.8-16.8-37.6-37.6-37.6z"
fill="#FFD79C" p-id="3317"></path>
<path d="M750.4 198.5H638.2c-1.4-41.6-35.6-75-77.5-75h-97.3c-41.9 0-76.1 33.4-77.5 75H273.6c-40.6 0-73.6 33-73.6 73.6v72.4c0 19.9 16.1 36 36 36h13.5v460c0 33.1 26.9 60 60 60H714.7c33.1 0 60-26.9 60-60v-460H788c19.9 0 36-16.1 36-36v-72.4c0-40.6-33-73.6-73.6-73.6z m-287.1-39h97.3c22.1 0 40.2 17.2 41.5 39H421.8c1.4-21.8 19.4-39 41.5-39z m-104.2 705h-49.6c-13.3 0-24-10.7-24-24V596h36.8c20.3 0 36.8 16.5 36.8 36.8v231.7z m189.7 0h-73.6V632.8c0-20.3 16.5-36.8 36.8-36.8 20.3 0 36.8 16.5 36.8 36.8v231.7z m189.7-24c0 13.3-10.7 24-24 24h-49.6V632.8c0-20.3 16.5-36.8 36.8-36.8h36.8v244.5z m0-280.5h-36.8c-40.1 0-72.8 32.6-72.8 72.8v231.7h-44.2V632.8c0-40.1-32.6-72.8-72.8-72.8-40.1 0-72.8 32.6-72.8 72.8v231.7h-44.2V632.8c0-40.1-32.6-72.8-72.8-72.8h-36.8v-74.5h279c9.9 0 18-8.1 18-18s-8.1-18-18-18h-279v-69h453V560zM788 344.5H236v-72.4c0-20.8 16.8-37.6 37.6-37.6h476.8c20.8 0 37.6 16.8 37.6 37.6v72.4z"
fill="#453B56" p-id="3318"></path>
<path d="M621.8 467.5m-18 0a18 18 0 1 0 36 0 18 18 0 1 0-36 0Z" fill="#453B56" p-id="3319"></path>
</svg>
</div>
</div>
</div>
</div>
</div>
<div class="layui-col-xs6 layui-col-md3">
<div class="layui-card top-panel">
<div class="layui-card-header">总注册</div>
<div class="layui-card-body">
<div class="layui-row layui-col-space5">
<div class="layui-col-xs8 layui-col-md8 top-panel-number" style="color: #28333E;" id="value4">
0
</div>
<div class="layui-col-xs4 layui-col-md4 top-panel-tips">
<svg t="1591462491887" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="3449" width="200" height="200">
<path d="M363.2 807c-9.9 0-18-8.1-18-18v-75.5c0-9.9 8.1-18 18-18h108.5c9.9 0 18 8.1 18 18V789c0 9.9-8.1 18-18 18H363.2z"
fill="#F56E73" p-id="3450"></path>
<path d="M441.7 713.5h30V789h-30z" fill="#FFA1A8" p-id="3451"></path>
<path d="M259.6 398c-9.9 0-18-8.1-18-18V178.6c0-23.8 19.3-43.1 43.1-43.1s43.1 19.3 43.1 43.1V380c0 9.9-8.1 18-18 18h-50.2zM525.1 398c-9.9 0-18-8.1-18-18V178.6c0-23.8 19.3-43.1 43.1-43.1s43.1 19.3 43.1 43.1V380c0 9.9-8.1 18-18 18h-50.2z"
fill="#65C8FF" p-id="3452"></path>
<path d="M550.2 153.5c-3.2 0-6.2 0.7-9 1.7 9.4 3.6 16.1 12.7 16.1 23.4V380h18V178.6c0.1-13.9-11.2-25.1-25.1-25.1z"
fill="#97DCFF" p-id="3453"></path>
<path d="M686 330.5H149c-9.9 0-18 8.1-18 18v63c0 9.9 8.1 18 18 18h33.2l45 225c8.7 43.4 47.1 75 91.4 75h197.6c44.3 0 82.7-31.5 91.4-75l45-225H686c9.9 0 18-8.1 18-18v-63c0-9.9-8.1-18-18-18z"
fill="#FCC66F" p-id="3454"></path>
<path d="M608 411.5L560.1 651c-7 35.2-37.9 60.5-73.8 60.5h30c35.9 0 66.7-25.3 73.8-60.5L638 411.5h-30zM656 348.5h30v63h-30z"
fill="#FFD79C" p-id="3455"></path>
<path d="M474.2 543.5m-18 0a18 18 0 1 0 36 0 18 18 0 1 0-36 0Z" fill="#453B56" p-id="3456"></path>
<path d="M416.9 525.5h-125c-9.9 0-18 8.1-18 18s8.1 18 18 18h125c9.9 0 18-8.1 18-18s-8.1-18-18-18zM893 543.5h-33.4c-65.2 0-118.2 53-118.2 118.2v19.6c0 9.9 8.1 18 18 18s18-8.1 18-18v-19.6c0-45.3 36.9-82.2 82.2-82.2H893c9.9 0 18-8.1 18-18s-8-18-18-18zM772.2 744.2c7-7 7-18.4 0-25.5-7-7-18.4-7-25.5 0s-7 18.4 0 25.5 18.4 7.1 25.5 0z"
fill="#453B56" p-id="3457"></path>
<path d="M759.5 761.6c-9.9 0-18 8.1-18 18v11.6c0 43.7-35.6 79.3-79.3 79.3H487.3c-26.4 0-48.3-19.9-51.4-45.5h35.8c19.9 0 36-16.1 36-36v-41.5h8.6c52.8 0 98.7-37.6 109.1-89.4l42.1-210.6H686c19.9 0 36-16.1 36-36v-63c0-19.9-16.1-36-36-36h-74.6V178.6c0-33.7-27.4-61.1-61.1-61.1s-61.1 27.4-61.1 61.1v133.9H345.9V178.6c0-33.7-27.4-61.1-61.1-61.1s-61.1 27.4-61.1 61.1v133.9H149c-19.9 0-36 16.1-36 36v63c0 19.9 16.1 36 36 36h18.5l42.1 210.6c10.4 51.8 56.2 89.4 109.1 89.4h8.6V789c0 19.9 16.1 36 36 36h36.6c3.3 45.5 41.2 81.5 87.5 81.5h174.8c63.6 0 115.3-51.7 115.3-115.3v-11.6c0-10-8.1-18-18-18z m-234.4-583c0-13.9 11.2-25.1 25.1-25.1s25.1 11.2 25.1 25.1v133.9H525V178.6z m-265.5 0c0-13.9 11.2-25.1 25.1-25.1s25.1 11.2 25.1 25.1v133.9h-50.3V178.6zM149 411.5v-63h537v63H149z m169.7 300c-35.9 0-66.7-25.3-73.8-60.5l-40.7-203.5h426.6L590.1 651c-7 35.2-37.9 60.5-73.8 60.5H318.7z m44.5 77.5v-41.5h108.5V789H363.2z"
fill="#453B56" p-id="3458"></path>
</svg>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="layui-row layui-col-space10">
<div class="layui-col-md9">
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-tab custom-tab layui-tab-brief" lay-filter="docDemoTabBrief">
<div id="echarts-records" style="background-color:#ffffff;min-height:400px;padding: 10px"></div>
</div>
</div>
</div>
</div>
<div class="layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">系统信息</div>
<div class="layui-card-body">
<table class="layui-table">
<colgroup>
<col width="50%">
<col>
</colgroup>
<tbody>
<tr>
<td>Workerman版本</td>
<td><?=$workerman_version?></td>
</tr>
<tr>
<td>Webman版本</td>
<td><?=$webman_version?></td>
</tr>
<tr>
<td>WebmanAdmin版本</td>
<td><?=$admin_version?></td>
</tr>
<tr>
<td>PHP版本</td>
<td><?=$php_version?></td>
</tr>
<tr>
<td>MYSQL版本</td>
<td><?=$mysql_version?></td>
</tr>
<tr>
<td>操作系统</td>
<td><?=$os?></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!--</div>-->
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script>
layui.use(['layer', 'echarts', 'element', 'count'], function() {
var $ = layui.jquery,
layer = layui.layer,
element = layui.element,
count = layui.count,
echarts = layui.echarts;
count.up("value1", {
time: 4000,
num: <?=$today_user_count?>,
bit: 0,
regulator: 50
})
count.up("value2", {
time: 4000,
num: <?=$day7_user_count?>,
bit: 0,
regulator: 50
})
count.up("value3", {
time: 4000,
num: <?=$day30_user_count?>,
bit: 0,
regulator: 50
})
count.up("value4", {
time: 4000,
bit: 0,
num: <?=$user_count?>,
regulator: 50
})
var echartsRecords = echarts.init(document.getElementById('echarts-records'), 'walden');
const colorList = ["#9E87FF", '#73DDFF', '#fe9a8b', '#F56948', '#9E87FF']
var option = {
backgroundColor: '#fff',
tooltip: {
show: false
},
grid: {
top: '10%',
bottom: '6%',
left: '6%',
right: '6%',
containLabel: true
},
xAxis: [{
type: 'category',
boundaryGap: false,
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
margin: 10,
//textStyle: {
fontSize: 14,
color: 'rgba(#999)'
//}
},
splitLine: {
show: true,
lineStyle: {
color: '#939ab6',
opacity: .15
}
},
data: <?=json_encode(array_keys($day7_detail))?>
}, ],
yAxis: [{
type: 'value',
offset: 15,
max: 100,
min: 0,
axisTick: {
show: false
},
axisLine: {
show: false
},
axisLabel: {
margin: 10,
//textStyle: {
fontSize: 14,
color: '#999'
//}
},
splitLine: {
show: false
}
}],
series: [{
name: '2',
type: 'line',
z: 3,
showSymbol: false,
smoothMonotone: 'x',
lineStyle: {
width: 3,
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: 'rgba(59,102,246)' // 0% 处的颜色
}, {
offset: 1,
color: 'rgba(118,237,252)' // 100% 处的颜色
}]
},
shadowBlur: 4,
shadowColor: 'rgba(69,126,247,.2)',
shadowOffsetY: 4
},
areaStyle: {
//normal: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: 'rgba(227,233,250,.9)' // 0% 处的颜色
}, {
offset: 1,
color: 'rgba(248,251,252,.3)' // 100% 处的颜色
}]
}
//}
},
smooth: true,
data: <?=json_encode(array_values($day7_detail))?>
},]
};
echartsRecords.setOption(option);
window.onresize = function() {
echartsRecords.resize();
}
});
</script>
</body>
</html>

View File

@ -0,0 +1,144 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>主页</title>
<!-- 依 赖 样 式 -->
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<!-- 加 载 样 式 -->
<link rel="stylesheet" href="/app/admin/admin/css/loader.css" />
<!-- 布 局 样 式 -->
<link rel="stylesheet" href="/app/admin/admin/css/admin.css" />
<!-- 重置样式 -->
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<!-- 结 构 代 码 -->
<body class="layui-layout-body pear-admin">
<!-- 布 局 框 架 -->
<div class="layui-layout layui-layout-admin">
<!-- 顶 部 样 式 -->
<div class="layui-header">
<!-- 菜 单 顶 部 -->
<div class="layui-logo">
<!-- 图 标 -->
<img class="logo">
<!-- 标 题 -->
<span class="title"></span>
</div>
<!-- 顶 部 左 侧 功 能 -->
<ul class="layui-nav layui-layout-left">
<li class="collapse layui-nav-item"><a href="#" class="layui-icon layui-icon-shrink-right"></a></li>
<li class="refresh layui-nav-item"><a href="#" class="layui-icon layui-icon-refresh-1" loading = 600></a></li>
</ul>
<!-- 多 系 统 菜 单 -->
<div id="control" class="layui-layout-control"></div>
<!-- 顶 部 右 侧 菜 单 -->
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item layui-hide-xs"><a href="#" class="menuSearch layui-icon layui-icon-search"></a></li>
<li class="layui-nav-item layui-hide-xs"><a href="#" class="fullScreen layui-icon layui-icon-screen-full"></a></li>
<li class="layui-nav-item layui-hide-xs message"></li>
<li class="layui-nav-item user">
<!-- 头 像 -->
<a class="layui-icon layui-icon-username" href="javascript:;"></a>
<!-- 功 能 菜 单 -->
<dl class="layui-nav-child">
<dd><a user-menu-url="/app/admin/account/index" user-menu-id="10" user-menu-title="基本资料">基本资料</a></dd>
<dd><a href="javascript:void(0);" class="logout">注销登录</a></dd>
</dl>
</li>
<!-- 主 题 配 置 -->
<li class="layui-nav-item setting"><a href="#" class="layui-icon layui-icon-more-vertical"></a></li>
</ul>
</div>
<!-- 侧 边 区 域 -->
<div class="layui-side layui-bg-black">
<!-- 菜 单 顶 部 -->
<div class="layui-logo">
<!-- 图 标 -->
<img class="logo">
<!-- 标 题 -->
<span class="title"></span>
</div>
<!-- 菜 单 内 容 -->
<div class="layui-side-scroll">
<div id="sideMenu"></div>
</div>
</div>
<!-- 视 图 页 面 -->
<div class="layui-body">
<!-- 内 容 页 面 -->
<div id="content"></div>
</div>
<!-- 页脚 -->
<div class="layui-footer layui-text">
<span class="left">
Released under the MIT license.
</span>
<span class="center"></span>
</div>
<!-- 遮 盖 层 -->
<div class="pear-cover"></div>
<!-- 加 载 动 画 -->
<div class="loader-main">
<!-- 动 画 对 象 -->
<div class="loader"></div>
</div>
</div>
<!-- 移 动 端 便 捷 操 作 -->
<div class="pear-collapsed-pe collapse">
<a href="#" class="layui-icon layui-icon-shrink-right"></a>
</div>
<!-- 依 赖 脚 本 -->
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<!-- 框 架 初 始 化 -->
<script>
// Admin
window.Admin = {
Account: {}
};
layui.use(["admin","jquery","popup","drawer"], function() {
var $ = layui.$;
var admin = layui.admin;
var popup = layui.popup;
admin.setConfigType("json");
admin.setConfigPath("/app/admin/config/get");
admin.render();
// 登出逻辑
admin.logout(function(){
$.ajax({
url: "/app/admin/account/logout",
dataType: "json",
success: function (res) {
if (res.code) {
return popup.error(res.msg);
}
popup.success("注销成功",function(){
location.reload();
})
}
});
return false;
})
$.ajax({
url: "/app/admin/account/info",
dataType: 'json',
success: function (res) {
window.Admin.Account = res.data;
}
});
// 消息点击回调
//admin.message(function(id, title, context, form) {});
});
</script>
</body>
</html>

View File

@ -0,0 +1,214 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Webman Admin 安装</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body class="pear-container">
<div class="layui-row layui-col-space10">
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card">
<div class="layui-card-body" style="padding-top: 40px;">
<h1 style="text-align:center;margin: 20px 0 40px">Webman Admin 安装</h1>
<div class="layui-carousel" id="stepForm" lay-filter="stepForm" style="margin: 0 auto;">
<div carousel-item>
<div>
<form class="layui-form" action="javascript:void(0);" style="margin: 0 auto;max-width: 460px;padding-top: 40px;">
<div class="layui-form-item">
<label class="layui-form-label">用户名</label>
<div class="layui-input-block">
<input type="text" placeholder="请填写入用户名" name="user" class="layui-input" lay-verify="required" required value="root" autocomplete="off"/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">密码</label>
<div class="layui-input-block">
<input type="password" placeholder="请填写入密码" name="password" class="layui-input" autocomplete="off"/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">数据库</label>
<div class="layui-input-block">
<input type="text" placeholder="请填写入数据库" name="database" class="layui-input" lay-verify="required" required value="webman_admin"/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">host</label>
<div class="layui-input-block">
<input type="text" placeholder="请填写入数据库host" name="host" class="layui-input" lay-verify="required" required value="127.0.0.1"/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">端口</label>
<div class="layui-input-block">
<input type="number" placeholder="请填写入数据库端口" name="port" class="layui-input" required value="3306" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">强制覆盖</label>
<div class="layui-input-block">
<input type="checkbox" name="overwrite" lay-skin="primary">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button type="button" class="pear-btn next">跳过此步骤</button>
<button class="pear-btn pear-btn-primary" lay-submit lay-filter="formStep">
&emsp;下一步&emsp;
</button>
</div>
</div>
</form>
</div>
<div>
<form class="layui-form" action="javascript:void(0);" style="margin: 0 auto;max-width: 460px;padding-top: 40px;">
<div class="layui-form-item">
<label class="layui-form-label">用户名</label>
<div class="layui-input-block">
<input type="text" placeholder="请填写入用户名" name="username" class="layui-input" lay-verify="required" required />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">密码</label>
<div class="layui-input-block">
<input type="password" placeholder="请填写入密码" name="password" class="layui-input"/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">确认密码</label>
<div class="layui-input-block">
<input type="password" placeholder="请填再次写入密码" name="password_confirm" class="layui-input"/>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button type="button" class="pear-btn pre">上一步</button>
<button class="pear-btn pear-btn-primary" lay-submit lay-filter="formStep2">
&emsp;提交&emsp;
</button>
</div>
</div>
</form>
</div>
<div>
<div style="text-align: center;margin-top: 90px;">
<i class="layui-icon layui-circle pear-back" style="color: white;font-size:30px;font-weight:bold;background: #52C41A;padding: 20px;line-height: 80px;">&#xe605;</i>
<div style="font-size: 24px;color: #333;font-weight: 500;margin-top: 30px;">
安装成功
</div>
<div style="font-size: 14px;color: #666;margin-top: 20px;">需要重启才能生效</div>
</div>
<div style="text-align: center;margin-top: 50px;">
<button class="pear-btn pear-btn-primary finish">进入后台</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
var color = localStorage.getItem("theme-color-color");
var second = localStorage.getItem("theme-color-second");
if (!color || !second) {
localStorage.setItem("theme-color-color", "#2d8cf0");
localStorage.setItem("theme-color-second", "#ecf5ff");
}
</script>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script>
layui.use(["form", "step","code","element", "popup"], function() {
var $ = layui.$,
form = layui.form,
step = layui.step;
layui.code();
step.render({
elem: "#stepForm",
filter: "stepForm",
width: "100%",
stepWidth: "70%",
height: "500px",
stepItems: [{
title: "填写数据库信息"
}, {
title: "填写管理员账户"
}, {
title: "完成"
}]
});
form.on("submit(formStep)", function(data) {
let loading = layer.load();
$.ajax({
url: "/app/admin/install/step1",
type: "POST",
dataType: "json",
data: data.field,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
layui.popup.success("操作成功", function () {
step.next("#stepForm");
});
},
complete: function () {
layer.close(loading);
}
})
return false;
});
form.on("submit(formStep2)", function(data) {
let loading = layer.load();
$.ajax({
url: "/app/admin/install/step2",
type: "POST",
dataType: "json",
data: data.field,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
//layui.popup.success("安装成功");
step.next("#stepForm");
layer.close(loading);
},
complete: function () {
layer.close(loading);
}
})
return false;
});
$(".pre").click(function() {
step.pre("#stepForm");
return false;
});
$(".next").click(function() {
step.next("#stepForm");
return false;
});
$(".finish").click(function() {
location.reload();
});
})
</script>
</body>
</html>

View File

@ -0,0 +1,259 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>浏览页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body class="pear-container">
<!-- 顶部查询表单 -->
<!-- 数据表格 -->
<div class="layui-card">
<div class="layui-card-body">
<table id="data-table" lay-filter="data-table"></table>
</div>
</div>
<!-- 表格顶部工具栏 -->
<script type="text/html" id="table-toolbar">
<button class="pear-btn pear-btn-primary pear-btn-md" lay-event="add" permission="app.admin.project.insert">
<i class="layui-icon layui-icon-add-1"></i>新增
</button>
<button class="pear-btn pear-btn-danger pear-btn-md" lay-event="batchRemove" permission="app.admin.project.delete">
<i class="layui-icon layui-icon-delete"></i>删除
</button>
</script>
<!-- 表格行工具栏 -->
<script type="text/html" id="table-bar">
<button class="pear-btn pear-btn-xs tool-btn" lay-event="edit" permission="app.admin.project.update">编辑</button>
<button class="pear-btn pear-btn-xs tool-btn" lay-event="remove" permission="app.admin.project.delete">删除</button>
</script>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script src="/app/admin/admin/js/common.js"></script>
<script>
// 相关常量
const PRIMARY_KEY = "Id";
const SELECT_API = "/app/admin/project/select";
const UPDATE_API = "/app/admin/project/update";
const DELETE_API = "/app/admin/project/delete";
const INSERT_URL = "/app/admin/project/insert";
const UPDATE_URL = "/app/admin/project/update";
// 表格渲染
layui.use(["table", "form", "common", "popup", "util"], function() {
let table = layui.table;
let form = layui.form;
let $ = layui.$;
let common = layui.common;
let util = layui.util;
// 表头参数
let cols = [
{
type: "checkbox"
},{
title: "Id",
field: "Id",
},{
title: "ProjectName",
field: "ProjectName",
},{
title: "State",
field: "State",
},{
title: "CreateTime",
field: "CreateTime",
},{
title: "CreateUserId",
field: "CreateUserId",
},{
title: "IsDelete",
field: "IsDelete",
},{
title: "IndexImage",
field: "IndexImage",
},{
title: "Remarks",
field: "Remarks",
},{
title: "创建时间",
field: "created_at",
},{
title: "更新时间",
field: "updated_at",
},{
title: "操作",
toolbar: "#table-bar",
align: "center",
fixed: "right",
width: 120,
}
];
// 渲染表格
table.render({
elem: "#data-table",
url: SELECT_API,
page: true,
cols: [cols],
skin: "line",
size: "lg",
toolbar: "#table-toolbar",
autoSort: false,
defaultToolbar: [{
title: "刷新",
layEvent: "refresh",
icon: "layui-icon-refresh",
}, "filter", "print", "exports"],
done: function () {
layer.photos({photos: 'div[lay-id="data-table"]', anim: 5});
}
});
// 编辑或删除行事件
table.on("tool(data-table)", function(obj) {
if (obj.event === "remove") {
remove(obj);
} else if (obj.event === "edit") {
edit(obj);
}
});
// 表格顶部工具栏事件
table.on("toolbar(data-table)", function(obj) {
if (obj.event === "add") {
add();
} else if (obj.event === "refresh") {
refreshTable();
} else if (obj.event === "batchRemove") {
batchRemove(obj);
}
});
// 表格顶部搜索事件
form.on("submit(table-query)", function(data) {
table.reload("data-table", {
page: {
curr: 1
},
where: data.field
})
return false;
});
// 表格顶部搜索重置事件
form.on("submit(table-reset)", function(data) {
table.reload("data-table", {
where: []
})
});
// 字段允许为空
form.verify({
phone: [/(^$)|^1\d{10}$/, "请输入正确的手机号"],
email: [/(^$)|^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/, "邮箱格式不正确"],
url: [/(^$)|(^#)|(^http(s*):\/\/[^\s]+\.[^\s]+)/, "链接格式不正确"],
number: [/(^$)|^\d+$/,'只能填写数字'],
date: [/(^$)|^(\d{4})[-\/](\d{1}|0\d{1}|1[0-2])([-\/](\d{1}|0\d{1}|[1-2][0-9]|3[0-1]))*$/, "日期格式不正确"],
identity: [/(^$)|(^\d{15}$)|(^\d{17}(x|X|\d)$)/, "请输入正确的身份证号"]
});
// 表格排序事件
table.on("sort(data-table)", function(obj){
table.reload("data-table", {
initSort: obj,
scrollPos: "fixed",
where: {
field: obj.field,
order: obj.type
}
});
});
// 表格新增数据
let add = function() {
layer.open({
type: 2,
title: "新增",
shade: 0.1,
area: [common.isModile()?"100%":"500px", common.isModile()?"100%":"450px"],
content: INSERT_URL
});
}
// 表格编辑数据
let edit = function(obj) {
let value = obj.data[PRIMARY_KEY];
layer.open({
type: 2,
title: "修改",
shade: 0.1,
area: [common.isModile()?"100%":"500px", common.isModile()?"100%":"450px"],
content: UPDATE_URL + "?" + PRIMARY_KEY + "=" + value
});
}
// 删除一行
let remove = function(obj) {
return doRemove(obj.data[PRIMARY_KEY]);
}
// 删除多行
let batchRemove = function(obj) {
let checkIds = common.checkField(obj, PRIMARY_KEY);
if (checkIds === "") {
layui.popup.warning("未选中数据");
return false;
}
doRemove(checkIds.split(","));
}
// 执行删除
let doRemove = function (ids) {
let data = {};
data[PRIMARY_KEY] = ids;
layer.confirm("确定删除?", {
icon: 3,
title: "提示"
}, function(index) {
layer.close(index);
let loading = layer.load();
$.ajax({
url: DELETE_API,
data: data,
dataType: "json",
type: "post",
success: function(res) {
layer.close(loading);
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", refreshTable);
}
})
});
}
// 刷新表格数据
window.refreshTable = function(param) {
table.reloadData("data-table", {
scrollPos: "fixed"
});
}
})
</script>
</body>
</html>

View File

@ -0,0 +1,163 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>新增页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body>
<form class="layui-form" action="">
<div class="mainBox">
<div class="main-container mr-5">
<div class="layui-form-item">
<label class="layui-form-label">ProjectName</label>
<div class="layui-input-block">
<textarea name="ProjectName" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">State</label>
<div class="layui-input-block">
<input type="number" name="State" value="-1" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">CreateTime</label>
<div class="layui-input-block">
<input type="text" name="CreateTime" id="CreateTime" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">CreateUserId</label>
<div class="layui-input-block">
<input type="number" name="CreateUserId" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">IsDelete</label>
<div class="layui-input-block">
<input type="number" name="IsDelete" value="-1" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">IndexImage</label>
<div class="layui-input-block">
<textarea name="IndexImage" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">Remarks</label>
<div class="layui-input-block">
<textarea name="Remarks" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">创建时间</label>
<div class="layui-input-block">
<input type="text" name="created_at" id="created_at" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">更新时间</label>
<div class="layui-input-block">
<input type="text" name="updated_at" id="updated_at" autocomplete="off" class="layui-input">
</div>
</div>
</div>
</div>
<div class="bottom">
<div class="button-container">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit=""
lay-filter="save">
提交
</button>
<button type="reset" class="pear-btn pear-btn-md">
重置
</button>
</div>
</div>
</form>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
// 相关接口
const INSERT_API = "/app/admin/project/insert";
// 字段 CreateTime CreateTime
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#CreateTime",
type: "datetime",
});
})
// 字段 创建时间 created_at
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#created_at",
type: "datetime",
});
})
// 字段 更新时间 updated_at
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#updated_at",
type: "datetime",
});
})
//提交事件
layui.use(["form", "popup"], function () {
// 字段验证允许为空
layui.form.verify({
phone: [/(^$)|^1\d{10}$/, "请输入正确的手机号"],
email: [/(^$)|^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/, "邮箱格式不正确"],
url: [/(^$)|(^#)|(^http(s*):\/\/[^\s]+\.[^\s]+)/, "链接格式不正确"],
number: [/(^$)|^\d+$/,'只能填写数字'],
date: [/(^$)|^(\d{4})[-\/](\d{1}|0\d{1}|1[0-2])([-\/](\d{1}|0\d{1}|[1-2][0-9]|3[0-1]))*$/, "日期格式不正确"],
identity: [/(^$)|(^\d{15}$)|(^\d{17}(x|X|\d)$)/, "请输入正确的身份证号"]
});
layui.form.on("submit(save)", function (data) {
layui.$.ajax({
url: INSERT_API,
type: "POST",
dateType: "json",
data: data.field,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", function () {
parent.refreshTable();
parent.layer.close(parent.layer.getFrameIndex(window.name));
});
}
});
return false;
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,199 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>更新页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body>
<form class="layui-form">
<div class="mainBox">
<div class="main-container mr-5">
<div class="layui-form-item">
<label class="layui-form-label">ProjectName</label>
<div class="layui-input-block">
<textarea name="ProjectName" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">State</label>
<div class="layui-input-block">
<input type="number" name="State" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">CreateTime</label>
<div class="layui-input-block">
<input type="text" name="CreateTime" id="CreateTime" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">CreateUserId</label>
<div class="layui-input-block">
<input type="number" name="CreateUserId" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">IsDelete</label>
<div class="layui-input-block">
<input type="number" name="IsDelete" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">IndexImage</label>
<div class="layui-input-block">
<textarea name="IndexImage" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">Remarks</label>
<div class="layui-input-block">
<textarea name="Remarks" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">创建时间</label>
<div class="layui-input-block">
<input type="text" name="created_at" id="created_at" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">更新时间</label>
<div class="layui-input-block">
<input type="text" name="updated_at" id="updated_at" autocomplete="off" class="layui-input">
</div>
</div>
</div>
</div>
<div class="bottom">
<div class="button-container">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit="" lay-filter="save">
提交
</button>
<button type="reset" class="pear-btn pear-btn-md">
重置
</button>
</div>
</div>
</form>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
// 相关接口
const PRIMARY_KEY = "Id";
const SELECT_API = "/app/admin/project/select" + location.search;
const UPDATE_API = "/app/admin/project/update";
// 获取数据库记录
layui.use(["form", "util", "popup"], function () {
let $ = layui.$;
$.ajax({
url: SELECT_API,
dataType: "json",
success: function (res) {
// 给表单初始化数据
layui.each(res.data[0], function (key, value) {
let obj = $('*[name="'+key+'"]');
if (key === "password") {
obj.attr("placeholder", "不更新密码请留空");
return;
}
if (typeof obj[0] === "undefined" || !obj[0].nodeName) return;
if (obj[0].nodeName.toLowerCase() === "textarea") {
obj.val(layui.util.escape(value));
} else {
obj.attr("value", value);
}
});
// 字段 CreateTime CreateTime
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#CreateTime",
type: "datetime",
});
})
// 字段 创建时间 created_at
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#created_at",
type: "datetime",
});
})
// 字段 更新时间 updated_at
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#updated_at",
type: "datetime",
});
})
// ajax返回失败
if (res.code) {
layui.popup.failure(res.msg);
}
}
});
});
//提交事件
layui.use(["form", "popup"], function () {
// 字段验证允许为空
layui.form.verify({
phone: [/(^$)|^1\d{10}$/, "请输入正确的手机号"],
email: [/(^$)|^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/, "邮箱格式不正确"],
url: [/(^$)|(^#)|(^http(s*):\/\/[^\s]+\.[^\s]+)/, "链接格式不正确"],
number: [/(^$)|^\d+$/,'只能填写数字'],
date: [/(^$)|^(\d{4})[-\/](\d{1}|0\d{1}|1[0-2])([-\/](\d{1}|0\d{1}|[1-2][0-9]|3[0-1]))*$/, "日期格式不正确"],
identity: [/(^$)|(^\d{15}$)|(^\d{17}(x|X|\d)$)/, "请输入正确的身份证号"]
});
layui.form.on("submit(save)", function (data) {
data.field[PRIMARY_KEY] = layui.url().search[PRIMARY_KEY];
layui.$.ajax({
url: UPDATE_API,
type: "POST",
dateType: "json",
data: data.field,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", function () {
parent.refreshTable();
parent.layer.close(parent.layer.getFrameIndex(window.name));
});
}
});
return false;
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,250 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>浏览页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body class="pear-container">
<!-- 顶部查询表单 -->
<!-- 数据表格 -->
<div class="layui-card">
<div class="layui-card-body">
<table id="data-table" lay-filter="data-table"></table>
</div>
</div>
<!-- 表格顶部工具栏 -->
<script type="text/html" id="table-toolbar">
<button class="pear-btn pear-btn-primary pear-btn-md" lay-event="add" permission="app.admin.projectdata.insert">
<i class="layui-icon layui-icon-add-1"></i>新增
</button>
<button class="pear-btn pear-btn-danger pear-btn-md" lay-event="batchRemove" permission="app.admin.projectdata.delete">
<i class="layui-icon layui-icon-delete"></i>删除
</button>
</script>
<!-- 表格行工具栏 -->
<script type="text/html" id="table-bar">
<button class="pear-btn pear-btn-xs tool-btn" lay-event="edit" permission="app.admin.projectdata.update">编辑</button>
<button class="pear-btn pear-btn-xs tool-btn" lay-event="remove" permission="app.admin.projectdata.delete">删除</button>
</script>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script src="/app/admin/admin/js/common.js"></script>
<script>
// 相关常量
const PRIMARY_KEY = "Id";
const SELECT_API = "/app/admin/projectdata/select";
const UPDATE_API = "/app/admin/projectdata/update";
const DELETE_API = "/app/admin/projectdata/delete";
const INSERT_URL = "/app/admin/projectdata/insert";
const UPDATE_URL = "/app/admin/projectdata/update";
// 表格渲染
layui.use(["table", "form", "common", "popup", "util"], function() {
let table = layui.table;
let form = layui.form;
let $ = layui.$;
let common = layui.common;
let util = layui.util;
// 表头参数
let cols = [
{
type: "checkbox"
},{
title: "Id",
field: "Id",
},{
title: "ProjectId",
field: "ProjectId",
},{
title: "CreateTime",
field: "CreateTime",
},{
title: "CreateUserId",
field: "CreateUserId",
},{
title: "ContentData",
field: "ContentData",
},{
title: "创建时间",
field: "created_at",
},{
title: "更新时间",
field: "updated_at",
},{
title: "操作",
toolbar: "#table-bar",
align: "center",
fixed: "right",
width: 120,
}
];
// 渲染表格
table.render({
elem: "#data-table",
url: SELECT_API,
page: true,
cols: [cols],
skin: "line",
size: "lg",
toolbar: "#table-toolbar",
autoSort: false,
defaultToolbar: [{
title: "刷新",
layEvent: "refresh",
icon: "layui-icon-refresh",
}, "filter", "print", "exports"],
done: function () {
layer.photos({photos: 'div[lay-id="data-table"]', anim: 5});
}
});
// 编辑或删除行事件
table.on("tool(data-table)", function(obj) {
if (obj.event === "remove") {
remove(obj);
} else if (obj.event === "edit") {
edit(obj);
}
});
// 表格顶部工具栏事件
table.on("toolbar(data-table)", function(obj) {
if (obj.event === "add") {
add();
} else if (obj.event === "refresh") {
refreshTable();
} else if (obj.event === "batchRemove") {
batchRemove(obj);
}
});
// 表格顶部搜索事件
form.on("submit(table-query)", function(data) {
table.reload("data-table", {
page: {
curr: 1
},
where: data.field
})
return false;
});
// 表格顶部搜索重置事件
form.on("submit(table-reset)", function(data) {
table.reload("data-table", {
where: []
})
});
// 字段允许为空
form.verify({
phone: [/(^$)|^1\d{10}$/, "请输入正确的手机号"],
email: [/(^$)|^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/, "邮箱格式不正确"],
url: [/(^$)|(^#)|(^http(s*):\/\/[^\s]+\.[^\s]+)/, "链接格式不正确"],
number: [/(^$)|^\d+$/,'只能填写数字'],
date: [/(^$)|^(\d{4})[-\/](\d{1}|0\d{1}|1[0-2])([-\/](\d{1}|0\d{1}|[1-2][0-9]|3[0-1]))*$/, "日期格式不正确"],
identity: [/(^$)|(^\d{15}$)|(^\d{17}(x|X|\d)$)/, "请输入正确的身份证号"]
});
// 表格排序事件
table.on("sort(data-table)", function(obj){
table.reload("data-table", {
initSort: obj,
scrollPos: "fixed",
where: {
field: obj.field,
order: obj.type
}
});
});
// 表格新增数据
let add = function() {
layer.open({
type: 2,
title: "新增",
shade: 0.1,
area: [common.isModile()?"100%":"500px", common.isModile()?"100%":"450px"],
content: INSERT_URL
});
}
// 表格编辑数据
let edit = function(obj) {
let value = obj.data[PRIMARY_KEY];
layer.open({
type: 2,
title: "修改",
shade: 0.1,
area: [common.isModile()?"100%":"500px", common.isModile()?"100%":"450px"],
content: UPDATE_URL + "?" + PRIMARY_KEY + "=" + value
});
}
// 删除一行
let remove = function(obj) {
return doRemove(obj.data[PRIMARY_KEY]);
}
// 删除多行
let batchRemove = function(obj) {
let checkIds = common.checkField(obj, PRIMARY_KEY);
if (checkIds === "") {
layui.popup.warning("未选中数据");
return false;
}
doRemove(checkIds.split(","));
}
// 执行删除
let doRemove = function (ids) {
let data = {};
data[PRIMARY_KEY] = ids;
layer.confirm("确定删除?", {
icon: 3,
title: "提示"
}, function(index) {
layer.close(index);
let loading = layer.load();
$.ajax({
url: DELETE_API,
data: data,
dataType: "json",
type: "post",
success: function(res) {
layer.close(loading);
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", refreshTable);
}
})
});
}
// 刷新表格数据
window.refreshTable = function(param) {
table.reloadData("data-table", {
scrollPos: "fixed"
});
}
})
</script>
</body>
</html>

View File

@ -0,0 +1,142 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>新增页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body>
<form class="layui-form" action="">
<div class="mainBox">
<div class="main-container mr-5">
<div class="layui-form-item">
<label class="layui-form-label required">ProjectId</label>
<div class="layui-input-block">
<input type="number" name="ProjectId" value="" required lay-verify="required" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">CreateTime</label>
<div class="layui-input-block">
<input type="text" name="CreateTime" id="CreateTime" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">CreateUserId</label>
<div class="layui-input-block">
<input type="number" name="CreateUserId" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">ContentData</label>
<div class="layui-input-block">
<textarea name="ContentData" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">创建时间</label>
<div class="layui-input-block">
<input type="text" name="created_at" id="created_at" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">更新时间</label>
<div class="layui-input-block">
<input type="text" name="updated_at" id="updated_at" autocomplete="off" class="layui-input">
</div>
</div>
</div>
</div>
<div class="bottom">
<div class="button-container">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit=""
lay-filter="save">
提交
</button>
<button type="reset" class="pear-btn pear-btn-md">
重置
</button>
</div>
</div>
</form>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
// 相关接口
const INSERT_API = "/app/admin/projectdata/insert";
// 字段 CreateTime CreateTime
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#CreateTime",
type: "datetime",
});
})
// 字段 创建时间 created_at
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#created_at",
type: "datetime",
});
})
// 字段 更新时间 updated_at
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#updated_at",
type: "datetime",
});
})
//提交事件
layui.use(["form", "popup"], function () {
// 字段验证允许为空
layui.form.verify({
phone: [/(^$)|^1\d{10}$/, "请输入正确的手机号"],
email: [/(^$)|^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/, "邮箱格式不正确"],
url: [/(^$)|(^#)|(^http(s*):\/\/[^\s]+\.[^\s]+)/, "链接格式不正确"],
number: [/(^$)|^\d+$/,'只能填写数字'],
date: [/(^$)|^(\d{4})[-\/](\d{1}|0\d{1}|1[0-2])([-\/](\d{1}|0\d{1}|[1-2][0-9]|3[0-1]))*$/, "日期格式不正确"],
identity: [/(^$)|(^\d{15}$)|(^\d{17}(x|X|\d)$)/, "请输入正确的身份证号"]
});
layui.form.on("submit(save)", function (data) {
layui.$.ajax({
url: INSERT_API,
type: "POST",
dateType: "json",
data: data.field,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", function () {
parent.refreshTable();
parent.layer.close(parent.layer.getFrameIndex(window.name));
});
}
});
return false;
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,178 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>更新页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body>
<form class="layui-form">
<div class="mainBox">
<div class="main-container mr-5">
<div class="layui-form-item">
<label class="layui-form-label required">ProjectId</label>
<div class="layui-input-block">
<input type="number" name="ProjectId" value="" required lay-verify="required" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">CreateTime</label>
<div class="layui-input-block">
<input type="text" name="CreateTime" id="CreateTime" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">CreateUserId</label>
<div class="layui-input-block">
<input type="number" name="CreateUserId" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">ContentData</label>
<div class="layui-input-block">
<textarea name="ContentData" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">创建时间</label>
<div class="layui-input-block">
<input type="text" name="created_at" id="created_at" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">更新时间</label>
<div class="layui-input-block">
<input type="text" name="updated_at" id="updated_at" autocomplete="off" class="layui-input">
</div>
</div>
</div>
</div>
<div class="bottom">
<div class="button-container">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit="" lay-filter="save">
提交
</button>
<button type="reset" class="pear-btn pear-btn-md">
重置
</button>
</div>
</div>
</form>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
// 相关接口
const PRIMARY_KEY = "Id";
const SELECT_API = "/app/admin/projectdata/select" + location.search;
const UPDATE_API = "/app/admin/projectdata/update";
// 获取数据库记录
layui.use(["form", "util", "popup"], function () {
let $ = layui.$;
$.ajax({
url: SELECT_API,
dataType: "json",
success: function (res) {
// 给表单初始化数据
layui.each(res.data[0], function (key, value) {
let obj = $('*[name="'+key+'"]');
if (key === "password") {
obj.attr("placeholder", "不更新密码请留空");
return;
}
if (typeof obj[0] === "undefined" || !obj[0].nodeName) return;
if (obj[0].nodeName.toLowerCase() === "textarea") {
obj.val(layui.util.escape(value));
} else {
obj.attr("value", value);
}
});
// 字段 CreateTime CreateTime
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#CreateTime",
type: "datetime",
});
})
// 字段 创建时间 created_at
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#created_at",
type: "datetime",
});
})
// 字段 更新时间 updated_at
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#updated_at",
type: "datetime",
});
})
// ajax返回失败
if (res.code) {
layui.popup.failure(res.msg);
}
}
});
});
//提交事件
layui.use(["form", "popup"], function () {
// 字段验证允许为空
layui.form.verify({
phone: [/(^$)|^1\d{10}$/, "请输入正确的手机号"],
email: [/(^$)|^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/, "邮箱格式不正确"],
url: [/(^$)|(^#)|(^http(s*):\/\/[^\s]+\.[^\s]+)/, "链接格式不正确"],
number: [/(^$)|^\d+$/,'只能填写数字'],
date: [/(^$)|^(\d{4})[-\/](\d{1}|0\d{1}|1[0-2])([-\/](\d{1}|0\d{1}|[1-2][0-9]|3[0-1]))*$/, "日期格式不正确"],
identity: [/(^$)|(^\d{15}$)|(^\d{17}(x|X|\d)$)/, "请输入正确的身份证号"]
});
layui.form.on("submit(save)", function (data) {
data.field[PRIMARY_KEY] = layui.url().search[PRIMARY_KEY];
layui.$.ajax({
url: UPDATE_API,
type: "POST",
dateType: "json",
data: data.field,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", function () {
parent.refreshTable();
parent.layer.close(parent.layer.getFrameIndex(window.name));
});
}
});
return false;
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,298 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>浏览页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body class="pear-container">
<!-- 顶部查询表单 -->
<!-- 数据表格 -->
<div class="layui-card">
<div class="layui-card-body">
<table id="data-table" lay-filter="data-table"></table>
</div>
</div>
<!-- 表格顶部工具栏 -->
<script type="text/html" id="table-toolbar">
<button class="pear-btn pear-btn-primary pear-btn-md" lay-event="add" permission="app.admin.role.insert">
<i class="layui-icon layui-icon-add-1"></i>新增
</button>
<button class="pear-btn pear-btn-danger pear-btn-md" lay-event="batchRemove" permission="app.admin.role.delete">
<i class="layui-icon layui-icon-delete"></i>删除
</button>
</script>
<!-- 表格行工具栏 -->
<script type="text/html" id="table-bar">
{{# if(d.id!==1&&d.pid&&!d.isRoot){ }}
<button class="pear-btn pear-btn-xs tool-btn" lay-event="edit" permission="app.admin.role.update">编辑</button>
<button class="pear-btn pear-btn-xs tool-btn" lay-event="remove" permission="app.admin.role.delete">删除</button>
{{# } }}
</script>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script src="/app/admin/admin/js/common.js"></script>
<script>
// 相关常量
const PRIMARY_KEY = "id";
const SELECT_API = "/app/admin/role/select";
const UPDATE_API = "/app/admin/role/update";
const DELETE_API = "/app/admin/role/delete";
const INSERT_URL = "/app/admin/role/insert";
const UPDATE_URL = "/app/admin/role/update";
// 表格渲染
layui.use(["table", "treetable", "form", "common", "popup", "util"], function() {
let treeTable = layui.treetable;
let table = layui.table;
let form = layui.form;
let $ = layui.$;
let common = layui.common;
let util = layui.util;
// 表头参数
let cols = [
{
type: "checkbox"
},{
title: "角色组",
field: "name",
},{
title: "主键",
field: "id",
},{
title: "权限",
field: "rules",
templet: function (d) {
let field = "rules";
if (typeof d[field] == "undefined") return "";
let items = [];
layui.each((d[field] + "").split(","), function (k , v) {
items.push(apiResults[field][v] || v);
});
return util.escape(items.join(","));
},
hide: true,
},{
title: "创建时间",
field: "created_at",
},{
title: "更新时间",
field: "updated_at",
},{
title: "父级",
field: "pid",
templet: function (d) {
let field = "pid";
if (typeof d[field] == "undefined") return "";
let items = [];
layui.each((d[field] + "").split(","), function (k , v) {
items.push(apiResults[field][v] || v);
});
return util.escape(items.join(","));
},
hide: true,
},{
title: "操作",
toolbar: "#table-bar",
align: "center",
fixed: "right",
width: 120,
}
];
// 渲染表格
function render()
{
treeTable.render({
elem: "#data-table",
url: SELECT_API,
treeColIndex: 1,
treeIdName: "id",
treePidName: "pid",
treeDefaultClose: false,
cols: [cols],
skin: "line",
size: "lg",
toolbar: "#table-toolbar",
defaultToolbar: [{
title: "刷新",
layEvent: "refresh",
icon: "layui-icon-refresh",
}, "filter", "print", "exports"]
});
}
// 获取表格中下拉或树形组件数据
let apis = [];
apis.push(["rules", "/app/admin/rule/get?type=0,1,2"]);
apis.push(["pid", "/app/admin/role/select?format=tree"]);
let apiResults = {};
apiResults["rules"] = [];
apiResults["pid"] = [];
let count = apis.length;
layui.each(apis, function (k, item) {
let [field, url] = item;
$.ajax({
url: url,
dateType: "json",
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
function travel(items) {
for (let k in items) {
let item = items[k];
apiResults[field][item.value] = item.name;
if (item.children) {
travel(item.children);
}
}
}
travel(res.data);
},
complete: function () {
if (--count === 0) {
render();
}
}
});
});
if (!count) {
render();
}
// 编辑或删除行事件
table.on("tool(data-table)", function(obj) {
if (obj.event === "remove") {
remove(obj);
} else if (obj.event === "edit") {
edit(obj);
}
});
// 表格顶部工具栏事件
table.on("toolbar(data-table)", function(obj) {
if (obj.event === "add") {
add();
} else if (obj.event === "refresh") {
refreshTable();
} else if (obj.event === "batchRemove") {
batchRemove(obj);
}
});
// 表格顶部搜索事件
form.on("submit(table-query)", function(data) {
table.reload("data-table", {
page: {
curr: 1
},
where: data.field
})
return false;
});
// 表格顶部搜索重置事件
form.on("submit(table-reset)", function(data) {
table.reload("data-table", {
where: []
})
});
// 表格排序事件
table.on("sort(data-table)", function(obj){
table.reload("data-table", {
initSort: obj,
scrollPos: "fixed",
where: {
field: obj.field,
order: obj.type
}
});
});
// 表格新增数据
let add = function() {
layer.open({
type: 2,
title: "新增",
shade: 0.1,
area: [common.isModile()?"100%":"500px", common.isModile()?"100%":"450px"],
content: INSERT_URL
});
}
// 表格编辑数据
let edit = function(obj) {
let value = obj.data[PRIMARY_KEY];
layer.open({
type: 2,
title: "修改",
shade: 0.1,
area: [common.isModile()?"100%":"500px", common.isModile()?"100%":"450px"],
content: UPDATE_URL + "?" + PRIMARY_KEY + "=" + value
});
}
// 删除一行
let remove = function(obj) {
return doRemove(obj.data[PRIMARY_KEY]);
}
// 删除多行
let batchRemove = function(obj) {
let checkIds = common.checkField(obj, PRIMARY_KEY);
if (checkIds === "") {
layui.popup.warning("未选中数据");
return false;
}
doRemove(checkIds.split(","));
}
// 执行删除
let doRemove = function (ids) {
let data = {};
data[PRIMARY_KEY] = ids;
layer.confirm("确定删除?", {
icon: 3,
title: "提示"
}, function(index) {
layer.close(index);
let loading = layer.load();
$.ajax({
url: DELETE_API,
data: data,
dataType: "json",
type: "post",
success: function(res) {
layer.close(loading);
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", refreshTable);
}
})
});
}
// 刷新表格数据
window.refreshTable = function(param) {
treeTable.reload("#data-table");
}
})
</script>
</body>
</html>

View File

@ -0,0 +1,149 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>新增页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body>
<form class="layui-form" action="">
<div class="mainBox">
<div class="main-container mr-5">
<div class="layui-form-item">
<label class="layui-form-label">父级</label>
<div class="layui-input-block">
<div name="pid" id="pid" value="1" ></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">角色名</label>
<div class="layui-input-block">
<input type="text" name="name" value="" required lay-verify="required" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">权限</label>
<div class="layui-input-block">
<div name="rules" id="rules" value="" ></div>
</div>
</div>
</div>
</div>
<div class="bottom">
<div class="button-container">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit=""
lay-filter="save">
提交
</button>
<button type="reset" class="pear-btn pear-btn-md">
重置
</button>
</div>
</div>
</form>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
// 相关接口
const INSERT_API = "/app/admin/role/insert";
// 字段 权限 rules
layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({
url: "/app/admin/role/rules?id=1",
dataType: "json",
success: function (res) {
let value = layui.$("#rules").attr("value");
let initValue = value ? value.split(",") : [];
layui.xmSelect.render({
el: "#rules",
name: "rules",
initValue: initValue,
data: res.data,
tree: {"show":true,expandedKeys:initValue},
toolbar: {show:true,list:["ALL","CLEAR","REVERSE"]},
})
}
});
});
// 字段 父级 pid
layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({
url: "/app/admin/role/select?format=tree",
dataType: "json",
success: function (res) {
let value = layui.$("#pid").attr("value");
let initValue = value ? value.split(",") : [];
layui.xmSelect.render({
el: "#pid",
name: "pid",
initValue: initValue,
tips: "请选择",
data: res.data,
value: "0",
model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true,
radio: true,
tree: {show: true,"strict":false,"clickCheck":true,"clickExpand":false,expandedKeys:true},
on: function(data){
let id = data.arr[0] ? data.arr[0].value : "";
if (!id) return;
layui.$.ajax({
url: '/app/admin/role/rules?id=' + id,
dataType: 'json',
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
layui.xmSelect.get('#rules')[0].update({data:res.data});
}
});
}
})
if (res.code) {
layui.popup.failure(res.msg);
}
}
});
});
//提交事件
layui.use(["form", "popup"], function () {
layui.form.on("submit(save)", function (data) {
layui.$.ajax({
url: INSERT_API,
type: "POST",
dateType: "json",
data: data.field,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", function () {
parent.refreshTable();
parent.layer.close(parent.layer.getFrameIndex(window.name));
});
}
});
return false;
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,188 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>更新页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body>
<form class="layui-form">
<div class="mainBox">
<div class="main-container mr-5">
<div class="layui-form-item">
<label class="layui-form-label">父级</label>
<div class="layui-input-block">
<div name="pid" id="pid" value="" ></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">角色名</label>
<div class="layui-input-block">
<input type="text" name="name" value="" required lay-verify="required" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">权限</label>
<div class="layui-input-block">
<div name="rules" id="rules" value="" ></div>
</div>
</div>
</div>
</div>
<div class="bottom">
<div class="button-container">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit="" lay-filter="save">
提交
</button>
<button type="reset" class="pear-btn pear-btn-md">
重置
</button>
</div>
</div>
</form>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
// 相关接口
const PRIMARY_KEY = "id";
const SELECT_API = "/app/admin/role/select" + location.search;
const UPDATE_API = "/app/admin/role/update";
// 获取数据库记录
layui.use(["form", "util", "popup"], function () {
let $ = layui.$;
$.ajax({
url: SELECT_API,
dataType: "json",
success: function (res) {
// 给表单初始化数据
layui.each(res.data[0], function (key, value) {
let obj = $('*[name="'+key+'"]');
if (key === "password") {
obj.attr("placeholder", "不更新密码请留空");
return;
}
if (typeof obj[0] === "undefined" || !obj[0].nodeName) return;
if (obj[0].nodeName.toLowerCase() === "textarea") {
obj.val(layui.util.escape(value));
} else {
obj.attr("value", value);
}
});
// 字段 权限 rules
layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({
url: "/app/admin/role/rules?id=" + res.data[0].pid,
dataType: "json",
success: function (res) {
let value = layui.$("#rules").attr("value");
let initValue = value ? value.split(",") : [];
layui.xmSelect.render({
el: "#rules",
name: "rules",
initValue: initValue,
data: res.data,
tree: {"show":true,expandedKeys:initValue},
toolbar: {show:true,list:["ALL","CLEAR","REVERSE"]},
})
if (res.code) {
layui.popup.failure(res.msg);
}
}
});
});
// 字段 父级角色组 pid
layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({
url: "/app/admin/role/select?format=tree",
dataType: "json",
success: function (res) {
let value = layui.$("#pid").attr("value");
let initValue = value ? value.split(",") : [];
layui.xmSelect.render({
el: "#pid",
name: "pid",
initValue: initValue,
tips: "请选择",
toolbar: {show: true, list: ["CLEAR"]},
data: res.data,
value: "0",
model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true,
radio: true,
tree: {show: true,"strict":false,"clickCheck":true,"clickExpand":false,expandedKeys:true},
on: function(data){
let id = data.arr[0] ? data.arr[0].value : "";
if (!id) return;
layui.$.ajax({
url: '/app/admin/role/rules?id=' + id,
dataType: 'json',
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
layui.xmSelect.get('#rules')[0].update({data:res.data});
}
});
}
});
if (res.code) {
layui.popup.failure(res.msg);
}
}
});
});
// ajax产生错误
if (res.code) {
layui.popup.failure(res.msg);
}
}
});
});
//提交事件
layui.use(["form", "popup"], function () {
layui.form.on("submit(save)", function (data) {
data.field[PRIMARY_KEY] = layui.url().search[PRIMARY_KEY];
layui.$.ajax({
url: UPDATE_API,
type: "POST",
dateType: "json",
data: data.field,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", function () {
parent.refreshTable();
parent.layer.close(parent.layer.getFrameIndex(window.name));
});
}
});
return false;
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,280 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body class="pear-container">
<!-- 数据表格 -->
<div class="layui-card">
<div class="layui-card-body">
<table id="data-table" lay-filter="data-table"></table>
</div>
</div>
<!-- 表格顶部工具栏 -->
<script type="text/html" id="table-toolbar">
<button class="pear-btn pear-btn-primary pear-btn-md" lay-event="add" permission="app.admin.rule.insert">
<i class="layui-icon layui-icon-add-1"></i>新增
</button>
<button class="pear-btn pear-btn-danger pear-btn-md" lay-event="batchRemove" permission="app.admin.rule.delete">
<i class="layui-icon layui-icon-delete"></i>删除
</button>
</script>
<!-- 表格行工具栏 -->
<script type="text/html" id="table-bar">
<button class="pear-btn pear-btn-xs tool-btn" lay-event="edit" permission="app.admin.rule.update">编辑</button>
<button class="pear-btn pear-btn-xs tool-btn" lay-event="remove" permission="app.admin.rule.delete">删除</button>
</script>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script src="/app/admin/admin/js/common.js"></script>
<script>
// 相关常量
const PRIMARY_KEY = "id";
const SELECT_API = "/app/admin/rule/select?limit=5000";
const DELETE_API = "/app/admin/rule/delete";
const UPDATE_API = "/app/admin/rule/update";
const INSERT_URL = "/app/admin/rule/insert";
const UPDATE_URL = "/app/admin/rule/update";
// 表格渲染
layui.use(["table", "treetable", "form", "common", "popup", "util"], function() {
let table = layui.table;
let form = layui.form;
let $ = layui.$;
let common = layui.common;
let treeTable = layui.treetable;
let util = layui.util;
// 表格头部列数据
let cols = [
{
type: "checkbox"
},{
title: "标题",
field: "title",
},{
title: "图标",
field: "icon",
templet: function (d) {
return '<i class="layui-icon ' + util.escape(d["icon"]) + '"></i>';
}
},{
title: "主键",
field: "id",
hide: true,
},{
title: "key",
field: "key",
},{
title: "上级菜单",
field: "pid",
hide: true,
templet: function (d) {
let field = "pid";
if (typeof d[field] == "undefined") return "";
let items = [];
layui.each((d[field] + "").split(","), function (k , v) {
items.push(apiResults[field][v] || v);
});
return util.escape(items.join(","));
}
},{
title: "创建时间",
field: "created_at",
hide: true,
},{
title: "更新时间",
field: "updated_at",
hide: true,
},{
title: "url",
field: "href",
},{
title: "类型",
field: "type",
width: 80,
templet: function (d) {
let field = "type";
let value = apiResults["type"][d["type"]] || d["type"];
let css = {"目录":"layui-bg-blue", "菜单": "layui-bg-green", "权限": "layui-bg-orange"}[value];
return '<span class="layui-badge '+css+'">'+util.escape(value)+'</span>';
}
},{
title: "排序",
field: "weight",
width: 80,
},{
title: "操作",
toolbar: "#table-bar",
align: "center",
fixed: "right",
width: 130,
}
];
// 渲染表格
function render()
{
treeTable.render({
elem: "#data-table",
url: SELECT_API,
treeColIndex: 1,
treeIdName: "id",
treePidName: "pid",
treeDefaultClose: true,
cols: [cols],
skin: "line",
size: "lg",
toolbar: "#table-toolbar",
defaultToolbar: [{
title: "刷新",
layEvent: "refresh",
icon: "layui-icon-refresh",
}, "filter", "print", "exports"]
});
}
// 获取下拉菜单及树形组件数据
let apis = [];
let apiResults = {};
apiResults["pid"] = [];
apis.push(["pid", "/app/admin/rule/select?format=tree&type=0,1"]);
apiResults["type"] = ["目录","菜单","权限"];
let count = apis.length;
layui.each(apis, function (k, item) {
let [field, url] = item;
$.ajax({
url: url,
dateType: "json",
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
function travel(items) {
for (let k in items) {
let item = items[k];
apiResults[field][item.value] = item.name;
if (item.children) {
travel(item.children);
}
}
}
travel(res.data);
},
complete: function () {
if (--count === 0) {
render();
}
}
});
});
if (!count) {
render();
}
// 删除或编辑行事件
table.on("tool(data-table)", function(obj) {
if (obj.event === "remove") {
remove(obj);
} else if (obj.event === "edit") {
edit(obj);
}
});
// 添加 批量删除 刷新事件
table.on("toolbar(data-table)", function(obj) {
if (obj.event === "add") {
add();
} else if (obj.event === "refresh") {
refreshTable();
} else if (obj.event === "batchRemove") {
batchRemove(obj);
}
});
// 添加行
let add = function() {
layer.open({
type: 2,
title: "新增",
shade: 0.1,
area: [common.isModile()?"100%":"520px", common.isModile()?"100%":"520px"],
content: INSERT_URL
});
}
// 编辑行
let edit = function(obj) {
let value = obj.data[PRIMARY_KEY];
layer.open({
type: 2,
title: "修改",
shade: 0.1,
area: [common.isModile()?"100%":"520px", common.isModile()?"100%":"520px"],
content: UPDATE_URL + "?" + PRIMARY_KEY + "=" + value
});
}
// 删除行
let remove = function(obj) {
return doRemove(obj.data[PRIMARY_KEY], obj);
}
// 删除多行
let batchRemove = function(obj) {
let checkIds = common.checkField(obj, PRIMARY_KEY);
if (checkIds === "") {
layui.popup.warning("未选中数据");
return false;
}
doRemove(checkIds.split(","));
}
// 执行删除
let doRemove = function (ids, obj) {
let data = {};
data[PRIMARY_KEY] = ids;
layer.confirm("确定删除?", {
icon: 3,
title: "提示"
}, function(index) {
layer.close(index);
let loading = layer.load();
$.ajax({
url: DELETE_API,
data: data,
dataType: "json",
type: "post",
success: function(res) {
layer.close(loading);
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", function () {
return obj ? obj.del() : refreshTable();
});
}
})
});
}
// 刷新表格
window.refreshTable = function(param) {
treeTable.reload("#data-table");
}
})
</script>
</body>
</html>

View File

@ -0,0 +1,174 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>新增页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
<style>
.layui-iconpicker .layui-anim {
bottom: 42px !important;
top: inherit !important;
}
</style>
</head>
<body>
<form class="layui-form" action="">
<div class="mainBox">
<div class="main-container mr-5">
<div class="layui-form-item">
<label class="layui-form-label required">标题</label>
<div class="layui-input-block">
<input type="text" name="title" required lay-verify="required" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">标识</label>
<div class="layui-input-block">
<input type="text" name="key" required lay-verify="required" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">上级菜单</label>
<div class="layui-input-block">
<div name="pid" id="pid" value="0" ></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">url</label>
<div class="layui-input-block">
<input type="text" name="href" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">图标</label>
<div class="layui-input-block">
<input name="icon" id="icon" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">类型</label>
<div class="layui-input-block">
<div name="type" id="type" value="1" ></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">排序</label>
<div class="layui-input-block">
<input type="number" name="weight" value="0" class="layui-input">
</div>
</div>
</div>
</div>
<div class="bottom">
<div class="button-container">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit=""
lay-filter="save">
提交
</button>
<button type="reset" class="pear-btn pear-btn-md">
重置
</button>
</div>
</div>
</form>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
// 接口
const INSERT_URL = "/app/admin/rule/insert";
// 图标选择
layui.use(["iconPicker"], function() {
layui.iconPicker.render({
elem: "#icon",
type: "fontClass",
page: false,
});
});
// 上级菜单
layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({
url: "/app/admin/rule/select?format=tree&type=0,1",
dataType: "json",
success: function (res) {
let value = layui.$("#pid").attr("value");
let initValue = value ? value.split(",") : [];
layui.xmSelect.render({
el: "#pid",
name: "pid",
initValue: initValue,
tips: "无",
toolbar: {show: true, list: ["CLEAR"]},
data: res.data,
value: "0",
model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true,
radio: true,
tree: {show: true,"strict":false,"clickCheck":true,"clickExpand":false},
});
if (res.code) {
return layui.popup.failure(res.msg);
}
}
});
});
// 菜单类型下拉列表
layui.use(["jquery", "xmSelect"], function() {
let value = layui.$("#type").attr("value");
let initValue = value ? value.split(",") : [];
layui.xmSelect.render({
el: "#type",
name: "type",
initValue: initValue,
data: [{"value":"0","name":"目录"},{"value":"1","name":"菜单"},{"value":"2","name":"权限"}],
value: "1",
model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true,
radio: true,
})
});
// 表单提交事件
layui.use(["form", "popup"], function () {
layui.form.on("submit(save)", function (data) {
layui.$.ajax({
url: INSERT_URL,
type: "POST",
dateType: "json",
data: data.field,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", function () {
parent.refreshTable();
parent.layer.close(parent.layer.getFrameIndex(window.name));
});
}
});
return false;
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,207 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>更新页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
<style>
.layui-iconpicker .layui-anim {
bottom: 42px !important;
top: inherit !important;
}
</style>
</head>
<body>
<form class="layui-form">
<div class="mainBox">
<div class="main-container mr-5">
<div class="layui-form-item">
<label class="layui-form-label required">标题</label>
<div class="layui-input-block">
<input type="text" name="title" required lay-verify="required" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">标识</label>
<div class="layui-input-block">
<input type="text" name="key" required lay-verify="required" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">上级菜单</label>
<div class="layui-input-block">
<div name="pid" id="pid" value="0" ></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">url</label>
<div class="layui-input-block">
<input type="text" name="href" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">图标</label>
<div class="layui-input-block">
<input name="icon" id="icon" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">类型</label>
<div class="layui-input-block">
<div name="type" id="type" value="1" ></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">排序</label>
<div class="layui-input-block">
<input type="number" name="weight" value="0" class="layui-input">
</div>
</div>
</div>
</div>
<div class="bottom">
<div class="button-container">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit="" lay-filter="save">
提交
</button>
<button type="reset" class="pear-btn pear-btn-md">
重置
</button>
</div>
</div>
</form>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
// 相关接口
let PRIMARY_KEY = "id";
const SELECT_API = "/app/admin/rule/select" + location.search;
const UPDATE_API = "/app/admin/rule/update";
// 获取行数据
layui.use(["form", "util", "popup"], function () {
let $ = layui.$;
$.ajax({
url: SELECT_API,
dataType: "json",
success: function (res) {
// 赋值表单
layui.each(res.data[0], function (key, value) {
let obj = $('*[name="'+key+'"]');
if (key === "password") {
obj.attr("placeholder", "不更新密码请留空");
return;
}
if (typeof obj[0] === "undefined" || !obj[0].nodeName) return;
if (obj[0].nodeName.toLowerCase() === "textarea") {
obj.html(layui.util.escape(value));
} else {
obj.attr("value", value);
}
});
// 图标选择
layui.use(["iconPicker"], function() {
layui.iconPicker.render({
elem: "#icon",
type: "fontClass",
page: false,
});
});
// 获取上级菜单
layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({
url: "/app/admin/rule/select?format=tree&type=0,1",
dataType: "json",
success: function (res) {
let value = layui.$("#pid").attr("value");
let initValue = value ? value.split(",") : [];
layui.xmSelect.render({
el: "#pid",
name: "pid",
initValue: initValue,
tips: "无",
toolbar: {show: true, list: ["CLEAR"]},
data: res.data,
model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true,
radio: true,
tree: {show: true,"strict":false,"clickCheck":true,"clickExpand":false,expandedKeys: initValue},
});
if (res.code) {
return layui.popup.failure(res.msg);
}
}
});
});
// 菜单类型下拉选择
layui.use(["jquery", "xmSelect"], function() {
let value = layui.$("#type").attr("value");
let initValue = value ? value.split(",") : [];
layui.xmSelect.render({
el: "#type",
name: "type",
initValue: initValue,
data: [{"value":"0","name":"目录"},{"value":"1","name":"菜单"},{"value":"2","name":"权限"}],
model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true,
radio: true,
})
});
// ajax产生错误
if (res.code) {
layui.popup.failure(res.msg);
}
}
});
});
// 提交事件
layui.use(["form", "popup"], function () {
layui.form.on("submit(save)", function (data) {
data.field[PRIMARY_KEY] = layui.url().search[PRIMARY_KEY];
layui.$.ajax({
url: UPDATE_API,
type: "POST",
dateType: "json",
data: data.field,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", function () {
parent.refreshTable();
parent.layer.close(parent.layer.getFrameIndex(window.name));
});
}
});
return false;
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,744 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>新增页面</title>
<link rel="stylesheet" href="/app/admin/component/layui/css/layui.css" />
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body>
<style>
.mainBox {
width: auto !important;
}
.layui-tab .layui-table-cell {
overflow:visible !important;
}
.layui-table-body ,.layui-table-box{
overflow:visible !important;
}
.layui-tab .layui-form-select dl {
max-height: 190px;
}
.layui-table-body .layui-table-col-special:last-child {
width: 100% !important;
border-right: 1px solid #eee !important;
}
.layui-table-view {
min-width: 1114px;
}
xm-select {
min-height: 38px;
line-height: 38px;
}
xm-select .xm-body .xm-option .xm-option-icon {
font-size: 18px !important;
}
</style>
<form class="layui-form" action="" lay-filter="create-table-form">
<div class="mainBox">
<div class="main-container">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">表名</label>
<div class="layui-input-inline">
<input type="text" name="table" required lay-verify="required" autocomplete="off" class="layui-input">
</div>
<label class="layui-form-label">注释</label>
<div class="layui-input-inline">
<input type="text" name="table_comment" required lay-verify="required" autocomplete="off" class="layui-input">
</div>
</div>
</div>
<div class="layui-tab layui-tab-brief" lay-filter="create-table-tab">
<ul class="layui-tab-title">
<li class="layui-this">字段属性</li>
<li>表单属性</li>
<li>索引</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<!-- 字段属性 -->
<table id="column-table" lay-filter="column-table"></table>
<script type="text/html" id="column-toolbar">
<button type="button" class="pear-btn pear-btn-primary pear-btn-md" lay-event="add">
<i class="layui-icon layui-icon-add-1"></i>新增
</button>
<button type="button" class="pear-btn pear-btn-danger pear-btn-md" lay-event="batchRemove">
<i class="layui-icon layui-icon-delete"></i>删除
</button>
</script>
<script type="text/html" id="col-field">
<input type="text" name="columns[{{ d.LAY_INDEX-1 }}][field]" placeholder="字段名称" autocomplete="off" class="layui-input" value="{{ d.field }}">
<input type="hidden" name="columns[{{ d.LAY_INDEX-1 }}][_field_id]" value="{{ d._field_id }}">
</script>
<script type="text/html" id="col-comment">
<input type="text" name="columns[{{ d.LAY_INDEX-1 }}][comment]" placeholder="备注" autocomplete="off" class="layui-input" value="{{ d.comment }}">
</script>
<script type="text/html" id="col-length">
<input type="text" name="columns[{{ d.LAY_INDEX-1 }}][length]" placeholder="长度/值" autocomplete="off" class="layui-input" value="{{ d.length }}">
</script>
<script type="text/html" id="col-default">
<input type="text" name="columns[{{ d.LAY_INDEX-1 }}][default]" placeholder="默认值" autocomplete="off" class="layui-input" value="{{ d.default }}">
</script>
<script type="text/html" id="col-type">
<select name="columns[{{ d.LAY_INDEX-1 }}][type]" lay-verify="">
{{# layui.each(["integer","string","text","date","enum","float","tinyInteger","smallInteger","mediumInteger","bigInteger","unsignedInteger","unsignedTinyInteger","unsignedSmallInteger","unsignedMediumInteger","unsignedBigInteger","decimal","double","mediumText","longText","dateTime","time","timestamp","char","binary","json"], function (index, item) { }}
<option value="{{ item }}" {{ d.type==item?"selected":""}}>{{ item }}</option>
{{# }); }}
</select>
</script>
<script type="text/html" id="col-primary_key">
<input type="checkbox" name="columns[{{ d.LAY_INDEX-1 }}][primary_key]" autocomplete="off" class="layui-input" lay-skin="primary" {{ d.primary_key ? 'checked' : '' }}>
</script>
<script type="text/html" id="col-auto_increment">
<input type="checkbox" name="columns[{{ d.LAY_INDEX-1 }}][auto_increment]" autocomplete="off" class="layui-input" lay-skin="primary" {{ d.auto_increment ? 'checked' : '' }}>
</script>
<script type="text/html" id="col-nullable">
<input type="checkbox" name="columns[{{ d.LAY_INDEX-1 }}][nullable]" autocomplete="off" class="layui-input" lay-skin="primary" {{ d.nullable ? 'checked' : '' }}>
</script>
</div>
<!-- 表单属性 -->
<div class="layui-tab-item">
<table id="form-table" lay-filter="form-table"></table>
<script type="text/html" id="form-field">
<input type="text" name="forms[{{ d.LAY_INDEX-1 }}][field]" autocomplete="off" class="layui-input" value="{{ d.field }}" disabled>
<input type="hidden" name="forms[{{ d.LAY_INDEX-1 }}][_field_id]" value="{{ d._field_id }}">
</script>
<script type="text/html" id="form-comment">
<input type="text" name="forms[{{ d.LAY_INDEX-1 }}][comment]" autocomplete="off" class="layui-input" value="{{ d.comment }}" disabled>
</script>
<script type="text/html" id="form-control">
<select name="forms[{{ d.LAY_INDEX-1 }}][control]" lay-verify="">
{{# layui.each([["input", "文本框"],["inputNumber", "数字文本框"],["textArea", "多行文本"],["richText", "富文本"],["select", "下拉单选"],["selectMulti", "下拉多选"],["treeSelect", "树形单选"],["treeSelectMulti", "树形多选"],["datePicker", "日期选择"],["dateTimePicker", "日期时间选择"],["switch", "开关"],["upload", "上传文件"],["uploadImage", "上传图片"],["iconPicker", "图标选择"]], function (index, item) { }}
<option value="{{ item[0] }}" {{ d.control.toLocaleLowerCase()==item[0].toLocaleLowerCase()?'selected':''}}>{{ item[1] }}</option>
{{# }); }}
</select>
</script>
<script type="text/html" id="form-control_args">
<input type="text" name="forms[{{ d.LAY_INDEX-1 }}][control_args]" placeholder="控件参数" autocomplete="off" class="layui-input" value="{{ d.control_args }}">
</script>
<script type="text/html" id="form-form_show">
<input type="checkbox" name="forms[{{ d.LAY_INDEX-1 }}][form_show]" autocomplete="off" class="layui-input" lay-skin="primary" {{ d.form_show ? 'checked' : '' }}>
</script>
<script type="text/html" id="form-list_show">
<input type="checkbox" name="forms[{{ d.LAY_INDEX-1 }}][list_show]" autocomplete="off" class="layui-input" lay-skin="primary" {{ d.list_show ? 'checked' : '' }}>
</script>
<script type="text/html" id="form-enable_sort">
<input type="checkbox" name="forms[{{ d.LAY_INDEX-1 }}][enable_sort]" autocomplete="off" class="layui-input" lay-skin="primary" {{ d.enable_sort ? 'checked' : '' }}>
</script>
<script type="text/html" id="form-searchable">
<input type="checkbox" name="forms[{{ d.LAY_INDEX-1 }}][searchable]" autocomplete="off" class="layui-input" lay-skin="primary" {{ d.searchable ? 'checked' : '' }}>
</script>
<script type="text/html" id="form-search_type">
<select name="forms[{{ d.LAY_INDEX-1 }}][search_type]" lay-verify="">
{{# layui.each([["normal", "普通查询"], ["between", "范围查询"], ["like", "模糊查询"]], function (index, item) { }}
<option value="{{ item[0] }}" {{ d.search_type==item[0]?'selected':''}}>{{ item[1] }}</option>
{{# }); }}
</select>
</script>
</div>
<!-- 索引 -->
<div class="layui-tab-item">
<div class="layui-tab-item layui-show">
<table id="key-table" lay-filter="key-table"></table>
<script type="text/html" id="key-name">
<input type="text" name="keys[{{ d.LAY_INDEX-1 }}][name]" placeholder="字段名称" autocomplete="off" class="layui-input" value="{{ d.name }}">
</script>
<script type="text/html" id="key-columns">
<div name="keys[{{ d.LAY_INDEX-1 }}][columns]" class="key-columns-div" value="{{ d.columns }}"></div>
</script>
<script type="text/html" id="key-type">
<select name="keys[{{ d.LAY_INDEX-1 }}][type]" lay-verify="">
{{# layui.each(["normal", "unique"], function (index, item) { }}
<option value="{{ item }}" {{ d.type==item?'selected':''}}>{{ item }}</option>
{{# }); }}
</select>
</script>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="bottom">
<div class="button-container">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit=""
lay-filter="save">
提交
</button>
<button type="reset" class="pear-btn pear-btn-md">
重置
</button>
</div>
</div>
</form>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
const CREATE_API = "/app/admin/table/create";
// 字段设置
layui.use(["table", "common", "popup"], function () {
let table = layui.table;
let common = layui.common;
let cols = [
{
type: "checkbox",
width: 50,
},
{
title: "字段名称",
field: "field",
templet: "#col-field",
width: 182
},
{
title: "字段备注",
field: "comment",
templet: "#col-comment",
width: 182
},
{
title: "长度/值",
field: "length",
templet: "#col-length",
width: 182
},
{
title: "默认值",
field: "default",
templet: "#col-default",
width: 182
},
{
title: "字段类型",
field: "type",
templet: "#col-type",
width: 182
},
{
title: "主键",
field: "primary_key",
templet: "#col-primary_key",
width: 50,
align: "center",
},
{
title: "自增",
field: "auto_increment",
templet: "#col-auto_increment",
width: 50,
align: "center",
},
{
title: "为空",
field: "nullable",
templet: "#col-nullable",
width: 50,
align: "center",
},
{
type: "space"
}
];
window._field_id = 0;
let data = [{
_field_id: _field_id++,
field : "id",
comment: "主键",
length: 11,
default: "",
type: "integer",
primary_key: true,
auto_increment: true,
nullable: false,
},{
_field_id: _field_id++,
field : "created_at",
comment: "创建时间",
length: "",
default: "",
type: "dateTime",
primary_key: false,
auto_increment: false,
nullable: true,
},{
_field_id: _field_id++,
field : "updated_at",
comment: "更新时间",
length: "",
default: "",
type: "dateTime",
primary_key: false,
auto_increment: false,
nullable: true,
},{
_field_id: _field_id++,
field : "",
comment: "",
length: "",
default: "",
type: "integer",
primary_key: false,
auto_increment: false,
nullable: true,
}];
table.render({
elem: "#column-table",
cols: [cols],
data: data,
cellMinWidth: 40,
skin: "line",
size: "lg",
limit: 10000,
page: false,
toolbar: "#column-toolbar",
defaultToolbar: [],
});
table.on("toolbar(column-table)", function(obj) {
if (obj.event === "add") {
add();
} else if (obj.event === "batchRemove") {
batchRemove(obj);
}
});
let add = function() {
syncTableData();
let options = table.getData("column-table");
options.push({
_field_id: _field_id++,
field : "",
comment: "",
length: "",
default: "",
type: "integer",
primary_key: false,
auto_increment: false,
nullable: true,
});
table.reloadData("column-table", {data:options});
}
let batchRemove = function(obj) {
var checkIds = common.checkField(obj,"_field_id");
if (checkIds === "") return layui.popup.warning("未选中数据");
let data = table.getData("column-table");
let newData = [];
let deleteIds = checkIds.split(",");
layui.each(data, function (index, item) {
if (deleteIds.indexOf(item._field_id + "") === -1) {
newData.push(item);
}
});
table.reloadData("column-table", {data: newData})
}
});
// 表单设置
layui.use(["table", "common", "element"], function () {
let table = layui.table;
layui.element.on("tab(create-table-tab)", function(){
syncTableData();
});
let cols = [
{
title: "字段名称",
field: "field",
templet: "#form-field",
width: 180
},
{
title: "字段备注",
field: "comment",
templet: "#form-comment",
width: 180
},
{
title: "控件类型",
field: "control",
templet: "#form-control",
width: 180
},
{
title: "控件参数",
field: "control_args",
templet: "#form-control_args",
width: 180
},
{
title: "表单显示",
field: "form_show",
templet: "#form-form_show",
width: 67,
align: "center",
},
{
title: "列表显示",
field: "list_show",
templet: "#form-list_show",
width: 67,
align: "center",
},
{
title: "支持排序",
field: "enable_sort",
templet: "#form-enable_sort",
width: 67,
align: "center",
},
{
title: "支持查询",
field: "searchable",
templet: "#form-searchable",
width: 67,
align: "center",
},
{
title: "查询类型",
field: "search_type",
templet: "#form-search_type",
width: 130,
},
{
type: "space"
}
];
let _id = 0;
let data = [{
_field_id: _id++,
field : "id",
comment: "主键",
control: "inputNumber",
control_args: "",
form_show: false,
list_show: true,
enable_sort: true,
searchable: true,
search_type: "normal",
},{
_field_id: _id++,
field : "created_at",
comment: "创建时间",
control: "dateTimePicker",
control_args: "",
form_show: false,
list_show: true,
enable_sort: true,
searchable: true,
search_type: "normal",
},{
_field_id: _id++,
field : "updated_at",
comment: "更新时间",
control: "dateTimePicker",
control_args: "",
form_show: false,
list_show: true,
enable_sort: false,
searchable: false,
search_type: "normal",
}];
table.render({
elem: "#form-table",
cols: [cols],
data: data,
cellMinWidth: 40,
skin: "line",
size: "lg",
limit: 10000,
page: false,
defaultToolbar: [],
});
});
// 索引设置
layui.use(["table", "common", "xmSelect", "popup"], function () {
let table = layui.table;
let common = layui.common;
let cols = [
{
type: "checkbox",
width: 52,
},
{
title: "索引名称",
field: "name",
templet: "#key-name",
width: 200,
},
{
title: "索引字段",
field: "columns",
templet: "#key-columns",
width: 200,
},
{
title: "索引类型",
field: "type",
templet: "#key-type",
width: 200,
},
{
type: "space"
}
];
window._key_id = 0;
let data = [{
_key_id: _key_id++,
name : "",
columns: "",
type: "normal"
}];
table.render({
elem: "#key-table",
cols: [cols],
data: data,
skin: "line",
size: "lg",
limit: 10000,
page: false,
toolbar: "#column-toolbar",
defaultToolbar: [],
});
table.on("toolbar(key-table)", function(obj) {
if (obj.event === "add") {
add();
} else if (obj.event === "batchRemove") {
batchRemove(obj);
}
});
let add = function() {
syncTableData();
let options = table.getData("key-table");
options.push({
_key_id: _key_id++,
name : "",
columns: "",
type: "normal"
});
table.reloadData("key-table", {data:options});
keyColumnMultiSelectRender();
}
let batchRemove = function(obj) {
var checkIds = common.checkField(obj,"_key_id");
if (checkIds === "") return layui.popup.warning("未选中数据");
let data = table.getData("key-table");
let newData = [];
let deleteIds = checkIds.split(",");
layui.each(data, function (index, item) {
if (deleteIds.indexOf(item._key_id + "") === -1) {
newData.push(item);
}
});
table.reloadData("key-table", {data: newData});
keyColumnMultiSelectRender();
}
window.syncTableData = function () {
let tableData = layui.form.val("create-table-form");
let formTableDataOld = [];
let columnTableData = [];
let formTableData = [];
let keyTableData = [];
let len = Object.keys(tableData).length;
let id = 0;
window._key_id = 0;
while (id < len) {
// column data
if (typeof tableData["columns[" + id + "][_field_id]"] !== "undefined") {
columnTableData.push({
_field_id: tableData["columns[" + id + "][_field_id]"],
field : tableData["columns[" + id + "][field]"],
comment: tableData["columns[" + id + "][comment]"],
length: tableData["columns[" + id + "][length]"],
default: tableData["columns[" + id + "][default]"],
type: tableData["columns[" + id + "][type]"],
primary_key: tableData["columns[" + id + "][primary_key]"] === "on",
auto_increment: tableData["columns[" + id + "][auto_increment]"] === "on",
nullable: tableData["columns[" + id + "][nullable]"] === "on",
});
}
// old form data
if (typeof tableData["forms[" + id + "][_field_id]"] !== "undefined") {
formTableDataOld.push({
_field_id: tableData["forms[" + id + "][_field_id]"],
field : tableData["columns[" + id + "][field]"], // column
comment: tableData["columns[" + id + "][comment]"], //column
control: tableData["forms[" + id + "][control]"],
control_args: tableData["forms[" + id + "][control_args]"],
form_show: tableData["forms[" + id + "][form_show]"] === "on",
list_show: tableData["forms[" + id + "][list_show]"] === "on",
enable_sort: tableData["forms[" + id + "][enable_sort]"] === "on",
searchable: tableData["forms[" + id + "][searchable]"],
search_type: tableData["forms[" + id + "][search_type]"],
});
}
// key data
if (typeof tableData["keys[" + _key_id + "][name]"] !== "undefined") {
keyTableData.push({
_key_id: _key_id,
name: tableData["keys[" + _key_id + "][name]"],
columns: tableData["keys[" + _key_id + "][columns]"],
type: tableData["keys[" + _key_id + "][type]"],
});
}
_key_id++;
id++;
}
let formTableOldDataMap = {};
layui.each(formTableDataOld, function (_, item) {
formTableOldDataMap[item._field_id] = item;
});
// form data
layui.each(columnTableData, function (_, item) {
if (!item.field) return;
let _field_id = item._field_id;
console.log(item.type);
if (!formTableOldDataMap[_field_id]) {
formTableData.push({
_field_id: _field_id,
field : item.field, // column
comment: item.comment, // column
control: item.type.toLocaleString().indexOf("int") !== -1 ? "inputNumber" : "input",
control_args: "",
form_show: true,
list_show: true,
enable_sort: false,
searchable: false,
search_type: "normal",
});
} else {
formTableData.push(formTableOldDataMap[_field_id]);
}
});
layui.table.reloadData("column-table", {data: columnTableData});
layui.table.reloadData("form-table", {data: formTableData});
layui.table.reloadData("key-table", {data: keyTableData});
keyColumnMultiSelectRender();
}
window.keyColumnMultiSelectRender = function () {
layui.use(["jquery", "xmSelect", "table"], function () {
let $ = layui.$;
let table = layui.table;
let columnData = table.getData("column-table");
let data = [];
layui.each(columnData, function (i, item) {
if (item.field) {
data.push({
name: item.field, value:item.field
});
}
});
layui.each($(".key-columns-div"), function (_, dom) {
let name = $(dom).attr("name");
let value = $(dom).attr("value");
let initValue = value ? value.split(",") : [];
layui.xmSelect.render({
el: dom,
name: name,
initValue: initValue,
data: data,
})
});
});
}
keyColumnMultiSelectRender();
});
layui.use(["form", "popup"], function () {
//提交事件
layui.form.on("submit(save)", function () {
syncTableData();
let data = layui.form.val("create-table-form");
layui.$.ajax({
url: CREATE_API,
type: "POST",
dateType: "json",
data: data,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", function () {
parent.refreshTable();
parent.layer.close(parent.layer.getFrameIndex(window.name));
});
}
});
return false;
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,139 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>一键菜单</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
<style>
.layui-iconpicker .layui-anim {
top: 42px !important;
bottom: inherit !important;
}
</style>
</head>
<body>
<form class="layui-form" action="">
<div class="mainBox">
<div class="main-container mr-5">
<input type="hidden" name="table" value="<?=htmlspecialchars($table)?>">
<div class="layui-form-item">
<label class="layui-form-label required">菜单名</label>
<div class="layui-input-block">
<input type="text" name="title" value="" required lay-verify="required" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">图标</label>
<div class="layui-input-block">
<input name="icon" id="icon" value="" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">上级菜单</label>
<div class="layui-input-block">
<div name="pid" id="pid"></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">控制器</label>
<div class="layui-input-block">
<input type="text" name="controller" value="<?=$controller?>" class="layui-input" required lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">模型</label>
<div class="layui-input-block">
<input type="text" name="model" value="<?=$model?>" class="layui-input" required lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">强制覆盖</label>
<div class="layui-input-block">
<input type="checkbox" name="overwrite" lay-skin="primary">
</div>
</div>
</div>
</div>
<div class="bottom">
<div class="button-container">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit=""
lay-filter="save">
提交
</button>
<button type="reset" class="pear-btn pear-btn-md">
重置
</button>
</div>
</div>
</form>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
const CRUD_API = "/app/admin/table/crud";
layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({
url: "/app/admin/rule/select?format=tree&type=0,1",
dataType: "json",
success: function (res) {
let value = layui.$("#pid").attr("value");
let initValue = value ? value.split(",") : [];
layui.xmSelect.render({
el: "#pid",
name: "pid",
initValue: initValue,
tips: "无",
data: res.data,
toolbar: {show: true, list: ["CLEAR"]},
model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true,
radio: true,
tree: {show:true, strict:false, clickCheck:true, clickExpand:false},
});
if (res.code) {
return layui.popup.failure(res.msg);
}
}
});
});
layui.use(["iconPicker"], function() {
layui.iconPicker.render({
elem: "#icon",
type: "fontClass",
page: false
});
});
layui.use(["form", "popup"], function () {
//提交事件
layui.form.on("submit(save)", function (data) {
layui.$.ajax({
url: CRUD_API,
type: "POST",
dateType: "json",
data: data.field,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", function () {
parent.layer.close(parent.layer.getFrameIndex(window.name));
});
}
});
return false;
});
});
</script>
</body>
</html>

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