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;
|
parentId: null | string;
|
||||||
name: string;
|
name: string;
|
||||||
ownerId: string;
|
ownerId: string;
|
||||||
|
/** 该文件夹下所有文件递归汇总的字节大小(getFolderTree 计算)。 */
|
||||||
|
size: number;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { createApp, watchEffect } from 'vue';
|
import { createApp, watchEffect } from 'vue';
|
||||||
|
|
||||||
import { registerAccessDirective } from '@vben/access';
|
import { registerAccessDirective } from '@vben/access';
|
||||||
|
import { setDefaultModalProps } from '@vben/common-ui';
|
||||||
import { registerLoadingDirective } from '@vben/common-ui/es/loading';
|
import { registerLoadingDirective } from '@vben/common-ui/es/loading';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { initStores } from '@vben/stores';
|
import { initStores } from '@vben/stores';
|
||||||
@ -23,14 +24,11 @@ async function bootstrap(namespace: string) {
|
|||||||
// 初始化表单组件
|
// 初始化表单组件
|
||||||
await initSetupVbenForm();
|
await initSetupVbenForm();
|
||||||
|
|
||||||
// // 设置弹窗的默认配置
|
// 设置弹窗的默认配置:统一默认宽度 640px(覆盖框架写死的 w-130/520px)。
|
||||||
// setDefaultModalProps({
|
// 个别弹窗可在自身 <Modal class="w-[Npx]"> 上覆盖(per-modal class 优先级高于此默认)。
|
||||||
// fullscreenButton: false,
|
setDefaultModalProps({
|
||||||
// });
|
class: 'w-[640px]',
|
||||||
// // 设置抽屉的默认配置
|
});
|
||||||
// setDefaultDrawerProps({
|
|
||||||
// zIndex: 1020,
|
|
||||||
// });
|
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
|
|||||||
@ -39,6 +39,7 @@ export function buildFileManagerRows(
|
|||||||
itemType: 'folder',
|
itemType: 'folder',
|
||||||
name: folder.name,
|
name: folder.name,
|
||||||
rowKey: `folder:${folder.id}`,
|
rowKey: `folder:${folder.id}`,
|
||||||
|
size: folder.size,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const fileRows: FileManagerRow[] = files
|
const fileRows: FileManagerRow[] = files
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { FileManagerRow } from './data';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
OnActionClickParams,
|
OnActionClickParams,
|
||||||
VxeTableGridOptions,
|
VxeTableGridOptions,
|
||||||
} from '#/adapter/vxe-table';
|
} from '#/adapter/vxe-table';
|
||||||
import type { FolderApi, ShareApi } from '#/api/core/file';
|
import type { FolderApi, ShareApi } from '#/api/core/file';
|
||||||
import type { FileManagerRow } from './data';
|
|
||||||
|
|
||||||
import { computed, nextTick, reactive, ref } from 'vue';
|
import { computed, nextTick, reactive, ref } from 'vue';
|
||||||
|
|
||||||
@ -54,16 +55,16 @@ import {
|
|||||||
} from '#/api/core/file';
|
} from '#/api/core/file';
|
||||||
import { uploadFileWithResume } from '#/api/core/multipart-uploader';
|
import { uploadFileWithResume } from '#/api/core/multipart-uploader';
|
||||||
|
|
||||||
import {
|
|
||||||
canMoveFileToFolder,
|
|
||||||
getDraggedFileId,
|
|
||||||
setDraggedFileId,
|
|
||||||
} from './drag';
|
|
||||||
import {
|
import {
|
||||||
buildFileManagerRows,
|
buildFileManagerRows,
|
||||||
useFileManagerColumns,
|
useFileManagerColumns,
|
||||||
useGridFormSchema,
|
useGridFormSchema,
|
||||||
} from './data';
|
} from './data';
|
||||||
|
import {
|
||||||
|
canMoveFileToFolder,
|
||||||
|
getDraggedFileId,
|
||||||
|
setDraggedFileId,
|
||||||
|
} from './drag';
|
||||||
|
|
||||||
defineOptions({ name: 'FileManagerPage' });
|
defineOptions({ name: 'FileManagerPage' });
|
||||||
|
|
||||||
@ -98,7 +99,10 @@ async function loadCurrentRows(keyword?: string) {
|
|||||||
getFolderTree().catch(() => allFolders.value),
|
getFolderTree().catch(() => allFolders.value),
|
||||||
]);
|
]);
|
||||||
allFolders.value = treeRes;
|
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;
|
folderFiles.value = folderRes.files;
|
||||||
rebuildBreadcrumbs(currentFolderId.value!);
|
rebuildBreadcrumbs(currentFolderId.value!);
|
||||||
}
|
}
|
||||||
@ -805,6 +809,8 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
submitOnChange: true,
|
submitOnChange: true,
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
|
// 用于 :deep(.fm-grid) 定位到 Grid 的包装 div,让其 flex 填充剩余高度
|
||||||
|
class: 'fm-grid',
|
||||||
columns: useFileManagerColumns(onActionClick),
|
columns: useFileManagerColumns(onActionClick),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
@ -930,7 +936,9 @@ function refreshGrid() {
|
|||||||
icon="lucide:grip-vertical"
|
icon="lucide:grip-vertical"
|
||||||
/>
|
/>
|
||||||
<IconifyIcon
|
<IconifyIcon
|
||||||
:icon="row.itemType === 'folder' ? 'lucide:folder' : 'lucide:file'"
|
:icon="
|
||||||
|
row.itemType === 'folder' ? 'lucide:folder' : 'lucide:file'
|
||||||
|
"
|
||||||
:class="
|
:class="
|
||||||
row.itemType === 'folder'
|
row.itemType === 'folder'
|
||||||
? 'name-icon-folder'
|
? 'name-icon-folder'
|
||||||
@ -949,7 +957,7 @@ function refreshGrid() {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #size="{ row }">
|
<template #size="{ row }">
|
||||||
{{ row.itemType === 'folder' ? '-' : formatSize(row.size ?? 0) }}
|
{{ formatSize(row.size ?? 0) }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #type="{ row }">
|
<template #type="{ row }">
|
||||||
@ -1122,6 +1130,18 @@ function refreshGrid() {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
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 {
|
.name-link {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: hsl(var(--primary));
|
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
color: hsl(var(--primary));
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@ -1169,8 +1189,8 @@ function refreshGrid() {
|
|||||||
.name-text {
|
.name-text {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: inherit;
|
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
color: inherit;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -58,6 +58,8 @@ export function useSchema(): VbenFormSchema[] {
|
|||||||
{
|
{
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
|
// antd Select 默认不撑满容器(不像 Input),需显式 w-full 才能完整显示选项文本
|
||||||
|
class: 'w-full',
|
||||||
options: [
|
options: [
|
||||||
{ label: '普通路由', value: 'Route' },
|
{ label: '普通路由', value: 'Route' },
|
||||||
{ label: '自定义表单', value: 'BuildFlowForm' },
|
{ label: '自定义表单', value: 'BuildFlowForm' },
|
||||||
@ -71,6 +73,7 @@ export function useSchema(): VbenFormSchema[] {
|
|||||||
{
|
{
|
||||||
component: 'ApiSelect',
|
component: 'ApiSelect',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
api: async () => {
|
api: async () => {
|
||||||
const result = await getFormDefinitionListApi({
|
const result = await getFormDefinitionListApi({
|
||||||
pageIndex: 1,
|
pageIndex: 1,
|
||||||
@ -93,6 +96,7 @@ export function useSchema(): VbenFormSchema[] {
|
|||||||
{
|
{
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
options: [
|
options: [
|
||||||
{ label: '列表', value: 'List' },
|
{ label: '列表', value: 'List' },
|
||||||
{ label: '表单', value: 'Form' },
|
{ label: '表单', value: 'Form' },
|
||||||
@ -121,28 +125,6 @@ export function useSchema(): VbenFormSchema[] {
|
|||||||
fieldName: 'order',
|
fieldName: 'order',
|
||||||
label: '排序',
|
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',
|
component: 'Switch',
|
||||||
fieldName: 'hideInMenu',
|
fieldName: 'hideInMenu',
|
||||||
@ -183,7 +165,9 @@ export function useColumns(
|
|||||||
{
|
{
|
||||||
field: 'path',
|
field: 'path',
|
||||||
title: '路径',
|
title: '路径',
|
||||||
width: 200,
|
// 用 minWidth 而非固定 width:作为弹性填充列,撑满操作列之外的剩余宽度,
|
||||||
|
// 避免宽屏下右侧出现空隙(与权限页 description 列同款做法)。
|
||||||
|
minWidth: 200,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'icon',
|
field: 'icon',
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import type { MenuApi } from '#/api';
|
import type { MenuApi } from '#/api';
|
||||||
|
|
||||||
interface MenuFormValues {
|
interface MenuFormValues {
|
||||||
authority?: string;
|
|
||||||
code: string;
|
code: string;
|
||||||
component?: string;
|
|
||||||
formDefinitionId?: string;
|
formDefinitionId?: string;
|
||||||
hideInMenu?: boolean;
|
hideInMenu?: boolean;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
@ -11,7 +9,6 @@ interface MenuFormValues {
|
|||||||
order?: number;
|
order?: number;
|
||||||
parentId?: null | number;
|
parentId?: null | number;
|
||||||
path: string;
|
path: string;
|
||||||
redirect?: string;
|
|
||||||
renderType?: 'BuildFlowForm' | 'Route';
|
renderType?: 'BuildFlowForm' | 'Route';
|
||||||
runtimeViewMode?: 'Detail' | 'Form' | 'List';
|
runtimeViewMode?: 'Detail' | 'Form' | 'List';
|
||||||
title: string;
|
title: string;
|
||||||
@ -27,11 +24,9 @@ function buildMenuFormPayload(data: MenuFormValues): MenuApi.CreateMenuParams {
|
|||||||
const isBuildFlowForm = data.renderType === 'BuildFlowForm';
|
const isBuildFlowForm = data.renderType === 'BuildFlowForm';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
authority: normalizeOptional(data.authority),
|
|
||||||
code: data.code,
|
code: data.code,
|
||||||
component: isBuildFlowForm
|
// 表单已移除「组件路径」输入:BuildFlowForm 仍自动指向运行时组件;Route 不再带 component
|
||||||
? buildFlowRuntimeComponent
|
component: isBuildFlowForm ? buildFlowRuntimeComponent : undefined,
|
||||||
: normalizeOptional(data.component),
|
|
||||||
formDefinitionId: isBuildFlowForm ? data.formDefinitionId : undefined,
|
formDefinitionId: isBuildFlowForm ? data.formDefinitionId : undefined,
|
||||||
hideInMenu: !!data.hideInMenu,
|
hideInMenu: !!data.hideInMenu,
|
||||||
icon: normalizeOptional(data.icon),
|
icon: normalizeOptional(data.icon),
|
||||||
@ -41,9 +36,10 @@ function buildMenuFormPayload(data: MenuFormValues): MenuApi.CreateMenuParams {
|
|||||||
order: data.order ?? 0,
|
order: data.order ?? 0,
|
||||||
parentId: data.parentId || null,
|
parentId: data.parentId || null,
|
||||||
path: data.path,
|
path: data.path,
|
||||||
redirect: normalizeOptional(data.redirect),
|
|
||||||
renderType: data.renderType ?? 'Route',
|
renderType: data.renderType ?? 'Route',
|
||||||
runtimeViewMode: isBuildFlowForm ? (data.runtimeViewMode ?? 'List') : undefined,
|
runtimeViewMode: isBuildFlowForm
|
||||||
|
? (data.runtimeViewMode ?? 'List')
|
||||||
|
: undefined,
|
||||||
title: data.title,
|
title: data.title,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -119,7 +119,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
zoom: true,
|
zoom: true,
|
||||||
},
|
},
|
||||||
treeConfig: {
|
treeConfig: {
|
||||||
expandAll: true,
|
// 不设 expandAll:默认折叠,只显示顶级菜单,点行首展开图标才加载显示子级
|
||||||
parentField: 'parentId',
|
parentField: 'parentId',
|
||||||
rowField: 'id',
|
rowField: 'id',
|
||||||
transform: true,
|
transform: true,
|
||||||
|
|||||||
@ -70,10 +70,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
path: data.path,
|
path: data.path,
|
||||||
icon: data.icon ?? '',
|
icon: data.icon ?? '',
|
||||||
order: data.order,
|
order: data.order,
|
||||||
authority: data.authority ?? '',
|
|
||||||
component: data.component ?? '',
|
|
||||||
formDefinitionId: data.formDefinitionId,
|
formDefinitionId: data.formDefinitionId,
|
||||||
redirect: data.redirect ?? '',
|
|
||||||
renderType: data.renderType ?? 'Route',
|
renderType: data.renderType ?? 'Route',
|
||||||
runtimeViewMode: data.runtimeViewMode ?? 'List',
|
runtimeViewMode: data.runtimeViewMode ?? 'List',
|
||||||
hideInMenu: data.hideInMenu,
|
hideInMenu: data.hideInMenu,
|
||||||
@ -95,7 +92,8 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Modal :title="editRow ? '编辑菜单' : '新增菜单'" width="640px">
|
<!-- Vben Modal 没有 width prop,宽度靠 class 覆盖默认的 w-130(520px) -->
|
||||||
|
<Modal :title="editRow ? '编辑菜单' : '新增菜单'" class="w-[820px]">
|
||||||
<Form class="mx-4" />
|
<Form class="mx-4" />
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -118,7 +118,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
parentField: 'parentId',
|
parentField: 'parentId',
|
||||||
rowField: 'id',
|
rowField: 'id',
|
||||||
transform: true,
|
transform: true,
|
||||||
expandAll: true,
|
// 不设 expandAll:默认折叠,只显示分组,点行首展开图标才显示该组权限
|
||||||
},
|
},
|
||||||
} as VxeTableGridOptions<PermissionRow>,
|
} as VxeTableGridOptions<PermissionRow>,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -103,7 +103,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Modal :title="getTitle" :width="960" fullscreen-button>
|
<Modal :title="getTitle" class="w-[960px]" fullscreen-button>
|
||||||
<ASpin :spinning="loading" class="block py-2">
|
<ASpin :spinning="loading" class="block py-2">
|
||||||
<p class="mb-3 text-sm text-muted-foreground">
|
<p class="mb-3 text-sm text-muted-foreground">
|
||||||
勾选该角色拥有的权限(全量替换)。右上角可全屏展开。
|
勾选该角色拥有的权限(全量替换)。右上角可全屏展开。
|
||||||
|
|||||||
@ -61,7 +61,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Modal :title="`分配角色 — ${targetUsername}`" width="480px">
|
<Modal :title="`分配角色 — ${targetUsername}`" class="w-[480px]">
|
||||||
<ASpin :spinning="loading" class="block py-2">
|
<ASpin :spinning="loading" class="block py-2">
|
||||||
<p class="mb-3 text-sm text-muted-foreground">
|
<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 deleteVisible = data.__selected && canDelete ? 'visible' : 'hidden';
|
||||||
|
|
||||||
const commonSelectedFrame = {
|
const commonSelectedFrame = {
|
||||||
|
// 选中框:四周相对节点对称外扩 7px 的蓝色虚线光晕。
|
||||||
|
// refX/refY:-7 把左上角放到节点外 7px;refWidth/refHeight:'100%' 仅等于节点宽高,
|
||||||
|
// 必须再补 refWidth2/refHeight2:14 才能让框 = 节点 + 14,否则框会整体往左上偏移
|
||||||
|
// (左/上外凸 7px、右/下内缩 7px)。
|
||||||
refX: -7,
|
refX: -7,
|
||||||
refY: -7,
|
refY: -7,
|
||||||
refWidth: '100%',
|
refWidth: '100%',
|
||||||
refHeight: '100%',
|
refHeight: '100%',
|
||||||
|
refWidth2: 14,
|
||||||
|
refHeight2: 14,
|
||||||
fill: 'transparent',
|
fill: 'transparent',
|
||||||
stroke: '#1677FF',
|
stroke: '#1677FF',
|
||||||
strokeDasharray: '7 4',
|
strokeDasharray: '7 4',
|
||||||
@ -291,7 +297,9 @@ export function applyWorkflowNodePresentation(node: Node) {
|
|||||||
export function setWorkflowNodeSelected(node: Node, selected: boolean) {
|
export function setWorkflowNodeSelected(node: Node, selected: boolean) {
|
||||||
if (!getNodeConfigByShape(node.shape)) return;
|
if (!getNodeConfigByShape(node.shape)) return;
|
||||||
const data = node.getData() ?? {};
|
const data = node.getData() ?? {};
|
||||||
node.setAttrs(buildNativeNodeAttrs(node.shape, { ...data, __selected: selected }));
|
node.setAttrs(
|
||||||
|
buildNativeNodeAttrs(node.shape, { ...data, __selected: selected }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 加载旧 definitionJson 后批量补齐 native rect 展示配置。 */
|
/** 加载旧 definitionJson 后批量补齐 native rect 展示配置。 */
|
||||||
|
|||||||
@ -9,6 +9,9 @@ import { $t } from '@vben/locales';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
Input,
|
Input,
|
||||||
Pagination,
|
Pagination,
|
||||||
PaginationContent,
|
PaginationContent,
|
||||||
@ -20,7 +23,6 @@ import {
|
|||||||
PaginationPrevious,
|
PaginationPrevious,
|
||||||
VbenIcon,
|
VbenIcon,
|
||||||
VbenIconButton,
|
VbenIconButton,
|
||||||
VbenPopover,
|
|
||||||
} from '@vben-core/shadcn-ui';
|
} from '@vben-core/shadcn-ui';
|
||||||
import { isFunction } from '@vben-core/shared/utils';
|
import { isFunction } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
@ -162,13 +164,7 @@ const getBindAttrs = computed(() => {
|
|||||||
defineExpose({ toggleOpenState, open, close });
|
defineExpose({ toggleOpenState, open, close });
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<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'">
|
<template v-if="props.type === 'input'">
|
||||||
<component
|
<component
|
||||||
v-if="props.inputComponent"
|
v-if="props.inputComponent"
|
||||||
@ -177,9 +173,9 @@ defineExpose({ toggleOpenState, open, close });
|
|||||||
:placeholder="$t('ui.iconPicker.placeholder')"
|
:placeholder="$t('ui.iconPicker.placeholder')"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
:aria-label="$t('ui.iconPicker.placeholder')"
|
:aria-label="$t('ui.iconPicker.placeholder')"
|
||||||
aria-expanded="visible"
|
|
||||||
:[`onUpdate:${modelValueProp}`]="updateCurrentSelect"
|
:[`onUpdate:${modelValueProp}`]="updateCurrentSelect"
|
||||||
v-bind="getBindAttrs"
|
v-bind="getBindAttrs"
|
||||||
|
@click="open"
|
||||||
>
|
>
|
||||||
<template #[iconSlot]>
|
<template #[iconSlot]>
|
||||||
<VbenIcon
|
<VbenIcon
|
||||||
@ -189,15 +185,15 @@ defineExpose({ toggleOpenState, open, close });
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</component>
|
</component>
|
||||||
<div class="relative w-full" v-else>
|
<div v-else class="relative w-full cursor-pointer" @click="open">
|
||||||
<Input
|
<Input
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
v-model="currentSelect"
|
:model-value="currentSelect"
|
||||||
|
readonly
|
||||||
:placeholder="$t('ui.iconPicker.placeholder')"
|
:placeholder="$t('ui.iconPicker.placeholder')"
|
||||||
class="h-8 w-full pr-8"
|
class="h-8 w-full cursor-pointer pr-8"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
:aria-label="$t('ui.iconPicker.placeholder')"
|
:aria-label="$t('ui.iconPicker.placeholder')"
|
||||||
aria-expanded="visible"
|
|
||||||
/>
|
/>
|
||||||
<VbenIcon
|
<VbenIcon
|
||||||
:icon="currentSelect || Grip"
|
:icon="currentSelect || Grip"
|
||||||
@ -207,13 +203,23 @@ defineExpose({ toggleOpenState, open, close });
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<VbenIcon
|
<VbenIcon
|
||||||
:icon="currentSelect || Grip"
|
|
||||||
v-else
|
v-else
|
||||||
class="size-4"
|
:icon="currentSelect || Grip"
|
||||||
|
class="size-4 cursor-pointer"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
|
@click="open"
|
||||||
/>
|
/>
|
||||||
</template>
|
|
||||||
<div class="mb-2 flex w-full">
|
<!-- 居中弹窗(与新增/编辑弹窗同款 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
|
<component
|
||||||
v-if="inputComponent"
|
v-if="inputComponent"
|
||||||
:is="inputComponent"
|
:is="inputComponent"
|
||||||
@ -221,14 +227,16 @@ defineExpose({ toggleOpenState, open, close });
|
|||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
v-else
|
v-else
|
||||||
class="mx-2 h-8 w-full"
|
class="h-8 w-full"
|
||||||
:placeholder="$t('ui.iconPicker.search')"
|
:placeholder="$t('ui.iconPicker.search')"
|
||||||
v-model="keyword"
|
v-model="keyword"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="paginationList.length > 0">
|
<template v-if="paginationList.length > 0">
|
||||||
<div class="grid max-h-90 w-full grid-cols-6 justify-items-center">
|
<div
|
||||||
|
class="grid max-h-[55vh] w-full grid-cols-8 justify-items-center overflow-y-auto"
|
||||||
|
>
|
||||||
<VbenIconButton
|
<VbenIconButton
|
||||||
v-for="(item, index) in paginationList"
|
v-for="(item, index) in paginationList"
|
||||||
:key="index"
|
:key="index"
|
||||||
@ -246,7 +254,7 @@ defineExpose({ toggleOpenState, open, close });
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="total >= pageSize"
|
v-if="total >= pageSize"
|
||||||
class="flex-center flex justify-end overflow-hidden border-t py-2 pr-3"
|
class="flex-center mt-2 flex justify-end overflow-hidden border-t py-2 pr-3"
|
||||||
>
|
>
|
||||||
<Pagination
|
<Pagination
|
||||||
:items-per-page="36"
|
:items-per-page="36"
|
||||||
@ -270,7 +278,9 @@ defineExpose({ toggleOpenState, open, close });
|
|||||||
as-child
|
as-child
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
:variant="item.value === currentPage ? 'default' : 'outline'"
|
:variant="
|
||||||
|
item.value === currentPage ? 'default' : 'outline'
|
||||||
|
"
|
||||||
class="size-5 p-0 text-sm"
|
class="size-5 p-0 text-sm"
|
||||||
>
|
>
|
||||||
{{ item.value }}
|
{{ item.value }}
|
||||||
@ -296,5 +306,6 @@ defineExpose({ toggleOpenState, open, close });
|
|||||||
<div class="mt-1 text-sm">{{ $t('common.noData') }}</div>
|
<div class="mt-1 text-sm">{{ $t('common.noData') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</VbenPopover>
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user