|
|
@@ -26,6 +26,7 @@
|
|
|
v-model="formData.classify"
|
|
|
placeholder="请选择"
|
|
|
:disabled="type == 'processEdit'"
|
|
|
+ @change="classifyChagne"
|
|
|
>
|
|
|
<el-option
|
|
|
v-for="item in recordSheet"
|
|
|
@@ -189,10 +190,24 @@
|
|
|
|
|
|
<header-title title="规则明细"> </header-title>
|
|
|
|
|
|
+ <el-tabs
|
|
|
+ v-if="formData.classify == 10"
|
|
|
+ v-model="statisticsType"
|
|
|
+ type="card"
|
|
|
+ >
|
|
|
+ <el-tab-pane
|
|
|
+ v-for="i in statisticsTypeList"
|
|
|
+ :label="i.label"
|
|
|
+ :name="i.value"
|
|
|
+ :key="i.value"
|
|
|
+ >
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+
|
|
|
<ele-pro-table
|
|
|
ref="table"
|
|
|
:columns="bankColumns"
|
|
|
- :datasource="formData.details"
|
|
|
+ :datasource="detialsDatasource"
|
|
|
:need-page="false"
|
|
|
row-key="id"
|
|
|
class="table_list"
|
|
|
@@ -225,6 +240,7 @@
|
|
|
<el-option label="范围" :value="6" />
|
|
|
<el-option label="文本" :value="7" />
|
|
|
<!-- <el-option label="枚举" :value="8" /> -->
|
|
|
+ <el-option label="计算" :value="9" />
|
|
|
</el-select>
|
|
|
</template>
|
|
|
|
|
|
@@ -257,6 +273,71 @@
|
|
|
autosize
|
|
|
></el-input>
|
|
|
</template>
|
|
|
+
|
|
|
+ <template v-slot:formula="{ row }">
|
|
|
+ <div v-if="row.paramType == 9" class="formula-builder">
|
|
|
+ <div class="formula-builder__selects">
|
|
|
+ <!-- 选择参数:从已填的非计算参数内容里取 -->
|
|
|
+ <el-select
|
|
|
+ v-model="row._paramSelect"
|
|
|
+ placeholder="选择参数"
|
|
|
+ size="mini"
|
|
|
+ style="width: 160px; margin-right: 8px"
|
|
|
+ @change="paramSelectChange($event, row)"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in getSelectOptionsByDetails()"
|
|
|
+ :key="item.value"
|
|
|
+ :label="item.label"
|
|
|
+ :value="item.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+
|
|
|
+ <!-- 选择运算符 -->
|
|
|
+ <el-select
|
|
|
+ v-model="row._opSelect"
|
|
|
+ placeholder="选择符号"
|
|
|
+ size="mini"
|
|
|
+ style="width: 120px"
|
|
|
+ @change="opSelectChange($event, row)"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="op in opSelectOptions"
|
|
|
+ :key="op"
|
|
|
+ :label="op"
|
|
|
+ :value="op"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 已组装公式标签展示 -->
|
|
|
+ <div
|
|
|
+ v-if="row.formulaParts && row.formulaParts.length"
|
|
|
+ style="display: inline-flex; flex-wrap: wrap; max-width: 100%"
|
|
|
+ >
|
|
|
+ <el-tag
|
|
|
+ v-for="(p, index) in row.formulaParts"
|
|
|
+ :key="index"
|
|
|
+ size="mini"
|
|
|
+ closable
|
|
|
+ @close="tagItemDelete(index, row)"
|
|
|
+ >
|
|
|
+ {{ p }}
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 公式字符串展示(只读) -->
|
|
|
+ <el-input
|
|
|
+ v-if="row.formulaParts && row.formulaParts.length"
|
|
|
+ :value="row.formulaParts.join('')"
|
|
|
+ size="mini"
|
|
|
+ disabled
|
|
|
+ placeholder="公式"
|
|
|
+ style="margin-top: 6px"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
<template v-slot:defaultValue="{ row }">
|
|
|
<el-row>
|
|
|
<el-col :span="12">
|
|
|
@@ -470,7 +551,7 @@
|
|
|
mixins: [dictMixins],
|
|
|
computed: {
|
|
|
bankColumns() {
|
|
|
- return [
|
|
|
+ const list = [
|
|
|
{
|
|
|
width: 110,
|
|
|
type: 'index',
|
|
|
@@ -492,6 +573,13 @@
|
|
|
slot: 'paramValue',
|
|
|
minWidth: 261
|
|
|
},
|
|
|
+ {
|
|
|
+ prop: 'formula',
|
|
|
+ label: '计算公式',
|
|
|
+ align: 'center',
|
|
|
+ slot: 'formula',
|
|
|
+ minWidth: 220
|
|
|
+ },
|
|
|
{
|
|
|
prop: 'defaultValue',
|
|
|
label: '默认值',
|
|
|
@@ -551,6 +639,11 @@
|
|
|
fixed: 'right'
|
|
|
}
|
|
|
];
|
|
|
+ if (this.formData.classify != 10) {
|
|
|
+ // 过滤掉 计算公式 列
|
|
|
+ return list.filter((i) => i.prop != 'formula');
|
|
|
+ }
|
|
|
+ return list;
|
|
|
},
|
|
|
versionText() {
|
|
|
if (this.type == 'change') {
|
|
|
@@ -613,12 +706,22 @@
|
|
|
},
|
|
|
cacheKeyUrl() {
|
|
|
return `main-permit-add-2510181429-reportWorkType`;
|
|
|
+ },
|
|
|
+ detialsDatasource() {
|
|
|
+ if (this.formData.classify == 10) {
|
|
|
+ // 根据 statisticsType 过滤
|
|
|
+ return this.formData.details.filter(
|
|
|
+ (i) => i.statisticsType == this.statisticsType
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ return this.formData.details;
|
|
|
+ }
|
|
|
}
|
|
|
},
|
|
|
data() {
|
|
|
const formDateBase = {
|
|
|
id: null,
|
|
|
- classify: null,
|
|
|
+ classify: '10',
|
|
|
deviceId: null,
|
|
|
deviceName: '',
|
|
|
frequencyUnit: 2,
|
|
|
@@ -699,7 +802,15 @@
|
|
|
loading: false,
|
|
|
produceTaskCode: '',
|
|
|
// 执行方式列表
|
|
|
- executeMethodList: []
|
|
|
+ executeMethodList: [],
|
|
|
+ // 1-成品统计,2-物料统计,3-工序统计"
|
|
|
+ statisticsType: '1',
|
|
|
+ statisticsTypeList: [
|
|
|
+ { label: '成品统计', value: '1' },
|
|
|
+ { label: '物料统计', value: '2' },
|
|
|
+ { label: '工序统计', value: '3' }
|
|
|
+ ],
|
|
|
+ opSelectOptions: ['+', '-', '*', '/', '%', '(', ')']
|
|
|
};
|
|
|
},
|
|
|
mounted() {
|
|
|
@@ -802,6 +913,25 @@
|
|
|
this.getExecuteMethodList();
|
|
|
}
|
|
|
|
|
|
+ // 处理详情的 公式 字段
|
|
|
+ this.formData.details.forEach((item) => {
|
|
|
+ if (item.paramType == 9 && item.formula) {
|
|
|
+ // formula格式为[A][+][b][*][C] 拆分 -> ['A','+','b','*','C']
|
|
|
+ if (item.formula) {
|
|
|
+ const matches = item.formula.match(/\[([^\]]+)\]/g);
|
|
|
+ if (matches) {
|
|
|
+ this.$set(
|
|
|
+ item,
|
|
|
+ 'formulaParts',
|
|
|
+ matches.map((m) => m.slice(1, -1)).filter(Boolean)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.$set(item, 'formulaParts', []);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
this.loading = false;
|
|
|
} catch (error) {
|
|
|
this.loading = false;
|
|
|
@@ -817,6 +947,7 @@
|
|
|
};
|
|
|
});
|
|
|
this.recordSheet = list;
|
|
|
+ console.log('this.recordSheet', this.recordSheet);
|
|
|
},
|
|
|
itemDel(index) {
|
|
|
this.formData.details.splice(index, 1);
|
|
|
@@ -846,6 +977,11 @@
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
+ const valid = this.validateFormula();
|
|
|
+ if (!valid) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
this.btnLoading = true;
|
|
|
|
|
|
const body = this.formatBody();
|
|
|
@@ -909,6 +1045,137 @@
|
|
|
}
|
|
|
});
|
|
|
},
|
|
|
+ // 保存或发布时验证公式是否正确
|
|
|
+ validateFormula() {
|
|
|
+ let invalid = false;
|
|
|
+ this.formData.details.forEach((detail) => {
|
|
|
+ if (invalid) return; // 已发现无效,后续直接跳过避免重复提示
|
|
|
+ if (detail.paramType == 9) {
|
|
|
+ const formula = detail.formula;
|
|
|
+ if (!formula || !formula.trim()) {
|
|
|
+ console.log('formula', formula);
|
|
|
+ this.$message.warning('规则明细中计算类型的公式不能为空!');
|
|
|
+ invalid = true;
|
|
|
+ }
|
|
|
+ // 解析公式为 token 列表
|
|
|
+ const rawTokens = (formula.match(/\[([^\]]+)\]/g) || [])
|
|
|
+ .map((m) => m.slice(1, -1))
|
|
|
+ .filter(Boolean);
|
|
|
+ if (!rawTokens.length) {
|
|
|
+ console.log('rawTokens', rawTokens, formula);
|
|
|
+ this.$message.warning('规则明细中计算类型的公式不能为空!');
|
|
|
+ invalid = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const operators = new Set(this.opSelectOptions);
|
|
|
+ const availableParams = this.formData.details
|
|
|
+ .filter((d) => d !== detail && d.paramType != 9 && d.paramValue)
|
|
|
+ .map((d) => d.paramValue);
|
|
|
+ const varsSet = new Set();
|
|
|
+
|
|
|
+ let parenBalance = 0;
|
|
|
+ let lastWasOperator = true; // 开头不能是普通数字/变量
|
|
|
+ let expression = '';
|
|
|
+
|
|
|
+ for (let i = 0; i < rawTokens.length; i++) {
|
|
|
+ const t = rawTokens[i];
|
|
|
+ if (invalid) break;
|
|
|
+
|
|
|
+ if (operators.has(t)) {
|
|
|
+ // 括号
|
|
|
+ if (t === '(') {
|
|
|
+ parenBalance++;
|
|
|
+ lastWasOperator = true;
|
|
|
+ } else if (t === ')') {
|
|
|
+ parenBalance--;
|
|
|
+ if (parenBalance < 0) {
|
|
|
+ this.$message.warning('公式括号不匹配');
|
|
|
+ invalid = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ lastWasOperator = false;
|
|
|
+ } else {
|
|
|
+ // 普通运算符不能连续出现
|
|
|
+ if (lastWasOperator) {
|
|
|
+ this.$message.warning('公式存在连续运算符或位置不合法');
|
|
|
+ invalid = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ lastWasOperator = true;
|
|
|
+ }
|
|
|
+ expression += t;
|
|
|
+ } else {
|
|
|
+ // 变量
|
|
|
+ if (!t.trim()) {
|
|
|
+ this.$message.warning('公式中存在空变量');
|
|
|
+ invalid = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ // 变量前必须是运算符或者开头或左括号
|
|
|
+ if (!lastWasOperator) {
|
|
|
+ this.$message.warning('公式中变量之间缺少运算符');
|
|
|
+ invalid = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ varsSet.add(t);
|
|
|
+ // 校验变量是否存在于其他明细
|
|
|
+ if (!availableParams.includes(t)) {
|
|
|
+ this.$message.warning(
|
|
|
+ `公式中引用的参数 ${t} 未在非计算类型的明细中定义`
|
|
|
+ );
|
|
|
+ invalid = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ // 用顺序数字替换变量
|
|
|
+ expression += varsSet.size; // 1,2,3...
|
|
|
+ lastWasOperator = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (invalid) return;
|
|
|
+
|
|
|
+ if (parenBalance !== 0) {
|
|
|
+ this.$message.warning('公式括号不匹配');
|
|
|
+ invalid = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (lastWasOperator) {
|
|
|
+ this.$message.warning('公式不能以运算符结尾');
|
|
|
+ invalid = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 最终表达式字符合法性校验
|
|
|
+ if (!/^[0-9+\-*/%() ]+$/.test(expression)) {
|
|
|
+ this.$message.warning('公式包含非法字符');
|
|
|
+ invalid = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算测试
|
|
|
+ try {
|
|
|
+ const result = Function(`"use strict";return (${expression})`)();
|
|
|
+ if (Number.isNaN(result) || !isFinite(result)) {
|
|
|
+ this.$message.warning('公式计算结果非法');
|
|
|
+ invalid = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.warning('公式计算失败,请检查语法');
|
|
|
+ invalid = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 回填拆分后的 parts
|
|
|
+ detail.formulaParts = rawTokens;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if (invalid) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ },
|
|
|
// 数据格式化
|
|
|
formatBody() {
|
|
|
this.formData.recordRulesCycleList =
|
|
|
@@ -1005,7 +1272,12 @@
|
|
|
tools: [],
|
|
|
unitName: null,
|
|
|
productName: '',
|
|
|
- productCode: ''
|
|
|
+ productCode: '',
|
|
|
+ // 1-成品统计,2-物料统计,3-工序统计"
|
|
|
+ statisticsType:
|
|
|
+ this.formData.classify == 10 ? this.statisticsType : null,
|
|
|
+ // 公式
|
|
|
+ formula: ''
|
|
|
});
|
|
|
console.log('this.formData.details', this.formData.details);
|
|
|
},
|
|
|
@@ -1171,6 +1443,46 @@
|
|
|
this.formData.executeMethodId = null;
|
|
|
this.formData.executeMethodName = '';
|
|
|
}
|
|
|
+ },
|
|
|
+ classifyChagne() {
|
|
|
+ // 生产统计
|
|
|
+ if (this.formData.classify == 10) {
|
|
|
+ // 清空规则明细
|
|
|
+ this.formData.details = [];
|
|
|
+ // 重置统计类型
|
|
|
+ this.statisticsType = '1';
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 基于详情返回selectOptions
|
|
|
+ getSelectOptionsByDetails() {
|
|
|
+ const paramTypeOptions = [];
|
|
|
+ for (const detail of this.formData.details) {
|
|
|
+ if (detail.paramType != 9 || !detail.paramType) {
|
|
|
+ paramTypeOptions.push({
|
|
|
+ value: detail.paramValue,
|
|
|
+ label: detail.paramValue
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return paramTypeOptions;
|
|
|
+ },
|
|
|
+ paramSelectChange(val, row) {
|
|
|
+ if (!val) return;
|
|
|
+ row.formulaParts = row.formulaParts || [];
|
|
|
+ row.formulaParts.push(val);
|
|
|
+ row.formula = row.formulaParts.map((p) => `[${p}]`).join('');
|
|
|
+ row._paramSelect = null;
|
|
|
+ },
|
|
|
+ opSelectChange(val, row) {
|
|
|
+ if (!val) return;
|
|
|
+ row.formulaParts = row.formulaParts || [];
|
|
|
+ row.formulaParts.push(val);
|
|
|
+ row.formula = row.formulaParts.map((p) => `[${p}]`).join('');
|
|
|
+ row._opSelect = null;
|
|
|
+ },
|
|
|
+ tagItemDelete(index, row) {
|
|
|
+ row.formulaParts.splice(index, 1);
|
|
|
+ row.formula = row.formulaParts.map((p) => `[${p}]`).join('');
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
@@ -1212,4 +1524,11 @@
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ .formula-builder {
|
|
|
+ .formula-builder__selects {
|
|
|
+ margin-bottom: 6px;
|
|
|
+ display: flex;
|
|
|
+ }
|
|
|
+ }
|
|
|
</style>
|