webman/plugin/admin/app/controller/TableController.php

1681 lines
60 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace plugin\admin\app\controller;
use Doctrine\Inflector\InflectorFactory;
use Illuminate\Database\Schema\Blueprint;
use plugin\admin\app\common\Layui;
use plugin\admin\app\common\Util;
use plugin\admin\app\model\Role;
use plugin\admin\app\model\Rule;
use plugin\admin\app\model\Option;
use support\exception\BusinessException;
use support\Request;
use support\Response;
use Throwable;
class TableController extends Base
{
/**
* 不需要鉴权的方法
* @var string[]
*/
protected $noNeedAuth = ['types'];
/**
* 浏览
* @return Response
* @throws Throwable
*/
public function index(): Response
{
return raw_view('table/index');
}
/**
* 查看表
* @param Request $request
* @return Response
* @throws BusinessException|Throwable
*/
public function view(Request $request): Response
{
$table = $request->get('table');
$table = Util::filterAlphaNum($table);
$form = Layui::buildForm($table, 'search');
$table_info = Util::getSchema($table, 'table');
$primary_key = $table_info['primary_key'][0] ?? null;
return raw_view("table/view", [
'form' => $form,
'table' => $table,
'primary_key' => $primary_key,
]);
}
/**
* 查询表
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function show(Request $request): Response
{
$table_name = $request->get('table_name','');
$limit = (int)$request->get('limit', 10);
$page = (int)$request->get('page', 1);
$offset = ($page - 1) * $limit;
$database = config('database.connections')['plugin.admin.mysql']['database'];
$field = $request->get('field', 'TABLE_NAME');
$field = Util::filterAlphaNum($field);
$order = $request->get('order', 'asc');
$allow_column = ['TABLE_NAME', 'TABLE_COMMENT', 'ENGINE', 'TABLE_ROWS', 'CREATE_TIME', 'UPDATE_TIME', 'TABLE_COLLATION'];
if (!in_array($field, $allow_column)) {
$field = 'TABLE_NAME';
}
$order = $order === 'asc' ? 'asc' : 'desc';
$total = Util::db()->select("SELECT count(*)total FROM information_schema.`TABLES` WHERE TABLE_SCHEMA='$database' AND TABLE_NAME like '%{$table_name}%'")[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' AND TABLE_NAME like '%{$table_name}%' order by $field $order limit $offset,$limit");
if ($tables) {
$table_names = array_column($tables, 'TABLE_NAME');
$table_rows_count = [];
foreach ($table_names as $table_name) {
$table_rows_count[$table_name] = Util::db()->table($table_name)->count();
}
foreach ($tables as $key => $table) {
$tables[$key]->TABLE_ROWS = $table_rows_count[$table->TABLE_NAME] ?? $table->TABLE_ROWS;
}
}
return json(['code' => 0, 'msg' => 'ok', 'count' => $total, 'data' => $tables]);
}
/**
* 创建表
* @param Request $request
* @return Response
* @throws BusinessException|Throwable
*/
public function create(Request $request): Response
{
if ($request->method() === 'GET') {
return raw_view("table/create", []);
}
$data = $request->post();
$table_name = Util::filterAlphaNum($data['table']);
$table_comment = Util::pdoQuote($data['table_comment']);
$columns = $data['columns'];
$forms = $data['forms'];
$keys = $data['keys'];
$primary_key_count = 0;
foreach ($columns as $index => $item) {
$columns[$index]['field'] = trim($item['field']);
if (!$item['field']) {
unset($columns[$index]);
continue;
}
$columns[$index]['primary_key'] = !empty($item['primary_key']);
if ($columns[$index]['primary_key']) {
$primary_key_count++;
}
$columns[$index]['auto_increment'] = !empty($item['auto_increment']);
$columns[$index]['nullable'] = !empty($item['nullable']);
if ($item['default'] === '') {
$columns[$index]['default'] = null;
} else if ($item['default'] === "''") {
$columns[$index]['default'] = '';
}
}
if ($primary_key_count > 1) {
throw new BusinessException('不支持复合主键');
}
foreach ($forms as $index => $item) {
if (!$item['field']) {
unset($forms[$index]);
continue;
}
$forms[$index]['form_show'] = !empty($item['form_show']);
$forms[$index]['list_show'] = !empty($item['list_show']);
$forms[$index]['enable_sort'] = !empty($item['enable_sort']);
$forms[$index]['searchable'] = !empty($item['searchable']);
}
foreach ($keys as $index => $item) {
if (!$item['name'] || !$item['columns']) {
unset($keys[$index]);
}
}
Util::schema()->create($table_name, function (Blueprint $table) use ($columns) {
$type_method_map = Util::methodControlMap();
foreach ($columns as $column) {
if (!isset($column['type'])) {
throw new BusinessException("请为{$column['field']}选择类型");
}
if (!isset($type_method_map[$column['type']])) {
throw new BusinessException("不支持的类型{$column['type']}");
}
$this->createColumn($column, $table);
}
$table->charset = 'utf8mb4';
$table->collation = 'utf8mb4_general_ci';
$table->engine = 'InnoDB';
});
Util::db()->statement("ALTER TABLE `$table_name` COMMENT $table_comment");
// 索引
Util::schema()->table($table_name, function (Blueprint $table) use ($keys) {
foreach ($keys as $key) {
$name = $key['name'];
$columns = is_array($key['columns']) ? $key['columns'] : explode(',', $key['columns']);
$type = $key['type'];
if ($type == 'unique') {
$table->unique($columns, $name);
continue;
}
$table->index($columns, $name);
}
});
$form_schema_map = [];
foreach ($forms as $item) {
$form_schema_map[$item['field']] = $item;
}
$form_schema_map = json_encode($form_schema_map, JSON_UNESCAPED_UNICODE);
$this->updateSchemaOption($table_name, $form_schema_map);
return $this->json(0, 'ok');
}
/**
* 修改表
* @param Request $request
* @return Response
* @throws BusinessException|Throwable
*/
public function modify(Request $request): Response
{
if ($request->method() === 'GET') {
return raw_view("table/modify", ['table' => $request->get('table')]);
}
$data = $request->post();
$old_table_name = Util::filterAlphaNum($data['old_table']);
$table_name = Util::filterAlphaNum($data['table']);
$table_comment = $data['table_comment'];
$columns = $data['columns'];
$forms = $data['forms'];
$keys = $data['keys'];
$primary_key = null;
$auto_increment_column = null;
$schema = Util::getSchema($old_table_name);
$old_columns = $schema['columns'];
$old_primary_key = $schema['table']['primary_key'][0] ?? null;
$primary_key_count = $auto_increment_count = 0;
foreach ($columns as $index => $item) {
$columns[$index]['field'] = trim($item['field']);
if (!$item['field']) {
unset($columns[$index]);
continue;
}
$field = $item['field'];
$columns[$index]['auto_increment'] = !empty($item['auto_increment']);
$columns[$index]['nullable'] = !empty($item['nullable']);
$columns[$index]['primary_key'] = !empty($item['primary_key']);
if ($columns[$index]['primary_key']) {
$primary_key = $item['field'];
$columns[$index]['nullable'] = false;
$primary_key_count++;
}
if ($item['default'] === '') {
$columns[$index]['default'] = null;
} else if ($item['default'] === "''") {
$columns[$index]['default'] = '';
}
if ($columns[$index]['auto_increment']) {
$auto_increment_count++;
if (!isset($old_columns[$field]) || !$old_columns[$field]['auto_increment']) {
$auto_increment_column = $columns[$index];
unset($auto_increment_column['old_field']);
$columns[$index]['auto_increment'] = false;
}
}
}
if ($primary_key_count > 1) {
throw new BusinessException('不支持复合主键');
}
if ($auto_increment_count > 1) {
throw new BusinessException('一个表只能有一个自增字段并且必须为key');
}
foreach ($forms as $index => $item) {
if (!$item['field']) {
unset($forms[$index]);
continue;
}
$forms[$index]['form_show'] = !empty($item['form_show']);
$forms[$index]['list_show'] = !empty($item['list_show']);
$forms[$index]['enable_sort'] = !empty($item['enable_sort']);
$forms[$index]['searchable'] = !empty($item['searchable']);
}
foreach ($keys as $index => $item) {
if (!$item['name'] || !$item['columns']) {
unset($keys[$index]);
}
}
// 改表名
if ($table_name != $old_table_name) {
Util::schema()->rename($old_table_name, $table_name);
}
$type_method_map = Util::methodControlMap();
foreach ($columns as $column) {
if (!isset($type_method_map[$column['type']])) {
throw new BusinessException("不支持的类型{$column['type']}");
}
$field = $column['old_field'] ?? $column['field'] ;
$old_column = $old_columns[$field] ?? [];
// 类型更改
foreach ($old_column as $key => $value) {
if (key_exists($key, $column) && ($column[$key] != $value || ($key === 'default' && $column[$key] !== $value))) {
$this->modifyColumn($column, $table_name);
break;
}
}
}
$table = Util::getSchema($table_name, 'table');
if ($table_comment !== $table['comment']) {
$table_comment = Util::pdoQuote($table_comment);
Util::db()->statement("ALTER TABLE `$table_name` COMMENT $table_comment");
}
$old_columns = Util::getSchema($table_name, 'columns');
Util::schema()->table($table_name, function (Blueprint $table) use ($columns, $old_columns, $keys, $table_name) {
foreach ($columns as $column) {
$field = $column['field'];
// 新字段
if (!isset($old_columns[$field])) {
$this->createColumn($column, $table);
}
}
// 更新索引名字
foreach ($keys as $key) {
if (!empty($key['old_name']) && $key['old_name'] !== $key['name']) {
$table->renameIndex($key['old_name'], $key['name']);
}
}
});
// 找到删除的字段
$old_columns = Util::getSchema($table_name, 'columns');
$exists_column_names = array_column($columns, 'field', 'field');
$old_columns_names = array_column($old_columns, 'field');
$drop_column_names = array_diff($old_columns_names, $exists_column_names);
$drop_column_names = Util::filterAlphaNum($drop_column_names);
foreach ($drop_column_names as $drop_column_name) {
Util::db()->statement("ALTER TABLE `$table_name` DROP COLUMN `$drop_column_name`");
}
$old_keys = Util::getSchema($table_name, 'keys');
Util::schema()->table($table_name, function (Blueprint $table) use ($keys, $old_keys, $table_name) {
foreach ($keys as $key) {
$key_name = $key['name'];
$old_key = $old_keys[$key_name] ?? [];
// 如果索引有变动,则删除索引,重新建立索引
if ($old_key && ($key['type'] != $old_key['type'] || $key['columns'] != implode(',', $old_key['columns']))) {
$old_key = [];
unset($old_keys[$key_name]);
echo "Drop Index $key_name\n";
$table->dropIndex($key_name);
}
// 重新建立索引
if (!$old_key) {
$name = $key['name'];
$columns = is_array($key['columns']) ? $key['columns'] : explode(',', $key['columns']);
$type = $key['type'];
if ($type == 'unique') {
$table->unique($columns, $name);
continue;
}
echo "Create Index $key_name\n";
$table->index($columns, $name);
}
}
// 找到删除的索引
$exists_key_names = array_column($keys, 'name', 'name');
$old_keys_names = array_column($old_keys, 'name');
$drop_keys_names = array_diff($old_keys_names, $exists_key_names);
foreach ($drop_keys_names as $name) {
echo "Drop Index $name\n";
$table->dropIndex($name);
}
});
// 变更主键
if ($old_primary_key != $primary_key) {
if ($old_primary_key) {
Util::db()->statement("ALTER TABLE `$table_name` DROP PRIMARY KEY");
}
if ($primary_key) {
$primary_key = Util::filterAlphaNum($primary_key);
Util::db()->statement("ALTER TABLE `$table_name` ADD PRIMARY KEY(`$primary_key`)");
}
}
// 一个表只能有一个 auto_increment 字段并且是key所以需要在最后设置
if ($auto_increment_column) {
$this->modifyColumn($auto_increment_column, $table_name);
}
$form_schema_map = [];
foreach ($forms as $item) {
$form_schema_map[$item['field']] = $item;
}
$form_schema_map = json_encode($form_schema_map, JSON_UNESCAPED_UNICODE);
$option_name = $this->updateSchemaOption($table_name, $form_schema_map);
return $this->json(0,$option_name);
}
/**
* 一键菜单
* @param Request $request
* @return Response
* @throws BusinessException|Throwable
*/
public function crud(Request $request): Response
{
$table_name = $request->input('table');
Util::checkTableName($table_name);
$prefix = 'wa_';
$table_basename = strpos($table_name, $prefix) === 0 ? substr($table_name, strlen($prefix)) : $table_name;
$inflector = InflectorFactory::create()->build();
$model_class = $inflector->classify($inflector->singularize($table_basename));
$base_path = '/plugin/admin/app';
if ($request->method() === 'GET') {
return raw_view("table/crud", [
'table' => $table_name,
'model' => "$base_path/model/$model_class.php",
'controller' => "$base_path/controller/{$model_class}Controller.php",
]);
}
$title = $request->post('title');
$pid = $request->post('pid', 0);
$icon = $request->post('icon', '');
$controller_file = '/' . trim($request->post('controller', ''), '/');
$model_file = '/' . trim($request->post('model', ''), '/');
$overwrite = $request->post('overwrite');
if ($controller_file === '/' || $model_file === '/') {
return $this->json(1, '控制器和model不能为空');
}
$controller_info = pathinfo($controller_file);
$model_info = pathinfo($model_file);
$controller_path = Util::filterPath($controller_info['dirname'] ?? '');
$model_path = Util::filterPath($model_info['dirname'] ?? '');
$controller_file_name = Util::filterAlphaNum($controller_info['filename'] ?? '');
$model_file_name = Util::filterAlphaNum($model_info['filename'] ?? '');
if ($controller_info['extension'] !== 'php' || $model_info['extension'] !== 'php' ) {
return $this->json(1, '控制器和model必须以.php为后缀');
}
$pid = (int)$pid;
if ($pid) {
$parent_menu = Rule::find($pid);
if (!$parent_menu) {
return $this->json(1, '父菜单不存在');
}
}
if (!$overwrite) {
if (is_file(base_path($controller_file))) {
return $this->json(1, "$controller_file 已经存在");
}
if (is_file(base_path($model_file))) {
return $this->json(1, "$model_file 已经存在");
}
}
$explode = explode('/', trim($controller_path, '/'));
$plugin = '';
if (strpos(strtolower($controller_file), '/controller/') === false) {
return $this->json(2, '控制器必须在controller目录下');
}
if ($explode[0] === 'plugin') {
if (count($explode) < 4) {
return $this->json(2, '控制器参数非法');
}
$plugin = $explode[1];
if (strtolower($explode[2]) !== 'app') {
return $this->json(2, '控制器必须在app目录');
}
$app = strtolower($explode[3]) !== 'controller' ? $explode[3] : '';
} else {
if (count($explode) < 2) {
return $this->json(3, '控制器参数非法');
}
if (strtolower($explode[0]) !== 'app') {
return $this->json(3, '控制器必须在app目录');
}
$app = strtolower($explode[1]) !== 'controller' ? $explode[1] : '';
}
Util::pauseFileMonitor();
try {
$model_class = $model_file_name;
$model_namespace = str_replace('/', '\\', trim($model_path, '/'));
// 创建model
$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_class = $controller_file_name;
$controller_namespace = str_replace('/', '\\', trim($controller_path, '/'));
// 创建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 = str_replace('_', '-', $inflector->tableize($controller_url_name));
if ($plugin) {
array_splice($explode, 0, 2);
}
array_shift($explode);
if ($app) {
array_shift($explode);
}
foreach ($explode as $index => $item) {
if (strtolower($item) === 'controller') {
unset($explode[$index]);
}
}
$controller_base = implode('/', $explode);
$controller_class_with_namespace = "$controller_namespace\\$controller_class";
$template_path = $controller_base ? "$controller_base/$controller_url_name" : $controller_url_name;
$this->createController($controller_class, $controller_namespace, base_path($controller_file), $model_class, $model_namespace, $title, $template_path);
// 创建模版
$template_file_path = ($plugin ? "/plugin/$plugin" : '') . '/app/' . ($app ? "$app/" : '') . 'view/' . $template_path;
$model_class_with_namespace = "$model_namespace\\$model_class";
$primary_key = (new $model_class_with_namespace)->getKeyName();
$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");
} finally {
Util::resumeFileMonitor();
}
$menu = Rule::where('key', $controller_class_with_namespace)->first();
if (!$menu) {
$menu = new Rule;
}
$menu->pid = $pid;
$menu->key = $controller_class_with_namespace;
$menu->title = $title;
$menu->icon = $icon;
$menu->href = "$url_path_base/index";
$menu->save();
$roles = admin('roles');
$rules = Role::whereIn('id', $roles)->pluck('rules');
$rule_ids = [];
foreach ($rules as $rule_string) {
if (!$rule_string) {
continue;
}
$rule_ids = array_merge($rule_ids, explode(',', $rule_string));
}
// 不是超级管理员,则需要给当前管理员这个菜单的权限
if (!in_array('*', $rule_ids) && $roles){
$role = Role::find(current($roles));
if ($role) {
$role->rules .= ",{$menu->id}";
}
$role->save();
}
return $this->json(0);
}
/**
* 创建model
* @param $class
* @param $namespace
* @param $file
* @param $table
* @return void
*/
protected function createModel($class, $namespace, $file, $table)
{
$this->mkdir($file);
$table_val = "'$table'";
$pk = 'id';
$properties = '';
$timestamps = '';
$incrementing = '';
$columns = [];
try {
$database = config('database.connections')['plugin.admin.mysql']['database'];
//plugin.admin.mysql
foreach (Util::db()->select("select COLUMN_NAME,DATA_TYPE,COLUMN_KEY,COLUMN_COMMENT from INFORMATION_SCHEMA.COLUMNS where table_name = '$table' and table_schema = '$database' order by ORDINAL_POSITION") as $item) {
if ($item->COLUMN_KEY === 'PRI') {
$pk = $item->COLUMN_NAME;
$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);
$properties .= " * @property $type \${$item->COLUMN_NAME} {$item->COLUMN_COMMENT}\n";
$columns[$item->COLUMN_NAME] = $item->COLUMN_NAME;
}
} catch (Throwable $e) {echo $e;}
if (!isset($columns['created_at']) || !isset($columns['updated_at'])) {
$timestamps = <<<EOF
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public \$timestamps = false;
EOF;
}
$properties = rtrim($properties) ?: ' *';
$model_content = <<<EOF
<?php
namespace $namespace;
use plugin\admin\app\model\Base;
/**
$properties
*/
class $class extends Base
{
/**
* The table associated with the model.
*
* @var string
*/
protected \$table = $table_val;
/**
* The primary key associated with the table.
*
* @var string
*/
protected \$primaryKey = '$pk';
$timestamps
$incrementing
}
EOF;
file_put_contents($file, $model_content);
}
/**
* 创建控制器
* @param $controller_class
* @param $namespace
* @param $file
* @param $model_class
* @param $model_namespace
* @param $name
* @param $template_path
* @return void
*/
protected function createController($controller_class, $namespace, $file, $model_class, $model_namespace, $name, $template_path)
{
$model_class_alias = $model_class;
if (strtolower($model_class) === strtolower($controller_class)) {
$model_class_alias = "$model_class as {$model_class}Model";
$model_class = "{$model_class}Model";
}
$this->mkdir($file);
$controller_content = <<<EOF
<?php
namespace $namespace;
use support\Request;
use support\Response;
use $model_namespace\\$model_class_alias;
use plugin\admin\app\controller\Crud;
use support\\exception\BusinessException;
/**
* $name
*/
class $controller_class extends Crud
{
/**
* @var $model_class
*/
protected \$model = null;
/**
* 构造函数
* @return void
*/
public function __construct()
{
\$this->model = new $model_class;
}
/**
* 浏览
* @return Response
*/
public function index(): Response
{
return view('$template_path/index');
}
/**
* 插入
* @param Request \$request
* @return Response
* @throws BusinessException
*/
public function insert(Request \$request): Response
{
if (\$request->method() === 'POST') {
return parent::insert(\$request);
}
return view('$template_path/insert');
}
/**
* 更新
* @param Request \$request
* @return Response
* @throws BusinessException
*/
public function update(Request \$request): Response
{
if (\$request->method() === 'POST') {
return parent::update(\$request);
}
return view('$template_path/update');
}
}
EOF;
file_put_contents($file, $controller_content);
}
/**
* 创建控制器
* @param $template_file_path
* @param $table
* @param $template_path
* @param $url_path_base
* @param $primary_key
* @param $controller_class_with_namespace
* @return void
*/
protected function createTemplate($template_file_path, $table, $url_path_base, $primary_key, $controller_class_with_namespace)
{
$this->mkdir($template_file_path . '/index.html');
$code_base = Util::controllerToUrlPath($controller_class_with_namespace);
$code_base = str_replace('/', '.', trim($code_base, '/'));
$form = Layui::buildForm($table, 'search');
$html = $form->html(3);
$html = $html ? <<<EOF
<div class="layui-card">
<div class="layui-card-body">
<form class="layui-form top-search-from">
$html
<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>
EOF
: '';
$html = str_replace("\n", "\n" . str_repeat(' ', 2), $html);
$js = $form->js(3);
$table_js = Layui::buildTable($table, 4);
$template_content = <<<EOF
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>浏览页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body class="pear-container">
<!-- 顶部查询表单 -->
$html
<!-- 数据表格 -->
<div class="layui-card">
<div class="layui-card-body">
<table id="data-table" lay-filter="data-table"></table>
</div>
</div>
<!-- 表格顶部工具栏 -->
<script type="text/html" id="table-toolbar">
<button class="pear-btn pear-btn-primary pear-btn-md" lay-event="add" permission="$code_base.insert">
<i class="layui-icon layui-icon-add-1"></i>新增
</button>
<button class="pear-btn pear-btn-danger pear-btn-md" lay-event="batchRemove" permission="$code_base.delete">
<i class="layui-icon layui-icon-delete"></i>删除
</button>
</script>
<!-- 表格行工具栏 -->
<script type="text/html" id="table-bar">
<button class="pear-btn pear-btn-xs tool-btn" lay-event="edit" permission="$code_base.update">编辑</button>
<button class="pear-btn pear-btn-xs tool-btn" lay-event="remove" permission="$code_base.delete">删除</button>
</script>
<script src="/app/admin/component/layui/layui.js?v=2.8.12"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script src="/app/admin/admin/js/common.js"></script>
<script>
// 相关常量
const PRIMARY_KEY = "$primary_key";
const SELECT_API = "$url_path_base/select";
const UPDATE_API = "$url_path_base/update";
const DELETE_API = "$url_path_base/delete";
const INSERT_URL = "$url_path_base/insert";
const UPDATE_URL = "$url_path_base/update";
$js
// 表格渲染
layui.use(["table", "form", "common", "popup", "util"], function() {
let table = layui.table;
let form = layui.form;
let $ = layui.$;
let common = layui.common;
let util = layui.util;
$table_js
// 编辑或删除行事件
table.on("tool(data-table)", function(obj) {
if (obj.event === "remove") {
remove(obj);
} else if (obj.event === "edit") {
edit(obj);
}
});
// 表格顶部工具栏事件
table.on("toolbar(data-table)", function(obj) {
if (obj.event === "add") {
add();
} else if (obj.event === "refresh") {
refreshTable();
} else if (obj.event === "batchRemove") {
batchRemove(obj);
}
});
// 表格顶部搜索事件
form.on("submit(table-query)", function(data) {
table.reload("data-table", {
page: {
curr: 1
},
where: data.field
})
return false;
});
// 表格顶部搜索重置事件
form.on("submit(table-reset)", function(data) {
table.reload("data-table", {
where: []
})
});
// 字段允许为空
form.verify({
phone: [/(^$)|^1\d{10}$/, "请输入正确的手机号"],
email: [/(^$)|^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/, "邮箱格式不正确"],
url: [/(^$)|(^#)|(^http(s*):\/\/[^\s]+\.[^\s]+)/, "链接格式不正确"],
number: [/(^$)|^\d+$/,'只能填写数字'],
date: [/(^$)|^(\d{4})[-\/](\d{1}|0\d{1}|1[0-2])([-\/](\d{1}|0\d{1}|[1-2][0-9]|3[0-1]))*$/, "日期格式不正确"],
identity: [/(^$)|(^\d{15}$)|(^\d{17}(x|X|\d)$)/, "请输入正确的身份证号"]
});
// 表格排序事件
table.on("sort(data-table)", function(obj){
table.reload("data-table", {
initSort: obj,
scrollPos: "fixed",
where: {
field: obj.field,
order: obj.type
}
});
});
// 表格新增数据
let add = function() {
layer.open({
type: 2,
title: "新增",
shade: 0.1,
maxmin: true,
area: [common.isModile()?"100%":"500px", common.isModile()?"100%":"450px"],
content: INSERT_URL
});
}
// 表格编辑数据
let edit = function(obj) {
let value = obj.data[PRIMARY_KEY];
layer.open({
type: 2,
title: "修改",
shade: 0.1,
maxmin: true,
area: [common.isModile()?"100%":"500px", common.isModile()?"100%":"450px"],
content: UPDATE_URL + "?" + PRIMARY_KEY + "=" + value
});
}
// 删除一行
let remove = function(obj) {
return doRemove(obj.data[PRIMARY_KEY]);
}
// 删除多行
let batchRemove = function(obj) {
let checkIds = common.checkField(obj, PRIMARY_KEY);
if (checkIds === "") {
layui.popup.warning("未选中数据");
return false;
}
doRemove(checkIds.split(","));
}
// 执行删除
let doRemove = function (ids) {
let data = {};
data[PRIMARY_KEY] = ids;
layer.confirm("确定删除?", {
icon: 3,
title: "提示"
}, function(index) {
layer.close(index);
let loading = layer.load();
$.ajax({
url: DELETE_API,
data: data,
dataType: "json",
type: "post",
success: function(res) {
layer.close(loading);
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", refreshTable);
}
})
});
}
// 刷新表格数据
window.refreshTable = function() {
table.reloadData("data-table", {
scrollPos: "fixed",
done: function (res, curr) {
if (curr > 1 && res.data && !res.data.length) {
curr = curr - 1;
table.reloadData("data-table", {
page: {
curr: curr
},
})
}
}
});
}
})
</script>
</body>
</html>
EOF;
file_put_contents("$template_file_path/index.html", $template_content);
$form = Layui::buildForm($table);
$html = $form->html(5);
$js = $form->js(3);
$template_content = <<<EOF
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>新增页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/component/jsoneditor/css/jsoneditor.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body>
<form class="layui-form" action="">
<div class="mainBox">
<div class="main-container mr-5">
$html
</div>
</div>
<div class="bottom">
<div class="button-container">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit=""
lay-filter="save">
提交
</button>
<button type="reset" class="pear-btn pear-btn-md">
重置
</button>
</div>
</div>
</form>
<script src="/app/admin/component/layui/layui.js?v=2.8.12"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/component/jsoneditor/jsoneditor.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
// 相关接口
const INSERT_API = "$url_path_base/insert";
$js
//提交事件
layui.use(["form", "popup"], function () {
// 字段验证允许为空
layui.form.verify({
phone: [/(^$)|^1\d{10}$/, "请输入正确的手机号"],
email: [/(^$)|^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/, "邮箱格式不正确"],
url: [/(^$)|(^#)|(^http(s*):\/\/[^\s]+\.[^\s]+)/, "链接格式不正确"],
number: [/(^$)|^\d+$/,'只能填写数字'],
date: [/(^$)|^(\d{4})[-\/](\d{1}|0\d{1}|1[0-2])([-\/](\d{1}|0\d{1}|[1-2][0-9]|3[0-1]))*$/, "日期格式不正确"],
identity: [/(^$)|(^\d{15}$)|(^\d{17}(x|X|\d)$)/, "请输入正确的身份证号"]
});
layui.form.on("submit(save)", function (data) {
layui.$.ajax({
url: INSERT_API,
type: "POST",
dateType: "json",
data: data.field,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", function () {
parent.refreshTable();
parent.layer.close(parent.layer.getFrameIndex(window.name));
});
}
});
return false;
});
});
</script>
</body>
</html>
EOF;
file_put_contents("$template_file_path/insert.html", $template_content);
$form = Layui::buildForm($table, 'update');
$html = $form->html(5);
$js = $form->js(6);
$template_content = <<<EOF
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>更新页面</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/component/jsoneditor/css/jsoneditor.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body>
<form class="layui-form">
<div class="mainBox">
<div class="main-container mr-5">
$html
</div>
</div>
<div class="bottom">
<div class="button-container">
<button type="submit" class="pear-btn pear-btn-primary pear-btn-md" lay-submit="" lay-filter="save">
提交
</button>
<button type="reset" class="pear-btn pear-btn-md">
重置
</button>
</div>
</div>
</form>
<script src="/app/admin/component/layui/layui.js?v=2.8.12"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/component/jsoneditor/jsoneditor.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
// 相关接口
const PRIMARY_KEY = "$primary_key";
const SELECT_API = "$url_path_base/select" + location.search;
const UPDATE_API = "$url_path_base/update";
// 获取数据库记录
layui.use(["form", "util", "popup"], function () {
let $ = layui.$;
$.ajax({
url: SELECT_API,
dataType: "json",
success: function (res) {
// 给表单初始化数据
layui.each(res.data[0], function (key, value) {
let obj = $('*[name="'+key+'"]');
if (key === "password") {
obj.attr("placeholder", "不更新密码请留空");
return;
}
if (typeof obj[0] === "undefined" || !obj[0].nodeName) return;
if (obj[0].nodeName.toLowerCase() === "textarea") {
obj.val(value);
} else {
obj.attr("value", value);
obj[0].value = value;
}
});
$js
// ajax返回失败
if (res.code) {
layui.popup.failure(res.msg);
}
}
});
});
//提交事件
layui.use(["form", "popup"], function () {
// 字段验证允许为空
layui.form.verify({
phone: [/(^$)|^1\d{10}$/, "请输入正确的手机号"],
email: [/(^$)|^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/, "邮箱格式不正确"],
url: [/(^$)|(^#)|(^http(s*):\/\/[^\s]+\.[^\s]+)/, "链接格式不正确"],
number: [/(^$)|^\d+$/,'只能填写数字'],
date: [/(^$)|^(\d{4})[-\/](\d{1}|0\d{1}|1[0-2])([-\/](\d{1}|0\d{1}|[1-2][0-9]|3[0-1]))*$/, "日期格式不正确"],
identity: [/(^$)|(^\d{15}$)|(^\d{17}(x|X|\d)$)/, "请输入正确的身份证号"]
});
layui.form.on("submit(save)", function (data) {
data.field[PRIMARY_KEY] = layui.url().search[PRIMARY_KEY];
layui.$.ajax({
url: UPDATE_API,
type: "POST",
dateType: "json",
data: data.field,
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("操作成功", function () {
parent.refreshTable();
parent.layer.close(parent.layer.getFrameIndex(window.name));
});
}
});
return false;
});
});
</script>
</body>
</html>
EOF;
file_put_contents("$template_file_path/update.html", $template_content);
}
/**
* 创建目录
* @param $file
* @return void
*/
protected function mkdir($file)
{
$path = pathinfo($file, PATHINFO_DIRNAME);
if (!is_dir($path)) {
mkdir($path, 0777, true);
}
}
/**
* 查询记录
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function select(Request $request): Response
{
$page = $request->get('page', 1);
$field = $request->get('field');
$order = $request->get('order', 'asc');
$table = Util::filterAlphaNum($request->get('table', ''));
$format = $request->get('format', 'normal');
$limit = $request->get('limit', $format === 'tree' ? 5000 : 10);
$allow_column = Util::db()->select("desc `$table`");
if (!$allow_column) {
return $this->json(2, '表不存在');
}
$allow_column = array_column($allow_column, 'Field', 'Field');
if (!in_array($field, $allow_column)) {
$field = current($allow_column);
}
$order = $order === 'asc' ? 'asc' : 'desc';
$paginator = Util::db()->table($table);
foreach ($request->get() as $column => $value) {
if ($value === '') {
continue;
}
if (isset($allow_column[$column])) {
if (is_array($value)) {
if ($value[0] === 'like') {
$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);
}
}
} else {
$paginator = $paginator->where($column, $value);
}
}
}
$paginator = $paginator->orderBy($field, $order)->paginate($limit, '*', 'page', $page);
$items = $paginator->items();
if ($format == 'tree') {
$items_map = [];
foreach ($items as $item) {
$items_map[$item->id] = (array)$item;
}
$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;
}
}
$items = $formatted_items;
}
return json(['code' => 0, 'msg' => 'ok', 'count' => $paginator->total(), 'data' => $items]);
}
/**
* 插入记录
* @param Request $request
* @return Response
* @throws BusinessException|Throwable
*/
public function insert(Request $request): Response
{
if ($request->method() === 'GET') {
$table = $request->get('table');
$form = Layui::buildForm($table);
return raw_view("table/insert", [
'form' => $form,
'table' => $table
]);
}
$table = Util::filterAlphaNum($request->input('table', ''));
$data = $request->post();
$allow_column = Util::db()->select("desc `$table`");
if (!$allow_column) {
throw new BusinessException('表不存在', 2);
}
$columns = array_column($allow_column, 'Type', 'Field');
foreach ($data as $col => $item) {
if (!isset($columns[$col])) {
unset($data[$col]);
continue;
}
// 非字符串类型传空则为null
if ($item === '' && strpos(strtolower($columns[$col]), 'varchar') === false && strpos(strtolower($columns[$col]), 'text') === false) {
$data[$col] = null;
}
if (is_array($item)) {
$data[$col] = implode(',', $item);
continue;
}
if ($col === 'password') {
$data[$col] = Util::passwordHash($item);
}
}
$datetime = date('Y-m-d H:i:s');
if (isset($columns['created_at']) && empty($data['created_at'])) {
$data['created_at'] = $datetime;
}
if (isset($columns['updated_at']) && empty($data['updated_at'])) {
$data['updated_at'] = $datetime;
}
$id = Util::db()->table($table)->insertGetId($data);
return $this->json(0, $id);
}
/**
* 更新记录
* @param Request $request
* @return Response
* @throws BusinessException|Throwable
*/
public function update(Request $request): Response
{
if ($request->method() === 'GET') {
$table = $request->get('table');
$table_info = Util::getSchema($table, 'table');
$primary_key = $table_info['primary_key'][0] ?? null;
$value = htmlspecialchars($request->get($primary_key, ''));
$form = Layui::buildForm($table,'update');
return raw_view("table/update", [
'primary_key' => $primary_key,
'value' => $value,
'form' => $form,
'table' => $table
]);
}
$table = Util::filterAlphaNum($request->post('table'));
$table_info = Util::getSchema($table, 'table');
$primary_keys = $table_info['primary_key'];
if (empty($primary_keys)) {
return $this->json(1, '该表没有主键,无法执行更新操作');
}
if (count($primary_keys) > 1) {
return $this->json(1, '不支持复合主键更新');
}
$primary_key = $primary_keys[0];
$value = $request->post($primary_key);
$data = $request->post();
$allow_column = Util::db()->select("desc `$table`");
if (!$allow_column) {
throw new BusinessException('表不存在', 2);
}
$columns = array_column($allow_column, 'Type', 'Field');
foreach ($data as $col => $item) {
if (!isset($columns[$col])) {
unset($data[$col]);
continue;
}
// 非字符串类型传空则为null
if ($item === '' && strpos(strtolower($columns[$col]), 'varchar') === false && strpos(strtolower($columns[$col]), 'text') === false) {
$data[$col] = null;
}
if (is_array($item)) {
$data[$col] = implode(',', $item);
}
if ($col === 'password') {
// 密码为空,则不更新密码
if ($item == '') {
unset($data[$col]);
continue;
}
$data[$col] = Util::passwordHash($item);
}
}
$datetime = date('Y-m-d H:i:s');
if (isset($columns['updated_at']) && empty($data['updated_at'])) {
$data['updated_at'] = $datetime;
}
Util::db()->table($table)->where($primary_key, $value)->update($data);
return $this->json(0);
}
/**
* 删除记录
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function delete(Request $request): Response
{
$table = $request->post('table');
$table_info = Util::getSchema($table, 'table');
$primary_keys = $table_info['primary_key'];
if (empty($primary_keys)) {
return $this->json(1, '该表没有主键,无法执行删除操作');
}
if (count($primary_keys) > 1) {
return $this->json(1, '不支持复合主键删除');
}
$primary_key = $primary_keys[0];
$value = (array)$request->post($primary_key);
Util::db()->table($table)->whereIn($primary_key, $value)->delete();
return $this->json(0);
}
/**
* 删除表
* @param Request $request
* @return Response
*/
public function drop(Request $request): Response
{
$tables = $request->post('tables');
if (!$tables) {
return $this->json(0, 'not found');
}
$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)) {
return $this->json(400, implode(',', $found) . '不允许删除');
}
foreach ($tables as $table) {
Util::schema()->drop($table);
// 删除schema
Util::db()->table('wa_options')->where('name', "table_form_schema_$table")->delete();
}
return $this->json(0, 'ok');
}
/**
* 表摘要
* @param Request $request
* @return Response
*/
public function schema(Request $request): Response
{
$table = $request->get('table');
$data = Util::getSchema($table);
return $this->json(0, 'ok', [
'table' => $data['table'],
'columns' => array_values($data['columns']),
'forms' => array_values($data['forms']),
'keys' => array_values($data['keys']),
]);
}
/**
* 创建字段
* @param $column
* @param Blueprint $table
* @return mixed
*/
protected function createColumn($column, Blueprint $table)
{
$method = $column['type'];
$args = [$column['field']];
if (stripos($method, 'int') !== false) {
// auto_increment 会自动成为主键
if ($column['auto_increment']) {
$column['nullable'] = false;
$column['default'] = null;
$args[] = true;
}
} elseif (in_array($method, ['string', 'char']) || stripos($method, 'time') !== false) {
if ($column['length']) {
$args[] = $column['length'];
}
} elseif ($method === 'enum') {
$args[] = array_map('trim', explode(',', $column['length']));
} elseif (in_array($method, ['float', 'decimal', 'double'])) {
if ($column['length']) {
$args = array_merge($args, array_map('trim', explode(',', $column['length'])));
}
} else {
$column['auto_increment'] = false;
}
$column_def = call_user_func_array([$table, $method], $args);
if (!empty($column['comment'])) {
$column_def = $column_def->comment($column['comment']);
}
if (!$column['auto_increment'] && $column['primary_key']) {
$column_def = $column_def->primary(true);
}
if ($column['auto_increment'] && !$column['primary_key']) {
$column_def = $column_def->primary(false);
}
$column_def = $column_def->nullable($column['nullable']);
if ($column['primary_key']) {
$column_def = $column_def->nullable(false);
}
if ($method != 'text' && $column['default'] !== null) {
$column_def->default($column['default']);
}
return $column_def;
}
/**
* 更改字段
* @param $column
* @param $table
* @return mixed
* @throws BusinessException
*/
protected function modifyColumn($column, $table)
{
$table = Util::filterAlphaNum($table);
$method = Util::filterAlphaNum($column['type']);
$field = Util::filterAlphaNum($column['field']);
$old_field = Util::filterAlphaNum($column['old_field'] ?? null);
$nullable = $column['nullable'];
$default = $column['default'] !== null ? Util::pdoQuote($column['default']) : null;
$comment = Util::pdoQuote($column['comment']);
$auto_increment = $column['auto_increment'];
$length = (int)$column['length'];
if ($column['primary_key']) {
$default = null;
}
if ($old_field && $old_field !== $field) {
$sql = "ALTER TABLE `$table` CHANGE COLUMN `$old_field` `$field` ";
} else {
$sql = "ALTER TABLE `$table` MODIFY `$field` ";
}
if (stripos($method, 'integer') !== false) {
$type = str_ireplace('integer', 'int', $method);
if (stripos($method, 'unsigned') !== false) {
$type = str_ireplace('unsigned', '', $type);
$sql .= "$type ";
$sql .= 'unsigned ';
} else {
$sql .= "$type ";
}
if ($auto_increment) {
$column['nullable'] = false;
$column['default'] = null;
$sql .= 'AUTO_INCREMENT ';
}
} else {
switch ($method) {
case 'string':
$length = $length ?: 255;
$sql .= "varchar($length) ";
break;
case 'char':
case 'time':
$sql .= $length ? "$method($length) " : "$method ";
break;
case 'enum':
$args = array_map('trim', explode(',', (string)$column['length']));
foreach ($args as $key => $value) {
$args[$key] = Util::pdoQuote($value);
}
$sql .= 'enum(' . implode(',', $args) . ') ';
break;
case 'double':
case 'float':
case 'decimal':
if (trim($column['length'])) {
$args = array_map('intval', explode(',', $column['length']));
$args[1] = $args[1] ?? $args[0];
$sql .= "$method($args[0], $args[1]) ";
break;
}
$sql .= "$method ";
break;
default :
$sql .= "$method ";
}
}
if (!$nullable) {
$sql .= "NOT NULL ";
}
if ($method != 'text' && $default !== null) {
$sql .= "DEFAULT $default ";
}
if ($comment !== null) {
$sql .= "COMMENT $comment ";
}
echo "$sql\n";
Util::db()->statement($sql);
}
/**
* 字段类型列表
* @param Request $request
* @return Response
*/
public function types(Request $request): Response
{
$types = Util::methodControlMap();
return $this->json(0, 'ok', $types);
}
/**
* 更新表的form schema信息
* @param $table_name
* @param $data
* @return string
*/
protected function updateSchemaOption($table_name, $data): string
{
$option_name = "table_form_schema_$table_name";
$option = Option::where('name', $option_name)->first();
if ($option) {
Option::where('name', $option_name)->update(['value' => $data]);
} else {
Option::insert(['name' => $option_name, 'value' => $data]);
}
return $option_name;
}
/**
* 字段类型到php类型映射
* @param string $type
* @return string
*/
protected function getType(string $type): string
{
if (strpos($type, 'int') !== false) {
return 'integer';
}
switch ($type) {
case 'varchar':
case 'string':
case 'text':
case 'date':
case 'time':
case 'guid':
case 'datetimetz':
case 'datetime':
case 'decimal':
case 'enum':
return 'string';
case 'boolean':
return 'integer';
case 'float':
return 'float';
default:
return 'mixed';
}
}
}