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, // 新增字段
  }
相关推荐
洛小豆22 分钟前
深入理解Pinia:Options API vs Composition API两种Store定义方式完全指南
前端·javascript·vue.js
洛小豆32 分钟前
JavaScript 对象属性访问的那些坑:她问我为什么用 result.id 而不是 result['id']?我说我不知道...
前端·javascript·vue.js
叹一曲当时只道是寻常35 分钟前
Softhub软件下载站实战开发(十六):仪表盘前端设计与实现
前端·golang
超级土豆粉40 分钟前
npm 包 scheduler 介绍
前端·npm·node.js
bug爱好者41 分钟前
原生小程序如何实现跨页面传值
前端·javascript
随笔记44 分钟前
uniapp开发的小程序输入框在ios自动填充密码,如何欺骗苹果手机不让自动填充
前端·ios·app
bug爱好者1 小时前
原生微信小程序最实用的工具函数合集
前端·javascript
3Katrina1 小时前
JS事件机制详解(2)--- 委托机制、事件应用
前端·javascript·面试
Allen Bright1 小时前
【CSS-15】深入理解CSS transition-duration:掌握过渡动画的时长控制
前端·css
张鑫旭1 小时前
40岁老前端2025年上半年都学了什么?
前端