vue3 + mark.js 实现文字标注功能

效果图

在这里插入图片描述

安装依赖

npm install mark.js --save-dev
npm i nanoid

代码块

<template><!-- 文档标注 --><header><el-buttontype="primary":disabled="selectedTextList.length == 0 ? true : false"ghost@click="handleAllDelete">清空标记</el-button><el-buttontype="primary":disabled="selectedTextList.length == 0 ? true : false"@click="handleSave">保存</el-button></header><main><div id="text-container" class="text">{{ markContent }}</div><!-- 标签选择 --><divv-if="tagInfo.visible && tagList.length > 0":class="['tag-box p-4 ']":style="{ top: tagInfo.top + 'px', left: tagInfo.left + 'px' }"><divv-for="i in tagList":key="i.tag_id"class="tag-name"@click="handleSelectLabel(i)"><div><p>{{ i.tag_name }}</p><el-buttonv-if="i.tag_id == editTag.tag_id"texttype="primary"></el-button></div><div:class="['w-4 h-4']"style="width: 30px; height: 30px":style="{background: i.tag_color,}"></div></div></div><!-- 重选/取消 --><divv-if="editTag.visible"class="edit-tag":style="{ top: editTag.top + 'px', left: editTag.left + 'px' }"><divclass="py-1 bg-gray-100 text-center"style="margin-bottom: 10px;"@click="handleCancel">取 消</div><div class="py-1 bg-gray-100 mt-2 text-center" @click="handleReset">重 选</div></div></main>
</template>
<script setup>
import { ref, onMounted, reactive } from 'vue'
import Mark from 'mark.js' //清空标记
import { nanoid } from 'nanoid' //一个小巧、安全、URL友好、唯一的 JavaScript 字符串 ID 生成器。const TAG_WIDTH = 1000const selectedTextList = ref([])const selectedText = reactive({start: 0,end: 0,content: '',
})const markContent = ref('作文是经过人的思想考虑和语言组织,通过文字来表达一个主题意义的记叙方法。作文体裁包括:记叙文、说明文、应用文、议论文。作文分为小学作文、中学作文、大学作文(论文)。'
)const tagInfo = ref({visible: false,top: 0,left: 0,
})const editTag = ref({visible: false,top: 0,left: 0,mark_id: '',content: '',tag_id: '',start: 0,end: 0,
})const tagList = [{tag_name: '1级',tag_color: `#DE050CFF`,tag_id: 'tag_id1',},{tag_name: '2级',tag_color: `#6ADE05FF`,tag_id: 'tag_id2',},{tag_name: '3级',tag_color: `#DE058BFF`,tag_id: 'tag_id3',},{tag_name: '4级',tag_color: `#9205DEFF`,tag_id: 'tag_id4',},{tag_name: '5级',tag_color: `#DE5F05FF`,tag_id: 'tag_id5',},
]const handleAllDelete = () => {selectedTextList.value = []const marker = new Mark(document.getElementById('text-container'))marker.unmark()
}const handleCancel = () => {if (!editTag.value.mark_id) returnconst markEl = new Mark(document.getElementById(editTag.value.mark_id))markEl.unmark()selectedTextList.value.splice(selectedTextList.value?.findIndex(t => t.mark_id == editTag.value.mark_id),1)tagInfo.value = {visible: false,top: 0,left: 0,}resetEditTag()
}const handleReset = () => {editTag.value.visible = falsetagInfo.value.visible = true
}const handleSave = () => {console.log('标注的数据', selectedTextList.value)
}const handleSelectLabel = t => {const { tag_color, tag_name, tag_id } = ttagInfo.value.visible = falseconst marker = new Mark(document.getElementById('text-container'))const markId = nanoid(10)const isReset = selectedTextList.value?.map(j => j.mark_id).includes(editTag.value.mark_id)? 1: 0 // 1:重选 0:新增if (isReset) {//如若重选,则删除后再新增标签const markEl = new Mark(document.getElementById(editTag.value.mark_id))markEl.unmark()selectedTextList.value.splice(selectedTextList.value?.findIndex(t => t.mark_id == editTag.value.mark_id),1)}marker.markRanges([{start: isReset ? editTag.value.start : selectedText.start,length: isReset? editTag.value.content.length: selectedText.content.length,},],{className: 'text-selected',element: 'span',each: element => {element.setAttribute('id', markId)element.style.borderBottom = `2px solid ${t.tag_color}`element.style.color = t.tag_colorelement.style.userSelect = 'none'element.style.paddingBottom = '6px'element.onclick = function(e) {e.preventDefault()if (!e.target.id) returnconst left = e.offsetX < TAG_WIDTH ? 0 : e.offsetX - 300const item = selectedTextList.value?.find?.(t => t.mark_id == e.target.id)const { mark_content, tag_id, start, end } = item || {}editTag.value = {visible: true,top: e.offsetY + 40,left: e.offsetX,mark_id: e.target.id,content: mark_content || '',tag_id: tag_id || '',start: start,end: end,}tagInfo.value = {visible: false,top: e.offsetY + 40,left: left,}}},})selectedTextList.value.push({tag_color,tag_name,tag_id,start: isReset ? editTag.value.start : selectedText.start,end: isReset ? editTag.value.end : selectedText.end,mark_content: isReset ? editTag.value.content : selectedText.content,mark_id: markId,})
}/*** 获取选取的文字数据*/
const getSelectedTextData = () => {const select = window?.getSelection()const nodeValue = select.focusNode?.nodeValueconst anchorOffset = select.anchorOffsetconst focusOffset = select.focusOffsetconst nodeValueSatrtIndex = markContent.value?.indexOf(nodeValue)selectedText.content = select.toString()if (anchorOffset < focusOffset) {//从左到右标注selectedText.start = nodeValueSatrtIndex + anchorOffsetselectedText.end = nodeValueSatrtIndex + focusOffset} else {//从右到左selectedText.start = nodeValueSatrtIndex + focusOffsetselectedText.end = nodeValueSatrtIndex + anchorOffset}
}const resetEditTag = () => {editTag.value = {visible: false,top: 0,left: 0,mark_id: '',content: '',tag_id: '',start: 0,end: 0,}
}const drawMark = () => {//模拟后端返回的数据const res = [{start: 0, //必备end: 1,tag_color: '#DE050CFF',tag_id: 'tag_id1',tag_name: '1级',mark_content: '作文',mark_id: 'mark_id1',},]selectedTextList.value = res?.map(t => ({tag_id: t.tag_id,tag_name: t.tag_name,tag_color: t.tag_color,start: t.start,end: t.end,mark_content: t.mark_content,mark_id: t.mark_id,}))const markList =selectedTextList.value?.map(j => ({...j,start: j.start, //必备length: j.end - j.start + 1, //必备})) || []const marker = new Mark(document.getElementById('text-container'))markList?.forEach?.(function(m) {marker.markRanges([m], {element: 'span',className: 'text-selected',each: element => {element.setAttribute('id', m.mark_id)element.style.borderBottom = `2px solid ${m.tag_color}`element.style.color = m.tag_colorelement.style.userSelect = 'none'element.style.paddingBottom = '6px'element.onclick = function(e) {console.log('cccccc', m)const left = e.offsetX < TAG_WIDTH ? 0 : e.offsetX - 300editTag.value = {visible: true,top: e.offsetY + 40,left: e.offsetX,mark_id: m.mark_id,content: m.mark_content,tag_id: m.tag_id,start: m.start,end: m.end,}tagInfo.value = {visible: false,top: e.offsetY + 40,left: left,}}},})})
}//页面初始化
onMounted(() => {const el = document.getElementById('text-container')//鼠标抬起el?.addEventListener('mouseup', e => {const text = window?.getSelection()?.toString() || ''if (text.length > 0) {const left = e.offsetX < 500 ? e.offsetX - 20 : 500tagInfo.value = {visible: true,top: e.offsetY + 40,left: left,}getSelectedTextData()} else {tagInfo.value.visible = false}//清空重选/取消数据resetEditTag()})//从后端获取标注数据,进行初始化标注drawMark()
})
</script><style lang="scss" scoped>
header {display: flex;// justify-content: space-between;align-items: center;padding: 0 24px;height: 80px;border-bottom: 1px solid #e5e7eb;user-select: none;background: #fff;
}main {background: #fff;margin: 24px;height: 80vh;padding: 24px;overflow-y: auto;position: relative;box-shadow: 0 3px 8px 0 rgb(0 0 0 / 13%);.text {color: #333;font-weight: 500;font-size: 16px;line-height: 50px;}.tag-box {position: absolute;z-index: 10;width: 150px;max-height: 40vh;overflow-y: auto;background: #fff;border-radius: 4px;box-shadow: 0 9px 28px 8px rgb(0 0 0 / 3%), 0 6px 16px 4px rgb(0 0 0 / 9%),0 3px 6px -2px rgb(0 0 0 / 20%);user-select: none;.tag-name {// width: 100%;background: rgba(243, 244, 246, var(--tw-bg-opacity));font-size: 14px;cursor: pointer;display: flex;justify-content: space-between;align-items: center;padding: 4px 8px;margin-top: 8px;}.tag-name:nth-of-type(1) {margin-top: 0;}}.edit-tag {position: absolute;z-index: 20;padding: 16px;cursor: pointer;width: 40px;background: #fff;border-radius: 4px;box-shadow: 0 9px 28px 8px rgb(0 0 0 / 3%), 0 6px 16px 4px rgb(0 0 0 / 9%),0 3px 6px -2px rgb(0 0 0 / 20%);user-select: none;}::selection {background: rgb(51 51 51 / 20%);}
}
</style>

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AMEYA360--罗姆与Quanmatic公司利用量子技术优化制造工序并完成验证

全球知名半导体制造商罗姆(总部位于日本京都市)于2023年1月起与 Quanmatic Inc.(总部位于日本东京都新宿区&#xff0c;以下简称“Quanmatic”)展开合作&#xff0c;在半导体制造工序之一的EDS工序中测试并引入量子技术&#xff0c;以优化制造工序中的组合。目前&#xff0c;双…

流量分析1--菜刀666

1&#xff1a;菜刀666&#xff1a; 题目描述 分析流量包&#xff0c;过滤http数据流 追踪TCP数据流 对比第5个流和第7个流发现&#xff0c;同样的目录下 多出了6666.jpg。猜测是由攻击者上传&#xff0c;直接在请求包里搜索FFD8--FFD9 保存为1.jpg 利用foremost工具对1.jpg进…

关于IDAE中maven的作用以及如何配置MAVEN

关于IDAE中maven的作用以及如何配置MAVEN 1、Maven是什么2、Idea中对于Maven的配置3、下载依赖时&#xff0c;Idea下方的显示3.1、Maven中央仓库的下载显示界面3.2、阿里云仓库的下载显示界面 4、Maven在Idea中的使用4.1、clean4.2、validate4.3、compile4.4、test&#xff08;…

git-vscode

git-vscode ctrlshiftp 创建分支 create branch 直接切到新的分支了 切换分支 直接点左下角自己选择 vscode中配置仓库 https://blog.csdn.net/zora_55/article/details/129709251 推送tag tag作用就是在 Git 中&#xff0c;标记存储库历史记录中特定提交的一种方式。t…

node.js和浏览器之间的区别

node.js是什么 Node.js是一种基于Chrome V8引擎的JavaScript运行环境&#xff0c;可以在服务器端运行JavaScript代码 Node.js 在浏览器之外运行 V8 JavaScript 引擎。 这使得 Node.js 非常高效。 浏览器如何运行js代码 nodejs运行环境 在浏览器中&#xff0c;大部分时间你所…

三层交换机配置DHCP服务

第一步&#xff1a;进入二层交换机Switch 1&#xff09;输入命令&#xff1a; Switch(config)#vlan 10 Switch(config)#vlan 20 2&#xff09;修改F0/1 和F0/2为access口&#xff0c;F0/24为trunk口 第二步&#xff1a;进入三层交换机 1&#xff09;输入命令 Switch(config)#…

工作中常用的RabbitMQ实践

目录 1.前置 2.导入依赖 3.生产者 4.消费者 5.验证 验证Direct 验证Fanout 验证Topic 1.前置 安装了rabbitmq&#xff0c;并成功启动 2.导入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-…

B树你需要了解一下

介绍B树的度数主要特点应用场景时间复杂度代码示例拓展 介绍 B树&#xff08;B-tree&#xff09;是一种自平衡的树&#xff0c;能够保持数据有序&#xff0c;常被用于数据库和文件系统的实现。 B树可以看作是一般化的二叉查找树&#xff0c;它允许拥有多于2个子节点。与自平衡…

Spring boot 使用Redis 消息发布订阅

Spring boot 使用Redis 消息发布订阅 文章目录 Spring boot 使用Redis 消息发布订阅Redis 消息发布订阅Redis 发布订阅 命令 Spring boot 实现消息发布订阅发布消息消息监听主题订阅 Spring boot 监听 Key 过期事件消息监听主题订阅 最近在做请求风控的时候&#xff0c;在网上搜…

ESP32-Web-Server编程- 在 Web 上开发动态纪念册

ESP32-Web-Server编程- 在 Web 上开发动态纪念册 概述 Web 有很多有趣的玩法&#xff0c;在打开网页的同时送她一个惊喜。 需求及功能解析 本节演示在 ESP32 上部署一个 Web&#xff0c;当打开对应的网页时&#xff0c;将运行动态的网页内容&#xff0c;显示炫酷的纪念贺词…

.NET 8 中 Android 资源生成的改进和变化

作者&#xff1a;Dean Ellis 排版&#xff1a;Alan Wang 随着 .NET 8 的发布&#xff0c;我们引入了一个新系统&#xff0c;用于生成访问 Android 资源的 C# 代码。 在 Xamarin.Android、.NET 6 和 .NET 7 中生成 Resource.designer.cs 文件的系统已经被弃用。 新系统生成一个名…

苍穹外卖+git开源

搁置了很久重新开始学 为了学习方便&#xff0c;苍穹外卖的前后端代码已放至git开源。前端源代码请看给i他-->sky-take-out: 苍穹外卖 git学习-->Git基础使用-CSDN博客 后端接口员工管理和分类管理模块 添加员工&#xff0c;添加的表单账号、手机号、身份证都…

Spring Boot的日志

打印日志 打印日志的步骤: • 在程序中得到日志对象. • 使用日志对象输出要打印的内容 在程序中得到日志对象 在程序中获取日志对象需要使用日志工厂LoggerFactory,代码如下: package com.example.demo;import org.slf4j.Logger; import org.slf4j.LoggerFactory;public c…

安装you-get(mac)

1、首先要有python环境 2、更新pip python -m pip install --upgrade pip 3、安装you-get pip install you-get;

T天池SQL训练营(五)-窗口函数等

–天池龙珠计划SQL训练营 5.1窗口函数 5.1.1窗口函数概念及基本的使用方法 窗口函数也称为OLAP函数。OLAP 是OnLine AnalyticalProcessing 的简称&#xff0c;意思是对数据库数据进行实时分析处理。 为了便于理解&#xff0c;称之为窗口函数。常规的SELECT语句都是对整张表进…

创建vue项目:node.js下载安装、配置环境变量,下载安装cnpm,配置npm的目录、镜像,安装vue、搭建vue项目开发环境(保姆级教程一)

今天讲解 Windows 如何创建 vue 项目&#xff0c;搭建 vue 开发环境&#xff0c;这是这个系列的第一章&#xff0c;有什么问题请留言&#xff0c;请点赞收藏&#xff01;&#xff01;&#xff01; 文章目录 一、Vue简单介绍二、开始搭建1、安装node.js环境2、配置npm下载时的默…

一文3000字从0到1用Python进行gRPC接口测试!

gRPC 是一个高性能、通用的开源RPC框架&#xff0c;其由 Google 主要面向移动应用开发并基于HTTP/2 协议标准而设计&#xff0c;基于 ProtoBuf(Protocol Buffers) 序列化协议开发&#xff0c;且支持众多开发语言。 自gRPC推出以来&#xff0c;已经广泛应用于各种服务之中。在测…

数据可视化免费化的双面影响探析

近年来数据可视化的免费化也越来越明显&#xff0c;今天就以我作为可视化设计师的经验来和大家分析一下&#xff0c;数据可视化工具免费化所带来的利与弊。 先从好处入手&#xff0c;最明显的就是免费化可以让数据可视化工具得到更广泛的使用。 免费数据可视化工具使得更多人可…

docker搭建nginx实现负载均衡

docker搭建nginx实现负载均衡 安装nginx 查询安装 [rootlocalhost ~]# docker search nginx [rootlocalhost ~]# docker pull nginx准备 创建一个空的nginx文件夹里面在创建一个nginx.conf文件和conf.d文件夹 运行映射之前创建的文件夹 端口&#xff1a;8075映射80 docker…

电脑版便签软件怎么设置在桌面上显示?

对于不少上班族来说&#xff0c;如果想要在使用电脑办公的时候&#xff0c;随手记录一些常用的工作资料、工作注意事项等内容&#xff0c;直接在电脑上使用便签软件记录是比较方便的。电脑桌面便签工具不仅方便我们随时记录各类工作事项&#xff0c;而且支持我们快速便捷使用这…