['name' => 'table', 'shortcut' => 't', 'mode' => Option::VALUE_REQUIRED, 'description' => 'table name without prefix', 'default' => null, 'solveArray' => false, 'setInputRule' => false], 'controller' => ['name' => 'controller', 'shortcut' => 'c', 'mode' => Option::VALUE_OPTIONAL, 'description' => 'controller name', 'default' => null, 'solveArray' => false, 'setInputRule' => false], 'model' => ['name' => 'model', 'shortcut' => 'm', 'mode' => Option::VALUE_OPTIONAL, 'description' => 'model name', 'default' => null, 'solveArray' => false, 'setInputRule' => false], 'fields' => ['name' => 'fields', 'shortcut' => 'f', 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'visible fields', 'default' => null, 'solveArray' => true, 'setInputRule' => false], 'force' => ['name' => 'force', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL, 'description' => 'force override or force delete,without tips', 'default' => null, 'solveArray' => false, 'setInputRule' => false], 'commonmodel' => ['name' => 'commonmodel', 'shortcut' => 'o', 'mode' => Option::VALUE_OPTIONAL, 'description' => 'common model', 'default' => null, 'solveArray' => false, 'setInputRule' => false], // 关联表 'relation' => ['name' => 'relation', 'shortcut' => 'r', 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'relation table name without prefix', 'default' => null, 'solveArray' => false, 'setInputRule' => false], 'relationmodel' => ['name' => 'relationmodel', 'shortcut' => 'e', 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'relation model name', 'default' => null, 'solveArray' => false, 'setInputRule' => false], 'relationforeignkey' => ['name' => 'relationforeignkey', 'shortcut' => 'k', 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'relation foreign key', 'default' => null, 'solveArray' => false, 'setInputRule' => false], 'relationprimarykey' => ['name' => 'relationprimarykey', 'shortcut' => 'p', 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'relation primary key', 'default' => null, 'solveArray' => false, 'setInputRule' => false], 'relationfields' => ['name' => 'relationfields', 'shortcut' => 'l', 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'relation table fields', 'default' => null, 'solveArray' => false, 'setInputRule' => false], 'relationmode' => ['name' => 'relationmode', 'shortcut' => 'a', 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'relation table mode,hasone or belongsto', 'default' => null, 'solveArray' => false, 'setInputRule' => false], // 关联远程下拉select显示字段 'remoteselectfield' => ['name' => 'remoteselectfield', 'shortcut' => 's', 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'relation table Remote select table field', 'default' => null, 'solveArray' => false, 'setInputRule' => false], // 是否删除模式 'delete' => ['name' => 'delete', 'shortcut' => 'd', 'mode' => Option::VALUE_OPTIONAL, 'description' => 'delete all files generated by CRUD', 'default' => null, 'solveArray' => false, 'setInputRule' => false], // 指定数据库 'db' => ['name' => 'db', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL, 'description' => 'database config name', 'default' => 'mysql', 'solveArray' => false, 'setInputRule' => false], // 快速搜索字段设置 'quicksearchfield' => ['name' => 'quicksearchfield', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'quick search field', 'default' => null, 'solveArray' => true, 'setInputRule' => false], // 排序字段设置 'sortfield' => ['name' => 'sortfield', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL, 'description' => 'sort field', 'default' => null, 'solveArray' => false, 'setInputRule' => false], // 排除字段设置 'ignorefields' => ['name' => 'ignorefields', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'ignore fields', 'default' => null, 'solveArray' => true, 'setInputRule' => false], // 设置后缀识别规则 'radiofieldsuffix' => ['name' => 'radiofieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for radio components', 'default' => null, 'solveArray' => true, 'setInputRule' => true], 'checkboxfieldsuffix' => ['name' => 'checkboxfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for checkbox components', 'default' => null, 'solveArray' => true, 'setInputRule' => true], 'datetimefieldsuffix' => ['name' => 'datetimefieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for datetime components', 'default' => null, 'solveArray' => true, 'setInputRule' => true], 'switchfieldsuffix' => ['name' => 'switchfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for switch components', 'default' => null, 'solveArray' => true, 'setInputRule' => true], 'editorfieldsuffix' => ['name' => 'editorfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for editor components', 'default' => null, 'solveArray' => true, 'setInputRule' => true], 'textareafieldsuffix' => ['name' => 'textareafieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for textarea components', 'default' => null, 'solveArray' => true, 'setInputRule' => true], 'cityfieldsuffix' => ['name' => 'cityfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for city components', 'default' => null, 'solveArray' => true, 'setInputRule' => true], 'remoteselectfieldsuffix' => ['name' => 'remoteselectfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for remote select components', 'default' => null, 'solveArray' => true, 'setInputRule' => true], 'arrayfieldsuffix' => ['name' => 'arrayfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for array components', 'default' => null, 'solveArray' => true, 'setInputRule' => true], 'imagefieldsuffix' => ['name' => 'imagefieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for image components', 'default' => null, 'solveArray' => true, 'setInputRule' => true], 'imagesfieldsuffix' => ['name' => 'imagesfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for images components', 'default' => null, 'solveArray' => true, 'setInputRule' => true], 'filefieldsuffix' => ['name' => 'filefieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for file components', 'default' => null, 'solveArray' => true, 'setInputRule' => true], 'filesfieldsuffix' => ['name' => 'filesfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for files components', 'default' => null, 'solveArray' => true, 'setInputRule' => true], 'numberfieldsuffix' => ['name' => 'numberfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for number components', 'default' => null, 'solveArray' => true, 'setInputRule' => true], 'selectfieldsuffix' => ['name' => 'selectfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for select components', 'default' => null, 'solveArray' => true, 'setInputRule' => true], 'selectsfieldsuffix' => ['name' => 'selectsfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for selects components', 'default' => null, 'solveArray' => true, 'setInputRule' => true], 'iconfieldsuffix' => ['name' => 'iconfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for icon components', 'default' => null, 'solveArray' => true, 'setInputRule' => true], ]; /** * 输入框类型的识别规则 */ protected $inputTypeRule = [ // 开关组件 [ 'type' => ['tinyint', 'int', 'enum'], 'suffix' => ['switch', 'toggle'], 'value' => 'switch', ], [ 'column_type' => ['tinyint(1)', 'char(1)', 'tinyint(1) unsigned'], 'suffix' => ['switch', 'toggle'], 'value' => 'switch', ], // 富文本-识别规则和textarea重合,优先识别为富文本 [ 'type' => ['longtext', 'text', 'mediumtext', 'smalltext', 'tinytext', 'bigtext'], 'suffix' => ['content', 'editor'], 'value' => 'editor', ], // textarea [ 'type' => ['varchar'], 'suffix' => ['textarea', 'multiline', 'rows'], 'value' => 'textarea', ], // Array [ 'suffix' => ['array'], 'value' => 'array', ], // 时间选择器-字段类型为int同时以['time', 'datetime']结尾 [ 'type' => ['int'], 'suffix' => ['time', 'datetime'], 'value' => 'datetime', ], [ 'type' => ['datetime', 'timestamp'], 'value' => 'datetime', ], [ 'type' => ['date'], 'value' => 'date', ], [ 'type' => ['year'], 'value' => 'year', ], [ 'type' => ['time'], 'value' => 'time', ], // 单选select [ 'suffix' => ['select', 'list', 'data'], 'value' => 'select', ], // 多选select [ 'suffix' => ['selects', 'multi', 'lists'], 'value' => 'selects', ], // 远程select [ 'suffix' => ['_id'], 'value' => 'remoteSelect', ], // 远程selects [ 'suffix' => ['_ids'], 'value' => 'remoteSelects', ], // 城市选择器 [ 'suffix' => ['city'], 'value' => 'city', ], // 单图上传 [ 'suffix' => ['image', 'avatar'], 'value' => 'image', ], // 多图上传 [ 'suffix' => ['images', 'avatars'], 'value' => 'images', ], // 文件上传 [ 'suffix' => ['file'], 'value' => 'file', ], // 多文件上传 [ 'suffix' => ['files'], 'value' => 'files', ], // icon选择器 [ 'suffix' => ['icon'], 'value' => 'icon', ], // 单选框 [ 'column_type' => ['tinyint(1)', 'char(1)', 'tinyint(1) unsigned'], 'suffix' => ['status', 'state', 'type'], 'value' => 'radio', ], // 数字输入框 [ 'suffix' => ['number', 'int', 'num'], 'value' => 'number', ], [ 'type' => ['bigint', 'int', 'mediumint', 'smallint', 'tinyint', 'decimal', 'double', 'float'], 'value' => 'number', ], // 富文本-低权重 [ 'type' => ['longtext', 'text', 'mediumtext', 'smalltext', 'tinytext', 'bigtext'], 'value' => 'textarea', ], // 单选框-低权重 [ 'type' => ['enum'], 'value' => 'radio', ], // 多选框 [ 'type' => ['set'], 'value' => 'checkbox', ], ]; /** * 表格字段渲染方案 */ protected $fieldRenderRule = [ // 字段名称为 ['id'] 则字段宽度为70,且开启排序 [ 'name' => ['id'], 'attr' => [ 'width' => 70, 'sortable' => 'custom', 'operator' => 'RANGE', ], ], // 字段名称为 ['weigh'] 则关闭字段筛选 [ 'name' => ['weigh'], 'attr' => [ 'sortable' => 'custom', 'operator' => 'false', ], ], // 输入框被判定为 ['number'] 则通用搜索中使用范围筛选 [ 'type' => ['number'], 'attr' => [ 'operator' => 'RANGE', ], ], // 输入框被判定为['radio', 'select'] 或者字段名后缀为 ['flag'] 则渲染为 tag [ 'type' => ['radio', 'select'], 'suffix' => ['flag'], 'attr' => [ 'render' => 'tag', ], ], // 输入框被判定为 ['textarea', 'editor', '...'] 则隐藏字段 [ 'type' => ['textarea', 'editor', 'file', 'files', 'array'], 'attr' => [ 'show' => false, ], ], [ 'type' => ['checkbox', 'selects'], 'suffix' => ['flags'], 'attr' => [ 'render' => 'tags', ], ], [ 'type' => ['datetime'], 'attr' => [ 'render' => 'datetime', 'sortable' => 'custom', 'operator' => 'RANGE', 'width' => 160 ], ], [ 'type' => ['switch'], 'attr' => [ 'render' => 'switch', ], ], [ 'type' => ['image'], 'suffix' => ['avatar'], 'attr' => [ 'render' => 'image', ], ], [ 'type' => ['images'], 'suffix' => ['avatars'], 'attr' => [ 'render' => 'images', ], ], [ 'type' => ['icon'], 'attr' => [ 'render' => 'icon', ], ], [ 'suffix' => ['url'], 'attr' => [ 'render' => 'url', ], ] ]; /** * select远程搜索字段关联 */ protected $remoteSelectFieldMap = [ 'nickname' => ['user_id', 'user_ids', 'admin_id', 'admin_ids'], ]; /** * 内部保留词 */ protected $reservedKeywords = [ 'abstract', 'and', 'array', 'as', 'break', 'callable', 'case', 'catch', 'class', 'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends', 'final', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'isset', 'list', 'namespace', 'new', 'or', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'return', 'static', 'switch', 'throw', 'trait', 'try', 'unset', 'use', 'var', 'while', 'xor', 'yield' ]; /** * 保留字段 */ protected $reservedField = []; /** * 快速搜索字段 */ protected $quicksearchfield = ['id']; /** * 默认排序字段,el-table只支持单字段排序 */ protected $defaultSortField = 'weigh,desc'; /** * 排除字段 */ protected $ignoreFields = []; /** * 添加时间字段 * @var string */ protected $createTimeField = 'createtime'; /** * 更新时间字段 * @var string */ protected $updateTimeField = 'updatetime'; /** * 子级菜单数组(权限节点) * @var string */ protected $menuChildren = [ ['type' => 'button', 'title' => '查看', 'name' => '/index', 'status' => '1'], ['type' => 'button', 'title' => '添加', 'name' => '/add', 'status' => '1'], ['type' => 'button', 'title' => '编辑', 'name' => '/edit', 'status' => '1'], ['type' => 'button', 'title' => '删除', 'name' => '/del', 'status' => '1'], ['type' => 'button', 'title' => '快速排序', 'name' => '/sortable', 'status' => '1'], ]; protected $langPrefix = ''; protected $stub = null; protected function configure() { $this->setName('crud')->setDescription('Build CRUD code controller and model, and views from table'); foreach ($this->options as $option) { $this->addOption($option['name'], $option['shortcut'], $option['mode'], $option['description'], $option['default']); } } protected function execute(Input $input, Output $output) { $this->stub = Stub::instance(); $this->input = $input; $this->output = $output; $adminPath = dirname(__DIR__) . DIRECTORY_SEPARATOR; $webPath = root_path() . 'web' . DIRECTORY_SEPARATOR; $webViewsPath = $webPath . 'src' . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . 'backend' . DIRECTORY_SEPARATOR; $webLangPath = $webPath . 'src' . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR; $webControllerUrls = $webPath . 'src' . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'controllerUrls.ts'; // 数据库配置名 $db = $this->getOption('db'); // 表名 $table = $this->getOption('table'); if (!$table) { throw new Exception('table name can\'t empty'); } // 自定义控制器 $controller = $this->getOption('controller'); // 自定义模型 $model = $this->getOption('model'); $model = $model ?: $controller; // 验证器类 $validate = $model; // 自定义显示字段 $fields = $this->getOption('fields'); // 强制覆盖 $force = $this->getOption('force'); // 是否将model放在app/common/model中 $commonModel = $this->getOption('commonmodel'); // 关联表 $relation = $this->getOption('relation'); // 自定义关联表模型 $relationModel = $this->getOption('relationmodel'); // 关联模式 $relationMode = $mode = $this->getOption('relationmode'); // 外键 $relationForeignKey = $this->getOption('relationforeignkey'); // 主键 $relationPrimaryKey = $this->getOption('relationprimarykey'); // 远程下拉字段 $remoteSelectField = $this->getOption('remoteselectfield'); // 关联表显示字段 $relationFields = $this->getOption('relationfields'); // 快速搜索字段 $quicksearchfield = $this->getOption('quicksearchfield'); // 排序字段 $sortfield = $this->getOption('sortfield'); // 排除字段 $ignoreFields = $this->getOption('ignorefields'); // 字段识别后缀规则批量设置 foreach ($this->options as $option) { if ($option['setInputRule']) { $field = explode('field', $option['name']); $this->setInputTypeRule($field[0], $this->getOption($option['name'])); } } if ($quicksearchfield) $this->quicksearchfield = $quicksearchfield; if ($ignoreFields) $this->ignoreFields = $ignoreFields; if ($sortfield) $this->defaultSortField = $sortfield; $this->reservedField = array_merge($this->reservedField, [$this->createTimeField, $this->updateTimeField]); $dbconnect = Db::connect($db); $prefix = Config::get('database.connections.' . $db . '.prefix'); $dbname = Config::get('database.connections.' . $db . '.database'); // 模块 $moduleName = 'admin'; $modelModuleName = $validateModuleName = $commonModel ? 'common' : $moduleName; // 检查主表 $modelName = $table = stripos($table, $prefix) === 0 ? substr($table, strlen($prefix)) : $table; $modelTableType = 'table'; $modelTableTypeName = $modelTableName = $modelName; $modelTableInfo = $dbconnect->query("SHOW TABLE STATUS LIKE '{$modelTableName}'", [], true); if (!$modelTableInfo) { $modelTableType = 'name'; $modelTableName = $prefix . $modelName; $modelTableInfo = $dbconnect->query("SHOW TABLE STATUS LIKE '{$modelTableName}'", [], true); if (!$modelTableInfo) { throw new Exception('table not found'); } } $modelTableInfo = $modelTableInfo[0]; $relations = []; // 检查关联表 if ($relation) { foreach ($relation as $index => $relationTable) { $relationName = stripos($relationTable, $prefix) === 0 ? substr($relationTable, strlen($prefix)) : $relationTable; $relationTableType = 'table'; $relationTableTypeName = $relationTableName = $relationName; $relationTableInfo = $dbconnect->query("SHOW TABLE STATUS LIKE '{$relationTableName}'", [], true); if (!$relationTableInfo) { $relationTableType = 'name'; $relationTableName = $prefix . $relationName; $relationTableInfo = $dbconnect->query("SHOW TABLE STATUS LIKE '{$relationTableName}'", [], true); if (!$relationTableInfo) { throw new Exception('relation table not found'); } } $relationTableInfo = $relationTableInfo[0];// 关联表信息 $relationModelTemp = $relationModel[$index] ?? '';// 关联模型 [$relationNamespace, $relationName, $relationFile] = $this->getModelData($modelModuleName, $relationModelTemp, $relationName); $relations[] = [ // 关联表基础名 'relationName' => $relationName, // 关联表类命名空间 'relationNamespace' => $relationNamespace, // 关联模型名 'relationModel' => $relationModelTemp, // 关联文件 'relationFile' => $relationFile, // 关联表名称 'relationTableName' => $relationTableName, // 关联表信息 'relationTableInfo' => $relationTableInfo, // 关联模型表类型(name或table) 'relationTableType' => $relationTableType, // 关联模型表类型名称 'relationTableTypeName' => $relationTableTypeName, // 关联表字段 'relationFields' => isset($relationFields[$index]) ? explode(',', $relationFields[$index]) : [], // 关联模式 'relationMode' => $relationMode[$index] ?? 'belongsto', // 关联表外键 'relationForeignKey' => $relationForeignKey[$index] ?? parse_name($relationName) . '_id', // 关联表主键 'relationPrimaryKey' => $relationPrimaryKey[$index] ?? 'id', // 远程下拉字段 'remoteSelectField' => $remoteSelectField[$index] ?? '', ]; } } // 控制器 [$controllerNamespace, $controllerName, $controllerFile, $controllerArr] = $this->getControllerData($moduleName, $controller, $table); // 模型 [$modelNamespace, $modelName, $modelFile, $modelArr] = $this->getModelData($modelModuleName, $model, $table); // 验证器 [$validateNamespace, $validateName, $validateFile, $validateArr] = $this->getValidateData($validateModuleName, $validate, $table); // 处理基础文件名,取消所有下划线并转换为小写 $baseNameArr = $controllerArr; $baseFileName = parse_name(array_pop($baseNameArr)); array_push($baseNameArr, $baseFileName); $controllerBaseName = strtolower(implode(DIRECTORY_SEPARATOR, $baseNameArr)); // 视图文件 $viewArr = $controllerArr; foreach ($viewArr as $index => $item) { $viewArr[$index] = parse_name($item, 1, false); } $viewPath = implode(DIRECTORY_SEPARATOR, $viewArr); $viewDir = $webViewsPath . $viewPath . DIRECTORY_SEPARATOR; $this->langPrefix = implode('.', $viewArr) . '.'; $controllerUrl = $originControllerUrl = implode('/', $viewArr); $controllerUrl = '/admin/' . preg_replace("/\//", '.', $controllerUrl) . '/'; $controllerUrlVarName = ''; foreach ($viewArr as $item) { $controllerUrlVarName .= parse_name($item, 1); } $controllerUrlVarName = lcfirst($controllerUrlVarName); // 最终将生成的文件路径 $formFile = $viewDir . 'popupForm.vue'; $indexFile = $viewDir . 'index.vue'; // $serverLangZhCnFile = $adminPath . 'lang' . DIRECTORY_SEPARATOR . 'zh-cn' . DIRECTORY_SEPARATOR . $controllerBaseName . '.php'; // $serverLangEnFile = $adminPath . 'lang' . DIRECTORY_SEPARATOR . 'en' . DIRECTORY_SEPARATOR . $controllerBaseName . '.php'; $webLangZhCnFile = $webLangPath . 'zh-cn' . DIRECTORY_SEPARATOR . $viewPath . '.ts'; $webLangEnFile = $webLangPath . 'en' . DIRECTORY_SEPARATOR . $viewPath . '.ts'; // 是否为删除模式 $delete = $this->getOption('delete'); if ($delete) { $readyFiles = [$controllerFile, $modelFile, $validateFile, $formFile, $indexFile, $webLangZhCnFile, $webLangEnFile]; foreach ($readyFiles as $v) { $output->warning($v); } if (!$force) { $output->info("Are you sure you want to delete all those files? Type 'yes' to continue: "); $line = fgets(defined('STDIN') ? STDIN : fopen('php://stdin', 'r')); if (trim($line) != 'yes') { throw new Exception('Operation is aborted!'); } } foreach ($readyFiles as $v) { if (file_exists($v)) { unlink($v); } //删除空文件夹 switch ($v) { case $modelFile: $this->removeEmptyBaseDir($v, $modelArr); break; case $validateFile: $this->removeEmptyBaseDir($v, $validateArr); break; case $formFile: case $indexFile: $this->removeEmptyBaseDir($v, $viewArr, 0); break; default: $this->removeEmptyBaseDir($v, $controllerArr); } } // 删除菜单 Menu::delete($originControllerUrl, true); $output->info('Delete Successed'); return; } // 非覆盖模式时如果存在控制器文件则报错 if (is_file($controllerFile) && !$force) { throw new Exception("controller already exists!\nIf you need to rebuild again, use the parameter --force=true "); } // 非覆盖模式时如果存在模型文件则报错 if (is_file($modelFile) && !$force) { throw new Exception("model already exists!\nIf you need to rebuild again, use the parameter --force=true "); } // 非覆盖模式时如果存在验证文件则报错 if (is_file($validateFile) && !$force) { throw new Exception("validate already exists!\nIf you need to rebuild again, use the parameter --force=true "); } require $adminPath . 'common.php'; // 从数据库中获取表字段信息 $sql = 'SELECT * FROM `information_schema`.`columns` ' . 'WHERE TABLE_SCHEMA = ? AND table_name = ? ' . 'ORDER BY ORDINAL_POSITION'; // 加载主表的列 $columnList = $dbconnect->query($sql, [$dbname, $modelTableName]); $fieldArr = []; foreach ($columnList as $v) { $fieldArr[] = $v['COLUMN_NAME']; } // 加载关联表的列 foreach ($relations as &$relation) { $relationColumnList = $dbconnect->query($sql, [$dbname, $relation['relationTableName']]); $relationFieldList = []; foreach ($relationColumnList as $v) { $relationFieldList[] = $v['COLUMN_NAME']; } if (!$relation['relationPrimaryKey']) { foreach ($relationColumnList as $v) { if ($v['COLUMN_KEY'] == 'PRI') { $relation['relationPrimaryKey'] = $v['COLUMN_NAME']; break; } } } // 如果主键为空 if (!$relation['relationPrimaryKey']) { throw new Exception('Relation Primary key not found!'); } // 如果主键不在表字段中 if (!in_array($relation['relationPrimaryKey'], $relationFieldList)) { throw new Exception('Relation Primary key not found in table!'); } $relation['relationColumnList'] = $relationColumnList; $relation['relationFieldList'] = $relationFieldList; } unset($relation); // 检查主键是否存在 $priKey = '';// 主键 foreach ($columnList as $v) { if ($v['COLUMN_KEY'] == 'PRI') { $priKey = $v['COLUMN_NAME']; break; } } if (!$priKey) { throw new Exception('Primary key not found!'); } // 检查快速搜索字段是否存在 foreach ($this->quicksearchfield as $key => $item) { if (!in_array($item, $fieldArr)) { unset($this->quicksearchfield[$key]); } } if (!$this->quicksearchfield) { $this->quicksearchfield = [$priKey]; } // 如果是关联模型 foreach ($relations as &$relation) { if ($relation['relationMode'] == 'hasone') { $relationForeignKey = $relation['relationForeignKey'] ?: $table . '_id'; $relationPrimaryKey = $relation['relationPrimaryKey'] ?: $priKey; if (!in_array($relationForeignKey, $relation['relationFieldList'])) { throw new Exception('relation table [' . $relation['relationTableName'] . '] must be contain field [' . $relationForeignKey . ']'); } if (!in_array($relationPrimaryKey, $fieldArr)) { throw new Exception('table [' . $modelTableName . '] must be contain field [' . $relationPrimaryKey . ']'); } } else { $relationForeignKey = $relation['relationForeignKey'] ?: parse_name($relation['relationName']) . '_id'; $relationPrimaryKey = $relation['relationPrimaryKey'] ?: $relation['relationPriKey']; if (!in_array($relationForeignKey, $fieldArr)) { throw new Exception('table [' . $modelTableName . '] must be contain field [' . $relationForeignKey . ']'); } if (!in_array($relationPrimaryKey, $relation['relationFieldList'])) { throw new Exception('relation table [' . $relation['relationTableName'] . '] must be contain field [' . $relationPrimaryKey . ']'); } } $relation['relationForeignKey'] = $relationForeignKey; $relation['relationPrimaryKey'] = $relationPrimaryKey; $relation['relationClassName'] = $modelNamespace != $relation['relationNamespace'] ? '\\' . $relation['relationNamespace'] . '\\' . $relation['relationName'] . '::class' : $relation['relationName'] . '::class'; } unset($relation); try { $quickSearchFieldNames = [];// 快速搜索字段列表 $formFieldList = [];// 表单字段列表 $dblClickNotEditColumn = ['undefined'];// 不允许双击编辑的字段 $optButtons = ['edit', 'delete'];// 表格行操作按钮 $inputDefaultItems = [];// 表单默认值 $defaultOrder = [];// 默认排序字段 $langList = ['en' => [], 'zh-cn' => []];// web端语言项列表 $controllerAttrList = [];// 控制器内要设置的变量列表 $importControllerUrls = [];// 要引入的控制器url列表 $importPackages = ''; $formItemRules = [];// 字段验证规则 $formDialogBig = false;// 存在富文本编辑器则加宽表单dialog $modelSetAttrArr = []; $modelFieldType = []; $enableDragSort = in_array('weigh', $fieldArr); $controllerData = [ 'controllerNamespace' => $controllerNamespace, 'controllerName' => $controllerName, 'modelName' => $modelName, 'modelNamespace' => $modelNamespace, 'methods' => [], ]; // 表格列数据 $tableColumnList = [ [ 'type' => 'selection', 'align' => 'center', 'operator' => 'false', ], ]; foreach ($columnList as $column) { $field = $column['COLUMN_NAME']; $inputType = $this->getFieldInputType($column); // 列替换数据 $columnData = $this->getColumnReplaceData($column, $field, $inputType); // 字段语言包数据 if ($column['COLUMN_COMMENT'] != '') { $columnLang = $this->getLangItem($field, $column['COLUMN_COMMENT']); $langList['en'] = array_merge($langList['en'], $columnLang['en']); $langList['zh-cn'] = array_merge($langList['zh-cn'], $columnLang['zh-cn']); } // 快速搜索字段 if (in_array($field, $this->quicksearchfield)) { $quickSearchFieldNames[] = $column['COLUMN_COMMENT'] ?? $field; } // 不允许双击编辑的字段 if ($inputType == 'switch') { $dblClickNotEditColumn[] = $field; } // 表单字段 if ($column['COLUMN_KEY'] != 'PRI' && !in_array($field, $this->reservedField) && !in_array($field, $this->ignoreFields)) { // 输入框默认值 if ($column['COLUMN_DEFAULT'] || ($column['COLUMN_DEFAULT'] === '0' && $inputType != 'remoteSelect' && $inputType != 'remoteSelects')) { if (in_array($inputType, ['checkbox', 'selects', 'remoteSelects', 'city'])) { $column['COLUMN_DEFAULT'] = explode(',', $column['COLUMN_DEFAULT']); } $inputDefaultItems[$field] = $column['COLUMN_DEFAULT']; } $formFieldList[$field] = [ ':label' => 't(\'' . $this->langPrefix . $field . '\')', 'type' => $inputType, 'v-model' => 'baTable.form.items!.' . $field, 'prop' => $field, ]; // 输入框类型的特殊属性 if (in_array($inputType, ['radio', 'checkbox', 'select', 'selects'])) { $formFieldList[$field][':data'] = [ 'content' => $columnData, ]; } elseif ($inputType == 'textarea') { $formFieldList[$field][':input-attr']['rows'] = 3; $formFieldList[$field]['@keyup.enter.stop'] = ''; $formFieldList[$field]['@keyup.ctrl.enter'] = 'baTable.onSubmit(formRef)'; } elseif ($inputType == 'remoteSelect' || $inputType == 'remoteSelects') { $formFieldList[$field][':input-attr']['field'] = $this->getRemoteSelectField($field, $relations); $remoteUrl = $this->getRemoteSelectUrl($field, $relations, $webControllerUrls); $formFieldList[$field][':input-attr']['remote-url'] = $remoteUrl['url']; $formFieldList[$field][':input-attr']['pk'] = $remoteUrl['pk']; if ($remoteUrl['importControllerUrls']) { $importControllerUrls[$remoteUrl['importControllerUrls']] = ''; } if ($inputType == 'remoteSelects') { $formFieldList[$field]['type'] = 'remoteSelect'; $formFieldList[$field][':input-attr']['multiple'] = 'true'; } } elseif ($inputType == 'number') { $formFieldList[$field]['v-model.number'] = $formFieldList[$field]['v-model']; unset($formFieldList[$field]['v-model']); $formFieldList[$field][':input-attr']['step'] = $column['NUMERIC_SCALE'] > 0 ? '0.' . str_repeat(0, $column['NUMERIC_SCALE'] - 1) . '1' : 1; } elseif ($inputType == 'icon') { $formFieldList[$field][':input-attr']['placement'] = 'top'; } elseif ($inputType == 'array') { $modelFieldType[$field] = 'json'; $inputDefaultItems[$field] = []; } elseif ($inputType == 'datetime' && $column['DATA_TYPE'] == 'int') { $modelFieldType[$field] = 'timestamp:Y-m-d H:i:s'; } elseif ($inputType == 'editor') { $formFieldList[$field]['@keyup.enter.stop'] = ''; $formFieldList[$field]['@keyup.ctrl.enter'] = 'baTable.onSubmit(formRef)'; $formDialogBig = true; // form 使用较宽的 Dialog // 重写edit和add方法 $controllerData['methods']['add'] = $this->stub->getReplacedStub('mixins' . DIRECTORY_SEPARATOR . 'controllerEditorAddMethod', []); $controllerData['methods']['edit'] = $this->stub->getReplacedStub('mixins' . DIRECTORY_SEPARATOR . 'controllerEditorEditMethod', []); } // 模型的属性修改器获取器 $this->getModelAttrMethod($modelSetAttrArr, $field, $inputType, $column); // placeholder if (!in_array($inputType, ['image', 'images', 'file', 'files'])) { if (in_array($inputType, ['radio', 'checkbox', 'datetime', 'year', 'date', 'time', 'select', 'selects', 'remoteSelect', 'remoteSelects', 'city', 'icon'])) { $formFieldList[$field][':input-attr']['placeholder'] = "t('Please select field', { field: t('" . $this->langPrefix . $field . "') })"; } else { $formFieldList[$field][':input-attr']['placeholder'] = "t('Please input field', { field: t('" . $this->langPrefix . $field . "') })"; } } // 字段验证规则 if ($column['IS_NULLABLE'] == 'NO' && !in_array($inputType, ['switch', 'icon'])) { if ($inputType == 'editor') { $formItemRules[$field][] = "buildValidatorData({name: 'editorRequired', message: t('Please input field', { field: t('" . $this->langPrefix . $field . "') })})"; } elseif (in_array($inputType, ['radio', 'checkbox', 'datetime', 'year', 'date', 'time', 'select', 'selects', 'remoteSelect', 'city', 'image', 'images', 'file', 'files', 'icon'])) { $formItemRules[$field][] = "buildValidatorData({name: 'required', message: t('Please select field', { field: t('" . $this->langPrefix . $field . "') })})"; } else { $formItemRules[$field][] = "buildValidatorData({name: 'required', title: t('" . $this->langPrefix . $field . "')})"; } } if ($field == 'mobile') { $formItemRules[$field][] = "buildValidatorData({name: 'mobile', message: t('Please enter the correct field', { field: t('" . $this->langPrefix . $field . "') })})"; } if ($inputType == 'datetime' || $inputType == 'date') { $formItemRules[$field][] = "buildValidatorData({name: 'date', message: t('Please enter the correct field', { field: t('" . $this->langPrefix . $field . "') })})"; } } // 表格列 if (!$fields || in_array($field, $fields)) { $tableColumnList[] = $this->getTableColumn($field, $inputType, $column['DATA_TYPE'], $columnData); } } // 关联表 foreach ($relations as $relation) { foreach ($relation['relationColumnList'] as $v) { // 不显示的字段直接过滤掉 if ($relation['relationFields'] && !in_array($v['COLUMN_NAME'], $relation['relationFields'])) { continue; } $inputType = $this->getFieldInputType($v); $relationField = strtolower($relation['relationName']) . '.' . $v['COLUMN_NAME']; $relationFieldLang = strtolower($relation['relationName']) . '__' . $v['COLUMN_NAME']; // 列替换数据 $columnData = $this->getColumnReplaceData($v, $relationFieldLang, $inputType); // 不允许双击编辑的字段 if ($inputType == 'switch') { $dblClickNotEditColumn[] = $relationField; } // 表格列 $tableColumnList[] = $this->getTableColumn($relationField, $inputType, $v['DATA_TYPE'], $columnData, $relationFieldLang); // 字段语言包数据 if ($v['COLUMN_COMMENT'] != '') { $columnLang = $this->getLangItem($relationFieldLang, $v['COLUMN_COMMENT']); $langList['en'] = array_merge($langList['en'], $columnLang['en']); $langList['zh-cn'] = array_merge($langList['zh-cn'], $columnLang['zh-cn']); } } } Stub::buildFormField($formFieldList); Stub::buildFormRules($formItemRules); // 表格列额外处理 $tableColumnList[] = [ 'label' => "t('operate')", 'align' => 'center', 'width' => $enableDragSort ? 140 : 100, 'render' => 'buttons', 'buttons' => 'optButtons', 'operator' => 'false', ]; foreach ($tableColumnList as $key => $item) { if (isset($item['show']) && !$item['show']) { unset($tableColumnList[$key]); } } Stub::buildTableColumn($tableColumnList); // 不允许双击编辑的字段 Stub::buildDblClickNotEditColumn($dblClickNotEditColumn); // 开启拖拽排序 if ($enableDragSort) { array_unshift($optButtons, 'weigh-sort'); } $optButtons = json_encode($optButtons); // 处理快速搜索提示信息 $langList['en']['quick Search Fields'] = implode('、', $this->quicksearchfield); $langList['zh-cn']['quick Search Fields'] = implode('、', $quickSearchFieldNames); $controllerAttrList['quickSearchField'] = $this->quicksearchfield; // 输入框默认值 $inputDefaultItems = json_encode($inputDefaultItems); // 默认排序 if ($this->defaultSortField) { $sortItem = explode(',', $this->defaultSortField); if (isset($sortItem[0]) && isset($sortItem[1]) && in_array($sortItem[0], $fieldArr)) { $defaultOrder = $sortItem; } } if (!$defaultOrder) { $defaultOrder = [$priKey, 'desc']; } $defaultOrderStub = Stub::buildDefaultOrder($defaultOrder); $controllerAttrList['defaultSortField'] = implode(',', $defaultOrder); // 添加Url到controllerUrls.ts $appendUrl = "export const {$controllerUrlVarName} = '{$controllerUrl}'"; $webControllerUrlsContents = @file_get_contents($webControllerUrls); if (strpos($webControllerUrlsContents, $appendUrl) === false) { @file_put_contents($webControllerUrls, $appendUrl . "\n", FILE_APPEND); } // 组装index.vue $indexVue = $this->stub->getReplacedStub('html' . DIRECTORY_SEPARATOR . 'index', [ 'tablePk' => $priKey, 'controllerUrlVarName' => $controllerUrlVarName, 'originControllerUrl' => $originControllerUrl, 'tableColumnList' => $tableColumnList, 'dblClickNotEditColumn' => $dblClickNotEditColumn, 'optButtons' => $optButtons, 'inputDefaultItems' => $inputDefaultItems ?: '', 'defaultOrderStub' => $defaultOrderStub, 'langPrefix' => $this->langPrefix, ]); Stub::writeToFile($indexFile, $indexVue); // 组装form.vue if ($importControllerUrls) { $importPackages .= "import { " . implode(',', array_keys($importControllerUrls)) . " } from '/@/api/controllerUrls'\n"; } $formDialogBig = $formDialogBig ? "\n" . Stub::tab(2) . "width='50%'" : ''; $formVue = $this->stub->getReplacedStub('html' . DIRECTORY_SEPARATOR . 'form', [ 'formItem' => $formFieldList ?: '', 'importPackages' => $importPackages, 'formItemRules' => $formItemRules ?: '', 'formDialogBig' => $formDialogBig, ]); Stub::writeToFile($formFile, $formVue); // 组装和写入web语言包 Stub::writeWebLangFile($langList, $webLangEnFile, $webLangZhCnFile); // 表注释 $tableComment = mb_substr($modelTableInfo['Comment'], -1) == '表' ? mb_substr($modelTableInfo['Comment'], 0, -1) . '管理' : $modelTableInfo['Comment']; $modelData = [ 'modelNamespace' => $modelNamespace, 'modelName' => $modelName, 'controllerUrlVarName' => $controllerUrlVarName, 'modelConnection' => $db == 'mysql' ? '' : "\n" . Stub::tab() . "protected \$connection = '{$db}';", 'modelTableType' => $modelTableType, 'modelTableTypeName' => $modelTableTypeName, 'modelAutoWriteTimestamp' => in_array($this->createTimeField, $fieldArr) || in_array($this->updateTimeField, $fieldArr) ? "'int'" : 'false', 'createTime' => in_array($this->createTimeField, $fieldArr) ? "'{$this->createTimeField}'" : 'false', 'updateTime' => in_array($this->updateTimeField, $fieldArr) ? "'{$this->updateTimeField}'" : 'false', 'modeAfterInsert' => '', 'priKey' => $priKey == 'id' ? '' : "\n" . Stub::tab() . "// 表主键\n" . Stub::tab() . 'protected $pk = ' . "'{$priKey}';\n" . Stub::tab(), ]; $controllerData['tableComment'] = $tableComment; if ($priKey != $defaultOrder[0]) { $modelData['modeAfterInsert'] = $this->stub->getReplacedStub('mixins' . DIRECTORY_SEPARATOR . 'modeAfterInsert', [ 'order' => $defaultOrder[0] ]); } // 如果使用关联模型 if ($relations) { $relationWithList = $relationMethodList = $relationVisibleFieldList = []; foreach ($relations as $relation) { // 需要构造关联的方法 $relation['relationMethod'] = strtolower($relation['relationName']); // 关联的模式 $relation['relationMode'] = $relation['relationMode'] == 'hasone' ? 'hasOne' : 'belongsTo'; // 关联字段 $relation['relationPrimaryKey'] = $relation['relationPrimaryKey'] ?: $priKey; // 预载入的方法 $relationWithList[] = $relation['relationMethod']; unset($relation['relationColumnList'], $relation['relationFieldList'], $relation['relationTableInfo']); // 构造关联模型的方法 $relationMethodList[] = $this->stub->getReplacedStub('mixins' . DIRECTORY_SEPARATOR . 'modelBelongsToMethod', $relation); // 显示的字段 if ($relation['relationFields']) { $relationVisibleFieldList[] = "\$res->visible(['{$relation['relationMethod']}' => ['" . implode("','", $relation['relationFields']) . "']]);"; } } $controllerData['relationVisibleFieldList'] = $relationVisibleFieldList ? implode("\n" . Stub::tab(2), $relationVisibleFieldList) : ''; $controllerAttrList['withJoinTable'] = $relationWithList; // 需要重写index方法 if ($controllerData['relationVisibleFieldList']) { $controllerData['methods']['index'] = $this->stub->getReplacedStub('mixins' . DIRECTORY_SEPARATOR . 'controllerIndex', $controllerData); } } // 排除字段 $preExcludeFields = ['createtime', 'updatetime', 'salt']; $controllerExcludeFields = []; foreach ($preExcludeFields as $preExcludeField) { if (in_array($preExcludeField, $fieldArr)) { $controllerExcludeFields[] = $preExcludeField; } } $controllerAttrList['preExcludeFields'] = $controllerExcludeFields; // 控制器属性 $controllerAttr = ''; foreach ($controllerAttrList as $key => $item) { if (is_array($item)) { $controllerAttr .= "\n" . Stub::tab() . "protected \${$key} = ['" . implode("', '", $item) . "'];\n"; } else { $controllerAttr .= "\n" . Stub::tab() . "protected \${$key} = '$item';\n"; } } $controllerData['controllerAttr'] = $controllerAttr; $controllerData['methods'] = (isset($controllerData['methods']) && $controllerData['methods']) ? implode("\n", $controllerData['methods']) : ''; $controllerContent = $this->stub->getReplacedStub('controller', $controllerData); // 生成控制器文件 Stub::writeToFile($controllerFile, $controllerContent); // 生成模型文件 $modelMethodList = array_merge($modelSetAttrArr, $relationMethodList ?? []); $modelData['modelMethodList'] = $modelMethodList ? implode("\n", $modelMethodList) : ''; $modelData['modelFieldType'] = Stub::buildModelFieldType($modelFieldType); $modelContent = $this->stub->getReplacedStub('model', $modelData); Stub::writeToFile($modelFile, $modelContent); // 生成关联模型文件 if ($relations) { foreach ($relations as $relation) { if (!is_file($relation['relationFile'])) { $relationFileContent = $this->stub->getReplacedStub('relationModel', $relation); Stub::writeToFile($relation['relationFile'], $relationFileContent); } } } // 生成验证器文件 Stub::writeToFile($validateFile, $this->stub->getReplacedStub('validate', [ 'validateNamespace' => $validateNamespace, 'validateName' => $validateName ])); // TODO 生成server端语言包文件备用 } catch (ErrorException $e) { throw new Exception('Code: ' . $e->getCode() . "\nLine: " . $e->getLine() . "\nMessage: " . $e->getMessage() . "\nFile: " . $e->getFile()); } // 继续生成菜单 if (MenuRule::where('name', $originControllerUrl)->value('id')) { $output->warning('Menu rules are not automatically created because they are repeated!'); } else { // 建立菜单目录 array_pop($baseNameArr); $pid = 0; if ($baseNameArr) { foreach ($baseNameArr as $item) { $pMenu = MenuRule::where('name', $item)->value('id'); if ($pMenu) { $pid = $pMenu; continue; } $menu = [ 'pid' => $pid, 'type' => 'menu_dir', 'title' => $item, 'name' => $item, 'path' => $item, ]; $menu = MenuRule::create($menu); $pid = $menu->id; } } // 建立菜单 foreach ($this->menuChildren as &$item) { $item['name'] = $originControllerUrl . $item['name']; } $componentPath = '/' . str_replace($webPath, '', $indexFile); $componentPath = str_replace('\\', '/', $componentPath); Menu::create([ [ 'type' => 'menu', 'title' => $tableComment ?: $table, 'name' => $originControllerUrl, 'path' => $originControllerUrl, 'menu_type' => 'tab', 'component' => $componentPath, 'children' => $this->menuChildren, ] ], $pid); } $output->info('Build Successed'); } /** * 获取输入选项 * @param $name string * @return string[]|string */ public function getOption(string $name) { $value = $this->input->getOption($name); // 数组处理,兼容[php think crud -f a,b -f c -f d,e,f] if (array_key_exists($name, $this->options) && $this->options[$name]['solveArray']) { $valueTmp = []; foreach ($value as $item) { if (stripos($item, ',') !== false) { $valueTmp = array_merge($valueTmp, explode(',', $item)); } elseif (stripos($item, ',') !== false) { $valueTmp = array_merge($valueTmp, explode(',', $item)); } else { $valueTmp[] = $item; } } return $valueTmp; } return $value; } public function setInputTypeRule($inputType, $newSuffix = null, $newType = null) { foreach ($this->inputTypeRule as &$item) { if ($item['value'] == $inputType) { if ($newSuffix) $item['suffix'] = $newSuffix; if ($newType) $item['suffix'] = $newType; } } } /** * 获取模型相关信息. * * @param $module * @param $model * @param $table * @return array */ protected function getModelData($module, $model, $table): array { return $this->getParseNameData($module, $model, $table, 'model'); } /** * 获取控制器相关信息. * @param $module * @param $controller * @param $table * @return array */ protected function getControllerData($module, $controller, $table): array { return $this->getParseNameData($module, $controller, $table, 'controller'); } /** * 获取验证器相关信息. * @param $module * @param $validate * @param $table * @return array */ protected function getValidateData($module, $validate, $table): array { return $this->getParseNameData($module, $validate, $table, 'validate'); } /** * 获取已解析相关信息. * * @param string $module 模块名称 * @param string $name 自定义名称 * @param string $table 数据表名 * @param string $type 解析类型,本例中为controller、model、validate * @return array */ protected function getParseNameData($module, $name, $table, $type): array { $arr = []; if (!$name) { $parseName = parse_name($table, 1); $parseArr = [$table]; } else { $name = str_replace(['.', '/', '\\'], '/', $name); $arr = explode('/', $name); $parseName = ucfirst(array_pop($arr)); $parseArr = $arr; array_push($parseArr, $parseName); } //类名不能为内部关键字 if (in_array(strtolower($parseName), $this->reservedKeywords)) { throw new Exception('Unable to use internal variable:' . $parseName); } $appNamespace = 'app'; //Config::get('app_namespace'); $parseNamespace = "{$appNamespace}\\{$module}\\{$type}" . ($arr ? '\\' . implode('\\', $arr) : ''); $moduleDir = app()->getBasePath() . $module . DIRECTORY_SEPARATOR; $parseFile = $moduleDir . $type . DIRECTORY_SEPARATOR . ($arr ? implode(DIRECTORY_SEPARATOR, $arr) . DIRECTORY_SEPARATOR : '') . $parseName . '.php'; return [$parseNamespace, $parseName, $parseFile, $parseArr]; } /** * 移除相对的空目录. * @param string $parseFile * @param array $parseArr * @param int $level * @return bool */ protected function removeEmptyBaseDir(string $parseFile, array $parseArr, int $level = 1): bool { if (count($parseArr) > $level) { $parentDir = dirname($parseFile); for ($i = 0; $i < count($parseArr); $i++) { try { $iterator = new FilesystemIterator($parentDir); $isDirEmpty = !$iterator->valid(); if ($isDirEmpty) { rmdir($parentDir); $parentDir = dirname($parentDir); } else { return true; } } catch (UnexpectedValueException $e) { return false; } } } return true; } protected function getItemArray($item, $field, $comment) { $itemArr = []; $comment = str_replace(',', ',', $comment); if (stripos($comment, ':') !== false && stripos($comment, ',') && stripos($comment, '=') !== false) { [$fieldLang, $item] = explode(':', $comment); foreach (explode(',', $item) as $v) { $valArr = explode('=', $v); if (count($valArr) == 2) { [$key, $value] = $valArr; $itemArr[$key] = $field . ' ' . $key; } } } else { foreach ($item as $v) { $itemArr[$v] = is_numeric($v) ? $field . ' ' . $v : $v; } } return $itemArr; } public function getTableColumn($field, $inputType, $fieldType, $columnData, $relationFieldLang = null) { if (!$relationFieldLang) $relationFieldLang = $field; $nameBool = false; $typeBool = false; $suffixBool = false; $extendAttr = []; foreach ($this->fieldRenderRule as $rule) { if (!$rule['attr']) { continue; } if (isset($rule['name']) && $rule['name'] && in_array($field, $rule['name'])) { $nameBool = true; } if (isset($rule['type']) && $rule['type'] && in_array($inputType, $rule['type'])) { $typeBool = true; } if (isset($item['suffix']) && $item['suffix'] && $this->isMatchSuffix($field, $item['suffix'])) { $suffixBool = true; } if ($nameBool || $typeBool || $suffixBool) { $extendAttr = $rule['attr']; break; } } $column = [ 'label' => "t('" . $this->langPrefix . $relationFieldLang . "')", 'prop' => $field, 'align' => 'center', ]; $column = array_merge($column, $extendAttr); $columnReplaceValue = [ 'tag', 'tags', 'switch', ]; if (isset($column['render']) && in_array($column['render'], $columnReplaceValue)) { $column['replaceValue'] = $columnData; } return $column; } protected function getLangItem($field, $content) { $en[$field] = $field; $zhCn[$field] = $field; if ($content || !Lang::has($field)) { $content = str_replace(',', ',', $content); $content = str_replace(':', ':', $content); if (stripos($content, ':') !== false && stripos($content, ',') && stripos($content, '=') !== false) { [$fieldLang, $item] = explode(':', $content); $zhCn[$field] = $fieldLang; foreach (explode(',', $item) as $v) { $valArr = explode('=', $v); if (count($valArr) == 2) { [$key, $value] = $valArr; $en[$field . ' ' . $key] = $field . ' ' . $key; $zhCn[$field . ' ' . $key] = $value; } } } else { $zhCn[$field] = $content; } } return [ 'en' => $en, 'zh-cn' => $zhCn ]; } protected function getFieldInputType($v) { $inputType = 'string'; foreach ($this->inputTypeRule as $item) { $typeBool = true; $suffixBool = true; $columnTypeBool = true; if (isset($item['type']) && $item['type'] && !in_array($v['DATA_TYPE'], $item['type'])) { $typeBool = false; } if (isset($item['suffix']) && $item['suffix']) { $suffixBool = $this->isMatchSuffix($v['COLUMN_NAME'], $item['suffix']); } if (isset($item['column_type']) && $item['column_type'] && !in_array($v['COLUMN_TYPE'], $item['column_type'])) { $columnTypeBool = false; } if ($typeBool && $suffixBool && $columnTypeBool) { return $item['value']; } } return $inputType; } /** * 判断是否符合指定后缀 * * @param string $field 字段名称 * @param mixed $suffixArr 后缀 * @return bool */ protected function isMatchSuffix($field, $suffixArr) { $suffixArr = is_array($suffixArr) ? $suffixArr : explode(',', $suffixArr); foreach ($suffixArr as $v) { if (preg_match("/{$v}$/i", $field)) { return true; } } return false; } protected function getRemoteSelectField($fieldName, $relations = []) { $field = explode('_', $fieldName); foreach ($relations as $relation) { if ($relation['remoteSelectField'] && $relation['relationTableTypeName'] == $field[0]) { return $relation['remoteSelectField']; } } foreach ($this->remoteSelectFieldMap as $field => $item) { if (in_array($fieldName, $item)) { return $field; } } return 'name'; } protected function getModelAttrMethod(&$modelSetAttrArr, $field, $inputType, $column) { $fieldName = ucfirst($this->getCamelizeName($field)); if ($inputType == 'switch') { $modelSetAttrArr[] = $this->stub->getReplacedStub('modelAttr' . DIRECTORY_SEPARATOR . 'setSwitch', [ 'field' => $fieldName ]); } elseif (in_array($inputType, ['checkbox', 'selects', 'remoteSelects', 'city', 'images', 'files'])) { $modelSetAttrArr[] = $this->stub->getReplacedStub('modelAttr' . DIRECTORY_SEPARATOR . 'stringToArrayMethod', [ 'field' => $fieldName ]); $modelSetAttrArr[] = $this->stub->getReplacedStub('modelAttr' . DIRECTORY_SEPARATOR . 'arrayToStringMethod', [ 'field' => $fieldName ]); } elseif ($inputType == 'array') { $modelSetAttrArr[] = $this->stub->getReplacedStub('modelAttr' . DIRECTORY_SEPARATOR . 'getArray', [ 'field' => $fieldName ]); } elseif (in_array($inputType, ['textarea', 'remoteSelect'])) { $modelSetAttrArr[] = $this->stub->getReplacedStub('modelAttr' . DIRECTORY_SEPARATOR . 'defaultEmptyStringMethod', [ 'field' => $fieldName ]); } elseif ($inputType == 'time') { $modelSetAttrArr[] = $this->stub->getReplacedStub('modelAttr' . DIRECTORY_SEPARATOR . 'setTime', [ 'field' => $fieldName ]); } elseif ($inputType == 'editor') { $modelSetAttrArr[] = $this->stub->getReplacedStub('modelAttr' . DIRECTORY_SEPARATOR . 'getEditor', [ 'field' => $fieldName ]); } } protected function getCamelizeName($uncamelized_words, $separator = '_') { $uncamelized_words = $separator . str_replace($separator, " ", strtolower($uncamelized_words)); return ltrim(str_replace(" ", "", ucwords($uncamelized_words)), $separator); } protected function getRemoteSelectUrl($fieldName, $relations, $webControllerUrls) { $fieldName = explode('_', $fieldName); $defaultValue = [ 'importControllerUrls' => false, 'url' => $fieldName[0], 'pk' => $fieldName[0] . '.id', ]; $relationFile = false; foreach ($relations as $relation) { if ($relation['relationTableTypeName'] == $fieldName[0]) { $relationFile = $relation['relationFile']; $defaultValue['pk'] = $fieldName[0] . '.' . $relation['relationPrimaryKey']; } } if (!$relationFile) { return $defaultValue; } // 在关联模型中获取控制器url名称 $preg = "/@controllerUrl '(.+?)'/i"; $controllerUrlName = $this->getPregValueFromFile($relationFile, $preg); if (!$controllerUrlName) { return $defaultValue; } // 确定$webControllerUrls中存在该url的定义 $preg = '/(' . $controllerUrlName . ')/is'; $existWebControllerUrls = $this->getPregValueFromFile($webControllerUrls, $preg); if (!$existWebControllerUrls) { return $defaultValue; } return [ 'importControllerUrls' => $controllerUrlName, 'url' => $controllerUrlName . " + 'index'", 'pk' => $defaultValue['pk'], ]; } public function getPregValueFromFile($file, $preg, $valIdx = 1) { $fileContent = @file_get_contents($file); if (!$fileContent) { return false; } preg_match($preg, $fileContent, $result); if ($result && isset($result[$valIdx])) { return $result[$valIdx]; } return false; } /** * 列替换数据 */ public function getColumnReplaceData($column, $fieldName, $inputType) { $columnData = []; if (in_array($column['DATA_TYPE'], ['enum', 'set', 'tinyint', 'char'])) { if ($column['DATA_TYPE'] !== 'tinyint') { $columnData = substr($column['COLUMN_TYPE'], strlen($column['DATA_TYPE']) + 1, -1); $columnData = explode(',', str_replace("'", '', $columnData)); } $columnData = $this->getItemArray($columnData, $fieldName, $column['COLUMN_COMMENT']); } if (!$columnData && in_array($inputType, ['select', 'selects'])) { $columnData = $this->getItemArray($columnData, $fieldName, $column['COLUMN_COMMENT']); } if ($columnData) { foreach ($columnData as $key => $columnDatum) { $columnData[$key] = "t('" . $this->langPrefix . $columnDatum . "')"; } } return $columnData; } }