Compare commits

..

No commits in common. "main" and "v0.5.8" have entirely different histories.
main ... v0.5.8

32 changed files with 425 additions and 513 deletions

View File

@ -2,7 +2,7 @@
"name": "webman/admin", "name": "webman/admin",
"type": "project", "type": "project",
"license": "MIT", "license": "MIT",
"description": "基于Webman官方的Admin修改", "description": "Webman Admin",
"require": { "require": {
"workerman/webman-framework": ">=1.4", "workerman/webman-framework": ">=1.4",
"illuminate/database": ">=7.30", "illuminate/database": ">=7.30",

View File

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

View File

@ -1,7 +1,6 @@
<?php <?php
namespace plugin\admin\app\common; namespace plugin\admin\app\common;
use plugin\admin\app\common\Util;
use support\exception\BusinessException; use support\exception\BusinessException;
class Layui class Layui
@ -125,31 +124,6 @@ EOF;
</div> </div>
</div> </div>
EOF;
}
/**
* 输入框模糊查询
* @param $options
* @return void
*/
public function inputLike($options)
{
[$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options);
$type = $props['type'] ?? 'text';
$this->htmlContent .= <<<EOF
<div class="layui-form-item">
$label
<div class="$class">
<div class="layui-input-block">
<input type="hidden" autocomplete="off" name="{$field}[]" value="like" class="layui-input inline-block">
<input type="$type" autocomplete="off" name="{$field}[]" class="layui-input">
</div>
</div>
</div>
EOF; EOF;
} }
@ -164,17 +138,6 @@ EOF;
$this->inputRange($options); $this->inputRange($options);
} }
/**
* 数字输入框模糊查询
* @param $options
* @return void
*/
public function inputNumberLike($options)
{
$options['props']['type'] = 'number';
$this->inputLike($options);
}
/** /**
* 密码输入框 * 密码输入框
* @param $options * @param $options
@ -207,58 +170,6 @@ EOF;
</div> </div>
</div> </div>
EOF;
}
/**
* 富文本
* @param $options
* @return void
*/
public function richText($options)
{
[$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options);
$placeholder_string = !empty($props['placeholder']) ? ' placeholder="'.$props['placeholder'].'"' : '';
$disabled_string = !empty($props['disabled']) ? ' disabled' : '';
$id = $field;
$this->htmlContent .= <<<EOF
<div class="layui-form-item">
$label
<div class="$class">
<textarea id="$id" name="$field"$required_string$verify_string$placeholder_string$disabled_string class="layui-textarea">$value</textarea>
</div>
</div>
EOF;
$options_string = '';
if (!isset($props['images_upload_url'])) {
$props['images_upload_url'] = '/app/admin/upload/image';
}
foreach ($props as $key => $item) {
if (is_array($item)) {
$item = json_encode($item, JSON_UNESCAPED_UNICODE);
$options_string .= "\n $key: $item,";
} else {
$options_string .= "\n $key: \"$item\",";
}
}
$this->jsContent .= <<<EOF
// 字段 {$options['label']} $field
layui.use(["tinymce"], function() {
var tinymce = layui.tinymce
var edit = tinymce.render({
elem: "#$id",$options_string
});
edit.on("blur", function(){
layui.$("#$id").val(edit.getContent());
});
});
EOF; EOF;
} }
@ -701,9 +612,6 @@ EOF;
$default_value_string = isset($props['initValue']) && $props['initValue'] != '' ? $props['initValue'] : $value; $default_value_string = isset($props['initValue']) && $props['initValue'] != '' ? $props['initValue'] : $value;
$url = $props['url'] ?? ''; $url = $props['url'] ?? '';
$options_string = ''; $options_string = '';
if (isset($props['lay-verify'])) {
$props['layVerify'] = $props['lay-verify'];
}
unset($props['lay-verify'], $props['url']); unset($props['lay-verify'], $props['url']);
foreach ($props as $key => $item) { foreach ($props as $key => $item) {
if (is_array($item)) { if (is_array($item)) {
@ -765,7 +673,7 @@ EOF;
<div class="layui-form-item"> <div class="layui-form-item">
$select_label $select_label
<div class="$class"> <div class="$class">
<div name="$field" id="$id"$required_string value="$default_value_string" ></div> <div name="$field" id="$id"$verify_string$required_string value="$default_value_string" ></div>
</div> </div>
</div> </div>
@ -799,7 +707,6 @@ EOF;
$field = $info['field']; $field = $info['field'];
$default = $columns[$key]['default']; $default = $columns[$key]['default'];
$control = strtolower($info['control']); $control = strtolower($info['control']);
$auto_increment = $columns[$key]['auto_increment'];
// 搜索框里上传组件替换为input // 搜索框里上传组件替换为input
if ($type == 'search' && in_array($control, ['upload', 'uploadimg'])) { if ($type == 'search' && in_array($control, ['upload', 'uploadimg'])) {
$control = 'input'; $control = 'input';
@ -808,34 +715,20 @@ EOF;
$props = Util::getControlProps($control, $info['control_args']); $props = Util::getControlProps($control, $info['control_args']);
// 增加修改记录验证必填项 // 增加修改记录验证必填项
if ($filter == 'form_show' && !$columns[$key]['nullable'] && $default === null && ($field !== 'password' || $type === 'insert')) { if ($filter == 'form_show' && !isset($props['lay-verify']) && !$columns[$key]['nullable'] && $default === null && ($field !== 'password' || $type === 'insert')) {
if (!isset($props['lay-verify'])) {
$props['lay-verify'] = 'required'; $props['lay-verify'] = 'required';
// 非类似字符串类型不允许传空
} elseif (!in_array($columns[$key]['type'], ['string', 'text', 'mediumText', 'longText', 'char', 'binary', 'json'])
&& strpos($props['lay-verify'], 'required') === false) {
$props['lay-verify'] = 'required|' . $props['lay-verify'];
}
} }
// 增加记录显示默认值 // 增加记录显示默认值
if ($type === 'insert' && !isset($props['value']) && $default !== null) { if ($type === 'insert' && !isset($props['value']) && $default !== null) {
$props['value'] = $default; $props['value'] = $default;
} }
// 主键是自增字段或者表单是更新类型不显示主键 // 表单不显示主键
if ($primary_key && $field == $primary_key && (($type == 'insert' && $auto_increment) || $type == 'update')) { if ($filter == 'form_show' && $primary_key && $field == $primary_key) {
continue; continue;
} }
// 查询类型 // 范围查询
if ($type == 'search') { if ($type == 'search' && $info['search_type'] == 'between' && method_exists($form, "{$control}Range")) {
if ($info['search_type'] == 'between' && method_exists($form, "{$control}Range")) {
$control = "{$control}Range"; $control = "{$control}Range";
} elseif ($info['search_type'] == 'like' && method_exists($form, "{$control}Like")) {
$control = "{$control}Like";
}
}
// 查询类型移除lay-verify
if ($type == 'search' && !empty($props['lay-verify'])) {
$props['lay-verify'] = '';
} }
$options = [ $options = [

View File

@ -2,15 +2,12 @@
namespace plugin\admin\app\common; namespace plugin\admin\app\common;
use process\Monitor;
use Throwable; use Throwable;
use Illuminate\Database\Connection; use Illuminate\Database\Connection;
use Illuminate\Database\Schema\Builder; use Illuminate\Database\Schema\Builder;
use plugin\admin\app\model\Option; use plugin\admin\app\model\Option;
use support\exception\BusinessException; use support\exception\BusinessException;
use support\Db; use support\Db;
use Workerman\Timer;
use Workerman\Worker;
class Util class Util
{ {
@ -315,8 +312,6 @@ class Util
'char' => ['Input'], 'char' => ['Input'],
'binary' => ['Input'], 'binary' => ['Input'],
'json' => ['input']
]; ];
} }
@ -375,7 +370,7 @@ class Util
{ {
Util::checkTableName($table); Util::checkTableName($table);
$database = config('database.connections')['plugin.admin.mysql']['database']; $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") : []; $schema_raw = $section !== 'table' ? Util::db()->select("select * from information_schema.COLUMNS where TABLE_SCHEMA = '$database' and table_name = '$table'") : [];
$forms = []; $forms = [];
$columns = []; $columns = [];
foreach ($schema_raw as $item) { foreach ($schema_raw as $item) {
@ -524,7 +519,7 @@ class Util
/** /**
* Reload webman * reload webman (不支持windows)
* @return bool * @return bool
*/ */
public static function reloadWebman() public static function reloadWebman()
@ -534,34 +529,8 @@ class Util
posix_kill(posix_getppid(), SIGUSR1); posix_kill(posix_getppid(), SIGUSR1);
return true; return true;
} catch (Throwable $e) {} } catch (Throwable $e) {}
} else {
Timer::add(1, function () {
Worker::stopAll();
});
} }
return false; return false;
} }
/**
* Pause file monitor
* @return void
*/
public static function pauseFileMonitor()
{
if (method_exists(Monitor::class, 'pause')) {
Monitor::pause();
}
}
/**
* Resume file monitor
* @return void
*/
public static function resumeFileMonitor()
{
if (method_exists(Monitor::class, 'resume')) {
Monitor::resume();
}
}
} }

View File

@ -129,9 +129,7 @@ class Crud extends Base
$model = $this->model; $model = $this->model;
foreach ($where as $column => $value) { foreach ($where as $column => $value) {
if (is_array($value)) { if (is_array($value)) {
if ($value[0] === 'like') { if (in_array($value[0], ['>', '=', '<', '<>', 'like', 'not like'])) {
$model = $model->where($column, 'like', "%$value[1]%");
} elseif (in_array($value[0], ['>', '=', '<', '<>', 'not like'])) {
$model = $model->where($column, $value[0], $value[1]); $model = $model->where($column, $value[0], $value[1]);
} elseif ($value[0] == 'in') { } elseif ($value[0] == 'in') {
$model = $model->whereIn($column, $value[1]); $model = $model->whereIn($column, $value[1]);
@ -141,7 +139,7 @@ class Crud extends Base
$model = $model->whereNull($column, $value[1]); $model = $model->whereNull($column, $value[1]);
} elseif ($value[0] == 'not null') { } elseif ($value[0] == 'not null') {
$model = $model->whereNotNull($column, $value[1]); $model = $model->whereNotNull($column, $value[1]);
} elseif ($value[0] !== '' || $value[1] !== '') { } else {
$model = $model->whereBetween($column, $value); $model = $model->whereBetween($column, $value);
} }
} else { } else {
@ -276,7 +274,7 @@ class Crud extends Base
protected function inputFilter(array $data): array protected function inputFilter(array $data): array
{ {
$table = config('plugin.admin.database.connections.mysql.prefix') . $this->model->getTable(); $table = config('plugin.admin.database.connections.mysql.prefix') . $this->model->getTable();
$allow_column = $this->model->getConnection()->select("desc `$table`"); $allow_column = Util::db()->select("desc `$table`");
if (!$allow_column) { if (!$allow_column) {
throw new BusinessException('表不存在', 2); throw new BusinessException('表不存在', 2);
} }

View File

@ -4,6 +4,7 @@ namespace plugin\admin\app\controller;
use plugin\admin\app\common\Util; use plugin\admin\app\common\Util;
use plugin\admin\app\model\User; use plugin\admin\app\model\User;
use support\Db;
use support\exception\BusinessException; use support\exception\BusinessException;
use support\Request; use support\Request;
use support\Response; use support\Response;
@ -60,7 +61,7 @@ class IndexController
// 总用户数 // 总用户数
$user_count = User::count(); $user_count = User::count();
// mysql版本 // mysql版本
$version = Util::db()->select('select VERSION() as version'); $version = Db::select('select VERSION() as version');
$mysql_version = $version[0]->version ?? 'unknown'; $mysql_version = $version[0]->version ?? 'unknown';
$day7_detail = []; $day7_detail = [];

View File

@ -198,17 +198,15 @@ EOF;
if ($password != $password_confirm) { if ($password != $password_confirm) {
return $this->json(1, '两次密码不一致'); return $this->json(1, '两次密码不一致');
} }
if (Admin::first()) {
return $this->json(1, '后台已经安装完毕,无法通过此页面创建管理员');
}
if (!is_file($config_file = base_path() . '/plugin/admin/config/database.php')) { if (!is_file($config_file = base_path() . '/plugin/admin/config/database.php')) {
return $this->json(1, '请先完成第一步数据库配置'); return $this->json(1, '请先完成第一步数据库配置');
} }
$config = include $config_file; $config = include $config_file;
$connection = $config['connections']['mysql']; $connection = $config['connections']['mysql'];
$pdo = $this->getPdo($connection['host'], $connection['username'], $connection['password'], $connection['port'], $connection['database']); $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)"); $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'); $time = date('Y-m-d H:i:s');
$data = [ $data = [
@ -381,7 +379,6 @@ EOF;
} }
$params = [ $params = [
\PDO::MYSQL_ATTR_INIT_COMMAND => "set names utf8mb4", \PDO::MYSQL_ATTR_INIT_COMMAND => "set names utf8mb4",
\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
\PDO::ATTR_EMULATE_PREPARES => false, \PDO::ATTR_EMULATE_PREPARES => false,
\PDO::ATTR_TIMEOUT => 5, \PDO::ATTR_TIMEOUT => 5,
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,

View File

@ -5,18 +5,10 @@ namespace plugin\admin\app\controller;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
use plugin\admin\app\common\Util; use plugin\admin\app\common\Util;
use plugin\admin\app\controller\Base;
use process\Monitor;
use support\exception\BusinessException; use support\exception\BusinessException;
use support\Log; use support\Log;
use support\Request; use support\Request;
use support\Response; 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 class PluginController extends Base
{ {
@ -28,14 +20,11 @@ class PluginController extends Base
/** /**
* @param Request $request * @param Request $request
* @return string * @return Response
* @throws GuzzleException
*/ */
public function index(Request $request) public function index(Request $request): Response
{ {
$client = $this->httpClient(); return view('plugin/index');
$response = $client->get("/webman-admin/apps");
return (string)$response->getBody();
} }
/** /**
@ -46,7 +35,14 @@ class PluginController extends Base
*/ */
public function list(Request $request): Response public function list(Request $request): Response
{ {
$installed = $this->getLocalPlugins(); $installed = [];
clearstatcache();
$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;
}
}
$client = $this->httpClient(); $client = $this->httpClient();
$query = $request->get(); $query = $request->get();
@ -61,49 +57,75 @@ class PluginController extends Base
return $this->json(1, '获取数据出错'); return $this->json(1, '获取数据出错');
} }
$disabled = is_phar(); $disabled = is_phar();
foreach ($data['data']['items'] as $key => $item) { foreach ($data['result']['items'] as $key => $item) {
$name = $item['name']; $name = $item['name'];
$data['data']['items'][$key]['installed'] = $installed[$name] ?? 0; $data['result']['items'][$key]['installed'] = $installed[$name] ?? 0;
$data['data']['items'][$key]['disabled'] = $disabled; $data['result']['items'][$key]['disabled'] = $disabled;
} }
$items = $data['data']['items']; $items = $data['result']['items'];
$count = $data['data']['total']; $count = $data['result']['total'];
return json(['code' => 0, 'msg' => 'ok', 'data' => $items, 'count' => $count]); return json(['code' =>0, 'msg' => 'ok', 'data' => $items, 'count' => $count]);
}
/**
* 摘要
* @param Request $request
* @return Response
* @throws GuzzleException
*/
public function schema(Request $request): Response
{
$client = $this->httpClient();
$response = $client->get('/api/app/schema', ['query' => $request->get()]);
$data = json_decode($response->getBody()->getContents(), true);
$result = $data['result'];
foreach ($result as &$item) {
$item['field'] = $item['field'] ?? $item['dataIndex'];
unset($item['dataIndex']);
}
return $this->json(0, 'ok', $result);
} }
/** /**
* 安装 * 安装
* @param Request $request * @param Request $request
* @return Response * @return Response
* @throws GuzzleException|BusinessException * @throws GuzzleException
*/ */
public function install(Request $request): Response public function install(Request $request): Response
{ {
$name = $request->post('name'); $name = $request->post('name');
$version = $request->post('version'); $version = $request->post('version');
$installed_version = $this->getPluginVersion($name); $installed_version = $this->getPluginVersion($name);
$host = $request->host(true);
if (!$name || !$version) { if (!$name || !$version) {
return $this->json(1, '缺少参数'); return $this->json(1, '缺少参数');
} }
$user = session('app-plugin-user'); $user = session('app-plugin-user');
if (!$user) { if (!$user) {
return $this->json(-1, '请登录'); return $this->json(0, '请登录', [
'code' => 401,
'msg' => '请登录'
]);
} }
// 获取下载zip文件url // 获取下载zip文件url
$data = $this->getDownloadUrl($name, $version); $data = $this->getDownloadUrl($name, $user['uid'], $host, $version);
if ($data['code'] != 0) { if ($data['code'] == -1) {
return $this->json($data['code'], $data['msg'], $data['data'] ?? []); return $this->json(0, '请登录', [
'code' => 401,
'msg' => '请登录'
]);
} }
// 下载zip文件 // 下载zip文件
$base_path = base_path() . "/plugin/$name"; $base_path = base_path() . "/plugin/$name";
$zip_file = "$base_path.zip"; $zip_file = "$base_path.zip";
$extract_to = base_path() . '/plugin/'; $extract_to = base_path() . '/plugin/';
$this->downloadZipFile($data['data']['url'], $zip_file); $this->downloadZipFile($data['result']['url'], $zip_file);
$has_zip_archive = class_exists(ZipArchive::class, false); $has_zip_archive = class_exists(\ZipArchive::class, false);
if (!$has_zip_archive) { if (!$has_zip_archive) {
$cmd = $this->getUnzipCmd($zip_file, $extract_to); $cmd = $this->getUnzipCmd($zip_file, $extract_to);
if (!$cmd) { if (!$cmd) {
@ -114,12 +136,10 @@ class PluginController extends Base
} }
} }
Util::pauseFileMonitor();
try {
// 解压zip到plugin目录 // 解压zip到plugin目录
if ($has_zip_archive) { if ($has_zip_archive) {
$zip = new ZipArchive; $zip = new \ZipArchive;
$zip->open($zip_file, ZIPARCHIVE::CHECKCONS); $zip->open($zip_file, \ZIPARCHIVE::CHECKCONS);
} }
$context = null; $context = null;
@ -151,9 +171,6 @@ class PluginController extends Base
call_user_func([$install_class, 'install'], $version); call_user_func([$install_class, 'install'], $version);
} }
} }
} finally {
Util::resumeFileMonitor();
}
Util::reloadWebman(); Util::reloadWebman();
@ -189,17 +206,7 @@ class PluginController extends Base
// 删除目录 // 删除目录
clearstatcache(); clearstatcache();
if (is_dir($path)) { if (is_dir($path)) {
$monitor_support_pause = method_exists(Monitor::class, 'pause');
if ($monitor_support_pause) {
Monitor::pause();
}
try {
$this->rmDir($path); $this->rmDir($path);
} finally {
if ($monitor_support_pause) {
Monitor::resume();
}
}
} }
clearstatcache(); clearstatcache();
@ -208,27 +215,6 @@ class PluginController extends Base
return $this->json(0); 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 * @param Request $request
@ -240,7 +226,7 @@ class PluginController extends Base
$client = $this->httpClient(); $client = $this->httpClient();
$response = $client->get('/user/captcha?type=login'); $response = $client->get('/user/captcha?type=login');
$sid_str = $response->getHeaderLine('Set-Cookie'); $sid_str = $response->getHeaderLine('Set-Cookie');
if (preg_match('/PHPSID=([a-zA-Z_0-9]+?);/', $sid_str, $match)) { if(preg_match('/PHPSID=([a-zA-z_0-9]+?);/', $sid_str, $match)) {
$sid = $match[1]; $sid = $match[1];
session()->set('app-plugin-token', $sid); session()->set('app-plugin-token', $sid);
} }
@ -250,17 +236,15 @@ class PluginController extends Base
/** /**
* 登录官网 * 登录官网
* @param Request $request * @param Request $request
* @return Response|string * @return Response
* @throws GuzzleException * @throws GuzzleException
*/ */
public function login(Request $request) public function login(Request $request): Response
{ {
$client = $this->httpClient();
if ($request->method() === 'GET') { if ($request->method() === 'GET') {
$response = $client->get("/webman-admin/login"); return view('plugin/auth-login');
return (string)$response->getBody();
} }
$client = $this->httpClient();
$response = $client->post('/api/user/login', [ $response = $client->post('/api/user/login', [
'form_params' => [ 'form_params' => [
'email' => $request->post('username'), 'email' => $request->post('username'),
@ -288,27 +272,37 @@ class PluginController extends Base
/** /**
* 获取zip下载url * 获取zip下载url
* @param $name * @param $name
* @param $uid
* @param $host
* @param $version * @param $version
* @return mixed * @return mixed
* @throws BusinessException * @throws BusinessException
* @throws GuzzleException * @throws GuzzleException
*/ */
protected function getDownloadUrl($name, $version) protected function getDownloadUrl($name, $uid, $host, $version)
{ {
$client = $this->httpClient(); $client = $this->httpClient();
$response = $client->get("/app/download/$name?version=$version"); $response = $client->post('/api/app/download', [
'form_params' => [
'name' => $name,
'uid' => $uid,
'token' => session('app-plugin-token'),
'referer' => $host,
'version' => $version,
]
]);
$content = $response->getBody()->getContents(); $content = $response->getBody()->getContents();
$data = json_decode($content, true); $data = json_decode($content, true);
if (!$data) { if (!$data) {
$msg = "/api/app/download return $content"; $msg = "/api/app/download return $content";
Log::error($msg); Log::error($msg);
throw new BusinessException('访问官方接口失败 ' . $response->getStatusCode() . ' ' . $response->getReasonPhrase()); throw new BusinessException('访问官方接口失败');
} }
if ($data['code'] && $data['code'] != -1 && $data['code'] != -2) { if ($data['code'] && $data['code'] != -1) {
throw new BusinessException($data['message']); throw new BusinessException($data['msg']);
} }
if ($data['code'] == 0 && !isset($data['data']['url'])) { if ($data['code'] == 0 && !isset($data['result']['url'])) {
throw new BusinessException('官方接口返回数据错误'); throw new BusinessException('官方接口返回数据错误');
} }
return $data; return $data;
@ -347,10 +341,10 @@ class PluginController extends Base
protected function getUnzipCmd($zip_file, $extract_to) protected function getUnzipCmd($zip_file, $extract_to)
{ {
if ($cmd = $this->findCmd('unzip')) { if ($cmd = $this->findCmd('unzip')) {
$cmd = "$cmd -o -qq $zip_file -d $extract_to"; $cmd = "$cmd -qq $zip_file -d $extract_to";
} else if ($cmd = $this->findCmd('7z')) { } else if ($cmd = $this->findCmd('7z')) {
$cmd = "$cmd x -bb0 -y $zip_file -o$extract_to"; $cmd = "$cmd x -bb0 -y $zip_file -o$extract_to";
} else if ($cmd = $this->findCmd('7zz')) { } else if ($cmd= $this->findCmd('7zz')) {
$cmd = "$cmd x -bb0 -y $zip_file -o$extract_to"; $cmd = "$cmd x -bb0 -y $zip_file -o$extract_to";
} }
return $cmd; return $cmd;
@ -365,8 +359,8 @@ class PluginController extends Base
protected function unzipWithCmd($cmd) protected function unzipWithCmd($cmd)
{ {
$desc = [ $desc = [
0 => ["pipe", "r"], 0 => STDIN,
1 => ["pipe", "w"], 1 => STDOUT,
2 => ["pipe", "w"], 2 => ["pipe", "w"],
]; ];
$handler = proc_open($cmd, $desc, $pipes); $handler = proc_open($cmd, $desc, $pipes);
@ -381,34 +375,6 @@ class PluginController extends Base
} }
} }
/**
* 获取已安装的插件列表
* @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 * @param $name
@ -440,10 +406,10 @@ class PluginController extends Base
protected function rmDir($src) protected function rmDir($src)
{ {
$dir = opendir($src); $dir = opendir($src);
while (false !== ($file = readdir($dir))) { while(false !== ( $file = readdir($dir)) ) {
if (($file != '.') && ($file != '..')) { if (( $file != '.' ) && ( $file != '..' )) {
$full = $src . '/' . $file; $full = $src . '/' . $file;
if (is_dir($full)) { if ( is_dir($full) ) {
$this->rmDir($full); $this->rmDir($full);
} else { } else {
unlink($full); unlink($full);
@ -511,8 +477,8 @@ class PluginController extends Base
*/ */
protected function findCmd(string $name, string $default = null, array $extraDirs = []) protected function findCmd(string $name, string $default = null, array $extraDirs = [])
{ {
if (ini_get('open_basedir')) { if (\ini_get('open_basedir')) {
$searchPath = array_merge(explode(PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs); $searchPath = array_merge(explode(\PATH_SEPARATOR, \ini_get('open_basedir')), $extraDirs);
$dirs = []; $dirs = [];
foreach ($searchPath as $path) { foreach ($searchPath as $path) {
if (@is_dir($path)) { if (@is_dir($path)) {
@ -525,19 +491,19 @@ class PluginController extends Base
} }
} else { } else {
$dirs = array_merge( $dirs = array_merge(
explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
$extraDirs $extraDirs
); );
} }
$suffixes = ['']; $suffixes = [''];
if ('\\' === DIRECTORY_SEPARATOR) { if ('\\' === \DIRECTORY_SEPARATOR) {
$pathExt = getenv('PATHEXT'); $pathExt = getenv('PATHEXT');
$suffixes = array_merge($pathExt ? explode(PATH_SEPARATOR, $pathExt) : ['.exe', '.bat', '.cmd', '.com'], $suffixes); $suffixes = array_merge($pathExt ? explode(\PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes);
} }
foreach ($suffixes as $suffix) { foreach ($suffixes as $suffix) {
foreach ($dirs as $dir) { foreach ($dirs as $dir) {
if (@is_file($file = $dir . DIRECTORY_SEPARATOR . $name . $suffix) && ('\\' === DIRECTORY_SEPARATOR || @is_executable($file))) { if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) {
return $file; return $file;
} }
} }

View File

@ -171,11 +171,6 @@ class RoleController extends Crud
if (!Auth::isSupperAdmin() && array_diff($ids, Auth::getScopeRoleIds())) { if (!Auth::isSupperAdmin() && array_diff($ids, Auth::getScopeRoleIds())) {
return $this->json(1, '无删除权限'); 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); $this->doDelete($ids);
return $this->json(0); return $this->json(0);
} }

View File

@ -12,7 +12,6 @@ use plugin\admin\app\model\Option;
use support\exception\BusinessException; use support\exception\BusinessException;
use support\Request; use support\Request;
use support\Response; use support\Response;
use Throwable;
class TableController extends Base class TableController extends Base
{ {
@ -59,20 +58,16 @@ class TableController extends Base
*/ */
public function show(Request $request): Response public function show(Request $request): Response
{ {
$limit = (int)$request->get('limit', 10);
$page = (int)$request->get('page', 1);
$offset = ($page - 1) * $limit;
$database = config('database.connections')['plugin.admin.mysql']['database']; $database = config('database.connections')['plugin.admin.mysql']['database'];
$field = $request->get('field', 'TABLE_NAME'); $field = $request->get('field', 'TABLE_NAME');
$field = Util::filterAlphaNum($field); $field = Util::filterAlphaNum($field);
$order = $request->get('order', 'asc'); $order = $request->get('order', 'asc');
$allow_column = ['TABLE_NAME', 'TABLE_COMMENT', 'ENGINE', 'TABLE_ROWS', 'CREATE_TIME', 'UPDATE_TIME', 'TABLE_COLLATION']; $allow_column = ['TABLE_NAME','TABLE_COMMENT','ENGINE','TABLE_ROWS','CREATE_TIME','UPDATE_TIME','TABLE_COLLATION'];
if (!in_array($field, $allow_column)) { if (!in_array($field, $allow_column)) {
$field = 'TABLE_NAME'; $field = 'TABLE_NAME';
} }
$order = $order === 'asc' ? 'asc' : 'desc'; $order = $order === 'asc' ? 'asc' : 'desc';
$total = Util::db()->select("SELECT count(*)total FROM information_schema.`TABLES` WHERE TABLE_SCHEMA='$database'")[0]->total ?? 0; $tables = Util::db()->select("SELECT TABLE_NAME,TABLE_COMMENT,ENGINE,TABLE_ROWS,CREATE_TIME,UPDATE_TIME,TABLE_COLLATION FROM information_schema.`TABLES` WHERE TABLE_SCHEMA='$database' order by $field $order");
$tables = Util::db()->select("SELECT TABLE_NAME,TABLE_COMMENT,ENGINE,TABLE_ROWS,CREATE_TIME,UPDATE_TIME,TABLE_COLLATION FROM information_schema.`TABLES` WHERE TABLE_SCHEMA='$database' order by $field $order limit $offset,$limit");
if ($tables) { if ($tables) {
$table_names = array_column($tables, 'TABLE_NAME'); $table_names = array_column($tables, 'TABLE_NAME');
@ -85,7 +80,7 @@ class TableController extends Base
} }
} }
return json(['code' => 0, 'msg' => 'ok', 'count' => $total, 'data' => $tables]); return $this->json(0, 'ok', $tables);
} }
/** /**
@ -108,7 +103,6 @@ class TableController extends Base
$primary_key_count = 0; $primary_key_count = 0;
foreach ($columns as $index => $item) { foreach ($columns as $index => $item) {
$columns[$index]['field'] = trim($item['field']);
if (!$item['field']) { if (!$item['field']) {
unset($columns[$index]); unset($columns[$index]);
continue; continue;
@ -213,7 +207,6 @@ class TableController extends Base
$primary_key_count = $auto_increment_count = 0; $primary_key_count = $auto_increment_count = 0;
foreach ($columns as $index => $item) { foreach ($columns as $index => $item) {
$columns[$index]['field'] = trim($item['field']);
if (!$item['field']) { if (!$item['field']) {
unset($columns[$index]); unset($columns[$index]);
continue; continue;
@ -471,17 +464,15 @@ class TableController extends Base
$app = strtolower($explode[1]) !== 'controller' ? $explode[1] : ''; $app = strtolower($explode[1]) !== 'controller' ? $explode[1] : '';
} }
Util::pauseFileMonitor();
try {
$model_class = $model_file_name; $model_class = $model_file_name;
$model_namespace = str_replace('/', '\\', trim($model_path, '/')); $model_namespace = str_replace('/' , '\\', trim($model_path, '/'));
// 创建model // 创建model
$this->createModel($model_class, $model_namespace, base_path($model_file), $table_name); $this->createModel($model_class, $model_namespace, base_path($model_file), $table_name);
$controller_suffix = $plugin ? config("plugin.$plugin.app.controller_suffix") : config('app.controller_suffix'); $controller_suffix = $plugin ? config("plugin.$plugin.app.controller_suffix") : config('app.controller_suffix');
$controller_class = $controller_file_name; $controller_class = $controller_file_name;
$controller_namespace = str_replace('/', '\\', trim($controller_path, '/')); $controller_namespace = str_replace('/' , '\\', trim($controller_path, '/'));
// 创建controller // 创建controller
$controller_url_name = $controller_suffix && substr($controller_class, -strlen($controller_suffix)) === $controller_suffix ? substr($controller_class, 0, -strlen($controller_suffix)) : $controller_class; $controller_url_name = $controller_suffix && substr($controller_class, -strlen($controller_suffix)) === $controller_suffix ? substr($controller_class, 0, -strlen($controller_suffix)) : $controller_class;
$controller_url_name = str_replace('_', '-', $inflector->tableize($controller_url_name)); $controller_url_name = str_replace('_', '-', $inflector->tableize($controller_url_name));
@ -511,9 +502,6 @@ class TableController extends Base
$primary_key = (new $model_class_with_namespace)->getKeyName(); $primary_key = (new $model_class_with_namespace)->getKeyName();
$url_path_base = ($plugin ? "/app/$plugin/" : '/') . ($app ? "$app/" : '') . $template_path; $url_path_base = ($plugin ? "/app/$plugin/" : '/') . ($app ? "$app/" : '') . $template_path;
$this->createTemplate(base_path($template_file_path), $table_name, $url_path_base, $primary_key, "$controller_namespace\\$controller_class"); $this->createTemplate(base_path($template_file_path), $table_name, $url_path_base, $primary_key, "$controller_namespace\\$controller_class");
} finally {
Util::resumeFileMonitor();
}
$menu = Rule::where('key', $controller_class_with_namespace)->first(); $menu = Rule::where('key', $controller_class_with_namespace)->first();
if (!$menu) { if (!$menu) {
@ -563,33 +551,20 @@ class TableController extends Base
$pk = 'id'; $pk = 'id';
$properties = ''; $properties = '';
$timestamps = ''; $timestamps = '';
$incrementing = '';
$columns = []; $columns = [];
try { try {
$database = config('database.connections')['plugin.admin.mysql']['database']; $database = config('database.connections')['plugin.admin.mysql']['database'];
//plugin.admin.mysql //plugin.admin.mysql
foreach (Util::db()->select("select COLUMN_NAME,DATA_TYPE,COLUMN_KEY,COLUMN_COMMENT from INFORMATION_SCHEMA.COLUMNS where table_name = '$table' and table_schema = '$database' order by ORDINAL_POSITION") as $item) { foreach (Util::db()->select("select COLUMN_NAME,DATA_TYPE,COLUMN_KEY,COLUMN_COMMENT from INFORMATION_SCHEMA.COLUMNS where table_name = '$table' and table_schema = '$database'") as $item) {
if ($item->COLUMN_KEY === 'PRI') { if ($item->COLUMN_KEY === 'PRI') {
$pk = $item->COLUMN_NAME; $pk = $item->COLUMN_NAME;
$item->COLUMN_COMMENT .= "(主键)"; $item->COLUMN_COMMENT .= "(主键)";
if (strpos(strtolower($item->DATA_TYPE), 'int') === false) {
$incrementing = <<<EOF
/**
* Indicates if the model's ID is auto-incrementing.
*
* @var bool
*/
public \$incrementing = false;
EOF;
;
}
} }
$type = $this->getType($item->DATA_TYPE); $type = $this->getType($item->DATA_TYPE);
$properties .= " * @property $type \${$item->COLUMN_NAME} {$item->COLUMN_COMMENT}\n"; $properties .= " * @property $type \${$item->COLUMN_NAME} {$item->COLUMN_COMMENT}\n";
$columns[$item->COLUMN_NAME] = $item->COLUMN_NAME; $columns[$item->COLUMN_NAME] = $item->COLUMN_NAME;
} }
} catch (Throwable $e) {echo $e;} } catch (\Throwable $e) {echo $e;}
if (!isset($columns['created_at']) || !isset($columns['updated_at'])) { if (!isset($columns['created_at']) || !isset($columns['updated_at'])) {
$timestamps = <<<EOF $timestamps = <<<EOF
/** /**
@ -598,7 +573,6 @@ EOF;
* @var bool * @var bool
*/ */
public \$timestamps = false; public \$timestamps = false;
EOF; EOF;
} }
@ -628,8 +602,9 @@ class $class extends Base
* @var string * @var string
*/ */
protected \$primaryKey = '$pk'; protected \$primaryKey = '$pk';
$timestamps $timestamps
$incrementing
} }
@ -857,9 +832,6 @@ EOF
// 表格顶部搜索事件 // 表格顶部搜索事件
form.on("submit(table-query)", function(data) { form.on("submit(table-query)", function(data) {
table.reload("data-table", { table.reload("data-table", {
page: {
curr: 1
},
where: data.field where: data.field
}) })
return false; return false;
@ -872,16 +844,6 @@ EOF
}) })
}); });
// 字段允许为空
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.on("sort(data-table)", function(obj){
table.reload("data-table", { table.reload("data-table", {
@ -1020,15 +982,6 @@ EOF;
$js $js
//提交事件 //提交事件
layui.use(["form", "popup"], function () { 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.form.on("submit(save)", function (data) {
layui.$.ajax({ layui.$.ajax({
url: INSERT_API, url: INSERT_API,
@ -1139,15 +1092,6 @@ EOF;
//提交事件 //提交事件
layui.use(["form", "popup"], function () { 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.form.on("submit(save)", function (data) {
data.field[PRIMARY_KEY] = layui.url().search[PRIMARY_KEY]; data.field[PRIMARY_KEY] = layui.url().search[PRIMARY_KEY];
layui.$.ajax({ layui.$.ajax({
@ -1226,21 +1170,17 @@ EOF;
} }
if (isset($allow_column[$column])) { if (isset($allow_column[$column])) {
if (is_array($value)) { if (is_array($value)) {
if ($value[0] === 'like') { if (in_array($value[0], ['', 'undefined']) || in_array($value[1], ['', 'undefined'])) {
$paginator = $paginator->where($column, 'like', "%$value[1]%"); continue;
} elseif (in_array($value[0], ['>', '=', '<', '<>', 'not like'])) { }
$paginator = $paginator->where($column, $value[0], $value[1]);
} else {
if($value[0] !== '' || $value[1] !== '') {
$paginator = $paginator->whereBetween($column, $value); $paginator = $paginator->whereBetween($column, $value);
}
}
} else { } else {
$paginator = $paginator->where($column, $value); $paginator = $paginator->where($column, $value);
} }
} }
} }
$paginator = $paginator->orderBy($field, $order)->paginate($limit, '*', 'page', $page); $paginator = $paginator->orderBy($field, $order)->paginate($limit, '*', 'page', $page);
$items = $paginator->items(); $items = $paginator->items();
if ($format == 'tree') { if ($format == 'tree') {
$items_map = []; $items_map = [];

View File

@ -3,9 +3,11 @@
* Here is your custom functions. * Here is your custom functions.
*/ */
use plugin\admin\app\model\User; use app\model\User;
use plugin\admin\app\model\Admin; use plugin\admin\app\model\Admin;
use plugin\admin\app\model\AdminRole; use plugin\admin\app\model\AdminRole;
use plugin\admin\app\model\Role;
use plugin\admin\app\model\Rule;
/** /**
* 当前管理员id * 当前管理员id

View File

@ -37,8 +37,6 @@ class AccessControl implements MiddlewareInterface
EOF EOF
); );
} else { } else {
$request->app = '';
$request->plugin = 'admin';
$response = view('common/error/403')->withStatus(403); $response = view('common/error/403')->withStatus(403);
} }
} }

View File

@ -300,9 +300,6 @@
// 表格顶部搜索事件 // 表格顶部搜索事件
form.on("submit(table-query)", function(data) { form.on("submit(table-query)", function(data) {
table.reload("data-table", { table.reload("data-table", {
page: {
curr: 1
},
where: data.field where: data.field
}) })
return false; return false;

View File

@ -156,9 +156,6 @@
// 表格顶部搜索事件 // 表格顶部搜索事件
form.on("submit(table-query)", function(data) { form.on("submit(table-query)", function(data) {
table.reload("data-table", { table.reload("data-table", {
page: {
curr: 1
},
where: data.field where: data.field
}) })
return false; return false;

View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>登录 workerman.net 官网</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 style="background: #fff !important;">
<form class="layui-form" style="margin-top:36px !important;">
<div class="layui-form-item pear-border" style="background:#ebf2f8;border:1px solid #ccddf6;padding:10px;border-radius: 4px;">
<div class="pear-text">注意此处登录 <a class="pear-text" href="https://www.workerman.net" target="_blank"><b>workerman.net</b></a> 官网账号</div>
</div>
<div class="layui-form-item">
<input lay-verify="required" hover class="layui-input" type="text" name="username" value="" />
</div>
<div class="layui-form-item">
<input lay-verify="required" hover class="layui-input" type="password" name="password" value="" />
</div>
<div class="layui-form-item">
<input hover lay-verify="required" class="code layui-input layui-input-inline" name="captcha" />
<img class="codeImage" width="120px"/>
</div>
<div class="layui-form-item">
<button type="submit" class="pear-btn pear-btn-primary login" lay-submit lay-filter="login">
登录
</button>
</div>
</form>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
layui.use(["form", "button", "popup", "layer"], function() {
var $ = layui.$, layer = layui.layer, form = layui.form;
function switchCaptcha() {
$(".codeImage").attr("src", "/app/admin/plugin/captcha?v=" + new Date().getTime());
}
switchCaptcha();
form.on("submit(login)", function (data) {
layer.load();
$.ajax({
url: "/app/admin/plugin/login",
type: "POST",
data: data.field,
success: function (res) {
layer.closeAll("loading");
if (!res.code) {
layui.popup.success("登录成功", function () {
parent.layer.close(parent.layer.getFrameIndex(window.name));
})
} else {
layui.popup.failure(res.msg, function () {
switchCaptcha();
})
}
}
});
return false;
});
$(".codeImage").on("click", function () {
switchCaptcha();
});
})
</script>
</body>
</html>

View File

@ -0,0 +1,145 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>应用插件管理</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body class="pear-container">
<div class="layui-card">
<div class="layui-card-body">
<table id="data-table" lay-filter="data-table"></table>
</div>
</div>
<script type="text/html" id="install">
{{# if(!d.installed){ }}
<button class="pear-btn pear-btn-xs pear-btn-primary" onclick="install('{{ d.name }}','{{ d.version }}')">
安装
</button>
{{# }else{ }}
{{# if(d.installed !== d.version){ }}
<button class="pear-btn pear-btn-xs pear-btn-success" onclick="install('{{ d.name }}','{{ d.version }}')">
升级
</button>
{{# } }}
{{# if(d.name !== "admin"){ }}
<button class="pear-btn pear-btn-xs pear-btn-danger" onclick="uninstall('{{ d.name }}','{{ d.installed }}')">
卸载
</button>
{{# } }}
{{# } }}
</script>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
const SELECT_API = "/app/admin/plugin/list";
const AUTH_URL = "/app/admin/plugin/login";
const SCHEMA_API = "/app/admin/plugin/schema";
layui.use(["table", "form","common", "popup"], function() {
let table = layui.table;
let form = layui.form;
let $ = layui.$;
let common = layui.common;
$.ajax({
url: SCHEMA_API,
dataType: "json",
success: function (res) {
let cols = res.data;
layui.each(cols, function (k, v) {
if (v.field === "installed") {
cols[k].templet = "#install";
}
})
function render()
{
table.render({
elem: "#data-table",
url: SELECT_API,
page: true,
cols: [cols],
skin: "line",
size: "lg",
autoSort: false,
defaultToolbar: [{
title: "刷新",
layEvent: "refresh",
icon: "layui-icon-refresh",
}, "filter", "print", "exports"]
});
}
render();
}
});
window.install = function(name, version) {
let loading = layer.load();
$.ajax({
url: "/app/admin/plugin/install",
type: "POST",
dataType: "json",
data: {name, version},
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
// 需要登录官网
if (res.data && res.data.code === 401) {
layer.open({
type: 2,
title: "登录 workerman.net 官网",
shade: 0.1,
area: [common.isModile()?"100%":"500px", common.isModile()?"100%":"450px"],
content: AUTH_URL
});
return;
}
return layui.popup.success("安装成功", function () {
parent.location.reload();
});
},
complete: function () {
layer.close(loading);
}
});
}
window.uninstall = function(name, version) {
layer.confirm("确定卸载?", {
icon: 3,
title: "提示"
}, function(index) {
layer.close(index);
let loading = layer.load();
$.ajax({
url: "/app/admin/plugin/uninstall",
type: "POST",
dataType: "json",
data: {name, version},
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("卸载成功", function () {
parent.location.reload();
});
},
complete: function () {
layer.close(loading);
}
});
});
}
})
</script>
</body>
</html>

View File

@ -196,9 +196,6 @@
// 表格顶部搜索事件 // 表格顶部搜索事件
form.on("submit(table-query)", function(data) { form.on("submit(table-query)", function(data) {
table.reload("data-table", { table.reload("data-table", {
page: {
curr: 1
},
where: data.field where: data.field
}) })
return false; return false;

View File

@ -96,7 +96,7 @@
<script type="text/html" id="col-type"> <script type="text/html" id="col-type">
<select name="columns[{{ d.LAY_INDEX-1 }}][type]" lay-verify=""> <select name="columns[{{ d.LAY_INDEX-1 }}][type]" lay-verify="">
{{# layui.each(["integer","string","text","date","enum","float","tinyInteger","smallInteger","mediumInteger","bigInteger","unsignedInteger","unsignedTinyInteger","unsignedSmallInteger","unsignedMediumInteger","unsignedBigInteger","decimal","double","mediumText","longText","dateTime","time","timestamp","char","binary","json"], function (index, item) { }} {{# 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"], function (index, item) { }}
<option value="{{ item }}" {{ d.type==item?"selected":""}}>{{ item }}</option> <option value="{{ item }}" {{ d.type==item?"selected":""}}>{{ item }}</option>
{{# }); }} {{# }); }}
</select> </select>
@ -132,7 +132,7 @@
<script type="text/html" id="form-control"> <script type="text/html" id="form-control">
<select name="forms[{{ d.LAY_INDEX-1 }}][control]" lay-verify=""> <select name="forms[{{ d.LAY_INDEX-1 }}][control]" lay-verify="">
{{# layui.each([["input", "文本框"],["inputNumber", "数字文本框"],["textArea", "多行文本"],["richText", "富文本"],["select", "下拉单选"],["selectMulti", "下拉多选"],["treeSelect", "树形单选"],["treeSelectMulti", "树形多选"],["datePicker", "日期选择"],["dateTimePicker", "日期时间选择"],["switch", "开关"],["upload", "上传文件"],["uploadImage", "上传图片"],["iconPicker", "图标选择"]], function (index, item) { }} {{# layui.each([["input", "文本框"],["inputNumber", "数字文本框"],["textArea", "多行文本"],["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> <option value="{{ item[0] }}" {{ d.control.toLocaleLowerCase()==item[0].toLocaleLowerCase()?'selected':''}}>{{ item[1] }}</option>
{{# }); }} {{# }); }}
</select> </select>
@ -160,7 +160,7 @@
<script type="text/html" id="form-search_type"> <script type="text/html" id="form-search_type">
<select name="forms[{{ d.LAY_INDEX-1 }}][search_type]" lay-verify=""> <select name="forms[{{ d.LAY_INDEX-1 }}][search_type]" lay-verify="">
{{# layui.each([["normal", "普通查询"], ["between", "范围查询"], ["like", "模糊查询"]], function (index, item) { }} {{# layui.each([["normal", "普通查询"], ["between", "范围查询"]], function (index, item) { }}
<option value="{{ item[0] }}" {{ d.search_type==item[0]?'selected':''}}>{{ item[1] }}</option> <option value="{{ item[0] }}" {{ d.search_type==item[0]?'selected':''}}>{{ item[1] }}</option>
{{# }); }} {{# }); }}
</select> </select>

View File

@ -125,6 +125,7 @@
return layui.popup.failure(res.msg); return layui.popup.failure(res.msg);
} }
return layui.popup.success("操作成功", function () { return layui.popup.success("操作成功", function () {
parent.refreshTable();
parent.layer.close(parent.layer.getFrameIndex(window.name)); parent.layer.close(parent.layer.getFrameIndex(window.name));
}); });
} }

View File

@ -40,17 +40,6 @@
<?=$form->js(3)?> <?=$form->js(3)?>
layui.use(["form", "popup"], function () { 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.form.on("submit(save)", function (data) {
layui.$.ajax({ layui.$.ajax({

View File

@ -98,7 +98,7 @@
<script type="text/html" id="col-type"> <script type="text/html" id="col-type">
<select name="columns[{{ d.LAY_INDEX-1 }}][type]" lay-verify=""> <select name="columns[{{ d.LAY_INDEX-1 }}][type]" lay-verify="">
{{# layui.each(["integer","string","text","date","enum","float","tinyInteger","smallInteger","mediumInteger","bigInteger","unsignedInteger","unsignedTinyInteger","unsignedSmallInteger","unsignedMediumInteger","unsignedBigInteger","decimal","double","mediumText","longText","dateTime","time","timestamp","char","binary","json"], function (index, item) { }} {{# 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"], function (index, item) { }}
<option value="{{ item }}" {{ d.type==item?'selected':''}}>{{ item }}</option> <option value="{{ item }}" {{ d.type==item?'selected':''}}>{{ item }}</option>
{{# }); }} {{# }); }}
</select> </select>
@ -136,7 +136,7 @@
<script type="text/html" id="form-control"> <script type="text/html" id="form-control">
<select name="forms[{{ d.LAY_INDEX-1 }}][control]" lay-verify=""> <select name="forms[{{ d.LAY_INDEX-1 }}][control]" lay-verify="">
{{# layui.each([["input", "文本框"],["inputNumber", "数字文本框"],["textArea", "多行文本"],["richText", "富文本"],["select", "下拉单选"],["selectMulti", "下拉多选"],["treeSelect", "树形单选"],["treeSelectMulti", "树形多选"],["datePicker", "日期选择"],["dateTimePicker", "日期时间选择"],["switch", "开关"],["upload", "上传文件"],["uploadImage", "上传图片"],["iconPicker", "图标选择"]], function (index, item) { }} {{# layui.each([["input", "文本框"],["inputNumber", "数字文本框"],["textArea", "多行文本"],["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> <option value="{{ item[0] }}" {{ d.control.toLocaleLowerCase()==item[0].toLocaleLowerCase()?'selected':''}}>{{ item[1] }}</option>
{{# }); }} {{# }); }}
</select> </select>
@ -164,7 +164,7 @@
<script type="text/html" id="form-search_type"> <script type="text/html" id="form-search_type">
<select name="forms[{{ d.LAY_INDEX-1 }}][search_type]" lay-verify=""> <select name="forms[{{ d.LAY_INDEX-1 }}][search_type]" lay-verify="">
{{# layui.each([["normal", "普通查询"], ["between", "范围查询"], ["like", "模糊查询"]], function (index, item) { }} {{# layui.each([["normal", "普通查询"], ["between", "范围查询"]], function (index, item) { }}
<option value="{{ item[0] }}" {{ d.search_type==item[0]?'selected':''}}>{{ item[1] }}</option> <option value="{{ item[0] }}" {{ d.search_type==item[0]?'selected':''}}>{{ item[1] }}</option>
{{# }); }} {{# }); }}
</select> </select>

View File

@ -76,15 +76,6 @@
}); });
layui.use(["form", "popup"], function () { 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.form.on("submit(save)", function (data) {
layui.$.ajax({ layui.$.ajax({

View File

@ -237,16 +237,6 @@
} }
}); });
// 字段验证允许为空
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) { form.on("submit(table-query)", function(data) {
table.reload("data-table", { table.reload("data-table", {
where: data.field where: data.field

View File

@ -315,9 +315,6 @@
// 表格顶部搜索事件 // 表格顶部搜索事件
form.on("submit(table-query)", function(data) { form.on("submit(table-query)", function(data) {
table.reload("data-table", { table.reload("data-table", {
page: {
curr: 1
},
where: data.field where: data.field
}) })
return false; return false;

View File

@ -305,9 +305,6 @@
// 表格顶部搜索事件 // 表格顶部搜索事件
form.on("submit(table-query)", function(data) { form.on("submit(table-query)", function(data) {
table.reload("data-table", { table.reload("data-table", {
page: {
curr: 1
},
where: data.field where: data.field
}) })
return false; return false;

View File

@ -75,7 +75,7 @@
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-form-label">余额()</label> <label class="layui-form-label">余额()</label>
<div class="layui-input-block"> <div class="layui-input-block">
<input type="number" name="money" value="" class="layui-input"> <input type="number" name="money" value="" class="layui-input">
</div> </div>
@ -287,7 +287,7 @@
field: "birthday", field: "birthday",
hide: true, hide: true,
},{ },{
title: "余额()", title: "余额()",
field: "money", field: "money",
hide: true, hide: true,
},{ },{
@ -442,9 +442,6 @@
// 表格顶部搜索事件 // 表格顶部搜索事件
form.on("submit(table-query)", function(data) { form.on("submit(table-query)", function(data) {
table.reload("data-table", { table.reload("data-table", {
page: {
curr: 1
},
where: data.field where: data.field
}) })
return false; return false;

View File

@ -84,7 +84,7 @@
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-form-label">余额()</label> <label class="layui-form-label">余额()</label>
<div class="layui-input-block"> <div class="layui-input-block">
<input type="number" name="money" value="0" class="layui-input"> <input type="number" name="money" value="0" class="layui-input">
</div> </div>

View File

@ -84,7 +84,7 @@
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-form-label">余额()</label> <label class="layui-form-label">余额()</label>
<div class="layui-input-block"> <div class="layui-input-block">
<input type="number" name="money" value="" class="layui-input"> <input type="number" name="money" value="" class="layui-input">
</div> </div>

View File

@ -17,5 +17,5 @@ return [
'controller_suffix' => 'Controller', 'controller_suffix' => 'Controller',
'controller_reuse' => false, 'controller_reuse' => false,
'plugin_market_host' => 'https://www.workerman.net', 'plugin_market_host' => 'https://www.workerman.net',
'version' => '0.6.12' 'version' => '0.5.0'
]; ];

View File

@ -21,5 +21,5 @@ return [
// Fallback language // Fallback language
'fallback_locale' => ['zh_CN', 'en'], 'fallback_locale' => ['zh_CN', 'en'],
// Folder where language files are stored // Folder where language files are stored
'path' => base_path() . '/plugin/admin/resource/translations' 'path' => base_path() . '/resource/translations',
]; ];

File diff suppressed because one or more lines are too long

View File

@ -25,9 +25,9 @@ layui.define(['jquery'],function (exports) {
} }
, success: function (res, succFun, failFun) {//图片上传完成回调 根据自己需要修改 , success: function (res, succFun, failFun) {//图片上传完成回调 根据自己需要修改
if (res[this.response.statusName] == this.response.statusCode.ok) { if (res[this.response.statusName] == this.response.statusCode.ok) {
succFun(res[this.response.dataName]["url"]); succFun(res[this.response.dataName]);
} else { } else {
failFun(res[this.response.msgName]["url"]); failFun(res[this.response.msgName]);
} }
} }
}; };