优化
This commit is contained in:
@@ -44,6 +44,7 @@ export function createPaginationItems(page: number, pageCount: number): Paginati
|
||||
<script setup lang="ts">
|
||||
import { computed, useSlots, watch } from 'vue'
|
||||
import { ChevronLeft, ChevronRight } from '@lucide/vue'
|
||||
import { splitTableActions, runTableAction, type TableAction } from './tableActions'
|
||||
|
||||
defineOptions({ inheritAttrs: false })
|
||||
|
||||
@@ -54,6 +55,7 @@ const props = withDefaults(defineProps<{
|
||||
emptyText?: string
|
||||
total?: number
|
||||
pageSizes?: number[]
|
||||
rowActions?: (row: object) => TableAction[]
|
||||
}>(), {
|
||||
loading: false,
|
||||
emptyText: '暂无数据',
|
||||
@@ -141,6 +143,13 @@ function setPage(value: number) {
|
||||
|
||||
<template>
|
||||
<div class="data-table">
|
||||
<header v-if="$slots.toolbar || $slots['toolbar-actions']" class="data-table__toolbar">
|
||||
<div class="data-table__toolbar-heading"><slot name="toolbar" /></div>
|
||||
<div class="data-table__toolbar-actions"><slot name="toolbar-actions" /></div>
|
||||
</header>
|
||||
<slot name="before-table" />
|
||||
<slot v-if="$slots.error" name="error" />
|
||||
<template v-else>
|
||||
<el-table v-bind="$attrs" v-loading="loading" :data="data" :empty-text="emptyText" :aria-busy="loading">
|
||||
<el-table-column
|
||||
v-for="(column, index) in columns"
|
||||
@@ -152,7 +161,26 @@ function setPage(value: number) {
|
||||
<template v-if="hasSlot(column.headerSlotName)" #header="scope">
|
||||
<slot :name="column.headerSlotName" v-bind="scope" />
|
||||
</template>
|
||||
<template v-if="hasSlot(column.slotName)" #default="scope">
|
||||
<template v-if="column.slotName === 'actions' && rowActions" #default="{ row }">
|
||||
<div class="data-table__row-actions">
|
||||
<el-button v-for="action in splitTableActions(rowActions(row)).visible" :key="action.key"
|
||||
:disabled="action.disabled" :loading="action.loading" @click="runTableAction(action)">
|
||||
<component :is="action.icon" v-if="action.icon" :size="14" />{{ action.label }}
|
||||
</el-button>
|
||||
<el-dropdown v-if="splitTableActions(rowActions(row)).overflow.length" trigger="click">
|
||||
<el-button class="data-table__more" aria-label="更多操作" title="更多操作">…</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-for="action in splitTableActions(rowActions(row)).overflow" :key="action.key"
|
||||
:disabled="action.disabled || action.loading" @click="runTableAction(action)">
|
||||
{{ action.label }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="hasSlot(column.slotName)" #default="scope">
|
||||
<slot :name="column.slotName" v-bind="scope" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -192,15 +220,29 @@ function setPage(value: number) {
|
||||
</nav>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.data-table { min-width: 0; }
|
||||
.data-table__toolbar { display: flex; min-height: 48px; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--client-gap); padding: var(--client-toolbar-padding); border-bottom: 1px solid var(--client-border); }
|
||||
.data-table__toolbar-heading, .data-table__toolbar-actions { display: flex; align-items: center; flex-wrap: wrap; gap: var(--client-gap); }
|
||||
.data-table__toolbar-heading { color: var(--client-muted); font-size: 12px; }
|
||||
.data-table__toolbar-heading :slotted(strong) { color: var(--client-text); font-size: 14px; }
|
||||
.data-table__toolbar-actions :deep(.el-button + .el-button) { margin-left: 0; }
|
||||
.data-table__row-actions { display: flex; align-items: center; gap: var(--client-gap); white-space: nowrap; }
|
||||
.data-table__row-actions :deep(.el-button) { height: var(--client-row-action-height); padding: 0 8px; margin: 0 !important; }
|
||||
.data-table__more { min-width: 28px; }
|
||||
:global(.client-ui .data-table .el-table) { color: var(--client-text); font-size: var(--client-font-size); --el-table-border-color: var(--client-border); --el-table-header-bg-color: var(--client-header-bg); --el-table-row-hover-bg-color: var(--client-hover-bg); }
|
||||
:global(.client-ui .data-table th.el-table__cell) { height: var(--client-table-header-height); padding: 0; color: var(--client-muted); font-weight: 600; }
|
||||
:global(.client-ui .data-table td.el-table__cell) { height: var(--client-table-row-height); padding: 0; }
|
||||
:global(.client-ui .data-table .el-table .cell) { padding: 0 12px; line-height: 20px; }
|
||||
:global(.client-ui .data-table .el-table__row:nth-child(even)) { --el-table-tr-bg-color: #fcfcfd; }
|
||||
.data-table :deep(.data-table__single-line-cell .cell) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.data-table :deep(.data-table__system-cell .cell) { white-space: nowrap; }
|
||||
.data-table :deep(.data-table__system-cell .el-button + .el-button), .data-table :deep(.data-table__system-cell .el-button + .el-dropdown) { margin-left: 8px; }
|
||||
.data-table__pagination { display: flex; min-height: 58px; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 18px; border-top: 1px solid var(--border); color: var(--muted); font-size: 12px; }
|
||||
.data-table__pagination { display: flex; min-height: 58px; align-items: center; justify-content: space-between; gap: 16px; padding: var(--client-pagination-padding); border-top: 1px solid var(--border); color: var(--muted); font-size: 12px; }
|
||||
.data-table__pagination-left { display: flex; min-width: 0; flex: 1; align-items: center; gap: 8px; }
|
||||
.data-table__pagination-controls, .data-table__page-size, .data-table__page-buttons { display: flex; align-items: center; gap: 8px; }
|
||||
.data-table__pagination-controls { min-width: 0; flex-wrap: wrap; justify-content: flex-end; }
|
||||
|
||||
@@ -23,12 +23,14 @@ export interface QueryField {
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
layout?: 'inline' | 'grid'
|
||||
ariaLabel: string
|
||||
fields: QueryField[]
|
||||
searchText?: string
|
||||
resetText?: string
|
||||
searchOnReset?: boolean
|
||||
}>(), {
|
||||
layout: 'inline',
|
||||
searchText: '查询',
|
||||
resetText: '重置',
|
||||
searchOnReset: true,
|
||||
@@ -77,6 +79,7 @@ defineExpose({ search, reset })
|
||||
<section
|
||||
v-bind="$attrs"
|
||||
class="query-toolbar"
|
||||
:class="{ 'query-toolbar--grid': layout === 'grid' }"
|
||||
:aria-label="ariaLabel"
|
||||
>
|
||||
<div class="query-toolbar__filters">
|
||||
@@ -133,5 +136,11 @@ defineExpose({ search, reset })
|
||||
.query-toolbar__filters :deep(.el-input), .query-toolbar__filters :deep(.el-select), .query-toolbar__filters :deep(.el-date-editor) { width: 260px; max-width: 100%; min-width: 0; }
|
||||
.query-toolbar__actions { display: flex; flex: 0 0 auto; flex-wrap: wrap; justify-content: flex-start; gap: 8px; }
|
||||
.query-toolbar__actions :deep(.el-button + .el-button) { margin-left: 0; }
|
||||
.query-toolbar--grid { display: block; padding: var(--client-query-padding); border: 1px solid var(--client-border); border-radius: var(--client-radius); }
|
||||
.query-toolbar--grid .query-toolbar__filters { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px 16px; }
|
||||
.query-toolbar--grid .query-toolbar__field { min-width: 0; }
|
||||
.query-toolbar--grid .query-toolbar__field :deep(.el-input), .query-toolbar--grid .query-toolbar__field :deep(.el-select), .query-toolbar--grid .query-toolbar__field :deep(.el-date-editor) { flex: 1; width: 100%; min-width: 0; }
|
||||
.query-toolbar--grid .query-toolbar__actions { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--client-border); }
|
||||
@media (max-width: 1200px) { .query-toolbar--grid .query-toolbar__filters { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||
@media (max-width: 720px) { .query-toolbar { align-items: stretch; flex-direction: column; } .query-toolbar__filters { width: 100%; flex-direction: column; } .query-toolbar__field { display: grid; gap: 6px; } .query-toolbar__filters :deep(.el-input), .query-toolbar__filters :deep(.el-select), .query-toolbar__filters :deep(.el-date-editor) { width: 100%; } .query-toolbar__actions { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); width: 100%; } .query-toolbar__actions :deep(.el-button) { min-height: 40px; margin-left: 0; } }
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { Component } from 'vue'
|
||||
|
||||
export interface TableAction {
|
||||
key: string
|
||||
label: string
|
||||
icon?: Component
|
||||
disabled?: boolean
|
||||
loading?: boolean
|
||||
run: () => unknown
|
||||
}
|
||||
|
||||
// Callers supply only permitted actions; disabled actions retain their place.
|
||||
export function splitTableActions(actions: readonly TableAction[]) {
|
||||
return actions.length > 3
|
||||
? { visible: actions.slice(0, 2), overflow: actions.slice(2) }
|
||||
: { visible: actions, overflow: [] }
|
||||
}
|
||||
|
||||
export function runTableAction(action: TableAction) {
|
||||
if (!action.disabled && !action.loading) return action.run()
|
||||
}
|
||||
Reference in New Issue
Block a user