前端技术栈为 Vue 2 + Element UI + 自研聊天组件。用户反馈集中于三点:

  1. 操作界面卡顿(尤其在 Pod 列表 >500 行时);
  2. AI 助手体验割裂(气泡样式与主界面不一致,无流式响应);
  3. 主题定制成本高(每次客户要换品牌色,需手动覆盖 30+ SCSS 变量)。

经评估,决定用 DevUI 替代 Element UI,用 MateChat 替代自研聊天模块。本文记录此次重构的完整过程、关键决策、踩坑记录与最终收益,不含任何营销话术。


一、为什么不是 Ant Design Vue?也不是 Naive UI?

项目初期,团队曾评估多个方案:

方案 优势 劣势 结论
Ant Design Vue 生态成熟,文档全 包体积大(gzip 后 800KB+),默认样式偏重 排除
Naive UI 轻量,TypeScript 支持好 企业级场景组件缺失(如高级表格、树形选择) 排除
DevUI 华为内部验证,支持虚拟滚动、主题变量、暗黑模式 社区较小 入选

核心判断依据只有一条:能否在不牺牲性能的前提下,支撑 1000+ 行数据的实时操作?

我们做了基准测试:

  • Element UI:500 行表格,滚动帧率 28 FPS,内存占用 180MB;
  • DevUI(开启 virtual-scroll):1000 行表格,滚动帧率 58 FPS,内存占用 95MB。

差距显著。更重要的是,DevUI 的 d-table 支持 row-keyrender 函数,允许我们在单元格内嵌入任意 Vue 节点——这正是我们需要的。


二、表格重构:从“能用”到“高效”

2.1 旧实现的问题

原代码使用 v-for 渲染表格:

<!-- 旧代码(已废弃) -->
<template>
  <el-table :data="pods" style="width: 100%">
    <el-table-column prop="name" label="名称" />
    <el-table-column label="状态">
      <template #default="{ row }">
        <el-tag :type="getStatusType(row.status)">{{ row.status }}</el-tag>
      </template>
    </el-table-column>
    <el-table-column label="操作">
      <template #default="{ row }">
        <el-button size="mini" @click="viewLogs(row)">日志</el-button>
        <el-button size="mini" type="danger" @click="deletePod(row)">删除</el-button>
      </template>
    </el-table-column>
  </el-table>
</template>

问题:

  • 无虚拟滚动,500 行即卡顿;
  • el-button 每行创建两个实例,DOM 节点爆炸;
  • 状态计算函数 getStatusType 在每帧重复执行。

2.2 新实现:DevUI + render 函数

<template>
  <d-table
    :data="pods"
    :columns="columns"
    row-key="metadata.name"
    virtual-scroll
    style="height: 600px"
  />
</template>

<script setup lang="ts">
import { h } from 'vue';
import { Button, Tag } from 'vue-devui';

const columns = [
  { title: '名称', field: 'metadata.name' },
  {
    title: '状态',
    width: 100,
    render: (row) => {
      const phase = row.status.phase;
      return h(Tag, {
        color: phase === 'Running' ? 'success' : phase === 'Pending' ? 'warning' : 'danger'
      }, () => phase);
    }
  },
  {
    title: '操作',
    width: 180,
    fixed: 'right',
    render: (row) => {
      return h('div', { class: 'ops' }, [
        h(Button, {
          size: 'sm',
          onClick: () => viewLogs(row)
        }, () => '日志'),
        h(Button, {
          size: 'sm',
          status: 'danger',
          onClick: () => deletePod(row)
        }, () => '删除')
      ]);
    }
  }
];

// 方法定义(略)
</script>

<style scoped>
.ops {
  display: flex;
  gap: 8px;
}
</style>

关键改进

  • 使用 h() 函数创建 VNode,避免模板编译开销;
  • virtual-scroll 仅渲染可视区域,内存下降 47%;
  • 所有组件来自 DevUI,自动继承主题。

上线后,用户反馈:“终于不卡了”。


三、主题系统:从“硬编码覆盖”到“变量驱动”

3.1 旧方案:SCSS 变量地狱

原项目通过 @import 覆盖 Element UI 变量:

// theme/finance.scss
$--color-primary: #0052D9;
$--font-size-base: 14px;
// ... 共 32 个变量
@import '~element-ui/packages/theme-chalk/src/index';

问题:

  • 每次升级 Element UI,需重新核对变量名;
  • 暗黑模式需另写一套;
  • 客户定制需新建文件,构建配置复杂。

3.2 新方案:CSS Variables 一统天下

DevUI 全面采用 CSS Variables,我们只需:

/* src/assets/themes/finance.css */
[data-theme='finance'] {
  --devui-brand-color: #0052D9;
  --devui-primary-color: #0052D9;
}

切换逻辑:

// utils/theme.ts
export const setTheme = (theme: string) => {
  document.documentElement.setAttribute('data-theme', theme);
  localStorage.setItem('theme', theme);
};

// 初始化
const saved = localStorage.getItem('theme') || 'light';
setTheme(saved);

效果

  • 所有 DevUI 组件自动适配;
  • 暗黑模式只需定义 [data-theme='dark']
  • 新增客户主题,只需新增一个 CSS 文件,无需动 JS。

构建产物减少 12KB(因不再打包多套 SCSS)。


四、AI 助手重构:从“弹窗玩具”到“工作流组件”

4.1 旧实现:自研聊天框

原 AI 助手是一个独立弹窗,使用原生 HTML + CSS 实现:

<div class="chat-bubble user">你好</div>
<div class="chat-bubble bot loading">...</div>

问题:

  • 气泡圆角(4px)与主界面按钮(6px)不一致;
  • 无流式更新,必须等模型返回完整结果;
  • 快捷提示需手动维护位置与事件。

4.2 新实现:MateChat 嵌入主布局

我们将助手集成到主界面的 Tab 中:

<template>
  <d-tabs v-model:active-id="activeTab">
    <d-tab-panel id="workloads">工作负载</d-tab-panel>
    <d-tab-panel id="ai">AI 助手</d-tab-panel>
  </d-tabs>

  <div v-if="activeTab === 'ai'" style="height: calc(100% - 40px)">
    <mc-layout style="height: 100%">
      <div class="messages">
        <mc-bubble
          v-for="(msg, i) in messages"
          :key="i"
          :from="msg.role"
          :loading="msg.loading"
        >
          {{ msg.content }}
        </mc-bubble>
      </div>
      <mc-input @send="send" />
    </mc-layout>
  </div>
</template>

关键优势

  • mc-bubble 圆角、间距、字体与 DevUI 一致;
  • 内置 loading 状态,天然支持流式输出;
  • mc-input 支持 Ctrl+Enter 发送,符合开发者习惯。

更重要的是,用户不再需要“打开弹窗”,对话成为工作流的一部分


五、流式响应:不是功能,而是体验底线

旧系统调用 AI 接口:

const res = await fetch('/api/ask', { method: 'POST', body: JSON.stringify({ q }) });
const answer = await res.text();
appendMessage('bot', answer);

用户看到的是:输入 → 等待 3~5 秒 → 突然出现整段文字。

新系统使用 OpenAI 流式 API + MateChat:

const stream = await client.chat.completions.create({
  model: 'gpt-4o-mini',
  messages: [{ role: 'user', content: text }],
  stream: true
});

// 添加 loading 消息
messages.push({ role: 'model', content: '', loading: true });

// 关闭 loading
messages[messages.length - 1].loading = false;

// 流式追加
for await (const chunk of stream) {
  const delta = chunk.choices[0]?.delta?.content || '';
  messages[messages.length - 1].content += delta;
}

用户体验差异

  • 旧:等待感强,不确定是否卡死;
  • 新:立即反馈,文字逐字出现,心理预期被满足。

MateChat 的 loading 属性让这一过程零成本实现。


六、上下文传递:让 AI 真正“知道你在哪”

早期 AI 助手常答非所问,因为模型不知道当前上下文。

我们在请求中注入 MCP(Model Context Protocol):

function buildContext() {
  return {
    cluster: currentCluster.value,
    namespace: currentNamespace.value,
    resourceType: 'Pod',
    resourceName: selectedPod.value?.metadata.name
  };
}

async function send(text: string) {
  const context = buildContext();
  
  const stream = await client.chat.completions.create({
    model: 'ops-assistant-v2',
    messages: [
      { role: 'system', content: JSON.stringify({ type: 'mcp_context', data: context }) },
      { role: 'user', content: text }
    ],
    stream: true
  });
  // ... 流式处理
}

前端只负责采集和透传,模型侧解析 MCP 并生成针对性回答。

结果:用户问“这个 Pod 为什么重启?”,AI 能直接引用该 Pod 的事件日志。


七、性能与包体积:数据说话

指标 重构前 重构后 变化
首屏加载时间 3.2s 2.1s ↓ 34%
表格 500 行滚动 FPS 28 58 ↑ 107%
JS 包体积(gzip) 1.8MB 1.4MB ↓ 22%
主题切换耗时 800ms(需 reload) 50ms(纯 CSS) ↓ 94%

关键优化点:

  • DevUI 支持按需引入(通过 unplugin-vue-components);
  • 移除自研聊天组件(约 40KB);
  • CSS Variables 替代 SCSS 编译。

八、踩过的坑与解决方案

坑 1:MateChat 与 DevUI 图标冲突

现象:mc-bubble 中的头像图标显示为方块。

原因:未引入 @devui-design/icons

解决:

// main.ts
import '@devui-design/icons/icomoon/devui-icon.css';

坑 2:虚拟滚动高度必须固定

现象:d-table 开启 virtual-scroll 后内容空白。

原因:父容器未设置明确高度。

解决:

<d-table style="height: 600px" virtual-scroll />

或使用 CSS 计算:

.d-table-container {
  height: calc(100vh - 200px);
}

坑 3:OpenAI 浏览器直连的安全警告

现象:控制台报错 “API key exposed in browser”。

说明:dangerouslyAllowBrowser: true 仅用于开发。

生产方案:必须通过后端代理

我们临时方案(仅限内网):

// 仅开发环境允许
if (import.meta.env.DEV) {
  new OpenAI({ dangerouslyAllowBrowser: true });
}

九、不是终点,而是新起点

这次重构没有“银弹”,只有对工程细节的极致关注

  • 表格卡顿?→ 用虚拟滚动 + render 函数;
  • 主题混乱?→ 用 CSS Variables 统一管理;
  • AI 割裂?→ 用 MateChat 嵌入工作流;
  • 响应慢?→ 用流式 API + loading 状态。

DevUI 和 MateChat 的价值,不在于它们“多酷”,而在于它们解决了我们每天面对的真实问题

未来,我们将探索:

  • 自然语言生成 DevUI 组件(NL2UI);
  • AI 助手主动建议操作(如“检测到异常,是否查看日志?”);
  • 跨会话记忆同步。

但这一切,都建立在稳定、高效、一致的界面基础之上

而这个基础,我们已经打下。

项目地址:

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐