基于Vue2的简易公式编辑器(自带公式校验)
vue
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
项目地址:https://gitcode.com/gh_mirrors/vu/vue
免费下载资源
·
话不多说,直接上源码
<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'">‍{{ item.value }}</div>
<div class="plain" v-else-if="item.type == 'plain'">‍{{ item.value }}</div>
<div class="obj" v-else-if="item.type == 'obj'">‍{{ 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>
GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5
* chore: fix link broken
Signed-off-by: snoppy <michaleli@foxmail.com>
* Update packages/template-compiler/README.md [skip ci]
---------
Signed-off-by: snoppy <michaleli@foxmail.com>
Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891
Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
更多推荐
已为社区贡献3条内容
所有评论(0)