vue展示修改前后对比,并显示修改标注diff

news2025/6/3 1:42:02

在这里插入图片描述

动态父组件

 <template>

              <el-button
                type="primary"
                size="small"
                plain
                @click="showDiffDialog(subItem)"
                >查看修改内容</el-button
              >

    <TextDiffDialog
      v-model:visible="diffDialogVisible"
      :before="currentDiffItem?.before?.[currentDiffItem?.fieldName] || ''"
      :after="currentDiffItem?.after?.[currentDiffItem?.fieldName] || ''"
    />
  </el-scrollbar>
</template>

<script>

import TextDiffDialog from './TextDiffDialog.vue';

export default {
  name: 'logIndex',
  components: { TextDiffDialog },
  data() {
    return {
      
      diffDialogVisible: false,
      currentDiffItem: null,
    };
  },
  props: {
    formId: String,
  },
  methods: {

    showDiffDialog(item) {
      this.currentDiffItem = item;
      this.diffDialogVisible = true;
    },
  },
  created() {},
  mounted() {

  },
};
</script>

<style scoped lang="scss">

.el-button--small {
  padding: 0px 6px;
  height: 26px;
  line-height: 26px;
  border: 0;
}
</style>

npm install diff

//子组件

<template>
  <el-dialog
    v-model="dialogVisible"
    title="变更对比"
    width="60%"
    :before-close="handleClose"
    class="text-diff-dialog"
  >
    <template #header>
      <div class="diff-header">
        <div class="diff-title">
          <i class="iconfont icon-shejibiangeng"></i>变更对比
        </div>
        <div class="diff-stats">
          <el-switch v-model="showDiff" active-text="显示修改标注" />
          <el-tooltip
            effect="light"
            content="<p><span style='background: #52bd94;color: #172890;padding: 2px;border-radius: 2px;'>绿色背景</span> 表示新增</p><p><span style='background:#f4b6b6;color: #172890;padding: 2px;border-radius: 2px;'>红色背景</span> 表示删除</p>"
            raw-content
          >
            <i style="color: #a5adba" class="el-icon-question"></i>
          </el-tooltip>
        </div>
      </div>
    </template>
    <div class="diff-container">
      <div class="diff-content" ref="diffContent">
        <div class="diff-panel before">
          <div class="panel-header">修改前</div>
          <div class="panel-content">
            <div
              v-for="(line, index) in beforeLines"
              :key="'before-' + index"
              class="diff-line"
            >
              <span class="line-number">{{ line.lineNumber }}</span>
              <span class="line-content" v-html="line.content"></span>
            </div>
          </div>
        </div>
        <div class="diff-panel after">
          <div
            class="panel-header"
            style="display: flex; justify-content: space-between"
          >
            修改后 <span class="total">{{ totalChanges }} 处修改</span>
          </div>
          <div class="panel-content">
            <div
              v-for="(line, index) in afterLines"
              :key="'after-' + index"
              class="diff-line"
            >
              <span class="line-number">{{ line.lineNumber }}</span>
              <span class="line-content" v-html="line.content"></span>
            </div>
          </div>
        </div>
      </div>
    </div>
  </el-dialog>
</template>

<script>
import { diffChars } from 'diff';

export default {
  name: 'TextDiffDialog',
  props: {
    visible: {
      type: Boolean,
      default: false,
    },
    before: {
      type: String,
      default: '',
    },
    after: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      dialogVisible: false,
      beforeLines: [],
      afterLines: [],
      showDiff: false,
      totalChanges: 0,
    };
  },
  watch: {
    visible(val) {
      this.dialogVisible = val;
      if (val) {
        this.$nextTick(() => {
          this.generateDiff();
        });
      }
    },
    dialogVisible(val) {
      if (!val) {
        this.showDiff = false;
        this.$emit('update:visible', false);
      }
    },
    showDiff() {
      this.generateDiff();
    },
  },
  methods: {
    handleClose() {
      this.dialogVisible = false;
    },
    generateDiff() {
      const beforeText = this.before || '';
      const afterText = this.after || '';

      // 将文本分割成行
      const beforeLines = beforeText.split('\n');
      const afterLines = afterText.split('\n');

      this.beforeLines = beforeLines.map((line, index) => ({
        lineNumber: index + 1,
        content: this.escapeHtml(line),
      }));

      // 使用diffChars进行字符级别的差异比较
      const changes = diffChars(beforeText, afterText);
      let currentLine = '';
      let lineNumber = 1;
      this.afterLines = [];
      this.totalChanges = 0;

      changes.forEach((change) => {
        if (change.added) {
          if (this.showDiff) {
            currentLine += `<span class="diff-added">${this.escapeHtml(change.value)}</span>`;
          } else {
            currentLine += this.escapeHtml(change.value);
          }
          this.totalChanges++;
        } else if (change.removed) {
          if (this.showDiff) {
            currentLine += `<span class="diff-deleted">${this.escapeHtml(change.value)}</span>`;
          }
          this.totalChanges++;
        } else {
          currentLine += this.escapeHtml(change.value);
        }

        // 处理换行
        if (change.value.includes('\n')) {
          const lines = currentLine.split('\n');
          lines.forEach((line, index) => {
            if (index < lines.length - 1) {
              this.afterLines.push({
                lineNumber: lineNumber++,
                content: line,
              });
            }
          });
          currentLine = lines[lines.length - 1];
        }
      });

      // 添加最后一行
      if (currentLine) {
        this.afterLines.push({
          lineNumber: lineNumber,
          content: currentLine,
        });
      }
    },
    escapeHtml(text) {
      const div = document.createElement('div');
      div.textContent = text;
      return div.innerHTML;
    },
  },
};
</script>

<style lang="scss" scoped>
.text-diff-dialog {
  :deep(.el-dialog__body) {
    padding: 0;
  }
}

.diff-container {
  height: 600px;
  display: flex;
  flex-direction: column;
  width: 100%;
}

.diff-header {
  padding: 0 6px 6px 6px;
  border-bottom: 1px solid #ebeef5;

  display: flex;
  justify-content: space-between;
  align-items: center;
}

.diff-stats {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-right: 20px;
}

.total {
  color: #6b778c !important;

  font-size: 13px;
}
.diff-content {
  flex: 1;
  display: flex;
  overflow: hidden;
}

.diff-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  border-right: 1px solid #ebeef5;

  &:last-child {
    border-right: none;
  }

  .panel-header {
    padding: 12px 16px;
    background-color: #f5f7fa;
    border-bottom: 1px solid #ebeef5;
    font-weight: 500;
    color: #172b4d;
    font-size: 16px;
  }

  .panel-content {
    flex: 1;
    overflow-y: auto;
    padding: 16px;
    font-family: monospace;
    white-space: pre-wrap;
    word-break: break-all;
  }
}

.diff-line {
  display: flex;
  line-height: 1.5;

  .line-number {
    color: #909399;
    text-align: right;
    padding-right: 8px;
    user-select: none;
  }

  .line-content {
    color: #172b4d;
    font-size: 14px;
    flex: 1;

    :deep(.diff-added) {
      background-color: #52bd94;
      border-radius: 2px;
      padding: 2px;
      margin-left: 2px;
    }

    :deep(.diff-deleted) {
      background-color: #f4b6b6;

      text-decoration: line-through;
      border-radius: 2px;
      padding: 2px;
      margin-left: 2px;
    }
  }
}
.diff-title {
  color: #172b4d;
  font-size: 16px;
  font-weight: 500;
  line-height: 24px; /* 150% */
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2392547.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LiveWallpaperMacOS:让你的 Mac 桌面动起来

随着桌面美化需求的不断提升,用户对于桌面壁纸的要求已经不再局限于静态图片。越来越多的 Mac 用户希望桌面能像 Windows 一样,拥有动态壁纸,展现个性、提升体验。LiveWallpaperMacOS 正是这样一款让你的 Mac 桌面焕发活力的开源项目。 本文将详细介绍 LiveWallpaperMacOS …

[预训练]Encoder-only架构的预训练任务核心机制

原创文章1FFN前馈网络与激活函数技术解析&#xff1a;Transformer模型中的关键模块2Transformer掩码技术全解析&#xff1a;分类、原理与应用场景3【大模型技术】Attention注意力机制详解一4Transformer核心技术解析LCPO方法&#xff1a;精准控制推理长度的新突破5Transformer模…

07-后端Web实战(部门管理)

5. 修改部门 对于任何业务的修改功能来说&#xff0c;一般都会分为两步进行&#xff1a;查询回显、修改数据。 5.1 查询回显 5.1.1 需求 当我们点击 "编辑" 的时候&#xff0c;需要根据ID查询部门数据&#xff0c;然后用于页面回显展示。 5.1.2 接口描述 参照参照…

mysql ACID 原理

序言&#xff1a;ACID 是一组数据库设计原则&#xff0c;他是业务数据和关键业务程序的可靠性保障。 1、atomicity&#xff08;原子性&#xff09; 依赖如下能力 autocommit commit rollback2、一致性 2.1 double write buffer 1、定义&#xff1a;double write buffer 是…

[Rust_1] 环境配置 | vs golang | 程序运行 | 包管理

目录 Rust 环境安装 GoLang和Rust 关于Go 关于Rust Rust vs. Go&#xff0c;优缺点 GoLang的优点 GoLang的缺点 Rust的优点 Rust的缺点 数据告诉我们什么&#xff1f; Rust和Go的主要区别 (1) 性能 (2) 并发性 (3) 内存安全性 (4) 开发速度 (5) 开发者体验 Ru…

二十五、面向对象底层逻辑-SpringMVC九大组件之HandlerMapping接口设计

一、引言&#xff1a;MVC架构的交通枢纽 在Spring MVC框架中&#xff0c;HandlerMapping接口扮演着"请求导航仪"的关键角色&#xff0c;它决定了HTTP请求如何被路由到对应的Controller处理器。作为MVC模式的核心组件之一&#xff0c;HandlerMapping在请求处理的生命…

HUAWEI交换机配置镜像口验证(eNSP)

技术术语&#xff1a; 流量观察口&#xff1a;就是我们常说的镜像口&#xff0c;被观察的流量的引流目的端口 流量源端口&#xff1a;企业生产端口&#xff0c;作为观察口观察对象。 命令介绍&#xff1a; [核心交换机]observe-port [观察端口ID或编号&#xff08;数字&am…

前端vue3实现图片懒加载

场景和指令用法 场景:电商网站的首页通常会很长&#xff0c;用户不一定能访问到页面靠下面的图片&#xff0c;这类图片通过懒加载优化手段可以做到只有进入视口区域才发送图片请求 核心原理:图片进入视口才发送资源请求 首先&#xff1a;我们需要定义一个全局的指令&#x…

计算机网络-MPLS VPN应用场景与组网

上一篇文章我们通过一个基础实验实现了企业分支间的MPLS VPN互联&#xff0c;如果还不理解的可以多看几遍前面的文章或者多敲下实验。今天来学习几种常见的MPLS VPN应用场景与这些场景下MPLS VPN的部署方法。 一、MPLS VPN典型应用 目前&#xff0c;MPLS VPN的主要应用包括企…

Linux 的编辑器--vim

1.Linux编辑器-vim使⽤ vi/vim的区别简单点来说&#xff0c;它们都是多模式编辑器&#xff0c;不同的是vim是vi的升级版本&#xff0c;它不仅兼容vi的所有指令&#xff0c;⽽且还有⼀些新的特性在⾥⾯。例如语法加亮&#xff0c;可视化操作不仅可以在终端运⾏&#xff0c;也可以…

[Protobuf] 快速上手:安全高效的序列化指南

标题&#xff1a;[Protobuf] (1)快速上手 水墨不写bug 文章目录 一、什么是protobuf&#xff1f;二、protobuf的特点三、使用protobuf的过程&#xff1f;1、定义消息格式&#xff08;.proto文件&#xff09;(1)指定语法版本(2)package 声明符 2、使用protoc编译器生成代码&…

如何将通话记录从Android传输到Android

“如何将通话记录从 Android 转移到 Android&#xff1f;我换了一部新的 Android 手机&#xff0c;想要将通话记录复制到其中。”您需要将通话记录从 Android 传输到 Android 是一种常见的情况&#xff0c;因为通话记录是手机上最重要的数据之一。幸运的是&#xff0c;如果您从…

数据结构第4章 栈、队列和数组 (竟成)

目录 第 4 章 栈、队列和数组 4.1 栈 4.1.1 栈的基本概念 4.1.2 栈的基本操作 4.1.3 栈的实现 1.顺序栈 2.链式栈 3.共享栈 4.1.4 顺序栈的基本操作实现 1.初始化栈 2.判空 3.判满 4.元素进栈 5.元素出栈 6.获取栈顶元素 4.1.5 链栈的基本操作实现 1.元素进栈 2.元素出栈 4.1.6…

2025年渗透测试面试题总结-匿名[校招]安全研究员(SAST方向)(题目+回答)

安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 匿名[校招]安全研究员(SAST方向) 一面问题回答框架 1. 自我介绍 2. 简历深挖&#xff08;漏洞挖掘&#x…

Unity 游戏优化(持续更新中...)

垃圾回收 是什么&#xff1f; 垃圾回收&#xff08;Garbage Collection&#xff09;GC 工作机制 1、Unity 为用户生成的代码和脚本采用了自动内存管理。 2、小块数据&#xff08;如值类型的局部变量&#xff09;分配在栈上。大块数据和长期存储分配在托管堆上。 3、垃圾收集…

20250529-C#知识:索引器

C#知识&#xff1a;索引器 索引器给对象添加了索引访问的功能&#xff0c;实际访问的是对象的成员&#xff0c;感觉不太常用。 1、主要内容及代码示例 索引器中类似属性&#xff0c;也包含get和set方法索引器能够使像访问数组一样访问对象一般当类中有数组类型的成员变量时&am…

【笔记】suna部署之获取 Tavily API key

#工作记录 Tavily 注册 Tavily 账号5&#xff1a; 打开浏览器&#xff0c;访问 Tavily 官网Tavily AI。点击页面上的 “注册” 按钮&#xff0c;按照提示填写注册信息&#xff0c;如邮箱地址、设置密码等&#xff0c;完成注册流程。也可以选择使用 Google 或 GitHub 账号授权登…

06-Web后端基础(java操作数据库)

1. 前言 在前面我们学习MySQL数据库时&#xff0c;都是利用图形化客户端工具(如&#xff1a;idea、datagrip)&#xff0c;来操作数据库的。 我们做为后端程序开发人员&#xff0c;通常会使用Java程序来完成对数据库的操作。Java程序操作数据库的技术呢&#xff0c;有很多啊&a…

什么是单片机?

众所周知&#xff0c;人类行为受大脑调控&#xff0c;正如视觉、听觉、味觉、嗅觉、触觉及运动功能等感官与肢体活动均受其指挥&#xff1b;换言之&#xff0c;大脑作为人体的中枢神经系统&#xff0c;负责管理所有可控制的生理功能。 在电子设备领域&#xff0c;单片机…

Ubuntu的shell脚本

关于shell脚本 • shell脚本是文本的一种。 • shell脚本是可以运行的文本。 • shell脚本的内容是由说辑和数据组成。 • shell 脚本是解释型语言。 shell脚本存在的意义 Shell脚本语言是实现Linux/UNIX系统管理及自动化运维所必备的重要工具 Linux/UNIX系统…