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