
这是一套基于Vue 3 (Composition API) Vue Router Element Plus实现的路由与页面设计方案。它完全满足了你在草图和原型图中所提出的业务逻辑如上传文件夹、正小数字数限制校验、权限隐藏标签页、按钮联动禁用等。核心路由配置 (router/index.js)这里采用嵌套路由children来实现三层页面的跳转首页Dashboard/HomeBBZDCL主页功能中心子功能页ExcelZXCZ / ZYRYGS汇总import { createRouter, createWebHistory } from vue-router; const routes [ { path: /, name: Home, // 假设你的首页组件 component: () import(/views/Home.vue), }, { path: /report-process, name: ReportProcess, // 第二张图BBZDCL的功能中心主页 component: () import(/views/report/Index.vue), children: [ { path: excel-online, name: ExcelOnline, component: () import(/views/report/ExcelOnline.vue), }, { path: staff-hours, name: StaffHoursSummary, // 第三张图ZYRYGS汇总页面 component: () import(/views/report/StaffHoursSummary.vue), } ] } ]; const router createRouter({ history: createWebHistory(), routes, }); export default router;页面代码实现1. BBZDCL功能中心 (views/report/Index.vue)对应第二张图片包含“ExcelZXCZ”和“ZYRYGS汇总”两个卡片入口。template !-- 如果在子路由页面则渲染子路由内容否则渲染功能中心主页 -- router-view v-if$route.path ! /report-process/router-view div v-else classreport-container div classheader h2⭐ BBZDCL/h2 /div div classcontent-panel h3功能中心/h3 div classcard-grid !-- 卡片 1: ExcelZXCZ -- div classmenu-card click$router.push(/report-process/excel-online) div classicon-wrapper excel-icon el-iconDocument //el-icon /div h4ExcelZXCZ/h4 p多人协作编辑 Excel 表格/p /div !-- 卡片 2: ZYRYGSHZ -- div classmenu-card click$router.push(/report-process/staff-hours) div classicon-wrapper staff-icon el-iconDocumentCopy //el-icon /div h4ZYRYGSHZ/h4 pXXXXXX/p /div /div /div /div /template script setup import { Document, DocumentCopy } from element-plus/icons-vue; /script style scoped .report-container { padding: 24px; background: #f5f7fa; min-height: 100vh; } .header h2 { margin-bottom: 30px; } .content-panel h3 { margin-bottom: 20px; color: #333; } .card-grid { display: flex; gap: 24px; } .menu-card { background: #fff; border-radius: 12px; padding: 30px; width: 280px; text-align: center; cursor: pointer; transition: all 0.3s; border: 1px solid #e4e7ed; } .menu-card:hover { transform: translateY(-5px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } .icon-wrapper { width: 70px; height: 70px; border-radius: 16px; margin: 0 auto 16px; display: flex; align-items: center; justify-content: center; font-size: 32px; color: #fff; } .excel-icon { background-color: #67c23a; } .staff-icon { background-color: #409eff; } .menu-card h4 { margin: 10px 0; font-size: 18px; } .menu-card p { color: #909399; font-size: 14px; } /style2. 重点ZYRYGS汇总页 (views/report/StaffHoursSummary.vue)对应第三张图片及草图说明逻辑1BM汇总 / QH汇总标签页切换QH汇总增加模拟角色权限控制。逻辑2点击上传文件可选择两个文件夹。逻辑3ZHGZL占比输入框限制最长50字 必须为正小数。逻辑4导出按钮联动——只有成功上传文件且输入占比正确时才可点击。template div classsummary-container !-- 面包屑页头 -- div classpage-header div classleft el-icon classpage-iconDocument //el-icon span classtitleBBZDCL/span /div div classright help-link el-iconQuestionFilled //el-icon 帮助 /div /div !-- 主体卡片区域 -- div classmain-card !-- 标签页切换 -- el-tabs v-modelactiveTab classcustom-tabs el-tab-pane labelBMHZ namedepartment/el-tab-pane !-- 权限控制模拟只有当前用户为特定用户/角色时才显示 -- el-tab-pane v-ifcurrentUser.username admin labelQHHZ nameplan /el-tab-pane /el-tabs !-- 工具栏操作区 -- div classtoolbar div classleft-actions !-- 上传文件夹directory 允许选择文件夹 -- el-upload action# multiple directory :auto-uploadfalse :show-file-listfalse :on-changehandleFolderUpload el-button typeprimary :iconUpload上传文件/el-button /el-upload !-- 导出按钮受上传状态和输入框校验联合控制 -- el-button typesuccess :iconDownload :disabled!isExportAvailable classexport-btn 导出 Excel 打包下载 /el-button /div !-- 占比输入框及校验逻辑 -- div classright-input span classlabel-requiredZHGZLZB:/span el-input v-modelratioInput placeholder输入框字数限制50要求正小数 maxlength50 show-word-limit :class{ input-error: ratioError } inputvalidateRatio stylewidth: 320px / div v-ifratioError classerror-msg请输入有效的正小数例如0.25/div /div /div !-- 数据表格 -- el-table :datatableData border stylewidth: 100%; margin-top: 20px empty-text请点击「上传文件」选择文件夹 el-table-column propfileName label名称 min-width250 / el-table-column propstatus label状态 width120 aligncenter template #defaultscope el-tag :typescope.row.status 成功 ? success : info{{ scope.row.status }}/el-tag /template /el-table-column /el-table /div /div /template script setup import { ref, computed } from vue; import { Document, QuestionFilled, Upload, Download } from element-plus/icons-vue; import { ElMessage } from element-plus; // 模拟当前登录用户数据改为非 admin 即可测试隐藏“QHHZ” const currentUser ref({ username: admin }); const activeTab ref(department); const ratioInput ref(); const ratioError ref(false); const tableData ref([]); // 上传的文件列表数据 // 校验正小数的正则 const validateRatio (val) { if (!val) { ratioError.value false; return; } // 正小数正则不包括0和负数 const reg /^(0|[1-9]\d*)(\.\d)?$/; if (reg.test(val) parseFloat(val) 0) { ratioError.value false; } else { ratioError.value true; } }; // 处理文件夹上传 const handleFolderUpload (uploadFile) { // 模拟读取文件夹下的文件并展示到表格中 const rawFile uploadFile.raw; const path rawFile.webkitRelativePath || rawFile.name; // 避免重复添加 if (tableData.value.some((item) item.fileName path)) return; tableData.value.push({ fileName: path, status: 成功, recordCount: Math.floor(Math.random() * 100) 10, reportCount: Math.floor(Math.random() * 10) 1, total: (Math.random() * 200).toFixed(2), }); ElMessage.success(读取文件成功: ${rawFile.name}); }; // 核心联动逻辑是否允许导出 const isExportAvailable computed(() { // 1. 必须有上传成功的文件 const hasFiles tableData.value.length 0; // 2. 输入框不能为空且必须校验通过无错误 const hasValidRatio ratioInput.value !ratioError.value; return hasFiles hasValidRatio; }); /script style scoped .summary-container { background: #f5f7fa; min-height: 100vh; padding-bottom: 30px; } .page-header { background: #fff; padding: 14px 24px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #e4e7ed; } .page-header .left { display: flex; align-items: center; gap: 8px; font-weight: bold; } .page-icon { color: #409eff; font-size: 20px; } .help-link { color: #409eff; cursor: pointer; font-size: 14px; display: flex; align-items: center; gap: 4px; } .main-card { background: #fff; margin: 20px; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05); } .toolbar { display: flex; justify-content: space-between; align-items: flex-start; margin-top: 20px; flex-wrap: wrap; gap: 16px; } .left-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; } .right-input { position: relative; display: flex; align-items: center; gap: 8px; } .label-required { font-weight: bold; font-size: 15px; white-space: nowrap; } /* 输入框标红框提示 */ :deep(.input-error .el-input__wrapper) { box-shadow: 0 0 0 1px #f56c6c inset !important; } .error-msg { position: absolute; bottom: -20px; right: 0; color: #f56c6c; font-size: 12px; } /style代码设计亮点说明文件夹上传功能 (directory)在el-upload上添加了原生的directory属性点击“上传文件”按钮时浏览器会自动弹出文件夹选择框而非单个文件选择框。正小数与字数动态校验利用正则响应式监听输入配合 Element Plus 的maxlength自动在右侧展示0/50字数限制。如果输入不合法如输入负数、字母、或留空输入框会立刻呈现高亮红框提示。按钮状态联合联动 (computed)采用计算属性isExportAvailable完美收拢业务逻辑。只有当tableData数组不为空表示上传成功且ratioInput输入合法时导出打包下载按钮才会激活逻辑清晰。问题上传文件夹可以同时上传两个文件夹吗或者可以上传文件夹套文件夹这样的吗在前端开发中使用 Element Plus 的directory或原生webkitdirectory属性时关于你提到的这两种复杂上传场景实际的浏览器行为和底层逻辑是这样的1. 可以同时选择并上传“两个独立的文件夹”吗结论原生文件选择弹窗不支持但可以通过“拖拽”或“分批上传”来实现。常规点击上传不支持多选文件夹 当你点击按钮触发系统的文件选择器时即使你配置了multiple在绝大多数操作系统Windows / macOS的标准弹窗里你一次只能双击选中并“打开/上传”一个文件夹。你无法按住Ctrl或Shift同时选中两个平级的文件夹点击确定。完美的替代解决方案方案 A拖拽上传如果你使用 Element Plus 的拖拽上传区域drag模式用户可以在桌面同时选中两个文件夹直接拖进上传区域前端是可以同时接收到这两个文件夹内所有文件的。方案 B分批多次点击用户可以点击第一次选择“文件夹 A”上传然后再次点击选择“文件夹 B”上传。我们在前端代码中通过一个数组把它们累加起来即可。2. 可以上传“文件夹套文件夹层级嵌套”吗结论完全可以但正如你截图中的官方文档所说数据会被“扁平化处理”。当你选择了一个最外层的母文件夹而这个文件夹里面还嵌套了子文件夹、孙文件夹时浏览器行为浏览器会递归地把这个母文件夹下所有层级里的文件全部读取出来。数据的“扁平化”正如文档所述Element 会把所有文件抽出来放进一个一维数组里传给你的后端或前端回调。如何保留层级关系虽然数组是扁平的但每一个文件对象上都包含一个关键属性file.raw.webkitRelativePath文件的相对路径。 例如母文件夹/文件1.xlsx母文件夹/子文件夹A/文件2.xlsx母文件夹/子文件夹A/孙文件夹B/文件3.xlsx你的后端或前端完全可以通过解析这个webkitRelativePath路径字符串在服务器端或者页面上完美还原出原本的“嵌套树状结构” 针对你业务的落地建议在写代码时为了完美支持你原型图上的需求建议这样处理// 前端数组用来接收多次上传或拖拽的多个文件夹文件 const allUploadedFiles ref([]); const handleFolderUpload (uploadFile) { const rawFile uploadFile.raw; // 核心通过 webkitRelativePath 拿到完整的层级路径 console.log(当前文件相对路径:, rawFile.webkitRelativePath); // 将文件追加到列表中这样用户分两次上传两个文件夹也能完美兼容 allUploadedFiles.value.push(rawFile); // 可以在这里通过解析路径判断用户一共上传了多少个不同的顶级文件夹 };如果业务一定要用户一次性交齐两个文件夹建议页面上直接提示用户“请将两个文件夹打包拖拽至此处或分两次点击上传”。如果需要区分不同的文件夹在前端通过对webkitRelativePath字符串进行截取取第一个/前的名字就可以轻松将它们归类。