feat: toolbar justify-between, selection, and card-fill list-scroll
- 搜索按钮置于表单最右(el-form--inline 设为 flex 容器) - ty-crud 工具条 justify-between:左新增/#toolbar、右批量 #toolbar-right(selectedRows) - ty-table 支持 selection 多选列 + selection-change - 卡片铺满剩余空间、列表内滚动(shell 固定视口 + el-table height=100%) - 用户管理页加批量删除示例 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
1ee3ed28a5
commit
bc26c0aa93
@ -4,20 +4,28 @@
|
||||
<ty-search-form :schema="searchSchema" :model-value="query" @search="handleSearch" @reset="handleReset" />
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<el-card class="ty-crud__table" shadow="never">
|
||||
<div class="ty-crud__toolbar">
|
||||
<div class="ty-crud__toolbar-left">
|
||||
<el-button type="primary" @click="openCreate">新增{{ entityName }}</el-button>
|
||||
<slot name="toolbar" />
|
||||
</div>
|
||||
<div class="ty-crud__toolbar-right">
|
||||
<slot name="toolbar-right" :selected-rows="selectedRows" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ty-crud__table-body">
|
||||
<ty-table
|
||||
:columns="columns"
|
||||
:data="list"
|
||||
:loading="loading"
|
||||
:selection="selection"
|
||||
:total="total"
|
||||
v-model:page="page"
|
||||
v-model:page-size="pageSize"
|
||||
:actions-width="actionsWidth"
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<template #actions="{ row }">
|
||||
<el-button
|
||||
@ -44,6 +52,7 @@
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</ty-table>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<ty-form-dialog
|
||||
@ -94,6 +103,8 @@ const props = withDefaults(
|
||||
actions?: CrudAction[]
|
||||
/** 操作列按钮超过该数量时折叠为「更多」。 */
|
||||
collapseCount?: number
|
||||
/** 是否启用多选列(配合 #toolbar-right 做批量操作)。 */
|
||||
selection?: boolean
|
||||
actionsWidth?: number | string
|
||||
}>(),
|
||||
{
|
||||
@ -103,6 +114,7 @@ const props = withDefaults(
|
||||
editReadonlyKeys: () => [],
|
||||
actions: () => ['edit', 'delete'],
|
||||
collapseCount: 2,
|
||||
selection: false,
|
||||
actionsWidth: 220,
|
||||
},
|
||||
)
|
||||
@ -112,6 +124,7 @@ const list = ref<Record<string, any>[]>([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const selectedRows = ref<Record<string, any>[]>([])
|
||||
|
||||
const query = reactive<Record<string, any>>(buildModel(props.searchSchema))
|
||||
|
||||
@ -223,15 +236,42 @@ function handleCommand(action: CrudActionItem, row: Record<string, any>): void {
|
||||
action.onClick(row)
|
||||
}
|
||||
|
||||
function onSelectionChange(rows: Record<string, any>[]): void {
|
||||
selectedRows.value = rows
|
||||
}
|
||||
|
||||
defineExpose({ reload: fetchList })
|
||||
|
||||
onMounted(fetchList)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.ty-crud {
|
||||
@apply flex h-full flex-col;
|
||||
}
|
||||
|
||||
.ty-crud__search {
|
||||
@apply mb-md;
|
||||
@apply mb-md flex-shrink-0;
|
||||
}
|
||||
|
||||
.ty-crud__table {
|
||||
@apply flex min-h-0 flex-1 flex-col;
|
||||
|
||||
:deep(.el-card__body) {
|
||||
@apply flex min-h-0 flex-1 flex-col;
|
||||
}
|
||||
}
|
||||
|
||||
.ty-crud__toolbar {
|
||||
@apply mb-md;
|
||||
@apply mb-md flex flex-shrink-0 items-center justify-between;
|
||||
}
|
||||
|
||||
.ty-crud__toolbar-left,
|
||||
.ty-crud__toolbar-right {
|
||||
@apply flex items-center gap-2;
|
||||
}
|
||||
|
||||
.ty-crud__table-body {
|
||||
@apply min-h-0 flex-1;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,6 +1,14 @@
|
||||
<template>
|
||||
<div class="ty-table">
|
||||
<el-table v-loading="loading" :data="data" border>
|
||||
<div class="ty-table__body">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="data"
|
||||
border
|
||||
height="100%"
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<el-table-column v-if="selection" type="selection" width="48" />
|
||||
<el-table-column
|
||||
v-for="col in columns"
|
||||
:key="col.prop"
|
||||
@ -29,6 +37,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<el-pagination
|
||||
v-if="total > 0"
|
||||
@ -57,6 +66,8 @@ const props = withDefaults(
|
||||
/** 表格数据。 */
|
||||
data: Record<string, any>[]
|
||||
loading?: boolean
|
||||
/** 是否启用多选列。 */
|
||||
selection?: boolean
|
||||
/** 总条数(分页用)。 */
|
||||
total?: number
|
||||
page?: number
|
||||
@ -67,6 +78,7 @@ const props = withDefaults(
|
||||
}>(),
|
||||
{
|
||||
loading: false,
|
||||
selection: false,
|
||||
total: 0,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
@ -78,6 +90,7 @@ const props = withDefaults(
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:page', page: number): void
|
||||
(e: 'update:pageSize', pageSize: number): void
|
||||
(e: 'selection-change', rows: Record<string, any>[]): void
|
||||
}>()
|
||||
|
||||
const currentPage = computed({
|
||||
@ -96,6 +109,10 @@ function onSizeChange(size: number): void {
|
||||
emit('update:pageSize', size)
|
||||
}
|
||||
|
||||
function onSelectionChange(rows: Record<string, any>[]): void {
|
||||
emit('selection-change', rows)
|
||||
}
|
||||
|
||||
function formatDateTime(value: unknown): string {
|
||||
if (value == null || value === '') return '-'
|
||||
const d = new Date(Number(value))
|
||||
@ -105,7 +122,15 @@ function formatDateTime(value: unknown): string {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.ty-table {
|
||||
@apply flex h-full flex-col;
|
||||
}
|
||||
|
||||
.ty-table__body {
|
||||
@apply min-h-0 flex-1;
|
||||
}
|
||||
|
||||
.ty-table__pagination {
|
||||
@apply mt-md justify-end;
|
||||
@apply mt-md flex-shrink-0 justify-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="app-shell" :style="cssVars">
|
||||
<div class="app-shell" :class="{ 'is-not-fixed': !app.headerFixed }" :style="cssVars">
|
||||
<ty-header
|
||||
:title="title"
|
||||
:logo="logo"
|
||||
@ -245,7 +245,14 @@ watch(
|
||||
.app-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
&.is-not-fixed {
|
||||
height: auto;
|
||||
min-height: 100vh;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.app-body {
|
||||
@ -260,11 +267,18 @@ watch(
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.app-main {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
background: var(--wm-bg-color-page);
|
||||
|
||||
.is-not-fixed & {
|
||||
overflow-y: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<ty-crud
|
||||
ref="crudRef"
|
||||
entity-name="用户"
|
||||
selection
|
||||
:columns="columns"
|
||||
:search-schema="searchSchema"
|
||||
:form-schema="formSchema"
|
||||
@ -12,7 +14,13 @@
|
||||
id-key="id"
|
||||
name-key="username"
|
||||
:edit-readonly-keys="['username']"
|
||||
/>
|
||||
>
|
||||
<template #toolbar-right="{ selectedRows }">
|
||||
<el-button type="danger" :disabled="selectedRows.length === 0" @click="batchDelete(selectedRows)">
|
||||
批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
</ty-crud>
|
||||
|
||||
<el-dialog v-model="resetPwd.visible" title="重置密码" width="420px" destroy-on-close>
|
||||
<el-form label-width="80px">
|
||||
@ -29,8 +37,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { CrudAction, JsonSchema, PageQuery, PageResult, TableColumn } from '@/types/crud'
|
||||
|
||||
const STATUS_MAP = {
|
||||
@ -196,4 +204,16 @@ async function submitResetPwd(): Promise<void> {
|
||||
ElMessage.success('密码已重置')
|
||||
resetPwd.visible = false
|
||||
}
|
||||
|
||||
const crudRef = ref()
|
||||
|
||||
async function batchDelete(rows: Record<string, any>[]): Promise<void> {
|
||||
await ElMessageBox.confirm(`确认删除选中的 ${rows.length} 个用户?`, '提示', { type: 'warning' })
|
||||
for (const r of rows) {
|
||||
const i = users.findIndex((x) => x.id === r.id)
|
||||
if (i >= 0) users.splice(i, 1)
|
||||
}
|
||||
ElMessage.success('删除成功')
|
||||
crudRef.value?.reload()
|
||||
}
|
||||
</script>
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user