feat: 重构前端项目结构并优化代码

refactor: 迁移前端资源文件至web目录
feat: 新增多种图标资源
style: 统一代码风格和格式化配置
docs: 更新README和文档说明
chore: 更新依赖和配置文件
fix: 修复部分类型定义和枚举
perf: 优化路由和组件加载逻辑
This commit is contained in:
zhangtao
2026-04-29 00:02:09 +08:00
parent 10a35bb4b5
commit eb150bcede
887 changed files with 116514 additions and 33990 deletions
@@ -0,0 +1,135 @@
/**
* 将「表结构」表单状态编译为可执行的 CREATE TABLE SQL(MySQL / PostgreSQL)。
*/
export type SqlDialect = 'mysql' | 'postgres';
export interface ColDef {
name: string;
type: string;
nullable: boolean;
isPk: boolean;
comment: string;
/** 仅 MySQL:数字主键自增 */
autoIncrement?: boolean;
}
export interface VisualBuildState {
dialect: SqlDialect;
mainTableName: string;
mainComment: string;
mainColumns: ColDef[];
subEnabled: boolean;
subTableName: string;
subComment: string;
/** 子表上指向主表的外键列名 */
fkColumn: string;
/** 主表被引用列,一般为 id */
fkRefColumn: string;
subColumns: ColDef[];
}
function qMysql(name: string): string {
return `\`${name.replace(/`/g, '')}\``;
}
function buildMysqlTable(name: string, comment: string, columns: ColDef[]): string {
const lines: string[] = [];
const pkCols = columns.filter((c) => c.isPk).map((c) => qMysql(c.name));
for (const c of columns) {
let line = ` ${qMysql(c.name)} ${c.type}`;
if (c.isPk && c.autoIncrement) {
line += ' NOT NULL AUTO_INCREMENT';
} else if (c.isPk) {
line += ' NOT NULL';
} else if (!c.nullable) {
line += ' NOT NULL';
} else {
line += ' DEFAULT NULL';
}
if (c.comment) line += ` COMMENT '${c.comment.replace(/'/g, "''")}'`;
lines.push(line);
}
const tail: string[] = [];
if (pkCols.length) {
tail.push(` PRIMARY KEY (${pkCols.join(', ')})`);
}
const uuidCol = columns.find((c) => c.name === 'uuid');
if (uuidCol) {
tail.push(` UNIQUE KEY ${qMysql(`uk_${name.replace(/`/g, '')}_uuid`)} (${qMysql('uuid')})`);
}
const allLines = [...lines, ...tail];
return (
`CREATE TABLE ${qMysql(name)} (\n${allLines.join(',\n')}\n)` +
` ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='${comment.replace(/'/g, "''")}';`
);
}
function buildMysqlFk(subName: string, fkCol: string, mainName: string, refCol: string): string {
return (
`ALTER TABLE ${qMysql(subName)} ADD CONSTRAINT ${qMysql(`fk_${subName}_${fkCol}`)} ` +
`FOREIGN KEY (${qMysql(fkCol)}) REFERENCES ${qMysql(mainName)} (${qMysql(refCol)}) ON DELETE CASCADE;`
);
}
function buildPostgresTable(name: string, comment: string, columns: ColDef[]): string {
const lines: string[] = [];
const pkCols = columns.filter((c) => c.isPk).map((c) => c.name);
for (const c of columns) {
let line = ` ${c.name} ${c.type}`;
const isSerial = c.type.toUpperCase().includes('SERIAL');
if (!isSerial && !c.nullable) line += ' NOT NULL';
lines.push(line);
}
const tail: string[] = [];
if (pkCols.length) {
tail.push(` PRIMARY KEY (${pkCols.join(', ')})`);
}
const body = [...lines, ...tail].join(',\n');
return (
`CREATE TABLE ${name} (\n${body}\n);\n` +
`COMMENT ON TABLE ${name} IS '${comment.replace(/'/g, "''")}';`
);
}
function buildPostgresColumnComments(table: string, columns: ColDef[]): string {
return columns
.filter((c) => c.comment)
.map((c) => `COMMENT ON COLUMN ${table}.${c.name} IS '${c.comment.replace(/'/g, "''")}';`)
.join('\n');
}
function buildPostgresFk(subName: string, fkCol: string, mainName: string, refCol: string): string {
return (
`ALTER TABLE ${subName} ADD CONSTRAINT fk_${subName}_${fkCol} ` +
`FOREIGN KEY (${fkCol}) REFERENCES ${mainName}(${refCol}) ON DELETE CASCADE;`
);
}
/** 由表结构状态生成完整 SQL(单表或主子表) */
export function buildSqlFromVisual(state: VisualBuildState): string {
const m = state.mainTableName.trim();
if (!m) return '';
const parts: string[] = [];
if (state.dialect === 'mysql') {
parts.push(buildMysqlTable(m, state.mainComment || m, state.mainColumns));
if (state.subEnabled && state.subTableName.trim()) {
const s = state.subTableName.trim();
parts.push(buildMysqlTable(s, state.subComment || s, state.subColumns));
parts.push(buildMysqlFk(s, state.fkColumn, m, state.fkRefColumn));
}
} else {
parts.push(buildPostgresTable(m, state.mainComment || m, state.mainColumns));
parts.push(buildPostgresColumnComments(m, state.mainColumns));
if (state.subEnabled && state.subTableName.trim()) {
const s = state.subTableName.trim();
parts.push(buildPostgresTable(s, state.subComment || s, state.subColumns));
parts.push(buildPostgresColumnComments(s, state.subColumns));
parts.push(buildPostgresFk(s, state.fkColumn, m, state.fkRefColumn));
}
}
return parts.filter(Boolean).join('\n\n');
}
@@ -0,0 +1,16 @@
import { buildSqlFromVisual } from './buildCreateTableSql';
import {
applySubColumns,
visualPresetMasterSub,
visualPresetSingle,
} from './createTableVisualPresets';
/** 与「表结构」单表模板一致的 SQL(便于两种模式对齐) */
export function getExampleFromPresetSingle(dialect: 'mysql' | 'postgres'): string {
return buildSqlFromVisual(visualPresetSingle(dialect));
}
/** 与「表结构」主子表模板一致的 SQL */
export function getExampleFromPresetMasterSub(dialect: 'mysql' | 'postgres'): string {
return buildSqlFromVisual(applySubColumns(visualPresetMasterSub(dialect)));
}
@@ -0,0 +1,243 @@
import type { ColDef, SqlDialect, VisualBuildState } from './buildCreateTableSql';
/** 与代码生成第三步「基本信息」联动时的输入(主表名、子表名、外键列名) */
export interface GenTableCreateLink {
table_name?: string;
table_comment?: string;
sub_table_name?: string;
sub_table_fk_name?: string;
}
function mixinMysql(): ColDef[] {
return [
{
name: 'status',
type: 'varchar(10)',
nullable: false,
isPk: false,
comment: '是否启用(0:启用 1:禁用)',
},
{ name: 'description', type: 'text', nullable: true, isPk: false, comment: '备注/描述' },
{
name: 'created_time',
type: 'datetime',
nullable: false,
isPk: false,
comment: '创建时间',
},
{
name: 'updated_time',
type: 'datetime',
nullable: false,
isPk: false,
comment: '更新时间',
},
{ name: 'created_id', type: 'int', nullable: true, isPk: false, comment: '创建人ID' },
{ name: 'updated_id', type: 'int', nullable: true, isPk: false, comment: '更新人ID' },
{
name: 'is_deleted',
type: 'tinyint(1)',
nullable: false,
isPk: false,
comment: '是否已删除(0:未删除 1:已删除)',
},
{ name: 'deleted_time', type: 'datetime', nullable: true, isPk: false, comment: '删除时间' },
{ name: 'deleted_id', type: 'int', nullable: true, isPk: false, comment: '删除人ID' },
];
}
function mixinPostgres(): ColDef[] {
return [
{
name: 'status',
type: 'varchar(10)',
nullable: false,
isPk: false,
comment: '是否启用(0:启用 1:禁用)',
},
{ name: 'description', type: 'text', nullable: true, isPk: false, comment: '备注/描述' },
{
name: 'created_time',
type: 'timestamp without time zone',
nullable: false,
isPk: false,
comment: '创建时间',
},
{
name: 'updated_time',
type: 'timestamp without time zone',
nullable: false,
isPk: false,
comment: '更新时间',
},
{ name: 'created_id', type: 'integer', nullable: true, isPk: false, comment: '创建人ID' },
{ name: 'updated_id', type: 'integer', nullable: true, isPk: false, comment: '更新人ID' },
{
name: 'is_deleted',
type: 'boolean',
nullable: false,
isPk: false,
comment: '是否已删除(0:未删除 1:已删除)',
},
{
name: 'deleted_time',
type: 'timestamp without time zone',
nullable: true,
isPk: false,
comment: '删除时间',
},
{ name: 'deleted_id', type: 'integer', nullable: true, isPk: false, comment: '删除人ID' },
];
}
function pkMysql(): ColDef[] {
return [
{
name: 'id',
type: 'bigint',
nullable: false,
isPk: true,
comment: '主键ID',
autoIncrement: true,
},
{
name: 'uuid',
type: 'varchar(64)',
nullable: false,
isPk: false,
comment: 'UUID全局唯一标识',
},
];
}
function pkPostgres(): ColDef[] {
return [
{ name: 'id', type: 'SERIAL', nullable: false, isPk: true, comment: '主键ID' },
{
name: 'uuid',
type: 'varchar(64)',
nullable: false,
isPk: false,
comment: 'UUID全局唯一标识',
},
];
}
function buildSubColumns(dialect: SqlDialect, mainTableName: string, fkColumn: string): ColDef[] {
const isMysql = dialect === 'mysql';
const mixin = isMysql ? mixinMysql() : mixinPostgres();
const subPk = isMysql ? pkMysql() : pkPostgres();
const fkColDef: ColDef = {
name: fkColumn,
type: 'bigint',
nullable: false,
isPk: false,
comment: `关联 ${mainTableName}.${'id'}`,
};
const subBusiness: ColDef[] = [
{
name: 'line_name',
type: 'varchar(128)',
nullable: true,
isPk: false,
comment: '明细名称',
},
{
name: 'qty',
type: isMysql ? 'int' : 'integer',
nullable: false,
isPk: false,
comment: '数量',
},
];
return [...subPk, fkColDef, ...subBusiness, ...mixin];
}
export function applySubColumns(state: VisualBuildState): VisualBuildState {
if (!state.subEnabled) {
return { ...state, subColumns: [] };
}
return {
...state,
subColumns: buildSubColumns(state.dialect, state.mainTableName, state.fkColumn),
};
}
/** 单表:带业务字段 name */
export function visualPresetSingle(dialect: SqlDialect): VisualBuildState {
const isMysql = dialect === 'mysql';
const mixin = isMysql ? mixinMysql() : mixinPostgres();
const pk = isMysql ? pkMysql() : pkPostgres();
const business: ColDef[] = [
{ name: 'name', type: 'varchar(64)', nullable: true, isPk: false, comment: '名称' },
];
return {
dialect,
mainTableName: 'gen_demo_single',
mainComment: '代码生成-单表示例',
mainColumns: [...pk, ...business, ...mixin],
subEnabled: false,
subTableName: 'gen_demo_order_item',
subComment: '子表示例',
fkColumn: 'order_id',
fkRefColumn: 'id',
subColumns: [],
};
}
/** 主子表:主表订单头 + 子表明细,子表含外键列 */
export function visualPresetMasterSub(dialect: SqlDialect): VisualBuildState {
const isMysql = dialect === 'mysql';
const mixin = isMysql ? mixinMysql() : mixinPostgres();
const pk = isMysql ? pkMysql() : pkPostgres();
const mainBusiness: ColDef[] = [
{
name: 'order_title',
type: 'varchar(128)',
nullable: true,
isPk: false,
comment: '订单标题',
},
];
return applySubColumns({
dialect,
mainTableName: 'gen_demo_order_master',
mainComment: '代码生成-主表示例(订单头)',
mainColumns: [...pk, ...mainBusiness, ...mixin],
subEnabled: true,
subTableName: 'gen_demo_order_item',
subComment: '代码生成-子表示例(订单明细)',
fkColumn: 'order_id',
fkRefColumn: 'id',
subColumns: [],
});
}
/**
* 将第三步已填的主子表信息合并到表结构预设。
* 规则:子表名与外键列名同时非空时使用主子表模板;否则为单表模板(仅主表名/注释覆盖)。
*/
export function mergeGenTableLinkIntoVisual(
link: GenTableCreateLink,
dialect: SqlDialect
): VisualBuildState {
const main = (link.table_name || '').trim();
const subSn = (link.sub_table_name || '').trim();
const subFk = (link.sub_table_fk_name || '').trim();
const hasSubPair = Boolean(subSn && subFk);
const base: VisualBuildState = hasSubPair
? visualPresetMasterSub(dialect)
: visualPresetSingle(dialect);
if (main) base.mainTableName = main;
const mc = (link.table_comment || '').trim();
if (mc) base.mainComment = mc;
if (hasSubPair) {
base.subEnabled = true;
base.subTableName = subSn;
base.fkColumn = subFk;
base.fkRefColumn = 'id';
} else {
base.subEnabled = false;
}
return applySubColumns(base);
}