fix: 修复文件管理/菜单/权限/工作流多处 UI 问题并优化弹窗
- 文件管理:列表高度自适应、文件大小展示、文件夹递归总大小 - 菜单/权限管理:树表默认折叠、菜单列宽自适应 - 菜单编辑弹窗:加宽、移除允许角色/组件路径/重定向字段 - 工作流:节点选中框位置偏移修复 - 弹窗:统一默认宽度 640px,修正 width prop 失效 - 图标选择:Popover 小浮层改为居中 Dialog 弹窗 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
c6d3079e54
commit
d5084cdf70
@ -110,6 +110,8 @@ export namespace FolderApi {
|
||||
parentId: null | string;
|
||||
name: string;
|
||||
ownerId: string;
|
||||
/** 该文件夹下所有文件递归汇总的字节大小(getFolderTree 计算)。 */
|
||||
size: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { createApp, watchEffect } from 'vue';
|
||||
|
||||
import { registerAccessDirective } from '@vben/access';
|
||||
import { setDefaultModalProps } from '@vben/common-ui';
|
||||
import { registerLoadingDirective } from '@vben/common-ui/es/loading';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { initStores } from '@vben/stores';
|
||||
@ -23,14 +24,11 @@ async function bootstrap(namespace: string) {
|
||||
// 初始化表单组件
|
||||
await initSetupVbenForm();
|
||||
|
||||
// // 设置弹窗的默认配置
|
||||
// setDefaultModalProps({
|
||||
// fullscreenButton: false,
|
||||
// });
|
||||
// // 设置抽屉的默认配置
|
||||
// setDefaultDrawerProps({
|
||||
// zIndex: 1020,
|
||||
// });
|
||||
// 设置弹窗的默认配置:统一默认宽度 640px(覆盖框架写死的 w-130/520px)。
|
||||
// 个别弹窗可在自身 <Modal class="w-[Npx]"> 上覆盖(per-modal class 优先级高于此默认)。
|
||||
setDefaultModalProps({
|
||||
class: 'w-[640px]',
|
||||
});
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
|
||||
@ -39,6 +39,7 @@ export function buildFileManagerRows(
|
||||
itemType: 'folder',
|
||||
name: folder.name,
|
||||
rowKey: `folder:${folder.id}`,
|
||||
size: folder.size,
|
||||
}));
|
||||
|
||||
const fileRows: FileManagerRow[] = files
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import type { FileManagerRow } from './data';
|
||||
|
||||
import type {
|
||||
OnActionClickParams,
|
||||
VxeTableGridOptions,
|
||||
} from '#/adapter/vxe-table';
|
||||
import type { FolderApi, ShareApi } from '#/api/core/file';
|
||||
import type { FileManagerRow } from './data';
|
||||
|
||||
import { computed, nextTick, reactive, ref } from 'vue';
|
||||
|
||||
@ -54,16 +55,16 @@ import {
|
||||
} from '#/api/core/file';
|
||||
import { uploadFileWithResume } from '#/api/core/multipart-uploader';
|
||||
|
||||
import {
|
||||
canMoveFileToFolder,
|
||||
getDraggedFileId,
|
||||
setDraggedFileId,
|
||||
} from './drag';
|
||||
import {
|
||||
buildFileManagerRows,
|
||||
useFileManagerColumns,
|
||||
useGridFormSchema,
|
||||
} from './data';
|
||||
import {
|
||||
canMoveFileToFolder,
|
||||
getDraggedFileId,
|
||||
setDraggedFileId,
|
||||
} from './drag';
|
||||
|
||||
defineOptions({ name: 'FileManagerPage' });
|
||||
|
||||
@ -98,7 +99,10 @@ async function loadCurrentRows(keyword?: string) {
|
||||
getFolderTree().catch(() => allFolders.value),
|
||||
]);
|
||||
allFolders.value = treeRes;
|
||||
subFolders.value = folderRes.subFolders;
|
||||
// 子文件夹从 tree 派生(tree 里的 FolderDto 带 size;getFolder.subFolders 不带 size)
|
||||
subFolders.value = treeRes.filter(
|
||||
(f) => f.parentId === currentFolderId.value,
|
||||
);
|
||||
folderFiles.value = folderRes.files;
|
||||
rebuildBreadcrumbs(currentFolderId.value!);
|
||||
}
|
||||
@ -805,6 +809,8 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
submitOnChange: true,
|
||||
},
|
||||
gridOptions: {
|
||||
// 用于 :deep(.fm-grid) 定位到 Grid 的包装 div,让其 flex 填充剩余高度
|
||||
class: 'fm-grid',
|
||||
columns: useFileManagerColumns(onActionClick),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
@ -930,7 +936,9 @@ function refreshGrid() {
|
||||
icon="lucide:grip-vertical"
|
||||
/>
|
||||
<IconifyIcon
|
||||
:icon="row.itemType === 'folder' ? 'lucide:folder' : 'lucide:file'"
|
||||
:icon="
|
||||
row.itemType === 'folder' ? 'lucide:folder' : 'lucide:file'
|
||||
"
|
||||
:class="
|
||||
row.itemType === 'folder'
|
||||
? 'name-icon-folder'
|
||||
@ -949,7 +957,7 @@ function refreshGrid() {
|
||||
</template>
|
||||
|
||||
<template #size="{ row }">
|
||||
{{ row.itemType === 'folder' ? '-' : formatSize(row.size ?? 0) }}
|
||||
{{ formatSize(row.size ?? 0) }}
|
||||
</template>
|
||||
|
||||
<template #type="{ row }">
|
||||
@ -1122,6 +1130,18 @@ function refreshGrid() {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
/* 填满 Page(auto-content-height) 内容区,让内部 Vxe Grid 的 height:'auto'
|
||||
能拿到确定的父级高度(否则首屏表格体高度计算异常,需点全屏再还原才恢复) */
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Grid 包装 div:占据面包屑/上传进度条之外的剩余高度,
|
||||
min-height:0 允许表格体按 height:'auto' 正确收缩计算 */
|
||||
.file-manager :deep(.fm-grid) {
|
||||
flex: 1 1 0%;
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 名称列:图标 + 文字水平排列 */
|
||||
@ -1156,8 +1176,8 @@ function refreshGrid() {
|
||||
.name-link {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: hsl(var(--primary));
|
||||
text-overflow: ellipsis;
|
||||
color: hsl(var(--primary));
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
@ -1169,8 +1189,8 @@ function refreshGrid() {
|
||||
.name-text {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: inherit;
|
||||
text-overflow: ellipsis;
|
||||
color: inherit;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
@ -58,6 +58,8 @@ export function useSchema(): VbenFormSchema[] {
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
// antd Select 默认不撑满容器(不像 Input),需显式 w-full 才能完整显示选项文本
|
||||
class: 'w-full',
|
||||
options: [
|
||||
{ label: '普通路由', value: 'Route' },
|
||||
{ label: '自定义表单', value: 'BuildFlowForm' },
|
||||
@ -71,6 +73,7 @@ export function useSchema(): VbenFormSchema[] {
|
||||
{
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
api: async () => {
|
||||
const result = await getFormDefinitionListApi({
|
||||
pageIndex: 1,
|
||||
@ -93,6 +96,7 @@ export function useSchema(): VbenFormSchema[] {
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
options: [
|
||||
{ label: '列表', value: 'List' },
|
||||
{ label: '表单', value: 'Form' },
|
||||
@ -121,28 +125,6 @@ export function useSchema(): VbenFormSchema[] {
|
||||
fieldName: 'order',
|
||||
label: '排序',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
componentProps: { placeholder: 'Admin(选填)' },
|
||||
fieldName: 'authority',
|
||||
label: '允许角色',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
componentProps: { placeholder: '/system/user/index(普通路由必填)' },
|
||||
dependencies: {
|
||||
disabled: (values) => values.renderType === 'BuildFlowForm',
|
||||
triggerFields: ['renderType'],
|
||||
},
|
||||
fieldName: 'component',
|
||||
label: '组件路径',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
componentProps: { placeholder: '父菜单重定向(选填)' },
|
||||
fieldName: 'redirect',
|
||||
label: '重定向',
|
||||
},
|
||||
{
|
||||
component: 'Switch',
|
||||
fieldName: 'hideInMenu',
|
||||
@ -183,7 +165,9 @@ export function useColumns(
|
||||
{
|
||||
field: 'path',
|
||||
title: '路径',
|
||||
width: 200,
|
||||
// 用 minWidth 而非固定 width:作为弹性填充列,撑满操作列之外的剩余宽度,
|
||||
// 避免宽屏下右侧出现空隙(与权限页 description 列同款做法)。
|
||||
minWidth: 200,
|
||||
},
|
||||
{
|
||||
field: 'icon',
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import type { MenuApi } from '#/api';
|
||||
|
||||
interface MenuFormValues {
|
||||
authority?: string;
|
||||
code: string;
|
||||
component?: string;
|
||||
formDefinitionId?: string;
|
||||
hideInMenu?: boolean;
|
||||
icon?: string;
|
||||
@ -11,7 +9,6 @@ interface MenuFormValues {
|
||||
order?: number;
|
||||
parentId?: null | number;
|
||||
path: string;
|
||||
redirect?: string;
|
||||
renderType?: 'BuildFlowForm' | 'Route';
|
||||
runtimeViewMode?: 'Detail' | 'Form' | 'List';
|
||||
title: string;
|
||||
@ -27,11 +24,9 @@ function buildMenuFormPayload(data: MenuFormValues): MenuApi.CreateMenuParams {
|
||||
const isBuildFlowForm = data.renderType === 'BuildFlowForm';
|
||||
|
||||
return {
|
||||
authority: normalizeOptional(data.authority),
|
||||
code: data.code,
|
||||
component: isBuildFlowForm
|
||||
? buildFlowRuntimeComponent
|
||||
: normalizeOptional(data.component),
|
||||
// 表单已移除「组件路径」输入:BuildFlowForm 仍自动指向运行时组件;Route 不再带 component
|
||||
component: isBuildFlowForm ? buildFlowRuntimeComponent : undefined,
|
||||
formDefinitionId: isBuildFlowForm ? data.formDefinitionId : undefined,
|
||||
hideInMenu: !!data.hideInMenu,
|
||||
icon: normalizeOptional(data.icon),
|
||||
@ -41,9 +36,10 @@ function buildMenuFormPayload(data: MenuFormValues): MenuApi.CreateMenuParams {
|
||||
order: data.order ?? 0,
|
||||
parentId: data.parentId || null,
|
||||
path: data.path,
|
||||
redirect: normalizeOptional(data.redirect),
|
||||
renderType: data.renderType ?? 'Route',
|
||||
runtimeViewMode: isBuildFlowForm ? (data.runtimeViewMode ?? 'List') : undefined,
|
||||
runtimeViewMode: isBuildFlowForm
|
||||
? (data.runtimeViewMode ?? 'List')
|
||||
: undefined,
|
||||
title: data.title,
|
||||
};
|
||||
}
|
||||
|
||||
@ -119,7 +119,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
zoom: true,
|
||||
},
|
||||
treeConfig: {
|
||||
expandAll: true,
|
||||
// 不设 expandAll:默认折叠,只显示顶级菜单,点行首展开图标才加载显示子级
|
||||
parentField: 'parentId',
|
||||
rowField: 'id',
|
||||
transform: true,
|
||||
|
||||
@ -70,10 +70,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
path: data.path,
|
||||
icon: data.icon ?? '',
|
||||
order: data.order,
|
||||
authority: data.authority ?? '',
|
||||
component: data.component ?? '',
|
||||
formDefinitionId: data.formDefinitionId,
|
||||
redirect: data.redirect ?? '',
|
||||
renderType: data.renderType ?? 'Route',
|
||||
runtimeViewMode: data.runtimeViewMode ?? 'List',
|
||||
hideInMenu: data.hideInMenu,
|
||||
@ -95,7 +92,8 @@ const [Modal, modalApi] = useVbenModal({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="editRow ? '编辑菜单' : '新增菜单'" width="640px">
|
||||
<!-- Vben Modal 没有 width prop,宽度靠 class 覆盖默认的 w-130(520px) -->
|
||||
<Modal :title="editRow ? '编辑菜单' : '新增菜单'" class="w-[820px]">
|
||||
<Form class="mx-4" />
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
@ -118,7 +118,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
parentField: 'parentId',
|
||||
rowField: 'id',
|
||||
transform: true,
|
||||
expandAll: true,
|
||||
// 不设 expandAll:默认折叠,只显示分组,点行首展开图标才显示该组权限
|
||||
},
|
||||
} as VxeTableGridOptions<PermissionRow>,
|
||||
});
|
||||
|
||||
@ -103,7 +103,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="getTitle" :width="960" fullscreen-button>
|
||||
<Modal :title="getTitle" class="w-[960px]" fullscreen-button>
|
||||
<ASpin :spinning="loading" class="block py-2">
|
||||
<p class="mb-3 text-sm text-muted-foreground">
|
||||
勾选该角色拥有的权限(全量替换)。右上角可全屏展开。
|
||||
|
||||
@ -61,7 +61,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="`分配角色 — ${targetUsername}`" width="480px">
|
||||
<Modal :title="`分配角色 — ${targetUsername}`" class="w-[480px]">
|
||||
<ASpin :spinning="loading" class="block py-2">
|
||||
<p class="mb-3 text-sm text-muted-foreground">
|
||||
勾选要授予的角色(全量替换)。
|
||||
|
||||
@ -66,10 +66,16 @@ export function buildNativeNodeAttrs(shape: string, data: Record<string, any>) {
|
||||
const deleteVisible = data.__selected && canDelete ? 'visible' : 'hidden';
|
||||
|
||||
const commonSelectedFrame = {
|
||||
// 选中框:四周相对节点对称外扩 7px 的蓝色虚线光晕。
|
||||
// refX/refY:-7 把左上角放到节点外 7px;refWidth/refHeight:'100%' 仅等于节点宽高,
|
||||
// 必须再补 refWidth2/refHeight2:14 才能让框 = 节点 + 14,否则框会整体往左上偏移
|
||||
// (左/上外凸 7px、右/下内缩 7px)。
|
||||
refX: -7,
|
||||
refY: -7,
|
||||
refWidth: '100%',
|
||||
refHeight: '100%',
|
||||
refWidth2: 14,
|
||||
refHeight2: 14,
|
||||
fill: 'transparent',
|
||||
stroke: '#1677FF',
|
||||
strokeDasharray: '7 4',
|
||||
@ -291,7 +297,9 @@ export function applyWorkflowNodePresentation(node: Node) {
|
||||
export function setWorkflowNodeSelected(node: Node, selected: boolean) {
|
||||
if (!getNodeConfigByShape(node.shape)) return;
|
||||
const data = node.getData() ?? {};
|
||||
node.setAttrs(buildNativeNodeAttrs(node.shape, { ...data, __selected: selected }));
|
||||
node.setAttrs(
|
||||
buildNativeNodeAttrs(node.shape, { ...data, __selected: selected }),
|
||||
);
|
||||
}
|
||||
|
||||
/** 加载旧 definitionJson 后批量补齐 native rect 展示配置。 */
|
||||
|
||||
@ -9,6 +9,9 @@ import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Input,
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
@ -20,7 +23,6 @@ import {
|
||||
PaginationPrevious,
|
||||
VbenIcon,
|
||||
VbenIconButton,
|
||||
VbenPopover,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
import { isFunction } from '@vben-core/shared/utils';
|
||||
|
||||
@ -162,139 +164,148 @@ const getBindAttrs = computed(() => {
|
||||
defineExpose({ toggleOpenState, open, close });
|
||||
</script>
|
||||
<template>
|
||||
<VbenPopover
|
||||
v-model:open="visible"
|
||||
:content-props="{ align: 'end', alignOffset: -11, sideOffset: 8 }"
|
||||
content-class="p-0 pt-3 w-full"
|
||||
trigger-class="w-full"
|
||||
>
|
||||
<template #trigger>
|
||||
<template v-if="props.type === 'input'">
|
||||
<component
|
||||
v-if="props.inputComponent"
|
||||
:is="inputComponent"
|
||||
:[modelValueProp]="currentSelect"
|
||||
:placeholder="$t('ui.iconPicker.placeholder')"
|
||||
role="combobox"
|
||||
:aria-label="$t('ui.iconPicker.placeholder')"
|
||||
aria-expanded="visible"
|
||||
:[`onUpdate:${modelValueProp}`]="updateCurrentSelect"
|
||||
v-bind="getBindAttrs"
|
||||
>
|
||||
<template #[iconSlot]>
|
||||
<VbenIcon
|
||||
:icon="currentSelect || Grip"
|
||||
class="size-4"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</template>
|
||||
</component>
|
||||
<div class="relative w-full" v-else>
|
||||
<Input
|
||||
v-bind="$attrs"
|
||||
v-model="currentSelect"
|
||||
:placeholder="$t('ui.iconPicker.placeholder')"
|
||||
class="h-8 w-full pr-8"
|
||||
role="combobox"
|
||||
:aria-label="$t('ui.iconPicker.placeholder')"
|
||||
aria-expanded="visible"
|
||||
/>
|
||||
<VbenIcon
|
||||
:icon="currentSelect || Grip"
|
||||
class="absolute top-1 right-1 size-6"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<!-- 触发器:点击打开居中弹窗选择图标 -->
|
||||
<template v-if="props.type === 'input'">
|
||||
<component
|
||||
v-if="props.inputComponent"
|
||||
:is="inputComponent"
|
||||
:[modelValueProp]="currentSelect"
|
||||
:placeholder="$t('ui.iconPicker.placeholder')"
|
||||
role="combobox"
|
||||
:aria-label="$t('ui.iconPicker.placeholder')"
|
||||
:[`onUpdate:${modelValueProp}`]="updateCurrentSelect"
|
||||
v-bind="getBindAttrs"
|
||||
@click="open"
|
||||
>
|
||||
<template #[iconSlot]>
|
||||
<VbenIcon
|
||||
:icon="currentSelect || Grip"
|
||||
class="size-4"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</template>
|
||||
</component>
|
||||
<div v-else class="relative w-full cursor-pointer" @click="open">
|
||||
<Input
|
||||
v-bind="$attrs"
|
||||
:model-value="currentSelect"
|
||||
readonly
|
||||
:placeholder="$t('ui.iconPicker.placeholder')"
|
||||
class="h-8 w-full cursor-pointer pr-8"
|
||||
role="combobox"
|
||||
:aria-label="$t('ui.iconPicker.placeholder')"
|
||||
/>
|
||||
<VbenIcon
|
||||
:icon="currentSelect || Grip"
|
||||
v-else
|
||||
class="size-4"
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
</template>
|
||||
<div class="mb-2 flex w-full">
|
||||
<component
|
||||
v-if="inputComponent"
|
||||
:is="inputComponent"
|
||||
v-bind="searchInputProps"
|
||||
/>
|
||||
<Input
|
||||
v-else
|
||||
class="mx-2 h-8 w-full"
|
||||
:placeholder="$t('ui.iconPicker.search')"
|
||||
v-model="keyword"
|
||||
class="absolute top-1 right-1 size-6"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<VbenIcon
|
||||
v-else
|
||||
:icon="currentSelect || Grip"
|
||||
class="size-4 cursor-pointer"
|
||||
v-bind="$attrs"
|
||||
@click="open"
|
||||
/>
|
||||
|
||||
<template v-if="paginationList.length > 0">
|
||||
<div class="grid max-h-90 w-full grid-cols-6 justify-items-center">
|
||||
<VbenIconButton
|
||||
v-for="(item, index) in paginationList"
|
||||
:key="index"
|
||||
:tooltip="item"
|
||||
tooltip-side="top"
|
||||
@click="handleClick(item)"
|
||||
>
|
||||
<VbenIcon
|
||||
:class="{
|
||||
'text-primary transition-all': currentSelect === item,
|
||||
}"
|
||||
:icon="item"
|
||||
/>
|
||||
</VbenIconButton>
|
||||
<!-- 居中弹窗(与新增/编辑弹窗同款 Dialog) -->
|
||||
<Dialog v-model:open="visible">
|
||||
<DialogContent
|
||||
append-to="body"
|
||||
class="left-1/2 top-1/2 w-[600px] max-w-[90vw] -translate-x-1/2 -translate-y-1/2"
|
||||
>
|
||||
<DialogTitle class="sr-only">
|
||||
{{ $t('ui.iconPicker.placeholder') }}
|
||||
</DialogTitle>
|
||||
<div class="mb-3 flex w-full">
|
||||
<component
|
||||
v-if="inputComponent"
|
||||
:is="inputComponent"
|
||||
v-bind="searchInputProps"
|
||||
/>
|
||||
<Input
|
||||
v-else
|
||||
class="h-8 w-full"
|
||||
:placeholder="$t('ui.iconPicker.search')"
|
||||
v-model="keyword"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="total >= pageSize"
|
||||
class="flex-center flex justify-end overflow-hidden border-t py-2 pr-3"
|
||||
>
|
||||
<Pagination
|
||||
:items-per-page="36"
|
||||
:sibling-count="1"
|
||||
:total="total"
|
||||
show-edges
|
||||
size="small"
|
||||
@update:page="handlePageChange"
|
||||
|
||||
<template v-if="paginationList.length > 0">
|
||||
<div
|
||||
class="grid max-h-[55vh] w-full grid-cols-8 justify-items-center overflow-y-auto"
|
||||
>
|
||||
<PaginationContent
|
||||
v-slot="{ items }"
|
||||
class="flex w-full items-center gap-1"
|
||||
<VbenIconButton
|
||||
v-for="(item, index) in paginationList"
|
||||
:key="index"
|
||||
:tooltip="item"
|
||||
tooltip-side="top"
|
||||
@click="handleClick(item)"
|
||||
>
|
||||
<PaginationFirst class="size-5" />
|
||||
<PaginationPrevious class="size-5" />
|
||||
<template v-for="(item, index) in items">
|
||||
<PaginationItem
|
||||
v-if="item.type === 'page'"
|
||||
:key="index"
|
||||
:value="item.value"
|
||||
as-child
|
||||
>
|
||||
<Button
|
||||
:variant="item.value === currentPage ? 'default' : 'outline'"
|
||||
class="size-5 p-0 text-sm"
|
||||
<VbenIcon
|
||||
:class="{
|
||||
'text-primary transition-all': currentSelect === item,
|
||||
}"
|
||||
:icon="item"
|
||||
/>
|
||||
</VbenIconButton>
|
||||
</div>
|
||||
<div
|
||||
v-if="total >= pageSize"
|
||||
class="flex-center mt-2 flex justify-end overflow-hidden border-t py-2 pr-3"
|
||||
>
|
||||
<Pagination
|
||||
:items-per-page="36"
|
||||
:sibling-count="1"
|
||||
:total="total"
|
||||
show-edges
|
||||
size="small"
|
||||
@update:page="handlePageChange"
|
||||
>
|
||||
<PaginationContent
|
||||
v-slot="{ items }"
|
||||
class="flex w-full items-center gap-1"
|
||||
>
|
||||
<PaginationFirst class="size-5" />
|
||||
<PaginationPrevious class="size-5" />
|
||||
<template v-for="(item, index) in items">
|
||||
<PaginationItem
|
||||
v-if="item.type === 'page'"
|
||||
:key="index"
|
||||
:value="item.value"
|
||||
as-child
|
||||
>
|
||||
{{ item.value }}
|
||||
</Button>
|
||||
</PaginationItem>
|
||||
<PaginationEllipsis
|
||||
v-else
|
||||
:key="item.type"
|
||||
:index="index"
|
||||
class="size-5"
|
||||
/>
|
||||
</template>
|
||||
<PaginationNext class="size-5" />
|
||||
<PaginationLast class="size-5" />
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
</div>
|
||||
</template>
|
||||
<Button
|
||||
:variant="
|
||||
item.value === currentPage ? 'default' : 'outline'
|
||||
"
|
||||
class="size-5 p-0 text-sm"
|
||||
>
|
||||
{{ item.value }}
|
||||
</Button>
|
||||
</PaginationItem>
|
||||
<PaginationEllipsis
|
||||
v-else
|
||||
:key="item.type"
|
||||
:index="index"
|
||||
class="size-5"
|
||||
/>
|
||||
</template>
|
||||
<PaginationNext class="size-5" />
|
||||
<PaginationLast class="size-5" />
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="flex-col-center min-h-37.5 w-full text-muted-foreground">
|
||||
<EmptyIcon class="size-10" />
|
||||
<div class="mt-1 text-sm">{{ $t('common.noData') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</VbenPopover>
|
||||
<template v-else>
|
||||
<div class="flex-col-center min-h-37.5 w-full text-muted-foreground">
|
||||
<EmptyIcon class="size-10" />
|
||||
<div class="mt-1 text-sm">{{ $t('common.noData') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user