这个代码实现了一个公式编辑器组件,主要用于构建和编辑包含变量和函数的规则表达式。下面是对代码逻辑的详细分析:
组件结构
组件主要分为三部分:

变量池:显示可用变量并支持搜索过滤
规则池:提供运算符和函数按钮,以及文本编辑区域
底部操作按钮:提供清空、取消和确认功能
主要功能
- 变量管理
通过getProperties方法从API获取变量列表
变量显示格式为identifieridentifieridentifier(如temptemptemp)
支持通过搜索框过滤变量
点击变量按钮可将变量插入到规则文本中
- 规则编辑
提供运算符和函数按钮(+、-、*、/、and、or等)
支持文本区域直接编辑
点击运算符/函数按钮可将其插入到规则文本中
函数插入时会自动添加括号(如int( ))
- 历史记录
实现撤销(undo)和重做(redo)功能
使用history数组记录所有操作状态
currentIndex跟踪当前状态位置
每次修改规则内容都会记录到历史中
- 数据流
通过props接收初始规则(initialRule)
通过emit事件confirm将编辑好的规则内容返回父组件
支持通过modelValue控制显示/隐藏
关键方法
insertVariable:
在光标位置插入变量
保持光标在变量后
记录操作历史
insertSymbol:
在光标位置插入运算符或函数
自动添加空格和括号
记录操作历史
onUndo/onRedo:
撤销/重做操作
更新当前索引和规则内容
onClearRule:
清空规则内容
弹出确认对话框
onConfirm:
将最终规则内容通过事件发送给父组件
样式设计
组件采用响应式布局,主要特点:
使用flex布局组织各部分
变量列表可滚动
运算符按钮加粗显示
各部分有边框和圆角
使用场景
这个组件适用于需要构建复杂表达式或公式的场景,如:
物联网设备数据处理规则
业务规则引擎
数据转换配置
条件判断逻辑构建
组件通过提供可视化的变量和函数选择,降低了编写复杂规则的难度。

公式编辑器组件代码分析
<template>
<el-dialog
v-model="visible"
title="公式编辑器"
width="900px"
:close-on-click-modal="false"
@close="onCancel"
append-to-body
>
<div class="data-flow-container">
<div class="formula-editor flex flex-column h-full">
<!-- 变量池 -->
<div class="variable-pool section-container">
<div class="section-label">变量池:</div>
<div class="search-bar">
<el-input v-model="searchKeyword" placeholder="模糊搜索" clearable></el-input>
</div>
<div class="variable-list">
<el-button
v-for="item in filteredVariables"
:key="item.key"
type="primary"
@click="insertVariable(item)"
:title="item.title"
size="small"
>
{{ item.label }}
</el-button>
</div>
</div>
<!-- 规则池 -->
<div class="rule-pool section-container">
<div class="section-label flex justify-between align-center">
<span>规则池:</span>
<div class="history-buttons">
<el-button :disabled="!canUndo" size="small" @click="onUndo">
<el-icon><ArrowLeft /></el-icon>上一步
</el-button>
<el-button :disabled="!canRedo" size="small" @click="onRedo">
下一步<el-icon><ArrowRight /></el-icon>
</el-button>
</div>
</div>
<div class="symbols-bar">
<el-button
v-for="symbol in ruleGroups"
:key="symbol.value"
@click="insertSymbol(symbol.value)"
size="small"
>
{{ symbol.label }}
</el-button>
</div>
<el-input
v-model="ruleContent"
type="textarea"
:rows="4"
placeholder="请输入规则"
ref="ruleInput"
></el-input>
</div>
<!-- 底部按钮 -->
<div class="footer">
<el-button type="warning" @click="onClearRule">清空规则池</el-button>
<el-button @click="onCancel">取消</el-button>
<el-button type="primary" @click="onConfirm">确定</el-button>
</div>
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ref, watch, computed, onMounted, nextTick } from 'vue'
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import FormulaEditorApi from '@/views/hp-equipment-sentinel/api/FormulaEditor'
import DeviceModelApi from '@/views/hp-equipment-sentinel/api/DeviceModel' // getProperties
import { deepClone } from '@/utils/index.js'
const visible = ref(false)
const emit = defineEmits(['update:modelValue', 'confirm'])
const name = ref('数据流转')
let formulaGroups = ref([])
let functionGroups = ref([])
let ruleGroups = ref([])
const formulaTest = ref([])
// 变量池数据
let variables = computed(() => {
let variables = formulaGroups.value.map((item) => {
// console.log('clg-on -> item',item)
return {
label: item.label,
key: item.name,
name: item.name,
title: item.description
}
})
return variables
})
const searchKeyword = ref('')
const ruleContent = ref('')
const ruleInput = ref(null)
// 过滤变量
const filteredVariables = computed(() => {
// console.log('clg-on -> variables.value', variables.value)
// console.log('clg-on -> searchKeyword.value', !searchKeyword.value)
if (!searchKeyword.value) return variables.value
return variables.value.filter(
(item) =>
item.label.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
item.key.toLowerCase().includes(searchKeyword.value.toLowerCase())
)
})
// 获取变量池数据
const getProperties = async () => {
let params = {
pageNum: 1,
pageSize: 100000,
thing_model: props.thingModel
}
const res = await DeviceModelApi.getProperties({ ...params, ...props.searchParam })
if (res.data.code !== 200) {
ElMessage.error(res.data.msg)
return
}
// console.log('clg-on -> getProperties', res)
// 修改 getProperties 方法中的映射逻辑
formulaGroups.value = res.data.data.list.map((item) => {
return {
label: item.name, // 修改为显示 name
key: item.identifier,
name: '$' + item.identifier + '$',
title: item.description // 保留 description 作为提示信息
}
})
}
// 获取规则池数据
const getFormulaConfigList = async () => {
const res = await FormulaEditorApi.getFormulaConfig()
if (res.data.code !== 200) {
ElMessage.error(res.data.msg)
return
}
functionGroups.value = res.data.data['functionGroups'].map((item) => {
item.functions.forEach((jtem) => {
jtem.label = jtem.description
jtem.key = jtem.name
})
return item
})
const functionGroupsTemp = deepClone(functionGroups.value)
// 通常情况下 我们只使用 逻辑预算符
if (props.isFunctionGroups) {
// 如果我是基本表 我需要删除 聚合函数 和 窗口函数
ruleGroups.value = functionGroupsTemp.filter(item=>{
return item.key !== 'agg_functions' && item.key !== 'window_functions'
})
} else {
// 全量情况下,我们才使用 逻辑预算符 和 聚合预算符
ruleGroups.value = [...functionGroups.value]
}
// 方法一:获取所有函数的params
ruleGroups.value = ruleGroups.value.reduce((acc, category) => {
const functionParams = category.functions.map((func) => ({
name: func.name,
title: func.description,
label: func.description,
key: func.name,
value: func.name
// params: func.params
}))
return [...acc, ...functionParams]
}, [])
// console.log(typeof props.isFunctionGroups , "props.isFunctionGroups",ruleGroups.value)
}
onMounted(() => {
getFormulaConfigList()
})
// 父级接受
const props = defineProps({
modelValue: {
type: Boolean,
default: false
},
searchParam: {
type: Object,
default: () => ({})
},
isFunctionGroups: {
type: Boolean,
default: false
},
initialRule: {
// 添加初始规则属性
type: String,
default: ''
},
thingModel: {
type: String,
default: ''
}
})
// 监听 modelValue 变化
watch(
() => props.modelValue,
(newVal) => {
visible.value = newVal
if (visible.value) {
getProperties()
}
if (newVal) {
// 打开弹窗时清空规则
ruleContent.value = props.initialRule || ''
// 重置历史记录
history.value = [ruleContent.value]
currentIndex.value = 0
}
},
{ immediate: true },
{ deep: true }
)
const onCancel = () => {
emit('update:modelValue', false)
}
// 符号列表
const symbols = [
{ label: ')', value: ')' },
{ label: '+', value: '+' },
{ label: '-', value: '-' },
{ label: '*', value: '*' },
{ label: '/', value: '/' },
{ label: '==', value: '==' },
{ label: '!=', value: '!=' },
{ label: '>', value: '>' },
{ label: '<', value: '<' },
{ label: '>=', value: '>=' },
{ label: '<=', value: '<=' },
{ label: 'and', value: 'and' },
{ label: 'or', value: 'or' },
{ label: 'int()', value: 'int()' },
{ label: 'float()', value: 'float()' },
{ label: 'string()', value: 'string()' }
]
// 操作历史记录
const history = ref([])
const currentIndex = ref(-1)
// 记录操作
const recordOperation = (content) => {
// 如果当前不是在最新的状态,需要清除后面的历史
if (currentIndex.value < history.value.length - 1) {
history.value = history.value.slice(0, currentIndex.value + 1)
}
history.value.push(content)
currentIndex.value = history.value.length - 1
}
// 是否可以撤销
const canUndo = computed(() => currentIndex.value > 0)
// 是否可以重做
const canRedo = computed(() => currentIndex.value < history.value.length - 1)
// 撤销操作
const onUndo = () => {
if (canUndo.value) {
currentIndex.value--
ruleContent.value = history.value[currentIndex.value]
}
}
// 重做操作
const onRedo = () => {
if (canRedo.value) {
currentIndex.value++
ruleContent.value = history.value[currentIndex.value]
}
}
const onConfirm = () => {
// console.log('clg-on -> ruleContent.value',ruleContent.value)
emit('confirm', ruleContent.value)
visible.value = false
}
// 修改插入变量的方法
const insertVariable = (variable) => {
const input = ruleInput.value.textarea
const position = input.selectionStart
const before = ruleContent.value.slice(0, position)
const after = ruleContent.value.slice(position)
const newContent = before + variable.key + after
ruleContent.value = newContent
recordOperation(newContent) // 记录操作
nextTick(() => {
input.setSelectionRange(position + variable.key.length, position + variable.key.length)
input.focus()
})
}
// 修改插入符号的方法
const insertSymbol = (symbol) => {
console.log('clg-on -> symbol',symbol)
symbol = ' ' + symbol + '( '
const input = ruleInput.value.textarea
const position = input.selectionStart
const before = ruleContent.value.slice(0, position)
const after = ruleContent.value.slice(position)
const newContent = before + symbol + after
ruleContent.value = newContent + ' )'
recordOperation(newContent) // 记录操作
nextTick(() => {
input.setSelectionRange(position + symbol.length, position + symbol.length)
input.focus()
})
}
// 修改清空规则池的方法
const onClearRule = () => {
ElMessageBox.confirm('确认清空规则池内容吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
ruleContent.value = ''
recordOperation('') // 记录清空操作
ElMessage.success('已清空规则池')
})
.catch(() => {
// 取消清空
})
}
</script>
<style scoped lang="scss">
.data-flow-container {
background-color: #fff;
height: 100%;
//padding: 20px;
border-radius: 4px;
//box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.formula-editor {
padding: 20px;
gap: 16px;
.section-container {
border: 1px solid #ddd;
padding: 16px;
border-radius: 4px;
margin-bottom: 16px;
}
.section-label {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 16px;
.history-buttons {
display: flex;
gap: 8px;
:deep(.el-button) {
display: flex;
align-items: center;
gap: 4px;
}
}
}
.variable-pool {
.search-bar {
margin-bottom: 16px;
}
.variable-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
max-height: 200px;
overflow: scroll;
}
}
.rule-pool {
.symbols-bar {
margin-bottom: 16px;
display: flex;
flex-wrap: wrap;
gap: 8px;
font-weight: 800;
:deep(.el-button) {
font-weight: 800;
color: #333;
}
}
}
.footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
}
.gateway-manager {
flex: 1;
padding: 20px;
background-color: #fff;
overflow: hidden;
.tips {
font-size: 14px;
font-weight: normal;
line-height: 22px;
letter-spacing: 0px;
color: #606266;
}
.input-width {
width: 220px;
}
}
.table-container {
flex: 1;
overflow: hidden;
margin: 16px 0;
}
</style>
// 父组件调用
<FormulaEditor
v-model="formulaVisible" // 开关
:initial-rule="formData.condition" // 回显
:thing-model="String(formData.thing_model)" // id
:isFunctionGroups="true" // 全量 非全量
@submit="handleFormulaSubmit"
@confirm="handleFormulaConfirm"
/>

3345

被折叠的 条评论
为什么被折叠?



