<?php

namespace app\admin\command;

use FilesystemIterator;
use think\Exception;
use think\facade\Db;
use think\facade\Lang;
use think\console\Input;
use think\console\Output;
use think\console\Command;
use think\facade\Config;
use app\common\library\Menu;
use app\admin\model\MenuRule;
use think\console\input\Option;
use think\exception\ErrorException;
use app\admin\command\Crud\library\Stub;
use UnexpectedValueException;

class Crud extends Command
{
    protected $input;
    protected $output;

    /**
     * 命令选项列表
     * solveArray 是否切割数组参数,可自动以`逗号`切割输入值
     * setInputRule 是否设置输入框识别规则,此类选项较多,方便自动化批量设置
     */
    public $options = [
        'table'                   => ['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;
    }
}