给一篇文章中的关键词进行标注

需求:鼠标选择关键词后点击鼠标右键给关键词添加到对应的标签下并给关键词添加不同颜色的下划线,添加后可以支持删除取消文章中关键词的下划线

效果图:

创建pagination/index.vue

<template>
  <div class="pagination-btn">
    <!-- 上 -->
    <span :disabled="pageNo == 1" class="number" @click="$emit('getPageNo', pageNo - 1)">
      <i class="el-icon-arrow-left"></i>
    </span>
    <span class="number"
      v-if="startNumAndEndNum.start > 1"
      @click="$emit('getPageNo', 1)"
      :class="[{'active': pageNo == 1}, dataList[0].manual ? 'spot' : 'unmarked']"
    >
      1
    </span>
    <span class="number" v-if="startNumAndEndNum.start > 2">···</span>
    <!-- 中间部分 -->
    <!-- eslint-disable-next-line vue/no-use-v-if-with-v-for -->
    <span class="number spot" v-for="(page, index) in startNumAndEndNum.end" :key="index" v-if="page >= startNumAndEndNum.start" @click="$emit('getPageNo', page)"
      :class="[{ active: pageNo == page }, dataList[page - 1].manual ? 'spot' : 'unmarked']">
      {{ page }}
    </span>

    <!-- 下 -->
    <span class="number" v-if="startNumAndEndNum.end < totalPage - 1">···</span>
    <span class="number spot"
      v-if="startNumAndEndNum.end < totalPage"
      @click="$emit('getPageNo', totalPage)"
      :class="[{ active: pageNo == totalPage }, dataList[totalPage - 1].manual ? 'spot' : 'unmarked']"

    >
      {{ totalPage }}
    </span>
    <span class="number"
      :disabled="pageNo == totalPage"
      @click="$emit('getPageNo', pageNo + 1)"
    >
      <i class="el-icon-arrow-right"></i>
    </span>
    <span class="jump">
      跳转到
      <el-input v-model.number="inputValue" @input="handleInput"></el-input>
    </span>
 </div>
</template>

<script>
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'Pagination',
  props: ['pageNo', 'pageSize', 'total', 'continues', 'dataList'],
  computed: {
    // 计算一共有多少页,用总的页数/每页展示的数据量,因为结果可能为小数,所有这里向上取整
    totalPage() {
      return Math.ceil(this.total / this.pageSize);
    },
    //计算出连续的页码的起始数字与结束数字[连续页码的数字:至少是3]
    startNumAndEndNum() {
      const { continues, pageNo, totalPage } = this
      //先定义两个变量存储起始数字与结束数字
      let start = 0,
        end = 0
      //连续页码数字3【就是至少3页】,如果出现不正常的现象【就是不够3页】
      //不正常现象【总页数没有连续页码多】
      if (continues > totalPage) {
        start = 1
        end = totalPage
      } else {
        //正常现象【连续页码3,但是你的总页数一定是大于3的】
        //起始数字
        start = pageNo - parseInt(continues / 2)
        //结束数字
        end = pageNo + parseInt(continues / 2)
        //把出现不正常的现象【start数字出现0|负数】纠正
        if (start < 1) {
          start = 1
          end = continues
        }
        //把出现不正常的现象[end数字大于总页码]纠正
        if (end > totalPage) {
          end = totalPage
          start = totalPage - continues + 1
        }
      }
      return { start, end }
    }
  },
  data() {
    return {
      inputValue: this.pageNo,
      lastValidValue: ''
    }
  },
  watch: {
    pageNo(val) {
      this.inputValue = val;
    }
  },
  methods: {
    handleInput(value) {
      const integerRegex = /^[+-]?\d*$/;
      if (integerRegex.test(value) && value <= this.total) {
        this.lastValidValue = value;
      } else {
        if (value === '') {
          this.$nextTick(() => {
            this.inputValue = 1;
          });
        }
        this.$nextTick(() => {
          this.inputValue = this.lastValidValue;
        });
      }
      this.$emit('getPageNo', this.inputValue)
    }
  }
}
</script>

<style lang="scss" scoped>
.pagination-btn {
  .number {
    display: inline-block;
    width: 26px;
    height: 26px;
    cursor: pointer;
    line-height: 26px;
    text-align: center;
    color: #4E5969;
    border-radius: 2px 2px 2px 2px;
    margin-right: 8px;
    border: 1px solid #E5E6EB;

  }
  .spot {
    position: relative;
    &::after {
      position: absolute;
      width: 4px;
      height: 4px;
      right: 2px;
      top: 2px;
      background: #165DFF;
      border-radius: 50%;
      content: "";
      position: absolute;
    }
  }
  .unmarked {
    position: relative;
    &::after {
      position: absolute;
      width: 4px;
      height: 4px;
      right: 2px;
      top: 2px;
      background: red;
      border-radius: 50%;
      content: "";
      position: absolute;
    }
  }
  // button {
  //   margin: 0 5px;
  //   background-color: #f4f4f5;
  //   color: #606266;
  //   outline: none;
  //   border-radius: 2px;
  //   padding: 0 4px;
  //   vertical-align: top;
  //   display: inline-block;
  //   font-size: 13px;
  //   min-width: 35.5px;
  //   height: 28px;
  //   line-height: 28px;
  //   cursor: pointer;
  //   box-sizing: border-box;
  //   text-align: center;
  //   border: 0;

  //   &[disabled] {
  //     color: #c0c4cc;
  //     cursor: not-allowed;
  //   }

  //   &.active {
  //     cursor: not-allowed;
  //     background-color: #409eff;
  //     color: #fff;
  //   }
  // }
}
.active {
  border: 1px solid #165DFF !important;
  color: #165DFF !important;
}
.jump {
  font-size: 14px;
  color: #3D3D3D;
  ::v-deep .el-input {
    height: 26px;
    width: 40px;
    .el-input__inner {
      height: 26px;
      padding: 0;
      text-align: center;
    }
  }
}
</style>

自定义分页组件,主要用于标记每篇文章标注状态

创建articleContentAnnotation.vue

<template>
  <div class="annotated-text" :style="`height: ${height}`">
    <div class="operation-content" v-if="JSON.stringify(articleDetails) != '{}'">
      <el-alert
          v-if="articleDetails.rejectReason"
          :title="'驳回原因:'+ articleDetails.rejectReason"
          type="error"
          class="alert_error"
          show-icon>
        </el-alert>
      <div class="text">
        <div class="title">
          <span v-html="articleDetails.title"></span>
          <div class="date">
            {{articleDetails.publishTime}}
          </div>
        </div>
        <div class="content" :key="keyIndex" ref="article-content" v-html="articleDetails.content" @contextmenu.prevent="onContextmenu">
      </div>
      </div>
      <div class="pagination">
        <pagination :dataList="dataList" :pageNo="pageData" :pageSize="1" :total="total" :continues="3" @getPageNo="getPageNo"></pagination>
      </div>
    </div>
    <el-empty v-else style="width:100%;height: 100%;" description="暂无数据"></el-empty>
  </div>
</template>

<script>
import pagination from '../../../componentsDTD/pagination/index.vue'
export default {
  components: {
    pagination,
  },
  props: {
    height: {
      type: String,
      default: '100%'
    },
    dataList: {
      type: Array,
      default: () => []
    },
    articleDetails: {
      type: Object,
      default: () => {}
    },
    total: {
      type: Number,
      default: 0
    },
    isChecked: {
      type: Boolean,
      default: true
    },
    page: {
      type: Number,
      default: 0
    }
  },
  data() {
    return {
      pageData: 1,
      cursorSelection: '',
      contextMenuData: [],
      keyIndex: 0
    }
  },
  watch: {
    dataList() {
      this.renderingKeywords()
    },
    pageData () {
      this.keyIndex += 1
      this.renderingKeywords()
    },
    page () {
      this.pageData = this.page
    }
  },
  mounted() {
    document.addEventListener('mouseup', this.handleMouseUp);
  },
  beforeDestroy() {
    document.removeEventListener('mouseup', this.handleMouseUp);
  },
  methods: {
    renderingKeywords (textType) {
      if (textType) {
        const div = this.$refs['article-content']
        div.innerHTML = this.articleDetails.content
      }
      this.contextMenuData = []
      this.$nextTick(() => {
        if (this.articleDetails && this.articleDetails.annotationTag) {
          this.articleDetails.annotationTag.forEach((i, index) => {
            if (i.inputBox === 1) {
              if (i.value) {
                i.value.forEach(j => {
                  this.replaceKeywords(j, `keyword${index}`)
                })
              }
              this.contextMenuData.push({
                label: `添加到${i.tag}`,
                // icon: "iconfont el-icon-edit-outline",
                onClick: () => {
                  this.EiditRowFun(i.tag, index);
                },
              })
            }
          })
        }
      })
    },
    replaceKeywords (keyword, className) {
      // if (!keyword) return htmlString
      const div = this.$refs['article-content']
      div.innerHTML = div.innerHTML
      const textNodes = this.getTextNodeList(div)
      const textList = this.getTextInfoList(textNodes)
      const content = textList.map(({ text }) => text).join('')
      const matchList = this.getMatchList(content, keyword)
      this.replaceMatchResult(textNodes, textList, matchList, className)
      return div.innerHTML
    },
    replaceMatchResult (textNodes, textList, matchList, className) {
      // 对于每一个匹配结果,可能分散在多个标签中,找出这些标签,截取匹配片段并用font标签替换出
      for (let i = matchList.length - 1; i >= 0; i--) {
      const match = matchList[i]
      const matchStart = match.index, matchEnd = matchStart + match[0].length // 匹配结果在拼接字符串中的起止索引
      // 遍历文本信息列表,查找匹配的文本节点
      for (let textIdx = 0; textIdx < textList.length; textIdx++) {
        const { text, startIdx, endIdx } = textList[textIdx] // 文本内容、文本在拼接串中开始、结束索引
        if (endIdx < matchStart) continue // 匹配的文本节点还在后面
        if (startIdx >= matchEnd) break // 匹配文本节点已经处理完了
        let textNode = textNodes[textIdx] // 这个节点中的部分或全部内容匹配到了关键词,将匹配部分截取出来进行替换
        const nodeMatchStartIdx = Math.max(0, matchStart - startIdx) // 匹配内容在文本节点内容中的开始索引
        const nodeMatchLength = Math.min(endIdx, matchEnd) - startIdx - nodeMatchStartIdx // 文本节点内容匹配关键词的长度
        if (nodeMatchStartIdx > 0) textNode = textNode.splitText(nodeMatchStartIdx) // textNode取后半部分
        if (nodeMatchLength < textNode.wholeText.length) textNode.splitText(nodeMatchLength)
        const font = document.createElement('font')
        if (className) {
          font.classList.add(className);
        }
        font.innerText = text.substr(nodeMatchStartIdx, nodeMatchLength)
        textNode.parentNode.replaceChild(font, textNode)
      }
      }
    },
    removeHighlightClass(className) {
      const elementsWithClass = document.getElementsByClassName(className);
      while (elementsWithClass.length > 0) {
        const element = elementsWithClass[0];
        // 如果是font标签,直接移除
        if (element.tagName.toLowerCase() === 'font') {
          const parent = element.parentNode;
          parent.replaceChild(document.createTextNode(element.textContent), element);
        } else {
          // 如果是其他元素,仅移除类名
          element.classList.remove(className);
        }
      }
    },
    getTextNodeList (dom) {
      const nodeList = [...dom.childNodes]
      const textNodes = []
      while (nodeList.length) {
        const node = nodeList.shift()
      if (node.nodeType === node.TEXT_NODE) {
        textNodes.push(node)
      } else {
        nodeList.unshift(...node.childNodes)
      }
      }
      return textNodes
    },
    getTextInfoList (textNodes) {
      let length = 0
      const textList = textNodes.map(node => {
      let startIdx = length, endIdx = length + node.wholeText.length
      length = endIdx
      return {
        text: node.wholeText,
        startIdx,
        endIdx
      }
      })
      return textList
    },
    getMatchList (content, keyword) {
      const characters = [...'\\[](){}?.+*^$:|'].reduce((r, c) => (r[c] = true, r), {})
      keyword = keyword.split('').map(s => characters[s] ? `\\${s}` : s).join('[\\s\\n]*')
      const reg = new RegExp(keyword, 'gmi')
      const matchList = []
      let match = reg.exec(content)
      while (match) {
        matchList.push(match)
        match = reg.exec(content)
      }
      return matchList
    },
    getPageNo(page) {
      if (page >= 1 && page <= this.total) {
        this.pageData = page;
        this.$emit('pageNo', page)
      }
    },
    // 鼠标右键事件
    onContextmenu (event) {
      if (this.isChecked) {
        if (this.cursorSelection.length > 0) {
          this.selectKeywords = this.cursorSelection
          this.cursorSelection = ''
          this.$contextmenu({
              items: this.contextMenuData,
              event, // 鼠标事件信息
              customClass: 'custom-class', // 自定义菜单 class
              zIndex: 3, // 菜单样式 z-index
              minWidth: 230 // 主菜单最小宽度
          });
          return false;
      }
      }

    },
    // 获取选中的文本
    handleMouseUp() {
      var selection = window.getSelection().toString();
      if (selection.length > 0) {
        this.cursorSelection = selection
      }
    },
    // 添加关键词
    EiditRowFun(tag, index) {
      this.replaceKeywords(this.selectKeywords, `keyword${index}`)
      this.$emit('EiditRowFun', this.selectKeywords, tag)
      this.selectKeywords = ''
    }
  },
}
</script>

<style lang="scss" scoped>
  .annotated-text {
    margin-top: 1px;
    width: calc(100% - 400px);
    float: left;
    .operation-content {
      padding: 32px 0;
      width: 726px;
      height: 100%;
      background-color: #fff;
      margin: 0 auto;
      position: relative;
      .alert_error{
        position: absolute;
        top:-15px;
        right: 10%;
        width:80%;
      }
      .text {
        height: calc(100% - 26px);
        overflow: auto;
      }
      .title {
        padding: 0 64px;
        text-align: center;
        font-weight: 600;
        font-size: 18px;
        color: #1D2129;
        line-height: 29px;
        padding-bottom: 12px;
        border-bottom: 1px solid #F1F1F5;
        .date {
          margin-top: 10px;
          font-size: 12px;
          color: #86909C;
          font-weight: 400;
          line-height: 16px;
        }
      }
      .content {
        padding: 0 64px;
        overflow: auto;
        margin-top: 12px;
        font-size: 14px;
        color: #1D2129;
        line-height: 22px;
        text-align: left;
      }
      .pagination {
        position: absolute;
        right: 0;
        bottom: 16px;
        margin-top: 16px;
        padding: 0 64px;
        text-align: right;
        // ::v-deep .number, ::v-deep .btn-prev, ::v-deep .el-icon-more, ::v-deep .btn-next {
        //   background-color: #fff;
        //   border: 1px solid #E5E6EB;
        // }
        // ::v-deep .active {
        //   border: 1px solid #165DFF;
        //   color: #165DFF;

        // }
        // ::v-deep .number {
        //   position: relative;
        //   &::after {
        //     position: absolute;
        //     width: 4px;
        //     height: 4px;
        //     right: 2px;
        //     top: 2px;
        //     background: #165DFF;
        //     border-radius: 50%;
        //     content: "";
        //     position: absolute;
        //   }
        // }
      }
    }
  }
  .keyword-highlight {
    color: red;
  }
  ::v-deep .selected-html-text {
    padding-bottom: 2px;
    display: inline-block;
    border-bottom: 2px solid red;
  }
  ::v-deep font {
    margin-bottom: 2px;
    display: inline-block;
    border-bottom: 2px solid red;
  }
  ::v-deep .keyword0 {
    border-bottom: 2px solid #002fa8;
  }
  ::v-deep .keyword1 {
    border-bottom: 2px solid #80d8cf;
  }
  ::v-deep .keyword2 {
    border-bottom: 2px solid #003153;
  }
  ::v-deep .keyword3 {
    border-bottom: 2px solid #e60000;
  }
  ::v-deep .keyword4 {
    border-bottom: 2px solid #b05a23;
  }
  ::v-deep .keyword5 {
    border-bottom: 2px solid #900020;
  }
  ::v-deep .keyword6 {
    border-bottom: 2px solid #fbd36a;
  }
  ::v-deep .keyword7 {
    border-bottom: 2px solid #8e4b26;
  }
  ::v-deep .keyword8 {
    border-bottom: 2px solid #01847e;
  }
  ::v-deep .keyword9 {
    border-bottom: 2px solid #3c7a17;
  }
</style>

创建keywordRighe.vue

<template>
  <div class="keyword-right" :style="`height: ${height}`">
    <div class="label" v-if="dataList && dataList.length > 0">
      <div class="label_item" v-for="(i, index) in dataList" :key="index">
        <normalTitle :title="i.tag" />
        <div v-if="i.inputBox === 1">
          <div v-if="i.value && i.value.length > 0" class="label_wrap">
            <span
              class="label_text"
              :style="`color: ${color[index]}; background-color: ${background[index]};`"
              v-for="(j, index2) in i.value"
              :key="index2"
            >
            {{ j }}
            <i class="el-icon-circle-close" v-if="isChecked" @click="deleteATap(index, index2)"></i>
          </span>
          </div>
          <el-empty v-else style="width:100%" description="暂无数据"></el-empty>
        </div>
        <div class="select" v-else>
          <el-select v-model="i.value" :disabled="!isChecked" placeholder="请选择">
            <el-option
              v-for="item in i.dicInfoList"
              :key="item.id"
              :label="item.dicValue"
              :value="item.id">
            </el-option>
          </el-select>
        </div>
      </div>
    </div>
    <el-empty v-else style="width:100%;height: 90%;" description="暂无数据"></el-empty>
    <div class="btn" v-if="isShowBtn">
      <el-button type="primary" :disabled="isVerify == 2" class="btn" :loading="subLoading" @click="finshData">任务完成</el-button>
    </div>
  </div>
</template>

<script>
import normalTitle from '@/views/componentsDTD/normalTitle/index.vue'
export default {
  components: {
    normalTitle
  },
  props: {
    height: {
      type: String,
      default: '100%'
    },
    isVerify: {
      type: String,
      default: ''
    },
    subLoading: {
      type: Boolean,
      default: false
    },
    dataList: {
      type: Array,
      required: true,
      default: () => []
    },
    page: {
      type: Number,
      default: 1
    },
    isChecked: {
      type: Boolean,
      default: true
    },
    isShowBtn: {
      type: Boolean,
      default: true
    }
  },
  watch: {
    // dataList: {
    //   handler(newUser, oldUser) {
    //   },
    //   deep: true,
    // },
  },
  data() {
    return {
      color: ['#002fa8', '#80d8cf', '#003153', '#e60000', '#b05a23', '#900020', '#fbd36a', '#8e4b26', '#01847e', '#3c7a17'],
      background: [
        'rgba(0, 47, 168, 0.1)',
        'rgba(128, 216, 207, 0.1)',
        'rgba(0, 49, 83, 0.1)',
        'rgba(230, 0, 0, 0.1)',
        'rgba(176, 90, 35, 0.1)',
        'rgba(144, 0, 32, 0.1)',
        'rgba(251, 211, 106, 0.1)',
        'rgba(142, 75, 38, 0.1)',
        'rgba(1, 132, 126, 0.1)',
        'rgba(60, 122, 23, 0.1)',
      ],
      selectedHtmlTextList: [], // 关联人物
    }
  },
  methods: {
    deleteATap(index, index2) {
      this.$emit('deleteATap', index, index2)
    },
    finshData () {
      this.$emit('finshData')
    }
  }
}
</script>

<style lang="scss" scoped>
.keyword-right {
  margin-top: 1px;
  width: 400px;
  overflow-y: auto;
  overflow-x: hidden;
  background-color: #fff;
  float: right;

  .label {
    // min-height: calc(100vh - 150px);
    height: calc(100% - 66px);
    background-color: #fff;
    overflow-y: auto;
    overflow-x: hidden;
    border-radius: 4px 4px 4px 4px;
    padding: 16px;
    .label_item {
      overflow: hidden;
      margin-bottom: 24px;
    }
    .el-empty {
      padding: 0 !important;
      ::v-deep .el-empty__image {
        width: 60px !important;
      }
    }
    ::v-deep .select {
      margin-top: 16px;
      .el-select {
        display: inline-block;
      }
      .el-input__inner {
        height: 32px;
      }
      .el-input__icon {
        line-height: 32px;
      }
    }
    .label_wrap{
      display: flex;
      flex-wrap:wrap;
      .label_text {
        // display: block;
        // float: left;
        margin-top: 16px;
        padding: 3px 8px;
        margin-right: 8px;
        border-radius: 2px 2px 2px 2px;
        font-size: 14px;
      }
    }
    .el-icon-circle-close {
      cursor: pointer;
    }
  }
}
.btn {
  margin-top: 10px;
  float: right;
  margin-right: 10px;
}
</style>

父组件中引用

<template>
  <div class="details-label" v-loading="loading">
    <headTop :btnText="'标注'" :btnLoading="btnLoading" :progress="progress" :typeL="'biaozhu'" :rejectText="rejectText" :rejectStatus="rejectStatus" :page="page" @completeData="completeData" />
    <articleContentAnnotation
      ref="articleContentAnnotation"
      :total="dataList.length"
      @pageNo="pageNo"
      :page="page"
      :dataList="dataList"
      :articleDetails="dataList[page - 1] || {}"
      :height="'calc(100% - 60px)'"
      @EiditRowFun="EiditRowFun"
    />
    <keywordRighe :subLoading="subLoading" @deleteATap="deleteATap" @finshData="finshData" :dataList="dataList.length > 0 ? dataList[page - 1].annotationTag : []" :page="page" ref="keywordRighe" :height="'calc(100% - 60px)'"/>
  </div>
</template>

<script>
import KeywordHighlight from './KeywordHighlight.vue';
import headTop from './components/head.vue'
import keywordRighe from './components/keywordRighe.vue'
import articleContentAnnotation from './components/articleContentAnnotation.vue'
import { searchAnnotationList,searchAnnotationListByAssigner,manpowerAnnotation,selectTaskSubInfo,saveAnnotationTask } from '@/api/taskManagement'
export default {
  components: {
    headTop,
    KeywordHighlight,
    keywordRighe,
    articleContentAnnotation
  },
  data() {
    return {
      btnLoading: false,
      subLoading: false,
      dataList: [
            {
                "manual": null,
                "index": "dtd_annotation_article_info",
                "documentId": "084686b1a7ce451ea550673f0233193c",
                "title": "东方精工飙升背后的秘密:机构力挺、估值洼地与技术突破",
                "translateTitle": null,
                "author": "",
                "source": null,
                "abstractText": null,
                "content": "东方精工大涨揭秘:机构观点、估值分析、技术面解析与热点事件深度解读 东方精工(股票代码:002611.SZ)在资本市场上大放异彩,股价连续飙升,吸引了众多投资者的目光。作为一家专注于高精度零部件研发和制造的企业,东方精工以其卓越的技术实力和市场表现,成为了市场关注的焦点。本文将从机构观点、估值分析、技术面解析以及热点事件深度解读四个维度,全面剖析东方精工大涨的深层次原因,为投资者提供有价值的参考。 机构观点:一致看好,力挺东方精工 多家知名投资机构对东方精工给予了高度评价,纷纷表示看好其未来发展前景。国投证券等权威机构在最新研报中明确指出,东方精工在智能装备制造领域的领先地位和技术创新能力是其股价上涨的重要驱动力。机构认为,随着全球智能化和自动化水平的不断提升,对高端智能装备的需求将持续增长,而东方精工凭借其深厚的技术积累和丰富的产品线,有望在这一领域持续扩大市场份额,实现业绩的快速增长。 估值分析:估值洼地,潜力巨大 从估值角度来看,东方精工目前处于相对低估的状态,具有较高的投资价值。与同行业可比公司相比,东方精工的市盈率、市净率等关键估值指标均处于较低水平,显示出其股价存在较大的上升空间。此外,公司近年来业绩稳健增长,净利润和营业收入均保持较高水平,为股价上涨提供了坚实的业绩支撑。随着市场对公司价值的重新认识,东方精工的估值有望得到进一步修复和提升。 技术面解析:强势突破,技术面支撑强劲 从技术面来看,东方精工股价的连续上涨并非偶然,而是有其内在的技术支撑。首先,从K线图上看,公司股价近期呈现出明显的上升趋势,成交量逐步放大,显示出市场资金对东方精工的积极追捧。其次,MACD、KDJ等技术指标均发出买入信号,进一步确认了股价上涨的趋势。此外,公司股价在连续两个交易日内涨幅偏离值累计达到20%,虽然触发了股票交易异常波动公告,但并未改变其整体上涨的趋势。这表明市场对公司未来发展前景的强烈信心。 本月也是快过去了,在A股现在的难点是缺少增量资金的行情下,还有月初做的大众交通也是实现了翻倍增长,工业富联,赛腾股份都是已经吃到肉了,在这个时刻,我要跟你说说我观察到的一支潜力的黑马,特点如下, 1、上一轮10倍大牟,横盘3年,横有多长竖有多高; 2、业绩优秀,且占据当下各大热点板块; 3、筹码目前也是底部单峰密集的形态,后市具有极大的上升空间 4、近期成交量放倍量,股价在不断的尝试突破2年周期的底部平台,有望启动,现在就是最佳的布局时机。 为了不打扰主力控盘!就不在此说了,如果你也想加入我们,相信你能在我这收获到你所需要的,成为追梦的一员,想跟上的常来的地方忪重号:君游一剑,别再犹豫了,机会总是留给有准备的人。与人为善只为自渡,期待与你同行! 热点事件深度解读:多重利好叠加,助力股价飙升1. 半年度业绩稳健增长 尽管公司尚未披露2024年半年度业绩预告,但根据过往业绩和当前市场情况分析,东方精工上半年业绩有望实现稳健增长。公司智能装备制造业务整体增势良好,智能瓦楞纸包装装备板块、数字印刷解决方案板块以及水上动力产品板块均表现出色,为业绩增长提供了有力保障。此外,非经常性损益的增加也将对公司净利润产生积极影响。 2. 技术创新与产品升级 东方精工在技术创新和产品升级方面持续加大投入,不断推出符合市场需求的新产品和技术解决方案。公司在智能包装装备和水上动力设备领域的技术积累和创新能力,为其赢得了市场竞争优势。随着全球智能化和自动化趋势的加速发展,东方精工的技术创新和产品升级将进一步推动其业绩增长和股价上涨。 3. 行业展会积极参与 通过积极参与行业展会,东方精工不仅展示了其最新技术和产品,还加强了与全球客户及合作伙伴的联系,提升了品牌影响力。这种积极的市场参与策略有助于公司更好地把握市场机遇,拓展新的业务领域和市场空间。 4. 股份回购计划 公司实施的股份回购计划进一步彰显了管理层对公司未来发展前景的信心。股份回购不仅有助于提升公司股价,还能增强投资者对公司价值的认识和信心。在市场环境复杂多变的背景下,股份回购计划为公司股价上涨提供了有力支撑。 结论 东方精工股价的连续上涨是多重利好因素共同作用的结果。机构的一致看好、估值洼地的存在、技术面的强劲支撑以及热点事件的积极推动,共同构成了东方精工股价上涨的强大动力。未来,随着全球智能化和自动化趋势的加速发展以及公司自身技术实力的不断提升,东方精工有望继续保持稳健增长态势,为投资者带来更加丰厚的回报。因此,对于关注智能制造领域的投资者而言,东方精工无疑是一个值得重点关注和布局的优质标的。 ​​​",
                "translateContent": null,
                "publishTime": "2024-07-26 17:00:00",
                "crawlerTime": null,
                "url": "https://weibo.com/ttarticle/p/show?id=2309405060552517878202",
                "picturesArray": [
                    "https://wx1.sinaimg.cn/mw1024/86f7799bly4hs1sqhmvbjj20u00ei0z3.jpg",
                    "https://wx1.sinaimg.cn/mw1024/86f7799bly4hs1sqii65ej20b0055dgx.jpg",
                    "https://wx1.sinaimg.cn/mw1024/86f7799bly4hs1sq7g17qj20u01jjn6e.jpg"
                ],
                "videosArray": null,
                "videosCoverArray": null,
                "mediaType": null,
                "likeCount": 0,
                "commentCount": 0,
                "readCount": null,
                "playCount": null,
                "rttCount": 0,
                "articleType": null,
                "ipAreaName": null,
                "ipAreaCountry": null,
                "ipAreaProvince": null,
                "rootPublishTime": null,
                "articleId": null,
                "rootArticleId": null,
                "parentArticleId": null,
                "rootArticleUrl": null,
                "uuid": "9d79f3d51f369a6339a570320e49ccdc",
                "timeDelay": null,
                "videoRurationSecond": null,
                "original": null,
                "barrageCount": null,
                "coinCount": null,
                "collectCount": null,
                "topicTags": null,
                "signInCountry": null,
                "signInProvince": null,
                "signInCity": null,
                "signInCounty": null,
                "signInAddress": null,
                "positionName": null,
                "audioUrl": null,
                "insertTime": "2024-07-26 17:22:54",
                "updateTime": "2024-07-31 06:01:39",
                "esInsertTime": "2024-07-31 06:01:39",
                "gid": null,
                "user": null,
                "analysis": {
                    "polarity": null,
                    "fingerprint": null,
                    "entityAddress": null,
                    "entityOrganization": null,
                    "entityPeople": null,
                    "entityGoods": null,
                    "entityAccount": null,
                    "entityBlockchainAddress": null,
                    "analyzeLocation": null,
                    "keywords": null,
                    "language": "1",
                    "isSensitive": "0",
                    "firstPublication": "0",
                    "geoLocation": null,
                    "personPremark": null,
                    "emotion": null,
                    "keyPhrase": null,
                    "heatNumber": null,
                    "sensitiveWords": null,
                    "sensitiveScore": null,
                    "sensitiveClassification": null,
                    "lifeSatisfactionScore": null,
                    "socialPositionSatisfactionScore": null,
                    "socialRiskSatisfactionScore": null,
                    "governmentSatisfactionScore": null,
                    "economicSatisfactionScore": null,
                    "categoriesRelatedToChina": null,
                    "stanceRelatedToChina": null,
                    "openness": null,
                    "neuroticism": null,
                    "agreeableness": null,
                    "extraversion": null,
                    "conscientiousness": null
                },
                "platform": {
                    "dataSourceType": "NEWSWEBSITE",
                    "websiteName": "新浪微博",
                    "host": "weibo.com",
                    "subHost": "weibo.com",
                    "country": "中国",
                    "isForeignMedia": 1,
                    "isSensitiveSite": null,
                    "mediaAreaCity": "",
                    "mediaAreaCounty": "",
                    "mediaAreaProvince": "",
                    "channelUrl": null,
                    "channelName": "",
                    "mediaTags": null
                },
                "taskId": 598,
                "departId": 200,
                "assigner": 101,
                "annotationTag": [
                    {
                        "id": 3,
                        "type": "通用标签",
                        "tag": "提及人物",
                        "inputBox": 1,
                        "dicInfoList": null,
                        "value": []
                    },
                    {
                        "id": 5,
                        "type": "通用标签",
                        "tag": "提及组织",
                        "inputBox": 1,
                        "dicInfoList": null,
                        "value": []
                    },
                    {
                        "id": 8,
                        "type": "通用标签",
                        "tag": "提及地点",
                        "inputBox": 1,
                        "dicInfoList": null,
                        "value": [
                            "供有价值的"
                        ]
                    },
                    {
                        "id": 10,
                        "type": "通用标签",
                        "tag": "关键词",
                        "inputBox": 1,
                        "dicInfoList": null,
                        "value": []
                    },
                    {
                        "id": 11,
                        "type": "通用标签",
                        "tag": "情感类别",
                        "inputBox": 2,
                        "dicInfoList": [
                            {
                                "id": 47,
                                "tagLibraryId": 11,
                                "dicValue": "正面"
                            },
                            {
                                "id": 48,
                                "tagLibraryId": 11,
                                "dicValue": "中立"
                            },
                            {
                                "id": 49,
                                "tagLibraryId": 11,
                                "dicValue": "负面"
                            }
                        ],
                        "value": []
                    },
                    {
                        "id": 12,
                        "type": "通用标签",
                        "tag": "敏感词",
                        "inputBox": 1,
                        "dicInfoList": null,
                        "value": []
                    },
                    {
                        "id": 25,
                        "type": "通用标签",
                        "tag": " 提及人物",
                        "inputBox": 1,
                        "dicInfoList": null,
                        "value": []
                    }
                ],
                "dataStatus": 3,
                "annotationTagResult": null,
                "seeAnnotation": null,
                "rejectReason": "1",
                "rejectStatus": 1
            },
            {
                "manual": true,
                "index": "dtd_annotation_article_info",
                "documentId": "6390307c355647bb97281a030d44f41a",
                "title": "打造梦幻儿童房,丹麦进口儿童家具Lifetime陪伴成长",
                "translateTitle": null,
                "author": "",
                "source": null,
                "abstractText": null,
                "content": "在孩子的成长过程中,一个温馨舒适的儿童房是他们健康成长的重要组成部分。而选择一款优质的儿童床更是关乎孩子的安全和舒适度。作为丹麦进口的儿童家具品牌,Lifetime以其出色的质量和创新设计赢得了家长们的信赖,让孩子们拥有一个梦幻般的成长空间。 ​   童趣设计,陪伴成长   Lifetime儿童床不仅仅是一张床,更是孩子成长的见证。其设计融入了童趣与实用性,吸引了无数宝爸宝妈的目光。每一款床都是经过精心设计,考虑到孩子的需求和安全,让他们在舒适的环境中自由成长。   独特创意,打造梦幻空间   Lifetime儿童床不仅在质量上保证,更在创意设计上领先。其独特的外形和多样化的功能让孩子们的房间变成了一个充满想象力和创造力的乐园。从高低床、上下铺、帐篷床到车床等,不同款式的床让孩子们享受到睡眠的乐趣,同时也能够满足他们的玩耍需求。 ​   安全质量,家长放心   在选择儿童家具时,安全是家长们最关心的问题。Lifetime儿童床严格符合欧洲安全标准,采用环保材料制造,确保孩子在使用过程中不受任何危险。结实的结构和可靠的固定装置,让家长们放心地让孩子在床上玩耍,享受安全的睡眠。   舒适体验,促进健康成长   优质的睡眠环境对孩子的成长至关重要。Lifetime儿童床配备舒适的床垫和柔软的床品,确保孩子拥有良好的睡眠质量,促进他们的健康成长。无论是午睡还是夜晚的安眠,都能让孩子轻松入眠,充满活力地迎接新的一天。 ​   定制服务,满足个性需求   Lifetime儿童床提供个性化定制服务,满足不同家庭的需求。无论是房间大小还是孩子的喜好,都可以找到最合适的床款。家长们可以根据自己的喜好和房间布局选择颜色、尺寸和配件,打造独一无二的梦幻儿童房。   结语   在孩子的成长过程中,一个舒适、安全、具有创意的睡眠空间对于他们的身心健康至关重要。丹麦进口儿童家具品牌Lifetime以其优质的产品和贴心的服务,成为无数家庭的首选。让我们携手为孩子打造一个充满童趣和温馨的梦幻儿童房,陪伴他们健康快乐地成长!​​​",
                "translateContent": null,
                "publishTime": "2024-07-26 17:00:00",
                "crawlerTime": null,
                "url": "https://weibo.com/ttarticle/p/show?id=2309405060552522334678",
                "picturesArray": [
                    "https://wx1.sinaimg.cn/large/82713679ly1hqdam4fjewj21000u043e.jpg",
                    "https://wx2.sinaimg.cn/large/82713679ly1hpwa4w1o3tj21000u0aee.jpg",
                    "https://wx3.sinaimg.cn/large/82713679ly1hqdalzaav9j21900u00xn.jpg"
                ],
                "videosArray": null,
                "videosCoverArray": null,
                "mediaType": null,
                "likeCount": 0,
                "commentCount": 0,
                "readCount": null,
                "playCount": null,
                "rttCount": 0,
                "articleType": null,
                "ipAreaName": null,
                "ipAreaCountry": null,
                "ipAreaProvince": null,
                "rootPublishTime": null,
                "articleId": null,
                "rootArticleId": null,
                "parentArticleId": null,
                "rootArticleUrl": null,
                "uuid": "e1dab3199f57e88e91d1fb43a0f8deaa",
                "timeDelay": null,
                "videoRurationSecond": null,
                "original": null,
                "barrageCount": null,
                "coinCount": null,
                "collectCount": null,
                "topicTags": null,
                "signInCountry": null,
                "signInProvince": null,
                "signInCity": null,
                "signInCounty": null,
                "signInAddress": null,
                "positionName": null,
                "audioUrl": null,
                "insertTime": "2024-07-26 17:22:54",
                "updateTime": "2024-07-31 06:01:39",
                "esInsertTime": "2024-07-31 06:01:39",
                "gid": null,
                "user": null,
                "analysis": {
                    "polarity": null,
                    "fingerprint": null,
                    "entityAddress": null,
                    "entityOrganization": null,
                    "entityPeople": null,
                    "entityGoods": null,
                    "entityAccount": null,
                    "entityBlockchainAddress": null,
                    "analyzeLocation": null,
                    "keywords": null,
                    "language": "1",
                    "isSensitive": "0",
                    "firstPublication": "0",
                    "geoLocation": null,
                    "personPremark": null,
                    "emotion": null,
                    "keyPhrase": null,
                    "heatNumber": null,
                    "sensitiveWords": null,
                    "sensitiveScore": null,
                    "sensitiveClassification": null,
                    "lifeSatisfactionScore": null,
                    "socialPositionSatisfactionScore": null,
                    "socialRiskSatisfactionScore": null,
                    "governmentSatisfactionScore": null,
                    "economicSatisfactionScore": null,
                    "categoriesRelatedToChina": null,
                    "stanceRelatedToChina": null,
                    "openness": null,
                    "neuroticism": null,
                    "agreeableness": null,
                    "extraversion": null,
                    "conscientiousness": null
                },
                "platform": {
                    "dataSourceType": "NEWSWEBSITE",
                    "websiteName": "新浪微博",
                    "host": "weibo.com",
                    "subHost": "weibo.com",
                    "country": "中国",
                    "isForeignMedia": 1,
                    "isSensitiveSite": null,
                    "mediaAreaCity": "",
                    "mediaAreaCounty": "",
                    "mediaAreaProvince": "",
                    "channelUrl": null,
                    "channelName": "",
                    "mediaTags": null
                },
                "taskId": 598,
                "departId": 200,
                "assigner": 101,
                "annotationTag": [
                    {
                        "id": 3,
                        "type": "通用标签",
                        "tag": "提及人物",
                        "inputBox": 1,
                        "dicInfoList": null,
                        "value": [
                            "床等,不同款式的床让"
                        ]
                    },
                    {
                        "id": 5,
                        "type": "通用标签",
                        "tag": "提及组织",
                        "inputBox": 1,
                        "dicInfoList": null,
                        "value": []
                    },
                    {
                        "id": 8,
                        "type": "通用标签",
                        "tag": "提及地点",
                        "inputBox": 1,
                        "dicInfoList": null,
                        "value": []
                    },
                    {
                        "id": 10,
                        "type": "通用标签",
                        "tag": "关键词",
                        "inputBox": 1,
                        "dicInfoList": null,
                        "value": []
                    },
                    {
                        "id": 11,
                        "type": "通用标签",
                        "tag": "情感类别",
                        "inputBox": 2,
                        "dicInfoList": [
                            {
                                "id": 47,
                                "tagLibraryId": 11,
                                "dicValue": "正面"
                            },
                            {
                                "id": 48,
                                "tagLibraryId": 11,
                                "dicValue": "中立"
                            },
                            {
                                "id": 49,
                                "tagLibraryId": 11,
                                "dicValue": "负面"
                            }
                        ],
                        "value": []
                    },
                    {
                        "id": 12,
                        "type": "通用标签",
                        "tag": "敏感词",
                        "inputBox": 1,
                        "dicInfoList": null,
                        "value": []
                    },
                    {
                        "id": 25,
                        "type": "通用标签",
                        "tag": " 提及人物",
                        "inputBox": 1,
                        "dicInfoList": null,
                        "value": []
                    }
                ],
                "dataStatus": 0,
                "annotationTagResult": null,
                "seeAnnotation": null,
                "rejectReason": null,
                "rejectStatus": null
            }
        ],
      loading: false,
      page: 1,
      progress: {},
      rejectText: '',
      rejectStatus: null
    }
  },
  created() {
    // this.searchAnnotationList(this.$route.query.id)
  },
  methods: {
    // 选中文章添加标签
    EiditRowFun (val, tag) {
      this.dataList[this.page - 1].annotationTag[this.findIndexesWithSameProperty(this.dataList[this.page - 1].annotationTag, 'tag', tag)].value.push(val)
    },
    findIndexesWithSameProperty(arr, propName, propValue) {
      let index;
      for (let i = 0; i < arr.length; i++) {
        if (arr[i][propName] === propValue) {
          index = i;
        }
      }
      return index;
    },
    pageNo(val) {
      this.page = val
      this.rejectText = this.dataList[this.page - 1].rejectReason || ''
      this.rejectStatus = this.dataList[this.page - 1].rejectStatus || null
    },
    // searchAnnotationList(id) {
    //   this.loading = true
    //   // 获取标注列表
    //   if (this.$route.query.type) {
    //     searchAnnotationListByAssigner(id).then(res => {
    //       this.dataList = res.data.data
    //       this.loading = false
    //       this.getPress()
    //     })
    //   } else {
    //     searchAnnotationList(id).then(res => {
    //       this.dataList = res.data.data
    //       this.rejectText = this.dataList[0].rejectReason
    //       this.rejectStatus = this.dataList[0].rejectStatus
    //       this.loading = false
    //       this.getPress()
    //     })
    //   }

    // },
    // 获取当前标注员的进度
    // getPress () {
    //   selectTaskSubInfo({ taskId: Number(this.$route.query.id) }).then(res => {
    //     this.progress = {
    //       id: res.data.id,
    //       taskId: res.data.taskId,
    //       markProgress: res.data.markProgress,
    //       markTotal: res.data.markTotal,
    //       total: res.data.total ? res.data.total : this.dataList.length,
    //     }
    //   })
    // },
    // 删除标签
    deleteATap(index, index2) {
      this.dataList[this.page - 1].annotationTag[index].value.splice(index2, 1);
      this.$refs.articleContentAnnotation.renderingKeywords(true)
    },
    // 上面的确定按钮
    completeData () {
      var data = this.dataList[this.page - 1]
      var params = {
        index: data.index,
        documentId: data.documentId,
        uuid: data.uuid,
        taskId: data.taskId,
        taskSubId: data.taskId,
        departId: data.departId,
        annotationTag: data.annotationTag,
        subTaskTotal: this.dataList.length,
        rejectReason: data.rejectReason || ''
      }
      this.btnLoading = true
      manpowerAnnotation(params).then(res => {
        this.$message({
          message: '标注成功',
          type: 'success'
        })
        this.dataList[this.page - 1].manual = true
        if (this.page < this.dataList.length) {
          this.page += 1
          this.rejectText = this.dataList[this.page - 1].rejectReason
          this.rejectStatus = this.dataList[this.page - 1].rejectStatus
        }
        this.getPress()
        this.btnLoading = false
      })
    },
    // 进度为100% 才能点击提交-任务完成按钮
    finshData () {
      if (this.progress.markProgress === 100) {
        this.subLoading = true
        saveAnnotationTask({ taskId: this.progress.taskId, taskSubId: this.progress.id }).then(res => {
          this.$message({
            message: '操作成功',
            type: 'success'
          })
          this.subLoading = false
          this.$router.push({ path: '/businessKnowledgeGraph/taskManagement' })
        })
      } else {
        this.$message({
          message: '请完成标注',
          type: 'warning'
        })
      }

    }
  }
}
</script>

<style lang="scss" scoped>
.details-label {
  height: calc(100vh - 60px);
  background-color: #f5f8ff;
  .head {
    padding: 0 16px;
    height: 56px;
    background-color: #fff;
    border-bottom: 1px solid #F2F3F5;
    .return {
      display: inline-block;
      line-height: 56px;
      font-size: 16px;
      cursor: pointer;
      color: #1D2129;
      &::after {
        content: '';
        width: 1px;
        position: relative;
        top: 4px;
        height: 16px;
        display: inline-block;
        background: #E5E6EB;
      }
    }
    .name {
      display: inline-block;
      line-height: 56px;
      font-size: 16px;
      position: relative;
      top: 1px;
      margin-left: 5px;
      color: #1D2129;
    }
    .schedule {
      position: relative;
      top: 1px;
      margin-left: 24px;
      display: inline-block;
      font-size: 16px;
      color: #86909C;
      line-height: 56px;
    }
    .btn {
      float: right;
      padding: 8px 16px;
      margin-top: 10px;
    }
  }
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值