Jelajahi Sumber

记录规则生产统计类别添加

lucw 7 bulan lalu
induk
melakukan
7a410b516f

+ 324 - 5
src/views/rulesManagement/releaseRules/components/permitAdd.vue

@@ -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>