自定义公式编辑器
在线预览地址
<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">
‍{{ item.label }}
</div>
<div class="sour" v-if="item.source == EType.sources">
#‍{{ item.label }}#
</div>
<div class="oper" v-if="item.source == EType.operator">
‍{{ 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>