恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Vue3 OA架构模板:动态路由+Schema表单+泛微SSO集成
首页
资讯中心
/
Vue3 OA架构模板:动态路由+Schema表单+泛微SSO集成
Vue3 OA架构模板:动态路由+Schema表单+泛微SSO集成
发布时间:2026/9/15 22:21:41
简介这是一份面向前端开发者与Vue初学者的办公自动化OA系统快速开发模板基于Vue.js 2/3主流架构设计聚焦单页应用SPA场景下的企业级管理界面搭建。资源提供开箱即用的HTML模板集合涵盖登录、导航菜单、公告通知、文档管理、任务看板及审批流程等核心OA功能模块集成Vue Router路由、Vuex状态管理及Element UI组件库支持响应式布局与基础权限结构示意。压缩包为RAR格式大小3.64MB包含HTML、Vue单文件组件.vue、CSS样式、JS逻辑及配套静态资源文件总数未披露但结构完整可直接运行调试或二次开发。已有664人学习下载适合希望跳过UI基建、专注业务逻辑实现的中初级前端工程师快速构建可演示的OA原型系统同时掌握组件化拆分、路由守卫配置与状态流设计等实战要点。1. 这不是“开箱即用”的OA而是 Vue 项目骨架的工业化切口你下载了一个叫VUE架构OA系统模板.rar的压缩包解压后看到src/views/下密密麻麻的workbench.vue、approval-list.vue、org-tree.vue还有router/index.js里按模块分组的路由、store/modules/下带命名空间的 Vuex 模块——但npm run serve启动后空白页报错Cannot find module ant-design-vuepackage.json里却没这行依赖。这不是模板漏装而是典型「架构级模板」的交付形态它不承诺功能完整只固化组织级前端工程范式——包括权限路由守卫的拦截粒度、表单校验与后端字段的映射契约、审批流节点状态机的组件封装边界、以及多租户场景下主题色与菜单配置的注入时机。适合已有 Java/Spring Boot 后端团队快速对齐前端基建标准也适合想跳过“从零搭脚手架”阶段、直接进入业务组件开发的中高级 Vue 工程师。它解决的不是“怎么写一个按钮”而是“当 OA 系统要接入泛微单点登录、对接金蝶主数据、支持 30 审批模板动态渲染时Vue 层该以什么结构承接”。2. 解压即启动还原 VUE 架构 OA 模板的最小可运行环境2.1 识别模板真实技术栈与版本锚点VUE架构OA系统模板.rar不是 Vue 2 或 Vue 3 的模糊概念其工程结构暴露了明确的技术选型线索src/main.js中存在createApp(App).use(store).use(router).mount(#app)→Vue 3 Composition APIpackage.json的dependencies包含element-plus: ^2.3.0和axios: ^1.4.0无vue-router3或vuex3→Vue Router 4 Pinia 替代 Vuexpublic/index.html的script标签引入了https://unpkg.com/echarts5.4.3/dist/echarts.min.js→图表能力外链 ECharts非 npm 安装。提示不要盲目npm install全量依赖。该模板采用“按需安装”策略——package.json中仅保留核心框架依赖UI 组件库如ant-design-vue、富文本编辑器如wangeditor、PDF 预览如pdfvuer等均需根据实际业务模块手动安装。这是为避免打包体积膨胀也是为后续对接泛微 OA 的 SSO 接口预留定制空间。2.2 补全依赖并修复启动报错执行以下命令补全基础依赖注意版本对齐# 进入解压目录 cd vue-oa-template # 安装 Vue 3 生态核心 npm install vue^3.3.0 vue-router^4.2.0 pinia^2.1.0 axios^1.4.0 # 安装 Element Plus模板中大量使用 el-* 组件 npm install element-plus^2.3.0 element-plus/icons-vue^2.3.0 # 安装图标字体模板中 menu 图标引用 el-icon-* npm install icon-park/vue-next^1.5.0 # 若需对接金蝶主数据或泛微 SSO需额外安装加密库 npm install crypto-js^4.18.0 jsencrypt^3.3.1关键参数说明vue^3.3.0锁定 Vue 3.3.x 版本避免defineOptions语法在低版本报错pinia^2.1.0模板中store/modules/user.ts使用defineStore需匹配 Pinia 2.1 的 TypeScript 类型推导element-plus/icons-vue^2.3.0Element Plus 2.3 将图标拆分为独立包缺失会导致el-iconEdit //el-icon渲染失败crypto-js泛微 OA 单点登录常要求 AES 加密 token此库提供CryptoJS.AES.encrypt()方法。2.3 配置环境变量与代理规则模板中src/utils/request.ts的baseURL默认指向/api需通过vite.config.ts配置代理规避跨域// vite.config.ts import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { port: 8080, proxy: { /api: { target: http://192.168.1.100:8081, // 替换为你的 Spring Boot 后端地址 changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) }, /sso: { // 泛微单点登录接口前缀 target: https://sso.weaver.com.cn, changeOrigin: true, secure: false } } } })注意rewrite规则必须存在。模板中axios请求写的是axios.get(/api/user/info)代理需将/api前缀剥离后转发至后端否则后端接收路径为/api/user/info导致 404。3. 路由与权限解析 OA 系统模板的菜单驱动机制3.1 动态路由加载从router/index.ts到menu-config.json模板的菜单并非硬编码在router/index.ts而是通过src/router/generator.ts动态生成// src/router/generator.ts import { RouteRecordRaw } from vue-router import { MenuConfigItem } from /types/menu export function generateRoutes(menuList: MenuConfigItem[]): RouteRecordRaw[] { return menuList.map(item ({ path: item.path, name: item.name, component: () import(/views/${item.component}.vue), // 按需加载 meta: { title: item.title, icon: item.icon, permission: item.permission // 权限标识符如 sys:user:list } })) }menuList数据源来自public/config/menu-config.json[ { path: /workbench, name: Workbench, title: 工作台, component: workbench/Index, icon: HomeFilled, permission: dashboard:access }, { path: /approval, name: Approval, title: 审批中心, component: approval/List, icon: DocumentChecked, permission: approval:read } ]参数说明component字段值workbench/Index对应src/views/workbench/Index.vue路径拼接规则为/views/${item.component}.vuepermission是后端返回的权限码用于router.beforeEach守卫比对用户角色权限icon值HomeFilled是 Element Plus 内置图标名需在main.ts中全局注册app.component(HomeFilled, HomeFilled)。3.2 权限守卫拦截无权访问的路由并重定向src/router/index.ts中的守卫逻辑如下router.beforeEach(async (to, from, next) { const userStore useUserStore() if (!userStore.token) { if (to.path ! /login) { next({ path: /login, query: { redirect: to.fullPath } }) } else { next() } } else { if (!userStore.menus.length) { await userStore.fetchMenus() // 调用 API 获取 menu-config.json 结构 const routes generateRoutes(userStore.menus) routes.forEach(route router.addRoute(route)) // 动态添加路由 } // 权限校验 const hasPermission userStore.permissions.includes(to.meta.permission as string) if (!hasPermission to.meta.permission) { next({ path: /403 }) // 无权限跳转 } else { next() } } })提示fetchMenus()返回的数据结构必须与menu-config.json一致否则generateRoutes()会因字段缺失导致路由生成失败。常见坑是后端返回的component字段带.vue后缀如component: workbench/Index.vue而模板代码期望无后缀需在fetchMenus()中统一item.component item.component.replace(.vue, )。3.3 菜单模板的响应式适配PC 与移动端折叠逻辑模板的左侧菜单在src/layout/components/Sidebar.vue中实现响应式template el-menu :default-activeactiveMenu :collapseisCollapse :collapse-transitionfalse :unique-openedtrue selecthandleSelect sidebar-item v-forroute in sidebarRouters :keyroute.path :itemroute :base-pathroute.path / /el-menu /template script setup import { computed, ref } from vue import { useAppStore } from /store/app const appStore useAppStore() const isCollapse computed(() appStore.sidebar.isCollapse) // 监听窗口宽度变化自动折叠菜单 const handleResize () { if (window.innerWidth 768) { appStore.toggleSidebar(true) // 移动端强制折叠 } } window.addEventListener(resize, handleResize) /script关键参数表参数作用可调值collapse控制菜单是否折叠true/falseunique-opened是否只展开一个子菜单trueOA 场景需避免多级菜单同时展开default-active默认高亮的菜单项通常设为route.pathisCollapse计算属性绑定appStore.sidebar.isCollapse实现状态同步由appStore.toggleSidebar()控制4. 表单与审批流复用模板中的业务组件开发范式4.1 审批表单的 Schema 驱动设计模板中src/views/approval/Create.vue不直接写el-form而是通过 JSON Schema 渲染template schema-form :schemacurrentSchema :modelformModel submithandleSubmit / /template script setup import { ref, onMounted } from vue import { useRoute } from vue-router import { getApprovalSchema } from /api/approval const route useRoute() const currentSchema ref({}) const formModel ref({}) onMounted(async () { // 根据路由参数 type 动态加载审批模板 const { data } await getApprovalSchema(route.query.type as string) currentSchema.value data.schema // 如请假、报销、采购等不同 schema }) /scriptgetApprovalSchema接口返回的schema示例{ type: object, properties: { reason: { title: 事由, type: string, ui:widget: textarea }, days: { title: 天数, type: number, ui:widget: input-number, minimum: 0.5, multipleOf: 0.5 } }, required: [reason, days] }模板字符串应用ui:widget字段值textarea、input-number是模板字符串占位符schema-form组件内部通过switch匹配并渲染对应 Element Plus 组件// src/components/schema-form/index.vue const renderWidget (schema: any) { switch (schema[ui:widget]) { case textarea: return h(ElInput, { type: textarea, modelValue: model[schema.title], onUpdate:modelValue: (v: any) model[schema.title] v }) case input-number: return h(ElInputNumber, { modelValue: model[schema.title], onUpdate:modelValue: (v: any) model[schema.title] v }) } }4.2 审批流节点状态机用 Pinia 管理多步骤状态src/store/modules/approval.ts定义了审批流的状态机import { defineStore } from pinia export const useApprovalStore defineStore(approval, { state: () ({ currentStep: 0, // 当前步骤索引 steps: [ { id: draft, name: 草稿, status: wait }, { id: submit, name: 已提交, status: process }, { id: approve, name: 审批中, status: process }, { id: done, name: 已完成, status: finish } ] as StepItem[], nodeData: {} as Recordstring, any // 各节点表单数据 }), actions: { nextStep() { if (this.currentStep this.steps.length - 1) { this.currentStep } }, prevStep() { if (this.currentStep 0) { this.currentStep-- } }, setNodeData(nodeId: string, data: any) { this.nodeData[nodeId] { ...this.nodeData[nodeId], ...data } } } })在src/views/approval/Process.vue中调用template el-steps :activeapprovalStore.currentStep finish-statussuccess el-step v-for(step, index) in approvalStore.steps :keyindex :titlestep.name / /el-steps div classstep-content component :isstepComponents[approvalStore.currentStep] / /div div classstep-actions el-button clickapprovalStore.prevStep v-ifapprovalStore.currentStep 0上一步/el-button el-button typeprimary clickapprovalStore.nextStep v-ifapprovalStore.currentStep approvalStore.steps.length - 1下一步/el-button /div /template注意stepComponents是一个数组每个元素是动态导入的组件如[() import(/views/approval/steps/Draft.vue), () import(/views/approval/steps/Submit.vue)]。这种设计使审批流节点可独立维护避免单文件过大。4.3 表单校验与后端字段对齐避免“前端校验通过后端拒绝”模板中src/utils/validator.ts提供字段级校验规则export const rules { user.name: [ { required: true, message: 姓名不能为空, trigger: blur }, { min: 2, max: 20, message: 姓名长度2-20个字符, trigger: blur } ], user.phone: [ { required: true, message: 手机号不能为空, trigger: blur }, { pattern: /^1[3-9]\d{9}$/, message: 请输入正确的手机号, trigger: blur } ] }关键点在于字段名user.name必须与后端 API 文档定义的 JSON Key 完全一致。例如后端要求提交{ user: { name: 张三, phone: 13800138000 } }则前端rules的 key 必须是user.name而非name。否则校验通过后后端解析时因字段嵌套层级不符而报错。5. 打包与部署解决 VUE 打包后布局异常及跨域问题5.1 修复vue 打包后 布局异常的三大根源VUE架构OA系统模板.rar在npm run build后出现布局错乱如侧边栏消失、按钮样式丢失常见原因及修复方案1. CSS 作用域污染未清除模板中部分组件使用style scoped但src/assets/styles/index.scss的全局样式被import多次// src/assets/styles/index.scss import ./variables.scss; // 定义 $--color-primary import ./element-variables.scss; // 覆盖 Element Plus 主题色 import ./base.scss; // 重置 body margin若base.scss中有body { margin: 0; }而element-variables.scss中又定义了$--body-background-color: #f5f5f5;构建时 CSS 顺序错乱会导致样式覆盖失效。修复命令# 删除重复 import确保 variables.scss 在最前 sed -i /import.*base\.scss/d src/assets/styles/index.scss echo import ./base.scss; src/assets/styles/index.scss2. 路径别名未生效于 CSSsrc/assets/styles/element-variables.scss中引用字体$--font-path: ~element-plus/lib/theme-chalk/fonts;Vite 默认不处理~别名需在vite.config.ts中显式配置export default defineConfig({ resolve: { alias: { ~: path.resolve(__dirname, src) } } })3. PostCSS 插件缺失模板使用autoprefixer适配老版浏览器但vite.config.ts未启用import postcss from postcss import autoprefixer from autoprefixer export default defineConfig({ css: { postcss: { plugins: [autoprefixer()] } } })5.2 Nginx 部署配置解决静态资源 404 与 history 模式路由构建产物dist/部署到 Nginx 后访问/approval/list返回 404需修改nginx.conflocation / { root /var/www/vue-oa; index index.html; try_files $uri $uri/ /index.html; # 关键history 模式回退 } # 静态资源缓存 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control public, immutable; }提示try_files $uri $uri/ /index.html是 Vue Router history 模式的必需配置。若省略Nginx 会直接返回 404而非将请求交由前端路由处理。5.3 环境变量分离区分开发、测试、生产 API 地址模板中src/env.d.ts定义了环境类型declare global { interface ImportMetaEnv { readonly VUE_APP_BASE_API: string readonly VUE_APP_SSO_URL: string } }在.env.production中设置VUE_APP_BASE_API /api VUE_APP_SSO_URL https://sso-prod.weaver.com.cnsrc/utils/request.ts中使用const service axios.create({ baseURL: import.meta.env.VUE_APP_BASE_API, timeout: 10000 })构建时通过npm run build -- --mode production自动加载对应.env.*文件避免手动修改代码。6. 对接泛微 OA 单点登录实现金蝶主数据同步的轻量级集成6.1 泛微 SSO 登录流程的 Vue 端实现模板中src/views/login/Login.vue需扩展泛微登录入口template div classlogin-container el-button clickhandleWeaverLogin使用泛微账号登录/el-button /div /template script setup import { useRouter } from vue-router import { useUserStore } from /store/user import CryptoJS from crypto-js const router useRouter() const userStore useUserStore() const handleWeaverLogin () { // 1. 生成随机 salt const salt Math.random().toString(36).substr(2, 9) // 2. 构造回调 URL需在泛微后台配置白名单 const redirectUri encodeURIComponent(${location.origin}/sso/callback) // 3. 拼接泛微登录 URL const weaverUrl https://sso.weaver.com.cn/login?redirect_uri${redirectUri}salt${salt} location.href weaverUrl } /script回调页面src/views/sso/Callback.vuescript setup import { onMounted } from vue import { useUserStore } from /store/user import CryptoJS from crypto-js const userStore useUserStore() onMounted(() { // 从 URL 获取泛微返回的加密参数 const urlParams new URLSearchParams(location.search) const encryptedData urlParams.get(data) // 泛微返回的 AES 加密字符串 const salt urlParams.get(salt) // 使用预共享密钥解密密钥由泛微管理员提供 const key CryptoJS.enc.Utf8.parse(your-weaver-shared-key) const iv CryptoJS.enc.Utf8.parse(salt?.padEnd(16, 0).substring(0, 16)) const decrypted CryptoJS.AES.decrypt(encryptedData!, key, { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }) const userInfo JSON.parse(decrypted.toString(CryptoJS.enc.Utf8)) userStore.setToken(userInfo.token) userStore.setUserInfo(userInfo) location.href / // 跳转首页 }) /script6.2 金蝶主数据同步通过 API 拉取组织架构src/api/k3cloud.ts封装金蝶接口import request from /utils/request // 获取金蝶组织架构需金蝶云星空开放平台授权 export function fetchK3OrgTree() { return request({ url: /k3/org/tree, method: get, headers: { Authorization: Bearer ${localStorage.getItem(k3-token)} // 金蝶 OAuth2 Token } }) }在src/views/org/Tree.vue中调用const loadOrgTree async () { try { const { data } await fetchK3OrgTree() // 金蝶返回数据格式{ items: [{ id: 1, name: 总部, children: [...] }] } orgTree.value convertToElTree(data.items) // 转为 Element Plus Tree 所需格式 } catch (error) { ElMessage.error(获取金蝶组织架构失败) } }金蝶字段映射表金蝶字段Vue 组件字段说明idvalue树节点唯一标识namelabel节点显示名称childrenchildren子节点数组codekey用于权限绑定的编码如ORG_001注意金蝶接口返回的children可能为空数组[]或nullconvertToElTree函数需做空值判断if (!item.children || item.children.length 0) delete item.children否则 Element Plus Tree 会报错。6.3 单点登录后的菜单动态合并泛微登录成功后需将泛微的菜单权限与本地menu-config.json合并// src/store/modules/user.ts actions: { async fetchMenus() { // 1. 获取本地菜单配置 const localMenus await fetchLocalMenuConfig() // 2. 获取泛微权限菜单通过泛微 API const weaverMenus await fetchWeaverMenus(this.token) // 3. 合并保留本地菜单结构仅替换 permission 字段 this.menus localMenus.map(local { const matched weaverMenus.find(w w.path local.path) return { ...local, permission: matched?.permission || local.permission } }) } }合并逻辑确保本地模板定义的路由结构path、component不变权限控制点permission由泛微实时下发实现“一次登录多系统权限联动”。本文还有配套的精品资源点击获取