|
|
@@ -1,42 +1,55 @@
|
|
|
<template>
|
|
|
<div class="compliance-table" :class="{ 'compliance-table--fluid': fluid }">
|
|
|
- <div class="compliance-table__header">
|
|
|
- <div
|
|
|
- v-for="column in columns"
|
|
|
- :key="column.key"
|
|
|
- class="compliance-table__cell compliance-table__cell--header"
|
|
|
- :style="cellStyle(column)"
|
|
|
- >
|
|
|
- {{ column.label }}
|
|
|
+ <div :class="{ 'compliance-table__sticky-chrome': stickyChrome }">
|
|
|
+ <div v-if="stickyChrome" class="compliance-table__chrome-content">
|
|
|
+ <slot name="chrome" />
|
|
|
</div>
|
|
|
- </div>
|
|
|
- <div class="compliance-table__body">
|
|
|
- <template v-if="paginatedRows.length">
|
|
|
- <div
|
|
|
- v-for="(row, rowIndex) in paginatedRows"
|
|
|
- :key="row.id ?? rowIndex"
|
|
|
- class="compliance-table__row"
|
|
|
- >
|
|
|
+ <div ref="headScrollRef" class="compliance-table-head-scroll">
|
|
|
+ <div class="compliance-table__header">
|
|
|
<div
|
|
|
v-for="column in columns"
|
|
|
:key="column.key"
|
|
|
- class="compliance-table__cell"
|
|
|
- :class="{ 'compliance-table__cell--action': column.type === 'action' }"
|
|
|
+ class="compliance-table__cell compliance-table__cell--header"
|
|
|
:style="cellStyle(column)"
|
|
|
>
|
|
|
- <button
|
|
|
- v-if="column.type === 'action'"
|
|
|
- type="button"
|
|
|
- class="compliance-table__link"
|
|
|
- @click="$emit('row-action', row)"
|
|
|
- >
|
|
|
- {{ column.actionLabel || '查看' }}
|
|
|
- </button>
|
|
|
- <template v-else>{{ row[column.key] ?? '暂无' }}</template>
|
|
|
+ {{ column.label }}
|
|
|
</div>
|
|
|
</div>
|
|
|
- </template>
|
|
|
- <div v-else class="compliance-table__empty">暂无符合条件的数据</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="compliance-table-body-scroll"
|
|
|
+ :class="{ 'compliance-table-body-scroll--sticky-chrome': stickyChrome }"
|
|
|
+ @scroll="syncTableScroll"
|
|
|
+ >
|
|
|
+ <div class="compliance-table__body">
|
|
|
+ <template v-if="paginatedRows.length">
|
|
|
+ <div
|
|
|
+ v-for="(row, rowIndex) in paginatedRows"
|
|
|
+ :key="row.id ?? rowIndex"
|
|
|
+ class="compliance-table__row"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ v-for="column in columns"
|
|
|
+ :key="column.key"
|
|
|
+ class="compliance-table__cell"
|
|
|
+ :class="{ 'compliance-table__cell--action': column.type === 'action' }"
|
|
|
+ :style="cellStyle(column)"
|
|
|
+ >
|
|
|
+ <button
|
|
|
+ v-if="column.type === 'action'"
|
|
|
+ type="button"
|
|
|
+ class="compliance-table__link"
|
|
|
+ @click="$emit('row-action', row)"
|
|
|
+ >
|
|
|
+ {{ column.actionLabel || '查看' }}
|
|
|
+ </button>
|
|
|
+ <template v-else>{{ row[column.key] ?? '暂无' }}</template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div v-else class="compliance-table__empty">暂无符合条件的数据</div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
<div class="compliance-table__pagination">
|
|
|
<button type="button" :disabled="currentPage === 1" @click="prevPage">
|
|
|
@@ -51,7 +64,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, computed, watch, defineProps, defineEmits} from 'vue';
|
|
|
+import { ref, computed, watch, defineProps, defineEmits } from 'vue';
|
|
|
|
|
|
const props = defineProps({
|
|
|
columns: {
|
|
|
@@ -70,10 +83,24 @@ const props = defineProps({
|
|
|
type: Boolean,
|
|
|
default: false,
|
|
|
},
|
|
|
+ stickyChrome: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
});
|
|
|
|
|
|
defineEmits(['row-action']);
|
|
|
|
|
|
+const headScrollRef = ref(null);
|
|
|
+
|
|
|
+const syncTableScroll = (event) => {
|
|
|
+ const head = headScrollRef.value;
|
|
|
+ const scrollLeft = event.target.scrollLeft;
|
|
|
+ if (head && head.scrollLeft !== scrollLeft) {
|
|
|
+ head.scrollLeft = scrollLeft;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
const cellStyle = (column) => {
|
|
|
if (props.fluid) {
|
|
|
if (column.type === 'action') {
|
|
|
@@ -137,6 +164,46 @@ watch(totalPages, (pages) => {
|
|
|
gap: 4px;
|
|
|
}
|
|
|
|
|
|
+/* 与 PageHeader 高度一致(layout/PageHeader.vue) */
|
|
|
+.compliance-table__sticky-chrome {
|
|
|
+ position: sticky;
|
|
|
+ top: 64px;
|
|
|
+ z-index: 50;
|
|
|
+ background: var(--color-surface);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: var(--section-gap);
|
|
|
+ margin: 0 calc(-1 * var(--card-padding));
|
|
|
+ padding: 0 var(--card-padding);
|
|
|
+ box-shadow: 0 4px 12px rgba(17, 17, 17, 0.06);
|
|
|
+}
|
|
|
+
|
|
|
+.compliance-table__chrome-content {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: var(--section-gap);
|
|
|
+}
|
|
|
+
|
|
|
+.compliance-table-head-scroll {
|
|
|
+ width: 100%;
|
|
|
+ overflow: hidden;
|
|
|
+ flex-shrink: 0;
|
|
|
+ border-bottom: 1px solid var(--color-divider);
|
|
|
+ padding-right: 8px;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.compliance-table-body-scroll {
|
|
|
+ width: 100%;
|
|
|
+ max-height: min(720px, calc(100vh - 280px));
|
|
|
+ overflow: auto;
|
|
|
+ scrollbar-gutter: stable;
|
|
|
+}
|
|
|
+
|
|
|
+.compliance-table-body-scroll--sticky-chrome {
|
|
|
+ max-height: min(640px, calc(100vh - 380px));
|
|
|
+}
|
|
|
+
|
|
|
.compliance-table__header,
|
|
|
.compliance-table__row {
|
|
|
display: flex;
|
|
|
@@ -168,7 +235,7 @@ watch(totalPages, (pages) => {
|
|
|
|
|
|
.compliance-table--fluid .compliance-table__cell--header {
|
|
|
white-space: nowrap;
|
|
|
- overflow: hidden;
|
|
|
+ overflow: visible;
|
|
|
text-overflow: ellipsis;
|
|
|
height: 42px;
|
|
|
line-height: 42px;
|
|
|
@@ -286,5 +353,4 @@ watch(totalPages, (pages) => {
|
|
|
font-size: 14px;
|
|
|
padding: 0;
|
|
|
}
|
|
|
-
|
|
|
</style>
|