管理页面

This commit is contained in:
2025-04-30 11:30:08 +08:00
parent b265ef40fc
commit 57bfd0524e
974 changed files with 347241 additions and 71 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
namespace app\controller;
use app\model\Location;
use app\util\Util;
use util\Util;
use support\Request;
class LocationController
+56
View File
@@ -0,0 +1,56 @@
<?php
namespace app\controller;
//use support\Request;
//use support\Log;
//use Webman\Http\Response;
use app\service\OssService;
use Shopwwi\WebmanFilesystem\Facade\Storage;
use support\Log;
use support\Request;
use util\Util;
class UploadController
{
/**
* 不需要登录的方法
*/
protected $noNeedLogin = ['uploadImage'];
// 上传单文件图片
public function uploadImage(Request $request)
{
try {
$file = $request->file('files');
if (!$file || !$file->isValid()) {
return Util::fail($file,'上传失败');
}
$result = Storage::adapter('oss')
->path('lingke_crm/'.date('Ymd'))
->size(1024*1024*5)
->extYes(['image/jpeg','image/png'])
->extNo(['image/gif'])
->upload($file);
$ossservice = new OssService();
$data = [
'path' => $result->file_name,
'original_name' => $result->origin_name,
'image_url' => $ossservice->generatePrivateUrl($result->file_name)
];
return Util::success($data,'上传成功');
}catch (\Throwable $e){
Log::error('文件上传失败: ' . $e->getMessage());
return Util::fail([],'file:'.$e->getFile().'line:'.$e->getLine().'message:'.$e->getMessage());
}
}
}
+12 -1
View File
@@ -3,11 +3,14 @@
namespace app\controller;
use app\model\Vehicle;
use app\util\Util;
use plugin\admin\app\model\Location;
use util\Util;
use support\Request;
class VehicleController
{
protected $status = ['未知','待发车','在途(发往fbo港口)','抵达发货港口','在途(发往目的地仓库)','抵达仓库','售出'];
/**
* Mask VIN number by showing only first 2 and last 6 characters
*/
@@ -29,6 +32,14 @@ class VehicleController
if (isset($vehicle->vin)) {
$vehicle->vin = $this->maskVin($vehicle->vin);
}
$vehicle->status = $this->status[$vehicle->status]??$this->status[0];
// $location = Location::find($vehicle->locations_id);
// $vehicle->location_name = $location ? $location->name : '';
// $final_location = Location::find($vehicle->final_locations_id);
// $vehicle->final_location_name = $final_location ? $final_location->name : '';
$fbo_location = Location::find($vehicle->fbo_locations_id);
$vehicle->fbo_destination = $fbo_location ? $fbo_location->name : '';
});
$result = ['list' => $vehicles,'count'=>count($vehicles)];
return Util::success($result);
+39 -19
View File
@@ -30,23 +30,28 @@ class Monitor
/**
* @var array
*/
protected $paths = [];
protected array $paths = [];
/**
* @var array
*/
protected $extensions = [];
protected array $extensions = [];
/**
* @var array
*/
protected $loadedFiles = [];
protected array $loadedFiles = [];
/**
* @var int
*/
protected int $ppid = 0;
/**
* Pause monitor
* @return void
*/
public static function pause()
public static function pause(): void
{
file_put_contents(static::lockFile(), time());
}
@@ -90,6 +95,7 @@ class Monitor
*/
public function __construct($monitorDir, $monitorExtensions, array $options = [])
{
$this->ppid = function_exists('posix_getppid') ? posix_getppid() : 0;
static::resume();
$this->paths = (array)$monitorDir;
$this->extensions = $monitorExtensions;
@@ -161,8 +167,8 @@ class Monitor
}
echo $file . " updated and reload\n";
// send SIGUSR1 signal to master process for reload
if (DIRECTORY_SEPARATOR === '/') {
posix_kill(posix_getppid(), SIGUSR1);
if (DIRECTORY_SEPARATOR === '/' && $this->ppid > 1) {
posix_kill($this->ppid, SIGUSR1);
} else {
return true;
}
@@ -196,12 +202,21 @@ class Monitor
* @param $memoryLimit
* @return void
*/
public function checkMemory($memoryLimit)
public function checkMemory($memoryLimit): void
{
if (static::isPaused() || $memoryLimit <= 0) {
return;
}
$ppid = posix_getppid();
$ppid = $this->ppid;
if ($ppid <= 1) {
return;
}
$processPath = "/proc/$ppid";
if (!is_dir($processPath)) {
// Process not exist
$this->ppid = 0;
return;
}
$childrenFile = "/proc/$ppid/task/$ppid/children";
if (!is_file($childrenFile) || !($children = file_get_contents($childrenFile))) {
return;
@@ -225,9 +240,10 @@ class Monitor
/**
* Get memory limit
* @return float
* @param $memoryLimit
* @return int
*/
protected function getMemoryLimit($memoryLimit)
protected function getMemoryLimit($memoryLimit): int
{
if ($memoryLimit === 0) {
return 0;
@@ -242,21 +258,25 @@ class Monitor
return 0;
}
$unit = strtolower($memoryLimit[strlen($memoryLimit) - 1]);
if ($unit === 'g') {
$memoryLimit = 1024 * (int)$memoryLimit;
} else if ($unit === 'm') {
$memoryLimit = (int)$memoryLimit;
if ($unit === 'g') {
$memoryLimit = 1024 * $memoryLimit;
} else if ($unit === 'k') {
$memoryLimit = ((int)$memoryLimit / 1024);
$memoryLimit = ($memoryLimit / 1024);
} else if ($unit === 'm') {
$memoryLimit = (int)($memoryLimit);
} else if ($unit === 't') {
$memoryLimit = (1024 * 1024 * $memoryLimit);
} else {
$memoryLimit = ((int)$memoryLimit / (1024 * 1024));
$memoryLimit = ($memoryLimit / (1024 * 1024));
}
if ($memoryLimit < 30) {
$memoryLimit = 30;
if ($memoryLimit < 50) {
$memoryLimit = 50;
}
if ($usePhpIni) {
$memoryLimit = (int)(0.8 * $memoryLimit);
$memoryLimit = (0.8 * $memoryLimit);
}
return $memoryLimit;
return (int)$memoryLimit;
}
}
+55
View File
@@ -0,0 +1,55 @@
<?php
namespace app\service;
use OSS\OssClient;
use OSS\Core\OssException;
class OssService
{
private $accessKeyId;
private $accessKeySecret;
private $bucket;
private $endpoint;
private $ossClient;
public function __construct()
{
$this->accessKeyId = 'LTAI5tAGiH7bkVkmrniXnfkA';
$this->accessKeySecret = 'nqklJBft6t3S0A4ZvxF4ci9ni91kmm';
$this->bucket = 'vehicle-album';
$this->endpoint = 'https://oss-cn-beijing.aliyuncs.com';
$this->ossClient = new OssClient($this->accessKeyId, $this->accessKeySecret, $this->endpoint);
}
/**
* 上传图片到 OSS
* @param string $filePath 本地文件路径
* @param string $objectKey OSS上存储的对象名称
* @return string 图片的 URL
*/
public function uploadImage($filePath, $objectKey)
{
try {
// 上传文件到 OSS
$this->ossClient->uploadFile($this->bucket, $objectKey, $filePath);
// 构造图片的私有 URL
$url = $this->generatePrivateUrl($objectKey);
return $url;
} catch (OssException $e) {
// 错误处理
throw new \RuntimeException('OSS Error: ' . $e->getMessage());
}
}
/**
* 生成图片的私有访问 URL
* @param string $objectKey
* @return string
*/
public function generatePrivateUrl($objectKey, $expireTime = 3600)
{
// 生成 OSS 私有 URL(有效期为 3600 秒)
return $this->ossClient->signUrl($this->bucket, $objectKey, $expireTime);
}
}
+6 -5
View File
@@ -27,11 +27,12 @@
"php": ">=8.1",
"workerman/webman-framework": "^2.1",
"monolog/monolog": "^2.0",
"webman/database": "^2.1",
"illuminate/pagination": "^12.9",
"illuminate/events": "^12.7",
"symfony/var-dumper": "^7.2",
"vlucas/phpdotenv": "^5.6"
"webman/admin": "^2.1",
"vlucas/phpdotenv": "^5.6",
"illuminate/pagination": "^12.10",
"shopwwi/webman-filesystem": "^2.0",
"shopwwi/filesystem-oss": "^1.0",
"iidestiny/flysystem-oss": "4"
},
"suggest": {
"ext-event": "For better performance. "
Generated
+724 -45
View File
@@ -4,8 +4,59 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "bedfd42d0a6262a6d7115c448546d65f",
"content-hash": "b833d228bf22ccb2654dba30b6d19926",
"packages": [
{
"name": "aliyuncs/oss-sdk-php",
"version": "v2.7.2",
"source": {
"type": "git",
"url": "https://github.com/aliyun/aliyun-oss-php-sdk.git",
"reference": "483dd0b8bff5d47f0e4ffc99f6077a295c5ccbb5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aliyun/aliyun-oss-php-sdk/zipball/483dd0b8bff5d47f0e4ffc99f6077a295c5ccbb5",
"reference": "483dd0b8bff5d47f0e4ffc99f6077a295c5ccbb5",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=5.3"
},
"require-dev": {
"php-coveralls/php-coveralls": "*",
"phpunit/phpunit": "*"
},
"type": "library",
"autoload": {
"psr-4": {
"OSS\\": "src/OSS"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Aliyuncs",
"homepage": "http://www.aliyun.com"
}
],
"description": "Aliyun OSS SDK for PHP",
"homepage": "http://www.aliyun.com/product/oss/",
"support": {
"issues": "https://github.com/aliyun/aliyun-oss-php-sdk/issues",
"source": "https://github.com/aliyun/aliyun-oss-php-sdk/tree/v2.7.2"
},
"time": "2024-10-28T10:41:12+00:00"
},
{
"name": "brick/math",
"version": "0.12.3",
@@ -826,9 +877,71 @@
],
"time": "2025-02-03T10:55:03+00:00"
},
{
"name": "iidestiny/flysystem-oss",
"version": "4.0",
"source": {
"type": "git",
"url": "https://github.com/iiDestiny/flysystem-oss.git",
"reference": "6b6cf6850be377c484c086505d942e61afa2875c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/iiDestiny/flysystem-oss/zipball/6b6cf6850be377c484c086505d942e61afa2875c",
"reference": "6b6cf6850be377c484c086505d942e61afa2875c",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"aliyuncs/oss-sdk-php": "^2.4",
"ext-curl": "*",
"ext-json": "*",
"ext-openssl": "*",
"league/flysystem": "^3.0",
"php": "^8.0.2"
},
"require-dev": {
"mockery/mockery": "^1.2",
"phpunit/phpunit": "^6.5",
"symfony/var-dumper": "^3.4"
},
"type": "library",
"autoload": {
"psr-4": {
"Iidestiny\\Flysystem\\Oss\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "iidestiny",
"email": "iidestiny@vip.qq.com"
}
],
"description": "Flysystem adapter for the Oss storage.",
"keywords": [
"alioss",
"laravel",
"oss",
"阿里oss"
],
"support": {
"issues": "https://github.com/iiDestiny/flysystem-oss/issues",
"source": "https://github.com/iiDestiny/flysystem-oss/tree/4.0"
},
"time": "2022-04-19T06:23:50+00:00"
},
{
"name": "illuminate/bus",
"version": "v12.7.1",
"version": "v12.10.1",
"source": {
"type": "git",
"url": "https://github.com/illuminate/bus.git",
@@ -887,16 +1000,16 @@
},
{
"name": "illuminate/collections",
"version": "v12.7.2",
"version": "v12.9.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/collections.git",
"reference": "f1d8ae882c014673b1f7bedab6435989553776e3"
"reference": "811d8676fd7030a7f69b8f298a278c7fd5c67af3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/illuminate/collections/zipball/f1d8ae882c014673b1f7bedab6435989553776e3",
"reference": "f1d8ae882c014673b1f7bedab6435989553776e3",
"url": "https://api.github.com/repos/illuminate/collections/zipball/811d8676fd7030a7f69b8f298a278c7fd5c67af3",
"reference": "811d8676fd7030a7f69b8f298a278c7fd5c67af3",
"shasum": "",
"mirrors": [
{
@@ -912,6 +1025,7 @@
"php": "^8.2"
},
"suggest": {
"illuminate/http": "Required to convert collections to API resources (^12.0).",
"symfony/var-dumper": "Required to use the dump method (^7.2)."
},
"type": "library",
@@ -945,11 +1059,11 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2025-04-03T17:58:47+00:00"
"time": "2025-04-14T15:21:48+00:00"
},
{
"name": "illuminate/conditionable",
"version": "v12.7.2",
"version": "v12.9.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/conditionable.git",
@@ -1001,7 +1115,7 @@
},
{
"name": "illuminate/container",
"version": "v12.7.2",
"version": "v12.9.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/container.git",
@@ -1058,7 +1172,7 @@
},
{
"name": "illuminate/contracts",
"version": "v12.7.2",
"version": "v12.9.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/contracts.git",
@@ -1112,16 +1226,16 @@
},
{
"name": "illuminate/database",
"version": "v12.7.2",
"version": "v12.9.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/database.git",
"reference": "f03c9825a2ec701a214762d2455a530ba854125d"
"reference": "baa39ce37d66f3b36c1d342d9283c385273a8067"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/illuminate/database/zipball/f03c9825a2ec701a214762d2455a530ba854125d",
"reference": "f03c9825a2ec701a214762d2455a530ba854125d",
"url": "https://api.github.com/repos/illuminate/database/zipball/baa39ce37d66f3b36c1d342d9283c385273a8067",
"reference": "baa39ce37d66f3b36c1d342d9283c385273a8067",
"shasum": "",
"mirrors": [
{
@@ -1147,6 +1261,7 @@
"illuminate/console": "Required to use the database commands (^12.0).",
"illuminate/events": "Required to use the observers with Eloquent (^12.0).",
"illuminate/filesystem": "Required to use the migrations (^12.0).",
"illuminate/http": "Required to convert Eloquent models to API resources (^12.0).",
"illuminate/pagination": "Required to paginate the result set (^12.0).",
"symfony/finder": "Required to use Eloquent model factories (^7.2)."
},
@@ -1183,11 +1298,11 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2025-04-03T12:55:52+00:00"
"time": "2025-04-16T14:44:02+00:00"
},
{
"name": "illuminate/events",
"version": "v12.7.2",
"version": "v12.9.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/events.git",
@@ -1248,7 +1363,7 @@
},
{
"name": "illuminate/filesystem",
"version": "v12.7.1",
"version": "v12.9.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/filesystem.git",
@@ -1321,16 +1436,16 @@
},
{
"name": "illuminate/http",
"version": "v12.7.2",
"version": "v12.9.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/http.git",
"reference": "23140773e07bfc84396a5aec70796d269b00aa62"
"reference": "baf0d722b978bd89a6454332aa5313c3fcb5fd25"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/illuminate/http/zipball/23140773e07bfc84396a5aec70796d269b00aa62",
"reference": "23140773e07bfc84396a5aec70796d269b00aa62",
"url": "https://api.github.com/repos/illuminate/http/zipball/baf0d722b978bd89a6454332aa5313c3fcb5fd25",
"reference": "baf0d722b978bd89a6454332aa5313c3fcb5fd25",
"shasum": "",
"mirrors": [
{
@@ -1384,11 +1499,11 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2025-04-03T14:58:53+00:00"
"time": "2025-04-09T21:27:11+00:00"
},
{
"name": "illuminate/macroable",
"version": "v12.7.2",
"version": "v12.9.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/macroable.git",
@@ -1440,7 +1555,7 @@
},
{
"name": "illuminate/pagination",
"version": "v12.9.0",
"version": "v12.10.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/pagination.git",
@@ -1496,7 +1611,7 @@
},
{
"name": "illuminate/pipeline",
"version": "v12.7.0",
"version": "v12.9.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/pipeline.git",
@@ -1550,7 +1665,7 @@
},
{
"name": "illuminate/session",
"version": "v12.5.0",
"version": "v12.9.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/session.git",
@@ -1613,16 +1728,16 @@
},
{
"name": "illuminate/support",
"version": "v12.7.2",
"version": "v12.9.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/support.git",
"reference": "1d5d07534bb2d9f16e655c2cb99607517ad43e8f"
"reference": "a1c8bc9c7bb664e36bdb2280cfae7e3de9517c1d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/illuminate/support/zipball/1d5d07534bb2d9f16e655c2cb99607517ad43e8f",
"reference": "1d5d07534bb2d9f16e655c2cb99607517ad43e8f",
"url": "https://api.github.com/repos/illuminate/support/zipball/a1c8bc9c7bb664e36bdb2280cfae7e3de9517c1d",
"reference": "a1c8bc9c7bb664e36bdb2280cfae7e3de9517c1d",
"shasum": "",
"mirrors": [
{
@@ -1692,20 +1807,20 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2025-04-02T19:35:42+00:00"
"time": "2025-04-16T11:58:41+00:00"
},
{
"name": "laravel/serializable-closure",
"version": "v2.0.4",
"name": "intervention/image",
"version": "2.7.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/serializable-closure.git",
"reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841"
"url": "https://github.com/Intervention/image.git",
"reference": "04be355f8d6734c826045d02a1079ad658322dad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/b352cf0534aa1ae6b4d825d1e762e35d43f8a841",
"reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841",
"url": "https://api.github.com/repos/Intervention/image/zipball/04be355f8d6734c826045d02a1079ad658322dad",
"reference": "04be355f8d6734c826045d02a1079ad658322dad",
"shasum": "",
"mirrors": [
{
@@ -1715,19 +1830,109 @@
]
},
"require": {
"php": "^8.1"
"ext-fileinfo": "*",
"guzzlehttp/psr7": "~1.1 || ^2.0",
"php": ">=5.4.0"
},
"require-dev": {
"illuminate/support": "^10.0|^11.0|^12.0",
"nesbot/carbon": "^2.67|^3.0",
"pestphp/pest": "^2.36|^3.0",
"phpstan/phpstan": "^2.0",
"symfony/var-dumper": "^6.2.0|^7.0.0"
"mockery/mockery": "~0.9.2",
"phpunit/phpunit": "^4.8 || ^5.7 || ^7.5.15"
},
"suggest": {
"ext-gd": "to use GD library based image processing.",
"ext-imagick": "to use Imagick based image processing.",
"intervention/imagecache": "Caching extension for the Intervention Image library"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"Image": "Intervention\\Image\\Facades\\Image"
},
"providers": [
"Intervention\\Image\\ImageServiceProvider"
]
},
"branch-alias": {
"dev-master": "2.4-dev"
}
},
"autoload": {
"psr-4": {
"Intervention\\Image\\": "src/Intervention/Image"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Oliver Vogel",
"email": "oliver@intervention.io",
"homepage": "https://intervention.io/"
}
],
"description": "Image handling and manipulation library with support for Laravel integration",
"homepage": "http://image.intervention.io/",
"keywords": [
"gd",
"image",
"imagick",
"laravel",
"thumbnail",
"watermark"
],
"support": {
"issues": "https://github.com/Intervention/image/issues",
"source": "https://github.com/Intervention/image/tree/2.7.2"
},
"funding": [
{
"url": "https://paypal.me/interventionio",
"type": "custom"
},
{
"url": "https://github.com/Intervention",
"type": "github"
}
],
"time": "2022-05-21T17:30:32+00:00"
},
{
"name": "laravel/serializable-closure",
"version": "v1.3.7",
"source": {
"type": "git",
"url": "https://github.com/laravel/serializable-closure.git",
"reference": "4f48ade902b94323ca3be7646db16209ec76be3d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/4f48ade902b94323ca3be7646db16209ec76be3d",
"reference": "4f48ade902b94323ca3be7646db16209ec76be3d",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": "^7.3|^8.0"
},
"require-dev": {
"illuminate/support": "^8.0|^9.0|^10.0|^11.0",
"nesbot/carbon": "^2.61|^3.0",
"pestphp/pest": "^1.21.3",
"phpstan/phpstan": "^1.8.2",
"symfony/var-dumper": "^5.4.11|^6.2.0|^7.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
"dev-master": "1.x-dev"
}
},
"autoload": {
@@ -1759,7 +1964,213 @@
"issues": "https://github.com/laravel/serializable-closure/issues",
"source": "https://github.com/laravel/serializable-closure"
},
"time": "2025-03-19T13:51:03+00:00"
"time": "2024-11-14T18:34:49+00:00"
},
{
"name": "league/flysystem",
"version": "3.29.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
"reference": "0adc0d9a51852e170e0028a60bd271726626d3f0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/0adc0d9a51852e170e0028a60bd271726626d3f0",
"reference": "0adc0d9a51852e170e0028a60bd271726626d3f0",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"league/flysystem-local": "^3.0.0",
"league/mime-type-detection": "^1.0.0",
"php": "^8.0.2"
},
"conflict": {
"async-aws/core": "<1.19.0",
"async-aws/s3": "<1.14.0",
"aws/aws-sdk-php": "3.209.31 || 3.210.0",
"guzzlehttp/guzzle": "<7.0",
"guzzlehttp/ringphp": "<1.1.1",
"phpseclib/phpseclib": "3.0.15",
"symfony/http-client": "<5.2"
},
"require-dev": {
"async-aws/s3": "^1.5 || ^2.0",
"async-aws/simple-s3": "^1.1 || ^2.0",
"aws/aws-sdk-php": "^3.295.10",
"composer/semver": "^3.0",
"ext-fileinfo": "*",
"ext-ftp": "*",
"ext-mongodb": "^1.3",
"ext-zip": "*",
"friendsofphp/php-cs-fixer": "^3.5",
"google/cloud-storage": "^1.23",
"guzzlehttp/psr7": "^2.6",
"microsoft/azure-storage-blob": "^1.1",
"mongodb/mongodb": "^1.2",
"phpseclib/phpseclib": "^3.0.36",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^9.5.11|^10.0",
"sabre/dav": "^4.6.0"
},
"type": "library",
"autoload": {
"psr-4": {
"League\\Flysystem\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frankdejonge.nl"
}
],
"description": "File storage abstraction for PHP",
"keywords": [
"WebDAV",
"aws",
"cloud",
"file",
"files",
"filesystem",
"filesystems",
"ftp",
"s3",
"sftp",
"storage"
],
"support": {
"issues": "https://github.com/thephpleague/flysystem/issues",
"source": "https://github.com/thephpleague/flysystem/tree/3.29.0"
},
"time": "2024-09-29T11:59:11+00:00"
},
{
"name": "league/flysystem-local",
"version": "3.29.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem-local.git",
"reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27",
"reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"ext-fileinfo": "*",
"league/flysystem": "^3.0.0",
"league/mime-type-detection": "^1.0.0",
"php": "^8.0.2"
},
"type": "library",
"autoload": {
"psr-4": {
"League\\Flysystem\\Local\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frankdejonge.nl"
}
],
"description": "Local filesystem adapter for Flysystem.",
"keywords": [
"Flysystem",
"file",
"files",
"filesystem",
"local"
],
"support": {
"source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0"
},
"time": "2024-08-09T21:24:39+00:00"
},
{
"name": "league/mime-type-detection",
"version": "1.15.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/mime-type-detection.git",
"reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301",
"reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"ext-fileinfo": "*",
"php": "^7.4 || ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.2",
"phpstan/phpstan": "^0.12.68",
"phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0"
},
"type": "library",
"autoload": {
"psr-4": {
"League\\MimeTypeDetection\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frankdejonge.nl"
}
],
"description": "Mime-type detection for Flysystem",
"support": {
"issues": "https://github.com/thephpleague/mime-type-detection/issues",
"source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0"
},
"funding": [
{
"url": "https://github.com/frankdejonge",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/league/flysystem",
"type": "tidelift"
}
],
"time": "2024-01-28T23:22:08+00:00"
},
{
"name": "monolog/monolog",
@@ -2628,6 +3039,124 @@
},
"time": "2019-03-08T08:55:37+00:00"
},
{
"name": "shopwwi/filesystem-oss",
"version": "v1.0.3",
"source": {
"type": "git",
"url": "https://github.com/shopwwi/filesystem-oss.git",
"reference": "85cb385d1a5d11ca3c22bc485a7b18c588fe7645"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/shopwwi/filesystem-oss/zipball/85cb385d1a5d11ca3c22bc485a7b18c588fe7645",
"reference": "85cb385d1a5d11ca3c22bc485a7b18c588fe7645",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"aliyuncs/oss-sdk-php": "^2.4",
"league/flysystem": "^2.1|^3.0",
"php": ">=7.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Shopwwi\\FilesystemOss\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Tycoon Song",
"email": "8988354@qq.com"
}
],
"description": "AliYun OSS adapter for flysystem V2. aliyuncs/oss-sdk-php ~2.4",
"homepage": "https://github.com/phcent/filesystem-oss",
"keywords": [
"Flysystem",
"aliyun-oss",
"flysystem-aliyun-oss"
],
"support": {
"issues": "https://github.com/shopwwi/filesystem-oss/issues",
"source": "https://github.com/shopwwi/filesystem-oss/tree/v1.0.3"
},
"time": "2022-10-01T03:00:18+00:00"
},
{
"name": "shopwwi/webman-filesystem",
"version": "v2.0.0",
"source": {
"type": "git",
"url": "https://github.com/shopwwi/webman-filesystem.git",
"reference": "de33afdbfc56678323f1aafb8d011d79378d164a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/shopwwi/webman-filesystem/zipball/de33afdbfc56678323f1aafb8d011d79378d164a",
"reference": "de33afdbfc56678323f1aafb8d011d79378d164a",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"league/flysystem": "^2.3|^3.0",
"php": ">=7.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Shopwwi\\WebmanFilesystem\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Tycoon Song",
"email": "8988354@qq.com"
}
],
"description": "Flysystem V2 adapter for the webman",
"keywords": [
"cos",
"filesystem",
"oss",
"qiniu",
"webman"
],
"support": {
"issues": "https://github.com/shopwwi/webman-filesystem/issues",
"source": "https://github.com/shopwwi/webman-filesystem/tree/v2.0.0"
},
"time": "2025-03-17T11:14:43+00:00"
},
{
"name": "symfony/clock",
"version": "v7.1.6",
@@ -4352,6 +4881,119 @@
],
"time": "2024-11-21T00:49:12+00:00"
},
{
"name": "webman/admin",
"version": "v2.1.4",
"source": {
"type": "git",
"url": "https://github.com/webman-php/admin.git",
"reference": "14563a1c768aff97c6eca3cd6775b75147a14752"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webman-php/admin/zipball/14563a1c768aff97c6eca3cd6775b75147a14752",
"reference": "14563a1c768aff97c6eca3cd6775b75147a14752",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"ext-json": "*",
"guzzlehttp/guzzle": "^7.5",
"illuminate/events": ">=7.30",
"illuminate/pagination": ">=7.30",
"intervention/image": "^2.7",
"laravel/serializable-closure": "^1.0",
"webman/captcha": "^1.0.0",
"webman/database": ">=2.1",
"webman/event": "^1.0",
"workerman/webman-framework": ">=2.1"
},
"type": "project",
"autoload": {
"psr-4": {
"Webman\\Admin\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Webman Admin",
"support": {
"issues": "https://github.com/webman-php/admin/issues",
"source": "https://github.com/webman-php/admin/tree/v2.1.4"
},
"time": "2025-04-14T01:28:03+00:00"
},
{
"name": "webman/captcha",
"version": "v1.0.4",
"source": {
"type": "git",
"url": "https://github.com/webman-php/captcha.git",
"reference": "44dc1e74b23e180e1dcb361c11b2ca8bc2e6b32c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webman-php/captcha/zipball/44dc1e74b23e180e1dcb361c11b2ca8bc2e6b32c",
"reference": "44dc1e74b23e180e1dcb361c11b2ca8bc2e6b32c",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"ext-gd": "*",
"ext-mbstring": "*",
"php": ">=5.6.0"
},
"require-dev": {
"phpunit/phpunit": "^6.4"
},
"type": "library",
"autoload": {
"psr-4": {
"Webman\\Captcha\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "walkor",
"email": "walkor@workerman.net"
},
{
"name": "Grégoire Passault",
"email": "g.passault@gmail.com",
"homepage": "http://www.gregwar.com/"
},
{
"name": "Jeremy Livingston",
"email": "jeremy.j.livingston@gmail.com"
}
],
"description": "Captcha generator",
"keywords": [
"bot",
"captcha",
"spam"
],
"support": {
"source": "https://github.com/webman-php/captcha/tree/v1.0.4"
},
"time": "2024-05-14T02:56:38+00:00"
},
{
"name": "webman/database",
"version": "v2.1.6",
@@ -4396,6 +5038,43 @@
},
"time": "2025-04-04T13:23:25+00:00"
},
{
"name": "webman/event",
"version": "v1.0.4",
"source": {
"type": "git",
"url": "https://github.com/webman-php/event.git",
"reference": "f4478941c3b7efa4d7e9c063f2f6efd7ee3071a7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webman-php/event/zipball/f4478941c3b7efa4d7e9c063f2f6efd7ee3071a7",
"reference": "f4478941c3b7efa4d7e9c063f2f6efd7ee3071a7",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"type": "library",
"autoload": {
"psr-4": {
"Webman\\Event\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Webman event plugin",
"support": {
"issues": "https://github.com/webman-php/event/issues",
"source": "https://github.com/webman-php/event/tree/v1.0.4"
},
"time": "2023-03-28T04:01:23+00:00"
},
{
"name": "workerman/coroutine",
"version": "v1.1.3",
+5
View File
@@ -0,0 +1,5 @@
<?php
return [
];
+14
View File
@@ -0,0 +1,14 @@
diff a/config/middleware.php b/config/middleware.php (rejected hunks)
@@ -12,9 +12,4 @@
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
-return [
- // 全局中间件
- '' => [
- app\middleware\CrossDomain::class, // 全局跨域中间件
- ]
-];
\ No newline at end of file
+return [];
\ No newline at end of file
+110
View File
@@ -0,0 +1,110 @@
<?php
return [
'enable' => true,
'default' => 'local',
'max_size' => 1024 * 1024 * 10, //单个文件大小10M
'ext_yes' => [], //允许上传文件类型 为空则为允许所有
'ext_no' => [], // 不允许上传文件类型 为空则不限制
'storage' => [
'public' => [
'driver' => \Shopwwi\WebmanFilesystem\Adapter\LocalAdapterFactory::class,
'root' => public_path(),
'url' => '//127.0.0.1:8787' // 静态文件访问域名
],
'local' => [
'driver' => \Shopwwi\WebmanFilesystem\Adapter\LocalAdapterFactory::class,
'root' => runtime_path(),
'url' => '//127.0.0.1:8787' // 静态文件访问域名
],
'ftp' => [
'driver' => \Shopwwi\WebmanFilesystem\Adapter\FtpAdapterFactory::class,
'host' => 'ftp.example.com',
'username' => 'username',
'password' => 'password',
'url' => '' // 静态文件访问域名
// 'port' => 21,
// 'root' => '/path/to/root',
// 'passive' => true,
// 'ssl' => true,
// 'timeout' => 30,
// 'ignorePassiveAddress' => false,
// 'timestampsOnUnixListingsEnabled' => true,
],
'memory' => [
'driver' => \Shopwwi\WebmanFilesystem\Adapter\MemoryAdapterFactory::class,
],
's3' => [
'driver' => \Shopwwi\WebmanFilesystem\Adapter\S3AdapterFactory::class,
'credentials' => [
'key' => 'S3_KEY',
'secret' => 'S3_SECRET',
],
'region' => 'S3_REGION',
'version' => 'latest',
'bucket_endpoint' => false,
'use_path_style_endpoint' => false,
'endpoint' => 'S3_ENDPOINT',
'bucket_name' => 'S3_BUCKET',
'url' => '' // 静态文件访问域名
],
'minio' => [
'driver' => \Shopwwi\WebmanFilesystem\Adapter\S3AdapterFactory::class,
'credentials' => [
'key' => 'S3_KEY',
'secret' => 'S3_SECRET',
],
'region' => 'S3_REGION',
'version' => 'latest',
'bucket_endpoint' => false,
'use_path_style_endpoint' => true,
'endpoint' => 'S3_ENDPOINT',
'bucket_name' => 'S3_BUCKET',
'url' => '' // 静态文件访问域名
],
'oss' => [
'driver' => \Shopwwi\WebmanFilesystem\Adapter\AliyunOssAdapterFactory::class,
// 'accessId' => 'OSS_ACCESS_ID',
// 'accessSecret' => 'OSS_ACCESS_SECRET',
// 'bucket' => 'OSS_BUCKET',
// 'endpoint' => 'OSS_ENDPOINT',
'accessKey' => 'LTAI5tAGiH7bkVkmrniXnfkA',
'secretKey' => 'nqklJBft6t3S0A4ZvxF4ci9ni91kmm',
'bucket' => 'vehicle-album',
'endpoint' => 'https://oss-cn-beijing.aliyuncs.com',
'url' => '', // 静态文件访问域名
// 'timeout' => 3600,
// 'connectTimeout' => 10,
'isCName' => false,
'prefix' => '',
// 'token' => null,
// 'proxy' => null,
],
'qiniu' => [
'driver' => \Shopwwi\WebmanFilesystem\Adapter\QiniuAdapterFactory::class,
'accessKey' => 'QINIU_ACCESS_KEY',
'secretKey' => 'QINIU_SECRET_KEY',
'bucket' => 'QINIU_BUCKET',
'domain' => 'QINBIU_DOMAIN',
'url' => '' // 静态文件访问域名
],
'cos' => [
'driver' => \Shopwwi\WebmanFilesystem\Adapter\CosAdapterFactory::class,
'region' => 'COS_REGION',
'app_id' => 'COS_APPID',
'secret_id' => 'COS_SECRET_ID',
'secret_key' => 'COS_SECRET_KEY',
// 可选,如果 bucket 为私有访问请打开此项
// 'signed_url' => false,
'bucket' => 'COS_BUCKET',
'read_from_cdn' => false,
'url' => '' // 静态文件访问域名
// 'timeout' => 60,
// 'connect_timeout' => 60,
// 'cdn' => '',
// 'scheme' => 'https',
],
],
];
+4
View File
@@ -0,0 +1,4 @@
<?php
return [
'enable' => true,
];
+17
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,
];
+7
View File
@@ -0,0 +1,7 @@
<?php
use Webman\Event\EventListCommand;
return [
EventListCommand::class
];
+127
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;
}
}
+93
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);
}
}
}
+147
View File
@@ -0,0 +1,147 @@
<?php
namespace plugin\admin\api;
use plugin\admin\app\model\Rule;
/**
* 对外提供的菜单接口
*/
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;
}
}
+48
View File
@@ -0,0 +1,48 @@
<?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 = admin_error_401_script();
} else {
$request->app = '';
$request->plugin = 'admin';
$response = view('common/error/403')->withStatus(403);
}
}
} else {
$response = $request->method() == 'OPTIONS' ? response('') : $handler($request);
}
return $response;
}
}
+80
View File
@@ -0,0 +1,80 @@
<?php
namespace plugin\admin\app\common;
use plugin\admin\app\model\AdminRole;
use plugin\admin\app\model\Role;
class Auth
{
/**
* 获取权限范围内的所有角色id
* @param bool $with_self
* @return array
* @throws \Exception
*/
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
* @throws \Exception
*/
public static function getScopeAdminIds(bool $with_self = false): array
{
$role_ids = static::getScopeRoleIds();
$admin_ids = AdminRole::whereIn('role_id', $role_ids)->pluck('admin_id')->toArray();
if ($with_self) {
$admin_ids[] = admin_id();
}
return array_unique($admin_ids);
}
/**
* 兼容旧版本
* @param int $admin_id
* @deprecated
* @return bool
*/
public static function isSupperAdmin(int $admin_id = 0): bool
{
return static::isSuperAdmin($admin_id);
}
/**
* 是否是超级管理员
* @param int $admin_id
* @return bool
* @throws \Exception
*/
public static function isSuperAdmin(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
+193
View File
@@ -0,0 +1,193 @@
<?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->data = array_map(function ($item) {
if (is_object($item) && method_exists($item, 'toArray')) {
return $item->toArray();
}
return $item;
}, $this->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)) {
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;
}
}
+575
View File
@@ -0,0 +1,575 @@
<?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 string $password
* @param string $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);
}
/**
* 检查表名是否合法
* @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;
}
/**
* @desc 检测是否是合法URL Path
* @param $var
* @return string
* @throws BusinessException
*/
public static function filterUrlPath($var): string
{
if (!is_string($var)) {
throw new BusinessException('参数不合法,地址必须是一个字符串!');
}
if (strpos($var, 'https://') === 0 || strpos($var, 'http://') === 0) {
if (!filter_var($var, FILTER_VALIDATE_URL)) {
throw new BusinessException('参数不合法,不是合法的URL地址!');
}
} elseif (!preg_match('/^[a-zA-Z0-9_\-\/&?.]+$/', $var)) {
throw new BusinessException('参数不合法,不是合法的Path!');
}
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 && strpos($values, "#") !== 0) {
$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();
}
}
}
@@ -0,0 +1,312 @@
<?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 plugin\admin\app\model\AdminRole;
use plugin\admin\app\model\User;
use support\exception\BusinessException;
use support\Request;
use support\Response;
use Throwable;
use Webman\Captcha\CaptchaBuilder;
use Webman\Captcha\PhraseBuilder;
use Webman\Config;
/**
* 管理员账户
*/
class AccountController extends Crud
{
/**
* 不需要登录的方法
* @var string[]
*/
protected $noNeedLogin = ['login', 'logout', 'captcha', 'register', 'registerPage'];
/**
* 不需要鉴权的方法
* @var string[]
*/
protected $noNeedAuth = ['info'];
/**
* @var Admin
*/
protected $model = null;
/**
* 构造函数
*/
public function __construct()
{
$this->model = new Admin;
}
/**
* 账户设置
* @return Response
* @throws Throwable
*/
public function index()
{
return raw_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();
$admin['password'] = md5($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'],
'isSuperAdmin' => Auth::isSuperAdmin(),
'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');
}
}
/**
* 注册页面
* @return Response
*/
public function registerPage(Request $request): Response
{
$config = Config::get('plugin.admin.app', []);
$data = [
'logo' => $config['logo'] ?? '/app/admin/admin/images/logo.png',
'title' => $config['name'] ?? '管理系统',
];
return view('account/register', $data);
}
/**
* 注册
* @param Request $request
* @return Response
*/
public function register(Request $request): Response
{
$username = $request->post('username');
$nickname = $request->post('nickname');
$password = $request->post('password');
if (empty($username) || empty($password) || empty($nickname)) {
return json(['code' => 1, 'msg' => '用户名、昵称和密码不能为空']);
}
// 检查用户名是否已存在
if (Admin::where('username', $username)->first()) {
return json(['code' => 1, 'msg' => '用户名已存在']);
}
// 创建新用户
$user = new Admin();
$user->username = $username;
$user->nickname = $nickname;
$user->password = password_hash($password, PASSWORD_DEFAULT);
$user->created_at = date('Y-m-d H:i:s');
$user->save();
$adminRole = new AdminRole();
$adminRole->admin_id = $user->id;
$adminRole->role_id = 3;
$adminRole->save();
return json(['code' => 0, 'msg' => '注册成功']);
}
}
@@ -0,0 +1,261 @@
<?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 plugin\admin\app\model\AdminRole;
use support\exception\BusinessException;
use support\Request;
use support\Response;
use Throwable;
use Webman\Captcha\CaptchaBuilder;
use Webman\Captcha\PhraseBuilder;
/**
* 管理员账户
*/
class AccountController extends Crud
{
/**
* 不需要登录的方法
* @var string[]
*/
protected $noNeedLogin = ['login', 'logout', 'captcha', 'register', 'registerPage'];
/**
* 不需要鉴权的方法
* @var string[]
*/
protected $noNeedAuth = ['info'];
/**
* @var Admin
*/
protected $model = null;
/**
* 构造函数
*/
public function __construct()
{
$this->model = new Admin;
}
/**
* 账户设置
* @return Response
* @throws Throwable
*/
public function index()
{
return raw_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();
$admin['password'] = md5($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'],
'isSuperAdmin' => Auth::isSuperAdmin(),
'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');
}
}
}
@@ -0,0 +1 @@
@@ -0,0 +1,210 @@
<?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;
use Throwable;
/**
* 管理员列表
*/
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
* @throws Throwable
*/
public function index(): Response
{
return raw_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|Throwable
*/
public function insert(Request $request): Response
{
if ($request->method() === 'POST') {
$data = $this->insertInput($request);
unset($data['id']);
$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::isSuperAdmin() && 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 raw_view('admin/insert');
}
/**
* 更新
* @param Request $request
* @return Response
* @throws BusinessException|Throwable
*/
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::isSuperAdmin();
$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 raw_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::isSuperAdmin() && array_diff($ids, Auth::getScopeAdminIds())) {
return $this->json(1, '无数据权限');
}
$this->model->whereIn($primary_key, $ids)->each(function (Admin $admin) {
$admin->delete();
});
AdminRole::whereIn('admin_id', $ids)->each(function (AdminRole $admin_role) {
$admin_role->delete();
});
return $this->json(0);
}
}
+67
View File
@@ -0,0 +1,67 @@
<?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 = [];
/**
* 数据限制
* null 不做限制,任何管理员都可以查看该表的所有数据
* auth 管理员能看到自己以及自己的子管理员插入的数据
* 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]);
}
protected function success(string $msg = '成功', array $data = []): Response
{
return $this->json(0, $msg, $data);
}
protected function fail(string $msg = '失败', array $data = []): Response
{
return $this->json(1, $msg, $data);
}
}
@@ -0,0 +1,148 @@
<?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;
use Throwable;
/**
* 系统设置
*/
class ConfigController extends Base
{
/**
* 不需要验证权限的方法
* @var string[]
*/
protected $noNeedAuth = ['get'];
/**
* 账户设置
* @return Response
* @throws Throwable
*/
public function index(): Response
{
return raw_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'] ?? '');
$data[$section]['icp'] = htmlspecialchars($items['icp'] ?? '');
$data[$section]['beian'] = htmlspecialchars($items['beian'] ?? '');
$data[$section]['footer_txt'] = htmlspecialchars($items['footer_txt'] ?? '');
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'] ?? 2000);
$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;
}
}
+469
View File
@@ -0,0 +1,469 @@
<?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) && (empty($value) || !in_array($value[0], ['null', 'not null']) && !isset($value[1]))
) {
unset($where[$column]);
}
}
// 按照数据限制字段返回数据
if (!Auth::isSuperAdmin()) {
if ($this->dataLimit === 'personal') {
$where[$this->dataLimitField] = admin_id();
} elseif ($this->dataLimit === 'auth') {
$primary_key = $this->model->getKeyName();
if (!Auth::isSuperAdmin() && (!isset($where[$primary_key]) || $this->dataLimitField != $primary_key)) {
$where[$this->dataLimitField] = ['in', Auth::getScopeAdminIds(true)];
}
}
}
return [$where, $format, $limit, $field, $order, $page];
}
/**
* 指定查询where条件,并没有真正的查询数据库操作
* @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' || $value[0] === 'not like') {
$model = $model->where($column, $value[0], "%$value[1]%");
} elseif (in_array($value[0], ['>', '=', '<', '<>'])) {
$model = $model->where($column, $value[0], $value[1]);
} elseif ($value[0] == 'in' && !empty($value[1])) {
$valArr = $value[1];
if (is_string($value[1])) {
$valArr = explode(",", trim($value[1]));
}
$model = $model->whereIn($column, $valArr);
} elseif ($value[0] == 'not in' && !empty($value[1])) {
$valArr = $value[1];
if (is_string($value[1])) {
$valArr = explode(",", trim($value[1]));
}
$model = $model->whereNotIn($column, $valArr);
}elseif ($value[0] == 'null') {
$model = $model->whereNull($column);
} elseif ($value[0] == 'not null') {
$model = $model->whereNotNull($column);
} 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();
if (method_exists($this, "afterQuery")) {
$items = call_user_func([$this, "afterQuery"], $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::isSuperAdmin()) {
if ($this->dataLimit === 'personal') {
$data[$this->dataLimitField] = admin_id();
} elseif ($this->dataLimit === 'auth') {
if (!empty($data[$this->dataLimitField])) {
$admin_id = $data[$this->dataLimitField];
if (!in_array($admin_id, Auth::getScopeAdminIds(true))) {
throw new BusinessException('无数据权限');
}
} else {
$data[$this->dataLimitField] = admin_id();
}
}
} elseif ($this->dataLimit && empty($data[$this->dataLimitField])) {
$data[$this->dataLimitField] = admin_id();
}
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());
$model = $this->model->find($id);
if (!$model) {
throw new BusinessException('记录不存在', 2);
}
if (!Auth::isSuperAdmin()) {
if ($this->dataLimit == 'personal') {
if ($model->{$this->dataLimitField} != admin_id()) {
throw new BusinessException('无数据权限');
}
} elseif ($this->dataLimit == 'auth') {
$scopeAdminIds = Auth::getScopeAdminIds(true);
$admin_ids = [
$data[$this->dataLimitField] ?? false, // 检查要更新的数据admin_id是否是有权限的值
$model->{$this->dataLimitField} ?? false // 检查要更新的记录的admin_id是否有权限
];
foreach ($admin_ids as $admin_id) {
if ($admin_id && !in_array($admin_id, $scopeAdminIds)) {
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
*/
protected function doUpdate($id, $data)
{
$model = $this->model->find($id);
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::isSuperAdmin()){
$admin_ids = [];
if ($this->dataLimit) {
$admin_ids = $this->model->where($primary_key, $ids)->pluck($this->dataLimitField)->toArray();
}
if ($this->dataLimit == 'personal') {
if (!in_array(admin_id(), $admin_ids)) {
throw new BusinessException('无数据权限');
}
} elseif ($this->dataLimit == 'auth') {
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)->each(function ($model) {
$model->delete();
});
}
/**
* 格式化树
* @param $items
* @return Response
*/
protected function formatTree($items): Response
{
$format_items = [];
$primary_key = $this->model->getKeyName();
foreach ($items as $item) {
$format_items[] = [
'name' => $this->guessName($item) ?: $item->$primary_key,
'value' => (string)$item->$primary_key,
'id' => $item->$primary_key,
'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 = [];
$primary_key = $this->model->getKeyName();
foreach ($items as $item) {
$formatted_items[] = [
'name' => $this->guessName($item) ?: $item->$primary_key,
'value' => $item->$primary_key
];
}
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]);
}
/**
* 查询数据库后置方法,可用于修改数据
* @param mixed $items 原数据
* @return mixed 修改后数据
*/
protected function afterQuery($items)
{
return $items;
}
/**
* 猜测记录名称
* @param $item
* @return mixed
*/
protected function guessName($item)
{
return $item->title ?? $item->name ?? $item->nickname ?? $item->username ?? $item->id;
}
}
@@ -0,0 +1,23 @@
<?php
namespace plugin\admin\app\controller;
use support\Response;
use Throwable;
/**
* 开发辅助相关
*/
class DevController
{
/**
* 表单构建
* @return Response
* @throws Throwable
*/
public function formBuild()
{
return raw_view('dev/form-build');
}
}
@@ -0,0 +1,122 @@
<?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;
use Throwable;
/**
* 字典管理
*/
class DictController extends Base
{
/**
* 不需要授权的方法
*/
protected $noNeedAuth = ['get'];
/**
* 浏览
* @return Response
* @throws Throwable
*/
public function index(): Response
{
return raw_view('dict/index');
}
/**
* 查询
* @param Request $request
* @return Response
*/
public function select(Request $request): Response
{
$name = $request->get('name', '');
$page = $request->get('page', 1);
$limit = $request->get('limit', 10);
$offset = ($page-1)*$limit;
if ($name && is_string($name)) {
$items = Option::where('name', 'like', "dict_$name%")->limit($limit)->offset($offset)->get()->toArray();
} else {
$items = Option::where('name', 'like', 'dict_%')->limit($limit)->offset($offset)->get()->toArray();
}
$get_items = Option::where('name', 'like', "dict_$name%")->get()->toArray();
$count = count($get_items);
foreach ($items as &$item) {
$item['name'] = Dict::optionNameTodictName($item['name']);
}
return json(['code' => 0, 'msg' => 'ok', 'count' => $count, 'data' => $items]);
}
/**
* 插入
* @param Request $request
* @return Response
* @throws BusinessException|Throwable
*/
public function insert(Request $request): Response
{
if ($request->method() === 'POST') {
$name = $request->post('name');
if (Dict::get($name)) {
return $this->json(1, '字典已经存在');
}
if (!preg_match('/^[a-zA-Z0-9]+$/', $name)) {
return $this->json(2, '字典名称只能是字母数字的组合');
}
$values = (array)$request->post('value', []);
Dict::save($name, $values);
}
return raw_view('dict/insert');
}
/**
* 更新
* @param Request $request
* @return Response
* @throws BusinessException|Throwable
*/
public function update(Request $request): Response
{
if ($request->method() === 'POST') {
$name = $request->post('name');
if (!Dict::get($name)) {
return $this->json(1, '字典不存在');
}
if (!preg_match('/^[a-zA-Z0-9]+$/', $name)) {
return $this->json(2, '字典名称只能是字母数字的组合');
}
Dict::save($name, $request->post('value'));
}
return raw_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', (array)Dict::get($name));
}
}
@@ -0,0 +1,97 @@
<?php
namespace plugin\admin\app\controller;
use plugin\admin\app\common\Util;
use plugin\admin\app\model\Option;
use plugin\admin\app\model\User;
use support\exception\BusinessException;
use support\Request;
use support\Response;
use think\db\Where;
use Throwable;
use Workerman\Worker;
class IndexController
{
/**
* 无需登录的方法
* @var string[]
*/
protected $noNeedLogin = ['index'];
/**
* 不需要鉴权的方法
* @var string[]
*/
protected $noNeedAuth = ['dashboard'];
/**
* 后台主页
* @param Request $request
* @return Response
* @throws BusinessException|Throwable
*/
public function index(Request $request): Response
{
clearstatcache();
if (!is_file(base_path('plugin/admin/config/database.php'))) {
return raw_view('index/install');
}
$admin = admin();
if (!$admin) {
$name = 'system_config';
$config = Option::where('name', $name)->value('value');
$config = json_decode($config, true);
$title = $config['logo']['title'] ?? 'webman admin';
$logo = $config['logo']['image'] ?? '/app/admin/admin/images/logo.png';
return raw_view('account/login',['logo'=>$logo,'title'=>$title]);
}
return raw_view('index/index');
}
/**
* 仪表板
* @param Request $request
* @return Response
* @throws Throwable
*/
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 raw_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' => Util::getPackageVersion('webman/admin'),
'mysql_version' => $mysql_version,
'os' => PHP_OS,
'day7_detail' => array_reverse($day7_detail),
]);
}
}
@@ -0,0 +1,391 @@
<?php
namespace plugin\admin\app\controller;
use Illuminate\Database\Capsule\Manager;
use plugin\admin\app\common\Util;
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, '请运行 composer require -W illuminate/database 安装illuminate/database组件并重启');
}
$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);
}
}
@@ -0,0 +1,79 @@
<?php
namespace plugin\admin\app\controller;
use support\Request;
use support\Response;
use plugin\admin\app\model\Location;
use plugin\admin\app\controller\Crud;
use support\exception\BusinessException;
use util\Util;
/**
* 仓库/港口
*/
class LocationController extends Crud
{
/**
* @var Location
*/
protected $model = null;
/**
* 构造函数
* @return void
*/
public function __construct()
{
$this->model = new Location;
}
/**
* 浏览
* @return Response
*/
public function index(): Response
{
return view('location/index');
}
/**
* 插入
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function insert(Request $request): Response
{
if ($request->method() === 'POST') {
return parent::insert($request);
}
return view('location/insert');
}
/**
* 更新
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function update(Request $request): Response
{
if ($request->method() === 'POST') {
return parent::update($request);
}
return view('location/update');
}
/**
* 获取下拉选项接口
*/
public function getAll()
{
$locations = Location::get();
return Util::success($locations);
}
}
@@ -0,0 +1,548 @@
<?php
namespace plugin\admin\app\controller;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use plugin\admin\app\common\Util;
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);
}
$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' => 60,
'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' => 59,
'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;
}
}
@@ -0,0 +1,257 @@
<?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;
use Throwable;
/**
* 角色管理
*/
class RoleController extends Crud
{
/**
* 不需要鉴权的方法
* @var array
*/
protected $noNeedAuth = ['select'];
/**
* @var Role
*/
protected $model = null;
/**
* 构造函数
*/
public function __construct()
{
$this->model = new Role;
}
/**
* 浏览
* @return Response
* @throws Throwable
*/
public function index(): Response
{
return raw_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);
$limit = 100000;
$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
* @throws Throwable
*/
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::isSuperAdmin() && !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 raw_view('role/insert');
}
/**
* 更新
* @param Request $request
* @return Response
* @throws BusinessException|Throwable
*/
public function update(Request $request): Response
{
if ($request->method() === 'GET') {
return raw_view('role/update');
}
[$id, $data] = $this->updateInput($request);
$is_supper_admin = Auth::isSuperAdmin();
$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(explode(',',$tmp_role->rules), $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::isSuperAdmin() && 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
* @throws \Exception
*/
public function rules(Request $request): Response
{
$role_id = $request->get('id');
if (empty($role_id)) {
return $this->json(0, 'ok', []);
}
if (!Auth::isSuperAdmin() && !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('数据超出权限范围');
}
}
}
}
@@ -0,0 +1,354 @@
<?php
namespace plugin\admin\app\controller;
use Exception;
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;
use Throwable;
/**
* 权限菜单
*/
class RuleController extends Crud
{
/**
* 不需要权限的方法
*
* @var string[]
*/
protected $noNeedAuth = ['get', 'permission'];
/**
* @var Rule
*/
protected $model = null;
/**
* 构造函数
*/
public function __construct()
{
$this->model = new Rule;
}
/**
* 浏览
* @return Response
* @throws Throwable
*/
public function index(): Response
{
return raw_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
* @throws Exception
*/
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);
$menus = $this->empty_filter(Tree::arrayValues($tree_items));
return $this->json(0, 'ok', $menus);
}
private function empty_filter($menus)
{
return array_map(
function ($menu) {
if (isset($menu['children'])) {
$menu['children'] = $this->empty_filter($menu['children']);
}
return $menu;
},
array_values(array_filter(
$menus,
function ($menu) {
return $menu['type'] != 0 || isset($menu['children']) && count($this->empty_filter($menu['children'])) > 0;
}
))
);
}
/**
* 获取权限
* @param Request $request
* @return Response
* @throws Exception
*/
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;
}
$permissions = ["app.admin.vehicle.select","app.admin.vehicle.detail"];
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|Throwable
*/
public function insert(Request $request): Response
{
if ($request->method() === 'GET') {
return raw_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|Throwable
*/
public function update(Request $request): Response
{
if ($request->method() === 'GET') {
return raw_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
@@ -0,0 +1,378 @@
<?php
namespace plugin\admin\app\controller;
use app\service\OssService;
use Exception;
use Intervention\Image\ImageManagerStatic as Image;
use plugin\admin\app\model\Upload;
use Shopwwi\WebmanFilesystem\Facade\Storage;
use support\exception\BusinessException;
use support\Log;
use support\Request;
use support\Response;
use Throwable;
use util\Util;
/**
* 附件管理
*/
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
* @throws Throwable
*/
public function index(): Response
{
return raw_view('upload/index');
}
/**
* 浏览附件
* @return Response
* @throws Throwable
*/
public function attachment(): Response
{
return raw_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|Throwable
*/
public function update(Request $request): Response
{
if ($request->method() === 'GET') {
return raw_view('upload/update');
}
return parent::update($request);
}
/**
* 添加附件
* @param Request $request
* @return Response
* @throws Exception|Throwable
*/
public function insert(Request $request): Response
{
if ($request->method() === 'GET') {
return raw_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
* @throws BusinessException
*/
public function delete(Request $request): Response
{
$ids = $this->deleteInput($request);
$primary_key = $this->model->getKeyName();
$files = $this->model->whereIn($primary_key, $ids)->get()->toArray();
$file_list = array_map(function ($item) {
$path = $item['url'];
if (preg_match("#^/app/admin#", $path)) {
$admin_public_path = config('plugin.admin.app.public_path') ?: base_path() . "/plugin/admin/public";
return $admin_public_path . str_replace("/app/admin", "", $item['url']);
}
return null;
}, $files);
$file_list = array_filter($file_list, function ($item) {
return !empty($item);
});
$result = parent::delete($request);
if (($res = json_decode($result->rawBody())) && $res->code === 0) {
foreach ($file_list as $file) {
@unlink($file);
}
}
return $result;
}
/**
* 获取上传数据
* @param Request $request
* @param $relative_dir
* @return array
* @throws BusinessException|\Random\RandomException
*/
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);
}
$admin_public_path = rtrim(config('plugin.admin.app.public_path', ''), '\\/');
$base_dir = $admin_public_path ? $admin_public_path . DIRECTORY_SEPARATOR : base_path() . '/plugin/admin/public/';
$full_dir = $base_dir . $relative_dir;
if (!is_dir($full_dir)) {
mkdir($full_dir, 0777, true);
}
$ext = $file->getUploadExtension() ?: null;
$mime_type = $file->getUploadMimeType();
$file_name = $file->getUploadName();
$file_size = $file->getSize();
if (!$ext && $file_name === 'blob') {
[$___image, $ext] = explode('/', $mime_type);
unset($___image);
}
$ext = strtolower($ext);
$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->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,
];
}
// 上传单文件图片
public function uploadImage(Request $request)
{
try {
// $file = $request->file('file');
$file = current($request->file());
if (!$file || !$file->isValid()) {
return Util::fail($file,'上传失败2');
}
$result = Storage::adapter('oss')
->path('lingke_crm/'.date('Ymd'))
->size(1024*1024*5)
->extYes(['image/jpeg','image/png'])
->extNo(['image/gif'])
->upload($file);
$ossservice = new OssService();
$data = [
'path' => $result->file_name,
'original_name' => $result->origin_name,
'image_url' => $ossservice->generatePrivateUrl($result->file_name)
];
return Util::success($data,'上传成功');
}catch (\Throwable $e){
Log::error('文件上传失败: ' . $e->getMessage());
return Util::fail([],'file:'.$e->getFile().'line:'.$e->getLine().'message:'.$e->getMessage());
}
}
}
@@ -0,0 +1,69 @@
<?php
namespace plugin\admin\app\controller;
use plugin\admin\app\model\User;
use support\exception\BusinessException;
use support\Request;
use support\Response;
use Throwable;
/**
* 用户管理
*/
class UserController extends Crud
{
/**
* @var User
*/
protected $model = null;
/**
* 构造函数
* @return void
*/
public function __construct()
{
$this->model = new User;
}
/**
* 浏览
* @return Response
* @throws Throwable
*/
public function index(): Response
{
return raw_view('user/index');
}
/**
* 插入
* @param Request $request
* @return Response
* @throws BusinessException|Throwable
*/
public function insert(Request $request): Response
{
if ($request->method() === 'POST') {
return parent::insert($request);
}
return raw_view('user/insert');
}
/**
* 更新
* @param Request $request
* @return Response
* @throws BusinessException|Throwable
*/
public function update(Request $request): Response
{
if ($request->method() === 'POST') {
return parent::update($request);
}
return raw_view('user/update');
}
}
@@ -0,0 +1,132 @@
<?php
namespace plugin\admin\app\controller;
use plugin\admin\app\model\Location;
use support\Request;
use support\Response;
use plugin\admin\app\model\Vehicle;
use plugin\admin\app\controller\Crud;
use support\exception\BusinessException;
/**
* 车辆信息
*/
class VehicleController extends Crud
{
/**
* 不需要登录的方法
* @var string[]
*/
protected $noNeedLogin = ['detail'];
/**
* 不需要鉴权的方法
* @var string[]
*/
protected $noNeedAuth = ['detail'];
protected $status = ['未知','待发车','在途(发往fbo港口)','抵达发货港口','在途(发往目的地仓库)','抵达仓库','售出'];
/**
* @var Vehicle
*/
protected $model = null;
/**
* 构造函数
* @return void
*/
public function __construct()
{
$this->model = new Vehicle;
}
/**
* 浏览
* @return Response
*/
public function index(): Response
{
return view('vehicle/index');
}
/**
* 插入
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function insert(Request $request): Response
{
if ($request->method() === 'POST') {
return parent::insert($request);
}
return view('vehicle/insert');
}
/**
* 更新
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function update(Request $request): Response
{
if ($request->method() === 'POST') {
return parent::update($request);
}
return view('vehicle/update');
}
/**
* 详情
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function detail(Request $request): Response
{
return view('vehicle/detail');
}
/**
* 查询
* @param Request $request
* @return Response
* @throws BusinessException
*/
/* public function list(Request $request)
{
[$where, $format, $limit, $field, $order] = $this->selectInput($request);
return json($where);
$query = $this->doSelect($where, $field, $order);
$data = $this->doFormat($query, $format, $limit);
// foreach ($data['data'] as &$value) {
// $value['locations'] = Location::find($value['locations_id'])->name;
// }
// $data = json_decode(json_encode($data), true);
return $data;
}*/
/**
* 查询后置处理,添加位置信息
* @param mixed $items
* @return mixed
*/
protected function afterQuery($items)
{
foreach ($items as &$item) {
$location = Location::find($item->locations_id);
$item->status = $this->status[$item->status]??$this->status[0];
$item->location_name = $location ? $location->name : '';
$final_location = Location::find($item->final_locations_id);
$item->final_location_name = $final_location ? $final_location->name : '';
$fbo_location = Location::find($item->fbo_locations_id);
$item->fbo_location_name = $fbo_location ? $fbo_location->name : '';
}
return $items;
}
}
+40
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);
}
}
+94
View File
@@ -0,0 +1,94 @@
<?php
/**
* Here is your custom functions.
*/
use plugin\admin\app\model\Admin;
use plugin\admin\app\model\AdminRole;
use support\Response;
/**
* 当前管理员id
* @return integer|null
*/
function admin_id(): ?int
{
return session('admin.id');
}
/**
* 当前管理员
* @param null|array|string $fields
* @return array|mixed|null
* @throws Exception
*/
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;
}
/**
* 刷新当前管理员session
* @param bool $force
* @return void
* @throws Exception
*/
function refresh_admin_session(bool $force = false)
{
$admin_session = session('admin');
if (!$admin_session) {
return null;
}
$admin_id = $admin_session['id'];
$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();
$admin['password'] = md5($admin['password']);
$admin_session['password'] = $admin_session['password'] ?? '';
if ($admin['password'] != $admin_session['password']) {
$session->forget('admin');
return null;
}
// 账户被禁用
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);
}
function admin_error_401_script(): Response
{
return response(<<<EOF
<script>top.location.href = '/app/admin';</script>
EOF
);
}
@@ -0,0 +1,47 @@
<?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 = admin_error_401_script();
} else {
$request->app = '';
$request->plugin = 'admin';
$response = view('common/error/403')->withStatus(403);
}
}
} else {
$response = $request->method() == 'OPTIONS' ? response('') : $handler($request);
}
return $response;
}
}
+40
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';
}
+31
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;
}
+26
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');
}
}
+96
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;
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace plugin\admin\app\model;
use plugin\admin\app\model\Base;
/**
* @property integer $id 唯一标识(主键)
* @property string $name 港口/仓库名称
* @property string $name_en 英文名称
* @property string $address 详细地址
* @property string $area 所在地区
* @property string $type 类型:港口/仓库
* @property integer $num 车辆数
* @property mixed $created_at 创建时间
* @property mixed $updated_at 更新时间
*/
class Location extends Base
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'locations';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
}
+29
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';
}
+38
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) : [];
}
}
+38
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';
}
+42
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';
}
+49
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';
}
+45
View File
@@ -0,0 +1,45 @@
<?php
namespace plugin\admin\app\model;
use plugin\admin\app\model\Base;
/**
* @property integer $id 自增主键(主键)
* @property string $vin 车架号(17位国际标准)
* @property string $classify
* @property string $brand 车辆品牌
* @property string $model 车辆型号
* @property string $status 车辆状态(如:待发运/运输中/已到达)
* @property string $purchase_location 买车地点
* @property string $procedure_location 手续办理地
* @property string $fbo_destination FBO最终目的地
* @property integer $fbo_locations_id fbo目的地id
* @property integer $locations_id 车辆所在地
* @property integer $final_locations_id 最终目的地
* @property string $departure_time 实际发车时间
* @property string $arrival_time 实际到达时间
* @property integer $shipping_cycle 运输周期(天)
* @property string $img_url 图片
* @property string $created_at 记录创建时间
* @property string $updated_at 最后更新时间
*/
class Vehicle extends Base
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'vehicles';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
}
+158
View File
@@ -0,0 +1,158 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="/app/admin/component/layui/css/layui.css?v=2.8.12" />
<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?v=2.8.12"></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>
+85
View File
@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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="<?=$logo?>" />
<div class="title pear-text"><?=$title?></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>
<div class="layui-form-item" style="text-align: center; margin-top: 10px;">
<!-- <a href="/app/admin/account/registerPage" class="pear-text">还没有账号?立即注册</a>-->
<a href="/app/admin/account/register/page" class="pear-text">还没有账号?立即注册</a>
</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?v=2.8.12"></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());
$('.codeImage').attr("src", "/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)
switchCaptcha();
}
}
});
return false;
});
$('.codeImage').on('click', function () {
switchCaptcha();
});
})
</script>
</body>
</html>
@@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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="<?=$logo?>" />
<div class="title pear-text"><?=$title?></div>
<div class="desc">注册新账号</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="text" name="nickname" 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 lay-verify="required" hover class="layui-input" type="password" name="confirm_password" value="" placeholder="确认密码" />
</div>
<div class="layui-form-item">
<button type="submit" class="pear-btn pear-btn-primary login" lay-submit lay-filter="register">
注 册
</button>
</div>
<div class="layui-form-item" style="text-align: center; margin-top: 10px;">
<a href="/app/admin" class="pear-text">已有账号?立即登录</a>
</div>
</form>
<!-- 资 源 引 入 -->
<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;
// 注 册 提 交
form.on('submit(register)', function (data) {
if (data.field.password !== data.field.confirm_password) {
layui.popup.failure('两次输入的密码不一致');
return false;
}
layer.load();
$.ajax({
url: '/app/admin/account/register',
type: "POST",
data: data.field,
success: function (res) {
layer.closeAll('loading');
if (res.code === 0) {
layui.popup.success('注册成功', function () {
location.href = '/app/admin';
});
} else {
layui.popup.failure(res.msg);
}
},
error: function() {
layer.closeAll('loading');
layui.popup.failure('注册失败,请稍后重试');
}
});
return false;
});
});
</script>
</body>
</html>
+414
View File
@@ -0,0 +1,414 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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?v=2.8.12"></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() {
table.reloadData("data-table", {
scrollPos: "fixed",
done: function (res, curr) {
if (curr > 1 && res.data && !res.data.length) {
curr = curr - 1;
table.reloadData("data-table", {
page: {
curr: curr
},
})
}
}
});
}
})
</script>
</body>
</html>
+181
View File
@@ -0,0 +1,181 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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?v=2.8.12"></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.isSuperAdmin) {
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>
+215
View File
@@ -0,0 +1,215 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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?v=2.8.12"></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.isSuperAdmin) {
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>
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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?v=2.8.12"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
</body>
</html>
+326
View File
@@ -0,0 +1,326 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="/app/admin/component/layui/css/layui.css?v=2.8.12" />
<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/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">
<label class="layui-form-label">ICP备案号</label>
<div class="layui-input-block">
<input type="text" name="icp" placeholder="请输入ICP备案号" 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="beian" 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="footer_txt" 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>
</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">默认菜单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?v=2.8.12"></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>
+113
View File
@@ -0,0 +1,113 @@
<!doctype html>
<html lang="zh-cn">
<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?v=2.8.12"></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>
+323
View File
@@ -0,0 +1,323 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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?v=2.8.12"></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 : 200,
templet: function (d) {
let field = "name";
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 : "value",
templet: function (d) {
const dicts = JSON.parse(d.value)
return dicts.map(dict => `<span class="layui-badge layui-bg-blue">${dict.name}: ${dict.value}</span>`).join(' ');
},
}, {
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",
},
];
let apiResults = {};
const loadDictNames = () => {
let apis = [];
apis.push(["name", "/app/admin/dict/get/dict_name"]);
apiResults["name"] = [];
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}: ${item.value}`;
if (item.children) {
travel(item.children);
}
}
}
travel(res.data);
},
complete: function () {
if (--count === 0) {
render();
}
},
});
});
}
loadDictNames();
// 渲染表格
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() {
table.reloadData("data-table", {
scrollPos: "fixed",
done: function (res, curr) {
if (curr > 1 && res.data && !res.data.length) {
curr = curr - 1;
table.reloadData("data-table", {
page: {
curr: curr
},
})
}
loadDictNames()
}
});
}
})
</script>
</body>
</html>
+224
View File
@@ -0,0 +1,224 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>新增字典</title>
<link rel="stylesheet" href="/app/admin/component/layui/css/layui.css?v=2.8.12" />
<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_NUM-1 }}][value]" placeholder="值" autocomplete="off" class="layui-input" value="{{ d.value }}">
<input type="hidden" name="value[{{ d.LAY_NUM-1 }}][_field_id]" value="{{ d._field_id }}">
</script>
<script type="text/html" id="col-name">
<input type="text" name="value[{{ d.LAY_NUM-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?v=2.8.12"></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>
+240
View File
@@ -0,0 +1,240 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>更新字典</title>
<link rel="stylesheet" href="/app/admin/component/layui/css/layui.css?v=2.8.12" />
<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" disabled 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_NUM-1 }}][value]" placeholder="值" autocomplete="off" class="layui-input" value="{{ d.value }}">
<input type="hidden" name="value[{{ d.LAY_NUM-1 }}][_field_id]" value="{{ d._field_id }}">
</script>
<script type="text/html" id="col-name">
<input type="text" name="value[{{ d.LAY_NUM-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?v=2.8.12"></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>
+358
View File
@@ -0,0 +1,358 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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?v=2.8.12"></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>
+143
View File
@@ -0,0 +1,143 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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" id="menu_but"><a href="#" id="menu_but1" 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">
<!-- 标 题 -->
<a href="/"><span class="title"></span></a>
</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?v=2.8.12"></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>
+214
View File
@@ -0,0 +1,214 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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?v=2.8.12"></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>
+280
View File
@@ -0,0 +1,280 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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.location.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.location.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.location.update">编辑</button>
<button class="pear-btn pear-btn-xs tool-btn" lay-event="remove" permission="app.admin.location.delete">删除</button>
</script>
<script src="/app/admin/component/layui/layui.js?v=2.8.12"></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/location/select";
const UPDATE_API = "/app/admin/location/update";
const DELETE_API = "/app/admin/location/delete";
const INSERT_URL = "/app/admin/location/insert";
const UPDATE_URL = "/app/admin/location/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",
align: "center"
},{
title: "唯一标识",align: "center",
field: "id",
},{
title: "港口/仓库名称",align: "center",
field: "name",
},{
title: "英文名称",align: "center",
field: "name_en",
},{
title: "详细地址",align: "center",
field: "address",
},{
title: "所在地区",align: "center",
field: "area",
},{
title: "类型:港口/仓库",align: "center",
field: "type",
templet: function (d) {
let field = "type";
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: "车辆数",align: "center",
field: "num",
},{
title: "创建时间",align: "center",
field: "created_at",
},{
title: "更新时间",align: "center",
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});
}
});
// 获取表格中下拉或树形组件数据
let apiResults = {};
apiResults["type"] = [];
// 编辑或删除行事件
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,
maxmin: true,
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,
maxmin: true,
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() {
table.reloadData("data-table", {
scrollPos: "fixed",
done: function (res, curr) {
if (curr > 1 && res.data && !res.data.length) {
curr = curr - 1;
table.reloadData("data-table", {
page: {
curr: curr
},
})
}
}
});
}
})
</script>
</body>
</html>
+153
View File
@@ -0,0 +1,153 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>新增页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/component/jsoneditor/css/jsoneditor.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">
<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 required">英文名称</label>
<div class="layui-input-block">
<input type="text" name="name_en" 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="address" 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="area" 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="type" id="type" value="port" ></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- -->
<!-- <div class="layui-form-item">-->
<!-- <label class="layui-form-label">车辆数</label>-->
<!-- <div class="layui-input-block">-->
<!-- <input type="number" name="num" 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?v=2.8.12"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/component/jsoneditor/jsoneditor.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
// 相关接口
const INSERT_API = "/app/admin/location/insert";
// 字段 类型:港口/仓库 type
layui.use(["jquery", "xmSelect"], function() {
let value = layui.$("#type").attr("value");
let initValue = value ? value.split(",") : [];
layui.xmSelect.render({
el: "#type",
name: "type",
filterable: true,
initValue: initValue,
value: "port",
model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true,
radio: true,
})
});
// 字段 创建时间 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>
+215
View File
@@ -0,0 +1,215 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>更新页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/component/jsoneditor/css/jsoneditor.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">
<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">
<input type="text" name="name_en" 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="address" 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="area" 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="type" id="type" value="" ></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">车辆数</label>
<div class="layui-input-block">
<input type="number" name="num" 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="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?v=2.8.12"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/component/jsoneditor/jsoneditor.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
// 相关接口
const PRIMARY_KEY = "id";
const SELECT_API = "/app/admin/location/select" + location.search;
const UPDATE_API = "/app/admin/location/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(value);
} else {
obj.attr("value", value);
obj[0].value = value;
}
// 多图渲染
if (obj[0].classList.contains('uploader-list')) {
let multiple_images = value.split(",");
$.each(multiple_images, function(index, value) {
$('#uploader-list-'+ key).append(
'<div class="file-iteme">' +
'<div class="handle"><i class="layui-icon layui-icon-delete"></i></div>' +
'<img src='+value +' alt="'+ value +'" >' +
'</div>'
);
});
}
});
// 字段 类型:港口/仓库 type
layui.use(["jquery", "xmSelect"], function() {
let value = layui.$("#type").attr("value");
let initValue = value ? value.split(",") : [];
layui.xmSelect.render({
el: "#type",
name: "type",
filterable: true,
initValue: initValue,
model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true,
radio: true,
})
});
// 字段 创建时间 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>
+310
View File
@@ -0,0 +1,310 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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?v=2.8.12"></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() {
table.reloadData("data-table", {
scrollPos: "fixed",
done: function (res, curr) {
if (curr > 1 && res.data && !res.data.length) {
curr = curr - 1;
table.reloadData("data-table", {
page: {
curr: curr
},
})
}
}
});
}
})
</script>
</body>
</html>
+149
View File
@@ -0,0 +1,149 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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?v=2.8.12"></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>
+188
View File
@@ -0,0 +1,188 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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?v=2.8.12"></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>
+292
View File
@@ -0,0 +1,292 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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?v=2.8.12"></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() {
table.reloadData("data-table", {
scrollPos: "fixed",
done: function (res, curr) {
if (curr > 1 && res.data && !res.data.length) {
curr = curr - 1;
table.reloadData("data-table", {
page: {
curr: curr
},
})
}
}
});
}
})
</script>
</body>
</html>
+174
View File
@@ -0,0 +1,174 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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?v=2.8.12"></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>
+207
View File
@@ -0,0 +1,207 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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?v=2.8.12"></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>
+744
View File
@@ -0,0 +1,744 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>新增页面</title>
<link rel="stylesheet" href="/app/admin/component/layui/css/layui.css?v=2.8.12" />
<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_NUM-1 }}][field]" placeholder="字段名称" autocomplete="off" class="layui-input" value="{{ d.field }}">
<input type="hidden" name="columns[{{ d.LAY_NUM-1 }}][_field_id]" value="{{ d._field_id }}">
</script>
<script type="text/html" id="col-comment">
<input type="text" name="columns[{{ d.LAY_NUM-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_NUM-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_NUM-1 }}][default]" placeholder="默认值" autocomplete="off" class="layui-input" value="{{ d.default }}">
</script>
<script type="text/html" id="col-type">
<select name="columns[{{ d.LAY_NUM-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_NUM-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_NUM-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_NUM-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_NUM-1 }}][field]" autocomplete="off" class="layui-input" value="{{ d.field }}" disabled>
<input type="hidden" name="forms[{{ d.LAY_NUM-1 }}][_field_id]" value="{{ d._field_id }}">
</script>
<script type="text/html" id="form-comment">
<input type="text" name="forms[{{ d.LAY_NUM-1 }}][comment]" autocomplete="off" class="layui-input" value="{{ d.comment }}" disabled>
</script>
<script type="text/html" id="form-control">
<select name="forms[{{ d.LAY_NUM-1 }}][control]" lay-verify="">
{{# layui.each([["input", "文本框"],["inputNumber", "数字文本框"],["textArea", "多行文本"],["richText", "富文本"],["jsonEditor", "json编辑框"],["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_NUM-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_NUM-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_NUM-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_NUM-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_NUM-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_NUM-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_NUM-1 }}][name]" placeholder="字段名称" autocomplete="off" class="layui-input" value="{{ d.name }}">
</script>
<script type="text/html" id="key-columns">
<div name="keys[{{ d.LAY_NUM-1 }}][columns]" class="key-columns-div" value="{{ d.columns }}"></div>
</script>
<script type="text/html" id="key-type">
<select name="keys[{{ d.LAY_NUM-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?v=2.8.12"></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: "between",
},{
_field_id: _id++,
field : "updated_at",
comment: "更新时间",
control: "dateTimePicker",
control_args: "",
form_show: false,
list_show: true,
enable_sort: false,
searchable: false,
search_type: "between",
}];
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("date") !== -1 ? "dateTimePicker" : (item.type.toLocaleString().indexOf("int") !== -1 ? "inputNumber" : "input"),
control_args: "",
form_show: true,
list_show: true,
enable_sort: false,
searchable: false,
search_type: item.type.toLocaleString().indexOf("date") !== -1 ? "between" : "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>
+139
View File
@@ -0,0 +1,139 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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?v=2.8.12"></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>
+295
View File
@@ -0,0 +1,295 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>admin管理</title>
<link rel="stylesheet" href="/app/admin/component/layui/css/layui.css?v=2.8.12" />
<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="table_name" id="table_name" placeholder="请输入表名" 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.table.create">
<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.table.drop">
<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.table.modify">编辑</button>
<button class="pear-btn pear-btn-xs tool-btn" lay-event="remove" permission="app.admin.table.drop">删除</button>
<button class="pear-btn pear-btn-xs tool-btn" lay-event="crud" permission="app.admin.table.crud">一键菜单</button>
</script>
<script src="/app/admin/component/layui/layui.js?v=2.8.12"></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/table/show";
const DROP_API = "/app/admin/table/drop";
const VIEW_URL = "/app/admin/table/view";
const CREATE_URL = "/app/admin/table/create";
const MODIFY_URL = "/app/admin/table/modify";
const CRUD_URL = "/app/admin/table/crud";
layui.use(["table", "form", "common", "popup"], function() {
let table = layui.table;
let form = layui.form;
let $ = layui.$;
let common = layui.common;
let cols = [
{
type: "checkbox"
}, {
title: "表名",
field: "TABLE_NAME",
templet: function (d) {
return '<a class="tab-link" src="' + VIEW_URL + '?table=' + d.TABLE_NAME + '">' + d.TABLE_NAME + '</a>';
}
},
{
title: "备注",
field: "TABLE_COMMENT",
},
{
title: "记录数",
field: "TABLE_ROWS",
width: 100,
},
{
title: "引擎",
field: "ENGINE",
width: 100,
},
{
title: "字符集",
field: "TABLE_COLLATION",
},
{
title: "创建时间",
field: "CREATE_TIME",
},
{
title: "操作",
toolbar: "#table-bar",
align: "center",
width: 200
}
];
table.render({
elem: "#data-table",
url: SELECT_API,
page: true,
cols: [cols],
skin: "line",
size: "lg",
toolbar: "#table-toolbar",
defaultToolbar: [{
title: "刷新",
layEvent: "refresh",
icon: "layui-icon-refresh",
}, "filter", "print", "exports"]
});
$(document).on("click", ".tab-link", function () {
let obj = $(this);
let table = obj.html();
let url = obj.attr("src");
parent.layui.admin.addTab(table , table + "表", url);
})
table.on("tool(data-table)", function(obj) {
if (obj.event === "remove") {
remove(obj);
} else if (obj.event === "edit") {
edit(obj);
} else if (obj.event === "crud") {
crud(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;
});
let add = function() {
layer.open({
type: 2,
title: "创建表格",
shade: 0.1,
maxmin: true,
area: [common.isModile()?"100%":"98%", common.isModile()?"100%":"95%"],
content: CREATE_URL
});
}
let edit = function(obj) {
let table = obj.data.TABLE_NAME;
layer.open({
type: 2,
title: "修改表格",
shade: 0.1,
maxmin: true,
area: [common.isModile()?"100%":"98%", common.isModile()?"100%":"95%"],
content: MODIFY_URL + "?table=" + table
});
}
let crud = function(obj) {
let table = obj.data.TABLE_NAME;
layer.open({
type: 2,
title: "一键菜单",
shade: 0.1,
maxmin: true,
area: [common.isModile()?"100%":"500px", common.isModile()?"100%":"500px"],
content: CRUD_URL + "?table=" + table
});
}
let remove = function(obj) {
return doRemove([obj.data.TABLE_NAME]);
}
let batchRemove = function(obj) {
var tables = common.checkField(obj, "TABLE_NAME");
if (tables === "") {
layui.popup.warning("未选中数据");
return false;
}
doRemove(tables.split(","));
}
let doRemove = function (tables) {
layer.confirm("确定删除?", {
icon: 3,
title: "提示"
}, function(index) {
layer.close(index);
let loading = layer.load();
$.ajax({
url: DROP_API,
data: {tables: tables},
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() {
table.reloadData("data-table", {
scrollPos: "fixed",
done: function (res, curr) {
if (curr > 1 && res.data && !res.data.length) {
curr = curr - 1;
table.reloadData("data-table", {
page: {
curr: curr
},
})
}
}
});
}
})
// 获取选择组件配置项
function getControlProps(control_args)
{
if (!control_args) {
return {};
}
let props = {};
let split = control_args.split(";");
for (let item of split) {
let pos = item.indexOf(":");
if (pos === -1) continue;
let name = item.substring(0, pos).trim();
let values = item.substring(pos + 1).trim();
// values = a:v,c:d
pos = values.indexOf(":");
if (pos !== -1) {
let options = values.split(",");
values = {};
for (const option of options) {
let [value, name] = option.split(":");
values[value] = name;
}
}
props[name] = values;
}
return props;
}
</script>
</body>
</html>
+80
View File
@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>新增页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/component/jsoneditor/css/jsoneditor.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">
<input type="hidden" name="table" value="<?=htmlspecialchars($table)?>">
<?= $form->html(5)?>
</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?v=2.8.12"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/component/jsoneditor/jsoneditor.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
const INSERT_API = "/app/admin/table/insert";
<?=$form->js(3)?>
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>
+714
View File
@@ -0,0 +1,714 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>新增页面</title>
<link rel="stylesheet" href="/app/admin/component/layui/css/layui.css?v=2.8.12" />
<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">
<input type="hidden" name="old_table" value="<?=htmlspecialchars($table)?>">
</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_NUM-1 }}][field]" placeholder="字段名称" autocomplete="off" class="layui-input" value="{{ d.field }}">
<input type="hidden" name="columns[{{ d.LAY_NUM-1 }}][_field_id]" value="{{ d._field_id }}">
<input type="hidden" name="columns[{{ d.LAY_NUM-1 }}][old_field]" value="{{ d.old_field }}">
</script>
<script type="text/html" id="col-comment">
<input type="text" name="columns[{{ d.LAY_NUM-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_NUM-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_NUM-1 }}][default]" placeholder="默认值" autocomplete="off" class="layui-input" value="{{ d.default }}">
</script>
<script type="text/html" id="col-type">
<select name="columns[{{ d.LAY_NUM-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_NUM-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_NUM-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_NUM-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_NUM-1 }}][field]" autocomplete="off" class="layui-input" value="{{ d.field }}" disabled>
<input type="hidden" name="forms[{{ d.LAY_NUM-1 }}][_field_id]" value="{{ d._field_id }}">
</script>
<script type="text/html" id="form-comment">
<input type="text" name="forms[{{ d.LAY_NUM-1 }}][comment]" autocomplete="off" class="layui-input" value="{{ d.comment }}" disabled>
</script>
<script type="text/html" id="form-control">
<select name="forms[{{ d.LAY_NUM-1 }}][control]" lay-verify="">
{{# layui.each([["input", "文本框"],["inputNumber", "数字文本框"],["textArea", "多行文本"],["richText", "富文本"],["jsonEditor", "json编辑框"],["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_NUM-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_NUM-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_NUM-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_NUM-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_NUM-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_NUM-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_NUM-1 }}][name]" placeholder="字段名称" autocomplete="off" class="layui-input" value="{{ d.name }}">
</script>
<script type="text/html" id="key-columns">
<div name="keys[{{ d.LAY_NUM-1 }}][columns]" class="key-columns-div" value="{{ d.columns }}"></div>
</script>
<script type="text/html" id="key-type">
<select name="keys[{{ d.LAY_NUM-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?v=2.8.12"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
const MODIFY_API = "/app/admin/table/modify";
const SCHEMA_API = "/app/admin/table/schema";
const TABLE_NAME = "<?=htmlspecialchars($table)?>";
layui.use(["popup"], function () {
let $ = layui.$;
$.ajax({
url: SCHEMA_API + "?table=" + TABLE_NAME,
dataType: "json",
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
// 表信息
$('input[name="table"]').val(res.data.table.name);
$('input[name="table_comment"]').val(res.data.table.comment);
// 字段 表单 索引信息
let columnsData = res.data.columns;
let formsData = res.data.forms;
let keysData = res.data.keys;
window._field_id = 0;
layui.each(columnsData, function (index, item) {
columnsData[index]._field_id = _field_id;
columnsData[index].old_field = item.field;
columnsData[index].default = item.default === "" ? "''" : item.default;
formsData[index]._field_id = _field_id;
_field_id++;
});
if (keysData.length === 0) {
keysData.push({
name : "",
columns: "",
type: "normal"
});
}
window._key_id = 0;
layui.each(keysData, function (index, item) {
keysData[index]._key_id = _key_id;
_key_id ++;
});
// 字段设置
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"
}
];
table.render({
elem: "#column-table",
cols: [cols],
data: columnsData,
cellMinWidth: 10,
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) {
let 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})
syncTableData()
}
});
// 表单设置
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"
}
];
table.render({
elem: "#form-table",
cols: [cols],
data: formsData,
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"
}
];
table.render({
elem: "#key-table",
cols: [cols],
data: keysData,
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]"],
old_field : tableData["columns[" + id + "][old_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) {
if (!item.field) return;
formTableOldDataMap[item._field_id] = item;
});
// form data
layui.each(columnTableData, function (_, item) {
if (!item.field) return;
let _field_id = item._field_id;
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: MODIFY_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>
+115
View File
@@ -0,0 +1,115 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>新增页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/component/jsoneditor/css/jsoneditor.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">
<input type="hidden" name="<?=htmlspecialchars($primary_key)?>" value="<?=htmlspecialchars($value)?>">
<input type="hidden" name="table" value="<?=htmlspecialchars($table)?>">
<?=$form->html(5)?>
</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?v=2.8.12"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/component/jsoneditor/jsoneditor.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
let PRIMARY_KEY = "id";
const SELECT_API = "/app/admin/table/select" + location.search;
const UPDATE_API = "/app/admin/table/update";
layui.use(["form", "util", "popup"], function () {
let $ = layui.$;
let util = layui.util;
$.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(value);
} else {
obj.attr("value", value);
}
});
<?=$form->js(6)?>
// 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) {
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>
+392
View File
@@ -0,0 +1,392 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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">
<?=$form->html(5)?>
<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.table.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.table.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.table.update">编辑</button>
<button class="pear-btn pear-btn-xs tool-btn" lay-event="remove" permission="app.admin.table.delete">删除</button>
</script>
<script src="/app/admin/component/layui/layui.js?v=2.8.12"></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 TABLE_NAME = "<?=htmlspecialchars($table)?>";
const PRIMARY_KEY = "<?=htmlspecialchars($primary_key)?>";
const SELECT_API = "/app/admin/table/select?table=" + TABLE_NAME;
const UPDATE_API = "/app/admin/table/update";
const DELETE_API = "/app/admin/table/delete";
const SCHEMA_API = "/app/admin/table/schema?table=" + TABLE_NAME;
const INSERT_URL = "/app/admin/table/insert?table=" + TABLE_NAME;
const UPDATE_URL = "/app/admin/table/update?table=" + TABLE_NAME;
<?=$form->js(3)?>
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 apis = [];
let apiResults = {};
$.ajax({
url: SCHEMA_API,
dataType: "json",
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
let forms = res.data.forms;
let cols = [{
type: "checkbox"
}];
for (let i in forms) {
let item = forms[i];
let field = item.field;
let schema = {
title: item.comment || item.field,
field: field,
hide: !item.list_show,
sort: item.enable_sort
}
let control = item.control.toLowerCase();
// switch 可以直接编辑
if (control === "switch") {
let props = getControlProps(item.control_args);
let layText = props["lay-text"] || "";
schema.templet = function (d) {
form.on("switch("+field+")", function (data) {
if (!PRIMARY_KEY) {
return layui.popup.warning("该表没有主键,无法完成此操作");
}
let load = layer.load();
let postData = { table: TABLE_NAME };
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);
}
return layui.popup.success("操作成功");
})
});
let checked = d[field] === 1 ? "checked" : "";
return '<input type="checkbox" value="'+util.escape(d[PRIMARY_KEY])+'" lay-filter="'+util.escape(field)+'" lay-skin="switch" lay-text="'+util.escape(layText)+'" '+checked+'/>';
}
} else if (control === "iconpicker") {
schema.templet = function (d) {
return '<i class="layui-icon ' + util.escape(d[field]) + '"></i>';
};
} else if (control === "upload") {
schema.templet = function (d) {
return '<a href="' + encodeURI(d[field]) + '" target="_blank">' + util.escape(d[field]) + '</a>';
};
} else if (control === "uploadimage") {
schema.templet = function (d) {
return '<img src="'+encodeURI(d[field])+'" style="max-width:32px;max-height:32px;" />';
};
} else if (["select", "selectmulti", "treeselect", "treeselectmulti"].indexOf(control) !== -1) {
let props = getControlProps(item.control_args);
apiResults[field] = [];
if (props.url) {
apis.push([field, props.url]);
} else if (props.data) {
apiResults[field] = props.data;
}
schema.templet = function (d) {
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(","));
}
}
cols.push(schema);
}
cols.push({
title: "操作",
toolbar: "#table-bar",
align: "center",
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 count = apis.length;
if (count) {
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();
}
}
});
});
} else {
render();
}
}
});
table.on("tool(data-table)", function(obj) {
if (obj.event === "remove") {
window.remove(obj);
} else if (obj.event === "edit") {
window.edit(obj);
}
});
table.on("toolbar(data-table)", function(obj) {
if (obj.event === "add") {
window.add();
} else if (obj.event === "refresh") {
window.refreshTable();
} else if (obj.event === "batchRemove") {
window.batchRemove(obj);
}
});
// 字段验证允许为空
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)$)/, "请输入正确的身份证号"]
});
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
}
});
});
window.add = function() {
layer.open({
type: 2,
title: "新增",
shade: 0.1,
area: [common.isModile()?"100%":"500px", common.isModile()?"100%":"450px"],
content: INSERT_URL
});
}
window.edit = function(obj) {
if (!PRIMARY_KEY) {
return layui.popup.warning("该表没有主键,无法完成此操作");
}
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
});
}
window.remove = function(obj) {
return window.doRemove(obj.data[PRIMARY_KEY]);
}
window.batchRemove = function(obj) {
var checkIds = common.checkField(obj, PRIMARY_KEY);
if (checkIds === "") {
layui.popup.warning("未选中数据");
return false;
}
window.doRemove(checkIds.split(","));
}
window.doRemove = function (ids) {
if (!PRIMARY_KEY) {
return layui.popup.warning("该表没有主键,无法完成此操作");
}
let data = {
table: TABLE_NAME
};
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() {
table.reloadData("data-table", {
scrollPos: "fixed",
done: function (res, curr) {
if (curr > 1 && res.data && !res.data.length) {
curr = curr - 1;
table.reloadData("data-table", {
page: {
curr: curr
},
})
}
}
});
}
})
// 获取选择组件配置项
function getControlProps(control_args)
{
if (!control_args) {
return {};
}
let props = {};
let split = control_args.split(";");
for (let item of split) {
let pos = item.indexOf(":");
if (pos === -1) continue;
let name = item.substring(0, pos).trim();
let values = item.substring(pos + 1).trim();
// values = a:v,c:d
pos = values.indexOf(":");
if (pos !== -1) {
let options = values.split(",");
values = {};
for (const option of options) {
let [value, name] = option.split(":");
values[value] = name;
}
}
props[name] = values;
}
return props;
}
</script>
</body>
</html>
@@ -0,0 +1,452 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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">
<div name="category" id="category" value="" ></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">扩展名</label>
<div class="layui-input-block">
<input type="text" name="ext" 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="name" 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.upload.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.upload.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 pear-btn-primary" lay-event="select">选择</button>
</script>
<script src="/app/admin/component/layui/layui.js?v=2.8.12"></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/upload/select";
const UPDATE_API = "/app/admin/upload/update";
const DELETE_API = "/app/admin/upload/delete";
const INSERT_URL = "/app/admin/upload/insert";
const UPDATE_URL = "/app/admin/upload/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;
// 字段 上传时间 created_at
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#created_at",
range: ["#created_at-date-start", "#created_at-date-end"],
type: "datetime",
});
})
// 字段 类别 category
layui.use(["jquery", "xmSelect"], function() {
layui.$.ajax({
url: "/app/admin/dict/get/upload",
dataType: "json",
success: function (res) {
let value = layui.$("#category").attr("value");
let initValue = value ? value.split(",") : [];
layui.xmSelect.render({
el: "#category",
name: "category",
initValue: initValue,
data: res.data,
model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true,
radio: true,
on: function(data){
let value = data.arr[0] ? data.arr[0].value : "";
table.reload("data-table", {
where: {category: value}
})
},
})
}
});
});
// 表头参数
let cols = [
{
type: "checkbox"
},{
title: "主键",
field: "id",
width: 80,
sort: true,
},{
title: "名字",
field: "name",
},{
title: "文件",
field: "url",
templet: function (d) {
if (["jpg", "jpeg", "png", "gif", "bmp", "webp", "svg"].indexOf(d.ext.toLowerCase()) !== -1) {
return '<img src="' + encodeURI(d['url']) + '" style="max-width:32px;max-height:32px;" />';
}
return '<a href="' + encodeURI(d['url']) + '" target="_blank">' + util.escape(d['url']) + '</a>';
}
},{
title: "管理员id",
field: "admin_id",
hide: true,
},{
title: "用户id",
field: "user_id",
hide: true,
},{
title: "文件大小",
field: "file_size",
templet: function (d) {
return formatSize(d.file_size);
}
},{
title: "mime类型",
field: "mime_type",
hide: true,
},{
title: "图片宽度",
field: "image_width",
},{
title: "图片高度",
field: "image_height",
},{
title: "扩展名",
field: "ext",
},{
title: "存储位置",
field: "storage",
hide: true,
},{
title: "上传时间",
field: "created_at",
hide: true,
},{
title: "类别",
field: "category",
templet: function (d) {
let field = "category";
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: "updated_at",
hide: true,
},{
title: "操作",
toolbar: "#table-bar",
align: "center",
fixed: "right",
width: 130,
}
];
// 渲染表格
function render()
{
// 查询指定后缀
let searchExt = layui.url().search.ext;
if (searchExt) {
layui.$('.top-search-from input[name="ext"]').val(searchExt);
}
table.render({
elem: "#data-table",
url: SELECT_API,
page: true,
cols: [cols],
skin: "line",
size: "lg",
toolbar: "#table-toolbar",
autoSort: false,
where: {ext: searchExt},
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(["category", "/app/admin/dict/get/upload"]);
let apiResults = {};
apiResults["category"] = [];
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);
} else if (obj.event === "select") {
select(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%":"380px"],
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%":"380px"],
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);
}
})
});
}
// 选择行
let select = function (obj) {
let index = parent.layer.getFrameIndex(window.name);
let callback = parent.layui.$("#layui-layer" + index).data("callback");
callback(obj.data);
parent.layer.close(index);
};
// 刷新表格数据
window.refreshTable = function() {
table.reloadData("data-table", {
scrollPos: "fixed",
done: function (res, curr) {
if (curr > 1 && res.data && !res.data.length) {
curr = curr - 1;
table.reloadData("data-table", {
page: {
curr: curr
},
})
}
}
});
}
// 格式化文件大小
let formatSize = function(value) {
if(null == value || "" === value){
return "0 Bytes";
}
let unitArr = ["Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"];
let index = 0;
let srcSize = parseFloat(value);
index = Math.floor(Math.log(srcSize) / Math.log(1024));
let size =srcSize / Math.pow(1024, index);
size = size.toFixed(2);
return size + unitArr[index];
}
})
</script>
</body>
</html>
+434
View File
@@ -0,0 +1,434 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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">
<div name="category" id="category" value="" ></div>
</div>
</div>
<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">
<label class="layui-form-label">扩展名</label>
<div class="layui-input-block">
<input type="text" name="ext" 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.upload.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.upload.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.upload.update">编辑</button>
<button class="pear-btn pear-btn-xs tool-btn" lay-event="remove" permission="app.admin.upload.delete">删除</button>
</script>
<script src="/app/admin/component/layui/layui.js?v=2.8.12"></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/upload/select";
const UPDATE_API = "/app/admin/upload/update";
const DELETE_API = "/app/admin/upload/delete";
const INSERT_URL = "/app/admin/upload/insert";
const UPDATE_URL = "/app/admin/upload/update";
// 字段 上传时间 created_at
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#created_at",
range: ["#created_at-date-start", "#created_at-date-end"],
type: "datetime",
});
})
// 字段 类别 category
layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({
url: "/app/admin/dict/get/upload",
dataType: "json",
success: function (res) {
let value = layui.$("#category").attr("value");
let initValue = value ? value.split(",") : [];
layui.xmSelect.render({
el: "#category",
name: "category",
initValue: initValue,
data: res.data,
model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true,
radio: true,
});
if (res.code) {
layui.popup.failure(res.msg);
}
}
});
});
// 表格渲染
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: "id",
width: 80,
sort: true,
},{
title: "名字",
field: "name",
},{
title: "文件",
field: "url",
templet: function (d) {
if (["jpg", "jpeg", "png", "gif", "bmp", "webp", "svg"].indexOf(d.ext.toLowerCase()) !== -1) {
return '<img src="' + encodeURI(d['url']) + '" style="max-width:32px;max-height:32px;" />';
}
return '<a href="' + encodeURI(d['url']) + '" target="_blank">' + util.escape(d['url']) + '</a>';
}
},{
title: "管理员id",
field: "admin_id",
hide: true,
},{
title: "用户id",
field: "user_id",
hide: true,
},{
title: "文件大小",
field: "file_size",
templet: function (d) {
return formatSize(d.file_size);
}
},{
title: "mime类型",
field: "mime_type",
hide: true,
},{
title: "图片宽度",
field: "image_width",
},{
title: "图片高度",
field: "image_height",
},{
title: "扩展名",
field: "ext",
},{
title: "存储位置",
field: "storage",
hide: true,
},{
title: "上传时间",
field: "created_at",
hide: true,
},{
title: "类别",
field: "category",
templet: function (d) {
let field = "category";
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: "updated_at",
hide: true,
},{
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(["category", "/app/admin/dict/get/upload"]);
let apiResults = {};
apiResults["category"] = [];
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%":"380px"],
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%":"380px"],
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() {
table.reloadData("data-table", {
scrollPos: "fixed",
done: function (res, curr) {
if (curr > 1 && res.data && !res.data.length) {
curr = curr - 1;
table.reloadData("data-table", {
page: {
curr: curr
},
})
}
}
});
}
// 格式化文件大小
let formatSize = function(value) {
if(null == value || "" === value){
return "0 Bytes";
}
let unitArr = ["Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"];
let index = 0;
let srcSize = parseFloat(value);
index = Math.floor(Math.log(srcSize) / Math.log(1024));
let size =srcSize / Math.pow(1024, index);
size = size.toFixed(2);
return size + unitArr[index];
}
})
</script>
</body>
</html>
+91
View File
@@ -0,0 +1,91 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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="category" id="category" value="" ></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">文件</label>
<div class="layui-input-block">
<span></span>
<input type="text" style="display:none" name="url" value="" />
<button type="button" class="pear-btn pear-btn-primary pear-btn-sm" id="url">
<i class="layui-icon layui-icon-upload"></i>上传文件
</button>
</div>
</div>
</div>
</div>
</form>
<script src="/app/admin/component/layui/layui.js?v=2.8.12"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
// 字段 文件 url
layui.use(["upload", "layer", "popup", "util"], function() {
let input = layui.$("#url").prev();
input.prev().html(layui.util.escape(input.val()));
layui.upload.render({
elem: "#url",
accept: "file",
data: {category: function () {
return layui.$('input[name="category"]').val();
}},
url: "/app/admin/upload/insert",
field: "__file__",
done: function (res) {
if (res.code) return layui.popup.failure(res.msg);
parent.refreshTable();
parent.layer.close(parent.layer.getFrameIndex(window.name));
}
});
});
// 字段 类别 category
layui.use(["jquery", "xmSelect"], function() {
layui.$.ajax({
url: "/app/admin/dict/get/upload",
dataType: "json",
success: function (res) {
let value = layui.$("#category").attr("value");
let initValue = value ? value.split(",") : [];
layui.xmSelect.render({
el: "#category",
name: "category",
initValue: initValue,
data: res.data,
model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true,
radio: true,
});
if (res.code) {
return layui.popup.failure(res.msg);
}
}
});
});
</script>
</body>
</html>
+139
View File
@@ -0,0 +1,139 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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" style="width:inherit">类别</label>
<div class="layui-input-block" style="margin-left:60px;">
<div name="category" id="category" 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?v=2.8.12"></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/upload/select" + location.search;
const UPDATE_API = "/app/admin/upload/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 (typeof obj[0] === "undefined" || !obj[0].nodeName) return;
obj.attr("value", value);
});
// 字段 文件 url
layui.use(["upload", "layer", "popup", "util"], function() {
let input = layui.$("#url").prev();
input.prev().html(layui.util.escape(input.val()));
layui.upload.render({
elem: "#url",
url: "/app/admin/upload/update",
accept: "file",
field: "__file__",
done: function (res) {
if (res.code) return layui.popup.failure(res.msg);
this.item.prev().val(res.data.url).prev().html(layui.util.escape(res.data.url));
}
});
});
// 字段 类别 category
layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({
url: "/app/admin/dict/get/upload",
dataType: "json",
success: function (res) {
let value = layui.$("#category").attr("value");
let initValue = value ? value.split(",") : [];
layui.xmSelect.render({
el: "#category",
name: "category",
initValue: initValue,
data: res.data,
model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true,
radio: true,
});
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>
+556
View File
@@ -0,0 +1,556 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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="number" name="id" 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="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">
<div name="sex" id="sex" value="" ></div>
</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">
<input type="number" name="level" 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="birthday">
<input type="text" autocomplete="off" name="birthday[]" id="birthday-date-start" class="layui-input inline-block" placeholder="开始时间">
-
<input type="text" autocomplete="off" name="birthday[]" id="birthday-date-end" class="layui-input inline-block" placeholder="结束时间">
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">余额(元)</label>
<div class="layui-input-block">
<input type="number" name="money" value="" 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="score" 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="last_time">
<input type="text" autocomplete="off" name="last_time[]" id="last_time-date-start" class="layui-input inline-block" placeholder="开始时间">
-
<input type="text" autocomplete="off" name="last_time[]" id="last_time-date-end" class="layui-input inline-block" placeholder="结束时间">
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">登录ip</label>
<div class="layui-input-block">
<input type="text" name="last_ip" 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="join_time">
<input type="text" autocomplete="off" name="join_time[]" id="join_time-date-start" class="layui-input inline-block" placeholder="开始时间">
-
<input type="text" autocomplete="off" name="join_time[]" id="join_time-date-end" class="layui-input inline-block" placeholder="结束时间">
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">注册ip</label>
<div class="layui-input-block">
<input type="text" name="join_ip" 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.user.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.user.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.user.update">编辑</button>
<button class="pear-btn pear-btn-xs tool-btn" lay-event="remove" permission="app.admin.user.delete">删除</button>
</script>
<script src="/app/admin/component/layui/layui.js?v=2.8.12"></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/user/select";
const UPDATE_API = "/app/admin/user/update";
const DELETE_API = "/app/admin/user/delete";
const INSERT_URL = "/app/admin/user/insert";
const UPDATE_URL = "/app/admin/user/update";
// 字段 性别 sex
layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({
url: "/app/admin/dict/get/sex",
dataType: "json",
success: function (res) {
let value = layui.$("#sex").attr("value");
let initValue = value ? value.split(",") : [];
layui.xmSelect.render({
el: "#sex",
name: "sex",
initValue: initValue,
data: res.data,
model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true,
radio: true,
});
if (res.code) {
layui.popup.failure(res.msg);
}
}
});
});
// 字段 生日 birthday
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#birthday",
range: ["#birthday-date-start", "#birthday-date-end"],
});
})
// 字段 登录时间 last_time
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#last_time",
range: ["#last_time-date-start", "#last_time-date-end"],
type: "datetime",
});
})
// 字段 注册时间 join_time
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#join_time",
range: ["#join_time-date-start", "#join_time-date-end"],
type: "datetime",
});
})
// 表格渲染
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: "id",
sort: true,
},{
title: "用户名",
field: "username",
},{
title: "昵称",
field: "nickname",
},{
title: "密码",
field: "password",
hide: true,
},{
title: "性别",
field: "sex",
templet: function (d) {
let field = "sex";
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: "avatar",
templet: function (d) {
return '<img src="'+encodeURI(d['avatar'])+'" style="max-width:32px;max-height:32px;" alt="" />'
}
},{
title: "邮箱",
field: "email",
},{
title: "手机",
field: "mobile",
},{
title: "等级",
field: "level",
hide: true,
},{
title: "生日",
field: "birthday",
hide: true,
},{
title: "余额(元)",
field: "money",
hide: true,
},{
title: "积分",
field: "score",
hide: true,
},{
title: "登录时间",
field: "last_time",
hide: true,
},{
title: "登录ip",
field: "last_ip",
hide: true,
},{
title: "注册时间",
field: "join_time",
hide: true,
},{
title: "注册ip",
field: "join_ip",
hide: true,
},{
title: "token",
field: "token",
hide: true,
},{
title: "创建时间",
field: "created_at",
hide: true,
},{
title: "更新时间",
field: "updated_at",
hide: true,
},{
title: "角色",
field: "role",
hide: true,
},{
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" : "";
return '<input type="checkbox" value="'+util.escape(d[PRIMARY_KEY])+'" lay-filter="'+util.escape(field)+'" lay-skin="switch" lay-text="'+util.escape('')+'" '+checked+'/>';
}
},{
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(["sex", "/app/admin/dict/get/sex"]);
let apiResults = {};
apiResults["sex"] = [];
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() {
table.reloadData("data-table", {
scrollPos: "fixed",
done: function (res, curr) {
if (curr > 1 && res.data && !res.data.length) {
curr = curr - 1;
table.reloadData("data-table", {
page: {
curr: curr
},
})
}
}
});
}
})
</script>
</body>
</html>
+272
View File
@@ -0,0 +1,272 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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">
<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">
<div name="sex" id="sex" value="1" ></div>
</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 class="layui-form-item">
<label class="layui-form-label">等级</label>
<div class="layui-input-block">
<input type="number" name="level" value="0" 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="birthday" id="birthday" 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="money" value="0" 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="score" value="0" 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="last_time" id="last_time" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">登录ip</label>
<div class="layui-input-block">
<input type="text" name="last_ip" 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="join_time" id="join_time" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">注册ip</label>
<div class="layui-input-block">
<input type="text" name="join_ip" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">禁用</label>
<div class="layui-input-block">
<input type="checkbox" id="status" lay-filter="status" lay-skin="switch" />
<input type="text" style="display:none" name="status" value="0" />
</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?v=2.8.12"></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/user/insert";
// 字段 性别 sex
layui.use(["jquery", "xmSelect"], function() {
layui.$.ajax({
url: "/app/admin/dict/get/sex",
dataType: "json",
success: function (res) {
let value = layui.$("#sex").attr("value");
let initValue = value ? value.split(",") : [];
layui.xmSelect.render({
el: "#sex",
name: "sex",
initValue: initValue,
data: res.data,
value: "1",
model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true,
radio: true,
});
if (res.code) {
layui.popup.failure(res.msg);
}
}
});
});
// 字段 头像 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);
}
});
});
// 字段 生日 birthday
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#birthday",
});
})
// 字段 登录时间 last_time
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#last_time",
type: "datetime",
});
})
// 字段 注册时间 join_time
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#join_time",
type: "datetime",
});
})
// 字段 禁用 status
layui.use(["form"], function() {
layui.$("#status").attr("checked", layui.$('input[name="status"]').val() != 0);
layui.form.render();
layui.form.on("switch(status)", function(data) {
layui.$('input[name="status"]').val(this.checked ? 1 : 0);
});
})
//提交事件
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>
+306
View File
@@ -0,0 +1,306 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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">
<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">
<div name="sex" id="sex" value="" ></div>
</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 class="layui-form-item">
<label class="layui-form-label">等级</label>
<div class="layui-input-block">
<input type="number" name="level" 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="birthday" id="birthday" 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="money" value="" 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="score" 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="last_time" id="last_time" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">登录ip</label>
<div class="layui-input-block">
<input type="text" name="last_ip" 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="join_time" id="join_time" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">注册ip</label>
<div class="layui-input-block">
<input type="text" name="join_ip" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">禁用</label>
<div class="layui-input-block">
<input type="checkbox" id="status" lay-filter="status" lay-skin="switch" />
<input type="text" style="display:none" name="status" value="0" />
</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?v=2.8.12"></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/user/select" + location.search;
const UPDATE_API = "/app/admin/user/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);
}
});
// 字段 性别 sex
layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({
url: "/app/admin/dict/get/sex",
dataType: "json",
success: function (res) {
let value = layui.$("#sex").attr("value");
let initValue = value ? value.split(",") : [];
layui.xmSelect.render({
el: "#sex",
name: "sex",
initValue: initValue,
data: res.data,
model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true,
radio: true,
});
if (res.code) {
layui.popup.failure(res.msg);
}
}
});
});
// 字段 头像 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);
}
});
});
// 字段 生日 birthday
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#birthday",
});
})
// 字段 登录时间 last_time
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#last_time",
type: "datetime",
});
})
// 字段 注册时间 join_time
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#join_time",
type: "datetime",
});
})
// 字段 禁用 status
layui.use(["form"], function() {
layui.$("#status").attr("checked", layui.$('input[name="status"]').val() != 0);
layui.form.render();
layui.form.on("switch(status)", function(data) {
layui.$('input[name="status"]').val(this.checked ? 1 : 0);
});
})
// 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>
+267
View File
@@ -0,0 +1,267 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>详情页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/component/jsoneditor/css/jsoneditor.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">车架号(17位国际标准)</label>
<div class="layui-input-block">
<input type="text" readonly name="vin" value="" required lay-verify="required" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">classify</label>
<div class="layui-input-block">
<input type="text" readonly name="classify" 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" readonly name="brand" 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" readonly name="model" 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" readonly name="status" 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" readonly name="purchase_location" 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" readonly name="procedure_location" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">FBO最终目的地</label>
<div class="layui-input-block">
<input type="text" name="fbo_destination" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">fbo目的地id</label>
<div class="layui-input-block">
<input type="number" readonly name="fbo_locations_id" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">车辆所在地</label>
<div class="layui-input-block">
<input type="number" readonly name="locations_id" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">最终目的地</label>
<div class="layui-input-block">
<input type="number" readonly name="final_locations_id" 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" readonly name="departure_time" 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" readonly name="arrival_time" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">运输周期(天)</label>
<div class="layui-input-block">
<input type="number" readonly name="shipping_cycle" 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="img_url" value="" class="layui-input">-->
<img name="img_url" src=''></img>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">记录创建时间</label>
<div class="layui-input-block">
<input type="text" readonly 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" readonly 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?v=2.8.12"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/component/jsoneditor/jsoneditor.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
// 相关接口
const PRIMARY_KEY = "id";
const SELECT_API = "/app/admin/vehicle/select" + location.search;
const UPDATE_API = "/app/admin/vehicle/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(value);
} else {
obj.attr("value", value);
obj[0].value = value;
}
if(key === "img_url"){
obj.attr("src", value);
}
// 多图渲染
if (obj[0].classList.contains('uploader-list')) {
let multiple_images = value.split(",");
$.each(multiple_images, function(index, value) {
$('#uploader-list-'+ key).append(
'<div class="file-iteme">' +
'<div class="handle"><i class="layui-icon layui-icon-delete"></i></div>' +
'<img src='+value +' alt="'+ value +'" >' +
'</div>'
);
});
}
});
// // 字段 记录创建时间 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>
+298
View File
@@ -0,0 +1,298 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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.vehicle.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.vehicle.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="view">查看</button>
<button class="pear-btn pear-btn-xs tool-btn" lay-event="edit" permission="app.admin.vehicle.update">编辑</button>
<button class="pear-btn pear-btn-xs tool-btn" lay-event="remove" permission="app.admin.vehicle.delete">删除</button>
</script>
<script src="/app/admin/component/layui/layui.js?v=2.8.12"></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/vehicle/select";
const UPDATE_API = "/app/admin/vehicle/update";
const DELETE_API = "/app/admin/vehicle/delete";
const INSERT_URL = "/app/admin/vehicle/insert";
const UPDATE_URL = "/app/admin/vehicle/update";
const DETAILE_URL = "/app/admin/vehicle/detail";
// 表格渲染
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",
align: "center"
},{
title: "ID",align: "center",
field: "id",
width: 10,
},{
title: "车架号(17位国际标准)",align: "center",
field: "vin",
},{
title: "车辆品牌",align: "center",
field: "brand",
},{
title: "车辆型号",align: "center",
field: "model",
},{
title: "车辆状态",align: "center",
field: "status",
},{
title: "手续办理地",align: "center",
field: "procedure_location",
},{
title: "FBO港口",align: "center",
field: "fbo_location_name",
},{
title: "车辆所在地",align: "center",
field: "location_name",
},{
title: "最终目的地",align: "center",
field: "final_location_name",
// },{
// title: "FBO发车时间",align: "center",
// field: "departure_time",
// },{
// title: "FBO到达时间",align: "center",
// field: "arrival_time",
},{
title: "图片",align: "center",
field: "img_url",
templet: function (d) {
return '<img src="' + encodeURI(d['img_url']) + '" style="max-width:32px;max-height:32px;" />';
}
},{
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);
} else if (obj.event === "view") {
view(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,
maxmin: true,
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,
maxmin: true,
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() {
table.reloadData("data-table", {
scrollPos: "fixed",
done: function (res, curr) {
if (curr > 1 && res.data && !res.data.length) {
curr = curr - 1;
table.reloadData("data-table", {
page: {
curr: curr
},
})
}
}
});
}
// 查看数据
let view = function(obj) {
let value = obj.data[PRIMARY_KEY];
layer.open({
type: 2,
title: "查看详情",
shade: 0.1,
maxmin: true,
area: ["100%", "100%"],
content: DETAILE_URL + "?" + PRIMARY_KEY + "=" + value + "&mode=view"
});
}
})
</script>
</body>
</html>
+321
View File
@@ -0,0 +1,321 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>新增页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/component/jsoneditor/css/jsoneditor.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">车架号(17位国际标准)</label>
<div class="layui-input-block">
<input type="text" name="vin" 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="classify" value="" class="layui-input">-->
<!-- </div>-->
<div class="layui-input-block">
<select name="classify" lay-filter="category" id="classify" required>
<option value="">请选择分类</option>
<option value="1">自主</option>
<option value="2">代理</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">车辆品牌</label>
<div class="layui-input-block">
<input type="text" name="brand" 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="model" 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="status" value="" class="layui-input">-->
<!-- </div>-->
<div class="layui-input-block">
<select name="status" lay-filter="category" required lay-verify="required" id="status" required>
<option value="">请选择分类</option>
<option value="1">待发车</option>
<option value="2">在途(发往fbo港口)</option>
<option value="3">抵达发货港口</option>
<option value="4">在途(发往目的地仓库)</option>
<option value="5">抵达仓库</option>
<option value="6">售出</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">买车地点</label>
<div class="layui-input-block">
<input type="text" name="purchase_location" 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="procedure_location" value="" class="layui-input">
</div>
</div>
<!-- <div class="layui-form-item">-->
<!-- <label class="layui-form-label">FBO最终目的地</label>-->
<!-- <div class="layui-input-block">-->
<!-- <input type="text" name="fbo_destination" value="" class="layui-input">-->
<!-- </div>-->
<!-- </div>-->
<!-- -->
<!-- <div class="layui-form-item">-->
<!-- <label class="layui-form-label">fbo目的地id</label>-->
<!-- <div class="layui-input-block">-->
<!-- <input type="number" name="fbo_locations_id" value="" class="layui-input">-->
<!-- </div>-->
<!-- </div>-->
<div class="layui-form-item">
<label class="layui-form-label">fbo目的地</label>
<div class="layui-input-block">
<select name="fbo_locations_id" lay-filter="category" id="fbo_locations_id" required>
<option value="">请选择分类</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">车辆所在地</label>
<!-- <div class="layui-input-block">-->
<!-- <input type="number" name="locations_id" value="" class="layui-input">-->
<!-- </div>-->
<div class="layui-input-block">
<select name="locations_id" lay-filter="category" id="locations_id" required>
<option value="">请选择分类</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">最终目的地</label>
<!-- <div class="layui-input-block">-->
<!-- <input type="number" name="final_locations_id" value="" class="layui-input">-->
<!-- </div>-->
<div class="layui-input-block">
<select name="final_locations_id" lay-filter="category" required lay-verify="required" id="final_locations_id" required>
<option value="">请选择分类</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">FBO发车时间</label>
<div class="layui-input-block">
<input type="text" name="departure_time" id="departure_time" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">FBO到达时间</label>
<div class="layui-input-block">
<input type="text" name="arrival_time" id="arrival_time" value="" 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="shipping_cycle" 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="img_url" value="" required lay-verify="required" class="layui-input">-->
<!-- </div>-->
<div class="layui-input-block">
<img class="img-3" src=""/>
<input type="text" style="display:none" name="img_url" value="" />
<button type="button" class="pear-btn pear-btn-primary pear-btn-sm" id="img_url" permission="app.admin.upload.avatar">
<i class="layui-icon layui-icon-upload"></i>上传图片
</button>
</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?v=2.8.12"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/component/jsoneditor/jsoneditor.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
layui.use(['form', 'jquery', 'layer'], function(){
const form = layui.form;
const $ = layui.jquery;
const layer = layui.layer;
function loadOptions() {
$.ajax({
url: '/app/admin/location/getAll',
type: 'GET',
dataType: 'json',
success: function(res) {
// 统一先检查状态码
if (res.code !== 0) {
layer.msg(res.msg || '加载失败');
return;
}
const select = $('#fbo_locations_id');
const locations_id = $('#locations_id');
const final_locations_id = $('#final_locations_id');
select.empty().append('<option value="">请选择分类</option>');
locations_id.empty().append('<option value="">请选择分类</option>');
$.each(res.data, function(index, item){
select.append(`<option value="${item.id}">${item.name}</option>`);
locations_id.append(`<option value="${item.id}">${item.name}</option>`);
final_locations_id.append(`<option value="${item.id}">${item.name}</option>`);
});
form.render('select');
},
error: function(xhr) {
layer.msg('请求失败: ' + xhr.status);
}
});
}
loadOptions();
});
</script>
<script>
// 图片上传
layui.use(["upload", "layer"], function() {
let input = layui.$("#img_url").prev();
input.prev().attr("src", input.val());
layui.upload.render({
elem: "#img_url",
url: "/app/admin/upload/uploadImage",
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);
}
});
});
</script>
<script>
// 相关接口
const INSERT_API = "/app/admin/vehicle/insert";
// 字段 记录创建时间 departure_time
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#departure_time",
type: "date",
});
})
// 字段 记录创建时间 arrival_time
layui.use(["laydate"], function() {
layui.laydate.render({
elem: "#arrival_time",
type: "date",
});
})
// 字段 记录创建时间 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>
+249
View File
@@ -0,0 +1,249 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>更新页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/component/jsoneditor/css/jsoneditor.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">车架号(17位国际标准)</label>
<div class="layui-input-block">
<input type="text" name="vin" value="" required lay-verify="required" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">classify</label>
<div class="layui-input-block">
<input type="text" name="classify" 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="brand" 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="model" 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="status" 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="purchase_location" 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="procedure_location" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">FBO最终目的地</label>
<div class="layui-input-block">
<input type="text" name="fbo_destination" value="" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">fbo目的地id</label>
<div class="layui-input-block">
<input type="number" name="fbo_locations_id" value="" 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="locations_id" value="" 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="final_locations_id" 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="departure_time" 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="arrival_time" value="" 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="shipping_cycle" 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="img_url" 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?v=2.8.12"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/component/jsoneditor/jsoneditor.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
// 相关接口
const PRIMARY_KEY = "id";
const SELECT_API = "/app/admin/vehicle/select" + location.search;
const UPDATE_API = "/app/admin/vehicle/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(value);
} else {
obj.attr("value", value);
obj[0].value = value;
}
// 多图渲染
if (obj[0].classList.contains('uploader-list')) {
let multiple_images = value.split(",");
$.each(multiple_images, function(index, value) {
$('#uploader-list-'+ key).append(
'<div class="file-iteme">' +
'<div class="handle"><i class="layui-icon layui-icon-delete"></i></div>' +
'<img src='+value +' alt="'+ value +'" >' +
'</div>'
);
});
}
});
// 字段 记录创建时间 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>
+22
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 [
'debug' => true,
'controller_suffix' => 'Controller',
'controller_reuse' => false,
'public_path' => base_path('plugin' . DIRECTORY_SEPARATOR. 'admin' . DIRECTORY_SEPARATOR . 'public'),
'plugin_market_host' => 'https://www.workerman.net',
'version' => '0.6.33',
];
+19
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 [
'files' => [
base_path() . '/plugin/admin/app/functions.php',
]
];
+15
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
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;

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