Compare commits

...

97 Commits
0.5.2 ... main

Author SHA1 Message Date
7283737f8e 更新 'composer.json' 2023-05-16 16:15:58 +00:00
walkor
4b652aac8d 查询移除lay-verify 2023-05-16 16:02:07 +08:00
walkor
09d4686437 设置lay-verify也允许传空 2023-05-16 15:54:43 +08:00
walkor
425113a0d7 查询时允许字段为空 2023-05-16 12:02:53 +08:00
walkor
ca2af73a2c 修复主键为字符串时返回0的问题 2023-05-16 11:16:46 +08:00
walkor
5d3ce0ff12 主键不自增时主键显示在表单 2023-05-16 10:23:06 +08:00
walkor
3d39d38287 富文本图片上传 2023-05-10 14:54:37 +08:00
walkor
b5c8abcb56 富文本支持 2023-05-10 11:45:49 +08:00
walkor
b0712fac25 mysql support json 2023-05-10 10:25:46 +08:00
walkor
b0d73eaccd fix namespace 2023-05-10 08:50:56 +08:00
walkor
ea86e944cf 支持模糊搜索 2023-05-05 11:01:43 +08:00
walkor
4cba3c47d9
Update Util.php 2023-05-04 16:39:08 +08:00
walkor
af0167a44b
Update Util.php 2023-05-03 10:38:43 +08:00
walkor
92b1551284
Update InstallController.php 2023-04-02 20:08:26 +08:00
walkor
e5af2418b0
Merge pull request #32 from Chance-fyi/main
search query reset pagination
2023-04-01 08:44:59 +08:00
walkor
4ad326a9af
Fix proc_open error 2023-03-31 22:07:21 +08:00
Chance
2418b0caa3
feat: search query reset pagination 2023-03-31 21:01:42 +08:00
walkor
37034552e4
Merge pull request #31 from Chance-fyi/table_show_pagination
table show pagination #25
2023-03-31 20:32:00 +08:00
Chance
f1257d99b6
feat: table show pagination #25 2023-03-31 20:28:06 +08:00
walkor
095c5cd81c fix key length 2023-03-25 08:27:14 +08:00
walkor
1d3938a1dd
Update IndexController.php 2023-03-24 14:24:36 +08:00
walkor
727dc88fb1
Update translation.php 2023-03-23 09:43:55 +08:00
walkor
0d94b22820 Merge branch 'main' of github.com:webman-php/admin into main 2023-03-22 09:04:13 +08:00
walkor
929e5cb543 fix xmslelect layVerify 2023-03-22 09:03:59 +08:00
walkor
c8414d2e0b
Update Middleware.php 2023-03-10 21:40:36 +08:00
walkor
07ef5cf755
Update functions.php 2023-03-08 21:19:42 +08:00
walkor
af61b6a96b
Update TableController.php 2023-03-08 15:03:55 +08:00
walkor
dc8c32e678
Update InstallController.php 2023-03-07 12:42:33 +08:00
walkor
c1c9c881f7
Merge pull request #22 from qnnp-me/main
For 支持其它插件中显示403无权限页面
2023-03-04 20:12:47 +08:00
qnnp-me
47047b9c65 避免接入admin的时候无权限的情况下调用不到admin的403文件而直接报错的情况 2023-03-04 20:00:30 +08:00
qnnp
6b1b64a05d
Merge branch 'webman-php:main' into main 2023-03-04 19:51:43 +08:00
walkor
f128cb7e6d
角色和子角色一起删除
Fix #20
2023-03-04 19:36:08 +08:00
walkor
89680d3754
For pullRequest 19
For https://github.com/webman-php/admin/pull/19
2023-03-04 19:11:44 +08:00
qnnp-me
28ff4ea2d9 避免接入admin的时候无权限的情况下调用不到admin的403文件而直接报错的情况 2023-03-04 02:57:16 +08:00
walkor
b923963f44
Merge pull request #17 from logdd/patch-1
使用当前模型进行表结构查询
2023-02-27 08:50:49 +08:00
logdd
1a37474eba
使用当前模型进行表结构查询 2023-02-27 00:11:21 +08:00
walkor
374d2fe59c update 2023-02-23 12:45:14 +08:00
walkor
d8e5dcff50
Merge pull request #16 from Chance-fyi/main
select COLUMNS order by ORDINAL_POSITION
2023-02-22 12:01:36 +08:00
Chance
6e08b61cf5
feat: select COLUMNS order by ORDINAL_POSITION 2023-02-22 11:29:24 +08:00
walkor
da5cea5321 0.6.0 2023-02-20 21:03:11 +08:00
walkor
ef4834517c save 2023-02-20 20:46:02 +08:00
walkor
8292b848d2 2023-02-20 11:04:19 +08:00
walkor
ef3e693e48 save 2023-02-20 11:02:51 +08:00
walkor
0101e2437e save 2023-02-20 09:38:36 +08:00
walkor
8df8c531e5
Update composer.json 2023-02-01 16:15:34 +08:00
walkor
d5c2d9277c
Merge pull request #14 from getda/main
补充Crud查询条件
2023-02-01 09:41:50 +08:00
王小大
ba8d25a5ea 补充Crud查询条件 2023-01-31 23:22:19 +08:00
walkor
da879d3c68
$this->_debug => $this->debug 2023-01-28 15:09:38 +08:00
walkor
17bf447521
Update TableController.php 2023-01-28 13:11:12 +08:00
walkor
c72cfdec8f
Update TableController.php 2023-01-28 12:49:51 +08:00
walkor
3bb069cf65
Update TableController.php 2023-01-05 15:05:52 +08:00
walkor
a795555220
Update TableController.php 2023-01-05 14:40:22 +08:00
walkor
8dc51585e8 save 2022-12-26 15:53:10 +08:00
walkor
b5ca8b20b1 save 2022-12-26 15:22:32 +08:00
walkor
4e6cd56577 save 2022-12-26 14:39:18 +08:00
walkor
11370fbb7a save 2022-12-26 11:33:56 +08:00
walkor
5f5055283d save 2022-12-26 11:10:17 +08:00
walkor
58d754a90e save 2022-12-26 11:05:02 +08:00
walkor
b2fa0399d5 save 2022-12-26 10:47:17 +08:00
walkor
c9df58cacf ci 2022-12-26 09:52:06 +08:00
walkor
cfa1d1296b save 2022-12-25 16:47:01 +08:00
walkor
1c1b2fb2c2 save 2022-12-25 16:29:06 +08:00
walkor
da6b3cc18e save 2022-12-24 18:48:37 +08:00
walkor
5660e16e99 save 2022-12-24 18:07:01 +08:00
walkor
64d4433aaa save 2022-12-23 16:34:37 +08:00
walkor
93ceaa4cb1 save 2022-12-23 15:20:28 +08:00
walkor
a0e348a422 save 2022-12-23 15:00:06 +08:00
walkor
e5c648a57f save 2022-12-23 14:39:57 +08:00
walkor
df1f319e57 save 2022-12-23 10:23:30 +08:00
walkor
58c0370eb4 save 2022-12-23 09:44:30 +08:00
walkor
5c9188a7ca save 2022-12-23 09:34:00 +08:00
walkor
f299d1e521 save 2022-12-22 16:38:45 +08:00
walkor
8032d26fb9 save 2022-12-22 15:53:29 +08:00
walkor
db97119704 save 2022-12-22 14:41:07 +08:00
walkor
e9a06cdf67 save 2022-12-22 14:07:41 +08:00
walkor
3402a1a936 save 2022-12-21 22:26:19 +08:00
walkor
d3e82c54f0 save 2022-12-21 22:22:47 +08:00
walkor
83151a50fc save 2022-12-21 20:32:03 +08:00
walkor
5fb38aa579 save 2022-12-21 19:07:50 +08:00
walkor
5ec88341e3 save 2022-12-21 15:46:47 +08:00
walkor
073ab4afcc save 2022-12-21 15:10:21 +08:00
walkor
4a8f901600 save 2022-12-20 21:04:00 +08:00
walkor
317d7b4fc8 save 2022-12-20 21:03:48 +08:00
walkor
846be6eecc save 2022-12-20 16:48:39 +08:00
walkor
f9b4394d20 save 2022-12-20 16:32:35 +08:00
walkor
e40adb0247 save 2022-12-20 15:50:48 +08:00
walkor
5bf2eabd0f save 2022-12-20 15:38:47 +08:00
walkor
5576a94f59 save 2022-12-19 15:03:34 +08:00
walkor
ddbe85c6c0 save 2022-12-19 14:46:37 +08:00
walkor
500a8f26b0 save 2022-12-19 14:39:51 +08:00
walkor
35a9e84b71 save 2022-12-19 11:48:02 +08:00
walkor
30508c0a68 save 2022-12-13 20:36:55 +08:00
walkor
3ee71065fb save 2022-12-13 19:56:33 +08:00
walkor
fe6abf93f4 save 2022-12-13 19:17:03 +08:00
walkor
da2ff4f09d save 2022-12-13 15:18:32 +08:00
walkor
24fd3951fe save 2022-12-13 14:55:12 +08:00
walkor
3a9162cc17 save 2022-12-12 15:33:32 +08:00
63 changed files with 2048 additions and 1128 deletions

View File

@ -2,9 +2,9 @@
"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",
"illuminate/pagination": ">=7.30", "illuminate/pagination": ">=7.30",
"illuminate/events": ">=7.30", "illuminate/events": ">=7.30",

View File

@ -39,10 +39,9 @@ class Auth
*/ */
public static function canAccess(string $controller, string $action, int &$code = 0, string &$msg = ''): bool public static function canAccess(string $controller, string $action, int &$code = 0, string &$msg = ''): bool
{ {
// 无控制器信息说明是函数调用,函数不属于任何控制器,鉴权操作应该在函数内部完成。
if (!$controller) { if (!$controller) {
$msg = '无法识别当前控制器'; return true;
$code = 3;
return false;
} }
// 获取控制器鉴权信息 // 获取控制器鉴权信息
$class = new \ReflectionClass($controller); $class = new \ReflectionClass($controller);
@ -99,9 +98,9 @@ class Auth
// 如果action为index规则里有任意一个以$controller开头的权限即可 // 如果action为index规则里有任意一个以$controller开头的权限即可
if (strtolower($action) === 'index') { if (strtolower($action) === 'index') {
$controller = str_replace('\\', '\\\\', $controller);
$rule = Rule::where(function ($query) use ($controller, $action) { $rule = Rule::where(function ($query) use ($controller, $action) {
$query->where('key', 'like', "$controller@%")->orWhere('key', $controller); $controller_like = str_replace('\\', '\\\\', $controller);
$query->where('key', 'like', "$controller_like@%")->orWhere('key', $controller);
})->whereIn('id', $rule_ids)->first(); })->whereIn('id', $rule_ids)->first();
if ($rule) { if ($rule) {
return true; return true;

View File

@ -1,13 +1,14 @@
<?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
{ {
@ -16,7 +17,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
@ -30,7 +31,20 @@ 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 {
$response = \response($msg, 401); if ($code === 401) {
$response = response(<<<EOF
<script>
if (self !== top) {
parent.location.reload();
}
</script>
EOF
);
} else {
$request->app = '';
$request->plugin = 'admin';
$response = view('common/error/403')->withStatus(403);
}
} }
} else { } else {
$response = $request->method() == 'OPTIONS' ? response('') : $handler($request); $response = $request->method() == 'OPTIONS' ? response('') : $handler($request);

View File

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

View File

@ -1,6 +1,7 @@
<?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
@ -124,6 +125,31 @@ 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;
} }
@ -138,6 +164,17 @@ 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
@ -159,16 +196,69 @@ EOF;
[$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options); [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options);
$placeholder_string = !empty($props['placeholder']) ? ' placeholder="'.$props['placeholder'].'"' : ''; $placeholder_string = !empty($props['placeholder']) ? ' placeholder="'.$props['placeholder'].'"' : '';
$disabled_string = !empty($props['disabled']) ? ' disabled' : '';
$this->htmlContent .= <<<EOF $this->htmlContent .= <<<EOF
<div class="layui-form-item"> <div class="layui-form-item">
$label $label
<div class="$class"> <div class="$class">
<textarea name="$field"$required_string$verify_string$placeholder_string class="layui-textarea">$value</textarea> <textarea name="$field"$required_string$verify_string$placeholder_string$disabled_string class="layui-textarea">$value</textarea>
</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;
} }
@ -611,6 +701,9 @@ 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)) {
@ -629,19 +722,22 @@ EOF;
$this->jsContent .= <<<EOF $this->jsContent .= <<<EOF
// 字段 {$options['label']} $field // 字段 {$options['label']} $field
layui.use(["jquery", "xmSelect"], function() { layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({ layui.$.ajax({
url: "$url", url: "$url",
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
let value = layui.$("#$id").attr("value"); let value = layui.$("#$id").attr("value");
let initValue = value ? value.split(",") : []; let initValue = value ? value.split(",") : [];
layui.xmSelect.render({ layui.xmSelect.render({
el: "#$id", el: "#$id",
name: "$field", name: "$field",
initValue: initValue, initValue: initValue,
data: e.data, $options_string data: res.data, $options_string
}) });
if (res.code) {
layui.popup.failure(res.msg);
}
} }
}); });
}); });
@ -669,7 +765,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"$verify_string$required_string value="$default_value_string" ></div> <div name="$field" id="$id"$required_string value="$default_value_string" ></div>
</div> </div>
</div> </div>
@ -684,7 +780,7 @@ EOF;
* @return Layui * @return Layui
* @throws BusinessException * @throws BusinessException
*/ */
static public function buildForm($table, string $type = 'insert'): Layui public static function buildForm($table, string $type = 'insert'): Layui
{ {
if (!in_array($type, ['insert', 'update', 'search'])) { if (!in_array($type, ['insert', 'update', 'search'])) {
$type = 'insert'; $type = 'insert';
@ -703,6 +799,7 @@ 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';
@ -711,20 +808,34 @@ EOF;
$props = Util::getControlProps($control, $info['control_args']); $props = Util::getControlProps($control, $info['control_args']);
// 增加修改记录验证必填项 // 增加修改记录验证必填项
if ($filter == 'form_show' && !isset($props['lay-verify']) && !$columns[$key]['nullable'] && $default === null && ($field !== 'password' || $type === 'insert')) { if ($filter == 'form_show' && !$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 ($filter == 'form_show' && $primary_key && $field == $primary_key) { if ($primary_key && $field == $primary_key && (($type == 'insert' && $auto_increment) || $type == 'update')) {
continue; continue;
} }
// 范围查询 // 查询类型
if ($type == 'search' && $info['search_type'] == 'between' && method_exists($form, "{$control}Range")) { if ($type == 'search') {
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 = [
@ -745,7 +856,7 @@ EOF;
* @return array|string|string[] * @return array|string|string[]
* @throws BusinessException * @throws BusinessException
*/ */
static public function buildTable($table, int $indent = 0) public static function buildTable($table, int $indent = 0)
{ {
$schema = Util::getSchema($table); $schema = Util::getSchema($table);
$forms = $schema['forms']; $forms = $schema['forms'];
@ -783,7 +894,10 @@ EOF;
$.post(UPDATE_API, postData, function (res) { $.post(UPDATE_API, postData, function (res) {
layer.close(load); layer.close(load);
if (res.code) { if (res.code) {
return layui.popup.failure(res.msg); return layui.popup.failure(res.msg, function () {
data.elem.checked = !data.elem.checked;
form.render();
});
} }
return layui.popup.success("操作成功"); return layui.popup.success("操作成功");
}) })
@ -930,6 +1044,9 @@ layui.each(apis, function (k, item) {
url: url, url: url,
dateType: "json", dateType: "json",
success: function (res) { success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
function travel(items) { function travel(items) {
for (let k in items) { for (let k in items) {
let item = items[k]; let item = items[k];

View File

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

View File

@ -2,23 +2,25 @@
namespace plugin\admin\app\common; namespace plugin\admin\app\common;
use process\Monitor;
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\Db;
use support\exception\BusinessException; use support\exception\BusinessException;
use Throwable; use support\Db;
use function config; use Workerman\Timer;
use Workerman\Worker;
class Util class Util
{ {
/** /**
* 密码哈希 * 密码哈希
* @param $password * @param $password
* @param $algo * @param string $algo
* @return false|string|null * @return false|string|null
*/ */
static public function passwordHash($password, $algo = PASSWORD_DEFAULT) public static function passwordHash($password, string $algo = PASSWORD_DEFAULT)
{ {
return password_hash($password, $algo); return password_hash($password, $algo);
} }
@ -29,7 +31,7 @@ class Util
* @param $hash * @param $hash
* @return bool * @return bool
*/ */
static public function passwordVerify($password, $hash): bool public static function passwordVerify(string $password, string $hash): bool
{ {
return password_verify($password, $hash); return password_verify($password, $hash);
} }
@ -38,7 +40,7 @@ class Util
* 获取webman-admin数据库连接 * 获取webman-admin数据库连接
* @return Connection * @return Connection
*/ */
static function db(): Connection public static function db(): Connection
{ {
return Db::connection('plugin.admin.mysql'); return Db::connection('plugin.admin.mysql');
} }
@ -47,17 +49,65 @@ class Util
* 获取SchemaBuilder * 获取SchemaBuilder
* @return Builder * @return Builder
*/ */
static function schema(): Builder public static function schema(): Builder
{ {
return Db::schema('plugin.admin.mysql'); 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 * @param $var
* @return false|string * @return false|string
*/ */
static public function pdoQuote($var) public static function pdoQuote($var)
{ {
return Util::db()->getPdo()->quote($var, \PDO::PARAM_STR); return Util::db()->getPdo()->quote($var, \PDO::PARAM_STR);
} }
@ -68,7 +118,7 @@ class Util
* @return string * @return string
* @throws BusinessException * @throws BusinessException
*/ */
static public function checkTableName(string $table): string public static function checkTableName(string $table): string
{ {
if (!preg_match('/^[a-zA-Z_0-9]+$/', $table)) { if (!preg_match('/^[a-zA-Z_0-9]+$/', $table)) {
throw new BusinessException('表名不合法'); throw new BusinessException('表名不合法');
@ -82,7 +132,7 @@ class Util
* @return mixed * @return mixed
* @throws BusinessException * @throws BusinessException
*/ */
static public function filterAlphaNum($var) public static function filterAlphaNum($var)
{ {
$vars = (array)$var; $vars = (array)$var;
array_walk_recursive($vars, function ($item) { array_walk_recursive($vars, function ($item) {
@ -99,7 +149,7 @@ class Util
* @return mixed * @return mixed
* @throws BusinessException * @throws BusinessException
*/ */
static public function filterNum($var) public static function filterNum($var)
{ {
$vars = (array)$var; $vars = (array)$var;
array_walk_recursive($vars, function ($item) { array_walk_recursive($vars, function ($item) {
@ -116,7 +166,7 @@ class Util
* @return string * @return string
* @throws BusinessException * @throws BusinessException
*/ */
static public function filterUrlPath($var): string public static function filterUrlPath($var): string
{ {
if (!is_string($var) || !preg_match('/^[a-zA-Z0-9_\-\/&?.]+$/', $var)) { if (!is_string($var) || !preg_match('/^[a-zA-Z0-9_\-\/&?.]+$/', $var)) {
throw new BusinessException('参数不合法'); throw new BusinessException('参数不合法');
@ -130,7 +180,7 @@ class Util
* @return string * @return string
* @throws BusinessException * @throws BusinessException
*/ */
static public function filterPath($var): string public static function filterPath($var): string
{ {
if (!is_string($var) || !preg_match('/^[a-zA-Z0-9_\-\/]+$/', $var)) { if (!is_string($var) || !preg_match('/^[a-zA-Z0-9_\-\/]+$/', $var)) {
throw new BusinessException('参数不合法'); throw new BusinessException('参数不合法');
@ -183,7 +233,7 @@ class Util
* @param string $value * @param string $value
* @return string * @return string
*/ */
static public function camel(string $value): string public static function camel(string $value): string
{ {
static $cache = []; static $cache = [];
$key = $value; $key = $value;
@ -202,7 +252,7 @@ class Util
* @param $value * @param $value
* @return string * @return string
*/ */
static public function smCamel($value): string public static function smCamel($value): string
{ {
return lcfirst(static::camel($value)); return lcfirst(static::camel($value));
} }
@ -212,7 +262,7 @@ class Util
* @param $comment * @param $comment
* @return false|mixed|string * @return false|mixed|string
*/ */
static public function getCommentFirstLine($comment) public static function getCommentFirstLine($comment)
{ {
if ($comment === false) { if ($comment === false) {
return false; return false;
@ -229,7 +279,7 @@ class Util
* 表单类型到插件的映射 * 表单类型到插件的映射
* @return \string[][] * @return \string[][]
*/ */
static public function methodControlMap(): array public static function methodControlMap(): array
{ {
return [ return [
//method=>[控件] //method=>[控件]
@ -265,6 +315,8 @@ class Util
'char' => ['Input'], 'char' => ['Input'],
'binary' => ['Input'], 'binary' => ['Input'],
'json' => ['input']
]; ];
} }
@ -273,7 +325,7 @@ class Util
* @param $type * @param $type
* @return string * @return string
*/ */
static public function typeToControl($type): string public static function typeToControl($type): string
{ {
if (stripos($type, 'int') !== false) { if (stripos($type, 'int') !== false) {
return 'inputNumber'; return 'inputNumber';
@ -296,7 +348,7 @@ class Util
* @param $unsigned * @param $unsigned
* @return string * @return string
*/ */
static public function typeToMethod($type, $unsigned = false) public static function typeToMethod($type, $unsigned = false)
{ {
if (stripos($type, 'int') !== false) { if (stripos($type, 'int') !== false) {
$type = str_replace('int', 'Integer', $type); $type = str_replace('int', 'Integer', $type);
@ -319,11 +371,11 @@ class Util
* @return array|mixed * @return array|mixed
* @throws BusinessException * @throws BusinessException
*/ */
static public function getSchema($table, $section = null) public static function getSchema($table, $section = null)
{ {
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'") : []; $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 = []; $forms = [];
$columns = []; $columns = [];
foreach ($schema_raw as $item) { foreach ($schema_raw as $item) {
@ -395,7 +447,7 @@ class Util
* @param $schema * @param $schema
* @return mixed|string * @return mixed|string
*/ */
static public function getLengthValue($schema) public static function getLengthValue($schema)
{ {
$type = $schema->DATA_TYPE; $type = $schema->DATA_TYPE;
if (in_array($type, ['float', 'decimal', 'double'])) { if (in_array($type, ['float', 'decimal', 'double'])) {
@ -421,7 +473,7 @@ class Util
* @param $control_args * @param $control_args
* @return array * @return array
*/ */
static public function getControlProps($control, $control_args): array public static function getControlProps($control, $control_args): array
{ {
if (!$control_args) { if (!$control_args) {
return []; return [];
@ -461,7 +513,7 @@ class Util
* @param string $package * @param string $package
* @return mixed|string * @return mixed|string
*/ */
static public function getPackageVersion(string $package) public static function getPackageVersion(string $package)
{ {
$installed_php = base_path('vendor/composer/installed.php'); $installed_php = base_path('vendor/composer/installed.php');
if (is_file($installed_php)) { if (is_file($installed_php)) {
@ -472,18 +524,44 @@ class Util
/** /**
* reload webman (不支持windows) * Reload webman
* @return bool * @return bool
*/ */
static public function reloadWebman() public static function reloadWebman()
{ {
if (function_exists('posix_kill')) { if (function_exists('posix_kill')) {
try { try {
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

@ -2,6 +2,7 @@
namespace plugin\admin\app\controller; namespace plugin\admin\app\controller;
use plugin\admin\app\common\Auth;
use plugin\admin\app\common\Util; use plugin\admin\app\common\Util;
use plugin\admin\app\model\Admin; use plugin\admin\app\model\Admin;
use support\exception\BusinessException; use support\exception\BusinessException;
@ -72,11 +73,15 @@ class AccountController extends Crud
if (!$admin || !Util::passwordVerify($password, $admin->password)) { if (!$admin || !Util::passwordVerify($password, $admin->password)) {
return $this->json(1, '账户不存在或密码错误'); 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); $this->removeLoginLimit($username);
$admin = $admin->toArray(); $admin = $admin->toArray();
$session = $request->session(); $session = $request->session();
unset($admin['password']); unset($admin['password']);
$admin['roles'] = $admin['roles'] ? explode(',', $admin['roles']) : [];
$session->set('admin', $admin); $session->set('admin', $admin);
return $this->json(0, '登录成功', [ return $this->json(0, '登录成功', [
'nickname' => $admin['nickname'], 'nickname' => $admin['nickname'],
@ -107,15 +112,14 @@ class AccountController extends Crud
return $this->json(1); return $this->json(1);
} }
$info = [ $info = [
'nickname' => $admin['nickname'], 'id' => $admin['id'],
'desc' => 'manager',
'avatar' => $admin['avatar'],
'token' => $request->sessionId(),
'userId' => $admin['id'],
'username' => $admin['username'], 'username' => $admin['username'],
'nickname' => $admin['nickname'],
'avatar' => $admin['avatar'],
'email' => $admin['email'], 'email' => $admin['email'],
'mobile' => $admin['mobile'], 'mobile' => $admin['mobile'],
'roles' => [] 'isSupperAdmin' => Auth::isSupperAdmin(),
'token' => $request->sessionId(),
]; ];
return $this->json(0, 'ok', $info); return $this->json(0, 'ok', $info);
} }

View File

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

View File

@ -4,6 +4,8 @@ namespace plugin\admin\app\controller;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder as QueryBuilder; 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 plugin\admin\app\common\Util;
use support\exception\BusinessException; use support\exception\BusinessException;
use support\Model; use support\Model;
@ -61,6 +63,7 @@ class Crud extends Base
* 删除 * 删除
* @param Request $request * @param Request $request
* @return Response * @return Response
* @throws BusinessException
*/ */
public function delete(Request $request): Response public function delete(Request $request): Response
{ {
@ -80,12 +83,13 @@ class Crud extends Base
$field = $request->get('field'); $field = $request->get('field');
$order = $request->get('order', 'asc'); $order = $request->get('order', 'asc');
$format = $request->get('format', 'normal'); $format = $request->get('format', 'normal');
$limit = $request->get('limit', $format === 'tree' ? 1000 : 10); $limit = (int)$request->get('limit', $format === 'tree' ? 1000 : 10);
$limit = $limit <= 0 ? 10 : $limit;
$order = $order === 'asc' ? 'asc' : 'desc'; $order = $order === 'asc' ? 'asc' : 'desc';
$where = $request->get(); $where = $request->get();
$page = (int)$request->get('page'); $page = (int)$request->get('page');
$page = $page > 0 ? $page : 1; $page = $page > 0 ? $page : 1;
$table = $this->model->getTable(); $table = config('plugin.admin.database.connections.mysql.prefix') . $this->model->getTable();
$allow_column = Util::db()->select("desc `$table`"); $allow_column = Util::db()->select("desc `$table`");
if (!$allow_column) { if (!$allow_column) {
@ -104,8 +108,12 @@ class Crud extends Base
// 按照数据限制字段返回数据 // 按照数据限制字段返回数据
if ($this->dataLimit === 'personal') { if ($this->dataLimit === 'personal') {
$where[$this->dataLimitField] = admin_id(); $where[$this->dataLimitField] = admin_id();
} elseif ($this->dataLimit === 'auth') {
$primary_key = $this->model->getKeyName();
if (!Auth::isSupperAdmin() && (!isset($where[$primary_key]) || $this->dataLimitField != $primary_key)) {
$where[$this->dataLimitField] = ['in', Auth::getScopeAdminIds(true)];
}
} }
return [$where, $format, $limit, $field, $order, $page]; return [$where, $format, $limit, $field, $order, $page];
} }
@ -121,11 +129,19 @@ 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 (in_array($value[0], ['>', '=', '<', '<>', 'like'])) { if ($value[0] === 'like') {
$model = $model->where($column, 'like', "%$value[1]%");
} elseif (in_array($value[0], ['>', '=', '<', '<>', 'not like'])) {
$model = $model->where($column, $value[0], $value[1]); $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]);
} else { } elseif ($value[0] == 'not in') {
$model = $model->whereNotIn($column, $value[1]);
} elseif ($value[0] == 'null') {
$model = $model->whereNull($column, $value[1]);
} elseif ($value[0] == 'not null') {
$model = $model->whereNotNull($column, $value[1]);
} elseif ($value[0] !== '' || $value[1] !== '') {
$model = $model->whereBetween($column, $value); $model = $model->whereBetween($column, $value);
} }
} else { } else {
@ -139,6 +155,7 @@ class Crud extends Base
} }
/** /**
* 格式化数据
* @param $query * @param $query
* @param $format * @param $format
* @param $limit * @param $limit
@ -146,17 +163,17 @@ class Crud extends Base
*/ */
protected function doFormat($query, $format, $limit): Response protected function doFormat($query, $format, $limit): Response
{ {
if (in_array($format, ['select', 'tree', 'table_tree'])) { $methods = [
$items = $query->get(); 'select' => 'formatSelect',
if ($format == 'select') { 'tree' => 'formatTree',
return $this->formatSelect($items); 'table_tree' => 'formatTableTree',
} elseif ($format == 'tree') { 'normal' => 'formatNormal',
return $this->formatTree($items); ];
}
return $this->formatTableTree($items);
}
$paginator = $query->paginate($limit); $paginator = $query->paginate($limit);
return json(['code' => 0, 'msg' => 'ok', 'count' => $paginator->total(), 'data' => $paginator->items()]); $total = $paginator->total();
$items = $paginator->items();
$format_function = $methods[$format] ?? 'formatNormal';
return call_user_func([$this, $format_function], $items, $total);
} }
/** /**
@ -172,6 +189,15 @@ class Crud extends Base
if (isset($data[$password_filed])) { if (isset($data[$password_filed])) {
$data[$password_filed] = Util::passwordHash($data[$password_filed]); $data[$password_filed] = Util::passwordHash($data[$password_filed]);
} }
if (!Auth::isSupperAdmin() && $this->dataLimit) {
if (!empty($data[$this->dataLimitField])) {
$admin_id = $data[$this->dataLimitField];
if (!in_array($admin_id, Auth::getScopeAdminIds(true))) {
throw new BusinessException('无数据权限');
}
}
}
return $data; return $data;
} }
@ -203,6 +229,12 @@ class Crud extends Base
$primary_key = $this->model->getKeyName(); $primary_key = $this->model->getKeyName();
$id = $request->post($primary_key); $id = $request->post($primary_key);
$data = $this->inputFilter($request->post()); $data = $this->inputFilter($request->post());
if (!Auth::isSupperAdmin() && $this->dataLimit && !empty($data[$this->dataLimitField])) {
$admin_id = $data[$this->dataLimitField];
if (!in_array($admin_id, Auth::getScopeAdminIds(true))) {
throw new BusinessException('无数据权限');
}
}
$password_filed = 'password'; $password_filed = 'password';
if (isset($data[$password_filed])) { if (isset($data[$password_filed])) {
// 密码为空,则不更新密码 // 密码为空,则不更新密码
@ -243,8 +275,8 @@ class Crud extends Base
*/ */
protected function inputFilter(array $data): array protected function inputFilter(array $data): array
{ {
$table = $this->model->getTable(); $table = config('plugin.admin.database.connections.mysql.prefix') . $this->model->getTable();
$allow_column = Util::db()->select("desc `$table`"); $allow_column = $this->model->getConnection()->select("desc `$table`");
if (!$allow_column) { if (!$allow_column) {
throw new BusinessException('表不存在', 2); throw new BusinessException('表不存在', 2);
} }
@ -275,11 +307,22 @@ class Crud extends Base
* 删除前置方法 * 删除前置方法
* @param Request $request * @param Request $request
* @return array * @return array
* @throws BusinessException
*/ */
protected function deleteInput(Request $request): array protected function deleteInput(Request $request): array
{ {
$primary_key = $this->model->getKeyName(); $primary_key = $this->model->getKeyName();
return (array)$request->post($primary_key, []); if (!$primary_key) {
throw new BusinessException('该表无主键,不支持删除');
}
$ids = (array)$request->post($primary_key, []);
if (!Auth::isSupperAdmin() && $this->dataLimit) {
$admin_ids = $this->model->where($primary_key, $ids)->pluck($this->dataLimitField)->toArray();
if (array_diff($admin_ids, Auth::getScopeAdminIds(true))) {
throw new BusinessException('无数据权限');
}
}
return $ids;
} }
/** /**
@ -303,26 +346,17 @@ class Crud extends Base
*/ */
protected function formatTree($items): Response protected function formatTree($items): Response
{ {
$items_map = []; $format_items = [];
foreach ($items as $item) { foreach ($items as $item) {
$items_map[$item->id] = [ $format_items[] = [
'name' => $item->title ?? $item->name ?? $item->id, 'name' => $item->title ?? $item->name ?? $item->id,
'value' => (string)$item->id, 'value' => (string)$item->id,
'id' => $item->id,
'pid' => $item->pid, 'pid' => $item->pid,
]; ];
} }
$formatted_items = []; $tree = new Tree($format_items);
foreach ($items_map as $index => $item) { return $this->json(0, 'ok', $tree->getTree());
if ($item['pid'] && isset($items_map[$item['pid']])) {
$items_map[$item['pid']]['children'][] = &$items_map[$index];
}
}
foreach ($items_map as $item) {
if (!$item['pid']) {
$formatted_items[] = $item;
}
}
return $this->json(0, 'ok', $formatted_items);
} }
/** /**
@ -332,22 +366,8 @@ class Crud extends Base
*/ */
protected function formatTableTree($items): Response protected function formatTableTree($items): Response
{ {
$items_map = []; $tree = new Tree($items);
foreach ($items as $item) { return $this->json(0, 'ok', $tree->getTree());
$items_map[$item->id] = $item->toArray();
}
$formatted_items = [];
foreach ($items_map as $index => $item) {
if ($item['pid'] && isset($items_map[$item['pid']])) {
$items_map[$item['pid']]['children'][] = &$items_map[$index];
}
}
foreach ($items_map as $item) {
if (!$item['pid']) {
$formatted_items[] = $item;
}
}
return $this->json(0, 'ok', $formatted_items);
} }
/** /**
@ -367,4 +387,15 @@ class Crud extends Base
return $this->json(0, 'ok', $formatted_items); return $this->json(0, 'ok', $formatted_items);
} }
/**
* 通用格式化
* @param $items
* @param $total
* @return Response
*/
protected function formatNormal($items, $total): Response
{
return json(['code' => 0, 'msg' => 'ok', 'count' => $total, 'data' => $items]);
}
} }

View File

@ -2,6 +2,7 @@
namespace plugin\admin\app\controller; namespace plugin\admin\app\controller;
use plugin\admin\app\model\Dict;
use plugin\admin\app\model\Option; use plugin\admin\app\model\Option;
use support\exception\BusinessException; use support\exception\BusinessException;
use support\Request; use support\Request;
@ -26,6 +27,25 @@ class DictController extends Base
return view('dict/index'); return view('dict/index');
} }
/**
* 查询
* @param Request $request
* @return Response
*/
public function select(Request $request): Response
{
$name = $request->get('name', '');
if ($name && is_string($name)) {
$items = Option::where('name', 'like', "dict_$name%")->get()->toArray();
} else {
$items = Option::where('name', 'like', 'dict_%')->get()->toArray();
}
foreach ($items as &$item) {
$item['name'] = Dict::optionNameTodictName($item['name']);
}
return $this->json(0, 'ok', $items);
}
/** /**
* 插入 * 插入
* @param Request $request * @param Request $request
@ -36,20 +56,11 @@ class DictController extends Base
{ {
if ($request->method() === 'POST') { if ($request->method() === 'POST') {
$name = $request->post('name'); $name = $request->post('name');
if (!preg_match('/[a-zA-Z]/', $name)) { if (Dict::get($name)) {
return $this->json(1, '字典名只能包含字母'); return $this->json(1, '字典已经存在');
}
$option_name = $this->dictNameToOptionName($name);
if (Option::where('name', $option_name)->first()) {
return $this->json(1, '字典已经存在' . $option_name);
} }
$values = (array)$request->post('value', []); $values = (array)$request->post('value', []);
$format_values = $this->filterValue($values); Dict::save($name, $values);
$option = new Option;
$option->name = $option_name;
$option->value = json_encode($format_values, JSON_UNESCAPED_UNICODE);
$option->save();
return $this->json(0);
} }
return view('dict/insert'); return view('dict/insert');
} }
@ -64,55 +75,26 @@ class DictController extends Base
{ {
if ($request->method() === 'POST') { if ($request->method() === 'POST') {
$name = $request->post('name'); $name = $request->post('name');
if (!preg_match('/[a-zA-Z]/', $name)) { if (!Dict::get($name)) {
return $this->json(1, '字典名只能包含字母');
}
$name = $this->dictNameToOptionName($name);
$option = Option::where('name', $name)->first();
if (!$option) {
return $this->json(1, '字典不存在'); return $this->json(1, '字典不存在');
} }
$format_values = $this->filterValue($request->post('value')); Dict::save($name, $request->post('value'));
$option->name = $this->dictNameToOptionName($request->post('name'));
$option->value = json_encode($format_values, JSON_UNESCAPED_UNICODE);
$option->save();
} }
return view('dict/update'); return view('dict/update');
} }
/** /**
* 删除
* @param Request $request * @param Request $request
* @return Response * @return Response
*/ */
public function delete(Request $request) public function delete(Request $request): Response
{ {
$names = (array)$request->post('name'); $names = (array)$request->post('name');
foreach ($names as $index => $name) { Dict::delete($names);
$names[$index] = $this->dictNameToOptionName($name);
}
Option::whereIn('name', $names)->delete();
return $this->json(0); return $this->json(0);
} }
/**
* 查询
* @param Request $request
* @return Response
*/
public function select(Request $request): Response
{
$name = $request->get('name', '');
if ($name && is_string($name)) {
$items = Option::where('name', 'like', "dict_$name%")->get()->toArray();
} else {
$items = Option::where('name', 'like', 'dict_%')->get()->toArray();
}
foreach ($items as &$item) {
$item['name'] = $this->optionNameTodictName($item['name']);
}
return $this->json(0, 'ok', $items);
}
/** /**
* 获取 * 获取
* @param Request $request * @param Request $request
@ -121,47 +103,7 @@ class DictController extends Base
*/ */
public function get(Request $request, $name): Response public function get(Request $request, $name): Response
{ {
$value = Option::where('name', $this->dictNameToOptionName($name))->value('value'); return $this->json(0, 'ok', Dict::get($name));
if ($value === null) {
return $this->json(1, '字典不存在');
}
return $this->json(1, 'ok', json_decode($value, true));
}
/**
* 过滤字典选项
* @param array $values
* @return array
* @throws BusinessException
*/
protected 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;
}
/**
* @param string $name
* @return string
*/
protected function dictNameToOptionName(string $name): string
{
return "dict_$name";
}
/**
* @param string $name
* @return string
*/
protected function optionNameTodictName(string $name): string
{
return substr($name, 5);
} }
} }

View File

@ -4,7 +4,6 @@ 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;
@ -61,7 +60,7 @@ class IndexController
// 总用户数 // 总用户数
$user_count = User::count(); $user_count = User::count();
// mysql版本 // mysql版本
$version = Db::select('select VERSION() as version'); $version = Util::db()->select('select VERSION() as version');
$mysql_version = $version[0]->version ?? 'unknown'; $mysql_version = $version[0]->version ?? 'unknown';
$day7_detail = []; $day7_detail = [];

View File

@ -70,6 +70,7 @@ class InstallController extends Base
$tables_to_install = [ $tables_to_install = [
'wa_admins', 'wa_admins',
'wa_admin_roles',
'wa_roles', 'wa_roles',
'wa_rules', 'wa_rules',
'wa_options', 'wa_options',
@ -77,18 +78,22 @@ class InstallController extends Base
'wa_uploads', 'wa_uploads',
]; ];
if (!$overwrite) {
$tables_exist = []; $tables_exist = [];
foreach ($tables as $table) { foreach ($tables as $table) {
$tables_exist[] = current($table); $tables_exist[] = current($table);
} }
$tables_conflict = array_intersect($tables_to_install, $tables_exist); $tables_conflict = array_intersect($tables_to_install, $tables_exist);
if (!$overwrite) {
if ($tables_conflict) { if ($tables_conflict) {
return $this->json(1, '以下表' . implode(',', $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/webman-admin.sql'; $sql_file = base_path() . '/plugin/admin/install.sql';
if (!is_file($sql_file)) { if (!is_file($sql_file)) {
return $this->json(1, '数据库SQL文件不存在'); return $this->json(1, '数据库SQL文件不存在');
} }
@ -193,22 +198,23 @@ 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']);
$smt = $pdo->prepare("insert into `wa_admins` (`username`, `password`, `nickname`, `roles`, `created_at`, `updated_at`) values (:username, :password, :nickname, :roles, :created_at, :updated_at)");
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'); $time = date('Y-m-d H:i:s');
$data = [ $data = [
'username' => $username, 'username' => $username,
'password' => Util::passwordHash($password), 'password' => Util::passwordHash($password),
'nickname' => '超级管理员', 'nickname' => '超级管理员',
'roles' => '1',
'created_at' => $time, 'created_at' => $time,
'updated_at' => $time 'updated_at' => $time
]; ];
@ -216,6 +222,13 @@ EOF;
$smt->bindValue($key, $value); $smt->bindValue($key, $value);
} }
$smt->execute(); $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(); $request->session()->flush();
return $this->json(0); return $this->json(0);
} }
@ -368,6 +381,7 @@ 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,10 +5,18 @@ 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
{ {
@ -20,11 +28,14 @@ class PluginController extends Base
/** /**
* @param Request $request * @param Request $request
* @return Response * @return string
* @throws GuzzleException
*/ */
public function index(Request $request): Response public function index(Request $request)
{ {
return view('plugin/index'); $client = $this->httpClient();
$response = $client->get("/webman-admin/apps");
return (string)$response->getBody();
} }
/** /**
@ -35,14 +46,7 @@ class PluginController extends Base
*/ */
public function list(Request $request): Response public function list(Request $request): Response
{ {
$installed = []; $installed = $this->getLocalPlugins();
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();
@ -57,75 +61,49 @@ class PluginController extends Base
return $this->json(1, '获取数据出错'); return $this->json(1, '获取数据出错');
} }
$disabled = is_phar(); $disabled = is_phar();
foreach ($data['result']['items'] as $key => $item) { foreach ($data['data']['items'] as $key => $item) {
$name = $item['name']; $name = $item['name'];
$data['result']['items'][$key]['installed'] = $installed[$name] ?? 0; $data['data']['items'][$key]['installed'] = $installed[$name] ?? 0;
$data['result']['items'][$key]['disabled'] = $disabled; $data['data']['items'][$key]['disabled'] = $disabled;
} }
$items = $data['result']['items']; $items = $data['data']['items'];
$count = $data['result']['total']; $count = $data['data']['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 * @throws GuzzleException|BusinessException
*/ */
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(0, '请登录', [ return $this->json(-1, '请登录');
'code' => 401,
'msg' => '请登录'
]);
} }
// 获取下载zip文件url // 获取下载zip文件url
$data = $this->getDownloadUrl($name, $user['uid'], $host, $version); $data = $this->getDownloadUrl($name, $version);
if ($data['code'] == -1) { if ($data['code'] != 0) {
return $this->json(0, '请登录', [ return $this->json($data['code'], $data['msg'], $data['data'] ?? []);
'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['result']['url'], $zip_file); $this->downloadZipFile($data['data']['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) {
@ -136,10 +114,12 @@ 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;
@ -171,6 +151,9 @@ 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();
@ -206,7 +189,17 @@ 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();
@ -215,6 +208,27 @@ 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
@ -226,7 +240,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);
} }
@ -236,15 +250,17 @@ class PluginController extends Base
/** /**
* 登录官网 * 登录官网
* @param Request $request * @param Request $request
* @return Response * @return Response|string
* @throws GuzzleException * @throws GuzzleException
*/ */
public function login(Request $request): Response public function login(Request $request)
{ {
if ($request->method() === 'GET') {
return view('plugin/auth-login');
}
$client = $this->httpClient(); $client = $this->httpClient();
if ($request->method() === 'GET') {
$response = $client->get("/webman-admin/login");
return (string)$response->getBody();
}
$response = $client->post('/api/user/login', [ $response = $client->post('/api/user/login', [
'form_params' => [ 'form_params' => [
'email' => $request->post('username'), 'email' => $request->post('username'),
@ -272,37 +288,27 @@ 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, $uid, $host, $version) protected function getDownloadUrl($name, $version)
{ {
$client = $this->httpClient(); $client = $this->httpClient();
$response = $client->post('/api/app/download', [ $response = $client->get("/app/download/$name?version=$version");
'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('访问官方接口失败'); throw new BusinessException('访问官方接口失败 ' . $response->getStatusCode() . ' ' . $response->getReasonPhrase());
} }
if ($data['code'] && $data['code'] != -1) { if ($data['code'] && $data['code'] != -1 && $data['code'] != -2) {
throw new BusinessException($data['msg']); throw new BusinessException($data['message']);
} }
if ($data['code'] == 0 && !isset($data['result']['url'])) { if ($data['code'] == 0 && !isset($data['data']['url'])) {
throw new BusinessException('官方接口返回数据错误'); throw new BusinessException('官方接口返回数据错误');
} }
return $data; return $data;
@ -341,10 +347,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 -qq $zip_file -d $extract_to"; $cmd = "$cmd -o -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;
@ -359,8 +365,8 @@ class PluginController extends Base
protected function unzipWithCmd($cmd) protected function unzipWithCmd($cmd)
{ {
$desc = [ $desc = [
0 => STDIN, 0 => ["pipe", "r"],
1 => STDOUT, 1 => ["pipe", "w"],
2 => ["pipe", "w"], 2 => ["pipe", "w"],
]; ];
$handler = proc_open($cmd, $desc, $pipes); $handler = proc_open($cmd, $desc, $pipes);
@ -375,6 +381,34 @@ 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
@ -406,10 +440,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);
@ -477,8 +511,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)) {
@ -491,19 +525,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) : $this->suffixes, $suffixes); $suffixes = array_merge($pathExt ? explode(PATH_SEPARATOR, $pathExt) : ['.exe', '.bat', '.cmd', '.com'], $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

@ -2,7 +2,10 @@
namespace plugin\admin\app\controller; 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\Role;
use plugin\admin\app\model\Rule;
use support\exception\BusinessException; use support\exception\BusinessException;
use support\Request; use support\Request;
use support\Response; use support\Response;
@ -12,6 +15,12 @@ use support\Response;
*/ */
class RoleController extends Crud class RoleController extends Crud
{ {
/**
* 不需要鉴权的方法
* @var array
*/
protected $noNeedAuth = ['select'];
/** /**
* @var Role * @var Role
*/ */
@ -34,6 +43,26 @@ class RoleController extends Crud
return view('role/index'); return view('role/index');
} }
/**
* 查询
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function select(Request $request): Response
{
$id = $request->get('id');
[$where, $format, $limit, $field, $order] = $this->selectInput($request);
$role_ids = Auth::getScopeRoleIds(true);
if (!$id) {
$where['id'] = ['in', $role_ids];
} elseif (!in_array($id, $role_ids)) {
throw new BusinessException('无权限');
}
$query = $this->doSelect($where, $field, $order);
return $this->doFormat($query, $format, $limit);
}
/** /**
* 插入 * 插入
* @param Request $request * @param Request $request
@ -43,7 +72,18 @@ class RoleController extends Crud
public function insert(Request $request): Response public function insert(Request $request): Response
{ {
if ($request->method() === 'POST') { if ($request->method() === 'POST') {
return parent::insert($request); $data = $this->insertInput($request);
$pid = $data['pid'] ?? null;
if (!$pid) {
return $this->json(1, '请选择父级角色组');
}
if (!Auth::isSupperAdmin() && !in_array($pid, Auth::getScopeRoleIds(true))) {
return $this->json(1, '父级角色组超出权限范围');
}
$this->checkRules($pid, $data['rules'] ?? '');
$id = $this->doInsert($data);
return $this->json(0, 'ok', ['id' => $id]);
} }
return view('role/insert'); return view('role/insert');
} }
@ -60,11 +100,59 @@ class RoleController extends Crud
return view('role/update'); return view('role/update');
} }
[$id, $data] = $this->updateInput($request); [$id, $data] = $this->updateInput($request);
// id为1的管理员权限固定为* $is_supper_admin = Auth::isSupperAdmin();
if (isset($data['rules']) && $id == 1) { $descendant_role_ids = Auth::getScopeRoleIds();
$data['rules'] = '*'; 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); $this->doUpdate($id, $data);
// 删除所有子角色组中已经不存在的权限
if (!$is_supper_role) {
$tree = new Tree(Role::select(['id', 'pid'])->get());
$descendant_roles = $tree->getDescendant([$id]);
$descendant_role_ids = array_column($descendant_roles, 'id');
$rule_ids = $data['rules'] ? explode(',', $data['rules']) : [];
foreach ($descendant_role_ids as $role_id) {
$tmp_role = Role::find($role_id);
$tmp_rule_ids = $role->getRuleIds();
$tmp_rule_ids = array_intersect($rule_ids, $tmp_rule_ids);
$tmp_role->rules = implode(',', $tmp_rule_ids);
$tmp_role->save();
}
}
return $this->json(0); return $this->json(0);
} }
@ -72,6 +160,7 @@ class RoleController extends Crud
* 删除 * 删除
* @param Request $request * @param Request $request
* @return Response * @return Response
* @throws BusinessException
*/ */
public function delete(Request $request): Response public function delete(Request $request): Response
{ {
@ -79,8 +168,85 @@ class RoleController extends Crud
if (in_array(1, $ids)) { if (in_array(1, $ids)) {
return $this->json(1, '无法删除超级管理员角色'); return $this->json(1, '无法删除超级管理员角色');
} }
if (!Auth::isSupperAdmin() && array_diff($ids, Auth::getScopeRoleIds())) {
return $this->json(1, '无删除权限');
}
$tree = new Tree(Role::get());
$descendants = $tree->getDescendant($ids);
if ($descendants) {
$ids = array_merge($ids, array_column($descendants, 'id'));
}
$this->doDelete($ids); $this->doDelete($ids);
return $this->json(0); return $this->json(0);
} }
/**
* 获取角色权限
* @param Request $request
* @return Response
*/
public function rules(Request $request): Response
{
$role_id = $request->get('id');
if (empty($role_id)) {
return $this->json(0, 'ok', []);
}
if (!Auth::isSupperAdmin() && !in_array($role_id, Auth::getScopeRoleIds(true))) {
return $this->json(1, '角色组超出权限范围');
}
$rule_id_string = Role::where('id', $role_id)->value('rules');
if ($rule_id_string === '') {
return $this->json(0, 'ok', []);
}
$rules = Rule::get();
$include = [];
if ($rule_id_string !== '*') {
$include = explode(',', $rule_id_string);
}
$items = [];
foreach ($rules as $item) {
$items[] = [
'name' => $item->title ?? $item->name ?? $item->id,
'value' => (string)$item->id,
'id' => $item->id,
'pid' => $item->pid,
];
}
$tree = new Tree($items);
return $this->json(0, 'ok', $tree->getTree($include));
}
/**
* 检查权限字典是否合法
* @param int $role_id
* @param $rule_ids
* @return void
* @throws BusinessException
*/
protected function checkRules(int $role_id, $rule_ids)
{
if ($rule_ids) {
$rule_ids = explode(',', $rule_ids);
if (in_array('*', $rule_ids)) {
throw new BusinessException('非法数据');
}
$rule_exists = Rule::whereIn('id', $rule_ids)->pluck('id');
if (count($rule_exists) != count($rule_ids)) {
throw new BusinessException('权限不存在');
}
$rule_id_string = Role::where('id', $role_id)->value('rules');
if ($rule_id_string === '') {
throw new BusinessException('数据超出权限范围');
}
if ($rule_id_string === '*') {
return;
}
$legal_rule_ids = explode(',', $rule_id_string);
if (array_diff($rule_ids, $legal_rule_ids)) {
throw new BusinessException('数据超出权限范围');
}
}
}
} }

View File

@ -2,6 +2,7 @@
namespace plugin\admin\app\controller; namespace plugin\admin\app\controller;
use plugin\admin\app\common\Tree;
use plugin\admin\app\common\Util; use plugin\admin\app\common\Util;
use plugin\admin\app\model\Role; use plugin\admin\app\model\Role;
use plugin\admin\app\model\Rule; use plugin\admin\app\model\Rule;
@ -19,7 +20,7 @@ class RuleController extends Crud
* *
* @var string[] * @var string[]
*/ */
protected $noNeedAuth = ['get', 'permissionCodes']; protected $noNeedAuth = ['get', 'permission'];
/** /**
* @var Rule * @var Rule
@ -59,54 +60,39 @@ class RuleController extends Crud
* 获取菜单 * 获取菜单
* @param Request $request * @param Request $request
* @return Response * @return Response
* @throws BusinessException
*/ */
function get(Request $request): Response function get(Request $request): Response
{ {
$rules = $this->getRules(admin('roles')); $rules = $this->getRules(admin('roles'));
$items = Rule::orderBy('weight', 'desc')->get()->toArray();
$types = $request->get('type', '0,1'); $types = $request->get('type', '0,1');
$types = is_string($types) ? explode(',', $types) : [0, 1]; $types = is_string($types) ? explode(',', $types) : [0, 1];
$items_map = []; $items = Rule::orderBy('weight', 'desc')->get()->toArray();
$formatted_items = [];
foreach ($items as $item) { foreach ($items as $item) {
$item['pid'] = (int)$item['pid']; $item['pid'] = (int)$item['pid'];
$item['name'] = $item['title']; $item['name'] = $item['title'];
$item['value'] = $item['id']; $item['value'] = $item['id'];
$items_map[$item['id']] = $item; $item['icon'] = $item['icon'] ? "layui-icon {$item['icon']}" : '';
}
$formatted_items = [];
foreach ($items_map as $index => $item) {
//$items_map[$index]['type'] = $items_map[$index]['href'] ? 1 : 0;
$items_map[$index]['icon'] = $items_map[$index]['icon'] ? "layui-icon {$items_map[$index]['icon']}" : '';
if ($item['pid'] && isset($items_map[$item['pid']])) {
$items_map[$item['pid']]['children'][] = &$items_map[$index];
}
}
foreach ($items_map as $item) {
if (!$item['pid']) {
$formatted_items[] = $item; $formatted_items[] = $item;
} }
}
$tree = new Tree($formatted_items);
$tree_items = $tree->getTree();
// 超级管理员权限为 * // 超级管理员权限为 *
if (!in_array('*', $rules)) { if (!in_array('*', $rules)) {
$this->removeUncontain($formatted_items, 'id', $rules); $this->removeNotContain($tree_items, 'id', $rules);
} }
$this->removeUncontain($formatted_items, 'type', $types); $this->removeNotContain($tree_items, 'type', $types);
$formatted_items = array_values($formatted_items); return $this->json(0, 'ok', Tree::arrayValues($tree_items));
foreach ($formatted_items as &$item) {
$this->arrayValues($item);
}
return $this->json(0, 'ok', $formatted_items);
} }
/** /**
* 获取控制器详细权限 * 获取权限
* @param Request $request * @param Request $request
* @return Response * @return Response
* @throws BusinessException
*/ */
public function permissionCodes(Request $request): Response public function permission(Request $request): Response
{ {
$rules = $this->getRules(admin('roles')); $rules = $this->getRules(admin('roles'));
// 超级管理员 // 超级管理员
@ -279,7 +265,7 @@ class RuleController extends Crud
* @param $values * @param $values
* @return void * @return void
*/ */
protected function removeUncontain(&$array, $key, $values) protected function removeNotContain(&$array, $key, $values)
{ {
foreach ($array as $k => &$item) { foreach ($array as $k => &$item) {
if (!is_array($item)) { if (!is_array($item)) {
@ -291,7 +277,7 @@ class RuleController extends Crud
if (!isset($item['children'])) { if (!isset($item['children'])) {
continue; continue;
} }
$this->removeUncontain($item['children'], $key, $values); $this->removeNotContain($item['children'], $key, $values);
} }
} }
} }
@ -322,25 +308,6 @@ class RuleController extends Crud
return false; return false;
} }
/**
* 递归删除某些key
* @param $array
* @param $keys
* @return void
*/
protected function recursiveRemove(&$array, $keys)
{
if (!is_array($array)) {
return;
}
foreach ($keys as $key) {
unset($array[$key]);
}
foreach ($array as &$item) {
$this->recursiveRemove($item, $keys);
}
}
/** /**
* 获取权限规则 * 获取权限规则
* @param $roles * @param $roles
@ -359,20 +326,4 @@ class RuleController extends Crud
return $rules; return $rules;
} }
/**
* 递归重建数组下标
* @return void
*/
protected function arrayValues(&$array)
{
if (!is_array($array) || !isset($array['children'])) {
return;
}
$array['children'] = array_values($array['children']);
foreach ($array['children'] as &$child) {
$this->arrayValues($child);
}
}
} }

View File

@ -12,6 +12,7 @@ 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
{ {
@ -58,16 +59,20 @@ 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';
$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"); $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 limit $offset,$limit");
if ($tables) { if ($tables) {
$table_names = array_column($tables, 'TABLE_NAME'); $table_names = array_column($tables, 'TABLE_NAME');
@ -80,7 +85,7 @@ class TableController extends Base
} }
} }
return $this->json(0, 'ok', $tables); return json(['code' => 0, 'msg' => 'ok', 'count' => $total, 'data' => $tables]);
} }
/** /**
@ -103,6 +108,7 @@ 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;
@ -195,7 +201,7 @@ class TableController extends Base
$data = $request->post(); $data = $request->post();
$old_table_name = Util::filterAlphaNum($data['old_table']); $old_table_name = Util::filterAlphaNum($data['old_table']);
$table_name = Util::filterAlphaNum($data['table']); $table_name = Util::filterAlphaNum($data['table']);
$table_comment = Util::pdoQuote($data['table_comment']); $table_comment = $data['table_comment'];
$columns = $data['columns']; $columns = $data['columns'];
$forms = $data['forms']; $forms = $data['forms'];
$keys = $data['keys']; $keys = $data['keys'];
@ -207,6 +213,7 @@ 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;
@ -284,6 +291,7 @@ class TableController extends Base
$table = Util::getSchema($table_name, 'table'); $table = Util::getSchema($table_name, 'table');
if ($table_comment !== $table['comment']) { if ($table_comment !== $table['comment']) {
$table_comment = Util::pdoQuote($table_comment);
Util::db()->statement("ALTER TABLE `$table_name` COMMENT $table_comment"); Util::db()->statement("ALTER TABLE `$table_name` COMMENT $table_comment");
} }
@ -320,9 +328,10 @@ class TableController extends Base
$key_name = $key['name']; $key_name = $key['name'];
$old_key = $old_keys[$key_name] ?? []; $old_key = $old_keys[$key_name] ?? [];
// 如果索引有变动,则删除索引,重新建立索引 // 如果索引有变动,则删除索引,重新建立索引
if ($old_key && ($key['type'] != $old_key['type'] || $key['columns'] != $old_key['columns'])) { if ($old_key && ($key['type'] != $old_key['type'] || $key['columns'] != implode(',', $old_key['columns']))) {
$old_key = []; $old_key = [];
unset($old_keys[$key_name]); unset($old_keys[$key_name]);
echo "Drop Index $key_name\n";
$table->dropIndex($key_name); $table->dropIndex($key_name);
} }
// 重新建立索引 // 重新建立索引
@ -334,6 +343,7 @@ class TableController extends Base
$table->unique($columns, $name); $table->unique($columns, $name);
continue; continue;
} }
echo "Create Index $key_name\n";
$table->index($columns, $name); $table->index($columns, $name);
} }
} }
@ -343,6 +353,7 @@ class TableController extends Base
$old_keys_names = array_column($old_keys, 'name'); $old_keys_names = array_column($old_keys, 'name');
$drop_keys_names = array_diff($old_keys_names, $exists_key_names); $drop_keys_names = array_diff($old_keys_names, $exists_key_names);
foreach ($drop_keys_names as $name) { foreach ($drop_keys_names as $name) {
echo "Drop Index $name\n";
$table->dropIndex($name); $table->dropIndex($name);
} }
}); });
@ -385,7 +396,8 @@ class TableController extends Base
{ {
$table_name = $request->input('table'); $table_name = $request->input('table');
Util::checkTableName($table_name); Util::checkTableName($table_name);
$table_basename = strpos($table_name, 'wa_') === 0 ? substr($table_name, 3) : $table_name; $prefix = 'wa_';
$table_basename = strpos($table_name, $prefix) === 0 ? substr($table_name, strlen($prefix)) : $table_name;
$inflector = InflectorFactory::create()->build(); $inflector = InflectorFactory::create()->build();
$model_class = $inflector->classify($inflector->singularize($table_basename)); $model_class = $inflector->classify($inflector->singularize($table_basename));
$base_path = '/plugin/admin/app'; $base_path = '/plugin/admin/app';
@ -459,15 +471,17 @@ 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));
@ -497,6 +511,9 @@ 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) {
@ -546,20 +563,33 @@ 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'") 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' order by ORDINAL_POSITION") 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
/** /**
@ -568,6 +598,7 @@ class TableController extends Base
* @var bool * @var bool
*/ */
public \$timestamps = false; public \$timestamps = false;
EOF; EOF;
} }
@ -597,9 +628,8 @@ class $class extends Base
* @var string * @var string
*/ */
protected \$primaryKey = '$pk'; protected \$primaryKey = '$pk';
$timestamps $timestamps
$incrementing
} }
@ -827,6 +857,9 @@ 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;
@ -839,6 +872,16 @@ 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", {
@ -977,6 +1020,15 @@ 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,
@ -1053,15 +1105,15 @@ EOF;
const UPDATE_API = "$url_path_base/update"; const UPDATE_API = "$url_path_base/update";
// 获取数据库记录 // 获取数据库记录
layui.use(["form", "util"], function () { layui.use(["form", "util", "popup"], function () {
let $ = layui.$; let $ = layui.$;
$.ajax({ $.ajax({
url: SELECT_API, url: SELECT_API,
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
// 给表单初始化数据 // 给表单初始化数据
layui.each(e.data[0], function (key, value) { layui.each(res.data[0], function (key, value) {
let obj = $('*[name="'+key+'"]'); let obj = $('*[name="'+key+'"]');
if (key === "password") { if (key === "password") {
obj.attr("placeholder", "不更新密码请留空"); obj.attr("placeholder", "不更新密码请留空");
@ -1076,12 +1128,26 @@ EOF;
}); });
$js $js
// ajax返回失败
if (res.code) {
layui.popup.failure(res.msg);
}
} }
}); });
}); });
//提交事件 //提交事件
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({
@ -1160,17 +1226,21 @@ EOF;
} }
if (isset($allow_column[$column])) { if (isset($allow_column[$column])) {
if (is_array($value)) { if (is_array($value)) {
if (in_array($value[0], ['', 'undefined']) || in_array($value[1], ['', 'undefined'])) { if ($value[0] === 'like') {
continue; $paginator = $paginator->where($column, 'like', "%$value[1]%");
} } 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 = [];
@ -1348,7 +1418,8 @@ EOF;
if (!$tables) { if (!$tables) {
return $this->json(0, 'not found'); return $this->json(0, 'not found');
} }
$table_not_allow_drop = ['wa_admins', 'wa_users', 'wa_options', 'wa_roles', 'wa_rules']; $prefix = 'wa_';
$table_not_allow_drop = ["{$prefix}admins", "{$prefix}users", "{$prefix}options", "{$prefix}roles", "{$prefix}rules", "{$prefix}admin_roles", "{$prefix}uploads"];
if ($found = array_intersect($tables, $table_not_allow_drop)) { if ($found = array_intersect($tables, $table_not_allow_drop)) {
return $this->json(400, implode(',', $found) . '不允许删除'); return $this->json(400, implode(',', $found) . '不允许删除');
} }
@ -1447,7 +1518,7 @@ EOF;
$field = Util::filterAlphaNum($column['field']); $field = Util::filterAlphaNum($column['field']);
$old_field = Util::filterAlphaNum($column['old_field'] ?? null); $old_field = Util::filterAlphaNum($column['old_field'] ?? null);
$nullable = $column['nullable']; $nullable = $column['nullable'];
$default = Util::filterAlphaNum($column['default']); $default = $column['default'] !== null ? Util::pdoQuote($column['default']) : null;
$comment = Util::pdoQuote($column['comment']); $comment = Util::pdoQuote($column['comment']);
$auto_increment = $column['auto_increment']; $auto_increment = $column['auto_increment'];
$length = (int)$column['length']; $length = (int)$column['length'];
@ -1457,9 +1528,9 @@ EOF;
} }
if ($old_field && $old_field !== $field) { if ($old_field && $old_field !== $field) {
$sql = "ALTER TABLE $table CHANGE COLUMN `$old_field` `$field` "; $sql = "ALTER TABLE `$table` CHANGE COLUMN `$old_field` `$field` ";
} else { } else {
$sql = "ALTER TABLE $table MODIFY `$field` "; $sql = "ALTER TABLE `$table` MODIFY `$field` ";
} }
if (stripos($method, 'integer') !== false) { if (stripos($method, 'integer') !== false) {
@ -1515,13 +1586,14 @@ EOF;
} }
if ($method != 'text' && $default !== null) { if ($method != 'text' && $default !== null) {
$sql .= "DEFAULT '$default' "; $sql .= "DEFAULT $default ";
} }
if ($comment !== null) { if ($comment !== null) {
$sql .= "COMMENT $comment "; $sql .= "COMMENT $comment ";
} }
echo "$sql\n";
Util::db()->statement($sql); Util::db()->statement($sql);
} }

View File

@ -306,19 +306,4 @@ class UploadController extends Crud
]; ];
} }
/**
* 格式化文件大小
* @param $file_size
* @return string
*/
protected function formatSize($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];
}
} }

View File

@ -27,13 +27,14 @@ class Handler extends \support\exception\Handler
public function render(Request $request, Throwable $exception): Response public function render(Request $request, Throwable $exception): Response
{ {
$code = $exception->getCode(); $code = $exception->getCode();
$debug = $this->_debug ?? $this->debug;
if ($request->expectsJson()) { if ($request->expectsJson()) {
$json = ['code' => $code ? $code : 500, 'msg' => $this->_debug ? $exception->getMessage() : 'Server internal error', 'type' => 'failed']; $json = ['code' => $code ?: 500, 'msg' => $debug ? $exception->getMessage() : 'Server internal error', 'type' => 'failed'];
$this->_debug && $json['traces'] = (string)$exception; $debug && $json['traces'] = (string)$exception;
return new Response(200, ['Content-Type' => 'application/json'], return new Response(200, ['Content-Type' => 'application/json'],
\json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); \json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
} }
$error = $this->_debug ? \nl2br((string)$exception) : 'Server internal error'; $error = $debug ? \nl2br((string)$exception) : 'Server internal error';
return new Response(500, [], $error); return new Response(500, [], $error);
} }
} }

View File

@ -3,15 +3,15 @@
* Here is your custom functions. * Here is your custom functions.
*/ */
use app\model\User; use plugin\admin\app\model\User;
use plugin\admin\app\model\Admin; use plugin\admin\app\model\Admin;
use support\exception\BusinessException; use plugin\admin\app\model\AdminRole;
/** /**
* 当前管理员id * 当前管理员id
* @return integer|null * @return integer|null
*/ */
function admin_id() function admin_id(): ?int
{ {
return session('admin.id'); return session('admin.id');
} }
@ -20,7 +20,6 @@ function admin_id()
* 当前管理员 * 当前管理员
* @param null|array|string $fields * @param null|array|string $fields
* @return array|mixed|null * @return array|mixed|null
* @throws BusinessException
*/ */
function admin($fields = null) function admin($fields = null)
{ {
@ -45,7 +44,7 @@ function admin($fields = null)
* 当前登录用户id * 当前登录用户id
* @return integer|null * @return integer|null
*/ */
function user_id() function user_id(): ?int
{ {
return session('user.id'); return session('user.id');
} }
@ -54,7 +53,6 @@ function user_id()
* 当前登录用户 * 当前登录用户
* @param null|array|string $fields * @param null|array|string $fields
* @return array|mixed|null * @return array|mixed|null
* @throws BusinessException
*/ */
function user($fields = null) function user($fields = null)
{ {
@ -79,7 +77,6 @@ function user($fields = null)
* 刷新当前管理员session * 刷新当前管理员session
* @param bool $force * @param bool $force
* @return void * @return void
* @throws BusinessException
*/ */
function refresh_admin_session(bool $force = false) function refresh_admin_session(bool $force = false)
{ {
@ -101,7 +98,12 @@ function refresh_admin_session(bool $force = false)
} }
$admin = $admin->toArray(); $admin = $admin->toArray();
unset($admin['password']); unset($admin['password']);
$admin['roles'] = $admin['roles'] ? explode(',', $admin['roles']) : []; // 账户被禁用
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; $admin['session_last_update_time'] = $time_now;
$session->set('admin', $admin); $session->set('admin', $admin);
} }
@ -111,7 +113,6 @@ function refresh_admin_session(bool $force = false)
* 刷新当前用户session * 刷新当前用户session
* @param bool $force * @param bool $force
* @return void * @return void
* @throws BusinessException
*/ */
function refresh_user_session(bool $force = false) function refresh_user_session(bool $force = false)
{ {

View File

@ -2,6 +2,8 @@
namespace plugin\admin\app\middleware; namespace plugin\admin\app\middleware;
use plugin\admin\api\Auth; use plugin\admin\api\Auth;
use ReflectionException;
use support\exception\BusinessException;
use Webman\Http\Request; use Webman\Http\Request;
use Webman\Http\Response; use Webman\Http\Response;
use Webman\MiddlewareInterface; use Webman\MiddlewareInterface;
@ -12,7 +14,7 @@ class AccessControl implements MiddlewareInterface
* @param Request $request * @param Request $request
* @param callable $handler * @param callable $handler
* @return Response * @return Response
* @throws \ReflectionException * @throws ReflectionException|BusinessException
*/ */
public function process(Request $request, callable $handler): Response public function process(Request $request, callable $handler): Response
{ {
@ -35,7 +37,9 @@ class AccessControl implements MiddlewareInterface
EOF EOF
); );
} else { } else {
$response = view('common/error/403'); $request->app = '';
$request->plugin = 'admin';
$response = view('common/error/403')->withStatus(403);
} }
} }

View File

@ -14,7 +14,9 @@ use plugin\admin\app\model\Base;
* @property string $mobile 手机 * @property string $mobile 手机
* @property string $created_at 创建时间 * @property string $created_at 创建时间
* @property string $updated_at 更新时间 * @property string $updated_at 更新时间
* @property string $login_at 登录时间
* @property string $roles 角色 * @property string $roles 角色
* @property integer $status 状态 0正常 1禁用
*/ */
class Admin extends Base class Admin extends Base
{ {

View File

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

View File

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

View File

@ -2,7 +2,6 @@
namespace plugin\admin\app\model; namespace plugin\admin\app\model;
use plugin\admin\app\model\Base;
/** /**
* @property integer $id 主键(主键) * @property integer $id 主键(主键)
@ -10,6 +9,7 @@ use plugin\admin\app\model\Base;
* @property string $rules 权限 * @property string $rules 权限
* @property string $created_at 创建时间 * @property string $created_at 创建时间
* @property string $updated_at 更新时间 * @property string $updated_at 更新时间
* @property integer $pid 上级id
*/ */
class Role extends Base class Role extends Base
{ {
@ -27,7 +27,12 @@ class Role extends Base
*/ */
protected $primaryKey = 'id'; protected $primaryKey = 'id';
/**
* @return mixed
*/
public function getRuleIds()
{
return $this->rules ? explode(',', $this->rules) : [];
}
} }

View File

@ -89,8 +89,10 @@
<!-- 表格行工具栏 --> <!-- 表格行工具栏 -->
<script type="text/html" id="table-bar"> <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="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> <button class="pear-btn pear-btn-xs tool-btn" lay-event="remove" permission="app.admin.admin.delete">删除</button>
{{# } }}
</script> </script>
<script src="/app/admin/component/layui/layui.js"></script> <script src="/app/admin/component/layui/layui.js"></script>
@ -130,6 +132,8 @@
},{ },{
title: "ID", title: "ID",
field: "id", field: "id",
width: 100,
sort: true,
},{ },{
title: "用户名", title: "用户名",
field: "username", field: "username",
@ -145,13 +149,16 @@
field: "avatar", field: "avatar",
templet: function (d) { templet: function (d) {
return '<img src="'+encodeURI(d['avatar'])+'" style="max-width:32px;max-height:32px;" alt="" />' return '<img src="'+encodeURI(d['avatar'])+'" style="max-width:32px;max-height:32px;" alt="" />'
} },
width: 90,
},{ },{
title: "邮箱", title: "邮箱",
field: "email", field: "email",
hide: true,
},{ },{
title: "手机", title: "手机",
field: "mobile", field: "mobile",
hide: true,
},{ },{
title: "创建时间", title: "创建时间",
field: "created_at", field: "created_at",
@ -160,6 +167,9 @@
title: "更新时间", title: "更新时间",
field: "updated_at", field: "updated_at",
hide: true, hide: true,
},{
title: "登录时间",
field: "login_at",
},{ },{
title: "角色", title: "角色",
field: "roles", field: "roles",
@ -172,6 +182,32 @@
}); });
return util.escape(items.join(",")); 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: "操作", title: "操作",
toolbar: "#table-bar", toolbar: "#table-bar",
@ -216,6 +252,9 @@
url: url, url: url,
dateType: "json", dateType: "json",
success: function (res) { success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
function travel(items) { function travel(items) {
for (let k in items) { for (let k in items) {
let item = items[k]; let item = items[k];
@ -261,6 +300,9 @@
// 表格顶部搜索事件 // 表格顶部搜索事件
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

@ -13,6 +13,13 @@
<div class="mainBox"> <div class="mainBox">
<div class="main-container mr-5"> <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"> <div class="layui-form-item">
<label class="layui-form-label required">用户名</label> <label class="layui-form-label required">用户名</label>
<div class="layui-input-block"> <div class="layui-input-block">
@ -62,13 +69,6 @@
</div> </div>
</div> </div>
<div class="layui-form-item">
<label class="layui-form-label">角色</label>
<div class="layui-input-block">
<div name="roles" id="roles" value="" ></div>
</div>
</div>
</div> </div>
</div> </div>
@ -125,21 +125,30 @@
}); });
// 字段 角色 roles // 字段 角色 roles
layui.use(["jquery", "xmSelect"], function() { layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({ layui.$.ajax({
url: "/app/admin/role/select?format=select", url: "/app/admin/role/select?format=tree",
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
let value = layui.$("#roles").attr("value"); let value = layui.$("#roles").attr("value");
let initValue = value ? value.split(",") : []; let initValue = value ? value.split(",") : [];
if (!top.Admin.Account.isSupperAdmin) {
layui.each(res.data, function (k, v) {
v.disabled = true;
});
}
layui.xmSelect.render({ layui.xmSelect.render({
el: "#roles", el: "#roles",
name: "roles", name: "roles",
initValue: initValue, initValue: initValue,
data: e.data, data: res.data,
tree: {"show":true}, layVerify: "required",
toolbar: {"show":true,"list":["ALL","CLEAR","REVERSE"]}, tree: {"show":true, expandedKeys:true, strict:false},
}) toolbar: {show:true, list:["ALL","CLEAR","REVERSE"]},
});
if (res.code) {
layui.popup.failure(res.msg);
}
} }
}); });
}); });

View File

@ -13,6 +13,13 @@
<div class="mainBox"> <div class="mainBox">
<div class="main-container mr-5"> <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"> <div class="layui-form-item">
<label class="layui-form-label required">用户名</label> <label class="layui-form-label required">用户名</label>
<div class="layui-input-block"> <div class="layui-input-block">
@ -62,13 +69,6 @@
</div> </div>
</div> </div>
<div class="layui-form-item">
<label class="layui-form-label">角色</label>
<div class="layui-input-block">
<div name="roles" id="roles" value="" ></div>
</div>
</div>
</div> </div>
</div> </div>
@ -96,15 +96,15 @@
const UPDATE_API = "/app/admin/admin/update"; const UPDATE_API = "/app/admin/admin/update";
// 获取数据库记录 // 获取数据库记录
layui.use(["form", "util"], function () { layui.use(["form", "util", "popup"], function () {
let $ = layui.$; let $ = layui.$;
$.ajax({ $.ajax({
url: SELECT_API, url: SELECT_API,
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
// 给表单初始化数据 // 给表单初始化数据
layui.each(e.data[0], function (key, value) { layui.each(res.data[0], function (key, value) {
let obj = $('*[name="'+key+'"]'); let obj = $('*[name="'+key+'"]');
if (key === "password") { if (key === "password") {
obj.attr("placeholder", "不更新密码请留空"); obj.attr("placeholder", "不更新密码请留空");
@ -150,23 +150,36 @@
// 字段 角色 roles // 字段 角色 roles
layui.use(["jquery", "xmSelect"], function() { layui.use(["jquery", "xmSelect"], function() {
layui.$.ajax({ layui.$.ajax({
url: "/app/admin/role/select?format=select", url: "/app/admin/role/select?format=tree",
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
let value = layui.$("#roles").attr("value"); let value = layui.$("#roles").attr("value");
let initValue = value ? value.split(",") : []; let initValue = value ? value.split(",") : [];
if (!top.Admin.Account.isSupperAdmin) {
layui.each(res.data, function (k, v) {
v.disabled = true;
});
}
layui.xmSelect.render({ layui.xmSelect.render({
el: "#roles", el: "#roles",
name: "roles", name: "roles",
initValue: initValue, initValue: initValue,
data: e.data, data: res.data,
tree: {show: true, expandedKeys: initValue}, layVerify: "required",
tree: {show: true, expandedKeys: true, strict: false},
toolbar: {show: true, list: ["ALL","CLEAR","REVERSE"]}, toolbar: {show: true, list: ["ALL","CLEAR","REVERSE"]},
}) })
if (res.code) {
layui.popup.failure(res.msg);
}
} }
}); });
}); });
// ajax产生错误
if (res.code) {
layui.popup.failure(res.msg);
}
} }
}); });

View File

@ -290,6 +290,9 @@
url: "/app/admin/config/get", url: "/app/admin/config/get",
dataType: "json", dataType: "json",
success: function (res) { success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
form.val("baseInfo", res.logo); form.val("baseInfo", res.logo);
$("#image").prev().val(res.logo.image).prev().attr("src", res.logo.image); $("#image").prev().val(res.logo.image).prev().attr("src", res.logo.image);
form.val("menuInfo", res.menu); form.val("menuInfo", res.menu);

View File

@ -156,6 +156,9 @@
// 表格顶部搜索事件 // 表格顶部搜索事件
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

@ -135,6 +135,10 @@
layui.each(data, function (k, v) { layui.each(data, function (k, v) {
data[k]["_field_id"] = _field_id++; data[k]["_field_id"] = _field_id++;
}) })
// ajax产生错误
if (res.code) {
layui.popup.failure(res.msg);
}
} }
}); });

View File

@ -43,7 +43,7 @@
<a class="layui-icon layui-icon-username" href="javascript:;"></a> <a class="layui-icon layui-icon-username" href="javascript:;"></a>
<!-- 功 能 菜 单 --> <!-- 功 能 菜 单 -->
<dl class="layui-nav-child"> <dl class="layui-nav-child">
<dd><a user-menu-url="/app/admin/account/index" user-menu-id="11" user-menu-title="基本资料">基本资料</a></dd> <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> <dd><a href="javascript:void(0);" class="logout">注销登录</a></dd>
</dl> </dl>
</li> </li>
@ -94,6 +94,12 @@
<script src="/app/admin/component/pear/pear.js"></script> <script src="/app/admin/component/pear/pear.js"></script>
<!-- 框 架 初 始 化 --> <!-- 框 架 初 始 化 -->
<script> <script>
// Admin
window.Admin = {
Account: {}
};
layui.use(["admin","jquery","popup","drawer"], function() { layui.use(["admin","jquery","popup","drawer"], function() {
var $ = layui.$; var $ = layui.$;
var admin = layui.admin; var admin = layui.admin;
@ -121,9 +127,18 @@
return false; 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) {}); //admin.message(function(id, title, context, form) {});
}) });
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,69 +0,0 @@
<!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

@ -1,145 +0,0 @@
<!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 (e) {
let cols = e.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

@ -10,44 +10,7 @@
<body class="pear-container"> <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">
<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">
@ -68,14 +31,17 @@
<!-- 表格行工具栏 --> <!-- 表格行工具栏 -->
<script type="text/html" id="table-bar"> <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="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> <button class="pear-btn pear-btn-xs tool-btn" lay-event="remove" permission="app.admin.role.delete">删除</button>
{{# } }}
</script> </script>
<script src="/app/admin/component/layui/layui.js"></script> <script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script> <script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script> <script src="/app/admin/admin/js/permission.js"></script>
<script src="/app/admin/admin/js/common.js"></script> <script src="/app/admin/admin/js/common.js"></script>
<script> <script>
// 相关常量 // 相关常量
@ -86,16 +52,9 @@
const INSERT_URL = "/app/admin/role/insert"; const INSERT_URL = "/app/admin/role/insert";
const UPDATE_URL = "/app/admin/role/update"; const UPDATE_URL = "/app/admin/role/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() { layui.use(["table", "treetable", "form", "common", "popup", "util"], function() {
let treeTable = layui.treetable;
let table = layui.table; let table = layui.table;
let form = layui.form; let form = layui.form;
let $ = layui.$; let $ = layui.$;
@ -106,13 +65,12 @@
let cols = [ let cols = [
{ {
type: "checkbox" type: "checkbox"
},{
title: "角色组",
field: "name",
},{ },{
title: "主键", title: "主键",
field: "id", field: "id",
hide: true,
},{
title: "角色名",
field: "name",
},{ },{
title: "权限", title: "权限",
field: "rules", field: "rules",
@ -124,50 +82,65 @@
items.push(apiResults[field][v] || v); items.push(apiResults[field][v] || v);
}); });
return util.escape(items.join(",")); return util.escape(items.join(","));
} },
hide: true,
},{ },{
title: "创建时间", title: "创建时间",
field: "created_at", field: "created_at",
},{ },{
title: "更新时间", title: "更新时间",
field: "updated_at", 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: "操作", title: "操作",
toolbar: "#table-bar", toolbar: "#table-bar",
align: "center", align: "center",
fixed: "right", fixed: "right",
width: 130, width: 120,
} }
]; ];
// 渲染表格 // 渲染表格
function render() function render()
{ {
table.render({ treeTable.render({
elem: "#data-table", elem: "#data-table",
url: SELECT_API, url: SELECT_API,
page: true, treeColIndex: 1,
treeIdName: "id",
treePidName: "pid",
treeDefaultClose: false,
cols: [cols], cols: [cols],
skin: "line", skin: "line",
size: "lg", size: "lg",
toolbar: "#table-toolbar", toolbar: "#table-toolbar",
autoSort: false,
defaultToolbar: [{ defaultToolbar: [{
title: "刷新", title: "刷新",
layEvent: "refresh", layEvent: "refresh",
icon: "layui-icon-refresh", icon: "layui-icon-refresh",
}, "filter", "print", "exports"], }, "filter", "print", "exports"]
done: function () {
layer.photos({photos: 'div[lay-id="data-table"]', anim: 5});
}
}); });
} }
// 获取表格中下拉或树形组件数据 // 获取表格中下拉或树形组件数据
let apis = []; let apis = [];
apis.push(["rules", "/app/admin/rule/get?type=0,1,2"]); apis.push(["rules", "/app/admin/rule/get?type=0,1,2"]);
apis.push(["pid", "/app/admin/role/select?format=tree"]);
let apiResults = {}; let apiResults = {};
apiResults["rules"] = []; apiResults["rules"] = [];
apiResults["pid"] = [];
let count = apis.length; let count = apis.length;
layui.each(apis, function (k, item) { layui.each(apis, function (k, item) {
let [field, url] = item; let [field, url] = item;
@ -175,6 +148,9 @@
url: url, url: url,
dateType: "json", dateType: "json",
success: function (res) { success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
function travel(items) { function travel(items) {
for (let k in items) { for (let k in items) {
let item = items[k]; let item = items[k];
@ -220,6 +196,9 @@
// 表格顶部搜索事件 // 表格顶部搜索事件
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;
@ -310,9 +289,7 @@
// 刷新表格数据 // 刷新表格数据
window.refreshTable = function(param) { window.refreshTable = function(param) {
table.reloadData("data-table", { treeTable.reload("#data-table");
scrollPos: "fixed"
});
} }
}) })

View File

@ -13,6 +13,13 @@
<div class="mainBox"> <div class="mainBox">
<div class="main-container mr-5"> <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"> <div class="layui-form-item">
<label class="layui-form-label required">角色名</label> <label class="layui-form-label required">角色名</label>
<div class="layui-input-block"> <div class="layui-input-block">
@ -47,31 +54,73 @@
<script src="/app/admin/component/layui/layui.js"></script> <script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script> <script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script> <script src="/app/admin/admin/js/permission.js"></script>
<script> <script>
// 相关接口 // 相关接口
const INSERT_API = "/app/admin/role/insert"; const INSERT_API = "/app/admin/role/insert";
// 字段 权限 rules // 字段 权限 rules
layui.use(["jquery", "xmSelect"], function() { layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({ layui.$.ajax({
url: "/app/admin/rule/get?type=0,1,2", url: "/app/admin/role/rules?id=1",
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
let value = layui.$("#rules").attr("value"); let value = layui.$("#rules").attr("value");
let initValue = value ? value.split(",") : []; let initValue = value ? value.split(",") : [];
layui.xmSelect.render({ layui.xmSelect.render({
el: "#rules", el: "#rules",
name: "rules", name: "rules",
initValue: initValue, initValue: initValue,
data: e.data, data: res.data,
tree: {"show":true}, tree: {"show":true,expandedKeys:initValue},
toolbar: {"show":true,"list":["ALL","CLEAR","REVERSE"]}, 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.use(["form", "popup"], function () {
layui.form.on("submit(save)", function (data) { layui.form.on("submit(save)", function (data) {

View File

@ -13,6 +13,13 @@
<div class="mainBox"> <div class="mainBox">
<div class="main-container mr-5"> <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"> <div class="layui-form-item">
<label class="layui-form-label required">角色名</label> <label class="layui-form-label required">角色名</label>
<div class="layui-input-block"> <div class="layui-input-block">
@ -46,6 +53,7 @@
<script src="/app/admin/component/layui/layui.js"></script> <script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script> <script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script> <script src="/app/admin/admin/js/permission.js"></script>
<script> <script>
// 相关接口 // 相关接口
@ -54,15 +62,15 @@
const UPDATE_API = "/app/admin/role/update"; const UPDATE_API = "/app/admin/role/update";
// 获取数据库记录 // 获取数据库记录
layui.use(["form", "util"], function () { layui.use(["form", "util", "popup"], function () {
let $ = layui.$; let $ = layui.$;
$.ajax({ $.ajax({
url: SELECT_API, url: SELECT_API,
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
// 给表单初始化数据 // 给表单初始化数据
layui.each(e.data[0], function (key, value) { layui.each(res.data[0], function (key, value) {
let obj = $('*[name="'+key+'"]'); let obj = $('*[name="'+key+'"]');
if (key === "password") { if (key === "password") {
obj.attr("placeholder", "不更新密码请留空"); obj.attr("placeholder", "不更新密码请留空");
@ -77,25 +85,74 @@
}); });
// 字段 权限 rules // 字段 权限 rules
layui.use(["jquery", "xmSelect"], function() { layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({ layui.$.ajax({
url: "/app/admin/rule/get?type=0,1,2", url: "/app/admin/role/rules?id=" + res.data[0].pid,
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
let value = layui.$("#rules").attr("value"); let value = layui.$("#rules").attr("value");
let initValue = value ? value.split(",") : []; let initValue = value ? value.split(",") : [];
layui.xmSelect.render({ layui.xmSelect.render({
el: "#rules", el: "#rules",
name: "rules", name: "rules",
initValue: initValue, initValue: initValue,
data: e.data, data: res.data,
tree: {show: true, expandedKeys: initValue}, tree: {"show":true,expandedKeys:initValue},
toolbar: {show: true, list: ["ALL","CLEAR","REVERSE"]}, 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);
}
} }
}); });

View File

@ -9,33 +9,6 @@
</head> </head>
<body class="pear-container"> <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="title" 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">
<div class="layui-card-body"> <div class="layui-card-body">
@ -157,7 +130,6 @@
elem: "#data-table", elem: "#data-table",
url: SELECT_API, url: SELECT_API,
treeColIndex: 1, treeColIndex: 1,
treeSpid: 0,
treeIdName: "id", treeIdName: "id",
treePidName: "pid", treePidName: "pid",
treeDefaultClose: true, treeDefaultClose: true,
@ -186,6 +158,9 @@
url: url, url: url,
dateType: "json", dateType: "json",
success: function (res) { success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
function travel(items) { function travel(items) {
for (let k in items) { for (let k in items) {
let item = items[k]; let item = items[k];

View File

@ -103,11 +103,11 @@
}); });
// 上级菜单 // 上级菜单
layui.use(["jquery", "xmSelect"], function() { layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({ layui.$.ajax({
url: "/app/admin/rule/select?format=tree&type=0,1", url: "/app/admin/rule/select?format=tree&type=0,1",
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
let value = layui.$("#pid").attr("value"); let value = layui.$("#pid").attr("value");
let initValue = value ? value.split(",") : []; let initValue = value ? value.split(",") : [];
layui.xmSelect.render({ layui.xmSelect.render({
@ -116,13 +116,16 @@
initValue: initValue, initValue: initValue,
tips: "无", tips: "无",
toolbar: {show: true, list: ["CLEAR"]}, toolbar: {show: true, list: ["CLEAR"]},
data: e.data, data: res.data,
value: "0", value: "0",
model: {"icon":"hidden","label":{"type":"text"}}, model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true, clickClose: true,
radio: true, radio: true,
tree: {show: true,"strict":false,"clickCheck":true,"clickExpand":false}, tree: {show: true,"strict":false,"clickCheck":true,"clickExpand":false},
}) });
if (res.code) {
return layui.popup.failure(res.msg);
}
} }
}); });
}); });

View File

@ -95,15 +95,15 @@
const UPDATE_API = "/app/admin/rule/update"; const UPDATE_API = "/app/admin/rule/update";
// 获取行数据 // 获取行数据
layui.use(["form", "util"], function () { layui.use(["form", "util", "popup"], function () {
let $ = layui.$; let $ = layui.$;
$.ajax({ $.ajax({
url: SELECT_API, url: SELECT_API,
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
// 赋值表单 // 赋值表单
layui.each(e.data[0], function (key, value) { layui.each(res.data[0], function (key, value) {
let obj = $('*[name="'+key+'"]'); let obj = $('*[name="'+key+'"]');
if (key === "password") { if (key === "password") {
obj.attr("placeholder", "不更新密码请留空"); obj.attr("placeholder", "不更新密码请留空");
@ -127,11 +127,11 @@
}); });
// 获取上级菜单 // 获取上级菜单
layui.use(["jquery", "xmSelect"], function() { layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({ layui.$.ajax({
url: "/app/admin/rule/select?format=tree&type=0,1", url: "/app/admin/rule/select?format=tree&type=0,1",
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
let value = layui.$("#pid").attr("value"); let value = layui.$("#pid").attr("value");
let initValue = value ? value.split(",") : []; let initValue = value ? value.split(",") : [];
layui.xmSelect.render({ layui.xmSelect.render({
@ -140,12 +140,15 @@
initValue: initValue, initValue: initValue,
tips: "无", tips: "无",
toolbar: {show: true, list: ["CLEAR"]}, toolbar: {show: true, list: ["CLEAR"]},
data: e.data, data: res.data,
model: {"icon":"hidden","label":{"type":"text"}}, model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true, clickClose: true,
radio: true, radio: true,
tree: {show: true,"strict":false,"clickCheck":true,"clickExpand":false,expandedKeys: initValue}, tree: {show: true,"strict":false,"clickCheck":true,"clickExpand":false,expandedKeys: initValue},
}) });
if (res.code) {
return layui.popup.failure(res.msg);
}
} }
}); });
}); });
@ -165,6 +168,10 @@
}) })
}); });
// ajax产生错误
if (res.code) {
layui.popup.failure(res.msg);
}
} }
}); });

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"], 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","json"], 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", "多行文本"],["select", "下拉单选"],["selectMulti", "下拉多选"],["treeSelect", "树形单选"],["treeSelectMulti", "树形多选"],["datePicker", "日期选择"],["dateTimePicker", "日期时间选择"],["switch", "开关"],["upload", "上传文件"],["uploadImage", "上传图片"],["iconPicker", "图标选择"]], function (index, item) { }} {{# layui.each([["input", "文本框"],["inputNumber", "数字文本框"],["textArea", "多行文本"],["richText", "富文本"],["select", "下拉单选"],["selectMulti", "下拉多选"],["treeSelect", "树形单选"],["treeSelectMulti", "树形多选"],["datePicker", "日期选择"],["dateTimePicker", "日期时间选择"],["switch", "开关"],["upload", "上传文件"],["uploadImage", "上传图片"],["iconPicker", "图标选择"]], function (index, item) { }}
<option value="{{ item[0] }}" {{ d.control.toLocaleLowerCase()==item[0].toLocaleLowerCase()?'selected':''}}>{{ item[1] }}</option> <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", "范围查询"]], function (index, item) { }} {{# layui.each([["normal", "普通查询"], ["between", "范围查询"], ["like", "模糊查询"]], 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

@ -78,11 +78,11 @@
const CRUD_API = "/app/admin/table/crud"; const CRUD_API = "/app/admin/table/crud";
layui.use(["jquery", "xmSelect"], function() { layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({ layui.$.ajax({
url: "/app/admin/rule/select?format=tree&type=0,1", url: "/app/admin/rule/select?format=tree&type=0,1",
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
let value = layui.$("#pid").attr("value"); let value = layui.$("#pid").attr("value");
let initValue = value ? value.split(",") : []; let initValue = value ? value.split(",") : [];
layui.xmSelect.render({ layui.xmSelect.render({
@ -90,13 +90,16 @@
name: "pid", name: "pid",
initValue: initValue, initValue: initValue,
tips: "无", tips: "无",
data: e.data, data: res.data,
toolbar: {show: true, list: ["CLEAR"]}, toolbar: {show: true, list: ["CLEAR"]},
model: {"icon":"hidden","label":{"type":"text"}}, model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true, clickClose: true,
radio: true, radio: true,
tree: {show:true, strict:false, clickCheck:true, clickExpand:false}, tree: {show:true, strict:false, clickCheck:true, clickExpand:false},
}) });
if (res.code) {
return layui.popup.failure(res.msg);
}
} }
}); });
}); });
@ -122,7 +125,6 @@
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,6 +40,17 @@
<?=$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"], 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","json"], 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", "多行文本"],["select", "下拉单选"],["selectMulti", "下拉多选"],["treeSelect", "树形单选"],["treeSelectMulti", "树形多选"],["datePicker", "日期选择"],["dateTimePicker", "日期时间选择"],["switch", "开关"],["upload", "上传文件"],["uploadImage", "上传图片"],["iconPicker", "图标选择"]], function (index, item) { }} {{# layui.each([["input", "文本框"],["inputNumber", "数字文本框"],["textArea", "多行文本"],["richText", "富文本"],["select", "下拉单选"],["selectMulti", "下拉多选"],["treeSelect", "树形单选"],["treeSelectMulti", "树形多选"],["datePicker", "日期选择"],["dateTimePicker", "日期时间选择"],["switch", "开关"],["upload", "上传文件"],["uploadImage", "上传图片"],["iconPicker", "图标选择"]], function (index, item) { }}
<option value="{{ item[0] }}" {{ d.control.toLocaleLowerCase()==item[0].toLocaleLowerCase()?'selected':''}}>{{ item[1] }}</option> <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", "范围查询"]], function (index, item) { }} {{# layui.each([["normal", "普通查询"], ["between", "范围查询"], ["like", "模糊查询"]], 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>
@ -226,12 +226,17 @@
const SCHEMA_API = "/app/admin/table/schema"; const SCHEMA_API = "/app/admin/table/schema";
const TABLE_NAME = "<?=htmlspecialchars($table)?>"; const TABLE_NAME = "<?=htmlspecialchars($table)?>";
layui.use(["jquery"], function () { layui.use(["popup"], function () {
let $ = layui.$; let $ = layui.$;
$.ajax({ $.ajax({
url: SCHEMA_API + "?table=" + TABLE_NAME, url: SCHEMA_API + "?table=" + TABLE_NAME,
dataType: "json", dataType: "json",
success: function (res) { success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
// 表信息 // 表信息
$('input[name="table"]').val(res.data.table.name); $('input[name="table"]').val(res.data.table.name);
$('input[name="table_comment"]').val(res.data.table.comment); $('input[name="table_comment"]').val(res.data.table.comment);

View File

@ -42,14 +42,15 @@
const SELECT_API = "/app/admin/table/select" + location.search; const SELECT_API = "/app/admin/table/select" + location.search;
const UPDATE_API = "/app/admin/table/update"; const UPDATE_API = "/app/admin/table/update";
layui.use(["form", "util"], function () { layui.use(["form", "util", "popup"], function () {
let $ = layui.$; let $ = layui.$;
let util = layui.util; let util = layui.util;
$.ajax({ $.ajax({
url: SELECT_API, url: SELECT_API,
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
layui.each(e.data[0], function (key, value) {
layui.each(res.data[0], function (key, value) {
let obj = $('*[name="'+key+'"]'); let obj = $('*[name="'+key+'"]');
if (key === "password") { if (key === "password") {
obj.attr("placeholder", "不更新密码请留空"); obj.attr("placeholder", "不更新密码请留空");
@ -65,11 +66,25 @@
<?=$form->js(6)?> <?=$form->js(6)?>
// ajax返回失败
if (res.code) {
layui.popup.failure(res.msg);
}
} }
}); });
}); });
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

@ -80,8 +80,11 @@
$.ajax({ $.ajax({
url: SCHEMA_API, url: SCHEMA_API,
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
let forms = e.data.forms; if (res.code) {
return layui.popup.failure(res.msg);
}
let forms = res.data.forms;
let cols = [{ let cols = [{
type: "checkbox" type: "checkbox"
}]; }];
@ -173,7 +176,10 @@
title: "刷新", title: "刷新",
layEvent: "refresh", layEvent: "refresh",
icon: "layui-icon-refresh", icon: "layui-icon-refresh",
}, "filter", "print", "exports"] }, "filter", "print", "exports"],
done: function () {
layer.photos({photos: 'div[lay-id="data-table"]', anim: 5});
}
}); });
} }
@ -185,6 +191,9 @@
url: url, url: url,
dateType: "json", dateType: "json",
success: function (res) { success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
function travel(items) { function travel(items) {
for (let k in items) { for (let k in items) {
let item = items[k]; let item = items[k];
@ -228,6 +237,16 @@
} }
}); });
// 字段验证允许为空
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

@ -121,14 +121,14 @@
layui.$.ajax({ layui.$.ajax({
url: "/app/admin/dict/get/upload", url: "/app/admin/dict/get/upload",
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
let value = layui.$("#category").attr("value"); let value = layui.$("#category").attr("value");
let initValue = value ? value.split(",") : []; let initValue = value ? value.split(",") : [];
layui.xmSelect.render({ layui.xmSelect.render({
el: "#category", el: "#category",
name: "category", name: "category",
initValue: initValue, initValue: initValue,
data: e.data, data: res.data,
model: {"icon":"hidden","label":{"type":"text"}}, model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true, clickClose: true,
radio: true, radio: true,
@ -265,6 +265,9 @@
url: url, url: url,
dateType: "json", dateType: "json",
success: function (res) { success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
function travel(items) { function travel(items) {
for (let k in items) { for (let k in items) {
let item = items[k]; let item = items[k];
@ -312,6 +315,9 @@
// 表格顶部搜索事件 // 表格顶部搜索事件
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

@ -110,22 +110,25 @@
}) })
// 字段 类别 category // 字段 类别 category
layui.use(["jquery", "xmSelect"], function() { layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({ layui.$.ajax({
url: "/app/admin/dict/get/upload", url: "/app/admin/dict/get/upload",
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
let value = layui.$("#category").attr("value"); let value = layui.$("#category").attr("value");
let initValue = value ? value.split(",") : []; let initValue = value ? value.split(",") : [];
layui.xmSelect.render({ layui.xmSelect.render({
el: "#category", el: "#category",
name: "category", name: "category",
initValue: initValue, initValue: initValue,
data: e.data, data: res.data,
model: {"icon":"hidden","label":{"type":"text"}}, model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true, clickClose: true,
radio: true, radio: true,
}) });
if (res.code) {
layui.popup.failure(res.msg);
}
} }
}); });
}); });
@ -254,6 +257,9 @@
url: url, url: url,
dateType: "json", dateType: "json",
success: function (res) { success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
function travel(items) { function travel(items) {
for (let k in items) { for (let k in items) {
let item = items[k]; let item = items[k];
@ -299,6 +305,9 @@
// 表格顶部搜索事件 // 表格顶部搜索事件
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

@ -66,18 +66,21 @@
layui.$.ajax({ layui.$.ajax({
url: "/app/admin/dict/get/upload", url: "/app/admin/dict/get/upload",
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
let value = layui.$("#category").attr("value"); let value = layui.$("#category").attr("value");
let initValue = value ? value.split(",") : []; let initValue = value ? value.split(",") : [];
layui.xmSelect.render({ layui.xmSelect.render({
el: "#category", el: "#category",
name: "category", name: "category",
initValue: initValue, initValue: initValue,
data: e.data, data: res.data,
model: {"icon":"hidden","label":{"type":"text"}}, model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true, clickClose: true,
radio: true, radio: true,
}) });
if (res.code) {
return layui.popup.failure(res.msg);
}
} }
}); });
}); });

View File

@ -47,15 +47,15 @@
const UPDATE_API = "/app/admin/upload/update"; const UPDATE_API = "/app/admin/upload/update";
// 获取数据库记录 // 获取数据库记录
layui.use(["form", "util"], function () { layui.use(["form", "util", "popup"], function () {
let $ = layui.$; let $ = layui.$;
$.ajax({ $.ajax({
url: SELECT_API, url: SELECT_API,
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
// 给表单初始化数据 // 给表单初始化数据
layui.each(e.data[0], function (key, value) { layui.each(res.data[0], function (key, value) {
let obj = $('*[name="'+key+'"]'); let obj = $('*[name="'+key+'"]');
if (typeof obj[0] === "undefined" || !obj[0].nodeName) return; if (typeof obj[0] === "undefined" || !obj[0].nodeName) return;
obj.attr("value", value); obj.attr("value", value);
@ -78,26 +78,33 @@
}); });
// 字段 类别 category // 字段 类别 category
layui.use(["jquery", "xmSelect"], function() { layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({ layui.$.ajax({
url: "/app/admin/dict/get/upload", url: "/app/admin/dict/get/upload",
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
let value = layui.$("#category").attr("value"); let value = layui.$("#category").attr("value");
let initValue = value ? value.split(",") : []; let initValue = value ? value.split(",") : [];
layui.xmSelect.render({ layui.xmSelect.render({
el: "#category", el: "#category",
name: "category", name: "category",
initValue: initValue, initValue: initValue,
data: e.data, data: res.data,
model: {"icon":"hidden","label":{"type":"text"}}, model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true, clickClose: true,
radio: true, radio: true,
}) });
if (res.code) {
layui.popup.failure(res.msg);
}
} }
}); });
}); });
// ajax产生错误
if (res.code) {
layui.popup.failure(res.msg);
}
} }
}); });
}); });

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>
@ -179,22 +179,25 @@
const UPDATE_URL = "/app/admin/user/update"; const UPDATE_URL = "/app/admin/user/update";
// 字段 性别 sex // 字段 性别 sex
layui.use(["jquery", "xmSelect"], function() { layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({ layui.$.ajax({
url: "/app/admin/dict/get/sex", url: "/app/admin/dict/get/sex",
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
let value = layui.$("#sex").attr("value"); let value = layui.$("#sex").attr("value");
let initValue = value ? value.split(",") : []; let initValue = value ? value.split(",") : [];
layui.xmSelect.render({ layui.xmSelect.render({
el: "#sex", el: "#sex",
name: "sex", name: "sex",
initValue: initValue, initValue: initValue,
data: e.data, data: res.data,
model: {"icon":"hidden","label":{"type":"text"}}, model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true, clickClose: true,
radio: true, radio: true,
}) });
if (res.code) {
layui.popup.failure(res.msg);
}
} }
}); });
}); });
@ -284,7 +287,7 @@
field: "birthday", field: "birthday",
hide: true, hide: true,
},{ },{
title: "余额()", title: "余额()",
field: "money", field: "money",
hide: true, hide: true,
},{ },{
@ -336,7 +339,10 @@
$.post(UPDATE_API, postData, function (res) { $.post(UPDATE_API, postData, function (res) {
layer.close(load); layer.close(load);
if (res.code) { if (res.code) {
return layui.popup.failure(res.msg); return layui.popup.failure(res.msg, function () {
data.elem.checked = !data.elem.checked;
form.render();
});
} }
return layui.popup.success("操作成功"); return layui.popup.success("操作成功");
}) })
@ -388,6 +394,9 @@
url: url, url: url,
dateType: "json", dateType: "json",
success: function (res) { success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
function travel(items) { function travel(items) {
for (let k in items) { for (let k in items) {
let item = items[k]; let item = items[k];
@ -433,6 +442,9 @@
// 表格顶部搜索事件 // 表格顶部搜索事件
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>
@ -163,19 +163,22 @@
layui.$.ajax({ layui.$.ajax({
url: "/app/admin/dict/get/sex", url: "/app/admin/dict/get/sex",
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
let value = layui.$("#sex").attr("value"); let value = layui.$("#sex").attr("value");
let initValue = value ? value.split(",") : []; let initValue = value ? value.split(",") : [];
layui.xmSelect.render({ layui.xmSelect.render({
el: "#sex", el: "#sex",
name: "sex", name: "sex",
initValue: initValue, initValue: initValue,
data: e.data, data: res.data,
value: "1", value: "1",
model: {"icon":"hidden","label":{"type":"text"}}, model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true, clickClose: true,
radio: true, radio: true,
}) });
if (res.code) {
layui.popup.failure(res.msg);
}
} }
}); });
}); });

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>
@ -160,15 +160,15 @@
const UPDATE_API = "/app/admin/user/update"; const UPDATE_API = "/app/admin/user/update";
// 获取数据库记录 // 获取数据库记录
layui.use(["form", "util"], function () { layui.use(["form", "util", "popup"], function () {
let $ = layui.$; let $ = layui.$;
$.ajax({ $.ajax({
url: SELECT_API, url: SELECT_API,
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
// 给表单初始化数据 // 给表单初始化数据
layui.each(e.data[0], function (key, value) { layui.each(res.data[0], function (key, value) {
let obj = $('*[name="'+key+'"]'); let obj = $('*[name="'+key+'"]');
if (key === "password") { if (key === "password") {
obj.attr("placeholder", "不更新密码请留空"); obj.attr("placeholder", "不更新密码请留空");
@ -183,22 +183,25 @@
}); });
// 字段 性别 sex // 字段 性别 sex
layui.use(["jquery", "xmSelect"], function() { layui.use(["jquery", "xmSelect", "popup"], function() {
layui.$.ajax({ layui.$.ajax({
url: "/app/admin/dict/get/sex", url: "/app/admin/dict/get/sex",
dataType: "json", dataType: "json",
success: function (e) { success: function (res) {
let value = layui.$("#sex").attr("value"); let value = layui.$("#sex").attr("value");
let initValue = value ? value.split(",") : []; let initValue = value ? value.split(",") : [];
layui.xmSelect.render({ layui.xmSelect.render({
el: "#sex", el: "#sex",
name: "sex", name: "sex",
initValue: initValue, initValue: initValue,
data: e.data, data: res.data,
model: {"icon":"hidden","label":{"type":"text"}}, model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true, clickClose: true,
radio: true, radio: true,
}) });
if (res.code) {
layui.popup.failure(res.msg);
}
} }
}); });
}); });
@ -264,6 +267,10 @@
}); });
}) })
// ajax产生错误
if (res.code) {
layui.popup.failure(res.msg);
}
} }
}); });

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.5.0' 'version' => '0.6.12'
]; ];

View File

@ -15,10 +15,12 @@
use plugin\admin\app\controller\AccountController; use plugin\admin\app\controller\AccountController;
use plugin\admin\app\controller\DictController; use plugin\admin\app\controller\DictController;
use Webman\Route; use Webman\Route;
use support\Request;
Route::any('/app/admin/account/captcha/{type}', [AccountController::class, 'captcha']); Route::any('/app/admin/account/captcha/{type}', [AccountController::class, 'captcha']);
Route::any('/app/admin', function () {
return redirect('/app/admin/');
});
Route::any('/app/admin/dict/get/{name}', [DictController::class, 'get']); Route::any('/app/admin/dict/get/{name}', [DictController::class, 'get']);
Route::fallback(function (Request $request) {
return response($request->uri() . ' not found' , 404);
}, 'admin');

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() . '/resource/translations', 'path' => base_path() . '/plugin/admin/resource/translations'
]; ];

File diff suppressed because one or more lines are too long

View File

@ -4,16 +4,24 @@
layui.$(function () { layui.$(function () {
let $ = layui.$; let $ = layui.$;
$.ajax({ $.ajax({
url: "/app/admin/rule/permission-codes", url: "/app/admin/rule/permission",
dataType: "json", dataType: "json",
success: function (res) { success: function (res) {
let style = ''; let style = '';
let codes = res.data || []; let codes = res.data || [];
let isSupperAdmin = false;
// codes里有*,说明是超级管理员,拥有所有权限 // codes里有*,说明是超级管理员,拥有所有权限
if (codes.indexOf('*') !== -1) { if (codes.indexOf('*') !== -1) {
$("head").append("<style>*[permission]{display: initial}</style>"); $("head").append("<style>*[permission]{display: initial}</style>");
return; isSupperAdmin = true;
} }
if (self !== top) {
top.Admin.Account.isSupperAdmin = isSupperAdmin;
} else {
window.Admin.Account.isSupperAdmin = isSupperAdmin;
}
if (isSupperAdmin) return;
// 细分权限 // 细分权限
layui.each(codes, function (k, code) { layui.each(codes, function (k, code) {
codes[k] = '*[permission^="'+code+'"]'; codes[k] = '*[permission^="'+code+'"]';

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]); succFun(res[this.response.dataName]["url"]);
} else { } else {
failFun(res[this.response.msgName]); failFun(res[this.response.msgName]["url"]);
} }
} }
}; };

View File

@ -49,7 +49,7 @@ layui.define(['layer', 'table'], function (exports) {
} }
} }
var sort = function (s_pid, data) { /*var sort = function (s_pid, data) {
for (var i = 0; i < data.length; i++) { for (var i = 0; i < data.length; i++) {
if (data[i].pid == s_pid) { if (data[i].pid == s_pid) {
var len = mData.length; var len = mData.length;
@ -61,7 +61,41 @@ layui.define(['layer', 'table'], function (exports) {
} }
} }
}; };
sort(param.treeSpid, tNodes); sort(param.treeSpid, tNodes);*/
var map = {}; // 变更
for (var k in data) {
map[data[k].id] = data[k];
}
for (var j in map) {
if(map[j].pid && map[map[j].pid]) {
var parent = map[map[j].pid];
if (!parent.children) {
parent.children = [];
parent.isParent = true;
}
parent.children.push(map[j]);
}
}
var tree = [];
for (var l in map) {
if (!map[l].pid || !map[map[l].pid]) {
map[l].isRoot = true;
tree.push(map[l]);
}
}
function travel(item)
{
mData.push(item);
if (item.children) {
for (var g in item.children) {
travel(item.children[g]);
}
}
}
for (var h in tree) {
travel(tree[h]);
}
param.prevUrl = param.url; param.prevUrl = param.url;
param.url = undefined; param.url = undefined;
@ -194,10 +228,10 @@ layui.define(['layer', 'table'], function (exports) {
}, },
// 检查参数 // 检查参数
checkParam: function (param) { checkParam: function (param) {
if (!param.treeSpid && param.treeSpid != 0) { /*if (!param.treeSpid && param.treeSpid != 0) {
layer.msg('参数treeSpid不能为空', {icon: 5}); layer.msg('参数treeSpid不能为空', {icon: 5});
return false; return false;
} }*/
if (!param.treeIdName) { if (!param.treeIdName) {
layer.msg('参数treeIdName不能为空', {icon: 5}); layer.msg('参数treeIdName不能为空', {icon: 5});

View File

@ -92,7 +92,6 @@
treetable.render({ treetable.render({
treeColIndex: 1, treeColIndex: 1,
treeSpid: 0,
treeIdName: 'powerId', treeIdName: 'powerId',
treePidName: 'parentId', treePidName: 'parentId',
skin:'line', skin:'line',

View File

@ -104,7 +104,6 @@
window.render = function(){ window.render = function(){
treetable.render({ treetable.render({
treeColIndex: 1, treeColIndex: 1,
treeSpid: 0,
treeIdName: 'powerId', treeIdName: 'powerId',
treePidName: 'parentId', treePidName: 'parentId',
skin:'line', skin:'line',

File diff suppressed because one or more lines are too long