415 lines
10 KiB
Vue
415 lines
10 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref } from 'vue';
|
|
|
|
import { Delete, EditPen, Search } from '@element-plus/icons-vue';
|
|
import {
|
|
ElButton,
|
|
ElEmpty,
|
|
ElIcon,
|
|
ElInput,
|
|
ElTable,
|
|
ElTableColumn,
|
|
ElTag,
|
|
} from 'element-plus';
|
|
|
|
const props = defineProps<{
|
|
managedTables: any[];
|
|
saveTableDescription: (
|
|
tableId: number | string,
|
|
tableDesc: string,
|
|
) => Promise<any>;
|
|
sourceLabel: string;
|
|
sourceTables: any[];
|
|
sourceUnavailable?: boolean;
|
|
}>();
|
|
|
|
const emit = defineEmits<{
|
|
registerTables: [rows: any[]];
|
|
removeTables: [rows: any[]];
|
|
selectTable: [row: any];
|
|
}>();
|
|
|
|
const tableKeyword = ref('');
|
|
const selectedRows = ref<any[]>([]);
|
|
const editingTableId = ref<null | number | string>(null);
|
|
const editingTableDesc = ref('');
|
|
const savingTableId = ref<null | number | string>(null);
|
|
|
|
const managedTableMap = computed(
|
|
() => new Map(props.managedTables.map((item) => [item.tableName, item])),
|
|
);
|
|
|
|
function compareTableName(a?: string, b?: string) {
|
|
return String(a || '').localeCompare(String(b || ''), 'en', {
|
|
numeric: true,
|
|
sensitivity: 'base',
|
|
});
|
|
}
|
|
|
|
const tableRows = computed(() => {
|
|
const keyword = tableKeyword.value.trim().toLowerCase();
|
|
return props.sourceTables
|
|
.filter((item) => {
|
|
if (!keyword) return true;
|
|
return item.tableName?.toLowerCase().includes(keyword);
|
|
})
|
|
.map((item) => {
|
|
const managedTable = managedTableMap.value.get(item.tableName) || null;
|
|
return {
|
|
...item,
|
|
managedTable,
|
|
managedTableId: managedTable?.id || null,
|
|
managedStatus: managedTable ? 'MANAGED' : 'UNMANAGED',
|
|
};
|
|
})
|
|
.sort((a, b) => {
|
|
const managedDiff =
|
|
Number(Boolean(b.managedTable)) - Number(Boolean(a.managedTable));
|
|
if (managedDiff !== 0) {
|
|
return managedDiff;
|
|
}
|
|
return compareTableName(a.tableName, b.tableName);
|
|
});
|
|
});
|
|
|
|
const selectedManagedRows = computed(() =>
|
|
selectedRows.value.map((row) => row.managedTable).filter(Boolean),
|
|
);
|
|
|
|
const selectedPendingRows = computed(() =>
|
|
selectedRows.value.filter((row) => !row.managedTable),
|
|
);
|
|
|
|
const canBatchRegister = computed(() => selectedPendingRows.value.length > 0);
|
|
const canBatchRemove = computed(() => selectedManagedRows.value.length > 0);
|
|
|
|
function handleSelectionChange(rows: any[]) {
|
|
selectedRows.value = rows;
|
|
}
|
|
|
|
function handleBatchRegister() {
|
|
emit('registerTables', selectedPendingRows.value);
|
|
}
|
|
|
|
function handleBatchRemove() {
|
|
emit('removeTables', selectedManagedRows.value);
|
|
}
|
|
|
|
function handleOpenDetail(row: any) {
|
|
if (row.managedTable) {
|
|
emit('selectTable', row.managedTable);
|
|
}
|
|
}
|
|
|
|
function startEdit(row: any) {
|
|
if (!row?.managedTable) return;
|
|
editingTableId.value = row.managedTable.id;
|
|
editingTableDesc.value = row.managedTable.tableDesc || '';
|
|
}
|
|
|
|
function cancelEdit() {
|
|
editingTableId.value = null;
|
|
editingTableDesc.value = '';
|
|
}
|
|
|
|
async function handleSaveTableDescription(row: any) {
|
|
if (!row?.managedTable || savingTableId.value) return;
|
|
savingTableId.value = row.managedTable.id;
|
|
try {
|
|
await props.saveTableDescription(
|
|
row.managedTable.id,
|
|
editingTableDesc.value,
|
|
);
|
|
cancelEdit();
|
|
} finally {
|
|
savingTableId.value = null;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="table-list-view">
|
|
<div class="view-header">
|
|
<h3 class="view-title">{{ sourceLabel }}</h3>
|
|
</div>
|
|
|
|
<div class="view-toolbar">
|
|
<div class="table-search-row">
|
|
<ElInput v-model="tableKeyword" placeholder="搜索表" clearable>
|
|
<template #prefix>
|
|
<ElIcon><Search /></ElIcon>
|
|
</template>
|
|
</ElInput>
|
|
</div>
|
|
|
|
<div class="batch-actions">
|
|
<span v-if="selectedRows.length > 0" class="selection-text">
|
|
已选 {{ selectedRows.length }} 项
|
|
</span>
|
|
<ElButton
|
|
size="small"
|
|
:disabled="!canBatchRegister"
|
|
@click="handleBatchRegister"
|
|
>
|
|
批量接入
|
|
</ElButton>
|
|
<ElButton
|
|
size="small"
|
|
:disabled="!canBatchRemove"
|
|
@click="handleBatchRemove"
|
|
>
|
|
批量去除
|
|
</ElButton>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-scroll-region">
|
|
<ElEmpty
|
|
v-if="sourceTables.length === 0"
|
|
description="当前连接下还没有可浏览的表"
|
|
/>
|
|
<ElEmpty v-else-if="tableRows.length === 0" description="没有匹配的表" />
|
|
<ElTable
|
|
v-else
|
|
:data="tableRows"
|
|
height="100%"
|
|
size="small"
|
|
row-key="tableName"
|
|
class="flat-table"
|
|
@selection-change="handleSelectionChange"
|
|
>
|
|
<ElTableColumn type="selection" width="52" />
|
|
<ElTableColumn
|
|
label="名称"
|
|
min-width="360"
|
|
class-name="table-name-column"
|
|
>
|
|
<template #default="{ row }">
|
|
<div class="name-cell">
|
|
<span class="table-name-text">{{ row.tableName }}</span>
|
|
<template v-if="row.managedTable">
|
|
<template v-if="editingTableId === row.managedTable.id">
|
|
<ElInput
|
|
v-model="editingTableDesc"
|
|
size="small"
|
|
clearable
|
|
maxlength="200"
|
|
class="description-input"
|
|
/>
|
|
<div class="inline-actions">
|
|
<ElButton
|
|
link
|
|
type="primary"
|
|
size="small"
|
|
:loading="savingTableId === row.managedTable.id"
|
|
@click="handleSaveTableDescription(row)"
|
|
>
|
|
保存
|
|
</ElButton>
|
|
<ElButton
|
|
link
|
|
size="small"
|
|
:disabled="savingTableId === row.managedTable.id"
|
|
@click="cancelEdit"
|
|
>
|
|
取消
|
|
</ElButton>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
<span class="description-inline">{{
|
|
row.managedTable.tableDesc || ''
|
|
}}</span>
|
|
<ElButton
|
|
class="icon-action icon-action--edit"
|
|
link
|
|
size="small"
|
|
@click="startEdit(row)"
|
|
>
|
|
<ElIcon><EditPen /></ElIcon>
|
|
</ElButton>
|
|
</template>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
</ElTableColumn>
|
|
<ElTableColumn label="状态" width="100">
|
|
<template #default="{ row }">
|
|
<ElTag
|
|
size="small"
|
|
effect="plain"
|
|
:type="row.managedTable ? 'primary' : 'info'"
|
|
>
|
|
{{ row.managedTable ? '已接入' : '未接入' }}
|
|
</ElTag>
|
|
</template>
|
|
</ElTableColumn>
|
|
<ElTableColumn label="操作" width="180" fixed="right">
|
|
<template #default="{ row }">
|
|
<div class="row-actions">
|
|
<ElButton
|
|
v-if="row.managedTable"
|
|
link
|
|
type="primary"
|
|
size="small"
|
|
@click="handleOpenDetail(row)"
|
|
>
|
|
查看
|
|
</ElButton>
|
|
<ElButton
|
|
v-if="row.managedTable"
|
|
class="icon-action icon-action--danger"
|
|
link
|
|
size="small"
|
|
@click="emit('removeTables', [row.managedTable])"
|
|
>
|
|
<ElIcon><Delete /></ElIcon>
|
|
</ElButton>
|
|
<ElButton
|
|
v-else
|
|
link
|
|
type="primary"
|
|
size="small"
|
|
@click="emit('registerTables', [row])"
|
|
>
|
|
接入
|
|
</ElButton>
|
|
</div>
|
|
</template>
|
|
</ElTableColumn>
|
|
</ElTable>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.table-list-view {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
padding: 0 0 0 12px;
|
|
background: transparent;
|
|
}
|
|
|
|
.view-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
margin-bottom: 10px;
|
|
padding: 0 0 4px;
|
|
}
|
|
|
|
.view-title {
|
|
margin: 0;
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: hsl(var(--text-strong));
|
|
}
|
|
|
|
.view-toolbar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.table-search-row {
|
|
width: min(320px, 100%);
|
|
}
|
|
|
|
.table-search-row :deep(.el-input__wrapper) {
|
|
min-height: 36px;
|
|
}
|
|
|
|
.batch-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.selection-text {
|
|
font-size: 13px;
|
|
color: hsl(var(--text-muted));
|
|
}
|
|
|
|
.table-scroll-region {
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.flat-table {
|
|
--el-table-bg-color: transparent;
|
|
--el-table-tr-bg-color: transparent;
|
|
--el-table-header-bg-color: hsl(var(--surface-subtle) / 0.72);
|
|
--el-table-border-color: hsl(var(--table-row-border) / 0.7);
|
|
--el-table-current-row-bg-color: hsl(var(--table-row-hover));
|
|
--el-fill-color-blank: transparent;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
border-radius: 0;
|
|
}
|
|
|
|
.flat-table :deep(.table-name-column .cell) {
|
|
font-size: 14px;
|
|
font-weight: 400;
|
|
color: hsl(var(--text-strong));
|
|
}
|
|
|
|
.name-cell {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.table-name-text {
|
|
flex-shrink: 0;
|
|
color: hsl(var(--text-strong));
|
|
}
|
|
|
|
.description-inline {
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
color: hsl(var(--text-muted));
|
|
}
|
|
|
|
.description-input {
|
|
width: min(320px, 100%);
|
|
}
|
|
|
|
.row-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.inline-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.icon-action {
|
|
min-width: auto;
|
|
padding: 0 2px;
|
|
}
|
|
|
|
.icon-action :deep(.el-icon) {
|
|
font-size: 15px;
|
|
}
|
|
|
|
.icon-action--edit {
|
|
color: hsl(var(--primary));
|
|
}
|
|
|
|
.icon-action--danger {
|
|
color: hsl(var(--destructive));
|
|
}
|
|
</style>
|