feat: table-toolbar

pull/210/head
xingyu4j 2025-09-09 17:04:57 +08:00
parent b3a65f2492
commit 29e5017913
40 changed files with 429 additions and 650 deletions

View File

@ -1,79 +0,0 @@
/* 来自 @vben/plugins/vxe-table style.css覆盖 vxe-table 原有的样式定义,使用 vben 的样式主题 */
:root {
--vxe-ui-font-color: hsl(var(--foreground));
--vxe-ui-font-primary-color: hsl(var(--primary));
/* --vxe-ui-font-lighten-color: #babdc0;
--vxe-ui-font-darken-color: #86898e; */
--vxe-ui-font-disabled-color: hsl(var(--foreground) / 50%);
/* base */
--vxe-ui-base-popup-border-color: hsl(var(--border));
--vxe-ui-input-disabled-color: hsl(var(--border) / 60%);
/* --vxe-ui-base-popup-box-shadow: 0px 12px 30px 8px rgb(0 0 0 / 50%); */
/* layout */
--vxe-ui-layout-background-color: hsl(var(--background));
--vxe-ui-table-resizable-line-color: hsl(var(--heavy));
/* --vxe-ui-table-fixed-left-scrolling-box-shadow: 8px 0px 10px -5px hsl(var(--accent));
--vxe-ui-table-fixed-right-scrolling-box-shadow: -8px 0px 10px -5px hsl(var(--accent)); */
/* input */
--vxe-ui-input-border-color: hsl(var(--border));
/* --vxe-ui-input-placeholder-color: #8d9095; */
/* --vxe-ui-input-disabled-background-color: #262727; */
/* loading */
--vxe-ui-loading-background-color: hsl(var(--overlay-content));
/* table */
--vxe-ui-table-header-background-color: hsl(var(--accent));
--vxe-ui-table-border-color: hsl(var(--border));
--vxe-ui-table-row-hover-background-color: hsl(var(--accent-hover));
--vxe-ui-table-row-striped-background-color: hsl(var(--accent) / 60%);
--vxe-ui-table-row-hover-striped-background-color: hsl(var(--accent));
--vxe-ui-table-row-radio-checked-background-color: hsl(var(--accent));
--vxe-ui-table-row-hover-radio-checked-background-color: hsl(
var(--accent-hover)
);
--vxe-ui-table-row-checkbox-checked-background-color: hsl(var(--accent));
--vxe-ui-table-row-hover-checkbox-checked-background-color: hsl(
var(--accent-hover)
);
--vxe-ui-table-row-current-background-color: hsl(var(--accent));
--vxe-ui-table-row-hover-current-background-color: hsl(var(--accent-hover));
--vxe-ui-font-primary-tinge-color: hsl(var(--primary));
--vxe-ui-font-primary-lighten-color: hsl(var(--primary) / 60%);
--vxe-ui-font-primary-darken-color: hsl(var(--primary));
/* height: auto !important; */
/* --vxe-ui-table-fixed-scrolling-box-shadow-color: rgb(0 0 0 / 80%); */
}
.vxe-tools--operate {
margin-right: 0.25rem;
margin-left: 0.75rem;
}
.vxe-table-custom--checkbox-option:hover {
background: none !important;
}
.vxe-toolbar {
padding: 0;
}
.vxe-buttons--wrapper:not(:empty),
.vxe-tools--operate:not(:empty),
.vxe-tools--wrapper:not(:empty) {
padding: 0.6em 0;
}
.vxe-tools--operate:not(:has(button)) {
margin-left: 0;
}

View File

@ -6,7 +6,8 @@ import { h } from 'vue';
import { IconifyIcon } from '@vben/icons';
import { $te } from '@vben/locales';
import {
AsyncComponents,
AsyncVxeColumn,
AsyncVxeTable,
createRequiredValidation,
setupVbenVxeTable,
useVbenVxeGrid,
@ -34,8 +35,6 @@ import { $t } from '#/locales';
import { useVbenForm } from './form';
import '#/adapter/style.css';
setupVbenVxeTable({
configVxeTable: (vxeUI) => {
vxeUI.setConfig({
@ -357,16 +356,8 @@ setupVbenVxeTable({
export { createRequiredValidation, useVbenVxeGrid };
const [VxeTable, VxeColumn, VxeToolbar] = AsyncComponents;
export { VxeColumn, VxeTable, VxeToolbar };
export const [VxeTable, VxeColumn] = [AsyncVxeTable, AsyncVxeColumn];
// add by 芋艿from https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/adapter/vxe-table.ts#L264-L270
export type OnActionClickParams<T = Recordable<any>> = {
code: string;
row: T;
};
export type OnActionClickFn<T = Recordable<any>> = (
params: OnActionClickParams<T>,
) => void;
export * from '#/components/table-action';
export type * from '@vben/plugins/vxe-table';

View File

@ -1 +0,0 @@
export { default as TableToolbar } from './table-toolbar.vue';

View File

@ -1,81 +0,0 @@
<!-- add by puhui999vxe table 工具栏二次封装提供给 vxe 原生列表使用 -->
<script setup lang="ts">
import type { VxeToolbarInstance } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { useContentMaximize, useRefresh } from '@vben/hooks';
import { IconifyIcon } from '@vben/icons';
import { Button, Tooltip } from 'ant-design-vue';
import { VxeToolbar } from '#/adapter/vxe-table';
/** 列表工具栏封装 */
defineOptions({ name: 'TableToolbar' });
const props = defineProps<{
hiddenSearch: boolean;
}>();
const emits = defineEmits(['update:hiddenSearch']);
const toolbarRef = ref<VxeToolbarInstance>();
const { toggleMaximizeAndTabbarHidden, contentIsMaximize } =
useContentMaximize();
const { refresh } = useRefresh();
/** 隐藏搜索栏 */
function onHiddenSearchBar() {
emits('update:hiddenSearch', !props.hiddenSearch);
}
defineExpose({
getToolbarRef: () => toolbarRef.value,
});
</script>
<template>
<VxeToolbar ref="toolbarRef" custom>
<template #toolPrefix>
<slot></slot>
<Tooltip placement="bottom">
<template #title>
<div class="max-w-52">搜索</div>
</template>
<Button
class="ml-2 font-normal"
shape="circle"
@click="onHiddenSearchBar"
>
<IconifyIcon icon="lucide:search" :size="15" />
</Button>
</Tooltip>
<Tooltip placement="bottom">
<template #title>
<div class="max-w-52">刷新</div>
</template>
<Button class="ml-2 font-medium" shape="circle" @click="refresh">
<IconifyIcon icon="lucide:refresh-cw" :size="15" />
</Button>
</Tooltip>
<Tooltip placement="bottom">
<template #title>
<div class="max-w-52">
{{ contentIsMaximize ? '还原' : '全屏' }}
</div>
</template>
<Button
class="ml-2 font-medium"
shape="circle"
@click="toggleMaximizeAndTabbarHidden"
>
<IconifyIcon
:icon="contentIsMaximize ? 'lucide:minimize' : 'lucide:maximize'"
:size="15"
/>
</Button>
</Tooltip>
</template>
</VxeToolbar>
</template>

View File

@ -1 +0,0 @@
export * from './use-table-toolbar';

View File

@ -1,15 +1,12 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { useAccess } from '@vben/access';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -105,9 +102,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns(
onActionClick?: OnActionClickFn<Demo03StudentApi.Demo03Student>,
): VxeTableGridOptions<Demo03StudentApi.Demo03Student>['columns'] {
export function useGridColumns(): VxeTableGridOptions<Demo03StudentApi.Demo03Student>['columns'] {
return [
{ type: 'checkbox', width: 40 },
{
@ -147,30 +142,10 @@ export function useGridColumns(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 200,
align: 'center',
width: 280,
fixed: 'right',
showOverflow: false,
cellRender: {
attrs: {
nameField: 'id',
nameTitle: '学生',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['infra:demo03-student:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['infra:demo03-student:delete']),
},
],
},
slots: { default: 'actions' },
},
];
}
@ -252,9 +227,7 @@ export function useDemo03CourseGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useDemo03CourseGridColumns(
onActionClick?: OnActionClickFn<Demo03StudentApi.Demo03Course>,
): VxeTableGridOptions<Demo03StudentApi.Demo03Course>['columns'] {
export function useDemo03CourseGridColumns(): VxeTableGridOptions<Demo03StudentApi.Demo03Course>['columns'] {
return [
{ type: 'checkbox', width: 40 },
{
@ -284,31 +257,10 @@ export function useDemo03CourseGridColumns(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 200,
align: 'center',
width: 280,
fixed: 'right',
showOverflow: false,
cellRender: {
attrs: {
nameField: 'id',
nameTitle: '学生课程',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['infra:demo03-student:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['infra:demo03-student:delete']),
},
],
},
slots: { default: 'actions' },
},
];
}
@ -390,9 +342,7 @@ export function useDemo03GradeGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useDemo03GradeGridColumns(
onActionClick?: OnActionClickFn<Demo03StudentApi.Demo03Grade>,
): VxeTableGridOptions<Demo03StudentApi.Demo03Grade>['columns'] {
export function useDemo03GradeGridColumns(): VxeTableGridOptions<Demo03StudentApi.Demo03Grade>['columns'] {
return [
{ type: 'checkbox', width: 40 },
{
@ -422,31 +372,10 @@ export function useDemo03GradeGridColumns(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 200,
align: 'center',
width: 280,
fixed: 'right',
showOverflow: false,
cellRender: {
attrs: {
nameField: 'id',
nameTitle: '学生班级',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['infra:demo03-student:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['infra:demo03-student:delete']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,8 +1,5 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { h, ref } from 'vue';
@ -13,7 +10,7 @@ import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { Button, message, Tabs } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteDemo03Student,
deleteDemo03StudentList,
@ -99,29 +96,12 @@ async function onExport() {
downloadFileFromBlobPart({ fileName: '学生.xls', source: data });
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<Demo03StudentApi.Demo03Student>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: '600px',
pagerConfig: {
enabled: true,
@ -193,6 +173,30 @@ const [Grid, gridApi] = useVbenVxeGrid({
批量删除
</Button>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['infra:demo03-student:update'],
onClick: onEdit.bind(null, row),
},
{
label: $t('common.delete'),
danger: true,
type: 'link',
icon: ACTION_ICON.DELETE,
auth: ['infra:demo03-student:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.id]),
confirm: onDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
<!-- 子表的表单 -->

View File

@ -1,8 +1,5 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { h, nextTick, ref, watch } from 'vue';
@ -13,7 +10,7 @@ import { isEmpty } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteDemo03Course,
deleteDemo03CourseList,
@ -92,29 +89,12 @@ function handleRowCheckboxChange({
checkedIds.value = records.map((item) => item.id!);
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<Demo03StudentApi.Demo03Course>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useDemo03CourseGridFormSchema(),
},
gridOptions: {
columns: useDemo03CourseGridColumns(onActionClick),
columns: useDemo03CourseGridColumns(),
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
@ -192,5 +172,29 @@ watch(
批量删除
</Button>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['infra:demo03-student:update'],
onClick: onEdit.bind(null, row),
},
{
label: $t('common.delete'),
danger: true,
type: 'link',
icon: ACTION_ICON.DELETE,
auth: ['infra:demo03-student:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.id]),
confirm: onDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</template>

View File

@ -1,8 +1,5 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { h, nextTick, ref, watch } from 'vue';
@ -13,7 +10,7 @@ import { isEmpty } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteDemo03Grade,
deleteDemo03GradeList,
@ -92,29 +89,12 @@ function handleRowCheckboxChange({
checkedIds.value = records.map((item) => item.id!);
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<Demo03StudentApi.Demo03Grade>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useDemo03GradeGridFormSchema(),
},
gridOptions: {
columns: useDemo03GradeGridColumns(onActionClick),
columns: useDemo03GradeGridColumns(),
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
@ -192,5 +172,29 @@ watch(
批量删除
</Button>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['infra:demo03-student:update'],
onClick: onEdit.bind(null, row),
},
{
label: $t('common.delete'),
danger: true,
type: 'link',
icon: ACTION_ICON.DELETE,
auth: ['infra:demo03-student:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.id]),
confirm: onDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</template>

View File

@ -1,15 +1,12 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { useAccess } from '@vben/access';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -105,9 +102,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns(
onActionClick?: OnActionClickFn<Demo03StudentApi.Demo03Student>,
): VxeTableGridOptions<Demo03StudentApi.Demo03Student>['columns'] {
export function useGridColumns(): VxeTableGridOptions<Demo03StudentApi.Demo03Student>['columns'] {
return [
{ type: 'checkbox', width: 40 },
{ type: 'expand', width: 80, slots: { content: 'expand_content' } },
@ -148,30 +143,11 @@ export function useGridColumns(
formatter: 'formatDateTime',
},
{
field: 'operation',
field: 'actions',
title: '操作',
minWidth: 200,
align: 'center',
width: 280,
fixed: 'right',
showOverflow: false,
cellRender: {
attrs: {
nameField: 'id',
nameTitle: '学生',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['infra:demo03-student:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['infra:demo03-student:delete']),
},
],
},
slots: { default: 'actions' },
},
];
}
@ -179,9 +155,7 @@ export function useGridColumns(
// ==================== 子表(学生课程) ====================
/** 新增/修改列表的字段 */
export function useDemo03CourseGridEditColumns(
onActionClick?: OnActionClickFn<Demo03StudentApi.Demo03Course>,
): VxeTableGridOptions<Demo03StudentApi.Demo03Course>['columns'] {
export function useDemo03CourseGridEditColumns(): VxeTableGridOptions<Demo03StudentApi.Demo03Course>['columns'] {
return [
{
field: 'name',
@ -196,26 +170,11 @@ export function useDemo03CourseGridEditColumns(
slots: { default: 'score' },
},
{
field: 'operation',
field: 'actions',
title: '操作',
minWidth: 60,
align: 'center',
width: 280,
fixed: 'right',
showOverflow: false,
cellRender: {
attrs: {
nameField: 'id',
nameTitle: '学生',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'delete',
show: hasAccessByCodes(['infra:demo03-student:delete']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,8 +1,5 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { h, ref } from 'vue';
@ -13,7 +10,7 @@ import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { Button, message, Tabs } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteDemo03Student,
deleteDemo03StudentList,
@ -98,29 +95,12 @@ async function onExport() {
downloadFileFromBlobPart({ fileName: '学生.xls', source: data });
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<Demo03StudentApi.Demo03Student>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
pagerConfig: {
enabled: true,
@ -198,6 +178,30 @@ const [Grid, gridApi] = useVbenVxeGrid({
批量删除
</Button>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['infra:demo03-student:update'],
onClick: onEdit.bind(null, row),
},
{
label: $t('common.delete'),
danger: true,
type: 'link',
icon: ACTION_ICON.DELETE,
auth: ['infra:demo03-student:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.id]),
confirm: onDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@ -1,5 +1,4 @@
<script lang="ts" setup>
import type { OnActionClickParams } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { h, nextTick, watch } from 'vue';
@ -8,7 +7,7 @@ import { Plus } from '@vben/icons';
import { Button, Input } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getDemo03CourseListByStudentId } from '#/api/infra/demo/demo03/inner';
import { $t } from '#/locales';
@ -18,22 +17,9 @@ const props = defineProps<{
studentId?: number; //
}>();
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<Demo03StudentApi.Demo03Course>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: useDemo03CourseGridEditColumns(onActionClick),
columns: useDemo03CourseGridEditColumns(),
border: true,
showOverflow: true,
autoResize: true,
@ -68,9 +54,12 @@ defineExpose({
gridApi.grid.getRemoveRecords() as Demo03StudentApi.Demo03Course[];
const insertRecords =
gridApi.grid.getInsertRecords() as Demo03StudentApi.Demo03Course[];
return data
.filter((row) => !removeRecords.some((removed) => removed.id === row.id))
.concat(insertRecords.map((row: any) => ({ ...row, id: undefined })));
return [
...data.filter(
(row) => !removeRecords.some((removed) => removed.id === row.id),
),
...insertRecords.map((row: any) => ({ ...row, id: undefined })),
];
},
});
@ -98,6 +87,23 @@ watch(
<template #score="{ row }">
<Input v-model:value="row.score" />
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.delete'),
danger: true,
type: 'link',
icon: ACTION_ICON.DELETE,
auth: ['infra:demo03-student:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.id]),
confirm: onDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
<div class="-mt-4 flex justify-center">
<Button

View File

@ -31,11 +31,11 @@ const [Grid, gridApi] = useVbenVxeGrid({
});
/** 刷新表格 */
const onRefresh = async () => {
async function onRefresh() {
await gridApi.grid.loadData(
await getDemo03CourseListByStudentId(props.studentId!),
);
};
}
/** 监听主表的关联字段的变化,加载对应的子表数据 */
watch(

View File

@ -31,11 +31,11 @@ const [Grid, gridApi] = useVbenVxeGrid({
});
/** 刷新表格 */
const onRefresh = async () => {
async function onRefresh() {
await gridApi.grid.loadData([
await getDemo03GradeByStudentId(props.studentId!),
]);
};
}
/** 监听主表的关联字段的变化,加载对应的子表数据 */
watch(

View File

@ -61,8 +61,8 @@ const [Modal, modalApi] = useVbenModal({
//
const data = (await formApi.getValues()) as Demo03StudentApi.Demo03Student;
//
data.demo03Courses = demo03CourseFormRef.value?.getData();
data.demo03Grade = await demo03GradeFormRef.value?.getValues();
data.demo03courses = demo03CourseFormRef.value?.getData();
data.demo03grade = await demo03GradeFormRef.value?.getValues();
try {
await (formData.value?.id
? updateDemo03Student(data)

View File

@ -1,15 +1,12 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import { useAccess } from '@vben/access';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -105,9 +102,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns(
onActionClick?: OnActionClickFn<Demo03StudentApi.Demo03Student>,
): VxeTableGridOptions<Demo03StudentApi.Demo03Student>['columns'] {
export function useGridColumns(): VxeTableGridOptions<Demo03StudentApi.Demo03Student>['columns'] {
return [
{ type: 'checkbox', width: 40 },
{
@ -141,30 +136,11 @@ export function useGridColumns(
formatter: 'formatDateTime',
},
{
field: 'operation',
field: 'actions',
title: '操作',
minWidth: 200,
align: 'center',
width: 280,
fixed: 'right',
showOverflow: false,
cellRender: {
attrs: {
nameField: 'id',
nameTitle: '学生',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['infra:demo03-student:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['infra:demo03-student:delete']),
},
],
},
slots: { default: 'actions' },
},
];
}
@ -172,9 +148,7 @@ export function useGridColumns(
// ==================== 子表(学生课程) ====================
/** 新增/修改列表的字段 */
export function useDemo03CourseGridEditColumns(
onActionClick?: OnActionClickFn<Demo03StudentApi.Demo03Course>,
): VxeTableGridOptions<Demo03StudentApi.Demo03Course>['columns'] {
export function useDemo03CourseGridEditColumns(): VxeTableGridOptions<Demo03StudentApi.Demo03Course>['columns'] {
return [
{
field: 'name',
@ -187,26 +161,11 @@ export function useDemo03CourseGridEditColumns(
slots: { default: 'score' },
},
{
field: 'operation',
field: 'actions',
title: '操作',
minWidth: 60,
align: 'center',
width: 280,
fixed: 'right',
showOverflow: false,
cellRender: {
attrs: {
nameField: 'id',
nameTitle: '学生',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'delete',
show: hasAccessByCodes(['infra:demo03-student:delete']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,8 +1,5 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import { h, ref } from 'vue';
@ -13,7 +10,7 @@ import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteDemo03Student,
deleteDemo03StudentList,
@ -92,29 +89,12 @@ async function onExport() {
downloadFileFromBlobPart({ fileName: '学生.xls', source: data });
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<Demo03StudentApi.Demo03Student>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
pagerConfig: {
enabled: true,
@ -181,6 +161,30 @@ const [Grid, gridApi] = useVbenVxeGrid({
批量删除
</Button>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['infra:demo03-student:update'],
onClick: onEdit.bind(null, row),
},
{
label: $t('common.delete'),
danger: true,
type: 'link',
icon: ACTION_ICON.DELETE,
auth: ['infra:demo03-student:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.id]),
confirm: onDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@ -1,5 +1,4 @@
<script lang="ts" setup>
import type { OnActionClickParams } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import { h, nextTick, watch } from 'vue';
@ -8,7 +7,7 @@ import { Plus } from '@vben/icons';
import { Button, Input } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getDemo03CourseListByStudentId } from '#/api/infra/demo/demo03/normal';
import { $t } from '#/locales';
@ -18,22 +17,9 @@ const props = defineProps<{
studentId?: number; //
}>();
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<Demo03StudentApi.Demo03Course>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: useDemo03CourseGridEditColumns(onActionClick),
columns: useDemo03CourseGridEditColumns(),
border: true,
showOverflow: true,
autoResize: true,
@ -68,9 +54,12 @@ defineExpose({
gridApi.grid.getRemoveRecords() as Demo03StudentApi.Demo03Course[];
const insertRecords =
gridApi.grid.getInsertRecords() as Demo03StudentApi.Demo03Course[];
return data
.filter((row) => !removeRecords.some((removed) => removed.id === row.id))
.concat(insertRecords.map((row: any) => ({ ...row, id: undefined })));
return [
...data.filter(
(row) => !removeRecords.some((removed) => removed.id === row.id),
),
...insertRecords.map((row: any) => ({ ...row, id: undefined })),
];
},
});
@ -98,6 +87,23 @@ watch(
<template #score="{ row }">
<Input v-model:value="row.score" />
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.delete'),
danger: true,
type: 'link',
icon: ACTION_ICON.DELETE,
auth: ['infra:demo03-student:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.id]),
confirm: onDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
<div class="-mt-4 flex justify-center">
<Button

View File

@ -61,8 +61,8 @@ const [Modal, modalApi] = useVbenModal({
//
const data = (await formApi.getValues()) as Demo03StudentApi.Demo03Student;
//
data.demo03Courses = demo03CourseFormRef.value?.getData();
data.demo03Grade = await demo03GradeFormRef.value?.getValues();
data.demo03courses = demo03CourseFormRef.value?.getData();
data.demo03grade = await demo03GradeFormRef.value?.getValues();
try {
await (formData.value?.id
? updateDemo03Student(data)

View File

@ -7,6 +7,7 @@ import { Page, useVbenModal } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { Download, Plus, Trash2 } from '@vben/icons';
import { useTableToolbar, VbenVxeTableToolbar } from '@vben/plugins/vxe-table';
import {
cloneDeep,
downloadFileFromBlobPart,
@ -33,8 +34,6 @@ import {
} from '#/api/infra/demo/demo01';
import { ContentWrap } from '#/components/content-wrap';
import { DictTag } from '#/components/dict-tag';
import { TableToolbar } from '#/components/table-toolbar';
import { useTableToolbar } from '#/hooks';
import { $t } from '#/locales';
import { getRangePickerDefaultProps } from '#/utils';
@ -55,7 +54,7 @@ const queryFormRef = ref(); // 搜索的表单
const exportLoading = ref(false); //
/** 查询列表 */
const getList = async () => {
async function getList() {
loading.value = true;
try {
const params = cloneDeep(queryParams) as any;
@ -68,19 +67,19 @@ const getList = async () => {
} finally {
loading.value = false;
}
};
}
/** 搜索按钮操作 */
const handleQuery = () => {
function handleQuery() {
queryParams.pageNo = 1;
getList();
};
}
/** 重置按钮操作 */
const resetQuery = () => {
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
};
}
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Demo01ContactForm,
@ -214,7 +213,7 @@ onMounted(() => {
<!-- 列表 -->
<ContentWrap title="示例联系人">
<template #extra>
<TableToolbar
<VbenVxeTableToolbar
ref="tableToolbarRef"
v-model:hidden-search="hiddenSearchBar"
>
@ -248,7 +247,7 @@ onMounted(() => {
>
批量删除
</Button>
</TableToolbar>
</VbenVxeTableToolbar>
</template>
<VxeTable
ref="tableRef"
@ -283,7 +282,7 @@ onMounted(() => {
<Button
size="small"
type="link"
@click="handleEdit(row as any)"
@click="handleEdit(row)"
v-access:code="['infra:demo01-contact:update']"
>
{{ $t('ui.actionTitle.edit') }}
@ -293,7 +292,7 @@ onMounted(() => {
type="link"
danger
class="ml-2"
@click="handleDelete(row as any)"
@click="handleDelete(row)"
v-access:code="['infra:demo01-contact:delete']"
>
{{ $t('ui.actionTitle.delete') }}

View File

@ -51,7 +51,7 @@ const getTitle = computed(() => {
});
/** 重置表单 */
const resetForm = () => {
function resetForm() {
formData.value = {
id: undefined,
name: undefined,
@ -61,7 +61,7 @@ const resetForm = () => {
avatar: undefined,
};
formRef.value?.resetFields();
};
}
const [Modal, modalApi] = useVbenModal({
async onConfirm() {

View File

@ -5,6 +5,7 @@ import { h, onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { useTableToolbar, VbenVxeTableToolbar } from '@vben/plugins/vxe-table';
import {
cloneDeep,
downloadFileFromBlobPart,
@ -21,8 +22,6 @@ import {
getDemo02CategoryList,
} from '#/api/infra/demo/demo02';
import { ContentWrap } from '#/components/content-wrap';
import { TableToolbar } from '#/components/table-toolbar';
import { useTableToolbar } from '#/hooks';
import { $t } from '#/locales';
import { getRangePickerDefaultProps } from '#/utils';
@ -40,7 +39,7 @@ const queryFormRef = ref(); // 搜索的表单
const exportLoading = ref(false); //
/** 查询列表 */
const getList = async () => {
async function getList() {
loading.value = true;
try {
const params = cloneDeep(queryParams) as any;
@ -51,18 +50,18 @@ const getList = async () => {
} finally {
loading.value = false;
}
};
}
/** 搜索按钮操作 */
const handleQuery = () => {
function handleQuery() {
getList();
};
}
/** 重置按钮操作 */
const resetQuery = () => {
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
};
}
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Demo02CategoryForm,
@ -171,7 +170,7 @@ onMounted(() => {
<!-- 列表 -->
<ContentWrap title="示例分类">
<template #extra>
<TableToolbar
<VbenVxeTableToolbar
ref="tableToolbarRef"
v-model:hidden-search="hiddenSearchBar"
>
@ -197,7 +196,7 @@ onMounted(() => {
>
{{ $t('ui.actionTitle.export') }}
</Button>
</TableToolbar>
</VbenVxeTableToolbar>
</template>
<VxeTable
ref="tableRef"
@ -225,7 +224,7 @@ onMounted(() => {
<Button
size="small"
type="link"
@click="onAppend(row as any)"
@click="onAppend(row)"
v-access:code="['infra:demo02-category:create']"
>
新增下级
@ -233,7 +232,7 @@ onMounted(() => {
<Button
size="small"
type="link"
@click="onEdit(row as any)"
@click="onEdit(row)"
v-access:code="['infra:demo02-category:update']"
>
{{ $t('ui.actionTitle.edit') }}
@ -243,8 +242,8 @@ onMounted(() => {
type="link"
danger
class="ml-2"
:disabled="!isEmpty(row?.children)"
@click="onDelete(row as any)"
:disabled="!isEmpty(row.children)"
@click="onDelete(row)"
v-access:code="['infra:demo02-category:delete']"
>
{{ $t('ui.actionTitle.delete') }}

View File

@ -38,17 +38,17 @@ const getTitle = computed(() => {
});
/** 重置表单 */
const resetForm = () => {
function resetForm() {
formData.value = {
id: undefined,
name: undefined,
parentId: undefined,
};
formRef.value?.resetFields();
};
}
/** 获得示例分类树 */
const getDemo02CategoryTree = async () => {
async function getDemo02CategoryTree() {
demo02CategoryTree.value = [];
const data = await getDemo02CategoryList({});
data.unshift({
@ -56,7 +56,7 @@ const getDemo02CategoryTree = async () => {
name: '顶级示例分类',
});
demo02CategoryTree.value = handleTree(data);
};
}
const [Modal, modalApi] = useVbenModal({
async onConfirm() {

View File

@ -7,6 +7,7 @@ import { Page, useVbenModal } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { Download, Plus, Trash2 } from '@vben/icons';
import { useTableToolbar, VbenVxeTableToolbar } from '@vben/plugins/vxe-table';
import {
cloneDeep,
downloadFileFromBlobPart,
@ -34,8 +35,6 @@ import {
} from '#/api/infra/demo/demo03/erp';
import { ContentWrap } from '#/components/content-wrap';
import { DictTag } from '#/components/dict-tag';
import { TableToolbar } from '#/components/table-toolbar';
import { useTableToolbar } from '#/hooks';
import { $t } from '#/locales';
import { getRangePickerDefaultProps } from '#/utils';
@ -66,7 +65,7 @@ const queryFormRef = ref(); // 搜索的表单
const exportLoading = ref(false); //
/** 查询列表 */
const getList = async () => {
async function getList() {
loading.value = true;
try {
const params = cloneDeep(queryParams) as any;
@ -79,7 +78,7 @@ const getList = async () => {
} finally {
loading.value = false;
}
};
}
/** 搜索按钮操作 */
const handleQuery = () => {
@ -88,10 +87,10 @@ const handleQuery = () => {
};
/** 重置按钮操作 */
const resetQuery = () => {
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
};
}
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Demo03StudentForm,
@ -225,7 +224,7 @@ onMounted(() => {
<!-- 列表 -->
<ContentWrap title="学生">
<template #extra>
<TableToolbar
<VbenVxeTableToolbar
ref="tableToolbarRef"
v-model:hidden-search="hiddenSearchBar"
>
@ -259,7 +258,7 @@ onMounted(() => {
>
批量删除
</Button>
</TableToolbar>
</VbenVxeTableToolbar>
</template>
<VxeTable
ref="tableRef"
@ -299,7 +298,7 @@ onMounted(() => {
<Button
size="small"
type="link"
@click="onEdit(row as any)"
@click="onEdit(row)"
v-access:code="['infra:demo03-student:update']"
>
{{ $t('ui.actionTitle.edit') }}
@ -309,7 +308,7 @@ onMounted(() => {
type="link"
danger
class="ml-2"
@click="onDelete(row as any)"
@click="onDelete(row)"
v-access:code="['infra:demo03-student:delete']"
>
{{ $t('ui.actionTitle.delete') }}

View File

@ -80,7 +80,7 @@ const [Modal, modalApi] = useVbenModal({
});
/** 重置表单 */
const resetForm = () => {
function resetForm() {
formData.value = {
id: undefined,
studentId: undefined,
@ -88,7 +88,7 @@ const resetForm = () => {
score: undefined,
};
formRef.value?.resetFields();
};
}
</script>
<template>

View File

@ -5,6 +5,7 @@ import { h, nextTick, onMounted, reactive, ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Plus, Trash2 } from '@vben/icons';
import { useTableToolbar, VbenVxeTableToolbar } from '@vben/plugins/vxe-table';
import { cloneDeep, formatDateTime, isEmpty } from '@vben/utils';
import {
@ -23,8 +24,6 @@ import {
getDemo03CoursePage,
} from '#/api/infra/demo/demo03/erp';
import { ContentWrap } from '#/components/content-wrap';
import { TableToolbar } from '#/components/table-toolbar';
import { useTableToolbar } from '#/hooks';
import { $t } from '#/locales';
import { getRangePickerDefaultProps } from '#/utils';
@ -112,18 +111,18 @@ const queryParams = reactive({
});
/** 搜索按钮操作 */
const handleQuery = () => {
function handleQuery() {
queryParams.pageNo = 1;
getList();
};
}
/** 重置按钮操作 */
const resetQuery = () => {
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
};
}
/** 查询列表 */
const getList = async () => {
async function getList() {
loading.value = true;
try {
if (!props.studentId) {
@ -140,7 +139,7 @@ const getList = async () => {
} finally {
loading.value = false;
}
};
}
/** 监听主表的关联字段的变化,加载对应的子表数据 */
watch(
@ -214,7 +213,7 @@ onMounted(() => {
<!-- 列表 -->
<ContentWrap title="学生">
<template #extra>
<TableToolbar
<VbenVxeTableToolbar
ref="tableToolbarRef"
v-model:hidden-search="hiddenSearchBar"
>
@ -238,7 +237,7 @@ onMounted(() => {
>
批量删除
</Button>
</TableToolbar>
</VbenVxeTableToolbar>
</template>
<VxeTable
ref="tableRef"
@ -263,7 +262,7 @@ onMounted(() => {
<Button
size="small"
type="link"
@click="onEdit(row as any)"
@click="onEdit(row)"
v-access:code="['infra:demo03-student:update']"
>
{{ $t('ui.actionTitle.edit') }}
@ -273,7 +272,7 @@ onMounted(() => {
type="link"
danger
class="ml-2"
@click="onDelete(row as any)"
@click="onDelete(row)"
v-access:code="['infra:demo03-student:delete']"
>
{{ $t('ui.actionTitle.delete') }}

View File

@ -80,7 +80,7 @@ const [Modal, modalApi] = useVbenModal({
});
/** 重置表单 */
const resetForm = () => {
function resetForm() {
formData.value = {
id: undefined,
studentId: undefined,
@ -88,7 +88,7 @@ const resetForm = () => {
teacher: undefined,
};
formRef.value?.resetFields();
};
}
</script>
<template>

View File

@ -5,6 +5,7 @@ import { h, nextTick, onMounted, reactive, ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Plus, Trash2 } from '@vben/icons';
import { useTableToolbar, VbenVxeTableToolbar } from '@vben/plugins/vxe-table';
import { cloneDeep, formatDateTime, isEmpty } from '@vben/utils';
import {
@ -23,8 +24,6 @@ import {
getDemo03GradePage,
} from '#/api/infra/demo/demo03/erp';
import { ContentWrap } from '#/components/content-wrap';
import { TableToolbar } from '#/components/table-toolbar';
import { useTableToolbar } from '#/hooks';
import { $t } from '#/locales';
import { getRangePickerDefaultProps } from '#/utils';
@ -112,18 +111,18 @@ const queryParams = reactive({
});
/** 搜索按钮操作 */
const handleQuery = () => {
function handleQuery() {
queryParams.pageNo = 1;
getList();
};
}
/** 重置按钮操作 */
const resetQuery = () => {
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
};
}
/** 查询列表 */
const getList = async () => {
async function getList() {
loading.value = true;
try {
if (!props.studentId) {
@ -140,7 +139,7 @@ const getList = async () => {
} finally {
loading.value = false;
}
};
}
/** 监听主表的关联字段的变化,加载对应的子表数据 */
watch(
@ -214,7 +213,7 @@ onMounted(() => {
<!-- 列表 -->
<ContentWrap title="学生">
<template #extra>
<TableToolbar
<VbenVxeTableToolbar
ref="tableToolbarRef"
v-model:hidden-search="hiddenSearchBar"
>
@ -238,7 +237,7 @@ onMounted(() => {
>
批量删除
</Button>
</TableToolbar>
</VbenVxeTableToolbar>
</template>
<VxeTable
ref="tableRef"
@ -263,7 +262,7 @@ onMounted(() => {
<Button
size="small"
type="link"
@click="onEdit(row as any)"
@click="onEdit(row)"
v-access:code="['infra:demo03-student:update']"
>
{{ $t('ui.actionTitle.edit') }}
@ -273,7 +272,7 @@ onMounted(() => {
type="link"
danger
class="ml-2"
@click="onDelete(row as any)"
@click="onDelete(row)"
v-access:code="['infra:demo03-student:delete']"
>
{{ $t('ui.actionTitle.delete') }}

View File

@ -49,7 +49,7 @@ const getTitle = computed(() => {
});
/** 重置表单 */
const resetForm = () => {
function resetForm() {
formData.value = {
id: undefined,
name: undefined,
@ -58,7 +58,7 @@ const resetForm = () => {
description: undefined,
};
formRef.value?.resetFields();
};
}
const [Modal, modalApi] = useVbenModal({
async onConfirm() {

View File

@ -7,6 +7,7 @@ import { Page, useVbenModal } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { Download, Plus, Trash2 } from '@vben/icons';
import { useTableToolbar, VbenVxeTableToolbar } from '@vben/plugins/vxe-table';
import {
cloneDeep,
downloadFileFromBlobPart,
@ -34,8 +35,6 @@ import {
} from '#/api/infra/demo/demo03/normal';
import { ContentWrap } from '#/components/content-wrap';
import { DictTag } from '#/components/dict-tag';
import { TableToolbar } from '#/components/table-toolbar';
import { useTableToolbar } from '#/hooks';
import { $t } from '#/locales';
import { getRangePickerDefaultProps } from '#/utils';
@ -62,7 +61,7 @@ const queryFormRef = ref(); // 搜索的表单
const exportLoading = ref(false); //
/** 查询列表 */
const getList = async () => {
async function getList() {
loading.value = true;
try {
const params = cloneDeep(queryParams) as any;
@ -75,19 +74,19 @@ const getList = async () => {
} finally {
loading.value = false;
}
};
}
/** 搜索按钮操作 */
const handleQuery = () => {
function handleQuery() {
queryParams.pageNo = 1;
getList();
};
}
/** 重置按钮操作 */
const resetQuery = () => {
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
};
}
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Demo03StudentForm,
@ -221,7 +220,7 @@ onMounted(() => {
<!-- 列表 -->
<ContentWrap title="学生">
<template #extra>
<TableToolbar
<VbenVxeTableToolbar
ref="tableToolbarRef"
v-model:hidden-search="hiddenSearchBar"
>
@ -255,7 +254,7 @@ onMounted(() => {
>
批量删除
</Button>
</TableToolbar>
</VbenVxeTableToolbar>
</template>
<VxeTable
ref="tableRef"
@ -303,7 +302,7 @@ onMounted(() => {
<Button
size="small"
type="link"
@click="onEdit(row as any)"
@click="onEdit(row)"
v-access:code="['infra:demo03-student:update']"
>
{{ $t('ui.actionTitle.edit') }}
@ -313,7 +312,7 @@ onMounted(() => {
type="link"
danger
class="ml-2"
@click="onDelete(row as any)"
@click="onDelete(row)"
v-access:code="['infra:demo03-student:delete']"
>
{{ $t('ui.actionTitle.delete') }}

View File

@ -1,5 +1,6 @@
<script lang="ts" setup>
import type { VxeTableInstance } from '#/adapter/vxe-table';
import type { VxeTableInstance } from '@vben/plugins/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import { h, ref, watch } from 'vue';
@ -19,14 +20,14 @@ const props = defineProps<{
const list = ref<Demo03StudentApi.Demo03Course[]>([]); //
const tableRef = ref<VxeTableInstance>();
/** 添加学生课程 */
const onAdd = async () => {
async function onAdd() {
await tableRef.value?.insertAt({} as Demo03StudentApi.Demo03Course, -1);
};
}
/** 删除学生课程 */
const onDelete = async (row: Demo03StudentApi.Demo03Course) => {
async function onDelete(row: Demo03StudentApi.Demo03Course) {
await tableRef.value?.remove(row);
};
}
/** 提供获取表格数据的方法供父组件调用 */
defineExpose({
@ -73,7 +74,7 @@ watch(
size="small"
type="link"
danger
@click="onDelete(row as any)"
@click="onDelete(row)"
v-access:code="['infra:demo03-student:delete']"
>
{{ $t('ui.actionTitle.delete') }}

View File

@ -16,7 +16,7 @@ const props = defineProps<{
const loading = ref(true); //
const list = ref<Demo03StudentApi.Demo03Course[]>([]); //
/** 查询列表 */
const getList = async () => {
async function getList() {
loading.value = true;
try {
if (!props.studentId) {
@ -26,7 +26,7 @@ const getList = async () => {
} finally {
loading.value = false;
}
};
}
/** 监听主表的关联字段的变化,加载对应的子表数据 */
watch(

View File

@ -15,7 +15,7 @@ const props = defineProps<{
const loading = ref(true); //
const list = ref<Demo03StudentApi.Demo03Grade[]>([]); //
/** 查询列表 */
const getList = async () => {
async function getList() {
loading.value = true;
try {
if (!props.studentId) {
@ -25,7 +25,7 @@ const getList = async () => {
} finally {
loading.value = false;
}
};
}
/** 监听主表的关联字段的变化,加载对应的子表数据 */
watch(

View File

@ -58,7 +58,7 @@ const demo03CourseFormRef = ref<InstanceType<typeof Demo03CourseForm>>();
const demo03GradeFormRef = ref<InstanceType<typeof Demo03GradeForm>>();
/** 重置表单 */
const resetForm = () => {
function resetForm() {
formData.value = {
id: undefined,
name: undefined,
@ -67,7 +67,7 @@ const resetForm = () => {
description: undefined,
};
formRef.value?.resetFields();
};
}
const [Modal, modalApi] = useVbenModal({
async onConfirm() {

View File

@ -7,6 +7,7 @@ import { Page, useVbenModal } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { Download, Plus, Trash2 } from '@vben/icons';
import { useTableToolbar, VbenVxeTableToolbar } from '@vben/plugins/vxe-table';
import {
cloneDeep,
downloadFileFromBlobPart,
@ -33,8 +34,6 @@ import {
} from '#/api/infra/demo/demo03/normal';
import { ContentWrap } from '#/components/content-wrap';
import { DictTag } from '#/components/dict-tag';
import { TableToolbar } from '#/components/table-toolbar';
import { useTableToolbar } from '#/hooks';
import { $t } from '#/locales';
import { getRangePickerDefaultProps } from '#/utils';
@ -56,7 +55,7 @@ const queryFormRef = ref(); // 搜索的表单
const exportLoading = ref(false); //
/** 查询列表 */
const getList = async () => {
async function getList() {
loading.value = true;
try {
const params = cloneDeep(queryParams) as any;
@ -69,19 +68,19 @@ const getList = async () => {
} finally {
loading.value = false;
}
};
}
/** 搜索按钮操作 */
const handleQuery = () => {
function handleQuery() {
queryParams.pageNo = 1;
getList();
};
}
/** 重置按钮操作 */
const resetQuery = () => {
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
};
}
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Demo03StudentForm,
@ -215,7 +214,7 @@ onMounted(() => {
<!-- 列表 -->
<ContentWrap title="学生">
<template #extra>
<TableToolbar
<VbenVxeTableToolbar
ref="tableToolbarRef"
v-model:hidden-search="hiddenSearchBar"
>
@ -249,7 +248,7 @@ onMounted(() => {
>
批量删除
</Button>
</TableToolbar>
</VbenVxeTableToolbar>
</template>
<VxeTable
ref="tableRef"
@ -283,7 +282,7 @@ onMounted(() => {
<Button
size="small"
type="link"
@click="onEdit(row as any)"
@click="onEdit(row)"
v-access:code="['infra:demo03-student:update']"
>
{{ $t('ui.actionTitle.edit') }}
@ -293,7 +292,7 @@ onMounted(() => {
type="link"
danger
class="ml-2"
@click="onDelete(row as any)"
@click="onDelete(row)"
v-access:code="['infra:demo03-student:delete']"
>
{{ $t('ui.actionTitle.delete') }}

View File

@ -19,14 +19,14 @@ const props = defineProps<{
const list = ref<Demo03StudentApi.Demo03Course[]>([]); //
const tableRef = ref<VxeTableInstance>();
/** 添加学生课程 */
const onAdd = async () => {
async function onAdd() {
await tableRef.value?.insertAt({} as Demo03StudentApi.Demo03Course, -1);
};
}
/** 删除学生课程 */
const onDelete = async (row: Demo03StudentApi.Demo03Course) => {
async function onDelete(row: Demo03StudentApi.Demo03Course) {
await tableRef.value?.remove(row);
};
}
/** 提供获取表格数据的方法供父组件调用 */
defineExpose({
@ -73,7 +73,7 @@ watch(
size="small"
type="link"
danger
@click="onDelete(row as any)"
@click="onDelete(row)"
v-access:code="['infra:demo03-student:delete']"
>
{{ $t('ui.actionTitle.delete') }}

View File

@ -1,13 +1,27 @@
export { AsyncComponents, setupVbenVxeTable } from './init';
import { defineAsyncComponent } from 'vue';
export { setupVbenVxeTable } from './init';
export { default as VbenVxeTableToolbar } from './table-toolbar.vue';
export type { VxeTableGridOptions } from './types';
export * from './use-vxe-grid';
export { default as VbenVxeGrid } from './use-vxe-grid.vue';
export { useTableToolbar } from './use-vxe-toolbar';
export * from './validation';
export type {
VxeGridListeners,
VxeGridProps,
VxeGridPropTypes,
VxeTableInstance,
VxeToolbarInstance,
} from 'vxe-table';
// 异步导出 vxe-table 相关组件提供给需要单独使用 vxe-table 的场景
export const AsyncVxeTable = defineAsyncComponent(() =>
import('vxe-table').then((mod) => mod.VxeTable),
);
export const AsyncVxeColumn = defineAsyncComponent(() =>
import('vxe-table').then((mod) => mod.VxeColumn),
);
export const AsyncVxeToolbar = defineAsyncComponent(() =>
import('vxe-table').then((mod) => mod.VxeToolbar),
);

View File

@ -1,6 +1,6 @@
import type { SetupVxeTable } from './types';
import { defineAsyncComponent, defineComponent, watch } from 'vue';
import { defineComponent, watch } from 'vue';
import { usePreferences } from '@vben/preferences';
@ -100,18 +100,6 @@ export function initVxeTable() {
isInit = true;
}
// 异步导出 vxe-table 相关组件提供给需要单独使用 vxe-table 的场景
const AsyncVxeTable = defineAsyncComponent(() =>
import('vxe-table').then((mod) => mod.VxeTable),
);
const AsyncVxeColumn = defineAsyncComponent(() =>
import('vxe-table').then((mod) => mod.VxeColumn),
);
const AsyncVxeToolbar = defineAsyncComponent(() =>
import('vxe-table').then((mod) => mod.VxeToolbar),
);
export const AsyncComponents = [AsyncVxeTable, AsyncVxeColumn, AsyncVxeToolbar];
export function setupVbenVxeTable(setupOptions: SetupVxeTable) {
const { configVxeTable, useVbenForm } = setupOptions;

View File

@ -0,0 +1,74 @@
<!-- add by puhui999vxe table 工具栏二次封装提供给 vxe 原生列表使用 -->
<script setup lang="ts">
import type { VxeToolbarInstance } from 'vxe-table';
import { ref } from 'vue';
import { useContentMaximize, useRefresh } from '@vben/hooks';
import { IconifyIcon } from '@vben/icons';
import { VxeButton, VxeTooltip } from 'vxe-pc-ui';
import { VxeToolbar } from 'vxe-table';
/** 列表工具栏封装 */
defineOptions({ name: 'TableToolbar' });
const props = defineProps<{
hiddenSearch: boolean;
}>();
const emits = defineEmits(['update:hiddenSearch']);
const toolbarRef = ref<VxeToolbarInstance>();
const { toggleMaximizeAndTabbarHidden, contentIsMaximize } =
useContentMaximize();
const { refresh } = useRefresh();
/** 隐藏搜索栏 */
function onHiddenSearchBar() {
emits('update:hiddenSearch', !props.hiddenSearch);
}
defineExpose({
getToolbarRef: () => toolbarRef.value,
});
</script>
<template>
<VxeToolbar ref="toolbarRef" custom>
<template #toolPrefix>
<slot></slot>
<VxeTooltip placement="bottom" content="搜索">
<template #default>
<VxeButton class="ml-2 font-normal" circle @click="onHiddenSearchBar">
<IconifyIcon icon="lucide:search" :size="15" />
</VxeButton>
</template>
</VxeTooltip>
<VxeTooltip
placement="bottom"
:content="contentIsMaximize ? '还原' : '全屏'"
>
<template #default>
<VxeButton class="ml-2 font-medium" circle @click="refresh">
<IconifyIcon icon="lucide:refresh-cw" :size="15" />
</VxeButton>
</template>
</VxeTooltip>
<VxeTooltip placement="bottom" content="全屏">
<template #default>
<VxeButton
class="ml-2 font-medium"
circle
@click="toggleMaximizeAndTabbarHidden"
>
<IconifyIcon
:icon="contentIsMaximize ? 'lucide:minimize' : 'lucide:maximize'"
:size="15"
/>
</VxeButton>
</template>
</VxeTooltip>
</template>
</VxeToolbar>
</template>

View File

@ -1,15 +1,16 @@
import type { VxeTableInstance, VxeToolbarInstance } from '#/adapter/vxe-table';
import type { TableToolbar } from '#/components/table-toolbar';
import type { VxeTableInstance, VxeToolbarInstance } from 'vxe-table';
import { ref, watch } from 'vue';
import VbenVxeTableToolbar from './table-toolbar.vue';
/**
* vxe
* 使 vxe-table
*/
export function useTableToolbar() {
const hiddenSearchBar = ref(false); // 隐藏搜索栏
const tableToolbarRef = ref<InstanceType<typeof TableToolbar>>();
const tableToolbarRef = ref<InstanceType<typeof VbenVxeTableToolbar>>();
const tableRef = ref<VxeTableInstance>();
const isBound = ref<boolean>(false);
@ -24,7 +25,7 @@ export function useTableToolbar() {
if (!toolbar) {
console.error('[toolbar 挂载失败] Table toolbar not found');
}
await table.connect(toolbar as VxeToolbarInstance);
await table.connectToolbar(toolbar as VxeToolbarInstance);
isBound.value = true;
}, 1000); // 延迟挂载确保 toolbar 正确挂载
}