zhangwencui
8 小时以前 ed36047f6ce0b91dad25efc10c8a0e83dd533a68
change
已添加2个文件
已修改13个文件
3128 ■■■■ 文件已修改
src/api/personnelManagement/class.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productProcessRoute.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productStructure.js 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionOrder.js 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ProcessParamListDialog.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Navbar.vue 149 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/classsSheduling/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/processRouteItem/index.vue 429 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/Detail/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/index.vue 685 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/components/ReportingDialog.vue 613 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/index.vue 732 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/index2.vue 420 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionPlan/productionPlan/index.vue 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/class.js
@@ -71,6 +71,7 @@
    url: "/personalShift/export",
    method: "get",
    params: query,
    responseType: "blob",
  });
}
src/api/productionManagement/productProcessRoute.js
@@ -4,23 +4,16 @@
// åˆ—表查询
export function findProductProcessRouteItemList(query) {
  return request({
    url: `/processRouteItemInstance/list/${query.orderId}`,
    method: "get",
    params: query,
  });
}
// åˆ—表查询-生产订单
export function findProcessRouteItemInstanceList(query) {
  return request({
    url: `/processRouteItemInstance/list/${query.orderId}`,
    url: `/productionOrderRouteItem/list/${query.orderId}`,
    method: "get",
  });
}
export function addOrUpdateProductProcessRouteItem(data) {
  return request({
    url: "/processRouteItemInstance/update",
    url: "/productionOrderRouteItem/update",
    method: "put",
    data: data,
  });
@@ -29,7 +22,7 @@
// ç”Ÿäº§è®¢å•下:新增工艺路线项目
export function addRouteItem(data) {
  return request({
    url: "/processRouteItemInstance/add",
    url: "/productionOrderRouteItem/add",
    method: "post",
    data,
  });
@@ -47,7 +40,7 @@
// åˆ é™¤å·¥è‰ºè·¯çº¿é¡¹ç›®ï¼ˆè·¯ç”±åŽæ‹¼æŽ¥ id)
export function deleteRouteItem(id) {
  return request({
    url: `/processRouteItemInstance/delete/${id}`,
    url: `/productionOrderRouteItem/delete/${id}`,
    method: "delete",
  });
}
@@ -55,7 +48,7 @@
// ç”Ÿäº§è®¢å•下:排序工艺路线项目
export function sortRouteItem(data) {
  return request({
    url: "/productProcessRoute/sortRouteItem",
    url: "/productionOrderRouteItem/sort",
    method: "post",
    data,
  });
@@ -63,7 +56,7 @@
// èŽ·å–å·¥åºå‚æ•°åˆ—è¡¨-生产订单
export function findProcessParamListOrder(query) {
  return request({
    url: `/processRouteItemParamInstance/list`,
    url: `/productionOrderRouteItemParam/list`,
    method: "get",
    params: query,
  });
@@ -71,7 +64,7 @@
// å·¥è‰ºè·¯çº¿å‚数新增-生产订单
export function addProcessRouteItemParamOrder(data) {
  return request({
    url: "/processRouteItemParamInstance/add",
    url: "/productionOrderRouteItemParam/add",
    method: "post",
    data: data,
  });
@@ -79,7 +72,7 @@
// å·¥è‰ºè·¯çº¿å‚数修改-生产订单
export function editProcessRouteItemParamOrder(data) {
  return request({
    url: "/processRouteItemParamInstance/update",
    url: "/productionOrderRouteItemParam/update",
    method: "put",
    data: data,
  });
@@ -87,7 +80,7 @@
// å·¥è‰ºè·¯çº¿å‚数删除-生产订单
export function delProcessRouteItemParamOrder(id) {
  return request({
    url: `/processRouteItemParamInstance/delete/${id}`,
    url: `/productionOrderRouteItemParam/delete/${id}`,
    method: "delete",
  });
}
src/api/productionManagement/productStructure.js
@@ -11,8 +11,23 @@
export function add(data) {
  return request({
    url: "/productStructure",
    url: "/productStructure/"+data.bomId,
    method: "post",
    data: data,
    data: data.children,
  });
}
// åˆ†é¡µæŸ¥è¯¢-产品订单
export function queryList2(id) {
  return request({
    url: "/productionOrderStructure/getBomStructs/" + id,
    method: "get",
  });
}
export function add2(data) {
  return request({
    url: "/productionOrderStructure/addOrUpdateBomStructs/"+data.orderId,
    method: "put",
    data: data.children,
  });
}
src/api/productionManagement/productionOrder.js
@@ -44,14 +44,21 @@
    data: data,
  });
}
//生产订单删除
export function delProductOrder(ids) {
  return request({
    url: `/productOrder/${ids}`,
    method: "delete",
  });
}
//生产订单退回
export function revokeProductOrder(data) {
  return request({
    url: "/productOrder/revoke",
    method: "post",
    data: data,
  });
}
// ç”Ÿäº§è®¢å•-查询产品结构列表
export function listProcessBom(query) {
  return request({
@@ -130,3 +137,10 @@
    data: data,
  });
}
export function getProductOrderSource(id) {
  return request({
    url: `/productOrder/productOrderSource/${id}`,
    method: "get",
  });
}
src/components/ProcessParamListDialog.vue
@@ -424,8 +424,9 @@
    if (props.pageType === "order") {
      addProcessRouteItemParamOrder({
        orderId: Number(props.orderId),
        processId: props.process.id,
        routeItemId: Number(props.routeId),
        // processId: props.process.id,
        routeItemId: props.process.id,
        // routeItemId: Number(props.routeId),
        paramId: selectedParam.value.id,
        standardValue: isNumericMode
          ? selectedParam.value.standardValue || ""
src/layout/components/Navbar.vue
@@ -1,39 +1,45 @@
<template>
  <div class="navbar">
    <div>
      <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container"
      <hamburger id="hamburger-container"
                 :is-active="appStore.sidebar.opened"
                 class="hamburger-container"
        @toggleClick="toggleSideBar" />
      <breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />
      <breadcrumb v-if="!settingsStore.topNav"
                  id="breadcrumb-container"
                  class="breadcrumb-container" />
    </div>
    <!--    <top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container" />-->
    <div class="right-menu">
      <!-- æ¶ˆæ¯é€šçŸ¥ -->
      <el-popover
        v-model:visible="notificationVisible"
      <el-popover v-model:visible="notificationVisible"
        :width="500"
        placement="bottom-end"
        trigger="click"
        :popper-options="{ modifiers: [{ name: 'offset', options: { offset: [0, 10] } }] }"
        popper-class="notification-popover"
      >
                  popper-class="notification-popover">
        <template #reference>
          <div class="notification-container right-menu-item hover-effect">
            <el-badge :value="unreadCount" :hidden="unreadCount === 0" class="notification-badge">
              <el-icon :size="20" style="cursor: pointer;">
            <el-badge :value="unreadCount"
                      :hidden="unreadCount === 0"
                      class="notification-badge">
              <el-icon :size="20"
                       style="cursor: pointer;">
                <Bell />
              </el-icon>
            </el-badge>
          </div>
        </template>
        <NotificationCenter
          @unreadCountChange="handleUnreadCountChange"
          ref="notificationCenterRef"
        />
        <NotificationCenter @unreadCountChange="handleUnreadCountChange"
                            ref="notificationCenterRef" />
      </el-popover>
      <div class="avatar-container">
        <el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
        <el-dropdown @command="handleCommand"
                     class="right-menu-item hover-effect"
                     trigger="click">
          <div class="avatar-wrapper">
            <img :src="userStore.avatar" class="user-avatar" />
            <img :src="userStore.avatar"
                 class="user-avatar" />
            <el-icon><caret-bottom /></el-icon>
          </div>
          <template #dropdown>
@@ -41,10 +47,12 @@
              <router-link to="/user/profile">
                <el-dropdown-item>个人中心</el-dropdown-item>
              </router-link>
              <el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">
              <el-dropdown-item command="setLayout"
                                v-if="settingsStore.showSettings">
                <span>布局设置</span>
              </el-dropdown-item>
              <el-dropdown-item divided command="logout">
              <el-dropdown-item divided
                                command="logout">
                <span>退出登录</span>
              </el-dropdown-item>
            </el-dropdown-menu>
@@ -56,29 +64,29 @@
</template>
<script setup>
import { ElMessageBox } from 'element-plus'
import { Bell } from '@element-plus/icons-vue'
import Breadcrumb from '@/components/Breadcrumb'
import TopNav from '@/components/TopNav'
import Hamburger from '@/components/Hamburger'
import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect'
import HeaderSearch from '@/components/HeaderSearch'
import RuoYiGit from '@/components/RuoYi/Git'
import RuoYiDoc from '@/components/RuoYi/Doc'
import NotificationCenter from './NotificationCenter/index.vue'
import useAppStore from '@/store/modules/app'
import useUserStore from '@/store/modules/user'
import useSettingsStore from '@/store/modules/settings'
  import { ElMessageBox } from "element-plus";
  import { Bell } from "@element-plus/icons-vue";
  import Breadcrumb from "@/components/Breadcrumb";
  import TopNav from "@/components/TopNav";
  import Hamburger from "@/components/Hamburger";
  import Screenfull from "@/components/Screenfull";
  import SizeSelect from "@/components/SizeSelect";
  import HeaderSearch from "@/components/HeaderSearch";
  import RuoYiGit from "@/components/RuoYi/Git";
  import RuoYiDoc from "@/components/RuoYi/Doc";
  import NotificationCenter from "./NotificationCenter/index.vue";
  import useAppStore from "@/store/modules/app";
  import useUserStore from "@/store/modules/user";
  import useSettingsStore from "@/store/modules/settings";
const appStore = useAppStore()
const userStore = useUserStore()
const settingsStore = useSettingsStore()
const notificationVisible = ref(false)
const notificationCenterRef = ref(null)
const unreadCount = ref(0)
  const appStore = useAppStore();
  const userStore = useUserStore();
  const settingsStore = useSettingsStore();
  const notificationVisible = ref(false);
  const notificationCenterRef = ref(null);
  const unreadCount = ref(0);
function toggleSideBar() {
  appStore.toggleSideBar()
    appStore.toggleSideBar();
}
// const redirect = ref(undefined)
// watch(route, (newRoute) => {
@@ -88,73 +96,75 @@
function handleCommand(command) {
  switch (command) {
    case "setLayout":
      setLayout()
      break
        setLayout();
        break;
    case "logout":
      logout()
      break
        logout();
        break;
    default:
      break
        break;
  }
}
function logout() {
  ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    userStore.logOut().then(() => {
      location.href = '/index'
    ElMessageBox.confirm("确定注销并退出系统吗?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    })
  }).catch(() => { })
      .then(() => {
        userStore.logOut().then(() => {
          location.href = "/index";
        });
      })
      .catch(() => {});
}
const emits = defineEmits(['setLayout'])
  const emits = defineEmits(["setLayout"]);
function setLayout() {
  emits('setLayout')
    emits("setLayout");
}
function toggleTheme() {
  settingsStore.toggleTheme()
    settingsStore.toggleTheme();
}
// æ¶ˆæ¯é€šçŸ¥ç›¸å…³
function handleUnreadCountChange(count) {
  unreadCount.value = count
    unreadCount.value = count;
}
// ç»„件挂载时加载未读数量和定时刷新
let unreadCountTimer = null
  let unreadCountTimer = null;
onMounted(() => {
  // å»¶è¿ŸåŠ è½½ï¼Œç¡®ä¿ç»„ä»¶å·²æ¸²æŸ“
  nextTick(() => {
    if (notificationCenterRef.value) {
      notificationCenterRef.value.loadUnreadCount()
        notificationCenterRef.value.loadUnreadCount();
    }
  })
    });
  // å®šæ—¶åˆ·æ–°æœªè¯»æ•°é‡ï¼ˆæ¯30秒)
  unreadCountTimer = setInterval(() => {
    if (notificationCenterRef.value) {
      notificationCenterRef.value.loadUnreadCount()
    }
  }, 30000)
})
    // unreadCountTimer = setInterval(() => {
    //   if (notificationCenterRef.value) {
    //     notificationCenterRef.value.loadUnreadCount()
    //   }
    // }, 30000)
  });
// ç›‘听 popover æ˜¾ç¤ºçŠ¶æ€ï¼Œæ‰“å¼€æ—¶åŠ è½½æ¶ˆæ¯åˆ—è¡¨
watch(notificationVisible, (val) => {
  watch(notificationVisible, val => {
  if (val && notificationCenterRef.value) {
    nextTick(() => {
      notificationCenterRef.value.loadMessages()
    })
        notificationCenterRef.value.loadMessages();
      });
  }
})
  });
onUnmounted(() => {
  if (unreadCountTimer) {
    clearInterval(unreadCountTimer)
      clearInterval(unreadCountTimer);
  }
})
  });
</script>
<style lang='scss' scoped>
@@ -271,7 +281,6 @@
    }
  }
}
</style>
<style lang="scss">
src/views/personnelManagement/classsSheduling/index.vue
src/views/productionManagement/processRoute/index.vue
@@ -93,6 +93,8 @@
    {
      label: "工艺路线编号",
      prop: "processRouteCode",
      width: "200px",
      className: "status-cell",
    },
    {
      label: "状态",
@@ -117,6 +119,7 @@
    {
      label: "产品类型",
      prop: "dictLabel",
      dataType: "tag",
    },
    {
      label: "BOM编号",
@@ -240,6 +243,7 @@
        model: row.model || "",
        bomNo: row.bomNo || "",
        dictLabel: row.dictLabel || "",
        orderId: row.id || "",
        bomId: row.bomId || null,
        description: row.description || "",
        type: "route",
@@ -320,3 +324,11 @@
</script>
<style scoped></style>
<style lang="scss">
  .status-cell {
    font-weight: 600;
    color: #409eff;
    font-family: "Courier New", monospace;
    text-shadow: 0 1px 2px rgba(64, 158, 255, 0.2);
  }
</style>
src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -169,6 +169,16 @@
    <div class="section-BOM">
      <div class="section-header">
        <div class="section-title">BOM</div>
        <div class="section-actions"
             v-if="pageType === 'order'">
          <el-button type="primary"
                     @click="toggleBomEdit">
            {{ bomDataValue.isEdit ? '取消' : '编辑' }}
          </el-button>
          <el-button v-if=" bomDataValue.isEdit"
                     type="success"
                     @click="saveBomChanges">保存</el-button>
        </div>
      </div>
      <div>
        <!-- BOM表格 -->
@@ -179,28 +189,110 @@
                  style="width: 100%">
          <el-table-column type="expand">
            <template #default="props">
              <el-form ref="bomFormRef"
                       :model="bomDataValue">
              <el-table :data="props.row.bomList"
                        row-key="id"
                          row-key="tempId"
                        default-expand-all
                        :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
                        style="width: 100%">
                <el-table-column prop="productName"
                                 label="产品" />
                <el-table-column prop="model"
                                 label="规格" />
                                   label="规格">
                    <template #default="{ row }">
                      <el-form-item v-if="bomDataValue.isEdit"
                                    style="margin: 0">
                        <el-select v-model="row.model"
                                   placeholder="请选择规格"
                                   :disabled="!bomDataValue.isEdit"
                                   style="width: 100%"
                                   @visible-change="(v) => { if (v) openBomProductDialog(row.tempId) }">
                          <el-option v-if="row.model"
                                     :label="row.model"
                                     :value="row.model" />
                        </el-select>
                      </el-form-item>
                      <span v-else>{{ row.model }}</span>
                    </template>
                  </el-table-column>
                <el-table-column prop="processName"
                                 label="消耗工序" />
                                   label="消耗工序">
                    <template #default="{ row }">
                      <el-form-item v-if="bomDataValue.isEdit"
                                    style="margin: 0">
                        <el-select v-model="row.processId"
                                   placeholder="请选择消耗工序"
                                   :disabled="!bomDataValue.isEdit"
                                   style="width: 100%">
                          <el-option v-for="process in processOptions"
                                     :key="process.id"
                                     :label="process.name"
                                     :value="process.id" />
                        </el-select>
                      </el-form-item>
                      <span v-else>{{ row.processName }}</span>
                    </template>
                  </el-table-column>
                <el-table-column prop="unitQuantity"
                                 label="单位产出所需数量" />
                                   label="单位产出所需数量">
                    <template #default="{ row }">
                      <el-form-item v-if="bomDataValue.isEdit"
                                    style="margin: 0">
                        <el-input-number v-model="row.unitQuantity"
                                         :min="0"
                                         :step="1"
                                         controls-position="right"
                                         style="width: 100%"
                                         :disabled="!bomDataValue.isEdit" />
                      </el-form-item>
                      <span v-else>{{ row.unitQuantity }}</span>
                    </template>
                  </el-table-column>
                <el-table-column prop="unit"
                                 label="单位" />
                                   label="单位">
                    <template #default="{ row }">
                      <el-form-item v-if="bomDataValue.isEdit"
                                    style="margin: 0">
                        <el-input v-model="row.unit"
                                  placeholder="请输入单位"
                                  clearable
                                  :disabled="!bomDataValue.isEdit" />
                      </el-form-item>
                      <span v-else>{{ row.unit }}</span>
                    </template>
                  </el-table-column>
                  <el-table-column label="操作"
                                   fixed="right"
                                   v-if="pageType === 'order'"
                                   width="180">
                    <template #default="{ row }">
                      <el-button v-if="bomDataValue.isEdit"
                                 type="danger"
                                 text
                                 size="small"
                                 @click="removeBomItem(row.tempId)">删除</el-button>
                      <el-button v-if="bomDataValue.isEdit"
                                 type="primary"
                                 text
                                 size="small"
                                 @click="addBomItem2(row.tempId)">添加子项</el-button>
                    </template>
                  </el-table-column>
              </el-table>
              </el-form>
            </template>
          </el-table-column>
          <el-table-column label="BOM编号"
                           prop="bomNo" />
          <el-table-column label="产品类型"
                           prop="dictLabel" />
          <!-- <el-table-column label="操作"
                           width="150">
            <template #default="{ row }">
            </template>
          </el-table-column> -->
          <!-- <el-table-column label="产品编码"
                           prop="productCode" />
          <el-table-column label="产品名称"
@@ -208,6 +300,18 @@
          <el-table-column label="规格型号"
                           prop="model" /> -->
        </el-table>
        <div v-if="bomDataValue.isEdit"
             style="text-align: center;border: 1px solid #e4e7ed;padding: 10px;transition: all 0.3s ease;cursor: pointer;"
             :class="{'hover-effect': bomDataValue.isEdit}">
          <el-button type="primary"
                     text
                     @click="addBomItem">
            <el-icon style="vertical-align: middle;margin-right: 5px;">
              <Plus />
            </el-icon>
            æ·»åŠ 
          </el-button>
        </div>
      </div>
    </div>
    <!-- æ–°å¢ž/编辑弹窗 -->
@@ -249,6 +353,10 @@
    <ProductSelectDialog v-model="showProductSelectDialog"
                         @confirm="handleProductSelect"
                         single />
    <!-- BOM产品选择对话框 -->
    <ProductSelectDialog v-model="bomDataValue.showProductDialog"
                         @confirm="handleBomProductSelect"
                         single />
    <!-- å‚数列表对话框 -->
    <ProcessParamListDialog v-model="showParamListDialog"
                            :title="`${currentProcess ? getProcessName(currentProcess.processId) : ''} - å‚数列表`"
@@ -289,7 +397,11 @@
    sortRouteItem,
  } from "@/api/productionManagement/productProcessRoute.js";
  import { processList } from "@/api/productionManagement/productionProcess.js";
  import { queryList } from "@/api/productionManagement/productStructure.js";
  import {
    queryList2,
    queryList,
    add2,
  } from "@/api/productionManagement/productStructure.js";
  import { useRoute } from "vue-router";
  import { ElMessageBox, ElMessage } from "element-plus";
  import Sortable from "sortablejs";
@@ -327,6 +439,14 @@
  const currentProcess = ref(null);
  const paramList = ref([]);
  const bomTableData = ref([]);
  const bomFormRef = ref(null);
  const bomDataValue = ref({
    dataList: [],
    showProductDialog: false,
    currentRowName: null,
    loading: false,
    isEdit: false,
  });
  let tableSortable = null;
  let cardSortable = null;
@@ -407,12 +527,21 @@
      bomId: route.query.bomId || null,
      description: route.query.description || "",
    };
    // å¦‚果有bomId,获取BOM数据
    if (routeInfo.value.bomId) {
      queryList(routeInfo.value.bomId)
    if (pageType.value === "order") {
      queryList2(route.query.orderId)
        .then(res => {
          if (res.data) {
            // ä¸ºBOM数据设置tempId
            const setTempIdRecursively = items => {
              items.forEach(item => {
                item.tempId = item.id || new Date().getTime();
                if (item.children && item.children.length > 0) {
                  setTempIdRecursively(item.children);
                }
              });
            };
            setTempIdRecursively(res.data);
            bomTableData.value = [
              {
                bomNo: routeInfo.value.bomNo,
@@ -423,12 +552,49 @@
                bomList: res.data,
              },
            ];
            // ä¿å­˜åŽŸå§‹BOM数据
            bomDataValue.value.dataList = res.data;
          }
        })
        .catch(err => {
          console.error("获取BOM数据失败:", err);
        });
    } else {
      queryList(Number(route.query.bomId))
        .then(res => {
          if (res.data) {
            // ä¸ºBOM数据设置tempId
            const setTempIdRecursively = items => {
              items.forEach(item => {
                item.tempId = item.id || new Date().getTime();
                if (item.children && item.children.length > 0) {
                  setTempIdRecursively(item.children);
                }
              });
            };
            setTempIdRecursively(res.data);
            bomTableData.value = [
              {
                bomNo: routeInfo.value.bomNo,
                dictLabel: routeInfo.value.dictLabel,
                productCode: "",
                productName: routeInfo.value.productName,
                model: routeInfo.value.model,
                bomList: res.data,
              },
            ];
            // ä¿å­˜åŽŸå§‹BOM数据
            bomDataValue.value.dataList = res.data;
          }
        })
        .catch(err => {
          console.error("获取BOM数据失败:", err);
        });
    }
    // èŽ·å–BOM数据,使用新的接口
  };
  // æ–°å¢ž
@@ -586,6 +752,241 @@
  const closeDialog = () => {
    dialogVisible.value = false;
    resetForm();
  };
  // BOM相关方法
  // åˆ‡æ¢BOM编辑模式
  const toggleBomEdit = () => {
    bomDataValue.value.isEdit = !bomDataValue.value.isEdit;
    if (!bomDataValue.value.isEdit) {
      // å–消编辑时重新加载数据
      getRouteInfo();
    }
  };
  // æ·»åŠ BOM项
  const addBomItem = () => {
    if (bomTableData.value.length > 0) {
      const newItem = {
        parentId: "",
        parentTempId: "",
        productName: "",
        productId: "",
        model: undefined,
        productModelId: undefined,
        processId: "",
        processName: "",
        unitQuantity: 0,
        unit: "",
        children: [],
        tempId: new Date().getTime(),
      };
      bomTableData.value[0].bomList.push(newItem);
      // ç”±äºŽbomDataValue.value.dataList和bomTableData.value[0].bomList指向同一个数组,不需要重复添加
    }
  };
  // æ·»åŠ BOM子项
  const addBomItem2 = tempId => {
    const addChildItem = (items, tempId) => {
      for (let i = 0; i < items.length; i++) {
        const item = items[i];
        if (item.tempId === tempId) {
          if (!item.children) {
            item.children = [];
          }
          item.children.push({
            parentId: item.id || "",
            parentTempId: item.tempId || "",
            productName: "",
            productId: "",
            model: undefined,
            productModelId: undefined,
            processId: "",
            processName: "",
            unitQuantity: 0,
            unit: "",
            children: [],
            tempId: new Date().getTime(),
          });
          return true;
        }
        if (item.children && item.children.length > 0) {
          if (addChildItem(item.children, tempId)) {
            return true;
          }
        }
      }
      return false;
    };
    if (bomTableData.value.length > 0) {
      addChildItem(bomTableData.value[0].bomList, tempId);
      // ç”±äºŽbomDataValue.value.dataList和bomTableData.value[0].bomList指向同一个数组,不需要重复添加
    }
  };
  // åˆ é™¤BOM项
  const removeBomItem = tempId => {
    // ä»ŽBOM表格数据中删除
    if (bomTableData.value.length > 0) {
      const removeFromList = (items, tempId) => {
        for (let i = 0; i < items.length; i++) {
          const item = items[i];
          if (item.tempId === tempId) {
            items.splice(i, 1);
            return true;
          }
          if (item.children && item.children.length > 0) {
            if (removeFromList(item.children, tempId)) {
              return true;
            }
          }
        }
        return false;
      };
      removeFromList(bomTableData.value[0].bomList, tempId);
      // ç”±äºŽbomDataValue.value.dataList和bomTableData.value[0].bomList指向同一个数组,不需要重复删除
    }
  };
  // æ‰“å¼€BOM产品选择对话框
  const openBomProductDialog = tempId => {
    bomDataValue.value.currentRowName = tempId;
    bomDataValue.value.showProductDialog = true;
  };
  // å¤„理BOM产品选择
  const handleBomProductSelect = products => {
    if (products && products.length > 0) {
      const product = products[0];
      const updateProductInfo = (items, tempId, productData) => {
        for (let i = 0; i < items.length; i++) {
          const item = items[i];
          if (item.tempId === tempId) {
            item.productName = productData.productName;
            item.model = productData.model;
            item.productModelId = productData.id;
            item.unit = productData.unit || "";
            return true;
          }
          if (item.children && item.children.length > 0) {
            if (updateProductInfo(item.children, tempId, productData)) {
              return true;
            }
          }
        }
        return false;
      };
      if (bomTableData.value.length > 0) {
        updateProductInfo(
          bomTableData.value[0].bomList,
          bomDataValue.value.currentRowName,
          product
        );
        // ç”±äºŽbomDataValue.value.dataList和bomTableData.value[0].bomList指向同一个数组,不需要重复更新
      }
      bomDataValue.value.showProductDialog = false;
    }
  };
  // ä¿å­˜BOM更改
  const saveBomChanges = () => {
    // æ ¡éªŒBOM数据
    const validateBomData = items => {
      for (let i = 0; i < items.length; i++) {
        const item = items[i];
        // æ ¡éªŒäº§å“æ˜¯å¦å¿…å¡«
        if (!item.productModelId) {
          ElMessage.error("请选择产品");
          return false;
        }
        // æ ¡éªŒå•位产出所需数量是否必填
        if (
          item.unitQuantity === undefined ||
          item.unitQuantity === null ||
          item.unitQuantity === 0
        ) {
          ElMessage.error("请填写单位产出所需数量");
          return false;
        }
        // é€’归校验子项
        if (item.children && item.children.length > 0) {
          if (!validateBomData(item.children)) {
            return false;
          }
        }
      }
      return true;
    };
    // æ‰§è¡Œæ ¡éªŒ
    if (bomTableData.value.length > 0) {
      if (!validateBomData(bomTableData.value[0].bomList)) {
        return;
      }
    }
    // è°ƒç”¨æ–°çš„保存接口
    // å‡†å¤‡ä¿å­˜æ•°æ®ï¼Œç¡®ä¿æ ¼å¼æ­£ç¡®
    // é€’归处理BOM项及其子项
    const processBomItem = (item, parentId = null, parentTempId = null) => {
      const cleanItem = {
        id: item.id || null,
        orderId: Number(orderId.value) || null,
        parentId: parentId,
        parentTempId: parentTempId || null,
        productModelId: item.productModelId || null,
        processId: item.processId || null,
        unitQuantity: item.unitQuantity || 0,
        demandedQuantity: item.demandedQuantity || null,
        unit: item.unit || "",
        tempId: item.tempId || new Date().getTime(),
        tenantId: item.tenantId || null,
        bomId: Number(route.query.bomId) || null,
        children: [],
      };
      // é€’归处理子项
      if (item.children && item.children.length > 0) {
        cleanItem.children = item.children.map(child =>
          processBomItem(child, item.id, item.tempId || null)
        );
      }
      return cleanItem;
    };
    const saveData = bomTableData.value[0].bomList.map(item =>
      processBomItem(item, item.parentId, item.parentTempId || null)
    );
    const formData = {
      orderId: Number(orderId.value) || null,
      children: saveData,
    };
    add2(formData)
      .then(res => {
        if (res.code === 200) {
          ElMessage.success("BOM保存成功");
          bomDataValue.value.isEdit = false;
          // é‡æ–°åŠ è½½æ•°æ®ä»¥èŽ·å–æœ€æ–°çŠ¶æ€
          getRouteInfo();
        } else {
          ElMessage.error("BOM保存失败:" + (res.msg || "未知错误"));
        }
      })
      .catch(err => {
        console.error("保存BOM数据失败:", err);
        ElMessage.error("BOM保存失败:网络错误");
      });
  };
  // å–消BOM编辑
  const cancelBomEdit = () => {
    bomDataValue.value.isEdit = false;
    getRouteInfo();
  };
  // åˆå§‹åŒ–拖拽排序
@@ -1113,3 +1514,11 @@
    word-break: break-all;
  }
</style>
<style scoped>
  .hover-effect:hover {
    border-color: #409eff;
    background-color: #ecf5ff;
    transform: translateY(-2px);
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  }
</style>
src/views/productionManagement/productStructure/Detail/index.vue
@@ -42,7 +42,6 @@
                                style="margin: 0">
                    <el-select v-model="row.model"
                               placeholder="请选择规格"
                               clearable
                               :disabled="!dataValue.isEdit"
                               style="width: 100%"
                               @visible-change="(v) => { if (v) openDialog(row.tempId) }">
@@ -81,7 +80,6 @@
                                style="margin: 0">
                    <el-input-number v-model="row.unitQuantity"
                                     :min="0"
                                     :precision="2"
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
src/views/productionManagement/productionOrder/index.vue
@@ -3,12 +3,43 @@
    <div class="search_form">
      <el-form :model="searchForm"
               :inline="true">
        <el-form-item label="订单号:">
          <el-input v-model="searchForm.npsNo"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 160px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="产品名称:">
          <el-input v-model="searchForm.productName"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 160px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="产品类型:">
          <el-select v-model="searchForm.strength"
                     style="width: 200px;"
                     placeholder="请选择产品类型"
                     clearable
                     @change="handleQuery">
            <el-option v-for="option in productTypeOptions2"
                       :key="option.dictLabel"
                       :label="option.dictLabel"
                       :value="option.dictLabel" />
          </el-select>
        </el-form-item>
        <el-form-item label="创建时间:">
          <el-date-picker v-model="createTime"
                          type="daterange"
                          range-separator="至"
                          start-placeholder="开始日期"
                          value-format="YYYY-MM-DD"
                          format="YYYY-MM-DD"
                          end-placeholder="结束日期"
                          style="width: 300px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="规格:">
@@ -35,13 +66,13 @@
                     @click="handleQuery">搜索</el-button>
          <el-button type="primary"
                     @click="handleReset">重置</el-button>
        </el-form-item>
      </el-form>
      <div>
        <el-button type="danger"
                   @click="handleDelete">退回</el-button>
        <el-button @click="handleOut">导出</el-button>
      </div>
        </el-form-item>
      </el-form>
      <!-- <div style="width:350px;text-align:right;">
      </div> -->
    </div>
    <div class="table_list">
      <PIMTable rowKey="id"
@@ -58,6 +89,12 @@
          <el-progress :percentage="toProgressPercentage(row?.completionStatus)"
                       :color="progressColor(toProgressPercentage(row?.completionStatus))"
                       :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''" />
        </template>
        <template #quantity="{ row }">
          {{ row.quantity || '-' }}<span style="color:rgb(63, 95, 211)"> å—</span>
        </template>
        <template #completeQuantity="{ row }">
          {{ row.completeQuantity || '-' }}<span style="color:rgb(42, 169, 146)"> æ–¹</span>
        </template>
      </PIMTable>
    </div>
@@ -89,6 +126,127 @@
    <new-product-order v-if="isShowNewModal"
                       v-model:visible="isShowNewModal"
                       @completed="handleQuery" />
    <!-- æ¥æºæ•°æ®å¼¹çª— -->
    <el-dialog v-model="sourceDataDialogVisible"
               title="来源数据"
               width="1000px">
      <div class="applyno-summary1">
        <div class="summary-item">
          <span class="summary-label">产品名称:</span>
          <span class="summary-value">
            <el-tag type="primary">{{ sourceRowData.productName || '-' }}</el-tag>
          </span>
        </div>
        <div class="summary-item">
          <span class="summary-label">产品规格:</span>
          <span class="summary-value">{{ sourceRowData.model || '-' }}</span>
        </div>
        <div class="summary-item">
          <span class="summary-label">物料编码:</span>
          <span class="summary-value">{{ sourceRowData.materialCode || '-' }}</span>
        </div>
        <div class="summary-item">
          <span class="summary-label">强度:</span>
          <span class="summary-value">{{ sourceRowData.strength || '-' }}</span>
        </div>
      </div>
      <div class="source-data-container">
        <!-- å·¦ä¾§applyNo列表 -->
        <div class="applyno-list">
          <div class="list-header">申请单列表</div>
          <div class="list-body">
            <div v-for="(item, index) in sourceTableData"
                 :key="item.applyNo || index"
                 class="applyno-item"
                 :class="{ active: selectedApplyNo === item.applyNo }"
                 @click="selectApplyNo(item)">
              <div class="applyno-text">{{ item.applyNo }}</div>
              <div class="applyno-info">{{ item.customerName }}</div>
            </div>
          </div>
        </div>
        <!-- å³ä¾§è¯¦ç»†ä¿¡æ¯ -->
        <div class="detail-info">
          <div v-if="selectedSourceData && selectedSourceData.items && selectedSourceData.items.length > 0">
            <div v-for="item in selectedSourceData.items"
                 :key="item.id"
                 class="source-data-card">
              <!-- <div class="card-header">
                <div class="data-source-tag">
                </div>
                <div class="card-title">产品明细</div>
              </div> -->
              <div class="card-body">
                <div class="info-grid">
                  <div class="info-item">
                    <div class="info-label">数据来源</div>
                    <div class="info-value">
                      <el-tag :type="item.dataSourceType == 1 ? 'primary' : 'warning'">
                        {{ item.dataSourceType == 1 ? '钉钉同步' : '手动新增' }}
                      </el-tag>
                    </div>
                  </div>
                  <div class="info-item">
                    <div class="info-label">块数</div>
                    <div class="info-value">{{ item.quantity || '-' }}<span style="color:rgb(63, 95, 211)"> å—</span></div>
                  </div>
                  <div class="info-item">
                    <div class="info-label">方数</div>
                    <div class="info-value">{{ item.volume || '-' }}<span style="color:rgba(27, 104, 90, 0.76)"> æ–¹</span></div>
                  </div>
                  <div class="info-item">
                    <div class="info-label">下发状态</div>
                    <div class="info-value">
                      <el-tag :type="{
                        0: 'warning',
                        1: 'primary',
                        2: 'info'
                      }[item.status] || 'info'">
                        {{ item.status == 0 ? '待下发' : item.status == 1 ? '部分下发' : '已下发' }}
                      </el-tag>
                    </div>
                  </div>
                  <div class="info-item">
                    <div class="info-label">已下发方数</div>
                    <div class="info-value">{{ item.assignedQuantity ? `${item.assignedQuantity}` : 0 }}<span style="color:rgba(214, 134, 22, 0.76)"> æ–¹</span></div>
                  </div>
                  <div class="info-item">
                    <div class="info-label">尺寸</div>
                    <div class="info-value">{{ item.length || '-' }}mm Ã— {{ item.width || '-' }}mm Ã— {{ item.height || '-' }}mm</div>
                  </div>
                  <div class="info-item">
                    <div class="info-label">计划开始日期</div>
                    <div class="info-value">{{ item.startDate ? dayjs(item.startDate).format('YYYY-MM-DD') : '' }}</div>
                  </div>
                  <div class="info-item">
                    <div class="info-label">计划结束日期</div>
                    <div class="info-value">{{ item.endDate ? dayjs(item.endDate).format('YYYY-MM-DD') : '' }}</div>
                  </div>
                  <!-- <div class="info-item">
                    <div class="info-label">强度</div>
                    <div class="info-value">{{ item.strength || '' }}</div>
                  </div> -->
                </div>
                <div class="remarks-section">
                  <div class="info-item full-width">
                    <div class="info-label">备注 1</div>
                    <div class="info-value">{{ item.remarkOne || '-' }}</div>
                  </div>
                  <div class="info-item full-width">
                    <div class="info-label">备注 2</div>
                    <div class="info-value">{{ item.remarkTwo || '-' }}</div>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <div v-else
               class="empty-state">
            <el-empty :description="selectedSourceData ? '该申请单暂无数据' : '请选择一个申请单'" />
          </div>
        </div>
      </div>
    </el-dialog>
  </div>
</template>
@@ -104,6 +262,8 @@
    bindingRoute,
    listProcessBom,
    delProductOrder,
    revokeProductOrder,
    getProductOrderSource,
  } from "@/api/productionManagement/productionOrder.js";
  import { listPage } from "@/api/productionManagement/processRoute.js";
  import { listMain as getOrderProcessRouteMain } from "@/api/productionManagement/productProcessRoute.js";
@@ -117,6 +277,11 @@
  const router = useRouter();
  const isShowNewModal = ref(false);
  const sourceDataDialogVisible = ref(false);
  const sourceTableData = ref([]);
  const selectedApplyNo = ref("");
  const selectedSourceData = ref(null);
  const sourceRowData = ref(null);
  const tableColumn = ref([
    {
@@ -153,11 +318,18 @@
      label: "产品名称",
      prop: "productName",
      width: "120px",
      dataType: "tag",
    },
    {
      label: "规格",
      prop: "model",
      width: "120px",
    },
    {
      label: "强度",
      prop: "strength",
      width: "120px",
      dataType: "tag",
    },
    {
      label: "物料编码",
@@ -168,14 +340,23 @@
      label: "工艺路线编号",
      prop: "processRouteCode",
      width: "200px",
      className: "status-cell",
    },
    {
      label: "需求数量",
      prop: "quantity",
      dataType: "slot",
      align: "right",
      slot: "quantity",
      width: 120,
    },
    {
      label: "完成数量",
      prop: "completeQuantity",
      dataType: "slot",
      align: "right",
      slot: "completeQuantity",
      width: 120,
    },
    {
      dataType: "slot",
@@ -200,6 +381,12 @@
      label: "交付日期",
      prop: "planCompleteTime",
      formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
      width: 120,
    },
    {
      label: "创建时间",
      prop: "createTime",
      formatData: val => (val ? dayjs(val).format("YYYY-MM-DD HH:mm:ss") : ""),
      width: 120,
    },
@@ -259,6 +446,156 @@
  });
  const selectedRows = ref([]);
  // æ¥æºæ•°æ®å¼¹çª—相关
  const sourceTableColumn = ref([
    {
      label: "数据来源",
      width: "100px",
      prop: "dataSourceType",
      dataType: "tag",
      formatType: params => {
        const typeMap = {
          2: "warning",
          1: "primary",
        };
        return typeMap[params] || "info";
      },
      formatData: cell => (cell == 1 ? "钉钉同步" : "手动新增"),
    },
    {
      label: "申请单编号",
      prop: "applyNo",
      width: "150px",
    },
    {
      label: "客户名称",
      prop: "customerName",
      width: "150px",
    },
    {
      label: "产品名称",
      prop: "productName",
      width: "200px",
      dataType: "tag",
      formatType: params => {
        return "primary";
      },
    },
    {
      label: "产品规格",
      prop: "model",
      width: "150px",
      className: "spec-cell",
    },
    {
      label: "物料编码",
      prop: "materialCode",
      width: "150px",
    },
    {
      label: "块数",
      prop: "quantity",
      align: "right",
      dataType: "slot",
      slot: "quantity",
    },
    {
      label: "方数",
      prop: "volume",
      width: "150px",
      align: "right",
      dataType: "slot",
      slot: "volume",
      className: "volume-cell",
    },
    {
      label: "下发状态",
      prop: "status",
      width: "150px",
      className: "status-cell",
      dataType: "tag",
      formatType: params => {
        const typeMap = {
          0: "warning",
          1: "primary",
          2: "info",
        };
        return typeMap[params] || "info";
      },
      formatData: cell => {
        const statusMap = {
          0: "待下发",
          1: "部分下发",
          2: "已下发",
        };
        return statusMap[cell] || "";
      },
    },
    {
      label: "已下发方数",
      prop: "assignedQuantity",
      width: "150px",
      className: "spec-cell",
      formatData: cell => (cell ? `${cell}方` : 0),
    },
    {
      label: "长",
      prop: "length",
      className: "dimension-cell",
      formatData: cell => (cell ? `${cell}mm` : ""),
    },
    {
      label: "宽",
      prop: "width",
      className: "dimension-cell",
      formatData: cell => (cell ? `${cell}mm` : ""),
    },
    {
      label: "高",
      prop: "height",
      className: "dimension-cell",
      formatData: cell => (cell ? `${cell}mm` : ""),
    },
    {
      label: "计划开始日期",
      prop: "startDate",
      width: "150px",
      className: "date-cell",
      formatData: cell => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
    },
    {
      label: "计划结束日期",
      prop: "endDate",
      width: "150px",
      className: "date-cell",
      formatData: cell => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
    },
    {
      label: "强度",
      prop: "strength",
      formatData: cell => {
        if (!cell) return "";
        return cell;
      },
    },
    {
      label: "备注 1",
      width: "150px",
      prop: "remarkOne",
    },
    {
      label: "备注 2",
      width: "150px",
      prop: "remarkTwo",
    },
  ]);
  const sourceTableLoading = ref(false);
  const sourcePage = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
  const data = reactive({
    searchForm: {
      customerName: "",
@@ -266,6 +603,11 @@
      projectName: "",
      productName: "",
      model: "",
      dictCode: null,
      startTime: null,
      endTime: null,
      strength: null,
      status: "",
    },
  });
  const { searchForm } = toRefs(data);
@@ -388,7 +730,11 @@
      productName: "",
      model: "",
      status: "",
      strength: null,
      startTime: null,
      endTime: null,
    };
    createTime.value = [];
    handleQuery();
  };
  // æŸ¥è¯¢åˆ—表
@@ -412,11 +758,25 @@
    }
    handleQuery();
  };
  const createTime = ref([]);
  const getList = () => {
    tableLoading.value = true;
    // æž„造一个新的对象,不包含entryDate字段
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined;
    params.startTime =
      createTime.value.length > 0
        ? dayjs(createTime.value[0]).format("YYYY-MM-DD HH:mm:ss")
        : undefined;
    params.endTime =
      createTime.value.length > 0
        ? dayjs(createTime.value[1])
            .hour(23)
            .minute(59)
            .second(59)
            .format("YYYY-MM-DD HH:mm:ss")
        : undefined;
    productOrderListPage(params)
      .then(res => {
        tableLoading.value = false;
@@ -474,21 +834,66 @@
    });
  };
  // é€‰æ‹©ç”³è¯·å•
  const selectApplyNo = item => {
    selectedApplyNo.value = item.applyNo;
    selectedSourceData.value = item;
  };
  // æŸ¥çœ‹æ¥æºç”Ÿäº§è®¡åˆ’数据
  const showSourceData = row => {
    // è¿™é‡Œéœ€è¦æ ¹æ®å®žé™…çš„API和路由进行调整
    // å‡è®¾ç”Ÿäº§è®¢å•中有生产计划ID字段,比如productionPlanId
    if (row.productionPlanId) {
      // è·³è½¬åˆ°ç”Ÿäº§è®¡åˆ’详情页面
      router.push({
        path: "/productionManagement/productionPlan",
        query: {
          id: row.productionPlanId,
        },
    // å­˜å‚¨ç‚¹å‡»æ¥æºæŒ‰é’®æ—¶ä¼ é€’çš„row参数
    sourceRowData.value = row;
    // è°ƒç”¨API获取来源数据
    getProductOrderSource(row.id)
      .then(res => {
        if (res.code === 200) {
          // å¤„理接口返回的数据,调整为我们需要的格式
          sourceTableData.value = res.data.map(item => {
            return {
              applyNo: item.applyNo,
              customerName: item.productPlans[0]?.customerName || "",
              items: item.productPlans.map(plan => {
                return {
                  id: plan.id,
                  dataSourceType: plan.dataSourceType,
                  productName: plan.productName,
                  model: plan.model,
                  materialCode: plan.materialCode,
                  quantity: plan.quantity,
                  volume: plan.volume,
                  status: plan.status,
                  assignedQuantity: plan.assignedQuantity,
                  length: plan.length,
                  width: plan.width,
                  height: plan.height,
                  startDate: plan.startDate,
                  endDate: plan.endDate,
                  strength: plan.strength,
                  remarkOne: plan.remarkOne,
                  remarkTwo: plan.remarkTwo,
                };
              }),
            };
      });
          sourcePage.total = sourceTableData.value.length;
          // é»˜è®¤é€‰æ‹©ç¬¬ä¸€ä¸ªç”³è¯·å•
          if (sourceTableData.value.length > 0) {
            selectApplyNo(sourceTableData.value[0]);
    } else {
      proxy.$modal.msgWarning("当前订单没有关联的生产计划");
            selectedApplyNo.value = "";
            selectedSourceData.value = null;
    }
          // æ‰“开弹窗
          sourceDataDialogVisible.value = true;
        } else {
          proxy.$modal.msgError(res.msg || "获取来源数据失败");
        }
      })
      .catch(err => {
        proxy.$modal.msgError("获取来源数据失败");
        console.error(err);
      });
  };
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
@@ -496,13 +901,13 @@
    selectedRows.value = selection;
  };
  const handleDeleteSolo = row => {
    ElMessageBox.confirm("选中的内容将被退回,是否确认退回?", "导出", {
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        delProductOrder([row.id]).then(res => {
        delProductOrder(row.id).then(res => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
@@ -526,8 +931,8 @@
      type: "warning",
    })
      .then(() => {
        delProductOrder(ids).then(res => {
          proxy.$modal.msgSuccess("删除成功");
        revokeProductOrder(ids).then(res => {
          proxy.$modal.msgSuccess("退回成功");
          getList();
        });
      })
@@ -556,8 +961,22 @@
  };
  const handleConfirmRoute = () => {};
  const productTypeOptions2 = ref([]);
  // èŽ·å–äº§å“ç±»åž‹å­—å…¸
  const getProductTypeOptions = () => {
    getDicts("block_strength")
      .then(res => {
        if (res.code === 200) {
          productTypeOptions2.value = res.data;
        }
      })
      .catch(err => {
        console.error("获取产品类型字典失败:", err);
      });
  };
  onMounted(() => {
    getProductTypeOptions();
    getList();
  });
</script>
@@ -583,3 +1002,233 @@
    background-color: #f4defa;
  }
</style>
<style lang="scss">
  .status-cell {
    font-weight: 600;
    color: #409eff;
    font-family: "Courier New", monospace;
    text-shadow: 0 1px 2px rgba(64, 158, 255, 0.2);
  }
  .source-data-container {
    display: flex;
    gap: 20px;
    height: 500px;
    .applyno-list {
      width: 250px;
      background: #fff;
      border-radius: 8px;
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
      overflow: hidden;
      .list-header {
        padding: 12px 16px;
        background: #f5f7fa;
        border-bottom: 1px solid #e4e7ed;
        font-weight: 600;
        color: #303133;
      }
      .list-body {
        height: calc(100% - 48px);
        overflow-y: auto;
        .applyno-item {
          padding: 12px 16px;
          border-bottom: 1px solid #f0f2f5;
          cursor: pointer;
          transition: all 0.3s;
          &:hover {
            background: #f5f7fa;
          }
          &.active {
            background: #ecf5ff;
            border-left: 4px solid #409eff;
          }
          .applyno-text {
            font-weight: 600;
            color: #303133;
            font-family: "Courier New", monospace;
            margin-bottom: 4px;
          }
          .applyno-info {
            font-size: 12px;
            color: #909399;
          }
        }
      }
    }
    .detail-info {
      flex: 1;
      background: #fff;
      border-radius: 8px;
      // box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
      overflow: auto;
      display: flex;
      flex-direction: column;
      .applyno-summary {
        padding: 16px 20px;
        background: #f5f7fa;
        border-bottom: 1px solid #e4e7ed;
        display: flex;
        flex-wrap: wrap;
        gap: 16px;
        .summary-item {
          display: flex;
          align-items: center;
          .summary-label {
            font-size: 13px;
            color: #909399;
            margin-right: 8px;
            font-weight: 500;
          }
          .summary-value {
            font-size: 14px;
            color: #303133;
            font-weight: 500;
          }
        }
      }
      .empty-state {
        flex: 1;
        display: flex;
        align-items: center;
        justify-content: center;
      }
      .source-data-card {
        flex: 1;
        display: flex;
        flex-direction: column;
        overflow: hidden;
        margin-top: 20px;
        margin-right: 20px;
        box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
        .card-header {
          display: flex;
          justify-content: space-between;
          align-items: center;
          padding: 16px 20px;
          background: #f5f7fa;
          border-bottom: 1px solid #e4e7ed;
          .data-source-tag {
            flex-shrink: 0;
          }
          .card-title {
            font-weight: 600;
            color: #303133;
            font-size: 14px;
          }
        }
        .card-body {
          flex: 1;
          padding: 20px;
          overflow-y: auto;
          background-color: #f5f7fa;
          .info-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 16px;
            margin-bottom: 20px;
            .info-item {
              display: flex;
              flex-direction: column;
              .info-label {
                font-size: 12px;
                color: #909399;
                margin-bottom: 4px;
                font-weight: 500;
              }
              .info-value {
                font-size: 14px;
                color: #303133;
                font-weight: 500;
              }
            }
            .info-item.full-width {
              grid-column: 1 / -1;
            }
          }
          .remarks-section {
            display: flex;
            // flex-direction: column;
            gap: 12px;
            border-top: 1px solid #e4e7ed;
            padding-top: 16px;
            .info-item {
              display: flex;
              width: 50%;
              flex-direction: column;
              .info-label {
                font-size: 12px;
                color: #909399;
                margin-bottom: 4px;
                font-weight: 500;
              }
              .info-value {
                font-size: 14px;
                color: #303133;
                line-height: 1.5;
                padding: 8px;
                background: #f9fafc;
                border-radius: 4px;
                border: 1px solid #ecf5ff;
              }
            }
          }
        }
      }
    }
  }
  .applyno-summary1 {
    padding: 16px 20px;
    background: #f5f7fa;
    border-bottom: 1px solid #e4e7ed;
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    .summary-item {
      display: flex;
      align-items: center;
      margin-right: 20px;
      .summary-label {
        font-size: 13px;
        color: #909399;
        margin-right: 8px;
        font-weight: 500;
      }
      .summary-value {
        font-size: 14px;
        color: #303133;
        font-weight: 500;
      }
    }
  }
</style>
src/views/productionManagement/productionReporting/components/ReportingDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,613 @@
<template>
  <el-dialog v-model="localVisible"
             :title="dialogTitle"
             width="800px"
             @close="handleClose">
    <!-- æ­¥éª¤æ¡ -->
    <el-steps :active="activeStep"
              finish-status="success"
              v-if="!props.data.id">
      <el-step title="选择生产订单" />
      <el-step title="填写基础信息" />
      <el-step title="查看工序参数" />
      <el-step title="填写产量信息" />
    </el-steps>
    <!-- ç¬¬ä¸€æ­¥ï¼šé€‰æ‹©ç”Ÿäº§è®¢å• -->
    <div v-if="activeStep === 0">
      <el-form :model="form"
               ref="formRef"
               label-width="120px">
        <el-form-item label="生产订单"
                      prop="orderId"
                      required>
          <el-select v-model="orderId"
                     placeholder="请选择生产订单"
                     clearable
                     filterable
                     style="width: 100%"
                     :loading="orderLoading"
                     @change="handleOrderChange">
            <el-option v-for="order in orderList"
                       :key="order.id"
                       :label="`${order.npsNo} - ${order.productName} ${order.model}`"
                       :value="order.id" />
          </el-select>
        </el-form-item>
      </el-form>
    </div>
    <!-- ç¬¬äºŒæ­¥ï¼šå¡«å†™åŸºç¡€ä¿¡æ¯ -->
    <div v-else-if="activeStep === 1">
      <el-form :model="form"
               :rules="rules"
               ref="formRef"
               label-width="120px">
        <el-form-item label="生产订单号"
                      prop="npsNo">
          <el-input disabled
                    v-model="form.npsNo" />
        </el-form-item>
        <el-form-item label="班组"
                      prop="teamName"
                      required>
          <el-select v-model="form.teamName"
                     placeholder="请选择班组"
                     style="width: 100%">
            <el-option label="白班"
                       value="白班" />
            <el-option label="夜班"
                       value="夜班" />
          </el-select>
        </el-form-item>
        <el-form-item label="产品编码"
                      prop="materialCode">
          <el-input disabled
                    v-model="form.materialCode" />
        </el-form-item>
        <el-form-item label="产品名称"
                      prop="productName">
          <el-input disabled
                    v-model="form.productName" />
        </el-form-item>
        <el-form-item label="规格"
                      prop="specification">
          <el-input disabled
                    v-model="form.specification" />
        </el-form-item>
        <el-form-item label="创建人"
                      prop="createBy"
                      required>
          <el-input v-model="form.createBy"
                    placeholder="请输入创建人" />
        </el-form-item>
        <el-form-item label="创建时间"
                      prop="createTime">
          <el-date-picker disabled
                          v-model="form.createTime"
                          type="datetime"
                          placeholder="请选择创建时间"
                          style="width: 100%" />
        </el-form-item>
      </el-form>
    </div>
    <!-- ç¬¬ä¸‰æ­¥ï¼šæŸ¥çœ‹å·¥åºå‚æ•° -->
    <div v-else-if="activeStep === 2">
      <!-- å·¥åºTab页 -->
      <el-tabs v-model="activeProcessId"
               @tab-click="handleTabClick">
        <el-tab-pane v-for="process in processList"
                     :key="process.id"
                     :label="process.processName"
                     :name="process.id + ''">
          <el-form :model="form"
                   ref="formRef"
                   label-width="120px">
            <!-- åŠ¨æ€å‚æ•° -->
            <el-form-item v-for="param in params"
                          :key="param.id"
                          :label="param.paramName">
              <template v-if="param.paramType == '1'">
                <!-- æ•°å­—类型 -->
                <el-input v-model="form.params[param.id]" />
              </template>
              <template v-else-if="param.paramType == '2'">
                <!-- æ–‡æœ¬ç±»åž‹ -->
                <el-input v-model="form.params[param.id]" />
              </template>
              <template v-else-if="param.paramType == '3'">
                <!-- å­—典类型 -->
                <el-select v-model="form.params[param.id]"
                           placeholder="请选择"
                           style="width: 100%">
                  <el-option v-for="option in dictOptions[param.paramFormat] || []"
                             :key="option.dictValue"
                             :label="option.dictLabel"
                             :value="option.dictValue" />
                </el-select>
              </template>
              <template v-else-if="param.paramType == '4'">
                <!-- æ—¥æœŸç±»åž‹ -->
                <el-date-picker v-model="form.params[param.id]" />
              </template>
              <template v-else>
                <!-- å…¶ä»–类型 -->
                <el-input v-model="form.params[param.id]" />
              </template>
            </el-form-item>
          </el-form>
        </el-tab-pane>
      </el-tabs>
    </div>
    <!-- ç¬¬å››æ­¥ï¼šå¡«å†™äº§é‡ä¿¡æ¯ -->
    <div v-else-if="activeStep === 3">
      <el-form :model="form"
               :rules="rules"
               ref="formRef"
               label-width="120px">
        <el-form-item label="产出方量"
                      prop="outputVolume"
                      required>
          <el-input-number v-model="form.outputVolume"
                           :min="0"
                           :precision="2"
                           style="width: 100%" />
        </el-form-item>
        <el-form-item label="不合格方量"
                      prop="unqualifiedVolume"
                      required>
          <el-input-number v-model="form.unqualifiedVolume"
                           :min="0"
                           :precision="2"
                           style="width: 100%" />
        </el-form-item>
        <el-form-item label="完成方量"
                      prop="completedVolume"
                      required>
          <el-input-number v-model="form.completedVolume"
                           :min="0"
                           :precision="2"
                           style="width: 100%" />
        </el-form-item>
      </el-form>
    </div>
    <!-- ç¼–辑模式:直接显示所有信息 -->
    <div v-if="props.data.id">
      <el-form :model="form"
               :rules="rules"
               ref="formRef"
               label-width="120px">
        <el-form-item label="生产订单号"
                      prop="npsNo"
                      disabled>
          <el-input v-model="form.npsNo" />
        </el-form-item>
        <el-form-item label="班组"
                      prop="teamName">
          <el-select v-model="form.teamName"
                     placeholder="请选择班组"
                     style="width: 100%">
            <el-option label="白班"
                       value="白班" />
            <el-option label="夜班"
                       value="夜班" />
          </el-select>
        </el-form-item>
        <el-form-item label="产品编码"
                      prop="materialCode"
                      disabled>
          <el-input v-model="form.materialCode" />
        </el-form-item>
        <el-form-item label="产品名称"
                      prop="productName"
                      disabled>
          <el-input v-model="form.productName" />
        </el-form-item>
        <el-form-item label="规格"
                      prop="specification"
                      disabled>
          <el-input v-model="form.specification" />
        </el-form-item>
        <el-form-item label="创建人"
                      prop="createBy">
          <el-input v-model="form.createBy"
                    placeholder="请输入创建人" />
        </el-form-item>
        <el-form-item label="创建时间"
                      prop="createTime"
                      disabled>
          <el-date-picker v-model="form.createTime"
                          type="datetime"
                          placeholder="请选择创建时间"
                          style="width: 100%" />
        </el-form-item>
        <el-form-item label="产出方量"
                      prop="outputVolume">
          <el-input-number v-model="form.outputVolume"
                           :min="0"
                           :precision="2"
                           style="width: 100%" />
        </el-form-item>
        <el-form-item label="不合格方量"
                      prop="unqualifiedVolume">
          <el-input-number v-model="form.unqualifiedVolume"
                           :min="0"
                           :precision="2"
                           style="width: 100%" />
        </el-form-item>
        <el-form-item label="完成方量"
                      prop="completedVolume">
          <el-input-number v-model="form.completedVolume"
                           :min="0"
                           :precision="2"
                           style="width: 100%" />
        </el-form-item>
      </el-form>
      <!-- å·¥åºTab页 -->
      <el-tabs v-model="activeProcessId"
               @tab-click="handleTabClick">
        <el-tab-pane v-for="process in processList"
                     :key="process.id"
                     :label="process.processName"
                     :name="process.id + ''">
          <el-form :model="form"
                   :rules="rules"
                   ref="formRef"
                   label-width="120px">
            <!-- åŠ¨æ€å‚æ•° -->
            <el-form-item v-for="param in params"
                          :key="param.id"
                          :label="param.paramName"
                          :prop="`params.${param.id}`"
                          :rules="param.isRequired ? [{ required: true, message: `请输入${param.paramName}`, trigger: 'blur' }] : []">
              <template v-if="param.paramType == '1'">
                <!-- æ•°å­—类型 -->
                <el-input-number v-model="form.params[param.id]"
                                 :min="0"
                                 :precision="getPrecision(param.paramFormat)"
                                 style="width: 100%" />
              </template>
              <template v-else-if="param.paramType == '2'">
                <!-- æ–‡æœ¬ç±»åž‹ -->
                <el-input v-model="form.params[param.id]"
                          :placeholder="`请输入${param.paramName}`" />
              </template>
              <template v-else-if="param.paramType == '3'">
                <!-- å­—典类型 -->
                <el-select v-model="form.params[param.id]"
                           placeholder="请选择"
                           style="width: 100%">
                  <el-option v-for="option in dictOptions[param.paramFormat] || []"
                             :key="option.dictValue"
                             :label="option.dictLabel"
                             :value="option.dictValue" />
                </el-select>
              </template>
              <template v-else-if="param.paramType == '4'">
                <!-- æ—¥æœŸç±»åž‹ -->
                <el-date-picker v-model="form.params[param.id]"
                                type="datetime"
                                placeholder="请选择日期时间"
                                style="width: 100%" />
              </template>
              <template v-else>
                <!-- å…¶ä»–类型 -->
                <el-input v-model="form.params[param.id]"
                          :placeholder="`请输入${param.paramName}`" />
              </template>
            </el-form-item>
          </el-form>
        </el-tab-pane>
      </el-tabs>
    </div>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="handleClose">取 æ¶ˆ</el-button>
        <el-button type="primary"
                   v-if="activeStep > 0"
                   @click="activeStep--">上一步</el-button>
        <el-button type="primary"
                   v-if="activeStep < 3 && !props.data.id"
                   @click="handleNextStep">下一步</el-button>
        <el-button type="primary"
                   v-if="activeStep === 3 || props.data.id"
                   :loading="submitLoading"
                   @click="handleSubmit">ç¡® è®¤</el-button>
      </span>
    </template>
  </el-dialog>
</template>
<script setup>
  import { ref, reactive, computed, watch } from "vue";
  import { ElMessage } from "element-plus";
  import { getDicts } from "@/api/system/dict/data";
  import { productOrderListPage } from "@/api/productionManagement/productionOrder.js";
  import {
    findProductProcessRouteItemList,
    findProcessParamListOrder,
  } from "@/api/productionManagement/productProcessRoute.js";
  const props = defineProps({
    visible: {
      type: Boolean,
      default: false,
    },
    data: {
      type: Object,
      default: () => ({}),
    },
  });
  const emit = defineEmits(["update:visible", "completed"]);
  const dialogTitle = computed(() => (props.data.id ? "编辑报工" : "新增报工"));
  const formRef = ref(null);
  const submitLoading = ref(false);
  const orderLoading = ref(false);
  const processLoading = ref(false);
  const localVisible = ref(props.visible);
  const activeStep = ref(0);
  const orderId = ref(props.data.orderId || "");
  const processId = ref(props.data.processId || "");
  const activeProcessId = ref("");
  const orderList = ref([]);
  const processList = ref([]);
  const params = ref([]);
  const dictOptions = ref({});
  const form = reactive({
    id: props.data.id || undefined,
    orderId: props.data.orderId || "",
    npsNo: props.data.npsNo || "",
    teamName: props.data.teamName || "",
    materialCode: props.data.materialCode || "",
    productName: props.data.productName || "",
    specification: props.data.specification || "",
    outputVolume: props.data.outputVolume || 0,
    unqualifiedVolume: props.data.unqualifiedVolume || 0,
    completedVolume: props.data.completedVolume || 0,
    createBy: props.data.createBy || "当前登录人",
    createTime: props.data.createTime || new Date(),
    params: props.data.params || {},
  });
  const rules = {
    teamName: [{ required: true, message: "请选择班组", trigger: "blur" }],
    outputVolume: [
      { required: true, message: "请输入产出方量", trigger: "blur" },
    ],
    unqualifiedVolume: [
      { required: true, message: "请输入不合格方量", trigger: "blur" },
    ],
    completedVolume: [
      { required: true, message: "请输入完成方量", trigger: "blur" },
    ],
    createBy: [{ required: true, message: "请输入创建人", trigger: "blur" }],
  };
  // åŠ è½½ç”Ÿäº§è®¢å•åˆ—è¡¨
  const loadOrders = () => {
    orderLoading.value = true;
    productOrderListPage({ pageNum: 1, pageSize: 100 })
      .then(res => {
        orderList.value = res.data.records || [];
      })
      .finally(() => {
        orderLoading.value = false;
      });
  };
  // å¤„理生产订单选择
  const handleOrderChange = val => {
    if (val) {
      const order = orderList.value.find(item => item.id === val);
      if (order) {
        form.orderId = val;
        form.npsNo = order.npsNo;
        form.materialCode = order.materialCode;
        form.productName = order.productName;
        form.specification = order.model;
      }
      // åŠ è½½å·¥åºåˆ—è¡¨
      loadProcesses(val);
    } else {
      form.orderId = "";
      form.npsNo = "";
      form.materialCode = "";
      form.productName = "";
      form.specification = "";
      processId.value = "";
      activeProcessId.value = "";
      processList.value = [];
      params.value = [];
      form.params = {};
    }
  };
  // åŠ è½½å·¥åºåˆ—è¡¨
  const loadProcesses = orderId => {
    processLoading.value = true;
    findProductProcessRouteItemList({ orderId })
      .then(res => {
        processList.value = res.data || [];
        // å¦‚果有工序,默认选择第一个
        if (processList.value.length > 0) {
          const firstProcess = processList.value[0];
          activeProcessId.value = firstProcess.id + "";
          processId.value = firstProcess.id;
          form.processId = firstProcess.id;
          // åŠ è½½ç¬¬ä¸€ä¸ªå·¥åºçš„å‚æ•°
          loadParams(firstProcess.id, orderId);
        }
      })
      .finally(() => {
        processLoading.value = false;
      });
  };
  // å¤„理tab页点击
  const handleTabClick = tab => {
    const selectedProcessId = parseInt(tab.paneName);
    processId.value = selectedProcessId;
    form.processId = selectedProcessId;
    // åŠ è½½å‚æ•°åˆ—è¡¨
    loadParams(selectedProcessId, form.orderId);
  };
  // èŽ·å–å­—å…¸æ•°æ®
  const getDictOptions = async dictType => {
    if (!dictType) return [];
    if (dictOptions.value[dictType]) return dictOptions.value[dictType];
    try {
      const res = await getDicts(dictType);
      if (res.code === 200) {
        dictOptions.value[dictType] = res.data;
        return res.data;
      }
      return [];
    } catch (error) {
      console.error("获取字典数据失败:", error);
      return [];
    }
  };
  // åŠ è½½å‚æ•°åˆ—è¡¨
  const loadParams = (processId, orderId) => {
    findProcessParamListOrder({ orderId, routeItemId: processId }).then(
      async res => {
        params.value = res.data || [];
        // åˆå§‹åŒ–参数值并获取字典数据
        for (const param of params.value) {
          if (!form.params[param.id]) {
            form.params[param.id] = param.standardValue || "";
          }
          // å¦‚果是字典类型参数,获取字典数据
          if (param.paramType == "3" && param.paramFormat) {
            await getDictOptions(param.paramFormat);
          }
        }
      }
    );
  };
  // èŽ·å–å°æ•°ç²¾åº¦
  const getPrecision = format => {
    if (!format) return 2;
    const match = format.match(/\.(\d+)/);
    return match ? parseInt(match[1].length) : 2;
  };
  // å¤„理下一步
  const handleNextStep = () => {
    if (activeStep.value === 0) {
      // ç¬¬ä¸€æ­¥ï¼šéªŒè¯ç”Ÿäº§è®¢å•选择
      if (!orderId.value) {
        ElMessage.error("请选择生产订单");
        return;
      }
      activeStep.value = 1;
    } else if (activeStep.value === 1) {
      // ç¬¬äºŒæ­¥ï¼šéªŒè¯åŸºç¡€ä¿¡æ¯
      formRef.value.validate(valid => {
        if (valid) {
          activeStep.value = 2;
        }
      });
    } else if (activeStep.value === 2) {
      // ç¬¬ä¸‰æ­¥ï¼šç›´æŽ¥è¿›å…¥ç¬¬å››æ­¥
      activeStep.value = 3;
    }
  };
  // å¤„理提交
  const handleSubmit = () => {
    formRef.value.validate(valid => {
      if (valid) {
        submitLoading.value = true;
        // è¿™é‡Œå¯ä»¥è°ƒç”¨API进行提交
        setTimeout(() => {
          ElMessage.success(props.data.id ? "修改成功" : "新增成功");
          localVisible.value = false;
          emit("completed");
          submitLoading.value = false;
        }, 1000);
      }
    });
  };
  // å¤„理关闭
  const handleClose = () => {
    // é‡ç½®åˆ°åˆå§‹çŠ¶æ€
    activeStep.value = 0;
    orderId.value = "";
    processId.value = "";
    activeProcessId.value = "";
    processList.value = [];
    params.value = [];
    Object.assign(form, {
      id: undefined,
      orderId: "",
      npsNo: "",
      teamName: "",
      materialCode: "",
      productName: "",
      specification: "",
      outputVolume: 0,
      unqualifiedVolume: 0,
      completedVolume: 0,
      createBy: "当前登录人",
      createTime: new Date(),
      params: {},
    });
    localVisible.value = false;
  };
  // åˆå§‹åŒ–
  const init = () => {
    if (!props.data.id) {
      // æ–°å¢žæ—¶åŠ è½½è®¢å•åˆ—è¡¨
      loadOrders();
      // è®¾ç½®é»˜è®¤åˆ›å»ºäººå’Œåˆ›å»ºæ—¶é—´
      form.createBy = "当前登录人";
      form.createTime = new Date();
    } else {
      // ç¼–辑时加载相关数据
      if (props.data.orderId) {
        orderId.value = props.data.orderId;
        if (props.data.processId) {
          processId.value = props.data.processId;
          activeProcessId.value = props.data.processId + "";
          loadParams(props.data.processId, props.data.orderId);
        } else {
          loadProcesses(props.data.orderId);
        }
      } else {
        loadOrders();
      }
    }
  };
  // ç›‘听props.visible变化
  watch(
    () => props.visible,
    newVal => {
      localVisible.value = newVal;
      if (newVal) {
        init();
      }
    }
  );
  // ç›‘听localVisible变化
  watch(localVisible, newVal => {
    emit("update:visible", newVal);
  });
</script>
<style scoped>
  .dialog-footer {
    text-align: right;
  }
</style>
src/views/productionManagement/productionReporting/index.vue
@@ -3,418 +3,544 @@
    <div class="search_form">
      <el-form :model="searchForm"
               :inline="true">
        <el-form-item label="报工人员名称:">
          <el-input v-model="searchForm.nickName"
        <el-form-item label="生产订单号:">
          <el-input v-model="searchForm.orderNo"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 200px;"
                    @change="handleQuery" />
                    style="width: 160px;"
                    @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item label="工单号:">
          <el-input v-model="searchForm.workOrderNo"
        <el-form-item label="班组:">
          <el-input v-model="searchForm.teamName"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 200px;"
                    @change="handleQuery" />
                    style="width: 160px;"
                    @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item label="产品名称:">
          <el-input v-model="searchForm.productName"
                    placeholder="请输入"
                    clearable
                    style="width: 160px;"
                    @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
          <el-button type="primary"
                     @click="handleReset">重置</el-button>
        </el-form-item>
      </el-form>
      <div>
        <el-button type="primary"
                   @click="handleAdd">新增</el-button>
        <el-button @click="handleExport">导出</el-button>
      </div>
    </div>
    <div class="table_list">
      <div style="text-align: right"
           class="mb10">
        <!-- <el-button type="primary"
                   @click="openForm('add')">生产报工</el-button> -->
        <el-button @click="handleOut">导出</el-button>
      </div>
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                :expandRowKeys="expandedRowKeys"
                @expand-change="expandChange"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
                :total="page.total">
        <template #expand="{ row }">
          <el-table :data="expandData"
                    border
                    show-summary
                    :summary-method="summarizeMainTable"
                    v-loading="childrenLoading">
            <el-table-column align="center"
                             label="序号"
                             type="index"
                             width="60" />
            <el-table-column label="本次生产数量"
                             prop="finishedNum"
                             align="center"
                             width="400">
              <template #default="scope">
                <el-input-number :step="0.01"
                                 :min="0"
                                 style="width: 100%"
                                 v-model="scope.row.finishedNum"
                                 :disabled="!scope.row.editType"
                                 :precision="2"
                                 placeholder="请输入"
                                 clearable
                                 @change="changeNum(scope.row)" />
                :isSelection="false"
                @selection-change="handleSelectionChange"
                @pagination="pagination">
        <template #outputVolume="{ row }">
          <span style="font-weight: bold;color: #409eff;">{{ row.outputVolume }}</span><span style="margin-left: 5px;color: #909399;">方</span>
              </template>
            </el-table-column>
            <!--                        <el-table-column label="待生产数量" prop="pendingNum" width="240" align="center"></el-table-column>-->
            <el-table-column label="生产人"
                             prop="schedulingUserId"
                             width="400">
              <template #default="scope">
                <el-select v-model="scope.row.schedulingUserId"
                           placeholder="选择人员"
                           :disabled="!scope.row.editType"
                           style="width: 100%;">
                  <el-option v-for="user in userList"
                             :key="user.userId"
                             :label="user.nickName"
                             :value="user.userId" />
                </el-select>
        <template #unqualifiedVolume="{ row }">
          <span style="font-weight: bold;color: #b43434;">{{ row.unqualifiedVolume }}</span><span style="margin-left: 5px;color: #909399;">方</span>
              </template>
            </el-table-column>
            <el-table-column label="生产日期"
                             prop="schedulingDate"
                             width="400">
              <template #default="scope">
                <el-date-picker v-model="scope.row.schedulingDate"
                                type="date"
                                :disabled="!scope.row.editType"
                                placeholder="请选择日期"
                                value-format="YYYY-MM-DD"
                                format="YYYY-MM-DD"
                                clearable
                                style="width: 100%" />
              </template>
            </el-table-column>
            <el-table-column label="操作"
                             >
              <template #default="scope">
                <el-button link
                           type="primary"
                           size="small"
                           @click="changeEditType(scope.row)"
                           v-if="!scope.row.editType"
                           :disabled="scope.row.parentStatus === 3">编辑</el-button>
                <el-button link
                           type="primary"
                           size="small"
                           @click="saveReceiptPayment(scope.row)"
                           v-if="scope.row.editType">保存</el-button>
              </template>
            </el-table-column>
          </el-table>
        <template #completedVolume="{ row }">
          <span style="font-weight: bold;color: #28e431;">{{ row.completedVolume }}</span><span style="margin-left: 5px;color: #909399;">方</span>
        </template>
      </PIMTable>
    </div>
    <form-dia ref="formDia"
              @close="handleQuery"></form-dia>
    <input-modal v-if="isShowInput"
                 v-model:visible="isShowInput"
                 :production-product-main-id="isShowingId" />
    <ReportingDialog v-model:visible="dialogVisible"
                     :data="form"
                     @completed="handleQuery" />
  </div>
</template>
<script setup>
  import { onMounted, ref } from "vue";
  import FormDia from "@/views/productionManagement/productionReporting/components/formDia.vue";
  import { ElMessageBox } from "element-plus";
  import { onMounted, ref, reactive, getCurrentInstance } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import dayjs from "dayjs";
  import {
    workListPage,
    productionReport,
    productionReportUpdate,
    workListPageById,
    productionReportDelete,
  } from "@/api/productionManagement/productionReporting.js";
  import { productionProductMainListPage } from "@/api/productionManagement/productionProductMain.js";
  import { userListNoPageByTenantId } from "@/api/system/user.js";
  import InputModal from "@/views/productionManagement/productionReporting/Input.vue";
  import PIMTable from "@/components/PIMTable/PIMTable.vue";
  import ReportingDialog from "./components/ReportingDialog.vue";
  const data = reactive({
    searchForm: {
      nickName: "",
      workOrderNo: "",
      workOrderStatus: "",
    },
  });
  const { searchForm } = toRefs(data);
  const expandedRowKeys = ref([]);
  const expandData = ref([]);
  const userList = ref([]);
  const { proxy } = getCurrentInstance();
  const tableColumn = ref([
    {
      label: "报工单号",
      prop: "productNo",
      width: 120,
      label: "生产订单号",
      prop: "orderNo",
      width: "150px",
    },
    {
      label: "报工人员",
      prop: "nickName",
      width: 120,
      label: "班组",
      prop: "teamName",
      width: "120px",
      dataType: "tag",
    },
    {
      label: "工序",
      prop: "process",
      width: 120,
    },
    {
      label: "工单编号",
      prop: "workOrderNo",
      width: 120,
    },
    {
      label: "销售合同号",
      prop: "salesContractNo",
      width: 120,
      label: "产品编码",
      prop: "materialCode",
      width: "150px",
    },
    {
      label: "产品名称",
      prop: "productName",
      width: 120,
      width: "150px",
    },
    {
      label: "产品规格型号",
      prop: "productModelName",
      width: 120,
      label: "规格",
      prop: "specification",
      width: "120px",
      className: "specification-cell",
    },
    {
      label: "产出数量",
      prop: "quantity",
      width: 120,
      label: "产出方量",
      prop: "outputVolume",
      width: "120px",
      align: "right",
      dataType: "slot",
      slot: "outputVolume",
    },
    {
      label: "报废数量",
      prop: "scrapQty",
      width: 120,
      label: "不合格方量",
      prop: "unqualifiedVolume",
      width: "120px",
      align: "right",
      dataType: "slot",
      slot: "unqualifiedVolume",
    },
    {
      label: "单位",
      prop: "unit",
      width: 120,
      label: "完成方量",
      prop: "completedVolume",
      width: "120px",
      align: "right",
      dataType: "slot",
      slot: "completedVolume",
    },
    {
      label: "创建人",
      prop: "createBy",
      width: "120px",
      dataType: "tag",
    },
    {
      label: "创建时间",
      prop: "createTime",
      width: 120,
      width: "160px",
      formatData: val => (val ? dayjs(val).format("YYYY-MM-DD HH:mm:ss") : ""),
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      dataType: "action",
      width: "200px",
      fixed: "right",
      operation: [
        {
          name: "查看投入",
          name: "详情",
          type: "text",
          clickFun: row => {
            showInput(row);
            handleDetail(row);
          },
        },
        {
          name: "编辑",
          type: "text",
          clickFun: row => {
            handleEdit(row);
          },
        },
        {
          name: "删除",
          type: "danger",
          type: "text",
          clickFun: row => {
            deleteReport(row);
            handleDelete(row);
          },
        },
      ],
    },
  ]);
  const tableData = ref([]);
  const selectedRows = ref([]);
  const tableLoading = ref(false);
  const childrenLoading = ref(false);
  const page = reactive({
    current: 1,
    size: 100,
    size: 10,
    total: 0,
  });
  const formDia = ref();
  const { proxy } = getCurrentInstance();
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  const searchForm = reactive({
    orderNo: "",
    teamName: "",
    productName: "",
  });
  const mockData = [
    {
      id: 1,
      orderNo: "PO202401001",
      teamName: "生产一组",
      materialCode: "PC001",
      productName: "标准砌块",
      specification: "600×240×200",
      outputVolume: 120.5,
      unqualifiedVolume: 2.3,
      completedVolume: 118.2,
      createBy: "张三",
      createTime: "2024-01-15 08:30:00",
    },
    {
      id: 2,
      orderNo: "PO202401002",
      teamName: "生产二组",
      materialCode: "PC002",
      productName: "标准砌块",
      specification: "600×240×200",
      outputVolume: 150.8,
      unqualifiedVolume: 1.5,
      completedVolume: 149.3,
      createBy: "李四",
      createTime: "2024-01-15 09:15:00",
    },
    {
      id: 3,
      orderNo: "PO202401003",
      teamName: "生产三组",
      materialCode: "PC003",
      productName: "加气砌块",
      specification: "600×240×250",
      outputVolume: 95.2,
      unqualifiedVolume: 0.8,
      completedVolume: 94.4,
      createBy: "王五",
      createTime: "2024-01-15 10:00:00",
    },
    {
      id: 4,
      orderNo: "PO202401004",
      teamName: "生产一组",
      materialCode: "PC004",
      productName: "标准砌块",
      specification: "600×240×200",
      outputVolume: 180.6,
      unqualifiedVolume: 3.2,
      completedVolume: 177.4,
      createBy: "赵六",
      createTime: "2024-01-15 14:20:00",
    },
    {
      id: 5,
      orderNo: "PO202401005",
      teamName: "生产二组",
      materialCode: "PC005",
      productName: "加气砌块",
      specification: "600×240×250",
      outputVolume: 110.3,
      unqualifiedVolume: 1.1,
      completedVolume: 109.2,
      createBy: "孙七",
      createTime: "2024-01-15 15:45:00",
    },
    {
      id: 6,
      orderNo: "PO202401006",
      teamName: "生产三组",
      materialCode: "PC006",
      productName: "标准砌块",
      specification: "600×240×200",
      outputVolume: 135.7,
      unqualifiedVolume: 2.5,
      completedVolume: 133.2,
      createBy: "周八",
      createTime: "2024-01-16 08:00:00",
    },
    {
      id: 7,
      orderNo: "PO202401007",
      teamName: "生产一组",
      materialCode: "PC007",
      productName: "加气砌块",
      specification: "600×240×250",
      outputVolume: 88.4,
      unqualifiedVolume: 0.6,
      completedVolume: 87.8,
      createBy: "吴九",
      createTime: "2024-01-16 09:30:00",
    },
    {
      id: 8,
      orderNo: "PO202401008",
      teamName: "生产二组",
      materialCode: "PC008",
      productName: "标准砌块",
      specification: "600×240×200",
      outputVolume: 165.2,
      unqualifiedVolume: 2.8,
      completedVolume: 162.4,
      createBy: "郑十",
      createTime: "2024-01-16 11:00:00",
    },
    {
      id: 9,
      orderNo: "PO202401009",
      teamName: "生产三组",
      materialCode: "PC009",
      productName: "加气砌块",
      specification: "600×240×250",
      outputVolume: 102.5,
      unqualifiedVolume: 1.3,
      completedVolume: 101.2,
      createBy: "钱十一",
      createTime: "2024-01-16 13:15:00",
    },
    {
      id: 10,
      orderNo: "PO202401010",
      teamName: "生产一组",
      materialCode: "PC010",
      productName: "标准砌块",
      specification: "600×240×200",
      outputVolume: 142.8,
      unqualifiedVolume: 2.1,
      completedVolume: 140.7,
      createBy: "刘十二",
      createTime: "2024-01-16 15:00:00",
    },
  ];
  const dialogVisible = ref(false);
  const form = reactive({
    id: undefined,
    orderId: "",
    orderNo: "",
    teamName: "",
    materialCode: "",
    productName: "",
    specification: "",
    outputVolume: 0,
    unqualifiedVolume: 0,
    completedVolume: 0,
    processId: "",
    params: {},
  });
  const selectedRows = ref([]);
  const getList = () => {
    tableLoading.value = true;
    setTimeout(() => {
      tableLoading.value = false;
      const start = (page.current - 1) * page.size;
      const end = start + page.size;
      tableData.value = mockData.slice(start, end);
      page.total = mockData.length;
    }, 500);
  };
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
  const changeDaterange = value => {
    if (value) {
      searchForm.value.entryDateStart = value[0];
      searchForm.value.entryDateEnd = value[1];
    } else {
      searchForm.value.entryDateStart = undefined;
      searchForm.value.entryDateEnd = undefined;
    }
    handleQuery();
  };
  const deleteReport = row => {
    ElMessageBox.confirm("确定删除该报工吗?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    }).then(() => {
      productionReportDelete({ id: row.id }).then(res => {
        if (res.code === 200) {
          proxy.$modal.msgSuccess("删除成功");
  const handleReset = () => {
    searchForm.orderNo = "";
    searchForm.teamName = "";
    searchForm.productName = "";
    page.current = 1;
          getList();
        } else {
          ElMessageBox.alert(res.msg || "删除失败", "提示", {
            confirmButtonText: "确定",
          });
        }
      });
    });
  };
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  };
  const getList = () => {
    tableLoading.value = true;
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined;
    expandedRowKeys.value = [];
    productionProductMainListPage(params)
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records.map(item => ({
          ...item,
          pendingFinishNum:
            (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0),
        }));
        page.total = res.data.total;
      })
      .catch(err => {
        tableLoading.value = false;
      });
  };
  // å±•开行
  const expandChange = (row, expandedRows) => {
    userListNoPageByTenantId().then(res => {
      userList.value = res.data;
    });
    if (expandedRows.length > 0) {
      nextTick(() => {
        expandedRowKeys.value = [];
        try {
          childrenLoading.value = true;
          workListPageById({ id: row.id }).then(res => {
            childrenLoading.value = false;
            const index = tableData.value.findIndex(item => item.id === row.id);
            if (index > -1) {
              expandData.value = res.data.map(item => ({
                ...item,
                pendingNum:
                  (Number(item.schedulingNum) || 0) -
                  (Number(item.finishedNum) || 0),
                parentStatus: row.status, // æ–°å¢žçˆ¶è¡¨çŠ¶æ€
              }));
            }
            expandedRowKeys.value.push(row.id);
          });
        } catch (error) {
          childrenLoading.value = false;
          console.log(error);
        }
      });
    } else {
      expandedRowKeys.value = [];
    }
  };
  const changeNum = row => {
    // æ‰¾åˆ°çˆ¶è¡¨æ ¼æ•°æ®
    const parentRow = tableData.value.find(
      item => item.id === expandedRowKeys.value[0]
    );
    // è®¡ç®—所有子表格 finishedNum çš„æ€»å’Œ
    const totalFinishedNum = expandData.value.reduce(
      (sum, item) => sum + (Number(item.finishedNum) || 0),
      0
    );
    // çˆ¶è¡¨æ ¼çš„æŽ’产数量
    const schedulingNum = parentRow ? Number(parentRow.schedulingNum) : 0;
    if (totalFinishedNum > schedulingNum) {
      // å›žé€€æœ¬æ¬¡è¾“å…¥
      row.finishedNum =
        schedulingNum - (totalFinishedNum - Number(row.finishedNum));
      proxy.$modal.msgWarning("所有本次生产数量之和不可大于排产数量");
    }
    row.pendingNum = row.schedulingNum - row.finishedNum;
  };
  // ç¼–辑修改状态
  const changeEditType = row => {
    row.editType = !row.editType;
  };
  // ä¿å­˜è®°å½•
  const saveReceiptPayment = row => {
    productionReportUpdate(row).then(res => {
      row.editType = !row.editType;
      getList();
      proxy.$modal.msgSuccess("提交成功");
    });
  };
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
  };
  const summarizeMainTable = param => {
    return proxy.summarizeTable(param, ["finishedNum"]);
  };
  // æ‰“开弹框
  const openForm = (type, row) => {
    if (selectedRows.value.length !== 1) {
      proxy.$message.error("请选择一条数据");
      return;
    }
    if (selectedRows.value[0].pendingFinishNum == 0) {
      proxy.$message.warning("无需再报工");
      return;
    }
    nextTick(() => {
      const rowInfo = type === "add" ? selectedRows.value[0] : row;
      formDia.value?.openDialog(type, rowInfo);
  const handleAdd = () => {
    Object.assign(form, {
      id: undefined,
      orderId: "",
      orderNo: "",
      teamName: "",
      materialCode: "",
      productName: "",
      specification: "",
      outputVolume: 0,
      unqualifiedVolume: 0,
      completedVolume: 0,
      processId: "",
      params: {},
    });
    dialogVisible.value = true;
  };
  // æ‰“开投入模态框
  const isShowInput = ref(false);
  const isShowingId = ref(0);
  const showInput = row => {
    isShowInput.value = true;
    isShowingId.value = row.id;
  const handleEdit = row => {
    Object.assign(form, {
      id: row.id,
      orderId: row.orderId || "",
      orderNo: row.orderNo,
      teamName: row.teamName,
      materialCode: row.materialCode,
      productName: row.productName,
      specification: row.specification,
      outputVolume: row.outputVolume,
      unqualifiedVolume: row.unqualifiedVolume,
      completedVolume: row.completedVolume,
      createBy: row.createBy || "",
      createTime: row.createTime || new Date(),
      processId: row.processId || "",
      params: row.params || {},
    });
    dialogVisible.value = true;
  };
  // å¯¼å‡º
  const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
      confirmButtonText: "确认",
  const handleDetail = row => {
    ElMessage.info("详情功能待实现");
  };
  const handleDelete = row => {
    ElMessageBox.confirm("确认删除该条数据吗?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        proxy.download("/productionProductMain/export", {}, "生产报工.xlsx");
        productionReportDelete({ id: row.id })
          .then(() => {
            ElMessage.success("删除成功");
            getList();
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
            ElMessage.error("删除失败");
      });
      })
      .catch(() => {});
  };
  const handleExport = () => {
    ElMessage.info("导出功能待实现");
  };
  onMounted(() => {
    getList();
  });
</script>
<style scoped></style>
<style scoped lang="scss">
  .app-container {
    padding: 24px;
    background-color: #f0f2f5;
    min-height: calc(100vh - 48px);
  }
  .search_form {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    flex-wrap: wrap;
    gap: 16px;
    margin-bottom: 24px;
    padding: 20px;
    background-color: #ffffff;
    border-radius: 6px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
    transition: all 0.3s ease;
    &:hover {
      box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.08);
    }
    :deep(.el-form) {
      display: flex;
      flex-wrap: wrap;
      gap: 0;
      .el-form-item {
        margin-right: 16px;
        margin-bottom: 0;
        &:last-child {
          margin-right: 0;
        }
      }
    }
    > div {
      display: flex;
      flex-wrap: wrap;
      gap: 8px;
      flex-shrink: 0;
    }
  }
  .table_list {
    background-color: #ffffff;
    border-radius: 6px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
    overflow: hidden;
    height: calc(100vh - 220px);
  }
  :deep(.el-table) {
    border: none;
    border-radius: 6px;
    overflow: hidden;
    .el-table__header-wrapper {
      background-color: #fafafa;
      th {
        background-color: #fafafa;
        font-weight: 600;
        color: #303133;
        border-bottom: 1px solid #ebeef5;
        padding: 14px 0;
      }
    }
    .el-table__body-wrapper {
      tr {
        transition: all 0.3s ease;
        &:hover {
          background-color: #f5f7fa;
        }
        td {
          border-bottom: 1px solid #ebeef5;
          padding: 12px 0;
        }
      }
      tr.current-row {
        background-color: #ecf5ff;
      }
    }
    .el-table__empty-block {
      padding: 40px 0;
    }
  }
</style>
<style lang="scss">
  .specification-cell {
    color: #7a7d81;
    font-style: italic;
  }
</style>
src/views/productionManagement/productionReporting/index2.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,420 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <el-form :model="searchForm"
               :inline="true">
        <el-form-item label="报工人员名称:">
          <el-input v-model="searchForm.nickName"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 200px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="工单号:">
          <el-input v-model="searchForm.workOrderNo"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 200px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
        </el-form-item>
      </el-form>
    </div>
    <div class="table_list">
      <div style="text-align: right"
           class="mb10">
        <!-- <el-button type="primary"
                   @click="openForm('add')">生产报工</el-button> -->
        <el-button @click="handleOut">导出</el-button>
      </div>
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                :expandRowKeys="expandedRowKeys"
                @expand-change="expandChange"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
                :total="page.total">
        <template #expand="{ row }">
          <el-table :data="expandData"
                    border
                    show-summary
                    :summary-method="summarizeMainTable"
                    v-loading="childrenLoading">
            <el-table-column align="center"
                             label="序号"
                             type="index"
                             width="60" />
            <el-table-column label="本次生产数量"
                             prop="finishedNum"
                             align="center"
                             width="400">
              <template #default="scope">
                <el-input-number :step="0.01"
                                 :min="0"
                                 style="width: 100%"
                                 v-model="scope.row.finishedNum"
                                 :disabled="!scope.row.editType"
                                 :precision="2"
                                 placeholder="请输入"
                                 clearable
                                 @change="changeNum(scope.row)" />
              </template>
            </el-table-column>
            <!--                        <el-table-column label="待生产数量" prop="pendingNum" width="240" align="center"></el-table-column>-->
            <el-table-column label="生产人"
                             prop="schedulingUserId"
                             width="400">
              <template #default="scope">
                <el-select v-model="scope.row.schedulingUserId"
                           placeholder="选择人员"
                           :disabled="!scope.row.editType"
                           style="width: 100%;">
                  <el-option v-for="user in userList"
                             :key="user.userId"
                             :label="user.nickName"
                             :value="user.userId" />
                </el-select>
              </template>
            </el-table-column>
            <el-table-column label="生产日期"
                             prop="schedulingDate"
                             width="400">
              <template #default="scope">
                <el-date-picker v-model="scope.row.schedulingDate"
                                type="date"
                                :disabled="!scope.row.editType"
                                placeholder="请选择日期"
                                value-format="YYYY-MM-DD"
                                format="YYYY-MM-DD"
                                clearable
                                style="width: 100%" />
              </template>
            </el-table-column>
            <el-table-column label="操作"
                             >
              <template #default="scope">
                <el-button link
                           type="primary"
                           size="small"
                           @click="changeEditType(scope.row)"
                           v-if="!scope.row.editType"
                           :disabled="scope.row.parentStatus === 3">编辑</el-button>
                <el-button link
                           type="primary"
                           size="small"
                           @click="saveReceiptPayment(scope.row)"
                           v-if="scope.row.editType">保存</el-button>
              </template>
            </el-table-column>
          </el-table>
        </template>
      </PIMTable>
    </div>
    <form-dia ref="formDia"
              @close="handleQuery"></form-dia>
    <input-modal v-if="isShowInput"
                 v-model:visible="isShowInput"
                 :production-product-main-id="isShowingId" />
  </div>
</template>
<script setup>
  import { onMounted, ref } from "vue";
  import FormDia from "@/views/productionManagement/productionReporting/components/formDia.vue";
  import { ElMessageBox } from "element-plus";
  import {
    productionReportUpdate,
    workListPageById,
    productionReportDelete,
  } from "@/api/productionManagement/productionReporting.js";
  import { productionProductMainListPage } from "@/api/productionManagement/productionProductMain.js";
  import { userListNoPageByTenantId } from "@/api/system/user.js";
  import InputModal from "@/views/productionManagement/productionReporting/Input.vue";
  const data = reactive({
    searchForm: {
      nickName: "",
      workOrderNo: "",
      workOrderStatus: "",
    },
  });
  const { searchForm } = toRefs(data);
  const expandedRowKeys = ref([]);
  const expandData = ref([]);
  const userList = ref([]);
  const tableColumn = ref([
    {
      label: "报工单号",
      prop: "productNo",
      width: 120,
    },
    {
      label: "报工人员",
      prop: "nickName",
      width: 120,
    },
    {
      label: "工序",
      prop: "process",
      width: 120,
    },
    {
      label: "工单编号",
      prop: "workOrderNo",
      width: 120,
    },
    {
      label: "销售合同号",
      prop: "salesContractNo",
      width: 120,
    },
    {
      label: "产品名称",
      prop: "productName",
      width: 120,
    },
    {
      label: "产品规格型号",
      prop: "productModelName",
      width: 120,
    },
    {
      label: "产出数量",
      prop: "quantity",
      width: 120,
    },
    {
      label: "报废数量",
      prop: "scrapQty",
      width: 120,
    },
    {
      label: "单位",
      prop: "unit",
      width: 120,
    },
    {
      label: "创建时间",
      prop: "createTime",
      width: 120,
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      operation: [
        {
          name: "查看投入",
          type: "text",
          clickFun: row => {
            showInput(row);
          },
        },
        {
          name: "删除",
          type: "danger",
          clickFun: row => {
            deleteReport(row);
          },
        },
      ],
    },
  ]);
  const tableData = ref([]);
  const selectedRows = ref([]);
  const tableLoading = ref(false);
  const childrenLoading = ref(false);
  const page = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
  const formDia = ref();
  const { proxy } = getCurrentInstance();
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
  const changeDaterange = value => {
    if (value) {
      searchForm.value.entryDateStart = value[0];
      searchForm.value.entryDateEnd = value[1];
    } else {
      searchForm.value.entryDateStart = undefined;
      searchForm.value.entryDateEnd = undefined;
    }
    handleQuery();
  };
  const deleteReport = row => {
    ElMessageBox.confirm("确定删除该报工吗?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    }).then(() => {
      productionReportDelete({ id: row.id }).then(res => {
        if (res.code === 200) {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        } else {
          ElMessageBox.alert(res.msg || "删除失败", "提示", {
            confirmButtonText: "确定",
          });
        }
      });
    });
  };
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  };
  const getList = () => {
    tableLoading.value = true;
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined;
    expandedRowKeys.value = [];
    productionProductMainListPage(params)
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records.map(item => ({
          ...item,
          pendingFinishNum:
            (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0),
        }));
        page.total = res.data.total;
      })
      .catch(err => {
        tableLoading.value = false;
      });
  };
  // å±•开行
  const expandChange = (row, expandedRows) => {
    userListNoPageByTenantId().then(res => {
      userList.value = res.data;
    });
    if (expandedRows.length > 0) {
      nextTick(() => {
        expandedRowKeys.value = [];
        try {
          childrenLoading.value = true;
          workListPageById({ id: row.id }).then(res => {
            childrenLoading.value = false;
            const index = tableData.value.findIndex(item => item.id === row.id);
            if (index > -1) {
              expandData.value = res.data.map(item => ({
                ...item,
                pendingNum:
                  (Number(item.schedulingNum) || 0) -
                  (Number(item.finishedNum) || 0),
                parentStatus: row.status, // æ–°å¢žçˆ¶è¡¨çŠ¶æ€
              }));
            }
            expandedRowKeys.value.push(row.id);
          });
        } catch (error) {
          childrenLoading.value = false;
          console.log(error);
        }
      });
    } else {
      expandedRowKeys.value = [];
    }
  };
  const changeNum = row => {
    // æ‰¾åˆ°çˆ¶è¡¨æ ¼æ•°æ®
    const parentRow = tableData.value.find(
      item => item.id === expandedRowKeys.value[0]
    );
    // è®¡ç®—所有子表格 finishedNum çš„æ€»å’Œ
    const totalFinishedNum = expandData.value.reduce(
      (sum, item) => sum + (Number(item.finishedNum) || 0),
      0
    );
    // çˆ¶è¡¨æ ¼çš„æŽ’产数量
    const schedulingNum = parentRow ? Number(parentRow.schedulingNum) : 0;
    if (totalFinishedNum > schedulingNum) {
      // å›žé€€æœ¬æ¬¡è¾“å…¥
      row.finishedNum =
        schedulingNum - (totalFinishedNum - Number(row.finishedNum));
      proxy.$modal.msgWarning("所有本次生产数量之和不可大于排产数量");
    }
    row.pendingNum = row.schedulingNum - row.finishedNum;
  };
  // ç¼–辑修改状态
  const changeEditType = row => {
    row.editType = !row.editType;
  };
  // ä¿å­˜è®°å½•
  const saveReceiptPayment = row => {
    productionReportUpdate(row).then(res => {
      row.editType = !row.editType;
      getList();
      proxy.$modal.msgSuccess("提交成功");
    });
  };
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
  };
  const summarizeMainTable = param => {
    return proxy.summarizeTable(param, ["finishedNum"]);
  };
  // æ‰“开弹框
  const openForm = (type, row) => {
    if (selectedRows.value.length !== 1) {
      proxy.$message.error("请选择一条数据");
      return;
    }
    if (selectedRows.value[0].pendingFinishNum == 0) {
      proxy.$message.warning("无需再报工");
      return;
    }
    nextTick(() => {
      const rowInfo = type === "add" ? selectedRows.value[0] : row;
      formDia.value?.openDialog(type, rowInfo);
    });
  };
  // æ‰“开投入模态框
  const isShowInput = ref(false);
  const isShowingId = ref(0);
  const showInput = row => {
    isShowInput.value = true;
    isShowingId.value = row.id;
  };
  // å¯¼å‡º
  const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        proxy.download("/productionProductMain/export", {}, "生产报工.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
  onMounted(() => {
    getList();
  });
</script>
<style scoped></style>
src/views/productionPlan/productionPlan/index.vue
@@ -107,6 +107,12 @@
                :selectable="isSelectable"
                @selection-change="handleSelectionChange"
                @pagination="pagination">
        <template #quantity="{ row }">
          {{ row.quantity || '-' }}<span style="color:rgb(63, 95, 211)"> å—</span>
        </template>
        <template #volume="{ row }">
          {{ row.volume || '-' }}<span style="color:rgba(12, 46, 40, 0.76)"> æ–¹</span>
        </template>
      </PIMTable>
    </div>
    <!-- åˆå¹¶ä¸‹å‘弹窗 -->
@@ -456,14 +462,18 @@
    {
      label: "块数",
      prop: "quantity",
      formatData: cell => (cell ? `${cell}块` : ""),
      align: "right",
      dataType: "slot",
      slot: "quantity",
    },
    {
      label: "方数",
      prop: "volume",
      width: "150px",
      align: "right",
      dataType: "slot",
      slot: "volume",
      className: "volume-cell",
      formatData: cell => (cell ? `${cell}方` : ""),
    },
    {
      label: "下发状态",
@@ -1063,7 +1073,7 @@
        sum +
        (row.volume == null
          ? 0
          : (Number(row.volume) - Number(row.assignedQuantity)).toFixed(4))
          : Number(Number(row.volume) - Number(row.assignedQuantity).toFixed(4)))
      );
    }, 0);
    sumAssignedQuantity.value = totalAssignedQuantity;