WishMeLz

生活其实很有趣

公式编辑器

自定义公式编辑器

在线预览地址

2024-06-21T08:26:08.png

<template>
  <div class="home">
    <div class="calc-main">
      <div
        class="formulaView"
        id="formulaView"
        ref="formulaView"
        @click.stop="recordPosition()"
      >
        <div
          class="content-item"
          v-for="(item, index) in formulaList"
          :key="index"
          @click.stop="recordPosition(index)"
        >
          <div class="num" v-if="item.source == EType.num">
            &zwj;{{ item.label }}
          </div>
          <div class="sour" v-if="item.source == EType.sources">
            #&zwj;{{ item.label }}#
          </div>
          <div class="oper" v-if="item.source == EType.operator">
            &zwj;{{ item.label }}
          </div>

          <div class="starttaking" v-if="item.type == 'placeholder'"></div>
          <!--光标-->
          <div class="cursor" v-if="item.cursor"></div>
        </div>
      </div>

      <!-- <span class="clearTxt" >清空</span> -->
    </div>

    <div class="operator">
      <div class="info">
        <div
          class="item"
          v-for="(v, i) in operator"
          :key="i"
          @click="addOperator(v)"
        >
          {{ v.label }}
        </div>
      </div>
    </div>

    <div class="sources">
      <div class="info">
        <div
          class="item"
          v-for="(v, i) in sources"
          :key="i"
          @click="addSources(v)"
        >
          {{ v.label }}
        </div>
      </div>
    </div>

    {{ formulaList }}
  </div>
</template>

<script>
// 光标占位
const PLHSTARTTAKING = {
  cursor: true,
  type: "placeholder",
  value: "",
};
const EType = {
  operator: 1, //运算符
  sources: 2, // 数据源
  num: 3, // 数字
  enter: 4, // 回车
};
export default {
  name: "HomeView",
  data() {
    return {
      EType,
      formulaList: [
        { cursor: false, type: "placeholder", value: "" },
        { label: "基本工资", value: "id123456", source: 2, cursor: false },
        { label: "+", value: "+", source: 1, cursor: false },
        { label: "(", value: "(", source: 1, cursor: false },
        { label: "出勤天数", value: "id198541", source: 2, cursor: false },
        { label: "*", value: "*", source: 1, cursor: false },
        { label: "4", value: "4", source: 3, cursor: false },
        { label: "5", value: "5", source: 3, cursor: false },
        { label: ")", value: ")", source: 1, cursor: false },
      ],
      operator: [
        { label: "+", value: "+" },
        { label: "-", value: "-" },
        { label: "*", value: "*" },
        { label: "/", value: "/" },
        { label: "(", value: "(" },
        { label: ")", value: ")" },
      ],
      sources: [
        { label: "基本工资", value: "id123456" },
        { label: "出勤天数", value: "id198541" },
      ],
    };
  },
  created() {
    // this.formulaList = [PLHSTARTTAKING];
  },
  mounted() {
    document.addEventListener("keydown", this.keydown, false);
    document.addEventListener("click", this.formulaBlur);
  },
  destroyed() {
    //移除监听事件
    document.removeEventListener("keydown", this.keydown, false);
    document.removeEventListener("click", this.formulaBlur);
  },
  methods: {
    addOperator(item) {
      let data = {
        label: item.label,
        value: item.value,
        source: EType.operator,
      };
      this.addItem(data);
    },
    addSources(item) {
      let data = {
        label: item.label,
        value: item.value,
        source: EType.sources,
      };
      this.addItem(data);
    },
    addItem(data, place = true) {
      if (this.formulaList && this.formulaList.length > 0) {
        const length = this.formulaList.length;
        for (let i = 0; i < length; i++) {
          //查找光标位置 如果光标位置为空 则在最后添加
          if (this.formulaList[i].cursor) {
            this.formulaList.splice(i + 1, 0, data);
            place && this.recordPosition(i + 1);
            break;
          } else if (i === this.formulaList.length - 1) {
            this.formulaList.push(data);
            this.recordPosition(this.formulaList.length - 1);
          }
        }
      } else {
        if (!this.formulaList) {
          this.formulaList = [];
        }
        this.formulaList.push(data);
        this.recordPosition(this.formulaList.length - 1);
      }
    },
    // 点选时记录光标位置
    recordPosition(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 = [PLHSTARTTAKING];
      }
    },

    // 键盘事件
    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 && e.keyCode == 46) {
        //delete键删除光标后面的值
        this.deleteItem("del");
      } else if (e && [110, 190].includes(e.keyCode)) {
        this.addItem({
          label: e.key,
          value: e.key,
          source: EType.num,
        });
      } else if (e && e.keyCode == 13) {
        //enter键
        this.formulaList.push({
          label: "",
          value: "",
          source: EType.enter,
          cursor: true,
        });
        setTimeout(() => {
          this.recordPosition(this.formulaList.length - 1);
        });
      } else {
        document.returnValue = false;
        var tt = /\d+/; //能输入正数
        if (tt.test(e.key)) {
          //输入为数字 插入数字
          this.addItem({
            label: e.key,
            value: e.key,
            source: EType.num,
          });
        }
      }
    },
    //删除
    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 && i > 0) {
          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;
        }
      }
    },
    //失去焦点
    formulaBlur(e) {
      const target = e.target;

      // inpcustomField
      let fdom = document.querySelector(".operator");
      // 点击运算符
      if (fdom.contains(target)) {
        return;
      }
      let fielddom = document.querySelector(".sources");
      // 点击字段列表
      if (fielddom.contains(target)) {
        return;
      }

      this.formulaList = this.formulaList?.map((item) => {
        item.cursor = false;
        return item;
      });
    },
  },
};
</script>

<style scoped lang="less">
* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}
.home {
  width: 900px;
  margin: 0 auto;
  // border: 1px solid #dfdcdc;
}
.operator {
  padding-top: 10px;
  .info {
    display: flex;
    align-items: center;
    gap: 10px;
    .item {
      display: grid;
      place-items: center;
      width: 30px;
      height: 30px;
      border: 1px solid #ccc;
      cursor: pointer;
      user-select: none;
    }
    .item:hover {
      background: #eee;
    }
  }
}
.sources {
  padding-top: 10px;
  .info {
    display: flex;
    align-items: center;
    gap: 10px;
    .item {
      display: grid;
      place-items: center;
      // width: 30px;
      // height: 30px;
      padding: 3px 10px;
      border: 1px solid #ccc;
      cursor: pointer;
      user-select: none;
    }
    .item:hover {
      background: #eee;
    }
  }
}

.formulaView {
  border: 1px solid #ccc;
  padding: 16px;
  width: 100%;
  height: 160px;
  line-height: 1.3;
  font-size: 14px;
  overflow-y: auto;
  box-sizing: border-box;
  .content-item {
    position: relative;
    height: 16px;
    cursor: text;
    user-select: none;
    display: flex;
    align-items: center;
    float: left;
    .cursor {
      height: 20px;
      width: 2px;
      background: #333;
      animation: defaultCursor 1s steps(2) infinite;
      position: absolute;
      right: 0;
    }
    .sour {
      padding: 0 5px;
      margin: 0 1px;
      // background: #f1f1f1;
      border-radius: 3px;
      color: #ff9500;
    }
    .err {
      text-decoration: line-through;
      color: #ff453a;
    }
    .num {
      color: #000;
      background: #fff;
      padding: 0 1px 0 0;
    }
    .oper {
      padding: 0 3px;
    }
    .starttaking {
      width: 5px;
      height: 16px;
    }
    #func {
      color: #006fff;
    }
    #lsxz {
      color: #797171;
    }
  }
}
@keyframes defaultCursor {
  0% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}
#enter {
  content: "";
  display: block;
  clear: both;
}
</style>