2025-12-18 13:50:39 +08:00
|
|
|
|
<template>
|
2025-12-27 10:50:25 +08:00
|
|
|
|
<div
|
|
|
|
|
|
ref="tableWrapperRef"
|
|
|
|
|
|
class="table-wrapper"
|
|
|
|
|
|
:class="{
|
|
|
|
|
|
'actions-expanded': actionsExpanded,
|
|
|
|
|
|
'is-scrollable': isScrollable
|
|
|
|
|
|
}"
|
|
|
|
|
|
>
|
2025-12-18 13:50:39 +08:00
|
|
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-dark-700">
|
2025-12-27 10:50:25 +08:00
|
|
|
|
<thead class="table-header bg-gray-50 dark:bg-dark-800">
|
2025-12-18 13:50:39 +08:00
|
|
|
|
<tr>
|
|
|
|
|
|
<th
|
2025-12-27 10:50:25 +08:00
|
|
|
|
v-for="(column, index) in columns"
|
2025-12-18 13:50:39 +08:00
|
|
|
|
:key="column.key"
|
|
|
|
|
|
scope="col"
|
2025-12-27 10:50:25 +08:00
|
|
|
|
:class="[
|
|
|
|
|
|
'sticky-header-cell px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-dark-400',
|
|
|
|
|
|
{ 'cursor-pointer hover:bg-gray-100 dark:hover:bg-dark-700': column.sortable },
|
|
|
|
|
|
getStickyColumnClass(column, index)
|
|
|
|
|
|
]"
|
2025-12-18 13:50:39 +08:00
|
|
|
|
@click="column.sortable && handleSort(column.key)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="flex items-center space-x-1">
|
|
|
|
|
|
<span>{{ column.label }}</span>
|
2025-12-27 10:50:25 +08:00
|
|
|
|
<!-- 操作列展开/折叠按钮 -->
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-if="column.key === 'actions' && hasExpandableActions"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
@click.stop="toggleActionsExpanded"
|
|
|
|
|
|
class="ml-2 flex items-center justify-center rounded p-1 text-gray-500 hover:bg-gray-200 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-dark-600 dark:hover:text-gray-300"
|
|
|
|
|
|
:title="actionsExpanded ? t('table.collapseActions') : t('table.expandActions')"
|
|
|
|
|
|
>
|
|
|
|
|
|
<!-- 展开状态:收起图标 -->
|
|
|
|
|
|
<svg
|
|
|
|
|
|
v-if="actionsExpanded"
|
|
|
|
|
|
class="h-4 w-4"
|
|
|
|
|
|
fill="none"
|
|
|
|
|
|
stroke="currentColor"
|
|
|
|
|
|
viewBox="0 0 24 24"
|
|
|
|
|
|
stroke-width="2"
|
|
|
|
|
|
>
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M18.75 19.5l-7.5-7.5 7.5-7.5m-6 15L5.25 12l7.5-7.5" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
<!-- 折叠状态:展开图标 -->
|
|
|
|
|
|
<svg
|
|
|
|
|
|
v-else
|
|
|
|
|
|
class="h-4 w-4"
|
|
|
|
|
|
fill="none"
|
|
|
|
|
|
stroke="currentColor"
|
|
|
|
|
|
viewBox="0 0 24 24"
|
|
|
|
|
|
stroke-width="2"
|
|
|
|
|
|
>
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M11.25 4.5l7.5 7.5-7.5 7.5m-6-15l7.5 7.5-7.5 7.5" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</button>
|
2025-12-18 13:50:39 +08:00
|
|
|
|
<span v-if="column.sortable" class="text-gray-400 dark:text-dark-500">
|
|
|
|
|
|
<svg
|
|
|
|
|
|
v-if="sortKey === column.key"
|
2025-12-25 08:40:12 -08:00
|
|
|
|
class="h-4 w-4"
|
|
|
|
|
|
:class="{ 'rotate-180 transform': sortOrder === 'desc' }"
|
2025-12-18 13:50:39 +08:00
|
|
|
|
fill="currentColor"
|
|
|
|
|
|
viewBox="0 0 20 20"
|
|
|
|
|
|
>
|
|
|
|
|
|
<path
|
|
|
|
|
|
fill-rule="evenodd"
|
|
|
|
|
|
d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z"
|
|
|
|
|
|
clip-rule="evenodd"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</svg>
|
2025-12-25 08:40:12 -08:00
|
|
|
|
<svg v-else class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
|
2025-12-18 13:50:39 +08:00
|
|
|
|
<path
|
|
|
|
|
|
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
2025-12-27 10:50:25 +08:00
|
|
|
|
<tbody class="table-body divide-y divide-gray-200 bg-white dark:divide-dark-700 dark:bg-dark-900">
|
2025-12-18 13:50:39 +08:00
|
|
|
|
<!-- Loading skeleton -->
|
|
|
|
|
|
<tr v-if="loading" v-for="i in 5" :key="i">
|
2025-12-25 08:40:12 -08:00
|
|
|
|
<td v-for="column in columns" :key="column.key" class="whitespace-nowrap px-6 py-4">
|
2025-12-18 13:50:39 +08:00
|
|
|
|
<div class="animate-pulse">
|
2025-12-25 08:40:12 -08:00
|
|
|
|
<div class="h-4 w-3/4 rounded bg-gray-200 dark:bg-dark-700"></div>
|
2025-12-18 13:50:39 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Empty state -->
|
|
|
|
|
|
<tr v-else-if="!data || data.length === 0">
|
2025-12-25 08:40:12 -08:00
|
|
|
|
<td
|
|
|
|
|
|
:colspan="columns.length"
|
|
|
|
|
|
class="px-6 py-12 text-center text-gray-500 dark:text-dark-400"
|
|
|
|
|
|
>
|
2025-12-18 13:50:39 +08:00
|
|
|
|
<slot name="empty">
|
|
|
|
|
|
<div class="flex flex-col items-center">
|
2025-12-25 08:40:12 -08:00
|
|
|
|
<svg
|
|
|
|
|
|
class="mb-4 h-12 w-12 text-gray-400 dark:text-dark-500"
|
|
|
|
|
|
fill="none"
|
|
|
|
|
|
stroke="currentColor"
|
|
|
|
|
|
viewBox="0 0 24 24"
|
|
|
|
|
|
>
|
2025-12-18 13:50:39 +08:00
|
|
|
|
<path
|
|
|
|
|
|
stroke-linecap="round"
|
|
|
|
|
|
stroke-linejoin="round"
|
|
|
|
|
|
stroke-width="2"
|
|
|
|
|
|
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</svg>
|
2025-12-25 08:40:12 -08:00
|
|
|
|
<p class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
|
|
|
|
|
{{ t('empty.noData') }}
|
|
|
|
|
|
</p>
|
2025-12-18 13:50:39 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</slot>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Data rows -->
|
2025-12-25 08:40:12 -08:00
|
|
|
|
<tr
|
|
|
|
|
|
v-else
|
|
|
|
|
|
v-for="(row, index) in sortedData"
|
|
|
|
|
|
:key="index"
|
|
|
|
|
|
class="hover:bg-gray-50 dark:hover:bg-dark-800"
|
|
|
|
|
|
>
|
2025-12-18 13:50:39 +08:00
|
|
|
|
<td
|
2025-12-27 10:50:25 +08:00
|
|
|
|
v-for="(column, colIndex) in columns"
|
2025-12-18 13:50:39 +08:00
|
|
|
|
:key="column.key"
|
2025-12-27 10:50:25 +08:00
|
|
|
|
:class="[
|
|
|
|
|
|
'whitespace-nowrap px-6 py-4 text-sm text-gray-900 dark:text-gray-100',
|
|
|
|
|
|
getStickyColumnClass(column, colIndex)
|
|
|
|
|
|
]"
|
2025-12-18 13:50:39 +08:00
|
|
|
|
>
|
2025-12-27 10:50:25 +08:00
|
|
|
|
<slot :name="`cell-${column.key}`" :row="row" :value="row[column.key]" :expanded="actionsExpanded">
|
2025-12-18 13:50:39 +08:00
|
|
|
|
{{ column.formatter ? column.formatter(row[column.key], row) : row[column.key] }}
|
|
|
|
|
|
</slot>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2025-12-27 10:50:25 +08:00
|
|
|
|
import { computed, ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
2025-12-18 13:50:39 +08:00
|
|
|
|
import { useI18n } from 'vue-i18n'
|
2025-12-18 14:26:55 +08:00
|
|
|
|
import type { Column } from './types'
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
|
|
const { t } = useI18n()
|
|
|
|
|
|
|
2025-12-27 10:50:25 +08:00
|
|
|
|
// 表格容器引用
|
|
|
|
|
|
const tableWrapperRef = ref<HTMLElement | null>(null)
|
|
|
|
|
|
const isScrollable = ref(false)
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否可滚动
|
|
|
|
|
|
const checkScrollable = () => {
|
|
|
|
|
|
if (tableWrapperRef.value) {
|
|
|
|
|
|
isScrollable.value = tableWrapperRef.value.scrollWidth > tableWrapperRef.value.clientWidth
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 监听尺寸变化
|
|
|
|
|
|
let resizeObserver: ResizeObserver | null = null
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
checkScrollable()
|
|
|
|
|
|
if (tableWrapperRef.value && typeof ResizeObserver !== 'undefined') {
|
|
|
|
|
|
resizeObserver = new ResizeObserver(checkScrollable)
|
|
|
|
|
|
resizeObserver.observe(tableWrapperRef.value)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 降级方案:不支持 ResizeObserver 时使用 window resize
|
|
|
|
|
|
window.addEventListener('resize', checkScrollable)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
|
resizeObserver?.disconnect()
|
|
|
|
|
|
window.removeEventListener('resize', checkScrollable)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-12-18 13:50:39 +08:00
|
|
|
|
interface Props {
|
|
|
|
|
|
columns: Column[]
|
|
|
|
|
|
data: any[]
|
|
|
|
|
|
loading?: boolean
|
2025-12-27 10:50:25 +08:00
|
|
|
|
stickyFirstColumn?: boolean
|
|
|
|
|
|
stickyActionsColumn?: boolean
|
|
|
|
|
|
expandableActions?: boolean
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
2025-12-27 10:50:25 +08:00
|
|
|
|
loading: false,
|
|
|
|
|
|
stickyFirstColumn: true,
|
|
|
|
|
|
stickyActionsColumn: true,
|
|
|
|
|
|
expandableActions: true
|
2025-12-18 13:50:39 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const sortKey = ref<string>('')
|
|
|
|
|
|
const sortOrder = ref<'asc' | 'desc'>('asc')
|
2025-12-27 10:50:25 +08:00
|
|
|
|
const actionsExpanded = ref(false)
|
|
|
|
|
|
|
|
|
|
|
|
// 数据/列/展开状态变化时重新检查滚动状态
|
|
|
|
|
|
watch(
|
|
|
|
|
|
[() => props.data.length, () => props.columns, actionsExpanded],
|
|
|
|
|
|
async () => {
|
|
|
|
|
|
await nextTick()
|
|
|
|
|
|
checkScrollable()
|
|
|
|
|
|
},
|
|
|
|
|
|
{ flush: 'post' }
|
|
|
|
|
|
)
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
|
|
const handleSort = (key: string) => {
|
|
|
|
|
|
if (sortKey.value === key) {
|
|
|
|
|
|
sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
|
|
|
|
|
|
} else {
|
|
|
|
|
|
sortKey.value = key
|
|
|
|
|
|
sortOrder.value = 'asc'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const sortedData = computed(() => {
|
|
|
|
|
|
if (!sortKey.value || !props.data) return props.data
|
|
|
|
|
|
|
|
|
|
|
|
return [...props.data].sort((a, b) => {
|
|
|
|
|
|
const aVal = a[sortKey.value]
|
|
|
|
|
|
const bVal = b[sortKey.value]
|
|
|
|
|
|
|
|
|
|
|
|
if (aVal === bVal) return 0
|
|
|
|
|
|
|
|
|
|
|
|
const comparison = aVal > bVal ? 1 : -1
|
|
|
|
|
|
return sortOrder.value === 'asc' ? comparison : -comparison
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
2025-12-27 10:50:25 +08:00
|
|
|
|
|
|
|
|
|
|
// 检查是否有可展开的操作列
|
|
|
|
|
|
const hasExpandableActions = computed(() => {
|
|
|
|
|
|
return props.expandableActions && props.columns.some((col) => col.key === 'actions')
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 切换操作列展开/折叠状态
|
|
|
|
|
|
const toggleActionsExpanded = () => {
|
|
|
|
|
|
actionsExpanded.value = !actionsExpanded.value
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查第一列是否为勾选列
|
|
|
|
|
|
const hasSelectColumn = computed(() => {
|
|
|
|
|
|
return props.columns.length > 0 && props.columns[0].key === 'select'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 生成固定列的 CSS 类
|
|
|
|
|
|
const getStickyColumnClass = (column: Column, index: number) => {
|
|
|
|
|
|
const classes: string[] = []
|
|
|
|
|
|
|
|
|
|
|
|
if (props.stickyFirstColumn) {
|
|
|
|
|
|
// 如果第一列是勾选列,固定前两列(勾选+名称)
|
|
|
|
|
|
if (hasSelectColumn.value) {
|
|
|
|
|
|
if (index === 0) {
|
|
|
|
|
|
classes.push('sticky-col sticky-col-left-first')
|
|
|
|
|
|
} else if (index === 1) {
|
|
|
|
|
|
classes.push('sticky-col sticky-col-left-second')
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 否则只固定第一列
|
|
|
|
|
|
if (index === 0) {
|
|
|
|
|
|
classes.push('sticky-col sticky-col-left')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 操作列固定(最后一列)
|
|
|
|
|
|
if (props.stickyActionsColumn && column.key === 'actions') {
|
|
|
|
|
|
classes.push('sticky-col sticky-col-right')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return classes.join(' ')
|
|
|
|
|
|
}
|
2025-12-18 13:50:39 +08:00
|
|
|
|
</script>
|
2025-12-27 10:50:25 +08:00
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
/* 表格横向滚动 */
|
|
|
|
|
|
.table-wrapper {
|
|
|
|
|
|
--select-col-width: 52px; /* 勾选列宽度:px-6 (24px*2) + checkbox (16px) */
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
overflow-x: auto;
|
|
|
|
|
|
isolation: isolate;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 表头容器,确保在滚动时覆盖表体内容 */
|
|
|
|
|
|
.table-wrapper .table-header {
|
|
|
|
|
|
position: sticky;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
z-index: 200;
|
|
|
|
|
|
background-color: rgb(249 250 251);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dark .table-wrapper .table-header {
|
|
|
|
|
|
background-color: rgb(31 41 55);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 表体保持在表头下方 */
|
|
|
|
|
|
.table-body {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
z-index: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 所有表头单元格固定在顶部 */
|
|
|
|
|
|
.sticky-header-cell {
|
|
|
|
|
|
position: sticky;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
z-index: 210; /* 必须高于所有表体内容 */
|
|
|
|
|
|
background-color: rgb(249 250 251);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dark .sticky-header-cell {
|
|
|
|
|
|
background-color: rgb(31 41 55);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Sticky 列基础样式 */
|
|
|
|
|
|
.sticky-col {
|
|
|
|
|
|
position: sticky;
|
|
|
|
|
|
z-index: 20; /* 表体固定列 */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 单列固定(无勾选列时) */
|
|
|
|
|
|
.sticky-col-left {
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 双列固定(有勾选列时):第一列(勾选) */
|
|
|
|
|
|
.sticky-col-left-first {
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 双列固定(有勾选列时):第二列(名称) */
|
|
|
|
|
|
.sticky-col-left-second {
|
|
|
|
|
|
left: var(--select-col-width);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 操作列固定 */
|
|
|
|
|
|
.sticky-col-right {
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 表头 sticky 列 - 需要比普通表头单元格更高的 z-index */
|
|
|
|
|
|
.sticky-header-cell.sticky-col {
|
|
|
|
|
|
z-index: 220; /* 高于普通表头单元格和表体固定列 */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 表体 sticky 列背景 */
|
|
|
|
|
|
tbody .sticky-col {
|
|
|
|
|
|
background-color: white;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dark tbody .sticky-col {
|
|
|
|
|
|
background-color: rgb(17 24 39);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* hover 状态保持 */
|
|
|
|
|
|
tbody tr:hover .sticky-col {
|
|
|
|
|
|
background-color: rgb(249 250 251);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dark tbody tr:hover .sticky-col {
|
|
|
|
|
|
background-color: rgb(31 41 55);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 阴影只在可滚动时显示 */
|
|
|
|
|
|
/* 单列固定右侧阴影 */
|
|
|
|
|
|
.is-scrollable .sticky-col-left::after {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
width: 10px;
|
|
|
|
|
|
transform: translateX(100%);
|
|
|
|
|
|
background: linear-gradient(to right, rgba(0, 0, 0, 0.08), transparent);
|
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 双列固定:只在第二列显示阴影 */
|
|
|
|
|
|
.is-scrollable .sticky-col-left-second::after {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
width: 10px;
|
|
|
|
|
|
transform: translateX(100%);
|
|
|
|
|
|
background: linear-gradient(to right, rgba(0, 0, 0, 0.08), transparent);
|
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 操作列左侧阴影 */
|
|
|
|
|
|
.is-scrollable .sticky-col-right::before {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
width: 10px;
|
|
|
|
|
|
transform: translateX(-100%);
|
|
|
|
|
|
background: linear-gradient(to left, rgba(0, 0, 0, 0.08), transparent);
|
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 暗色模式阴影 */
|
|
|
|
|
|
.dark .is-scrollable .sticky-col-left::after,
|
|
|
|
|
|
.dark .is-scrollable .sticky-col-left-second::after {
|
|
|
|
|
|
background: linear-gradient(to right, rgba(0, 0, 0, 0.2), transparent);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dark .is-scrollable .sticky-col-right::before {
|
|
|
|
|
|
background: linear-gradient(to left, rgba(0, 0, 0, 0.2), transparent);
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|