| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- <script lang="ts" setup generic="T">
- import type { TableInstance, TableProps } from 'element-plus'
- import { FilterPayload, SortField, SortOrder, TableContextKey } from './token'
- import { DefaultRow } from 'element-plus/es/components/table/src/table/defaults'
- interface Props
- extends /* @vue-ignore */ Partial<
- Omit<TableProps<T extends DefaultRow ? T : DefaultRow>, 'data'>
- > {
- data: T[]
- loading: boolean
- handleQuery?: (payload?: FilterPayload) => void
- sortingFields?: SortField[]
- sortFn?: (prop: string, order: SortOrder | null) => void
- customClass?: boolean
- showBorder?: boolean
- }
- const props = defineProps<Props>()
- const emits = defineEmits<{
- 'update:sortingFields': [fields: SortField[]]
- }>()
- const attrs = useAttrs()
- const tableRef = ref<TableInstance>()
- const defaultOptions: Partial<Props> = {
- size: 'default',
- stripe: true,
- border: true,
- highlightCurrentRow: true,
- showOverflowTooltip: true,
- scrollbarAlwaysOn: true,
- showBorder: false,
- customClass: false,
- tooltipOptions: {
- popperClass: 'max-w-120'
- }
- }
- const bindProps = computed(() => {
- const { data, sortingFields, ...otherProps } = props
- return {
- ...defaultOptions,
- ...attrs,
- ...otherProps,
- data: data || []
- }
- })
- const handleDefaultSort = (prop: string, order: SortOrder | null) => {
- const newFields = [...(props.sortingFields || [])]
- const idx = newFields.findIndex((f) => f.field === prop)
- if (order === null) {
- if (idx > -1) {
- newFields.splice(idx, 1)
- }
- } else {
- if (idx > -1) {
- newFields[idx] = { ...newFields[idx], order }
- } else {
- newFields.push({ field: prop, order })
- }
- }
- emits('update:sortingFields', newFields)
- props.handleQuery?.()
- }
- const safeSortingFields = computed(() => props.sortingFields || [])
- const safeData = computed(() => props.data || [])
- const safeLoading = computed(() => props.loading)
- provide(TableContextKey, {
- onQuery: (payload) => props.handleQuery?.(payload),
- onSort: (prop, order) => {
- if (props.sortFn) {
- props.sortFn(prop, order)
- } else {
- handleDefaultSort(prop, order)
- }
- },
- // 关键:传递响应式的 data 和 sortingFields
- data: safeData,
- sortingFields: safeSortingFields,
- loading: safeLoading
- })
- defineExpose({
- elTableRef: tableRef
- })
- </script>
- <template>
- <el-table
- ref="tableRef"
- v-loading="loading"
- :class="{ 'zm-table': !customClass, 'show-border': showBorder }"
- v-bind="bindProps"
- :data="data"
- >
- <template v-for="(_, name) in $slots" #[name]="slotData">
- <slot :name="name" v-bind="slotData || {}"></slot>
- </template>
- </el-table>
- </template>
- <style lang="scss">
- .zm-table {
- --zm-table-radius: 10px;
- --zm-table-border-color: #e7edf4;
- --zm-table-header-border-color: #e3eaf2;
- --zm-table-row-border-color: #edf2f7;
- --zm-table-header-bg: #f7f9fc;
- --zm-table-header-text-color: #6b7f99;
- --zm-table-body-text-color: #40546d;
- --zm-table-strong-text-color: #24364d;
- --zm-table-stripe-bg: #fcfdff;
- --zm-table-hover-bg: #f5f9ff;
- --zm-table-current-bg: #eef6ff;
- width: 100%;
- overflow: hidden;
- font-size: 12px;
- color: var(--zm-table-body-text-color);
- background: var(--el-bg-color);
- border: 1px solid var(--zm-table-border-color);
- border-radius: var(--zm-table-radius);
- box-shadow: none;
- &::before,
- &::after {
- display: none;
- }
- .el-table__inner-wrapper {
- &::before,
- &::after {
- display: none;
- }
- }
- .el-table__border-left-patch {
- display: none;
- }
- .el-table__inner-wrapper,
- .el-table__header-wrapper,
- .el-table__body-wrapper,
- .el-scrollbar__wrap {
- background: transparent;
- }
- .el-table__inner-wrapper {
- border-radius: var(--zm-table-radius);
- }
- .el-table__cell {
- height: 38px;
- padding: 0;
- color: var(--zm-table-body-text-color);
- background: var(--el-bg-color);
- border-right: 1px solid var(--zm-table-row-border-color) !important;
- border-bottom: 1px solid var(--zm-table-row-border-color) !important;
- transition:
- background-color 0.16s ease,
- color 0.16s ease;
- &:last-child {
- border-right: none !important;
- }
- }
- .cell {
- padding-right: 13px;
- padding-left: 13px;
- line-height: 18px;
- }
- .el-table__header {
- color: var(--zm-table-header-text-color);
- .el-table__cell {
- height: 36px;
- font-size: 12px;
- font-weight: 600;
- color: var(--zm-table-header-text-color);
- background: var(--zm-table-header-bg) !important;
- border-right: 1px solid var(--zm-table-header-border-color) !important;
- border-bottom: 1px solid var(--zm-table-header-border-color) !important;
- .cell {
- display: flex;
- min-height: 100%;
- align-items: center;
- justify-content: center;
- padding-top: 0;
- padding-bottom: 0;
- }
- &:last-child {
- .cell {
- border-right: none;
- }
- }
- }
- tr:first-child {
- .el-table__cell {
- &:first-child {
- border-top-left-radius: var(--zm-table-radius);
- }
- &:last-child {
- border-top-right-radius: var(--zm-table-radius);
- }
- }
- }
- tr:not(:last-child) {
- .el-table__cell {
- height: 42px;
- border-bottom-color: var(--zm-table-header-border-color) !important;
- }
- }
- }
- .el-table__body {
- tr.el-table__row--striped {
- .el-table__cell {
- background: var(--zm-table-stripe-bg);
- }
- }
- tr:hover,
- tr.hover-row {
- .el-table__cell {
- background: var(--zm-table-hover-bg) !important;
- }
- }
- tr.current-row {
- .el-table__cell {
- // color: var(--el-color-primary);
- background: var(--zm-table-current-bg) !important;
- }
- }
- }
- .el-table__row {
- .el-table__cell {
- font-weight: 500;
- color: var(--zm-table-strong-text-color);
- &:first-child {
- .cell {
- padding-left: 16px;
- }
- }
- &:last-child {
- .cell {
- padding-right: 16px;
- }
- }
- }
- }
- .el-table__empty-block {
- min-height: 148px;
- background: var(--el-bg-color);
- }
- .el-table__empty-text {
- font-size: 12px;
- color: var(--el-text-color-secondary);
- }
- .el-table__cell.el-table-fixed-column--left,
- .el-table__cell.el-table-fixed-column--right {
- background: inherit;
- }
- .el-table__cell.el-table-fixed-column--left.is-last-column {
- box-shadow: 6px 0 12px -10px rgb(15 23 42 / 22%);
- }
- .el-table__cell.el-table-fixed-column--right.is-first-column {
- box-shadow: -6px 0 12px -10px rgb(15 23 42 / 22%);
- }
- .el-table__fixed-right-patch {
- background: var(--zm-table-header-bg);
- border-bottom: 1px solid var(--zm-table-header-border-color);
- }
- .el-scrollbar__bar {
- &.is-horizontal {
- height: 7px;
- }
- &.is-vertical {
- width: 7px;
- }
- }
- .el-scrollbar__thumb {
- background: #b8c5d6;
- border-radius: 999px;
- opacity: 0.55;
- &:hover {
- opacity: 0.85;
- }
- }
- }
- .zm-table:not(.show-border) {
- .el-table__header {
- .el-table__cell {
- border-right-color: var(--zm-table-header-border-color) !important;
- .cell {
- border-right: none;
- }
- &:last-child {
- .cell {
- border-right: none;
- }
- }
- }
- }
- }
- </style>
|