|
|
|
|
<template>
|
|
|
|
|
<div class="list-table-v2">
|
|
|
|
|
<!-- Debug info -->
|
|
|
|
|
<div v-if="debug" class="debug-info">
|
|
|
|
|
<json-view :data="debugInfo" :deep="3" :virtual="true" :show-icon="true" :show-double-quotes="false" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Main table container with ResizeObserver for width tracking -->
|
|
|
|
|
<div ref="tableContainerRef" class="table-container" :style="tableContainerStyle">
|
|
|
|
|
<!-- Header row (fixed) -->
|
|
|
|
|
<div class="table-header" :style="{ height: `${resolvedHeaderHeight}px` }">
|
|
|
|
|
<div v-for="col in visibleColumns" :key="col.key" class="header-cell" :style="getColumnStyle(col)">
|
|
|
|
|
<slot :name="`header-${col.key}`">
|
|
|
|
|
<span class="header-cell-text" :style="getTextStyle(col)">{{ getHeaderText(col) }}</span>
|
|
|
|
|
</slot>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Scrollable body -->
|
|
|
|
|
<div ref="scrollBodyRef" class="table-body" :style="{ height: `${viewportHeight}px` }" @scroll="handleScroll">
|
|
|
|
|
<!-- Total height spacer -->
|
|
|
|
|
<div class="table-spacer" :style="{ height: `${virtualTotalHeight}px` }">
|
|
|
|
|
<!-- Visible rows -->
|
|
|
|
|
<div
|
|
|
|
|
v-for="row in visibleRows"
|
|
|
|
|
:key="row.index"
|
|
|
|
|
class="table-row"
|
|
|
|
|
:style="{
|
|
|
|
|
transform: `translateY(${row.offsetY}px)`,
|
|
|
|
|
height: `${row.height}px`,
|
|
|
|
|
}"
|
|
|
|
|
@click="handleRowClick(row.index)"
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
v-for="col in visibleColumns"
|
|
|
|
|
:key="col.key"
|
|
|
|
|
class="table-cell"
|
|
|
|
|
:style="getColumnStyle(col)"
|
|
|
|
|
@click="handleCellClick(row.index, col)"
|
|
|
|
|
>
|
|
|
|
|
<slot :name="col.key" :row="getRow(row.index)">
|
|
|
|
|
<span class="cell-text" :style="getTextStyle(col)">{{
|
|
|
|
|
getRow(row.index) ? formatCellValue(getRow(row.index)!, col) : "--"
|
|
|
|
|
}}</span>
|
|
|
|
|
</slot>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Pagination -->
|
|
|
|
|
<div v-if="page" class="my-pagination">
|
|
|
|
|
<el-pagination
|
|
|
|
|
:small="state.size.size == 'small'"
|
|
|
|
|
@size-change="handleSizeChange"
|
|
|
|
|
@current-change="handleCurrentChange"
|
|
|
|
|
:current-page="example.page"
|
|
|
|
|
:page-sizes="[10, 20, 50, 100, 200]"
|
|
|
|
|
:page-size="example.size"
|
|
|
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
:total="data.total"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script lang="tsx" setup generic="T">
|
|
|
|
|
import { ref, computed, onMounted, onUnmounted, toRef, StyleValue, CSSProperties } from "vue";
|
|
|
|
|
import { useStore } from "vuex";
|
|
|
|
|
import { useI18n } from "vue3-i18n";
|
|
|
|
|
import * as lodash from "lodash-es";
|
|
|
|
|
import type { ListTableColumn, PageResponse } from "./types";
|
|
|
|
|
import { usePretextColumnWidths } from "./usePretextColumnWidths";
|
|
|
|
|
import { resolveRowHeights } from "./usePretextRowHeights";
|
|
|
|
|
import { useVirtualRows } from "./useVirtualRows";
|
|
|
|
|
import { formatTimestampFromValue } from "../../../../plugs/composables";
|
|
|
|
|
import { match } from "ts-pattern";
|
|
|
|
|
import { JsonView } from "noob-mengyxu";
|
|
|
|
|
|
|
|
|
|
const DEFAULT_FONT = "14px sans-serif";
|
|
|
|
|
const DEFAULT_TEXT_MAX_WIDTH = 400;
|
|
|
|
|
|
|
|
|
|
const { t } = useI18n();
|
|
|
|
|
const { state } = useStore();
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Container refs and dimensions
|
|
|
|
|
// =============================================================================
|
|
|
|
|
const tableContainerRef = ref<HTMLElement | null>(null);
|
|
|
|
|
const scrollBodyRef = ref<HTMLElement | null>(null);
|
|
|
|
|
const containerWidth = ref(800);
|
|
|
|
|
const viewportHeight = ref(400);
|
|
|
|
|
let resizeObserver: ResizeObserver | null = null;
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Props - same interface as original list-table-v2.vue
|
|
|
|
|
// =============================================================================
|
|
|
|
|
interface Props {
|
|
|
|
|
data?: T[] | PageResponse<T>;
|
|
|
|
|
columns?: ListTableColumn<T>[];
|
|
|
|
|
page?: boolean;
|
|
|
|
|
height?: number | string;
|
|
|
|
|
minHeight?: number | string;
|
|
|
|
|
maxHeight?: number | string;
|
|
|
|
|
example?: { page?: number; size?: number };
|
|
|
|
|
rowKey?: string;
|
|
|
|
|
selectKey?: string;
|
|
|
|
|
treeProps?: any;
|
|
|
|
|
lazy?: boolean;
|
|
|
|
|
border?: boolean;
|
|
|
|
|
rowHeight?: number;
|
|
|
|
|
estimatedRowHeight?: number;
|
|
|
|
|
headerHeight?: number;
|
|
|
|
|
font?: string;
|
|
|
|
|
debug?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
|
|
|
data: () => [],
|
|
|
|
|
columns: () => [],
|
|
|
|
|
page: false,
|
|
|
|
|
height: undefined,
|
|
|
|
|
minHeight: 300,
|
|
|
|
|
maxHeight: undefined,
|
|
|
|
|
example: () => ({}),
|
|
|
|
|
rowKey: "id",
|
|
|
|
|
selectKey: undefined,
|
|
|
|
|
treeProps: undefined,
|
|
|
|
|
lazy: false,
|
|
|
|
|
border: false,
|
|
|
|
|
rowHeight: undefined,
|
|
|
|
|
estimatedRowHeight: undefined,
|
|
|
|
|
headerHeight: 44,
|
|
|
|
|
font: DEFAULT_FONT,
|
|
|
|
|
debug: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
(e: "query"): void;
|
|
|
|
|
(e: "selection-change", selection: any[]): void;
|
|
|
|
|
(e: "row-click", row: T): void;
|
|
|
|
|
(e: "cell-click", row: T, column: ListTableColumn<T>, ...args: any[]): void;
|
|
|
|
|
}>();
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Data and columns as computed refs for hooks
|
|
|
|
|
// =============================================================================
|
|
|
|
|
const pageData = computed<T[]>(() => {
|
|
|
|
|
if (!props.data) return [];
|
|
|
|
|
if (props.page && (props.data as PageResponse<T>).data) {
|
|
|
|
|
return (props.data as PageResponse<T>).data;
|
|
|
|
|
}
|
|
|
|
|
if (Array.isArray(props.data)) {
|
|
|
|
|
return props.data;
|
|
|
|
|
}
|
|
|
|
|
return [];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Helper function for template to safely access row data
|
|
|
|
|
function getRow(index: number): T | undefined {
|
|
|
|
|
return pageData.value[index];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const columnsRef = toRef(props, "columns");
|
|
|
|
|
|
|
|
|
|
// Pass computed pageData to hooks
|
|
|
|
|
const pageDataRef = toRef(pageData);
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Cell formatting
|
|
|
|
|
// =============================================================================
|
|
|
|
|
const getHeaderText = (col: ListTableColumn<T>): string => {
|
|
|
|
|
if (col.name) return col.name;
|
|
|
|
|
if (col.i18n) return t(col.i18n);
|
|
|
|
|
return col.key;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const formatCellValue = (row: T, col: ListTableColumn<T>): string => {
|
|
|
|
|
const value = (row as any)[col.dataKey || col.key];
|
|
|
|
|
|
|
|
|
|
if (col.dict) {
|
|
|
|
|
return formatterByDist(col.dict, value);
|
|
|
|
|
}
|
|
|
|
|
if (col.timestamp) {
|
|
|
|
|
if ((typeof value !== "string" && typeof value !== "number") || value === "") {
|
|
|
|
|
return "--";
|
|
|
|
|
}
|
|
|
|
|
return formatTimestampFromValue(value, col.timestamp);
|
|
|
|
|
}
|
|
|
|
|
if (col.filesize) {
|
|
|
|
|
return formatFileSize(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (value === undefined || value === null || value === "") {
|
|
|
|
|
return "--";
|
|
|
|
|
}
|
|
|
|
|
return String(value);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getValue = (value: any): string => {
|
|
|
|
|
if ((typeof value === "undefined" || value === null || value === "") && value !== 0) {
|
|
|
|
|
return "--";
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const formatFileSize = (value: any): string => {
|
|
|
|
|
const k = value / 1024;
|
|
|
|
|
if (k < 1) return value + "B";
|
|
|
|
|
const m = k / 1024;
|
|
|
|
|
if (m < 1) return k.toFixed(2) + "K";
|
|
|
|
|
const g = m / 1024;
|
|
|
|
|
if (g < 1) return m.toFixed(2) + "M";
|
|
|
|
|
return g.toFixed(2) + "G";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const formatterByDist = (dictKey: string, cellData: any): string => {
|
|
|
|
|
if (!dictKey) return getValue(cellData);
|
|
|
|
|
const mapping = (state as any).dict[dictKey];
|
|
|
|
|
if (mapping == null) return getValue(cellData);
|
|
|
|
|
return mapping[cellData] == null ? cellData : mapping[cellData];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Wire hooks
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
// Column width computation via pretext
|
|
|
|
|
const { computedConfigs, totalFlexBasis, columnWidths } = usePretextColumnWidths(
|
|
|
|
|
pageDataRef,
|
|
|
|
|
columnsRef,
|
|
|
|
|
containerWidth,
|
|
|
|
|
{
|
|
|
|
|
font: "14px Inter, sans-serif",
|
|
|
|
|
headerFont: "bold 14px Inter, sans-serif",
|
|
|
|
|
formatCellValue,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Row heights via pretext - use actual column widths from flexbox algorithm
|
|
|
|
|
const { rowHeights, cellHeights } = resolveRowHeights(
|
|
|
|
|
pageDataRef,
|
|
|
|
|
columnsRef,
|
|
|
|
|
columnWidths,
|
|
|
|
|
DEFAULT_TEXT_MAX_WIDTH,
|
|
|
|
|
formatCellValue,
|
|
|
|
|
{
|
|
|
|
|
font: props.font,
|
|
|
|
|
lineHeight: 20,
|
|
|
|
|
debug: props.debug,
|
|
|
|
|
fixedRowHeight: props.rowHeight,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Virtualizer (needs rowHeights as number[] and viewportHeight)
|
|
|
|
|
const virtualizer = useVirtualRows(rowHeights, viewportHeight, { overscan: 5 });
|
|
|
|
|
|
|
|
|
|
// Destructure for template auto-unwrap (Vue 3 auto-unwraps top-level refs)
|
|
|
|
|
const { visibleRows, totalHeight: virtualTotalHeight, onScroll } = virtualizer;
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Visible columns (non-fixed columns only for now - fixed columns in PR4)
|
|
|
|
|
// =============================================================================
|
|
|
|
|
const visibleColumns = computed(() => {
|
|
|
|
|
return props.columns.filter((col) => !col.fixed);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Computed styles
|
|
|
|
|
// =============================================================================
|
|
|
|
|
const tableContainerStyle = computed(() => {
|
|
|
|
|
const style: CSSProperties = {
|
|
|
|
|
display: "flex",
|
|
|
|
|
flexDirection: "column" as const,
|
|
|
|
|
width: "100%",
|
|
|
|
|
overflow: "hidden" as const,
|
|
|
|
|
};
|
|
|
|
|
if (props.height !== undefined) {
|
|
|
|
|
const h = String(props.height);
|
|
|
|
|
style.maxHeight = h.endsWith("px") ? h : `${h}px`;
|
|
|
|
|
}
|
|
|
|
|
// if (props.maxHeight !== undefined) {
|
|
|
|
|
// const mh = String(props.maxHeight);
|
|
|
|
|
// style.maxHeight = mh.endsWith("px") ? mh : `${mh}px`;
|
|
|
|
|
// }
|
|
|
|
|
return style;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Column styling
|
|
|
|
|
// =============================================================================
|
|
|
|
|
function getColumnStyle(col: ListTableColumn<T>): CSSProperties {
|
|
|
|
|
const config = computedConfigs.value.find((c) => c.key === col.key);
|
|
|
|
|
const explicitWidth = col.width !== undefined && col.width !== "auto" ? Number(col.width) : undefined;
|
|
|
|
|
const explicitMaxWidth =
|
|
|
|
|
col.maxWidth !== undefined && Number.isFinite(Number(col.maxWidth)) ? Number(col.maxWidth) : undefined;
|
|
|
|
|
const align = match(col.align)
|
|
|
|
|
.returnType<CSSProperties>()
|
|
|
|
|
.with("left", () => ({ alignSelf: "flex-start", textAlign: "left" }))
|
|
|
|
|
.with("right", () => ({ alignSelf: "flex-end", textAlign: "right" }))
|
|
|
|
|
.otherwise(() => ({ textAlign: "center" }));
|
|
|
|
|
|
|
|
|
|
if (explicitWidth !== undefined && Number.isFinite(explicitWidth) && explicitWidth > 0) {
|
|
|
|
|
return {
|
|
|
|
|
flex: `0 0 ${explicitWidth}px`,
|
|
|
|
|
width: `${explicitWidth}px`,
|
|
|
|
|
minWidth: `${explicitWidth}px`,
|
|
|
|
|
...align,
|
|
|
|
|
...(explicitMaxWidth !== undefined ? { maxWidth: `${explicitMaxWidth}px` } : {}),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find the column index to get the computed width
|
|
|
|
|
const colIndex = columnsRef.value.findIndex((c) => c.key === col.key);
|
|
|
|
|
const computedWidth = colIndex >= 0 ? columnWidths.value[colIndex] : 0;
|
|
|
|
|
const fallbackBasis = Number(col.minWidth ?? 120);
|
|
|
|
|
|
|
|
|
|
if (!config) {
|
|
|
|
|
return {
|
|
|
|
|
flexGrow: "1.1",
|
|
|
|
|
flexShrink: "1.1",
|
|
|
|
|
flexBasis: `${fallbackBasis}px`,
|
|
|
|
|
minWidth: `${Math.max(fallbackBasis, 50)}px`,
|
|
|
|
|
...align,
|
|
|
|
|
...(explicitMaxWidth !== undefined ? { maxWidth: `${explicitMaxWidth}px` } : {}),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply the computed flex factors to the real cells so the row can consume
|
|
|
|
|
// any remaining space after fixed-width columns and scrollbar width.
|
|
|
|
|
const flexBasis = computedWidth > 0 ? computedWidth : config.flexBasis;
|
|
|
|
|
return {
|
|
|
|
|
flexGrow: `${config.flexGrow}`,
|
|
|
|
|
flexShrink: `${config.flexShrink}`,
|
|
|
|
|
flexBasis: `${flexBasis}px`,
|
|
|
|
|
minWidth: `${config.minWidth}px`,
|
|
|
|
|
...align,
|
|
|
|
|
...(explicitMaxWidth !== undefined ? { maxWidth: `${explicitMaxWidth}px` } : {}),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getTextStyle(col: ListTableColumn<T>): CSSProperties {
|
|
|
|
|
return {
|
|
|
|
|
maxWidth: `${col.maxTextWidth ?? DEFAULT_TEXT_MAX_WIDTH}px`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Header height resolution
|
|
|
|
|
// =============================================================================
|
|
|
|
|
const resolvedHeaderHeight = computed(() => {
|
|
|
|
|
return props.headerHeight ?? 44;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Scroll handling
|
|
|
|
|
// =============================================================================
|
|
|
|
|
function handleScroll(event: Event) {
|
|
|
|
|
const target = event.target as HTMLElement;
|
|
|
|
|
onScroll(target.scrollTop);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Event handlers
|
|
|
|
|
// =============================================================================
|
|
|
|
|
function handleRowClick(index: number) {
|
|
|
|
|
const row = pageData.value[index];
|
|
|
|
|
if (row) {
|
|
|
|
|
emit("row-click", row);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleCellClick(index: number, col: ListTableColumn<T>) {
|
|
|
|
|
const row = pageData.value[index];
|
|
|
|
|
if (row) {
|
|
|
|
|
emit("cell-click", row, col, null, null);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleSizeChange = (val: number) => {
|
|
|
|
|
props.example.size = val;
|
|
|
|
|
emit("query");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleCurrentChange = (val: number) => {
|
|
|
|
|
props.example.page = val;
|
|
|
|
|
emit("query");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Debug Info
|
|
|
|
|
// ============================================================================
|
|
|
|
|
const debugInfo = computed(() => ({
|
|
|
|
|
containerWidth,
|
|
|
|
|
virtualTotalHeight,
|
|
|
|
|
visibleRows: visibleRows.value.map((entry) => entry.height).join(","),
|
|
|
|
|
cellHeights: cellHeights.value?.map((rowCellHeights) =>
|
|
|
|
|
rowCellHeights.map((entry) => (entry ? `${entry.isCustomRenderer ? "*" : ""}${entry.height}` : "null")).join(",")
|
|
|
|
|
),
|
|
|
|
|
totalRows: pageData.value.length,
|
|
|
|
|
columnWidths: columnWidths.value.map((w) => Math.round(w)).join(","),
|
|
|
|
|
columnConfigs: computedConfigs,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// ResizeObserver for container dimensions
|
|
|
|
|
// =============================================================================
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
// Initial measurement
|
|
|
|
|
if (tableContainerRef.value) {
|
|
|
|
|
const rect = tableContainerRef.value.getBoundingClientRect();
|
|
|
|
|
containerWidth.value = rect.width;
|
|
|
|
|
viewportHeight.value = rect.height - resolvedHeaderHeight.value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ResizeObserver for container width changes
|
|
|
|
|
resizeObserver = new ResizeObserver(
|
|
|
|
|
lodash.debounce((entries) => {
|
|
|
|
|
for (const entry of entries) {
|
|
|
|
|
const { width, height } = entry.contentRect;
|
|
|
|
|
containerWidth.value = width;
|
|
|
|
|
viewportHeight.value = height - resolvedHeaderHeight.value;
|
|
|
|
|
}
|
|
|
|
|
}, 50)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (tableContainerRef.value) {
|
|
|
|
|
resizeObserver.observe(tableContainerRef.value);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
if (resizeObserver) {
|
|
|
|
|
resizeObserver.disconnect();
|
|
|
|
|
resizeObserver = null;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.debug-info {
|
|
|
|
|
font-size: 9px;
|
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
background: #f5f5f5;
|
|
|
|
|
border-bottom: 1px solid #ddd;
|
|
|
|
|
font-family: monospace;
|
|
|
|
|
white-space: pre;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.list-table-v2 {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
position: relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.table-container {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.table-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
background: v-bind("state.style.tableHeadBg");
|
|
|
|
|
border-bottom: 1px solid v-bind("state.style.tableBorderColor");
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header-cell {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex: 1 1 120px;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
padding: 8px 4px;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
border-right: 1px solid v-bind("state.style.tableBorderColor");
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
word-break: break-word;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
|
|
|
|
&:last-child {
|
|
|
|
|
border-right: none;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header-cell-text {
|
|
|
|
|
display: block;
|
|
|
|
|
width: 100%;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
color: v-bind("state.style.tableColor");
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
font-size: var(--el-font-size-base);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.table-body {
|
|
|
|
|
flex: 1;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
overflow-x: hidden;
|
|
|
|
|
background: v-bind("state.style.tableBg");
|
|
|
|
|
position: relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.table-spacer {
|
|
|
|
|
position: relative;
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.table-row {
|
|
|
|
|
position: absolute;
|
|
|
|
|
left: 0;
|
|
|
|
|
right: 0;
|
|
|
|
|
display: flex;
|
|
|
|
|
border-bottom: 1px solid v-bind("state.style.tableBorderColor");
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
background: v-bind("state.style.tableBg");
|
|
|
|
|
transition: background-color 0.15s;
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
background: v-bind("state.style.tableCurBg");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.table-cell {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex: 1 1 120px;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
padding: 8px 4px;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
border-right: 1px solid v-bind("state.style.tableBorderColor");
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
word-break: break-word;
|
|
|
|
|
min-height: 100%;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
|
|
|
|
&:last-child {
|
|
|
|
|
border-right: none;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.cell-text {
|
|
|
|
|
display: block;
|
|
|
|
|
width: 100%;
|
|
|
|
|
overflow: visible;
|
|
|
|
|
text-overflow: clip;
|
|
|
|
|
white-space: normal;
|
|
|
|
|
word-break: break-word;
|
|
|
|
|
color: v-bind("state.style.tableColor");
|
|
|
|
|
font: v-bind("props.font");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.my-pagination {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
padding-top: 5px;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.my-pagination * {
|
|
|
|
|
--el-pagination-bg-color: v-bind("state.style.bodyBg") !important;
|
|
|
|
|
--el-pagination-disabled-bg-color: v-bind("state.style.bodyBg") !important;
|
|
|
|
|
--el-pagination-text-color: v-bind("state.style.color") !important;
|
|
|
|
|
--el-pagination-button-color: v-bind("state.style.color") !important;
|
|
|
|
|
--el-pagination-button-disabled-bg-color: v-bind("state.style.bodyBg") !important;
|
|
|
|
|
}
|
|
|
|
|
</style>
|