国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

vue3 + mark.js 實(shí)現(xiàn)文字標(biāo)注功能

這篇具有很好參考價(jià)值的文章主要介紹了vue3 + mark.js 實(shí)現(xiàn)文字標(biāo)注功能。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

效果圖

vue3 + mark.js 實(shí)現(xiàn)文字標(biāo)注功能,WEB前端,javascript,vue.js,前端文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-756419.html

安裝依賴(lài)

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

代碼塊

<template>
  <!-- 文檔標(biāo)注 -->
  <header>
    <el-button
      type="primary"
      :disabled="selectedTextList.length == 0 ? true : false"
      ghost
      @click="handleAllDelete"
    >
      清空標(biāo)記
    </el-button>
    <el-button
      type="primary"
      :disabled="selectedTextList.length == 0 ? true : false"
      @click="handleSave"
    >
      保存
    </el-button>
  </header>
  <main>
    <div id="text-container" class="text">
      {{ markContent }}
    </div>
    <!-- 標(biāo)簽選擇 -->
    <div
      v-if="tagInfo.visible && tagList.length > 0"
      :class="['tag-box p-4 ']"
      :style="{ top: tagInfo.top + 'px', left: tagInfo.left + 'px' }"
    >
      <div
        v-for="i in tagList"
        :key="i.tag_id"
        class="tag-name"
        @click="handleSelectLabel(i)"
      >
        <div>
          <p>{{ i.tag_name }}</p>
          <el-button
            v-if="i.tag_id == editTag.tag_id"
            text
            type="primary"
          ></el-button>
        </div>
        <div
          :class="['w-4 h-4']"
          style="width: 30px; height: 30px"
          :style="{
            background: i.tag_color,
          }"
        ></div>
      </div>
    </div>
    <!-- 重選/取消 -->
    <div
      v-if="editTag.visible"
      class="edit-tag"
      :style="{ top: editTag.top + 'px', left: editTag.left + 'px' }"
    >
      <div
        class="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' //清空標(biāo)記
import { nanoid } from 'nanoid' //一個(gè)小巧、安全、URL友好、唯一的 JavaScript 字符串 ID 生成器。

const TAG_WIDTH = 1000

const selectedTextList = ref([])

const selectedText = reactive({
  start: 0,
  end: 0,
  content: '',
})

const markContent = ref(
  '作文是經(jīng)過(guò)人的思想考慮和語(yǔ)言組織,通過(guò)文字來(lái)表達(dá)一個(gè)主題意義的記敘方法。作文體裁包括:記敘文、說(shuō)明文、應(yīng)用文、議論文。作文分為小學(xué)作文、中學(xué)作文、大學(xué)作文(論文)。'
)

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級(jí)',
    tag_color: `#DE050CFF`,
    tag_id: 'tag_id1',
  },
  {
    tag_name: '2級(jí)',
    tag_color: `#6ADE05FF`,
    tag_id: 'tag_id2',
  },
  {
    tag_name: '3級(jí)',
    tag_color: `#DE058BFF`,
    tag_id: 'tag_id3',
  },
  {
    tag_name: '4級(jí)',
    tag_color: `#9205DEFF`,
    tag_id: 'tag_id4',
  },
  {
    tag_name: '5級(jí)',
    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) return
  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
  )
  tagInfo.value = {
    visible: false,
    top: 0,
    left: 0,
  }
  resetEditTag()
}

const handleReset = () => {
  editTag.value.visible = false
  tagInfo.value.visible = true
}

const handleSave = () => {
  console.log('標(biāo)注的數(shù)據(jù)', selectedTextList.value)
}

const handleSelectLabel = t => {
  const { tag_color, tag_name, tag_id } = t
  tagInfo.value.visible = false
  const 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) {
    //如若重選,則刪除后再新增標(biāo)簽
    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_color
        element.style.userSelect = 'none'
        element.style.paddingBottom = '6px'
        element.onclick = function(e) {
          e.preventDefault()
          if (!e.target.id) return
          const left = e.offsetX < TAG_WIDTH ? 0 : e.offsetX - 300
          const 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,
  })
}

/**
 * 獲取選取的文字?jǐn)?shù)據(jù)
 */
const getSelectedTextData = () => {
  const select = window?.getSelection()
  const nodeValue = select.focusNode?.nodeValue
  const anchorOffset = select.anchorOffset
  const focusOffset = select.focusOffset
  const nodeValueSatrtIndex = markContent.value?.indexOf(nodeValue)
  selectedText.content = select.toString()
  if (anchorOffset < focusOffset) {
    //從左到右標(biāo)注
    selectedText.start = nodeValueSatrtIndex + anchorOffset
    selectedText.end = nodeValueSatrtIndex + focusOffset
  } else {
    //從右到左
    selectedText.start = nodeValueSatrtIndex + focusOffset
    selectedText.end = nodeValueSatrtIndex + anchorOffset
  }
}

const resetEditTag = () => {
  editTag.value = {
    visible: false,
    top: 0,
    left: 0,
    mark_id: '',
    content: '',
    tag_id: '',
    start: 0,
    end: 0,
  }
}

const drawMark = () => {
  //模擬后端返回的數(shù)據(jù)
  const res = [
    {
      start: 0, //必備
      end: 1,
      tag_color: '#DE050CFF',
      tag_id: 'tag_id1',
      tag_name: '1級(jí)',
      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_color
        element.style.userSelect = 'none'
        element.style.paddingBottom = '6px'
        element.onclick = function(e) {
          console.log('cccccc', m)
          const left = e.offsetX < TAG_WIDTH ? 0 : e.offsetX - 300
          editTag.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,
          }
        }
      },
    })
  })
}

//頁(yè)面初始化
onMounted(() => {
  const el = document.getElementById('text-container')
  //鼠標(biāo)抬起
  el?.addEventListener('mouseup', e => {
    const text = window?.getSelection()?.toString() || ''
    if (text.length > 0) {
      const left = e.offsetX < 500 ? e.offsetX - 20 : 500
      tagInfo.value = {
        visible: true,
        top: e.offsetY + 40,
        left: left,
      }
      getSelectedTextData()
    } else {
      tagInfo.value.visible = false
    }
    //清空重選/取消數(shù)據(jù)
    resetEditTag()
  })
  //從后端獲取標(biāo)注數(shù)據(jù),進(jìn)行初始化標(biāo)注
  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>

到了這里,關(guān)于vue3 + mark.js 實(shí)現(xiàn)文字標(biāo)注功能的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶(hù)投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • #vue3 實(shí)現(xiàn)前端下載excel文件模板功能

    #vue3 實(shí)現(xiàn)前端下載excel文件模板功能

    一、需求: 前端無(wú)需通過(guò)后端接口,即可實(shí)現(xiàn)模板下載功能。 通過(guò)構(gòu)造一個(gè) JSON 對(duì)象,使用前端常用的 第三方庫(kù) xlsx ,可以直接將該 JSON 對(duì)象轉(zhuǎn)換成 Excel 文件,讓用戶(hù)下載模板 二、效果: 三、源碼如下:

    2024年01月19日
    瀏覽(127)
  • vue3+emelenui實(shí)現(xiàn)前端分頁(yè)功能—最簡(jiǎn)單

    vue3+emelenui實(shí)現(xiàn)前端分頁(yè)功能—最簡(jiǎn)單

    在一些后臺(tái)管理系統(tǒng)或者博客管理系統(tǒng)中分頁(yè)功能是很常見(jiàn)的一種服務(wù),因?yàn)榭偛豢赡馨押芏鄶?shù)據(jù)放在一塊,那樣閱讀起來(lái)很麻煩,所以需要分頁(yè)。也是前后端中最為常見(jiàn)的一個(gè)功能 ?先看一下分頁(yè)場(chǎng)景的模擬。 ?首先我們要去后端寫(xiě)點(diǎn)數(shù)據(jù)通過(guò)接口給前端: 連接數(shù)據(jù)庫(kù):

    2024年02月09日
    瀏覽(21)
  • 前端vue3+element plus 分頁(yè)table排序功能實(shí)現(xiàn)

    我有如下所示的一個(gè)table,數(shù)據(jù)data是一個(gè)computed計(jì)算屬性,一般情況下篩選使用element的sortable屬性就可以了,可查看Element - The world\\\'s most popular Vue UI framework 但我的 table 是一個(gè)分頁(yè)的 table ,當(dāng)我使用?sortable 時(shí)發(fā)現(xiàn),它只是對(duì)當(dāng)前頁(yè)的數(shù)據(jù)進(jìn)行排序,但這并不是我需要的,我的

    2024年02月14日
    瀏覽(27)
  • 前端js如何實(shí)現(xiàn)截屏功能,插件推薦js-web-screen-shot

    前端js如何實(shí)現(xiàn)截屏功能,插件推薦js-web-screen-shot

    讀取dom結(jié)構(gòu)轉(zhuǎn)換成canvas,最后轉(zhuǎn)成圖片形式展示 缺點(diǎn) :沒(méi)有編輯功能 鏈接:html2canvas 大佬模仿qq截圖實(shí)現(xiàn)的,也可以搭配webrtc實(shí)現(xiàn)web端遠(yuǎn)程桌面共享 鏈接: github gitee 簡(jiǎn)單使用 注意點(diǎn):

    2024年02月06日
    瀏覽(28)
  • vue3+vant+cropper.js實(shí)現(xiàn)移動(dòng)端圖片裁剪功能

    最近做項(xiàng)目中遇到一個(gè)需求,需要對(duì)海報(bào)圖片按照一定的比例進(jìn)行裁剪并上傳到oss。一開(kāi)始這個(gè)需求思路有兩個(gè),使用canvas原生或者尋找現(xiàn)成的第三方庫(kù),對(duì)比了一番覺(jué)得canvas實(shí)現(xiàn)時(shí)間耗費(fèi)較長(zhǎng),且秉承著不重復(fù)造輪子的原則(其實(shí)是菜)。 在進(jìn)行技術(shù)調(diào)研后,決定使用vu

    2024年02月01日
    瀏覽(30)
  • vue3插件——vue-web-screen-shot——實(shí)現(xiàn)頁(yè)面截圖功能

    vue3插件——vue-web-screen-shot——實(shí)現(xiàn)頁(yè)面截圖功能

    最近在看前同事發(fā)我的 vue3 框架時(shí),發(fā)現(xiàn)他們有個(gè)功能是要實(shí)現(xiàn)頁(yè)面截圖功能。 最近項(xiàng)目遇到的要求是彈出框上傳文件,需要用到頁(yè)面截圖,由于使用的是Vue3的框架于是選擇用vue-web-screen-shot組件進(jìn)行操作。(由于插件是Vue3編寫(xiě)的,所以只適用于Vue3的項(xiàng)目,如果是Vue2的項(xiàng)目,

    2024年02月05日
    瀏覽(24)
  • 使用ExcelJS實(shí)現(xiàn)excel的前端導(dǎo)出功能(Vue3+TS)

    ExcelJS :讀取,操作并寫(xiě)入電子表格數(shù)據(jù)和樣式到 XLSX 和 JSON 文件。一個(gè) Excel 電子表格文件逆向工程項(xiàng)目。 github中文文檔:https://github.com/exceljs/exceljs/blob/master/README_zh.md ?封裝excel.ts工具文件 表格頁(yè)面調(diào)用excel工具文件?

    2024年02月14日
    瀏覽(29)
  • vue+face-api.js實(shí)現(xiàn)前端人臉識(shí)別功能

    vue+face-api.js實(shí)現(xiàn)前端人臉識(shí)別功能

    近期做了一個(gè)前端vue實(shí)現(xiàn)人臉識(shí)別的功能,主要功能邏輯包含:人臉識(shí)別,人臉驗(yàn)證,喚起攝像頭視頻流之后從三個(gè)事件(用戶(hù)點(diǎn)頭、搖頭、眨眼睛)中隨機(jī)選中兩個(gè)事件,待兩個(gè)事件通過(guò)判斷后人臉靜止不動(dòng)3秒鐘后截取視頻流生成圖片,上傳到阿里或者騰訊oss,通過(guò)oss返回

    2024年02月05日
    瀏覽(25)
  • 基于VUE3+Layui從頭搭建通用后臺(tái)管理系統(tǒng)(前端篇)二:登錄界面及對(duì)應(yīng)功能實(shí)現(xiàn)

    基于VUE3+Layui從頭搭建通用后臺(tái)管理系統(tǒng)(前端篇)二:登錄界面及對(duì)應(yīng)功能實(shí)現(xiàn)

    ??本章介紹系統(tǒng)登錄界面、登錄流程、登錄接口等相關(guān)內(nèi)容的開(kāi)發(fā),實(shí)現(xiàn)包括賬號(hào)密碼登錄、短信驗(yàn)證登錄等不同的登錄方式,使用svg-capter生成圖形驗(yàn)證碼,使用expressjwt實(shí)現(xiàn)登錄token的生成及驗(yàn)證。 1. 詳細(xì)課程地址: https://edu.csdn.net/course/detail/38183 2. 源碼下載地址: 點(diǎn)擊

    2024年02月11日
    瀏覽(34)
  • vue3引入JS-SDK實(shí)現(xiàn)h5分享小卡片、跳轉(zhuǎn)微信小程序功能

    vue3引入JS-SDK實(shí)現(xiàn)h5分享小卡片、跳轉(zhuǎn)微信小程序功能

    微信js-sdk官方文檔: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html 想要實(shí)現(xiàn)的效果: 1.登錄微信公眾平臺(tái),進(jìn)入“公眾號(hào)設(shè)置”的“功能設(shè)置”里填寫(xiě)“JS接口安全域名”。 2.通過(guò)npm引入js-sdk 安裝成功后,可以在package.json中找到\\\"weixin-js-sdk\\\" 3.在main.js中,將js-sdk掛載

    2024年02月11日
    瀏覽(101)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包