基于Vue2的简易公式编辑器(自带公式校验)

话不多说,直接上源码

<template>
  <!-- 输入数字后不许直接接字段   输入符号前后不能是符号 -->

  <!--
 
  1、括号前后必须是符号
 
  2、数字前后面必须是符号
 
  3、首位是数字或者 - 或者 ()
 
  4、符号前后必须是数字或者括号 -->

  <!-- 判断1:首位必须是obj或者除了-和() 判断2:除了()两个plain不能挨着
 
  判断3:两个obj不能挨着 判断4:最后一个字符不能是除了()的符号-->

  <div id="formulaPage">
    <!-- 公式编辑区域 -->
    <div class="btn_box">
      <a-button type="primary" style="margin-left:5px;" @click="addgsx">增加公式项</a-button>
      <a-button type="primary" style="margin-left:5px;" @click="formulaVerification()">测试公式值</a-button>
      <!-- <a-button type="primary" style="margin-left:5px;" @click="count(formulaList)">计算</a-button> -->
      <a-button type="danger" style="margin-left:5px;" @click="clearAll()">清除全部</a-button>
    </div>
    <div class="formulaView" id="formulaView" ref="formulaView" @click.stop="recordPosition(currentIndex)">
      <div class="content-item" v-for="(item, index) in formulaList" :key="index" @click.stop="recordPosition(index)">
        <!-- &zwj零宽连接符 -->
        <div class="num" v-if="item.type == 'num'">&zwj;{{ item.value }}</div>
        <div class="plain" v-else-if="item.type == 'plain'">&zwj;{{ item.value }}</div>
        <div class="obj" v-else-if="item.type == 'obj'">&zwj;{{ item.value }}</div>
        <!--光标-->
        <div class="cursor" v-if="item.cursor"></div>
      </div>
    </div>
    <div class="tab mt_10 flex-lr">
      <div class="">
        <!-- <el-select
 
          @change="
            (e) => {
              addItem(e, 'obj');
            }
          "
 
          style="width: 120px"
 
          v-model="dataId"
 
          placeholder="选择字段"
 
        >
 
          <el-option
 
            v-for="item in dataList"
 
            :label="item.label"
 
            :value="item.value"
 
            :key="item.value"
 
          ></el-option>
 
        </el-select> -->

        <!-- <el-tag :key="tag.label" v-for="tag in dataList" :disable-transitions="false" @click="addItem(tag.value, 'obj')">
          {{ tag.label }}
        </el-tag>

        <el-select
          @change="
            e => {
              addItem(e, 'plain')
            }
          "
          v-model="operatorId"
          placeholder="选择数学运算符"
          style="width: 120px"
          class="ml_20"
        >
          <el-option v-for="item in operatorList" :label="item.label" :value="item.value" :key="item.value"> </el-option>
        </el-select> -->
        <div class="count_tag">
          公式项:{{ dataList.length > 0 ? '' : '空' }}
          <a-tag
            :key="tag.value"
            v-for="tag in dataList"
            :disable-transitions="false"
            @click="addItem(tag.value, 'obj')"
          >
            {{ tag.label }}
          </a-tag>
        </div>
        <div class="count_tag">
          数学运算符:
          <a-tag
            :key="tag.value"
            color="blue"
            v-for="tag in operatorList"
            :disable-transitions="false"
            @click="addItem(tag.value, 'plain')"
          >
            {{ tag.label }}
          </a-tag>
        </div>
        <!-- 
        <a-select
          style="width: 120px"
          v-model="operatorId"
          placeholder="选择数学运算符"
          @change="
            e => {
              addItem(e, 'plain')
            }
          "
        >
          <a-select-option v-for="item in operatorList" :label="item.label" :value="item.value" :key="item.value">{{
            item.label
          }}</a-select-option>
        </a-select> -->
      </div>

      <!-- <div class="">
        <span class="mr_10 pointer theme-col" @click="clearAll()"> 清除全部</span>
      </div>

      <div class="">
        <span class="mr_10 pointer theme-col" @click="formulaVerification()"> 公式校验</span>
      </div>

      <div class="">
        <span class="mr_10 pointer theme-col" @click="count(formulaList)"> 计算</span>
      </div> -->
    </div>
  </div>
</template>

<script>
/**
 
 * dataList 需要选择数据的集合
 
 * defaultList 初始值的集合
 
 * @change 比变更后回传的数据
 
 * **/
import _ from 'lodash'
export default {
  name: '',

  props: {
    dataLists: {
      type: Array,

      default() {
        return [
          // { label: '营业务收入', value: 'dz100001' },
          // { label: '主营业务收入1', value: 'dz100002' },
          // { label: '主营业务收入2', value: 'dz100003' }
        ]
      }
    },

    defaultList: {
      type: Array,

      default() {
        return []
      }
    }
  },

  data: function() {
    return {
      // 公式字符串
      dataList: [],
      formulaStr: '',
      dataId: '',
      operatorId: '',
      formulaList: [
        {
          cursor: true
        }
      ],
      //运算符

      operatorList: [
        {
          label: '+',
          value: '+'
        },
        {
          label: '-',
          value: '-'
        },

        {
          label: '*',
          value: '*'
        },
        {
          label: '/',
          value: '/'
        },
        {
          label: '()',
          value: '()'
        }
      ],
      str: '',
      //当前光标所在位置
      currentIndex: 0,
      //校验标识
      verificationFlag: false
    }
  },
  watch: {
    // formulaList(val) {
    //   //将输入的值转化为字符串
    //   let editStr = val
    //     .map((item) => {
    //       return item.key;
    //     })
    //     .join("");
    //   this.str = editStr;
    //   let num = eval(editStr);
    //   console.log(num.toFixed(2));
    //   this.$emit("change", editStr);
    // },
  },

  created() {
    //监听鼠标事件
    this.$nextTick(function() {
      document.addEventListener('keydown', this.keydown, false)

      document.addEventListener('click', this.formulaBlur)
    })
  },
  destroyed() {
    //移除监听事件
    document.removeEventListener('keydown', this.keydown, false)
    document.removeEventListener('click', this.formulaBlur)
  },

  methods: {
    //添加公式项
    addgsx() {
      this.dataList = _.cloneDeep(this.dataLists || [])
      this.clearAll()
    },
    //计算(由于选项中key不一定是数字,所以注释掉)
    count(val) {
      if (!this.verificationFlag) {
        this.$message.error('请先通过公式校验')
        return
      } else {
        let editStr = val
          .map(item => {
            return item.key
          })
          .join('')
        this.str = editStr
        let num = eval(editStr)
        console.log(num.toFixed(2))
        this.$emit('change', editStr)
      }
    },
    //公式校验
    formulaVerification() {
      console.log(this.formulaList)
      //数组长度为0不校验
      if (this.formulaList.length == 1) {
        return
      } else if (this.formulaList.length == 2) {
        //数组长度为1默认校验不通过
        this.$message.error('请正确编辑公式')
        return
      }
      let list = this.formulaList
      for (var i = 0; i < list.length - 1; i++) {
        if (list[i].type != 'num') {
          if (list[i].type == list[i + 1].type) {
            if (list[i].type == 'plain' && list[i + 1].value == '(') {
              // 忽略符号+(的情况
            } else if (list[i].value == '(' && list[i + 1].value == ')') {
              //两半括号之间没有字段
              this.verificationFlag = false
              this.$message.error('请正确编辑公式6')
              return
            } else if (list[i].value == ')' && list[i + 1].value == '(') {
              //两个括号之间没有其他符号
              this.verificationFlag = false
              this.$message.error('请正确编辑公式7')
              return
            } else {
              this.verificationFlag = false
              this.$message.error('请正确编辑公式1')
              return
            }
          }
        }
        //最后一个字符不能为除)之外的符号

        if (list[list.length - 1].type == 'plain') {
          if (list[list.length - 1].value != ')') {
            this.verificationFlag = false

            this.$message.error('请正确编辑公式5')

            return
          }
        }

        //手输数字 + 选择字段

        if (list[i].type == 'num') {
          if (list[i + 1].type == 'obj') {
            this.verificationFlag = false

            this.$message.error('请正确编辑公式2')

            return
          }
        }

        //选择字段 + 手输数字
        if (list[i].type == 'obj') {
          if (list[i + 1].type == 'num') {
            this.verificationFlag = false

            this.$message.error('请正确编辑公式3')

            return
          }
        }
      }

      this.$message.success('验证通过可以计算')

      this.verificationFlag = true
    },

    // 获取

    getFormula: function() {},

    // 点选时记录光标位置

    recordPosition(index) {
      this.currentIndex = index

      if (this.formulaList && this.formulaList.length > 0) {
        this.formulaList = this.formulaList.map((item, itemIndex) => {
          item.cursor = false

          if (index > -1 && index == itemIndex) {
            item.cursor = true
          } else if (index !== 0 && !index && itemIndex == this.formulaList.length - 1) {
            item.cursor = true
          }

          return item
        })
      } else {
        this.formulaList = [
          {
            cursor: true,

            type: 'placeholder',

            value: ''
          }
        ]
      }

      // this.$forceUpdate();
    },

    //失去焦点

    formulaBlur(e) {
      this.formulaList = this.formulaList.map((item, index) => {
        //光标聚焦在字符串最后面

        if (index == this.formulaList.length) {
          item.cursor = true
        }

        return item
      })
    },

    /**
 
     * @returns {addItem<*, void, *>}
 
     * 添加字段
 
     * type obj 字段  num 数字 plain符号
 
     * place 是否修改光标位置
 
     */

    addItem: function(val, type, place = true) {
      if (!val) return false

      this.verificationFlag = false

      let that = this

      let addFlag = true

      //插入括号

      if (type == 'plain' && val == '()') {
        val = '('

        setTimeout(function() {
          that.addItem(')', type, false)
        }, 50)
      }

      let obj = {},
        data = {
          value: '',

          key: val,

          type: type
        }

      if (type == 'obj') {
        //获取数据 为 value 赋值

        obj = this.dataList.find(item => item.value == val)

        data.value = obj.label
      } else {
        data.value = val
      }

      console.log(data, that.str)

      console.log(that.currentIndex)

      //增加公式验证

      // if (type == "plain") {

      //   // 判断首位不为符号

      //   if (that.str == "") {

      //     if (val != "-" && val != ")" && val != "(") {

      //       that.operatorId = "";

      //       return;

      //     }

      //   }

      //   let currentType = "";

      //   //判断符号前后不能为符号  除了括号

      //   that.operatorList.forEach((item) => {

      //     if (data.value != "(" && data.value != ")") {

      //       if (

      //         item.value == that.str[that.currentIndex - 1] ||

      //         item.value == that.str[that.currentIndex]

      //       ) {

      //         currentType = "plain";

      //       }

      //     }

      //     //括号前面必须是除括号外的符号

      //     if (data.value == ")" || data.value == "(") {

      //       if (

      //         item.value != that.str[that.currentIndex - 1] &&

      //         item.value != that.str[that.currentIndex]

      //       ) {

      //         currentType = "plain";

      //       }

      //     }

      //   });

      //   if (currentType == type) {

      //     addFlag = false;

      //     that.$message.error("请正确编辑公式");

      //     that.operatorId = "";

      //     return;

      //   }

      // }

      // if (type == "obj") {

      //   if (that.str != "") {

      //     //判断字段前后不能为字段

      //     let currentType = "";

      //     that.dataList.forEach((item) => {

      //       if (

      //         item.value == that.str[that.currentIndex - 1] ||

      //         item.value == that.str[that.currentIndex]

      //       ) {

      //         currentType = "obj";

      //       }

      //     });

      //     if (currentType == type) {

      //       addFlag = false;

      //       that.$message.error("请正确编辑公式");

      //       that.dataId = "";

      //       return;

      //     }

      //   }

      // }

      if (addFlag) {
        if (that.formulaList && that.formulaList.length > 0) {
          const length = that.formulaList.length

          for (let i = 0; i < length; i++) {
            //查找光标位置 如果光标位置为空 则在最后添加

            if (that.formulaList[i].cursor) {
              that.formulaList.splice(i + 1, 0, data)

              place && that.recordPosition(i + 1)

              break
            } else if (i === that.formulaList.length - 1) {
              that.formulaList.push(data)

              that.recordPosition(that.formulaList.length)
            }
          }
        } else {
          if (!that.formulaList) {
            that.formulaList = []
          }

          that.formulaList.push(data)

          that.recordPosition(that.formulaList.length)
        }
      }
    },

    //清除全部

    clearAll() {
      this.formulaList = []

      let that = this

      setTimeout(function() {
        that.recordPosition()
      }, 100)
    },

    //删除

    deleteItem(type) {
      let arr = JSON.parse(JSON.stringify(this.formulaList)),
        index = null

      const length = arr.length

      for (let i = 0; i < length; i++) {
        if (arr[i].cursor && arr[i].key) {
          index = i

          if (type == 'del') {
            index = i + 1
          }

          if (index > -1) {
            this.formulaList.splice(index, 1)

            if (type == 'del') {
            } else {
              this.recordPosition(index - 1)
            }
          }

          break
        }
      }
    },

    // 键盘输入

    keydown(e) {
      //禁止输入

      // 检测光标是否存在

      let index,
        cursorData = this.formulaList.find((item, itemIndex) => {
          if (item.cursor) {
            index = itemIndex
          }

          return item.cursor
        })

      if (!cursorData) {
        return false
      }

      //左右移动键控制光标位置

      if (e && [37, 39].includes(e.keyCode)) {
        if (e.keyCode == 37) {
          index = index - 1
        } else {
          index = index + 1
        }

        if (index > -1 && index < this.formulaList.length) {
          this.recordPosition(index)
        }
      } else if (e && e.keyCode == 8) {
        //Backspace 键 删除前面的值

        this.deleteItem()
      } else if (e && [107, 109, 106, 111].includes(e.keyCode)) {
        //运算符列表

        this.addItem(e.key, 'plain')
      } else if (e && e.shiftKey && [48, 57].includes(e.keyCode)) {
        //括号

        if (e.keyCode == 48) e.key = ')'

        if (e.keyCode == 57) e.key = '('

        this.addItem(e.key, 'plain')
      } else if (e && e.keyCode == 46) {
        //delete键删除光标后面的值

        this.deleteItem('del')
      } else {
        document.returnValue = false

        var tt = /^([1-9]{1}[0-9]{0,7})$/ //能输入正数

        if (tt.test(e.key)) {
          //输入为数字 插入数字

          this.addItem(e.key, 'num')
        }
      }
    },

    /**
 
     * 公式转为字符串
 
     * 格式 [value]符号数字
 
     * **/

    parsingFormula: function(formulaStr) {
      let str = '',
        arr = []

      arr = this.formulaList.map(item => {
        let val = item.key

        if (val) {
          if (item.type == 'obj') {
            val = '[' + val + ']'
          }

          str = str + val
        }

        return val
      })

      return str
    },

    /**
 
     * 格式效验
 
     * */

    formatValidation() {
      let objData = null

      let arr = this.formulaList.filter(item => {
          if (item.type == 'obj') {
            objData = item
          }

          return item.key
        }),
        data = { type: true, mag: '' }

      if (!objData) {
        data.mag = '至少添加一个指标'
      } else {
        for (let i = 0; i < arr.length; i++) {
          if (i < arr.length - 1) {
            //判断当前类型

            if (arr[i].type == 'obj' && arr[i + 1].type == 'plain') {
              //类型为obj时 后一个 需以 符号结尾

              data.mag = '指标后缀'
            }
          }
        }
      }

      if (data.mag) {
        data.type = false
      }

      return data
    }
  }
}
</script>

<style lang="less">
#formulaPage {
  .btn_box {
    padding: 5px;
  }
  .formulaView {
    padding: 3px 4px;

    width: 100%;

    height: 120px;

    border: 1px solid #eee;

    line-height: 1.3;

    font-size: 12px;

    overflow-y: scroll;

    .content-item {
      position: relative;

      height: 16px;

      cursor: text;

      user-select: none;

      display: flex;

      align-items: center;

      float: left;

      .cursor {
        height: 13px;

        width: 1px;

        background: #333;

        animation: defaultCursor 1s steps(2) infinite;

        position: absolute;

        right: 0;
      }

      .obj {
        padding: 0 5px;

        margin: 0 1px;

        background: #f1f1f1;

        border-radius: 3px;
      }

      .num {
        color: #000;

        background: #fff;

        padding: 0 1px 0 0;
      }
    }
  }
  .count_tag {
    margin-top: 10px;
  }
}

@keyframes defaultCursor {
  0% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}
</style>

 引入formulaPage组件后可直接使用<formulaPage :dataList="dataList"></formulaPage>

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值