refactor: tooltip延迟显示等

This commit is contained in:
meilin.huang
2023-10-31 12:36:04 +08:00
parent f2b0f294d8
commit 3155380f16
17 changed files with 134 additions and 110 deletions

View File

@@ -59,15 +59,20 @@ export async function useLoginCaptcha(): Promise<boolean> {
*/
export async function useWatermark(): Promise<any> {
const value = await getConfigValue(UseWatermarkConfigKey);
if (!value) {
return {
const defaultValue = {
isUse: true,
};
if (!value) {
return defaultValue;
}
try {
const jsonValue = JSON.parse(value);
// 将字符串转为bool
jsonValue.isUse = convertBool(jsonValue.isUse, true);
return jsonValue;
} catch (e) {
return defaultValue;
}
}
function convertBool(value: string, defaultValue: boolean) {

View File

@@ -132,7 +132,7 @@ const languageArr = [
},
];
const options = {
const defaultOptions = {
language: 'shell',
theme: 'SolarizedLight',
automaticLayout: true, //自适应宽高布局
@@ -223,9 +223,9 @@ const initMonacoEditorIns = () => {
// options参数参考 https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html#language
// 初始化一些主题
monaco.editor.defineTheme('SolarizedLight', SolarizedLight);
options.language = state.languageMode;
options.theme = themeConfig.value.editorTheme;
monacoEditorIns = monaco.editor.create(monacoTextarea.value, Object.assign(options, props.options as any));
defaultOptions.language = state.languageMode;
defaultOptions.theme = themeConfig.value.editorTheme;
monacoEditorIns = monaco.editor.create(monacoTextarea.value, Object.assign(defaultOptions, props.options as any));
// 监听内容改变,双向绑定
monacoEditorIns.onDidChangeModelContent(() => {

View File

@@ -151,7 +151,7 @@ export const useThemeConfig = defineStore('themeConfig', {
this.themeConfig.editorTheme = 'vs-dark';
} else {
body.setAttribute('class', '');
this.themeConfig.editorTheme = 'SolarizedLight';
this.themeConfig.editorTheme = 'vs';
}
},
// 设置水印配置信息

View File

@@ -1,6 +1,6 @@
<template>
<div style="display: inline-flex; justify-content: center; align-items: center; cursor: pointer; vertical-align: middle">
<el-popover @show="showTagInfo" placement="top-start" title="标签信息" :width="300" trigger="hover">
<el-popover :show-after="500" @show="showTagInfo" placement="top-start" title="标签信息" :width="300" trigger="hover">
<template #reference>
<el-icon>
<InfoFilled />

View File

@@ -52,6 +52,10 @@
</span>
</template>
<template #host="{ data }">
{{ `${data.host}:${data.port}` }}
</template>
<template #database="{ data }">
<el-popover placement="right" trigger="click" :width="300">
<template #reference>
@@ -195,6 +199,10 @@ const queryConfig = [TableQuery.slot('tagPath', '标签', 'tagPathSelect'), Tabl
const columns = ref([
TableColumn.new('tagPath', '标签路径').isSlot().setAddWidth(20),
TableColumn.new('instanceName', '实例名'),
TableColumn.new('type', '类型'),
TableColumn.new('host', 'ip:port').isSlot().setAddWidth(20),
TableColumn.new('username', 'username'),
TableColumn.new('name', '名称'),
TableColumn.new('database', '数据库').isSlot().setMinWidth(70),
TableColumn.new('remark', '备注'),

View File

@@ -39,7 +39,7 @@
>
<template #prefix="{ data }">
<span v-if="data.type == NodeType.DbInst">
<el-popover placement="right-start" title="数据库实例信息" trigger="hover" :width="210">
<el-popover :show-after="500" placement="right-start" title="数据库实例信息" trigger="hover" :width="210">
<template #reference>
<SvgIcon v-if="data.params.type === 'mysql'" name="iconfont icon-op-mysql" :size="18" />
<SvgIcon v-if="data.params.type === 'postgres'" name="iconfont icon-op-postgres" :size="18" />
@@ -47,8 +47,10 @@
<SvgIcon name="InfoFilled" v-else />
</template>
<template #default>
<el-form class="instances-pop-form" label-width="55px" :size="'small'">
<el-form class="instances-pop-form" label-width="auto" :size="'small'">
<el-form-item label="类型:">{{ data.params.type }}</el-form-item>
<el-form-item label="host:">{{ `${data.params.host}:${data.params.port}` }}</el-form-item>
<el-form-item label="user:">{{ data.params.username }}</el-form-item>
<el-form-item label="名称:">{{ data.params.name }}</el-form-item>
<el-form-item v-if="data.params.remark" label="备注:">{{ data.params.remark }}</el-form-item>
</el-form>
@@ -60,7 +62,13 @@
<SvgIcon name="Calendar" v-if="data.type == NodeType.TableMenu" color="#409eff" />
<el-tooltip v-if="data.type == NodeType.Table" effect="customized" :content="data.params.tableComment" placement="top-end">
<el-tooltip
:show-after="500"
v-if="data.type == NodeType.Table"
effect="customized"
:content="data.params.tableComment"
placement="top-end"
>
<SvgIcon name="Calendar" color="#409eff" />
</el-tooltip>

View File

@@ -30,7 +30,7 @@
:sortable="sortable"
>
<template #header v-if="showColumnTip">
<el-tooltip raw-content placement="top" effect="customized">
<el-tooltip :show-after="500" raw-content placement="top" effect="customized">
<template #content> {{ getColumnTip(item) }} </template>
{{ item.columnName }}
</el-tooltip>
@@ -45,7 +45,7 @@
import { onMounted, watch, reactive, toRefs } from 'vue';
import { DbInst, UpdateFieldsMeta, FieldsMeta } from '../db';
const emits = defineEmits(['sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField'])
const emits = defineEmits(['sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField']);
const props = defineProps({
dbId: {
@@ -54,7 +54,7 @@ const props = defineProps({
},
dbType: {
type: String,
default: ''
default: '',
},
db: {
type: String,
@@ -88,9 +88,9 @@ const props = defineProps({
},
height: {
type: String,
default: '600'
}
})
default: '600',
},
});
const state = reactive({
dbId: 0, // 当前选中操作的数据库实例
@@ -108,13 +108,7 @@ const state = reactive({
updatedFields: [] as UpdateFieldsMeta[], // 各个tab表被修改的字段信息
});
const {
tableHeight,
datas,
sortable,
loading,
showColumnTip,
} = toRefs(state);
const { tableHeight, datas } = toRefs(state);
watch(props, (newValue: any) => {
setState(newValue);
@@ -123,7 +117,7 @@ watch(props, (newValue: any) => {
onMounted(async () => {
console.log('in DbTable mounted');
setState(props);
})
});
const setState = (props: any) => {
state.dbId = props.dbId;
@@ -137,7 +131,7 @@ const setState = (props: any) => {
state.columns = props.columns;
state.showColumnTip = props.showColumnTip;
state.emptyText = props.emptyText;
}
};
const getColumnTip = (column: any) => {
const comment = column.columnComment;
@@ -181,7 +175,7 @@ const cellClick = (row: any, column: any, cell: any) => {
row[property] = input.value;
cell.replaceChildren(div);
if (input.value !== text) {
let currentUpdatedFields = state.updatedFields
let currentUpdatedFields = state.updatedFields;
const dbInst = getNowDbInst();
// 主键
const primaryKey = await dbInst.loadTableColumn(state.db, state.table);
@@ -189,73 +183,75 @@ const cellClick = (row: any, column: any, cell: any) => {
// 更新字段列信息
const updateColumn = await dbInst.loadTableColumn(state.db, state.table, property);
const newField = {
div, row,
div,
row,
fieldName: property,
fieldType: updateColumn.columnType,
oldValue: text,
newValue: input.value
newValue: input.value,
} as FieldsMeta;
// 被修改的字段
const primaryKeyFields = currentUpdatedFields.filter((meta) => meta.primaryKey === primaryKeyValue)
const primaryKeyFields = currentUpdatedFields.filter((meta) => meta.primaryKey === primaryKeyValue);
let hasKey = false;
if (primaryKeyFields.length <= 0) {
primaryKeyFields[0] = {
primaryKey: primaryKeyValue,
primaryKeyName: primaryKey.columnName,
primaryKeyType: primaryKey.columnType,
fields: [newField]
}
fields: [newField],
};
} else {
hasKey = true
let hasField = primaryKeyFields[0].fields.some(a => {
hasKey = true;
let hasField = primaryKeyFields[0].fields.some((a) => {
if (a.fieldName === newField.fieldName) {
a.newValue = newField.newValue
a.newValue = newField.newValue;
}
return a.fieldName === newField.fieldName
})
return a.fieldName === newField.fieldName;
});
if (!hasField) {
primaryKeyFields[0].fields.push(newField)
primaryKeyFields[0].fields.push(newField);
}
}
let fields = primaryKeyFields[0].fields
let fields = primaryKeyFields[0].fields;
const fieldsParam = fields.filter((a) => {
if (a.fieldName === column.property) {
a.newValue = input.value
a.newValue = input.value;
}
return a.fieldName === column.property
})
return a.fieldName === column.property;
});
const field = fieldsParam.length > 0 && fieldsParam[0] || {} as FieldsMeta
if (field.oldValue === input.value) { // 新值=旧值
const field = (fieldsParam.length > 0 && fieldsParam[0]) || ({} as FieldsMeta);
if (field.oldValue === input.value) {
// 新值=旧值
// 删除数据
div.classList.remove('update_field_active')
div.classList.remove('update_field_active');
let delIndex: number[] = [];
currentUpdatedFields.forEach((a, i) => {
if (a.primaryKey === primaryKeyValue) {
a.fields = a.fields && a.fields.length > 0 ? a.fields.filter(f => f.fieldName !== column.property) : [];
a.fields.length <= 0 && delIndex.push(i)
a.fields = a.fields && a.fields.length > 0 ? a.fields.filter((f) => f.fieldName !== column.property) : [];
a.fields.length <= 0 && delIndex.push(i);
}
});
delIndex.forEach(i => delete currentUpdatedFields[i])
currentUpdatedFields = currentUpdatedFields.filter(a => a)
delIndex.forEach((i) => delete currentUpdatedFields[i]);
currentUpdatedFields = currentUpdatedFields.filter((a) => a);
} else {
// 新增数据
div.classList.add('update_field_active')
div.classList.add('update_field_active');
if (hasKey) {
currentUpdatedFields.forEach((value, index, array) => {
if (value.primaryKey === primaryKeyValue) {
array[index].fields = fields
array[index].fields = fields;
}
})
});
} else {
currentUpdatedFields.push({
primaryKey: primaryKeyValue,
primaryKeyName: primaryKey.columnName,
primaryKeyType: primaryKey.columnType,
fields
})
fields,
});
}
}
state.updatedFields = currentUpdatedFields;
@@ -266,7 +262,7 @@ const cellClick = (row: any, column: any, cell: any) => {
};
const submitUpdateFields = () => {
const dbInst = DbInst.getInst(state.dbId)
const dbInst = DbInst.getInst(state.dbId);
let currentUpdatedFields = state.updatedFields;
if (currentUpdatedFields.length <= 0) {
return;
@@ -274,58 +270,62 @@ const submitUpdateFields = () => {
const db = state.db;
let res = '';
let divs: HTMLElement[] = [];
currentUpdatedFields.forEach(a => {
currentUpdatedFields.forEach((a) => {
let sql = `UPDATE ${dbInst.wrapName(state.table)} SET `;
let primaryKey = a.primaryKey;
let primaryKeyType = a.primaryKeyType;
let primaryKeyName = a.primaryKeyName;
a.fields.forEach(f => {
sql += ` ${dbInst.wrapName(f.fieldName)} = ${DbInst.wrapColumnValue(f.fieldType, f.newValue)},`
a.fields.forEach((f) => {
sql += ` ${dbInst.wrapName(f.fieldName)} = ${DbInst.wrapColumnValue(f.fieldType, f.newValue)},`;
// 如果修改的字段是主键
if (f.fieldName === primaryKeyName) {
primaryKey = f.oldValue
primaryKey = f.oldValue;
}
divs.push(f.div)
})
sql = sql.substring(0, sql.length - 1)
sql += ` WHERE ${dbInst.wrapName(primaryKeyName)} = ${DbInst.wrapColumnValue(primaryKeyType, primaryKey)} ;`
divs.push(f.div);
});
sql = sql.substring(0, sql.length - 1);
sql += ` WHERE ${dbInst.wrapName(primaryKeyName)} = ${DbInst.wrapColumnValue(primaryKeyType, primaryKey)} ;`;
res += sql;
})
});
dbInst.promptExeSql(db, res, () => { }, () => {
dbInst.promptExeSql(
db,
res,
() => {},
() => {
currentUpdatedFields = [];
divs.forEach(a => {
divs.forEach((a) => {
a.classList.remove('update_field_active');
})
});
state.updatedFields = [];
changeUpdatedField();
});
}
);
};
const cancelUpdateFields = () => {
state.updatedFields.forEach((a: any) => {
a.fields.forEach((b: any) => {
b.div.classList.remove('update_field_active')
b.row[b.fieldName] = b.oldValue
})
})
b.div.classList.remove('update_field_active');
b.row[b.fieldName] = b.oldValue;
});
});
state.updatedFields = [];
changeUpdatedField();
}
};
const changeUpdatedField = () => {
emits('changeUpdatedField', state.updatedFields);
}
};
const getNowDbInst = () => {
return DbInst.getInst(state.dbId);
}
};
defineExpose({
submitUpdateFields,
cancelUpdateFields
})
cancelUpdateFields,
});
</script>
<style lang="scss">

View File

@@ -6,12 +6,12 @@
<el-link @click="onRunSql()" :underline="false" class="ml15" icon="VideoPlay"> </el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-tooltip class="box-item" effect="dark" content="format sql" placement="top">
<el-tooltip :show-after="1000" class="box-item" effect="dark" content="format sql" placement="top">
<el-link @click="formatSql()" type="primary" :underline="false" icon="MagicStick"> </el-link>
</el-tooltip>
<el-divider direction="vertical" border-style="dashed" />
<el-tooltip class="box-item" effect="dark" content="commit" placement="top">
<el-tooltip :show-after="1000" class="box-item" effect="dark" content="commit" placement="top">
<el-link @click="onCommit()" type="success" :underline="false" icon="CircleCheck"> </el-link>
</el-tooltip>
<el-divider direction="vertical" border-style="dashed" />
@@ -27,12 +27,12 @@
multiple
:limit="100"
>
<el-tooltip class="box-item" effect="dark" content="SQL脚本执行" placement="top">
<el-tooltip :show-after="1000" class="box-item" effect="dark" content="SQL脚本执行" placement="top">
<el-link type="success" :underline="false" icon="Document"></el-link>
</el-tooltip>
</el-upload>
<el-divider direction="vertical" border-style="dashed" />
<el-tooltip class="box-item" effect="dark" content="limit" placement="top">
<el-tooltip :show-after="1000" class="box-item" effect="dark" content="limit" placement="top">
<el-link @click="onLimit()" type="success" :underline="false" icon="Operation"> </el-link>
</el-tooltip>
</div>
@@ -153,7 +153,7 @@ const state = reactive({
hasUpdatedFileds: false,
});
const { tableDataHeight, editorHeight, ti, execRes, table, sqlName, loading, hasUpdatedFileds } = toRefs(state);
const { tableDataHeight, ti, execRes, table, loading, hasUpdatedFileds } = toRefs(state);
watch(
() => props.editorHeight,

View File

@@ -74,7 +74,8 @@
:data="columns"
max-height="500"
size="small"
@row-click="(...event: any) => {
@row-click="
(...event: any) => {
onConditionRowClick(event);
}
"
@@ -153,6 +154,7 @@
<el-form ref="dataForm" :model="addDataDialog.data" label-width="auto" size="small">
<el-form-item
v-for="column in columns"
:key="column.columnName"
class="w100"
:prop="column.columnName"
:label="column.columnName"

View File

@@ -54,7 +54,7 @@
<template #action="{ data }">
<span v-auth="'machine:terminal'">
<el-tooltip effect="customized" content="按住ctrl则为新标签打开" placement="top">
<el-tooltip :show-after="500" effect="customized" content="按住ctrl则为新标签打开" placement="top">
<el-button :disabled="data.status == -1" type="primary" @click="showTerminal(data, $event)" link>终端</el-button>
</el-tooltip>

View File

@@ -5,7 +5,7 @@
<tag-tree @node-click="nodeClick" :load="loadNode">
<template #prefix="{ data }">
<span v-if="data.type == NodeType.Mongo">
<el-popover placement="right-start" title="mongo实例信息" trigger="hover" :width="210">
<el-popover :show-after="500" placement="right-start" title="mongo实例信息" trigger="hover" :width="210">
<template #reference>
<SvgIcon name="iconfont icon-op-mongo" :size="18" />
</template>

View File

@@ -7,7 +7,7 @@
<tag-tree @node-click="nodeClick" :load="loadNode">
<template #prefix="{ data }">
<span v-if="data.type == NodeType.Redis">
<el-popover placement="right-start" title="redis实例信息" trigger="hover" :width="210">
<el-popover :show-after="500" placement="right-start" title="redis实例信息" trigger="hover" :width="210">
<template #reference>
<SvgIcon name="iconfont icon-op-redis" :size="18" />
</template>
@@ -471,7 +471,7 @@ const removeDataTab = (targetName: string) => {
delete state.dataTabs[targetName];
};
const keyTreeNodeExpand = (data: any, node: any, component: any) => {
const keyTreeNodeExpand = (data: any, node: any) => {
state.keyTreeExpanded.add(data.key);
// async sort nodes
if (!node.customSorted) {
@@ -480,7 +480,7 @@ const keyTreeNodeExpand = (data: any, node: any, component: any) => {
}
};
const keyTreeNodeCollapse = (data: any, node: any, component: any) => {
const keyTreeNodeCollapse = (data: any) => {
state.keyTreeExpanded.delete(data.key);
};

View File

@@ -49,7 +49,7 @@ func (d *Db) Dbs(rc *req.Ctx) {
}
queryCond.TagIds = tagIds
res, err := d.DbApp.GetPageList(queryCond, page, new([]vo.SelectDataDbVO))
res, err := d.DbApp.GetPageList(queryCond, page, new([]vo.DbListVO))
biz.ErrIsNil(err)
rc.ResData = res
}

View File

@@ -24,7 +24,7 @@ type Instance struct {
// @router /api/instances [get]
func (d *Instance) Instances(rc *req.Ctx) {
queryCond, page := ginx.BindQueryAndPage[*entity.InstanceQuery](rc.GinCtx, new(entity.InstanceQuery))
res, err := d.InstanceApp.GetPageList(queryCond, page, new([]vo.SelectDataInstanceVO))
res, err := d.InstanceApp.GetPageList(queryCond, page, new([]vo.InstanceListVO))
biz.ErrIsNil(err)
rc.ResData = res
}

View File

@@ -2,8 +2,7 @@ package vo
import "time"
type SelectDataDbVO struct {
//models.BaseModel
type DbListVO struct {
Id *int64 `json:"id"`
Name *string `json:"name"`
Database *string `json:"database"`
@@ -14,6 +13,9 @@ type SelectDataDbVO struct {
InstanceId *int64 `json:"instanceId"`
InstanceName *string `json:"instanceName"`
InstanceType *string `json:"type"`
Host string `json:"host"`
Port int `json:"port"`
Username string `json:"username"`
CreateTime *time.Time `json:"createTime"`
Creator *string `json:"creator"`

View File

@@ -2,8 +2,7 @@ package vo
import "time"
type SelectDataInstanceVO struct {
//models.BaseModel
type InstanceListVO struct {
Id *int64 `json:"id"`
Name *string `json:"name"`
Host *string `json:"host"`

View File

@@ -19,7 +19,7 @@ func newDbRepo() repository.Db {
// 分页获取数据库信息列表
func (d *dbRepoImpl) GetDbList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
qd := gormx.NewQueryWithTableName("t_db db").
Select("db.*, inst.name instance_name, inst.type instance_type").
Select("db.*, inst.name instance_name, inst.type instance_type, inst.host, inst.port, inst.username ").
Joins("JOIN t_db_instance inst ON db.instance_id = inst.id").
Eq("db.instance_id", condition.InstanceId).
Like("db.database", condition.Database).