Vue3+TypeScript 企业管理平台统一UI:新增一个模块,为什么还要重新调样式?

Vue3+TypeScript 企业管理平台统一UI:新增一个模块,为什么还要重新调样式?

做企业管理软件,新增一张业务单据,真正属于业务的需求可能只有几项:选哪些对象、填哪些字段、交给谁审批、审批后做什么。

但开发时,团队很容易又花一轮时间处理标题位置、字段间距、附件表格、底部按钮和审批页签。上线之后,用户还要重新找一次"提交在哪里""进度在哪里看"。

模块少时,这些差异像是细节;当OA、人力、资产等业务放进同一个平台,细节就变成了持续发生的维护成本。

一体化平台的统一UI,应该让同类业务拥有稳定的使用方式,让开发人员把时间留给业务规则。 这需要的不只是一份配色规范,还包括可复用的页面骨架、组件契约和清楚的扩展边界。

共享组件与业务模块的关系示意。下文用真实页面和源码说明这套分工如何落地。

一、先看三个业务:字段不同,使用习惯可以相同

RuoYi Office的PC端采用Vue3、TypeScript、Vben Admin和Ant Design Vue。下面三张页面分别来自OA、人力和资产模块,展示审批中、审批通过和维修执行阶段的已有业务记录。

用印申请:进入页面先知道"这是什么单、现在到哪了"

用印需要选择印章、说明用章类型和事由,再进入审批。对于查看人,首先要确认单据身份和当前状态,然后才是具体内容。

真实完整PC视口。重点看顶部单据身份、右上状态、单据信息与审批信息页签,以及底部操作区。手机阅读可点开放大;附件为空是该记录的真实状态。

这张单据处于"审批中",主要字段已只读。标题、编号、申请人和组织信息位于表头;用章事由放在基本信息中;审批轨迹有固定入口。用户不需要从一屏字段中猜测单据是否已经提交。

这些布局与状态表达,如果每个业务都重新写一遍,就容易出现一部分页面用页签看审批,另一部分页面又把审批藏进弹窗的情况。

培训计划:保留同一骨架,容纳不同业务内容

培训计划的业务对象变成了计划名称、年度、学时、场次与培训范围。它的内容比普通申请多,但用户进入页面之后,仍能沿着熟悉的位置阅读。

真实完整PC视口。该计划已审批通过,基本信息下方展示研发部门的培训范围。表头、页签与蓝色分区标题沿用相同结构;页面下方内容可自然滚动查看。

这里最值得关注的是"培训范围"。部门、岗位、人员怎样选择,范围怎样解释,属于培训业务本身。页面应当给它足够的表达空间,而不是为了统一,把这些内容硬塞成一组通用输入框。

在源码中,培训计划通过BasicForm承接公共表头、字段区域和事件,通过form-extension插槽放入培训范围及附件。公共结构保持一致,业务内容仍由业务页面组织。

资产维修:审批通过后,还有自己的执行动作

资产维修先记录故障与报修资产,经过审批后继续维修,满足条件时才能办结。审批状态与维修进度是两件事。

真实完整PC视口。该记录顶部为"审批通过",底部仍有"办结维修",说明审批完成不等于维修完成。图中附件支持预览、下载,手机可点开放大查看。

维修页面复用同样的表头、页签、分区和附件能力,又通过底部扩展插槽加入"办结维修"。这个动作没有被强加给用印或培训计划。

三张页面不是同一张单据的连续操作录像,而是三个模块在不同业务阶段的对照。共同点让用户少找入口,差异部分让业务表达完整。

二、组件全貌:从一个输入框,到一整类业务工作台

这三张单据展示的是统一体验的一部分。沿着源码继续往下看,RuoYi Office的组件能力分布在基础框架、应用公共组件以及业务模块内部。只看BasicForm,会漏掉大量承担细节的组件。

按当前源码整理的能力地图。基础库、项目封装与领域规则分层表达;列举代表组件,不意味着所有页面都已接入。

1. 输入与语义展示:把企业字段的习惯做进组件

这一类包括input-amount、number-range-input、shortcut-date-range-picker、dict-tag、area等。它们解决金额、范围、快捷日期、字典状态和区域字段的重复交互。

以InputAmount为例,显示时使用千分位,聚焦时切换到便于编辑的原始数字;默认精度为两位,支持配置上下限,清空后输出null。失焦时校验,合法值才发出更新,无效输入恢复原值。用印单的合同金额字段已经通过Schema接入它。

这里抽象的是"金额怎么输入、显示和回传"。税额计算、币种换算、后端精度仍属于业务和服务端,不能因为使用了金额输入框就认为财务计算问题全部解决。

2. 业务对象选择:选择的不是一段文字,而是有身份的对象

user-select、unit-select、partner-select、material-select负责不同对象入口。其中客商选择将客户和供应商纳入同一套选择交互,通过role区分查询范围;物料选择拆成查询网格和弹窗确认两部分。

页面拿到的是带业务身份的类型化对象,可以据此保存ID、回填名称等字段。分页、检索、单选、双击确认和未选择提示,由选择器组织。财务应收、应付和开票页面有客商选择器调用,ERP产品维护有物料选择器调用。

统一的是"寻找并确认对象"的过程。选中客商之后怎样计算账期、选中物料之后怎样处理库存,仍由业务负责。这样才能复用对象入口,又避免让选择器变成业务处理中心。

3. 列表与批量处理:统一查询、动作和操作反馈

项目通过useVbenVxeGrid接入查询表单、列配置和分页,通过TableAction组织行操作,再用ImportExcelModal承接模板下载、选文件、覆盖选项和导入结果。

用户、角色、部门、字典及办公用品等页面已有通用导入组件接入。组件通过回调获得各业务的模板和导入接口,业务不需要再复制一套文件选择与结果弹窗。

这类抽象覆盖的是一段完整任务,而不只是一个表格外观:用户需要知道导入新增了什么、更新了什么、哪些项失败,页面也需要知道何时刷新数据。

4. 单据与审批:BasicForm是组合层,不是组件库的全部

BasicForm组织HeaderForm、CardContainer、FooterForm以及审批信息、流程图和评论入口。它把单据身份、字段区域和操作位置稳定下来,再通过事件和插槽接入领域内容。

用印、培训和维修的共同外观来自这层组合。培训范围、报修资产和办结动作则留给页面扩展。审批模块内部还保留自己的流程相关组件,不必将所有领域组件都搬进全局目录。

5. 文件与内容:上传、附件管理、预览、编辑各有职责

源码中存在上传相关组件、attachment-list、file-preview、ImageViewer,以及tinymce、markdown-view、onlyoffice-editor、contract-template-editor等内容能力。

这些能力不能简单合称"文件组件"。上传负责取得文件,附件列表负责单据中的附件集合,预览负责找到适合格式的展示方式,编辑器负责可修改的内容场景。OnlyOffice等集成的可用范围还依赖相应服务和配置,组件目录存在不等于所有环境已经启用。

维修截图里的附件预览、下载,就是附件集合与文件访问能力组合后的入口。调用方无需各写一套文件后缀判断和访问地址处理。

6. 档案与复杂布局:长信息页面也有共同组织方式

ProfileArchiveLayout面向身份信息与多分区资料,支持页签、垂直平铺两种模式;resizable-split-layout则提供分栏布局能力。资产卡片详情已经接入档案布局。

档案组件按preferenceKey保存布局偏好,过滤不展示的分区;如果当前激活分区消失,会切换到第一个可见分区。它解决的不是某一张表单的摆放,而是一类长信息页面的阅读连续性。

这六类是按职责划分的能力地图,并非把目录名堆成宣传清单。底层Ant Design Vue、Vben、VxeTable等提供基础能力,RuoYi Office在其上组合和封装企业业务交互;领域页面再补充自身规则。丰富度来自覆盖的任务范围,抽象能力来自每层边界是否清楚。

三、数据关系:统一的是契约,不是把业务数据混在一起

前端组件化不需要把培训计划、用印申请和维修单合并成一张大表。每种单据仍然拥有自己的数据、接口和业务状态,公共组件读取的是约定好的展示与交互信息。

组件与数据关系图:业务页面组织领域数据,公共组件承接展示和交互,业务API负责各自的保存或提交。

在当前实现中,headerData提供单据身份,formData与formSchema提供字段数据及配置,readonly、字段权限和流程状态参与页面行为。事件向上通知业务页面,插槽向下接入特有内容。

这几类信息不能随意混用。只读意味着当前场景下不允许编辑,不代表用户一定拥有查看所有字段的权限;审批通过意味着流程结果已确定,也不代表维修已经办结。组件接口需要保留这些语义差别。

数据归属也应清楚。培训页面在保存时合并表单值与自身维护的scopes,公共表单没有替它定义培训范围的存储结构。维修页面判断业务状态,再决定是否提供办结动作。这样的关系才能允许一个组件服务不同模块。

四、精细设计藏在边界条件里

客商选择:同一入口,角色条件落到查询

PartnerSelectGrid不是只改弹窗标题,而是把角色转换为查询条件:

ts 复制代码
function roleParams() {
  if (props.role === 1) {
    return { customer: true };
  }
  if (props.role === 2) {
    return { supplier: true };
  }
  return {};
}

网格查询同时携带页码、页大小和筛选字段。外层弹窗打开时清空上次选择,确认前检查是否选中对象,确认后发出select事件并关闭;双击行也走同一确认方法。这让"选择供应商"不必在每张单据里重新实现一次。

附件管理:身份稳定,展示地址与业务数据分开

附件列表为行补充稳定的rowKey,识别附件时依次考虑行键、持久化ID、文件URL,再以名称、大小和上传时间作后备匹配。这样处理了表格克隆行对象后,不能依赖对象引用判断同一附件的问题。

预览入口将文件名、扩展名和地址交给FilePreviewModal,不支持的情况提供下载回调。格式识别也有明确优先级,下面是实际实现:

ts 复制代码
export function resolveFileExtension(
  fileName?: string,
  fileUrl?: string,
  fileExtension?: string,
): string {
  const explicit = knownExtension(
    fileExtension?.startsWith('.') ? fileExtension.slice(1) : fileExtension,
  );
  if (explicit) {
    return explicit;
  }
  const fromName = knownExtension(fileName);
  if (fromName) {
    return fromName;
  }
  return knownExtension(fileUrl);
}

knownExtension只接受已知后缀,先去掉查询参数和锚点。显式扩展名优先,其次文件名,最后才从URL推断,减少带签名地址对格式判断的干扰。

文件访问工具还会判断签名是否有效,必要时请求可访问地址或回退同源预览接口。格式选择与地址可用性是两个问题,分开处理后,业务页面只需表达"预览这份附件"。这并不保证任意文件都能预览,也不替代服务端文件权限。

导入结果:接口差异收敛到统一反馈

ImportExcelModal接收不同业务的导入回调,并把两种已有返回字段命名归一:

ts 复制代码
function normalizeResult(raw: any): ImportExcelResult {
  if (!raw || typeof raw !== 'object') {
    return { createKeys: [], updateKeys: [], failureMap: {} };
  }
  return {
    createKeys: raw.createKeys ?? raw.createUsernames ?? [],
    updateKeys: raw.updateKeys ?? raw.updateUsernames ?? [],
    failureMap: raw.failureMap ?? raw.failureUsernames ?? {},
  };
}

新增、更新与失败明细因此可以使用同一套展示。切换导入类型时会清空之前选中的文件和结果,避免拿一种模板的文件提交到另一类接口;请求期间锁定弹窗,结束后解锁。

这也是当前实现的边界:归一函数保留了any与历史字段兼容,接入方需要按约定返回结果,不能把任意响应都当成业务成功。旧账等"全量校验通过才落库"的任务可以采用专门流程,不应为了复用外观改变导入语义。

档案分区:可见性变化之后,激活状态仍然有效

档案组件不只提供两种布局,还维护切换后的有效分区:

ts 复制代码
const visibleSections = computed(() =>
  props.sections.filter((s) => s.visible !== false),
);

watch(
  visibleSections,
  (list) => {
    if (!list.some((s) => s.key === activeKey.value)) {
      activeKey.value = list[0]?.key || '';
    }
  },
  { immediate: true },
);

隐藏某分区之后,页面不会继续停留在失效页签上。布局偏好按调用方提供的键保存,避免不同档案页面无意共用偏好。可见性是展示契约,具体权限仍要由调用方和服务端确定。

动作与样式:统一规则,也留下业务出口

TableAction先判断业务ifShow,再判断权限码;没有配置权限码的动作会通过权限这一层,所以接入配置必须完整。CardContainer用hsl(var(--primary))统一标题及标记颜色,减少逐页硬编码。

维修页面的"办结维修"通过footer-extra插槽接入,条件仍由维修页面判断:非审批内嵌、存在单据ID、审批通过且维修状态符合要求。公共组件维护动作位置,业务维护动作资格,前端显示判断也不能替代后端校验。

五、组合起来:一次导入怎样跨过公共层与业务层

组件体系的价值要落到完整任务上。以通用Excel导入为例,页面提供模板下载函数与导入请求函数;公共组件处理选文件、覆盖选项、提交中状态和结果展示;业务接口处理真正的数据校验与写入。

按当前导入组件源码整理的对象结果分支。返回非空字符串时走单独的成功提示分支;本次未执行实际导入。

选择文件时,组件返回false阻止上传控件自动提交,等用户明确确认后才调用业务回调。接口返回后,组件归一结果、统计新增与更新数量;有成功项才在这个分支发出success事件,调用页面据此刷新。失败项留在弹窗中供用户查看。

统一的并不是每个业务的数据处理算法,而是用户完成同类任务的操作路径。模板内容、唯一键、覆盖策略和服务端事务仍归各业务所有;公共组件把可重复的交互集中起来。

六、新增模块时,应该补业务,不再复制一整页

对开发团队而言,新模块接入可以沿着一条稳定路径进行:先找同类业务页面,复用公共骨架;再定义字段、数据类型和权限;然后补充领域内容与动作;最后接入本模块API。

例如增加一种包含人员范围的申请,可以复用表头、分区、附件和操作布局,但人员范围的筛选条件仍由新业务决定。列表页则沿用项目的useVbenVxeGrid和TableAction,不要因为某一列显示特殊就重写整套列表。

有业务差异时,先判断能否通过现有配置、事件或插槽表达。如果每个接入页面都需要深度覆盖CSS,或者一个公共组件不断增长布尔开关,往往意味着抽象边界需要重新检查。当前实现也存在多种配置开关,后续仍要控制复杂度,而不是把"已经封装"当成终点。

公共改动的回归范围同样要可控。调整标题间距,关注不同内容长度的页面;修改提交交互,覆盖普通提交、自选审批人和校验失败;修改附件行为,覆盖可编辑与只读场景。这里的成本不会消失,但可以从逐页修补转为围绕公共规则维护。

七、降低用户使用成本,靠的是可迁移的操作经验

员工今天申请用印,明天查看培训,后天报修资产,业务知识可以不同,但不应每次重新学习界面。单据身份位置稳定、状态含义清楚、审批入口一致、附件操作一致,之前积累的操作经验就能继续使用。

这也给统一UI划出了边界:布局相似不等于业务含义相同。"审批通过"不能替代"维修完成",培训范围不能被简化成一个没有规则的人员框。真正需要降低的是重复寻找入口和重复理解基础交互的成本。

对于中大型企业管理软件,组件化的长期价值,是让团队拥有一套可以持续演进的公共能力。新增业务时,开发人员集中处理领域规则;公共体验改进时,已经接入的页面有共同的维护入口。模块继续增加,界面与使用习惯仍能保持连贯,这才是一体化平台值得投入的统一UI。

🤖 在线体验·智能体 :https://ruoyioffice.com/ai/work

🏢 在线体验·业务系统 :https://ruoyioffice.com/web/

📘 产品手册 :https://ruoyioffice.com/

💬 微信:17156169080(备注「AgentOS」)

相关推荐
流烟默21 小时前
SSE 实战:在 SpringBoot + Vue 项目中零依赖实现轻量级实时消息推送
vue.js·spring boot·后端·sse
ly76891 天前
Spring Boot 集成 PostgreSQL 的连接池调优:HikariCP、prepared statement 与服务器资源边界
spring boot·postgresql·hikaricp·连接池调优
wangjialelele1 天前
一文梳理 Spring Cloud 核心组件:Eureka、Nacos、OpenFeign、LoadBalancer 与 Gateway
java·spring boot·spring·spring cloud·微服务·nacos
七夜zippoe1 天前
Spring Boot 接通义千问:从拿 Key 到第一个对话接口跑通(保姆级)
java·spring boot·后端
Nebula_g1 天前
SpringBoot企业级实战项目—云禾外卖—SpringBoot常用注解解析
java·网络·数据库·spring boot·后端·spring·javase
Shockang1 天前
TanStack Router 实战:URL 校验、Loader 与路由权限
typescript·react·路由·前端开发·tanstack router
企业数字化笔记1 天前
租户停用后数据和任务怎么处理?冻结、恢复与保留策略的实现
android·java·spring boot
vx_Biye_Design1 天前
PHP中医养生馆预约系统60841-计算机课程设计、毕业设计
vue.js·spring boot·后端·mysql·php·课程设计·express
TYC91 天前
Mac创建Vue3+TypeScript或者React+TypeScript项目
前端·javascript·typescript