zck 1 deň pred
rodič
commit
d4a9fb932a

+ 3 - 2
src/view/SalaryManage/ProcessProduction.vue

@@ -300,10 +300,11 @@ const EMBEDDED_TAB_TABLE_HEIGHT = '52vh'
 
 /** 弹窗按行数自适应高度,避免数据少时底部大块空白;主页面固定高度 */
 const tableHeight = computed(() => {
-  if (props.embeddedInTab) return EMBEDDED_TAB_TABLE_HEIGHT
+  if (props.embeddedInTab && !props.inDialog) return EMBEDDED_TAB_TABLE_HEIGHT
   if (!props.inDialog) return MAIN_TABLE_HEIGHT
   const rows = displayTableData.value.length
-  const maxH = Math.max(200, window.innerHeight - DIALOG_TABLE_OFFSET)
+  const headerOffset = props.embeddedInTab ? 90 : DIALOG_TABLE_OFFSET
+  const maxH = Math.max(200, window.innerHeight - headerOffset)
   if (!rows) return Math.min(DIALOG_TABLE_EMPTY_HEIGHT, maxH)
   const needH = rows * 32 + 36 + 12 + TABLE_SUMMARY_ROW_HEIGHT
   return Math.min(Math.max(needH, 200), maxH)

+ 426 - 62
src/view/SalaryManage/SalaryList.vue

@@ -5,17 +5,56 @@
         <div>
           <el-form
             class="demo-form-inline"
-            @keyup.enter="applyStaffSearch"
+            @submit.prevent="handleStaffQueryClick"
           >
             <el-form-item>
-              <el-input
-                v-model="staffSearchInput"
-                placeholder="查询订单编号、员工编号或员工姓名"
-                clearable
-                class="search"
-                style="width: 260px"
-              />
-              <el-button type="primary" icon="search" class="bt" @click="applyStaffSearch">查询</el-button>
+              <div ref="searchWrapRef" class="workorder-search-wrap search">
+                <el-input
+                  v-model="staffSearchInput"
+                  placeholder="查询订单编号、员工编号或员工姓名"
+                  clearable
+                  style="width: 260px"
+                  @keyup.enter="handleStaffQueryClick"
+                  @input="onSearchKeywordInput"
+                  @clear="closeWorkOrderDropdown"
+                />
+              </div>
+              <!-- 挂到 body,避免被 layout overflow 裁切;内容与工序产量核查一致 -->
+              <Teleport to="body">
+                <div
+                  v-show="workOrderDropdownVisible && workOrderOptions.length"
+                  ref="workOrderDropdownRef"
+                  class="salary-workorder-dropdown-panel"
+                  :style="workOrderDropdownStyle"
+                >
+                  <div class="workorder-suggest-header">
+                    <span>订单编号</span>
+                    <span>生产款号</span>
+                    <span>款式</span>
+                  </div>
+                  <div class="workorder-dropdown-list">
+                    <div
+                      v-for="item in workOrderOptions"
+                      :key="item.订单编号"
+                      class="workorder-suggest-row"
+                      @click="onPickWorkOrder(item)"
+                    >
+                      <span class="workorder-suggest-no" :title="item.订单编号">{{ item.订单编号 }}</span>
+                      <span class="workorder-suggest-style" :title="item.生产款号">{{ item.生产款号 }}</span>
+                      <span class="workorder-suggest-name" :title="item.款式">{{ item.款式 }}</span>
+                    </div>
+                  </div>
+                </div>
+              </Teleport>
+              <el-button
+                type="primary"
+                icon="search"
+                class="bt"
+                :loading="workOrderSuggestLoading"
+                @click="handleStaffQueryClick"
+              >
+                查询
+              </el-button>
               <el-button type="primary" icon="search" class="bt" @click="openProcessProductionDialog">工序产量核查</el-button>
             </el-form-item>
           </el-form>
@@ -106,11 +145,41 @@
                 <el-table-column
                   label="员工姓名"
                   prop="员工姓名"
-                  min-width="100"
+                  min-width="120"
                   align="center"
                   fixed="left"
                   show-overflow-tooltip
-                />
+                >
+                  <template #header>
+                    <TableColumnFilterHeader
+                      label="员工姓名"
+                      v-model="pivotStaffNameFilter"
+                      :options="pivotStaffNameFilterOptions"
+                      :active="isPivotStaffNameFilterActive"
+                      @select-all="selectAllPivotStaffNameFilter"
+                      @clear="clearPivotStaffNameFilter"
+                    />
+                  </template>
+                </el-table-column>
+                <el-table-column
+                  label="月份"
+                  prop="月份"
+                  width="80"
+                  align="center"
+                  fixed="left"
+                  show-overflow-tooltip
+                >
+                  <template #header>
+                    <TableColumnFilterHeader
+                      label="月份"
+                      v-model="pivotMonthFilter"
+                      :options="pivotMonthFilterOptions"
+                      :active="isPivotMonthFilterActive"
+                      @select-all="selectAllPivotMonthFilter"
+                      @clear="clearPivotMonthFilter"
+                    />
+                  </template>
+                </el-table-column>
                 <el-table-column
                   v-for="d in DAY_INDEXES"
                   :key="'d' + d"
@@ -124,10 +193,10 @@
                     <span
                       class="salary-day-cell"
                       :class="{
-                        'is-outside': d > daysInSelectedMonth,
+                        'is-outside': d > (row.daysInMonth || daysInSelectedMonth),
                       }"
                       :title="
-                        d <= daysInSelectedMonth && row['_has' + d]
+                        d <= (row.daysInMonth || daysInSelectedMonth) && row['_has' + d]
                           ? '双击可查看该日工资明细'
                           : undefined
                       "
@@ -422,7 +491,7 @@
 
 <script setup>
 import { Layout, LayoutHeader, LayoutSider, LayoutContent } from '@arco-design/web-vue'
-import { ref, computed, onMounted, nextTick, watch } from 'vue'
+import { ref, computed, onMounted, onUnmounted, nextTick, watch } from 'vue'
 import { ElMessage } from 'element-plus'
 import {
   GetReportingWorkMonth,
@@ -430,6 +499,7 @@ import {
   GetStaffSalaryDetail,
   UpdateReportingWork,
   DeleteReportingWork,
+  WorkOrderList,
 } from '@/api/mes/job'
 import ProcessProduction from '@/view/SalaryManage/ProcessProduction.vue'
 import TableColumnFilterHeader from '@/view/SalaryManage/TableColumnFilterHeader.vue'
@@ -472,6 +542,118 @@ const staffSearchInput = ref('')
 /** 点击「查询」后传给接口的 search */
 const searchKeywordApplied = ref('')
 
+/** 订单模糊下拉(与 ProcessProduction 一致:订单编号/生产款号/款式) */
+const searchWrapRef = ref(null)
+const workOrderDropdownRef = ref(null)
+const workOrderDropdownVisible = ref(false)
+const workOrderOptions = ref([])
+const workOrderSuggestLoading = ref(false)
+const workOrderDropdownStyle = ref({
+  top: '0px',
+  left: '0px',
+  width: '680px',
+})
+let workOrderSuggestSeq = 0
+let searchInputTimer = null
+
+const updateWorkOrderDropdownPosition = () => {
+  const el = searchWrapRef.value
+  if (!el) return
+  const rect = el.getBoundingClientRect()
+  workOrderDropdownStyle.value = {
+    top: `${Math.round(rect.bottom + 4)}px`,
+    left: `${Math.round(rect.left)}px`,
+    width: `${Math.min(680, Math.max(360, window.innerWidth - rect.left - 16))}px`,
+  }
+}
+
+const openWorkOrderDropdown = () => {
+  if (!workOrderOptions.value.length) {
+    workOrderDropdownVisible.value = false
+    return
+  }
+  updateWorkOrderDropdownPosition()
+  workOrderDropdownVisible.value = true
+}
+
+const closeWorkOrderDropdown = () => {
+  workOrderDropdownVisible.value = false
+  workOrderOptions.value = []
+}
+
+const onSearchKeywordInput = () => {
+  clearTimeout(searchInputTimer)
+  searchInputTimer = setTimeout(async () => {
+    const q = String(staffSearchInput.value || '').trim()
+    if (!q) {
+      closeWorkOrderDropdown()
+      return
+    }
+    await remoteSearchWorkOrder(q)
+    openWorkOrderDropdown()
+  }, 300)
+}
+
+const onDocClick = (event) => {
+  const t = event.target
+  if (searchWrapRef.value?.contains(t) || workOrderDropdownRef.value?.contains(t)) return
+  workOrderDropdownVisible.value = false
+}
+
+const remoteSearchWorkOrder = async (query) => {
+  const q = String(query || '').trim()
+  if (!q) {
+    workOrderOptions.value = []
+    return
+  }
+  const seq = ++workOrderSuggestSeq
+  workOrderSuggestLoading.value = true
+  try {
+    const res = await WorkOrderList({ search: q, page: 1, limit: 20 })
+    if (seq !== workOrderSuggestSeq) return
+    workOrderOptions.value = res?.code === 0 && Array.isArray(res.data?.data) ? res.data.data : []
+  } catch (error) {
+    console.error(error)
+    workOrderOptions.value = []
+  } finally {
+    if (seq === workOrderSuggestSeq) {
+      workOrderSuggestLoading.value = false
+    }
+  }
+}
+
+/** 选中工单后按订单编号查工资列表 */
+const onPickWorkOrder = (item) => {
+  const no = String(item?.订单编号 || '').trim()
+  if (!no) return
+  staffSearchInput.value = no
+  workOrderDropdownVisible.value = false
+  runStaffSearch(no)
+}
+
+/**
+ * 查询 / 回车:优先走工单模糊匹配;
+ * 仅 1 条直接查,多条展开下拉;无工单匹配则按原逻辑用关键词查员工编号/姓名
+ */
+const handleStaffQueryClick = async () => {
+  const q = String(staffSearchInput.value || '').trim()
+  if (!q) {
+    ElMessage.warning('请输入订单编号/员工编号/姓名进行查询')
+    return
+  }
+  await remoteSearchWorkOrder(q)
+  if (workOrderOptions.value.length === 1) {
+    onPickWorkOrder(workOrderOptions.value[0])
+    return
+  }
+  if (workOrderOptions.value.length > 1) {
+    openWorkOrderDropdown()
+    return
+  }
+  workOrderDropdownVisible.value = false
+  runStaffSearch(q)
+}
+
 /** 左侧树有日期,或顶部已点过查询(有 search)均可展示列表 */
 const hasQueryCondition = computed(
   () =>
@@ -614,7 +796,16 @@ const pickPivotGroup = (row) => {
   return g && g !== '—' ? g : ''
 }
 
+const pickPivotStaffName = (row) => {
+  const n = String(row?.员工姓名 ?? '').trim()
+  return n && n !== '—' ? n : ''
+}
+
+const pickPivotMonth = (row) => String(row?.月份 ?? '').trim()
+
 const pivotGroupFilterCtrl = createListColumnFilter(pivotRowsAll, pickPivotGroup)
+const pivotStaffNameFilterCtrl = createListColumnFilter(pivotRowsAll, pickPivotStaffName)
+const pivotMonthFilterCtrl = createListColumnFilter(pivotRowsAll, pickPivotMonth, { sortDesc: true })
 
 const detailOrderNoFilterCtrl = createListColumnFilter(detailRowsAll, (row) => String(row.订单编号 || '').trim())
 const detailStyleNoFilterCtrl = createListColumnFilter(detailRowsAll, (row) => String(row.生产款号 || '').trim())
@@ -637,6 +828,30 @@ const {
   apply: applyPivotGroupFilter,
 } = pivotGroupFilterCtrl
 
+const {
+  selected: pivotStaffNameFilter,
+  options: pivotStaffNameFilterOptions,
+  isActive: isPivotStaffNameFilterActive,
+  selectAll: selectAllPivotStaffNameFilter,
+  clear: clearPivotStaffNameFilter,
+  apply: applyPivotStaffNameFilter,
+} = pivotStaffNameFilterCtrl
+
+const {
+  selected: pivotMonthFilter,
+  options: pivotMonthFilterOptions,
+  isActive: isPivotMonthFilterActive,
+  selectAll: selectAllPivotMonthFilter,
+  clear: clearPivotMonthFilter,
+  apply: applyPivotMonthFilter,
+} = pivotMonthFilterCtrl
+
+function selectAllPivotFilters() {
+  selectAllPivotGroupFilter()
+  selectAllPivotStaffNameFilter()
+  selectAllPivotMonthFilter()
+}
+
 const {
   selected: detailOrderNoFilter,
   options: detailOrderNoFilterOptions,
@@ -721,7 +936,13 @@ watch(detailRowsAll, () => {
   syncAllDetailFilters()
 })
 
-const pivotRowsFiltered = computed(() => applyPivotGroupFilter(pivotRowsAll.value))
+const pivotRowsFiltered = computed(() => {
+  let rows = pivotRowsAll.value
+  rows = applyPivotGroupFilter(rows)
+  rows = applyPivotStaffNameFilter(rows)
+  rows = applyPivotMonthFilter(rows)
+  return rows
+})
 
 const pagedPivotRows = computed(() => {
   const list = pivotRowsFiltered.value
@@ -736,10 +957,10 @@ watch([pivotRowsFiltered, pivotPageSize], ([arr, ps]) => {
 })
 
 watch(pivotRowsAll, () => {
-  selectAllPivotGroupFilter()
+  selectAllPivotFilters()
 })
 
-watch(pivotGroupFilter, () => {
+watch([pivotGroupFilter, pivotStaffNameFilter, pivotMonthFilter], () => {
   pivotPage.value = 1
 })
 
@@ -764,6 +985,14 @@ function parseStaffKey(staffStr) {
   return { staff_no: '', staff_name: s || '—' }
 }
 
+/** 从日期串解析 YYYY-MM */
+function parseYearMonthFromDate(dateStr) {
+  const ds = String(dateStr ?? '').trim()
+  const m = ds.match(/^(\d{4})[-/.年](\d{1,2})/)
+  if (!m) return ''
+  return `${m[1]}-${String(Number(m[2])).padStart(2, '0')}`
+}
+
 /** 从日期串解析为「日」,且必须属于 monthYm(YYYY-MM) */
 function parseDayInMonth(dateStr, monthYm) {
   if (!dateStr || !monthYm) return -1
@@ -779,8 +1008,15 @@ function parseDayInMonth(dateStr, monthYm) {
   return -1
 }
 
-/** 从列表项解析小组名(兼容多种后端字段) */
-function pickGroupFromItem(item, fallbackGroup = '') {
+function daysCountInYm(monthYm) {
+  if (!monthYm || !/^\d{4}-\d{2}$/.test(monthYm)) return 31
+  const [y, m] = monthYm.split('-').map(Number)
+  if (!m || m < 1 || m > 12) return 31
+  return new Date(y, m, 0).getDate()
+}
+
+/** 优先取指定月份 children 上的小组 */
+function pickGroupFromItem(item, fallbackGroup = '', monthYm = '') {
   const direct =
     item?.['小组'] ??
     item?.group ??
@@ -789,65 +1025,110 @@ function pickGroupFromItem(item, fallbackGroup = '') {
     item?.team_name
   if (direct != null && String(direct).trim()) return String(direct).trim()
   for (const c of item?.children || []) {
+    if (monthYm && parseYearMonthFromDate(c['日期']) !== monthYm) continue
     const g = c?.['小组'] ?? c?.group ?? c?.['设备编组'] ?? c?.['设备编号']
     if (g != null && String(g).trim()) return String(g).trim()
   }
+  if (monthYm) {
+    for (const c of item?.children || []) {
+      const g = c?.['小组'] ?? c?.group ?? c?.['设备编组'] ?? c?.['设备编号']
+      if (g != null && String(g).trim()) return String(g).trim()
+    }
+  }
   return String(fallbackGroup || '').trim()
 }
 
 /**
- * 将 GetStaffSalaryList 转为一行一人、day1~day31 为当日工资
- * 同月同日多条 children 时金额相加
+ * 将 GetStaffSalaryList 转为透视行:同一员工若跨月则拆成多行
+ * 同月同日多条 children 时金额相加;月计为该月合计
  */
-function buildPivotRows(list, monthYm, fallbackGroup = '') {
-  const dim = (() => {
-    if (!monthYm || !/^\d{4}-\d{2}$/.test(monthYm)) return 31
-    const [y, m] = monthYm.split('-').map(Number)
-    return new Date(y, m, 0).getDate()
-  })()
+function buildPivotRows(list, preferredMonthYm, fallbackGroup = '') {
   const out = []
   let i = 0
   for (const item of list || []) {
     const p = parseStaffKey(item.staff)
     const baseNo = String(item['员工编号'] ?? p.staff_no ?? '').trim()
     const baseName = String(item['员工姓名'] ?? p.staff_name ?? '').trim()
-    const groupName = pickGroupFromItem(item, fallbackGroup)
-    const sumByDay = {}
+
+    /** @type {Map<string, { sumByDay: Record<number, number>, monthTotal: number }>} */
+    const byMonth = new Map()
     for (const c of item.children || []) {
-      const day = parseDayInMonth(c['日期'], monthYm)
-      if (day < 1 || day > dim) continue
+      const ym = parseYearMonthFromDate(c['日期'])
+      if (!ym) continue
+      let bucket = byMonth.get(ym)
+      if (!bucket) {
+        bucket = { sumByDay: {}, monthTotal: 0 }
+        byMonth.set(ym, bucket)
+      }
+      const day = parseDayInMonth(c['日期'], ym)
       const w = c['工资']
       if (w === null || w === undefined || w === '') continue
       const n = Number(w)
       if (Number.isNaN(n)) continue
-      sumByDay[day] = (sumByDay[day] || 0) + n
+      if (day >= 1 && day <= 31) {
+        bucket.sumByDay[day] = (bucket.sumByDay[day] || 0) + n
+      }
+      bucket.monthTotal += n
     }
-    const row = {
-      rowKey: `pv-${i++}-${baseNo}-${groupName || i}`,
-      小组: groupName || '—',
-      员工编号: baseNo || '—',
-      员工姓名: baseName || '—',
-      // 添加月计列,使用接口返回的total_salary数据
-      月计: item.total_salary ? Number(item.total_salary).toFixed(2) : '0.00'
+
+    // 月份大的在前(如 9 在 8 前)
+    let monthKeys = [...byMonth.keys()].sort((a, b) => b.localeCompare(a))
+    if (!monthKeys.length) {
+      monthKeys =
+        preferredMonthYm && /^\d{4}-\d{2}$/.test(preferredMonthYm)
+          ? [preferredMonthYm]
+          : ['']
     }
-    for (let d = 1; d <= 31; d++) {
-      const keyH = '_has' + d
-      if (d > dim) {
-        row['day' + d] = ''
-        row[keyH] = false
-        continue
+
+    for (const ym of monthKeys) {
+      const dim = daysCountInYm(ym)
+      const monthNum = ym ? String(Number(ym.split('-')[1])) : ''
+      const bucket = byMonth.get(ym) || { sumByDay: {}, monthTotal: 0 }
+      const groupName = pickGroupFromItem(item, fallbackGroup, ym)
+      const row = {
+        rowKey: `pv-${i++}-${baseNo}-${ym || 'na'}-${groupName || i}`,
+        小组: groupName || '—',
+        员工编号: baseNo || '—',
+        员工姓名: baseName || '—',
+        月份: monthNum,
+        monthYm: ym,
+        daysInMonth: dim,
+        月计:
+          byMonth.size > 0
+            ? Number(bucket.monthTotal).toFixed(2)
+            : item.total_salary
+              ? Number(item.total_salary).toFixed(2)
+              : '0.00',
       }
-      const v = sumByDay[d]
-      if (v != null && !Number.isNaN(Number(v))) {
-        row['day' + d] = Number(v).toFixed(2)
-        row[keyH] = true
-      } else {
-        row['day' + d] = ''
-        row[keyH] = false
+      for (let d = 1; d <= 31; d++) {
+        const keyH = '_has' + d
+        if (!ym || d > dim) {
+          row['day' + d] = ''
+          row[keyH] = false
+          continue
+        }
+        const v = bucket.sumByDay[d]
+        if (v != null && !Number.isNaN(Number(v))) {
+          row['day' + d] = Number(v).toFixed(2)
+          row[keyH] = true
+        } else {
+          row['day' + d] = ''
+          row[keyH] = false
+        }
       }
+      out.push(row)
     }
-    out.push(row)
   }
+  // 全局按月份倒序:如 9 月全部在前,8 月全部在后
+  out.sort((a, b) => {
+    const ymA = String(a.monthYm || '')
+    const ymB = String(b.monthYm || '')
+    if (ymA !== ymB) return ymB.localeCompare(ymA)
+    const noA = String(a.员工编号 || '')
+    const noB = String(b.员工编号 || '')
+    if (noA !== noB) return noA.localeCompare(noB)
+    return String(a.小组 || '').localeCompare(String(b.小组 || ''), 'zh-CN')
+  })
   return out
 }
 
@@ -1191,9 +1472,9 @@ async function loadStaffList() {
   }
 }
 
-function applyStaffSearch() {
-  const search = (staffSearchInput.value || '').trim()
-  if (!search) {
+function runStaffSearch(search) {
+  const q = String(search || '').trim()
+  if (!q) {
     ElMessage.warning('请输入订单编号/员工编号/姓名进行查询')
     return
   }
@@ -1201,24 +1482,29 @@ function applyStaffSearch() {
   listQuery.value = { sys_rq: '', big_process: '', group: '' }
   currentMenuKey.value = ''
   restoreMenuCurrentKey()
-  searchKeywordApplied.value = search
+  searchKeywordApplied.value = q
   pivotPage.value = 1
   loadStaffList()
 }
 
+function applyStaffSearch() {
+  handleStaffQueryClick()
+}
+
 /** 与 renyuanjibenziliao 表头一致:紧凑;保留字号便于密列阅读 */
 function pivotHeaderCellStyle() {
   return { padding: '0px', fontSize: '12px', textAlign: 'center' }
 }
 
-function pivotCellStyle({ column }) {
+function pivotCellStyle({ row, column }) {
   const base = { padding: '0px', fontSize: '12px', textAlign: 'center' }
   const prop = column?.property
   if (!prop || !String(prop).startsWith('day')) return base
   const m = /^day(\d+)$/.exec(prop)
   if (!m) return base
   const d = Number(m[1])
-  if (d > daysInSelectedMonth.value) {
+  const dim = Number(row?.daysInMonth) || daysInSelectedMonth.value
+  if (d > dim) {
     return {
       ...base,
       background: 'var(--el-fill-color-lighter)',
@@ -1259,8 +1545,10 @@ function openSalaryDetailForRowDay(row, dayNum) {
     ElMessage.warning('缺少员工编号')
     return
   }
-  const monthYm = String(selectedMonthYm.value || '').trim()
+  const monthYm = String(row.monthYm || selectedMonthYm.value || '').trim()
   if (!/^\d{4}-\d{2}$/.test(monthYm)) return
+  const dim = Number(row.daysInMonth) || daysCountInYm(monthYm)
+  if (dayNum > dim) return
   const dateStr = `${monthYm}-${String(dayNum).padStart(2, '0')}`
   detailContext.value = {
     staff_no: no,
@@ -1275,7 +1563,8 @@ function openSalaryDetailForRowDay(row, dayNum) {
 /** 有金额时日格上双击,直接带日 d 打开,避免 el-table 未带上 column */
 function onDayCellDblClick(row, d) {
   if (!row) return
-  if (d > daysInSelectedMonth.value) return
+  const dim = Number(row.daysInMonth) || daysInSelectedMonth.value
+  if (d > dim) return
   if (row['_has' + d]) {
     openSalaryDetailForRowDay(row, d)
   }
@@ -1288,7 +1577,7 @@ function onDayCellDblClick(row, d) {
  */
 function onPivotDblOpenDetail(row, column) {
   if (!row) return
-  const dim = daysInSelectedMonth.value
+  const dim = Number(row.daysInMonth) || daysInSelectedMonth.value
   const dCol = getDayIndexFromTableColumn(column)
   if (dCol >= 1) {
     if (dCol > dim) return
@@ -1542,6 +1831,16 @@ const getDetailSummaries = (param) => {
 
 onMounted(() => {
   loadLeftMenu()
+  document.addEventListener('click', onDocClick)
+  window.addEventListener('resize', updateWorkOrderDropdownPosition)
+  window.addEventListener('scroll', updateWorkOrderDropdownPosition, true)
+})
+
+onUnmounted(() => {
+  document.removeEventListener('click', onDocClick)
+  window.removeEventListener('resize', updateWorkOrderDropdownPosition)
+  window.removeEventListener('scroll', updateWorkOrderDropdownPosition, true)
+  clearTimeout(searchInputTimer)
 })
 </script>
 
@@ -1553,6 +1852,11 @@ onMounted(() => {
   margin-left: 0 !important;
   margin-right: 10px !important;
 }
+.workorder-search-wrap {
+  position: relative;
+  display: inline-block;
+  vertical-align: middle;
+}
 .bt {
   margin-left: 2px !important;
   padding: 3px !important;
@@ -1746,6 +2050,66 @@ onMounted(() => {
 </style>
 
 <style>
+/* 订单模糊下拉:Teleport 到 body,避免被页面 overflow 裁切 */
+.salary-workorder-dropdown-panel {
+  position: fixed;
+  z-index: 4000;
+  background: #fff;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
+  box-sizing: border-box;
+  overflow: hidden;
+}
+.salary-workorder-dropdown-panel .workorder-suggest-header {
+  padding: 8px 12px 6px;
+  border-bottom: 1px solid #ebeef5;
+  background: #fafafa;
+  font-weight: 600;
+  color: #909399;
+  font-size: 12px;
+}
+.salary-workorder-dropdown-panel .workorder-dropdown-list {
+  max-height: 280px;
+  overflow-y: auto;
+}
+.salary-workorder-dropdown-panel .workorder-suggest-header,
+.salary-workorder-dropdown-panel .workorder-suggest-row {
+  display: grid;
+  grid-template-columns: 118px minmax(220px, 1fr) minmax(140px, 180px);
+  gap: 10px;
+  align-items: center;
+  line-height: 1.4;
+  font-size: 13px;
+  box-sizing: border-box;
+}
+.salary-workorder-dropdown-panel .workorder-suggest-row {
+  padding: 8px 12px;
+  cursor: pointer;
+}
+.salary-workorder-dropdown-panel .workorder-suggest-row:hover {
+  background: #f5f7fa;
+}
+.salary-workorder-dropdown-panel .workorder-suggest-no {
+  color: #303133;
+  font-weight: 500;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.salary-workorder-dropdown-panel .workorder-suggest-style {
+  color: #606266;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.salary-workorder-dropdown-panel .workorder-suggest-name {
+  color: #303133;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
 /* 工资明细弹窗:加宽、加高;append-to-body 单独块便于命中 .el-dialog */
 .salary-detail-dialog.el-dialog {
   width: min(1680px, 94vw) !important;

+ 309 - 5
src/view/performance/WorkScoreReporting/gongfenbaogong.vue

@@ -105,6 +105,27 @@
                         </el-table>
                       </el-tab-pane>
 
+                    <el-tab-pane label="员工当天报工记录" name="employeeDaily" lazy>
+                      <div class="employee-daily-wrap">
+                        <div class="employee-daily-tip">双击员工卡片查看该员工当日所有报工记录</div>
+                        <div v-if="employees.length > 0" class="employee-grid employee-daily-grid">
+                          <div
+                            v-for="employee in employees"
+                            :key="employee['员工编号']"
+                            class="employee-card employee-daily-card"
+                            @dblclick="openEmployeeDailyRecords(employee)"
+                          >
+                            <div class="employee-avatar" :style="{ backgroundColor: getAvatarColor(employee['员工编号']) }">
+                              {{ employee['员工姓名'].charAt(0) }}
+                            </div>
+                            <div class="employee-name">{{ employee['员工姓名'] }}</div>
+                            <div class="employee-id">{{ employee['员工编号'] }}</div>
+                          </div>
+                        </div>
+                        <el-empty v-else description="暂无员工数据" :image-size="80" />
+                      </div>
+                    </el-tab-pane>
+
                     <el-tab-pane label="工序产量核查" name="processProduction" lazy>
                       <ProcessProduction
                         ref="processProductionRef"
@@ -117,6 +138,39 @@
                       />
                     </el-tab-pane>
 
+                    <el-tab-pane label="近期报工订单" name="recentOrders">
+                      <el-table
+                        class="gongfen-history-table recent-report-orders-table"
+                        style="height: 52vh"
+                        tooltip-effect="dark"
+                        :row-style="{ height: '38px' }"
+                        :header-cell-style="gongfenHistoryHeaderCellStyle"
+                        :cell-style="gongfenHistoryCellStyle"
+                        :header-row-style="{ height: '34px' }"
+                        :data="recentReportOrders"
+                        border
+                        size="small"
+                        scrollbar-always-on
+                        highlight-current-row
+                        show-overflow-tooltip
+                      >
+                        <el-table-column align="center" label="订单编号" prop="订单编号" :width="130" />
+                        <el-table-column align="center" label="生产款号" prop="生产款号" :width="330"  />
+                        <el-table-column align="center" label="款式" prop="款式" :width="160"  />
+                        <el-table-column align="center" label="订单数量" prop="订单数量" :width="100" />
+                        <el-table-column align="center" label="操作" :width="140" fixed="right">
+                          <template #default="scope">
+                            <el-button
+                              type="primary"
+                              size="small"
+                              :loading="recentOrderDetailChecking && recentOrderDetailWorkorder === scope.row['订单编号']"
+                              @click.stop="openRecentOrderProcessProduction(scope.row)"
+                            >工序产量核查</el-button>
+                          </template>
+                        </el-table-column>
+                      </el-table>
+                    </el-tab-pane>
+
                     <el-tab-pane label="订单资料附件" name="third">
                       <el-table ref="multipleTable"
                                 :row-style="{ height: '30px' }"
@@ -178,6 +232,45 @@
                         <el-button type="primary" @click="handleConfirm">确认</el-button>
                     </div>
                 </el-dialog>
+
+                <!-- 员工当天报工记录弹窗 -->
+                <el-dialog
+                  v-model="employeeDailyDialogVisible"
+                  :title="employeeDailyDialogTitle"
+                  width="840px"
+                  destroy-on-close
+                  append-to-body
+                  class="employee-daily-dialog"
+                >
+                  <el-table
+                    :data="employeeDailyRecords"
+                    border
+                    size="small"
+                    style="width: 100%; height: 380px"
+                    tooltip-effect="dark"
+                    :row-style="{ height: '34px' }"
+                    :header-cell-style="employeeDailyHeaderCellStyle"
+                    :cell-style="employeeDailyCellStyle"
+                    :footer-cell-style="employeeDailyFooterCellStyle"
+                    :header-row-style="{ height: '34px' }"
+                    show-overflow-tooltip
+                    highlight-current-row
+                    scrollbar-always-on
+                    show-summary
+                    :summary-method="getEmployeeDailySummaries"
+                  >
+                    <el-table-column align="center" label="小组" prop="sys_id" :width="90" show-overflow-tooltip />
+                    <el-table-column align="center" label="订单编号" prop="work_order" :width="120" />
+                    <el-table-column align="center" label="工序号" prop="process_code" :width="80" />
+                    <el-table-column align="center" label="工序名称" prop="process_name" min-width="150" show-overflow-tooltip />
+                    <el-table-column align="center" label="员工姓名" prop="staff_name" :width="110" />
+                    <el-table-column align="center" label="数量" prop="number" :width="80" />
+                    <el-table-column align="center" label="报工时间" prop="sys_rq" :width="170" show-overflow-tooltip />
+                  </el-table>
+                  <template #footer>
+                    <el-button @click="employeeDailyDialogVisible = false">关闭</el-button>
+                  </template>
+                </el-dialog>
                 
                 <!-- 工分报工对话框 -->
                 <el-dialog
@@ -459,6 +552,42 @@
     <SubOrderPage ref="SubOrderPageRef" />
     <!-- 子订单详情页 -->
     <luckyexcelPage ref="luckyexcelPageRef" />
+
+    <!-- 近期报工订单:工序产量核查详情 -->
+    <el-dialog
+      v-model="recentOrderDetailVisible"
+      width="100%"
+      style="height: 100%;margin: 0px;"
+      top="2vh"
+      :close-on-click-modal="true"
+      :show-close="false"
+      destroy-on-close
+      append-to-body
+      class="process-production-dialog"
+    >
+      <template #header>
+        <div class="process-production-dialog-header">
+          <span class="process-production-dialog-title">工序产量核查({{ recentOrderDetailWorkorder }})</span>
+          <el-button
+            type="danger"
+            size="large"
+            class="process-production-dialog-close"
+            @click="recentOrderDetailVisible = false"
+          >
+            关闭
+          </el-button>
+        </div>
+      </template>
+      <ProcessProduction
+        :key="recentOrderDetailKey"
+        :initial-workorder="recentOrderDetailWorkorder"
+        :initial-process="add_formData['工序']"
+        :initial-sys-id="add_formData['组别']"
+        in-dialog
+        embedded-in-tab
+        hide-amount-columns
+      />
+    </el-dialog>
     </div>
   </template>
 <script>
@@ -528,6 +657,7 @@ import {ElMessage,ElMessageBox} from "element-plus";
 import luckyexcelPage from '@/view/yunyin/shengchanguanli/components/luckyexcel.vue';
 import SubOrderPage from '@/view/yunyin/shengchanguanli/components/SubOrder.vue';
 import ProcessProduction from '@/view/SalaryManage/ProcessProduction.vue';
+import { ensureProcessProductionData } from '@/utils/processProductionDialog'
 import { useUserStore } from '@/pinia/modules/user';
 defineOptions({name: 'Company'})
 const path = `${import.meta.env.VITE_BASE_PATH}:${import.meta.env.VITE_UPLOADS_PORT}`
@@ -553,6 +683,7 @@ const currentDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
 const currentDates = `${year}-${month}-${day}`;
 const add_searchInfo = ref('')
 const gongfenHistoryData = ref([])
+const recentReportOrders = ref([])
 const gongfenHistoryTableRef = ref()
 const staffNameFilter = ref([])
 const currentOrderNo = ref('')
@@ -673,6 +804,72 @@ const getGongfenHistorySummaries = ({ columns, data }) => {
   return sums
 }
 
+// ===== 员工当天报工记录 =====
+const employeeDailyDialogVisible = ref(false)
+const employeeDailyEmployee = ref({})
+
+const employeeDailyDialogTitle = computed(() => {
+  const name = String(employeeDailyEmployee.value?.['员工姓名'] || '').trim()
+  const no = String(employeeDailyEmployee.value?.['员工编号'] || '').trim()
+  if (!name && !no) return '员工当天报工记录'
+  return `${name}${no ? `(${no})` : ''} - 当天报工记录`
+})
+
+// 该员工当日所有报工记录
+const employeeDailyRecords = computed(() => {
+  const name = String(employeeDailyEmployee.value?.['员工姓名'] || '').trim()
+  if (!name) return []
+  return (gongfenHistoryData.value || []).filter(
+    (row) => String(row.staff_name || '').trim() === name
+  )
+})
+
+// 双击员工卡片打开弹窗
+const openEmployeeDailyRecords = (employee) => {
+  if (!employee) return
+  employeeDailyEmployee.value = employee
+  employeeDailyDialogVisible.value = true
+}
+
+const employeeDailyHeaderCellStyle = () => ({
+  padding: '6px 0',
+  fontSize: '16px',
+  textAlign: 'center',
+})
+
+const employeeDailyCellStyle = () => ({
+  padding: '6px 0',
+  fontSize: '16px',
+  textAlign: 'center',
+})
+
+const employeeDailyFooterCellStyle = () => ({
+  padding: '6px 0',
+  fontSize: '16px',
+  fontWeight: '600',
+  background: '#fafafa',
+  textAlign: 'center',
+})
+
+const getEmployeeDailySummaries = ({ columns, data }) => {
+  const sums = []
+  columns.forEach((column, index) => {
+    if (column.property === 'process_code') {
+      sums[index] = '合计'
+      return
+    }
+    if (column.property === 'number') {
+      sums[index] = data.reduce((total, row) => {
+        const val = Number(row.number)
+        return total + (Number.isNaN(val) ? 0 : val)
+      }, 0)
+      return
+    }
+    sums[index] = ''
+  })
+  return sums
+}
+
 //全局获取code,接口接收code
 const _code = ref('裁剪')
 //全局接口接受的物理地址
@@ -1628,6 +1825,29 @@ const orderInfoList = reactive([]);
 /** 当前已加载订单编号(红框订单信息区,与搜索 input 解耦) */
 const processProductionRef = ref(null)
 const processProductionRefreshKey = ref(0)
+const recentOrderDetailVisible = ref(false)
+const recentOrderDetailWorkorder = ref('')
+const recentOrderDetailKey = ref(0)
+const recentOrderDetailChecking = ref(false)
+
+const openRecentOrderProcessProduction = async (row) => {
+  const orderNo = String(row?.['订单编号'] || '').trim()
+  if (!orderNo) {
+    ElMessage.warning('缺少订单编号')
+    return
+  }
+  recentOrderDetailChecking.value = true
+  recentOrderDetailWorkorder.value = orderNo
+  try {
+    const result = await ensureProcessProductionData(orderNo)
+    if (!result.ok) return
+    recentOrderDetailWorkorder.value = result.workorder
+    recentOrderDetailKey.value += 1
+    recentOrderDetailVisible.value = true
+  } finally {
+    recentOrderDetailChecking.value = false
+  }
+}
 
 const getActiveOrderNo = () => {
   const no = String(currentOrderNo.value || orderInfoList[0]?.['订单编号'] || '').trim()
@@ -1645,10 +1865,10 @@ const refreshProcessProduction = async () => {
   }
 }
 
-/** 点击「当天报工记录 / 工序产量核查」时重新拉取最新数据(含再次点击当前 Tab) */
+/** 点击「当天报工记录 / 近期报工订单 / 工序产量核查」时重新拉取最新数据(含再次点击当前 Tab) */
 const onGongfenTabClick = async (pane) => {
   const name = pane?.paneName ?? pane?.props?.name
-  if (name === 'first') {
+  if (name === 'first' || name === 'recentOrders') {
     await getGongfenHistory()
     return
   }
@@ -1737,25 +1957,36 @@ const add_onSubmit = async ()=>{
   }
 };
 
-// 获取工分报工历史记录
+// 获取工分报工历史记录 / 近期报工订单
 const getGongfenHistory = async () => {
   const seq = ++historyQuerySeq
   try {
     const response = await GetReportingWorkRecord({
       date: currentDates,
-      machine: add_formData['机台号']
+      machine: add_formData['机台号'],
+      majorprocess: add_formData['工序']
     });
     if (seq !== historyQuerySeq) return
     if (response.code === 0 && response.data) {
-      gongfenHistoryData.value = response.data || [];
+      const payload = response.data
+      // 兼容:旧接口直接返回数组;新接口为 { records, count, orders }
+      if (Array.isArray(payload)) {
+        gongfenHistoryData.value = payload
+        recentReportOrders.value = []
+      } else {
+        gongfenHistoryData.value = payload.records || []
+        recentReportOrders.value = payload.orders || []
+      }
     } else {
       gongfenHistoryData.value = [];
+      recentReportOrders.value = [];
     }
     relayoutGongfenHistoryTable()
   } catch (error) {
     if (seq !== historyQuerySeq) return
     console.error('获取工分报工历史记录失败:', error);
     gongfenHistoryData.value = [];
+    recentReportOrders.value = [];
     relayoutGongfenHistoryTable()
   }
 };
@@ -3131,6 +3362,32 @@ const yellowTopTip = (text) => {
   color: #999;
 }
 
+/* 员工当天报工记录 */
+.employee-daily-wrap {
+  padding: 8px 4px;
+}
+
+.employee-daily-tip {
+  font-size: 14px;
+  color: #909399;
+  margin-bottom: 10px;
+}
+
+.employee-daily-grid {
+  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
+  gap: 10px;
+}
+
+.employee-daily-card {
+  user-select: none;
+}
+
+.employee-daily-empty {
+  padding: 30px 0;
+  text-align: center;
+  color: #909399;
+}
+
 </style>
 
 <style>
@@ -3166,6 +3423,29 @@ const yellowTopTip = (text) => {
   background: #ffffff !important;
 }
 
+/* 员工当天报工记录弹窗:固定尺寸,锚定左上区域,内容超出滚动 */
+.employee-daily-dialog.el-dialog,
+.employee-daily-dialog {
+  position: absolute;
+  left: 15%;
+  top: 15vh;
+  margin: 0 !important;
+  width: 840px !important;
+}
+.employee-daily-dialog .el-dialog__header {
+  padding: 12px 16px 6px;
+}
+.employee-daily-dialog .el-dialog__title {
+  font-size: 18px;
+  font-weight: 600;
+}
+.employee-daily-dialog .el-dialog__body {
+  padding: 8px 16px 8px;
+}
+.employee-daily-dialog .el-dialog__footer {
+  padding: 6px 16px 12px;
+}
+
 /* 确认报工弹窗:固定尺寸 + 内容居中放大(append-to-body 用全局样式) */
 .work-score-confirm-dialog.el-dialog,
 .work-score-confirm-dialog {
@@ -3338,4 +3618,28 @@ const yellowTopTip = (text) => {
   color: #f56c6c;
   font-weight: 600;
 }
+
+:deep(.process-production-dialog .el-dialog__header) {
+  display: block;
+  width: 100%;
+  margin-right: 0;
+  padding: 16px 20px 12px;
+  box-sizing: border-box;
+}
+:deep(.process-production-dialog .el-dialog__body) {
+  padding: 0;
+  background: #fff;
+}
+.process-production-dialog-header {
+  display: flex;
+  align-items: center;
+  width: 100%;
+  box-sizing: border-box;
+}
+.process-production-dialog-title {
+  flex: 1;
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+}
 </style>

+ 49 - 5
src/view/yunyin/shengchanguanli/MonthlySampleGarments.vue

@@ -31,6 +31,10 @@
           <layout-content>
           <el-main>
             <div class="gva-table-box">
+              <div class="monthly-sample-toolbar">
+                <el-button  type="primary" @click="addFirstRow()">新增</el-button>
+                <el-button  type="danger" :disabled="selectedRows.length === 0" @click="batchDeleteRows">删除</el-button>
+              </div>
               <el-table ref="multipleTable" style="width: 100%;height: 70vh" tooltip-effect="dark"
                         :row-style="{ height: '32px' }" :header-cell-style="{ padding: '0px' }"
                         :cell-style="{ padding: '0px' }" :header-row-style="{ height: '32px' }"
@@ -39,10 +43,11 @@
                         class="monthly-sample-table"
                         :cell-class-name="gxbgCellClass"
                         highlight-current-row="true"
-                        @row-click="tableRowClick" :show-overflow-tooltip="true" 
+                        @row-click="tableRowClick" :show-overflow-tooltip="true"
                         @selection-change="handleSelectionChange"
                         @cell-click="handleTableCellClick"
                         @row-dblclick="handleRowDblclick">
+               <el-table-column type="selection" width="45" align="center" />
                <el-table-column sortable align="center" label="客户编号" prop="客户编号" width="100">
                  <template #default="{ row }">
                    <el-input v-if="isEditing(row, '客户编号')"
@@ -189,11 +194,9 @@
                    <div v-else class="cell-span">{{ row.备注 || '\u00A0' }}</div>
                  </template>
                </el-table-column>
-               <el-table-column align="center" label="操作" width="230" fixed="right">
+               <el-table-column align="center" label="操作" width="100" fixed="right">
                  <template #default="{ row, $index }">
-                   <el-button size="small" type="primary" @click="addRow($index)">新增一行</el-button>
                    <el-button size="small" type="warning" @click="handleRowDblclick(row)">详情</el-button>
-                   <el-button size="small" type="danger" @click="deleteRow(row, $index)">删除</el-button>
                  </template>
                </el-table-column>
               </el-table>
@@ -257,6 +260,7 @@ const defaultProps = {
   label: 'label'
 }
 
+const selectedRows = ref([])
 const tableData = ref([])
 const multipleTable = ref(null)
 const _noderq = ref('')
@@ -443,7 +447,9 @@ const onSubmit = async () => {
 }
 
 const tableRowClick = () => {}
-const handleSelectionChange = () => {}
+const handleSelectionChange = (val) => {
+  selectedRows.value = val
+}
 const handleCurrentChange = async (val) => {
   page.value = val
   if (searchInfo.value) {
@@ -504,6 +510,38 @@ const deleteRow = async (row, index) => {
   }
 }
 
+// 批量删除勾选行:UniqId 用逗号连接传给后端
+const batchDeleteRows = async () => {
+  if (selectedRows.value.length === 0) return
+  const savedRows = selectedRows.value.filter((r) => r.UniqId)
+  const tempRows = selectedRows.value.filter((r) => !r.UniqId)
+  try {
+    if (savedRows.length > 0) {
+      const uniqIds = savedRows.map((r) => r.UniqId).join(',')
+      const res = await deleteMonthlyGarments({ UniqId: uniqIds, nickName: userStore.userInfo.nickName })
+      if (res.code !== 0) {
+        ElMessage.error('删除失败')
+        return
+      }
+    }
+    // 从 tableData 中移除选中行
+    const selectedKeys = new Set(selectedRows.value.map(rowKeyOf))
+    tableData.value = tableData.value.filter((r) => !selectedKeys.has(rowKeyOf(r)))
+    total.value = Math.max(total.value - savedRows.length, 0)
+    selectedRows.value = []
+    ensureEditableRows()
+    ElMessage.success(`删除成功,共删除${savedRows.length + tempRows.length}条`)
+    await refreshTreePreserveSelection()
+    if (!tableData.value.some((r) => r.UniqId) && page.value > 1 && total.value > 0) {
+      page.value -= 1
+      await loadListByMonth(_noderq.value, _nodecode.value, { resetPage: false })
+    }
+  } catch (error) {
+    ElMessage.error('删除失败')
+    console.error('删除失败:', error)
+  }
+}
+
 const EDITABLE_FIELDS = [
   '客户编号',
   '款号',
@@ -789,6 +827,12 @@ const fetchAllForExport = async () => {
 .gva-table-box {
   padding: 0px !important;
 }
+.monthly-sample-toolbar {
+  padding: 4px 8px;
+  display: flex;
+  gap: 8px;
+  align-items: center;
+}
 .cell-span {
   display: block;
   width: 100%;