m0_70156489 il y a 1 jour
Parent
commit
f50a3cb793

+ 86 - 61
src/view/SalaryManage/ProcessProduction.vue

@@ -84,15 +84,12 @@
           :header-row-style="{ height: '36px' }"
           :span-method="tableSpanMethod"
           :row-class-name="processTableRowClassName"
-          :cell-style="processTableCellStyle"
           @row-click="onProcessTableRowClick"
-          @row-mouseenter="onProcessTableRowMouseEnter"
-          @row-mouseleave="onProcessTableRowMouseLeave"
           :summary-method="tableSummaryMethod"
           show-summary
           show-overflow-tooltip
         >
-          <el-table-column label="小组" prop="小组" width="100" align="center">
+          <el-table-column label="小组" prop="小组" width="97" align="center">
             <template #header>
               <div class="process-production-col-header">
                 <span>小组</span>
@@ -118,13 +115,13 @@
             </template>
           </el-table-column>
           <el-table-column label="部件名称" prop="部件名称" width="90" align="center"  />
-          <el-table-column label="工序编号" prop="工序号" width="90" align="center"  />
+          <el-table-column label="工序号" prop="工序号" width="70" align="center"  />
           <el-table-column label="工序名称" prop="工序名称" min-width="280" align="left"  show-overflow-tooltip />
-          <el-table-column label="员工姓名" prop="员工姓名" width="110" align="left">
+          <el-table-column label="员工姓名" prop="员工姓名" width="108" align="left">
             <template #header>
               <div class="process-production-col-header">
                 <span>员工姓名</span>
-                <el-popover trigger="click" placement="bottom-start" :width="180" :teleported="true" :z-index="10050" append-to-body popper-class="gy-detail-filter-popper">
+                <el-popover trigger="click" placement="bottom-start" :width="170" :teleported="true" :z-index="10050" append-to-body popper-class="gy-detail-filter-popper">
                   <template #reference>
                     <span class="gy-detail-filter-btn" :class="{ 'is-active': isStaffNameFilterActive }" @click.stop>
                       <svg viewBox="0 0 1024 1024" width="14" height="14" aria-hidden="true"><path fill="currentColor" d="M880.1 154H143.9c-24.5 0-39.9 26.7-27.6 48L349 597.4V838c0 17.7 14.2 32 31.8 32h262.4c17.6 0 31.8-14.3 31.8-32V597.4L907.7 202c12.2-21.3-3.1-48-27.6-48z"/></svg>
@@ -145,7 +142,10 @@
               </div>
             </template>
           </el-table-column>
-          <el-table-column label="数量" prop="数量" width="90" align="center" />
+          <el-table-column label="数量" prop="数量" width="85" align="center" />
+          <el-table-column label="累计完成数" prop="累计完成数" width="96" align="center" />
+          <el-table-column label="未完成数" prop="未完成数" width="90" align="center" />
+
           <el-table-column v-if="!hideAmountColumns" label="定额分" prop="工分" width="100" align="center" />
           <el-table-column v-if="!hideAmountColumns" label="标准总工时(秒)" prop="工时" width="150" align="center" />
           <el-table-column v-if="!hideAmountColumns" label="工资" prop="工资" width="100" align="center" />
@@ -248,7 +248,7 @@ const sumTableColumn = (data, prop) => {
 
 const formatSummaryValue = (total, prop) => {
   if (!Number.isFinite(total)) return ''
-  if (prop === '数量') {
+  if (prop === '数量' || prop === '累计完成数' || prop === '未完成数') {
     return Number.isInteger(total) ? String(total) : total.toFixed(2)
   }
   return total.toFixed(2)
@@ -256,8 +256,8 @@ const formatSummaryValue = (total, prop) => {
 
 const tableSummaryMethod = ({ columns, data }) => {
   const summaryProps = props.hideAmountColumns
-    ? ['数量']
-    : ['数量', '工分', '工时', '工资']
+    ? ['数量', '累计完成数', '未完成数']
+    : ['数量', '累计完成数', '未完成数', '工分', '工时', '工资']
 
   return columns.map((column, index) => {
     if (index === 0) return '合计'
@@ -335,6 +335,8 @@ const flattenProcessList = (processList = []) => {
         工序名称: proc.工序名称 ?? '',
         员工姓名: '',
         数量: '',
+        累计完成数: proc.累计完成数 ?? '',
+        未完成数: proc.未完成数 ?? '',
         开工日期: '',
         完工日期: '',
         工分: '',
@@ -342,6 +344,7 @@ const flattenProcessList = (processList = []) => {
         工资: '',
         _processRowSpan: 1,
         _groupRowSpan: 1,
+        _statRowSpan: 1,
       })
       continue
     }
@@ -354,6 +357,9 @@ const flattenProcessList = (processList = []) => {
         工序名称: proc.工序名称 ?? '',
         员工姓名: staff.员工姓名 ?? '',
         数量: staff.数量 ?? '',
+        // 累计/未完成是工序级,只在首行展示(后面合并单元格)
+        累计完成数: index === 0 ? (staff.累计完成数 ?? proc.累计完成数 ?? '') : '',
+        未完成数: index === 0 ? (staff.未完成数 ?? proc.未完成数 ?? '') : '',
         开工日期: staff.开工日期 ?? '',
         完工日期: staff.完工日期 ?? '',
         工分: staff.工分 ?? staff.金额 ?? '',
@@ -361,6 +367,7 @@ const flattenProcessList = (processList = []) => {
         工资: staff.工资 ?? '',
         _processRowSpan: index === 0 ? staffs.length : 0,
         _groupRowSpan: 1,
+        _statRowSpan: index === 0 ? staffs.length : 0,
       })
     })
   }
@@ -482,11 +489,15 @@ const recomputeProcessRowSpan = (rows) => {
     const group = rows.slice(i, j)
     const groupName = String(group[0].小组 || '').trim()
     const mergeGroup = groupName && group.every((r) => String(r.小组 || '').trim() === groupName)
+    const partName = String(group[0].部件名称 || '').trim()
+    const mergePart = partName && group.every((r) => String(r.部件名称 || '').trim() === partName)
     group.forEach((row, idx) => {
       result.push({
         ...row,
         _processRowSpan: idx === 0 ? group.length : 0,
         _groupRowSpan: mergeGroup ? (idx === 0 ? group.length : 0) : 1,
+        _partRowSpan: mergePart ? (idx === 0 ? group.length : 0) : 1,
+        _statRowSpan: idx === 0 ? group.length : 0,
       })
     })
     i = j
@@ -513,53 +524,21 @@ const resetAllColumnFilters = () => {
 const getProcessKey = (row) => `${row?.工序号 ?? ''}||${row?.工序名称 ?? ''}`
 
 const activeProcessKey = ref('')
-const hoveredProcessKey = ref('')
 
 watch(flatTableRows, () => {
   resetAllColumnFilters()
   activeProcessKey.value = ''
-  hoveredProcessKey.value = ''
 })
 
 const onProcessTableRowClick = (row) => {
   activeProcessKey.value = getProcessKey(row)
 }
 
-const onProcessTableRowMouseEnter = (row) => {
-  hoveredProcessKey.value = getProcessKey(row)
-}
-
-const onProcessTableRowMouseLeave = () => {
-  hoveredProcessKey.value = ''
-}
-
-const isProcessRowHighlighted = (row, activeKey, hoverKey) => {
-  const key = getProcessKey(row)
-  return (activeKey && key === activeKey) || (hoverKey && key === hoverKey)
-}
-
+/** 点击选中:同一工序整组高亮(含多员工子行);无悬停高亮 */
 const processTableRowClassName = computed(() => {
   const activeKey = activeProcessKey.value
-  const hoverKey = hoveredProcessKey.value
-  return ({ row }) => {
-    if (isProcessRowHighlighted(row, activeKey, hoverKey)) {
-      return activeKey && getProcessKey(row) === activeKey
-        ? 'process-production-row-active'
-        : 'process-production-row-hover'
-    }
-    return ''
-  }
-})
-
-const processTableCellStyle = computed(() => {
-  const activeKey = activeProcessKey.value
-  const hoverKey = hoveredProcessKey.value
-  return ({ row }) => {
-    if (isProcessRowHighlighted(row, activeKey, hoverKey)) {
-      return { backgroundColor: '#ff80ff' }
-    }
-    return {}
-  }
+  return ({ row }) =>
+    activeKey && getProcessKey(row) === activeKey ? 'process-production-row-active' : ''
 })
 
 const tableSpanMethod = ({ row, column }) => {
@@ -568,6 +547,16 @@ const tableSpanMethod = ({ row, column }) => {
     if (rowspan > 0) return { rowspan, colspan: 1 }
     return { rowspan: 0, colspan: 0 }
   }
+  if (column.property === '部件名称') {
+    const rowspan = row._partRowSpan ?? 1
+    if (rowspan > 0) return { rowspan, colspan: 1 }
+    return { rowspan: 0, colspan: 0 }
+  }
+  if (column.property === '累计完成数' || column.property === '未完成数') {
+    const rowspan = row._statRowSpan ?? 1
+    if (rowspan > 0) return { rowspan, colspan: 1 }
+    return { rowspan: 0, colspan: 0 }
+  }
   if (column.property !== '工序号' && column.property !== '工序名称') {
     return { rowspan: 1, colspan: 1 }
   }
@@ -578,22 +567,53 @@ const tableSpanMethod = ({ row, column }) => {
   return { rowspan: 0, colspan: 0 }
 }
 
+const SUMMARY_FIELD_ALIASES = {
+  客户编号: ['客户编号', '客户', '客户代码', '客户代号', 'customer_code', 'customerCode', 'CustomerNo'],
+  订单编号: ['订单编号', '工单编号', 'workorder', 'work_order'],
+  生产款号: ['生产款号', '款号'],
+  款式: ['款式', '款式名称'],
+  订单数量: ['订单数量', '数量', '制单数'],
+}
+
+const pickSummaryField = (data, fieldKey) => {
+  const aliases = SUMMARY_FIELD_ALIASES[fieldKey] || [fieldKey]
+  for (const key of aliases) {
+    const val = data?.[key]
+    if (val != null && String(val).trim() !== '') return val
+  }
+  return ''
+}
+
 const resetOrderSummary = (workorder = '') => {
   Object.keys(orderSummary).forEach((key) => {
     orderSummary[key] = ''
   })
+  ;['客户编号', '订单编号', '生产款号', '款式', '订单数量'].forEach((key) => {
+    orderSummary[key] = ''
+  })
   orderSummary.订单编号 = workorder
 }
 
-const applyOrderSummaryFromWorkOrder = (data, workorder) => {
-  Object.keys(orderSummary).forEach((key) => {
-    orderSummary[key] = ''
-  })
+/** @param {{ merge?: boolean }} options merge=true 时保留已有非空字段(避免核查接口覆盖掉客户编号等) */
+const applyOrderSummaryFromWorkOrder = (data, workorder, options = {}) => {
+  const { merge = false } = options
+  if (!merge) {
+    Object.keys(orderSummary).forEach((key) => {
+      orderSummary[key] = ''
+    })
+  }
   for (const [key, val] of Object.entries(data || {})) {
-    if (isScalarSummaryValue(val)) {
-      orderSummary[key] = val ?? ''
-    }
+    if (!isScalarSummaryValue(val)) continue
+    if (merge && (val == null || String(val).trim() === '') && orderSummary[key]) continue
+    orderSummary[key] = val ?? ''
   }
+  // 统一摘要展示字段(兼容接口别名;merge 时不覆盖已有非空值)
+  ;['客户编号', '订单编号', '生产款号', '款式', '订单数量'].forEach((fieldKey) => {
+    const picked = pickSummaryField(data, fieldKey)
+    if (!picked) return
+    if (merge && orderSummary[fieldKey]) return
+    orderSummary[fieldKey] = picked
+  })
   orderSummary.订单编号 = orderSummary.订单编号 || workorder
 }
 
@@ -732,11 +752,11 @@ const executeProductionSearch = async (orderNo, processFilter = '') => {
     }
 
     const data = res.data || {}
-    applyOrderSummaryFromWorkOrder(data, workorder)
+    // 与 WorkOrderList 摘要合并:核查接口通常不含客户编号,不能整表覆盖
+    applyOrderSummaryFromWorkOrder(data, workorder, { merge: true })
     processListRaw.value = data.工序列表 || []
     resetAllColumnFilters()
     activeProcessKey.value = ''
-    hoveredProcessKey.value = ''
     if (!processListRaw.value.length) {
       pageHint.value = '未找到报工数据'
     }
@@ -928,15 +948,20 @@ defineExpose({ loadByWorkorder })
   background: #fafafa;
 }
 
-/* 合并单元格时按工序组整组高亮,同工序多员工行同步着色 */
-.process-production-table-wrap :deep(.el-table__body tr.process-production-row-active > td.el-table__cell),
-.process-production-table-wrap :deep(.el-table__body tr.process-production-row-active.el-table__row--striped > td.el-table__cell) {
-  background-color: #ff80ff !important;
+/* 取消原生悬停高亮;仅保留点击选中整组高亮 */
+.process-production-table-wrap :deep(.el-table__body tr:hover > td.el-table__cell) {
+  background-color: var(--el-table-tr-bg-color, #fff) !important;
+}
+.process-production-table-wrap :deep(.el-table__body tr.el-table__row--striped:hover > td.el-table__cell) {
+  background-color: var(--el-fill-color-lighter, #fafafa) !important;
 }
 
-.process-production-table-wrap :deep(.el-table__body tr.process-production-row-hover > td.el-table__cell),
-.process-production-table-wrap :deep(.el-table__body tr.process-production-row-hover.el-table__row--striped > td.el-table__cell) {
+.process-production-table-wrap :deep(.el-table__body tr.process-production-row-active > td.el-table__cell),
+.process-production-table-wrap :deep(.el-table__body tr.process-production-row-active > td.el-table__cell .cell),
+.process-production-table-wrap :deep(.el-table__body tr:hover.process-production-row-active > td.el-table__cell),
+.process-production-table-wrap :deep(.el-table__body tr.el-table__row--striped.process-production-row-active > td.el-table__cell) {
   background-color: #ff80ff !important;
+  background: #ff80ff !important;
 }
 
 .process-production-col-header {

+ 4 - 2
src/view/layout/index.vue

@@ -14,11 +14,13 @@
           class="min-h-[60px] text-center transition-all duration-300 flex items-center justify-center gap-2"
           :style="{background: backgroundColor}"
         >
-          <img
+          <!-- <img
             alt
             class="w-9 h-9 p-1 bg-white rounded-full"
             :src="`https://www.reachingkayo.com/Uploads/62f88d57efb9d749.jpg`"
-          >
+          > -->
+          <img class="w-9 h-9 p-1 bg-white rounded-full" :src="`/src/assets/dacheng.jpg`" alt width="400px" height="400px" />
+             
           <div
             v-if="isSider"
             class="inline-flex font-bold text-1xl"

+ 2 - 2
src/view/login/index.vue

@@ -14,8 +14,8 @@
           <div>
             <!--LOGO-->
             <div class="flex items-center justify-center" style="height:100px;width: 100px;margin-left: 110px;" >
-              <!-- <img class="w-48 h-16" :src="`/src/assets/dacheng.jpg`" alt /> -->
-              <img class="w-48 h-48" :src="`https://reachingkayo.com/Uploads/62f88d57efb9d749.jpg`" alt width="400px" height="400px"/>
+              <img class="w-48 h-48" :src="`/src/assets/dacheng.jpg`" alt width="400px" height="400px" />
+              <!-- <img class="w-48 h-48" :src="`https://reachingkayo.com/Uploads/62f88d57efb9d749.jpg`" alt width="400px" height="400px"/> -->
             </div>
             <div class="mb-9">
               <p class="text-center text-3xl font-bold">

+ 27 - 12
src/view/performance/WorkScoreReporting/gongfenbaogong.vue

@@ -276,8 +276,10 @@
                                 class="work-score-process-table"
                                 style="width: 100%;"
                               >
-                                <el-table-column prop="工艺编号" label="工序号" width="72" />
-                                <el-table-column prop="工艺名称" label="工序名称" min-width="140" />
+                                <el-table-column prop="工艺编号" label="工序号" width="72" align="center" />
+                                <el-table-column prop="工艺名称" label="工序名称" min-width="240" />
+                                <el-table-column prop="累计完成数" label="累计完成数" min-width="60" align="center" />
+                                <el-table-column prop="未完成数" label="未完成数" min-width="60" align="center" />
                                 <el-table-column label="完工人员" width="120">
                                   <template #default="scope">
                                     <div v-if="getReportingData(selectedPartIndex, scope.$index).staffList">
@@ -1755,10 +1757,20 @@ const warningres = (arr) => {
 :deep(.el-table__body tr.current-row) > td {
   background: #ff80ff !important;
 }
-/* 鼠标悬停时的背景色 */
+/* 鼠标悬停时的背景色(工序产量核查表关闭悬停高亮) */
 :deep(.el-table__body tr:hover) > td {
   background: #ff80ff !important;
 }
+:deep(.process-production-table-wrap .el-table__body tr:hover > td) {
+  background-color: var(--el-table-tr-bg-color, #fff) !important;
+}
+:deep(.process-production-table-wrap .el-table__body tr.el-table__row--striped:hover > td) {
+  background-color: var(--el-fill-color-lighter, #fafafa) !important;
+}
+:deep(.process-production-table-wrap .el-table__body tr.process-production-row-active > td) {
+  background-color: #ff80ff !important;
+  background: #ff80ff !important;
+}
 
 /* 员工选择区域样式 */
 .employee-grid {
@@ -1949,7 +1961,7 @@ const warningres = (arr) => {
   overflow: hidden;
 }
 .work-score-sidebar {
-  width: 240px;
+  width: 170px;
   border-right: 1px solid #e0e0e0;
   display: flex;
   flex-direction: column;
@@ -1992,10 +2004,13 @@ const warningres = (arr) => {
   border-bottom: 1px solid #e0e0e0;
   background-color: #fafafa;
 }
+.work-score-employee-section .employee-grid {
+  margin-top: 0;
+}
 .work-score-step-header {
   flex-shrink: 0;
-  padding: 6px 12px;
-  min-height: 34px;
+  padding: 4px 12px;
+  min-height: 28px;
   box-sizing: border-box;
   background: #fafafa;
 }
@@ -2066,8 +2081,8 @@ const warningres = (arr) => {
   display: flex;
   flex-wrap: nowrap;
   gap: 6px;
-  padding: 6px 10px 8px;
-  min-height: 86px;
+  padding: 4px 10px 4px;
+  min-height: 0;
   box-sizing: border-box;
 }
 .work-score-employee-section .employee-card {
@@ -2120,7 +2135,7 @@ const warningres = (arr) => {
   overflow: hidden;
   display: flex;
   flex-direction: column;
-  padding: 6px 10px 8px;
+  padding: 2px 10px 4px;
 }
 .work-score-process-block {
   flex: 1;
@@ -2136,8 +2151,8 @@ const warningres = (arr) => {
   justify-content: space-between;
   flex-wrap: wrap;
   gap: 8px;
-  margin-bottom: 6px;
-  padding-bottom: 6px;
+  margin-bottom: 2px;
+  padding-bottom: 2px;
   background: #fff;
   border-bottom: 1px solid #ebeef5;
   z-index: 2;
@@ -2149,7 +2164,7 @@ const warningres = (arr) => {
 }
 .work-score-process-toolbar--simple {
   flex-shrink: 0;
-  padding: 6px 12px;
+  padding: 4px 12px;
   background: #fafafa;
   border-bottom: 1px solid #ebeef5;
   border-radius: 0;

+ 434 - 489
src/view/yunyin/shengchanguanli/MonthlySampleGarments.vue

@@ -3,42 +3,38 @@
       <layout>
         <layout-header>
           <div class="">
-            <!--          按钮部分-->
-            <el-form ref="elSearchFormRef"   class="demo-form-inline" :rules="searchRule" >
+            <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" @keyup.enter="onSubmit"></el-input>
-                <el-button type="primary" class="bt" icon="download" @click="hzToExcel" >导出到Excel(汇总)</el-button>
-                <el-button type="primary" class="bt" icon="download" @click="mxToExcel" >导出到Excel(明细)</el-button> -->
               </el-form-item>
             </el-form>
-  
-        
-  
           </div>
         </layout-header>
-  
+
         <layout>
-          <!--    左侧树侧形结构-->
           <layout-sider :resize-directions="['right']" :width="190" style="margin-right: 10px;">
             <div class="JKWTree-tree" style="height: 200px">
               <h3>样衣生产月份</h3>
-              <el-tree :data="treeData"  :props="defaultProps" @node-click="handleNodeClick" class="treecolor">
-              </el-tree>
+              <el-tree
+                ref="menuTreeRef"
+                :data="treeData"
+                :props="defaultProps"
+                node-key="id"
+                :default-expanded-keys="defaultExpandedKeys"
+                :current-node-key="currentTreeKey"
+                highlight-current
+                @node-click="handleNodeClick"
+                class="treecolor"
+              />
             </div>
           </layout-sider>
-  
-          <!-- 右侧区域 -->
-          <layout-content >
+
+          <layout-content>
           <el-main>
             <div class="gva-table-box">
-              <!-- <div style="margin-bottom: 10px; text-align: right;">
-                <el-button size="small" type="primary" @click="addFirstRow">新增一行</el-button>
-              </div> -->
-              <!-- 表格数据 -->
               <el-table ref="multipleTable" style="width: 100%;height: 70vh" tooltip-effect="dark"
-                        :row-style="{ height: '25px' }"  :header-cell-style="{ padding: '0px' }"
+                        :row-style="{ height: '25px' }" :header-cell-style="{ padding: '0px' }"
                         :cell-style="{ padding: '0px' }" :header-row-style="{ height: '20px' }"
-                        :data="tableData"  border row-key="ID"
+                        :data="tableData" border row-key="ID"
                         size="small"
                         :cell-class-name="gxbgCellClass"
                         highlight-current-row="true"
@@ -48,128 +44,131 @@
                         @row-dblclick="handleRowDblclick">
                <el-table-column sortable align="center" label="客户编号" prop="客户编号" width="120">
                  <template #default="{ row }">
-                   <el-input v-if="editingCell.rowId === (row.UniqId || row.tempId) && editingCell.prop === '客户编号'"
+                   <el-input v-if="isEditing(row, '客户编号')"
                              v-model="row.客户编号"
                              @blur="handleCellBlur(row, '客户编号')"
                              @keyup.enter="handleCellBlur(row, '客户编号')"
                              :ref="el => { if (el) el.focus() }" />
-                   <span v-else @click="handleCellClick(row, '客户编号')" class="cell-span">{{ row.客户编号 }}</span>
+                   <div v-else class="cell-span">{{ row.客户编号 || '\u00A0' }}</div>
                  </template>
                </el-table-column>
                <el-table-column sortable align="center" label="款号" prop="款号" width="120">
                  <template #default="{ row }">
-                   <el-input v-if="editingCell.rowId === (row.UniqId || row.tempId) && editingCell.prop === '款号'"
+                   <el-input v-if="isEditing(row, '款号')"
                              v-model="row.款号"
                              @blur="handleCellBlur(row, '款号')"
                              @keyup.enter="handleCellBlur(row, '款号')"
                              :ref="el => { if (el) el.focus() }" />
-                   <span v-else @click="handleCellClick(row, '款号')" class="cell-span">{{ row.款号 }}</span>
+                   <div v-else class="cell-span">{{ row.款号 || '\u00A0' }}</div>
                  </template>
                </el-table-column>
                <el-table-column sortable align="center" label="款式" prop="款式" width="120">
                  <template #default="{ row }">
-                   <el-input v-if="editingCell.rowId === (row.UniqId || row.tempId) && editingCell.prop === '款式'"
+                   <el-input v-if="isEditing(row, '款式')"
                              v-model="row.款式"
                              @blur="handleCellBlur(row, '款式')"
                              @keyup.enter="handleCellBlur(row, '款式')"
                              :ref="el => { if (el) el.focus() }" />
-                   <span v-else @click="handleCellClick(row, '款式')" class="cell-span">{{ row.款式 }}</span>
+                   <div v-else class="cell-span">{{ row.款式 || '\u00A0' }}</div>
                  </template>
                </el-table-column>
                <el-table-column sortable align="center" label="样办种类" prop="样办种类" width="120">
                  <template #default="{ row }">
-                   <el-input v-if="editingCell.rowId === (row.UniqId || row.tempId) && editingCell.prop === '样办种类'"
+                   <el-input v-if="isEditing(row, '样办种类')"
                              v-model="row.样办种类"
                              @blur="handleCellBlur(row, '样办种类')"
                              @keyup.enter="handleCellBlur(row, '样办种类')"
                              :ref="el => { if (el) el.focus() }" />
-                   <span v-else @click="handleCellClick(row, '样办种类')" class="cell-span">{{ row.样办种类 }}</span>
+                   <div v-else class="cell-span">{{ row.样办种类 || '\u00A0' }}</div>
                  </template>
                </el-table-column>
                <el-table-column sortable align="center" label="做办部门" prop="做办部门" width="120">
                  <template #default="{ row }">
-                   <el-input v-if="editingCell.rowId === (row.UniqId || row.tempId) && editingCell.prop === '做办部门'"
+                   <el-input v-if="isEditing(row, '做办部门')"
                              v-model="row.做办部门"
                              @blur="handleCellBlur(row, '做办部门')"
                              @keyup.enter="handleCellBlur(row, '做办部门')"
                              :ref="el => { if (el) el.focus() }" />
-                   <span v-else @click="handleCellClick(row, '做办部门')" class="cell-span">{{ row.做办部门 }}</span>
+                   <div v-else class="cell-span">{{ row.做办部门 || '\u00A0' }}</div>
                  </template>
                </el-table-column>
                <el-table-column sortable align="center" label="数量" prop="数量" width="100">
                  <template #default="{ row }">
-                   <el-input v-if="editingCell.rowId === (row.UniqId || row.tempId) && editingCell.prop === '数量'"
+                   <el-input v-if="isEditing(row, '数量')"
                              v-model="row.数量"
                              @blur="handleCellBlur(row, '数量')"
                              @keyup.enter="handleCellBlur(row, '数量')"
                              :ref="el => { if (el) el.focus() }" />
-                   <span v-else @click="handleCellClick(row, '数量')" class="cell-span">{{ row.数量 }}</span>
+                   <div v-else class="cell-span">{{ row.数量 || '\u00A0' }}</div>
                  </template>
                </el-table-column>
                <el-table-column sortable align="center" label="下单日期" prop="下单日期" width="120">
                  <template #default="{ row }">
-                   <el-date-picker v-if="editingCell.rowId === (row.UniqId || row.tempId) && editingCell.prop === '下单日期'"
+                   <el-date-picker v-if="isEditing(row, '下单日期')"
                                    v-model="row.下单日期"
                                    type="date"
                                    value-format="YYYY-MM-DD"
                                    placeholder="选择日期"
                                    style="width: 100%"
-                                   @blur="handleCellBlur(row, '下单日期')" />
-                   <span v-else @click="handleCellClick(row, '下单日期')" class="cell-span">{{ row.下单日期 }}</span>
+                                   @blur="handleCellBlur(row, '下单日期')"
+                                   @change="handleCellBlur(row, '下单日期')" />
+                   <div v-else class="cell-span">{{ row.下单日期 || '\u00A0' }}</div>
                  </template>
                </el-table-column>
                <el-table-column sortable align="center" label="面料进度" prop="面料进度" width="120">
                  <template #default="{ row }">
-                   <el-input v-if="editingCell.rowId === (row.UniqId || row.tempId) && editingCell.prop === '面料进度'"
+                   <el-input v-if="isEditing(row, '面料进度')"
                              v-model="row.面料进度"
                              @blur="handleCellBlur(row, '面料进度')"
                              @keyup.enter="handleCellBlur(row, '面料进度')"
                              :ref="el => { if (el) el.focus() }" />
-                   <span v-else @click="handleCellClick(row, '面料进度')" class="cell-span">{{ row.面料进度 }}</span>
+                   <div v-else class="cell-span">{{ row.面料进度 || '\u00A0' }}</div>
                  </template>
                </el-table-column>
                <el-table-column sortable align="center" label="辅料进度" prop="辅料进度" width="120">
                  <template #default="{ row }">
-                   <el-input v-if="editingCell.rowId === (row.UniqId || row.tempId) && editingCell.prop === '辅料进度'"
+                   <el-input v-if="isEditing(row, '辅料进度')"
                              v-model="row.辅料进度"
                              @blur="handleCellBlur(row, '辅料进度')"
                              @keyup.enter="handleCellBlur(row, '辅料进度')"
                              :ref="el => { if (el) el.focus() }" />
-                   <span v-else @click="handleCellClick(row, '辅料进度')" class="cell-span">{{ row.辅料进度 }}</span>
+                   <div v-else class="cell-span">{{ row.辅料进度 || '\u00A0' }}</div>
                  </template>
                </el-table-column>
                <el-table-column sortable align="center" label="计划交办日期" prop="计划交办日期" width="140">
                  <template #default="{ row }">
-                   <el-date-picker v-if="editingCell.rowId === (row.UniqId || row.tempId) && editingCell.prop === '计划交办日期'"
+                   <el-date-picker v-if="isEditing(row, '计划交办日期')"
                                    v-model="row.计划交办日期"
                                    type="date"
                                    value-format="YYYY-MM-DD"
                                    placeholder="选择日期"
                                    style="width: 100%"
-                                   @blur="handleCellBlur(row, '计划交办日期')" />
-                   <span v-else @click="handleCellClick(row, '计划交办日期')" class="cell-span">{{ row.计划交办日期 }}</span>
+                                   @blur="handleCellBlur(row, '计划交办日期')"
+                                   @change="handleCellBlur(row, '计划交办日期')" />
+                   <div v-else class="cell-span">{{ row.计划交办日期 || '\u00A0' }}</div>
                  </template>
                </el-table-column>
                <el-table-column sortable align="center" label="实际交办日期" prop="实际交办日期" width="140">
                  <template #default="{ row }">
-                   <el-date-picker v-if="editingCell.rowId === (row.UniqId || row.tempId) && editingCell.prop === '实际交办日期'"
+                   <el-date-picker v-if="isEditing(row, '实际交办日期')"
                                    v-model="row.实际交办日期"
                                    type="date"
                                    value-format="YYYY-MM-DD"
                                    placeholder="选择日期"
                                    style="width: 100%"
-                                   @blur="handleCellBlur(row, '实际交办日期')" />
-                   <span v-else @click="handleCellClick(row, '实际交办日期')" class="cell-span">{{ row.实际交办日期 }}</span>
+                                   @blur="handleCellBlur(row, '实际交办日期')"
+                                   @change="handleCellBlur(row, '实际交办日期')" />
+                   <div v-else class="cell-span">{{ row.实际交办日期 || '\u00A0' }}</div>
                  </template>
                </el-table-column>
                <el-table-column sortable align="center" label="备注" prop="备注" width="150">
                  <template #default="{ row }">
-                   <el-input v-if="editingCell.rowId === (row.UniqId || row.tempId) && editingCell.prop === '备注'"
+                   <el-input v-if="isEditing(row, '备注')"
                              v-model="row.备注"
                              @blur="handleCellBlur(row, '备注')"
                              @keyup.enter="handleCellBlur(row, '备注')"
                              :ref="el => { if (el) el.focus() }" />
-                   <span v-else @click="handleCellClick(row, '备注')" class="cell-span">{{ row.备注 }}</span>
+                   <div v-else class="cell-span">{{ row.备注 || '\u00A0' }}</div>
                  </template>
                </el-table-column>
                <el-table-column align="center" label="操作" width="230" fixed="right">
@@ -181,13 +180,11 @@
                </el-table-column>
               </el-table>
 
-              <!-- 分页 -->
               <div class="gva-pagination">
                 <el-pagination layout="total" :current-page="page" :page-size="pageSize"
                  :total="total" @current-change="handleCurrentChange" @size-change="handleSizeChange" />
               </div>
 
-              <!-- 日志弹窗 -->
               <el-dialog title="修改记录" v-model="logDialogVisible" width="800px">
                 <el-table :data="logTableData" border size="small">
                   <el-table-column prop="ModifyField" label="修改字段" width="120" />
@@ -198,485 +195,433 @@
                 </el-table>
               </el-dialog>
             </div>
-
           </el-main>
         </layout-content>
         </layout>
       </layout>
     </div>
   </template>
-  <script setup>
-  import { Layout, LayoutSider, LayoutContent } from '@arco-design/web-vue';
-  import * as XLSX from 'xlsx'
-  import FileSaver from 'file-saver'
-  import {ref, reactive} from 'vue'
-  import { exportExcelFile } from '@/utils/excel'
-  import {ElMessage} from "element-plus";
-  import { getDateTree, getMonthlyGarmentsList, saveMonthlyGarments, updateMonthlyGarments, deleteMonthlyGarments, getLogs, } from '@/api/mes/job'
-  import { useUserStore } from '@/pinia/modules/user';
-  //获取登录用户信息
-    const userStore = useUserStore()
-    const _username = ref('')
-    _username.value = userStore.userInfo.userName + '/' + userStore.userInfo.nickName
-  defineOptions({name: 'Company'})
-
-  const treeData = ref([])
-  const defaultProps = {
-    children: 'children',
-    label: 'label'
-  }
+<script setup>
+import { Layout, LayoutSider, LayoutContent } from '@arco-design/web-vue'
+import * as XLSX from 'xlsx'
+import { ref, reactive, nextTick } from 'vue'
+import { ElMessage } from 'element-plus'
+import { getDateTree, getMonthlyGarmentsList, saveMonthlyGarments, updateMonthlyGarments, deleteMonthlyGarments, getLogs } from '@/api/mes/job'
+import { useUserStore } from '@/pinia/modules/user'
 
-  const getTree = async () => {
-    try {
-      const response = await getDateTree() 
-      const data = response.data || {}
-      
-      const sortedYearMonths = Object.keys(data).sort((a, b) => {
-        const yearMonthA = parseInt(a);
-        const yearMonthB = parseInt(b);
-        return yearMonthB - yearMonthA;
-      });
-      
-      treeData.value = sortedYearMonths.map(yearMonth => {
-        return {
-          label: `${yearMonth}`,
-          children: data[yearMonth].map(customer => {
-            return {
-              label: `${customer.客户编号} (${customer.total}条)`,
-              yearMonth: yearMonth,
-              customerCode: customer.客户编号,
-              total: customer.total,
-            }
-          })
-        }
-      })
+defineOptions({ name: 'Company' })
 
-      if (treeData.value.length === 0 && tableData.value.length === 0) {
-        tableData.value.push({
-          tempId: 'empty_row_' + Date.now(),
-          '客户编号': '',
-          '款号': '',
-          '款式': '',
-          '样办种类': '',
-          '做办部门': '',
-          '数量': '',
-          '下单日期': '',
-          '面料进度': '',
-          '辅料进度': '',
-          '计划交办日期': '',
-          '实际交办日期': '',
-          '备注': ''
-        })
-      }
-    } catch (error) {
-      ElMessage.error('获取数据失败')
-      console.error('获取树形数据失败:', error)
-    }
-  }
-  getTree()
-
-  const tableData = ref([])
-  const _noderq = ref('')
-  const _nodecode = ref('')
-  const page = ref(1)
-  const pageSize = ref(20)
-  const total = ref(0)
-  const editingCell = reactive({ rowId: null, prop: null })
-  const editingOriginalValue = ref('')
-  const logDialogVisible = ref(false)
-  const logTableData = ref([])
-
-  const handleNodeClick = async (node) => {
-    const allNodes = document.querySelectorAll('.treecolor .el-tree-node');
-    allNodes.forEach(node => {
-      node.querySelector('.el-tree-node__label').style.color = '';
-    });
-    
-    const clickedNodeId = node['$treeNodeId'];
-    const clickedNode = document.querySelector(`.treecolor .el-tree-node[data-key="${clickedNodeId}"]`);
-    if (clickedNode) {
-      clickedNode.querySelector('.el-tree-node__label').style.color = 'red';
-    }
-    
-    _noderq.value = node.yearMonth
-    _nodecode.value = node.customerCode
-    
-    if (!node.children) {
-        const res = await getMonthlyGarmentsList({sys_rq: node.yearMonth, customer: node.customerCode})
-        if(res.code === 0){
-          tableData.value = res.data.result || []
-          total.value = res.data.total || res.data.result.length
-        }
-        if (tableData.value.length === 0) {
-          tableData.value.push({
-            tempId: 'empty_row_' + Date.now(),
-            '客户编号': '',
-            '款号': '',
-            '款式': '',
-            '样办种类': '',
-            '做办部门': '',
-            '数量': '',
-            '下单日期': '',
-            '面料进度': '',
-            '辅料进度': '',
-            '计划交办日期': '',
-            '实际交办日期': '',
-            '备注': ''
-          })
-        }
-      }
-  }
+const userStore = useUserStore()
+const _username = ref('')
+_username.value = userStore.userInfo.userName + '/' + userStore.userInfo.nickName
 
-  const searchInfo = ref('')
-  const searchRule = {}
-  const elSearchFormRef = ref()
+const treeData = ref([])
+const menuTreeRef = ref(null)
+const defaultExpandedKeys = ref([])
+const currentTreeKey = ref('')
+const defaultProps = {
+  children: 'children',
+  label: 'label'
+}
 
-  const onSubmit = async () => {
-    if (!searchInfo.value) {
-      ElMessage.warning('请输入搜索内容');
-      return;
-    } else {
-      const res = await getMonthlyGarmentsList({search: searchInfo.value})
-      if(res.code === 0){
-        tableData.value = res.data.result
-        total.value = res.data.total || res.data.result.length
-      }
-    }
-  }
+const tableData = ref([])
+const _noderq = ref('')
+const _nodecode = ref('')
+const page = ref(1)
+const pageSize = ref(20)
+const total = ref(0)
+const editingCell = reactive({ rowId: null, prop: null })
+const editingOriginalValue = ref('')
+const logDialogVisible = ref(false)
+const logTableData = ref([])
+const searchInfo = ref('')
+const searchRule = {}
+const elSearchFormRef = ref()
 
-  const tableRowClick = (row) => {
-    console.log('当前行数据:', row)
-  }
+const getCurrentYearMonth = () => {
+  const now = new Date()
+  const y = now.getFullYear()
+  const m = String(now.getMonth() + 1).padStart(2, '0')
+  return `${y}-${m}`
+}
 
-  const handleSelectionChange = (selectedItems) => {
-    console.log('选中项:', selectedItems)
-  }
+const createEmptyRow = () => ({
+  tempId: 'empty_row_' + Date.now() + '_' + Math.random().toString(36).slice(2, 7),
+  客户编号: '',
+  款号: '',
+  款式: '',
+  样办种类: '',
+  做办部门: '',
+  数量: '',
+  下单日期: '',
+  面料进度: '',
+  辅料进度: '',
+  计划交办日期: '',
+  实际交办日期: '',
+  备注: ''
+})
 
-  const handleCurrentChange = (val) => {
-    page.value = val
+const ensureEditableRows = () => {
+  if (!tableData.value.length) {
+    tableData.value.push(createEmptyRow())
   }
+}
 
-  const handleSizeChange = (val) => {
-    pageSize.value = val
-  }
+const rowKeyOf = (row) => row.UniqId || row.tempId
+const isEditing = (row, prop) =>
+  editingCell.rowId === rowKeyOf(row) && editingCell.prop === prop
 
-  const updateCompanyFunc = (row) => {
-    console.log('双击行:', row)
-  }
+const loadListByMonth = async (sys_rq, customer = '') => {
+  _noderq.value = sys_rq || ''
+  _nodecode.value = customer || ''
+  if (!sys_rq) {
+    tableData.value = [createEmptyRow()]
+    total.value = 0
+    return
+  }
+  const params = { sys_rq }
+  if (customer) params.customer = customer
+  const res = await getMonthlyGarmentsList(params)
+  if (res.code === 0) {
+    tableData.value = res.data.result || []
+    total.value = res.data.total || (res.data.result?.length ?? 0)
+  } else {
+    tableData.value = []
+    total.value = 0
+  }
+  ensureEditableRows()
+}
 
-  const gxbgCellClass = ({row, column, rowIndex, columnIndex}) => {
-    return ''
-  }
+const highlightTreeNode = (node) => {
+  const allNodes = document.querySelectorAll('.treecolor .el-tree-node')
+  allNodes.forEach((el) => {
+    const label = el.querySelector('.el-tree-node__label')
+    if (label) label.style.color = ''
+  })
+  const clickedNodeId = node?.['$treeNodeId']
+  if (!clickedNodeId) return
+  const clickedNode = document.querySelector(`.treecolor .el-tree-node[data-key="${clickedNodeId}"]`)
+  if (clickedNode) {
+    const label = clickedNode.querySelector('.el-tree-node__label')
+    if (label) label.style.color = 'red'
+  }
+}
 
-  const addRow = (index) => {
-    const newRow = {
-      tempId: 'temp_' + Date.now(),
-      '客户编号': '',
-      '款号': '',
-      '款式': '',
-      '样办种类': '',
-      '做办部门': '',
-      '数量': '',
-      '下单日期': '',
-      '面料进度': '',
-      '辅料进度': '',
-      '计划交办日期': '',
-      '实际交办日期': '',
-      '备注': ''
-    }
-    tableData.value.splice(index + 1, 0, newRow)
-  }
+const buildTreeData = (data) => {
+  const sortedYearMonths = Object.keys(data || {}).sort((a, b) => parseInt(b) - parseInt(a))
+  return sortedYearMonths.map((yearMonth) => ({
+    id: `m-${yearMonth}`,
+    label: `${yearMonth}`,
+    yearMonth,
+    children: (data[yearMonth] || []).map((customer) => ({
+      id: `c-${yearMonth}-${customer.客户编号}`,
+      label: `${customer.客户编号} (${customer.total}条)`,
+      yearMonth,
+      customerCode: customer.客户编号,
+      total: customer.total,
+    })),
+  }))
+}
 
-  const addFirstRow = () => {
-    const newRow = {
-      tempId: 'temp_' + Date.now(),
-      '客户编号': '',
-      '款号': '',
-      '款式': '',
-      '样办种类': '',
-      '做办部门': '',
-      '数量': '',
-      '下单日期': '',
-      '面料进度': '',
-      '辅料进度': '',
-      '计划交办日期': '',
-      '实际交办日期': '',
-      '备注': ''
-    }
-    tableData.value.push(newRow)
-  }
+const getTree = async () => {
+  try {
+    const response = await getDateTree()
+    const data = response.data || {}
+    treeData.value = buildTreeData(data)
+
+    // 进入页面默认展示当天所在月份数据
+    const todayYm = getCurrentYearMonth()
+    const monthNode =
+      treeData.value.find((n) => n.yearMonth === todayYm) ||
+      treeData.value[0] ||
+      null
 
-  const handleRowDblclick = async (row) => {
-    if (!row.UniqId) return
-    const res = await getLogs({ UniqId: row.UniqId })
-    if (res.code === 0) {
-      logTableData.value = res.data.result || []
-      logDialogVisible.value = true
+    if (monthNode) {
+      currentTreeKey.value = monthNode.id
+      defaultExpandedKeys.value = [monthNode.id]
+      await loadListByMonth(monthNode.yearMonth)
+      await nextTick()
+      menuTreeRef.value?.setCurrentKey?.(monthNode.id)
     } else {
-      ElMessage.error('获取日志失败')
+      ensureEditableRows()
     }
+  } catch (error) {
+    ElMessage.error('获取数据失败')
+    console.error('获取树形数据失败:', error)
+    ensureEditableRows()
   }
+}
+getTree()
+
+const handleNodeClick = async (node) => {
+  highlightTreeNode(node)
+  currentTreeKey.value = node.id || ''
+  // 月份节点:查当月全部;客户节点:按客户过滤
+  if (node.children) {
+    await loadListByMonth(node.yearMonth || node.label)
+    return
+  }
+  if (node.yearMonth) {
+    await loadListByMonth(node.yearMonth, node.customerCode || '')
+  }
+}
+
+const onSubmit = async () => {
+  if (!searchInfo.value) {
+    ElMessage.warning('请输入搜索内容')
+    return
+  }
+  const res = await getMonthlyGarmentsList({ search: searchInfo.value })
+  if (res.code === 0) {
+    tableData.value = res.data.result
+    total.value = res.data.total || res.data.result.length
+    ensureEditableRows()
+  }
+}
+
+const tableRowClick = () => {}
+const handleSelectionChange = () => {}
+const handleCurrentChange = (val) => { page.value = val }
+const handleSizeChange = (val) => { pageSize.value = val }
+const gxbgCellClass = () => ''
+
+const addRow = (index) => {
+  tableData.value.splice(index + 1, 0, createEmptyRow())
+}
+
+const addFirstRow = () => {
+  tableData.value.push(createEmptyRow())
+}
 
-  const deleteRow = async (row, index) => {
-    try {
-      if (row.UniqId) {
-        const res = await deleteMonthlyGarments({ UniqId: row.UniqId, nickName: userStore.userInfo.nickName })
-        if(res.code !== 0){
-          ElMessage.error('删除失败')
-          return
-        }
+const handleRowDblclick = async (row) => {
+  if (!row.UniqId) return
+  const res = await getLogs({ UniqId: row.UniqId })
+  if (res.code === 0) {
+    logTableData.value = res.data.result || []
+    logDialogVisible.value = true
+  } else {
+    ElMessage.error('获取日志失败')
+  }
+}
+
+const deleteRow = async (row, index) => {
+  try {
+    if (row.UniqId) {
+      const res = await deleteMonthlyGarments({ UniqId: row.UniqId, nickName: userStore.userInfo.nickName })
+      if (res.code !== 0) {
+        ElMessage.error('删除失败')
+        return
       }
-      tableData.value.splice(index, 1)
-      ElMessage.success('删除成功')
-      await refreshData()
-    } catch (error) {
-      ElMessage.error('删除失败')
-      console.error('删除失败:', error)
     }
-  }
+    tableData.value.splice(index, 1)
+    ElMessage.success('删除成功')
+    await refreshData()
+  } catch (error) {
+    ElMessage.error('删除失败')
+    console.error('删除失败:', error)
+  }
+}
 
-  const handleCellClick = (row, prop) => {
-    editingCell.rowId = row.UniqId || row.tempId
-    editingCell.prop = prop
-    editingOriginalValue.value = row[prop]
-  }
+const EDITABLE_FIELDS = [
+  '客户编号',
+  '款号',
+  '款式',
+  '样办种类',
+  '做办部门',
+  '数量',
+  '下单日期',
+  '面料进度',
+  '辅料进度',
+  '计划交办日期',
+  '实际交办日期',
+  '备注',
+]
 
-  const handleTableCellClick = (row, column, cell, event) => {
-    console.log('cell-click triggered:', { rowId: row.UniqId || row.tempId, columnLabel: column.label, columnProp: column.prop, columnIndex: column.index })
-    if (column.prop && column.prop !== '') {
-      handleCellClick(row, column.prop)
-      console.log('editingCell after click:', editingCell)
-    }
+const normalizeCellValue = (val) => String(val ?? '').trim()
+
+const isRowAllEmpty = (row) =>
+  EDITABLE_FIELDS.every((field) => !normalizeCellValue(row[field]))
+
+const isCellUnchanged = (row, prop) =>
+  normalizeCellValue(row[prop]) === normalizeCellValue(editingOriginalValue.value)
+
+const clearEditingCell = () => {
+  editingCell.rowId = null
+  editingCell.prop = null
+}
+
+const handleCellClick = (row, prop) => {
+  if (!prop || prop === '操作') return
+  editingCell.rowId = rowKeyOf(row)
+  editingCell.prop = prop
+  editingOriginalValue.value = row[prop] ?? ''
+}
+
+const handleTableCellClick = (row, column) => {
+  // Element Plus 单元格点击事件里字段名在 column.property
+  const prop = column?.property || column?.prop
+  if (!prop || prop === '操作') return
+  handleCellClick(row, prop)
+}
+
+const refreshData = async () => {
+  const keepRq = _noderq.value
+  const keepCode = _nodecode.value
+  try {
+    const response = await getDateTree()
+    treeData.value = buildTreeData(response.data || {})
+  } catch (e) {
+    console.error(e)
+  }
+  await loadListByMonth(keepRq, keepCode)
+}
+
+const handleCellBlur = async (row, prop) => {
+  // 已退出编辑态,忽略重复 blur/change
+  if (editingCell.rowId !== rowKeyOf(row) || editingCell.prop !== prop) {
+    return
   }
 
-  const refreshData = async () => {
-    await getTree()
-    if (_noderq.value && _nodecode.value) {
-      const res = await getMonthlyGarmentsList({sys_rq: _noderq.value, customer: _nodecode.value})
-      if(res.code === 0){
-        tableData.value = res.data.result || []
-        total.value = res.data.total || res.data.result.length
-        if (tableData.value.length === 0) {
-          tableData.value.push({
-            tempId: 'empty_row_' + Date.now(),
-            '客户编号': '',
-            '款号': '',
-            '款式': '',
-            '样办种类': '',
-            '做办部门': '',
-            '数量': '',
-            '下单日期': '',
-            '面料进度': '',
-            '辅料进度': '',
-            '计划交办日期': '',
-            '实际交办日期': '',
-            '备注': ''
-          })
-        }
-      }
-    }
+  // 仅点击未改内容:不触发新增/修改
+  if (isCellUnchanged(row, prop)) {
+    clearEditingCell()
+    return
   }
 
-  const handleCellBlur = async (row, prop) => {
-    try {
-      if (row.UniqId) {
-        if (String(row[prop]) === String(editingOriginalValue.value)) {
-          editingCell.rowId = null
-          editingCell.prop = null
-          return
-        }
-        const res = await updateMonthlyGarments({
-          UniqId: row.UniqId,
-          nickName: userStore.userInfo.nickName,
-          '客户编号': row['客户编号'],
-          '款号': row['款号'],
-          '款式': row['款式'],
-          '样办种类': row['样办种类'],
-          '做办部门': row['做办部门'],
-          '数量': row['数量'],
-          '下单日期': row['下单日期'],
-          '面料进度': row['面料进度'],
-          '辅料进度': row['辅料进度'],
-          '计划交办日期': row['计划交办日期'],
-          '实际交办日期': row['实际交办日期'],
-          '备注': row['备注']
-        })
-        if(res.code !== 0){
-          ElMessage.error('修改失败')
-          editingCell.rowId = null
-          editingCell.prop = null
-          return
-        }
-        await refreshData()
-      } else if (row.tempId) {
-        const res = await saveMonthlyGarments({
-          '客户编号': row['客户编号'],
-          '款号': row['款号'],
-          '款式': row['款式'],
-          '样办种类': row['样办种类'],
-          '做办部门': row['做办部门'],
-          '数量': row['数量'],
-          '下单日期': row['下单日期'],
-          '面料进度': row['面料进度'],
-          '辅料进度': row['辅料进度'],
-          '计划交办日期': row['计划交办日期'],
-          '实际交办日期': row['实际交办日期'],
-          '备注': row['备注']
-        })
-        if(res.code === 0){
-          row.UniqId = parseInt(res.data.id)
-          delete row.tempId
-          await refreshData()
-        } else {
-          ElMessage.error('保存失败')
-          editingCell.rowId = null
-          editingCell.prop = null
-          return
-        }
-      }
-      editingCell.rowId = null
-      editingCell.prop = null
-      ElMessage.success('保存成功')
-    } catch (error) {
-      ElMessage.error('保存失败')
-      console.error('保存失败:', error)
-      editingCell.rowId = null
-      editingCell.prop = null
-    }
+  // 新行且所有列都为空:不触发保存
+  if (row.tempId && isRowAllEmpty(row)) {
+    clearEditingCell()
+    return
   }
 
-  const exportToExcel = (data, filename = '导出数据.xlsx', sheetName = 'Sheet1') => {
-    try {
-      const wb = XLSX.utils.book_new();
-      
-      if (typeof data === 'object' && data.nodeType === 1) {
-        const worksheet = XLSX.utils.table_to_sheet(data, { raw: true });
-        XLSX.utils.book_append_sheet(wb, worksheet, sheetName);
-      } else if (Array.isArray(data)) {
-        const worksheet = XLSX.utils.json_to_sheet(data);
-        XLSX.utils.book_append_sheet(wb, worksheet, sheetName);
-      } else if (data['!ref']) {
-        XLSX.utils.book_append_sheet(wb, data, sheetName);
+  // 先退出编辑态,避免重复触发
+  clearEditingCell()
+
+  try {
+    if (row.UniqId) {
+      const res = await updateMonthlyGarments({
+        UniqId: row.UniqId,
+        nickName: userStore.userInfo.nickName,
+        客户编号: row['客户编号'],
+        款号: row['款号'],
+        款式: row['款式'],
+        样办种类: row['样办种类'],
+        做办部门: row['做办部门'],
+        数量: row['数量'],
+        下单日期: row['下单日期'],
+        面料进度: row['面料进度'],
+        辅料进度: row['辅料进度'],
+        计划交办日期: row['计划交办日期'],
+        实际交办日期: row['实际交办日期'],
+        备注: row['备注']
+      })
+      if (res.code !== 0) {
+        ElMessage.error('修改失败')
+        return
+      }
+      await refreshData()
+    } else if (row.tempId) {
+      const res = await saveMonthlyGarments({
+        客户编号: row['客户编号'],
+        款号: row['款号'],
+        款式: row['款式'],
+        样办种类: row['样办种类'],
+        做办部门: row['做办部门'],
+        数量: row['数量'],
+        下单日期: row['下单日期'],
+        面料进度: row['面料进度'],
+        辅料进度: row['辅料进度'],
+        计划交办日期: row['计划交办日期'],
+        实际交办日期: row['实际交办日期'],
+        备注: row['备注']
+      })
+      if (res.code === 0) {
+        row.UniqId = parseInt(res.data.id)
+        delete row.tempId
+        await refreshData()
       } else {
-        throw new Error('不支持的导出数据类型');
+        ElMessage.error('保存失败')
+        return
       }
-      
-      XLSX.writeFile(wb, filename);
-      return true;
-    } catch (error) {
-      console.error('导出Excel失败:', error);
-      ElMessage.error('导出失败: ' + error.message);
-      return false;
     }
-  };
-
-  const hzToExcel = () => {
-    if (tableData.value.length === 0) {
-      ElMessage.warning('暂无数据可导出');
-      return;
-    }
-    exportToExcel(tableData.value, '月份样衣生产进度表.xlsx', '月份样衣生产进度表');
+    ElMessage.success('保存成功')
+  } catch (error) {
+    ElMessage.error('保存失败')
+    console.error('保存失败:', error)
   }
+}
 
-  const mxToExcel = () => {
-    if (tableData.value.length === 0) {
-      ElMessage.warning('暂无数据可导出');
-      return;
+const exportToExcel = (data, filename = '导出数据.xlsx', sheetName = 'Sheet1') => {
+  try {
+    const wb = XLSX.utils.book_new()
+    if (typeof data === 'object' && data.nodeType === 1) {
+      const worksheet = XLSX.utils.table_to_sheet(data, { raw: true })
+      XLSX.utils.book_append_sheet(wb, worksheet, sheetName)
+    } else if (Array.isArray(data)) {
+      const worksheet = XLSX.utils.json_to_sheet(data)
+      XLSX.utils.book_append_sheet(wb, worksheet, sheetName)
+    } else if (data['!ref']) {
+      XLSX.utils.book_append_sheet(wb, data, sheetName)
+    } else {
+      throw new Error('不支持的导出数据类型')
     }
-    exportToExcel(tableData.value, '月份样衣生产进度明细表.xlsx', '月份样衣生产进度明细表');
-  }
-  </script>
-  
-  <style scoped>
-  .form-container {
-    display: flex;
-    flex-wrap: wrap;
-  }
-  
-  .form-column {
-    /*flex: 1;*/
-    margin-right: 15px; /* 调整列之间的间距 */
-  }
-  
-  /* 左侧输入框宽度调整 */
-  .form-column .el-form-item .el-input {
-    width: 150px; /* 调整左侧输入框的宽度 */
-  }
-  
-  :deep(.hui-plan-usage-lows  div) {
-    color: #8c939d !important;
-  }
-  :deep(.lan-plan-usage-lows  div) {
-    color: blue !important;
-    font-weight: bold;
-  }
-  
+    XLSX.writeFile(wb, filename)
+    return true
+  } catch (error) {
+    console.error('导出Excel失败:', error)
+    ElMessage.error('导出失败: ' + error.message)
+    return false
+  }
+}
 
-  /* 媒体查询,根据需要调整断点 */
-  @media screen and (max-width: 768px) {
-    .form-column {
-      flex: 1 0 100%; /* 在小屏幕下变成单列布局 */
-      margin-right: 0;
-    }
-  }
-  /*:deep(.el-table td .cell) {*/
-  /*  line-height: 30px !important;*/
-  /*}*/
-  .JKWTree-container {
-    display: flex;
-  }
-  .JKWTree-tree {
-    /*width: 300px;*/
-    background-color: #fff;
-    padding: 10px;
-    margin-right: 20px;
-  }
-  .JKWTree-tree h3 {
-    font-size: 15px;
-    font-weight: 700;
-    margin: 10px 0;
-  }
-  .JKWTree-content {
-    flex: 1;
+const hzToExcel = () => {
+  if (tableData.value.length === 0) {
+    ElMessage.warning('暂无数据可导出')
+    return
   }
+  exportToExcel(tableData.value, '月份样衣生产进度表.xlsx', '月份样衣生产进度表')
+}
 
-  
-  /* 选中某行时的背景色 */
-  :deep(.el-table__body tr.current-row) > td {
-    background: #ff80ff !important;
-  }
-  </style>
-  <style scoped>
-  :deep(.el-table td .cell) {
-    line-height: 20px !important;
-  }
-  :deep(.el-tabs__header){
-    margin-bottom: 0;
-  }
-  .search{
-    margin-left: 0px !important;
-    margin-right: 10px !important;
-  }
-  .bt{
-    margin-left: 2px !important;
-    padding: 3px !important;
-    font-size: 12px;
-  }
-  .el-tabs__header{
-    margin: 0px !important;
-  }
-  .gva-table-box{
-    padding: 0px !important;
-  }
-  .mab{
-    margin-bottom: 5px;
-  }
-  .cell-span {
-    display: inline-block;
-    width: 100%;
-    height: 100%;
-    cursor: pointer;
-    padding: 4px 0;
-  }
-  </style>
-  
+const mxToExcel = () => {
+  if (tableData.value.length === 0) {
+    ElMessage.warning('暂无数据可导出')
+    return
+  }
+  exportToExcel(tableData.value, '月份样衣生产进度明细表.xlsx', '月份样衣生产进度明细表')
+}
+</script>
+
+<style scoped>
+.JKWTree-tree {
+  background-color: #fff;
+  padding: 10px;
+  margin-right: 20px;
+}
+.JKWTree-tree h3 {
+  font-size: 15px;
+  font-weight: 700;
+  margin: 10px 0;
+}
+:deep(.el-table__body tr.current-row) > td {
+  background: #ff80ff !important;
+}
+:deep(.el-table td .cell) {
+  line-height: 20px !important;
+  min-height: 24px;
+}
+.gva-table-box {
+  padding: 0px !important;
+}
+.cell-span {
+  display: block;
+  width: 100%;
+  min-height: 24px;
+  line-height: 24px;
+  cursor: pointer;
+  box-sizing: border-box;
+  padding: 0 4px;
+}
+:deep(.el-table td.el-table__cell) {
+  cursor: pointer;
+}
+</style>

+ 180 - 56
src/view/yunyin/shengchanguanli/gongdanziliao.vue

@@ -604,7 +604,7 @@
 		               <span v-if="gyDetailReadonly">{{ row.金额 }}</span>
 		               <template v-else>
 		                 <div v-if="!isGyDetailCellEditing(row, '金额')" class="gy-detail-cell" @click.stop="startGyDetailCellEdit(row, '金额')">{{ formatGyDetailCellText(row.金额) }}</div>
-		                 <el-input v-else :ref="setGyDetailCellInputRef" v-model="row.金额" size="small" class="gy-detail-cell-input" @blur="finishGyDetailCellEdit(row)" @keyup.enter="finishGyDetailCellEdit(row)" @keydown="(e) => handleGyDetailCellKeydown(e, row, '金额')" />
+		                 <el-input v-else :ref="setGyDetailCellInputRef" :model-value="row.金额" size="small" class="gy-detail-cell-input" @update:model-value="(v) => onGyDetailDecimalInput(row, '金额', v)" @blur="finishGyDetailCellEdit(row)" @keyup.enter="finishGyDetailCellEdit(row)" @keydown="(e) => handleGyDetailCellKeydown(e, row, '金额')" />
 		               </template>
 		             </template>
 		           </vxe-column>
@@ -5152,8 +5152,55 @@ const sortTableData = ref([])
     return s
   }
 
+  /** 四舍五入保留两位;空/非法返回空串 */
+  const roundGyDetail2 = (n) => {
+    if (!Number.isFinite(n)) return ''
+    return (Math.round(n * 100) / 100).toFixed(2)
+  }
+
+  /**
+   * 工分 ↔ 金额 ↔ 秒/分 联动
+   * 秒 = 60/0.25 * 金额;分 = 秒/60
+   * 金额 = 工分 * 0.067;工分 = 金额 / 0.067
+   * (两式互为倒数;若都乘 0.067 无法双向换算)
+   */
+  const applyGyDetailScoreMoneyCalc = (row, sourceField) => {
+    if (!row || (sourceField !== '定额分' && sourceField !== '金额')) return
+    const raw = String(row[sourceField] ?? '').trim()
+    if (raw === '' || raw === '.') {
+      if (sourceField === '定额分') {
+        row.金额 = ''
+        row.秒 = ''
+        row.分 = ''
+      } else {
+        row.定额分 = ''
+        row.秒 = ''
+        row.分 = ''
+      }
+      return
+    }
+    const num = parseFloat(raw)
+    if (!Number.isFinite(num)) return
+
+    if (sourceField === '定额分') {
+      const money = num * 0.067
+      const seconds = (60 / 0.25) * money
+      row.金额 = roundGyDetail2(money)
+      row.秒 = roundGyDetail2(seconds)
+      row.分 = roundGyDetail2(seconds / 60)
+      return
+    }
+
+    const score = num / 0.067
+    const seconds = (60 / 0.25) * num
+    row.定额分 = roundGyDetail2(score)
+    row.秒 = roundGyDetail2(seconds)
+    row.分 = roundGyDetail2(seconds / 60)
+  }
+
   const onGyDetailDecimalInput = (row, field, val) => {
     row[field] = sanitizeGyDetailDecimalInput(val)
+    applyGyDetailScoreMoneyCalc(row, field)
     refreshGyDetailTableFooter()
   }
 
@@ -5281,9 +5328,27 @@ const sortTableData = ref([])
     if (!isFieldChanged && !row._gyDetailNew) {
       return
     }
+
+    // 失焦时再按工分/金额公式落一次(四舍五入两位),并带上联动字段一起保存
+    if (editingField === '定额分' || editingField === '金额') {
+      applyGyDetailScoreMoneyCalc(row, editingField)
+      if (editingField === '定额分' && String(row.定额分 ?? '').trim() !== '') {
+        row.定额分 = roundGyDetail2(parseFloat(row.定额分))
+      }
+      if (editingField === '金额' && String(row.金额 ?? '').trim() !== '') {
+        row.金额 = roundGyDetail2(parseFloat(row.金额))
+      }
+      refreshGyDetailTableFooter()
+    }
     
+    // 新行:未齐三个必填时,工分等字段暂不提交;已齐则继续(可能已新增过,由 handleUpdateProcess 决定增/改)
     if (row._gyDetailNew && editingField !== '工序名称' && editingField !== '部件名称') {
-      return
+      const buJianMingCheng = String(row.部件名称 || '').trim()
+      const gongXuMingCheng = String(row.工序名称 || '').trim()
+      const daGongXu = String(row.大工序 || '').trim()
+      if (!buJianMingCheng || !gongXuMingCheng || !daGongXu) {
+        return
+      }
     }
     
     if (editingField === '部件名称') {
@@ -5354,6 +5419,7 @@ const sortTableData = ref([])
 }
     
     await handleUpdateProcess(row)
+    refreshGyDetailTableFooter()
   }
 
   watch(gyDetailEditingCell, async (cell) => {
@@ -5630,14 +5696,14 @@ const sortTableData = ref([])
   }))
 
   const gyDetailFooterMethod = () => {
-  // 状态为 '0' 表示正常,'1' 表示已删除或其他
-  const normalData = gyDetailDisplayProcessList.value.filter(
-    row => row.状态 === '0'
-  );
+  // 与拆分判断一致:状态 1 为被拆分,其余(含数字 0 / 字符串 '0')都计入合计
+  const normalData = (gyDetailDisplayProcessList.value || []).filter(
+    (row) => !isGyDetailRowSplit(row)
+  )
   
   // 如果没有正常数据,合计全部为0
   if (normalData.length === 0) {
-    return [['合计', '', '', '', '', '', '0.00', '0.00', '0.00', '0.00', '', '', '', '']];
+    return [['合计', '', '', '', '', '', '0.00', '0.00', '0.00', '0.00', '', '', '', '']]
   }
   
   // 计算各项合计
@@ -5646,7 +5712,7 @@ const sortTableData = ref([])
     分: normalData.reduce((sum, row) => sum + (Number(row.分) || 0), 0),
     定额分: normalData.reduce((sum, row) => sum + (Number(row.定额分) || 0), 0),
     金额: normalData.reduce((sum, row) => sum + (Number(row.金额) || 0), 0),
-  };
+  }
   
   return [[
     '合计',
@@ -5663,8 +5729,8 @@ const sortTableData = ref([])
     '',
     '',
     '',
-  ]];
-};
+  ]]
+}
 
   const refreshGyDetailTableFooter = () => {
     nextTick(() => {
@@ -5775,6 +5841,7 @@ const sortTableData = ref([])
       return
     }
     const batchCount = 10
+    const startIndex = gyDetailFormData.工艺列表.length
     for (let i = 0; i < batchCount; i++) {
       const nextCode = getNextGyDetailProcessCode()
       const newRow = {
@@ -5797,10 +5864,11 @@ const sortTableData = ref([])
       gyDetailFormData.工艺列表.push(newRow)
       newRow._gyDetailOrigin = snapshotGyDetailRow(newRow)
     }
-    const lastNewRow = gyDetailFormData.工艺列表[gyDetailFormData.工艺列表.length - 1]
-    await scrollGyDetailTableToBottom(lastNewRow)
-    if (!gyDetailReadonly.value) {
-      startGyDetailCellEdit(lastNewRow, '工序名称')
+    // 聚焦本次新增的第一条空白行(不是最后一条)
+    const firstNewRow = gyDetailFormData.工艺列表[startIndex]
+    await scrollGyDetailTableToBottom(firstNewRow)
+    if (!gyDetailReadonly.value && firstNewRow) {
+      startGyDetailCellEdit(firstNewRow, '工序名称')
     }
   }
 
@@ -6406,6 +6474,38 @@ const sortTableData = ref([])
   }
   
   // 修改工艺(未改动不请求接口)
+  // 新行填齐必填项后会先新增;再改工分等字段应走修改。若新增成功但未带回 id,或再次新增报工序编号已存在,则绑定 id 后改为 UpdateProcess。
+  const resolveGyDetailProcessId = (payload) => {
+    if (payload == null || payload === '') return ''
+    if (typeof payload === 'number' || typeof payload === 'string') return payload
+    return payload.id ?? payload.ID ?? payload.Id ?? ''
+  }
+
+  const isGyProcessCodeExistsError = (res) => {
+    const msg = String(res?.msg || res?.message || '')
+    return /工序编号/.test(msg) && /(已存在|已经有|重复)/.test(msg)
+  }
+
+  const bindGyDetailRowIdByProcessCode = async (row) => {
+    const orderNo = String(gyDetailFormData.订单编号 || '').trim()
+    const code = String(row?.工序编号 ?? '').trim()
+    if (!orderNo || !code) return false
+    try {
+      const processRes = await getWorkOrderProcess({ workorder: orderNo })
+      if (processRes?.code !== 0 || !Array.isArray(processRes.data)) return false
+      const found = processRes.data.find(
+        (item) => String(item.工序编号 ?? item.process_code ?? '').trim() === code
+      )
+      const id = resolveGyDetailProcessId(found)
+      if (!id) return false
+      row.id = id
+      row._gyDetailNew = false
+      return true
+    } catch {
+      return false
+    }
+  }
+
   const handleUpdateProcess = async (row) => {
     const orderNo = String(gyDetailFormData.订单编号 || '').trim()
     if (!orderNo) return
@@ -6415,48 +6515,12 @@ const sortTableData = ref([])
       return
     }
 
-    try {
-      if (row._gyDetailNew || !row.id) {
-        // 只有在编辑工序名称时,才检查三个字段是否都填写
-        if (gyDetailLastEditingField.value === '工序名称') {
-          const buJianMingCheng = String(row.部件名称 || '').trim()
-          const gongXuMingCheng = String(row.工序名称 || '').trim()
-          const daGongXu = String(row.大工序 || '').trim()
-          
-          if (!buJianMingCheng || !gongXuMingCheng || !daGongXu) {
-            ElMessage.warning('部件名称、工序名称、生产工序三个字段都必须填写')
-            return
-          }
-        }
-        
-        row._gyDetailSaving = true
-        
-        const addRes = await AddProcess({
-          workorder: orderNo,
-          part_code: row.部件编号 || '',
-		  part_name: row.部件名称 || '',
-          process_name: row.工序名称 || '',
-          process_code: String(row.工序编号 ?? ''),
-          big_process: row.大工序 || '',
-          standard_hour: row.秒 || '',
-          standard_score: row.定额分 || '',
-          coefficient: row.难度系数 || '',
-          remark: row.备注 || '',
-          sys_id: userStore.userInfo?.nickName || '',
-        })
-        if (addRes?.code === 0) {
-          row._gyDetailNew = false
-          if (addRes.data) {
-            row.id = addRes.data.id || addRes.data
-          }
-          row._gyDetailOrigin = snapshotGyDetailRow(row)
-          ElMessage.success('保存成功')
-        }
-        return
-      }
-      
-      row._gyDetailSaving = true
-      
+    const buJianMingCheng = String(row.部件名称 || '').trim()
+    const gongXuMingCheng = String(row.工序名称 || '').trim()
+    const daGongXu = String(row.大工序 || '').trim()
+    const requiredReady = !!(buJianMingCheng && gongXuMingCheng && daGongXu)
+
+    const runUpdate = async () => {
       const result = await UpdateProcess({
         id: row.id,
         process_name: row.工序名称 || '',
@@ -6473,9 +6537,69 @@ const sortTableData = ref([])
         process_code: row.工序编号 || '',
       })
       if (result.code === 0) {
+        row._gyDetailNew = false
         row._gyDetailOrigin = snapshotGyDetailRow(row)
         ElMessage.success('修改成功')
+        return true
+      }
+      ElMessage.error(result.msg || '修改失败')
+      return false
+    }
+
+    try {
+      // 已有 id:一律修改
+      if (row.id) {
+        row._gyDetailSaving = true
+        row._gyDetailNew = false
+        await runUpdate()
+        return
+      }
+
+      // 无 id:需三个必填齐全才能新增
+      if (!requiredReady) {
+        if (gyDetailLastEditingField.value === '工序名称' || gyDetailLastEditingField.value === '部件名称') {
+          ElMessage.warning('部件名称、工序名称、生产工序三个字段都必须填写')
+        }
+        return
+      }
+
+      row._gyDetailSaving = true
+
+      const addRes = await AddProcess({
+        workorder: orderNo,
+        part_code: row.部件编号 || '',
+        part_name: row.部件名称 || '',
+        process_name: row.工序名称 || '',
+        process_code: String(row.工序编号 ?? ''),
+        big_process: row.大工序 || '',
+        standard_hour: row.秒 || '',
+        standard_score: row.定额分 || '',
+        coefficient: row.难度系数 || '',
+        remark: row.备注 || '',
+        sys_id: userStore.userInfo?.nickName || '',
+      })
+
+      if (addRes?.code === 0) {
+        row._gyDetailNew = false
+        row.id = resolveGyDetailProcessId(addRes.data)
+        if (!row.id) {
+          await bindGyDetailRowIdByProcessCode(row)
+        }
+        row._gyDetailOrigin = snapshotGyDetailRow(row)
+        ElMessage.success('保存成功')
+        return
       }
+
+      // 工序已在库中(例如必填项保存时已新增):改为修改
+      if (isGyProcessCodeExistsError(addRes)) {
+        const bound = await bindGyDetailRowIdByProcessCode(row)
+        if (bound && row.id) {
+          await runUpdate()
+          return
+        }
+      }
+
+      ElMessage.error(addRes?.msg || '保存失败')
     } catch {
       /* 忽略 */
     } finally {