feat: 数据库表数据支持字段设置、表格宽度自适应调整

This commit is contained in:
meilin.huang
2023-06-29 11:49:14 +08:00
parent 1bc53b4c80
commit d9807b1bf0
8 changed files with 138 additions and 92 deletions

View File

@@ -105,8 +105,7 @@
</template>
<template #default="scope" v-else>
<span>{{ item.formatFunc ? item.formatFunc(scope.row[item.prop]) : scope.row[item.prop]
}}</span>
<span>{{ item.getValueByData(scope.row)}}</span>
</template>
</el-table-column>
@@ -224,7 +223,7 @@ watch(() => props.data, (newValue: any) => {
if (newValue.length > 0) {
props.columns.forEach(item => {
if (item.autoWidth && item.show) {
item.minWidth = TableColumn.flexColumnWidth(item.prop, item.label, props.data) + item.addWidth
item.autoCalculateMinWidth(props.data);
}
})
}

View File

@@ -45,8 +45,15 @@ export class TableColumn {
align: string = "center"
/**
* 指定格式化函数对原始值进行格式化,如时间格式化等
* param1: data, param2: prop
*/
formatFunc: Function
/**
* 是否显示该列
*/
show: boolean = true
constructor(prop: string, label: string) {
@@ -54,6 +61,18 @@ export class TableColumn {
this.label = label;
}
/**
* 获取该列在指定行数据中的值
* @param rowData 该行对应的数据
* @returns 该列对应的值
*/
getValueByData(rowData: any) {
if (this.formatFunc) {
return this.formatFunc(rowData, this.prop);
}
return rowData[this.prop];
}
static new(prop: string, label: string): TableColumn {
return new TableColumn(prop, label)
}
@@ -74,6 +93,11 @@ export class TableColumn {
return this;
}
/**
* 设置该列的格式化回调函数
* @param func 格式化回调函数(参数为 -> data: 该行对应的数据prop: 该列对应的prop属性值)
* @returns
*/
setFormatFunc(func: Function): TableColumn {
this.formatFunc = func;
return this;
@@ -84,7 +108,9 @@ export class TableColumn {
* @returns this
*/
isTime(): TableColumn {
this.setFormatFunc(dateFormat)
this.setFormatFunc((data: any, prop: string) => {
return dateFormat(data[prop])
})
return this;
}
@@ -100,41 +126,49 @@ export class TableColumn {
/**
*
* 自动计算最小宽度
* @param str 字符串
* @param tableData 表数据
* @param label 表头label也参与宽度计算
* @returns 列宽度
*/
static flexColumnWidth = (str: any, label: string, tableData: any): number => {
// str为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
str = str + '';
let columnContent = '';
autoCalculateMinWidth = (tableData: any) => {
const prop = this.prop
const label = this.label
if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
return 0;
}
if (!str || !str.length || str.length === 0 || str === undefined) {
return 0;
}
let maxWidthText = ""
let maxWidthValue
// 为了兼容formatFunc格式化回调函数
let maxData
// 获取该列中最长的数据(内容)
let index = 0;
for (let i = 0; i < tableData.length; i++) {
if (!tableData[i][str]) {
let nowData = tableData[i]
let nowValue = nowData[prop]
if (!nowValue) {
continue;
}
const now_temp = tableData[i][str] + '';
const max_temp = tableData[index][str] + '';
if (now_temp.length > max_temp.length) {
index = i;
// 转为字符串比较长度
let nowText = nowValue + "";
if (nowText.length > maxWidthText.length) {
maxWidthText = nowText;
maxWidthValue = nowValue;
maxData = nowData;
}
}
columnContent = tableData[index][str] + '';
if (this.formatFunc && maxWidthValue) {
maxWidthText = this.formatFunc(maxData, prop) + ""
}
// 需要加上表格的内间距等,视情况加
const contentWidth: number = getTextWidth(columnContent) + 30;
const contentWidth: number = getTextWidth(maxWidthText) + 30;
// 获取label的宽度取较大的宽度
const columnWidth: number = getTextWidth(label) + 60;
const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
return flexWidth > 400 ? 400 : flexWidth;
// 设置上限与累加需要额外增加的宽度
this.minWidth = (flexWidth > 400 ? 400 : flexWidth) + this.addWidth;
};
}

View File

@@ -26,10 +26,6 @@
</span>
</template>
<template #hostPort="{ data }">
{{ `${data.host}:${data.port}` }}
</template>
<template #database="{ data }">
<el-popover placement="right" trigger="click" :width="300">
<template #reference>
@@ -313,7 +309,7 @@ const state = reactive({
columns: [
TableColumn.new("tagPath", "标签路径").setSlot("tagPath").setAddWidth(20),
TableColumn.new("name", "名称"),
TableColumn.new("host", "host:port").setSlot("hostPort").setAddWidth(35),
TableColumn.new("host", "host:port").setFormatFunc((data: any, _prop: string) => `${data.host}:${data.port}`),
TableColumn.new("type", "类型"),
TableColumn.new("database", "数据库").setSlot("database").setMinWidth(70),
TableColumn.new("username", "用户名"),

View File

@@ -517,7 +517,7 @@ const registerSqlCompletionItemProvider = () => {
},
kind: monaco.languages.CompletionItemKind.Property,
detail: '', // 不显示detail, 否则选中时备注等会被遮挡
insertText: fieldName + ' ', // create_time
insertText: fieldName, // create_time
range,
sortText: 100 + index + '' // 使用表字段声明顺序排序,排序需为字符串类型
});

View File

@@ -1,20 +1,24 @@
<template>
<div>
<el-table @cell-dblclick="(row: any, column: any, cell: any, event: any) => cellClick(row, column, cell)"
@sort-change="(sort: any) => onTableSortChange(sort)" @selection-change="onDataSelectionChange"
:data="datas" size="small" :max-height="tableHeight" v-loading="loading" element-loading-text="查询中..."
:empty-text="emptyText" highlight-current-row stripe border class="mt5">
@sort-change="(sort: any) => onTableSortChange(sort)" @selection-change="onDataSelectionChange" :data="datas"
size="small" :max-height="tableHeight" v-loading="loading" element-loading-text="查询中..." :empty-text="emptyText"
highlight-current-row stripe border class="mt5">
<el-table-column v-if="datas.length > 0 && table" type="selection" width="35" />
<el-table-column min-width="100" :width="DbInst.flexColumnWidth(item, datas)" align="center"
v-for="item in columnNames" :key="item" :prop="item" :label="item" show-overflow-tooltip
:sortable="sortable">
<template #header v-if="showColumnTip">
<el-tooltip raw-content placement="top" effect="customized">
<template #content> {{ getColumnTip(item) }} </template>
{{ item }}
</el-tooltip>
</template>
</el-table-column>
<template v-for="(item, index) in columns">
<el-table-column min-width="100" :width="DbInst.flexColumnWidth(item.columnName, datas)" align="center"
v-if="item.show" :key="index" :prop="item.columnName" :label="item.columnName" show-overflow-tooltip
:sortable="sortable">
<template #header v-if="showColumnTip">
<el-tooltip raw-content placement="top" effect="customized">
<template #content> {{ getColumnTip(item) }} </template>
{{ item.columnName }}
</el-tooltip>
</template>
</el-table-column>
</template>
</el-table>
</div>
</template>
@@ -45,8 +49,8 @@ const props = defineProps({
data: {
type: Array,
},
columnNames: {
type: Array,
columns: {
type: Array<any>,
},
sortable: {
type: [String, Boolean],
@@ -76,7 +80,6 @@ const state = reactive({
db: '', // 数据库名
table: '', // 当前的表名
datas: [],
columnNames: [],
columns: [],
sortable: false,
loading: false,
@@ -92,7 +95,6 @@ const {
datas,
sortable,
loading,
columnNames,
showColumnTip,
} = toRefs(state);
@@ -114,24 +116,16 @@ const setState = (props: any) => {
state.tableHeight = props.height;
state.sortable = props.sortable;
state.loading = props.loading;
state.columnNames = props.columnNames;
state.columns = props.columns;
state.showColumnTip = props.showColumnTip;
state.emptyText = props.emptyText;
}
const getColumnTip = (columnName: string) => {
// 优先从 table map中获取
let columns = getNowDb().getColumns(state.table);
if (!columns) {
return '';
}
const column = columns.find((c: any) => c.columnName == columnName);
const getColumnTip = (column: any) => {
const comment = column.columnComment;
return `${column.columnType} ${comment ? ' | ' + comment : ''}`;
};
/**
* 表排序字段变更
*/
@@ -269,7 +263,7 @@ const submitUpdateFields = () => {
a.fields.forEach(f => {
sql += ` ${f.fieldName} = ${DbInst.wrapColumnValue(f.fieldType, f.newValue)},`
// 如果修改的字段是主键
if(f.fieldName === primaryKeyName){
if (f.fieldName === primaryKeyName) {
primaryKey = f.oldValue
}
divs.push(f.div)
@@ -305,10 +299,6 @@ const changeUpdatedField = () => {
emits('changeUpdatedField', state.updatedFields);
}
const getNowDb = () => {
return DbInst.getInst(state.dbId).getDb(state.db);
}
const getNowDbInst = () => {
return DbInst.getInst(state.dbId);
}

View File

@@ -70,7 +70,7 @@
</span>
</el-row>
<db-table ref="dbTableRef" :db-id="state.ti.dbId" :db="state.ti.db" :data="execRes.data" :table="state.table"
:column-names="execRes.tableColumn" :loading="loading" :height="tableDataHeight"
:columns="execRes.tableColumn" :loading="loading" :height="tableDataHeight"
empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改" @selection-change="onDataSelectionChange"
@change-updated-field="changeUpdatedField"></db-table>
</div>
@@ -327,7 +327,13 @@ const onRunSql = async () => {
ElMessage.warning('未查询到结果集')
}
state.execRes.data = colAndData.res;
state.execRes.tableColumn = colAndData.colNames;
// 兼容表格字段配置
state.execRes.tableColumn = colAndData.colNames.map((x: any) => {
return {
columnName: x,
show: true,
}
});
cancelUpdateFields()
} catch (e: any) {
state.execRes.data = [];
@@ -486,7 +492,7 @@ const replaceSelection = (str: string, selection: any) => {
const exportData = () => {
const dataList = state.execRes.data as any;
isTrue(dataList.length > 0, '没有数据可导出');
exportCsv(`数据查询导出-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, state.execRes.tableColumn, dataList)
exportCsv(`数据查询导出-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, state.execRes.tableColumn.map((x: any) => x.columnName), dataList)
};
const beforeUpload = (file: File) => {

View File

@@ -6,6 +6,18 @@
</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-popover placement="bottom" title="表格字段配置" width="auto" trigger="click">
<div v-for="(item, index) in columns" :key="index">
<el-checkbox v-model="item.show"
:label="`${!item.columnComment ? item.columnName : item.columnName + ' [' + item.columnComment + ']'}`"
:true-label="true" :false-label="false" />
</div>
<template #reference>
<el-link icon="Operation" size="small" :underline="false"></el-link>
</template>
</el-popover>
<el-divider direction="vertical" border-style="dashed" />
<el-link @click="onShowAddDataDialog()" type="primary" icon="plus" :underline="false"></el-link>
<el-divider direction="vertical" border-style="dashed" />
@@ -44,11 +56,10 @@
<template #reference>
<el-link type="success" :underline="false">选择列</el-link>
</template>
<el-table :data="columns" max-height="500" size="small" @row-click="
(...event: any) => {
onConditionRowClick(event);
}
" style="cursor: pointer">
<el-table :data="columns" max-height="500" size="small" @row-click="(...event: any) => {
onConditionRowClick(event);
}
" style="cursor: pointer">
<el-table-column property="columnName" label="列名" show-overflow-tooltip>
</el-table-column>
<el-table-column property="columnComment" label="备注" show-overflow-tooltip>
@@ -65,9 +76,9 @@
</el-row>
<db-table ref="dbTableRef" :db-id="state.ti.dbId" :db="state.ti.db" :data="datas" :table="state.table"
:column-names="columnNames" :loading="loading" :height="tableHeight" :show-column-tip="true"
:sortable="'custom'" @sort-change="(sort: any) => onTableSortChange(sort)"
@selection-change="onDataSelectionChange" @change-updated-field="changeUpdatedField"></db-table>
:columns="columns" :loading="loading" :height="tableHeight" :show-column-tip="true" :sortable="'custom'"
@sort-change="(sort: any) => onTableSortChange(sort)" @selection-change="onDataSelectionChange"
@change-updated-field="changeUpdatedField"></db-table>
<el-row type="flex" class="mt5" justify="center">
<el-pagination small :total="count" @current-change="pageChange()" layout="prev, pager, next, total, jumper"
@@ -156,7 +167,6 @@ const state = reactive({
orderBy: '',
condition: '', // 当前条件框的条件
loading: false, // 是否在加载数据
columnNames: [],
columns: [] as any,
pageNum: 1,
count: 0,
@@ -185,7 +195,6 @@ const {
condition,
loading,
columns,
columnNames,
pageNum,
count,
hasUpdatedFileds,
@@ -205,8 +214,10 @@ onMounted(async () => {
notBlank(state.table, "TableData组件params.table信息不能为空")
const columns = await state.ti.getNowDbInst().loadColumns(state.ti.db, state.table);
columns.forEach((x: any) => {
x.show = true;
})
state.columns = columns;
state.columnNames = columns.map((t: any) => t.columnName);
await onRefresh();
})
@@ -253,7 +264,13 @@ const selectData = async () => {
const exportData = () => {
const dataList = state.datas as any;
isTrue(dataList.length > 0, '没有数据可导出');
exportCsv(`数据导出-${state.table}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, state.columnNames, dataList)
let columnNames = [];
for (let column of state.columns) {
if (column.show) {
columnNames.push(column.columnName);
}
}
exportCsv(`数据导出-${state.table}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, columnNames, dataList)
};

View File

@@ -90,6 +90,7 @@ export class DbInst {
// 优先从 table map中获取
let columns = db.getColumns(table);
if (columns) {
return columns;
}
console.log(`load columns -> dbName: ${dbName}, table: ${table}`);
@@ -301,32 +302,35 @@ export class DbInst {
* @param flag 标志
* @returns 列宽度
*/
static flexColumnWidth = (str: any, tableData: any) => {
// str为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
str = str + '';
let columnContent = '';
static flexColumnWidth = (prop: any, tableData: any) => {
if (!prop || !prop.length || prop.length === 0 || prop === undefined) {
return;
}
// 获取列名称的长度 加上排序图标长度
const columnWidth: number = getTextWidth(prop) + 40;
// prop为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
return;
}
if (!str || !str.length || str.length === 0 || str === undefined) {
return;
return columnWidth;
}
// 获取该列中最长的数据(内容)
let maxWidthText = ""
let maxWidthValue
// 获取该列中最长的数据(内容)
let index = 0;
for (let i = 0; i < tableData.length; i++) {
if (!tableData[i][str]) {
let nowValue = tableData[i][prop]
if (!nowValue) {
continue;
}
const now_temp = tableData[i][str] + '';
const max_temp = tableData[index][str] + '';
if (now_temp.length > max_temp.length) {
index = i;
// 转为字符串比较长度
let nowText = nowValue + "";
if (nowText.length > maxWidthText.length) {
maxWidthText = nowText;
maxWidthValue = nowValue;
}
}
columnContent = tableData[index][str] + '';
const contentWidth: number = getTextWidth(columnContent) + 15;
// 获取列名称的长度 加上排序图标长度
const columnWidth: number = getTextWidth(str) + 40;
const contentWidth: number = getTextWidth(maxWidthText) + 15;
const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
return flexWidth > 500 ? 500 : flexWidth;
};