本文主要介绍在「媒体内容产品 + 管理后台」混合型前端(以 Octans Web 为例)里,如何做 UI 基座长期选型:候选如何比、为何不再把强预设组件库当视觉底座,以及落地后的实际栈形态。结论面向同类 Vue 3 产品可复用,不绑定某一仓库路径。

适用场景是:既有海报墙、详情页、播放页这类内容型界面,又有高密度列表、批处理、设置页与用户治理这类控制台界面;技术栈大致为 Vue 3 + Vite,并希望主题与组件外观由自己主导。若项目只是标准 CRUD 中后台,权重会不同,不宜原样照搬。

产品形态约束

混合前端的难点不在「有没有好看的表格」,而在同一套 UI 基座要同时服务两种气质完全不同的页面。

以媒体库类产品为例,前端至少会同时承担:

页面族群典型任务对 UI 基座的要求
内容读取海报墙、人物墙、详情页深色优先、品牌感、大图与卡片节奏
播放自定义 overlay、轨道菜单、侧栏设置界面主导权在自己,不能被强预设壳绑死
库工作台高密度列表、筛选、批处理表格状态机、虚拟滚动、工具栏密度
控制台设置、用户、库治理表单、弹层、选择器、消息确认

因此选型不能只对「表单 + 表格」友好。至少要同时满足:

  • 深色与自有主题是主路径,不是事后给 vendor 打补丁。
  • 内容页 / 播放页要有自由度,看起来像内容产品,而不是企业后台皮肤。
  • 后台能力仍要完整:表单、筛选、弹层、菜单、设置页不能缺胳膊少腿。
  • 虚拟化是硬约束:海报墙与后台表都要能扛到十万级量级目标;测试库小不等于生产小。
  • 数据权威在服务端:排序、筛选、分页由后端 API 完成;前端表格库只做状态机、渲染与虚拟化(manual / lazy 模式),不是把全量数据塞进浏览器再算。

还有一条常被低估的边界:Web UI 基座只负责把 Web 端做好。若多端路线是 Web 与 Android / Apple / Desktop 各自原生 UI,而不是「一个 Web 壳推全平台」,则 Quasar 一类「全应用壳 + 跨端运行时」的优势很难兑现,不应被高估。

最后是层级约束:页面层不应直接建立在 vendor 内部类名上;换一个强预设 styled suite,并不等于已经拥有设计系统。新基座必须允许形成项目自己的 ui/ 封装层,token 成为主题事实来源。

候选方向与评价维度

评价维度

本轮比较不考虑迁移成本,只按长期架构价值排序。权重可按产品自行调整,这里给出一组对混合型产品较稳的分配:

维度权重(示意)关注点
自有主题控制权tokens、组件外观、视觉节奏是否由自己掌握
深色主题一等公民暗色是主路径还是事后补丁
内容页与播放页自由度海报墙 / 详情 / 播放是否像内容产品
后台能力完整度表单、表格、筛选、弹层是否够用
与现有技术栈契合Vue 3、Vite、Tailwind 等是否顺手
代码整洁 / 低覆写压力是否减少大规模 vendor DOM 覆写与 !important
移动端窄屏适配低~中窄屏、触摸、响应式收敛
与全端矩阵一致性是否符合「Web 做基线、原生各走平台 UI」

硬约束建议固定写死,避免选型讨论滑回「哪个库默认更好看」:

  • 页面层不绑 vendor 内部类名。
  • 不把「换一个 styled suite」误认为设计系统已完成。
  • 播放页 UI 主导权留在自己(例如 hls.js + 原生 <video> + 自有 overlay)。
  • 必须允许形成自有 ui/ 边界。
  • 表格运行在服务端权威(manual / lazy)模式下。

候选范围

常见候选可以分成几档来想:

路线代表一句话
强预设中后台库Element Plus、Vuetify、Arco Design Vue 等控制台快,内容气质与主题主导权弱
现代工程工具库Naive UI 等做控制台顺,内容页仍偏 suite 思路
全应用壳框架Quasar 等壳与响应式强,战略价值依赖「整站围绕它组织」
Headless + open-codeReka UI + shadcn-vue 类行为与可访问性在底层,外观落仓自有
Unstyled 套件 + 现成 DataTablePrimeVue Unstyled / Volt 等主题可控,后台重组件面更完整

对「重组件」需求要先收敛,避免假分水岭:

  • 磁盘路径浏览往往是「面包屑 + 当前层懒加载列表」,不是 Tree / Cascader 刚需。
  • 真正硬的是虚拟化数据网格:虚拟化 grid(海报墙)+ 虚拟化 table(后台列表)。

因此第一档比较不宜只比「按钮和 Dialog 谁好看」,而应把 UI 原语datagrid 内核 绑在一起看:

  • Route AReka UI(+ open-code recipe,如 shadcn-vue 思路)+ @tanstack/vue-table + @tanstack/vue-virtual
  • Route BPrimeVue Unstyled / Volt + 其 DataTable 的 virtual scroller + lazy

页面适配度(定性)

候选内容墙 / 详情 / 播放控制台 / 列表深色 + 自有主题
Reka + open-code中高
PrimeVue Unstyled / Volt
Naive UI中高
Quasar
Element Plus 等强预设低~中低低~中

解读可以很直接:

  • 第一档是 A / B 两条可维护路线,不是「有没有组件」的有无题。
  • PrimeVue 路线的优势主要在现成虚拟化 DataTable;Reka 路线的优势在 open-code + 自有 datagrid 基础设施
  • Element Plus 继续擅长设置页与列表筛选,但不适合作为海报墙、详情页与播放页的长期视觉底座。

最终方向与理由

已落地主线

选型讨论之后,路线已经收敛并落地为当前运行时事实(表述为产品侧结论,不绑内部仓库路径):

层级选择职责
项目组件边界自有 src/ui(或等价目录)原语、overlay、feedback、form foundation、data-grid、scroll-area、recipe
行为与可访问性Reka UIheadless 交互、焦点、键盘、ARIA 等
数据网格TanStack Table + TanStack Virtual列状态、排序筛选状态机 + 虚拟化渲染
滚动区域OverlayScrollbars统一滚动条体验;原生滚动条 fallback 可清理
主题自有 design token(如 --octans-*唯一主题事实来源

Element Plus 不再是当前运行时依赖,也不再作为过渡期可借用组件集。下文写它,仅作历史背景:说明「为何离开 / 为何不作为长期主线」。

为何选 Route A,而不是对等的 Route B

两条第一档路线都合理,优先级取决于团队对 datagrid 归属 的态度:

更倾向 Route A(已选)时:

  • 更看重 open-code、Tailwind-first、最低样式中间层。
  • 希望组件源码落在自己仓库,而不是长期依赖第三方内部结构。
  • 愿意把数据网格当成自己的长期基础设施维护,而不是完全交给套件。
  • 表头交互、列状态、虚拟化策略是产品设计资产,要原样延续并继续演化,而不是寄生在现成 DataTable 骨架里。
  • 若项目里已经有自建网格壳、路径浏览也不是 Tree 问题,A 与现状更贴近。

更倾向 Route B 时:

  • 希望保留 code ownership,但对重组件更保守。
  • 不想自己扛虚拟化 datagrid 底层复杂度,优先「现成 virtual scroller + lazy」。
  • 在「完全自有主题」与「更完整的后台组件面」之间取更稳的平衡。

我们最终把 A 定为首选,而不是「A/B 并列第一」,核心不是 Reka「更轻」或「更好看」,而是共识偏向:

  1. 长期拥有自己的 datagrid 基础设施。
  2. 保留并演化自有表头交互、列状态与虚拟化策略。
  3. 避免页面层再绑定强预设成品组件库。

只有当目标明确改成「最短路径拿到现成十万级虚拟化表格」时,才应回切 B。

落地时需要同步钉死的几件事

选型文档若只写库名,落地时仍会漂。下面几条对 A 路线特别关键:

Datagrid 是基础设施,不是「某个 UI 库附带的表格」。

  • 正式路线:@tanstack/vue-table + @tanstack/vue-virtual
  • open-code 的 Data Table 样板可以当 V0 脚手架,不是长期边界。
  • 样板默认常是客户端排序 / 筛选 / 分页;对齐服务端权威时,应在落仓时立即打开 manualPagination / manualSorting / manualFiltering,不要等接真实接口再改——否则早期「看起来能跑」会掩盖架构错位。

命令式全局服务不要一刀切成纯声明式。

历史上若已有大量 Message / MessageBox 式调用落在 request、router、store 等非组件上下文,全站改成纯 <Toast> / <Dialog> 组合,迁移成本与回归风险都不划算。更稳的做法是:

  • 对外保留 toast.success()confirm.open() 一类命令式体验。
  • 底层用自有 provider + headless 原语承接。
  • confirm 返回 Promise<boolean>toast / 全局 loading 返回可 close() 的句柄。

表单校验栈与 Form 原语要一起定。

若 recipe 默认围绕 vee-validate + zod,就直接定这条主线,避免三选一悬空。Form 字段注册、校验消息、aria-* 关联属于阻塞基建,不宜跳过。

图标与尺寸外壳要统一。

图标集可以继续用已有方案(例如 Phosphor),但应用项目级 Icon wrapper 接住尺寸、对齐与 currentColor,避免散落的 vendor 图标壳。

ui/ API 契约先行。

例如:值类控件 v-model;显隐统一 v-model:opensizedensity 分开;slots 用短名。否则即使换掉旧库,新的内部组件层也会很快失控。

不选纯 Element Plus 长期主线的原因

需要先澄清:这不是因为 Element Plus「不支持深色」或「后台能力不够」。它在设置页、控制台、列表筛选上仍然很强,也适合大量标准中后台。

在混合内容产品里,问题会变成另一组事实:

默认气质偏企业后台

海报墙、人物墙、详情页、播放页需要的是内容产品节奏。强预设组件库的默认视觉与交互密度,更像管理后台。你可以覆写,但覆写本身会成为长期税。

使用面广 ≠ 深度绑定生态

历史上常见状态是:广泛使用按钮、表单、弹层、消息、下拉等基础控件层,而不是深度依赖 Table / Tree / 整套生态。 这意味着「换基座」的真实成本,往往不等于「重写整个后台框架」,而是「把基础控件层与主题接管权收回来」。

样式接管已经过重时,说明基座选错了层级

当项目出现系统性 html.dark .el-* 全局覆写、大量 :deep(.el-*)!important、对 vendor 内部 DOM 结构重写时,问题通常不是「组件功能不够」,而是:

我们已经在系统性接管 vendor 的视觉实现,却仍把页面层建在 vendor 类名之上。

这种模式会持续侵蚀页面层与组件层边界:任何升级、DOM 结构调整或主题微调,都会牵动全局 CSS。继续加抽象壳包一层 Element Plus,再期待「以后平滑换库」,往往只会多一层假边界。

与自有主题目标冲突

若设计目标不是「轻度换肤」,而是自有主题系统(token / foundation / recipe / 业务域样式分层),选型核心就应改成:

哪个方案最适合让项目真正拥有自己的视觉系统?

在这个目标下,强预设 suite 的默认值与内部结构都是阻力。Element Plus 更适合被定义为历史过渡承载,而不是长期视觉底座——过渡结束后,就应从运行时依赖中退出。

对同类项目的可复用清单

若你也在做「内容墙 + 控制台」混合前端,可以按下面清单自检。

先写清产品约束

  • 是否同时存在内容型页面与高密度管理页?
  • 深色 / 品牌主题是主路径还是可选项?
  • 播放或其它沉浸式页面是否必须自定义 UI?
  • 数据是否服务端权威?表格是否必须 virtual + lazy / manual?
  • 多端是「Web 壳全平台」还是「Web + 各端原生 UI」?

先砍假需求

  • 路径选择是否其实是「面包屑 + 分层懒加载」,而不是 Tree?
  • 所谓重组件清单里,有多少是真实页面入口,有多少是「库里有所以想要」?
  • 虚拟化网格是否被明确写成硬约束(含量级目标)?

再比第一档路线

  • A:headless + open-code + 自有 TanStack 网格内核——主题与交互资产可控,网格要自己养。
  • B:Unstyled 套件 + 现成虚拟化 DataTable——后台面更快,网格归属在第三方。
  • 是否把「UI 原语选型」和「datagrid 选型」绑在一起决策?

落地规则(与库名同等重要)

  • 是否形成自有 ui/ 边界与依赖方向(ui 不反向依赖业务页)?
  • token 是否为唯一主题事实来源?
  • 新代码是否禁止直接绑 vendor 内部类名?
  • 命令式 toast / confirm 是否有全局 provider 方案?
  • 表单校验栈与 Form 原语是否同步定稿?
  • open-code Data Table 样板是否立刻切到 manual 模式?
  • 自有基础设施是否从第一天起有最小测试 / 无障碍守护(如 vitest + axe)?

不建议的做法

  • 在强预设库上继续堆全局覆写,指望「以后再整理 CSS」。
  • 先包一层「兼容壳」假装解耦,页面仍直接依赖 vendor DOM。
  • 因库内建 Tree / Cascader 而回退已验证的分层浏览模式。
  • 用跨端壳框架的优势,去解决其实不存在的「全平台统一 Web 壳」目标。

局限与后续

局限

  • 自有 datagrid 有维护成本。 Route A 把十万级表格可用性从「组件整合问题」部分转成「自建内核问题」。表头交互、列冻结、列状态持久化、多列排序等,都要自己演化和回归。
  • 后台组件面不会一夜之间齐全。 阻塞项(Dialog、Menu、Combobox、Form 家族等)要先落仓;DatePicker、Upload 等可等真实需求再进,避免一次造全库。
  • 迁移期双栈最容易失控。 即便最终目标是清退旧库,过渡规则仍建议写死:新代码不新增旧库 import、不新增 vendor 内部类名覆写、z-index 用 token 统一。
  • 性能数字要因设备校准。 例如「万级行滚动帧率 / 挂载时间」适合作为试点硬线,而不是终态教条;应用 production build 测,不要只在 dev 里主观感觉。

后续可继续做的事

  • ui/ 与 datagrid 的契约、token、无障碍基线固化成文档与 smoke。
  • 内容页 recipe(海报墙、详情、播放周边)与控制台密度体系分开演进,共用同一 token 与原语。
  • 列状态序列化、多列排序等能力,优先落在统一 state 形状上(A 路线通常更直接)。
  • 若产品战略变成「整站围绕单一应用壳 / 跨端 Web 运行时」,再重新评估 Quasar 一类方案;那是战略变化,不是本轮权重微调。

核心结论(三句)

  1. 混合前端的 UI 选型,本质是主题主导权 + 内容页自由度 + 虚拟化网格归属,而不是「哪个中后台库组件更多」。
  2. 强预设组件库适合标准控制台;当项目已在系统性覆写 vendor 视觉时,应转向自有 ui 边界 + headless 行为层,而不是继续加壳。
  3. 当前可落地的稳妥主线是:自有组件层 + Reka UI + TanStack Table/Virtual + 统一滚动条方案;Element Plus 仅作为历史过渡背景,不再作为运行时主线。

相关阅读

同系列还可对照:

来源

本文改写自 Octans 项目前端 UI 基座选型相关决策与研究材料,结论按产品侧可复用表述整理,不绑定内部仓库路径。

参考资料