Merge branch 'dev_银川_中盛建材' of http://114.132.189.42:9002/r/product-inventory-management into dev_银川_中盛建材
已添加2个文件
已修改25个文件
4539 ■■■■ 文件已修改
src/api/personnelManagement/class.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/processRouteItem.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productProcessRoute.js 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productStructure.js 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionOrder.js 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/PIMTable.vue 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ProcessParamListDialog.vue 119 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Navbar.vue 149 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/customerFile/index.vue 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/officeEnergyConsumption/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/productionEnergyConsumption/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/classsSheduling/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/Edit.vue 168 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/New.vue 199 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/index.vue 84 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/index2.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/processRouteItem/index.vue 698 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/Detail/index.vue 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/StructureEdit.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/index.vue 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/index.vue 941 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/index.vue 71 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/components/ReportingDialog.vue 588 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/index.vue 732 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/index2.vue 420 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionPlan/productionPlan/index.vue 140 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/class.js
@@ -71,6 +71,7 @@
    url: "/personalShift/export",
    method: "get",
    params: query,
    responseType: "blob",
  });
}
src/api/productionManagement/processRouteItem.js
@@ -60,7 +60,7 @@
    data: data,
  });
}
// å·¥è‰ºè·¯çº¿å‚数修改
// å·¥è‰ºè·¯çº¿å‚数删除
export function delProcessRouteItemParam(id) {
  return request({
    url: `/ProcessRouteItemParam/remove/${id}`,
src/api/productionManagement/productProcessRoute.js
@@ -4,16 +4,17 @@
// åˆ—表查询
export function findProductProcessRouteItemList(query) {
  return request({
    url: "/productProcessRoute/list",
    url: `/productionOrderRouteItem/list/${query.orderId}`,
    method: "get",
    params: query,
  });
}
export function addOrUpdateProductProcessRouteItem(data) {
  return request({
    url: "/productProcessRoute/updateRouteItem",
    method: "post",
    url: "/productionOrderRouteItem/update",
    method: "put",
    data: data,
  });
}
@@ -21,7 +22,7 @@
// ç”Ÿäº§è®¢å•下:新增工艺路线项目
export function addRouteItem(data) {
  return request({
    url: "/productProcessRoute/addRouteItem",
    url: "/productionOrderRouteItem/add",
    method: "post",
    data,
  });
@@ -39,7 +40,7 @@
// åˆ é™¤å·¥è‰ºè·¯çº¿é¡¹ç›®ï¼ˆè·¯ç”±åŽæ‹¼æŽ¥ id)
export function deleteRouteItem(id) {
  return request({
    url: `/productProcessRoute/deleteRouteItem/${id}`,
    url: `/productionOrderRouteItem/delete/${id}`,
    method: "delete",
  });
}
@@ -47,8 +48,39 @@
// ç”Ÿäº§è®¢å•下:排序工艺路线项目
export function sortRouteItem(data) {
  return request({
    url: "/productProcessRoute/sortRouteItem",
    url: "/productionOrderRouteItem/sort",
    method: "post",
    data,
  });
}
// èŽ·å–å·¥åºå‚æ•°åˆ—è¡¨-生产订单
export function findProcessParamListOrder(query) {
  return request({
    url: `/productionOrderRouteItemParam/list`,
    method: "get",
    params: query,
  });
}
// å·¥è‰ºè·¯çº¿å‚数新增-生产订单
export function addProcessRouteItemParamOrder(data) {
  return request({
    url: "/productionOrderRouteItemParam/add",
    method: "post",
    data: data,
  });
}
// å·¥è‰ºè·¯çº¿å‚数修改-生产订单
export function editProcessRouteItemParamOrder(data) {
  return request({
    url: "/productionOrderRouteItemParam/update",
    method: "put",
    data: data,
  });
}
// å·¥è‰ºè·¯çº¿å‚数删除-生产订单
export function delProcessRouteItemParamOrder(id) {
  return request({
    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
@@ -30,7 +30,7 @@
// ç”Ÿäº§è®¢å•-绑定工艺路线
export function bindingRoute(data) {
  return request({
    url: "/productOrder/bindingRoute",
    url: "/appendix/bindingRoute",
    method: "post",
    data,
  });
@@ -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/PIMTable/PIMTable.vue
@@ -22,7 +22,8 @@
    <el-table-column align="center"
                     type="selection"
                     width="55"
                     v-if="isSelection" />
                     v-if="isSelection"
                     :selectable="selectable" />
    <el-table-column align="center"
                     label="序号"
                     type="index"
@@ -311,6 +312,10 @@
      type: [String, Object],
      default: () => ({ width: "100%" }),
    },
    selectable: {
      type: Function,
      default: () => true,
    },
  });
  // Data
src/components/ProcessParamListDialog.vue
@@ -152,7 +152,9 @@
                        placeholder="请输入排序" />
            </el-form-item>
            <el-form-item label="是否必填">
              <el-switch v-model="selectedParam.isRequired" />
              <el-switch :active-value="true"
                         :inactive-value="false"
                         v-model="selectedParam.isRequired" />
            </el-form-item>
          </el-form>
          <el-empty v-else
@@ -235,6 +237,12 @@
    editProcessRouteItemParam,
    addProcessRouteItemParam,
  } from "@/api/productionManagement/processRouteItem.js";
  import {
    addProcessRouteItemParamOrder,
    delProcessRouteItemParamOrder,
    editProcessRouteItemParamOrder,
  } from "@/api/productionManagement/productProcessRoute.js";
  import { getBaseParamList } from "@/api/basicData/parameterMaintenance.js";
  const props = defineProps({
@@ -261,6 +269,14 @@
    editable: {
      type: Boolean,
      default: true,
    },
    orderId: {
      type: Number,
      default: 0,
    },
    pageType: {
      type: String,
      default: "route",
    },
  });
@@ -292,7 +308,7 @@
    minValue: null,
    maxValue: null,
    sort: 1,
    isRequired: 0,
    isRequired: false,
    paramType: null,
    paramFormat: "",
    unit: "",
@@ -326,7 +342,7 @@
      minValue: param.minValue,
      maxValue: param.maxValue,
      sort: param.sort || 1,
      isRequired: param.isRequired || 0,
      isRequired: param.isRequired || false,
      paramType: param.parameterType || param.paramType,
      paramFormat: param.parameterFormat || param.paramFormat,
      unit: param.unit || param.unit,
@@ -343,6 +359,17 @@
    })
      .then(() => {
        // è°ƒç”¨API删除参数
        if (props.pageType === "order") {
          delProcessRouteItemParamOrder(param.id)
            .then(res => {
              ElMessage.success("删除成功");
              emit("refresh");
            })
            .catch(err => {
              ElMessage.error("删除参数失败");
              console.error("删除参数失败:", err);
            });
        } else {
        delProcessRouteItemParam(param.id)
          .then(res => {
            ElMessage.success("删除成功");
@@ -352,6 +379,7 @@
            ElMessage.error("删除参数失败");
            console.error("删除参数失败:", err);
          });
        }
      })
      .catch(() => {});
  };
@@ -390,16 +418,22 @@
    }
    // åˆ¤æ–­å‚数类型,只有数值类型才传标准值、最大值和最小值
    const isNumericMode = selectedParam.value.valueMode === 1;
    const isNumericMode = selectedParam.value.paramType == 1;
    console.log(isNumericMode, "isNumericMode");
    // è°ƒç”¨API新增参数
    addProcessRouteItemParam({
    if (props.pageType === "order") {
      addProcessRouteItemParamOrder({
        orderId: Number(props.orderId),
        // processId: props.process.id,
      routeItemId: props.process.id,
        // routeItemId: Number(props.routeId),
      paramId: selectedParam.value.id,
      standardValue: isNumericMode ? selectedParam.value.standardValue || "" : "",
        standardValue: isNumericMode
          ? selectedParam.value.standardValue || ""
          : "",
      minValue: isNumericMode ? selectedParam.value.minValue || 0 : null,
      maxValue: isNumericMode ? selectedParam.value.maxValue || 0 : null,
      isRequired: selectedParam.value.isRequired || 0,
        isRequired: selectedParam.value.isRequired || false,
      sort: selectedParam.value.sort || 1,
    })
      .then(res => {
@@ -415,6 +449,32 @@
        ElMessage.error("添加参数失败");
        console.error("添加参数失败:", err);
      });
    } else {
      addProcessRouteItemParam({
        routeItemId: props.process.id,
        paramId: selectedParam.value.id,
        standardValue: isNumericMode
          ? selectedParam.value.standardValue || ""
          : "",
        minValue: isNumericMode ? selectedParam.value.minValue || 0 : null,
        maxValue: isNumericMode ? selectedParam.value.maxValue || 0 : null,
        isRequired: selectedParam.value.isRequired || false,
        sort: selectedParam.value.sort || 1,
      })
        .then(res => {
          if (res.code === 200) {
            ElMessage.success("添加参数成功");
            selectParamDialogVisible.value = false;
            emit("refresh");
          } else {
            ElMessage.error(res.msg || "添加参数失败");
          }
        })
        .catch(err => {
          ElMessage.error("添加参数失败");
          console.error("添加参数失败:", err);
        });
    }
  };
  // æäº¤ç¼–辑参数
@@ -423,19 +483,19 @@
    editParamFormRef.value.validate(valid => {
      if (valid) {
        // åˆ¤æ–­å‚数类型,只有数值类型才传标准值、最大值和最小值
        const isNumericMode = editParamForm.value.valueMode == 1;
        // è°ƒç”¨API修改参数
        editProcessRouteItemParam({
        const isNumericMode = editParamForm.value.paramType == 1;
        console.log(isNumericMode, "isNumericMode");
        if (props.pageType === "order") {
          editProcessRouteItemParamOrder({
          id: editParamForm.value.id,
          routeItemId: props.process.id,
          paramId: editParamForm.value.paramId,
            // routeItemId: props.process.id,
            // paramId: editParamForm.value.paramId,
          standardValue: isNumericMode
            ? editParamForm.value.standardValue || ""
            : "",
          minValue: isNumericMode ? editParamForm.value.minValue || 0 : null,
          maxValue: isNumericMode ? editParamForm.value.maxValue || 0 : null,
          isRequired: editParamForm.value.isRequired || 0,
            isRequired: editParamForm.value.isRequired || false,
        })
          .then(res => {
            if (res.code === 200) {
@@ -450,6 +510,33 @@
            ElMessage.error("编辑参数失败");
            console.error("编辑参数失败:", err);
          });
        } else {
          // è°ƒç”¨API修改参数
          editProcessRouteItemParam({
            id: editParamForm.value.id,
            routeItemId: props.process.id,
            paramId: editParamForm.value.paramId,
            standardValue: isNumericMode
              ? editParamForm.value.standardValue || ""
              : "",
            minValue: isNumericMode ? editParamForm.value.minValue || 0 : null,
            maxValue: isNumericMode ? editParamForm.value.maxValue || 0 : null,
            isRequired: editParamForm.value.isRequired || false,
          })
            .then(res => {
              if (res.code === 200) {
                ElMessage.success("编辑成功");
                editParamDialogVisible.value = false;
                emit("refresh");
              } else {
                ElMessage.error(res.msg || "编辑失败");
              }
            })
            .catch(err => {
              ElMessage.error("编辑参数失败");
              console.error("编辑参数失败:", err);
            });
        }
      }
    });
  };
@@ -497,7 +584,7 @@
          minValue: null,
          maxValue: null,
          sort: 1,
          isRequired: 0,
          isRequired: false,
          paramType: null,
          paramFormat: "",
          unit: "",
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/basicData/customerFile/index.vue
@@ -784,7 +784,7 @@
      label: "操作",
      align: "center",
      fixed: "right",
      width: 250,
      width: 150,
      operation: [
        {
          name: "编辑",
@@ -800,20 +800,20 @@
            openDetailDialog(row);
          },
        },
        {
          name: "回访提醒",
          type: "text",
          clickFun: row => {
            openReminderDialog(row);
          },
        },
        {
          name: "添加洽谈进度",
          type: "text",
          clickFun: row => {
            openNegotiationDialog(row);
          },
        },
        // {
        //   name: "回访提醒",
        //   type: "text",
        //   clickFun: row => {
        //     openReminderDialog(row);
        //   },
        // },
        // {
        //   name: "添加洽谈进度",
        //   type: "text",
        //   clickFun: row => {
        //     openNegotiationDialog(row);
        //   },
        // },
      ],
    },
  ]);
src/views/basicData/product/index.vue
@@ -913,8 +913,8 @@
    gap: 20px;
  }
  .left {
    width: 465px;
    min-width: 465px;
    width: 35%;
    min-width: 35%;
    background: #ffffff;
    border-radius: 8px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
src/views/energyManagement/officeEnergyConsumption/index.vue
@@ -1049,7 +1049,6 @@
  }
  .search_form {
    :deep(.el-form-item) {
      margin-bottom: 0px !important;
    }
  }
</style>
src/views/energyManagement/productionEnergyConsumption/index.vue
@@ -1049,7 +1049,6 @@
  }
  .search_form {
    :deep(.el-form-item) {
      margin-bottom: 0px !important;
    }
  }
</style>
src/views/personnelManagement/classsSheduling/index.vue
src/views/productionManagement/processRoute/Edit.vue
@@ -8,21 +8,25 @@
               :model="formState"
               label-position="top"
               ref="formRef">
        <el-form-item label="产品名称"
                      prop="productModelId"
        <el-form-item label="产品类型"
                      prop="dictCode"
                      :rules="[
                {
                required: true,
                message: '请选择产品',
                message: '请选择产品类型',
                trigger: 'change',
              }
            ]">
          <el-button type="primary"
                     @click="showProductSelectDialog = true">
            {{ formState.productName && formState.productModelName
              ? `${formState.productName} - ${formState.productModelName}`
              : '选择产品' }}
          </el-button>
          <el-select v-model="formState.dictCode"
                     placeholder="请选择产品类型"
                     clearable
                     style="width: 100%"
                     @change="handleProductTypeChange">
            <el-option v-for="item in productTypeOptions"
                       :key="item.dictCode"
                       :label="item.dictLabel"
                       :value="item.dictCode" />
          </el-select>
        </el-form-item>
        <el-form-item label="BOM"
                      prop="bomId"
@@ -36,7 +40,7 @@
          <el-select v-model="formState.bomId"
                     placeholder="请选择BOM"
                     clearable
                     :disabled="!formState.productModelId || bomOptions.length === 0"
                     :disabled="!formState.dictCode || bomOptions.length === 0"
                     style="width: 100%">
            <el-option v-for="item in bomOptions"
                       :key="item.id"
@@ -50,10 +54,7 @@
                    type="textarea" />
        </el-form-item>
      </el-form>
      <!-- äº§å“é€‰æ‹©å¼¹çª— -->
      <ProductSelectDialog v-model="showProductSelectDialog"
                           @confirm="handleProductSelect"
                           single />
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
@@ -75,8 +76,8 @@
    watch,
  } from "vue";
  import { update } from "@/api/productionManagement/processRoute.js";
  import { getByModel } from "@/api/productionManagement/productBom.js";
  import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
  import { listPage } from "@/api/productionManagement/productBom.js";
  import { getDicts } from "@/api/system/dict/data.js";
  const props = defineProps({
    visible: {
@@ -94,10 +95,8 @@
  // å“åº”式数据(替代选项式的 data)
  const formState = ref({
    productId: undefined,
    productModelId: undefined,
    productName: "",
    productModelName: "",
    dictCode: undefined,
    dictLabel: "",
    bomId: undefined,
    description: "",
  });
@@ -111,10 +110,48 @@
    },
  });
  const showProductSelectDialog = ref(false);
  const productTypeOptions = ref([]);
  const bomOptions = ref([]);
  let { proxy } = getCurrentInstance();
  // èŽ·å–äº§å“ç±»åž‹å­—å…¸
  const getProductTypeOptions = () => {
    getDicts("product_type")
      .then(res => {
        if (res.code === 200) {
          productTypeOptions.value = res.data;
        }
      })
      .catch(err => {
        console.error("获取产品类型字典失败:", err);
      });
  };
  // æ ¹æ®äº§å“ç±»åž‹èŽ·å–BOM列表
  const getBomListByProductType = async dictCode => {
    if (!dictCode) {
      bomOptions.value = [];
      return;
    }
    try {
      // ä½¿ç”¨listPage接口,根据dictCode查询BOM
      const res = await listPage({ dictCode });
      // å¤„理返回的BOM数据
      let bomList = [];
      if (res.data && res.data.records) {
        bomList = res.data.records;
      }
      bomOptions.value = bomList;
      if (bomList.length === 0) {
        proxy.$modal.msgError("该产品类型没有BOM,请先创建BOM");
      }
    } catch (error) {
      // å¦‚果接口返回404或其他错误,说明没有BOM
      proxy.$modal.msgError("该产品类型没有BOM,请先创建BOM");
      bomOptions.value = [];
    }
  };
  const closeModal = () => {
    isShow.value = false;
@@ -125,94 +162,42 @@
    if (props.record) {
      formState.value = {
        ...props.record,
        productId: props.record.productId,
        productModelId: props.record.productModelId,
        productName: props.record.productName || "",
        // æ³¨æ„ï¼šrecord中的字段是model,需要映射到productModelName
        productModelName:
          props.record.model || props.record.productModelName || "",
        dictCode: props.record.dictCode,
        dictLabel: props.record.dictLabel || "",
        bomId: props.record.bomId,
        description: props.record.description || "",
      };
      // å¦‚果有产品型号ID,加载BOM列表
      if (props.record.productModelId) {
        loadBomList(props.record.productModelId);
      // å¦‚果有产品类型,加载BOM列表
      if (props.record.dictCode) {
        getBomListByProductType(props.record.dictCode);
      }
    }
  };
  // åŠ è½½BOM列表
  const loadBomList = async productModelId => {
    if (!productModelId) {
      bomOptions.value = [];
      return;
  // äº§å“ç±»åž‹é€‰æ‹©å¤„理
  const handleProductTypeChange = async dictCode => {
    if (dictCode) {
      const selectedType = productTypeOptions.value.find(item => item.dictCode === dictCode);
      if (selectedType) {
        formState.value.dictLabel = selectedType.dictLabel;
    }
    try {
      const res = await getByModel(productModelId);
      // å¤„理返回的BOM数据:可能是数组、对象或包含data字段
      let bomList = [];
      if (Array.isArray(res)) {
        bomList = res;
      } else if (res && res.data) {
        bomList = Array.isArray(res.data) ? res.data : [res.data];
      } else if (res && typeof res === "object") {
        bomList = [res];
      }
      bomOptions.value = bomList;
    } catch (error) {
      console.error("加载BOM列表失败:", error);
      bomOptions.value = [];
    }
  };
  // äº§å“é€‰æ‹©å¤„理
  const handleProductSelect = async products => {
    if (products && products.length > 0) {
      const product = products[0];
      // å…ˆæŸ¥è¯¢BOM列表(必选)
      try {
        const res = await getByModel(product.id);
        // å¤„理返回的BOM数据:可能是数组、对象或包含data字段
        let bomList = [];
        if (Array.isArray(res)) {
          bomList = res;
        } else if (res && res.data) {
          bomList = Array.isArray(res.data) ? res.data : [res.data];
        } else if (res && typeof res === "object") {
          bomList = [res];
        }
        if (bomList.length > 0) {
          formState.value.productModelId = product.id;
          formState.value.productName = product.productName;
          formState.value.productModelName = product.model;
          // å¦‚果当前选择的BOM不在新列表中,则重置BOM选择
          const currentBomExists = bomList.some(
            bom => bom.id === formState.value.bomId
          );
          if (!currentBomExists) {
            formState.value.bomId = undefined;
          }
          bomOptions.value = bomList;
          showProductSelectDialog.value = false;
      await getBomListByProductType(dictCode);
          // è§¦å‘表单验证更新
          proxy.$refs["formRef"]?.validateField("productModelId");
      proxy.$refs["formRef"]?.validateField("dictCode");
        } else {
          proxy.$modal.msgError("该产品没有BOM,请先创建BOM");
        }
      } catch (error) {
        // å¦‚果接口返回404或其他错误,说明没有BOM
        proxy.$modal.msgError("该产品没有BOM,请先创建BOM");
      }
      formState.value.dictLabel = "";
      bomOptions.value = [];
    }
  };
  const handleSubmit = () => {
    proxy.$refs["formRef"].validate(valid => {
      if (valid) {
        // éªŒè¯æ˜¯å¦é€‰æ‹©äº†äº§å“å’ŒBOM
        if (!formState.value.productModelId) {
          proxy.$modal.msgError("请选择产品");
        // éªŒè¯æ˜¯å¦é€‰æ‹©äº†äº§å“ç±»åž‹å’ŒBOM
        if (!formState.value.dictCode) {
          proxy.$modal.msgError("请选择产品类型");
          return;
        }
        if (!formState.value.bomId) {
@@ -250,6 +235,7 @@
  );
  onMounted(() => {
    getProductTypeOptions();
    if (props.visible && props.record) {
      setFormData();
    }
src/views/productionManagement/processRoute/New.vue
@@ -1,32 +1,34 @@
<template>
  <div>
    <el-dialog
        v-model="isShow"
    <el-dialog v-model="isShow"
        title="新增工艺路线"
        width="400"
        @close="closeModal"
    >
      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
        <el-form-item
            label="产品名称"
            prop="productModelId"
               @close="closeModal">
      <el-form label-width="140px"
               :model="formState"
               label-position="top"
               ref="formRef">
        <el-form-item label="产品类型"
                      prop="dictCode"
            :rules="[
                {
                required: true,
                message: '请选择产品',
                message: '请选择产品类型',
                trigger: 'change',
              }
            ]"
        >
          <el-button type="primary" @click="showProductSelectDialog = true">
            {{ formState.productName && formState.productModelName
              ? `${formState.productName} - ${formState.productModelName}`
              : '选择产品' }}
          </el-button>
            ]">
          <el-select v-model="formState.dictCode"
                     placeholder="请选择产品类型"
                     clearable
                     style="width: 100%"
                     @change="handleProductTypeChange">
            <el-option v-for="item in productTypeOptions"
                       :key="item.dictCode"
                       :label="item.dictLabel"
                       :value="item.dictCode" />
          </el-select>
        </el-form-item>
        <el-form-item
            label="BOM"
        <el-form-item label="BOM"
            prop="bomId"
            :rules="[
                {
@@ -34,38 +36,32 @@
                message: '请选择BOM',
                trigger: 'change',
              }
            ]"
        >
          <el-select
              v-model="formState.bomId"
            ]">
          <el-select v-model="formState.bomId"
              placeholder="请选择BOM"
              clearable
              :disabled="!formState.productModelId || bomOptions.length === 0"
              style="width: 100%"
          >
            <el-option
                v-for="item in bomOptions"
                     :disabled="!formState.dictCode || bomOptions.length === 0"
                     style="width: 100%">
            <el-option v-for="item in bomOptions"
                :key="item.id"
                :label="item.bomNo || `BOM-${item.id}`"
                :value="item.id"
            />
                       :value="item.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="备注" prop="description">
          <el-input v-model="formState.description" type="textarea" />
        <el-form-item label="备注"
                      prop="description">
          <el-input v-model="formState.description"
                    type="textarea" />
        </el-form-item>
      </el-form>
      <!-- äº§å“é€‰æ‹©å¼¹çª— -->
      <ProductSelectDialog
          v-model="showProductSelectDialog"
      <ProductSelectDialog v-model="showProductSelectDialog"
          @confirm="handleProductSelect"
          single
      />
                           single />
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleSubmit">确认</el-button>
          <el-button type="primary"
                     @click="handleSubmit">确认</el-button>
          <el-button @click="closeModal">取消</el-button>
        </div>
      </template>
@@ -74,10 +70,10 @@
</template>
<script setup>
import {ref, computed, getCurrentInstance} from "vue";
  import { ref, computed, getCurrentInstance, onMounted } from "vue";
import {add} from "@/api/productionManagement/processRoute.js";
import {getByModel} from "@/api/productionManagement/productBom.js";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
  import { listPage } from "@/api/productionManagement/productBom.js";
  import { getDicts } from "@/api/system/dict/data.js";
const props = defineProps({
  visible: {
@@ -86,16 +82,14 @@
  },
});
const emit = defineEmits(['update:visible', 'completed']);
  const emit = defineEmits(["update:visible", "completed"]);
// å“åº”式数据(替代选项式的 data)
const formState = ref({
  productId: undefined,
  productModelId: undefined,
  productName: "",
  productModelName: "",
    dictCode: undefined,
    dictLabel: "",
  bomId: undefined,
  description: '',
    description: "",
});
const isShow = computed({
@@ -103,71 +97,90 @@
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
      emit("update:visible", val);
  },
});
const showProductSelectDialog = ref(false);
  const productTypeOptions = ref([]);
const bomOptions = ref([]);
let { proxy } = getCurrentInstance()
  let { proxy } = getCurrentInstance();
  // èŽ·å–äº§å“ç±»åž‹å­—å…¸
  const getProductTypeOptions = () => {
    getDicts("product_type")
      .then(res => {
        if (res.code === 200) {
          productTypeOptions.value = res.data;
        }
      })
      .catch(err => {
        console.error("获取产品类型字典失败:", err);
      });
  };
  // æ ¹æ®äº§å“ç±»åž‹èŽ·å–BOM列表
  const getBomListByProductType = async dictCode => {
    if (!dictCode) {
      bomOptions.value = [];
      return;
    }
    try {
      // ä½¿ç”¨listPage接口,根据dictCode查询BOM
      const res = await listPage({ dictCode });
      // å¤„理返回的BOM数据
      let bomList = [];
      if (res.data && res.data.records) {
        bomList = res.data.records;
      }
      bomOptions.value = bomList;
      if (bomList.length === 0) {
        proxy.$modal.msgError("该产品类型没有BOM,请先创建BOM");
      }
    } catch (error) {
      // å¦‚果接口返回404或其他错误,说明没有BOM
      proxy.$modal.msgError("该产品类型没有BOM,请先创建BOM");
      bomOptions.value = [];
    }
  };
const closeModal = () => {
  // é‡ç½®è¡¨å•数据
  formState.value = {
    productId: undefined,
    productModelId: undefined,
    productName: "",
    productModelName: "",
      dictCode: undefined,
      dictLabel: "",
    bomId: undefined,
    description: '',
      description: "",
  };
  bomOptions.value = [];
  isShow.value = false;
};
// äº§å“é€‰æ‹©å¤„理
const handleProductSelect = async (products) => {
  if (products && products.length > 0) {
    const product = products[0];
    // å…ˆæŸ¥è¯¢BOM列表(必选)
    try {
      const res = await getByModel(product.id);
      // å¤„理返回的BOM数据:可能是数组、对象或包含data字段
      let bomList = [];
      if (Array.isArray(res)) {
        bomList = res;
      } else if (res && res.data) {
        bomList = Array.isArray(res.data) ? res.data : [res.data];
      } else if (res && typeof res === 'object') {
        bomList = [res];
  // äº§å“ç±»åž‹é€‰æ‹©å¤„理
  const handleProductTypeChange = async dictCode => {
    if (dictCode) {
      const selectedType = productTypeOptions.value.find(
        item => item.dictCode === dictCode
      );
      if (selectedType) {
        formState.value.dictLabel = selectedType.dictLabel;
      }
      if (bomList.length > 0) {
        formState.value.productModelId = product.id;
        formState.value.productName = product.productName;
        formState.value.productModelName = product.model;
        formState.value.bomId = undefined; // é‡ç½®BOM选择
        bomOptions.value = bomList;
        showProductSelectDialog.value = false;
      await getBomListByProductType(dictCode);
        // è§¦å‘表单验证更新
        proxy.$refs["formRef"]?.validateField('productModelId');
      proxy.$refs["formRef"]?.validateField("dictCode");
      } else {
        proxy.$modal.msgError("该产品没有BOM,请先创建BOM");
      }
    } catch (error) {
      // å¦‚果接口返回404或其他错误,说明没有BOM
      proxy.$modal.msgError("该产品没有BOM,请先创建BOM");
    }
      formState.value.dictLabel = "";
      bomOptions.value = [];
  }
};
const handleSubmit = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      // éªŒè¯æ˜¯å¦é€‰æ‹©äº†äº§å“å’ŒBOM
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择产品");
        // éªŒè¯æ˜¯å¦é€‰æ‹©äº†äº§å“ç±»åž‹å’ŒBOM
        if (!formState.value.dictCode) {
          proxy.$modal.msgError("请选择产品类型");
        return;
      }
      if (!formState.value.bomId) {
@@ -178,13 +191,17 @@
        // å…³é—­æ¨¡æ€æ¡†
        isShow.value = false;
        // å‘ŠçŸ¥çˆ¶ç»„件已完成
        emit('completed');
          emit("completed");
        proxy.$modal.msgSuccess("提交成功");
      })
        });
    }
  })
    });
};
  // ç»„件挂载时获取产品类型字典
  onMounted(() => {
    getProductTypeOptions();
  });
defineExpose({
  closeModal,
src/views/productionManagement/processRoute/index.vue
@@ -3,13 +3,29 @@
    <div class="search_form">
      <el-form :model="searchForm"
               :inline="true">
        <el-form-item label="规格名称:">
          <el-input v-model="searchForm.model"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
        <el-form-item label="产品类型:">
          <el-select v-model="searchForm.dictCode"
                    style="width: 200px;"
                    @change="handleQuery" />
                     placeholder="请选择产品类型"
                     clearable
                     @change="handleQuery">
            <el-option v-for="option in productTypeOptions"
                       :key="option.dictCode"
                       :label="option.dictLabel"
                       :value="option.dictCode" />
          </el-select>
        </el-form-item>
        <el-form-item label="状态:">
          <el-select v-model="searchForm.status"
                     style="width: 200px;"
                     placeholder="请选择状态"
                     clearable
                     @change="handleQuery">
            <el-option label="已批准"
                       :value="true" />
            <el-option label="草稿"
                       :value="false" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary"
@@ -63,11 +79,13 @@
  } from "@/api/productionManagement/processRoute.js";
  import { useRouter } from "vue-router";
  import { ElMessageBox, ElMessage } from "element-plus";
  import { getDicts } from "@/api/system/dict/data";
  const router = useRouter();
  const data = reactive({
    searchForm: {
      model: "",
      dictCode: "",
      status: "",
    },
  });
  const { searchForm } = toRefs(data);
@@ -75,14 +93,33 @@
    {
      label: "工艺路线编号",
      prop: "processRouteCode",
      width: "200px",
      className: "status-cell",
    },
    {
      label: "产品名称",
      prop: "productName",
      label: "状态",
      prop: "status",
      dataType: "tag",
      formatData: params => {
        if (params) {
          return "已批准";
        } else {
          return "草稿";
        }
      },
      formatType: params => {
        if (params) {
          return "success";
        } else {
          return "info";
        }
      },
    },
    {
      label: "规格名称",
      prop: "model",
      label: "产品类型",
      prop: "dictLabel",
      dataType: "tag",
    },
    {
      label: "BOM编号",
@@ -145,6 +182,7 @@
  const isShowEditModal = ref(false);
  const isShowItemModal = ref(false);
  const record = ref({});
  const productTypeOptions = ref([]);
  const page = reactive({
    current: 1,
    size: 100,
@@ -204,6 +242,8 @@
        productName: row.productName || "",
        model: row.model || "",
        bomNo: row.bomNo || "",
        dictLabel: row.dictLabel || "",
        orderId: row.id || "",
        bomId: row.bomId || null,
        description: row.description || "",
        type: "route",
@@ -264,9 +304,31 @@
    });
  };
  // èŽ·å–äº§å“ç±»åž‹å­—å…¸
  const getProductTypeOptions = () => {
    getDicts("product_type")
      .then(res => {
        if (res.code === 200) {
          productTypeOptions.value = res.data;
        }
      })
      .catch(err => {
        console.error("获取产品类型字典失败:", err);
      });
  };
  onMounted(() => {
    getProductTypeOptions();
    getList();
  });
</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/index2.vue
@@ -397,8 +397,8 @@
            </el-form-item>
            <el-form-item label="是否质检">
              <el-tag size="small"
                      :type="selectedProcessItem.isQuality ? 'success' : 'info'">
                {{ selectedProcessItem.isQuality ? '质检' : '非质检' }}
                      :type="selectedProcessItem.isQuality == 1 ? 'success' : 'info'">
                {{ selectedProcessItem.isQuality == 1 ? '质检' : '非质检' }}
              </el-tag>
            </el-form-item>
            <el-form-item label="产品名称"
@@ -420,8 +420,8 @@
            <el-form-item label="是否质检"
                          prop="isQuality">
              <el-switch v-model="processForm.isQuality"
                         :active-value="true"
                         inactive-value="false" />
                         :active-value="1"
                         :inactive-value="0" />
            </el-form-item>
          </el-form>
          <el-empty v-else
@@ -1366,7 +1366,7 @@
    processForm.productName = "";
    processForm.productModelName = "";
    processForm.unit = "";
    processForm.isQuality = row.isQuality || false;
    processForm.isQuality = row.isQuality || 0;
  };
  // å¤„理工序选择时的产品选择
src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -16,18 +16,10 @@
        </div>
        <div class="info-item">
          <div class="info-label-wrapper">
            <span class="info-label">产品名称</span>
            <span class="info-label">产品类型</span>
          </div>
          <div class="info-value-wrapper">
            <span class="info-value">{{ routeInfo.productName || '-' }}</span>
          </div>
        </div>
        <div class="info-item">
          <div class="info-label-wrapper">
            <span class="info-label">规格名称</span>
          </div>
          <div class="info-value-wrapper">
            <span class="info-value">{{ routeInfo.model || '-' }}</span>
            <span class="info-value">{{ routeInfo.dictLabel || '-' }}</span>
          </div>
        </div>
        <div class="info-item">
@@ -55,6 +47,7 @@
         class="section-header">
      <div class="section-title">工艺路线项目列表</div>
      <div class="section-actions">
        <div class="sort-tip">拖拽表格排序</div>
        <el-button icon="Grid"
                   @click="toggleView"
                   style="margin-right: 10px;">
@@ -71,7 +64,9 @@
              :data="tableData"
              :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
              row-key="id"
              height="350"
              tooltip-effect="dark"
              style="margin-bottom: 20px;"
              class="lims-table">
      <el-table-column align="center"
                       label="序号"
@@ -84,12 +79,6 @@
          {{ getProcessName(scope.row.processId) || '-' }}
        </template>
      </el-table-column>
      <el-table-column label="产品名称"
                       prop="productName"
                       min-width="160" />
      <el-table-column label="规格名称"
                       prop="model"
                       min-width="140" />
      <el-table-column label="参数列表"
                       min-width="160">
        <template #default="scope">
@@ -99,14 +88,12 @@
                     @click="handleViewParams(scope.row)">参数列表</el-button>
        </template>
      </el-table-column>
      <el-table-column label="单位"
                       prop="unit"
                       width="100" />
      <el-table-column label="是否质检"
                       prop="isQuality"
                       width="100">
                       prop="isQuality">
        <template #default="scope">
          {{scope.row.isQuality ? "是" : "否"}}
          <el-tag :type="scope.row.isQuality == 1 ? 'success' : 'danger'">
            {{scope.row.isQuality == 1 ? '是' : '否' }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column label="操作"
@@ -119,10 +106,6 @@
                     size="small"
                     @click="handleEdit(scope.row)"
                     :disabled="scope.row.isComplete">编辑</el-button>
          <!-- <el-button type="info"
                     link
                     size="small"
                     @click="handleViewParams(scope.row)">参数列表</el-button> -->
          <el-button type="danger"
                     link
                     size="small"
@@ -136,6 +119,7 @@
      <div class="section-header">
        <div class="section-title">工艺路线项目列表</div>
        <div class="section-actions">
          <div class="sort-tip">长按拖拽卡片排序</div>
          <el-button icon="Menu"
                     @click="toggleView"
                     style="margin-right: 10px;">
@@ -159,22 +143,8 @@
              <div class="card-process-name">{{ getProcessName(item.processId) || '-' }}</div>
            </div>
            <!-- äº§å“ä¿¡æ¯ -->
            <div class="card-content">
              <div v-if="item.productName"
                   class="product-info">
                <div class="product-name">{{ item.productName }}</div>
                <div v-if="item.model"
                     class="product-model">
                  {{ item.model }}
                  <!-- <span v-if="item.unit" class="product-unit">{{ item.unit }}</span> -->
                </div>
                <el-tag type="primary"
                        class="product-tag"
                        v-if="item.isQuality">质检</el-tag>
              </div>
              <div v-else
                   class="product-info empty">暂无产品信息</div>
            </div>
            <!-- <div class="card-content">
            </div> -->
            <!-- æ“ä½œæŒ‰é’® -->
            <div class="card-footer">
              <el-button type="primary"
@@ -196,6 +166,154 @@
        </div>
      </div>
    </template>
    <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表格 -->
        <el-table :data="bomTableData"
                  border
                  :preserve-expanded-content="false"
                  :default-expand-all="true"
                  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="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="规格">
                    <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="消耗工序">
                    <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="单位产出所需数量">
                    <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="单位">
                    <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="产品名称"
                           prop="productName" />
          <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>
    <!-- æ–°å¢ž/编辑弹窗 -->
    <el-dialog v-model="dialogVisible"
               :title="operationType === 'add' ? '新增工艺路线项目' : '编辑工艺路线项目'"
@@ -217,27 +335,11 @@
                       :value="process.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="产品名称"
                      prop="productModelId">
          <el-button type="primary"
                     @click="showProductSelectDialog = true">
            {{ form.productName && form.model
              ? `${form.productName} - ${form.model}`
              : '选择产品' }}
          </el-button>
        </el-form-item>
        <el-form-item label="单位"
                      prop="unit">
          <el-input v-model="form.unit"
                    :placeholder="form.productModelId ? '根据选择的产品自动带出' : '请先选择产品'"
                    clearable
                    :disabled="true" />
        </el-form-item>
        <el-form-item label="是否质检"
                      prop="isQuality">
          <el-switch v-model="form.isQuality"
                     :active-value="true"
                     inactive-value="false" />
                     :active-value="1"
                     :inactive-value="0" />
        </el-form-item>
      </el-form>
      <template #footer>
@@ -251,12 +353,18 @@
    <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) : ''} - å‚数列表`"
                            :route-id="routeId"
                            :editable="false"
                            :editable="editable"
                            :order-id="orderId"
                            :process="currentProcess"
                            :page-type="pageType"
                            :param-list="paramList"
                            @refresh="refreshParamList" />
  </div>
@@ -284,10 +392,16 @@
    findProductProcessRouteItemList,
    deleteRouteItem,
    addRouteItem,
    findProcessParamListOrder,
    addOrUpdateProductProcessRouteItem,
    sortRouteItem,
  } from "@/api/productionManagement/productProcessRoute.js";
  import { processList } from "@/api/productionManagement/productionProcess.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";
@@ -298,6 +412,7 @@
  const routeId = computed(() => route.query.id);
  const orderId = computed(() => route.query.orderId);
  const pageType = computed(() => route.query.type);
  const editable = computed(() => route.query.editable === "true");
  const tableLoading = ref(false);
  const tableData = ref([]);
@@ -313,6 +428,7 @@
    productName: "",
    model: "",
    bomNo: "",
    dictLabel: "",
    bomId: null,
    description: "",
  });
@@ -322,6 +438,15 @@
  const showParamListDialog = ref(false);
  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;
@@ -342,14 +467,11 @@
    productName: "",
    model: "",
    unit: "",
    isQuality: false,
    isQuality: 0,
  });
  const rules = {
    processId: [{ required: true, message: "请选择工序", trigger: "change" }],
    productModelId: [
      { required: true, message: "请选择产品", trigger: "change" },
    ],
  };
  // æ ¹æ®å·¥åºID获取工序名称
@@ -401,9 +523,78 @@
      productName: route.query.productName || "",
      model: route.query.model || "",
      bomNo: route.query.bomNo || "",
      dictLabel: route.query.dictLabel || "",
      bomId: route.query.bomId || null,
      description: route.query.description || "",
    };
    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,
                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);
        });
    } 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数据,使用新的接口
  };
  // æ–°å¢ž
@@ -483,12 +674,10 @@
          const addPromise = isOrderPage
            ? addRouteItem({
                productOrderId: orderId.value,
                productRouteId: routeId.value,
                orderId: orderId.value,
                routeId: routeId.value,
                processId: form.value.processId,
                productModelId: form.value.productModelId,
                isQuality: form.value.isQuality,
                dragSort,
              })
            : addOrUpdateProcessRouteItem({
                routeId: routeId.value,
@@ -518,7 +707,6 @@
            ? addOrUpdateProductProcessRouteItem({
                id: form.value.id,
                processId: form.value.processId,
                productModelId: form.value.productModelId,
                isQuality: form.value.isQuality,
              })
            : addOrUpdateProcessRouteItem({
@@ -564,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();
  };
  // åˆå§‹åŒ–拖拽排序
@@ -641,6 +1064,7 @@
        ghostClass: "sortable-ghost",
        handle: ".process-card",
        filter: ".el-button",
        delay: 500, // é•¿æŒ‰500毫秒后开始拖拽
        onEnd: evt => {
          if (evt.oldIndex === evt.newIndex || !tableData.value[evt.oldIndex])
            return;
@@ -711,6 +1135,29 @@
  const handleViewParams = process => {
    currentProcess.value = process;
    // è°ƒç”¨API获取参数列表
    if (pageType.value === "order") {
      findProcessParamListOrder({
        orderId: orderId.value,
        routeItemId: process.id,
        pageNum: 1,
        pageSize: 1000,
      })
        .then(res => {
          if (res.code === 200) {
            paramList.value = res.data || [];
          } else {
            ElMessage.error(res.msg || "获取参数列表失败");
            paramList.value = [];
          }
          showParamListDialog.value = true;
        })
        .catch(err => {
          console.error("获取参数列表失败:", err);
          ElMessage.error("获取参数列表失败");
          paramList.value = [];
          showParamListDialog.value = true;
        });
    } else {
    getProcessParamList({
      routeItemId: process.id,
      pageNum: 1,
@@ -731,12 +1178,34 @@
        paramList.value = [];
        showParamListDialog.value = true;
      });
    }
  };
  // åˆ·æ–°å‚数列表
  const refreshParamList = () => {
    if (!currentProcess.value) return;
    // é‡æ–°è°ƒç”¨API获取参数列表
    if (pageType.value === "order") {
      findProcessParamListOrder({
        orderId: orderId.value,
        routeItemId: currentProcess.value.id,
        pageNum: 1,
        pageSize: 1000,
      })
        .then(res => {
          if (res.code === 200) {
            paramList.value = res.data || [];
          } else {
            ElMessage.error(res.msg || "获取参数列表失败");
            paramList.value = [];
          }
        })
        .catch(err => {
          console.error("获取参数列表失败:", err);
          ElMessage.error("获取参数列表失败");
          paramList.value = [];
        });
    } else {
    getProcessParamList({
      routeItemId: currentProcess.value.id,
      pageNum: 1,
@@ -755,6 +1224,7 @@
        ElMessage.error("获取参数列表失败");
        paramList.value = [];
      });
    }
  };
  onUnmounted(() => {
@@ -765,14 +1235,16 @@
<style scoped>
  .card-container {
    padding: 20px 0;
    /* height: 350px; */
    margin-bottom: 20px;
  }
  .cards-wrapper {
    display: flex;
    gap: 16px;
    gap: 24px;
    overflow-x: auto;
    padding: 10px 0;
    min-height: 200px;
    /* min-height: 250px; */
  }
  .cards-wrapper::-webkit-scrollbar {
@@ -795,11 +1267,12 @@
  .process-card {
    flex-shrink: 0;
    width: 220px;
    /* width: 300px; */
    background: #fff;
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    padding: 16px;
    border-radius: 12px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
    /* padding: 30px 24px; */
    padding: 25px 50px;
    display: flex;
    flex-direction: column;
    cursor: move;
@@ -807,45 +1280,45 @@
  }
  .process-card:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    transform: translateY(-2px);
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
    transform: translateY(-4px);
  }
  .card-header {
    text-align: center;
    margin-bottom: 12px;
    margin-bottom: 20px;
  }
  .card-number {
    width: 36px;
    height: 36px;
    line-height: 36px;
    width: 60px;
    height: 60px;
    line-height: 60px;
    border-radius: 50%;
    background: #409eff;
    color: #fff;
    font-weight: bold;
    font-size: 16px;
    margin: 0 auto 8px;
    font-size: 20px;
    margin: 0 auto 16px;
  }
  .card-process-name {
    font-size: 14px;
    font-size: 18px;
    color: #333;
    font-weight: 500;
    font-weight: 600;
    word-break: break-all;
  }
  .card-content {
    flex: 1;
    margin-bottom: 12px;
    min-height: 60px;
    margin-bottom: 20px;
    min-height: 80px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .product-info {
    font-size: 13px;
    font-size: 14px;
    color: #666;
    text-align: center;
    width: 100%;
@@ -858,7 +1331,7 @@
  }
  .product-name {
    margin-bottom: 6px;
    margin-bottom: 8px;
    word-break: break-all;
    line-height: 1.5;
    text-align: center;
@@ -866,7 +1339,7 @@
  .product-model {
    color: #909399;
    font-size: 12px;
    font-size: 13px;
    word-break: break-all;
    line-height: 1.5;
    text-align: center;
@@ -878,19 +1351,32 @@
  }
  .product-tag {
    margin: 10px 0;
    margin: 12px 0;
  }
  .card-footer {
    display: flex;
    justify-content: space-around;
    padding-top: 12px;
    justify-content: center;
    gap: 20px;
    padding-top: 16px;
    border-top: 1px solid #f0f0f0;
  }
  .card-footer .el-button {
    padding: 0;
    font-size: 12px;
    font-size: 14px;
  }
  .card-footer .el-button:nth-child(1) {
    color: #409eff;
  }
  .card-footer .el-button:nth-child(2) {
    color: #67c23a;
  }
  .card-footer .el-button:nth-child(3) {
    color: #f56c6c;
  }
  :deep(.sortable-ghost) {
@@ -902,13 +1388,13 @@
    opacity: 0.8;
  }
  /* è¡¨æ ¼è§†å›¾æ ·å¼ */
  :deep(.el-table__row) {
  /* è¡¨æ ¼è§†å›¾æ ·å¼ - ä»…应用于项目列表 */
  :deep(.lims-table .el-table__row) {
    transition: background-color 0.2s;
    cursor: move;
  }
  :deep(.el-table__row:hover) {
  :deep(.lims-table .el-table__row:hover) {
    background-color: #f9fafc !important;
  }
@@ -945,6 +1431,12 @@
    display: flex;
    align-items: center;
  }
  .sort-tip {
    font-size: 12px;
    color: #909399;
    margin-left: 8px;
    margin-right: 20px;
  }
  /* å·¥è‰ºè·¯çº¿ä¿¡æ¯å¡ç‰‡æ ·å¼ */
  .route-info-card {
@@ -957,7 +1449,7 @@
  .route-info {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
    gap: 16px;
    padding: 4px;
  }
@@ -1022,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
@@ -38,12 +38,11 @@
                               label="规格">
                <template #default="{ row, $index }">
                  <el-form-item v-if="dataValue.isEdit"
                                :rules="[{ required: true, message: '请选择规格', trigger: ['blur','change'] }]"
                                :rules="[{ required: false, message: '请选择规格', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-select v-model="row.model"
                               placeholder="请选择规格"
                               clearable
                               :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)"
                               :disabled="!dataValue.isEdit"
                               style="width: 100%"
                               @visible-change="(v) => { if (v) openDialog(row.tempId) }">
                      <el-option v-if="row.model"
@@ -57,14 +56,14 @@
                               label="消耗工序">
                <template #default="{ row, $index }">
                  <el-form-item v-if="dataValue.isEdit"
                                :rules="dataValue.dataList.some(item => (item as any).tempId === row.tempId) ? [] : [{ required: true, message: '请选择消耗工序', trigger: 'change' }]"
                                :rules="[{ required: true, message: '请选择消耗工序', trigger: 'change' }]"
                                style="margin: 0">
                    <el-select v-model="row.processId"
                               placeholder="请选择"
                               filterable
                               clearable
                               style="width: 100%"
                               :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)">
                               :disabled="!dataValue.isEdit">
                      <el-option v-for="item in dataValue.processOptions"
                                 :key="item.id"
                                 :label="item.name"
@@ -81,11 +80,10 @@
                                style="margin: 0">
                    <el-input-number v-model="row.unitQuantity"
                                     :min="0"
                                     :precision="2"
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
                                     :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
                                     :disabled="!dataValue.isEdit" />
                  </el-form-item>
                </template>
              </el-table-column>
@@ -102,7 +100,7 @@
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
                                     :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
                                     :disabled="!dataValue.isEdit" />
                  </el-form-item>
                </template>
              </el-table-column>
@@ -115,7 +113,7 @@
                    <el-input v-model="row.unit"
                              placeholder="请输入单位"
                              clearable
                              :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
                              :disabled="!dataValue.isEdit" />
                  </el-form-item>
                </template>
              </el-table-column>
@@ -123,7 +121,7 @@
                               fixed="right"
                               width="200">
                <template #default="{ row, $index }">
                  <el-button v-if="dataValue.isEdit && !dataValue.dataList.some(item => (item as any).tempId === row.tempId)"
                  <el-button v-if="dataValue.isEdit"
                             type="danger"
                             text
                             @click="removeItem(row.tempId)">删除
@@ -141,13 +139,21 @@
      </el-table-column>
      <el-table-column label="BOM编号"
                       prop="bomNo" />
      <el-table-column label="产品编码"
                       prop="productCode" />
      <el-table-column label="产品名称"
                       prop="productName" />
      <el-table-column label="规格型号"
                       prop="model" />
      <el-table-column label="产品类型"
                       prop="dictLabel" />
    </el-table>
    <div v-if="dataValue.isEdit"
         style="text-align: center;border: 1px solid #e4e7ed;padding: 10px;transition: all 0.3s ease;cursor: pointer;"
         :class="{'hover-effect': dataValue.isEdit}">
      <el-button type="primary"
                 text
                 @click="addItem">
        <el-icon style="vertical-align: middle;margin-right: 5px;">
          <Plus />
        </el-icon>
        æ·»åŠ 
      </el-button>
    </div>
    <product-select-dialog v-if="dataValue.showProductDialog"
                           v-model:model-value="dataValue.showProductDialog"
                           single
@@ -194,11 +200,8 @@
  // ä»Žè·¯ç”±å‚数获取产品信息
  const routeBomNo = computed(() => route.query.bomNo || "");
  const routeProductCode = computed(() => route.query.productCode || "");
  const routeProductName = computed(() => route.query.productName || "");
  const routeProductModelName = computed(
    () => route.query.productModelName || ""
  );
  const routeDictLabel = computed(() => route.query.dictLabel || "");
  const routeOrderId = computed(() => route.query.orderId);
  const pageType = computed(() => route.query.type);
  const isOrderPage = computed(
@@ -218,9 +221,8 @@
  const tableData = reactive([
    {
      productName: "",
      model: "",
      bomNo: "",
      dictLabel: "",
    },
  ]);
@@ -332,12 +334,7 @@
    const validateItem = (item: any, isTopLevel = false) => {
      console.log(item, "item");
      // æ ¡éªŒå½“前项的必填字段
      if (!item.model) {
        ElMessage.error("请选择规格");
        isValid = false;
        return;
      }
      if (!isTopLevel && !item.processId) {
      if (!item.processId) {
        ElMessage.error("请选择消耗工序");
        isValid = false;
        return;
@@ -431,6 +428,23 @@
      }
    });
  };
  const addItem = () => {
    dataValue.dataList.push({
      parentId: "",
      parentTempId: "",
      productName: "",
      productId: "",
      model: undefined,
      productModelId: undefined,
      processId: "",
      processName: "",
      unitQuantity: 0,
      demandedQuantity: 0,
      unit: "",
      children: [],
      tempId: new Date().getTime(),
    });
  };
  const addItem2 = tempId => {
    dataValue.dataList.map(item => {
      if (item.tempId === tempId) {
@@ -510,9 +524,8 @@
  onMounted(async () => {
    // ä»Žè·¯ç”±å‚数回显数据
    tableData[0].productName = routeProductName.value as string;
    tableData[0].model = routeProductModelName.value as string;
    tableData[0].bomNo = routeBomNo.value as string;
    tableData[0].dictLabel = routeDictLabel.value as string;
    // è®¢å•情况下禁用编辑
    if (isOrderPage.value) {
@@ -524,3 +537,12 @@
    await fetchData();
  });
</script>
<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/StructureEdit.vue
@@ -38,7 +38,7 @@
                <template #default="{ row, $index }">
                  <el-form-item v-if="dataValue.isEdit"
                                :prop="`dataList.${$index}.model`"
                                :rules="[{ required: true, message: '请选择规格', trigger: ['blur','change'] }]"
                                :rules="[{ required: false, message: '请选择规格', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-select v-model="row.model"
                               placeholder="请选择产品"
src/views/productionManagement/productStructure/index.vue
@@ -567,8 +567,7 @@
      query: {
        id: row.id,
        bomNo: row.bomNo || "",
        productName: row.productName || "",
        productModelName: row.productModelName || "",
        dictLabel: row.dictLabel || "",
      },
    });
  };
src/views/productionManagement/productionOrder/index.vue
@@ -3,16 +3,8 @@
    <div class="search_form">
      <el-form :model="searchForm"
               :inline="true">
        <el-form-item label="客户名称:">
          <el-input v-model="searchForm.customerName"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 160px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="合同号:">
          <el-input v-model="searchForm.salesContractNo"
        <el-form-item label="订单号:">
          <el-input v-model="searchForm.npsNo"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
@@ -20,31 +12,67 @@
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="产品名称:">
          <el-input v-model="searchForm.productCategory"
          <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="规格:">
          <el-input v-model="searchForm.specificationModel"
          <el-input v-model="searchForm.model"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 160px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="状态:">
          <el-select v-model="searchForm.status"
                     placeholder="请选择"
                     style="width: 160px;"
                     @change="handleQuery">
            <el-option v-for="item in statusOptions"
                       :key="item.value"
                       :label="item.label"
                       :value="item.value" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
          <el-button type="primary"
                     @click="handleReset">重置</el-button>
          <el-button type="danger"
                     @click="handleDelete">退回</el-button>
          <el-button @click="handleOut">导出</el-button>
        </el-form-item>
      </el-form>
      <div>
        <el-button type="primary" @click="isShowNewModal = true">新增</el-button>
        <el-button type="danger" @click="handleDelete">删除</el-button>
        <el-button @click="handleOut">导出</el-button>
      </div>
      <!-- <div style="width:350px;text-align:right;">
      </div> -->
    </div>
    <div class="table_list">
      <PIMTable rowKey="id"
@@ -54,14 +82,19 @@
                :tableLoading="tableLoading"
                :row-class-name="tableRowClassName"
                :isSelection="true"
                :selectable="row => row.status != 4"
                @selection-change="handleSelectionChange"
                @pagination="pagination">
        <template #completionStatus="{ row }">
          <el-progress
            :percentage="toProgressPercentage(row?.completionStatus)"
          <el-progress :percentage="toProgressPercentage(row?.completionStatus)"
            :color="progressColor(toProgressPercentage(row?.completionStatus))"
            :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''"
          />
                       :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>
@@ -90,10 +123,130 @@
        </span>
      </template>
    </el-dialog>
    <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>
@@ -102,60 +255,108 @@
  import { ElMessageBox } from "element-plus";
  import dayjs from "dayjs";
  import { useRouter } from "vue-router";
  import { getDicts } from "@/api/system/dict/data";
  import {
    productOrderListPage,
    listProcessRoute,
    bindingRoute,
    listProcessBom, delProductOrder,
    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";
  import {fileDel} from "@/api/financialManagement/revenueManagement.js";
  import PIMTable from "@/components/PIMTable/PIMTable.vue";
  const NewProductOrder = defineAsyncComponent(() => import("@/views/productionManagement/productionOrder/New.vue"));
  const NewProductOrder = defineAsyncComponent(() =>
    import("@/views/productionManagement/productionOrder/New.vue")
  );
  const { proxy } = getCurrentInstance();
  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([
    {
      label: "状态",
      prop: "status",
      dataType: "tag",
      formatData: val => {
        const statusMap = {
          1: "待开始",
          2: "进行中",
          3: "已完成",
          4: "已取消",
        };
        return statusMap[val] || "";
      },
      formatType: val => {
        const statusMap = {
          1: "error",
          2: "warning",
          3: "success",
          4: "info",
        };
        return statusMap[val] || "info";
      },
      width: 100,
    },
    {
      label: "生产订单号",
      prop: "npsNo",
      width: '120px',
    },
    {
      label: "销售合同号",
      prop: "salesContractNo",
      width: '150px',
    },
    {
      label: "客户名称",
      prop: "customerName",
      width: '200px',
      width: "150px",
    },
    {
      label: "产品名称",
      prop: "productCategory",
      width: '120px',
      prop: "productName",
      width: "120px",
      dataType: "tag",
    },
    {
      label: "规格",
      prop: "specificationModel",
      width: '120px',
      prop: "model",
      width: "120px",
    },
    {
      label: "强度",
      prop: "strength",
      width: "120px",
      dataType: "tag",
    },
    {
      label: "物料编码",
      prop: "materialCode",
      width: "120px",
    },
    {
      label: "工艺路线编号",
      prop: "processRouteCode",
      width: '200px',
      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",
@@ -178,17 +379,31 @@
    },
    {
      label: "交付日期",
      prop: "deliveryDate",
      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,
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 200,
      width: 300,
      operation: [
        {
          name: "来源",
          type: "text",
          clickFun: row => {
            showSourceData(row);
          },
        },
        {
          name: "工艺路线",
          type: "text",
@@ -199,18 +414,26 @@
        {
          name: "绑定工艺路线",
          type: "text",
          showHide: row => !row.processRouteCode,
          showHide: row => !row.routeId,
          clickFun: row => {
            openBindRouteDialog(row);
          },
        },
        {
          name: "产品结构",
          name: "删除",
          type: "text",
          showHide: row => row.status == 4,
          clickFun: row => {
            showProductStructure(row);
            handleDeleteSolo(row);
          },
        },
        // {
        //   name: "产品结构",
        //   type: "text",
        //   clickFun: row => {
        //     showProductStructure(row);
        //   },
        // },
      ],
    },
  ]);
@@ -223,13 +446,168 @@
  });
  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: "",
      salesContractNo: "",
      projectName: "",
      productCategory: "",
      specificationModel: "",
      productName: "",
      model: "",
      dictCode: null,
      startTime: null,
      endTime: null,
      strength: null,
      status: "",
    },
  });
  const { searchForm } = toRefs(data);
@@ -253,19 +631,18 @@
  // æ·»åŠ è¡¨è¡Œç±»åæ–¹æ³•
  const tableRowClassName = ({ row }) => {
    if (!row.deliveryDate) return '';
    if (row.isFh) return '';
    const diff = row.deliveryDaysDiff;
    if (diff === 15) {
      return 'yellow';
    } else if (diff === 10) {
      return 'pink';
    } else if (diff === 2) {
      return 'purple';
    } else if (diff < 2) {
      return 'red';
    }
    // if (!row.planCompleteTime) return "";
    // if (row.isFh) return "";
    // const diff = row.deliveryDaysDiff;
    // if (diff === 15) {
    //   return "yellow";
    // } else if (diff === 10) {
    //   return "pink";
    // } else if (diff === 2) {
    //   return "purple";
    // } else if (diff < 2) {
    //   return "red";
    // }
  };
  // ç»‘定工艺路线弹框
@@ -273,6 +650,7 @@
  const bindRouteLoading = ref(false);
  const bindRouteSaving = ref(false);
  const routeOptions = ref([]);
  const productTypeOptions = ref([]);
  const bindForm = reactive({
    orderId: null,
    routeId: null,
@@ -283,15 +661,32 @@
    bindForm.routeId = null;
    bindRouteDialogVisible.value = true;
    routeOptions.value = [];
    if (!row.productModelId) {
    if (!row.model) {
      proxy.$modal.msgWarning("当前订单缺少产品型号,无法查询工艺路线");
      bindRouteDialogVisible.value = false;
      return;
    }
    bindRouteLoading.value = true;
    const distName =
      row.productName == "板材"
        ? row.productName
        : row.productName + "-" + row.strength;
    try {
      const res = await listProcessRoute({ productModelId: row.productModelId });
      routeOptions.value = res.data || [];
      // èŽ·å–äº§å“ç±»åž‹å­—å…¸
      const dictRes = await getDicts("product_type");
      if (dictRes.code === 200) {
        productTypeOptions.value = dictRes.data;
        // ç”¨distName匹配dictLabel,获取dictCode
        const matchedType = productTypeOptions.value.find(
          item => item.dictLabel === distName
        );
        const dictCode = matchedType ? matchedType.dictCode : row.productType;
        // ä½¿ç”¨dictCode查询工艺路线列表
        const res = await listPage({ dictCode, status: true });
        routeOptions.value = res.data.records || [];
      }
    } catch (e) {
      console.error("获取工艺路线列表失败:", e);
      proxy.$modal.msgError("获取工艺路线列表失败");
@@ -321,7 +716,27 @@
      bindRouteSaving.value = false;
    }
  };
  const statusOptions = ref([
    { value: 1, label: "待开始" },
    { value: 2, label: "进行中" },
    { value: 3, label: "已完成" },
    { value: 4, label: "已取消" },
  ]);
  const handleReset = () => {
    searchForm.value = {
      customerName: "",
      salesContractNo: "",
      projectName: "",
      productName: "",
      model: "",
      status: "",
      strength: null,
      startTime: null,
      endTime: null,
    };
    createTime.value = [];
    handleQuery();
  };
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  const handleQuery = () => {
@@ -343,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;
@@ -362,23 +791,23 @@
  const showRouteItemModal = async row => {
    const orderId = row.id;
    try {
      const res = await getOrderProcessRouteMain(orderId);
      const data = res.data || {};
      if (!data || !data.id) {
        proxy.$modal.msgWarning("未找到关联的工艺路线");
        return;
      }
      router.push({
        path: "/productionManagement/processRouteItem",
        query: {
          id: data.id,
          processRouteCode: data.processRouteCode || "",
          productName: data.productName || "",
          model: data.model || "",
          bomNo: data.bomNo || "",
          description: data.description || "",
          orderId,
          id: row.routeId,
          processRouteCode: row.processRouteCode || "",
          productName: row.productName || "",
          model: row.model || "",
          bomNo: row.bomNo || "",
          bomId: row.bomId || "",
          description: row.description || "",
          dictLabel:
            row.productName == "板材"
              ? row.productName
              : row.productName + "-" + row.strength,
          orderId: row.id,
          type: "order",
          editable: true,
        },
      });
    } catch (e) {
@@ -388,42 +817,126 @@
  };
  const showProductStructure = row => {
    if (!row.processRouteCode) {
      proxy.$modal.msgWarning("请先绑定工艺路线");
      return;
    }
    router.push({
      path: "/productionManagement/productStructureDetail",
      query: {
        id: row.id,
        bomNo: row.bomNo || "",
        productName: row.productCategory || "",
        productModelName: row.specificationModel || "",
        productName: row.productName || "",
        productModelName: row.model || "",
        orderId: row.id,
        type: "order",
      },
    });
  };
  // é€‰æ‹©ç”³è¯·å•
  const selectApplyNo = item => {
    selectedApplyNo.value = item.applyNo;
    selectedSourceData.value = item;
  };
  // æŸ¥çœ‹æ¥æºç”Ÿäº§è®¡åˆ’数据
  const showSourceData = row => {
    // å­˜å‚¨ç‚¹å‡»æ¥æºæŒ‰é’®æ—¶ä¼ é€’çš„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 {
            selectedApplyNo.value = "";
            selectedSourceData.value = null;
          }
          // æ‰“开弹窗
          sourceDataDialogVisible.value = true;
        } else {
          proxy.$modal.msgError(res.msg || "获取来源数据失败");
        }
      })
      .catch(err => {
        proxy.$modal.msgError("获取来源数据失败");
        console.error(err);
      });
  };
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = (selection) => {
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
  };
  const handleDeleteSolo = row => {
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        delProductOrder(row.id).then(res => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
  const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
      ids = selectedRows.value.map((item) => item.id);
      ids = selectedRows.value.map(item => item.id);
    } else {
      proxy.$modal.msgWarning("请选择数据");
      return;
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
    ElMessageBox.confirm("选中的内容将被退回,是否确认退回?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    }).then(() => {
      delProductOrder(ids).then((res) => {
        proxy.$modal.msgSuccess("删除成功");
    })
      .then(() => {
        revokeProductOrder(ids).then(res => {
          proxy.$modal.msgSuccess("退回成功");
        getList();
      });
    }).catch(() => {
      })
      .catch(() => {
      proxy.$modal.msg("已取消");
    });
  };
@@ -436,7 +949,11 @@
      type: "warning",
    })
      .then(() => {
        proxy.download("/productOrder/export", {...searchForm.value}, "生产订单.xlsx");
        proxy.download(
          "/productOrder/export",
          { ...searchForm.value },
          "生产订单.xlsx"
        );
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
@@ -444,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>
@@ -456,11 +987,11 @@
}
::v-deep .yellow {
  background-color: #FAF0DE;
    background-color: #faf0de;
}
::v-deep .pink {
  background-color: #FAE1DE;
    background-color: #fae1de;
}
::v-deep .red {
@@ -468,6 +999,236 @@
}
::v-deep .purple{
  background-color: #F4DEFA;
    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/productionProcess/index.vue
@@ -51,9 +51,9 @@
                  {{ process.status ? '启用' : '停用' }}
                </el-tag>
                <el-tag size="small"
                        :type="process.isQuality ? 'warning' : 'info'"
                        :type="process.isQuality == 1 ? 'warning' : 'info'"
                        style="margin-left: 8px">
                  {{ process.isQuality ? '质检' : '非质检' }}
                  {{ process.isQuality == 1 ? '质检' : '非质检' }}
                </el-tag>
              </div>
              <span class="param-count">工资定额: Â¥{{ process.salaryQuota || 0 }}</span>
@@ -119,8 +119,8 @@
        <el-form-item label="是否质检"
                      prop="isQuality">
          <el-switch v-model="processForm.isQuality"
                     :active-value="true"
                     inactive-value="false" />
                     :active-value="1"
                     :inactive-value="0" />
        </el-form-item>
        <el-form-item label="工序描述"
                      prop="remark">
@@ -249,8 +249,8 @@
            </el-form-item>
            <el-form-item label="是否必填">
              <el-switch v-model="selectedParam.isRequired"
                         :active-value="1"
                         :inactive-value="0" />
                         :active-value="true"
                         :inactive-value="false" />
            </el-form-item>
          </el-form>
          <el-empty v-else
@@ -313,8 +313,8 @@
        <el-form-item label="是否必填"
                      prop="isRequired">
          <el-switch v-model="editParamForm.isRequired"
                     :active-value="1"
                     :inactive-value="0" />
                     :active-value="true"
                     :inactive-value="false" />
        </el-form-item>
      </el-form>
      <template #footer>
@@ -368,7 +368,7 @@
    no: "",
    name: "",
    salaryQuota: null,
    isQuality: false,
    isQuality: 0,
    remark: "",
    status: true,
  });
@@ -418,7 +418,7 @@
    minValue: null,
    maxValue: null,
    sort: 1,
    isRequired: 0,
    isRequired: false,
    tenantId: 1,
  });
  const editParamRules = {
@@ -562,8 +562,8 @@
      label: "是否必填",
      prop: "isRequired",
      dataType: "tag",
      formatType: row => (row.isRequired === 1 ? "success" : "info"),
      formatData: row => (row.isRequired === 1 ? "是" : "否"),
      formatType: row => (row.isRequired === true ? "success" : "info"),
      formatData: row => (row.isRequired === true ? "是" : "否"),
    },
    {
      label: "操作",
@@ -626,7 +626,7 @@
    processForm.no = "";
    processForm.name = "";
    processForm.salaryQuota = null;
    processForm.isQuality = false;
    processForm.isQuality = 0;
    processForm.remark = "";
    processForm.status = true;
    processDialogVisible.value = true;
@@ -638,7 +638,7 @@
    processForm.no = process.no;
    processForm.name = process.name;
    processForm.salaryQuota = process.salaryQuota;
    processForm.isQuality = process.isQuality || false;
    processForm.isQuality = process.isQuality || 0;
    processForm.remark = process.remark || "";
    processForm.status = process.status;
    processDialogVisible.value = true;
@@ -797,7 +797,7 @@
    editParamForm.minValue = row.minValue;
    editParamForm.maxValue = row.maxValue;
    editParamForm.sort = row.sort || 1;
    editParamForm.isRequired = row.isRequired || 0;
    editParamForm.isRequired = row.isRequired || false;
    editParamForm.tenantId = 1;
    editParamDialogVisible.value = true;
  };
@@ -830,7 +830,7 @@
      standardValue: selectedParam.value.standardValue,
      minValue: selectedParam.value.minValue,
      maxValue: selectedParam.value.maxValue,
      isRequired: selectedParam.value.isRequired || 0,
      isRequired: selectedParam.value.isRequired || false,
      tenantId: 1,
    })
      .then(() => {
@@ -990,17 +990,52 @@
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
    display: flex;
    flex-direction: column;
    min-width: 0;
  }
  .param-table-wrapper {
    flex: 1;
    padding: 0 20px 20px;
    overflow: auto;
    min-width: 100%;
  }
  /* è¡¨æ ¼æ¨ªå‘滚动 */
  :deep(.el-table) {
    // min-width: 800px;
  .param-table-wrapper :deep(.el-table) {
    min-width: 100%;
  }
  .param-table-wrapper :deep(.el-table__body-wrapper) {
    overflow-x: auto;
  }
  .pagination-container {
    margin-top: 10px;
    overflow-x: auto;
    padding-bottom: 8px;
  }
  .pagination-container .el-pagination {
    white-space: nowrap;
  }
  /* å“åº”式调整 */
  @media screen and (max-width: 768px) {
    .pagination-container {
      font-size: 12px;
    }
    .pagination-container .el-pagination__sizes {
      margin-right: 8px;
    }
    .pagination-container .el-pagination__jump {
      margin-left: 8px;
    }
    .pagination-container .el-pagination__page-size {
      font-size: 12px;
    }
  }
  .empty-tip {
src/views/productionManagement/productionReporting/components/ReportingDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,588 @@
<template>
  <el-dialog v-model="localVisible"
             :title="dialogTitle"
             width="800px"
             @close="handleClose">
    <!-- æ­¥éª¤æ¡ -->
    <el-steps :active="activeStep"
              finish-status="success">
      <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 + ''">
          <div>
            <!-- å‚数组列表 -->
            <div v-for="(group, groupIndex) in form.paramGroups[process.id] || []"
                 :key="groupIndex"
                 class="param-group">
              <div class="group-header">
                <span>参数组 {{ groupIndex + 1 }}</span>
                <el-button type="danger"
                           size="small"
                           @click="removeParamGroup(process.id, groupIndex)"
                           v-if="(form.paramGroups[process.id] || []).length > 1">
                  åˆ é™¤
                </el-button>
              </div>
              <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'">
                    <!-- æ•°å­—类型 -->
                    <div style="display: flex; align-items: center; gap: 8px;">
                      <el-input-number v-model="group[param.id]"
                                       controls-position="right"
                                       :precision="getPrecision(param.paramFormat)"
                                       style="flex: 1" />
                      <span v-if="param.unit && param.unit != '/'">
                        {{ param.unit }}
                      </span>
                    </div>
                  </template>
                  <template v-else-if="param.paramType == '2'">
                    <!-- æ–‡æœ¬ç±»åž‹ -->
                    <div style="display: flex; align-items: center; gap: 8px;">
                      <el-input v-model="group[param.id]"
                                style="flex: 1" />
                      <span v-if="param.unit && param.unit != '/'">
                        {{ param.unit }}
                      </span>
                    </div>
                  </template>
                  <template v-else-if="param.paramType == '3'">
                    <!-- å­—典类型 -->
                    <div style="display: flex; align-items: center; gap: 8px;">
                      <el-select v-model="group[param.id]"
                                 placeholder="请选择"
                                 style="flex: 1;width: 150px">
                        <el-option v-for="option in dictOptions[param.paramFormat] || []"
                                   :key="option.dictValue"
                                   :label="option.dictLabel"
                                   :value="option.dictValue" />
                      </el-select>
                      <span v-if="param.unit && param.unit != '/'">
                        {{ param.unit }}
                      </span>
                    </div>
                  </template>
                  <template v-else-if="param.paramType == '4'">
                    <!-- æ—¥æœŸç±»åž‹ -->
                    <div style="display: flex; align-items: center; gap: 8px;">
                      <el-date-picker :value-format="param.paramFormat"
                                      :format="param.paramFormat"
                                      :type="param.paramFormat=='YYYY-MM-DD'?'daterange':'datetimerange'"
                                      v-model="group[param.id]"
                                      style="flex: 1" />
                      <span v-if="param.unit && param.unit != '/'">
                        {{ param.unit }}
                      </span>
                    </div>
                  </template>
                  <template v-else>
                    <!-- å…¶ä»–类型 -->
                    <div style="display: flex; align-items: center; gap: 8px;">
                      <el-input v-model="group[param.id]"
                                style="flex: 1" />
                      <span v-if="param.unit && param.unit != '/'">
                        {{ param.unit }}
                      </span>
                    </div>
                  </template>
                </el-form-item>
              </el-form>
            </div>
            <!-- æ–°å¢žå‚数组按钮 -->
            <el-button type="primary"
                       size="small"
                       @click="addParamGroup(process.id)">
              æ–°å¢žå‚数组
            </el-button>
          </div>
        </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>
    <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"
                   @click="handleNextStep">下一步</el-button>
        <el-button type="primary"
                   v-if="activeStep === 3"
                   :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(),
    paramGroups: props.data.paramGroups || {}, // å­˜å‚¨æ¯ä¸ªå·¥åºçš„参数组
  });
  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 || [];
        // åˆå§‹åŒ–参数组
        if (!form.paramGroups[processId]) {
          form.paramGroups[processId] = [];
        }
        // å¦‚果没有参数组,添加一个默认参数组
        if (form.paramGroups[processId].length === 0) {
          const defaultGroup = {};
          for (const param of params.value) {
            defaultGroup[param.id] = param.standardValue || "";
            // å¦‚果是字典类型参数,获取字典数据
            if (param.paramType == "3" && param.paramFormat) {
              await getDictOptions(param.paramFormat);
            }
          }
          form.paramGroups[processId].push(defaultGroup);
        }
      }
    );
  };
  // èŽ·å–å°æ•°ç²¾åº¦
  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 addParamGroup = processId => {
    if (!form.paramGroups[processId]) {
      form.paramGroups[processId] = [];
    }
    // åˆ›å»ºä¸€ä¸ªæ–°çš„参数组,使用默认值
    const newGroup = {};
    params.value.forEach(param => {
      newGroup[param.id] = param.standardValue || "";
    });
    form.paramGroups[processId].push(newGroup);
  };
  // åˆ é™¤å‚数组
  const removeParamGroup = (processId, index) => {
    if (form.paramGroups[processId] && form.paramGroups[processId].length > 1) {
      form.paramGroups[processId].splice(index, 1);
    }
  };
  // åˆå§‹åŒ–
  const init = () => {
    // æ— è®ºæ–°å¢žè¿˜æ˜¯ç¼–辑,都加载订单列表
    loadOrders();
    if (props.data.id) {
      // ç¼–辑时设置表单数据
      Object.assign(form, props.data);
      // è®¾ç½®orderId
      orderId.value = props.data.orderId || "";
      // å¦‚果有订单ID,加载工序和参数
      if (props.data.orderId) {
        // æ¨¡æ‹Ÿé€‰æ‹©è®¢å•的操作,触发数据加载
        setTimeout(() => {
          handleOrderChange(props.data.orderId);
        }, 100);
      }
    } else {
      // æ–°å¢žæ—¶è®¾ç½®é»˜è®¤å€¼
      form.createBy = "当前登录人";
      form.createTime = new Date();
    }
    // å§‹ç»ˆä»Žç¬¬ä¸€æ­¥å¼€å§‹
    activeStep.value = 0;
  };
  // ç›‘听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;
  }
  .param-group {
    border: 1px solid #e4e7ed;
    border-radius: 4px;
    padding: 16px;
    margin-bottom: 16px;
    background-color: #f9f9f9;
  }
  .group-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 16px;
    padding-bottom: 8px;
    border-bottom: 1px solid #e4e7ed;
  }
  .group-header span {
    font-weight: bold;
    font-size: 14px;
  }
</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
@@ -3,36 +3,9 @@
    <div class="search_form">
      <el-form :model="searchForm"
               :inline="true">
        <el-form-item label="客户名称:">
          <el-input v-model="searchForm.customerName"
                    placeholder="请输入"
                    clearable
                    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 label="产品规格:">
          <el-input v-model="searchForm.model"
                    placeholder="请输入"
                    clearable
                    style="width: 160px;"
                    @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item label="物料编码:">
          <el-input v-model="searchForm.materialCode"
                    placeholder="请输入"
                    clearable
                    style="width: 160px;"
                    @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item label="申请单编号:">
          <el-input v-model="searchForm.applyNo"
                    placeholder="请输入"
                    clearable
                    style="width: 160px;"
@@ -62,6 +35,37 @@
                       value="2" />
          </el-select>
        </el-form-item>
        <!-- å±•开版搜索条件 -->
        <template v-if="searchFormExpanded">
          <el-form-item label="客户名称:">
            <el-input v-model="searchForm.customerName"
                      placeholder="请输入"
                      clearable
                      style="width: 160px;"
                      @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="产品规格:">
            <el-input v-model="searchForm.model"
                      placeholder="请输入"
                      clearable
                      style="width: 160px;"
                      @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="物料编码:">
            <el-input v-model="searchForm.materialCode"
                      placeholder="请输入"
                      clearable
                      style="width: 160px;"
                      @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="申请单编号:">
            <el-input v-model="searchForm.applyNo"
                      placeholder="请输入"
                      clearable
                      style="width: 160px;"
                      @keyup.enter="handleQuery" />
          </el-form-item>
        </template>
        <el-form-item>
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
@@ -82,6 +86,16 @@
      <div>
      </div>
    </div>
    <div class="search-header">
      <el-button type="text"
                 @click="toggleSearchForm">
        <el-icon>
          <ArrowUp v-if="searchFormExpanded" />
          <ArrowDown v-else />
        </el-icon>
        {{ searchFormExpanded ? '收起搜索条件' : '展开搜索条件' }}
      </el-button>
    </div>
    <div class="table_list">
      <PIMTable rowKey="id"
                :column="tableColumn"
@@ -93,10 +107,17 @@
                :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>
    <!-- åˆå¹¶ä¸‹å‘弹窗 -->
    <el-dialog v-model="isShowNewModal"
               destroy-on-close
               title="合并下发"
               width="600px">
      <el-form :model="mergeForm"
@@ -168,6 +189,7 @@
    </el-dialog>
    <!-- è¿½è¸ªè¿›åº¦å¼¹çª— -->
    <el-dialog v-model="showTrackProgressDialog"
               destroy-on-close
               :title="`追踪进度 - ${trackProgressForm.materialCode || ''}`"
               width="600px">
      <el-form :model="trackProgressForm"
@@ -246,6 +268,7 @@
                  @close="handleImportClose" />
    <!-- æ–°å¢ž/编辑弹窗 -->
    <el-dialog v-model="dialogVisible"
               destroy-on-close
               :title="operationType === 'add' ? '新增生产计划' : '编辑生产计划'"
               width="600px">
      <el-form ref="formRef"
@@ -366,6 +389,7 @@
<script setup>
  import { onMounted, ref, reactive, getCurrentInstance, toRefs } from "vue";
  import { ElMessage } from "element-plus";
  import { ArrowUp, ArrowDown } from "@element-plus/icons-vue";
  import dayjs from "dayjs";
  import ImportDialog from "@/components/Dialog/ImportDialog.vue";
  import { getToken } from "@/utils/auth";
@@ -441,14 +465,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: "下发状态",
@@ -896,8 +924,14 @@
      applyNo: "",
      dateRange: [],
    },
    searchFormExpanded: false,
  });
  const { searchForm } = toRefs(data);
  const { searchForm, searchFormExpanded } = toRefs(data);
  // åˆ‡æ¢æœç´¢è¡¨å•展开/收起状态
  const toggleSearchForm = () => {
    data.searchFormExpanded = !data.searchFormExpanded;
  };
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
@@ -1042,7 +1076,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;
@@ -1087,10 +1121,12 @@
    }
    console.log(mergeForm, "mergeForm");
    const strengthItem = block_strength.value.find(item => item.id === mergeForm.strength);
    const strengthItem = block_strength.value.find(
      item => item.id === mergeForm.strength
    );
    const payload = {
      ...mergeForm,
      strength: strengthItem ? strengthItem.label : mergeForm.strength
      strength: strengthItem ? strengthItem.label : mergeForm.strength,
    };
    productionPlanCombine(payload)
      .then(res => {
@@ -1323,10 +1359,7 @@
  }
  .search_form {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 24px;
    // margin-bottom: 24px;
    padding: 20px;
    background-color: #ffffff;
    border-radius: 6px;
@@ -1338,6 +1371,36 @@
    }
  }
  .search-header {
    display: flex;
    justify-content: center;
    align-items: center;
    // margin-bottom: 5px;
    // padding-bottom: 5px;
    position: relative;
    bottom: 35px;
    // border-bottom: 1px solid #ebeef5;
  }
  .search-title {
    font-size: 16px;
    font-weight: 600;
    color: #303133;
  }
  .search-header .el-button {
    color: #606266;
    transition: all 0.3s ease;
  }
  .search-header .el-button:hover {
    color: #409eff;
  }
  .search-header .el-icon {
    margin-right: 4px;
  }
  .table_list {
    // margin-bottom: 24px;
    background-color: #ffffff;
@@ -1345,6 +1408,7 @@
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
    overflow: hidden;
    height: calc(100vh - 250px);
    margin-top: 0px !important;
  }
  :deep(.el-table) {