vue实现公式编辑器组件
2023-12-14 17:40:45
1、效果图
2、实现代码?
组件弹框实现 样式自己调整? 公式的数字与汉字元素、符号 建立元素表 动态获取
完整代码(calculate.vue)
<template>
<div id="formulaPage">
<div
ref="formulaView"
class="formulaView"
contentEditable="false"
@click="recordPosition"
@keyup="editEnter($event)"
@copy="copy($event)"
@paste="paste($event)"
/>
<div class="infomationContent">
<div class="infomationContent-leftFlexbox">
<div class="tab">
<ul style="width: 200px">
<li
v-for="(v, i) in Num"
:key="i"
class="numberClass"
:style="{ background: v.backgroundColor }"
@click="addItem($event, 2, v, false)"
>
{{ v.displayValue }}
</li>
</ul>
</div>
<div class="tab">
<ul style="width: 500px">
<li
v-for="(v, i) in customType"
:key="i"
class="typeClass"
:style="{ background: v.backgroundColor }"
:class="{ noclick: v.isFlag }"
@click="addItem($event, 2, v, v.isFlag)"
>
{{ v.displayValue }}
</li>
</ul>
</div>
<div class="tab">
<ul style="width: 200px">
<li
v-for="(v, i) in Symbol"
:key="i"
class="symbolClass"
:style="{ background: v.backgroundColor }"
@click="addItem($event, 2, v, false)"
>
{{ v.displayValue }}
</li>
</ul>
</div>
</div>
<div class="infomationContent-rightBtnbox">
<div class="imgboxinfo" style="margin-bottom: 32px">
<img src="@/assets/images/rolback.png" alt="" style="width: 40px; height: 40px" @click="deleteHumerText" />
</div>
<div class="imgboxinfo">
<img src="@/assets/images/calSave.png" alt="" style="width: 40px; height: 40px" @click="onSaveformulaEvent" />
</div>
</div>
</div>
<!-- <div class="footerComtent">
<el-button type="primary" style="width: 200px; height: 30px" @click="onSaveformulaEvent">保存</el-button>
</div> -->
<!-- <button @click="parsingFormula('(长+宽+高)*2')">反向解析公式</button> -->
<!-- <button @click="deleteHumerText()">删除</button> -->
</div>
</template>
<script>
export default {
name: 'FormulaPage',
props: {
content: { type: String, default: '' },
recoverdate: {
type: Array,
default: () => []
},
domflag: {
type: String,
default: ''
}
},
data() {
return {
// 权限分类 元素分类不可用集合
btnsRuleArray: {
offer: ['thickness', 'nailmouth', 'bellsocket', 'gramweight'], // 报价
weight: ['thickness', 'unitprice'], // 重量
areavolume: ['gramweight', 'unitprice'] // 体积、面积、纸宽、纸长
},
// 公式字符串
formulaStr: '',
// 公式编辑器最后光标位置
formulaLastRange: null,
Num: [{
"displayValue":"1",
"saveValue":"1"
},{
"displayValue":"2",
"saveValue":"2"
},{
"displayValue":"3",
"saveValue":"3"
},{
"displayValue":"4",
"saveValue":"4"
},{
"displayValue":"5",
"saveValue":"5"
},{
"displayValue":"6",
"saveValue":"6"
},{
"displayValue":"7",
"saveValue":"7"
},{
"displayValue":"8",
"saveValue":"8"
},{
"displayValue":"9",
"saveValue":"9"
},{
"displayValue":"0",
"saveValue":"0"
},{
"displayValue":".",
"saveValue":"."
}],
Symbol: [
{
"displayValue":"+",
"saveValue":"+"
},{
"displayValue":"-",
"saveValue":"-"
},{
"displayValue":"x",
"saveValue":"*"
},{
"displayValue":"÷",
"saveValue":"/"
},{
"displayValue":"(",
"saveValue":"("
},{
"displayValue":")",
"saveValue":")"
},{
"displayValue":"{",
"saveValue":"{"
},{
"displayValue":"}",
"saveValue":"}"
},
],
customType: [{
"displayValue":"上报产量",
"saveValue":"report_production"
},{
"displayValue":"实际产量",
"saveValue":"reality_production"
},{
"displayValue":"红矿",
"saveValue":"red_ore"
},{
"displayValue":"青堆",
"saveValue":"black_stack"
},{
"displayValue":"红堆",
"saveValue":"red_stack"
},{
"displayValue":"煤存",
"saveValue":"coal_storage"
},{
"displayValue":"槽存",
"saveValue":"slot_storage"
},{
"displayValue":"土耗",
"saveValue":"soil_used"
},{
"displayValue":"土存",
"saveValue":"soil_stock"
},{
"displayValue":"煤耗",
"saveValue":"coal_used"
},{
"displayValue":"产量",
"saveValue":"production"
},{
"displayValue":"用电量",
"saveValue":"meter_used"
}],
backFormulaStrArray: [] // 按键值存储编辑器数据
}
},
created() {
this.backFormulaStrArray = []
if (this.recoverdate) {
this.backFormulaStrArray = this.recoverdate
this.$nextTick(function () {
this.parsingFormulaCustom(this.recoverdate)
})
}
// this.getList()
},
methods: {
/** 查询所有公式元素 */
getList() {
listElement({ pageNum: 1, pageSize: 50 }).then(response => {
response.rows.forEach(item => {
item.isFlag = false
if (this.domflag) {
const newLegth = this.btnsRuleArray[this.domflag].filter(e => e == item.saveValue)
if (newLegth.length > 0) {
item.isFlag = true
}
}
})
const numberDate = response.rows
.filter(e => {
return e.elementType == 'number'
})
.sort((a, b) => {
return a.order - b.order
})
const formulaElementDate = response.rows
.filter(e => {
return e.elementType == 'formulaElement'
})
.sort((a, b) => {
return a.order - b.order
})
const symbolDate = response.rows
.filter(e => {
return e.elementType == 'symbol'
})
.sort((a, b) => {
return a.order - b.order
})
this.Num = numberDate
this.Symbol = symbolDate
this.customType = formulaElementDate
})
},
// 删除操作---从最后删除
deleteHumerText() {
if (this.backFormulaStrArray.length > 0) {
this.backFormulaStrArray.splice(-1, 1)
while (this.$refs.formulaView.firstChild) {
this.$refs.formulaView.removeChild(this.$refs.formulaView.firstChild)
}
this.backFormulaStrArray.forEach(item => {
const fd = document.createDocumentFragment()
const empty = document.createTextNode(' ')
const formulaEl = document.createTextNode(' ' + item.displayValue + ' ')
fd.appendChild(empty)
fd.appendChild(formulaEl)
fd.appendChild(empty)
this.$refs.formulaView.appendChild(fd)
// 创建新的光标对象
var range = document.createRange()
// 光标对象的范围界定
range.selectNodeContents(formulaEl)
// 光标位置定位
range.setStart(formulaEl, formulaEl.data.length - 1)
// 使光标开始和光标结束重叠
range.collapse(true)
// 清除选定对象的所有光标对象
window.getSelection().removeAllRanges()
// 插入新的光标对象
window.getSelection().addRange(range)
// 保存新光标
this.recordPosition()
})
}
},
// 保存数据 回调父组件
onSaveformulaEvent() {
const strParams = this.backFormulaStrArray.map(e => e.saveValue).join('')
const strParamsValue = this.backFormulaStrArray.map(e => e.displayValue).join('')
const newbackFormulaStrArray = []
this.backFormulaStrArray.forEach(item => {
const obj = {
displayValue: item.displayValue,
elementType: item.elementType,
saveValue: item.saveValue
}
newbackFormulaStrArray.push(obj)
})
this.parsingFormula('')
this.$emit('onChangeSuccess', strParams, strParamsValue, newbackFormulaStrArray)
this.backFormulaStrArray = []
console.log(strParams)
console.log(strParamsValue)
console.log(newbackFormulaStrArray)
// const ifLegal1 = [...strParamsValue].reduce((a, i) => (i === '(' ? a + 1 : a - 1), 0)
},
isValidate(str) {
const inArr = []
const arr = str.split('')
for (const s of arr) {
if (s === '{' || s === '[' || s === '(') {
// 入栈
inArr.push(s)
}
if (s === '}' || s === ']' || s === ')') {
let temp
switch (s) {
case '}':
temp = '{'
break
case ']':
temp = '['
break
case ')':
temp = '('
break
}
// 出栈
const out = inArr.pop()
if (temp !== out) {
return false
}
}
}
return true
},
// 获取公式
getFormula: function () {
var nodes = this.$refs.formulaView.childNodes
var str = ''
for (let i = 0; i < nodes.length; i++) {
var el = nodes[i]
if (el.nodeName == 'SPAN') {
// console.log(el);
str += '#' + el.innerHTML.trim() + '#'
} else {
// console.log(el.data);
str += el.data ? el.data.trim() : ''
}
}
// console.log(str);
this.formulaStr = str
},
// 点选时记录光标位置
recordPosition: function () {
// 保存最后光标点
this.formulaLastRange = window.getSelection().getRangeAt(0)
},
// 添加字段 type 1 字段 2 公式
addItem: function (e, type, itemRows, isusable) {
if (isusable) {
e.preventDefault()
return false
}
if (this.backFormulaStrArray.length > 0) {
if (itemRows.elementType == 'formulaElement' || itemRows.elementType == 'symbol') {
// 检验连续相同两个元素不能重复 符号/元素
if (this.backFormulaStrArray[this.backFormulaStrArray.length - 1].displayValue == itemRows.displayValue) {
e.preventDefault()
return false
}
// 检验不同元素连续两个不能是用一个类型
if (
this.backFormulaStrArray[this.backFormulaStrArray.length - 1].elementType == 'formulaElement' &&
itemRows.elementType == 'formulaElement'
) {
e.preventDefault()
return false
}
if (
this.backFormulaStrArray[this.backFormulaStrArray.length - 1].elementType == 'number' &&
itemRows.elementType == 'formulaElement'
) {
e.preventDefault()
return false
}
}
if (itemRows.elementType == 'number' && itemRows.saveValue == '.') {
if (this.backFormulaStrArray[this.backFormulaStrArray.length - 1].displayValue == itemRows.displayValue) {
e.preventDefault()
return false
}
}
// this.backFormulaStrArray.forEach(item => {
// console.log(item.displayValue,itemRows.displayValue)
// const reg = RegExp(`(${item.displayValue})\1`, 'g'))
// console.log(strParamsValue.match(reg))
// })
}
this.backFormulaStrArray.push(itemRows)
const isValiStr = this.backFormulaStrArray.map(e => e.displayValue).join('')
if (!this.isValidate(isValiStr)) {
this.backFormulaStrArray.splice(-1, 1)
e.preventDefault()
return false
}
// 当前元素所有子节点
var nodes = this.$refs.formulaView.childNodes
// 当前子元素偏移量
var offset = this.formulaLastRange && this.formulaLastRange.startOffset
// 当前光标后的元素
var nextEl = this.formulaLastRange && this.formulaLastRange.endContainer
// 创建节点片段
var fd = document.createDocumentFragment()
// 创建字段节点 空白间隔节点 公式节点
var spanEl = document.createElement('span')
spanEl.setAttribute('contentEditable', false)
// 标识为新添加元素 用于定位光标
spanEl.setAttribute('new-el', true)
spanEl.innerHTML = e.target.innerHTML
var empty = document.createTextNode(' ')
var formulaEl = document.createTextNode(' ' + e.target.innerHTML + ' ')
// 区分文本节点替换 还是父节点插入
if (nextEl && nextEl.className != 'formulaView') {
// 获取文本节点内容
var content = nextEl.data
// 添加前段文本
fd.appendChild(document.createTextNode(content.substr(0, offset) + ' '))
fd.appendChild(type == 1 ? spanEl : formulaEl)
// 添加后段文本
fd.appendChild(document.createTextNode(' ' + content.substr(offset)))
// 替换节点
this.$refs.formulaView.replaceChild(fd, nextEl)
} else {
// 添加前段文本
fd.appendChild(empty)
fd.appendChild(type == 1 ? spanEl : formulaEl)
fd.appendChild(empty)
// 如果有偏移元素且不是最后节点 中间插入节点 最后添加节点
if (nodes.length && nodes.length > offset) {
this.$refs.formulaView.insertBefore(fd, nextEl && nextEl.className != 'formulaView' ? nextEl : nodes[offset])
} else {
this.$refs.formulaView.appendChild(fd)
}
}
// 遍历光标偏移数据 删除标志
var elOffSet = 0
for (let i = 0; i < nodes.length; i++) {
const el = nodes[i]
// console.log(el,el.nodeName == 'SPAN'&&el.getAttribute('new-el'));
if (el.nodeName == 'SPAN' && el.getAttribute('new-el')) {
elOffSet = i
el.removeAttribute('new-el')
}
}
// 创建新的光标对象
var range = document.createRange()
// 光标对象的范围界定
range.selectNodeContents(type == 1 ? this.$refs.formulaView : formulaEl)
// 光标位置定位
range.setStart(
type == 1 ? this.$refs.formulaView : formulaEl,
type == 1 ? elOffSet + 1 : formulaEl.data.length - 1
)
// 使光标开始和光标结束重叠
range.collapse(true)
// 清除选定对象的所有光标对象
window.getSelection().removeAllRanges()
// 插入新的光标对象
window.getSelection().addRange(range)
// 保存新光标
this.recordPosition()
},
// 复制
copy: function (e) {
// 选中复制内容
e.preventDefault()
//
var selContent = document.getSelection().toString().split('\n')[0]
// 替换选中内容
e.clipboardData.setData('text/plain', selContent)
},
// 输入回车
editEnter: function (e) {
e.preventDefault()
if (e.keyCode == 13) {
// 获取标签内容 并把多个换行替换成1个
var content = this.$refs.formulaView.innerHTML.replace(/(<div><br><\/div>){2,2}/g, '<div><br></div>')
// 记录是否第一行回车
var divCount = this.$refs.formulaView.querySelectorAll('div')
// var tE = this.$refs.formulaView.querySelect('div');
// console.log(this.$refs.formulaView.childNodes);
// console.log(this.$refs.formulaView.querySelectorAll("div"));
// 获取当前元素内所有子节点
var childNodes = this.$refs.formulaView.childNodes
// 记录当前光标子节点位置
var rangeIndex = 0
for (let i = 0; i < childNodes.length; i++) {
var one = childNodes[i]
if (one.nodeName == 'DIV') {
rangeIndex = i
}
}
// 如果有替换则进行光标复位
if (divCount.length >= 1) {
// 替换回车插入的div标签
content = content.replace(/<div>|<\/div>/g, function (word) {
// console.log(word);
if (word == '<div>') {
// 如果是第一行不在替换br
return divCount.length > 1 ? ' ' : ' <br>'
} else if (word == '</div>') {
return ' '
}
})
// 更新替换内容,光标复位
this.$refs.formulaView.innerHTML = content
// 创建新的光标对象
var range = document.createRange()
// 光标对象的范围界定为新建的表情节点
range.selectNodeContents(this.$refs.formulaView)
// 光标位置定位在表情节点的最大长度
range.setStart(this.$refs.formulaView, rangeIndex + (divCount.length > 1 ? 0 : 1))
// 使光标开始和光标结束重叠
range.collapse(true)
// 清除选定对象的所有光标对象
window.getSelection().removeAllRanges()
// 插入新的光标对象
window.getSelection().addRange(range)
}
}
// 保存最后光标点
this.formulaLastRange = window.getSelection().getRangeAt(0)
},
// 获取粘贴事件
paste: function (e) {
e.preventDefault()
// var txt=e.clipboardData.getData();
// console.log(e, e.clipboardData.getData());
return ''
},
// 公式反向解析
parsingFormula: function (formulaStr) {
// 渲染视口
var view = this.$refs.formulaView
// 反向解析公式
var str = formulaStr.replace(/#(.+?)#/g, function (word, $1) {
// console.log(word,$1);
return "<span contentEditable='false'>" + $1 + '</span>'
})
// console.log(str,fd.innerHTML);
view.innerHTML = str
// this.$refs.formulaView.appendChild(fd);
// 创建新的光标对象
var range = document.createRange()
// 光标对象的范围界定为新建的表情节点
range.selectNodeContents(view)
// 光标位置定位在表情节点的最大长度
range.setStart(view, view.childNodes.length)
// 使光标开始和光标结束重叠
range.collapse(true)
// 清除选定对象的所有光标对象
window.getSelection().removeAllRanges()
// 插入新的光标对象
window.getSelection().addRange(range)
// 保存新光标
this.recordPosition()
},
parsingFormulaCustom(arrayDatelist) {
arrayDatelist.forEach(item => {
// 当前元素所有子节点
const nodes = this.$refs.formulaView.childNodes
// 当前子元素偏移量
const offset = this.formulaLastRange && this.formulaLastRange.startOffset
// 当前光标后的元素
const nextEl = this.formulaLastRange && this.formulaLastRange.endContainer
const fd = document.createDocumentFragment()
const empty = document.createTextNode(' ')
const formulaEl = document.createTextNode(' ' + item.displayValue + ' ')
if (nextEl && nextEl.className != 'formulaView') {
var content = nextEl.data
fd.appendChild(document.createTextNode(content.substr(0, offset) + ' '))
fd.appendChild(formulaEl)
fd.appendChild(document.createTextNode(' ' + content.substr(offset)))
this.$refs.formulaView.replaceChild(fd, nextEl)
} else {
fd.appendChild(empty)
fd.appendChild(formulaEl)
fd.appendChild(empty)
// 如果有偏移元素且不是最后节点 中间插入节点 最后添加节点
if (nodes.length && nodes.length > offset) {
this.$refs.formulaView.insertBefore(
fd,
nextEl && nextEl.className != 'formulaView' ? nextEl : nodes[offset]
)
} else {
this.$refs.formulaView.appendChild(fd)
}
}
// 创建新的光标对象
var range = document.createRange()
// 光标对象的范围界定
range.selectNodeContents(formulaEl)
// 光标位置定位
range.setStart(formulaEl, formulaEl.data.length - 1)
// 使光标开始和光标结束重叠
range.collapse(true)
// 清除选定对象的所有光标对象
window.getSelection().removeAllRanges()
// 插入新的光标对象
window.getSelection().addRange(range)
// 保存新光标
this.recordPosition()
})
}
}
}
</script>
<style lang="scss">
#formulaPage {
width: 100%;
height: 100%;
> .formulaView {
margin-bottom: 20px;
min-height: 130px;
width: 100%;
padding: 5px;
border: 5px solid rgb(198, 226, 255);
resize: both;
overflow: auto;
line-height: 25px;
font-size: 20px;
span {
user-select: none;
display: inline-block;
/* // margin: 0 3px; */
height: 20px;
line-height: 20px;
/* // letter-spacing: 2px; */
border-radius: 3px;
white-space: nowrap;
&:first-child {
margin-left: 0;
}
}
}
.footerComtent {
margin-top: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.infomationContent {
display: flex;
&-leftFlexbox {
flex: 1;
display: flex;
> .tab {
flex: 1;
> ul {
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
/* // justify-content: space-between; */
&:after {
content: '';
display: table;
clear: both;
}
> li {
/* // margin-right: 20px; */
margin: 5px 10px;
width: 65px;
text-align: center;
float: left;
padding: 0 10px;
height: 30px;
line-height: 30px;
border-radius: 5px;
font-weight: bold;
border: 1px solid #fff;
list-style-type: none;
cursor: pointer;
-moz-user-select: none; /* 火狐 */
-webkit-user-select: none; /* 谷歌、Safari */
-ms-user-select: none; /* IE10+ */
user-select: none;
user-drag: none;
box-shadow: 0px 0px 2px 2px rgba(55, 114, 203, 0.2), /*下面深蓝色立体阴影*/ 0px 0px 6px 1px #4379d0,
/*内部暗色阴影*/ 0 -15px 2px 2px rgba(55, 114, 203, 0.1) inset;
color: #333333;
}
> li:hover {
color: #fff;
/* // color: #409eff; */
border-color: #c6e2ff;
background-color: #ecf5ff;
}
> li:active {
color: #3a8ee6;
border-color: #3a8ee6;
outline: none;
}
.numberClass {
width: 40px;
}
.typeClass {
width: 100px
}
.symbolClass {
}
.noclick {
cursor: not-allowed !important;
background: #bcc0c4 !important;
color: #e4eaf1 !important;
border: none !important;
}
}
}
}
&-rightBtnbox {
width: 50px;
padding-top: 10px;
box-sizing: border-box;
.imgboxinfo {
width: 40px;
height: 40px;
img {
cursor: pointer;
}
}
}
}
}
</style>
在父组件中引用即可
文章来源:https://blog.csdn.net/askuld/article/details/134996471
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!