vue实现公式编辑器组件

2023-12-14 17:40:45

1、效果图

2、实现代码?

组件弹框实现 样式自己调整? 公式的数字与汉字元素、符号 建立元素表 动态获取

完整代码(calculate.vue)

<template>
    <div id="formulaPage">
      <div
        ref="formulaView"
        class="formulaView"
        contentEditable="false"
        @click="recordPosition"
        @keyup="editEnter($event)"
        @copy="copy($event)"
        @paste="paste($event)"
      />
      <div class="infomationContent">
        <div class="infomationContent-leftFlexbox">
          <div class="tab">
            <ul style="width: 200px">
              <li
                v-for="(v, i) in Num"
                :key="i"
                class="numberClass"
                :style="{ background: v.backgroundColor }"
                @click="addItem($event, 2, v, false)"
              >
                {{ v.displayValue }}
              </li>
            </ul>
          </div>
          <div class="tab">
            <ul style="width: 500px">
              <li
                v-for="(v, i) in customType"
                :key="i"
                class="typeClass"
                :style="{ background: v.backgroundColor }"
                :class="{ noclick: v.isFlag }"
                @click="addItem($event, 2, v, v.isFlag)"
              >
                {{ v.displayValue }}
              </li>
            </ul>
          </div>
          <div class="tab">
            <ul style="width: 200px">
              <li
                v-for="(v, i) in Symbol"
                :key="i"
                class="symbolClass"
                :style="{ background: v.backgroundColor }"
                @click="addItem($event, 2, v, false)"
              >
                {{ v.displayValue }}
              </li>
            </ul>
          </div>
        </div>
        <div class="infomationContent-rightBtnbox">
          <div class="imgboxinfo" style="margin-bottom: 32px">
            <img src="@/assets/images/rolback.png" alt="" style="width: 40px; height: 40px" @click="deleteHumerText" />
          </div>
          <div class="imgboxinfo">
            <img src="@/assets/images/calSave.png" alt="" style="width: 40px; height: 40px" @click="onSaveformulaEvent" />
          </div>
        </div>
      </div>
   
      <!-- <div class="footerComtent">
        <el-button type="primary" style="width: 200px; height: 30px" @click="onSaveformulaEvent">保存</el-button>
      </div> -->
      <!-- <button @click="parsingFormula('(长+宽+高)*2')">反向解析公式</button> -->
      <!-- <button @click="deleteHumerText()">删除</button> -->
    </div>
  </template>
   
  <script>
   
  export default {
    name: 'FormulaPage',
    props: {
      content: { type: String, default: '' },
      recoverdate: {
        type: Array,
        default: () => []
      },
      domflag: {
        type: String,
        default: ''
      }
    },
    data() {
      return {
        // 权限分类 元素分类不可用集合
        btnsRuleArray: {
          offer: ['thickness', 'nailmouth', 'bellsocket', 'gramweight'], // 报价
          weight: ['thickness', 'unitprice'], // 重量
          areavolume: ['gramweight', 'unitprice'] // 体积、面积、纸宽、纸长
        },
        // 公式字符串
        formulaStr: '',
        // 公式编辑器最后光标位置
        formulaLastRange: null,
        Num: [{
          "displayValue":"1",
          "saveValue":"1"
        },{
          "displayValue":"2",
          "saveValue":"2"
        },{
          "displayValue":"3",
          "saveValue":"3"
        },{
          "displayValue":"4",
          "saveValue":"4"
        },{
          "displayValue":"5",
          "saveValue":"5"
        },{
          "displayValue":"6",
          "saveValue":"6"
        },{
          "displayValue":"7",
          "saveValue":"7"
        },{
          "displayValue":"8",
          "saveValue":"8"
        },{
          "displayValue":"9",
          "saveValue":"9"
        },{
          "displayValue":"0",
          "saveValue":"0"
        },{
          "displayValue":".",
          "saveValue":"."
        }],
        Symbol: [
        {
          "displayValue":"+",
          "saveValue":"+"
        },{
          "displayValue":"-",
          "saveValue":"-"
        },{
          "displayValue":"x",
          "saveValue":"*"
        },{
          "displayValue":"÷",
          "saveValue":"/"
        },{
          "displayValue":"(",
          "saveValue":"("
        },{
          "displayValue":")",
          "saveValue":")"
        },{
          "displayValue":"{",
          "saveValue":"{"
        },{
          "displayValue":"}",
          "saveValue":"}"
        },
        ],
        customType: [{
          "displayValue":"上报产量",
          "saveValue":"report_production"
        },{
          "displayValue":"实际产量",
          "saveValue":"reality_production"
        },{
          "displayValue":"红矿",
          "saveValue":"red_ore"
        },{
          "displayValue":"青堆",
          "saveValue":"black_stack"
        },{
          "displayValue":"红堆",
          "saveValue":"red_stack"
        },{
          "displayValue":"煤存",
          "saveValue":"coal_storage"
        },{
          "displayValue":"槽存",
          "saveValue":"slot_storage"
        },{
          "displayValue":"土耗",
          "saveValue":"soil_used"
        },{
          "displayValue":"土存",
          "saveValue":"soil_stock"
        },{
          "displayValue":"煤耗",
          "saveValue":"coal_used"
        },{
          "displayValue":"产量",
          "saveValue":"production"
        },{
          "displayValue":"用电量",
          "saveValue":"meter_used"
        }],
        backFormulaStrArray: [] // 按键值存储编辑器数据
      }
    },
    created() {
      this.backFormulaStrArray = []
   
      if (this.recoverdate) {
        this.backFormulaStrArray = this.recoverdate
        this.$nextTick(function () {
          this.parsingFormulaCustom(this.recoverdate)
        })
      }
   
    //   this.getList()
    },
    methods: {
      /** 查询所有公式元素 */
      getList() {
        listElement({ pageNum: 1, pageSize: 50 }).then(response => {
          response.rows.forEach(item => {
            item.isFlag = false
            if (this.domflag) {
              const newLegth = this.btnsRuleArray[this.domflag].filter(e => e == item.saveValue)
              if (newLegth.length > 0) {
                item.isFlag = true
              }
            }
          })
          const numberDate = response.rows
            .filter(e => {
              return e.elementType == 'number'
            })
            .sort((a, b) => {
              return a.order - b.order
            })
          const formulaElementDate = response.rows
            .filter(e => {
              return e.elementType == 'formulaElement'
            })
            .sort((a, b) => {
              return a.order - b.order
            })
          const symbolDate = response.rows
            .filter(e => {
              return e.elementType == 'symbol'
            })
            .sort((a, b) => {
              return a.order - b.order
            })
          this.Num = numberDate
          this.Symbol = symbolDate
          this.customType = formulaElementDate
        })
      },
      // 删除操作---从最后删除
      deleteHumerText() {
        if (this.backFormulaStrArray.length > 0) {
          this.backFormulaStrArray.splice(-1, 1)
          while (this.$refs.formulaView.firstChild) {
            this.$refs.formulaView.removeChild(this.$refs.formulaView.firstChild)
          }
          this.backFormulaStrArray.forEach(item => {
            const fd = document.createDocumentFragment()
            const empty = document.createTextNode(' ')
            const formulaEl = document.createTextNode(' ' + item.displayValue + ' ')
            fd.appendChild(empty)
            fd.appendChild(formulaEl)
            fd.appendChild(empty)
            this.$refs.formulaView.appendChild(fd)
   
            // 创建新的光标对象
            var range = document.createRange()
            // 光标对象的范围界定
            range.selectNodeContents(formulaEl)
            // 光标位置定位
            range.setStart(formulaEl, formulaEl.data.length - 1)
   
            // 使光标开始和光标结束重叠
            range.collapse(true)
            // 清除选定对象的所有光标对象
            window.getSelection().removeAllRanges()
            // 插入新的光标对象
            window.getSelection().addRange(range)
   
            // 保存新光标
            this.recordPosition()
          })
        }
        
      },
      // 保存数据 回调父组件
      onSaveformulaEvent() {
        const strParams = this.backFormulaStrArray.map(e => e.saveValue).join('')
        const strParamsValue = this.backFormulaStrArray.map(e => e.displayValue).join('')
   
        const newbackFormulaStrArray = []
        this.backFormulaStrArray.forEach(item => {
          const obj = {
            displayValue: item.displayValue,
            elementType: item.elementType,
            saveValue: item.saveValue
          }
   
          newbackFormulaStrArray.push(obj)
        })
   
        this.parsingFormula('')
        this.$emit('onChangeSuccess', strParams, strParamsValue, newbackFormulaStrArray)
        this.backFormulaStrArray = []
        console.log(strParams)
        console.log(strParamsValue)
        console.log(newbackFormulaStrArray)
        // const ifLegal1 = [...strParamsValue].reduce((a, i) => (i === '(' ? a + 1 : a - 1), 0)
      },
      isValidate(str) {
        const inArr = []
   
        const arr = str.split('')
   
        for (const s of arr) {
          if (s === '{' || s === '[' || s === '(') {
            // 入栈
            inArr.push(s)
          }
          if (s === '}' || s === ']' || s === ')') {
            let temp
            switch (s) {
              case '}':
                temp = '{'
                break
              case ']':
                temp = '['
                break
              case ')':
                temp = '('
                break
            }
            // 出栈
            const out = inArr.pop()
   
            if (temp !== out) {
              return false
            }
          }
        }
        return true
      },
      // 获取公式
      getFormula: function () {
        var nodes = this.$refs.formulaView.childNodes
        var str = ''
        for (let i = 0; i < nodes.length; i++) {
          var el = nodes[i]
          if (el.nodeName == 'SPAN') {
            // console.log(el);
            str += '#' + el.innerHTML.trim() + '#'
          } else {
            // console.log(el.data);
            str += el.data ? el.data.trim() : ''
          }
        }
        // console.log(str);
        this.formulaStr = str
      },
      // 点选时记录光标位置
      recordPosition: function () {
        // 保存最后光标点
        this.formulaLastRange = window.getSelection().getRangeAt(0)
      },
      // 添加字段 type 1 字段  2 公式
      addItem: function (e, type, itemRows, isusable) {
        if (isusable) {
          e.preventDefault()
          return false
        }
        if (this.backFormulaStrArray.length > 0) {
          if (itemRows.elementType == 'formulaElement' || itemRows.elementType == 'symbol') {
            // 检验连续相同两个元素不能重复 符号/元素
            if (this.backFormulaStrArray[this.backFormulaStrArray.length - 1].displayValue == itemRows.displayValue) {
              e.preventDefault()
              return false
            }
            // 检验不同元素连续两个不能是用一个类型
            if (
              this.backFormulaStrArray[this.backFormulaStrArray.length - 1].elementType == 'formulaElement' &&
              itemRows.elementType == 'formulaElement'
            ) {
              e.preventDefault()
              return false
            }
   
            if (
              this.backFormulaStrArray[this.backFormulaStrArray.length - 1].elementType == 'number' &&
              itemRows.elementType == 'formulaElement'
            ) {
              e.preventDefault()
              return false
            }
          }
          if (itemRows.elementType == 'number' && itemRows.saveValue == '.') {
            if (this.backFormulaStrArray[this.backFormulaStrArray.length - 1].displayValue == itemRows.displayValue) {
              e.preventDefault()
              return false
            }
          }
   
          //  this.backFormulaStrArray.forEach(item => {
          //         console.log(item.displayValue,itemRows.displayValue)
          // const reg = RegExp(`(${item.displayValue})\1`, 'g'))
          // console.log(strParamsValue.match(reg))
          // })
        }
   
        this.backFormulaStrArray.push(itemRows)
   
        const isValiStr = this.backFormulaStrArray.map(e => e.displayValue).join('')
        if (!this.isValidate(isValiStr)) {
          this.backFormulaStrArray.splice(-1, 1)
          e.preventDefault()
          return false
        }
   
        // 当前元素所有子节点
        var nodes = this.$refs.formulaView.childNodes
        // 当前子元素偏移量
        var offset = this.formulaLastRange && this.formulaLastRange.startOffset
        // 当前光标后的元素
        var nextEl = this.formulaLastRange && this.formulaLastRange.endContainer
   
        // 创建节点片段
        var fd = document.createDocumentFragment()
        // 创建字段节点  空白间隔节点  公式节点
        var spanEl = document.createElement('span')
        spanEl.setAttribute('contentEditable', false)
        // 标识为新添加元素 用于定位光标
        spanEl.setAttribute('new-el', true)
        spanEl.innerHTML = e.target.innerHTML
        var empty = document.createTextNode(' ')
        var formulaEl = document.createTextNode(' ' + e.target.innerHTML + ' ')
   
        // 区分文本节点替换 还是父节点插入
        if (nextEl && nextEl.className != 'formulaView') {
          // 获取文本节点内容
          var content = nextEl.data
          // 添加前段文本
          fd.appendChild(document.createTextNode(content.substr(0, offset) + ' '))
          fd.appendChild(type == 1 ? spanEl : formulaEl)
          // 添加后段文本
          fd.appendChild(document.createTextNode(' ' + content.substr(offset)))
          // 替换节点
          this.$refs.formulaView.replaceChild(fd, nextEl)
        } else {
          // 添加前段文本
          fd.appendChild(empty)
          fd.appendChild(type == 1 ? spanEl : formulaEl)
          fd.appendChild(empty)
   
          // 如果有偏移元素且不是最后节点  中间插入节点  最后添加节点
          if (nodes.length && nodes.length > offset) {
            this.$refs.formulaView.insertBefore(fd, nextEl && nextEl.className != 'formulaView' ? nextEl : nodes[offset])
          } else {
            this.$refs.formulaView.appendChild(fd)
          }
        }
   
        // 遍历光标偏移数据 删除标志
        var elOffSet = 0
        for (let i = 0; i < nodes.length; i++) {
          const el = nodes[i]
          // console.log(el,el.nodeName == 'SPAN'&&el.getAttribute('new-el'));
          if (el.nodeName == 'SPAN' && el.getAttribute('new-el')) {
            elOffSet = i
            el.removeAttribute('new-el')
          }
        }
   
        // 创建新的光标对象
        var range = document.createRange()
        // 光标对象的范围界定
        range.selectNodeContents(type == 1 ? this.$refs.formulaView : formulaEl)
        // 光标位置定位
        range.setStart(
          type == 1 ? this.$refs.formulaView : formulaEl,
          type == 1 ? elOffSet + 1 : formulaEl.data.length - 1
        )
   
        // 使光标开始和光标结束重叠
        range.collapse(true)
        // 清除选定对象的所有光标对象
        window.getSelection().removeAllRanges()
        // 插入新的光标对象
        window.getSelection().addRange(range)
   
        // 保存新光标
        this.recordPosition()
      },
      // 复制
      copy: function (e) {
        // 选中复制内容
        e.preventDefault()
        //
        var selContent = document.getSelection().toString().split('\n')[0]
        // 替换选中内容
        e.clipboardData.setData('text/plain', selContent)
      },
      // 输入回车
      editEnter: function (e) {
        e.preventDefault()
        if (e.keyCode == 13) {
          // 获取标签内容 并把多个换行替换成1个
          var content = this.$refs.formulaView.innerHTML.replace(/(<div><br><\/div>){2,2}/g, '<div><br></div>')
          // 记录是否第一行回车
          var divCount = this.$refs.formulaView.querySelectorAll('div')
          // var tE = this.$refs.formulaView.querySelect('div');
          // console.log(this.$refs.formulaView.childNodes);
          // console.log(this.$refs.formulaView.querySelectorAll("div"));
          // 获取当前元素内所有子节点
          var childNodes = this.$refs.formulaView.childNodes
          // 记录当前光标子节点位置
          var rangeIndex = 0
          for (let i = 0; i < childNodes.length; i++) {
            var one = childNodes[i]
            if (one.nodeName == 'DIV') {
              rangeIndex = i
            }
          }
          // 如果有替换则进行光标复位
          if (divCount.length >= 1) {
            // 替换回车插入的div标签
            content = content.replace(/<div>|<\/div>/g, function (word) {
              // console.log(word);
              if (word == '<div>') {
                // 如果是第一行不在替换br
                return divCount.length > 1 ? ' ' : ' <br>'
              } else if (word == '</div>') {
                return ' '
              }
            })
            // 更新替换内容,光标复位
            this.$refs.formulaView.innerHTML = content
            // 创建新的光标对象
            var range = document.createRange()
            // 光标对象的范围界定为新建的表情节点
            range.selectNodeContents(this.$refs.formulaView)
            // 光标位置定位在表情节点的最大长度
            range.setStart(this.$refs.formulaView, rangeIndex + (divCount.length > 1 ? 0 : 1))
            // 使光标开始和光标结束重叠
            range.collapse(true)
            // 清除选定对象的所有光标对象
            window.getSelection().removeAllRanges()
            // 插入新的光标对象
            window.getSelection().addRange(range)
          }
        }
        // 保存最后光标点
        this.formulaLastRange = window.getSelection().getRangeAt(0)
      },
      // 获取粘贴事件
      paste: function (e) {
        e.preventDefault()
        // var txt=e.clipboardData.getData();
        // console.log(e, e.clipboardData.getData());
        return ''
      },
      // 公式反向解析
      parsingFormula: function (formulaStr) {
        // 渲染视口
        var view = this.$refs.formulaView
        // 反向解析公式
        var str = formulaStr.replace(/#(.+?)#/g, function (word, $1) {
          // console.log(word,$1);
          return "<span contentEditable='false'>" + $1 + '</span>'
        })
   
        // console.log(str,fd.innerHTML);
        view.innerHTML = str
        // this.$refs.formulaView.appendChild(fd);
   
        // 创建新的光标对象
        var range = document.createRange()
        // 光标对象的范围界定为新建的表情节点
        range.selectNodeContents(view)
        // 光标位置定位在表情节点的最大长度
        range.setStart(view, view.childNodes.length)
   
        // 使光标开始和光标结束重叠
        range.collapse(true)
        // 清除选定对象的所有光标对象
        window.getSelection().removeAllRanges()
        // 插入新的光标对象
        window.getSelection().addRange(range)
   
        // 保存新光标
        this.recordPosition()
      },
      parsingFormulaCustom(arrayDatelist) {
        arrayDatelist.forEach(item => {
          // 当前元素所有子节点
          const nodes = this.$refs.formulaView.childNodes
          // 当前子元素偏移量
          const offset = this.formulaLastRange && this.formulaLastRange.startOffset
          // 当前光标后的元素
          const nextEl = this.formulaLastRange && this.formulaLastRange.endContainer
          const fd = document.createDocumentFragment()
          const empty = document.createTextNode(' ')
          const formulaEl = document.createTextNode(' ' + item.displayValue + ' ')
   
          if (nextEl && nextEl.className != 'formulaView') {
            var content = nextEl.data
            fd.appendChild(document.createTextNode(content.substr(0, offset) + ' '))
            fd.appendChild(formulaEl)
            fd.appendChild(document.createTextNode(' ' + content.substr(offset)))
            this.$refs.formulaView.replaceChild(fd, nextEl)
          } else {
            fd.appendChild(empty)
            fd.appendChild(formulaEl)
            fd.appendChild(empty)
            // 如果有偏移元素且不是最后节点  中间插入节点  最后添加节点
            if (nodes.length && nodes.length > offset) {
              this.$refs.formulaView.insertBefore(
                fd,
                nextEl && nextEl.className != 'formulaView' ? nextEl : nodes[offset]
              )
            } else {
              this.$refs.formulaView.appendChild(fd)
            }
          }
   
          // 创建新的光标对象
          var range = document.createRange()
          // 光标对象的范围界定
          range.selectNodeContents(formulaEl)
          // 光标位置定位
          range.setStart(formulaEl, formulaEl.data.length - 1)
   
          // 使光标开始和光标结束重叠
          range.collapse(true)
          // 清除选定对象的所有光标对象
          window.getSelection().removeAllRanges()
          // 插入新的光标对象
          window.getSelection().addRange(range)
   
          // 保存新光标
          this.recordPosition()
        })
      }
    }
  }
  </script>
   
  <style lang="scss">
  #formulaPage {
    width: 100%;
    height: 100%;
   
    > .formulaView {
      margin-bottom: 20px;
      min-height: 130px;
      width: 100%;
      padding: 5px;
      border: 5px solid rgb(198, 226, 255);
      resize: both;
      overflow: auto;
      line-height: 25px;
      font-size: 20px;
      span {
        user-select: none;
        display: inline-block;
        /* // margin: 0 3px; */
        height: 20px;
        line-height: 20px;
        /* // letter-spacing: 2px; */
        border-radius: 3px;
        white-space: nowrap;
        &:first-child {
          margin-left: 0;
        }
      }
    }
    .footerComtent {
      margin-top: 20px;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    .infomationContent {
      display: flex;
      &-leftFlexbox {
        flex: 1;
        display: flex;
        > .tab {
          flex: 1;
          > ul {
            margin: 0;
            padding: 0;
            display: flex;
            flex-wrap: wrap;
            /* // justify-content: space-between; */
            &:after {
              content: '';
              display: table;
              clear: both;
            }
            > li {
              /* // margin-right: 20px; */
              margin: 5px 10px;
              width: 65px;
              text-align: center;
              float: left;
              padding: 0 10px;
              height: 30px;
              line-height: 30px;
              border-radius: 5px;
              font-weight: bold;
              border: 1px solid #fff;
              list-style-type: none;
              cursor: pointer;
              -moz-user-select: none; /* 火狐 */
              -webkit-user-select: none; /* 谷歌、Safari */
              -ms-user-select: none; /* IE10+ */
              user-select: none;
              user-drag: none;
              box-shadow: 0px 0px 2px 2px rgba(55, 114, 203, 0.2), /*下面深蓝色立体阴影*/ 0px 0px 6px 1px #4379d0,
                /*内部暗色阴影*/ 0 -15px 2px 2px rgba(55, 114, 203, 0.1) inset;
              color: #333333;
            }
   
            > li:hover {
              color: #fff;
              /* // color: #409eff; */
              border-color: #c6e2ff;
              background-color: #ecf5ff;
            }
            > li:active {
              color: #3a8ee6;
              border-color: #3a8ee6;
              outline: none;
            }
            .numberClass {
              width: 40px;
            }
            .typeClass {
              width: 100px
            }
            .symbolClass {
            }
            .noclick {
              cursor: not-allowed !important;
              background: #bcc0c4 !important;
              color: #e4eaf1 !important;
              border: none !important;
            }
          }
        }
      }
      &-rightBtnbox {
        width: 50px;
        padding-top: 10px;
        box-sizing: border-box;
        .imgboxinfo {
          width: 40px;
          height: 40px;
          img {
            cursor: pointer;
          }
        }
      }
    }
  }
  </style>

在父组件中引用即可

文章来源:https://blog.csdn.net/askuld/article/details/134996471
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。