mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 08:20:25 +08:00 
			
		
		
		
	refactor: 数据库表使用虚拟表替换,提升数据量较大时的渲染速度
This commit is contained in:
		@@ -366,7 +366,7 @@ const state = reactive({
 | 
			
		||||
    activeName: '',
 | 
			
		||||
    reloadStatus: false,
 | 
			
		||||
    tabs,
 | 
			
		||||
    dataTabsTableHeight: '600',
 | 
			
		||||
    dataTabsTableHeight: 600,
 | 
			
		||||
    editorHeight: '600',
 | 
			
		||||
    tablesOpHeight: '600',
 | 
			
		||||
});
 | 
			
		||||
@@ -388,7 +388,7 @@ onBeforeUnmount(() => {
 | 
			
		||||
 */
 | 
			
		||||
const setHeight = () => {
 | 
			
		||||
    state.editorHeight = window.innerHeight - 500 + 'px';
 | 
			
		||||
    state.dataTabsTableHeight = window.innerHeight - 255 + 'px';
 | 
			
		||||
    state.dataTabsTableHeight = window.innerHeight - 255;
 | 
			
		||||
    state.tablesOpHeight = window.innerHeight - 220 + 'px';
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -50,10 +50,7 @@
 | 
			
		||||
 | 
			
		||||
        <div class="mt5">
 | 
			
		||||
            <el-row>
 | 
			
		||||
                <el-link v-if="table" @click="onDeleteData()" class="ml5" type="danger" icon="delete" :underline="false"></el-link>
 | 
			
		||||
 | 
			
		||||
                <span v-if="execRes.data.length > 0">
 | 
			
		||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
                    <el-link type="success" :underline="false" @click="exportData"><span style="font-size: 12px">导出</span></el-link>
 | 
			
		||||
                </span>
 | 
			
		||||
                <span v-if="hasUpdatedFileds">
 | 
			
		||||
@@ -77,6 +74,7 @@
 | 
			
		||||
                empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改"
 | 
			
		||||
                @selection-change="onDataSelectionChange"
 | 
			
		||||
                @change-updated-field="changeUpdatedField"
 | 
			
		||||
                @data-delete="onDeleteData"
 | 
			
		||||
            ></db-table-data>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -149,7 +147,7 @@ const state = reactive({
 | 
			
		||||
    hasUpdatedFileds: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { tableDataHeight, execRes, table, loading, hasUpdatedFileds } = toRefs(state);
 | 
			
		||||
const { tableDataHeight, execRes, loading, hasUpdatedFileds } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.editorHeight,
 | 
			
		||||
@@ -529,27 +527,20 @@ const onDataSelectionChange = (datas: []) => {
 | 
			
		||||
    state.selectionDatas = datas;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const changeUpdatedField = (updatedFields: []) => {
 | 
			
		||||
const changeUpdatedField = (updatedFields: any) => {
 | 
			
		||||
    // 如果存在要更新字段,则显示提交和取消按钮
 | 
			
		||||
    state.hasUpdatedFileds = updatedFields && updatedFields.length > 0;
 | 
			
		||||
    state.hasUpdatedFileds = updatedFields && updatedFields.size > 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 执行删除数据事件
 | 
			
		||||
 * 数据删除事件
 | 
			
		||||
 */
 | 
			
		||||
const onDeleteData = async () => {
 | 
			
		||||
    const deleteDatas = state.selectionDatas;
 | 
			
		||||
    isTrue(deleteDatas && deleteDatas.length > 0, '请先选择要删除的数据');
 | 
			
		||||
const onDeleteData = async (deleteDatas: any) => {
 | 
			
		||||
    const db = props.dbName;
 | 
			
		||||
    const dbInst = getNowDbInst();
 | 
			
		||||
    const primaryKey = await dbInst.loadTableColumn(db, state.table);
 | 
			
		||||
    const primaryKeyColumnName = primaryKey.columnName;
 | 
			
		||||
    dbInst.promptExeSql(db, dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas), null, () => {
 | 
			
		||||
        state.execRes.data = state.execRes.data.filter(
 | 
			
		||||
            (d: any) => !(deleteDatas.findIndex((x: any) => x[primaryKeyColumnName] == d[primaryKeyColumnName]) != -1)
 | 
			
		||||
        );
 | 
			
		||||
        state.selectionDatas = [];
 | 
			
		||||
    });
 | 
			
		||||
    state.execRes.data = state.execRes.data.filter((d: any) => !(deleteDatas.findIndex((x: any) => x[primaryKeyColumnName] == d[primaryKeyColumnName]) != -1));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const submitUpdateFields = () => {
 | 
			
		||||
@@ -572,10 +563,6 @@ const cancelUpdateFields = () => {
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.update_field_active {
 | 
			
		||||
    background-color: var(--el-color-success);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.editor-move-resize {
 | 
			
		||||
    cursor: n-resize;
 | 
			
		||||
    height: 3px;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,51 +1,122 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-table
 | 
			
		||||
            @cell-dblclick="(row: any, column: any, cell: any, event: any) => cellClick(row, column, cell)"
 | 
			
		||||
            @sort-change="(sort: any) => onTableSortChange(sort)"
 | 
			
		||||
            @selection-change="onDataSelectionChange"
 | 
			
		||||
            :data="datas"
 | 
			
		||||
            size="small"
 | 
			
		||||
            :max-height="tableHeight"
 | 
			
		||||
            v-loading="loading"
 | 
			
		||||
            element-loading-text="查询中..."
 | 
			
		||||
            :empty-text="emptyText"
 | 
			
		||||
            highlight-current-row
 | 
			
		||||
            stripe
 | 
			
		||||
            border
 | 
			
		||||
            class="mt5"
 | 
			
		||||
        >
 | 
			
		||||
            <el-table-column v-if="datas.length > 0 && table" type="selection" width="35" />
 | 
			
		||||
 | 
			
		||||
            <template v-for="(item, index) in columns">
 | 
			
		||||
                <el-table-column
 | 
			
		||||
                    min-width="100"
 | 
			
		||||
                    :width="DbInst.flexColumnWidth(item.columnName, datas)"
 | 
			
		||||
                    align="center"
 | 
			
		||||
                    v-if="item.show"
 | 
			
		||||
                    :key="index"
 | 
			
		||||
                    :prop="item.columnName"
 | 
			
		||||
                    :label="item.columnName"
 | 
			
		||||
                    show-overflow-tooltip
 | 
			
		||||
                    :sortable="sortable"
 | 
			
		||||
    <div class="db-table-data mt5" :style="{ height: `${tableHeight}px` }">
 | 
			
		||||
        <el-auto-resizer>
 | 
			
		||||
            <template #default="{ height, width }">
 | 
			
		||||
                <el-table-v2
 | 
			
		||||
                    ref="tableRef"
 | 
			
		||||
                    :header-height="32"
 | 
			
		||||
                    :row-height="32"
 | 
			
		||||
                    :row-class="rowClass"
 | 
			
		||||
                    :columns="state.columns"
 | 
			
		||||
                    :data="datas"
 | 
			
		||||
                    :width="width"
 | 
			
		||||
                    :height="height"
 | 
			
		||||
                    fixed
 | 
			
		||||
                    :row-event-handlers="rowEventHandlers"
 | 
			
		||||
                >
 | 
			
		||||
                    <template #header v-if="showColumnTip">
 | 
			
		||||
                        <el-tooltip :show-after="500" raw-content placement="top">
 | 
			
		||||
                            <template #content> {{ getColumnTip(item) }} </template>
 | 
			
		||||
                            {{ item.columnName }}
 | 
			
		||||
                        </el-tooltip>
 | 
			
		||||
                    <template #header="{ columns }">
 | 
			
		||||
                        <div v-for="(column, i) in columns" :key="i">
 | 
			
		||||
                            <div
 | 
			
		||||
                                :style="{
 | 
			
		||||
                                    width: `${column.width}px`,
 | 
			
		||||
                                    height: '100%',
 | 
			
		||||
                                    lineHeight: '32px',
 | 
			
		||||
                                    textAlign: 'center',
 | 
			
		||||
                                    borderRight: 'var(--el-table-border)',
 | 
			
		||||
                                }"
 | 
			
		||||
                            >
 | 
			
		||||
                                <!-- 行号列表头 -->
 | 
			
		||||
                                <div v-if="column.key == rowNoColumn.key || !showColumnTip">
 | 
			
		||||
                                    <el-text tag="b"> {{ column.title }} </el-text>
 | 
			
		||||
                                </div>
 | 
			
		||||
 | 
			
		||||
                                <div v-else @contextmenu="headerContextmenuClick($event, column)">
 | 
			
		||||
                                    <div v-if="showColumnTip" @mouseover="column.showSetting = true" @mouseleave="column.showSetting = false">
 | 
			
		||||
                                        <el-tooltip :show-after="500" raw-content placement="top">
 | 
			
		||||
                                            <template #content> {{ getColumnTip(column) }} </template>
 | 
			
		||||
                                            <el-text tag="b" style="cursor: pointer"> {{ column.title }} </el-text>
 | 
			
		||||
                                        </el-tooltip>
 | 
			
		||||
 | 
			
		||||
                                        <span>
 | 
			
		||||
                                            <SvgIcon
 | 
			
		||||
                                                color="var(--el-color-primary)"
 | 
			
		||||
                                                v-if="column.title == nowSortColumn?.columnName"
 | 
			
		||||
                                                :name="nowSortColumn?.order == 'asc' ? 'top' : 'bottom'"
 | 
			
		||||
                                            ></SvgIcon>
 | 
			
		||||
                                        </span>
 | 
			
		||||
                                    </div>
 | 
			
		||||
 | 
			
		||||
                                    <div v-else>
 | 
			
		||||
                                        <el-text tag="b" style="cursor: pointer"> {{ column.title }} </el-text>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
 | 
			
		||||
                    <template #cell="{ rowData, column, rowIndex, columnIndex }">
 | 
			
		||||
                        <div style="width: 100%; height: 100%; line-height: 32px">
 | 
			
		||||
                            <!-- 行号列 -->
 | 
			
		||||
                            <div v-if="column.key == 'tableDataRowNo'">
 | 
			
		||||
                                <el-text tag="b" size="small">
 | 
			
		||||
                                    {{ rowIndex + 1 }}
 | 
			
		||||
                                </el-text>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <!-- 数据列 -->
 | 
			
		||||
                            <div v-else @dblclick="onEnterEditMode($event.target, rowData, column, rowIndex, columnIndex)">
 | 
			
		||||
                                <div v-if="canEdit(rowIndex, columnIndex)">
 | 
			
		||||
                                    <el-input
 | 
			
		||||
                                        :ref="(el: any) => el?.focus()"
 | 
			
		||||
                                        @blur="onExitEditMode(rowData, column, rowIndex)"
 | 
			
		||||
                                        class="w100"
 | 
			
		||||
                                        input-style="text-align: center"
 | 
			
		||||
                                        size="small"
 | 
			
		||||
                                        v-model="rowData[column.dataKey!]"
 | 
			
		||||
                                    ></el-input>
 | 
			
		||||
                                </div>
 | 
			
		||||
 | 
			
		||||
                                <div v-else :class="isUpdated(rowIndex, column.dataKey) ? 'update_field_active' : ''">
 | 
			
		||||
                                    <el-text :title="rowData[column.dataKey!]" size="small" truncated>
 | 
			
		||||
                                        {{ rowData[column.dataKey!] }}
 | 
			
		||||
                                    </el-text>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </template>
 | 
			
		||||
 | 
			
		||||
                    <template v-if="loading" #overlay>
 | 
			
		||||
                        <div class="el-loading-mask" style="display: flex; align-items: center; justify-content: center">
 | 
			
		||||
                            <SvgIcon name="loading" color="var(--el-color-primary)" :size="26" />
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </template>
 | 
			
		||||
 | 
			
		||||
                    <template #empty>
 | 
			
		||||
                        <div style="text-align: center">
 | 
			
		||||
                            <el-empty :description="state.emptyText" :image-size="100" />
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-v2>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-table>
 | 
			
		||||
        </el-auto-resizer>
 | 
			
		||||
 | 
			
		||||
        <el-dialog @close="state.genSqlDialog.visible = false" v-model="state.genSqlDialog.visible" :title="state.genSqlDialog.title" width="1000px">
 | 
			
		||||
            <el-input v-model="state.genSqlDialog.sql" type="textarea" rows="20" />
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <contextmenu :dropdown="state.contextmenu.dropdown" :items="state.contextmenu.items" ref="headerContextmenuRef" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { onMounted, watch, reactive, toRefs } from 'vue';
 | 
			
		||||
import { DbInst, UpdateFieldsMeta, FieldsMeta } from '@/views/ops/db/db';
 | 
			
		||||
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 SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(['sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField']);
 | 
			
		||||
const emits = defineEmits(['dataDelete', 'sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField']);
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    dbId: {
 | 
			
		||||
@@ -87,50 +158,348 @@ const props = defineProps({
 | 
			
		||||
        default: false,
 | 
			
		||||
    },
 | 
			
		||||
    height: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        default: '600',
 | 
			
		||||
        type: Number,
 | 
			
		||||
        default: 600,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const headerContextmenuRef = ref();
 | 
			
		||||
const tableRef = ref();
 | 
			
		||||
 | 
			
		||||
const cmHeaderAsc = new ContextmenuItem('asc', '升序').withIcon('top').withOnClick((data: any) => {
 | 
			
		||||
    onTableSortChange({ columnName: data.dataKey, order: 'asc' });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const cmHeaderDesc = new ContextmenuItem('desc', '降序').withIcon('bottom').withOnClick((data: any) => {
 | 
			
		||||
    onTableSortChange({ columnName: data.dataKey, order: 'desc' });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const cmDataDel = new ContextmenuItem('desc', '删除')
 | 
			
		||||
    .withIcon('delete')
 | 
			
		||||
    .withOnClick(() => onDeleteData())
 | 
			
		||||
    .withHideFunc(() => {
 | 
			
		||||
        return state.table == '';
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
const cmDataGenInsertSql = new ContextmenuItem('genInsertSql', 'Insert SQL')
 | 
			
		||||
    .withIcon('document')
 | 
			
		||||
    .withOnClick(() => onGenerateInsertSql())
 | 
			
		||||
    .withHideFunc(() => {
 | 
			
		||||
        return state.table == '';
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
const cmDataGenJson = new ContextmenuItem('genJson', '生成JSON').withIcon('document').withOnClick(() => onGenerateJson());
 | 
			
		||||
 | 
			
		||||
class NowUpdateCell {
 | 
			
		||||
    rowIndex: number;
 | 
			
		||||
    colIndex: number;
 | 
			
		||||
    oldValue: any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class UpdatedRow {
 | 
			
		||||
    /**
 | 
			
		||||
     * 主键值
 | 
			
		||||
     */
 | 
			
		||||
    primaryValue: any;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 行数据
 | 
			
		||||
     */
 | 
			
		||||
    rowData: any;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 修改到的列信息, columnName -> tablecelldata
 | 
			
		||||
     */
 | 
			
		||||
    columnsMap: Map<string, TableCellData> = new Map();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class TableCellData {
 | 
			
		||||
    /**
 | 
			
		||||
     * 旧值
 | 
			
		||||
     */
 | 
			
		||||
    oldValue: any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let nowSortColumn = null as any;
 | 
			
		||||
 | 
			
		||||
// 当前正在更新的单元格
 | 
			
		||||
let nowUpdateCell: NowUpdateCell = null as any;
 | 
			
		||||
 | 
			
		||||
// 选中的数据, key->rowIndex  value->primaryKeyValue
 | 
			
		||||
const selectionRowsMap: Map<number, any> = new Map();
 | 
			
		||||
 | 
			
		||||
// 更新单元格  key-> rowIndex  value -> 更新行
 | 
			
		||||
const cellUpdateMap: Map<number, UpdatedRow> = new Map();
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    dbId: 0, // 当前选中操作的数据库实例
 | 
			
		||||
    dbType: '',
 | 
			
		||||
    db: '', // 数据库名
 | 
			
		||||
    table: '', // 当前的表名
 | 
			
		||||
    datas: [],
 | 
			
		||||
    columns: [],
 | 
			
		||||
    columns: [] as any,
 | 
			
		||||
    sortable: false,
 | 
			
		||||
    loading: false,
 | 
			
		||||
    selectionDatas: [] as any,
 | 
			
		||||
    showColumnTip: false,
 | 
			
		||||
    tableHeight: '600',
 | 
			
		||||
    tableHeight: 600,
 | 
			
		||||
    emptyText: '',
 | 
			
		||||
    updatedFields: [] as UpdateFieldsMeta[], // 各个tab表被修改的字段信息
 | 
			
		||||
 | 
			
		||||
    contextmenu: {
 | 
			
		||||
        dropdown: {
 | 
			
		||||
            x: 0,
 | 
			
		||||
            y: 0,
 | 
			
		||||
        },
 | 
			
		||||
        items: [] as ContextmenuItem[],
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    genSqlDialog: {
 | 
			
		||||
        title: 'SQL',
 | 
			
		||||
        visible: false,
 | 
			
		||||
        sql: '',
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { tableHeight, datas } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
watch(props, (newValue: any) => {
 | 
			
		||||
    setState(newValue);
 | 
			
		||||
});
 | 
			
		||||
/**
 | 
			
		||||
 * 行号字段列
 | 
			
		||||
 */
 | 
			
		||||
const rowNoColumn = {
 | 
			
		||||
    title: 'No.',
 | 
			
		||||
    key: 'tableDataRowNo',
 | 
			
		||||
    dataKey: 'tableDataRowNo',
 | 
			
		||||
    width: 45,
 | 
			
		||||
    fixed: true,
 | 
			
		||||
    align: 'center',
 | 
			
		||||
    headerClass: 'table-data-cell',
 | 
			
		||||
    class: 'table-data-cell',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.data,
 | 
			
		||||
    (newValue: any) => {
 | 
			
		||||
        setTableData(newValue);
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.columns,
 | 
			
		||||
    (newValue: any) => {
 | 
			
		||||
        setTableColumns(newValue);
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        deep: true,
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.table,
 | 
			
		||||
    (newValue: any) => {
 | 
			
		||||
        state.table = newValue;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.height,
 | 
			
		||||
    (newValue: any) => {
 | 
			
		||||
        state.tableHeight = newValue;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.loading,
 | 
			
		||||
    (newValue: any) => {
 | 
			
		||||
        state.loading = newValue;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
    console.log('in DbTable mounted');
 | 
			
		||||
    setState(props);
 | 
			
		||||
});
 | 
			
		||||
    state.tableHeight = props.height;
 | 
			
		||||
    state.sortable = props.sortable as any;
 | 
			
		||||
    state.loading = props.loading;
 | 
			
		||||
    state.showColumnTip = props.showColumnTip;
 | 
			
		||||
    state.emptyText = props.emptyText;
 | 
			
		||||
 | 
			
		||||
const setState = (props: any) => {
 | 
			
		||||
    state.dbId = props.dbId;
 | 
			
		||||
    state.dbType = props.dbType;
 | 
			
		||||
    state.db = props.db;
 | 
			
		||||
    state.table = props.table;
 | 
			
		||||
    state.datas = props.data;
 | 
			
		||||
    state.tableHeight = props.height;
 | 
			
		||||
    state.sortable = props.sortable;
 | 
			
		||||
    state.loading = props.loading;
 | 
			
		||||
    state.columns = props.columns;
 | 
			
		||||
    state.showColumnTip = props.showColumnTip;
 | 
			
		||||
    state.emptyText = props.emptyText;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const setTableData = (datas: any) => {
 | 
			
		||||
    console.log('set table datas', props);
 | 
			
		||||
    tableRef.value.scrollTo({ scrollLeft: 0, scrollTop: 0 });
 | 
			
		||||
    selectionRowsMap.clear();
 | 
			
		||||
    cellUpdateMap.clear();
 | 
			
		||||
    state.datas = datas;
 | 
			
		||||
    setTableColumns(props.columns);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const setTableColumns = (columns: any) => {
 | 
			
		||||
    state.columns = columns.map((x: any) => {
 | 
			
		||||
        const columnName = x.columnName;
 | 
			
		||||
        return {
 | 
			
		||||
            ...x,
 | 
			
		||||
            key: columnName,
 | 
			
		||||
            dataKey: columnName,
 | 
			
		||||
            width: DbInst.flexColumnWidth(columnName, state.datas),
 | 
			
		||||
            title: columnName,
 | 
			
		||||
            align: 'center',
 | 
			
		||||
            headerClass: 'table-data-cell',
 | 
			
		||||
            class: 'table-data-cell',
 | 
			
		||||
            sortable: true,
 | 
			
		||||
            hidden: !x.show,
 | 
			
		||||
        };
 | 
			
		||||
    });
 | 
			
		||||
    state.columns.unshift(rowNoColumn);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 当前单元格是否允许编辑
 | 
			
		||||
 * @param rowIndex ri
 | 
			
		||||
 * @param colIndex ci
 | 
			
		||||
 */
 | 
			
		||||
const canEdit = (rowIndex: number, colIndex: number) => {
 | 
			
		||||
    return state.table && nowUpdateCell && nowUpdateCell.rowIndex == rowIndex && nowUpdateCell.colIndex == colIndex;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const isUpdated = (rowIndex: number, columnName: string) => {
 | 
			
		||||
    return cellUpdateMap.get(rowIndex)?.columnsMap.get(columnName);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 判断当前行是否被选中
 | 
			
		||||
 * @param rowIndex
 | 
			
		||||
 */
 | 
			
		||||
const isSelection = (rowIndex: number): boolean => {
 | 
			
		||||
    return selectionRowsMap.get(rowIndex);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 选中指定行
 | 
			
		||||
 * @param rowIndex
 | 
			
		||||
 * @param rowData
 | 
			
		||||
 * @param isMultiple 是否允许多选
 | 
			
		||||
 */
 | 
			
		||||
const selectionRow = (rowIndex: number, rowData: any, isMultiple = false) => {
 | 
			
		||||
    if (isMultiple) {
 | 
			
		||||
        // 如果重复点击,则取消改选中数据
 | 
			
		||||
        if (selectionRowsMap.get(rowIndex)) {
 | 
			
		||||
            selectionRowsMap.delete(rowIndex);
 | 
			
		||||
            triggerRefresh();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        selectionRowsMap.clear();
 | 
			
		||||
    }
 | 
			
		||||
    selectionRowsMap.set(rowIndex, rowData);
 | 
			
		||||
    triggerRefresh();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 行事件处理
 | 
			
		||||
 */
 | 
			
		||||
const rowEventHandlers = {
 | 
			
		||||
    onClick: (e: any) => {
 | 
			
		||||
        const event = e.event;
 | 
			
		||||
        const rowIndex = e.rowIndex;
 | 
			
		||||
        const rowData = e.rowData;
 | 
			
		||||
        // 按住ctrl点击,则新建标签页打开, metaKey对应mac command键
 | 
			
		||||
        if (event.ctrlKey || event.metaKey) {
 | 
			
		||||
            selectionRow(rowIndex, rowData, true);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        selectionRow(rowIndex, rowData);
 | 
			
		||||
    },
 | 
			
		||||
    onContextmenu: (e: any) => {
 | 
			
		||||
        dataContextmenuClick(e.event, e.rowIndex, e.rowData);
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const headerContextmenuClick = (event: any, data: any) => {
 | 
			
		||||
    event.preventDefault(); // 阻止默认的右击菜单行为
 | 
			
		||||
 | 
			
		||||
    const { clientX, clientY } = event;
 | 
			
		||||
    state.contextmenu.dropdown.x = clientX;
 | 
			
		||||
    state.contextmenu.dropdown.y = clientY;
 | 
			
		||||
    state.contextmenu.items = [cmHeaderAsc, cmHeaderDesc];
 | 
			
		||||
    headerContextmenuRef.value.openContextmenu(data);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const dataContextmenuClick = (event: any, rowIndex: number, data: any) => {
 | 
			
		||||
    event.preventDefault(); // 阻止默认的右击菜单行为
 | 
			
		||||
 | 
			
		||||
    // 当前行未选中,则单行选中该行
 | 
			
		||||
    if (!isSelection(rowIndex)) {
 | 
			
		||||
        selectionRow(rowIndex, data);
 | 
			
		||||
    }
 | 
			
		||||
    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) => {
 | 
			
		||||
@@ -138,184 +507,127 @@ const getColumnTip = (column: any) => {
 | 
			
		||||
    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 onTableSortChange = async (sort: any) => {
 | 
			
		||||
    if (!sort.prop) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    nowSortColumn = sort;
 | 
			
		||||
    cancelUpdateFields();
 | 
			
		||||
    emits('sortChange', sort);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onDataSelectionChange = (datas: []) => {
 | 
			
		||||
    state.selectionDatas = datas;
 | 
			
		||||
    emits('selectionChange', datas);
 | 
			
		||||
/**
 | 
			
		||||
 * 执行删除数据事件
 | 
			
		||||
 */
 | 
			
		||||
const onDeleteData = async () => {
 | 
			
		||||
    const deleteDatas = Array.from(selectionRowsMap.values());
 | 
			
		||||
    const db = state.db;
 | 
			
		||||
    const dbInst = getNowDbInst();
 | 
			
		||||
    dbInst.promptExeSql(db, await dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas as any), null, () => {
 | 
			
		||||
        emits('dataDelete', deleteDatas);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 监听单元格点击事件
 | 
			
		||||
const cellClick = (row: any, column: any, cell: any) => {
 | 
			
		||||
    const property = column.property;
 | 
			
		||||
    // 如果当前操作的表名不存在 或者 当前列的property不存在(如多选框),则不允许修改当前单元格内容
 | 
			
		||||
    if (!state.table || !property) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    let div: HTMLElement = cell.children[0];
 | 
			
		||||
    if (div && div.tagName === 'DIV') {
 | 
			
		||||
        // 转为字符串比较,可能存在数字等
 | 
			
		||||
        let text = (row[property] || row[property] == 0 ? row[property] : '') + '';
 | 
			
		||||
        let input = document.createElement('input');
 | 
			
		||||
        input.setAttribute('value', text);
 | 
			
		||||
        // 将表格width也赋值于输入框,避免输入框长度超过表格长度
 | 
			
		||||
        input.setAttribute('style', 'height:23px;text-align:center;border:none;' + div.getAttribute('style'));
 | 
			
		||||
        cell.replaceChildren(input);
 | 
			
		||||
        input.focus();
 | 
			
		||||
        input.addEventListener('blur', async () => {
 | 
			
		||||
            row[property] = input.value;
 | 
			
		||||
            cell.replaceChildren(div);
 | 
			
		||||
            if (input.value !== text) {
 | 
			
		||||
                let currentUpdatedFields = state.updatedFields;
 | 
			
		||||
                const dbInst = getNowDbInst();
 | 
			
		||||
                // 主键
 | 
			
		||||
                const primaryKey = await dbInst.loadTableColumn(state.db, state.table);
 | 
			
		||||
                const primaryKeyValue = row[primaryKey.columnName];
 | 
			
		||||
                // 更新字段列信息
 | 
			
		||||
                const updateColumn = await dbInst.loadTableColumn(state.db, state.table, property);
 | 
			
		||||
                const newField = {
 | 
			
		||||
                    div,
 | 
			
		||||
                    row,
 | 
			
		||||
                    fieldName: property,
 | 
			
		||||
                    fieldType: updateColumn.columnType,
 | 
			
		||||
                    oldValue: text,
 | 
			
		||||
                    newValue: input.value,
 | 
			
		||||
                } as FieldsMeta;
 | 
			
		||||
 | 
			
		||||
                // 被修改的字段
 | 
			
		||||
                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],
 | 
			
		||||
                    };
 | 
			
		||||
                } else {
 | 
			
		||||
                    hasKey = true;
 | 
			
		||||
                    let hasField = primaryKeyFields[0].fields.some((a) => {
 | 
			
		||||
                        if (a.fieldName === newField.fieldName) {
 | 
			
		||||
                            a.newValue = newField.newValue;
 | 
			
		||||
                        }
 | 
			
		||||
                        return a.fieldName === newField.fieldName;
 | 
			
		||||
                    });
 | 
			
		||||
                    if (!hasField) {
 | 
			
		||||
                        primaryKeyFields[0].fields.push(newField);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                let fields = primaryKeyFields[0].fields;
 | 
			
		||||
 | 
			
		||||
                const fieldsParam = fields.filter((a) => {
 | 
			
		||||
                    if (a.fieldName === column.property) {
 | 
			
		||||
                        a.newValue = input.value;
 | 
			
		||||
                    }
 | 
			
		||||
                    return a.fieldName === column.property;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                const field = (fieldsParam.length > 0 && fieldsParam[0]) || ({} as FieldsMeta);
 | 
			
		||||
                if (field.oldValue === input.value) {
 | 
			
		||||
                    // 新值=旧值
 | 
			
		||||
                    // 删除数据
 | 
			
		||||
                    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);
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    delIndex.forEach((i) => delete currentUpdatedFields[i]);
 | 
			
		||||
                    currentUpdatedFields = currentUpdatedFields.filter((a) => a);
 | 
			
		||||
                } else {
 | 
			
		||||
                    // 新增数据
 | 
			
		||||
                    div.classList.add('update_field_active');
 | 
			
		||||
                    if (hasKey) {
 | 
			
		||||
                        currentUpdatedFields.forEach((value, index, array) => {
 | 
			
		||||
                            if (value.primaryKey === primaryKeyValue) {
 | 
			
		||||
                                array[index].fields = fields;
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                    } else {
 | 
			
		||||
                        currentUpdatedFields.push({
 | 
			
		||||
                            primaryKey: primaryKeyValue,
 | 
			
		||||
                            primaryKeyName: primaryKey.columnName,
 | 
			
		||||
                            primaryKeyType: primaryKey.columnType,
 | 
			
		||||
                            fields,
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                state.updatedFields = currentUpdatedFields;
 | 
			
		||||
                changeUpdatedField();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
const onGenerateInsertSql = async () => {
 | 
			
		||||
    const selectionDatas = Array.from(selectionRowsMap.values());
 | 
			
		||||
    state.genSqlDialog.sql = await getNowDbInst().genInsertSql(state.db, state.table, selectionDatas);
 | 
			
		||||
    state.genSqlDialog.title = 'SQL';
 | 
			
		||||
    state.genSqlDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const submitUpdateFields = () => {
 | 
			
		||||
    const dbInst = DbInst.getInst(state.dbId);
 | 
			
		||||
    let currentUpdatedFields = state.updatedFields;
 | 
			
		||||
    if (currentUpdatedFields.length <= 0) {
 | 
			
		||||
const onGenerateJson = async () => {
 | 
			
		||||
    const selectionDatas = Array.from(selectionRowsMap.values());
 | 
			
		||||
    // 按列字段重新排序对象key
 | 
			
		||||
    const jsonObj = [];
 | 
			
		||||
    for (let selectionData of selectionDatas) {
 | 
			
		||||
        let obj = {};
 | 
			
		||||
        for (let column of state.columns) {
 | 
			
		||||
            obj[column.title] = selectionData[column.dataKey];
 | 
			
		||||
        }
 | 
			
		||||
        jsonObj.push(obj);
 | 
			
		||||
    }
 | 
			
		||||
    state.genSqlDialog.sql = JSON.stringify(jsonObj, null, 4);
 | 
			
		||||
    state.genSqlDialog.title = 'JSON';
 | 
			
		||||
    state.genSqlDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const submitUpdateFields = async () => {
 | 
			
		||||
    const dbInst = getNowDbInst();
 | 
			
		||||
    if (cellUpdateMap.size == 0) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const db = state.db;
 | 
			
		||||
    let res = '';
 | 
			
		||||
    let divs: HTMLElement[] = [];
 | 
			
		||||
    currentUpdatedFields.forEach((a) => {
 | 
			
		||||
 | 
			
		||||
    for (let updateRow of cellUpdateMap.values()) {
 | 
			
		||||
        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)},`;
 | 
			
		||||
            // 如果修改的字段是主键
 | 
			
		||||
            if (f.fieldName === primaryKeyName) {
 | 
			
		||||
                primaryKey = f.oldValue;
 | 
			
		||||
        const rowData = updateRow.rowData;
 | 
			
		||||
        // 主键列信息
 | 
			
		||||
        const primaryKey = await dbInst.loadTableColumn(db, state.table);
 | 
			
		||||
        let primaryKeyType = primaryKey.columnType;
 | 
			
		||||
        let primaryKeyName = primaryKey.columnName;
 | 
			
		||||
        let primaryKeyValue = rowData[primaryKeyName];
 | 
			
		||||
 | 
			
		||||
        for (let k of updateRow.columnsMap.keys()) {
 | 
			
		||||
            const v = updateRow.columnsMap.get(k);
 | 
			
		||||
            if (!v) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            divs.push(f.div);
 | 
			
		||||
        });
 | 
			
		||||
            // 更新字段列信息
 | 
			
		||||
            const updateColumn = await dbInst.loadTableColumn(db, state.table, k);
 | 
			
		||||
            sql += ` ${dbInst.wrapName(k)} = ${DbInst.wrapColumnValue(updateColumn.columnType, rowData[k])},`;
 | 
			
		||||
 | 
			
		||||
            // 如果修改的字段是主键
 | 
			
		||||
            if (k === primaryKeyName) {
 | 
			
		||||
                primaryKeyValue = v.oldValue;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        sql = sql.substring(0, sql.length - 1);
 | 
			
		||||
        sql += ` WHERE ${dbInst.wrapName(primaryKeyName)} = ${DbInst.wrapColumnValue(primaryKeyType, primaryKey)} ;`;
 | 
			
		||||
        sql += ` WHERE ${dbInst.wrapName(primaryKeyName)} = ${DbInst.wrapColumnValue(primaryKeyType, primaryKeyValue)} ;`;
 | 
			
		||||
        res += sql;
 | 
			
		||||
    });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dbInst.promptExeSql(
 | 
			
		||||
        db,
 | 
			
		||||
        res,
 | 
			
		||||
        () => {},
 | 
			
		||||
        () => {
 | 
			
		||||
            currentUpdatedFields = [];
 | 
			
		||||
            divs.forEach((a) => {
 | 
			
		||||
                a.classList.remove('update_field_active');
 | 
			
		||||
            });
 | 
			
		||||
            state.updatedFields = [];
 | 
			
		||||
            triggerRefresh();
 | 
			
		||||
            cellUpdateMap.clear();
 | 
			
		||||
            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;
 | 
			
		||||
    const updateRows = cellUpdateMap.values();
 | 
			
		||||
    // 恢复原值
 | 
			
		||||
    for (let updateRow of updateRows) {
 | 
			
		||||
        const rowData = updateRow.rowData;
 | 
			
		||||
        updateRow.columnsMap.forEach((v: TableCellData, k: string) => {
 | 
			
		||||
            rowData[k] = v.oldValue;
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
    state.updatedFields = [];
 | 
			
		||||
    }
 | 
			
		||||
    cellUpdateMap.clear();
 | 
			
		||||
    changeUpdatedField();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const changeUpdatedField = () => {
 | 
			
		||||
    emits('changeUpdatedField', state.updatedFields);
 | 
			
		||||
    emits('changeUpdatedField', cellUpdateMap);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getNowDbInst = () => {
 | 
			
		||||
@@ -328,8 +640,22 @@ defineExpose({
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.update_field_active {
 | 
			
		||||
    background-color: var(--el-color-success);
 | 
			
		||||
<style>
 | 
			
		||||
.db-table-data {
 | 
			
		||||
    .table-data-cell {
 | 
			
		||||
        padding: 0 2px;
 | 
			
		||||
        font-size: 12px;
 | 
			
		||||
        border-right: var(--el-table-border);
 | 
			
		||||
    }
 | 
			
		||||
    .data-selection {
 | 
			
		||||
        background-color: var(--el-color-success-light-8);
 | 
			
		||||
    }
 | 
			
		||||
    .data-spacing {
 | 
			
		||||
        background-color: var(--el-fill-color-lighter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .update_field_active {
 | 
			
		||||
        background-color: var(--el-color-success);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -30,19 +30,11 @@
 | 
			
		||||
                <el-link @click="onShowAddDataDialog()" type="primary" icon="plus" :underline="false"></el-link>
 | 
			
		||||
                <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
                <el-link @click="onDeleteData()" type="danger" icon="delete" :underline="false"></el-link>
 | 
			
		||||
                <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
                <el-tooltip :show-after="500" class="box-item" effect="dark" content="commit" placement="top">
 | 
			
		||||
                    <el-link @click="onCommit()" type="success" icon="CircleCheck" :underline="false"> </el-link>
 | 
			
		||||
                </el-tooltip>
 | 
			
		||||
                <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
                <el-tooltip :show-after="500" class="box-item" effect="dark" content="生成insert sql" placement="top">
 | 
			
		||||
                    <el-link @click="onGenerateInsertSql()" type="success" :underline="false">gi</el-link>
 | 
			
		||||
                </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>
 | 
			
		||||
@@ -118,6 +110,7 @@
 | 
			
		||||
            @sort-change="(sort: any) => onTableSortChange(sort)"
 | 
			
		||||
            @selection-change="onDataSelectionChange"
 | 
			
		||||
            @change-updated-field="changeUpdatedField"
 | 
			
		||||
            @data-delete="onRefresh"
 | 
			
		||||
        ></db-table-data>
 | 
			
		||||
 | 
			
		||||
        <el-row type="flex" class="mt5" justify="center">
 | 
			
		||||
@@ -187,10 +180,6 @@
 | 
			
		||||
                </span>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <el-dialog @close="state.genSqlDialog.visible = false" v-model="state.genSqlDialog.visible" title="SQL" width="1000px">
 | 
			
		||||
            <el-input v-model="state.genSqlDialog.sql" type="textarea" rows="20" />
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -221,8 +210,8 @@ const props = defineProps({
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
    tableHeight: {
 | 
			
		||||
        type: [String],
 | 
			
		||||
        default: '600',
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        default: 600,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -258,11 +247,7 @@ const state = reactive({
 | 
			
		||||
        placeholder: '',
 | 
			
		||||
        visible: false,
 | 
			
		||||
    },
 | 
			
		||||
    genSqlDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
        sql: '',
 | 
			
		||||
    },
 | 
			
		||||
    tableHeight: '600',
 | 
			
		||||
    tableHeight: 600,
 | 
			
		||||
    hasUpdatedFileds: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -446,11 +431,8 @@ const onSelectByCondition = async () => {
 | 
			
		||||
 * 表排序字段变更
 | 
			
		||||
 */
 | 
			
		||||
const onTableSortChange = async (sort: any) => {
 | 
			
		||||
    if (!sort.prop) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    const sortType = sort.order == 'descending' ? 'DESC' : 'ASC';
 | 
			
		||||
    state.orderBy = `ORDER BY ${sort.prop} ${sortType}`;
 | 
			
		||||
    const sortType = sort.order == 'desc' ? 'DESC' : 'ASC';
 | 
			
		||||
    state.orderBy = `ORDER BY ${sort.columnName} ${sortType}`;
 | 
			
		||||
    await onRefresh();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -458,28 +440,9 @@ const onDataSelectionChange = (datas: []) => {
 | 
			
		||||
    state.selectionDatas = datas;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const changeUpdatedField = (updatedFields: []) => {
 | 
			
		||||
const changeUpdatedField = (updatedFields: any) => {
 | 
			
		||||
    // 如果存在要更新字段,则显示提交和取消按钮
 | 
			
		||||
    state.hasUpdatedFileds = updatedFields && updatedFields.length > 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 执行删除数据事件
 | 
			
		||||
 */
 | 
			
		||||
const onDeleteData = async () => {
 | 
			
		||||
    const deleteDatas = state.selectionDatas;
 | 
			
		||||
    isTrue(deleteDatas && deleteDatas.length > 0, '请先选择要删除的数据');
 | 
			
		||||
    const db = props.dbName;
 | 
			
		||||
    const dbInst = getNowDbInst();
 | 
			
		||||
    dbInst.promptExeSql(db, dbInst.genDeleteByPrimaryKeysSql(db, props.tableName, deleteDatas), null, () => {
 | 
			
		||||
        onRefresh();
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onGenerateInsertSql = async () => {
 | 
			
		||||
    isTrue(state.selectionDatas && state.selectionDatas.length > 0, '请先选择数据');
 | 
			
		||||
    state.genSqlDialog.sql = getNowDbInst().genInsertSql(props.dbName, props.tableName, state.selectionDatas);
 | 
			
		||||
    state.genSqlDialog.visible = true;
 | 
			
		||||
    state.hasUpdatedFileds = updatedFields && updatedFields.size > 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const submitUpdateFields = () => {
 | 
			
		||||
@@ -530,8 +493,4 @@ const addRow = async () => {
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.update_field_active {
 | 
			
		||||
    background-color: var(--el-color-success);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -232,11 +232,12 @@ export class DbInst {
 | 
			
		||||
     * @param table 表名
 | 
			
		||||
     * @param datas 要生成的数据
 | 
			
		||||
     */
 | 
			
		||||
    genInsertSql(dbName: string, table: string, datas: any[]): string {
 | 
			
		||||
    async genInsertSql(dbName: string, table: string, datas: any[]) {
 | 
			
		||||
        if (!datas) {
 | 
			
		||||
            return '';
 | 
			
		||||
        }
 | 
			
		||||
        const columns = this.getDb(dbName).getColumns(table);
 | 
			
		||||
 | 
			
		||||
        const columns = await this.loadColumns(dbName, table);
 | 
			
		||||
        const sqls = [];
 | 
			
		||||
        for (let data of datas) {
 | 
			
		||||
            let colNames = [];
 | 
			
		||||
@@ -256,8 +257,8 @@ export class DbInst {
 | 
			
		||||
     * @param table 表名
 | 
			
		||||
     * @param datas 要删除的记录
 | 
			
		||||
     */
 | 
			
		||||
    genDeleteByPrimaryKeysSql(db: string, table: string, datas: any[]) {
 | 
			
		||||
        const primaryKey = this.getDb(db).getColumn(table);
 | 
			
		||||
    async genDeleteByPrimaryKeysSql(db: string, table: string, datas: any[]) {
 | 
			
		||||
        const primaryKey = await this.loadTableColumn(db, table);
 | 
			
		||||
        const primaryKeyColumnName = primaryKey.columnName;
 | 
			
		||||
        const ids = datas.map((d: any) => `${DbInst.wrapColumnValue(primaryKey.columnType, d[primaryKeyColumnName])}`).join(',');
 | 
			
		||||
        return `DELETE FROM ${this.wrapName(table)} WHERE ${this.wrapName(primaryKeyColumnName)} IN (${ids})`;
 | 
			
		||||
@@ -389,7 +390,7 @@ export class DbInst {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 获取列名称的长度 加上排序图标长度
 | 
			
		||||
        const columnWidth: number = getTextWidth(prop) + 40;
 | 
			
		||||
        const columnWidth: number = getTextWidth(prop) + 23;
 | 
			
		||||
        // prop为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
 | 
			
		||||
        if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
 | 
			
		||||
            return columnWidth;
 | 
			
		||||
@@ -531,33 +532,6 @@ export class TabInfo {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 修改表字段所需数据 */
 | 
			
		||||
export type UpdateFieldsMeta = {
 | 
			
		||||
    // 主键值
 | 
			
		||||
    primaryKey: string;
 | 
			
		||||
    // 主键名
 | 
			
		||||
    primaryKeyName: string;
 | 
			
		||||
    // 主键类型
 | 
			
		||||
    primaryKeyType: string;
 | 
			
		||||
    // 新值
 | 
			
		||||
    fields: FieldsMeta[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type FieldsMeta = {
 | 
			
		||||
    // 字段所在div
 | 
			
		||||
    div: HTMLElement;
 | 
			
		||||
    // 字段名
 | 
			
		||||
    fieldName: string;
 | 
			
		||||
    // 字段所在的表格行数据
 | 
			
		||||
    row: any;
 | 
			
		||||
    // 字段类型
 | 
			
		||||
    fieldType: string;
 | 
			
		||||
    // 原值
 | 
			
		||||
    oldValue: string;
 | 
			
		||||
    // 新值
 | 
			
		||||
    newValue: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 注册数据库表、字段等信息提示
 | 
			
		||||
 *
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user