feat(CRUD):多数据库连接配置支持

This commit is contained in:
2368302435@qq.com 2024-02-29 21:06:37 +08:00
parent c95fc126d0
commit bbeb267649
11 changed files with 259 additions and 172 deletions

View File

@ -59,7 +59,7 @@ class Crud extends Backend
*/ */
protected array $dtStringToArray = ['checkbox', 'selects', 'remoteSelects', 'city', 'images', 'files']; protected array $dtStringToArray = ['checkbox', 'selects', 'remoteSelects', 'city', 'images', 'files'];
protected array $noNeedPermission = ['logStart', 'getFileData', 'parseFieldData', 'generateCheck', 'databaseList']; protected array $noNeedPermission = ['logStart', 'getFileData', 'parseFieldData', 'generateCheck'];
public function initialize(): void public function initialize(): void
{ {
@ -89,11 +89,11 @@ class Crud extends Backend
]); ]);
// 表名称 // 表名称
$tableName = TableManager::tableName($table['name'], false); $tableName = TableManager::tableName($table['name'], false, $table['databaseConnection']);
if ($type == 'create' || $table['rebuild'] == 'Yes') { if ($type == 'create' || $table['rebuild'] == 'Yes') {
// 数据表存在则删除 // 数据表存在则删除
TableManager::phinxTable($tableName)->drop()->save(); TableManager::phinxTable($tableName, [], true, $table['databaseConnection'])->drop()->save();
} }
// 处理表设计 // 处理表设计
@ -127,6 +127,7 @@ class Crud extends Backend
$this->modelData['beforeInsertMixins'] = []; $this->modelData['beforeInsertMixins'] = [];
$this->modelData['beforeInsert'] = ''; $this->modelData['beforeInsert'] = '';
$this->modelData['afterInsert'] = ''; $this->modelData['afterInsert'] = '';
$this->modelData['connection'] = $table['databaseConnection'];
$this->modelData['name'] = $tableName; $this->modelData['name'] = $tableName;
$this->modelData['className'] = $modelFile['lastName']; $this->modelData['className'] = $modelFile['lastName'];
$this->modelData['namespace'] = $modelFile['namespace']; $this->modelData['namespace'] = $modelFile['namespace'];
@ -205,7 +206,7 @@ class Crud extends Backend
// 关联表数据解析 // 关联表数据解析
if (in_array($field['designType'], ['remoteSelect', 'remoteSelects'])) { if (in_array($field['designType'], ['remoteSelect', 'remoteSelects'])) {
$this->parseJoinData($field); $this->parseJoinData($field, $table);
} }
// 模型方法 // 模型方法
@ -308,9 +309,15 @@ class Crud extends Backend
} }
// 数据表是否有数据 // 数据表是否有数据
$adapter = TableManager::phinxAdapter(); $connection = TableManager::getConnection($info['table']['databaseConnection']);
if ($adapter->hasTable($info['table']['name'])) { $tableName = TableManager::tableName($info['table']['name'], false, $connection);
$info['table']['empty'] = Db::name($info['table']['name'])->limit(1)->select()->isEmpty(); $adapter = TableManager::phinxAdapter(true, $connection);
if ($adapter->hasTable($tableName)) {
$info['table']['empty'] = Db::connect($connection)
->name($tableName)
->limit(1)
->select()
->isEmpty();
} else { } else {
$info['table']['empty'] = true; $info['table']['empty'] = true;
} }
@ -438,9 +445,13 @@ class Crud extends Backend
*/ */
public function checkCrudLog(): void public function checkCrudLog(): void
{ {
$table = $this->request->get('table'); $table = $this->request->get('table');
$connection = $this->request->get('connection');
$connection = $connection ?: config('database.default');
$crudLog = Db::name('crud_log') $crudLog = Db::name('crud_log')
->where('table_name', $table) ->where('table_name', $table)
->where('connection', $connection)
->order('create_time desc') ->order('create_time desc')
->find(); ->find();
$this->success('', [ $this->success('', [
@ -455,27 +466,36 @@ class Crud extends Backend
public function parseFieldData(): void public function parseFieldData(): void
{ {
AdminLog::setTitle(__('Parse field data')); AdminLog::setTitle(__('Parse field data'));
$type = $this->request->post('type'); $type = $this->request->post('type');
$table = $this->request->post('table'); $table = $this->request->post('table');
$table = TableManager::tableName($table); $connection = $this->request->post('connection');
$connection = TableManager::getConnection($connection);
$table = TableManager::tableName($table, true, $connection);
$connectionConfig = TableManager::getConnectionConfig($connection);
if ($type == 'db') { if ($type == 'db') {
$sql = 'SELECT * FROM `information_schema`.`tables` ' $sql = 'SELECT * FROM `information_schema`.`tables` '
. 'WHERE TABLE_SCHEMA = ? AND table_name = ?'; . 'WHERE TABLE_SCHEMA = ? AND table_name = ?';
$tableInfo = Db::query($sql, [config('database.connections.mysql.database'), $table]); $tableInfo = Db::connect($connection)->query($sql, [$connectionConfig['database'], $table]);
if (!$tableInfo) { if (!$tableInfo) {
$this->error(__('Record not found')); $this->error(__('Record not found'));
} }
// 数据表是否有数据 // 数据表是否有数据
$adapter = TableManager::phinxAdapter(false); $adapter = TableManager::phinxAdapter(false, $connection);
if ($adapter->hasTable($table)) { if ($adapter->hasTable($table)) {
$empty = Db::table($table)->limit(1)->select()->isEmpty(); $empty = Db::connect($connection)
->table($table)
->limit(1)
->select()
->isEmpty();
} else { } else {
$empty = true; $empty = true;
} }
$this->success('', [ $this->success('', [
'columns' => Helper::parseTableColumns($table), 'columns' => Helper::parseTableColumns($table, false, $connection),
'comment' => $tableInfo[0]['TABLE_COMMENT'] ?? '', 'comment' => $tableInfo[0]['TABLE_COMMENT'] ?? '',
'empty' => $empty, 'empty' => $empty,
]); ]);
@ -490,6 +510,7 @@ class Crud extends Backend
{ {
$table = $this->request->post('table'); $table = $this->request->post('table');
$controllerFile = $this->request->post('controllerFile', ''); $controllerFile = $this->request->post('controllerFile', '');
$connection = $this->request->post('connection');
if (!$table) { if (!$table) {
$this->error(__('Parameter error')); $this->error(__('Parameter error'));
@ -505,8 +526,8 @@ class Crud extends Backend
$this->error($e->getMessage()); $this->error($e->getMessage());
} }
$tableList = TableManager::getTableList(); $tableList = TableManager::getTableList($connection);
$tableExist = array_key_exists(TableManager::tableName($table), $tableList); $tableExist = array_key_exists(TableManager::tableName($table, true, $connection), $tableList);
$controllerExist = file_exists(root_path() . $controllerFile); $controllerExist = file_exists(root_path() . $controllerFile);
if ($controllerExist || $tableExist) { if ($controllerExist || $tableExist) {
@ -518,55 +539,21 @@ class Crud extends Backend
$this->success(); $this->success();
} }
/**
* 数据表列表
* @throws Throwable
*/
public function databaseList(): void
{
$tablePrefix = config('database.connections.mysql.prefix');
$outExcludeTable = [
// 功能表
'area',
'token',
'captcha',
'admin_group_access',
'config',
'admin_log',
// 不建议生成crud的表
'user_money_log',
'user_score_log',
];
$outTables = [];
$tables = TableManager::getTableList();
$pattern = '/^' . $tablePrefix . '/i';
foreach ($tables as $table => $tableComment) {
if (!preg_match($pattern, $table)) continue;
$table = preg_replace($pattern, '', $table);
if (!in_array($table, $outExcludeTable)) {
$outTables[$table] = $tableComment;
}
}
$this->success('', [
'dbs' => $outTables,
]);
}
/** /**
* 关联表数据解析 * 关联表数据解析
* @param $field * @param $field
* @param $table
* @throws Throwable * @throws Throwable
*/ */
private function parseJoinData($field): void private function parseJoinData($field, $table): void
{ {
$dictEn = []; $dictEn = [];
$dictZhCn = []; $dictZhCn = [];
if ($field['form']['relation-fields'] && $field['form']['remote-table']) { if ($field['form']['relation-fields'] && $field['form']['remote-table']) {
$columns = Helper::parseTableColumns($field['form']['remote-table'], true); $columns = Helper::parseTableColumns($field['form']['remote-table'], true, $table['databaseConnection']);
$relationFields = explode(',', $field['form']['relation-fields']); $relationFields = explode(',', $field['form']['relation-fields']);
$tableName = TableManager::tableName($field['form']['remote-table'], false); $tableName = TableManager::tableName($field['form']['remote-table'], false, $table['databaseConnection']);
$rnPattern = '/(.*)(_ids|_id)$/'; $rnPattern = '/(.*)(_ids|_id)$/';
if (preg_match($rnPattern, $field['name'])) { if (preg_match($rnPattern, $field['name'])) {
$relationName = parse_name(preg_replace($rnPattern, '$1', $field['name']), 1, false); $relationName = parse_name(preg_replace($rnPattern, '$1', $field['name']), 1, false);
@ -586,6 +573,7 @@ class Crud extends Backend
$joinModelData['beforeInsertMixins'] = []; $joinModelData['beforeInsertMixins'] = [];
$joinModelData['beforeInsert'] = ''; $joinModelData['beforeInsert'] = '';
$joinModelData['afterInsert'] = ''; $joinModelData['afterInsert'] = '';
$joinModelData['connection'] = $table['databaseConnection'];
$joinModelData['name'] = $tableName; $joinModelData['name'] = $tableName;
$joinModelData['className'] = $joinModelFile['lastName']; $joinModelData['className'] = $joinModelFile['lastName'];
$joinModelData['namespace'] = $joinModelFile['namespace']; $joinModelData['namespace'] = $joinModelFile['namespace'];

View File

@ -281,10 +281,13 @@ class Helper
]); ]);
return $data['id']; return $data['id'];
} }
$log = CrudLog::create([
$connection = $data['table']['databaseConnection'] ?: config('database.default');
$log = CrudLog::create([
'table_name' => $data['table']['name'], 'table_name' => $data['table']['name'],
'table' => $data['table'], 'table' => $data['table'],
'fields' => $data['fields'], 'fields' => $data['fields'],
'connection' => $connection,
'status' => $data['status'], 'status' => $data['status'],
]); ]);
return $log->id; return $log->id;
@ -424,16 +427,17 @@ class Helper
/** /**
* 表字段排序 * 表字段排序
* @param string $tableName 表名 * @param string $tableName 表名
* @param array $fields 字段数据 * @param array $fields 字段数据
* @param array $designChange 前端字段改变数据 * @param array $designChange 前端字段改变数据
* @param ?string $connection 数据库连接标识
* @return void * @return void
* @throws Throwable * @throws Throwable
*/ */
public static function updateFieldOrder(string $tableName, array $fields, array $designChange): void public static function updateFieldOrder(string $tableName, array $fields, array $designChange, ?string $connection = null): void
{ {
if ($designChange) { if ($designChange) {
$table = TableManager::phinxTable($tableName, [], false); $table = TableManager::phinxTable($tableName, [], false, $connection);
foreach ($designChange as $item) { foreach ($designChange as $item) {
if (!$item['sync']) continue; if (!$item['sync']) continue;
@ -469,10 +473,10 @@ class Helper
*/ */
public static function handleTableDesign(array $table, array $fields): array public static function handleTableDesign(array $table, array $fields): array
{ {
$name = TableManager::tableName($table['name']); $name = TableManager::tableName($table['name'], true, $table['databaseConnection']);
$comment = $table['comment'] ?? ''; $comment = $table['comment'] ?? '';
$designChange = $table['designChange'] ?? []; $designChange = $table['designChange'] ?? [];
$adapter = TableManager::phinxAdapter(false); $adapter = TableManager::phinxAdapter(false, $table['databaseConnection']);
$pk = self::searchArray($fields, function ($item) { $pk = self::searchArray($fields, function ($item) {
return $item['primaryKey']; return $item['primaryKey'];
@ -482,8 +486,8 @@ class Helper
if ($adapter->hasTable($name)) { if ($adapter->hasTable($name)) {
// 更新表 // 更新表
if ($designChange) { if ($designChange) {
$table = TableManager::phinxTable($name, [], false); $tableManager = TableManager::phinxTable($name, [], false, $table['databaseConnection']);
$table->changeComment($comment)->update(); $tableManager->changeComment($comment)->update();
// 改名和删除操作优先 // 改名和删除操作优先
$priorityOpt = false; $priorityOpt = false;
@ -491,23 +495,23 @@ class Helper
if (!$item['sync']) continue; if (!$item['sync']) continue;
if (in_array($item['type'], ['change-field-name', 'del-field']) && !$table->hasColumn($item['oldName'])) { if (in_array($item['type'], ['change-field-name', 'del-field']) && !$tableManager->hasColumn($item['oldName'])) {
// 字段不存在 // 字段不存在
throw new BaException(__($item['type'] . ' fail not exist', [$item['oldName']])); throw new BaException(__($item['type'] . ' fail not exist', [$item['oldName']]));
} }
if ($item['type'] == 'change-field-name') { if ($item['type'] == 'change-field-name') {
$priorityOpt = true; $priorityOpt = true;
$table->renameColumn($item['oldName'], $item['newName']); $tableManager->renameColumn($item['oldName'], $item['newName']);
} elseif ($item['type'] == 'del-field') { } elseif ($item['type'] == 'del-field') {
$priorityOpt = true; $priorityOpt = true;
$table->removeColumn($item['oldName']); $tableManager->removeColumn($item['oldName']);
} }
} }
// 保存需要优先执行的操作,避免先改名再改属性时找不到字段 // 保存需要优先执行的操作,避免先改名再改属性时找不到字段
if ($priorityOpt) { if ($priorityOpt) {
$table->update(); $tableManager->update();
} }
// 修改字段属性和添加字段操作 // 修改字段属性和添加字段操作
@ -517,7 +521,7 @@ class Helper
if ($item['type'] == 'change-field-attr') { if ($item['type'] == 'change-field-attr') {
if (!$table->hasColumn($item['oldName'])) { if (!$tableManager->hasColumn($item['oldName'])) {
// 字段不存在 // 字段不存在
throw new BaException(__($item['type'] . ' fail not exist', [$item['oldName']])); throw new BaException(__($item['type'] . ' fail not exist', [$item['oldName']]));
} }
@ -525,10 +529,10 @@ class Helper
$phinxFieldData = self::getPhinxFieldData(self::searchArray($fields, function ($field) use ($item) { $phinxFieldData = self::getPhinxFieldData(self::searchArray($fields, function ($field) use ($item) {
return $field['name'] == $item['oldName']; return $field['name'] == $item['oldName'];
})); }));
$table->changeColumn($item['oldName'], $phinxFieldData['type'], $phinxFieldData['options']); $tableManager->changeColumn($item['oldName'], $phinxFieldData['type'], $phinxFieldData['options']);
} elseif ($item['type'] == 'add-field') { } elseif ($item['type'] == 'add-field') {
if ($table->hasColumn($item['newName'])) { if ($tableManager->hasColumn($item['newName'])) {
// 字段已经存在 // 字段已经存在
throw new BaException(__($item['type'] . ' fail exist', [$item['newName']])); throw new BaException(__($item['type'] . ' fail exist', [$item['newName']]));
} }
@ -536,28 +540,28 @@ class Helper
$phinxFieldData = self::getPhinxFieldData(self::searchArray($fields, function ($field) use ($item) { $phinxFieldData = self::getPhinxFieldData(self::searchArray($fields, function ($field) use ($item) {
return $field['name'] == $item['newName']; return $field['name'] == $item['newName'];
})); }));
$table->addColumn($item['newName'], $phinxFieldData['type'], $phinxFieldData['options']); $tableManager->addColumn($item['newName'], $phinxFieldData['type'], $phinxFieldData['options']);
} }
} }
$table->update(); $tableManager->update();
// 表更新结构完成再处理字段排序 // 表更新结构完成再处理字段排序
self::updateFieldOrder($name, $fields, $designChange); self::updateFieldOrder($name, $fields, $designChange, $table['databaseConnection']);
} }
} else { } else {
// 创建表 // 创建表
$table = TableManager::phinxTable($name, [ $tableManager = TableManager::phinxTable($name, [
'id' => false, 'id' => false,
'comment' => $comment, 'comment' => $comment,
'row_format' => 'DYNAMIC', 'row_format' => 'DYNAMIC',
'primary_key' => $pk, 'primary_key' => $pk,
'collation' => 'utf8mb4_unicode_ci', 'collation' => 'utf8mb4_unicode_ci',
], false); ], false, $table['databaseConnection']);
foreach ($fields as $field) { foreach ($fields as $field) {
$phinxFieldData = self::getPhinxFieldData($field); $phinxFieldData = self::getPhinxFieldData($field);
$table->addColumn($field['name'], $phinxFieldData['type'], $phinxFieldData['options']); $tableManager->addColumn($field['name'], $phinxFieldData['type'], $phinxFieldData['options']);
} }
$table->create(); $tableManager->create();
} }
return [$pk]; return [$pk];
@ -732,15 +736,19 @@ class Helper
* 根据数据表解析字段数据 * 根据数据表解析字段数据
* @throws Throwable * @throws Throwable
*/ */
public static function parseTableColumns(string $table, bool $analyseField = false): array public static function parseTableColumns(string $table, bool $analyseField = false, ?string $connection = null): array
{ {
$connection = TableManager::getConnection($connection);
$connectionConfig = TableManager::getConnectionConfig($connection);
// 从数据库中获取表字段信息 // 从数据库中获取表字段信息
$sql = 'SELECT * FROM `information_schema`.`columns` ' $sql = 'SELECT * FROM `information_schema`.`columns` '
. 'WHERE TABLE_SCHEMA = ? AND table_name = ? ' . 'WHERE TABLE_SCHEMA = ? AND table_name = ? '
. 'ORDER BY ORDINAL_POSITION'; . 'ORDER BY ORDINAL_POSITION';
$columns = []; $columns = [];
$tableColumn = Db::query($sql, [config('database.connections.mysql.database'), TableManager::tableName($table)]); $tableColumn = Db::connect($connection)->query($sql, [$connectionConfig['database'], TableManager::tableName($table, true, $connection)]);
foreach ($tableColumn as $item) { foreach ($tableColumn as $item) {
$isNullAble = $item['IS_NULLABLE'] == 'YES'; $isNullAble = $item['IS_NULLABLE'] == 'YES';
if (str_contains($item['COLUMN_TYPE'], '(')) { if (str_contains($item['COLUMN_TYPE'], '(')) {
@ -989,7 +997,13 @@ class Helper
public static function writeModelFile(string $tablePk, array $fieldsMap, array $modelData, array $modelFile): void public static function writeModelFile(string $tablePk, array $fieldsMap, array $modelData, array $modelFile): void
{ {
$modelData['pk'] = $tablePk == 'id' ? '' : "\n" . self::tab() . "// 表主键\n" . self::tab() . 'protected $pk = ' . "'$tablePk';\n" . self::tab(); if ($modelData['connection'] && $modelData['connection'] != config('database.default')) {
$modelData['connection'] = "\n" . self::tab() . "// 数据库连接配置标识\n" . self::tab() . 'protected $connection = ' . "'{$modelData['connection']}';\n";
} else {
$modelData['connection'] = '';
}
$modelData['pk'] = $tablePk == 'id' ? '' : "\n" . self::tab() . "// 表主键\n" . self::tab() . 'protected $pk = ' . "'$tablePk';\n";
$modelData['autoWriteTimestamp'] = array_key_exists(self::$createTimeField, $fieldsMap) || array_key_exists(self::$updateTimeField, $fieldsMap) ? 'true' : 'false'; $modelData['autoWriteTimestamp'] = array_key_exists(self::$createTimeField, $fieldsMap) || array_key_exists(self::$updateTimeField, $fieldsMap) ? 'true' : 'false';
if ($modelData['autoWriteTimestamp'] == 'true') { if ($modelData['autoWriteTimestamp'] == 'true') {
$modelData['createTime'] = array_key_exists(self::$createTimeField, $fieldsMap) ? '' : "\n" . self::tab() . "protected \$createTime = false;"; $modelData['createTime'] = array_key_exists(self::$createTimeField, $fieldsMap) ? '' : "\n" . self::tab() . "protected \$createTime = false;";

View File

@ -8,7 +8,7 @@ use think\Model;
* {%className%} * {%className%}
*/ */
class {%className%} extends Model class {%className%} extends Model
{{%pk%} {{%connection%}{%pk%}
// 表名 // 表名
protected $name = '{%name%}'; protected $name = '{%name%}';

View File

@ -26,5 +26,11 @@ class Version206 extends Migrator
$table = $this->table('config'); $table = $this->table('config');
$table->insert($rows)->saveData(); $table->insert($rows)->saveData();
} }
$crudLog = $this->table('crud_log');
if (!$crudLog->hasColumn('connection')) {
$crudLog->addColumn('connection', 'string', ['limit' => 100, 'default' => '', 'comment' => '数据库连接配置标识', 'null' => false, 'after' => 'status']);
$crudLog->save();
}
} }
} }

View File

@ -15,13 +15,6 @@ export function generate(data: anyObj) {
) )
} }
export function getDatabaseList() {
return createAxios({
url: url + 'databaseList',
method: 'get',
})
}
export function getFileData(table: string, commonModel = 0) { export function getFileData(table: string, commonModel = 0) {
return createAxios({ return createAxios({
url: url + 'getFileData', url: url + 'getFileData',
@ -46,15 +39,11 @@ export function generateCheck(data: anyObj) {
) )
} }
export function parseFieldData(type: string, table = '', sql = '') { export function parseFieldData(data: anyObj) {
return createAxios({ return createAxios({
url: url + 'parseFieldData', url: url + 'parseFieldData',
method: 'post', method: 'post',
data: { data: data,
type: type,
table: table,
sql: sql,
},
}) })
} }
@ -78,12 +67,13 @@ export function postDel(id: number) {
}) })
} }
export function checkCrudLog(table: string) { export function checkCrudLog(table: string, connection: string) {
return createAxios({ return createAxios({
url: url + 'checkCrudLog', url: url + 'checkCrudLog',
method: 'get', method: 'get',
params: { params: {
table: table, table: table,
connection: connection,
}, },
}) })
} }

View File

@ -71,6 +71,10 @@ export default {
'Generated Validator Location': 'Generated Validator Location', 'Generated Validator Location': 'Generated Validator Location',
'Common model': 'Common model', 'Common model': 'Common model',
'WEB end view directory': 'WEB end view directory', 'WEB end view directory': 'WEB end view directory',
'Database connection': 'Database connection',
'Database connection help': 'You can configure multiple database connections in config/database.php and select it here',
'Check model class': "Check whether protected $connection = '{connection}'; is configured in the above data model class",
'There is no connection attribute in model class': 'If no configuration is available, you can configure it manually',
'Advanced Configuration': 'Advanced Configuration', 'Advanced Configuration': 'Advanced Configuration',
'Common Fields': 'Common Fields', 'Common Fields': 'Common Fields',
'Base Fields': 'Base Fields', 'Base Fields': 'Base Fields',

View File

@ -69,6 +69,10 @@ export default {
'Generated Data Model Location': '生成的数据模型位置', 'Generated Data Model Location': '生成的数据模型位置',
'Generated Validator Location': '生成的验证器位置', 'Generated Validator Location': '生成的验证器位置',
'WEB end view directory': 'WEB端视图目录', 'WEB end view directory': 'WEB端视图目录',
'Database connection': '数据库连接配置标识',
'Database connection help': '您可以在 config/database.php 内配置多个数据库连接,然后在此处选择它',
'Check model class': "请检查以上数据模型类中是否已经配置 protected $connection = '{connection}';",
'There is no connection attribute in model class': '未配置请手动配置。',
'Common model': '公共模型', 'Common model': '公共模型',
'Advanced Configuration': '高级配置', 'Advanced Configuration': '高级配置',
'Common Fields': '常用字段', 'Common Fields': '常用字段',

View File

@ -138,6 +138,20 @@
labelWidth: 140, labelWidth: 140,
}" }"
/> />
<FormItem
:label="t('crud.crud.Database connection')"
v-model="state.table.databaseConnection"
type="remoteSelect"
:attr="{
labelWidth: 140,
blockHelp: t('crud.crud.Database connection help'),
}"
:input-attr="{
pk: 'key',
field: 'key',
'remote-url': getDatabaseConnectionListUrl,
}"
/>
</div> </div>
</div> </div>
</transition> </transition>
@ -418,15 +432,31 @@
v-if="state.remoteSelectPre.index != -1 && state.fields[state.remoteSelectPre.index]" v-if="state.remoteSelectPre.index != -1 && state.fields[state.remoteSelectPre.index]"
> >
<FormItem <FormItem
prop="table"
type="select"
:label="t('crud.crud.Associated Data Table')" :label="t('crud.crud.Associated Data Table')"
v-model="state.remoteSelectPre.form.table" v-model="state.remoteSelectPre.form.table"
:key="JSON.stringify(state.remoteSelectPre.dbList)" type="remoteSelect"
:data="{ :key="state.table.databaseConnection"
content: state.remoteSelectPre.dbList, :input-attr="{
pk: 'table',
field: 'comment',
params: {
connection: state.table.databaseConnection,
samePrefix: 1,
excludeTable: [
'area',
'token',
'captcha',
'admin_group_access',
'config',
'admin_log',
'user_money_log',
'user_score_log',
],
},
'remote-url': getTableListUrl,
onChange: onJoinTableChange,
}" }"
:input-attr="{ onChange: onJoinTableChange }" prop="table"
/> />
<div v-loading="state.loading.remoteSelect"> <div v-loading="state.loading.remoteSelect">
<FormItem <FormItem
@ -494,6 +524,16 @@
), ),
}" }"
/> />
<el-form-item
v-if="state.table.databaseConnection && state.remoteSelectPre.form.modelFile"
:label="t('crud.crud.Database connection')"
>
<el-text size="large" type="danger">{{ state.table.databaseConnection }}</el-text>
<div class="block-help">
<div>{{ t('crud.crud.Check model class', { connection: state.table.databaseConnection }) }}</div>
<div>{{ t('crud.crud.There is no connection attribute in model class') }}</div>
</div>
</el-form-item>
<el-form-item :label="t('Reminder')"> <el-form-item :label="t('Reminder')">
<div class="block-help"> <div class="block-help">
{{ t('crud.crud.Design remote select tips') }} {{ t('crud.crud.Design remote select tips') }}
@ -604,8 +644,8 @@ import { useTemplateRefsList } from '@vueuse/core'
import { changeStep, state as crudState, getTableAttr, fieldItem, designTypes, tableFieldsKey } from '/@/views/backend/crud/index' import { changeStep, state as crudState, getTableAttr, fieldItem, designTypes, tableFieldsKey } from '/@/views/backend/crud/index'
import { ElNotification, ElMessageBox, ElMessage } from 'element-plus' import { ElNotification, ElMessageBox, ElMessage } from 'element-plus'
import type { FormItemRule, FormInstance, TimelineItemProps, MessageHandler } from 'element-plus' import type { FormItemRule, FormInstance, TimelineItemProps, MessageHandler } from 'element-plus'
import { getDatabaseList, getFileData, generateCheck, generate, parseFieldData, postLogStart } from '/@/api/backend/crud' import { getFileData, generateCheck, generate, parseFieldData, postLogStart } from '/@/api/backend/crud'
import { getTableFieldList } from '/@/api/common' import { getTableFieldList, getTableListUrl, getDatabaseConnectionListUrl } from '/@/api/common'
import { buildValidatorData, regularVarName } from '/@/utils/validate' import { buildValidatorData, regularVarName } from '/@/utils/validate'
import { getArrayKey } from '/@/utils/common' import { getArrayKey } from '/@/utils/common'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
@ -635,6 +675,7 @@ const state: {
controllerFile: string controllerFile: string
validateFile: string validateFile: string
webViewsDir: string webViewsDir: string
databaseConnection: string
designChange: TableDesignChange[] designChange: TableDesignChange[]
rebuild: string rebuild: string
} }
@ -644,7 +685,6 @@ const state: {
remoteSelectPre: { remoteSelectPre: {
show: boolean show: boolean
index: number index: number
dbList: anyObj
fieldList: anyObj fieldList: anyObj
modelFileList: anyObj modelFileList: anyObj
controllerFileList: anyObj controllerFileList: anyObj
@ -692,6 +732,7 @@ const state: {
controllerFile: '', controllerFile: '',
validateFile: '', validateFile: '',
webViewsDir: '', webViewsDir: '',
databaseConnection: '',
designChange: [], designChange: [],
rebuild: 'No', rebuild: 'No',
}, },
@ -701,7 +742,6 @@ const state: {
remoteSelectPre: { remoteSelectPre: {
show: false, show: false,
index: -1, index: -1,
dbList: [],
fieldList: [], fieldList: [],
modelFileList: [], modelFileList: [],
controllerFileList: [], controllerFileList: [],
@ -884,34 +924,30 @@ const showRemoteSelectPre = (index: number, hideDelField = false) => {
state.remoteSelectPre.loading = true state.remoteSelectPre.loading = true
state.remoteSelectPre.index = index state.remoteSelectPre.index = index
state.remoteSelectPre.hideDelField = hideDelField state.remoteSelectPre.hideDelField = hideDelField
getDatabaseList()
.then((res) => { if (state.fields[index] && state.fields[index].form['remote-table'].value) {
state.remoteSelectPre.dbList = res.data.dbs state.remoteSelectPre.form.table = state.fields[index].form['remote-table'].value
if (state.fields[index] && state.fields[index].form['remote-table'].value) { state.remoteSelectPre.form.pk = state.fields[index].form['remote-pk'].value
state.remoteSelectPre.form.table = state.fields[index].form['remote-table'].value state.remoteSelectPre.form.label = state.fields[index].form['remote-field'].value
state.remoteSelectPre.form.pk = state.fields[index].form['remote-pk'].value state.remoteSelectPre.form.controllerFile = state.fields[index].form['remote-controller'].value
state.remoteSelectPre.form.label = state.fields[index].form['remote-field'].value state.remoteSelectPre.form.modelFile = state.fields[index].form['remote-model'].value
state.remoteSelectPre.form.controllerFile = state.fields[index].form['remote-controller'].value state.remoteSelectPre.form.joinField = state.fields[index].form['relation-fields'].value.split(',')
state.remoteSelectPre.form.modelFile = state.fields[index].form['remote-model'].value getTableFieldList(state.fields[index].form['remote-table'].value).then((res) => {
state.remoteSelectPre.form.joinField = state.fields[index].form['relation-fields'].value.split(',') const fieldSelect: anyObj = {}
getTableFieldList(state.fields[index].form['remote-table'].value).then((res) => { for (const key in res.data.fieldList) {
const fieldSelect: anyObj = {} fieldSelect[key] = (key ? key + ' - ' : '') + res.data.fieldList[key]
for (const key in res.data.fieldList) {
fieldSelect[key] = (key ? key + ' - ' : '') + res.data.fieldList[key]
}
state.remoteSelectPre.fieldList = fieldSelect
})
if (isEmpty(state.remoteSelectPre.modelFileList) || isEmpty(state.remoteSelectPre.controllerFileList)) {
getFileData(state.fields[index].form['remote-table'].value).then((res) => {
state.remoteSelectPre.modelFileList = res.data.modelFileList
state.remoteSelectPre.controllerFileList = res.data.controllerFileList
})
}
} }
state.remoteSelectPre.fieldList = fieldSelect
}) })
.finally(() => { if (isEmpty(state.remoteSelectPre.modelFileList) || isEmpty(state.remoteSelectPre.controllerFileList)) {
state.remoteSelectPre.loading = false getFileData(state.fields[index].form['remote-table'].value).then((res) => {
}) state.remoteSelectPre.modelFileList = res.data.modelFileList
state.remoteSelectPre.controllerFileList = res.data.controllerFileList
})
}
}
state.remoteSelectPre.loading = false
} }
const onEditField = (index: number, field: FieldItem) => { const onEditField = (index: number, field: FieldItem) => {
@ -987,6 +1023,7 @@ const onGenerate = () => {
state.loading.generate = true state.loading.generate = true
generateCheck({ generateCheck({
table: state.table.name, table: state.table.name,
connection: state.table.databaseConnection,
controllerFile: state.table.controllerFile, controllerFile: state.table.controllerFile,
}) })
.then(() => { .then(() => {
@ -1112,7 +1149,12 @@ const loadData = () => {
} }
// sql // sql
parseFieldData(crudState.type, crudState.startData.db, crudState.startData.sql) parseFieldData({
type: crudState.type,
table: crudState.startData.table,
sql: crudState.startData.sql,
connection: crudState.startData.databaseConnection,
})
.then((res) => { .then((res) => {
let fields = [] let fields = []
for (const key in res.data.columns) { for (const key in res.data.columns) {
@ -1134,12 +1176,13 @@ const loadData = () => {
} }
state.fields = fields state.fields = fields
state.table.comment = res.data.comment state.table.comment = res.data.comment
state.table.databaseConnection = crudState.startData.databaseConnection
if (res.data.empty) { if (res.data.empty) {
state.table.rebuild = 'Yes' state.table.rebuild = 'Yes'
} }
if (crudState.type == 'db' && crudState.startData.db) { if (crudState.type == 'db' && crudState.startData.table) {
state.table.name = crudState.startData.db state.table.name = crudState.startData.table
onTableChange(crudState.startData.db) onTableChange(crudState.startData.table)
} }
}) })
.finally(() => { .finally(() => {

View File

@ -27,17 +27,19 @@ export const state: {
step: 'Start' | 'Design' step: 'Start' | 'Design'
type: string type: string
startData: { startData: {
db: string
sql: string sql: string
table: string
logId: string logId: string
databaseConnection: string
} }
} = reactive({ } = reactive({
step: 'Start', step: 'Start',
type: '', type: '',
startData: { startData: {
db: '',
sql: '', sql: '',
table: '',
logId: '', logId: '',
databaseConnection: '',
}, },
}) })

View File

@ -13,7 +13,15 @@
:buttons="['refresh', 'quickSearch', 'columnDisplay']" :buttons="['refresh', 'quickSearch', 'columnDisplay']"
:quick-search-placeholder="t('Quick search placeholder', { fields: t('crud.log.quick Search Fields') })" :quick-search-placeholder="t('Quick search placeholder', { fields: t('crud.log.quick Search Fields') })"
/> />
<Table ref="tableRef" /> <Table ref="tableRef">
<template #tableName>
<el-table-column :show-overflow-tooltip="true" prop="table_name" align="center" :label="t('crud.log.table_name')">
<template #default="scope">
{{ (scope.row.table.databaseConnection ? scope.row.table.databaseConnection + '.' : '') + scope.row.table.name }}
</template>
</el-table-column>
</template>
</Table>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
@ -105,11 +113,10 @@ const baTable = new baTableClass(
{ label: t('crud.log.id'), prop: 'id', align: 'center', width: 70, operator: '=', operatorPlaceholder: t('Id'), sortable: 'custom' }, { label: t('crud.log.id'), prop: 'id', align: 'center', width: 70, operator: '=', operatorPlaceholder: t('Id'), sortable: 'custom' },
{ {
label: t('crud.log.table_name'), label: t('crud.log.table_name'),
prop: 'table_name',
align: 'center',
operatorPlaceholder: t('Fuzzy query'), operatorPlaceholder: t('Fuzzy query'),
operator: 'LIKE', operator: 'LIKE',
sortable: false, render: 'slot',
slotName: 'tableName',
}, },
{ {
label: t('crud.log.status'), label: t('crud.log.status'),

View File

@ -59,7 +59,7 @@
</el-row> </el-row>
<el-dialog <el-dialog
class="ba-operate-dialog select-db-dialog" class="ba-operate-dialog select-table-dialog"
v-model="state.dialog.visible" v-model="state.dialog.visible"
:title="state.dialog.type == 'sql' ? t('crud.crud.Please enter SQL') : t('crud.crud.Please select a data table')" :title="state.dialog.type == 'sql' ? t('crud.crud.Please enter SQL') : t('crud.crud.Please select a data table')"
:destroy-on-close="true" :destroy-on-close="true"
@ -67,7 +67,7 @@
<el-form <el-form
:label-width="140" :label-width="140"
@keyup.enter="onSubmit()" @keyup.enter="onSubmit()"
class="select-db-form" class="select-table-form"
ref="formRef" ref="formRef"
:model="crudState.startData" :model="crudState.startData"
:rules="rules" :rules="rules"
@ -87,22 +87,51 @@
</template> </template>
<template v-else-if="state.dialog.type == 'db'"> <template v-else-if="state.dialog.type == 'db'">
<FormItem <FormItem
:label="t('crud.crud.data sheet')" :label="t('crud.crud.Database connection')"
class="select-db" v-model="crudState.startData.databaseConnection"
v-model="crudState.startData.db" type="remoteSelect"
type="select"
:key="JSON.stringify(state.dialog.dbList)"
:placeholder="t('crud.crud.Please select a data table')"
:data="{
content: state.dialog.dbList,
}"
:attr="{ :attr="{
labelWidth: 140,
blockHelp: t('crud.crud.Database connection help'),
}"
:input-attr="{
pk: 'key',
field: 'key',
'remote-url': getDatabaseConnectionListUrl,
onChange: onDatabaseChange,
}"
/>
<FormItem
:label="t('crud.crud.data sheet')"
v-model="crudState.startData.table"
type="remoteSelect"
:key="crudState.startData.databaseConnection"
:placeholder="t('crud.crud.Please select a data table')"
:attr="{
labelWidth: 140,
blockHelp: t('crud.crud.data sheet help'), blockHelp: t('crud.crud.data sheet help'),
}" }"
:input-attr="{ :input-attr="{
onChange: onDbStartChange, pk: 'table',
field: 'comment',
params: {
connection: crudState.startData.databaseConnection,
samePrefix: 1,
excludeTable: [
'area',
'token',
'captcha',
'admin_group_access',
'config',
'admin_log',
'user_money_log',
'user_score_log',
],
},
'remote-url': getTableListUrl,
onChange: onTableStartChange,
}" }"
prop="db" prop="table"
/> />
<el-alert <el-alert
v-if="state.successRecord" v-if="state.successRecord"
@ -132,7 +161,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive } from 'vue' import { ref, reactive } from 'vue'
import { getDatabaseList, checkCrudLog } from '/@/api/backend/crud' import { checkCrudLog } from '/@/api/backend/crud'
import FormItem from '/@/components/formItem/index.vue' import FormItem from '/@/components/formItem/index.vue'
import { changeStep, state as crudState } from '/@/views/backend/crud/index' import { changeStep, state as crudState } from '/@/views/backend/crud/index'
import { ElNotification } from 'element-plus' import { ElNotification } from 'element-plus'
@ -140,6 +169,7 @@ import type { FormInstance, FormItemRule } from 'element-plus'
import { buildValidatorData } from '/@/utils/validate' import { buildValidatorData } from '/@/utils/validate'
import CrudLog from '/@/views/backend/crud/log.vue' import CrudLog from '/@/views/backend/crud/log.vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { getDatabaseConnectionListUrl, getTableListUrl } from '/@/api/common'
const { t } = useI18n() const { t } = useI18n()
const sqlInputRef = ref() const sqlInputRef = ref()
@ -148,7 +178,6 @@ const state = reactive({
dialog: { dialog: {
type: '', type: '',
visible: false, visible: false,
dbList: [],
}, },
showLog: false, showLog: false,
loading: false, loading: false,
@ -164,15 +193,12 @@ const onShowDialog = (type: string) => {
}, 200) }, 200)
} else if (type == 'db') { } else if (type == 'db') {
state.successRecord = 0 state.successRecord = 0
crudState.startData.db = '' crudState.startData.table = ''
getDatabaseList().then((res) => {
state.dialog.dbList = res.data.dbs
})
} }
} }
const rules: Partial<Record<string, FormItemRule[]>> = reactive({ const rules: Partial<Record<string, FormItemRule[]>> = reactive({
db: [buildValidatorData({ name: 'required', message: t('crud.crud.Please select a data table') })], table: [buildValidatorData({ name: 'required', message: t('crud.crud.Please select a data table') })],
}) })
const onSubmit = () => { const onSubmit = () => {
@ -191,11 +217,16 @@ const onSubmit = () => {
}) })
} }
const onDbStartChange = () => { const onDatabaseChange = () => {
if (crudState.startData.db) { state.successRecord = 0
crudState.startData.table = ''
}
const onTableStartChange = () => {
if (crudState.startData.table) {
// CRUD // CRUD
state.loading = true state.loading = true
checkCrudLog(crudState.startData.db) checkCrudLog(crudState.startData.table, crudState.startData.databaseConnection)
.then((res) => { .then((res) => {
state.successRecord = res.data.id state.successRecord = res.data.id
}) })
@ -218,16 +249,17 @@ const isDev = () => {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
:deep(.select-db-dialog) .el-dialog__body { :deep(.select-table-dialog) .el-dialog__body {
height: unset; height: unset;
.select-db-form { .select-table-form {
width: 88%; width: 88%;
padding: 40px 0;
} }
.success-record-alert { .success-record-alert {
width: calc(100% - 140px); width: calc(100% - 140px);
margin-left: 140px; margin-left: 140px;
margin-bottom: 30px; margin-bottom: 30px;
margin-top: -30px; margin-top: -10px;
} }
} }
.crud-title { .crud-title {
@ -264,9 +296,6 @@ const isDev = () => {
.sql-input { .sql-input {
margin: 20px 0; margin: 20px 0;
} }
.select-db {
margin: 40px 0;
}
.crud-tips { .crud-tips {
margin-top: 60px; margin-top: 60px;
padding: 20px; padding: 20px;