Procházet zdrojové kódy

财务分析全部搞定,增加了水电费成本模型

wukai před 1 měsícem
rodič
revize
329e5c3bca
1 změnil soubory, kde provedl 270 přidání a 132 odebrání
  1. 270 132
      src/views/lean/cost/fs2.vue

+ 270 - 132
src/views/lean/cost/fs2.vue

@@ -28,85 +28,117 @@
           >
           </el-date-picker>
         </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery">查询</el-button>
+        </el-form-item>
       </el-form>
     </el-card>
 
     <!-- 成本性态分析结果展示 -->
-    <el-card v-if="costAnalysisResult" class="cost-analysis-card" id="analysisCard">
+    <el-card v-if="costAnalysisResult || waterElectricityAnalysisResult" class="cost-analysis-card" id="analysisCard">
       <div slot="header" class="clearfix">
         <span>成本性态分析结果</span>
       </div>
-      <el-row :gutter="20">
-        <el-col :span="6">
-          <div class="analysis-item">
-            <div class="analysis-label">固定成本(a)</div>
-            <div class="analysis-value">{{ costAnalysisResult.fixedCost.toFixed(2) }} <span class="unit">元</span></div>
-          </div>
-        </el-col>
-        <el-col :span="6">
-          <div class="analysis-item">
-            <div class="analysis-label">单位变动成本(b)</div>
-            <div class="analysis-value">{{ costAnalysisResult.variableCostPerUnit.toFixed(2) }} <span class="unit">元/米</span></div>
-          </div>
-        </el-col>
-        <el-col :span="6">
-          <div class="analysis-item">
-            <div class="analysis-label">相关系数(R)</div>
-            <div class="analysis-value">{{ costAnalysisResult.correlationCoefficient.toFixed(4) }}</div>
-          </div>
-        </el-col>
-        <el-col :span="6">
-          <div class="analysis-item">
-            <div class="analysis-label">R方(R²)</div>
-            <div class="analysis-value">{{ costAnalysisResult.rSquared !== undefined ? costAnalysisResult.rSquared.toFixed(4) : 'N/A' }}</div>
-          </div>
-        </el-col>
-      </el-row>
-      <el-row :gutter="20" style="margin-top: 20px;">
-        <el-col :span="6">
-          <div class="analysis-item">
-            <div class="analysis-label">数据点数量</div>
-            <div class="analysis-value">{{ costAnalysisResult.dataPoints }} <span class="unit">个</span></div>
-          </div>
-        </el-col>
-        <el-col :span="6">
-          <div class="analysis-item">
-            <div class="analysis-label">F统计量</div>
-            <div class="analysis-value">{{ costAnalysisResult.fStatistic !== undefined ? costAnalysisResult.fStatistic.toFixed(2) : 'N/A' }}</div>
-          </div>
-        </el-col>
-        <el-col :span="6">
-          <div class="analysis-item">
-            <div class="analysis-label">显著性</div>
-            <div class="analysis-value">{{ costAnalysisResult.significance !== undefined ? costAnalysisResult.significance : 'N/A' }}
-              <div v-if="costAnalysisResult.pValue !== undefined && costAnalysisResult.pValue !== null" class="p-value">
-                (P = {{ costAnalysisResult.pValue.toFixed(6) }})
+      
+      <div class="analysis-container">
+        <!-- 总成本分析 -->
+        <div class="analysis-column" v-if="costAnalysisResult">
+          <div class="analysis-section">
+            <div class="section-title">总成本模型</div>
+            <div class="metrics-grid">
+              <div class="metric-card primary">
+                <div class="metric-label">固定成本(a)</div>
+                <div class="metric-value">{{ costAnalysisResult.fixedCost.toFixed(2) }} <span class="unit">元</span></div>
+              </div>
+              <div class="metric-card primary">
+                <div class="metric-label">单位变动成本(b)</div>
+                <div class="metric-value">{{ costAnalysisResult.variableCostPerUnit.toFixed(2) }} <span class="unit">元/米</span></div>
+              </div>
+              <div class="metric-card secondary">
+                <div class="metric-label">相关系数(R)</div>
+                <div class="metric-value">{{ costAnalysisResult.correlationCoefficient.toFixed(4) }}</div>
+              </div>
+              <div class="metric-card secondary">
+                <div class="metric-label">R方(R²)</div>
+                <div class="metric-value">{{ costAnalysisResult.rSquared !== undefined ? costAnalysisResult.rSquared.toFixed(4) : 'N/A' }}</div>
+              </div>
+              <div class="metric-card info">
+                <div class="metric-label">数据点数量</div>
+                <div class="metric-value">{{ costAnalysisResult.dataPoints }} <span class="unit">个</span></div>
+              </div>
+              <div class="metric-card info">
+                <div class="metric-label">F统计量</div>
+                <div class="metric-value">{{ costAnalysisResult.fStatistic !== undefined ? costAnalysisResult.fStatistic.toFixed(2) : 'N/A' }}</div>
+              </div>
+              <div class="metric-card warning">
+                <div class="metric-label">显著性</div>
+                <div class="metric-value">{{ costAnalysisResult.significance }}</div>
+                <div class="p-value" v-if="costAnalysisResult.pValue !== undefined && costAnalysisResult.pValue !== null">
+                  (P={{ formatPValue(costAnalysisResult.pValue) }})
+                </div>
+              </div>
+              <div class="metric-card warning">
+                <div class="metric-label">标准误差</div>
+                <div class="metric-value">{{ costAnalysisResult.standardError !== undefined ? costAnalysisResult.standardError.toFixed(2) : 'N/A' }}</div>
               </div>
             </div>
           </div>
-        </el-col>
-        <el-col :span="6">
-          <div class="analysis-item">
-            <div class="analysis-label">标准误差</div>
-            <div class="analysis-value">{{ costAnalysisResult.standardError !== undefined ? costAnalysisResult.standardError.toFixed(2) : 'N/A' }}</div>
-          </div>
-        </el-col>
-      </el-row>
-      <el-row :gutter="20" style="margin-top: 20px;">
-        <el-col :span="24">
-          <div class="model-display">
-            <div class="model-label">成本模型:</div>
-            <div class="model-formula">总成本 = {{ costAnalysisResult.fixedCost.toFixed(2) }} + {{ costAnalysisResult.variableCostPerUnit.toFixed(2) }} × 产量(米)</div>
+        </div>
+        
+        <!-- 分隔线 -->
+        <div class="divider"></div>
+        
+        <!-- 水电分析 -->
+        <div class="analysis-column" v-if="waterElectricityAnalysisResult">
+          <div class="analysis-section">
+            <div class="section-title">水电费成本模型</div>
+            <div class="metrics-grid">
+              <div class="metric-card primary">
+                <div class="metric-label">固定成本(a)</div>
+                <div class="metric-value">{{ waterElectricityAnalysisResult.fixedCost.toFixed(2) }} <span class="unit">元</span></div>
+              </div>
+              <div class="metric-card primary">
+                <div class="metric-label">单位变动成本(b)</div>
+                <div class="metric-value">{{ waterElectricityAnalysisResult.variableCostPerUnit.toFixed(2) }} <span class="unit">元/米</span></div>
+              </div>
+              <div class="metric-card secondary">
+                <div class="metric-label">相关系数(R)</div>
+                <div class="metric-value">{{ waterElectricityAnalysisResult.correlationCoefficient.toFixed(4) }}</div>
+              </div>
+              <div class="metric-card secondary">
+                <div class="metric-label">R方(R²)</div>
+                <div class="metric-value">{{ waterElectricityAnalysisResult.rSquared !== undefined ? waterElectricityAnalysisResult.rSquared.toFixed(4) : 'N/A' }}</div>
+              </div>
+              <div class="metric-card info">
+                <div class="metric-label">数据点数量</div>
+                <div class="metric-value">{{ waterElectricityAnalysisResult.dataPoints }} <span class="unit">个</span></div>
+              </div>
+              <div class="metric-card info">
+                <div class="metric-label">F统计量</div>
+                <div class="metric-value">{{ waterElectricityAnalysisResult.fStatistic !== undefined ? waterElectricityAnalysisResult.fStatistic.toFixed(2) : 'N/A' }}</div>
+              </div>
+              <div class="metric-card warning">
+                <div class="metric-label">显著性</div>
+                <div class="metric-value">{{ waterElectricityAnalysisResult.significance }}</div>
+                <div class="p-value" v-if="waterElectricityAnalysisResult.pValue !== undefined && waterElectricityAnalysisResult.pValue !== null">
+                  (P={{ formatPValue(waterElectricityAnalysisResult.pValue) }})
+                </div>
+              </div>
+              <div class="metric-card warning">
+                <div class="metric-label">标准误差</div>
+                <div class="metric-value">{{ waterElectricityAnalysisResult.standardError !== undefined ? waterElectricityAnalysisResult.standardError.toFixed(2) : 'N/A' }}</div>
+              </div>
+            </div>
           </div>
-        </el-col>
-      </el-row>
+        </div>
+      </div>
     </el-card>
 
     <el-card class="table-card">
       <div slot="header" class="clearfix">
         <span>成本数据明细</span>
         <div style="float: right;">
-          <el-button type="success" size="mini" @click="handlePredict" :disabled="!costAnalysisResult">成本预测</el-button>
+          <el-button type="success" size="mini" @click="handlePredict" :disabled="!costAnalysisResult || !waterElectricityAnalysisResult">成本预测</el-button>
         </div>
       </div>
       <el-table
@@ -184,17 +216,17 @@
             {{ formatNumber(scope.row.otherCost) }}
           </template>
         </el-table-column>
-        <el-table-column label="预测成本(元)" align="center" prop="predictedCost" width="120">
+        <el-table-column label="预测成本(元)" align="center" prop="predictedTotalCost" width="120">
           <template #default="scope">
-            <span :class="{ 'predicted-high': scope.row.predictedCost > scope.row.totalCost }">
-              {{ formatNumber(scope.row.predictedCost) }}
+            <span :class="{ 'predicted-high': scope.row.predictedTotalCost > scope.row.totalCost }">
+              {{ formatNumber(scope.row.predictedTotalCost) }}
             </span>
           </template>
         </el-table-column>
-        <el-table-column label="实际偏离(元)" align="center" prop="deviation" width="120">
+        <el-table-column label="预测水电费(元)" align="center" prop="predictedWaterElectricity" width="120">
           <template #default="scope">
-            <span :class="getDeviationClass(scope.row.deviation)">
-              {{ formatNumber(scope.row.deviation) }}
+            <span :class="{ 'predicted-high': scope.row.predictedWaterElectricity > scope.row.waterElectricity }">
+              {{ formatNumber(scope.row.predictedWaterElectricity) }}
             </span>
           </template>
         </el-table-column>
@@ -217,6 +249,7 @@ const total = ref(0);
 const originalRows = ref({}); // 用于保存原始数据,便于取消编辑时恢复
 const selectedQueryYear = ref(new Date().getFullYear().toString()); // 查询条件中的年份,默认当年,转为字符串
 const costAnalysisResult = ref(null); // 成本性态分析结果
+const waterElectricityAnalysisResult = ref(null); // 水电费分析结果
 
 // 获取车间名称
 function getWsName(wsValue) {
@@ -244,18 +277,16 @@ function handleYearChange(value) {
 
 // 产量变化处理
 function handleLengthChange(row) {
-  if (row.length && costAnalysisResult.value) {
-    const { fixedCost, variableCostPerUnit } = costAnalysisResult.value;
+  if (row.length && costAnalysisResult.value && waterElectricityAnalysisResult.value) {
+    const { fixedCost: totalFixedCost, variableCostPerUnit: totalVariableCostPerUnit } = costAnalysisResult.value;
+    const { fixedCost: waterElectricityFixedCost, variableCostPerUnit: waterElectricityVariableCostPerUnit } = waterElectricityAnalysisResult.value;
+    
     const length = parseFloat(row.length);
 
     if (!isNaN(length)) {
-      // 计算预测成本
-      row.predictedCost = fixedCost + variableCostPerUnit * length;
-
-      // 如果没有实际成本,则将预测成本作为显示成本
-      if (!row.totalCost) {
-        row.displayCost = row.predictedCost;
-      }
+      // 计算预测总成本和预测水电费
+      row.predictedTotalCost = totalFixedCost + totalVariableCostPerUnit * length;
+      row.predictedWaterElectricity = waterElectricityFixedCost + waterElectricityVariableCostPerUnit * length;
 
       // 自动执行成本预测
       nextTick(() => {
@@ -279,6 +310,21 @@ function formatNumber(value) {
   return num.toFixed(2);
 }
 
+// 格式化P值,避免科学计数法
+function formatPValue(pValue) {
+  if (pValue === null || pValue === undefined || isNaN(pValue)) {
+    return 'N/A';
+  }
+  
+  // 对于非常小的数值,使用toPrecision来避免科学计数法
+  if (pValue < 0.000001) {
+    return pValue.toPrecision(6);
+  }
+  
+  // 对于一般的数值,保留6位小数
+  return pValue.toFixed(6);
+}
+
 // 获取偏离值的样式类
 function getDeviationClass(deviation) {
   if (deviation === null || deviation === undefined || deviation === '') {
@@ -299,16 +345,16 @@ function getDeviationClass(deviation) {
 }
 
 // 成本性态分析函数 - 使用最小二乘法进行线性回归分析
-function performCostAnalysis(data) {
+function performCostAnalysis(data, property = 'totalCost') {
   // 过滤掉无效数据
-  const validData = data.filter(item =>
-    item.length !== null &&
-    item.totalCost !== null &&
-    !isNaN(parseFloat(item.length)) &&
-    !isNaN(parseFloat(item.totalCost))
+  const validData = data.filter(item => 
+    item.length !== null && 
+    item[property] !== null &&
+    !isNaN(parseFloat(item.length)) && 
+    !isNaN(parseFloat(item[property]))
   ).map(item => ({
     x: parseFloat(item.length), // 产量
-    y: parseFloat(item.totalCost) // 总成本
+    y: parseFloat(item[property]) // 成本(总成本或水电费)
   }));
 
   if (validData.length < 2) {
@@ -329,7 +375,7 @@ function performCostAnalysis(data) {
 
   // 计算回归系数
   const denominator = n * sumXX - sumX * sumX;
-
+  
   if (denominator === 0) {
     return null; // 无法计算(分母为零)
   }
@@ -341,7 +387,7 @@ function performCostAnalysis(data) {
   // 计算相关系数
   const numerator = n * sumXY - sumX * sumY;
   const denominatorR = Math.sqrt((n * sumXX - sumX * sumX) * (n * sumYY - sumY * sumY));
-
+  
   const r = denominatorR !== 0 ? numerator / denominatorR : 0; // 相关系数
 
   // 计算R方 (决定系数)
@@ -351,25 +397,25 @@ function performCostAnalysis(data) {
   let ssTotal = 0; // 总平方和
   let ssResidual = 0; // 残差平方和
   let ssRegression = 0; // 回归平方和
-
+  
   const yMean = sumY / n; // y的均值
-
+  
   validData.forEach(point => {
     const yPredicted = a + b * point.x; // 预测值
     ssTotal += Math.pow(point.y - yMean, 2);
     ssResidual += Math.pow(point.y - yPredicted, 2);
   });
-
+  
   ssRegression = ssTotal - ssResidual;
-
+  
   // 计算标准误差
   const standardError = Math.sqrt(ssResidual / (n - 2));
-
+  
   // 计算F统计量 (ANOVA F-test)
   const msRegression = ssRegression / 1; // 回归均方 (自由度为1)
   const msResidual = ssResidual / (n - 2); // 残差均方 (自由度为n-2)
   const fStatistic = msRegression / msResidual;
-
+  
   // 计算 P 值 (使用近似计算)
   const pValue = calculatePValue(fStatistic, 1, n - 2);
 
@@ -542,32 +588,35 @@ function generateFullYearData(data, year) {
 
 // 执行成本预测
 function performCostForecast() {
-  if (!costAnalysisResult.value) {
+  if (!costAnalysisResult.value || !waterElectricityAnalysisResult.value) {
     proxy.$modal.msgWarning("请先进行成本性态分析");
     return;
   }
-
-  const { fixedCost, variableCostPerUnit } = costAnalysisResult.value;
-
-  // 为每个数据项计算预测成本和实际偏离
+  
+  const { fixedCost: totalFixedCost, variableCostPerUnit: totalVariableCostPerUnit } = costAnalysisResult.value;
+  const { fixedCost: waterElectricityFixedCost, variableCostPerUnit: waterElectricityVariableCostPerUnit } = waterElectricityAnalysisResult.value;
+  
+  // 为每个数据项计算预测成本
   costList.value = costList.value.map(item => {
     // 如果有产量数据,则进行预测
     if (item.length !== null && !isNaN(parseFloat(item.length))) {
-      const predictedCost = fixedCost + variableCostPerUnit * parseFloat(item.length);
-      const totalCost = item.totalCost !== null && !isNaN(parseFloat(item.totalCost)) ? parseFloat(item.totalCost) : null;
-      const deviation = totalCost !== null ? totalCost - predictedCost : null;
-
+      const length = parseFloat(item.length);
+      
+      // 计算预测总成本和预测水电费
+      const predictedTotalCost = totalFixedCost + totalVariableCostPerUnit * length;
+      const predictedWaterElectricity = waterElectricityFixedCost + waterElectricityVariableCostPerUnit * length;
+      
       return {
         ...item,
-        predictedCost: predictedCost,
-        deviation: deviation
+        predictedTotalCost: predictedTotalCost,
+        predictedWaterElectricity: predictedWaterElectricity
       };
     } else {
-      // 如果没有产量数据,则预测和偏离都为空
+      // 如果没有产量数据,则预测为空
       return {
         ...item,
-        predictedCost: null,
-        deviation: null
+        predictedTotalCost: null,
+        predictedWaterElectricity: null
       };
     }
   });
@@ -626,7 +675,7 @@ function getList() {
   listCost(queryParam).then(response => {
     // 生成完整的12个月数据
     const fullYearData = generateFullYearData(response.rows, selectedQueryYear.value);
-
+    
     costList.value = fullYearData.map(item => ({
       ...item,
       isEditing: true, // 查询出来的数据也默认处于编辑状态
@@ -642,13 +691,21 @@ function getList() {
     });
 
     // 执行成本性态分析
-    costAnalysisResult.value = performCostAnalysis(costList.value.filter(item =>
-      item.length !== null &&
+    costAnalysisResult.value = performCostAnalysis(costList.value.filter(item => 
+      item.length !== null && 
       item.totalCost !== null &&
-      !isNaN(parseFloat(item.length)) &&
+      !isNaN(parseFloat(item.length)) && 
       !isNaN(parseFloat(item.totalCost))
     ));
 
+    // 执行水电费分析
+    waterElectricityAnalysisResult.value = performCostAnalysis(costList.value.filter(item => 
+      item.length !== null && 
+      item.waterElectricity !== null &&
+      !isNaN(parseFloat(item.length)) && 
+      !isNaN(parseFloat(item.waterElectricity))
+    ), 'waterElectricity');
+
     // 执行成本预测
     if (costAnalysisResult.value) {
       performCostForecast();
@@ -685,11 +742,11 @@ function handleQuery() {
 
 // 成本预测处理
 function handlePredict() {
-  if (!costAnalysisResult.value) {
+  if (!costAnalysisResult.value || !waterElectricityAnalysisResult.value) {
     proxy.$modal.msgWarning("没有足够的数据进行成本预测,请先查询数据");
     return;
   }
-
+  
   performCostForecast();
   proxy.$modal.msgSuccess("成本预测完成");
 }
@@ -733,38 +790,103 @@ onMounted(() => {
   color: #333;
 }
 
-.analysis-item {
-  background: #f5f7fa;
-  border-radius: 4px;
-  padding: 15px;
+.analysis-container {
+  display: flex;
+  justify-content: space-between;
+}
+
+.analysis-column {
+  flex: 1;
+  padding: 0 10px;
+}
+
+.divider {
+  width: 1px;
+  background-color: #ebeef5;
+  margin: 0 20px;
+}
+
+.section-title {
+  font-size: 18px;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 15px;
   text-align: center;
-  height: 100px;
+  padding-bottom: 10px;
+  border-bottom: 1px solid #ebeef5;
+}
+
+.metrics-grid {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 15px;
+  margin-top: 15px;
+}
+
+.metric-card {
+  background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
+  border-radius: 8px;
+  padding: 15px 10px;
+  text-align: center;
+  transition: all 0.3s ease;
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
+  border: 1px solid #e9ecef;
   display: flex;
   flex-direction: column;
   justify-content: center;
-  transition: all 0.3s;
 }
 
-.analysis-item:hover {
-  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+.metric-card:hover {
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
   transform: translateY(-2px);
+  border-color: #adb5bd;
+}
+
+.metric-card.primary {
+  background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
+  border-color: #90caf9;
+}
+
+.metric-card.secondary {
+  background: linear-gradient(135deg, #f3e5f5 0%, #e1bee7 100%);
+  border-color: #ce93d8;
 }
 
-.analysis-label {
-  font-size: 14px;
-  color: #606266;
+.metric-card.info {
+  background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
+  border-color: #a5d6a7;
+}
+
+.metric-card.warning {
+  background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
+  border-color: #ffcc80;
+}
+
+.metric-label {
+  font-size: 12px;
+  color: #495057;
   margin-bottom: 8px;
+  font-weight: 500;
 }
 
-.analysis-value {
-  font-size: 18px;
+.metric-value {
+  font-size: 16px;
   font-weight: bold;
-  color: #333;
+  color: #212529;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
 
 .unit {
-  font-size: 14px;
-  color: #999;
+  font-size: 12px;
+  color: #6c757d;
+}
+
+.p-value {
+  font-size: 11px;
+  color: #666;
+  margin-top: 3px;
 }
 
 .model-display {
@@ -810,9 +932,25 @@ onMounted(() => {
   font-weight: bold;
 }
 
-.p-value {
-  font-size: 12px;
-  color: #666;
-  margin-top: 2px;
+@media (max-width: 1200px) {
+  .metrics-grid {
+    grid-template-columns: repeat(2, 1fr);
+  }
+}
+
+@media (max-width: 768px) {
+  .analysis-container {
+    flex-direction: column;
+  }
+  
+  .divider {
+    width: 100%;
+    height: 1px;
+    margin: 20px 0;
+  }
+  
+  .metrics-grid {
+    grid-template-columns: repeat(2, 1fr);
+  }
 }
 </style>