vue3+diff简单实现对比差异工具组件

结果图

背景

某天要给管理后台的数据配置添加一个审核功能,为了更好的审核,就需要看到数据差异,于是就要开发一款对比差异的页面

调研

  1. 调研有没有现成的组件
  2. 评估自研成本(有AI的加入,成本大幅度降低)

现成的组件感觉有点一般,于是我打算自研,用的是diffdiffChars功能,因为要精确到字符级 ,如果是行级,可以用diffLines,更简单

diffChars需要自己还原原来的数据,最重要的还是他的精度高,可以做字符级的高亮,他的数据结构参考

json 复制代码
[
    {
        "count": 17,
        "added": false,
        "removed": false,
        "value": "{\n  \"name\": \"Alic"
    },
    ....
]

代码

技术栈:vue3、unocss

子组件--Diff.vue

html 复制代码
<script lang="ts" setup>
import type { Change } from 'diff'
import { diffChars } from 'diff'

const props = defineProps<{
  oldData: unknown
  newData: unknown
}>()

const oldStr = computed(() => `${JSON.stringify(props.oldData, null, 2)}\n`)
const newStr = computed(() => `${JSON.stringify(props.newData, null, 2)}\n`)

const diffCharList = computed<Change[]>(() => diffChars(oldStr.value, newStr.value))

interface Segment {
  text: string
  added?: boolean
  removed?: boolean
}

function buildLines() {
  const oldLines: { segments: Segment[]; changed: boolean }[] = []
  const newLines: { segments: Segment[]; changed: boolean }[] = []

  const oldCurrent: Segment[] = []
  const newCurrent: Segment[] = []

  const pushLine = () => {
    oldLines.push({
      segments: [...oldCurrent],
      changed: oldCurrent.some((s) => s.removed),
    })
    newLines.push({
      segments: [...newCurrent],
      changed: newCurrent.some((s) => s.added),
    })
    oldCurrent.length = 0
    newCurrent.length = 0
  }

  diffCharList.value.forEach((part) => {
    const lines = part.value.split(/(?<=\n)/)
    lines.forEach((line, i) => {
      const isNewLine = line.endsWith('\n')

      if (part.added) {
        newCurrent.push({ text: line, added: true })
        if (isNewLine) pushLine()
      } else if (part.removed) {
        oldCurrent.push({ text: line, removed: true })
        if (isNewLine) pushLine()
      } else {
        oldCurrent.push({ text: line })
        newCurrent.push({ text: line })
        if (isNewLine) pushLine()
      }
    })
  })

  if (oldCurrent.length || newCurrent.length) pushLine()

  return { oldLines, newLines }
}

const { oldLines, newLines } = computed(() => buildLines()).value
</script>

<template>
  <div class="font-mono text-14 lh-32 flex overflow-auto b b-gray-300 bg-white">
    <!-- 旧数据 -->
    <div class="flex-1 p-4 b-r b-gray-300 overflow-x-auto">
      <div
        v-for="(line, index) in oldLines"
        :key="'old-' + index"
        :class="{
          'bg-red-100': line.changed,
        }"
        class="whitespace-pre-wrap"
      >
        <template v-for="(segment, i) in line.segments" :key="i">
          <span
            :class="{
              'bg-red-300 line-through': segment.removed,
            }"
          >
            {{ segment.text }}
          </span>
        </template>
      </div>
    </div>
    <!-- 新数据 -->
    <div class="flex-1 p-4 overflow-x-auto">
      <div
        v-for="(line, index) in newLines"
        :key="'new-' + index"
        :class="{
          'bg-green-100': line.changed,
        }"
        class="whitespace-pre-wrap"
      >
        <template v-for="(segment, i) in line.segments" :key="i">
          <span :class="segment.added ? 'bg-green-300' : ''">
            {{ segment.text }}
          </span>
        </template>
      </div>
    </div>
  </div>
</template>

<style lang="scss" scoped></style>

父组件

html 复制代码
<Diff :old-data="diffObj.oldData" :new-data="diffObj.newData" />

  diffObj.value.oldData = {
    name: 'Alice',
    age: 25,
    address: {
      city: 'New York',
      zip: '10001',
    },
    tags: ['developer', 'frontend'],
    text: 'hello world fsdkafjs撒放假就疯狂拉萨大家疯狂了发啥地方撒的',
    text2: 'hello world fsdkafjs撒放假就疯狂拉萨大家疯狂的发啥地方撒的',
  }
  diffObj.value.newData = {
    name: 'Alicia', // 修改
    age: 26, // 修改
    address: {
      city: 'San Francisco', // 修改
      zip: '94105', // 修改
    },
    tags: ['developer', 'fullstack'], // 修改数组项
    text: 'hello world fsdkafjs撒放假就疯狂拉萨大家疯狂了发啥地方撒的',
    text2: 'hello world fsdkafjs撒放假就疯狂拉萨大家疯狂了发啥地方撒的',
    active: true, // 新增字段
  }
相关推荐
往上跑山几秒前
【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读
前端
文心快码BaiduComate19 分钟前
从个人效能到组织资产:文心快码企业版Agent Hub上线,提升团队AI编程效能
前端·后端·程序员
咖啡星人k34 分钟前
从需求到交付:我用MonkeyCode的AI Agent完成了一个React数据看板
前端·人工智能·react.js·monkeycode
sxlishaobin37 分钟前
linux 自动清除日志 脚本
linux·服务器·前端
ZC跨境爬虫1 小时前
跟着 MDN 学CSS day_37:(从文档流到粘性定位的底层原理)
前端·javascript·css·ui·html
IccBoY1 小时前
NVM超详细全解教程:解决Node版本冲突(Win/Mac/Linux安装+使用+踩坑合集)
前端·node.js
wuhen_n1 小时前
前端工程师进阶提示词工程实战
前端·langchain·ai编程
GISer_Jing1 小时前
Claude Code MCP Server 集成全解析
前端·人工智能·ai·架构
蚰蜒螟1 小时前
走进 Linux 内核:从 touch 命令到磁盘 inode 的完整旅程
java·linux·前端
因_崔斯汀1 小时前
用 AI 编程助手从零生成 3D 智慧校园数据大屏 —— Claude Code 实战全记录
前端