mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 15:30:25 +08:00
refactor: 虚拟表格与contextmenu菜单优化
This commit is contained in:
@@ -101,7 +101,7 @@ const refreshWatermarkTime = () => {
|
||||
} else {
|
||||
clearInterval(refreshWatermarkTimeInterval);
|
||||
}
|
||||
}, 10000);
|
||||
}, 60000);
|
||||
};
|
||||
|
||||
// 页面销毁时,关闭监听布局配置
|
||||
|
||||
17
mayfly_go_web/src/common/use.ts
Normal file
17
mayfly_go_web/src/common/use.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
const vw = ref(document.documentElement.clientWidth);
|
||||
const vh = ref(document.documentElement.clientHeight);
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
vw.value = document.documentElement.clientWidth;
|
||||
vh.value = document.documentElement.clientHeight;
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取视图宽高
|
||||
* @returns 视图宽高
|
||||
*/
|
||||
export function useViewport() {
|
||||
return { vw, vh };
|
||||
}
|
||||
@@ -29,15 +29,19 @@ export function exportCsv(filename: string, columns: string[], datas: []) {
|
||||
cvsData.push(dataValueArr);
|
||||
}
|
||||
const csvString = cvsData.map((e) => e.join(',')).join('\n');
|
||||
exportFile(`${filename}.csv`, csvString);
|
||||
}
|
||||
|
||||
export function exportFile(filename: string, content: string) {
|
||||
// 导出
|
||||
let link = document.createElement('a');
|
||||
let exportContent = '\uFEFF';
|
||||
let blob = new Blob([exportContent + csvString], {
|
||||
let blob = new Blob([exportContent + content], {
|
||||
type: 'text/plain;charset=utf-8',
|
||||
});
|
||||
link.id = 'download-csv';
|
||||
link.id = 'download-file';
|
||||
link.setAttribute('href', URL.createObjectURL(blob));
|
||||
link.setAttribute('download', `${filename}.csv`);
|
||||
link.setAttribute('download', `${filename}`);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export class ContextmenuItem {
|
||||
import Contextmenu from './index.vue';
|
||||
|
||||
class ContextmenuItem {
|
||||
clickId: any;
|
||||
|
||||
txt: string;
|
||||
@@ -53,3 +55,5 @@ export class ContextmenuItem {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export { Contextmenu, ContextmenuItem };
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<transition name="el-zoom-in-center">
|
||||
<transition @enter="onEnter" name="el-zoom-in-center">
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu"
|
||||
role="tooltip"
|
||||
data-popper-placement="bottom"
|
||||
:style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
|
||||
:style="`top: ${state.dropdown.y + 5}px;left: ${state.dropdown.x}px;`"
|
||||
:key="Math.random()"
|
||||
v-show="state.isShow && !allHide"
|
||||
>
|
||||
@@ -25,7 +25,7 @@
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
<div class="el-popper__arrow" :style="{ left: `${state.arrowLeft}px` }"></div>
|
||||
<div v-if="state.arrowLeft > 0" class="el-popper__arrow" :style="{ left: `${state.arrowLeft}px` }"></div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
@@ -33,6 +33,8 @@
|
||||
<script setup lang="ts" name="layoutTagsViewContextmenu">
|
||||
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
|
||||
import { ContextmenuItem } from './index';
|
||||
import { useViewport } from '@/common/use';
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
|
||||
// 定义父组件传过来的值
|
||||
const props = defineProps({
|
||||
@@ -54,14 +56,56 @@ const props = defineProps({
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['currentContextmenuClick']);
|
||||
|
||||
const { vw, vh } = useViewport();
|
||||
|
||||
// 定义变量内容
|
||||
const state = reactive({
|
||||
isShow: false,
|
||||
dropdownList: [] as ContextmenuItem[],
|
||||
item: {} as any,
|
||||
arrowLeft: 10,
|
||||
dropdown: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
});
|
||||
|
||||
// 下拉菜单宽高
|
||||
let contextmenuWidth = 117;
|
||||
let contextmenuHeight = 117;
|
||||
// 下拉菜单元素
|
||||
let ele = null as any;
|
||||
|
||||
const onEnter = (el: any) => {
|
||||
if (ele || el.offsetHeight == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ele = el;
|
||||
contextmenuHeight = el.offsetHeight;
|
||||
contextmenuWidth = el.offsetWidth;
|
||||
setDropdowns(props.dropdown);
|
||||
};
|
||||
|
||||
const setDropdowns = (dropdown: any) => {
|
||||
let { x, y } = dropdown;
|
||||
|
||||
state.arrowLeft = 10;
|
||||
|
||||
// `Dropdown 下拉菜单` 的宽度
|
||||
if (x + contextmenuWidth > vw.value) {
|
||||
state.arrowLeft = contextmenuWidth - (vw.value - x);
|
||||
x = vw.value - contextmenuWidth - 5;
|
||||
}
|
||||
if (y + contextmenuHeight > vh.value) {
|
||||
y = vh.value - contextmenuHeight - 5;
|
||||
state.arrowLeft = 0;
|
||||
}
|
||||
|
||||
state.dropdown.x = x;
|
||||
state.dropdown.y = y;
|
||||
};
|
||||
|
||||
const allHide = computed(() => {
|
||||
for (let item of state.dropdownList) {
|
||||
if (!item.isHide(state.item)) {
|
||||
@@ -71,18 +115,6 @@ const allHide = computed(() => {
|
||||
return true;
|
||||
});
|
||||
|
||||
// 父级传过来的坐标 x,y 值
|
||||
const dropdowns = computed(() => {
|
||||
// 117 为 `Dropdown 下拉菜单` 的宽度
|
||||
if (props.dropdown.x + 117 > document.documentElement.clientWidth) {
|
||||
return {
|
||||
x: document.documentElement.clientWidth - 117 - 5,
|
||||
y: props.dropdown.y,
|
||||
};
|
||||
} else {
|
||||
return props.dropdown;
|
||||
}
|
||||
});
|
||||
// 当前项菜单点击
|
||||
const onCurrentContextmenuClick = (ci: ContextmenuItem) => {
|
||||
// 存在点击事件,则触发该事件函数
|
||||
@@ -91,6 +123,7 @@ const onCurrentContextmenuClick = (ci: ContextmenuItem) => {
|
||||
}
|
||||
emit('currentContextmenuClick', { id: ci.clickId, item: state.item });
|
||||
};
|
||||
|
||||
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
|
||||
const openContextmenu = (item: any) => {
|
||||
state.item = item;
|
||||
@@ -99,30 +132,34 @@ const openContextmenu = (item: any) => {
|
||||
state.isShow = true;
|
||||
}, 10);
|
||||
};
|
||||
|
||||
// 关闭右键菜单
|
||||
const closeContextmenu = () => {
|
||||
state.isShow = false;
|
||||
};
|
||||
|
||||
// 监听页面监听进行右键菜单的关闭
|
||||
onMounted(() => {
|
||||
document.body.addEventListener('click', closeContextmenu);
|
||||
state.dropdownList = props.items;
|
||||
});
|
||||
|
||||
// 页面卸载时,移除右键菜单监听事件
|
||||
onUnmounted(() => {
|
||||
document.body.removeEventListener('click', closeContextmenu);
|
||||
});
|
||||
// 监听下拉菜单位置
|
||||
|
||||
watch(
|
||||
() => props.dropdown,
|
||||
({ x }) => {
|
||||
if (x + 117 > document.documentElement.clientWidth) state.arrowLeft = 117 - (document.documentElement.clientWidth - x);
|
||||
else state.arrowLeft = 10;
|
||||
() => {
|
||||
// 元素置为空,重新在onEnter赋值元素,否则会造成堆栈溢出
|
||||
ele = null;
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.items,
|
||||
(x: any) => {
|
||||
|
||||
@@ -54,8 +54,7 @@ import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
import Sortable from 'sortablejs';
|
||||
import Contextmenu from '@/components/contextmenu/index.vue';
|
||||
import { ContextmenuItem } from '@/components/contextmenu/index';
|
||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
|
||||
import { getTagViews, setTagViews, removeTagViews } from '@/common/utils/storage';
|
||||
import { useTagsViews } from '@/store/tagsViews';
|
||||
import { useKeepALiveNames } from '@/store/keepAliveNames';
|
||||
|
||||
@@ -42,16 +42,13 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, ref, watch, toRefs } from 'vue';
|
||||
import { onMounted, reactive, ref, watch, toRefs, onUnmounted } from 'vue';
|
||||
import { TagTreeNode } from './tag';
|
||||
import TagInfo from './TagInfo.vue';
|
||||
import Contextmenu from '@/components/contextmenu/index.vue';
|
||||
import { Contextmenu } from '@/components/contextmenu';
|
||||
import { useViewport } from '@/common/use';
|
||||
|
||||
const props = defineProps({
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 0,
|
||||
},
|
||||
load: {
|
||||
type: Function,
|
||||
required: false,
|
||||
@@ -76,6 +73,8 @@ const emit = defineEmits(['nodeClick', 'currentContextmenuClick']);
|
||||
const treeRef: any = ref(null);
|
||||
const contextmenuRef = ref();
|
||||
|
||||
const { vh } = useViewport();
|
||||
|
||||
const state = reactive({
|
||||
height: 600 as any,
|
||||
filterText: '',
|
||||
@@ -89,18 +88,18 @@ const state = reactive({
|
||||
const { filterText } = toRefs(state);
|
||||
|
||||
onMounted(async () => {
|
||||
if (!props.height) {
|
||||
setHeight();
|
||||
window.onresize = () => setHeight();
|
||||
} else {
|
||||
state.height = props.height;
|
||||
}
|
||||
setHeight();
|
||||
window.addEventListener('resize', setHeight);
|
||||
});
|
||||
|
||||
const setHeight = () => {
|
||||
state.height = window.innerHeight - 157 + 'px';
|
||||
state.height = vh.value - 148 + 'px';
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', setHeight);
|
||||
});
|
||||
|
||||
watch(filterText, (val) => {
|
||||
treeRef.value?.filter(val);
|
||||
});
|
||||
|
||||
@@ -159,7 +159,7 @@ import TagTree from '../component/TagTree.vue';
|
||||
import { dbApi } from './api';
|
||||
import { dispposeCompletionItemProvider } from '@/components/monaco/completionItemProvider';
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import { ContextmenuItem } from '@/components/contextmenu/index';
|
||||
import { ContextmenuItem } from '@/components/contextmenu';
|
||||
|
||||
const DbSqlEditor = defineAsyncComponent(() => import('./component/sqleditor/DbSqlEditor.vue'));
|
||||
const DbTableDataOp = defineAsyncComponent(() => import('./component/table/DbTableDataOp.vue'));
|
||||
|
||||
@@ -50,9 +50,6 @@
|
||||
|
||||
<div class="mt5">
|
||||
<el-row>
|
||||
<span v-if="execRes.data.length > 0">
|
||||
<el-link type="success" :underline="false" @click="exportData"><span style="font-size: 12px">导出</span></el-link>
|
||||
</span>
|
||||
<span v-if="hasUpdatedFileds">
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
<el-link type="success" :underline="false" @click="submitUpdateFields()"><span style="font-size: 12px">提交</span></el-link>
|
||||
@@ -83,7 +80,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, nextTick, watch, onMounted, reactive, toRefs, ref, Ref } from 'vue';
|
||||
import { getToken } from '@/common/utils/storage';
|
||||
import { isTrue, notBlank } from '@/common/assert';
|
||||
import { notBlank } from '@/common/assert';
|
||||
import { format as sqlFormatter } from 'sql-formatter';
|
||||
import config from '@/common/config';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
@@ -93,8 +90,6 @@ import { editor } from 'monaco-editor';
|
||||
|
||||
import DbTableData from '@/views/ops/db/component/table/DbTableData.vue';
|
||||
import { DbInst } from '../../db';
|
||||
import { exportCsv } from '@/common/utils/export';
|
||||
import { dateStrFormat } from '@/common/utils/date';
|
||||
import { dbApi } from '../../api';
|
||||
|
||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
|
||||
@@ -143,7 +138,7 @@ const state = reactive({
|
||||
},
|
||||
selectionDatas: [] as any,
|
||||
editorHeight: '500',
|
||||
tableDataHeight: 240 as any,
|
||||
tableDataHeight: 255 as any,
|
||||
hasUpdatedFileds: false,
|
||||
});
|
||||
|
||||
@@ -458,19 +453,6 @@ 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.map((x: any) => x.columnName),
|
||||
dataList
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* sql文件执行进度通知缓存
|
||||
*/
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
:width="width"
|
||||
:height="height"
|
||||
fixed
|
||||
class="table"
|
||||
:row-event-handlers="rowEventHandlers"
|
||||
>
|
||||
<template #header="{ columns }">
|
||||
@@ -104,7 +105,7 @@
|
||||
<el-input v-model="state.genSqlDialog.sql" type="textarea" rows="20" />
|
||||
</el-dialog>
|
||||
|
||||
<contextmenu :dropdown="state.contextmenu.dropdown" :items="state.contextmenu.items" ref="headerContextmenuRef" />
|
||||
<contextmenu :dropdown="state.contextmenu.dropdown" :items="state.contextmenu.items" ref="contextmenuRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -112,9 +113,10 @@
|
||||
import { ref, onMounted, watch, reactive, toRefs } from 'vue';
|
||||
import { ElInput } from 'element-plus';
|
||||
import { DbInst } from '@/views/ops/db/db';
|
||||
import Contextmenu from '@/components/contextmenu/index.vue';
|
||||
import { ContextmenuItem } from '@/components/contextmenu/index';
|
||||
import { ContextmenuItem, Contextmenu } from '@/components/contextmenu';
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import { exportCsv, exportFile } from '@/common/utils/export';
|
||||
import { dateStrFormat } from '@/common/utils/date';
|
||||
|
||||
const emits = defineEmits(['dataDelete', 'sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField']);
|
||||
|
||||
@@ -163,9 +165,11 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const headerContextmenuRef = ref();
|
||||
const contextmenuRef = ref();
|
||||
const tableRef = ref();
|
||||
|
||||
/** 表头 contextmenu items **/
|
||||
|
||||
const cmHeaderAsc = new ContextmenuItem('asc', '升序').withIcon('top').withOnClick((data: any) => {
|
||||
onTableSortChange({ columnName: data.dataKey, order: 'asc' });
|
||||
});
|
||||
@@ -174,7 +178,21 @@ const cmHeaderDesc = new ContextmenuItem('desc', '降序').withIcon('bottom').wi
|
||||
onTableSortChange({ columnName: data.dataKey, order: 'desc' });
|
||||
});
|
||||
|
||||
const cmDataDel = new ContextmenuItem('desc', '删除')
|
||||
const cmHeaderFixed = new ContextmenuItem('fixed', '固定')
|
||||
.withIcon('Paperclip')
|
||||
.withOnClick((data: any) => {
|
||||
data.fixed = true;
|
||||
})
|
||||
.withHideFunc((data: any) => data.fixed);
|
||||
|
||||
const cmHeaderCancenFixed = new ContextmenuItem('cancelFixed', '取消固定')
|
||||
.withIcon('Minus')
|
||||
.withOnClick((data: any) => (data.fixed = false))
|
||||
.withHideFunc((data: any) => !data.fixed);
|
||||
|
||||
/** 表数据 contextmenu items **/
|
||||
|
||||
const cmDataDel = new ContextmenuItem('deleteData', '删除')
|
||||
.withIcon('delete')
|
||||
.withOnClick(() => onDeleteData())
|
||||
.withHideFunc(() => {
|
||||
@@ -182,13 +200,22 @@ const cmDataDel = new ContextmenuItem('desc', '删除')
|
||||
});
|
||||
|
||||
const cmDataGenInsertSql = new ContextmenuItem('genInsertSql', 'Insert SQL')
|
||||
.withIcon('document')
|
||||
.withIcon('tickets')
|
||||
.withOnClick(() => onGenerateInsertSql())
|
||||
.withHideFunc(() => {
|
||||
return state.table == '';
|
||||
});
|
||||
|
||||
const cmDataGenJson = new ContextmenuItem('genJson', '生成JSON').withIcon('document').withOnClick(() => onGenerateJson());
|
||||
const cmDataGenJson = new ContextmenuItem('genJson', '生成JSON').withIcon('tickets').withOnClick(() => onGenerateJson());
|
||||
|
||||
const cmDataExportCsv = new ContextmenuItem('exportCsv', '导出CSV').withIcon('document').withOnClick(() => onExportCsv());
|
||||
|
||||
const cmDataExportSql = new ContextmenuItem('exportSql', '导出SQL')
|
||||
.withIcon('document')
|
||||
.withOnClick(() => onExportSql())
|
||||
.withHideFunc(() => {
|
||||
return state.table == '';
|
||||
});
|
||||
|
||||
class NowUpdateCell {
|
||||
rowIndex: number;
|
||||
@@ -328,7 +355,6 @@ onMounted(async () => {
|
||||
});
|
||||
|
||||
const setTableData = (datas: any) => {
|
||||
console.log('set table datas', props);
|
||||
tableRef.value.scrollTo({ scrollLeft: 0, scrollTop: 0 });
|
||||
selectionRowsMap.clear();
|
||||
cellUpdateMap.clear();
|
||||
@@ -364,6 +390,11 @@ const canEdit = (rowIndex: number, colIndex: number) => {
|
||||
return state.table && nowUpdateCell && nowUpdateCell.rowIndex == rowIndex && nowUpdateCell.colIndex == colIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* 判断当前单元格是否被更新了
|
||||
* @param rowIndex ri
|
||||
* @param columnName cn
|
||||
*/
|
||||
const isUpdated = (rowIndex: number, columnName: string) => {
|
||||
return cellUpdateMap.get(rowIndex)?.columnsMap.get(columnName);
|
||||
};
|
||||
@@ -423,8 +454,8 @@ const headerContextmenuClick = (event: any, data: any) => {
|
||||
const { clientX, clientY } = event;
|
||||
state.contextmenu.dropdown.x = clientX;
|
||||
state.contextmenu.dropdown.y = clientY;
|
||||
state.contextmenu.items = [cmHeaderAsc, cmHeaderDesc];
|
||||
headerContextmenuRef.value.openContextmenu(data);
|
||||
state.contextmenu.items = [cmHeaderAsc, cmHeaderDesc, cmHeaderFixed, cmHeaderCancenFixed];
|
||||
contextmenuRef.value.openContextmenu(data);
|
||||
};
|
||||
|
||||
const dataContextmenuClick = (event: any, rowIndex: number, data: any) => {
|
||||
@@ -437,86 +468,8 @@ const dataContextmenuClick = (event: any, rowIndex: number, data: any) => {
|
||||
const { clientX, clientY } = event;
|
||||
state.contextmenu.dropdown.x = clientX;
|
||||
state.contextmenu.dropdown.y = clientY;
|
||||
state.contextmenu.items = [cmDataDel, cmDataGenInsertSql, cmDataGenJson];
|
||||
headerContextmenuRef.value.openContextmenu(data);
|
||||
};
|
||||
|
||||
const onEnterEditMode = (el: any, rowData: any, column: any, rowIndex = 0, columnIndex = 0) => {
|
||||
if (!state.table) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerRefresh();
|
||||
|
||||
const oldVal = rowData[column.dataKey];
|
||||
nowUpdateCell = {
|
||||
rowIndex: rowIndex,
|
||||
colIndex: columnIndex,
|
||||
oldValue: oldVal,
|
||||
};
|
||||
};
|
||||
|
||||
const onExitEditMode = (rowData: any, column: any, rowIndex = 0) => {
|
||||
const oldValue = nowUpdateCell.oldValue;
|
||||
const newValue = rowData[column.dataKey];
|
||||
|
||||
// 未改变单元格值
|
||||
if (oldValue == newValue) {
|
||||
nowUpdateCell = null as any;
|
||||
triggerRefresh();
|
||||
return;
|
||||
}
|
||||
|
||||
let updatedRow = cellUpdateMap.get(rowIndex);
|
||||
if (!updatedRow) {
|
||||
updatedRow = new UpdatedRow();
|
||||
updatedRow.rowData = rowData;
|
||||
cellUpdateMap.set(rowIndex, updatedRow);
|
||||
}
|
||||
|
||||
const columnName = column.dataKey;
|
||||
let cellData = updatedRow.columnsMap.get(columnName);
|
||||
if (cellData) {
|
||||
// 多次修改情况,可能又修改回原值,则移除该修改单元格
|
||||
if (cellData.oldValue == newValue) {
|
||||
cellUpdateMap.delete(rowIndex);
|
||||
}
|
||||
} else {
|
||||
cellData = new TableCellData();
|
||||
cellData.oldValue = oldValue;
|
||||
updatedRow.columnsMap.set(columnName, cellData);
|
||||
}
|
||||
|
||||
nowUpdateCell = null as any;
|
||||
triggerRefresh();
|
||||
changeUpdatedField();
|
||||
};
|
||||
|
||||
const rowClass = (row: any) => {
|
||||
if (isSelection(row.rowIndex)) {
|
||||
return 'data-selection';
|
||||
}
|
||||
if (row.rowIndex % 2 != 0) {
|
||||
return 'data-spacing';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const getColumnTip = (column: any) => {
|
||||
const comment = column.columnComment;
|
||||
return `${column.columnType} ${comment ? ' | ' + comment : ''}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 触发响应式实时刷新,否则需要滑动或移动才能使样式实时生效
|
||||
*/
|
||||
const triggerRefresh = () => {
|
||||
// 改变columns等属性值,才能触发slot中的if条件等, 暂不知为啥
|
||||
if (state.columns[0].opTimes) {
|
||||
state.columns[0].opTimes = state.columns[0].opTimes + 1;
|
||||
} else {
|
||||
state.columns[0].opTimes = 1;
|
||||
}
|
||||
state.contextmenu.items = [cmDataDel, cmDataGenInsertSql, cmDataGenJson, cmDataExportCsv, cmDataExportSql];
|
||||
contextmenuRef.value.openContextmenu(data);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -563,6 +516,77 @@ const onGenerateJson = async () => {
|
||||
state.genSqlDialog.visible = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 导出当前页数据
|
||||
*/
|
||||
const onExportCsv = () => {
|
||||
const dataList = state.datas as any;
|
||||
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);
|
||||
};
|
||||
|
||||
const onExportSql = async () => {
|
||||
const selectionDatas = state.datas;
|
||||
exportFile(
|
||||
`数据导出-${state.table}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}.sql`,
|
||||
await getNowDbInst().genInsertSql(state.db, state.table, selectionDatas)
|
||||
);
|
||||
};
|
||||
|
||||
const onEnterEditMode = (el: any, rowData: any, column: any, rowIndex = 0, columnIndex = 0) => {
|
||||
if (!state.table) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerRefresh();
|
||||
nowUpdateCell = {
|
||||
rowIndex: rowIndex,
|
||||
colIndex: columnIndex,
|
||||
oldValue: rowData[column.dataKey],
|
||||
};
|
||||
};
|
||||
|
||||
const onExitEditMode = (rowData: any, column: any, rowIndex = 0) => {
|
||||
const oldValue = nowUpdateCell.oldValue;
|
||||
const newValue = rowData[column.dataKey];
|
||||
|
||||
// 未改变单元格值
|
||||
if (oldValue == newValue) {
|
||||
nowUpdateCell = null as any;
|
||||
triggerRefresh();
|
||||
return;
|
||||
}
|
||||
|
||||
let updatedRow = cellUpdateMap.get(rowIndex);
|
||||
if (!updatedRow) {
|
||||
updatedRow = new UpdatedRow();
|
||||
updatedRow.rowData = rowData;
|
||||
cellUpdateMap.set(rowIndex, updatedRow);
|
||||
}
|
||||
|
||||
const columnName = column.dataKey;
|
||||
let cellData = updatedRow.columnsMap.get(columnName);
|
||||
if (cellData) {
|
||||
// 多次修改情况,可能又修改回原值,则移除该修改单元格
|
||||
if (cellData.oldValue == newValue) {
|
||||
cellUpdateMap.delete(rowIndex);
|
||||
}
|
||||
} else {
|
||||
cellData = new TableCellData();
|
||||
cellData.oldValue = oldValue;
|
||||
updatedRow.columnsMap.set(columnName, cellData);
|
||||
}
|
||||
|
||||
nowUpdateCell = null as any;
|
||||
triggerRefresh();
|
||||
changeUpdatedField();
|
||||
};
|
||||
|
||||
const submitUpdateFields = async () => {
|
||||
const dbInst = getNowDbInst();
|
||||
if (cellUpdateMap.size == 0) {
|
||||
@@ -630,6 +654,33 @@ const changeUpdatedField = () => {
|
||||
emits('changeUpdatedField', cellUpdateMap);
|
||||
};
|
||||
|
||||
const rowClass = (row: any) => {
|
||||
if (isSelection(row.rowIndex)) {
|
||||
return 'data-selection';
|
||||
}
|
||||
if (row.rowIndex % 2 != 0) {
|
||||
return 'data-spacing';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const getColumnTip = (column: any) => {
|
||||
const comment = column.columnComment;
|
||||
return `${column.columnType} ${comment ? ' | ' + comment : ''}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 触发响应式实时刷新,否则需要滑动或移动才能使样式实时生效
|
||||
*/
|
||||
const triggerRefresh = () => {
|
||||
// 改变columns等属性值,才能触发slot中的if条件等, 暂不知为啥
|
||||
if (state.columns[0].opTimes) {
|
||||
state.columns[0].opTimes = state.columns[0].opTimes + 1;
|
||||
} else {
|
||||
state.columns[0].opTimes = 1;
|
||||
}
|
||||
};
|
||||
|
||||
const getNowDbInst = () => {
|
||||
return DbInst.getInst(state.dbId);
|
||||
};
|
||||
@@ -640,8 +691,13 @@ defineExpose({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style lang="scss">
|
||||
.db-table-data {
|
||||
.table {
|
||||
border-left: var(--el-table-border);
|
||||
border-top: var(--el-table-border);
|
||||
}
|
||||
|
||||
.table-data-cell {
|
||||
padding: 0 2px;
|
||||
font-size: 12px;
|
||||
@@ -650,6 +706,7 @@ defineExpose({
|
||||
.data-selection {
|
||||
background-color: var(--el-color-success-light-8);
|
||||
}
|
||||
|
||||
.data-spacing {
|
||||
background-color: var(--el-fill-color-lighter);
|
||||
}
|
||||
|
||||
@@ -35,11 +35,6 @@
|
||||
</el-tooltip>
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
|
||||
<el-tooltip :show-after="500" class="box-item" effect="dark" content="导出当前页的csv文件" placement="top">
|
||||
<el-link type="success" :underline="false" @click="exportData"><span class="f12">导出</span></el-link>
|
||||
</el-tooltip>
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
|
||||
<el-tooltip :show-after="500" v-if="hasUpdatedFileds" class="box-item" effect="dark" content="提交修改" placement="top">
|
||||
<el-link @click="submitUpdateFields()" type="success" :underline="false" class="f12">提交</el-link>
|
||||
</el-tooltip>
|
||||
@@ -185,12 +180,10 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, computed, watch, reactive, toRefs, ref, Ref, onUnmounted } from 'vue';
|
||||
import { isTrue, notEmpty } from '@/common/assert';
|
||||
import { notEmpty } from '@/common/assert';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { DbInst } from '@/views/ops/db/db';
|
||||
import { exportCsv } from '@/common/utils/export';
|
||||
import { dateStrFormat } from '@/common/utils/date';
|
||||
import DbTableData from './DbTableData.vue';
|
||||
|
||||
const dataForm: any = ref(null);
|
||||
@@ -227,7 +220,7 @@ const state = reactive({
|
||||
columns: [] as any,
|
||||
pageNum: 1,
|
||||
pageSize: DbInst.DefaultLimit,
|
||||
pageSizes: [20, 40, 80, 100, 200, 300, 400],
|
||||
pageSizes: [20, 50, 100, 200, 500, 1000],
|
||||
count: 0,
|
||||
selectionDatas: [] as any,
|
||||
condPopVisible: false,
|
||||
@@ -333,21 +326,6 @@ const handleSizeChange = async (size: any) => {
|
||||
await selectData();
|
||||
};
|
||||
|
||||
/**
|
||||
* 导出当前页数据
|
||||
*/
|
||||
const exportData = () => {
|
||||
const dataList = state.datas as any;
|
||||
isTrue(dataList.length > 0, '没有数据可导出');
|
||||
let columnNames = [];
|
||||
for (let column of state.columns) {
|
||||
if (column.show) {
|
||||
columnNames.push(column.columnName);
|
||||
}
|
||||
}
|
||||
exportCsv(`数据导出-${props.tableName}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, columnNames, dataList);
|
||||
};
|
||||
|
||||
/**
|
||||
* 选择条件列
|
||||
*/
|
||||
|
||||
@@ -179,8 +179,7 @@ import { isTrue, notBlank, notNull } from '@/common/assert';
|
||||
import { TagTreeNode, NodeType } from '../component/tag';
|
||||
import TagTree from '../component/TagTree.vue';
|
||||
import { keysToTree, sortByTreeNodes, keysToList } from './utils';
|
||||
import Contextmenu from '@/components/contextmenu/index.vue';
|
||||
import { ContextmenuItem } from '@/components/contextmenu/index';
|
||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
|
||||
|
||||
const KeyDetail = defineAsyncComponent(() => import('./KeyDetail.vue'));
|
||||
|
||||
|
||||
@@ -88,8 +88,7 @@ import { toRefs, ref, watch, reactive, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { tagApi } from './api';
|
||||
import { dateFormat } from '@/common/utils/date';
|
||||
import Contextmenu from '@/components/contextmenu/index.vue';
|
||||
import { ContextmenuItem } from '@/components/contextmenu/index';
|
||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu/index';
|
||||
|
||||
interface Tree {
|
||||
id: number;
|
||||
|
||||
@@ -101,8 +101,7 @@ import { ResourceTypeEnum } from '../enums';
|
||||
import { resourceApi } from '../api';
|
||||
import { dateFormat } from '@/common/utils/date';
|
||||
import EnumValue from '@/common/Enum';
|
||||
import Contextmenu from '@/components/contextmenu/index.vue';
|
||||
import { ContextmenuItem } from '@/components/contextmenu/index';
|
||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
|
||||
|
||||
const menuTypeValue = ResourceTypeEnum.Menu.value;
|
||||
const permissionTypeValue = ResourceTypeEnum.Permission.value;
|
||||
|
||||
Reference in New Issue
Block a user