三十三、【扩展工具篇】代码比对:集成 Monaco Editor 实现 Diff 功能
前言
准备工作
第一部分:前端环境配置 (Vite + Monaco Editor)
第二部分:前端页面与组件实现
1. 添加路由和侧边栏菜单入口
2. 创建代码比对页面 (`src/views/tools/DiffCheckerView.vue`)
第三部分:全面测试与验证
总结
前言
在日常的测试开发工作中,我们经常需要比较两段代码或文本的差异。例如,比较接口返回的新旧 JSON、对比不同版本的配置文件、或者查看代码的细微修改。为了完成这些操作,通常需要借助外部的 Diff 工具。
为了提升效率,在测试平台中集成一个代码比对工具。用户可以在两个并排的编辑器中输入或粘贴内容,系统将实时、高亮地显示两者之间的差异。

本文目标:
在测试平台中新增一个“代码比对”工具页面。用户可以在两个并排的编辑器中粘贴或输入文本/代码,系统会实时、高亮地显示两者之间的差异。
准备工作
前端项目就绪: test-platform/frontend 项目可以正常运行 (npm run dev)。
Element Plus 集成完毕。
安装 Monaco Editor 库:
在前端项目根目录 (test-platform/frontend) 下打开终端,运行:
npm install monaco-editor@latest --save

第一部分:前端环境配置 (Vite + Monaco Editor)
配置 vite.config.ts:
打开 test-platform/frontend/vite.config.ts,进行如下修改:

// test-platform/frontend/vite.config.ts
import {
fileURLToPath, URL } from 'node:url'
import {
defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig(({
mode }) => {
const env = loadEnv(mode, process.cwd(), '')
return {
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
host: '0.0.0.0',
port: parseInt(env.VITE_PORT) || 5173,
proxy: {
'/api': {
target: 'http://127.0.0.1:8000',
changeOrigin: true,
}
}
},
worker: {
format: 'es',
},
build: {
rollupOptions: {
output: {
manualChunks: {
'monaco-editor': ['monaco-editor']
}
}
}
}
}
})
关键配置解释:
build.rollupOptions.output.manualChunks:
这是一个非常重要的配置。它告诉 Vite 在生产构建时,将所有来自 monaco-editor 包的模块打包到一个名为 monaco-editor.js 的独立文件中。
这有助于优化应用的加载性能,因为 Monaco Editor 体积较大,将其分离出来可以实现按需加载,不会阻塞主应用的渲染。
如果没有这个配置,在生产构建后你可能会遇到 Monaco Editor 的 Web Worker 加载失败或路径错误的问题。
验证配置:
保存 vite.config.ts 后,重启你的 Vite 开发服务器 (npm run dev)。
如果服务器正常启动且没有报错,说明基本配置是正确的。
第二部分:前端页面与组件实现
1. 添加路由和侧边栏菜单入口
a. 路由 (frontend/src/router/index.ts):

// ... (在 Layout 的 children 中添加)
{
path: '/tools',
name: 'tools',
redirect: '/tools/diff-checker',
meta: {
title: '工具管理', requiresAuth: true, icon: 'Tools' },
children: [
{
path: 'diff-checker',
name: 'diffChecker',
component: () => import('../views/tools/DiffCheckerView.vue'),
meta: {
title: '代码比对', requiresAuth: true, icon: 'DocumentCopy' }
}
]
},
// ...
注意: 将“代码比对”放在一个新的“工具管理”子菜单下,这样未来可以方便地添加更多工具。
b. 侧边栏入口 (frontend/src/layout/index.vue):

// ... 在 <el-sub-menu index="/system"> 之前
<el-sub-menu index="/tools">
<template #title>
<el-icon><Tools /></el-icon> <!-- 使用 Tools 图标 -->
<span>工具管理</span>
</template>
<el-menu-item index="/tools/diff-checker">
<el-icon><DocumentCopy /></el-icon>
<span>代码比对</span>
</el-menu-item>
<!-- 未来可以添加更多工具的菜单项 -->
</el-sub-menu>
// ...
// 导入图标
import { /* ..., */ Tools } from '@element-plus/icons-vue'
2. 创建代码比对页面 (src/views/tools/DiffCheckerView.vue)
a. 创建 src/views/tools/DiffCheckerView.vue 文件:

b. 编写 DiffCheckerView.vue:
<!-- test-platform/frontend/src/views/tools/DiffCheckerView.vue -->
<template>
<div class="diff-checker-view">
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>代码比对工具</span>
<div class="controls">
<!-- 语言选择 -->
<el-select v-model="language" placeholder="选择语言">
<el-option v-for="lang in supportedLanguages" :key="lang" :label="lang.toUpperCase()" :value="lang" />
</el-select>
<!-- 主题切换 -->
<el-switch
v-model="theme"
active-value="vs-dark"
inactive-value="vs"
active-text="深色模式"
inactive-text="浅色模式"
inline-prompt
/>
<el-button-group>
<el-button :icon="Switch" @click="swapContent">交换内容</el-button>
<el-button :icon="Delete" @click="clearContent">清空内容</el-button>
</el-button-group>
</div>
</div>
</template>
<!-- Monaco Diff Editor 组件 -->
<div class="editor-container" ref="editorContainer"></div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onBeforeUnmount, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { Switch, Delete } from '@element-plus/icons-vue';
import * as monaco from 'monaco-editor';
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
// 设置 Monaco Editor 的 workers
self.MonacoEnvironment = {
getWorker(_, label) {
if (label === 'json') {
return new jsonWorker();
}
if (label === 'css' || label === 'scss' || label === 'less') {
return new cssWorker();
}
if (label === 'html' || label === 'handlebars' || label === 'razor') {
return new htmlWorker();
}
if (label === 'typescript' || label === 'javascript') {
return new tsWorker();
}
return new editorWorker();
}
};
const editorContainer = ref<HTMLElement | null>(null);
let diffEditor: monaco.editor.IStandaloneDiffEditor | null = null;
// 左侧编辑器内容
const originalCode = ref(`{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org"
}`);
// 右侧编辑器内容
const modifiedCode = ref(`{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.com",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874"
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona"
}
}`);
const language = ref('json'); // 默认语言
const theme = ref('vs'); // 默认主题 'vs' (light), 可选 'vs-dark'
const supportedLanguages = [
'json', 'javascript', 'typescript', 'html', 'css', 'python', 'yaml', 'xml', 'sql', 'shell', 'markdown'
];
// Monaco Editor 的配置项
const editorOptions = reactive({
automaticLayout: true, // 自动布局
readOnly: false, // 允许编辑
renderSideBySide: true, // 并排显示
minimap: { enabled: true }, // 显示小地图
scrollBeyondLastLine: false, // 禁止滚动超过最后一行
wordWrap: 'on', // 自动换行
fontSize: 14,
originalEditable: true, // 允许编辑原始代码
enableSplitViewResizing: true,
});
// 初始化编辑器
const initEditor = () => {
if (editorContainer.value) {
diffEditor = monaco.editor.createDiffEditor(editorContainer.value, {
...editorOptions,
theme: theme.value
});
// 设置初始内容
diffEditor.setModel({
original: monaco.editor.createModel(originalCode.value, language.value),
modified: monaco.editor.createModel(modifiedCode.value, language.value)
});
// 监听修改后的内容变化
const modifiedEditor = diffEditor.getModifiedEditor();
modifiedEditor.onDidChangeModelContent(() => {
modifiedCode.value = modifiedEditor.getValue();
});
}
};
// 更新编辑器内容
const updateEditor = () => {
if (diffEditor) {
diffEditor.setModel({
original: monaco.editor.createModel(originalCode.value, language.value),
modified: monaco.editor.createModel(modifiedCode.value, language.value)
});
}
};
// 监听主题变化
watch(theme, (newTheme) => {
monaco.editor.setTheme(newTheme);
});
// 监听语言变化
watch(language, () => {
updateEditor();
});
// 交换左右两侧内容
const swapContent = () => {
const temp = originalCode.value;
originalCode.value = modifiedCode.value;
modifiedCode.value = temp;
updateEditor();
ElMessage.success('已交换左右两侧内容');
};
// 清空所有内容
const clearContent = () => {
originalCode.value = '';
modifiedCode.value = '';
updateEditor();
ElMessage.info('已清空所有内容');
};
// 组件挂载时初始化编辑器
onMounted(() => {
initEditor();
});
// 组件卸载前销毁编辑器
onBeforeUnmount(() => {
if (diffEditor) {
diffEditor.dispose();
}
});
</script>
<style scoped lang="scss">
.diff-checker-view {
padding: 20px;
height: calc(100vh - 50px - 40px); // 减去顶部导航和页面 padding
display: flex;
flex-direction: column;
.box-card {
flex-grow: 1;
display: flex;
flex-direction: column;
:deep(.el-card__header) {
padding: 15px 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 1.1em;
font-weight: bold;
}
:deep(.el-card__body) {
padding: 0; // 移除卡片 body 的内边距,让编辑器占满
flex-grow: 1;
height: 0; // 关键:让 flex-grow 生效
}
.editor-container {
height: 100%; // 编辑器容器占满 Card Body
border: 1px solid #dcdfe6; // 给编辑器一个边框
border-radius: 4px;
}
}
}
</style>
第三部分:全面测试与验证
环境验证:
重启 Vite 开发服务器。
在浏览器中打开 http://127.0.0.1:5173/tools/diff-checker。
按 F12 打开开发者工具,切换到 “Network” (网络) 标签页。
刷新页面。确认没有与 monaco-editor 相关的 404 错误。你应该能看到一些 *.worker.js 文件被成功加载。
功能测试:
默认加载: 页面应能正常显示,左右编辑器中应有我们的示例 JSON,并且差异部分(email、address.geo、company)应被高亮显示。
实时比对:
在任一编辑器中进行修改、删除或添加内容。
确认差异高亮会实时更新。
语法高亮:
通过顶部的语言选择器,切换语言为 “Python”。
在编辑器中粘贴一些 Python 代码,确认语法高亮正确。
切换回 “JSON”,语法高亮也应恢复。
主题切换: 点击“深色模式”开关,确认编辑器主题变为深色。
操作按钮:
测试“交换内容”按钮。
测试“清空内容”按钮。
布局与滚动:
粘贴大量代码,确认编辑器内部可以正常滚动。
调整浏览器窗口大小,确认编辑器布局自适应。
总结
在本文中,测试平台集成了一个专业级的代码比对工具:
✅ 安装了 monaco-editor 核心库及其 Vue3 集成组件 monaco-editor-vue3。
✅ 配置了 Vite (vite.config.ts),通过 manualChunks 确保 Monaco Editor 在生产构建时被正确打包,优化了加载性能。
✅ 创建了新的“工具管理”模块,并添加了“代码比对”页面的路由和侧边栏菜单入口。
✅ 实现了 DiffCheckerView.vue 页面:
使用 monaco-editor-vue3 提供的 MonacoDiffEditor 组件作为核心。
实现了左右两侧内容的双向数据绑定。
提供了动态切换语言(语法高亮)和主题(浅色/深色模式)的功能。
添加了“交换内容”和“清空内容”等便捷操作。
通过 CSS Flexbox 和 :deep 选择器,实现了编辑器在 ElCard 中自适应高度的良好布局。
✅ 指导了如何全面测试和验证该工具的各项功能。




















暂无评论内容