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

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

话不多说,直接上源码

<template>

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



  <!--

  1、括号前后必须是符号

  2、数字前后面必须是符号

  3、首位是数字或者 - 或者 ()

  4、符号前后必须是数字或者括号 -->



  <!-- 判断1:首位必须是obj或者除了-和() 判断2:除了()两个plain不能挨着

  判断3:两个obj不能挨着 判断4:最后一个字符不能是除了()的符号-->

  <div id="formulaPage">

    <!-- 公式编辑区域 -->

    <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.name"

            :value="item.id"

            :key="item.id"

          ></el-option>

        </el-select> -->

        <el-tag

          :key="tag.name"

          v-for="tag in dataList"

          :disable-transitions="false"

          @click="addItem(tag.id, 'obj')"

        >

          {{ tag.name }}

        </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.name"

            :value="item.id"

            :key="item.id"

          >

          </el-option>

        </el-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 比变更后回传的数据

 * **/



export default {

  name: "",

  props: {

    dataList: {

      type: Array,

      default() {

        return [

          { name: "value1", id: "1" },

          { name: "value2", id: "2" },

          { name: "value3", id: "3" },

        ];

      },

    },

    defaultList: {

      type: Array,

      default() {

        return [];

      },

    },

  },

  data: function () {

    return {

      // 公式字符串

      formulaStr: "",



      dataId: "",

      operatorId: "",

      formulaList: [

        {

          cursor: true,

        },

      ],

      //运算符

      operatorList: [

        {

          name: "+",

          id: "+",

        },

        {

          name: "-",

          id: "-",

        },

        {

          name: "*",

          id: "*",

        },

        {

          name: "/",

          id: "/",

        },

        {

          name: "()",

          id: "()",

        },

      ],

      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: {

    //计算

    count() {

      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.id == val);

        data.value = obj.name;

      } 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.id == that.str[that.currentIndex - 1] ||

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

      //       ) {

      //         currentType = "plain";

      //       }

      //     }

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

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

      //       if (

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

      //         item.id != 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.id == that.str[that.currentIndex - 1] ||

      //         item.id == 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");

        }

      }

    },



    /**

     * 公式转为字符串

     * 格式 [id]符号数字

     * **/

    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="scss">

#formulaPage {

  .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;

      }

    }

  }

}



@keyframes defaultCursor {

  0% {

    opacity: 1;

  }

  100% {

    opacity: 0;

  }

}

</style>

如有侵权,请联系我删除

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DHGT666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值