/* eslint-disable no-unused-vars */ import { dbApi } from './api'; import SqlExecBox from './component/SqlExecBox'; const dbInstCache: Map = new Map(); export class DbInst { /** * 标签路径 */ tagPath: string /** * 实例id */ id: number /** * 实例名 */ name: string /** * 数据库类型, mysql postgres */ type: string /** * schema -> db */ dbs: Map = new Map() /** * 默认查询分页数量 */ static DefaultLimit = 20; /** * 获取指定数据库实例,若不存在则新建并缓存 * @param dbName 数据库名 * @returns db实例 */ getDb(dbName: string) { if (!dbName) { throw new Error('dbName不能为空') } let db = this.dbs.get(dbName) if (db) { return db; } console.info(`new db -> dbId: ${this.id}, dbName: ${dbName}`); db = new Db(); db.name = dbName; this.dbs.set(dbName, db); return db; } /** * 加载数据库表信息 * @param dbName 数据库名 * @returns 表信息 */ async loadTables(dbName: string) { const db = this.getDb(dbName); // 优先从 table map中获取 let tables = db.tables; if (tables) { return tables; } console.log(`load tables -> dbName: ${dbName}`); tables = await dbApi.tableMetadata.request({ id: this.id, db: dbName }); db.tables = tables; return tables; } /** * 获取表的所有列信息 * @param table 表名 */ async loadColumns(dbName: string, table: string) { const db = this.getDb(dbName); // 优先从 table map中获取 let columns = db.getColumns(table); if (columns) { return columns; } console.log(`load columns -> dbName: ${dbName}, table: ${table}`); columns = await dbApi.columnMetadata.request({ id: this.id, db: dbName, tableName: table, }); db.columnsMap.set(table, columns); return columns; } /** * 获取指定表的指定信息 * @param table 表名 */ async loadTableColumn(dbName: string, table: string, columnName?: string) { // 确保该表的列信息都已加载 await this.loadColumns(dbName, table); return this.getDb(dbName).getColumn(table, columnName); } /** * 获取库信息提示 */ async loadDbHints(dbName: string) { const db = this.getDb(dbName); if (db.tableHints) { return db.tableHints; } console.log(`load db-hits -> dbName: ${dbName}`); const hits = await dbApi.hintTables.request({ id: this.id, db: db.name, }) db.tableHints = hits; return hits; } /** * 执行sql * * @param sql sql * @param remark 执行备注 */ async runSql(dbName: string, sql: string, remark: string = '') { return await dbApi.sqlExec.request({ id: this.id, db: dbName, sql: sql.trim(), remark, }); } // 获取指定表的默认查询sql getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number = DbInst.DefaultLimit) { const baseSql = `SELECT * FROM ${table} ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''}`; if (this.type == 'mysql') { return `${baseSql} LIMIT ${(pageNum - 1) * limit}, ${limit};`; } if (this.type == 'postgres') { return `${baseSql} OFFSET ${(pageNum - 1) * limit} LIMIT ${limit};`; } return baseSql; } /** * 生成指定数据的insert语句 * @param dbName 数据库名 * @param table 表名 * @param datas 要生成的数据 */ genInsertSql(dbName: string, table: string, datas: any[]): string { if (!datas) { return ''; } const columns = this.getDb(dbName).getColumns(table); const sqls = []; for (let data of datas) { let colNames = []; let values = []; for (let column of columns) { const colName = column.columnName; colNames.push(colName); values.push(DbInst.wrapValueByType(data[colName])); } sqls.push(`INSERT INTO ${table} (${colNames.join(', ')}) VALUES(${values.join(', ')})`); } return sqls.join(';\n') + ';' } /** * 生成根据主键删除的sql语句 * @param table 表名 * @param datas 要删除的记录 */ genDeleteByPrimaryKeysSql(db: string, table: string, datas: any[]) { const primaryKey = this.getDb(db).getColumn(table); const primaryKeyColumnName = primaryKey.columnName; const ids = datas.map((d: any) => `${DbInst.wrapColumnValue(primaryKey.columnType, d[primaryKeyColumnName])}`).join(','); return `DELETE FROM ${table} WHERE ${primaryKeyColumnName} IN (${ids})`; } /* * 弹框提示是否执行sql */ promptExeSql = (db: string, sql: string, cancelFunc: any = null, successFunc: any = null) => { SqlExecBox({ sql, dbId: this.id, db, runSuccessCallback: successFunc, cancelCallback: cancelFunc, }); }; /** * 获取或新建dbInst,如果缓存中不存在则新建,否则直接返回 * @param inst 数据库实例,后端返回的列表接口中的信息 * @returns DbInst */ static getOrNewInst(inst: any) { if (!inst) { throw new Error('inst不能为空') } let dbInst = dbInstCache.get(inst.id); if (dbInst) { return dbInst; } console.info(`new dbInst: ${inst.id}, tagPath: ${inst.tagPath}`); dbInst = new DbInst(); dbInst.tagPath = inst.tagPath; dbInst.id = inst.id; dbInst.name = inst.name; dbInst.type = inst.type; dbInstCache.set(dbInst.id, dbInst); return dbInst; } /** * 获取数据库实例id,若不存在,则新建一个并缓存 * @param dbId 数据库实例id * @param dbType 第一次获取时为必传项,即第一次创建时 * @returns 数据库实例 */ static getInst(dbId?: number): DbInst { if (!dbId) { throw new Error('dbId不能为空'); } let dbInst = dbInstCache.get(dbId); if (dbInst) { return dbInst; } throw new Error('dbInst不存在! 请在合适调用点使用DbInst.newInst()新建该实例'); } /** * 清空所有实例缓存信息 */ static clearAll() { dbInstCache.clear(); } /** * 获取count sql * @param table 表名 * @param condition 条件 * @returns count sql */ static getDefaultCountSql = (table: string, condition?: string) => { return `SELECT COUNT(*) count FROM ${table} ${condition ? 'WHERE ' + condition : ''}`; }; /** * 根据返回值包装值,若值为字符串类型则添加'' * @param val 值 * @returns 包装后的值 */ static wrapValueByType = (val: any) => { if (val == null) { return 'NULL'; } if (typeof val == 'number') { return val; } return `'${val}'`; }; /** * 根据字段类型包装字段值,如为字符串等则添加‘’,数字类型则直接返回即可 */ static wrapColumnValue(columnType: string, value: any) { if (columnType.match(/int|double|float|nubmer|decimal|byte|bit/gi)) { return value; } return `'${value}'`; }; /** * * @param str 字符串 * @param tableData 表数据 * @param flag 标志 * @returns 列宽度 */ static flexColumnWidth = (str: any, tableData: any, flag = 'equal') => { // str为该列的字段名(传字符串);tableData为该表格的数据源(传变量); // flag为可选值,可不传该参数,传参时可选'max'或'equal',默认为'max' // flag为'max'则设置列宽适配该列中最长的内容,flag为'equal'则设置列宽适配该列中第一行内容的长度。 str = str + ''; let columnContent = ''; if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) { return; } if (!str || !str.length || str.length === 0 || str === undefined) { return; } if (flag === 'equal') { // 获取该列中第一个不为空的数据(内容) for (let i = 0; i < tableData.length; i++) { // 转为字符串后比较 if ((tableData[i][str] + '').length > 0) { columnContent = tableData[i][str] + ''; break; } } } else { // 获取该列中最长的数据(内容) let index = 0; for (let i = 0; i < tableData.length; i++) { if (tableData[i][str] === null) { return; } const now_temp = tableData[i][str] + ''; const max_temp = tableData[index][str] + ''; if (now_temp.length > max_temp.length) { index = i; } } columnContent = tableData[index][str] + ''; } const contentWidth: number = DbInst.getContentWidth(columnContent); // 获取列名称的长度 加上排序图标长度 const columnWidth: number = DbInst.getContentWidth(str) + 43; const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth; return flexWidth + 'px'; }; /** * 获取内容所需要占用的宽度 */ static getContentWidth = (content: any): number => { // 以下分配的单位长度可根据实际需求进行调整 let flexWidth = 0; for (const char of content) { if (flexWidth > 500) { break; } if ((char >= '0' && char <= '9') || (char >= 'a' && char <= 'z')) { // 如果是小写字母、数字字符,分配8个单位宽度 flexWidth += 8.5; continue; } if (char >= 'A' && char <= 'Z') { flexWidth += 9; continue; } if (char >= '\u4e00' && char <= '\u9fa5') { // 如果是中文字符,为字符分配16个单位宽度 flexWidth += 16; } else { // 其他种类字符,为字符分配9个单位宽度 flexWidth += 8; } } if (flexWidth > 500) { // 设置最大宽度 flexWidth = 500; } return flexWidth; }; } /** * 数据库实例信息 */ class Db { name: string // 库名 tables: [] // 数据库实例表信息 columnsMap: Map = new Map // table -> columns tableHints: any = null // 提示词 /** * 获取指定表列信息(前提需要dbInst.loadColumns) * @param table 表名 */ getColumns(table: string) { return this.columnsMap.get(table); } /** * 获取指定表中的指定列名信息,若列名为空则默认返回主键 * @param table 表名 * @param columnName 列名 */ getColumn(table: string, columnName: string = '') { const cols = this.getColumns(table); if (!columnName) { const col = cols.find((c: any) => c.columnKey == 'PRI'); return col || cols[0]; } return cols.find((c: any) => c.columnName == columnName); } } export enum TabType { /** * 表数据 */ TableData, /** * 查询框 */ Query, } export class TabInfo { /** * tab唯一key。与label、name都一致 */ key: string /** * 菜单树节点key */ treeNodeKey: string /** * 数据库实例id */ dbId: number /** * 库名 */ db: string = '' /** * tab 类型 */ type: TabType /** * tab需要的其他信息 */ params: any getNowDbInst() { return DbInst.getInst(this.dbId); } getNowDb() { return this.getNowDbInst().getDb(this.db); } } /** 修改表字段所需数据 */ export type UpdateFieldsMeta = { // 主键值 primaryKey: string // 主键名 primaryKeyName: string // 主键类型 primaryKeyType: string // 新值 fields: FieldsMeta[] } export type FieldsMeta = { // 字段所在div div: HTMLElement // 字段名 fieldName: string // 字段所在的表格行数据 row: any // 字段类型 fieldType: string // 原值 oldValue: string // 新值 newValue: string }