文章目录
- 需求描述
- 技术栈
- 最终效果演示
- 功能实现
- 逻辑拆分
- 代码目录结构
- 实现思路
- 光标实现
- 底部单个符号或字段结构设计
- 监听键盘事件&处理光标
- 公式规则校验
- 总结
需求描述
需要一个弹窗,弹窗内部需要能够进行公式规则的配置并进行公式规则合法性校验。
技术栈
- vue2
- element-ui
最终效果演示
公式规则功能效果演示
功能实现
逻辑拆分
我将弹窗大致拆分成了三部分,具体如下:
代码目录结构
实现思路
光标实现
使用的是div块级元素+css3动画实现,核心代码如下:
// template
<div v-if="item.show" class="expression-blink-cursor" :key="item.id"></div>
// style
.expression-blink-cursor {
width: 1px;
height: 18px;
border-left: 1px solid;
margin: 2px;
animation:cursorImg 1s infinite steps(1, start);
@keyframes cursorImg {
0%, 100% {
opacity: 0;
}
50% {
opacity: 1;
}
}
}
底部单个符号或字段结构设计
实现公式规则的编辑功能主要是通过操作数组实现的,所以对于弹窗底部的符号或字段设计为对象数组,便于后续数组操作。部分代码如下:
// 子组件template
<template>
<div id="singleSymbol">
<template v-for="item in symbolBtnList">
<template v-if="item.code === 'formFields'">
<el-cascader class="custom-cascader-class" v-model="selectedFormFieldsValue" :options="formFieldsOptions" :key="item.id" :placeholder="item.label" @change="selectFormFieldsChange">
<template slot-scope="{ node, data }">
<span>{{ data.label }}</span>
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
</template>
</el-cascader>
</template>
<template v-else>
<el-button class="symbol-btn" :key="item.code" @click="singleSymbolClick(item)">
{{item.label}}
</el-button>
</template>
</template>
</div>
</template>
// js
import {nanoid} from 'nanoid'
export const singleSymbolBtnList = [
{
label: '表单字段',
code: 'formFields',
templateName: 'formFields',
type: 'customTemplate',
id: nanoid()
},
{
label: '+',
code: 'plusSign',
type: 'bottomSingleSign',
id: nanoid()
},
...
]
export const formFieldsOptions = [
{
value: 'testForm1',
label: '测试表单1',
children: [
{
value: 'formFields1',
uuid: '',
label: '表单字段1'
},
{
value: 'formFields2',
uuid: '',
label: '表单字段2'
},
{
value: 'formFields3',
uuid: '',
label: '表单字段3'
},
{
value: 'formFields4',
uuid: '',
label: '表单字段4'
}
]
},
{
value: 'testForm2',
label: '测试表单2',
children: [
{
value: 'formFields1',
uuid: '',
label: '表单字段1'
},
{
value: 'formFields2',
uuid: '',
label: '表单字段2'
},
{
value: 'formFields3',
uuid: '',
label: '表单字段3'
},
{
value: 'formFields4',
uuid: '',
label: '表单字段4'
}
]
}
]
// 父组件template
<template>
<div id="formulaRulesMain" class="formula-rules-main">
<!-- 公式规则配置区域 -->
<template v-for="(item, index) in formulaRulesArray">
<template v-if="item.type === 'customTemplate'">
<template v-if="item.templateName === 'blinkCursor'">
<div v-if="item.show" class="expression-blink-cursor" :key="item.id"></div>
</template>
<template v-if="item.templateName === 'staticValue'">
<el-input type="text" v-focus class="custom-input" :key="item.id" v-model="item.bindValue" @focus="inputFocusOrBlur('focus')" @blur="inputFocusOrBlur('blur')"></el-input>
</template>
<template v-if="item.templateName === 'booleanValue'">
<div class="custom-boolean-value" :key="item.id">{{item.label}}</div>
</template>
<template v-if="item.templateName === 'formFields'">
<div class="custom-form-fields" :key="item.id">{{item.label}}</div>
</template>
<template v-if="item.templateName === 'emptyDiv'">
<div class="custom-form-empty-div" :key="item.id" @click="emptyDivClick(index)"></div>
</template>
</template>
<template v-else>
<template v-if="item.type === 'bottomSingleSign'">
<div :key="item.id">
<span class="expression-item">{{item.label}}</span>
</div>
</template>
</template>
</template>
</div>
</template>
监听键盘事件&处理光标
在mounted挂载键盘监听事件,并对不同按键操作做出不同逻辑处理(对数组进行操作),核心代码如下:
mounted () {
this.keyDown()
},
methods: {
// 监听键盘
keyDown () {
window.onkeyup = (e) => {
// 事件对象兼容
// console.log('查看对象兼容', e, e.keyCode)
let e1 = e || event || window.event
// 键盘按键判断:左箭头-37;上箭头-38;右箭头-39;下箭头-40;删除键-8
// 左
if (e1 && e1.keyCode === 37) {
// 按下左箭头
this.handleKeydownEvent('left')
} else if (e1 && e1.keyCode === 39) {
// 按下右箭头
this.handleKeydownEvent('right')
} else if (e1 && e1.keyCode === 8) {
// 按下删除键
this.handleKeydownEvent('delete')
}
}
},
// 处理键盘事件
handleKeydownEvent (type) {
let arr = this.formulaRulesArray
if (type === 'left') {
if (this.cursorIndex !== 0) arr[this.cursorIndex] = arr.splice(this.cursorIndex - 2, 1, arr[this.cursorIndex])[0];
} else if (type === 'right') {
if (this.cursorIndex !== arr.length - 1) arr[this.cursorIndex] = arr.splice(this.cursorIndex + 2, 1, arr[this.cursorIndex])[0];
} else if (type === 'delete') {
if (this.cursorIndex !== 0) arr.splice(this.cursorIndex - 2, 2)
}
},
}
公式规则校验
通过上述步骤 基本的编辑功能已经实现,只差一步校验就完成所需功能了。
最开始考虑的是通过正则去判断公式规则的合法性,但是整不出来😊 所以换了另外一种思路。采用关键字替换然后使用eval函数执行调用自定义函数看是否能够成功执行。
- 首先可以校验一下左右括号个数是否匹配 比较简单
- 检验完括号后 如果匹配 调用eval函数执行公式规则字符串。
- js函数能够支持多个传参 省参执行,js函数会通过实参(arguments)对象接收,无论调用函数时传递了多少个参数,都会被arguments接收,利用这一点就可以替换完关键字后直接调用自定义函数通过js引擎去帮我们“校验”公式规则的合法性😄。
核心代码如下:
methods: {
// 处理公式规则结构类型
handleFormulaArray (formulaArr) {
let tempArr = Array.from(formulaArr, ({label}) => label)
let finalArr = []
formulaArr.forEach(item => {
let each = item.label
switch (item.label) {
case 'SUM':
each = 'this.sum'
break;
case 'SUB':
each = 'this.sub'
break
case '静态值':
each = item.bindValue
break
case 'TRUE':
each = true
break
case 'FALSE':
each = false
break
case 'IF':
each = 'this.judgeIf'
break
case 'INT1':
each = 'this.int1'
break
case 'INT2':
each = 'this.int2'
break
case 'INT3':
each = 'this.int3'
break
}
if (item.code === 'formFields') {
// TODO 通过uuid获取到表单对应字段的值,item有完整数据(uuid...)
each = 5
}
finalArr.push(each)
})
console.log('查看处理后的数组: ', finalArr, finalArr.join(''))
try {
if (finalArr.length === 0) {
this.$message({
message: '校验失败,公式规则不可为空!',
type: 'warning'
});
return
}
this.showResultVisible = true
this.resltFormulaStr = finalArr.join('')
const str = finalArr.join('')
if (!eval(str)) throw new Error()
this.showResultVisible = true
this.resltFormulaStr = finalArr.join('')
this.resultFormulaValue = eval(str)
// TODO 预留存储公式规则逻辑 包括公式规则完整信息formulaArr及转化后的字符串str、校验成功后进行字段赋值操作及关闭弹窗
// this.dialogVisible = false
} catch (error) {
this.$message({
message: '校验失败,请检查公式规则!',
type: 'warning'
});
this.resultFormulaValue = null
}
}
}
// mixin
export default {
methods: {
// ------------------------------------------- 数学函数 -----------------------------------------------
// 求和
sum () {
try {
if (!arguments[0] || arguments.length < 2) throw new Error()
let result = 0
for (let i = 0; i < arguments.length; i++) {
result = result + arguments[i]
}
return result
} catch (error) {
this.$message({
message: '校验失败,请检查SUM公式规则!',
type: 'warning'
});
}
},
// 相减
sub () {
try {
if (!arguments[0] || arguments.length < 2) throw new Error()
let result = arguments[0]
for (let i = 1; i < arguments.length; i++) {
result = result - arguments[i]
}
return result
} catch (error) {
this.$message({
message: '校验失败,请检查SUB公式规则!',
type: 'warning'
});
}
},
// if判断函数
judgeIf (judgeCode, trueCode, falseCode) {
try {
if (arguments.length !== 3) throw new Error()
return judgeCode ? trueCode : falseCode
} catch (error) {
this.$message({
message: '校验失败,请检查IF公式规则!',
type: 'warning'
});
}
},
// 向下取整
int1 () {
try {
if (!arguments[0]) throw new Error()
return parseInt(arguments[0])
} catch (error) {
this.$message({
message: '校验失败,请检查向下取整函数INT1公式规则!',
type: 'warning'
});
}
},
// 向上取整
int2 () {
try {
if (!arguments[0]) throw new Error()
return parseInt(arguments[0]) + 1
} catch (error) {
this.$message({
message: '校验失败,请检查向上取整函数INT2公式规则!',
type: 'warning'
});
}
},
// 四舍五入取整
int3 () {
try {
if (!arguments[0]) throw new Error()
return Math.round(arguments[0])
} catch (error) {
this.$message({
message: '校验失败,请检查四舍五入取整函数INT3公式规则!',
type: 'warning'
});
}
}
}
}
总结
遇到较难的问题可以变通一下去考虑解决方案(正则太难了😭), 然后一步步去验证,此需求实现代码是我的一个小demo,不完善的地方还有很多,望大佬们能够指正,当然,基本功能是完全OK的。