fix: 修复数据库表数据横向滚动后切换tab导致表头错位&数据取消居中显示

This commit is contained in:
meilin.huang
2024-05-31 12:12:40 +08:00
parent d85bbff270
commit 4814793546
15 changed files with 118 additions and 32 deletions

View File

@@ -10,7 +10,7 @@
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.1",
"@vueuse/core": "^10.9.0", "@vueuse/core": "^10.10.0",
"asciinema-player": "^3.7.1", "asciinema-player": "^3.7.1",
"axios": "^1.6.2", "axios": "^1.6.2",
"clipboard": "^2.0.11", "clipboard": "^2.0.11",
@@ -22,8 +22,8 @@
"jsencrypt": "^3.3.2", "jsencrypt": "^3.3.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"monaco-editor": "^0.48.0", "monaco-editor": "^0.49.0",
"monaco-sql-languages": "^0.11.0", "monaco-sql-languages": "^0.12.0",
"monaco-themes": "^0.4.4", "monaco-themes": "^0.4.4",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.1.7", "pinia": "^2.1.7",

View File

@@ -15,7 +15,7 @@ const config = {
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`, baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
// 系统版本 // 系统版本
version: 'v1.8.6', version: 'v1.8.7',
}; };
export default config; export default config;

View File

@@ -177,6 +177,7 @@
:db-name="dt.db" :db-name="dt.db"
:table-name="dt.params.table" :table-name="dt.params.table"
:table-height="state.dataTabsTableHeight" :table-height="state.dataTabsTableHeight"
:ref="(el: any) => (dt.componentRef = el)"
></db-table-data-op> ></db-table-data-op>
<db-sql-editor <db-sql-editor
@@ -185,6 +186,7 @@
:db-name="dt.db" :db-name="dt.db"
:sql-name="dt.params.sqlName" :sql-name="dt.params.sqlName"
@save-sql-success="reloadSqls" @save-sql-success="reloadSqls"
:ref="(el: any) => (dt.componentRef = el)"
> >
</db-sql-editor> </db-sql-editor>
@@ -579,7 +581,7 @@ const loadTableData = async (db: any, dbName: string, tableName: string) => {
} }
changeDb(db, dbName); changeDb(db, dbName);
const key = `${db.id}:\`${dbName}\`.${tableName}`; const key = `tableData:${db.id}.${dbName}.${tableName}`;
let tab = state.tabs.get(key); let tab = state.tabs.get(key);
state.activeName = key; state.activeName = key;
// 如果存在该表tab则直接返回 // 如果存在该表tab则直接返回
@@ -614,7 +616,7 @@ const addQueryTab = async (db: any, dbName: string, sqlName: string = '') => {
// 存在sql模板名则该模板名只允许一个tab // 存在sql模板名则该模板名只允许一个tab
if (sqlName) { if (sqlName) {
label = `查询-${sqlName}`; label = `查询-${sqlName}`;
key = `查询:${dbId}:${dbName}.${sqlName}`; key = `query:${dbId}.${dbName}.${sqlName}`;
} else { } else {
let count = 1; let count = 1;
state.tabs.forEach((v) => { state.tabs.forEach((v) => {
@@ -623,7 +625,7 @@ const addQueryTab = async (db: any, dbName: string, sqlName: string = '') => {
} }
}); });
label = `新查询-${count}`; label = `新查询-${count}`;
key = `新查询${count}:${dbId}:${dbName}`; key = `query:${count}.${dbId}.${dbName}`;
} }
state.activeName = key; state.activeName = key;
let tab = state.tabs.get(key); let tab = state.tabs.get(key);
@@ -660,7 +662,7 @@ const addTablesOpTab = async (db: any) => {
changeDb(db, dbName); changeDb(db, dbName);
const dbId = db.id; const dbId = db.id;
let key = `表操作:${dbId}:${dbName}.tablesOp`; let key = `tablesOp:${dbId}.${dbName}`;
state.activeName = key; state.activeName = key;
let tab = state.tabs.get(key); let tab = state.tabs.get(key);
@@ -691,15 +693,22 @@ const onRemoveTab = (targetName: string) => {
if (tabName !== targetName) { if (tabName !== targetName) {
continue; continue;
} }
state.tabs.delete(targetName);
if (activeName != targetName) {
break;
}
// 如果删除的tab是当前激活的tab则切换到前一个或后一个tab
const nextTab = tabNames[i + 1] || tabNames[i - 1]; const nextTab = tabNames[i + 1] || tabNames[i - 1];
if (nextTab) { if (nextTab) {
activeName = nextTab; activeName = nextTab;
} else { } else {
activeName = ''; activeName = '';
} }
state.tabs.delete(targetName);
state.activeName = activeName; state.activeName = activeName;
onTabChange(); onTabChange();
break;
} }
}; };
@@ -719,6 +728,9 @@ const onTabChange = () => {
registerDbCompletionItemProvider(nowTab.dbId, nowTab.db, nowTab.params.dbs, nowDbInst.value.type); registerDbCompletionItemProvider(nowTab.dbId, nowTab.db, nowTab.params.dbs, nowDbInst.value.type);
} }
// 激活当前tab需要调用DbTableData组件的active否则表头与数据会出现错位暂不知为啥先这样处理
nowTab?.componentRef?.active();
if (dbConfig.value.locationTreeNode) { if (dbConfig.value.locationTreeNode) {
locationNowTreeNode(nowTab); locationNowTreeNode(nowTab);
} }

View File

@@ -23,7 +23,10 @@ export const dbApi = {
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
console.log(param.sql); console.log(param.sql);
} }
param.sql = Base64.encode(param.sql); // 非base64编码sql则进行base64编码refreshToken时会重复调用该方法故简单判断下
if (!Base64.isValid(param.sql)) {
param.sql = Base64.encode(param.sql);
}
} }
return param; return param;
}), }),

View File

@@ -52,7 +52,7 @@
<Pane :size="100 - state.editorSize"> <Pane :size="100 - state.editorSize">
<div class="mt5 sql-exec-res h100"> <div class="mt5 sql-exec-res h100">
<el-tabs class="h100 w100" v-if="state.execResTabs.length > 0" @tab-remove="onRemoveTab" v-model="state.activeTab"> <el-tabs class="h100 w100" v-if="state.execResTabs.length > 0" @tab-remove="onRemoveTab" @tab-change="active" v-model="state.activeTab">
<el-tab-pane class="h100" closable v-for="dt in state.execResTabs" :label="dt.id" :name="dt.id" :key="dt.id"> <el-tab-pane class="h100" closable v-for="dt in state.execResTabs" :label="dt.id" :name="dt.id" :key="dt.id">
<template #label> <template #label>
<el-popover :show-after="1000" placement="top-start" title="执行信息" trigger="hover" :width="300"> <el-popover :show-after="1000" placement="top-start" title="执行信息" trigger="hover" :width="300">
@@ -700,6 +700,19 @@ const initMonacoEditor = () => {
}, },
}); });
}; };
const active = () => {
const resTab = state.execResTabs[state.activeTab - 1];
if (!resTab || !resTab.dbTableRef) {
return;
}
resTab.dbTableRef?.active();
};
defineExpose({
active,
});
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@@ -152,6 +152,10 @@ const getEditorLangByValue = (value: any) => {
<style lang="scss"> <style lang="scss">
.string-input-container { .string-input-container {
position: relative; position: relative;
.el-input__wrapper {
padding: 1px 3px;
}
} }
.string-input-container-show-icon { .string-input-container-show-icon {
.el-input__inner { .el-input__inner {
@@ -174,6 +178,10 @@ const getEditorLangByValue = (value: any) => {
.el-input__prefix { .el-input__prefix {
display: none; display: none;
} }
.el-input__wrapper {
padding: 1px 3px;
}
} }
.edit-time-picker-popper { .edit-time-picker-popper {

View File

@@ -15,6 +15,7 @@
fixed fixed
class="table" class="table"
:row-event-handlers="rowEventHandlers" :row-event-handlers="rowEventHandlers"
@scroll="onTableScroll"
> >
<template #header="{ columns }"> <template #header="{ columns }">
<div v-for="(column, i) in columns" :key="i"> <div v-for="(column, i) in columns" :key="i">
@@ -59,9 +60,7 @@
</div> </div>
<div v-else class="header-column-title"> <div v-else class="header-column-title">
<b class="el-text"> <b class="el-text"> {{ column.title }} </b>
{{ column.title }}
</b>
</div> </div>
<!-- 字段列右部分内容 --> <!-- 字段列右部分内容 -->
@@ -96,7 +95,7 @@
/> />
</div> </div>
<div v-else :class="isUpdated(rowIndex, column.dataKey) ? 'update_field_active' : ''"> <div v-else :class="isUpdated(rowIndex, column.dataKey) ? 'update_field_active ml2 mr2' : 'ml2 mr2'">
<span v-if="rowData[column.dataKey!] === null" style="color: var(--el-color-info-light-5)"> NULL </span> <span v-if="rowData[column.dataKey!] === null" style="color: var(--el-color-info-light-5)"> NULL </span>
<span v-else :title="rowData[column.dataKey!]" class="el-text el-text--small is-truncated"> <span v-else :title="rowData[column.dataKey!]" class="el-text el-text--small is-truncated">
@@ -486,7 +485,7 @@ const setTableColumns = (columns: any) => {
dataKey: columnName, dataKey: columnName,
width: DbInst.flexColumnWidth(columnName, state.datas), width: DbInst.flexColumnWidth(columnName, state.datas),
title: columnName, title: columnName,
align: 'center', align: x.dataType == DataType.Number ? 'right' : 'left',
headerClass: 'table-column', headerClass: 'table-column',
class: 'table-column', class: 'table-column',
sortable: true, sortable: true,
@@ -841,11 +840,23 @@ const triggerRefresh = () => {
} }
}; };
const scrollLeftValue = ref(0);
const onTableScroll = (param: any) => {
scrollLeftValue.value = param.scrollLeft;
};
/**
* 激活表格,恢复滚动位置,否则会造成表头与数据单元格错位(暂不知为啥,先这样解决)
*/
const active = () => {
setTimeout(() => tableRef.value.scrollToLeft(scrollLeftValue.value));
};
const getNowDbInst = () => { const getNowDbInst = () => {
return DbInst.getInst(state.dbId); return DbInst.getInst(state.dbId);
}; };
defineExpose({ defineExpose({
active,
submitUpdateFields, submitUpdateFields,
cancelUpdateFields, cancelUpdateFields,
}); });

View File

@@ -48,6 +48,21 @@
<el-tooltip :show-after="500" class="box-item" effect="dark" content="commit" placement="top"> <el-tooltip :show-after="500" class="box-item" effect="dark" content="commit" placement="top">
<el-link @click="onCommit()" type="success" icon="CircleCheck" :underline="false"> </el-link> <el-link @click="onCommit()" type="success" icon="CircleCheck" :underline="false"> </el-link>
</el-tooltip> </el-tooltip>
<el-divider direction="vertical" border-style="dashed" />
<!-- 表数据展示配置 -->
<el-popover
popper-style="max-height: 550px; overflow: auto; max-width: 450px"
placement="bottom"
width="auto"
title="展示配置"
trigger="click"
>
<el-checkbox v-model="dbConfig.showColumnComment" label="显示字段备注" :true-value="true" :false-value="false" size="small" />
<template #reference>
<el-link type="primary" icon="setting" :underline="false"></el-link>
</template>
</el-popover>
<el-divider direction="vertical" border-style="dashed" /> <el-divider direction="vertical" border-style="dashed" />
@@ -243,7 +258,7 @@ import { DbInst } from '@/views/ops/db/db';
import DbTableData from './DbTableData.vue'; import DbTableData from './DbTableData.vue';
import { DbDialect } from '@/views/ops/db/dialect'; import { DbDialect } from '@/views/ops/db/dialect';
import SvgIcon from '@/components/svgIcon/index.vue'; import SvgIcon from '@/components/svgIcon/index.vue';
import { useEventListener } from '@vueuse/core'; import { useEventListener, useStorage } from '@vueuse/core';
import { copyToClipboard } from '@/common/utils/string'; import { copyToClipboard } from '@/common/utils/string';
import DbTableDataForm from './DbTableDataForm.vue'; import DbTableDataForm from './DbTableDataForm.vue';
@@ -273,6 +288,8 @@ const condDialogInputRef: Ref = ref(null);
const defaultPageSize = DbInst.DefaultLimit; const defaultPageSize = DbInst.DefaultLimit;
const dbConfig = useStorage('dbConfig', { showColumnComment: false });
const state = reactive({ const state = reactive({
datas: [], datas: [],
sql: '', // 当前数据tab执行的sql sql: '', // 当前数据tab执行的sql
@@ -605,6 +622,10 @@ const onShowAddDataDialog = async () => {
state.addDataDialog.title = `添加'${props.tableName}'表数据`; state.addDataDialog.title = `添加'${props.tableName}'表数据`;
state.addDataDialog.visible = true; state.addDataDialog.visible = true;
}; };
defineExpose({
active: () => dbTableRef.value.active(),
});
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@@ -450,8 +450,8 @@ export class DbInst {
return; return;
} }
// 获取列名称的长度 加上排序图标长度、abc为字段类型简称占位符 // 获取列名称的长度 加上排序图标长度、abc为字段类型简称占位符、排序图标等
const columnWidth: number = getTextWidth(prop + 'abc') + 23; const columnWidth: number = getTextWidth(prop + 'abc') + 10;
// prop为该列的字段名(传字符串);tableData为该表格的数据源(传变量); // prop为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) { if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
return columnWidth; return columnWidth;
@@ -471,7 +471,7 @@ export class DbInst {
maxWidthText = nowText; maxWidthText = nowText;
} }
} }
const contentWidth: number = getTextWidth(maxWidthText) + 15; const contentWidth: number = getTextWidth(maxWidthText) + 3;
const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth; const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
return flexWidth > 500 ? 500 : flexWidth; return flexWidth > 500 ? 500 : flexWidth;
}; };
@@ -601,6 +601,11 @@ export class TabInfo {
*/ */
params: any; params: any;
/**
* 组件ref
*/
componentRef: any;
getNowDbInst() { getNowDbInst() {
return DbInst.getInst(this.dbId); return DbInst.getInst(this.dbId);
} }

View File

@@ -71,9 +71,9 @@ export enum DataType {
/** 列数据类型角标 */ /** 列数据类型角标 */
export const ColumnTypeSubscript = { export const ColumnTypeSubscript = {
/** 字符串 */ /** 字符串 */
string: 'abc', string: 'ab',
/** 数字 */ /** 数字 */
number: '123', number: '12',
/** 日期 */ /** 日期 */
date: 'icon-clock', date: 'icon-clock',
/** 时间 */ /** 时间 */

View File

@@ -497,20 +497,27 @@ const onRemoveTab = (targetName: string) => {
if (tabName !== targetName) { if (tabName !== targetName) {
continue; continue;
} }
state.tabs.delete(targetName);
let info = state.tabs.get(targetName);
if (info) {
terminalRefs[info.key]?.close();
}
if (activeTermName != targetName) {
break;
}
// 如果删除的tab是当前激活的tab则切换到前一个或后一个tab
const nextTab = tabNames[i + 1] || tabNames[i - 1]; const nextTab = tabNames[i + 1] || tabNames[i - 1];
if (nextTab) { if (nextTab) {
activeTermName = nextTab; activeTermName = nextTab;
} else { } else {
activeTermName = ''; activeTermName = '';
} }
let info = state.tabs.get(targetName);
if (info) {
terminalRefs[info.key]?.close();
}
state.tabs.delete(targetName);
state.activeTermName = activeTermName; state.activeTermName = activeTermName;
// onTabChange(); break;
} }
}; };

View File

@@ -28,7 +28,7 @@ require (
github.com/pquerna/otp v1.4.0 github.com/pquerna/otp v1.4.0
github.com/redis/go-redis/v9 v9.5.1 github.com/redis/go-redis/v9 v9.5.1
github.com/robfig/cron/v3 v3.0.1 // github.com/robfig/cron/v3 v3.0.1 //
github.com/sijms/go-ora/v2 v2.8.17 github.com/sijms/go-ora/v2 v2.8.19
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
github.com/veops/go-ansiterm v0.0.5 github.com/veops/go-ansiterm v0.0.5
go.mongodb.org/mongo-driver v1.15.0 // mongo go.mongodb.org/mongo-driver v1.15.0 // mongo

View File

@@ -297,7 +297,7 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *dto.DumpDb) error {
// 生成insert sql数据在索引前加速insert // 生成insert sql数据在索引前加速insert
if reqParam.DumpData { if reqParam.DumpData {
writer.WriteString(fmt.Sprintf("\n-- ----------------------------\n-- 表记录: %s \n-- ----------------------------\n", tableName)) writer.WriteString(fmt.Sprintf("\n-- ----------------------------\n-- 表数据: %s \n-- ----------------------------\n", tableName))
dumpHelper.BeforeInsert(writer, quoteTableName) dumpHelper.BeforeInsert(writer, quoteTableName)
// 获取列信息 // 获取列信息

View File

@@ -26,7 +26,7 @@ type ResourceAuthCert interface {
// GetAuthCert 根据授权凭证名称获取授权凭证 // GetAuthCert 根据授权凭证名称获取授权凭证
GetAuthCert(authCertName string) (*entity.ResourceAuthCert, error) GetAuthCert(authCertName string) (*entity.ResourceAuthCert, error)
// GetResourceAuthCert 获取资源授权凭证,默认获取特权账号,若没有则返回第一个 // GetResourceAuthCert 获取资源授权凭证,优先获取默认账号,若不存在默认账号则返回特权账号,都不存在则返回第一个
GetResourceAuthCert(resourceType entity.TagType, resourceCode string) (*entity.ResourceAuthCert, error) GetResourceAuthCert(resourceType entity.TagType, resourceCode string) (*entity.ResourceAuthCert, error)
// FillAuthCertByAcs 根据授权凭证列表填充资源的授权凭证信息 // FillAuthCertByAcs 根据授权凭证列表填充资源的授权凭证信息
@@ -224,6 +224,12 @@ func (r *resourceAuthCertAppImpl) GetResourceAuthCert(resourceType entity.TagTyp
return nil, errorx.NewBiz("该资源不存在授权凭证账号") return nil, errorx.NewBiz("该资源不存在授权凭证账号")
} }
for _, resourceAuthCert := range resourceAuthCerts {
if resourceAuthCert.Type == entity.AuthCertTypePrivateDefault {
return r.decryptAuthCert(resourceAuthCert)
}
}
for _, resourceAuthCert := range resourceAuthCerts { for _, resourceAuthCert := range resourceAuthCerts {
if resourceAuthCert.Type == entity.AuthCertTypePrivileged { if resourceAuthCert.Type == entity.AuthCertTypePrivileged {
return r.decryptAuthCert(resourceAuthCert) return r.decryptAuthCert(resourceAuthCert)

View File

@@ -4,7 +4,7 @@ import "fmt"
const ( const (
AppName = "mayfly-go" AppName = "mayfly-go"
Version = "v1.8.6" Version = "v1.8.7"
) )
func GetAppInfo() string { func GetAppInfo() string {