zck 1 день назад
Родитель
Сommit
f75eb2b9a4

+ 9 - 0
src/api/jixiaoguanli/caiwubaobiao.js

@@ -249,4 +249,13 @@ export const CompletionWorkOrderCostList = (params) => {
   })
 }
 
+//v23财务--完工工单工序成本明细(按工单编号查成本v23_月度成本明细)
+export const getCompletionWorkOrderCostDetailList = (params) => {
+  return service({
+    url:'/mes_server/Cost_Accounting/getCompletionWorkOrderCostDetailList',
+    method: 'get',
+    params
+  })
+}
+
 

+ 116 - 0
src/api/mes/job.js

@@ -1131,6 +1131,15 @@ export const PisbatchDelete = (data) => {
   })
 }
 
+//查询任意时间段的汇总数据
+export const getRangeSummary = (params) => {
+  return service({
+    url: '/mes_server/production_information_statistics/getRangeSummary',
+    method: 'get',
+    params
+  })
+}
+
 //导出车间生产信息表Excel
 export const ToExcel = (params) => {
   return service({
@@ -1139,3 +1148,110 @@ export const ToExcel = (params) => {
     params
   })
 }
+
+export const csbatch = (params) => {
+  return service({
+    url: '/mes_server/demoss/batch',
+    method: 'get',
+    params
+  })
+}
+
+
+
+
+// 客户反馈与投诉记录 - 左侧菜单栏
+export const getCustomerFeedbackTab = (params) => {
+  return service({
+    url: '/mes_server/customer_feedback/getTab',
+    method: 'get',
+    params
+  })
+}
+
+// 客户反馈与投诉记录 - 右侧表格数据
+export const getCustomerFeedbackList = (params) => {
+  return service({
+    url: '/mes_server/customer_feedback/getList',
+    method: 'get',
+    params
+  })
+}
+
+// 客户反馈与投诉记录 - 新增
+export const addCustomerFeedback = (data) => {
+  return service({
+    url: '/mes_server/customer_feedback/add',
+    method: 'post',
+    data
+  })
+}
+
+// 客户反馈与投诉记录 - 修改
+export const editCustomerFeedback = (data) => {
+  return service({
+    url: '/mes_server/customer_feedback/edit',
+    method: 'post',
+    data
+  })
+}
+
+// 客户反馈与投诉记录 - 删除(批量 / 单条)
+export const delCustomerFeedback = (data) => {
+  return service({
+    url: '/mes_server/customer_feedback/del',
+    method: 'post',
+    data
+  })
+}
+
+// 客户反馈与投诉记录 - 处理环节列表
+export const getCustomerFeedbackFlowList = (params) => {
+  return service({
+    url: '/mes_server/customer_feedback/getFlowList',
+    method: 'get',
+    params
+  })
+}
+// 客户反馈与投诉记录 - 新增处理环节
+export const addCustomerFeedbackFlow = (data) => {
+  return service({
+    url: '/mes_server/customer_feedback/addFlow',
+    method: 'post',
+    data
+  })
+}
+// 客户反馈与投诉记录 - 删除处理环节
+export const delCustomerFeedbackFlow = (data) => {
+  return service({
+    url: '/mes_server/customer_feedback/delFlow',
+    method: 'post',
+    data
+  })
+}
+
+// 客户反馈与投诉记录 - 整改附件列表
+export const getCustomerFeedbackAttachmentList = (params) => {
+  return service({
+    url: '/mes_server/customer_feedback/getAttachmentList',
+    method: 'get',
+    params
+  })
+}
+// 客户反馈与投诉记录 - 新增整改附件
+export const addCustomerFeedbackAttachment = (formData) => {
+  return service({
+    url: '/mes_server/customer_feedback/addAttachment',
+    method: 'post',
+    data: formData,
+    headers: { 'Content-Type': 'multipart/form-data' }
+  })
+}
+// 客户反馈与投诉记录 - 删除整改附件
+export const delCustomerFeedbackAttachment = (data) => {
+  return service({
+    url: '/mes_server/customer_feedback/delAttachment',
+    method: 'post',
+    data
+  })
+}

+ 1 - 0
src/main.js

@@ -2,6 +2,7 @@ import 'element-plus/es/components/message/style/css'
 import 'element-plus/es/components/loading/style/css'
 import 'element-plus/es/components/notification/style/css'
 import 'element-plus/es/components/message-box/style/css'
+import 'element-plus/es/components/checkbox/style/css'
 import './style/element_visiable.scss'
 import { createApp } from 'vue'
 // 引入gin-vue-admin前端初始化相关内容

+ 973 - 28
src/view/quality/Customerfeedback.vue

@@ -1,67 +1,1012 @@
 <template>
   <div>
+    <!-- 按钮区 -->
+    <header class="cf-header">
+      <el-button type="primary" @click="onADD">新增</el-button>
+      <el-button type="primary" :disabled="selectionRows.length === 0" @click="onDel">删除</el-button>
+    </header>
+
     <el-container>
-      <el-aside width="200px">
+      <el-aside width="260px">
         <div class="JKWTree-tree">
-          <h3> 客户反馈与投诉记录</h3>
-          <el-tree :data="treeData" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
+          <h3>客户反馈与投诉记录</h3>
+          <el-tree
+            :data="treeData"
+            :props="defaultProps"
+            @node-click="handleNodeClick"
+            :expand-on-click-node="false"
+            node-key="label"
+            default-expand-all
+          ></el-tree>
         </div>
       </el-aside>
       <el-container>
-
         <el-main>
+          <el-table
+            ref="multipleTable"
+            style="width: 100%; height: 45vh"
+            tooltip-effect="dark"
+            :row-style="{ height: '25px' }"
+            :header-cell-style="{ padding: '0px' }"
+            :cell-style="{ padding: '0px' }"
+            :header-row-style="{ height: '22px' }"
+            :data="tableData"
+            border
+            row-key="UniqID"
+            size="small"
+            highlight-current-row="true"
+            @row-click="tableRowClick"
+            @row-dblclick="onEdit"
+            @selection-change="handleSelectionChange"
+            :show-overflow-tooltip="true"
+          >
+            <el-table-column type="selection" width="45" align="center" />
+            <el-table-column sortable align="center" label="客诉单号"   prop="客诉单号"   width="120" />
+            <el-table-column sortable align="center" label="客诉日期"   prop="客诉日期"   width="120" />
+            <el-table-column sortable align="center" label="客诉方式"   prop="客诉方式"   width="100"  />
+            <el-table-column sortable align="center" label="订单编号"   prop="订单编号"   width="120" />
+            <el-table-column sortable align="center" label="产品编号"   prop="产品编号"   width="100" />
+            <el-table-column sortable align="center" label="产品名称"   prop="产品名称"   width="200" />
+            <el-table-column sortable align="center" label="客户编号"   prop="客户编号"   width="100" />
+            <el-table-column sortable align="center" label="客户名称"   prop="客户名称"   width="160" />
+            <el-table-column sortable align="center" label="客诉描述"   prop="客诉描述"   min-width="260" />
+            <el-table-column sortable align="center" label="缺陷关键字" prop="缺陷关键字" width="120" />
+            <el-table-column sortable align="center" label="客诉性质"   prop="客诉性质"   width="100"  />
+            <el-table-column sortable align="center" label="扣分"       prop="扣分"       width="70"  />
+            <el-table-column sortable align="center" label="主要责任部门" prop="主要责任部门" width="160" />
+            <el-table-column sortable align="center" label="次要责任部门" prop="次要责任部门" width="160" />
+            <!-- <el-table-column sortable align="center" label="负责人A"   prop="负责人A"   width="130" />
+            <el-table-column sortable align="center" label="负责人B"   prop="负责人B"   width="130" />
+            <el-table-column sortable align="center" label="关闭状态"   prop="关闭状态"   width="100" /> -->
+          </el-table>
+
+          <!-- ============ 下方 Tab:处理环节记录 / 相关整改附件 ============ -->
+          <el-tabs v-model="bottomTab" type="card" style="margin-top: 8px;" @tab-click="onBottomTabClick">
+            <el-tab-pane label="处理环节记录" name="flow">
+              <div style="display: flex; gap: 6px; margin-bottom: 6px;">
+                <el-button size="small" type="primary" @click="onFlowAdd">新增环节</el-button>
+                <el-button size="small" type="danger" :disabled="flowSelection.length === 0" @click="onFlowDel">删除</el-button>
+                <span style="margin-left: auto; color: #909399; font-size: 12px;" v-if="!currentRow">请先在上方选择一条客诉记录</span>
+              </div>
+              <el-table
+                :data="flowList"
+                border
+                size="small"
+                style="height: 200px;"
+                :header-row-style="{ height: '22px' }"
+                :row-style="{ height: '23px' }"
+                @selection-change="(rows) => { flowSelection = rows }"
+                row-key="UniqId"
+                highlight-current-row
+              >
+                <el-table-column type="selection" width="40" align="center" />
+                <el-table-column type="index" label="序号" width="55" align="center" />
+                <el-table-column label="操作者" prop="操作者" width="120" />
+                <el-table-column label="处理环节" prop="处理环节" width="120" />
+                <el-table-column label="备注说明" prop="备注说明" />
+                <el-table-column label="建档人" prop="sys_id" width="100" />
+                <el-table-column label="建档时间" prop="sys_rq" width="160" />
+                <el-table-column label="修改时间" prop="mod_rq" width="160" />
+              </el-table>
+            </el-tab-pane>
+
+            <el-tab-pane label="相关整改附件" name="attachment">
+              <div style="display: flex; gap: 6px; margin-bottom: 6px;">
+                <el-button size="small" type="primary" @click="onAttachmentAdd">新增附件</el-button>
+                <el-button size="small" type="danger" :disabled="attSelection.length === 0" @click="onAttachmentDel">删除</el-button>
+                <span style="margin-left: auto; color: #909399; font-size: 12px;" v-if="!currentRow">请先在上方选择一条客诉记录</span>
+              </div>
+              <el-table
+                :data="attachmentList"
+                border
+                size="small"
+                style="height: 200px;"
+                :header-row-style="{ height: '22px' }"
+                :row-style="{ height: '23px' }"
+                @selection-change="(rows) => { attSelection = rows }"
+                row-key="UniqId"
+                highlight-current-row
+              >
+                <el-table-column type="selection" width="40" align="center" />
+                <el-table-column type="index" label="序号" width="55" align="center" />
+                <el-table-column label="名称及路径" prop="名称及路径" min-width="200" show-overflow-tooltip />
+                <el-table-column label="修订日期" prop="修订日期" width="160" />
+                <el-table-column label="附件类型" prop="附件类型" width="90" />
+                <el-table-column label="建档人" prop="sys_id" width="100" />
+                <el-table-column label="建档时间" prop="sys_rq" width="160" />
+                <el-table-column label="修改时间" prop="mod_rq" width="160" />
+              </el-table>
+            </el-tab-pane>
+          </el-tabs>
         </el-main>
       </el-container>
     </el-container>
+
+    <!-- ============ 新增/修改 弹窗 ============ -->
+    <el-dialog
+      v-model="formVisible"
+      :title="isEdit ? '修改客诉记录' : '新增客诉记录'"
+      width="800px"
+      destroy-on-close
+      @close="closeForm"
+      @opened="onFormOpened"
+    >
+      <el-form :model="formData" ref="formRef" label-width="95px" size="default">
+        <!-- 第一行 -->
+        <el-row :gutter="15">
+          <el-col :span="8">
+            <el-form-item label="客诉单号">
+              <el-input
+                id="f_客诉单号"
+                v-model="formData.客诉单号"
+                placeholder="如 / 则留空"
+                @keydown="(e) => handleFieldKeydown(e, 'f_客诉单号')"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="10">
+            <el-form-item label="客诉日期">
+              <el-input
+                id="f_客诉日期"
+                v-model="formData.客诉日期"
+                placeholder="回车为当天"
+                readonly
+                @keydown="(e) => handleFieldKeydown(e, 'f_客诉日期')"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="方式">
+              <el-input
+                id="f_客诉方式"
+                v-model="formData.客诉方式"
+                placeholder="回车选择"
+                readonly
+                @keydown="(e) => handleFieldKeydown(e, 'f_客诉方式')"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 第二行 -->
+        <el-row :gutter="15">
+          <el-col :span="8">
+            <el-form-item label="订单编号">
+              <el-input
+                id="f_订单编号"
+                v-model="formData.订单编号"
+                @keydown="(e) => handleFieldKeydown(e, 'f_订单编号')"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="5">
+            <el-form-item label="产品编号">
+              <el-input
+                id="f_产品编号"
+                v-model="formData.产品编号"
+                placeholder="回车选产品"
+                @keydown="(e) => handleFieldKeydown(e, 'f_产品编号')"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="产品名称">
+              <el-input
+                id="f_产品名称"
+                v-model="formData.产品名称"
+                @keydown="(e) => handleFieldKeydown(e, 'f_产品名称')"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 第三行 -->
+        <el-row :gutter="15">
+          <el-col :span="5">
+            <el-form-item label="客户编号">
+              <el-input
+                id="f_客户编号"
+                v-model="formData.客户编号"
+                placeholder="回车选客户"
+                @keydown="(e) => handleFieldKeydown(e, 'f_客户编号')"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="客户名称">
+              <el-input
+                id="f_客户名称"
+                v-model="formData.客户名称"
+                @keydown="(e) => handleFieldKeydown(e, 'f_客户名称')"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 第四行 -->
+        <el-row :gutter="15">
+          <el-col :span="24">
+            <el-form-item label="反馈问题">
+              <el-input
+                id="f_客诉描述"
+                v-model="formData.客诉描述"
+                type="textarea"
+                :rows="4"
+                placeholder="请输入反馈问题"
+                @keydown="(e) => handleFieldKeydown(e, 'f_客诉描述')"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 第五行 -->
+        <el-row :gutter="15">
+          <el-col :span="10">
+            <el-form-item label="缺陷关键字">
+              <el-input
+                id="f_缺陷关键字"
+                v-model="formData.缺陷关键字"
+                @keydown="(e) => handleFieldKeydown(e, 'f_缺陷关键字')"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="性质">
+              <el-input
+                id="f_客诉性质"
+                v-model="formData.客诉性质"
+                placeholder="回车选择"
+                readonly
+                @keydown="(e) => handleFieldKeydown(e, 'f_客诉性质')"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="扣分">
+              <el-input
+                id="f_扣分"
+                v-model.number="formData.扣分"
+                @keydown="(e) => handleFieldKeydown(e, 'f_扣分')"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 第六行 -->
+        <el-row :gutter="15">
+          <el-col :span="12">
+            <el-form-item label="主责部门">
+              <el-input
+                id="f_主要责任部门"
+                v-model="formData.主要责任部门"
+                @keydown="(e) => handleFieldKeydown(e, 'f_主要责任部门')"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="次责部门">
+              <el-input
+                id="f_次要责任部门"
+                v-model="formData.次要责任部门"
+                @keydown="(e) => handleFieldKeydown(e, 'f_次要责任部门')"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+
+      <template #footer>
+        <el-button @click="formVisible = false">取消</el-button>
+        <el-button type="primary" @click="onSubmit">确定</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- ============ 方式 选择小弹窗 ============ -->
+    <el-dialog v-model="wayVisible" title="选择方式" width="300px" @opened="onWayOpened">
+      <div style="display: flex; justify-content: space-around; padding: 20px 0;">
+        <el-button id="way_书面" @click="selectWay('书面')">书面</el-button>
+        <el-button id="way_口头" @click="selectWay('口头')">口头</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- ============ 性质 选择小弹窗 ============ -->
+    <el-dialog v-model="natureVisible" title="选择性质" width="300px" @opened="onNatureOpened">
+      <div style="display: flex; justify-content: space-around; padding: 20px 0;">
+        <el-button @click="selectNature('A类')">A类</el-button>
+        <el-button @click="selectNature('B类')">B类</el-button>
+        <el-button @click="selectNature('C类')">C类</el-button>
+        <el-button @click="selectNature('D类')">D类</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- ============ 产品编号 选择弹窗 ============ -->
+    <el-dialog
+      v-model="productModal"
+      title="查询产品名称 (输入) 按 Enter 搜索"
+      width="720px"
+      @opened="onProductModalOpened"
+      @close="closeProductModal"
+      @keydown="handleProductModalKeydown"
+    >
+      <el-input
+        v-model="productKeyword"
+        placeholder="输入关键字后回车搜索"
+        @keyup.enter="loadProductList"
+        ref="productKeywordInput"
+      />
+      <el-table
+        ref="productTableRef"
+        :data="productList"
+        border
+        style="margin-top: 10px; height: 360px;"
+        highlight-current-row
+        @row-click="pickProduct"
+        @row-dblclick="pickProduct"
+        row-key="产品编号"
+      >
+        <el-table-column prop="产品编号" label="产品编号" width="160" />
+        <el-table-column prop="产品名称" label="产品名称" />
+      </el-table>
+      <template #footer>
+        <el-button @click="productModal = false">放弃</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- ============ 客户编号 选择弹窗 ============ -->
+    <el-dialog
+      v-model="clientModal"
+      title="查询客户编号 (输入) 按 Enter 搜索"
+      width="620px"
+      @opened="onClientModalOpened"
+      @close="closeClientModal"
+      @keydown="handleClientModalKeydown"
+    >
+      <el-input
+        v-model="clientKeyword"
+        placeholder="输入关键字后回车搜索"
+        @keyup.enter="loadClientList"
+        ref="clientKeywordInput"
+      />
+      <el-table
+        ref="clientTableRef"
+        :data="clientList"
+        border
+        style="margin-top: 10px; height: 360px;"
+        highlight-current-row
+        @row-click="pickClient"
+        @row-dblclick="pickClient"
+        row-key="客户编号"
+      >
+        <el-table-column prop="客户编号" label="客户编号" width="160" />
+        <el-table-column prop="客户名称" label="客户名称" />
+      </el-table>
+      <template #footer>
+        <el-button @click="clientModal = false">放弃</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- ============ 新增处理环节 弹窗 ============ -->
+    <el-dialog v-model="flowFormVisible" title="新增处理环节" width="480px" destroy-on-close>
+      <el-form :model="flowForm" label-width="80px">
+        <el-form-item label="操作者">
+          <el-input v-model="flowForm.操作者" readonly />
+        </el-form-item>
+        <el-form-item label="处理环节">
+          <el-select v-model="flowForm.处理环节" placeholder="请选择处理环节" style="width: 100%;">
+            <el-option label="客诉发起" value="客诉发起" />
+            <el-option label="客诉处理中" value="客诉处理中" />
+            <el-option label="处理完毕" value="处理完毕" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="备注说明">
+          <el-input v-model="flowForm.备注说明" type="textarea" :rows="3" placeholder="请输入备注说明" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="flowFormVisible = false">取消</el-button>
+        <el-button type="primary" @click="onFlowSubmit">确定</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- ============ 新增整改附件 弹窗 ============ -->
+    <el-dialog v-model="attFormVisible" title="新增整改附件" width="480px" destroy-on-close>
+      <el-form :model="attForm" label-width="80px">
+        <el-form-item label="名称及路径">
+          <el-input v-model="attForm.名称及路径" placeholder="选择文件后自动填充" readonly />
+        </el-form-item>
+        <el-form-item label="附件类型">
+          <el-input v-model="attForm.附件类型" placeholder="如 PDF/DOC/XLSX" readonly />
+        </el-form-item>
+        <el-form-item label="选择文件">
+          <input type="file" @change="onAttachmentFileChange" style="width: 100%;" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="attFormVisible = false">取消</el-button>
+        <el-button type="primary" @click="onAttachmentSubmit">确定上传</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
+
 <script setup>
-import {
-  getDepartment,
-  getMachineInfo
-} from '@/api/mes/job'
-import {
-  createCompany,
-  deleteCompany,
-  deleteCompanyByIds,
-  updateCompany,
-  findCompany,
-  getCompanyList
-} from '@/api/company'
-// 全量引入格式化工具 请按需保留
-import { getDictFunc, formatDate, formatBoolean, filterDict, ReturnArrImg, onDownloadFile } from '@/utils/format'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { ref, reactive } from 'vue'
+import { ref, reactive, onMounted, nextTick } from 'vue'
+import dayjs from 'dayjs'
+import { ClientList, ProductCodeList } from '@/api/yunyin/yunying'
 import { useUserStore } from '@/pinia/modules/user'
+import {
+  getCustomerFeedbackTab,
+  getCustomerFeedbackList,
+  addCustomerFeedback,
+  editCustomerFeedback,
+  delCustomerFeedback,
+  getCustomerFeedbackFlowList,
+  addCustomerFeedbackFlow,
+  delCustomerFeedbackFlow,
+  getCustomerFeedbackAttachmentList,
+  addCustomerFeedbackAttachment,
+  delCustomerFeedbackAttachment
+} from '@/api/mes/job'
+
+defineOptions({ name: 'Customerfeedback' })
+
 const userStore = useUserStore()
-const sys_id='['+userStore.userInfo.userName+'/'+userStore.userInfo.nickName+']'
-defineOptions({
-    name: 'Company'
+const _username = ref('')
+_username.value = userStore.userInfo.userName + '/' + userStore.userInfo.nickName
+
+// =========== 左侧树 ===========
+const treeData = ref([])
+const defaultProps = { children: 'children', label: 'label' }
+
+const getMenuData = async () => {
+  try {
+    const response = await getCustomerFeedbackTab()
+    if (response.code === 0) treeData.value = response.data
+  } catch (error) { console.error(error) }
+}
+
+// =========== 右侧表格 ===========
+const tableData = reactive([])
+const selectionRows = ref([])
+const lastLoadParams = ref({})
+
+const loadList = async (params = {}) => {
+  lastLoadParams.value = params
+  try {
+    const response = await getCustomerFeedbackList(params)
+    if (response.code === 0) {
+      tableData.splice(0, tableData.length, ...(response.data || []))
+    }
+  } catch (error) { console.error(error) }
+}
+
+const handleNodeClick = async (node) => {
+  if (!node.type) return
+  await loadList({ type: node.type, code: node.code || '', name: node.name || '' })
+}
+
+// =========== 当前选中的主表行 ===========
+const currentRow = ref(null)
+
+// =========== 下方 Tab ===========
+const bottomTab = ref('flow')
+const flowList = ref([])
+const attachmentList = ref([])
+const flowSelection = ref([])
+const attSelection = ref([])
+
+const tableRowClick = (row) => {
+  currentRow.value = row
+  if (bottomTab.value === 'flow') {
+    loadFlowList()
+  } else {
+    loadAttachmentList()
+  }
+}
+const handleSelectionChange = (rows) => { selectionRows.value = rows }
+
+const onBottomTabClick = () => {
+  if (!currentRow.value) return
+  if (bottomTab.value === 'flow') {
+    loadFlowList()
+  } else {
+    loadAttachmentList()
+  }
+}
+
+const loadFlowList = async () => {
+  if (!currentRow.value) return
+  try {
+    const res = await getCustomerFeedbackFlowList({ RelationID: currentRow.value.UniqID })
+    if (res.code === 0) flowList.value = res.data || []
+  } catch (e) { console.error(e) }
+}
+const loadAttachmentList = async () => {
+  if (!currentRow.value) return
+  try {
+    const res = await getCustomerFeedbackAttachmentList({ relationId: currentRow.value.UniqID })
+    if (res.code === 0) attachmentList.value = res.data || []
+  } catch (e) { console.error(e) }
+}
+
+// =========== 处理环节:新增 / 删除 ===========
+const flowFormVisible = ref(false)
+const flowForm = reactive({ 操作者: '', 处理环节: '', 备注说明: '' })
+
+const onFlowAdd = () => {
+  if (!currentRow.value) { ElMessage.warning('请先选择主表记录'); return }
+  // 操作者默认当前登录人(账号/姓名),无需手填;处理环节从下拉选择
+  Object.assign(flowForm, { 操作者: _username.value, 处理环节: '', 备注说明: '' })
+  flowFormVisible.value = true
+}
+const onFlowSubmit = async () => {
+  if (!flowForm.处理环节) { ElMessage.warning('请选择处理环节'); return }
+  try {
+    const res = await addCustomerFeedbackFlow({
+      RelationID: currentRow.value.UniqID,
+      操作者: flowForm.操作者,
+      处理环节: flowForm.处理环节,
+      备注说明: flowForm.备注说明,
+      sys_id: userStore.userInfo.userName
+    })
+    if (res.code === 0) {
+      ElMessage.success('新增成功')
+      flowFormVisible.value = false
+      await loadFlowList()
+      // 若标记为处理完毕,该客诉应从处理中客诉移除,同步刷新主表与左侧树
+      if (flowForm.处理环节 === '处理完毕') {
+        await loadList(lastLoadParams.value)
+        await getMenuData()
+      }
+    } else {
+      ElMessage.error(res.msg || '新增失败')
+    }
+  } catch (e) { console.error(e) }
+}
+const onFlowDel = async () => {
+  if (flowSelection.value.length === 0) return
+  try {
+    await ElMessageBox.confirm(`确定删除选中的 ${flowSelection.value.length} 条环节记录吗?`, '提示', { type: 'warning' })
+    for (const row of flowSelection.value) {
+      await delCustomerFeedbackFlow({ UniqId: row.UniqId })
+    }
+    ElMessage.success('删除成功')
+    await loadFlowList()
+  } catch (_) {}
+}
+
+// =========== 整改附件:新增 / 删除 ===========
+const attFormVisible = ref(false)
+const attForm = reactive({ 名称及路径: '', 附件类型: '', 附件内容: null })
+
+const onAttachmentAdd = () => {
+  if (!currentRow.value) { ElMessage.warning('请先选择主表记录'); return }
+  Object.assign(attForm, { 名称及路径: '', 附件类型: '', 附件内容: null })
+  attFormVisible.value = true
+}
+const onAttachmentFileChange = (event) => {
+  const file = event.target.files[0]
+  if (!file) return
+  attForm.附件内容 = file
+  attForm.名称及路径 = file.name
+  // 根据扩展名推断附件类型
+  const ext = file.name.split('.').pop().toUpperCase()
+  attForm.附件类型 = ext.length <= 5 ? ext : 'FILE'
+}
+const onAttachmentSubmit = async () => {
+  if (!attForm.附件内容) { ElMessage.warning('请选择文件'); return }
+  try {
+    // FileReader 读 base64
+    const dataUrl = await new Promise((resolve, reject) => {
+      const fr = new FileReader()
+      fr.onload = () => resolve(fr.result)
+      fr.onerror = reject
+      fr.readAsDataURL(attForm.附件内容)
+    })
+    const fd = new FormData()
+    fd.append('relationId', currentRow.value.UniqID)
+    fd.append('名称及路径', attForm.名称及路径)
+    fd.append('附件类型', attForm.附件类型)
+    fd.append('附件内容', dataUrl)
+    fd.append('sys_id', userStore.userInfo.userName)
+
+    const res = await addCustomerFeedbackAttachment(fd)
+    if (res.code === 0) {
+      ElMessage.success('上传成功')
+      attFormVisible.value = false
+      await loadAttachmentList()
+    } else {
+      ElMessage.error(res.msg || '上传失败')
+    }
+  } catch (e) { console.error(e) }
+}
+const onAttachmentDel = async () => {
+  if (attSelection.value.length === 0) return
+  try {
+    await ElMessageBox.confirm(`确定删除选中的 ${attSelection.value.length} 个附件吗?`, '提示', { type: 'warning' })
+    for (const row of attSelection.value) {
+      await delCustomerFeedbackAttachment({ UniqId: row.UniqId })
+    }
+    ElMessage.success('删除成功')
+    await loadAttachmentList()
+  } catch (_) {}
+}
+
+onMounted(async () => {
+  await getMenuData()
+  await loadList()
 })
 
+// =========== 表单 字段 顺序 ===========
+// 控制上下左右和回车的跳转
+const fieldOrder = [
+  'f_客诉单号', 'f_客诉日期', 'f_客诉方式',
+  'f_订单编号', 'f_产品编号', 'f_产品名称',
+  'f_客户编号', 'f_客户名称',
+  'f_客诉描述',
+  'f_缺陷关键字', 'f_客诉性质', 'f_扣分',
+  'f_主要责任部门', 'f_次要责任部门'
+]
+
+const focusField = async (id) => {
+  await nextTick()
+  const el = document.getElementById(id)
+  if (el) {
+    el.focus()
+    // 全选文本方便直接输入
+    try { el.select() } catch (_) {}
+  }
+}
+
+/**
+ * 表单字段键盘处理
+ * - 回车:若是日期→填当天;若是方式→打开方式弹窗;若是产品编号→产品弹窗;若是客户编号→客户弹窗;若是性质→性质弹窗;否则跳到下一个字段
+ * - 下/右箭头:跳到下一个字段
+ * - 上/左箭头:跳到上一个字段
+ */
+const handleFieldKeydown = (e, currentId) => {
+  // textarea 里上下左右正常操作,不拦截(但 Enter 仍要跳转,除非按住 Shift)
+  const isTextarea = e.target && e.target.tagName === 'TEXTAREA'
+
+  if (e.key === 'Enter') {
+    e.preventDefault()
+    // 特殊字段:弹选择弹窗
+    if (currentId === 'f_客诉日期') {
+      formData.客诉日期 = dayjs().format('YYYY.MM.DD')
+      nextField(currentId)
+      return
+    }
+    if (currentId === 'f_客诉方式') {
+      wayVisible.value = true
+      return
+    }
+    if (currentId === 'f_客诉性质') {
+      natureVisible.value = true
+      return
+    }
+    if (currentId === 'f_产品编号') {
+      openProductModal()
+      return
+    }
+    if (currentId === 'f_客户编号') {
+      openClientModal()
+      return
+    }
+    if (currentId === 'f_客诉描述' && e.shiftKey) return // Shift+Enter 在 textarea 里换行
+    if (currentId === 'f_客诉描述') return // 普通 Enter 在 textarea 里换行
+    nextField(currentId)
+    return
+  }
+
+  if (isTextarea) return
+
+  if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
+    e.preventDefault()
+    nextField(currentId)
+  } else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
+    e.preventDefault()
+    prevField(currentId)
+  } else if (e.key === 'Tab') {
+    // Tab 默认行为即可
+  }
+}
+
+const nextField = (currentId) => {
+  const idx = fieldOrder.indexOf(currentId)
+  if (idx >= 0 && idx < fieldOrder.length - 1) {
+    focusField(fieldOrder[idx + 1])
+  }
+}
+const prevField = (currentId) => {
+  const idx = fieldOrder.indexOf(currentId)
+  if (idx > 0) {
+    focusField(fieldOrder[idx - 1])
+  }
+}
+
+// =========== 新增/修改 ===========
+const formVisible = ref(false)
+const isEdit = ref(false)
+const formData = reactive(createEmptyForm())
+
+function createEmptyForm() {
+  return {
+    UniqID: null,
+    客诉单号: '/',
+    客诉日期: dayjs().format('YYYY.MM.DD'),
+    客诉方式: '',
+    订单编号: '',
+    产品编号: '',
+    产品名称: '',
+    客户编号: '',
+    客户名称: '',
+    客诉描述: '',
+    缺陷关键字: '',
+    客诉性质: '',
+    扣分: 0,
+    主要责任部门: '',
+    次要责任部门: '',
+    // 左侧树按 Sys_id(建档人)分组,新增时默认当前登录人(账号/姓名)
+    Sys_id: _username.value,
+  }
+}
+
+const onADD = () => {
+  Object.assign(formData, createEmptyForm())
+  isEdit.value = false
+  formVisible.value = true
+}
+const onEdit = (row) => {
+  Object.assign(formData, createEmptyForm(), row)
+  isEdit.value = true
+  formVisible.value = true
+}
+const closeForm = () => { formVisible.value = false }
+
+// 弹窗打开后自动聚焦第一个字段
+const onFormOpened = async () => {
+  await nextTick()
+  focusField('f_客诉单号')
+}
+
+const onDel = async () => {
+  if (selectionRows.value.length === 0) {
+    ElMessage.warning('请选择要删除的记录')
+    return
+  }
+  try {
+    await ElMessageBox.confirm(`确定删除选中的 ${selectionRows.value.length} 条记录吗?`, '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    })
+    const ids = selectionRows.value.map(r => r.UniqID)
+    const res = await delCustomerFeedback({ ids })
+    if (res.code === 0) {
+      ElMessage.success('删除成功')
+      await loadList()
+    } else {
+      ElMessage.error(res.msg || '删除失败')
+    }
+  } catch (_) {}
+}
+
+const onSubmit = async () => {
+  if (!formData.客诉日期) {
+    formData.客诉日期 = dayjs().format('YYYY.MM.DD')
+  }
+  try {
+    const res = isEdit.value
+      ? await editCustomerFeedback(formData)
+      : await addCustomerFeedback(formData)
+    if (res.code === 0) {
+      ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
+      formVisible.value = false
+      await loadList()
+    } else {
+      ElMessage.error(res.msg || '操作失败')
+    }
+  } catch (error) {
+    console.error(error)
+    ElMessage.error('网络错误')
+  }
+}
+
+// =========== 方式 选择 ===========
+const wayVisible = ref(false)
+const selectWay = (val) => {
+  formData.客诉方式 = val
+  wayVisible.value = false
+  nextTick(() => focusField('f_订单编号'))
+}
+const onWayOpened = async () => {
+  await nextTick()
+  // 默认聚焦当前值,否则聚焦第一个
+  const cur = formData.客诉方式
+  const target = cur === '口头' ? document.getElementById('way_口头') : document.getElementById('way_书面')
+  if (target) target.focus()
+}
+
+// =========== 性质 选择 ===========
+const natureVisible = ref(false)
+const selectNature = (val) => {
+  formData.客诉性质 = val
+  natureVisible.value = false
+  nextTick(() => focusField('f_扣分'))
+}
+const onNatureOpened = async () => {
+  await nextTick()
+  // 让弹窗能接受键盘
+}
+
+// =========== 产品编号 弹窗 ===========
+const productModal = ref(false)
+const productKeyword = ref('')
+const productList = ref([])
+const productHighlightIndex = ref(-1)
+const productTableRef = ref(null)
+
+const openProductModal = async () => {
+  productKeyword.value = formData.产品编号 || ''
+  productModal.value = true
+}
+const onProductModalOpened = async () => {
+  await loadProductList()
+  await nextTick()
+  // 聚焦搜索框
+  const input = document.querySelector('.product-search-input') || document.activeElement
+  // 让第一个搜索框拿到焦点
+  const inputs = document.querySelectorAll('.el-dialog__body .el-input__inner')
+  if (inputs && inputs.length) inputs[0].focus()
+}
+const closeProductModal = () => {
+  productHighlightIndex.value = -1
+  productList.value = []
+}
+const loadProductList = async () => {
+  try {
+    const res = await ProductCodeList({ cilent: productKeyword.value })
+    productList.value = res.data || []
+    productHighlightIndex.value = productList.value.length > 0 ? 0 : -1
+    await nextTick()
+    if (productTableRef.value && productHighlightIndex.value >= 0) {
+      productTableRef.value.setCurrentRow(productList.value[0])
+    }
+  } catch (e) { console.error(e) }
+}
+const pickProduct = (row) => {
+  formData.产品编号 = row.产品编号
+  formData.产品名称 = row.产品名称
+  productModal.value = false
+  nextTick(() => focusField('f_客户编号'))
+}
+const handleProductModalKeydown = (e) => {
+  // 拦截弹窗内部的 方向键 / 回车 / Esc
+  if (e.key === 'Escape') {
+    productModal.value = false
+    return
+  }
+  if (productList.value.length === 0) return
+
+  if (e.key === 'ArrowDown') {
+    e.preventDefault()
+    productHighlightIndex.value = Math.min(productHighlightIndex.value + 1, productList.value.length - 1)
+    productTableRef.value?.setCurrentRow(productList.value[productHighlightIndex.value])
+    return
+  }
+  if (e.key === 'ArrowUp') {
+    e.preventDefault()
+    productHighlightIndex.value = Math.max(productHighlightIndex.value - 1, 0)
+    productTableRef.value?.setCurrentRow(productList.value[productHighlightIndex.value])
+    return
+  }
+  if (e.key === 'Enter') {
+    // 搜索框里的回车已经由 @keyup.enter 处理,只有不在搜索框里时才选行
+    const isInInput = e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA')
+    if (isInInput && e.target.value === productKeyword.value) return // 就是刚刚搜索完
+    if (productHighlightIndex.value >= 0) {
+      e.preventDefault()
+      pickProduct(productList.value[productHighlightIndex.value])
+    }
+    return
+  }
+}
+
+// =========== 客户编号 弹窗 ===========
+const clientModal = ref(false)
+const clientKeyword = ref('')
+const clientList = ref([])
+const clientHighlightIndex = ref(-1)
+const clientTableRef = ref(null)
+
+const openClientModal = async () => {
+  clientKeyword.value = formData.客户编号 || ''
+  clientModal.value = true
+}
+const onClientModalOpened = async () => {
+  await loadClientList()
+  await nextTick()
+  const inputs = document.querySelectorAll('.el-dialog__body .el-input__inner')
+  if (inputs && inputs.length) inputs[0].focus()
+}
+const closeClientModal = () => {
+  clientHighlightIndex.value = -1
+  clientList.value = []
+}
+const loadClientList = async () => {
+  try {
+    const res = await ClientList({ search: clientKeyword.value })
+    clientList.value = res.data || []
+    clientHighlightIndex.value = clientList.value.length > 0 ? 0 : -1
+    await nextTick()
+    if (clientTableRef.value && clientHighlightIndex.value >= 0) {
+      clientTableRef.value.setCurrentRow(clientList.value[0])
+    }
+  } catch (e) { console.error(e) }
+}
+const pickClient = (row) => {
+  formData.客户编号 = row.客户编号
+  formData.客户名称 = row.客户名称
+  clientModal.value = false
+  nextTick(() => focusField('f_客诉描述'))
+}
+const handleClientModalKeydown = (e) => {
+  if (e.key === 'Escape') {
+    clientModal.value = false
+    return
+  }
+  if (clientList.value.length === 0) return
+
+  if (e.key === 'ArrowDown') {
+    e.preventDefault()
+    clientHighlightIndex.value = Math.min(clientHighlightIndex.value + 1, clientList.value.length - 1)
+    clientTableRef.value?.setCurrentRow(clientList.value[clientHighlightIndex.value])
+    return
+  }
+  if (e.key === 'ArrowUp') {
+    e.preventDefault()
+    clientHighlightIndex.value = Math.max(clientHighlightIndex.value - 1, 0)
+    clientTableRef.value?.setCurrentRow(clientList.value[clientHighlightIndex.value])
+    return
+  }
+  if (e.key === 'Enter') {
+    const isInInput = e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA')
+    if (isInInput && e.target.value === clientKeyword.value) return
+    if (clientHighlightIndex.value >= 0) {
+      e.preventDefault()
+      pickClient(clientList.value[clientHighlightIndex.value])
+    }
+    return
+  }
+}
 </script>
 
-<style>
+<style scoped>
+.cf-header {
+  display: flex;
+  gap: 8px;
+  padding: 8px 0;
+}
 .JKWTree-container {
   display: flex;
 }
-/* 选中某行时的背景色*/
-.el-table__body tr.current-row>td {
+.el-table__body tr.current-row > td {
   background: #ff80ff !important;
 }
 .JKWTree-tree {
-  width: 300px;
+  width: 260px;
   background-color: #fff;
   padding: 10px;
   margin-right: 20px;
+  max-height: 78vh;
+  overflow-y: auto;
 }
-
 .JKWTree-tree h3 {
   font-size: 15px;
   font-weight: 700;
   margin: 10px 0;
 }
-
 .JKWTree-content {
   flex: 1;
 }

+ 96 - 51
src/view/v23financial/v23CostOrder.vue

@@ -95,7 +95,7 @@
           <div class="gva-table-box">
             <!-- 表格数据 -->
             <el-table 
-			  ref="multipleTable" class="cost-order-table" style="width: 100%;height: 52vh" tooltip-effect="dark"
+			  ref="multipleTable" class="cost-order-table" style="width: 100%;height: 38vh" tooltip-effect="dark"
 			  :row-style="{ height: '20px' }"  :header-cell-style="{ padding: '0px' }"
 			  :cell-style="{ padding: '0px' }" :header-row-style="{ height: '20px' }"
 			  :data="hztableData"  border row-key="ID"
@@ -128,6 +128,10 @@
 			  <el-table-column align="left" label="成本合计" prop="成本合计" width="100" />
 			  <el-table-column align="left" label="录入人" prop="Sys_id" width="80" />
 			  <el-table-column align="left" label="录入时间" prop="Sys_rq" width="160" />
+        <el-table-column align="left" label="实际入仓数" prop="成品入仓数量" width="160" />
+        <el-table-column align="left" label="实际合格率" prop="工单合格率" width="160" />
+        <el-table-column align="left" label="订单时间" prop="开单日期" width="160" />
+        <el-table-column align="left" label="单位产品总成本" prop="单位成本" width="160" />
             </el-table>
 
             <!-- 分页 -->
@@ -142,51 +146,50 @@
                 </el-pagination>
 			</div>
 
-		  <div class="formula-description">
-			  <div class="formula-title">计算公式说明:</div>
-			  <div class="formula-grid">
-				<div class="formula-item">
-				  <span class="formula-label">水电分摊因子:</span>
-				  <span class="formula-content">工单机台生产工时</span>
-				</div>
-				<div class="formula-item">
-				  <span class="formula-label">材料分摊因子:</span>
-				  <span class="formula-content">色度数</span>
-				</div>
-				<div class="formula-item">
-				  <span class="formula-label">人工分摊因子:</span>
-				  <span class="formula-content">直接人工成本</span>
-				</div>
-				<div class="formula-item">
-				  <span class="formula-label">小时折旧额:</span>
-				  <span class="formula-content">机台小时折旧 × 水电分摊因子</span>
-				</div>
-				<div class="formula-item">
-				  <span class="formula-label">车间人工:</span>
-				  <span class="formula-content">车间人工成本 × (工单工序色度数 ÷ 车间总色度数)</span>
-				</div>
-				<div class="formula-item">
-				  <span class="formula-label">部门人工附加:</span>
-				  <span class="formula-content">部门人工 × (车间直接人工成本 ÷ 总直接人工成本) × (工单工序色度数 ÷ 车间总色度数)</span>
-				</div>
-				<div class="formula-item">
-				  <span class="formula-label">直接材料:</span>
-				  <span class="formula-content">ERP直接材料数量 × 单价</span>
-				</div>
-				<div class="formula-item">
-				  <span class="formula-label">分摊材料:</span>
-				  <span class="formula-content">ERP分摊材料数量 × 单价 × (工单工序色度数 ÷ 车间总色度数)</span>
-				</div>
-				<div class="formula-item">
-				  <span class="formula-label">直接水电:</span>
-				  <span class="formula-content">上报运行时长 × 电量单价</span>
-				</div>
-				<div class="formula-item full-width">
-				  <span class="formula-label">废气处理、锅炉、热水锅炉、空压机、空调、鼓风机:</span>
-				  <span class="formula-content">上报数量 × 单价 × (水电分摊因子 ÷ 总分摊因子)(锅炉和热水锅炉只算卷凹机组)</span>
-				</div>
-			  </div>
-			</div>
+			<!-- 下方:工序成本明细表 -->
+			<el-table
+			  ref="detailTableRef"
+			  class="cost-order-detail-table"
+			  style="width: 100%; margin-top: 6px"
+			  :data="detailTableData"
+			  border
+			  size="small"
+			  :row-style="{ height: '20px' }"
+			  :header-cell-style="{ padding: '0px' }"
+			  :cell-style="{ padding: '0px' }"
+			  :header-row-style="{ height: '20px' }"
+			  :show-overflow-tooltip="true"
+			  height="30vh"
+			>
+			  <el-table-column label="年月" prop="sys_ny" width="80" align="left" />
+			  <el-table-column label="工单编号" prop="sczl_gdbh" width="130" align="left" />
+			  <el-table-column label="印件名称" prop="印件名称" width="200" align="left" />
+			  <el-table-column label="印件号" prop="sczl_yjno" width="70" align="left" />
+			  <el-table-column label="工序号" prop="sczl_gxh" width="70" align="left" />
+			  <el-table-column label="工序名称" prop="工序名称" width="100" align="left" />
+			  <el-table-column label="机组编号" prop="sczl_jtbh" width="90" align="left" />
+			  <el-table-column label="车间名称" prop="车间名称" width="100" align="left" />
+			  <el-table-column label="车头产量" prop="班组车头产量" width="90" align="left" />
+			  <el-table-column label="计件产量" prop="计件产量" width="90" align="left" />
+			  <el-table-column label="车间人工" prop="车间人工" width="100" align="left" />
+			  <el-table-column label="部门人工附加" prop="部门人工附加" width="120" align="left" />
+			  <el-table-column label="直接材料" prop="直接材料" width="100" align="left" />
+			  <el-table-column label="考核直接材料" prop="考核直接材料" width="120" align="left" />
+			  <el-table-column label="分摊材料" prop="分摊材料" width="100" align="left" />
+			  <el-table-column label="直接折旧" prop="直接折旧" width="100" align="left" />
+			  <el-table-column label="分摊折旧" prop="分摊折旧" width="100" align="left" />
+			  <el-table-column label="场地租金" prop="场地租金" width="100" align="left" />
+			  <el-table-column label="直接水电" prop="直接水电" width="100" align="left" />
+			  <el-table-column label="分摊水电" prop="分摊水电" width="100" align="left" />
+			  <el-table-column label="废气处理" prop="废气处理" width="100" align="left" />
+			  <el-table-column label="锅炉" prop="锅炉" width="100" align="left" />
+			  <el-table-column label="空压机" prop="空压机" width="100" align="left" />
+			  <el-table-column label="热水锅炉" prop="热水锅炉" width="100" align="left" />
+			  <el-table-column label="真空鼓风机" prop="真空鼓风机" width="100" align="left" />
+			  <el-table-column label="中央空调" prop="中央空调" width="100" align="left" />
+			  <el-table-column label="分摊其他" prop="分摊其他" width="100" align="left" />
+			  <el-table-column label="成本合计" prop="成本合计" width="100" align="left" />
+			</el-table>
           </div>
 
           <el-dialog
@@ -230,7 +233,7 @@ import {ElMessage} from "element-plus";
 import * as XLSX from 'xlsx'
 import FileSaver from 'file-saver'
 import LuckyExcel from 'luckyexcel';
-import {getSummaryTab,SummaryCostAccountingList,GetCompletionWorkOrderCostTab,CompletionWorkOrderCostList} from '@/api/jixiaoguanli/caiwubaobiao'
+import {getSummaryTab,SummaryCostAccountingList,GetCompletionWorkOrderCostTab,CompletionWorkOrderCostList,getCompletionWorkOrderCostDetailList} from '@/api/jixiaoguanli/caiwubaobiao'
 
 defineOptions({name: 'Company'})
 
@@ -490,6 +493,41 @@ const page = ref(1)
 const limit = ref(50)
 const nodeclick = ref(null)
 const hztableData = ref([])
+const detailTableData = ref([])
+const detailLoading = ref(false)
+const currentWorkOrder = ref('')
+
+// 点击上方表格行 → 调用明细接口加载下方表格数据
+const tableRowClick = async (row) => {
+  const gdbh = row['工单编号'] || ''
+  if (!gdbh) {
+    detailTableData.value = []
+    return
+  }
+  // 同一工单不重复请求
+  if (currentWorkOrder.value === gdbh && detailTableData.value.length > 0) return
+  currentWorkOrder.value = gdbh
+  detailLoading.value = true
+  try {
+    const res = await getCompletionWorkOrderCostDetailList({ gdbh })
+    if (res.code === 0 && res.data) {
+      detailTableData.value = res.data.list || []
+    } else {
+      detailTableData.value = []
+      ElMessage.warning(res.msg || '未找到该工单工序成本明细')
+    }
+  } catch (error) {
+    console.error('加载明细失败:', error)
+    detailTableData.value = []
+  } finally {
+    detailLoading.value = false
+  }
+}
+
+// 空占位函数,避免模板中引用未定义函数报错
+const updateCompanyFunc = () => {}
+const handleSelectionChange = () => {}
+const gxbgCellClass = () => ''
 
 const loadByDate = async (d) => {
   const res = await CompletionWorkOrderCostList({
@@ -532,6 +570,9 @@ const loadByCustomer = async (nodeData) => {
 };
 
 const reloadTableByCurrentSelection = async () => {
+  // 清空明细表,避免切换后残留旧工单明细
+  detailTableData.value = []
+  currentWorkOrder.value = ''
   if (nodeclick.value) {
     await loadByCustomer(nodeclick.value);
   } else if (selectedDate.value) {
@@ -547,6 +588,8 @@ const selectDate = async (d) => {
   selectedMonthStr.value = '';
   nodeclick.value = null;
   page.value = 1;
+  detailTableData.value = [];
+  currentWorkOrder.value = '';
   applyFoldForSelectedDate(d);
   await loadByDate(d);
 };
@@ -557,6 +600,8 @@ const selectMonth = async (yi, mi, m) => {
   selectedMonthStr.value = m.month;
   nodeclick.value = null;
   page.value = 1;
+  detailTableData.value = [];
+  currentWorkOrder.value = '';
   applyFoldForMonth(yi, mi);
   await loadByMonth(m.month);
 };
@@ -569,6 +614,8 @@ const handleCustomerClick = async (nodeData) => {
   applyFoldForSelectedDate('');
   nodeclick.value = nodeData;
   page.value = 1;
+  detailTableData.value = [];
+  currentWorkOrder.value = '';
   await loadByCustomer(nodeData);
 };
 
@@ -596,6 +643,8 @@ const onSubmit = async () => {
     if(res.code === 0){
         hztableData.value = res.data.list
         total.value = res.data.total
+        detailTableData.value = []
+        currentWorkOrder.value = ''
     }
   } catch (error) {
     console.error(error);
@@ -722,10 +771,6 @@ costlist.value = true;
 .cost-order-main {
   padding-left: 4px;
 }
-.formula-description {
-  padding-left: 12px;
-  box-sizing: border-box;
-}
 .JKWTree-tree h3 {
   font-size: 15px;
   font-weight: 700;

+ 91 - 41
src/view/v23financial/v23Costmaintenance.vue

@@ -7,7 +7,7 @@
             <el-form ref="elSearchFormRef"   class="demo-form-inline" :rules="searchRule" >
               <el-form-item>
                 <!-- <el-input v-model="searchInfo" placeholder="搜索工单编号" clearable style="width: 200px;margin: 5px"></el-input>
-                <el-button type="primary" class="bt" icon="edit" @click="onout">月度费用结构建立</el-button>
+                <el-button type="primary" class="bt" icon="edit" @click="onout">月度费用结构建F立</el-button>
                 <el-button type="primary" class="bt" icon="edit" @click="onout">月度费用处理</el-button>-->
                 <el-button type="primary" class="bt" icon="edit" @click="onmonthcbcreate">月度工单成本计算</el-button> 
                 <el-button type="primary" class="bt" icon="edit" @click="onordercbcreate">完工工单成本计算</el-button> 
@@ -15,7 +15,7 @@
                 <el-button type="primary" class="bt" icon="edit" :disabled="tableStates.showrgdownTable === false" @click="onartificial">月度人工维护</el-button>
                 <el-button type="primary" class="bt" icon="edit" :disabled="tableStates.showsdqzjupTable === false" @click="onsdqzj">水电气直接费用增加</el-button>
                 <el-button type="primary" class="bt" icon="edit" :disabled="tableStates.showsdqftupTable === false" @click="onsdqft">水电气分摊批量维护</el-button>
-                <el-button type="primary" class="bt" icon="edit" :disabled="tableStates.showqtdtTable === false" @click="onqtdt">其他待摊费用增加</el-button>
+                <el-button type="primary" class="bt" icon="edit" :disabled="tableStates.showqtdtTable === false" @click="onqtdt">其他待摊费用维护</el-button>
                 <el-button type="primary" class="bt" icon="edit" :disabled="tableStates.showqtdtTable === false" @click="onqtdtcopy">其他待摊费用复制</el-button>
                 <el-button type="primary" class="bt" icon="edit" :disabled="tableStates.showsdsupTable === false" @click="onsds">车间色度数添加</el-button>
                 <!-- <el-button type="primary" class="bt" icon="edit" @click="onout">分摊清单设置</el-button> -->
@@ -36,7 +36,7 @@
           <layout-sider :resize-directions="['right']" :width="190" style="margin-right: 10px;">
             <div class="JKWTree-tree" style="height: 700px">
               <h3>V23各月制造费用维护</h3>
-              <el-tree :data="treeData"  :props="defaultProps" @node-click="handleNodeClick" @node-expand="handleNodeExpand">
+              <el-tree class="treecolor" :data="treeData"  :props="defaultProps" node-key="value" @node-click="handleNodeClick" @node-expand="handleNodeExpand">
               </el-tree>
             </div>
           </layout-sider>
@@ -54,7 +54,7 @@
                         :data="rguptableData"  border row-key="ID"
                         size="small" id="up-rg"
                         :cell-class-name="gxbgCellClass"
-                        highlight-current-row="true" @row-dblclick="updateCompanyFunc"
+                        highlight-current-row @row-dblclick="updateCompanyFunc"
                         @row-click="rguptableRowClick" :show-overflow-tooltip="true"
                         @selection-change="handleSelectionChange">
                <el-table-column  sortable align="center" label="年月" prop="年月"  width="120" />
@@ -76,7 +76,7 @@
                         :data="rgdowntableData"  border row-key="ID"
                         size="small" id="down-rg"
                         :cell-class-name="gxbgCellClass"
-                        highlight-current-row="true" @row-dblclick="updateCompanyFunc"
+                        highlight-current-row @row-dblclick="updateCompanyFunc"
                         @row-click="tableRowClick" :show-overflow-tooltip="true"
                         @selection-change="handleSelectionChange">
                <el-table-column  sortable align="center" label="工单编号" prop="工单编号"  width="120" />
@@ -102,7 +102,7 @@
                         :data="sdqzjuptableData"  border row-key="ID"
                         size="small" id="up-sdqzj"
                         :cell-class-name="gxbgCellClass"
-                        highlight-current-row="true" @row-dblclick="updateCompanyFunc"
+                        highlight-current-row @row-dblclick="updateCompanyFunc"
                         @row-click="sdqzjuptableRowClick" :show-overflow-tooltip="true"
                         @selection-change="handleSelectionChange">
                <el-table-column  sortable align="center" label="部门名称" prop="部门名称"  width="120" />
@@ -127,7 +127,7 @@
                         :data="sdqzjdowntableData"  border row-key="ID"
                         size="small" id="down-sdqzj"
                         :cell-class-name="gxbgCellClass"
-                        highlight-current-row="true" @row-dblclick="updateCompanyFunc"
+                        highlight-current-row @row-dblclick="updateCompanyFunc"
                         @row-click="tableRowClick" :show-overflow-tooltip="true"
                         @selection-change="handleSelectionChange">
                <el-table-column  sortable align="center" label="工单编号" prop="工单编号"  width="120" />
@@ -150,7 +150,7 @@
                         :data="sdqftuptableData"  border row-key="ID"
                         size="small" id="up-sdqft"
                         :cell-class-name="gxbgCellClass"
-                        highlight-current-row="true" @row-dblclick="updateCompanyFunc"
+                        highlight-current-row @row-dblclick="updateCompanyFunc"
                         @row-click="sdqftuptableRowClick" :show-overflow-tooltip="true"
                         @selection-change="handleSelectionChange">
                <el-table-column  sortable align="center" label="部门名称" prop="部门名称"  width="120" />
@@ -162,7 +162,7 @@
                <el-table-column  sortable align="center" label="单位气价" prop="单位气价"  width="100" />
                <el-table-column  sortable align="center" label="直接费用合计" prop="直接费用合计"  width="130" />
                <el-table-column  sortable align="center" label="创建用户" prop="创建用户"  width="130" />
-               <el-table-column  sortable align="center" label="创建时间" prop="创建时间"  width="100" />
+               <el-table-column  sortable align="center" label="创建时间" prop="创建时间"  width="110 " />
               </el-table>
 
               <el-table v-if="tableStates.showsdqftdownTable"
@@ -172,19 +172,16 @@
                         :data="sdqftdowntableData"  border row-key="ID"
                         size="small" id="down-sdqft"
                         :cell-class-name="gxbgCellClass"
-                        highlight-current-row="true" @row-dblclick="updateCompanyFunc"
+                        highlight-current-row @row-dblclick="updateCompanyFunc"
                         @row-click="tableRowClick" :show-overflow-tooltip="true"
                         @selection-change="handleSelectionChange">
                <el-table-column  sortable align="center" label="设备编号" prop="设备编号"  width="120" />
-               <el-table-column  sortable align="center" label="设备名称" prop="设备名称"  width="120" />
-               <el-table-column  sortable align="center" label="通电工时_全部" prop="通电工时_全部"  width="200" />
-               <el-table-column  sortable align="center" label="通电工时_成本" prop="通电工时_成本"   width="200" />
+               <el-table-column  sortable align="center" label="设备名称" prop="设备名称"  min-width="300" />
+               <el-table-column  sortable align="center" label="通电工时_全部" prop="通电工时_全部"  width="140" />
+               <el-table-column  sortable align="center" label="通电工时_成本" prop="通电工时_成本"   width="140" />
                <el-table-column  sortable align="center" label="年月" prop="年月"  width="100" />
-               <el-table-column  sortable align="center" label="科目名称" prop="科目名称"  width="100" />
+               <el-table-column  sortable align="center" label="科目名称" prop="科目名称"  width="160" />
                <el-table-column  sortable align="center" label="使用部门" prop="使用部门"  width="100" />
-               <el-table-column  sortable align="center" label="创建用户" prop="创建用户"  width="100" />
-               <el-table-column  sortable align="center" label="创建时间" prop="创建时间"  width="100" />
-               <el-table-column  sortable align="center" label="修改时间" prop="修改时间"  width="100" />
               </el-table>
 
               <el-table v-if="tableStates.showqtdtTable"
@@ -194,7 +191,7 @@
                         :data="qtdttableData"  border row-key="ID"
                         size="small"
                         :cell-class-name="gxbgCellClass"
-                        highlight-current-row="true" @row-dblclick="updateCompanyFunc"
+                        highlight-current-row @row-dblclick="updateCompanyFunc"
                         @row-click="tableRowClick" :show-overflow-tooltip="true"
                         @selection-change="handleSelectionChange">
                <el-table-column  sortable align="center" label="部门一线工资" prop="部门人员工资"  width="140" />
@@ -216,7 +213,7 @@
                         :data="sdsuptableData"  border row-key="ID"
                         size="small" id="up-sds"
                         :cell-class-name="gxbgCellClass"
-                        highlight-current-row="true" @row-dblclick="updateCompanyFunc"
+                        highlight-current-row @row-dblclick="updateCompanyFunc"
                         @row-click="sdsuptableRowClick" :show-overflow-tooltip="true"
                         @selection-change="handleSelectionChange">
                <el-table-column  sortable align="center" label="年月" prop="年月"  width="120" />
@@ -231,7 +228,7 @@
                         :data="sdsdowntableData"  border row-key="ID"
                         size="small" id="down-sds"
                         :cell-class-name="gxbgCellClass"
-                        highlight-current-row="true" @row-dblclick="updateCompanyFunc"
+                        highlight-current-row @row-dblclick="updateCompanyFunc"
                         @row-click="tableRowClick" :show-overflow-tooltip="true"
                         @selection-change="handleSelectionChange">
                <el-table-column  sortable align="center" label="工单编号" prop="sczl_gdbh"  width="120" />
@@ -403,7 +400,7 @@
 
           <el-dialog
     v-model="qtdtlist"
-    title="添加其他待摊费用"
+    title="其他待摊费用维护"
     width="600px"
     :before-close="handleClose"
   >
@@ -507,7 +504,7 @@
     <template #footer>
       <span class="dialog-footer">
         <el-button @click="qtdthandleClose">取消</el-button>
-        <el-button type="primary" @click="qtdthandleSubmit">添加</el-button>
+        <el-button type="primary" @click="qtdthandleSubmit">保存</el-button>
       </span>
     </template>
   </el-dialog>
@@ -703,14 +700,25 @@ const sdsdowntableData = ref([])
 
 const _noderq = ref('')
 
-const handleNodeClick = (node) => {
+const handleNodeClick = (nodeData, node) => {
+  // 取消所有节点的颜色
+  const allNodes = document.querySelectorAll('.treecolor .el-tree-node');
+  allNodes.forEach(n => {
+    n.querySelector('.el-tree-node__label').style.color = '';
+  });
+  // 给当前点击的节点改变颜色(node-key="value",data-key 即 nodeData.value)
+  const clickedNode = document.querySelector(`.treecolor .el-tree-node[data-key="${nodeData.value}"]`);
+  if (clickedNode) {
+    clickedNode.querySelector('.el-tree-node__label').style.color = 'red';
+  }
+
   // 首先重置所有表格的显示状态
   Object.keys(tableStates).forEach(key => {
     tableStates[key] = false
   })
 console.log(node)
   // 根据节点值判断显示哪些表格
-  const nodeValue = node.value // 假设node.value是类似 "202410-2、水电气直接费用-胶印车间" 的格式
+  const nodeValue = nodeData.value // 假设node.value是类似 "202410-2、水电气直接费用-胶印车间" 的格式
   const parts = nodeValue.split('-') // 分割节点值
 
   // 第一层:年度/月份节点 (如 "202410")
@@ -835,10 +843,10 @@ const sdqzjuptableRowClick  = async (row) => {
 // 水电费分摊上方数据行点击事件
 const sdqftuptableRowClick = async (row) => {
   console.log(row)
-  const res = await shuidianqiMachineDetailList({month: _noderq.value,sist: row.部门名称,search: '待分摊总额'})
+  const res = await shuidianqiMachineDetailList({month: _noderq.value,sist: row.部门名称,search: row.设备名称})
   if(res.code === 0){
     console.log(res)
-    rgdowntableData.value = res.data
+    sdqftdowntableData.value = res.data
   }
 }
 
@@ -1060,25 +1068,40 @@ const UtilitiesListdata = async ()=>{
   let Sys_ny = _noderq.value;
   try {
     const UtilitiesListdata = await UtilitiesList({month:Sys_ny});
-    console.log(UtilitiesListdata.data[0])
-    if(UtilitiesListdata.data.耗电量 === undefined){
-      sdqfttableData.value = SDQFT_WORKSHOPS.map((workshop, index) => ({
-      年月: _noderq.value,
-      部门名称: workshop,
-      科目名称: SDQFT_kmmc[index],
-      耗电量: '',
-      单位电价: 0.690,
-      耗气量: '',
-      单位气价: 3.037
-}));
-    }else{
-      sdqfttableData.value = UtilitiesListdata.data;
+    const list = UtilitiesListdata.data;
+    // 后端返回的是数组:当月有数据时含 耗电量/耗气量 等字段;
+    // 当月无数据时回退查询只返回 部门名称/科目名称(无耗电量字段)
+    if (Array.isArray(list) && list.length > 0 && list[0].耗电量 !== undefined) {
+      // 当月已维护过数据,直接带出
+      sdqfttableData.value = list;
       isNewData.value = false;
+    } else if (Array.isArray(list) && list.length > 0) {
+      // 只有部门/科目模板,补默认单价,耗电量/耗气量留空
+      sdqfttableData.value = list.map(item => ({
+        ...item,
+        耗电量: item.耗电量 ?? '',
+        单位电价: item.单位电价 ?? 0.690,
+        耗气量: item.耗气量 ?? '',
+        单位气价: item.单位气价 ?? 3.037,
+      }));
+      isNewData.value = true;
+    } else {
+      // 完全无数据,用内置模板
+      sdqfttableData.value = SDQFT_WORKSHOPS.map((workshop, index) => ({
+        年月: _noderq.value,
+        部门名称: workshop,
+        科目名称: SDQFT_kmmc[index],
+        耗电量: '',
+        单位电价: 0.690,
+        耗气量: '',
+        单位气价: 3.037
+      }));
+      isNewData.value = true;
     }
   } catch (error) {
     console.error(error);
   }
-} 
+}
 
 //水电气分摊【更新】
 
@@ -1106,6 +1129,7 @@ const sdqft_handleUpdateData = async () => {
   if (UtilitiesAdds.code === 0) {
     sdqftlist.value = false;
     ElMessage({type: 'success',message: '更新成功'})
+    loadSdqftDetailData(_noderq.value); // 刷新主页面水电气分摊表格
   } else {
     ElMessage({type: 'error',message: '更新失败'})
   }
@@ -1166,9 +1190,35 @@ const sdqftCancel = () => {
 
 
 const qtdtlist = ref(false)
-const onqtdt = () => {
+const onqtdt = async () => {
   qtdtlist.value = true
   qtdtformData.value.month = _noderq.value;
+  // 加载该月已有数据并回填
+  try {
+    const qtdtres = await PrepaidExpensesList({ month: _noderq.value })
+    if (qtdtres.code === 0 && qtdtres.data) {
+      const data = qtdtres.data
+      qtdtformData.value.salary1     = data['部门人员工资'] ?? ''
+      qtdtformData.value.salary2     = data['管理人员工资'] ?? ''
+      qtdtformData.value.rental      = data['场地租金']     ?? ''
+      qtdtformData.value.depreciation= data['待摊折旧']     ?? ''
+      qtdtformData.value.proportion  = data['工资成本占比'] ?? ''
+      qtdtformData.value.rest        = data['其他']         ?? ''
+      qtdtformData.value.profit      = data['后勤人员工资'] ?? ''
+    } else {
+      // 无已有数据时清空
+      qtdtformData.value.salary1 = ''
+      qtdtformData.value.salary2 = ''
+      qtdtformData.value.rental = ''
+      qtdtformData.value.depreciation = ''
+      qtdtformData.value.proportion = ''
+      qtdtformData.value.rest = ''
+      qtdtformData.value.profit = ''
+    }
+  } catch (error) {
+    // 接口报错时不影响弹窗打开
+    console.error('加载其他待摊费用数据失败:', error)
+  }
 }
 
 const qtdtformData = ref({

+ 93 - 40
src/view/yunyin/shengchanguanli/WorkshopInformationStatistics.vue

@@ -78,7 +78,7 @@
       <el-dialog v-model="addDialogVisible" :title="isEdit ? '修改生产信息' : '新增生产信息'" width="95%" style="top: 2%" class="add-dialog">
         <div class="add-header">
           <el-form-item label="日期:" class="date-form-item">
-            <el-date-picker v-model="addDate" type="date" placeholder="选择日期" style="width: 200px;" />
+            <el-date-picker v-model="addDate" type="date" placeholder="选择日期" style="width: 200px;" @change="handleDateChange" />
           </el-form-item>
           <span class="hint">注:每个机台自动生成A班和B班两条数据</span>
         </div>
@@ -212,8 +212,11 @@
 
       <el-dialog v-model="exportDialogVisible" title="导出Excel" width="400px" style="top: 20%">
         <div style="padding: 20px;">
-          <el-form-item label="日期:">
-            <el-date-picker v-model="exportDate" type="month" placeholder="选择月份" style="width: 100%;" />
+          <el-form-item label="开始日期:">
+            <el-date-picker v-model="exportStartDate" type="date" placeholder="选择开始日期" style="width: 100%;" />
+          </el-form-item>
+          <el-form-item label="结束日期:">
+            <el-date-picker v-model="exportEndDate" type="date" placeholder="选择结束日期" style="width: 100%;" />
           </el-form-item>
         </div>
         <template #footer>
@@ -229,12 +232,14 @@
   </template>
   <script setup>
   import { Layout, LayoutHeader, LayoutSider, LayoutContent } from '@arco-design/web-vue';
-  import {PisgetMenu,PisgetList,PisgetBaseData,PisbatchAdd,PisbatchDelete,PisbatchUpdate,ToExcel} from '@/api/mes/job'
+  import {PisgetMenu,PisgetList,PisgetBaseData,PisbatchAdd,PisbatchDelete,PisbatchUpdate,getRangeSummary,ToExcel} from '@/api/mes/job'
   import {ref, reactive, onMounted} from 'vue'
   import {ElMessage, ElMessageBox} from "element-plus";
   import service from '@/utils/request';
   defineOptions({name: 'WorkshopInformationStatistics'})
 const path = `${import.meta.env.VITE_BASE_PATH}:${import.meta.env.VITE_UPLOADS_PORT}`
+
+console.log(import.meta.env)
 console.log(path)
   const treeData = ref([])
   const defaultProps = reactive({
@@ -259,7 +264,8 @@ console.log(path)
   const tableHeight = ref(500)
   const isEdit = ref(false)
   const exportDialogVisible = ref(false)
-  const exportDate = ref('')
+  const exportStartDate = ref('')
+  const exportEndDate = ref('')
 
   const formatDate = (date) => {
     if (!date) return ''
@@ -322,35 +328,64 @@ console.log(path)
     }
   }
 
-  const fetchBaseData = async () => {
+  const fetchBaseData = async (date) => {
     try {
-      const res = await PisgetBaseData()
+      const res = await PisgetBaseData({ date })
       if (res.code === 0) {
         const equipmentList = res.data || []
         const tempData = []
         let rowIndex = 0
+        // 第一步:收集接口返回中已带班组数据的记录,按 机台编号_班组 存入 map
+        const shiftDataMap = new Map()
+        const baseInfoMap = new Map() // 机台基础信息(车间、机组、名称、班组列表)按 机台编号 存
         equipmentList.forEach(item => {
-          const shiftList = item.班组列表 || ['A班', 'B班']
-          shiftList.forEach(shift => {
-            tempData.push({
-              __rowKey: `${item.机台编号}_${shift}_${rowIndex++}`,
+          const jtbh = item.机台编号 || item.设备编号
+          if (!jtbh) return
+          // 记录机台基础信息(优先第一次遇到的)
+          if (!baseInfoMap.has(jtbh)) {
+            baseInfoMap.set(jtbh, {
               车间: item.车间,
               机组: item.机组,
-              机台编号: item.机台编号,
+              机台编号: jtbh,
               机台名称: item.机台名称,
-              sczl_jtbh: item.机台编号,
-              sczl_bzdh: shift,
-              theoretical_speed: item.理论速度 || 0,
-              scheduled_production: 0,
-              scheduled_maintenance: 0,
-              scheduled_handover: 0,
-              fault: 0,
-              changeover: 0,
-              proofing: 0,
-              abnormal: 0,
-              planned_output: 0,
-              actual_output: 0,
-              defective_product: 0
+              理论速度: item.理论速度,
+              班组列表: item.班组列表 || ['A班', 'B班']
+            })
+          }
+          // 若该条记录自带班组,说明是接口直接返回的某班组的具体数据
+          if (item.sczl_bzdh || item.班组) {
+            const shift = item.sczl_bzdh || item.班组
+            const key = `${jtbh}_${shift}`
+            if (!shiftDataMap.has(key)) {
+              shiftDataMap.set(key, item)
+            }
+          }
+        })
+        // 第二步:遍历每个机台的班组列表,生成最终表格数据(有接口数据用接口,没有填默认)
+        baseInfoMap.forEach((base, jtbh) => {
+          const shiftList = base.班组列表 || ['A班', 'B班']
+          shiftList.forEach(shift => {
+            const key = `${jtbh}_${shift}`
+            const item = shiftDataMap.get(key) || {}
+            tempData.push({
+              __rowKey: `${jtbh}_${shift}_${rowIndex++}`,
+              车间: item.车间 ?? base.车间,
+              机组: item.机组 ?? base.机组,
+              机台编号: item.机台编号 ?? base.机台编号,
+              机台名称: item.机台名称 ?? base.机台名称,
+              sczl_jtbh: item.sczl_jtbh ?? item.机台编号 ?? jtbh,
+              sczl_bzdh: item.sczl_bzdh ?? item.班组 ?? shift,
+              theoretical_speed: item.theoretical_speed ?? item.理论速度 ?? base.理论速度 ?? 0,
+              scheduled_production: item.scheduled_production ?? item.计划生产时间 ?? item.标准计划生产时间 ?? 0,
+              scheduled_maintenance: item.scheduled_maintenance ?? item.计划保养时间 ?? item.标准计划保养时间 ?? 0,
+              scheduled_handover: item.scheduled_handover ?? item.计划早会交接班时间 ?? item.计划交接班时间 ?? 0,
+              fault: item.fault ?? item.设备故障时间 ?? 0,
+              changeover: item.changeover ?? item.实际换型时间 ?? item.换型时间 ?? 0,
+              proofing: item.proofing ?? item.实际打样时间 ?? item.打样时间 ?? 0,
+              abnormal: item.abnormal ?? item.异常时间 ?? 0,
+              planned_output: item.planned_output ?? item.计划完成产量 ?? item.计划产量 ?? 0,
+              actual_output: item.实际产量 ?? item.actual_output ?? 0,
+              defective_product: item.defective_product ?? item.不良品数量 ?? 0
             })
           })
         })
@@ -406,10 +441,10 @@ console.log(path)
     }
   }
 
-  const openAddDialog = async () => {
+  const openAddDialog = () => {
     addDate.value = ''
     editRowKey.value = ''
-    await fetchBaseData()
+    addTableData.value = []
     addDialogVisible.value = true
     setTimeout(() => {
       calculateTableHeight()
@@ -417,6 +452,14 @@ console.log(path)
     }, 200)
   }
 
+  const handleDateChange = (val) => {
+    if (val) {
+      fetchBaseData(formatDate(val))
+    } else {
+      addTableData.value = []
+    }
+  }
+
   const closeAddDialog = () => {
     addDialogVisible.value = false
     isEdit.value = false
@@ -600,38 +643,48 @@ console.log(path)
   }
 
   const hzToExcel = () => {
-    let defaultDate = ''
+    let defaultStart = ''
+    let defaultEnd = ''
     if (selectedDate.value) {
       if (selectedDate.value.length === 7) {
-        defaultDate = selectedDate.value
+        const [y, m] = selectedDate.value.split('-')
+        const lastDay = new Date(parseInt(y), parseInt(m), 0).getDate()
+        defaultStart = `${selectedDate.value}-01`
+        defaultEnd = `${selectedDate.value}-${String(lastDay).padStart(2, '0')}`
       } else if (selectedDate.value.length === 10) {
-        defaultDate = selectedDate.value.substring(0, 7)
+        defaultStart = selectedDate.value
+        defaultEnd = selectedDate.value
       }
     }
-    if (!defaultDate) {
-      const now = new Date()
-      const year = now.getFullYear()
-      const month = String(now.getMonth() + 1).padStart(2, '0')
-      defaultDate = `${year}-${month}`
+    if (!defaultStart) {
+      defaultStart = formatDate(new Date())
+      defaultEnd = formatDate(new Date())
     }
-    exportDate.value = defaultDate
+    exportStartDate.value = defaultStart
+    exportEndDate.value = defaultEnd
     exportDialogVisible.value = true
   }
 
   const confirmExport = async () => {
-    if (!exportDate.value) {
-      ElMessage.warning('请选择日期')
+    if (!exportStartDate.value) {
+      ElMessage.warning('请选择开始日期')
+      return
+    }
+    if (!exportEndDate.value) {
+      ElMessage.warning('请选择结束日期')
       return
     }
     try {
-      const month = typeof exportDate.value === 'string' ? exportDate.value : formatDate(exportDate.value).substring(0, 7)
-      const res = await ToExcel({ sys_id: 'admin', month })
+      const start_date = typeof exportStartDate.value === 'string' ? exportStartDate.value : formatDate(exportStartDate.value)
+      const end_date = typeof exportEndDate.value === 'string' ? exportEndDate.value : formatDate(exportEndDate.value)
+      const res = await getRangeSummary({start_date, end_date })
       if (res.code === 0) {
         let url = res.data.url
         if (url) {
 if (!url.startsWith('http')) {
     url = `${path}/${url}`
 }
+console.log(url)
           const link = document.createElement('a')
           link.href = url
           link.download = url.split('/').pop()

+ 98 - 1
src/view/yunyin/shengchanguanli/shebeiyunxing.vue

@@ -1038,6 +1038,18 @@
 				<el-table-column align="left" label="流程" sortable prop="num"  width="80"/>
 				<el-table-column align="left" label="补产标准" prop="补产标准"  width="85"/>
 				<el-table-column align="left" label="难度系数" prop="难度系数"  width="85"/>
+				<!-- <el-table-column align="left" label="实际系数" prop="实际系数" width="90">
+					<template #default="scope">
+						<span>{{ scope.row.实际系数 }}</span>
+						<el-tooltip
+							v-if="scope.row.结果"
+							:content="scope.row.结果"
+							placement="top"
+						>
+							<span class="result-marker">{{ getResultMarker(scope.row.结果) }}</span>
+						</el-tooltip>
+					</template>
+				</el-table-column> -->
 				<el-table-column align="left" label="备注" prop="备注"  width="200"/>
 				<el-table-column align="left" label="组员1" prop="组员1" width="150" />
 				<el-table-column align="left" label="组员2" prop="组员2" width="150" />
@@ -1065,7 +1077,20 @@
 				<el-table-column align="left" label="码包" prop="码包" width="70" />
 				<el-table-column align="left" label="主电表" prop="主电表"  width="70"/>
 				<el-table-column align="left" label="辅电表" prop="辅电表" width="70" />
-				<el-table-column align="left" label="色度数" prop="色度数" width="70" />
+				<el-table-column align="left" label="色度数" prop="色度数" width="90">
+					<!-- <template #default="scope">
+						<span :class="{'cs-error-text': csBatchErrorMap[`${scope.row.工单编号}-${scope.row.gxh}`]?.is_valid === 0}">
+							{{ scope.row.色度数 }}
+						</span>
+						<el-tooltip
+							v-if="csBatchErrorMap[`${scope.row.工单编号}-${scope.row.gxh}`]?.is_valid === 0"
+							:content="csBatchErrorMap[`${scope.row.工单编号}-${scope.row.gxh}`]?.reason"
+							placement="top"
+						>
+							<span class="cs-error-icon">?</span>
+						</el-tooltip>
+					</template> -->
+				</el-table-column>
 				<el-table-column align="left" label="印刷方式" prop="印刷方式"  width="85"/>
 				<el-table-column align="left" label="用户" prop="用户" width="210" />
 				<el-table-column align="left" label="更新时间" prop="更新时间" width="160" />
@@ -2595,6 +2620,7 @@ import {
 	getMountList,
 	leaderConfirm,
 	statisticsConfirm,
+	csbatch,
 } from "@/api/mes/job";
 import Shebeizhuangtai from '@/view/performance/09-workOrderVerification/componets/shebeizhuangtai.vue'
 // 全量引入格式化工具 请按需保留
@@ -2632,6 +2658,15 @@ const printPageRef = ref()
 const DelType = ref(false)
 const AddType = ref(false)
 const gytableData = ref([])
+const csBatchErrorMap = reactive({})
+
+// 提取难度系数校验结果开头的状态标识(如 ✅合规 -> ✅,❌异常 -> ❌)
+const getResultMarker = (result) => {
+	if (!result) return ''
+	const text = String(result)
+	const m = text.match(/^([\u{1F000}-\u{1FAFF}\u2700-\u27BF\u2600-\u26FF\u2B00-\u2BFF✓✔✗×☑])/u)
+	return m ? m[1] : text.charAt(0)
+}
 const drzcDatas =ref([])
 const detailData = reactive([])
 const treeData=ref([])
@@ -8874,6 +8909,33 @@ const FacilitychanLiang = async () => {
 		}
 		gytableData.value.push(rowB)
 	}
+
+	// 获取色度数异常数据
+	try {
+		// 清空之前的异常数据
+		Object.keys(csBatchErrorMap).forEach(key => delete csBatchErrorMap[key]);
+		
+		// 收集所有不重复的工单编号(排除合计行)
+		const uniqueGdbhs = [...new Set(
+			gytableData.value
+				.filter(row => !row.isTotal && row.工单编号)
+				.map(row => row.工单编号)
+		)];
+		
+		if (uniqueGdbhs.length > 0) {
+			// 批量调用 csbatch 接口
+			const csResponse = await csbatch({ gdbhs: uniqueGdbhs.join(',') });
+			if (csResponse.code === 0 && csResponse.data) {
+				// 构建映射:key = gdbh-gxh(短横线连接,与后端返回格式一致)
+				Object.values(csResponse.data).forEach(item => {
+					const mapKey = `${item.gdbh}-${item.gxh}`;
+					csBatchErrorMap[mapKey] = item;
+				});
+			}
+		}
+	} catch (err) {
+		console.error('获取色度数异常数据失败:', err);
+	}
   }
 }
 
@@ -8927,6 +8989,12 @@ function duohandleClick(table) {
   }
 }
 
+// 以下两个函数为 el-tab-pane 上历史保留的占位绑定,
+// 各表格数据在弹窗打开时已统一加载,tab 切换时无需额外处理;
+// 保留空实现以避免 "showTable/handleClick is not a function" 报错阻断双击等交互。
+function showTable() {}
+function handleClick() {}
+
 
 /*
 	样式事件
@@ -9056,6 +9124,35 @@ const loadInspectionRecords = async () => {
 </script>
 
 <style scoped>
+.cs-error-text {
+  color: #f56c6c;
+  font-weight: bold;
+}
+.cs-error-icon {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  width: 14px;
+  height: 14px;
+  margin-left: 3px;
+  background-color: #f56c6c;
+  color: #fff;
+  font-size: 10px;
+  font-weight: bold;
+  border-radius: 50%;
+  cursor: help;
+  vertical-align: middle;
+}
+.result-marker {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  margin-left: 3px;
+  font-size: 16px;
+  line-height: 1;
+  cursor: help;
+  vertical-align: middle;
+}
 /* .button-container {
   display: flex;
   flex-wrap: wrap;