廖井涛
2025-04-28 d648ebc69905befd409cac296dc4b6c7e8bd1d7b
添加第三方优化对接模块
7个文件已修改
27个文件已添加
7537 ■■■■■ 已修改文件
north-glass-erp/northglass-erp/src/router/index.js 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/GlassComputed.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/page/Compute.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/GlassComputed.vue 328 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/Optimization.vue 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/OptimizationRectPrint.vue 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/Optimize.vue 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/OptimizeControl.vue 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/OptimizeMove.vue 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/OptimizeParms.vue 522 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/OptimizePrint.vue 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/OptimizeProject.vue 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/ProjectCreate.vue 391 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/ProjectMange.vue 702 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/CheckInventory.vue 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/Compute.vue 426 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/ComputeCard.vue 185 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/ComputeDetail.vue 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/GlassInventory.vue 322 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/OptimizationRect.vue 1122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/OptimizeCompute.vue 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/ProcessCard.vue 374 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/ProcessCardDetail.vue 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/ProjectDetail.vue 611 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/ProjectList.vue 281 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/RectRenderer.vue 341 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/SetAmount.vue 217 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/SetTrimming.vue 205 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/src/main/java/com/example/erp/controller/pp/GlassOptimizeController.java 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/src/main/java/com/example/erp/entity/pp/OptimizeDetail.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/src/main/java/com/example/erp/entity/pp/OptimizeHeatDetail.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/src/main/java/com/example/erp/mapper/pp/GlassOptimizeMapper.java 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/src/main/java/com/example/erp/service/pp/GlassOptimizeService.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/src/main/resources/mapper/pp/GlassOptimize.xml 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/router/index.js
@@ -1232,6 +1232,53 @@
          ]
        },
        {
          //第三方玻璃优化
          path: 'glassOptimizeThirdParty',
          name: 'glassOptimizeThirdParty',
          component: () => import('../views/pp/glassOptimizeThirdParty/Optimize.vue'),
          children:[
            {
              path: 'optimizeProject',
              name: 'optimizeProject',
              component: () => import('../views/pp/glassOptimizeThirdParty/OptimizeProject.vue'),
            },
            {
              path: 'optimizePrint',
              name: 'optimizePrint',
              component: () => import('../views/pp/glassOptimizeThirdParty/OptimizePrint.vue'),
            },
            {
              path: 'OptimizationRectPrint',
              name: 'OptimizationRectPrint',
              component: () => import('../views/pp/glassOptimizeThirdParty/OptimizationRectPrint.vue'),
            },
            {
              path: 'OptimizeControl',
              name: 'OptimizeControl',
              component: () => import('../views/pp/glassOptimizeThirdParty/OptimizeControl.vue'),
            },
            {
              path: 'Optimization',
              name: 'Optimization',
              component: () => import('../views/pp/glassOptimizeThirdParty/Optimization.vue'),
            },
            {
              path: 'optimizeparms',
              name: 'optimizeparms',
              component: () => import('../views/pp/glassOptimizeThirdParty/OptimizeParms.vue'),
            },
            {
              path: '/optimizeProject/:projectNo/:thickNess/:model',
              name: 'optimizeInfo',
              component: () => import('../views/pp/glassOptimizeThirdParty/OptimizeProject.vue')
            },
            {
              path: '',
              redirect:'/main/glassOptimizeThirdParty/optimizeProject'
            }
          ]
        },
        {
          path: '',
          redirect:'/main/order'
        }
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/GlassComputed.vue
@@ -256,8 +256,8 @@
              "order_number": 33,
              "process_id": "NG24120028A005",
              "technology_number": 2,
              "tempering_feed_sequence": 14,
              "tempering_layout_id": 15,
              "tempering_feed_sequence": 1,
              "tempering_layout_id": 1,
              "width": 814,
              "x_coordinate": 1582.0,
              "y_coordinate": 3768.0
@@ -270,8 +270,8 @@
              "order_number": 33,
              "process_id": "NG24120028A005",
              "technology_number": 2,
              "tempering_feed_sequence": 14,
              "tempering_layout_id": 15,
              "tempering_feed_sequence": 2,
              "tempering_layout_id": 1,
              "width": 814,
              "x_coordinate": 1582.0,
              "y_coordinate": 3768.0
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/page/Compute.vue
@@ -237,10 +237,14 @@
const handleSave = () => {
  
  if (props.data) {
    console.log(props.data)
    let projectData = ref({
      projectdetail: props.data,
      projectdetail: props.data.data[0].glass_details,
      ratioResult: props.data.data[0].ratioResult,
      rackinfos: props.data.data[0].rackinfos,
      resultSum: props.data.data[0].resultSum,
      guidance: props.data.data[0].guidance,
      userName : username,
      inputValues:inputValues
    })
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/GlassComputed.vue
New file
@@ -0,0 +1,328 @@
<script setup>
import ComputeCard from "@/views/pp/glassOptimizeThirdParty/page/ComputeCard.vue";
import ComputeDetail from "@/views/pp/glassOptimizeThirdParty/page/ComputeDetail.vue";
import Compute from "@/views/pp/glassOptimizeThirdParty/page/Compute.vue";
import {onMounted, ref} from 'vue';
import {ElMessage, ElLoading} from "element-plus";
import request from "@/utils/request";
const props = defineProps({
  project : null
});
const computed = ref(null);
const computedCard = ref(null);
const computedData = ref({
  // 要传递给子组件的数据
});
// 监听子组件ComputeCard的数据
const  computeCardRef=ref(null)
const handleCardData = (data) => {
  computedCard.value = data;
  // 将 computed.value 合并到 computedCard.value 的最外层
  computedCard.value = {
    ...computed.value,
    ...computedCard.value
  };
};
// 监听子组件ComputeDetail的数据
const handleData = (data) => {
  computed.value = data;
};
onMounted(() => {
  if (props.project) {
    console.log(props.project)
    handleFetchData(props.project.projectNumber);
    //handleTableData(data);
    //handleDataReceive(data);
  }
});
// 在父组件中定义处理接收数据的函数
const handleDataReceive= async (data) => {
  console.log('接收到子组件的数据2:', data);
  // 处理数据,例如更新父组件的状态或调用后端API
}
// 用于存储从后端获取到的数据,初始化为空数组
const receivedData = ref([]);
console.log(receivedData)
const handleFetchData = async (projectNumber) => {
  try {
    const res = await request.post(`/glassOptimize/selectProjectComputeMpThirdParty/${projectNumber}`);
    if (Number(res.code) === 200 && res.data && res.data.data) {
      receivedData.value = res.data.data;
    } else {
      console.error('请求出现问题,状态码:', res.code, ',错误信息:', res.msg);
      if (res.code === 404) {
        ElMessage.error('未找到对应工程号的数据,请检查输入是否正确');
      } else if (res.code === 500) {
        ElMessage.error('服务器内部出现错误,请稍后再试');
      } else {
        ElMessage.warning(res.msg);
      }
    }
  } catch (error) {
    console.error('请求出错', error);
    ElMessage.error('请输入工程号!');
  }
};
const  handleTableData= async (data) => {
  // 处理接收到的表格数据
  console.log('父组件接收到的数据1:', data);
}
const handleSimulation = async () => {
  const data=computeCardRef.value.selectFullData();
  if(props.project!=null){
    computed.value.glass_thickness=props.project.glass_thickness
    computed.value.glass_type=props.project.glass_type
  }
  data.forEach(item=>{
    const processCard = {
      process_no: item.processId,
      layers: item.technologyNumber,
      total_layers: item.total_layers,
      total_num: item.total_num,
      total_area: item.total_area,
      is_must: true,
      allow_rotate: item.allow_rotate,
      priority_level: 0,
      tempering: item.tempering,
      curtain_wall: item.curtain_wall,
      glass_details: []
    };
    request.post(`/glassOptimize/selectComputeDetailThirdParty/${item.processId}/${item.technologyNumber}`).then((res) => {
      if(Number(res.code) === 200){
        res.data.data.forEach(items=>{
          const detail={
            process_id:null,
            technology_number:null,
            order_number:null,
            layers_number:null,
            max_width:null,
            max_height:null,
            child_width:null,
            child_height:null,
            quantity:null,
            patch_state:null
          }
          detail.process_id=items.process_id
          detail.technology_number=items.technology_number
          detail.order_number=items.order_number
          detail.layers_number=item.total_layers
          detail.max_width=items.maxwidth
          detail.max_height=items.maxheight
          detail.child_width=items.width
          detail.child_height=items.height
          detail.quantity=items.quantity
          detail.patch_state=item.patch_state
          processCard.glass_details.push(detail)
        })
      }else{
        ElMessage.warning(res.msg)
      }
    })
    computed.value.process_cards.push(processCard)
  })
  console.log(computed.value)
  const loading = ElLoading.service({
      lock: true,
      text: '正在计算中,请稍候...',
      background: 'rgba(0, 0, 0, 0.7)'
    });
   console.log('发送到后端的数据',computedCard.value)
  const mockData = {
      code: 200,
      data: [
        {
          "glass_details": [
            {
              "angle": 0.0,
              "glass_type": 3,
              "height": 862,
              "layers_number": 2,
              "order_number": 33,
              "process_id": "NG24120028A005",
              "technology_number": 2,
              "tempering_feed_sequence": 1,
              "tempering_layout_id": 1,
              "width": 814,
              "x_coordinate": 1582.0,
              "y_coordinate": 3768.0
            },
            {
              "angle": 0.1,
              "glass_type": 4,
              "height": 862,
              "layers_number": 2,
              "order_number": 33,
              "process_id": "NG24120028A005",
              "technology_number": 2,
              "tempering_feed_sequence": 2,
              "tempering_layout_id": 1,
              "width": 814,
              "x_coordinate": 1582.0,
              "y_coordinate": 3768.0
            }
          ],
          "guidance": 15,
          "rackinfos": [
            "NG24120026A003-2",
            "NG24120027A003-2",
            "NG24120028A005-2"
          ],
          "ratioResult": [
            {
              "area": 10.53,
              "glass_total": 15,
              "ratio": 0.74,
              "tempering_layout_id": "13"
            },
            {
              "area": 10.53,
              "glass_total": 15,
              "ratio": 0.74,
              "tempering_layout_id": "14"
            },
            {
              "area": 9.82,
              "glass_total": 14,
              "ratio": 0.69,
              "tempering_layout_id": "15"
            }
          ],
          "resultSum": [15.0, 0.74]
        }
      ]
    };
    if (mockData.code === 200) {
    loading.close();
    ElMessage.success('模拟计算成功!');
    }
    // 更新computedData
    computedData.value = mockData;
  // try {
  //   if (!computed.value ) {
  //     ElMessage.warning('请先加载数据再进行模拟计算');
  //     return;
  //   }
  //   // 显示加载提示
  //   const loading = ElLoading.service({
  //     lock: true,
  //     text: '正在计算中,请稍候...',
  //     background: 'rgba(0, 0, 0, 0.7)'
  //   });
  //   try {
  //     // 将数据提交到后端
  //     const response = await request.post('/glassOptimize/simulationCalculate', {
  //       data: computedCard.value
  //     });
  //     if (response.code === 200) {
  //       ElMessage.success('模拟计算成功!');
  //     } else {
  //       ElMessage.error(response.msg || '模拟计算失败');
  //     }
  //   } catch (error) {
  //     console.error('请求失败:', error);
  //     ElMessage.error('网络异常,请稍后再试');
  //   } finally {
  //     // 关闭加载提示
  //     loading.close();
  //   }
  // } catch (error) {
  //   console.error('请求失败:', error);
  //   ElMessage.error('网络异常,请稍后再试');
  // }
};
//接受子组件ComputeCard的流程卡号
let projectRow = ref({
  processId:null,
  patchState:null,
  technologyNumber:null
})
const handleUpdateProcessId = (processId,technologyNumber) => {
  projectRow.value.processId = processId;
};
const handleTechnologyNumberUpdate = (processId,technologyNumber) => {
  projectRow.value.technologyNumber = technologyNumber;
};
</script>
<template>
  <div style="width: 100%; height: 100%;">
    <div id="compute">
      <compute  :data="computedData"  @fetch-data="handleFetchData" :project="props.project"  @sendData="handleData"  @simulate-click="handleSimulation" />
    </div>
    <div id="computeCard">
      <compute-card ref="computeCardRef" :table-data="receivedData"
                    :process-id="projectRow.processId===null?null:projectRow.processId"
                    :technology-number="projectRow.technologyNumber===null?null:projectRow.technologyNumber"
                    @upProcessId="handleUpdateProcessId"  @updateTechnologyNumber="handleTechnologyNumberUpdate" @sendData="handleCardData"
      />
    </div>
    <div id="computeDetail">
      <compute-detail :process-id="projectRow.processId===null?null:projectRow.processId"
                      :technology-number="projectRow.technologyNumber===null?null:projectRow.technologyNumber"
                      :patch-state="projectRow.patchState===null?null:projectRow.patchState"/>
    </div>
  </div>
</template>
<style scoped>
#compute{
  float: left;
  margin-top: -30px;
  margin-bottom: 2%;
  width: 100%;
  height: 37%;
}
#computeCard{
  width: 64%;
  height: 55%;
  float: left;
}
#computeDetail{
  margin-left: 1%;
  float: left;
  width: 35%;
  height: 55%;
}
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/Optimization.vue
New file
@@ -0,0 +1,52 @@
<template>
  <div>
    <RectRenderer
      v-if="dataLoaded"
      :layoutData="layoutData"
      :gw="1150"
      :gh="850"
      style="width: 1000px; height: 800px; position: relative;"
    />
  </div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import RectRenderer from './page/OptimizationRect.vue';
import mockLayoutData from '../../../components/pp/MockData';
import request from "@/utils/request";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
const { t } = useI18n();
const savedProjectNo = localStorage.getItem('projectNo');
const processId = savedProjectNo;
const layoutData = ref(null);
const dataLoaded = ref(false);
const selectLayout = () => {
  request.post(`/glassOptimize/selectOptimizeResult/${processId}`)
    .then((res) => {
      if (res.code == 200) {
        try {
          const parsedData = JSON.parse(res.data.data[0].Layouts);
          layoutData.value = parsedData;
          dataLoaded.value = true;
          ElMessage.success("打开版图成功");
        } catch (error) {
          ElMessage.error("解析数据时出错:", error);
        }
      } else {
        ElMessage.error(t('basicData.msg.requestFailed'));
      }
    })
    .catch((error) => {
      console.error("请求失败:", error);
      ElMessage.error(t('basicData.msg.requestFailed'));
    });
}
onMounted(() => {
  selectLayout();
});
</script>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/OptimizationRectPrint.vue
New file
@@ -0,0 +1,103 @@
<template>
  <div>
    <el-button id="button" type="primary" @click="handlePrint">打印版图</el-button>
    <el-select
      v-model="printLayout"
      placeholder="选择打印布局"
      @change="handleLayoutChange"
      style="width: 150px; margin-bottom: 10px;">
      <el-option label="四行两列" value="4rows-2cols"></el-option>
      <el-option label="三行两列" value="3rows-2cols"></el-option>
      <el-option label="三行一列" value="3rows-1col"></el-option>
      <el-option label="两行两列" value="2rows-2cols"></el-option>
    </el-select>
    <div ref="printContainer" style="position: relative;">
      <RectRenderer
        ref="rectRenderer"
        :layoutData="layoutData"
        :gw="currentGw"
        :gh="currentGh"
        :printLayout="printLayout"
        :printWidth="currentPrintWidth"
        :printHeight="currentPrintHeight"
        style="position: absolute;"
        v-if="dataLoaded"
      />
    </div>
  </div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import RectRenderer from './page/RectRenderer.vue';
import request from "@/utils/request";
const printLayout = ref('2rows-2cols');
const rectRenderer = ref(null);
const savedProjectNo = localStorage.getItem('projectNo');
const processId = savedProjectNo;
const layoutData = ref(null);
const dataLoaded = ref(false);
// 定义不同布局对应的尺寸
const layoutDimensions = {
  '4rows-2cols': { width: 1000, height: 1000 },
  '3rows-2cols': { width: 1000, height: 1000 },
  '3rows-1col': { width: 1000, height: 1000 },
  '2rows-2cols': { width: 1200, height: 1200 }
};
// 当前布局的尺寸
const currentGw = ref(layoutDimensions[printLayout.value].width);
const currentGh = ref(layoutDimensions[printLayout.value].height);
const currentPrintWidth = ref(layoutDimensions[printLayout.value].width);
const currentPrintHeight = ref(layoutDimensions[printLayout.value].height);
const selectLayout = () => {
  request.post(`/glassOptimize/selectOptimizeResult/${processId}`)
    .then((res) => {
      if (res.code == 200) {
        try {
          const parsedData = JSON.parse(res.data.data[0].Layouts);
          layoutData.value = parsedData;
          dataLoaded.value = true;
        } catch (error) {
          console.error("解析布局数据失败:", error);
        }
      } else {
        console.error("请求失败,状态码:", res.code);
      }
    })
    .catch((error) => {
      console.error("请求失败:", error);
    });
};
onMounted(() => {
  selectLayout();
});
const handlePrint = () => {
  if (rectRenderer.value) {
    rectRenderer.value.print();
  }
};
const handleLayoutChange = () => {
  // 更新布局尺寸
  const dimensions = layoutDimensions[printLayout.value];
  currentGw.value = dimensions.width;
  currentGh.value = dimensions.height;
  currentPrintWidth.value = dimensions.width;
  currentPrintHeight.value = dimensions.height;
  if (rectRenderer.value) {
    rectRenderer.value.updateLayout();
  }
};
// 监听布局变化
watch(printLayout, (newVal) => {
  handleLayoutChange();
});
</script>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/Optimize.vue
New file
@@ -0,0 +1,72 @@
<script setup>
import {ArrowDown, ArrowLeftBold, ArrowRight, Search} from "@element-plus/icons-vue"
import {useRouter,useRoute,onBeforeRouteUpdate} from "vue-router"
import {useI18n} from "vue-i18n"
const { t } = useI18n()
const router = useRouter()
const route = useRoute()
let indexFlag=$ref(1)
function changeRouter(index){
  indexFlag=index
}
</script>
<template>
  <div id="main-div">
    <div id="div-title">
      <el-breadcrumb :separator-icon="ArrowRight">
        <el-breadcrumb-item @click="changeRouter(1)" :class="indexFlag===1?'indexTag':''" :to="{ path: '/main/glassOptimizeThirdParty/optimizeProject' }">{{'工程信息'}}</el-breadcrumb-item>
        <el-breadcrumb-item v-show="false" @click="changeRouter(2)" :class="indexFlag===2?'indexTag':''" :to="{ path: '/main/order/createOrder' }">{{$t('order.page.createOrder')}}</el-breadcrumb-item>
        <el-breadcrumb-item @click="changeRouter(5)" :class="indexFlag===5?'indexTag':''" :to="{ path: '/main/glassOptimizeThirdParty/OptimizeControl' }">数控
        </el-breadcrumb-item>
        <el-breadcrumb-item @click="changeRouter(6)" :class="indexFlag===6?'indexTag':''" :to="{ path: '/main/glassOptimizeThirdParty/optimizeparms' }">设置
        </el-breadcrumb-item>
        <el-breadcrumb-item></el-breadcrumb-item>
      </el-breadcrumb>
    </div>
    <div id="main-body">
      <router-view :key="route.fullPath" />
    </div>
  </div>
</template>
<style scoped>
#main-div{
  width: 99%;
  height: 100%;
}
#div-title{
  height: 2%;
  width: 100%;
}
#searchButton{
  margin-top: -5px;
  margin-left: 1rem;
}
#searchButton1{
//margin-left: 10rem;
}
/*main-body样式*/
#main-body{
  width: 100%;
  height: 95%;
  margin-top: 1%;
}
#select{
  margin-left:0.5rem;
}
:deep(.indexTag .el-breadcrumb__inner){
  color: #5CADFE !important;
}
.example-showcase .el-dropdown-link {
  cursor: pointer;
  color: var(--el-color-primary);
  display: flex;
  align-items: center;
}
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/OptimizeControl.vue
New file
@@ -0,0 +1,93 @@
<template>
    <div >
      <RectRenderer
    :layoutData="layoutData"
    :gw="1400"
    :gh="1100"
    style="width: 1500px; height: 800px; position: relative;"
    v-if="dataLoaded"
  />
    </div>
    <button @click="submitLayouts" style="position: fixed; bottom: 20px; right: 20px; padding: 10px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer;">
      保存OPT
    </button>
  </template>
  <script setup>
  import { ref,onMounted } from 'vue';
  import RectRenderer from './page/RectRenderer.vue';
  import mockLayoutData from '../../../components/pp/MockData';
  import request from "@/utils/request";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
const { t } = useI18n();
 // const layoutData = ref(mockLayoutData);
  const savedProjectNo = localStorage.getItem('projectNo');
const processId = savedProjectNo;
const layoutData = ref(null);
const dataLoaded = ref(false);
const selectLayout = () => {
request.post(`/glassOptimize/selectOptimizeResult/${processId}`)
.then((res) => {
  if (res.code == 200) {
    try {
      const parsedData = JSON.parse(res.data.data[0].Layouts);
      layoutData.value = parsedData;
      dataLoaded.value = true;
    } catch (error) {
    }
  } else {
  }
})
.catch((error) => {
  console.error("请求失败:", error);
  ElMessage.error(t('basicData.msg.requestFailed'));
});
}
onMounted(() => {
  selectLayout();
});
  const submitLayouts = async () => {
  try {
    const response = await request.post('/glassOptimize/generateOpt', {
      Layouts: layoutData.value.Layouts
    }, {
      headers: {
        'Content-Type': 'application/json'
      },
      responseType: 'blob' // 以 blob 形式接收响应
    });
    // 处理下载
    const downloadUrl = window.URL.createObjectURL(response);
    const a = document.createElement('a');
    a.href = downloadUrl;
    a.download = 'output.opt';
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    window.URL.revokeObjectURL(downloadUrl);
    ElMessage.success('OPT文件下载成功,请选择文件路径');
  } catch (error) {
    console.error('下载失败:', error);
    // 显示错误消息给用户
    ElMessage.error('下载失败,请稍后再试');
  }
};
  </script>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/OptimizeMove.vue
New file
@@ -0,0 +1,23 @@
<template>
    <div >
      <RectRenderer
    :layoutData="layoutData"
    :gw="1400"
    :gh="1100"
    style="width: 1500px; height: 800px; position: relative;"
  />
    </div>
  </template>
  <script setup>
  import { ref } from 'vue';
  import RectRenderer from './page/RectRenderer.vue';
  import mockLayoutData from '../../../components/pp/MockData';
  const layoutData = ref(mockLayoutData);
  </script>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/OptimizeParms.vue
New file
@@ -0,0 +1,522 @@
<script setup>
import { ref, reactive, onMounted, watch } from 'vue';
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
import request from "@/utils/request";
import useUserInfoStore from "@/stores/userInfo";
const { t } = useI18n();
const userStore = useUserInfoStore()
const username = userStore.user.userName
const currentComponent = ref('optimization');
const settings = reactive({
  optimization: {},
  display: {},
  cutting: {},
  server: {},
  tempering: {}
});
const selectComponent = (component) => {
  currentComponent.value = component;
};
const saveToDatabase = () => {
  request.post(`/glassOptimize/optimizeParms`, settings).then((res) => {
    if (res.code == 200 && res.data === true) {
      ElMessage.success(t('basicData.msg.saveSuccess'))
    } else {
      ElMessage.warning(res.msg)
    }
  });
};
const fetchSettings = async (username) => {
  try {
    const response = await request.post(`/glassOptimize/selectOptimizeParms/${username}`);
    if (response.code == 200) {
      if (!response.data) {
        console.error('响应数据为空');
        return;
      }
      const parsedData = JSON.parse(response.data);
      Object.assign(settings, parsedData);
      console.log('设置已更新:', settings);
    } else {
      console.error('请求失败,状态码:', response.code);
    }
  } catch (error) {
    console.error('请求发生错误:', error);
  }
};
onMounted(() => {
  fetchSettings(username);
});
// 参数映射表,用于将英文参数名映射为中文名称和类型
const paramMapping = {
  yShapeJoinOptimization: { name: '异形拼接优化', type: 'checkbox' },
  autoMiddleEmptyPairing: { name: '自动中空配对', type: 'checkbox' },
  smallPieceRotationProhibited: { name: '小片禁止旋转', type: 'checkbox' },
  maxFramesOnSite: { name: '现场最大可放架子数量', type: 'text' },
  bendEdgeDistance: { name: '掰边距(mm)', type: 'text' },
  positiveTolerance: { name: '正公差(mm)', type: 'text' },
  negativeTolerance: { name: '负公差(mm)', type: 'text' },
  cutterOriginPosition: { name: '切割机原点位置', type: 'select' },
  uniformShapeEdgeTrimAmount: { name: '统一设置异形修边量', type: 'text' },
  edgeTrimMode: { name: '修边模式', type: 'select' },
  parallelOptimizationChannels: { name: '并行优化的通道数量', type: 'text' },
  optimizationIterations: { name: '重复优化次数', type: 'text' },
  finishedProductGrindingAmount: { name: '成品默认磨量', type: 'text' },
  rawPieceEdgeTrimAmount: { name: '原片默认修边量', type: 'text' },
  finishedSinglePieceBelowGrindingAmount: { name: '成品单片小于多少时磨量为', type: 'text' }
};
// 动态生成参数列表
const parameterList = ref(Object.keys(paramMapping).map(key => ({
  key,
  ...paramMapping[key]
})));
</script>
<template>
  <div class="settings-container">
    <div class="sidebar">
      <div
        @click="selectComponent('optimization')"
        :class="{ active: currentComponent === 'optimization' }"
        class="sidebar-item"
      >
        <div class="sidebar-header">优化参数</div>
        <div class="sidebar-content">优化参数设置</div>
      </div>
      <div
        @click="selectComponent('display')"
        :class="{ active: currentComponent === 'display' }"
        class="sidebar-item"
      >
        <div class="sidebar-header">版图显示</div>
        <div class="sidebar-content">版图显示设置</div>
      </div>
      <div
        @click="selectComponent('cutting')"
        :class="{ active: currentComponent === 'cutting' }"
        class="sidebar-item"
      >
        <div class="sidebar-header">刀路</div>
        <div class="sidebar-content">刀路设置</div>
      </div>
      <div
        @click="selectComponent('server')"
        :class="{ active: currentComponent === 'server' }"
        class="sidebar-item"
      >
        <div class="sidebar-header">输出</div>
        <div class="sidebar-content">结果输出设置</div>
      </div>
      <div
        @click="selectComponent('tempering')"
        :class="{ active: currentComponent === 'tempering' }"
        class="sidebar-item"
      >
        <div class="sidebar-header">钢化</div>
        <div class="sidebar-content">钢化设置</div>
      </div>
    </div>
    <div class="main-content">
      <template v-if="currentComponent === 'display'">
        <div class="display-settings">
          <h2>版图显示设置</h2>
          <div class="form-group">
            <label>矩形成品显示颜色</label>
            <input type="color" v-model="settings.display.themeColor" />
          </div>
          <div class="form-group">
            <label>成品尺寸包含磨边量</label>
            <input type="checkbox" v-model="settings.display.includeProductEdge" />
          </div>
          <div class="form-group">
            <label>异形尺寸包含磨边量</label>
            <input type="checkbox" v-model="settings.display.includeIrregularEdge" />
          </div>
          <div class="form-group">
            <label>按架号合并版图</label>
            <input type="checkbox" v-model="settings.display.mergeByFrameNumber" />
          </div>
          <div style="border: 1px solid #d2d0d0; margin-top: 20px;">
            <div style="background-color: #D5EAFF;">小片信息</div>
            <div class="form-group">
              <label>架号</label>
              <input type="checkbox" v-model="settings.display.frameNumber" />
            </div>
            <div class="form-group">
              <label>订单编号</label>
              <input type="checkbox" v-model="settings.display.orderNumber" />
            </div>
            <div class="form-group">
              <label>产品名称</label>
              <input type="checkbox" v-model="settings.display.productName" />
            </div>
            <div class="form-group">
              <label>加工信息</label>
              <input type="checkbox" v-model="settings.display.processingInfo" />
            </div>
            <div class="form-group">
              <label>备注</label>
              <input type="checkbox" v-model="settings.display.remarks" />
            </div>
            <div class="form-group">
              <label>楼层编号</label>
              <input type="checkbox" v-model="settings.display.floorNumber" />
            </div>
            <div class="form-group">
              <label>边长</label>
              <input type="checkbox" v-model="settings.display.edgeLength" />
            </div>
            <div class="form-group">
              <label>工艺</label>
              <input type="checkbox" v-model="settings.display.manufacturingProcess" />
            </div>
            <div class="form-group">
              <label>ID</label>
              <input type="checkbox" v-model="settings.display.identifier" />
            </div>
          </div>
        </div>
      </template>
      <template v-else-if="currentComponent === 'cutting'">
        <div class="display-settings">
          <h2>刀路设置</h2>
          <div class="form-group">
            <label>正向切割</label>
            <input type="checkbox" v-model="settings.cutting.cutting_direction" />
          </div>
          <div class="form-group">
            <label>显示切割方向</label>
            <input type="checkbox" v-model="settings.cutting.show_cutting_path" />
          </div>
          <div class="form-group">
            <label>强制弧线和直线连续切割</label>
            <input type="checkbox" v-model="settings.cutting.force_continuous_cutting" />
          </div>
          <div class="form-group">
            <label>孔洞最小切割半径</label>
            <input type="number" v-model="settings.cutting.min_radius" />
          </div>
          <div class="form-group">
            <label>两条平行线最小间隔</label>
            <input type="number" v-model="settings.cutting.min_parallel_offset" />
          </div>
          <div class="form-group">
            <label>T型退缩</label>
            <input type="number" v-model="settings.cutting.t_shaped_recess" />
          </div>
          <div class="form-group">
            <label>下刀间距</label>
            <input type="number" v-model="settings.cutting.down_cut_spacing" />
          </div>
          <div class="form-group">
            <label>抬刀退缩</label>
            <input type="number" v-model="settings.cutting.lift_retract" />
          </div>
        </div>
      </template>
      <template v-else-if="currentComponent === 'server'">
        <div class="display-settings">
          <h2>结果输出设置</h2>
          <div class="form-group">
            <label>工程文件保存路径</label>
            <input type="text" v-model="settings.server.output_format" />
          </div>
          <div class="form-group">
            <label>切割代码保存路径</label>
            <input type="text" v-model="settings.server.cutting_code_save_path" />
          </div>
          <div class="form-group">
            <label>保存文件后打开所在文件夹</label>
            <input type="text" v-model="settings.server.open_folder_after_save" />
          </div>
          <div class="form-group">
            <label>G代码文件格式</label>
            <input type="text" v-model="settings.server.g_code_file_format" />
          </div>
          <div class="form-group">
            <label>TRF文件保存路径</label>
            <input type="text" v-model="settings.server.trf_file_save_path" />
          </div>
          <div class="form-group">
            <label>保特罗文件保存路径</label>
            <input type="text" v-model="settings.server.btl_file_save_path" />
          </div>
          <div class="form-group">
            <label>OPTIMA文件保存路径</label>
            <input type="text" v-model="settings.server.optima_file_save_path" />
          </div>
          <div class="form-group">
            <label>优化深度限制启用</label>
            <input type="text" v-model="settings.server.optimization_depth_limit_enable" />
          </div>
          <div class="form-group">
            <label>原片切材率计算</label>
            <input type="text" v-model="settings.server.original_sheet_material_calculation" />
          </div>
        </div>
      </template>
      <template v-else-if="currentComponent === 'tempering'">
        <div class="display-settings">
          <h2>钢化设置</h2>
          <div class="form-group">
            <label>炉长(mm)</label>
            <input type="number" v-model="settings.tempering.furnaceLength" step="1" />
          </div>
          <div class="form-group">
            <label>炉宽(mm)</label>
            <input type="number" v-model="settings.tempering.furnaceWidth" step="1" />
          </div>
          <div class="form-group">
            <label>最大装载率(%)</label>
            <input type="number" v-model="settings.tempering.maxLoadingRate" step="1" />
          </div>
          <div class="form-group">
            <label>混乱程度(%)</label>
            <input type="number" v-model="settings.tempering.chaosLevel" step="1" />
          </div>
          <div class="form-group">
            <label>钢化加热时间(秒)</label>
            <input type="number" v-model="settings.tempering.temperingTime" step="1" />
          </div>
          <div class="form-group">
            <label>默认钢化推荐模式</label>
            <select v-model="settings.tempering.defaultTemperingMode">
              <option value="auto">自动推荐</option>
              <option value="manual">手动选择</option>
            </select>
          </div>
          <div class="form-group">
            <label>最大面积设置(㎡)</label>
            <input type="number" v-model="settings.tempering.maxArea" step="0.1" />
          </div>
          <div class="form-group">
            <label>最大片数设置(片)</label>
            <input type="number" v-model="settings.tempering.maxPieceCount" step="1" />
          </div>
          <div class="form-group">
            <label>长轴默认间隔(mm)</label>
            <input type="number" v-model="settings.tempering.xAxisInterval" step="1" />
          </div>
          <div class="form-group">
            <label>宽轴默认间隔(mm)</label>
            <input type="number" v-model="settings.tempering.yAxisInterval" step="1" />
          </div>
        </div>
      </template>
      <template v-else>
        <div class="display-settings">
          <h2>优化参数设置</h2>
          <div class="parameter-list">
            <div
              class="form-group"
              v-for="(param, index) in parameterList"
              :key="index"
            >
              <label class="parameter-name">{{ param.name }}</label>
              <div class="parameter-control">
                <input
                  v-if="param.type === 'checkbox'"
                  type="checkbox"
                  :checked="settings.optimization[param.key] === '1'"
                  @click="() => { settings.optimization[param.key] = settings.optimization[param.key] === '1' ? '0' : '1' }"
                >
                <select
                  v-else-if="param.type === 'select'"
                  :name="param.key"
                  class="cs"
                >
                  <option
                    v-for="option in getOptions(param.key)"
                    :key="option.value"
                    :selected="option.value === settings.optimization[param.key]"
                  >
                    {{ option.label }}
                  </option>
                </select>
                <input
                  v-else
                  :type="param.type || 'text'"
                  :name="param.key"
                  class="cs"
                  v-model="settings.optimization[param.key]"
                >
              </div>
            </div>
          </div>
        </div>
      </template>
    </div>
    <button class="button" @click="saveToDatabase">保存</button>
  </div>
</template>
<style>
.settings-container {
  display: flex;
  gap: 20px;
  padding: 20px;
  background-color: #f0f2f5;
}
.sidebar {
  width: 300px;
  background-color: white;
  padding: 15px;
  border-radius: 8px;
  box-shadow: 0 2px 4 rgba(0, 0, 0, 0.1);
}
.sidebar-item {
  cursor: pointer;
  padding: 10px;
  margin-bottom: 5px;
  border-radius: 6px;
  transition: all 0.3s ease;
}
.sidebar-item.active {
  background-color: #f8f9fa;
  transform: translateX(5px);
}
.sidebar-header {
  font-size: 12px;
  color: #666;
  margin-bottom: 5px;
}
.sidebar-content {
  font-size: 14px;
  color:#333;
}
.main-content {
  flex-grow: 1;
  padding: 20px;
}
button {
  padding: 10px 20px;
  background-color: #5cadfe;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
@media (max-width: 768px) {
  .settings-container {
    flex-direction: column;
  }
  .sidebar {
    order: -1;
  }
  .main-content {
    margin-top: 20px;
  }
}
.form-group {
  margin-bottom: 15px;
  display: flex;
  align-items: center;
  gap: 10px;
}
.form-group label {
  font-weight: normal;
  min-width: 120px;
}
.form-group input,
.form-group select {
  flex: 1;
  padding: 5px;
  border: 1px solid #ddd;
  border-radius: 4px;
}
.form-group select {
  cursor: pointer;
}
.display-settings {
  background-color: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.parameter-list {
  display: flex;
  flex-direction: column;
  gap: 0px;
}
.parameter-item {
  display: flex;
  align-items: center;
  gap: 0px;
}
.parameter-name {
  font-weight: normal;
  min-width: 120px;
}
.parameter-control {
  flex: 1;
}
.button {
  text-align: center;
  width: 70px;
  height: 28px;
  border: none;
  background-color: #5CADFE;
  box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0), 0 6px 5px 0 rgba(0, 0, 0, 0.19);
  margin-left: 500px;
}
</style>
<script>
// 辅助函数,根据参数键获取选项
function getOptions(key) {
  const optionsMapping = {
    cutterOriginPosition: [
      { value: 'topLeft', label: '左上角' },
      { value: 'bottomLeft', label: '左下角' },
      { value: 'topRight', label: '右上角' },
      { value: 'bottomRight', label: '右下角' }
    ],
    edgeTrimMode: [
      { value: 'intelligent', label: '智能修边' },
      { value: 'complete', label: '完全修边' }
    ]
  };
  return optionsMapping[key] || [];
}
</script>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/OptimizePrint.vue
New file
@@ -0,0 +1,80 @@
<script setup>
 import {ref} from "vue";
 const layout=ref([
   {"x":0,"y":0,"w":50,"h":50,"i":"0"},
   /*{"x":2,"y":0,"w":2,"h":4,"i":"1"},
   {"x":4,"y":0,"w":2,"h":5,"i":"2"},
   {"x":6,"y":0,"w":2,"h":3,"i":"3"},
   {"x":8,"y":0,"w":2,"h":3,"i":"4"},
   {"x":10,"y":0,"w":2,"h":3,"i":"5"},
   {"x":0,"y":5,"w":2,"h":5,"i":"6"},
   {"x":2,"y":5,"w":2,"h":5,"i":"7"},
   {"x":4,"y":5,"w":2,"h":5,"i":"8"},
   {"x":6,"y":3,"w":2,"h":4,"i":"9"},
   {"x":8,"y":4,"w":2,"h":4,"i":"10"},
   {"x":10,"y":4,"w":2,"h":4,"i":"11"},
   {"x":0,"y":10,"w":2,"h":5,"i":"12"},
   {"x":2,"y":10,"w":2,"h":5,"i":"13"},
   {"x":4,"y":8,"w":2,"h":4,"i":"14"},
   {"x":6,"y":8,"w":2,"h":4,"i":"15"},
   {"x":8,"y":10,"w":2,"h":5,"i":"16"},
   {"x":10,"y":4,"w":2,"h":2,"i":"17"},
   {"x":0,"y":9,"w":2,"h":3,"i":"18"},
   {"x":2,"y":6,"w":2,"h":2,"i":"19"}*/
 ])
 const layoutUpdated = (newLayout) => {
   //checkLayoutBounds(newLayout)
 }
 const checkLayoutBounds = (layout1) => {
   layout1.forEach(item => {
     // 检查边界,例如确保x和y不小于0,并且w和h不超出最大值等。
     if (item.x < 0) item.x = 0;
     if (item.y < 0) item.y = 0;
     if (item.w > 12) item.w = 12; // 假设最大列数为12
     if (item.h > 10) item.h = 10; // 假设最大行为10(根据需要调整)
   });
   layout.value = layout1; // 应用边界检查后的布局
   console.log(layout1)
 }
 const moveEvent = (i, newX, newY) => {
   console.log(`移动元素 ${i} 到 (${newX}, ${newY})`)
 }
</script>
<template>
  <grid-layout
      style="border:1px solid black;height: 244px;width: 366px;overflow: hidden;grid-template-columns: 1px"
      :layout.sync="layout"
      :col-num="366"
      :row-height="1"
      :is-draggable="true"
      :is-resizable="false"
      :is-mirrored="false"
      :vertical-compact="true"
      :margin="[0, 0]"
      :use-css-transforms="true"
      :autoSize="false"
      @layout-updated="layoutUpdated"
  >
    <grid-item v-for="item in layout"
               :x="item.x"
               :y="item.y"
               :w="item.w"
               :h="item.h"
               :i="item.i"
               :key="item.i"
               @move="moveEvent"
               style="background-color: white"
    >
      {{item.i}}
    </grid-item>
  </grid-layout>
</template>
<style scoped>
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/OptimizeProject.vue
New file
@@ -0,0 +1,96 @@
<script setup>
import GlassInventory from "@/views/pp/glassOptimizeThirdParty/page/GlassInventory.vue"
import ProjectDetail from "@/views/pp/glassOptimizeThirdParty/page/ProjectDetail.vue"
import GlassComputed from "@/views/pp/glassOptimizeThirdParty/GlassComputed.vue";
import ProjectMange from "@/views/pp/glassOptimizeThirdParty/ProjectMange.vue";
import {ref} from "vue";
let dialogVisible = ref(false)
let detailPage = ref(0)
const changeDialog = (value) => {
  dialogVisible.value = true
  detailPage.value = value
}
//从工程管理获取工程号,并跳转
const projectNumber = ref();
const switchDialog = (number) => {
  projectNumber.value = number;
  detailPage.value = 3;
};
//工程管理关闭弹窗
const handlePopupClose = () => {
  dialogVisible.value = false;
};
//获取SetTrimming的值
const dataForGlassInventory = ref();
const handleProjectDetailData = (data) => {
  dataForGlassInventory.value = data;
};
//获取GlassInventory的值(查询库存)
const sendDataGlassInventory = ref()
const handleInventoyData = (selectedLabel1,selectedLabel2,type) => {
  sendDataGlassInventory.value = {
    selectedLabel1, selectedLabel2,type
  };
};
//右键菜单打开修边
const isTrimmingDialogVisible = ref(false);
const sendTrimming = (value) => {
    isTrimmingDialogVisible.value = value;
  };
</script>
<template >
 <div style="width: 100%;height: 100%">
   <div id="main-body">
     <project-detail @changeDialog="changeDialog"
                     @forward-data-to-grandparent="handleProjectDetailData"
                     @send-inventory-to-op="handleInventoyData"
                     :TrimmingDialogVisible="isTrimmingDialogVisible" />
   </div>
   <div id="main-footer">
     <glass-inventory :receivedData="dataForGlassInventory"
                      :InventoryData="sendDataGlassInventory"
                      @select-trimming="sendTrimming"/>
   </div>
   <el-dialog
       v-model="dialogVisible"
       :title=" detailPage ===2? '工程管理' : detailPage ===3? '模拟计算':''"
       destroy-on-close
       style="width: 90%;height:90%;margin-top: 3vh"
       z-index="100"
   >
     <project-mange  v-if="detailPage===2" @switch-dialog="switchDialog" @closeDetailPage="handlePopupClose"/>
     <glass-computed v-if="detailPage===3" :project="projectNumber"/>
     <div v-else></div>
   </el-dialog>
 </div>
</template>
<style scoped>
#main-body{
  width: 100%;
  height: 60%;
}
#main-footer{
  margin-top: 10px;
  width: 100%;
  height: calc(40% - 10px);
}
:deep( .el-dialog__body){
  height: calc(100% - 55px);
  width: 100%;
  margin-top: 30px;
  //padding: 0;
}
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/ProjectCreate.vue
New file
@@ -0,0 +1,391 @@
<script setup>
import ProcessCard from "@/views/pp/glassOptimize/page/ProcessCard.vue";
import ProcessCardDetail from "@/views/pp/glassOptimize/page/ProcessCardDetail.vue";
import ProjectList from "@/views/pp/glassOptimize/page/ProjectList.vue";
import {defineEmits, nextTick, onMounted, reactive, ref} from "vue";
import {useI18n} from "vue-i18n";
import GlassComputed from "@/views/pp/glassOptimize/GlassComputed.vue";
import ProjectMange from "@/views/pp/glassOptimize/ProjectMange.vue";
import request from "@/utils/request";
import {ElMessage, ElMessageBox} from "element-plus";
import deepClone from "@/utils/deepClone";
import {useRouter} from "vue-router";
import {changeFilterEvent, filterChanged} from "@/hook";
const router = useRouter();
let projectRow = ref({
  processId:null,
  technologyNumber:null
})
const  processCardRef=ref(null)
const handleProcessIdUpdate = newProcessId => {
  projectRow.value.processId = newProcessId;
};
const handleTechnologyNumberUpdate = newTechnologyNumber => {
  projectRow.value.technologyNumber = newTechnologyNumber;
};
const updateState = state => {
  getProject();
};
const { t } = useI18n()
const xGrid = ref()
const gridOptions = reactive({
  height:'100%',
  loading: false,
  border:  "full",//表格加边框
  keepSource: true,//保持源数据
  align: 'center',//文字居中
  stripe:true,//斑马纹
  rowConfig: {isCurrent: true, isHover: true,height: 30, useKey: true},//鼠标移动或选择高亮
  id: 'ProjectList',
  scrollX:{enabled: true},
  scrollY:{ enabled: true ,gt:0},//开启虚拟滚动
  showOverflow:true,
  columnConfig: {
    resizable: true,
    useKey: true
  },
  filterConfig: {   //筛选配置项
    //remote: true
  },
  customConfig: {
    storage: true
  },
  editConfig: {
    trigger: 'click',
    mode: 'row',
    showStatus: true
  },
  columns:[
    {field: 'id',width: 150, title: 'ID'},
    {field: 'projectNumber',width: 150, title: '工程号',filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true,filterMethod:filterChanged},
    {field: 'project_name',width: 150, title: '项目名称',filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true,filterMethod:filterChanged},
    {field: 'glass_type',width: 150, title: '膜系',filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true,filterMethod:filterChanged},
    {field: 'glass_thickness',width: 150, title: '厚度',filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true,filterMethod:filterChanged},
    {field: 'type',width: 150, title: '类型',filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true,filterMethod:filterChanged},
    {field: 'state',width: 150, title: '状态',filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true,filterMethod:filterChanged},
    {field: 'quantity',width: 150, title: t('order.quantity')},
    {field: 'glass_total_area',width: 150, title: t('order.grossArea'),filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true,filterMethod:filterChanged},
    {field: 'process_qty',width: 150, title: '流程数量',filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true,filterMethod:filterChanged},
    {field: 'process_cards',width: 150, title: '流程卡号',filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true,filterMethod:filterChanged},
  ],//表头参数
  data:null,//表格数据
  toolbarConfig: {
    buttons: [
    ],
    import: false,
    // export: true,
    // print: true,
    zoom: true,
    custom: true
  },
  //右键菜单选项
  menuConfig: {
    body: {
      options: [
        [
          {code: 'openProject', name: '打开工程', prefixIcon: 'vxe-icon-folder-open'},
          {code: 'updateProject', name: '修改工程', prefixIcon: 'vxe-icon-folder-open'},
          {code: 'compute', name: '模拟计算', prefixIcon: 'vxe-icon-subtable'},
          {code: 'delProject', name: '删除工程', prefixIcon: 'vxe-icon-delete'},
        ],
        []
      ]
    }
  }
})
// 定义操作配置对象数组,集中管理不同操作选项对应的参数
const operationConfigs = [
  {
    code: 'openProject', // 打开工程
    initialState: ['1'], //
    targetState: null,
    successMsg: '已打开!',
    checkMessage: '当前工程状态不符合条件,请确认工程状态后再操作!',
    requiresRow: true,
    openFile: async ({row}) => {
      const projectNumber = row.projectNumber;
      const thickness = row.glass_thickness;
      const glassType = row.glass_type;
      await router.push({
        name: 'optimizeInfo',
        params: {
          projectNo: projectNumber,
          thickNess: thickness,
          model: glassType
        }
      });
    }
  },
  {
    code: 'compute', // 打开模拟计算操作
    initialState: ['1'], //
    targetState: null,
    successMsg: '模拟计算已启动!',
    checkMessage: '当前工程状态不符合模拟计算条件,请确认工程状态后再操作!',
    requiresRow: true,
    actionFunction: async ({row}) => {
      const projectNo = row.projectNumber;
      emit('switch-dialog', row);
    }
  },
  {
    code: 'delProject',
    initialState: ['1'],
    targetState: null,
    successMsg: '工程删除成功!',
    checkMessage: '当前工程状态不符合删除条件,请确认工程状态后再操作!',
  },
  {
    code: 'updateProject',
        initialState: ['1'],
      targetState: null,
      successMsg: '',
      checkMessage: '当前工程状态不符合删除条件,请确认工程状态后再操作!',
  }
];
//定义切换模拟计算弹窗
const emit = defineEmits(['switch-dialog']);
onMounted(async () => {
  getProject();
})
// 定义数据返回结果,使用ref创建响应式数据,初始化为空数组
let produceList = ref([])
const getProject = ()=>{
  request.post(`/glassOptimize/getProjectList`).then((res) => {
    if(res.code==200){
      produceList.value = deepClone(res.data.data);
      xGrid.value.loadData(produceList.value)
    }else{
      ElMessage.warning(res.msg)
    }
  })
}
// 公共处理函数,处理相同数据时的操作,并返回targetRoute对象(右键菜单和双击打开)
const handleSameDataOperation = async ({projectNumber, thickness, glassType}) => {
  const targetRoute = {
    name: 'optimizeInfo',
    params: {
      projectNo: projectNumber,
      thickNess: String(thickness),
      model: glassType
    }
  };
  const currentRoute = router.currentRoute.value;
  const isRoutesEqual = currentRoute.name === targetRoute.name &&
      currentRoute.params.projectNo === targetRoute.params.projectNo &&
      currentRoute.params.thickNess === targetRoute.params.thickNess &&
      currentRoute.params.model === targetRoute.params.model;
  if (isRoutesEqual) {
    handleConfirm();
  }
  return {isRoutesEqual};
};
//选中相同数据时弹窗提示
const handleConfirm = () => {
  const currentRoute = router.currentRoute.value;
  const projectNumber = currentRoute.params.projectNo;
  ElMessageBox.confirm(`当前工程(工程号:${projectNumber})已打开,是否重新打开?`, '确认操作', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  })
      .then(() => {
        emit('close-detail-page');
        ElMessage.success('已打开!');
      })
      .catch(() => {
        ElMessage.info('已取消操作');
      });
};
const gridEvents = {
  menuClick({menu, row}) {
    const $grid = xGrid.value;
    if ($grid) {
      const config = operationConfigs.find(c => c.code === menu.code);
      if (config) {
        if (config.requiresRow && !row) {
          ElMessage.warning('未选中工程,请选中工程后再进行当前操作!');
          return;
        }
        if (config.code === 'compute') {
          config.actionFunction({row});
          return;
        }
        if (config.code === 'updateProject') {
          if (!row) {
            ElMessage.warning(config.checkMessage);
            return;
          }
          processCardRef.value.getUpdateFlowCardList(row.projectNumber,row.glass_type,row.glass_thickness);
          return;
        }
        if (config.code === 'openProject') {
          handleSameDataOperation(row).then(({isRoutesEqual}) => {
            if (!isRoutesEqual) {
              config.openFile({row});
              ElMessage.success(config.successMsg);
            }
          });
        }
        // 添加确认提示弹窗,询问用户是否进行当前操作
        ElMessageBox.confirm('是否进行当前操作?', '确认操作', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          if (config.code === 'delProject') {
            if (!row) {
              ElMessage.warning(config.checkMessage);
              return;
            }
            const isInitialStateMatched = config.initialState.includes(String(row.state));
            if (!isInitialStateMatched) {
              ElMessage.warning(config.checkMessage);
              return;
            }
            deleteProject(row.projectNumber, config);
          }
        }).catch(() => {
          // 用户点击取消后执行的逻辑
          ElMessage.info('已取消操作');
        });
      } else {
        console.error(`未找到操作选项 ${menu.code} 对应的配置,请检查配置项`);
      }
    }
  }
};
function deleteProject(projectNumber, config) {
  request.post(`/glassOptimize/deleteProject/${projectNumber}`, {
    headers: {
      'Content-Type': 'application/json'
    }
  }).then((res) => {
    if (res.code==200 && res.data===true) {
      ElMessage.success(config.successMsg);
      // 从列表中移除已删除的工程数据
      const index = produceList.value.findIndex(item => item.projectNumber === projectNumber);
      if (index !== -1) {
        produceList.value.splice(index, 1);
        xGrid.value.reloadData(produceList.value);
      }
      //刷新工程号
      processCardRef.value.getProjectId();
      processCardRef.value.selectGlassType();
      processCardRef.value.selectFlowCardList();
    } else {
      console.log('res.code 的值:', res.code, ', 类型:', typeof res.code);
      console.log('res.msg 的值:', res.msg, ', 类型:', typeof res.msg);
      const errorMsg = res.data ? res.data.errorMessage : config.failureMsg;
      ElMessage.error(`操作失败,原因: ${errorMsg}`);
    }
  }).catch((error) => {
    console.error('请求出错,工程删除未完成,详细错误信息:', error);
    ElMessage.error(`请求出错,工程删除未完成,原因: ${errorMsg}`);
  });
}
</script>
<template>
  <div style="width: 100%; height: 100%;">
    <div id="processCard">
      <process-card ref="processCardRef" :process-id="projectRow.processId===null?null:projectRow.processId"
                    :technology-number="projectRow.technologyNumber===null?null:projectRow.technologyNumber"
                    @updateProcessId="handleProcessIdUpdate"
                    @updateTechnologyNumber="handleTechnologyNumberUpdate"
                    @updateState="updateState"
      />
    </div>
    <div id="processCard-detail" >
      <process-card-detail :process-id="projectRow.processId===null?null:projectRow.processId"
                           :technology-number="projectRow.technologyNumber===null?null:projectRow.technologyNumber"/>
    </div>
    <div id="project-list">
      <div style="width: 100%;height: 100%">
        <h1>工程列表</h1>
        <vxe-grid
            size="small"
            height="100%"
            class="mytable-scrollbar"
            ref="xGrid"
            v-bind="gridOptions"
            v-on="gridEvents"
        >
          <template #num2_filter="{ column, $panel }">
            <div>
              <div v-for="(option, index) in column.filters" :key="index">
                <vxe-select v-model="option.data" :placeholder="$t('processCard.pleaseSelect')"  @change="changeFilterEvent($event, option, $panel)">
                  <vxe-option value="0" :label="$t('basicData.unchecked')"></vxe-option>
                  <vxe-option value="1" :label="$t('basicData.selected')"></vxe-option>
                </vxe-select>
              </div>
            </div>
          </template>
          <template #num1_filter="{ column, $panel }">
            <div>
              <div v-for="(option, index) in column.filters" :key="index">
                <input type="type" v-model="option.data" @keyup.enter.native="$panel.confirmFilter()" @input="changeFilterEvent($event, option, $panel)"/>
              </div>
            </div>
          </template>
        </vxe-grid>
      </div>
    </div>
  </div>
</template>
<style scoped>
#processCard{
  width: 64%;
  height: 55%;
  float: left;
}
#processCard-detail{
  margin-left: 1%;
  float: left;
  width: 35%;
  height: 55%;
}
#project-list{
  float: left;
  margin-top: 2%;
  width: 100%;
  height: 30%;
}
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/ProjectMange.vue
New file
@@ -0,0 +1,702 @@
<script setup>
import {computed, nextTick, reactive, ref} from "vue";
import {useI18n} from "vue-i18n";
import deepClone from "@/utils/deepClone"
import {Search} from "@element-plus/icons-vue";
import request from "@/utils/request";
import {ElDatePicker, ElMessage, ElMessageBox} from "element-plus";
import useOrderInfoStore from "@/stores/sd/order/orderInfo";
import useUserInfoStore from "@/stores/userInfo";
import userInfo from "@/stores/userInfo";
import {useRouter} from 'vue-router';
import {defineEmits} from 'vue';
import {changeFilterEvent, filterChanged} from "@/hook";
const {t} = useI18n()
const userStore = useUserInfoStore()
const user = userInfo()
const orderInfo = useOrderInfoStore()
const router = useRouter();
const xGrid = ref()
const gridOptions = reactive({
  height: '100%',
  loading: false,
  border: "full",//表格加边框
  keepSource: true,//保持源数据
  align: 'center',//文字居中
  stripe: true,//斑马纹
  rowConfig: {isCurrent: true, isHover: true, height: 30, useKey: true},//鼠标移动或选择高亮
  id: 'ProjectMange',
  scrollX: {enabled: true},
  scrollY: {enabled: true, gt: 0},//开启虚拟滚动
  showOverflow: true,
  columnConfig: {
    resizable: true,
    useKey: true
  },
  filterConfig: {   //筛选配置项
    //remote: true
  },
  customConfig: {
    storage: true
  },
  editConfig: {
    trigger: 'click',
    mode: 'row',
    showStatus: true
  },
  columns: [
    {type: 'seq', title: t('basicData.Number'), width: 80},
    {field: 'projectNumber', width: 100, title: '工程号',sortable: true,showOverflow:'ellipsis' ,filters:[{ data: '' }],slots: { filter: 'num1_filter' },filterMethod:filterChanged},
    {field: 'projectName', width: 50, title: '名称',},
    {field: 'glassType', width: 50, title: '膜系',},
    {field: 'thickness', width: 50, title: '厚度',},
    {field: 'type', width: 50, title: '类型',},
    {field: 'state', width: 50, title: '状态',},
    {field: 'quantity', width: 50, title: '数量',},
    {field: 'area', width: 50, title: '面积',},
    {field: 'processCardQuantity', width: 100, title: '流程卡数量',},
    {field: 'usingQuantity', width: 100, title: '原片使用数',},
    {field: 'processCardCollection', width: 70, title: '流程卡',filters: [{data: ''}], slots: {filter: 'num1_filter'},filterMethod:filterChanged},
    {field: 'temperedLoadingRate', width: 100, title: '钢化装载率',},
    {field: 'numberOfTemperingFurnaces', width: 100, title: '钢化炉数',},
    {field: 'averageCuttingRate', width: 100, title: '平均切裁率',},
    {field: 'effectiveCuttingRate', width: 100, title: '有效切裁率',},
    {field: 'residueGlassCuttingRate', width: 100, title: '尾片切裁率',},
    {field: 'amountOfOriginalGlassUsage1', width: 100, title: '原料使用数',},
    {field: 'averageCuttingRate1', width: 100, title: '平均切裁率',},
    {field: 'notes', width: 50, title: '备注',},
    {field: 'creator', width: 100, title: '创建人',},
    {field: 'createTime', width: 100, title: '创建时间',},
    {field: 'modifyTime', width: 100, title: '修改时间',},
  ],//表头参数
  data: null,//表格数据
  toolbarConfig: {
    buttons: [
    ],
    import: false,
    // export: true,
    // print: true,
    zoom: true,
    custom: true
  },
  //右键菜单选项
  menuConfig: {
    body: {
      options: [
        [
          {code: 'openProject', name: '打开工程', prefixIcon: 'vxe-icon-folder-open'},
          {code: 'compute', name: '模拟计算', prefixIcon: 'vxe-icon-subtable'},
          {code: 'production', name: '允许生产', prefixIcon: 'vxe-icon-square-checked'},
          {code: 'novisible', name: '生产不可见', prefixIcon: 'vxe-icon-eye-fill-close'},
          {code: 'undoCompute', name: '撤销模拟计算', prefixIcon: 'vxe-icon-error-circle-fill'},
          {code: 'delProject', name: '删除工程', prefixIcon: 'vxe-icon-delete'},
          {code: 'viewTempered', name: '查看钢化版图', prefixIcon: 'vxe-icon-custom-column'},
          {code: 'Export', name: '数据导出', prefixIcon: 'vxe-icon-download', visible: true, disabled: false},
        ],
        []
      ]
    },
  },
})
// 定义操作配置对象数组,集中管理不同操作选项对应的参数
const operationConfigs = [
  {
    code: 'openProject', // 打开工程
    initialState: ['2','10', '100', '200'], //
    targetState: null,
    successMsg: '已打开!',
    checkMessage: '当前工程状态不符合条件,请确认工程状态后再操作!',
    requiresRow: true,
    openFile: async ({row}) => {
      const projectNumber = row.projectNumber;
      const thickness = row.thickness;
      const glassType = row.glassType;
      await router.push({
        name: 'optimizeInfo',
        params: {
          projectNo: projectNumber,
          thickNess: thickness,
          model: glassType
        }
      });
    }
  },
  {
    code: 'compute', // 打开模拟计算操作
    initialState: '2',
    targetState: null,
    successMsg: '模拟计算已启动!',
    checkMessage: '当前工程状态不符合模拟计算条件,请确认工程状态后再操作!',
    requiresRow: true,
    actionFunction: async ({row}) => {
      const projectNo = row.projectNumber;
      emit('switch-dialog', row);
    }
  },
  {
    code: 'undoCompute',
    initialState: '10',
    targetState: 2,
    successMsg: '撤销模拟计算成功,数据已更新!',
    checkMessage: '当前工程状态不符合撤销模拟计算条件,请确认工程状态后再操作!',
    requiresRow: true,
  },
  {
    code: 'production',
    initialState: '10',
    targetState: 100,
    successMsg: '设置成功,允许生产!',
    checkMessage: '当前工程状态不符合允许生产条件,请确认工程状态后再操作!',
    requiresRow: true,
  },
  {
    code: 'novisible',
    initialState: '100',
    targetState: 10,
    successMsg: '设置成功,生产不可见!',
    checkMessage: '当前工程状态不符合生产不可见条件,请确认工程状态后再操作!',
    requiresRow: true,
  },
  {
    code: 'delProject',
    initialState: ['2', '10'],
    targetState: null,
    successMsg: '工程删除成功!',
    checkMessage: '当前工程状态不符合删除条件,请确认工程状态后再操作!',
    requiresRow: true,
  },
  {
    code: 'Export', // 导出文件操作的配置
    initialState: [],
    targetState: null,
    successMsg: '文件导出成功!',
    gridRef: xGrid,
    requiresRow: false,
  },
  {
    code: 'viewTempered',
    initialState: [],
    targetState: null,
    successMsg: '钢化版图已打开!',
    failureMsg: '工程删除失败,请联系管理员!',
  }
];
function getOriginalState(targetState) {
  // 根据工程状态返回对应的原始状态
  const stateMapping = {
    2: '10',
    10: '100',
    100: '200',
    200: '100'
  };
  return stateMapping[targetState] || targetState;
}
//右键菜单条件判断
function checkOperationCondition(config, row) {
  if (!config.requiresRow || !row) {
    return false;
  }
  if (Array.isArray(config.initialState)) {
    return config.initialState.includes(String(row.state));
  }
  return config.initialState === String(row.state);
}
// 右键菜单点击事件
const gridEvents = {
  menuClick({menu, row}) {
    const $grid = xGrid.value;
    if ($grid) {
      const config = operationConfigs.find(c => c.code === menu.code);
      if (config) {
        if (config.requiresRow && !row) {
          ElMessage.warning('未选中工程,请选中工程后再进行当前操作!');
          return;
        }
        if (!checkOperationCondition(config, row)) {
          ElMessage.warning(config.checkMessage);
          return;
        }
        if (config.code === 'Export') {
          config.gridRef.value.exportData();
          ElMessage.success(config.successMsg);
          return;
        }
        if (config.code === 'compute') {
          config.actionFunction({row});
          return;
        }
        // 添加确认提示弹窗,询问用户是否进行当前操作
        ElMessageBox.confirm('是否进行当前操作?', '确认操作', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          if (config.code === 'viewTempered') {
            ElMessageBox.alert('当前点击的是查看钢化版图功能,目前暂时仅做提示,暂无实际查看操作!', '功能提示', {
              confirmButtonText: '我知道了'
            });
            return;
          }
          if (config.code === 'viewOptimize') {
            ElMessageBox.alert('当前点击的是查看钢化版图功能,目前暂时仅做提示,暂无实际查看操作!', '功能提示', {
              confirmButtonText: '我知道了'
            });
            return;
          }
          if (config.code === 'delProject') {
            if (!row) {
              ElMessage.warning(config.checkMessage);
              return;
            }
            deleteProject(row.projectNumber, config);
          } else {
            if (config.code === 'openProject') {
              handleSameDataOperation(row).then(({isRoutesEqual}) => {
                if (!isRoutesEqual) {
                  config.openFile({row});
                  ElMessage.success(config.successMsg);
                }
              });
            } else if (config.code === 'compute') {
              config.actionFunction({row});
            } else  if (config.code === 'optimizeTypography') {
              handleSameDataOperation(row).then(({isRoutesEqual}) => {
                if (!isRoutesEqual) {
                  config.Typography({row});
                  ElMessage.success(config.successMsg);
                }
              });
            }
            else {
              row.state = config.targetState;
              let code=0
              const index = produceList.value.findIndex(item => item === row);
              if (index !== -1) {
                produceList.value.splice(index, 1, {...row});
                xGrid.value.reloadData(produceList.value);
              }
              if(config.code === 'undoCompute'){
                code=1
              }else if(config.code === 'undoOptimize'){
                code=2
              }
              else if(config.code === 'production'){
                code=3
              }
              else if(config.code === 'novisible'){
                code=4
              }
              else if(config.code === 'InitializeProject'){
                code=5
              }
              updateProjectStateAndHandleResponse(row, row.projectNumber, config.targetState,code, config.successMsg);
            }
          }
        }).catch(() => {
          // 用户点击取消后执行的逻辑
          ElMessage.info('已取消操作');
        });
      } else {
        console.error(`未找到操作选项 ${menu.code} 对应的配置,请检查配置项`);
      }
    }
  },
  cellDblclick: ({row}) => {
    const menu = {code: 'openProject'};
    nextTick(() => {
      handleSameDataOperation(row).then(({isRoutesEqual}) => {
        if (!isRoutesEqual) {
          gridEvents.menuClick({menu, row});
        }
      });
    });
  },
};
// 公共处理函数,处理相同数据时的操作,并返回targetRoute对象(右键菜单和双击打开)
const handleSameDataOperation = async ({projectNumber, thickness, glassType}) => {
  const targetRoute = {
    name: 'optimizeInfo',
    params: {
      projectNo: projectNumber,
      thickNess: String(thickness),
      model: glassType
    }
  };
  const currentRoute = router.currentRoute.value;
  const isRoutesEqual = currentRoute.name === targetRoute.name &&
      currentRoute.params.projectNo === targetRoute.params.projectNo &&
      currentRoute.params.thickNess === targetRoute.params.thickNess &&
      currentRoute.params.model === targetRoute.params.model;
  if (isRoutesEqual) {
    handleConfirm();
  }
  return {isRoutesEqual};
};
//选中相同数据时弹窗提示
const handleConfirm = () => {
  const currentRoute = router.currentRoute.value;
  const projectNumber = currentRoute.params.projectNo;
  ElMessageBox.confirm(`当前工程(工程号:${projectNumber})已打开,是否重新打开?`, '确认操作', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  })
      .then(() => {
        emit('close-detail-page');
        ElMessage.success('已打开!');
      })
      .catch(() => {
        ElMessage.info('已取消操作');
      });
};
// 封装发送右键菜单请求、处理响应以及错误回滚等逻辑的函数
function rollbackStateAndReloadGrid(row, targetState) {
  row.state = getOriginalState(targetState);
  const rollbackIndex = produceList.value.findIndex(item => item === row);
  if (rollbackIndex !== -1) {
    produceList.value.splice(rollbackIndex, 1, {...row});
    xGrid.value.reloadData(produceList.value);
  }
}
function updateProjectStateAndHandleResponse(row, projectNumber, targetState,code, successMsg) {
  const updateParams = {
    projectNumber: projectNumber,
    stateToUpdate: targetState
  };
  request.post(`/glassOptimize/updateProjectState/${projectNumber}/${targetState}/${code}`, updateParams, {
    headers: {
      'Content-Type': 'application/json'
    }
  }).then((res) => {
    if (Number(res.code) === 200 && (res.msg === "" || res.msg === null)) {
      ElMessage.success(successMsg);
    } else {
      console.log('res.code 的值:', res.code, ', 类型:', typeof res.code);
      console.log('res.msg 的值:', res.msg, ', 类型:', typeof res.msg);
      const errorMsg = res.data ? res.data.errorMessage : '操作失败,未获取到具体原因,请联系管理员';
      ElMessage.error(`操作失败,原因: ${errorMsg}`);
      rollbackStateAndReloadGrid(row, targetState); // 调用回滚函数
    }
  }).catch((error) => {
    console.error('请求出错,操作未完成,详细错误信息:', error);
    const errorMsg = (res.data && res.data.errorMessage) ? res.data.errorMessage : '操作失败,未获取到具体原因,请联系管理员';
    ElMessage.error(`请求出错,操作未完成,原因: ${errorMsg}`);
    rollbackStateAndReloadGrid(row, targetState); // 调用回滚函数
  });
}
//删除工程
function deleteProject(projectNumber, config) {
  request.post(`/glassOptimize/deleteProjectThirdParty/${projectNumber}`, {
    headers: {
      'Content-Type': 'application/json'
    }
  }).then((res) => {
    if (res.code==200 && res.data===true) {
      ElMessage.success(config.successMsg);
      // 从列表中移除已删除的工程数据
      const index = produceList.value.findIndex(item => item.projectNumber === projectNumber);
      if (index !== -1) {
        produceList.value.splice(index, 1);
        xGrid.value.reloadData(produceList.value);
      }
    } else {
      console.log('res.code 的值:', res.code, ', 类型:', typeof res.code);
      console.log('res.msg 的值:', res.msg, ', 类型:', typeof res.msg);
      const errorMsg = res.data ? res.data.errorMessage : config.failureMsg;
      ElMessage.error(`操作失败,原因: ${errorMsg}`);
    }
  }).catch((error) => {
    console.error('请求出错,工程删除未完成,详细错误信息:', error);
    const errorMsg = (res.data && res.data.errorMessage) ? res.data.errorMessage : config.failureMsg;
    ElMessage.error(`请求出错,工程删除未完成,原因: ${errorMsg}`);
  });
}
//定义切换模拟计算弹窗
const emit = defineEmits(['switch-dialog', 'close-detail-page']);
//定义工程状态
const optionVal = ref('all')
const options = [
  {
    value: 'all',
    label: '全部',
  },
  {
    value: '2',
    label: '优化完成,2',
  },
  {
    value: '10',
    label: '模拟计算,10',
  },
  {
    value: '100',
    label: '生产可见,100',
  },
  {
    value: '200',
    label: '生产领取,200',
  },
]
//工程状态函数
function handleOptionChange() {
  if (optionVal.value === 'all') {
    // 清空工程状态相关的已有筛选条件
    delete filterData.value['state'];
  } else {
    filterData.value['state'] = optionVal.value; // 明确添加选择的工程状态值到筛选条件中
  }
  getWorkOrder();
}
//公共函数
function buildRequestParams() {
  let startSelectTime = orderInfo.workOrderDate[0];
  let endSelectTime = orderInfo.workOrderDate[1];
  const params = {
    startSelectTime,
    endSelectTime,
  };
  return params;
}
//筛选条件
let filterData = ref({
  projectNumber: '',
})
// 定义数据返回结果,使用ref创建响应式数据,初始化为空数组
let produceList = ref([])
// 获取15天前到当前时间
function getNowTime() {
  const start = new Date(new Date().getTime() - 3600 * 1000 * 24 * 70)
      .toISOString()
      .replace('T', ' ')
      .slice(0, 10) //默认开始时间15天前
  const end = new Date(new Date().getTime())
      .toISOString()
      .replace('T', ' ')
      .slice(0, 10)//默认结束时间当前时间
  return [start, end]
}
// 初始化时间范围,若为空则设为近15天时间
if (orderInfo.workOrderDate[0] === "" && orderInfo.workOrderDate[1] === "") {
  orderInfo.workOrderDate = getNowTime();
}
//获取选中时间
let startSelectTime = orderInfo.workOrderDate[0]
let endSelectTime = orderInfo.workOrderDate[1]
// 首次加载数据
request.post(`/glassOptimize/optimizeProjectMange/${startSelectTime}/${endSelectTime}`, filterData.value).then((res) => {
  if (res.code == 200) {
    produceList.value = produceList.value.concat(deepClone(res.data.data))
    xGrid.value.reloadData(produceList.value);
    gridOptions.loading = false;
  } else {
    ElMessage.warning(res.msg);
  }
})
// 点击查询按钮触发的函数,根据当前筛选条件获取数据并加载到表格
const getWorkOrder = () => {
  const params = buildRequestParams();
  request.post(`/glassOptimize/optimizeProjectMange/${params.startSelectTime}/${params.endSelectTime}`, filterData.value).then((res) => {
    if (res.code == 200) {
      produceList.value = deepClone(res.data.data);
      gridOptions.data = produceList.value;
      xGrid.value.loadData(produceList.value);
      gridOptions.loading = false;
    } else {
      ElMessage.warning(res.msg);
    }
  });
};
// 计算小片数量
const smallPieceQuantityInput = computed(() => {
  return produceList.value.reduce((acc, item) => acc + item.quantity, 0);
});
// 计算小片面积
const smallPieceAreaInput = computed(() => {
  const sum = produceList.value.reduce((acc, item) => acc + item.area, 0);
  return Number(sum.toFixed(2)); // 先使用toFixed保留两位小数,再转换回数值类型(因为toFixed返回字符串)
});
// 计算原片数量
const originalPieceQuantityInput = computed(() => {
  return produceList.value.reduce((acc, item) => acc + item.usingQuantity, 0);
});
// 原片面积固定为0,直接返回0
const originalPieceAreaInput = 0;
//日期左侧栏快捷选项
const shortcuts = [
  {
    text: '当日',
    value: () => {
      const end = new Date()
      const start = new Date(end)
      start.setHours(0, 0, 0, 0) // 设置为当天的凌晨 00:00:00
      return [start, end]
    }
  },
  {
    text: '近1周',
    value: () => {
      const end = new Date()
      const start = new Date()
      start.setDate(start.getDate() - 7)
      return [start, end]
    }
  },
  {
    text: '近1个月',
    value: () => {
      const end = new Date()
      const start = new Date()
      start.setMonth(start.getMonth() - 1)
      return [start, end]
    }
  },
  {
    text: '近2个月',
    value: () => {
      const end = new Date()
      const start = new Date()
      start.setMonth(start.getMonth() - 2)
      return [start, end]
    }
  },
  {
    text: '近3个月',
    value: () => {
      const end = new Date()
      const start = new Date()
      start.setMonth(start.getMonth() - 3)
      return [start, end]
    }
  }
]
</script>
<template>
  <div id="mange">
    <div id="select" style="">
      <span>优化日期</span>
      <el-date-picker
          style="margin-left:10px; margin-top: -10px; "
          v-model="orderInfo.workOrderDate"
          :default-time="defaultTime"
          :start-placeholder="$t('basicData.startDate')"
          :end-placeholder="$t('basicData.endDate')"
          type="daterange"
          :shortcuts="shortcuts"
          showToday
          format="YYYY/MM/DD"
          value-format="YYYY-MM-DD"
      />
      <span class="input">工程状态</span>
      <el-select
          style="margin-left:10px; margin-top: -5px; width: 150px"
          :default-first-option="true"
          ref="getSelect"
          v-model="optionVal"
          clearable
          class="m-2"
          @change="handleOptionChange"
      >
        <el-option
            v-for="item in options"
            :key="item.value"
            :label="item.label"
            :value="item.value"
        />
      </el-select>
      <!--查询-->
      <el-button
          style="margin-left:10px;margin-top: -5px"
          :icon="Search"
          type="primary"
          @click="getWorkOrder">{{ $t('basicData.search') }}
      </el-button>
    </div>
    <vxe-grid
        height="100%"
        class="mytable-scrollbar"
        ref="xGrid"
        v-bind="gridOptions"
        v-on="gridEvents"
    >
      <template #select_filter="{ column, $panel }">
        <div>
          <div v-for="(option, index) in column.filters" :key="index">
            <vxe-select v-model="option.data" @change="changeFilterEvent($event, option, $panel)">
              <vxe-option value="0" :label="$t('basicData.unchecked')"></vxe-option>
              <vxe-option value="1" :label="$t('basicData.selected')"></vxe-option>
            </vxe-select>
          </div>
        </div>
      </template>
      <template #num1_filter="{ column, $panel }">
        <div>
          <div v-for="(option, index) in column.filters" :key="index">
            <input type="type" v-model="option.data" @keyup.enter.native="$panel.confirmFilter()" @input="changeFilterEvent($event, option, $panel)"/>
          </div>
        </div>
      </template>
    </vxe-grid>
    <div id="last">
      小片数量
      <el-input class="input" disabled v-model="smallPieceQuantityInput"></el-input>
      小片面积
      <el-input class="input" disabled v-model="smallPieceAreaInput"></el-input>
      原片数量
      <el-input class="input" disabled v-model="originalPieceQuantityInput"></el-input>
      原片面积
      <el-input class="input" disabled v-model="originalPieceAreaInput"></el-input>
    </div>
  </div>
</template>
<style scoped>
#mange {
  width: 100%;
  height: 85%;
}
:deep(.vxe-button.type--button.is--circle) {
  margin-top: -20px;
}
#last {
  margin-top: 10px;
}
.input {
  width: 80px;
  margin-left: 10px;
}
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/CheckInventory.vue
New file
@@ -0,0 +1,97 @@
<script setup>
import {defineEmits, ref} from "vue";
const value1 = ref('')
const value2 = ref('')
const options1 = [
  {
    value: 'Option1',
    label: '5',
  },
  {
    value: 'Option2',
    label: '6',
  },
  {
    value: 'Option3',
    label: '8',
  },
  {
    value: 'Option4',
    label: '10',
  },
  {
    value: 'Option5',
    label: '12',
  },
]
const options2 = [
  {
    value: 'Option1',
    label: '白玻',
  },
  {
    value: 'Option2',
    label: '灰镜',
  },
  {
    value: 'Option3',
    label: 'Low-e',
  },
]
const emit = defineEmits(['send-data-inventory',]);
const props = defineProps({
  closeDialog: Function,
  thickNess:null,
  model:null
});
value1.value=props.thickNess
value2.value=props.model
const CheckInventory = () => {
  const selectedLabel1 = value1.value;
  const selectedLabel2 = value2.value;
  // 判断两个值是否都被选择了,如果有一个为空字符串,则提示并返回,不执行后续操作
  if (selectedLabel1==="" || selectedLabel2==="") {
    window.alert('请输入');
    return;
  }
  props.closeDialog(1);
  emit('send-data-inventory', selectedLabel1,selectedLabel2);
};
</script>
<template>
  <div id="box">
    <div>
      <span>厚度(mm):</span>
      <el-input v-model="value1"  style="width: 140px"></el-input>
    </div>
    <div style="margin-top: 30px">
    <span>玻璃类型  :</span>
      <el-input v-model="value2" style="width: 140px">
      </el-input>
    </div>
    <div style="float: right; margin:-55px 35px 0 0;">
      <el-button type="primary" @click="CheckInventory">查询</el-button>
    </div>
  </div>
</template>
<style scoped>
#box {
  width: 100%;
  height: 100%;
  padding: 10px;
  margin-top: -20px;
  border-radius: 5px;
}
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/Compute.vue
New file
@@ -0,0 +1,426 @@
<script setup>
import {onMounted, reactive, ref, watch} from "vue";
import {useI18n} from "vue-i18n";
import {Platform, Search, SuccessFilled} from "@element-plus/icons-vue";
import useUserInfoStore from "@/stores/userInfo";
import request from "@/utils/request";
import {ElMessage} from "element-plus";
const userStore = useUserInfoStore()
const username = userStore.user.userName
const {t} = useI18n()
const xGrid = ref()
//获取工程号
const props = defineProps({
  projectNo : String,
  project: null,
  data: {
    type: Array,
    default: () => []
  }
});
const selectOptions = [
  {
    value: '0',
    label: '默认'
  },
  {
    value: '1',
    label: '优先竖排'
  },
  {
    value: '2',
    label: '优先横排'
  }
]
// 定义混排等级
const optionVal = ref(50)
// 定义装载率
const percentage1 = ref(80)
const percentage2 = ref(50)
// 定义其他表单数据
const furnaceWidth = ref('') // 炉宽
const furnaceLength = ref('') // 炉长
const heatingTime = ref('') // 钢化加热时间
const rotateMode = ref({value: '0', label: '默认'}) // 钢化旋转模式
const spacingLong = ref('') // 长轴间隔
const spacingWidth = ref('') // 宽轴间隔
const quantity = ref('') // 工程片数
// 定义响应式数据,用于绑定工程号输入框的值
let inputValue=ref(null)
if(props.project!==undefined){
  inputValue= ref(props.project.projectNumber);
  quantity.value=props.project.quantity
}
const fetchSettings = async (username) => {
  try {
    const response = await request.post(`/glassOptimize/selectOptimizeParms/${username}`);
    if (response.code == 200) {
      if (!response.data) {
        console.error('响应数据为空');
        return;
      }
      const parsedData = JSON.parse(response.data);
      if (parsedData.tempering) {
        furnaceLength.value = parsedData.tempering.furnaceLength;
        furnaceWidth.value = parsedData.tempering.furnaceWidth;
        spacingWidth.value=parsedData.tempering.xAxisInterval;
        spacingLong.value=parsedData.tempering.yAxisInterval;
        heatingTime.value=parsedData.tempering.temperingTime;
      }
    } else {
      console.error('请求失败,状态码:', response.code);
    }
  } catch (error) {
    console.error('请求发生错误:', error);
  }
};
fetchSettings(username);
const gridOptions = reactive({
  height: '100%',
  loading: false,
  border: "full",//表格加边框
  keepSource: true,//保持源数据
  align: 'center',//文字居中
  stripe: true,//斑马纹
  rowConfig: {isCurrent: true, isHover: true, height: 30, useKey: true},//鼠标移动或选择高亮
  id: 'Compute',
  scrollX: {enabled: true},
  scrollY: {enabled: true, gt: 0},//开启虚拟滚动
  showOverflow: true,
  columnConfig: {
    resizable: true,
    useKey: true
  },
  filterConfig: {   //筛选配置项
    remote: true
  },
  customConfig: {
    storage: true
  },
  editConfig: {
    trigger: 'click',
    mode: 'row',
    showStatus: true
  },
  columns: [
    {field: 'id', width: 70, title: '序号'},
    {field: 'layoutsNumber', width: 100, title: '版图数'},
    {field: 'loadingRate', width: 100, title: '装载率'},
    {field: 'processCards', width: 100, title: '流程卡数'},
    {field: 'simulatedPieces', width: 100, title: '模拟片数'},
    {
      field: 'processId',
      width: 350,
      title: t('processCard.processId'),
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true
    },
  ],//表头参数
  data: null,//表格数据
  toolbarConfig: {
  },
})
onMounted(async() => {
  await firstLoading()
})
const firstLoading = async() => {
    request.post(`/glassOptimize/selectOptimizeParms/${username}`).then((res) => {
      if (res.code == "200") {
        const parsedData = JSON.parse(res.data);
          furnaceWidth.value=parsedData.tempering.furnaceWidth
          furnaceLength.value=parsedData.tempering.furnaceLength
          spacingLong.value=parsedData.tempering.xAxisInterval
          spacingWidth.value=parsedData.tempering.yAxisInterval
      } else {
        ElMessage.warning(res.msg)
      }
    })
}
// 监听父组件传递的数据变化
watch(() => props.data, (newValue) => {
  if (newValue) {
    // 处理数据并更新表格
    const processData = newValue.data[0];
    const processedData = [
      {
        id: "1",
        layoutsNumber: processData.resultSum[0],
        loadingRate: processData.resultSum[1],
        processCards: processData.rackinfos.length,
        simulatedPieces: processData.glass_details.length,
        processId:processData.rackinfos
      }
    ];
    gridOptions.data = processedData;
  } else {
    console.error("数据格式不正确或为空");
    gridOptions.data = [];
  }
})
const inputValues = {
  project_no:inputValue.value,
  glass_thickness:"",
  glass_type:"",
  chaos_pct: optionVal.value*0.01,//混排等级
  cage_free:percentage2.value*0.01,//理片龙空闲度
  tempering_time:heatingTime.value,//钢化加热时间
  heat_mode: 0,//模式选择
  max_load_pct: percentage1.value*0.01,//最大装载率
  max_area: 0,//最大面积
  max_qty:0,//最大片数
  load_width:null,
  load_length: null,
  x_space: null,
  y_space: null,
  load_rate: null,
  furnaces_qty:"",
  rotate_mode:0,
  polys_allow_rotate: 0,
  process_cards:[]
};
// let emit = defineEmits(['fetch-data']);
let emit = defineEmits(['fetch-data', 'sendData'])
const handleSearchClick = () => {
  // 通过 $emit 触发自定义事件,将工程号传递给父组件,由父组件去调用接口获取数据
  emit('fetch-data', inputValue.value);
};
const handleSimulationClick = () => {
      inputValues.load_width = furnaceWidth.value
      inputValues.load_length = furnaceLength.value
      inputValues.x_space = spacingWidth.value
      inputValues.y_space = spacingLong.value
      inputValues.polys_allow_rotate = parseInt(rotateMode.value.value)
  emit('sendData', {
    ...inputValues
  })
  // 触发父组件的 simulate-click 事件
  emit('simulate-click');
};
const handleSave = () => {
  if (props.data) {
    console.log(props.data)
    let projectData = ref({
      projectdetail: props.data.data[0].glass_details,
      ratioResult: props.data.data[0].ratioResult,
      rackinfos: props.data.data[0].rackinfos,
      resultSum: props.data.data[0].resultSum,
      guidance: props.data.data[0].guidance,
      userName : username,
      inputValues:inputValues
    })
    request.post(`/glassOptimize/simulationSave`, projectData.value).then((res) => {
      if (res.code == 200 && res.data === true) {
        ElMessage.success(t('basicData.msg.saveSuccess'))
      } else {
        ElMessage.warning(res.msg)
      }
    })
  }
}
</script>
<template>
<div style="width: 100%;height: 100%">
    <!--模拟计算表头-->
<div id="title" style="margin-top: -10px;height: 50%">
  <div style="display: flex">
    <div style="width: 850px">
      工程编号
      <el-input style="width:150px;margin-left: 30px" clearable v-model="inputValue" placeholder="请输入工程号"></el-input>
      <el-button
          type="primary"
          :icon="Search"
          style="margin-left: 20px"
          @click="handleSearchClick"
      >{{ $t('basicData.search') }}
      </el-button>
    </div>
    <div style="display: flex ; width: 700px;align-items: center;">
      <span>工程混排等级</span>
      <el-slider
          style="max-width: 200px; flex: 1; margin-left: 10px"
          v-model="optionVal"
          :min="0"
          :max="100"
          :step="1"/>
      <span style="margin-left: 20px ; width: 35px;">{{ optionVal }}%</span>
      <el-button type="primary" style="margin-left: 10px" :icon="Platform"  @click="handleSimulationClick">模拟计算</el-button>
      <el-button type="primary" style="margin-left: 20px" :icon="SuccessFilled"  @click="handleSave">保存</el-button>
    </div>
  </div><br>
  <div style="display:flex">
    <div class="demo-progress" style="margin-top: 5px;width: 50%">
      <div style="display: flex; align-items: center">
        <span>钢化最大装载</span>
        <!-- 进度条设置 -->
        <el-slider
            style="max-width: 400px; flex: 1; margin-left: 10px"
            v-model="percentage1"
            :min="0"
            :max="100"
            :step="1"/>
        <span style="margin-left: 20px ; width: 35px;">{{ percentage1 }}%</span>
        <!--      <span style="float: right ; margin-left: 150px;">
                    工程片数 <vxe-input size="small" disabled class="input"  v-model="quantity"></vxe-input>
                    宽轴间隔 <vxe-input size="small" class="input" clearable v-model="spacingWidth"></vxe-input>
                    炉宽(mm) <vxe-input size="small" class="input" clearable v-model="furnaceWidth"></vxe-input>
              </span>-->
      </div>
      <div style="display: flex; align-items: center">
        <span>理片笼空闲度</span>
        <!-- 进度条设置 -->
        <el-slider
            style="max-width: 400px; flex: 1; margin-left: 10px"
            v-model="percentage2"
            :min="0"
            :max="100"
            :step="1"/>
        <span style="margin-left: 20px ; width: 35px;">{{ percentage2 }}%</span>
      </div>
    </div>
    <div class="demo-progress" style="display: flex;font-size: 15px">
      <div style="height: 35px;line-height: 35px">
        <div>工程片数 <el-input size="small" disabled class="input"  v-model="quantity"></el-input></div>
        <div>钢化旋转方式
          <el-select style="width: 100px;height: 30px" v-model="rotateMode">
            <el-option
                v-for="item in selectOptions"
                :key="item.value"
                :label="item.label"
                :value="item.value"
            />
          </el-select>
        </div>
      </div>
      <div style="margin-left: 15px;height: 35px;line-height: 35px">
        <div>宽轴间隔 <el-input size="small" class="input" clearable v-model="spacingWidth"></el-input></div>
        <div>长轴间隔 <vxe-input size="small" class="input" clearable v-model="spacingLong"></vxe-input></div>
      </div>
      <div style="margin-left: 15px;height: 35px;line-height: 35px">
        <div>炉宽(mm) <el-input size="small" class="input" clearable v-model="furnaceWidth"></el-input></div>
        <div>炉长(mm) <vxe-input size="small" class="input" clearable v-model="furnaceLength"></vxe-input></div>
      </div>
    </div>
  </div>
<!--  <div class="demo-progress" style="margin-top: -10px">
    <div style="display: flex; align-items: center">
      <span>理片笼空闲度</span>
      &lt;!&ndash; 进度条设置 &ndash;&gt;
      <el-slider
          style="max-width: 400px; flex: 1; margin-left: 10px"
          v-model="percentage2"
          :min="0"
          :max="100"
          :step="1"/>
      <span style="margin-left: 20px ; width: 35px;">{{ percentage2 }}%</span>
      <span style="float: right ; margin-left: 150px;">
              加热时间(秒)<el-select style="width: 50px;height: 30px" v-model="rotateMode">
                            <el-option
                                v-for="item in selectOptions"
                                :key="item.value"
                                :label="item.label"
                                :value="item.value"
                            />
                        </el-select>
              长轴间隔 <vxe-input size="small" class="input" clearable v-model="spacingLong"></vxe-input>
              炉长(mm) <vxe-input size="small" class="input" clearable v-model="furnaceLength"></vxe-input>
      </span>
    </div>
  </div>-->
</div>
<div style="height: 50%">
  <vxe-grid
      size="small"
      @filter-change="filterChanged"
      height="50%"
      class="mytable-scrollbar"
      ref="xGrid"
      v-bind="gridOptions"
      v-on="gridEvents"
  >
    <template #num2_filter="{ column, $panel }">
      <div>
        <div v-for="(option, index) in column.filters" :key="index">
          <vxe-select v-model="option.data" :placeholder="$t('processCard.pleaseSelect')"
                      @change="changeFilterEvent($event, option, $panel)">
            <vxe-option value="0" :label="$t('basicData.unchecked')"></vxe-option>
            <vxe-option value="1" :label="$t('basicData.selected')"></vxe-option>
          </vxe-select>
        </div>
      </div>
    </template>
    <template #num1_filter="{ column, $panel }">
      <div>
        <div v-for="(option, index) in column.filters" :key="index">
          <input
              type="type"
              v-model="option.data"
              @keyup.enter.native="$panel.confirmFilter()"
              @input="changeFilterEvent($event, option, $panel)"/>
        </div>
      </div>
    </template>
  </vxe-grid>
</div>
  </div>
</template>
<style scoped>
.input {
  width: 80px;
  height: 30px;
}
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/ComputeCard.vue
New file
@@ -0,0 +1,185 @@
<script setup>
import {nextTick, onMounted, reactive, ref, watch} from "vue";
import request from "@/utils/request";
import {useI18n} from "vue-i18n";
import {ElMessage} from "element-plus";
import deepClone from "@/utils/deepClone";
const { t } = useI18n()
const xGrid = ref()
const checkboxCellRender = reactive({
  name: 'VxeCheckboxGroup',
  options: [
    { label: '幕墙模式', value: '1' },
    { label: '允许横排', value: '2' },
    { label: '钢化', value: '3' },
  ]
})
const gridOptions = reactive({
  height:'100%',
  loading: false,
  border:  "full",//表格加边框
  keepSource: true,//保持源数据
  align: 'center',//文字居中
  stripe:true,//斑马纹
  rowConfig: {isCurrent: true, isHover: true,height: 30, useKey: true},//鼠标移动或选择高亮
  id: 'ComputeCard',
  scrollX:{enabled: true},
  scrollY:{ enabled: true ,gt:0},//开启虚拟滚动
  showOverflow:true,
  columnConfig: {
    resizable: true,
    useKey: true
  },
  filterConfig: {   //筛选配置项
    remote: true
  },
  customConfig: {
    storage: true
  },
  editConfig: {
    trigger: 'click',
    mode: 'row',
    showStatus: true
  },
  columns:[
    {type: 'seq', title: t('basicData.Number'), width: 80},
    {field: 'curtain_wall',title: '幕墙模式',width: 80, slots: { default: 'state1' }},
    {field: "allow_rotate", title: '允许横排', width: 80, slots: { default: 'state2' }},
    {field: "tempering", title: '钢化', width: 80, slots: { default: 'state3' }},
    {field: 'processId',width: 150, title: t('processCard.processId'), sortable: true},
    {field: 'technologyNumber',width: 70, title: '层', sortable: true},
    {field: 'total_layers',width: 150, title: '总层数', sortable: true},
    {field: 'TotalNumber',width: 150, title: '规格', sortable: true},
    {field: 'total_num',width: 150, title: t('order.quantity'), sortable: true},
    {field: 'glass_child',width: 150, title: t('order.product'), sortable: true},
    {field: 'project', width:150, title: t('order.project'), showOverflow: "ellipsis"},
    {field: 'total_area',width: 150, title: t('order.area'), sortable: true},
  ],//表头参数
  data:null,//表格数据
  toolbarConfig: {
    buttons: [],
    zoom: true,
    custom: true
  },
})
let emit = defineEmits([
  'changeDialog','upProcessId', 'sendData'
]);
const selectFullData =  () => {
  return xGrid.value.getTableData().fullData
}
const props = defineProps({
  tableData: Array,
  processId: null,
  technologyNumber: null,
  patchState: null,
});
watch(() => props.tableData, async (newData) => {
  if (Array.isArray(newData)) {
    console.log(newData)
    xGrid.value.loadData(deepClone(newData))
    const data = xGrid.value.getTableData().fullData
    data.forEach(item => {
      item.tempering = item.tempering === 1;
      item.allow_rotate = item.allow_rotate === 1;
      item.curtain_wall = item.curtain_wall === 1;
    })
  }
});
let process_id = ref()
//获取流程卡号详情
let rowClickIndex = ref(null)
const gridEvents = {
  cellClick({row}) {
    rowClickIndex.value = row
    // Emit 事件将更新后的值传递给父组件
    emit('upProcessId', rowClickIndex.value.processId,rowClickIndex.value.technologyNumber);
    emit('updateTechnologyNumber', rowClickIndex.value.processId,rowClickIndex.value.technologyNumber);
  }
}
defineExpose({selectFullData})
</script>
<template>
  <div style="width: 100%;height: 100%">
    <span>流程卡</span>
    <vxe-grid
        size="small"
        height="100%"
        class="mytable-scrollbar"
        ref="xGrid"
        v-bind="gridOptions"
        v-on="gridEvents"
    >
      <template #num2_filter="{ column, $panel }">
        <div>
          <div v-for="(option, index) in column.filters" :key="index">
            <vxe-select v-model="option.data" :placeholder="$t('processCard.pleaseSelect')"  @change="changeFilterEvent($event, option, $panel)">
              <vxe-option value="0" :label="$t('basicData.unchecked')"></vxe-option>
              <vxe-option value="1" :label="$t('basicData.selected')"></vxe-option>
            </vxe-select>
          </div>
        </div>
      </template>
      <template #num1_filter="{ column, $panel }">
        <div>
          <div v-for="(option, index) in column.filters" :key="index">
            <input
                type="type"
                v-model="option.data"
                @keyup.enter.native="$panel.confirmFilter()"
                @input="changeFilterEvent($event, option, $panel)"/>
          </div>
        </div>
      </template>
      <template #state1="{ row,column}">
        <el-checkbox
            v-model="row.curtain_wall"/>
      </template>
      <template #state2="{ row,column}">
        <el-checkbox
            v-model="row.allow_rotate"/>
      </template>
      <template #state3="{ row,column}">
        <el-checkbox
            v-model="row.tempering"/>
      </template>
    </vxe-grid>
  </div>
</template>
<style scoped>
:deep(.vxe-tools--operate){
  height: 20px;
  margin-top: -20px;
}
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/ComputeDetail.vue
New file
@@ -0,0 +1,134 @@
<script setup>
import {onMounted, reactive, ref, watch} from "vue";
import {useI18n} from "vue-i18n";
import request from "@/utils/request";
import {ElMessage} from "element-plus";
import {Minus, Plus, Search} from "@element-plus/icons-vue";
import {changeFilterEvent, filterChanged} from "@/hook";
const { t } = useI18n()
// 接收父组件传递过来的流程卡号(processId)
let props = defineProps({
  processId:null,
  patchState:null,
  technologyNumber:null
});
const xGrid = ref()
const gridOptions = reactive({
  height:'100%',
  loading: false,
  border:  "full",//表格加边框
  keepSource: true,//保持源数据
  align: 'center',//文字居中
  stripe:true,//斑马纹
  rowConfig: {isCurrent: true, isHover: true,height: 30, useKey: true},//鼠标移动或选择高亮
  id: 'ComputeDetail',
  scrollX:{enabled: true},
  scrollY:{ enabled: true ,gt:0},//开启虚拟滚动
  showOverflow:true,
  columnConfig: {
    resizable: true,
    useKey: true
  },
  filterConfig: {   //筛选配置项
    //remote: true
  },
  customConfig: {
    storage: true
  },
  editConfig: {
    trigger: 'click',
    mode: 'row',
    showStatus: true
  },
  columns:[
    {type: 'seq',width: 70,  title: '序号',filters:[{ data: '' }],},
    {field: 'width',width: 150,title: t('order.width'),filters:[{ data: '' }],slots: { filter: 'num1_filter' },filterMethod:filterChanged},
    {field: 'height',width: 150,title: t('order.height'),filters:[{ data: '' }],slots: { filter: 'num1_filter' },filterMethod:filterChanged},
    {field: 'quantity',width: 150, title: t('order.quantity'),filters:[{ data: '' }],slots: { filter: 'num1_filter' },filterMethod:filterChanged},
    {field: 'building_number',width: 150, title: t('order.buildingNumber'),filters:[{ data: '' }],slots: { filter: 'num1_filter' },filterMethod:filterChanged},
    {field: 'shape',width: 150, title: t('order.shape'),filters:[{ data: '' }],slots: { filter: 'num1_filter' },filterMethod:filterChanged},
    {field: 'area',width: 150, title: t('order.grossArea'),filters:[{ data: '' }],slots: { filter: 'num1_filter' },filterMethod:filterChanged},
    {field: 'icon',width: 150, title: '印标',filters:[{ data: '' }],slots: { filter: 'num1_filter' },filterMethod:filterChanged},
  ],//表头参数
  data:null,//表格数据
  toolbarConfig: {
    buttons: [],
    zoom: true,
    custom: true
  },
})
watch(
    ()=> [props.processId,props.technologyNumber],
    ([newValue, oldValue])=> {
  if (props.processId!=null){
    selectComputeDetail()
  }
});
const selectComputeDetail =  () => {
  if (props.processId!=null || props.processId!=""){
    request.post(`/glassOptimize/selectComputeDetailThirdParty/${props.processId}/${props.technologyNumber}`).then((res) => {
      if(Number(res.code) === 200){
        xGrid.value.loadData(res.data.data)
      }else{
        ElMessage.warning(res.msg)
      }
    })
  }
}
</script>
<template>
  <div style="width: 100%;height: 100%">
    <span>流程卡详情</span>
    <vxe-grid
        size="small"
        @filter-change="filterChanged"
        height="100%"
        class="mytable-scrollbar"
        ref="xGrid"
        v-bind="gridOptions"
        v-on="gridEvents"
    >
      <template #num2_filter="{ column, $panel }">
        <div>
          <div v-for="(option, index) in column.filters" :key="index">
            <vxe-select v-model="option.data" :placeholder="$t('processCard.pleaseSelect')"  @change="changeFilterEvent($event, option, $panel)">
              <vxe-option value="0" :label="$t('basicData.unchecked')"></vxe-option>
              <vxe-option value="1" :label="$t('basicData.selected')"></vxe-option>
            </vxe-select>
          </div>
        </div>
      </template>
      <template #num1_filter="{ column, $panel }">
        <div>
          <div v-for="(option, index) in column.filters" :key="index">
            <input type="type" v-model="option.data" @keyup.enter.native="$panel.confirmFilter()" @input="changeFilterEvent($event, option, $panel)"/>
          </div>
        </div>
      </template>
    </vxe-grid>
  </div>
</template>
<style scoped>
:deep(.vxe-tools--operate){
  height: 20px;
  margin-top: -20px;
}
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/GlassInventory.vue
New file
@@ -0,0 +1,322 @@
<script setup>
import {nextTick, onMounted, reactive, ref, watch} from "vue";
import {useI18n} from "vue-i18n";
import request from "@/utils/request";
import {ElMessage, ElMessageBox} from "element-plus";
import {useRoute} from "vue-router";
import {addListener, toolbarButtonClickEvent} from "@/hook/mouseMove";
const { t } = useI18n()
const xGrid = ref()
const gridOptions = reactive({
  height:'100%',
  loading: false,
  border:  "full",//表格加边框
  keepSource: true,//保持源数据
  align: 'center',//文字居中
  stripe:true,//斑马纹
  rowConfig: {isCurrent: true, isHover: true,height: 30, useKey: true},//鼠标移动或选择高亮
  id: 'GlassInventory',
  scrollX:{enabled: true},
  scrollY:{ enabled: true ,gt:0},//开启虚拟滚动
  showOverflow:true,
  columnConfig: {
    resizable: true,
    useKey: true
  },
  filterConfig: {   //筛选配置项
    remote: true
  },
  customConfig: {
    storage: true
  },
  editConfig: {
    trigger: 'dblclick',
    mode: 'cell',
    showStatus: true
  },
  columns:[
    {type:'seq',fixed:"left", title:' ', width: 50},
    {type: 'checkbox', fixed: "left", title: t('basicData.check'), width: 80},
    {field: 'id', title: '物料编码',},
    {field:'width',editRender: { name: 'input',attrs: {disabled:true}}, title: t('order.width'),},
    {field: 'height',editRender: { name: 'input',attrs: {disabled:true}},title: t('order.height')},
    {field: 'thickness', title: t('order.totalThickness'),},
    {field: 'model', title: t('warehouseBasicData.type'),},
    {field: 'leftTrim',editRender: { name: 'input',attrs: {disabled:false}}, title: '左修边',},
    {field: 'downTrim',editRender: { name: 'input',attrs: {disabled:false}}, title: '下修边',},
    {field: 'rightTrim',editRender: { name: 'input',attrs: {disabled:false}}, title: '右修边',},
    {field: 'upTrim',editRender: { name: 'input',attrs: {disabled:false}}, title: '上修边',},
    {field: 'available_quantity',editRender: { name: 'input',attrs: {disabled:false}}, title: '库存数量',},
    {field: 'processingQuantity', title: '加工数量',},
    {field: 'name', title: '名称',},
    {field: 'producer', title: '供应商',}
  ],//表头参数
  data:null,//表格数据
  //右键菜单
  menuConfig: {
    body: {
      options: [
        [
          {code: 'selectTrimming', name: '设置统一修边',},
          {code: 'Exports', name: '数据导出', prefixIcon: 'vxe-icon-download', visible: true, disabled: false},
          {code: 'addRow', name: t('basicData.add'), prefixIcon: 'vxe-icon-square-plus', visible: true, disabled: true },
        ],
      ]
    },
  },
  toolbarConfig: {
    buttons: [],
  },
})
const emit = defineEmits(['select-trimming']);
// 右键菜单
const operationConfigs = [
  {
    code: 'selectTrimming', // 设置统一修边
    successMsg: '已打开!',
    gridRef: xGrid,
    requiresRow: false,
    openTrimming: async () => {
     emit ( 'select-trimming', true)
    }
  },
  {
    code: 'Exports', // 导出文件操作的配置
    successMsg: '文件导出成功!',
    gridRef: xGrid,
    requiresRow: false,
  },
  {
    code: 'addRow', // 导出文件操作的配置
    successMsg: '添加成功!',
    gridRef: xGrid,
    requiresRow: false,
  },
]
// 右键菜单点击逻辑
const gridEvents = {
  menuClick({menu}) {
    const $grid = xGrid.value;
    if ($grid) {
      const config = operationConfigs.find(c => c.code === menu.code);
      if (config) {
        if (config.code === 'Exports') {
          config.gridRef.value.exportData();
          ElMessage.success(config.successMsg);
          return;
        }
        if (config.code === 'addRow') {
          if ($grid.getTableData().tableData.length >=100){
            ElMessage.error(t('order.msg.tableLengthMax'))
            return
          }
          if ($grid.getCheckedFilters().length!==0){
            ElMessage.error(t('order.msg.pleaseCancelTheFilteringFirst'))
            return
          }
          console.log($grid.getTableData().visibleData)
          let result = toolbarButtonClickEvent()
          let lengths = 0
          if (result!=null){
            lengths=result.start
          }
          if($grid.getTableData().fullData.length>lengths+1){
            $grid.insertAt({}, lengths+1)
          }else{
            $grid.insertAt({}, -1)
          }
          return;
        }
        // 添加确认提示弹窗,询问用户是否进行当前操作
        ElMessageBox.confirm('是否进行当前操作?', '确认操作', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          if (config.code === 'selectTrimming') {
            config.openTrimming();
            ElMessage.success(config.successMsg);
          }
        }).catch(() => {
          // 用户点击取消后执行的逻辑
          ElMessage.info('已取消操作');
        });
      } else {
        console.error(`未找到操作选项 ${menu.code} 对应的配置,请检查配置项`);
      }
    }
  },
};
const route = useRoute();
const thickness = ref(route.params.thickNess);
const model = ref(route.params.model);
let projectNo = ref(null);
let type = ref(1);
const selectMaterialStore = async () =>{
  request.get(`/glassOptimize/materialStoreSvThirdParty/${projectNo}`).then((res) => {
    if (Number(res.code) === 200) {
      const rawData = res.data.data;
      const edgeTrimming = res.data.edgeTrimming;
      let state = res.data.state;
      if (Array.isArray(rawData) && rawData.length > 0) {
        const formattedData = rawData.map(item => {
          const formattedItem = {};
          for (const key in item) {
            if (typeof item[key] === 'string') {
              //去除字符串属性值开头和结尾的双引号
              formattedItem[key] = item[key].replace(/^\"|\"$/g, '');
            } else {
              formattedItem[key] = item[key];
            }
          }
          return formattedItem;
        });
          gridOptions.columns[7].editRender.attrs.disabled=true
          gridOptions.columns[8].editRender.attrs.disabled=true
          gridOptions.columns[9].editRender.attrs.disabled=true
          gridOptions.columns[10].editRender.attrs.disabled=true
          gridOptions.columns[11].editRender.attrs.disabled=true
        xGrid.value.loadData(formattedData);
        gridOptions.data = formattedData;
      } else {
      }
    } else {
      ElMessage.warning(res.msg);
      console.error('请求获取库存失败,状态码:', res.code, ',错误信息:', res.msg);
    }
  });
};
onMounted(() => {
  addListener(xGrid.value,gridOptions)
  if(route.params.projectNo!=null){
    projectNo=route.params.projectNo
    selectMaterialStore();
  }
});
watch(() => props.receivedData, (newData) => {
  if (newData) {
    Trimming(newData);
  }
}, { immediate: true });
watch(() => props.InventoryData, (newInventoryData) => {
  if (newInventoryData) {
    thickness.value = newInventoryData.selectedLabel1;
    model.value = newInventoryData.selectedLabel2;
    type.value=newInventoryData.type
    // 由于 thickness 和 model 的值改变了,更新表格,调用 selectMaterialStore 重新获取数据
    selectMaterialStore();
  }
});
const props = defineProps({
  receivedData : {
    type: Object,
    required: false,
    properties: {
      quicksetLeft: { type: Number },
      quicksetBottom: { type: Number },
      quicksetRight: { type: Number },
      quicksetTop: { type: Number }
    }
  },
  InventoryData : {
    type: Object,
    required: false,
    properties: {
      selectedLabel1: { type: String },
      selectedLabel2: { type: String },
      type: { type: String }
    }
  }
});
//接受SetTrimming的值 (修边)
const Trimming = (receivedData) => {
  nextTick(() => {
    const data = gridOptions.data;
    if (data) {
      try {
        const updatedData = [];
        for (let i = 0; i < data.length; i++) {
          const item = data[i];
          const updatedItem = {
            ...item,
            // 从 receivedData 中获取对应的值来更新表格数据项
            leftTrim: Number(receivedData.quicksetLeft),
            downTrim: Number(receivedData.quicksetBottom),
            rightTrim: Number(receivedData.quicksetRight),
            upTrim: Number(receivedData.quicksetTop),
          };
          updatedData.push(updatedItem);
        }
        gridOptions.data = updatedData;
        xGrid.value.loadData(updatedData);
      } catch (error) {
        console.error('更新表格数据时出错:', error);
        ElMessage.error('更新表格数据时出现错误,请检查输入或联系管理员');
      }
    } else {
      console.warn('表格数据为空,无法更新磨量值');
    }
  });
};
</script>
<template>
  <div style="width: 100%;height: 100%">
    <vxe-grid
        height="100%"
        class="mytable-scrollbar"
        ref="xGrid"
        v-bind="gridOptions"
        v-on="gridEvents"
    >
      <template #num2_filter="{ column, $panel }">
        <div>
          <div v-for="(option, index) in column.filters" :key="index">
            <vxe-select v-model="option.data" :placeholder="$t('processCard.pleaseSelect')"  @change="changeFilterEvent($event, option, $panel)">
              <vxe-option value="0" :label="$t('basicData.unchecked')"></vxe-option>
              <vxe-option value="1" :label="$t('basicData.selected')"></vxe-option>
            </vxe-select>
          </div>
        </div>
      </template>
      <template #num1_filter="{ column, $panel }">
        <div>
          <div v-for="(option, index) in column.filters" :key="index">
            <input
                type="type"
                v-model="option.data"
                @keyup.enter.native="$panel.confirmFilter()"
                @input="changeFilterEvent($event, option, $panel)"/>
          </div>
        </div>
      </template>
    </vxe-grid>
  </div>
</template>
<style scoped>
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/OptimizationRect.vue
New file
@@ -0,0 +1,1122 @@
<template>
  <div style="display: flex; height: 100vh;">
    <!-- Sidebar -->
    <div class="sidebar" style="width: 200px; background: #f4f4f4; padding: 10px;">
      <div
        v-for="(layout, layoutIndex) in layouts"
        :key="layoutIndex"
        class="sidebar-item"
        @click="selectLayout(layoutIndex)"
        :class="{ 'selected': selectedLayoutIndex === layoutIndex }"
      >
        {{ layout.width }} × {{ layout.height }} × {{ layout.SameCount }}
      </div>
    </div>
    <!-- Main Layout Panel -->
    <div ref="layoutPanel" :class="panelClass" :style="panelStyle">
      <div
        v-for="(layout, layoutIndex) in layouts"
        :key="layoutIndex"
        class="layout-wrapper"
        :style="{ display: selectedLayoutIndex === layoutIndex ? 'block' : 'none', top: '-150px' }"
      >
        <!-- Layout Info Label -->
        <div class="layout-info" :style="layoutInfoStyle(layoutIndex)">
          {{ getCurrentRectInfo(layoutIndex) }}
        </div>
        <!-- Layout Container -->
        <div class="layout-container" :style="layoutContainerStyle(layoutIndex)">
          <!-- 灰色矩形 -->
          <div
            v-for="(rect, rectIndex) in layout.rects.filter(r => r.isRemain)"
            :key="`gray-${rectIndex}`"
            :ref="(el) => { if (el) rectsElements[layoutIndex + '-' + rectIndex] = el }"
            class="layout-rect"
            :style="rectStyle(rect, layoutIndex)"
            @contextmenu.prevent="handleGrayRectRightClick(layoutIndex, rectIndex)"
          />
          <!-- 蓝色矩形 -->
          <div
            v-for="(rect, rectIndex) in layout.rects.filter(r => !r.isRemain)"
            :key="`blue-${rectIndex}`"
            :ref="(el) => { if (el) rectsElements[layoutIndex + '-' + rectIndex] = el }"
            class="layout-rect"
            :style="rectStyle(rect, layoutIndex)"
            @contextmenu.prevent="handleRectRightClick(layoutIndex, rectIndex)"
            @mousedown="handleRectDragStart(layoutIndex, rectIndex)"
            @mousemove="handleRectDragging"
            @mouseup="handleRectDragEnd"
            @mouseleave="handleRectDragEnd"
            @click="handleRectClick(layoutIndex, rectIndex)"
          >
            <div class="rect-content">
              <div class="size">{{ rect.w }}×{{ rect.h }}</div>
              <div v-if="showJiaHao" class="jia-hao">{{ rect.JiaHao }}</div>
              <div v-if="showProcessId" class="liuchengka">{{ rect.liuchengka }}</div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <!-- 提交按钮 -->
    <button @click="submitLayouts" style="position: fixed; bottom: 20px; right: 20px; padding: 10px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer;">
      保存调整
    </button>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue';
import request from "@/utils/request";
import { useI18n } from "vue-i18n";
import { ElMessage, ElMessageBox } from "element-plus";
import useUserInfoStore from "@/stores/userInfo";
const { t } = useI18n();
const userStore = useUserInfoStore()
const username = userStore.user.userName
let clickEventListener = null;
const props = defineProps({
  layoutData: { type: Object, required: true },
  gw: { type: Number, default: 1000 },
  gh: { type: Number, default: 1000 },
  style: { type: String, default: 'width:1000px;height:600px;display:block;background:gray' }
});
const emit = defineEmits(['rectClicked']);
const layoutPanel = ref(null);
const rectsElements = ref({});
const focusIndex = ref(null);
const layouts = ref([]);
const panelClass = ref('');
const panelStyle = ref(props.style);
const rectClass = ref('layout-rect');
const selectedLayoutIndex = ref(0);
const currentRect = ref(null);
const dragging = ref(false);
const dragStartPos = ref({ x: 0, y: 0 });
const dragRect = ref(null);
const showJiaHao = ref(false);
const showProcessId = ref(false);
const themeColor = ref(null);
const submitLayouts = async () => {
  layouts.value.forEach(layout => {
    layout.rects.forEach(rect => {
      rect.x = Math.round(rect.x);
      rect.y = Math.round(rect.y);
      rect.w = Math.round(rect.w);
      rect.h = Math.round(rect.h);
    });
  });
  const savedProjectNo = localStorage.getItem('projectNo');
  const processId = savedProjectNo;
  const Layouts = layouts.value;
  request.post(`/glassOptimize/updateOptimizeResult/${processId}`, JSON.stringify({ Layouts }), {
    headers: {
      'Content-Type': 'application/json'
    }
  }).then((res) => {
    if (res.code == 200 && res.data === true) {
      ElMessage.success(t('basicData.msg.saveSuccess'));
    } else {
      ElMessage.warning(res.msg);
    }
  });
};
const fetchSettings = async (username) => {
  try {
    const response = await request.post(`/glassOptimize/selectOptimizeParms/${username}`);
    if (response.code == 200) {
      if (!response.data) {
        console.error('响应数据为空');
        return;
      }
      const parsedData = JSON.parse(response.data);
      if (parsedData.display && parsedData.frameNumber) {
        showJiaHao.value = parsedData.display.frameNumber;
      }
      if (parsedData.display && parsedData.orderNumber) {
        showProcessId.value = parsedData.display.orderNumber;
      }
      if (parsedData.display) {
        themeColor.value = parsedData.display.themeColor;
      }
    } else {
      console.error('请求失败,状态码:', response.code);
    }
  } catch (error) {
    console.error('请求发生错误:', error);
  }
};
const showAddDialog = (layoutIndex, rectIndex) => {
  ElMessageBox.prompt('请输入成品的宽度和高度', '添加成品', {
    inputType: 'text',
    inputValidator: (value) => {
      const values = value.split(',').map(v => v.trim());
      if (values.length !== 2) {
        return '请输入两个数字,用逗号分隔';
      }
      const [width, height] = values;
      if (isNaN(width) || isNaN(height)) {
        return '请输入有效的数字';
      }
      if (width <= 0 || height <= 0) {
        return '宽度和高度必须大于0';
      }
      return true;
    },
    inputErrorMessage: '输入格式不正确'
  })
    .then(({ value }) => {
      const values = value.split(',').map(v => parseFloat(v.trim()));
      const newRect = {
        x: 0,
        y: 0,
        w: values[0],
        h: values[1],
        isRemain: false
      };
      addNewRect(layoutIndex, newRect);
    })
    .catch(() => {
      // 用户取消
    });
};
const addNewRect = (layoutIndex, newRect) => {
  const layout = layouts.value[layoutIndex];
  const bestFitPosition = findBestFitPosition(layoutIndex, newRect);
  if (bestFitPosition) {
    newRect.x = bestFitPosition.x;
    newRect.y = bestFitPosition.y;
    layout.rects.push(newRect);
    adjustGrayRectangles(layoutIndex);
  } else {
    ElMessage.warning('无法放置,没有足够的空间');
  }
};
const findBestFitPosition = (layoutIndex, newRect) => {
  const layout = layouts.value[layoutIndex];
  const obstacles = layout.rects.filter(r => !r.isRemain);
  let bestFit = null;
  let minAreaDifference = Infinity;
  const remainingAreas = calculateRemainingAreas(layout.width, layout.height, obstacles);
  remainingAreas.forEach(area => {
    if (newRect.w <= area.w && newRect.h <= area.h) {
      const areaDifference = Math.abs(area.w * area.h - newRect.w * newRect.h);
      if (areaDifference < minAreaDifference) {
        minAreaDifference = areaDifference;
        bestFit = {
          x: area.x,
          y: area.y,
          w: newRect.w,
          h: newRect.h
        };
      }
    }
  });
  return bestFit;
};
const layoutContainerStyle = (layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const scale = Math.min(
    (props.gw - 100) / layout.width,
    (props.gh - 100) / layout.height
  );
  return {
    position: 'absolute',
    left: `${(props.gw - layout.width * scale) / 2}px`,
    top: `${(props.gh - layout.height * scale) / 2}px`,
    width: `${layout.width * scale}px`,
    height: `${layout.height * scale}px`,
    overflow: 'visible',
    border: '1px solid #ccc',
    background: '#fff'
  };
};
const layoutInfoStyle = (layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const scale = Math.min(
    (props.gw - 100) / layout.width,
    (props.gh - 100) / layout.height
  );
  return {
    position: 'absolute',
    left: `${(props.gw - layout.width * scale) / 2}px`,
    top: `${(props.gh - layout.height * scale) / 2 - 45}px`,
    background: 'none',
    textAlign: 'center',
    zIndex: 1000
  };
};
const rectStyle = (rect, layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const scale = Math.min(
    (props.gw - 100) / layout.width,
    (props.gh - 100) / layout.height
  );
  return {
    position: 'absolute',
    left: `${rect.x * scale}px`,
    top: `${rect.y * scale}px`,
    width: `${rect.w * scale}px`,
    height: `${rect.h * scale}px`,
    backgroundColor: rect.isRemain ? '#f0f0f0' : themeColor.value,
    border: '1px solid #000',
    cursor: 'pointer',
    draggable: !rect.isRemain,
    zIndex: rect.isRemain ? 1 : 2
  };
};
const handleRectClick = (layoutIndex, rectIndex) => {
  focusIndex.value = { layoutIndex, rectIndex };
  emit('rectClicked', layoutIndex, rectIndex);
};
const handleRectRightClick = (layoutIndex, rectIndex) => {
  const rect = layouts.value[layoutIndex].rects[rectIndex];
  if (rect.isRemain) return;
  const contextMenu = document.createElement('div');
  contextMenu.className = 'context-menu';
  contextMenu.style.position = 'absolute';
  contextMenu.style.left = `${event.clientX}px`;
  contextMenu.style.top = `${event.clientY}px`;
  contextMenu.style.backgroundColor = '#fff';
  contextMenu.style.border = '1px solid #ccc';
  contextMenu.style.padding = '5px';
  contextMenu.style.zIndex = 1001;
  const rotateItem = document.createElement('div');
  rotateItem.textContent = '旋转';
  rotateItem.style.cursor = 'pointer';
  rotateItem.addEventListener('click', () => {
    rotateRect(layoutIndex, rectIndex);
    document.body.removeChild(contextMenu);
  });
  const moveUpAndRotateItem = document.createElement('div');
  moveUpAndRotateItem.textContent = '向上移动并旋转';
  moveUpAndRotateItem.style.cursor = 'pointer';
  moveUpAndRotateItem.addEventListener('click', () => {
    moveRectAndRotate(layoutIndex, rectIndex, 'up');
    document.body.removeChild(contextMenu);
  });
  const moveDownAndRotateItem = document.createElement('div');
  moveDownAndRotateItem.textContent = '向下移动并旋转';
  moveDownAndRotateItem.style.cursor = 'pointer';
  moveDownAndRotateItem.addEventListener('click', () => {
    moveRectAndRotate(layoutIndex, rectIndex, 'down');
    document.body.removeChild(contextMenu);
  });
  const moveLeftAndRotateItem = document.createElement('div');
  moveLeftAndRotateItem.textContent = '向左移动并旋转';
  moveLeftAndRotateItem.style.cursor = 'pointer';
  moveLeftAndRotateItem.addEventListener('click', () => {
    moveRectAndRotate(layoutIndex, rectIndex, 'left');
    document.body.removeChild(contextMenu);
  });
  const moveRightAndRotateItem = document.createElement('div');
  moveRightAndRotateItem.textContent = '向右移动并旋转';
  moveRightAndRotateItem.style.cursor = 'pointer';
  moveRightAndRotateItem.addEventListener('click', () => {
    moveRectAndRotate(layoutIndex, rectIndex, 'right');
    document.body.removeChild(contextMenu);
  });
  const moveUpItem = document.createElement('div');
  moveUpItem.textContent = '向上移动';
  moveUpItem.style.cursor = 'pointer';
  moveUpItem.addEventListener('click', () => {
    moveRect(layoutIndex, rectIndex, 'up');
    document.body.removeChild(contextMenu);
  });
  const moveDownItem = document.createElement('div');
  moveDownItem.textContent = '向下移动';
  moveDownItem.style.cursor = 'pointer';
  moveDownItem.addEventListener('click', () => {
    moveRect(layoutIndex, rectIndex, 'down');
    document.body.removeChild(contextMenu);
  });
  const moveLeftItem = document.createElement('div');
  moveLeftItem.textContent = '向左移动';
  moveLeftItem.style.cursor = 'pointer';
  moveLeftItem.addEventListener('click', () => {
    moveRect(layoutIndex, rectIndex, 'left');
    document.body.removeChild(contextMenu);
  });
  const moveRightItem = document.createElement('div');
  moveRightItem.textContent = '向右移动';
  moveRightItem.style.cursor = 'pointer';
  moveRightItem.addEventListener('click', () => {
    moveRect(layoutIndex, rectIndex, 'right');
    document.body.removeChild(contextMenu);
  });
  const deleteItem = document.createElement('div');
  deleteItem.textContent = '删除';
  deleteItem.style.cursor = 'pointer';
  deleteItem.addEventListener('click', () => {
    deleteRect(layoutIndex, rectIndex);
    document.body.removeChild(contextMenu);
  });
  const addItem = document.createElement('div');
  addItem.textContent = '添加成品';
  addItem.style.cursor = 'pointer';
  addItem.addEventListener('click', () => {
    showAddDialog(layoutIndex, rectIndex);
    document.body.removeChild(contextMenu);
  });
  const mirrorXItem = document.createElement('div');
  mirrorXItem.textContent = 'X镜像';
  mirrorXItem.style.cursor = 'pointer';
  mirrorXItem.addEventListener('click', () => {
    mirrorLayoutX(layoutIndex);
    document.body.removeChild(contextMenu);
  });
  const mirrorYItem = document.createElement('div');
  mirrorYItem.textContent = 'Y镜像';
  mirrorYItem.style.cursor = 'pointer';
  mirrorYItem.addEventListener('click', () => {
    mirrorLayoutY(layoutIndex);
    document.body.removeChild(contextMenu);
  });
  contextMenu.appendChild(rotateItem);
  contextMenu.appendChild(moveUpAndRotateItem);
  contextMenu.appendChild(moveDownAndRotateItem);
  contextMenu.appendChild(moveLeftAndRotateItem);
  contextMenu.appendChild(moveRightAndRotateItem);
  contextMenu.appendChild(moveUpItem);
  contextMenu.appendChild(moveDownItem);
  contextMenu.appendChild(moveLeftItem);
  contextMenu.appendChild(moveRightItem);
  contextMenu.appendChild(deleteItem);
  contextMenu.appendChild(addItem);
  contextMenu.appendChild(mirrorXItem);
  contextMenu.appendChild(mirrorYItem);
  document.body.appendChild(contextMenu);
};
const handleGrayRectRightClick = (layoutIndex, rectIndex) => {
  const rect = layouts.value[layoutIndex].rects[rectIndex];
  if (!rect.isRemain) return;
  const contextMenu = document.createElement('div');
  contextMenu.className = 'context-menu';
  contextMenu.style.position = 'absolute';
  contextMenu.style.left = `${event.clientX}px`;
  contextMenu.style.top = `${event.clientY}px`;
  contextMenu.style.backgroundColor = '#fff';
  contextMenu.style.border = '1px solid #ccc';
  contextMenu.style.padding = '5px';
  contextMenu.style.zIndex = 1001;
  const addItem = document.createElement('div');
  addItem.textContent = '添加成品';
  addItem.style.cursor = 'pointer';
  addItem.addEventListener('click', () => {
    showAddDialog(layoutIndex, rectIndex);
    document.body.removeChild(contextMenu);
  });
  const mirrorXItem = document.createElement('div');
  mirrorXItem.textContent = 'X镜像';
  mirrorXItem.style.cursor = 'pointer';
  mirrorXItem.addEventListener('click', () => {
    mirrorLayoutX(layoutIndex);
    document.body.removeChild(contextMenu);
  });
  const mirrorYItem = document.createElement('div');
  mirrorYItem.textContent = 'Y镜像';
  mirrorYItem.style.cursor = 'pointer';
  mirrorYItem.addEventListener('click', () => {
    mirrorLayoutY(layoutIndex);
    document.body.removeChild(contextMenu);
  });
  contextMenu.appendChild(addItem);
  contextMenu.appendChild(mirrorXItem);
  contextMenu.appendChild(mirrorYItem);
  document.body.appendChild(contextMenu);
};
const handleRectDragStart = (layoutIndex, rectIndex) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[rectIndex];
  if (rect.isRemain) return;
  dragging.value = true;
  dragRect.value = { layoutIndex, rectIndex };
  dragStartPos.value = {
    x: event.clientX,
    y: event.clientY
  };
};
const handleRectDragging = (event) => {
  if (!dragging.value || !dragRect.value) return;
  const layoutIndex = dragRect.value.layoutIndex;
  const rectIndex = dragRect.value.rectIndex;
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[rectIndex];
  const scale = Math.min(
    (props.gw - 100) / layout.width,
    (props.gh - 100) / layout.height
  );
  const deltaX = event.clientX - dragStartPos.value.x;
  const deltaY = event.clientY - dragStartPos.value.y;
  const newRect = { ...rect };
  newRect.x += deltaX / scale;
  newRect.y += deltaY / scale;
  const otherRects = layout.rects.filter(r => !r.isRemain && r !== rect);
  let isValidMove = true;
  otherRects.forEach(otherRect => {
    if (checkOverlap(newRect, otherRect)) {
      isValidMove = false;
    }
  });
  if (newRect.x < 0 || newRect.y < 0 ||
      newRect.x + newRect.w > layout.width ||
      newRect.y + newRect.h > layout.height) {
    isValidMove = false;
  }
  if (isValidMove) {
    rect.x = newRect.x;
    rect.y = newRect.y;
    dragStartPos.value = {
      x: event.clientX,
      y: event.clientY
    };
    adjustGrayRectangles(layoutIndex);
  }
};
const handleRectDragEnd = () => {
  if (dragRect.value) {
    const layoutIndex = dragRect.value.layoutIndex;
    const rectIndex = dragRect.value.rectIndex;
    const rect = layouts.value[layoutIndex].rects[rectIndex];
    const layout = layouts.value[layoutIndex];
    const scale = Math.min(
      (props.gw - 100) / layout.width,
      (props.gh - 100) / layout.height
    );
    rect.x = Math.round(rect.x);
    rect.y = Math.round(rect.y);
    adjustAlignmentPosition(layoutIndex, rectIndex);
    adjustGrayRectangles(layoutIndex);
  }
  dragging.value = false;
  dragRect.value = null;
};
const adjustAlignmentPosition = (layoutIndex, rectIndex) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[rectIndex];
  const otherRects = layout.rects.filter((r, i) => i !== rectIndex);
  const threshold = Math.max(rect.w, rect.h) * 0.1;
  otherRects.forEach(otherRect => {
    // 水平对齐
    if (Math.abs(rect.x - otherRect.x) < threshold) {
      rect.x = Math.round((rect.x + otherRect.x) / 2);
    }
    // 水平对齐右侧边缘
    if (Math.abs((rect.x + rect.w) - (otherRect.x + otherRect.w)) < threshold) {
      rect.x = Math.round((otherRect.x + otherRect.w - rect.w));
    }
    // 垂直对齐
    if (Math.abs(rect.y - otherRect.y) < threshold) {
      rect.y = Math.round((rect.y + otherRect.y) / 2);
    }
    // 垂直对齐下边缘
    if (Math.abs((rect.y + rect.h) - (otherRect.y + otherRect.h)) < threshold) {
      rect.y = Math.round((otherRect.y + otherRect.h - rect.h));
    }
  });
  // 确保矩形不会超出布局边界
  rect.x = Math.max(0, rect.x);
  rect.y = Math.max(0, rect.y);
  rect.x = Math.min(rect.x, layout.width - rect.w);
  rect.y = Math.min(rect.y, layout.height - rect.h);
  // 调整后重新计算灰色余料
  adjustGrayRectangles(layoutIndex);
};
const mergeAdjacentGrayRects = (rects) => {
  const grayRects = rects.filter(r => r.isRemain);
  let merged = [];
  grayRects.sort((a, b) => {
    if (a.x !== b.x) return a.x - b.x;
    return a.y - b.y;
  });
  if (grayRects.length === 0) return;
  merged.push({ ...grayRects[0] });
  for (let i = 1; i < grayRects.length; i++) {
    const last = merged[merged.length - 1];
    const current = grayRects[i];
    if (current.x === last.x + last.w &&
        current.y === last.y &&
        current.h === last.h) {
      last.w += current.w;
      last.x = Math.round(last.x);
      last.y = Math.round(last.y);
      last.w = Math.round(last.w);
      last.h = Math.round(last.h);
    } else if (current.y === last.y + last.h &&
               current.x === last.x &&
               current.w === last.w) {
      last.h += current.h;
      last.x = Math.round(last.x);
      last.y = Math.round(last.y);
      last.w = Math.round(last.w);
      last.h = Math.round(last.h);
    } else {
      merged.push({
        x: Math.round(current.x),
        y: Math.round(current.y),
        w: Math.round(current.w),
        h: Math.round(current.h),
        isRemain: true
      });
    }
  }
  const nonGray = rects.filter(r => !r.isRemain);
  rects.splice(0, rects.length, ...nonGray, ...merged);
};
const adjustGrayRectangles = (layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const rects = layout.rects;
  const nonGrayRects = rects.filter(rect => !rect.isRemain);
  const remainingAreas = calculateRemainingAreas(layout.width, layout.height, nonGrayRects);
  const currentGrayRects = rects.filter(r => r.isRemain);
  currentGrayRects.forEach((_, index) => {
    if (index >= remainingAreas.length) {
      rects.splice(index, 1);
    }
  });
  remainingAreas.forEach((area, index) => {
    if (index < currentGrayRects.length) {
      currentGrayRects[index].x = Math.round(area.x);
      currentGrayRects[index].y = Math.round(area.y);
      currentGrayRects[index].w = Math.round(area.w);
      currentGrayRects[index].h = Math.round(area.h);
    } else {
      rects.push({
        x: Math.round(area.x),
        y: Math.round(area.y),
        w: Math.round(area.w),
        h: Math.round(area.h),
        isRemain: true
      });
    }
  });
  mergeAdjacentGrayRects(rects);
};
const rotateRect = (layoutIndex, rectIndex) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[rectIndex];
  const originalState = { ...rect };
  const temp = rect.w;
  rect.w = rect.h;
  rect.h = temp;
  const otherRects = layout.rects.filter(r => !r.isRemain && r !== rect);
  let isValidRotation = true;
  otherRects.forEach(otherRect => {
    if (checkOverlap(rect, otherRect)) {
      isValidRotation = false;
    }
  });
  if (rect.x + rect.w > layout.width || rect.y + rect.h > layout.height) {
    isValidRotation = false;
  }
  if (isValidRotation) {
    adjustGrayRectangles(layoutIndex);
  } else {
    rect.w = originalState.w;
    rect.h = originalState.h;
    ElMessage.warning('无法旋转,存在重叠或超出边界');
  }
};
const moveRectAndRotate = (layoutIndex, rectIndex, direction) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[rectIndex];
  const grayRects = layout.rects.filter(r => r.isRemain);
  const temp = rect.w;
  rect.w = rect.h;
  rect.h = temp;
  const canPlace = grayRects.some(grayRect => {
    return grayRect.w >= rect.w && grayRect.h >= rect.h;
  });
  if (!canPlace) {
    const temp = rect.w;
    rect.w = rect.h;
    rect.h = temp;
    ElMessage.warning('无法旋转,没有足够的空间');
    return;
  }
  adjustGrayRectangles(layoutIndex);
  moveRect(layoutIndex, rectIndex, direction);
};
const moveRect = (layoutIndex, rectIndex, direction) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[rectIndex];
  const originalState = { ...rect };
  let maxStep = 0;
  const obstacles = layout.rects.filter(r => r.isRemain || r !== rect);
  switch (direction) {
    case 'up':
      maxStep = getAvailableSpaceUp(rect, layout, obstacles);
      break;
    case 'down':
      maxStep = getAvailableSpaceDown(rect, layout, obstacles);
      break;
    case 'left':
      maxStep = getAvailableSpaceLeft(rect, layout, obstacles);
      break;
    case 'right':
      maxStep = getAvailableSpaceRight(rect, layout, obstacles);
      break;
  }
  if (maxStep <= 0) {
    ElMessage.warning('无法移动,没有足够的空间');
    return;
  }
  switch (direction) {
    case 'up':
      rect.y -= maxStep;
      break;
    case 'down':
      rect.y += maxStep;
      break;
    case 'left':
      rect.x -= maxStep;
      break;
    case 'right':
      rect.x += maxStep;
      break;
  }
  const otherRects = layout.rects.filter(r => !r.isRemain && r !== rect);
  let isValidMove = true;
  otherRects.forEach(otherRect => {
    if (checkOverlap(rect, otherRect)) {
      isValidMove = false;
    }
  });
  if (rect.x < 0 || rect.y < 0 ||
      rect.x + rect.w > layout.width ||
      rect.y + rect.h > layout.height) {
    isValidMove = false;
  }
  if (isValidMove) {
    adjustGrayRectangles(layoutIndex);
  } else {
    rect.x = originalState.x;
    rect.y = originalState.y;
    ElMessage.warning('无法移动,存在重叠或超出边界');
  }
};
const deleteRect = (layoutIndex, rectIndex) => {
  const layout = layouts.value[layoutIndex];
  layout.rects.splice(rectIndex, 1);
  adjustGrayRectangles(layoutIndex);
};
const checkOverlap = (rect1, rect2) => {
  return !(rect1.x + rect1.w <= rect2.x ||
           rect1.x >= rect2.x + rect2.w ||
           rect1.y + rect1.h <= rect2.y ||
           rect1.y >= rect2.y + rect2.h);
};
const calculateRemainingAreas = (totalWidth, totalHeight, obstacles) => {
  let remaining = [{ x: 0, y: 0, w: totalWidth, h: totalHeight }];
  obstacles.forEach(rect => {
    remaining = cutRemainingAreas(remaining, rect);
  });
  return remaining;
};
const cutRemainingAreas = (remainingAreas, obstacle) => {
  const newRemaining = [];
  remainingAreas.forEach(area => {
    if (checkOverlap(area, obstacle)) {
      if (obstacle.x > area.x) {
        newRemaining.push({
          x: area.x,
          y: area.y,
          w: obstacle.x - area.x,
          h: area.h
        });
      }
      if (obstacle.x + obstacle.w < area.x + area.w) {
        newRemaining.push({
          x: obstacle.x + obstacle.w,
          y: area.y,
          w: area.w - (obstacle.x + obstacle.w - area.x),
          h: area.h
        });
      }
      if (obstacle.y > area.y) {
        newRemaining.push({
          x: area.x,
          y: area.y,
          w: area.w,
          h: obstacle.y - area.y
        });
      }
      if (obstacle.y + obstacle.h < area.y + area.h) {
        newRemaining.push({
          x: area.x,
          y: obstacle.y + obstacle.h,
          w: area.w,
          h: area.h - (obstacle.y + obstacle.h - area.y)
        });
      }
    } else {
      newRemaining.push(area);
    }
  });
  return newRemaining;
};
const getCurrentRectInfo = (layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[focusIndex.value?.rectIndex || 0];
  if (!rect) return '';
  const totalRects = layouts.value.length;
  const currentRectIndex = layoutIndex + 1;
  const width = layout.width;
  const height = layout.height;
  const percentage = ((rect.w / layout.width) * 100).toFixed(1) + '%';
  return `${currentRectIndex}/${totalRects} ${width}×${height} ×1 ${percentage}`;
};
const selectLayout = (layoutIndex) => {
  selectedLayoutIndex.value = layoutIndex;
};
const updateLayout = () => {
  if (!layoutPanel.value) return;
  layouts.value = props.layoutData.Layouts;
};
const getAvailableSpaceUp = (rect, layout, obstacles) => {
  let maxSpace = rect.y;
  obstacles.forEach(obstacle => {
    if (obstacle.y + obstacle.h < rect.y &&
        obstacle.x <= rect.x + rect.w &&
        obstacle.x + obstacle.w >= rect.x) {
      maxSpace = Math.min(maxSpace, rect.y - (obstacle.y + obstacle.h));
    }
  });
  return maxSpace;
};
const getAvailableSpaceDown = (rect, layout, obstacles) => {
  let maxSpace = layout.height - (rect.y + rect.h);
  obstacles.forEach(obstacle => {
    if (obstacle.y > rect.y + rect.h &&
        obstacle.x <= rect.x + rect.w &&
        obstacle.x + obstacle.w >= rect.x) {
      maxSpace = Math.min(maxSpace, obstacle.y - (rect.y + rect.h));
    }
  });
  return maxSpace;
};
const getAvailableSpaceLeft = (rect, layout, obstacles) => {
  let maxSpace = rect.x;
  obstacles.forEach(obstacle => {
    if (obstacle.x + obstacle.w < rect.x &&
        obstacle.y <= rect.y + rect.h &&
        obstacle.y + obstacle.h >= rect.y) {
      maxSpace = Math.min(maxSpace, rect.x - (obstacle.x + obstacle.w));
    }
  });
  return maxSpace;
};
const getAvailableSpaceRight = (rect, layout, obstacles) => {
  let maxSpace = layout.width - (rect.x + rect.w);
  obstacles.forEach(obstacle => {
    if (obstacle.x > rect.x + rect.w &&
        obstacle.y <= rect.y + rect.h &&
        obstacle.y + obstacle.h >= rect.y) {
      maxSpace = Math.min(maxSpace, obstacle.x - (rect.x + rect.w));
    }
  });
  return maxSpace;
};
let moveInterval = null;
const handleKeyDown = (event) => {
  if (!focusIndex.value) return;
  const { layoutIndex, rectIndex } = focusIndex.value;
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[rectIndex];
  const obstacles = layout.rects.filter(r => r.isRemain || r !== rect);
  switch (event.key) {
    case 'ArrowUp':
      event.preventDefault();
      if (!moveInterval) {
        moveInterval = setInterval(() => {
          moveRect(layoutIndex, rectIndex, 'up');
        }, 50);
      }
      break;
    case 'ArrowDown':
      event.preventDefault();
      if (!moveInterval) {
        moveInterval = setInterval(() => {
          moveRect(layoutIndex, rectIndex, 'down');
        }, 50);
      }
      break;
    case 'ArrowLeft':
      event.preventDefault();
      if (!moveInterval) {
        moveInterval = setInterval(() => {
          moveRect(layoutIndex, rectIndex, 'left');
        }, 50);
      }
      break;
    case 'ArrowRight':
      event.preventDefault();
      if (!moveInterval) {
        moveInterval = setInterval(() => {
          moveRect(layoutIndex, rectIndex, 'right');
        }, 50);
      }
      break;
  }
};
const handleKeyUp = (event) => {
  if (event.key === 'ArrowUp' || event.key === 'ArrowDown' ||
      event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
    if (moveInterval) {
      clearInterval(moveInterval);
      moveInterval = null;
    }
  }
};
const mirrorLayoutX = (layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const width = layout.width;
  const rects = [...layout.rects]; // 创建副本避免直接修改
  rects.forEach(rect => {
    // 计算X镜像后的坐标
    const newX = width - rect.x - rect.w;
    const newY = rect.y;
    // 更新矩形位置
    rect.x = newX;
    rect.y = newY;
  });
  // 更新布局
  layout.rects = rects;
  adjustGrayRectangles(layoutIndex);
};
const mirrorLayoutY = (layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const height = layout.height;
  const rects = [...layout.rects]; // 创建副本避免直接修改
  rects.forEach(rect => {
    // 计算Y镜像后的坐标
    const newX = rect.x;
    const newY = height - rect.y - rect.h;
    // 更新矩形位置
    rect.y = newY;
  });
  // 更新布局
  layout.rects = rects;
  adjustGrayRectangles(layoutIndex);
};
onMounted(() => {
  fetchSettings(username);
 updateLayout();
  selectedLayoutIndex.value = 0;
  clickEventListener = (event) => {
    const contextMenus = document.querySelectorAll('.context-menu');
    if (contextMenus.length > 0) {
      contextMenus.forEach(menu => menu.remove());
    }
  };
  document.addEventListener('click', clickEventListener);
  document.addEventListener('keydown', handleKeyDown);
  document.addEventListener('keyup', handleKeyUp);
});
onUnmounted(() => {
  rectsElements.value = {};
  if (clickEventListener) {
    document.removeEventListener('click', clickEventListener);
    clickEventListener = null;
  }
  document.removeEventListener('keydown', handleKeyDown);
  document.removeEventListener('keyup', handleKeyUp);
});
</script>
<style scoped>
.layout-wrapper {
  position: relative;
  margin-top: 50px;
}
.layout-rect {
  user-select: none;
}
.layout-container {
  position: relative;
  overflow: visible;
}
.layout-info {
  color: #444;
  font-size: 12px;
  background-color: #ffffff;
  padding: 5px 10px;
  border-radius: 3px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  font-weight: bold;
}
.rect-content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  padding: 5px;
  min-width: 60px;
  min-height: 20px;
  white-space: normal;
  overflow-wrap: break-word;
}
.size {
  font-size: 12px;
  color: #444;
  white-space: normal;
  word-wrap: break-word;
}
.jia-hao, .liuchengka {
  font-size: 14px;
  font-weight: bold;
  white-space: normal;
  word-wrap: break-word;
}
.sidebar-item {
  padding: 10px;
  cursor: pointer;
}
.sidebar-item.selected {
  background: #ddd;
}
.context-menu {
  position: absolute;
  background-color: #fff;
  border: 1px solid #ccc;
  padding: 5px;
  z-index: 1001;
}
.context-menu div {
  padding: 5px;
  cursor: pointer;
}
.context-menu div:hover {
  background-color: #f0f0f0;
}
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/OptimizeCompute.vue
New file
@@ -0,0 +1,144 @@
<script setup>
import {ref, computed} from "vue";
import {Connection} from "@element-plus/icons-vue";
//计时器
let seconds = ref(0);
let isRunning = ref(false);
let intervalId;
//优化进度条
let progress = ref(0);
//优化次数;时长;效率
let progressValue1 = ref(10);
let progressValue2 = ref(1000);
let progressValue3 = ref(50);
const formattedTime = computed(() => {
  const minutes = Math.floor(seconds.value / 60);
  const remainingSeconds = seconds.value % 60;
  return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
});
const startTimer = () => {
  if (!isRunning.value) {
    intervalId = setInterval(() => {
      seconds.value++;
      progress.value += 10;
      if (progress.value >= 100) {
        progress.value = 100;
        clearInterval(intervalId);
      }
    }, 1000);
    isRunning.value = true;
  }
};
const pauseTimer = () => {
  if (isRunning.value) {
    clearInterval(intervalId);
    isRunning.value = false;
  }
};
const resetTimer = () => {
  clearInterval(intervalId);
  isRunning.value = false;
  seconds.value = 0;
  progress.value = 0;
};
</script>
<template>
  <div class="header">
    <el-header>
      <div class="header-content">
        <span style="margin-left: -20px;">需切成品</span><br>
        <div>
          <span>总面积(m2):</span>
          <vxe-input class="input" disabled placeholder=""></vxe-input>
          <span>总数量(片):</span>
          <vxe-input class="input" disabled placeholder=""></vxe-input>
          <span>优化时长(秒):</span>
          <span class="time-display">{{ formattedTime }}</span>
          <el-button type="primary" style="margin-left: 20px" @click="startTimer">开始优化</el-button>
          <el-button class="buttons" type="primary" @click="pauseTimer">暂停</el-button>
          <el-button class="buttons" type="primary" @click="resetTimer">完成</el-button>
        </div>
        <br>
        <div style="display: flex;margin-top: -10px">
          <span>方案池 优化次数:</span>
          <el-slider v-model="progressValue1" :min="2" :max="20" size="small" style="max-width: 200px;"></el-slider>
          <span>单次优化时长:</span>
          <el-slider v-model="progressValue2" :min="50" :max="2000" size="small" style="max-width: 200px;"></el-slider>
          <span>钢化效率优先:</span>
          <el-slider v-model="progressValue3" :show-tooltip="false" size="small" style="max-width: 200px;"></el-slider>
          <span>切裁率优先</span>
        </div>
      </div>
    </el-header>
  </div>
  <div id="box">
    <span style="font-size: 20px">优化进度</span>
    <div class="progress-bar">
      <el-progress :percentage="progress"
                   :text-inside="true"
                   :stroke-width="20"
                   status="success"
                   striped
                   striped-flow
                   duration="45"/>
    </div>
    <div style="height: 100px; margin-top: 20px; border: 2px solid #e6e6e6; padding: 10px; border-radius: 5px;">
      <span style="font-weight: bold">耗用原片</span>
    </div>
    <div style="height: 130px; margin-top: 20px;  display: flex; border: 2px solid #e6e6e6; padding: 10px; border-radius: 5px;">
      <span style="font-weight: bold">当前结果</span>
      <div style="margin-top: 5%">
        <span>产生时间:{{ formattedTime }}</span>
        <span style="margin-left: 20px">总利用率:0.0%</span>
        <span style="margin-left: 20px">尾片:</span>
      </div>
    </div>
  </div>
</template>
<style scoped>
.header {
  height: 120px;
  background-color: #d5eaff;
  margin-top: -30px;
  padding: 10px;
  border-radius: 5px;
}
div header span {
  margin-left: 10px;
  font-weight: bold;
}
.input {
  border: none !important;
  height: 25px;
  width: 80px;
}
.time-display {
  font-size: 18px;
  color: #409eff;
}
.buttons {
  float: right;
}
#box {
  padding: 10px;
  margin-top: 10px;
  width: 100%;
  height: 80%;
}
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/ProcessCard.vue
New file
@@ -0,0 +1,374 @@
<script setup>
import {onMounted, reactive, ref} from "vue";
import {useI18n} from "vue-i18n";
import {Search} from "@element-plus/icons-vue";
import request from "@/utils/request";
import deepClone from "@/utils/deepClone";
import {ElMessage} from "element-plus";
import useUserInfoStore from "@/stores/userInfo";
import {changeFilterEvent, filterChanged} from "@/hook";
const {t} = useI18n()
const userStore = useUserInfoStore()
const username = userStore.user.userName
let rowClickIndex = ref(null)
let props = defineProps({
  processId: null,
  technologyNumber: null
})
//膜系
const optionVal = ref()
//膜系
const projectNmae = ref()
//工程编号个数
let projectId = ref()
//工程编号
const Id = ref()
let oddNumbers = ref()
//定义接收加载表头下拉数据
const titleSelectJson = ref({
  processType: [],
})
const selectGlassType = () => {
  //查询膜系
  request.post(`/glassOptimize/selectGlassType`).then((res) => {
    if (res.code == 200) {
      titleSelectJson.value.processType = res.data.data
    } else {
      ElMessage.warning(res.msg)
    }
  })
}
//查询最新工程号
const getProjectId = () => {
  request.post(`/glassOptimize/getProjectId`).then((res) => {
    if (res.code == 200) {
      projectId = res.data.data[0].project_no
      // 获取字符串的最后两位数字
      let maximum = projectId.slice(-2);
      let lastTwoInteger = parseInt(maximum, 10);
       // 设置两位不够补0
      let formattedNumber = (lastTwoInteger + 1).toString().padStart(2, '0');
      // 格式化当前日期为 "yyMMdd"
      let currentDate = new Date();
      let formattedDate = currentDate.getFullYear().toString().slice(-2) +
          (currentDate.getMonth() + 1).toString().padStart(2, '0') +
          currentDate.getDate().toString().padStart(2, '0');
      // 拼接成最终的字符串
      oddNumbers.value = 'P' + formattedDate + formattedNumber;
    } else {
      ElMessage.warning(res.msg)
    }
  })
}
onMounted(() => {
  getProjectId();
  selectGlassType();
})
const xGrid = ref()
const gridOptions = reactive({
  height: '100%',
  loading: false,
  border: "full",//表格加边框
  keepSource: true,//保持源数据
  align: 'center',//文字居中
  stripe: true,//斑马纹
  rowConfig: {isCurrent: true, isHover: true, height: 30, useKey: true},//鼠标移动或选择高亮
  id: 'ProcessCard',
  scrollX: {enabled: true},
  scrollY: {enabled: true, gt: 0},//开启虚拟滚动
  showOverflow: true,
  columnConfig: {
    resizable: true,
    useKey: true
  },
  filterConfig: {   //筛选配置项
    //remote: true
  },
  customConfig: {
    storage: true
  },
  editConfig: {
    trigger: 'click',
    mode: 'row',
    showStatus: true
  },
  columns: [
    {field: 'select',type:'checkbox',title: t('basicData.check'), width: 80,fixed:"left"},
    {
      field: 'process_id',
      width: 150,
      title: t('processCard.processId'),
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true,
      filterMethod:filterChanged
    },
    {
      field: 'technology_number',
      width: 70,
      title: '层',
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true,
      filterMethod:filterChanged
    },
    {
      field: 'TotalFloors',
      width: 150,
      title: '总层数',
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true,
      filterMethod:filterChanged
    },
    {
      field: 'TotalNumber',
      width: 150,
      title: '规格',
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true,
      filterMethod:filterChanged
    },
    {
      field: 'quantity',
      width: 150,
      title: t('order.quantity'),
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true,
      filterMethod:filterChanged
    },
    {
      field: 'shape',
      width: 150,
      title: t('order.shape'),
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true,
      filterMethod:filterChanged
    },
    {
      field: 'glass_child',
      width: 150,
      title: t('order.product'),
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true,
      filterMethod:filterChanged
    },
    {field: 'project', width: 150, title: t('order.project'), showOverflow: "ellipsis",filterMethod:filterChanged},
    {
      field: 'area',
      width: 150,
      title: t('order.area'),
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true,
      filterMethod:filterChanged
    },{
      field: 'patch_state',
      width: 100,
      title: '补片状态'
    },
  ],//表头参数
  data: null,//表格数据
  toolbarConfig: {
    buttons: [],
    slots: {
      buttons: "toolbar_buttons"
    },
  },
})
const gridEvents = {
  cellClick({row}) {
    rowClickIndex.value = row
    // Emit 事件将更新后的值传递给父组件
    emit('updateProcessId', rowClickIndex.value.process_id);
    emit('updateTechnologyNumber', rowClickIndex.value.technology_number);
  }
}
const emit = defineEmits(['updateProcessId', 'updateTechnologyNumber', 'updateState']);
//小圆点单选框
let radio = ref(1);
let isButtonDisabledAdd = ref(false);
let isButtonDisabledUpdate = ref(true);
const selectFlowCardList = async () => {
  request.post(`/glassOptimize/getFlowCardList/${optionVal.value}/${radio.value}`).then((res) => {
    if (res.code == 200) {
      getProjectId()
      isButtonDisabledAdd.value=false
      isButtonDisabledUpdate.value = true
      xGrid.value.loadData(res.data.data)
    } else {
      ElMessage.warning(res.msg)
    }
  })
}
const getUpdateFlowCardList = async (projectNumber,type,thickness) => {
  radio.value=1
  request.post(`/glassOptimize/getUpdateFlowCardList/${type}/${thickness}/${radio.value}/${projectNumber}`).then((res) => {
    if (res.code == 200) {
      oddNumbers.value=projectNumber
      optionVal.value=thickness+"mm"+type
      isButtonDisabledAdd.value=true
      isButtonDisabledUpdate.value=false
      xGrid.value.loadData(deepClone(res.data.data))
      xGrid.value.getTableData().fullData.forEach(item => {
        if(item.occupyState===0){
          xGrid.value.setCheckboxRow(item, true);
        }
      })
    } else {
      ElMessage.warning(res.msg)
    }
  })
}
//创建工程
const addProject = (type) => {
  const $table = xGrid.value
  if ($table) {
    const selectRecords = $table.getCheckboxRecords()
    if($table.getCheckedFilters().length!==0){
      ElMessage.error(t('order.msg.pleaseCancelTheFilteringFirst'))
      return
    }
    if (selectRecords.length == 0) {
      ElMessage.warning(t('reportingWorks.selectProcessCardData'))
      return;
    }
    let projectData = ref({
      projectdetail: selectRecords,
      userName : username,
      projectType: type
    })
    let inputProject = projectNmae.value
    if (inputProject == undefined) {
      inputProject = null
    }
    request.post(`/glassOptimize/addProject/${optionVal.value}/${oddNumbers.value}/${inputProject}`, projectData.value).then((res) => {
      if (res.code == 200 && res.data === "true") {
        emit('updateState', 1);
        ElMessage.success(t('basicData.msg.saveSuccess'))
        selectFlowCardList()
        getProjectId();
      }else if(res.data === "false1") {
        ElMessage.warning("工程号已存在请重新刷新界面")
      } else {
        ElMessage.warning(res.msg)
      }
    })
  }
}
const handleRowClassName = ({ row, rowIndex }) => {
  if (row.patch_state === 1) {
    return 'high-score';
  }
  if (row.occupyState === 0) {
    return 'high-score2';
  }
}
//抛出方法到父界面
defineExpose({getProjectId,selectFlowCardList,selectGlassType,getUpdateFlowCardList})
</script>
<template>
  <div style="width: 100%;height: 110%; margin-top: -36px">
    <div>
      流程卡列表
      <span style="margin-left: 140px;font-size: 14px">工程编号:</span>
<!--      <span>{{oddNumbers}}</span>-->
      <el-input disabled v-model="oddNumbers" style="width: 100px"></el-input>
<!--      <vxe-input :disabled="isDisabled" placeholder="" v-model="oddNumbers" size="small" style="color:black;"></vxe-input>&nbsp;-->
      <span style="font-size: 14px">工程名称:</span>
      <el-input style="width: 150px" placeholder="" v-model="projectNmae" size="small"></el-input>
      <el-button style="margin-left: 20px" type="primary" :disabled="isButtonDisabledAdd" @click="addProject(1)">创建</el-button>
      <el-button style="margin-left: 20px;" type="primary"  :disabled="isButtonDisabledUpdate"  @click="addProject(2)">修改</el-button>
    </div>
    <vxe-grid
        ref="xGrid"
        class="mytable-scrollbar"
        height="100%"
        size="small"
        v-bind="gridOptions"
        v-on="gridEvents"
        :row-class-name="handleRowClassName"
    >
      <template #num2_filter="{ column, $panel }">
        <div>
          <div v-for="(option, index) in column.filters" :key="index">
            <vxe-select v-model="option.data" :placeholder="$t('processCard.pleaseSelect')"
                        @change="changeFilterEvent($event, option, $panel)">
              <vxe-option :label="$t('basicData.unchecked')" value="0"></vxe-option>
              <vxe-option :label="$t('basicData.selected')" value="1"></vxe-option>
            </vxe-select>
          </div>
        </div>
      </template>
      <template #num1_filter="{ column, $panel }">
        <div>
          <div v-for="(option, index) in column.filters" :key="index">
            <input type="type" v-model="option.data" @keyup.enter.native="$panel.confirmFilter()" @input="changeFilterEvent($event, option, $panel)"/>
          </div>
        </div>
      </template>
      <template #toolbar_buttons>
        <h1>膜系筛选:</h1>
        <el-select v-model="optionVal" clearable default-value="default_city" placeholder="选择膜系"
                   style="width: 120px">
          <el-option
              v-for="item in titleSelectJson['processType']"
              :key="item.id"
              :label="item.glassType"
              :value="item.glassType"
          />
        </el-select>
        <el-button :icon="Search" style="margin-left: 20px" type="primary" @click="selectFlowCardList">查询</el-button>
        <vxe-radio-group v-model="radio" style="margin-left: 20px">
          <vxe-radio content="全部" label="1"></vxe-radio>
          <vxe-radio content="正单" label="2"></vxe-radio>
          <vxe-radio content="补单" label="3"></vxe-radio>
        </vxe-radio-group>
      </template>
    </vxe-grid>
  </div>
</template>
<style lang="css">
.high-score {
  background-color: #d4baba !important;
}
.high-score2 {
  background-color: #a5aec9 !important;
}
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/ProcessCardDetail.vue
New file
@@ -0,0 +1,149 @@
<script setup>
import {onMounted, reactive, ref, watch} from "vue";
import {useI18n} from "vue-i18n";
import request from "@/utils/request";
import {ElMessage} from "element-plus";
import {changeFilterEvent, filterChanged} from "@/hook";
const { t } = useI18n()
let props = defineProps({
  processId:null,
  technologyNumber:null
})
const xGrid = ref()
const gridOptions = reactive({
  height:'100%',
  loading: false,
  border:  "full",//表格加边框
  keepSource: true,//保持源数据
  align: 'center',//文字居中
  stripe:true,//斑马纹
  rowConfig: {isCurrent: true, isHover: true,height: 30, useKey: true},//鼠标移动或选择高亮
  id: 'ProcessCardDetail',
  scrollX:{enabled: true},
  scrollY:{ enabled: true ,gt:0},//开启虚拟滚动
  showOverflow:true,
  columnConfig: {
    resizable: true,
    useKey: true
  },
  filterConfig: {   //筛选配置项
    //remote: true
  },
  customConfig: {
    storage: true
  },
  editConfig: {
    trigger: 'click',
    mode: 'row',
    showStatus: true
  },
  /*formConfig: {
    data: {
      width: '',
      height: '',
      quantity: ''
    },
    items: [
      { field: 'width', title: t('order.width')+':', itemRender: { name: 'VxeInput' } },
      { field: 'height', title: t('order.height')+':', itemRender: { name: 'VxeInput' } },
      { field: 'quantity', title: t('order.quantity')+':', itemRender: { name: 'VxeInput' } },
      {
        itemRender: {
          name: 'VxeButtonGroup',
          options: [
            { type: 'submit', content: t('craft.sure'), status: 'primary' },
            { type: 'reset', content: t('product.msg.reset') }
          ]
        }
      }
    ]
  },*/
  columns:[
    {field: 'order_number',width: 70,  title: '序号',filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true,filterMethod:filterChanged},
    {field:'child_width',width: 150,title: t('order.width'),filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true,filterMethod:filterChanged},
    {field: 'child_height',width: 150,title: t('order.height'),filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true,filterMethod:filterChanged},
    {field: 'quantity',width: 150, title: t('order.quantity'),filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true,filterMethod:filterChanged},
    {field: 'building_number',width: 150, title: t('order.buildingNumber'),filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true,filterMethod:filterChanged},
    {field: 'shape',width: 150, title: t('order.shape'),filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true,filterMethod:filterChanged},
    {field: 'grossArea',width: 150, title: t('order.grossArea'),filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true,filterMethod:filterChanged},
    {field: 'separation',width: 150, title: t('craft.TrademarkAttribute'),filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true,filterMethod:filterChanged},
  ],//表头参数
  data:null,//表格数据
  toolbarConfig: {
    buttons: [],
    slots:{
    },
  },
})
onMounted(()=>{
})
watch(
    () => props.processId,
    (newValue, oldValue) => {
if (props.processId!=null){
  getWorkOrder()
}
    }
);
const getWorkOrder = () => {
  if (props.processId!=null || props.processId!=""){
    request.post(`/glassOptimize/getProcessCardDetail/${props.processId}/${props.technologyNumber}`).then((res) => {
      if(res.code==200){
        xGrid.value.loadData(res.data.data)
      }else{
        ElMessage.warning(res.msg)
      }
    })
  }
}
</script>
<template>
  <div style="width: 100%;height: 100%">
    <h1>流程卡详情</h1>
    <vxe-grid
        size="small"
        height="100%"
        class="mytable-scrollbar"
        ref="xGrid"
        v-bind="gridOptions"
        v-on="gridEvents"
    >
      <template #num2_filter="{ column, $panel }">
        <div>
          <div v-for="(option, index) in column.filters" :key="index">
            <vxe-select v-model="option.data" :placeholder="$t('processCard.pleaseSelect')"  @change="changeFilterEvent($event, option, $panel)">
              <vxe-option value="0" :label="$t('basicData.unchecked')"></vxe-option>
              <vxe-option value="1" :label="$t('basicData.selected')"></vxe-option>
            </vxe-select>
          </div>
        </div>
      </template>
      <template #num1_filter="{ column, $panel }">
        <div>
          <div v-for="(option, index) in column.filters" :key="index">
            <input type="type" v-model="option.data" @keyup.enter.native="$panel.confirmFilter()" @input="changeFilterEvent($event, option, $panel)"/>
          </div>
        </div>
      </template>
    </vxe-grid>
  </div>
</template>
<style scoped>
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/ProjectDetail.vue
New file
@@ -0,0 +1,611 @@
<script setup>
import {nextTick, onMounted, reactive, ref, watch,onBeforeUnmount } from "vue";
import {useI18n} from "vue-i18n";
import {Folder, Plus, Setting, Operation,} from "@element-plus/icons-vue";
import OptimizeCompute from "@/views/pp/glassOptimize/page/OptimizeCompute.vue";
import SetAmount from "@/views/pp/glassOptimize/page/SetAmount.vue";
import SetTrimming from "@/views/pp/glassOptimize/page/SetTrimming.vue";
import CheckInventory from "@/views/pp/glassOptimize/page/CheckInventory.vue";
import request from "@/utils/request";
import {ElMessage, ElMessageBox} from "element-plus";
import {useRoute} from 'vue-router';
const {t} = useI18n()
const xGrid = ref()
const gridOptions = reactive({
  height: '100%',
  loading: false,
  border: "full",//表格加边框
  keepSource: true,//保持源数据
  align: 'center',//文字居中
  stripe: true,//斑马纹
  rowConfig: {isCurrent: true, isHover: true, height: 30, useKey: true},//鼠标移动或选择高亮
  id: 'ProjectDetail',
  scrollX: {enabled: true},
  scrollY: {enabled: true, gt: 0},//开启虚拟滚动
  showOverflow: true,
  columnConfig: {
    resizable: true,
    useKey: true
  },
  filterConfig: {   //筛选配置项
    remote: true
  },
  customConfig: {
    storage: true
  },
  editConfig: {
    trigger: 'click',
    mode: 'row',
    showStatus: true
  },
  columns: [
    {type: 'seq', title: t('basicData.Number'), width: 80},
    {field: 'order_number', title: '订序', width: 70},
    {field: 'width',
      width: 100,
      title: t('order.width'),
      sortable: true
    },
    {
      field: 'height',
      width: 100,
      title: t('order.height'),
      sortable: true
    },
    {
      field: 'quantity',
      width: 150,
      title: t('order.quantity'),
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true
    },
    {
      field: 'longGrind1',
      width: 150,
      title: '长磨1',
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true
    },
    {
      field: 'longGrind2',
      width: 150,
      title: '长磨2',
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true
    },
    {
      field: 'shortGrind1',
      width: 150,
      title: '短磨1',
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true
    },
    {
      field: 'shortGrind2',
      width: 150,
      title: '短磨2',
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true
    },
    {
      field: 'shape',
      width: 150,
      title: t('order.shape'),
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true
    },
    {
      field: 'process_id',
      width: 150,
      title: '流程卡号',
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true
    },
    {
      field: 'product_name',
      width: 150,
      title: t('order.product'),
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true
    },
    {
      field: 'price',
      width: 150,
      title: t('单价'),
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true
    },
    {
      field: 'remark',
      width: 150,
      title: t('basicData.remarks'),
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true
    },
    {
      field: 'building_number',
      width: 150,
      title: '楼层号',
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true
    },
    {
      field: 'perimeter',
      width: 150,
      title: t('order.perimeter'),
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true
    },
    {
      field: 'area',
      width: 150,
      title: t('order.grossArea'),
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true
    },
    {
      field: 'layout_id',
      width: 150,
      title: '架号',
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true
    },
    {
      field: 'technology_number',
      width: 150,
      title: '层',
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true
    },
    {
      field: 'glass_child',
      width: 150,
      title: '单片名称',
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true
    },
    {
      field: 'icon',
      width: 150,
      title: '印标类型',
      filters: [{data: ''}],
      slots: {filter: 'num1_filter'},
      sortable: true
    },
  ],//表头参数
  data: null,//表格数据
  toolbarConfig: {
    buttons: [
    ],
    import: false,
    // export: true,
    // print: true,
    zoom: true,
    custom: true
  },
  //右键菜单
  menuConfig: {
    body: {
      options: [
        [
          {code: 'displayProcessCard', name: '显示流程卡',},
          {code: 'hideProcessCard', name: '隐藏流程卡',},
          {code: 'setShape', name: '设置图形',},
          {code: 'Export', name: '数据导出', prefixIcon: 'vxe-icon-download', visible: true, disabled: false},
          {code: 'safeDXF', name: '图形另存为DXF',},
        ],
        []
      ]
    },
  },
})
const processCardColumns = reactive({
  columns:[
  {field: 'process_id', title: '流程卡', width: 200, align: 'center'},
  {field: 'project', title: '项目名', width: 150, align: 'center'},
  {field: 'order_number', title: '订序', width: 100, align: 'center'},
  {field: 'sizes', title: '尺寸', width: 200, align: 'center'},
  {field: 'layer', title: '层', width: 100, align: 'center'},
  {field: 'quantity', title: '数量', width: 100, align: 'center'}
],
  toolbarConfig: {
    buttons: [
    ],
    import: false,
    // export: true,
    // print: true,
  },
});
// 右键菜单
const operationConfigs = [
  {
    code: 'setAmount', // 设置统一磨量
    successMsg: '已打开!',
    gridRef: xGrid,
    requiresRow: false,
    openAmount: async () => {
      dialogVisible.value[2] = true;
    }
  },
  {
    code: 'displayProcessCard',
    successMsg: '操作成功!',
    gridRef: xGrid,
    requiresRow: false,
    displayProcess: () => {
      getProcessCard();
      // 显示流程卡时,将 left-table 宽度改为 50%
      leftTableWidth.value = 60;
      showProcessCardTable.value = true;
    }
  },
  {
    code: 'hideProcessCard',
    successMsg: '操作成功!',
    gridRef: xGrid,
    requiresRow: false,
    hideProcess: () => {
      leftTableWidth.value = 100;
      showProcessCardTable.value = false;
    }
  },
  {
    code: 'setShape',
    successMsg: '操作成功!',
    gridRef: xGrid,
    requiresRow: false,
    showMessage: () => {
      ElMessage.info('此功能暂未完善,暂时无法执行设置图形操作。');
    }
  },
  {
    code: 'Export', // 导出文件操作的配置
    successMsg: '文件导出成功!',
    gridRef: xGrid,
    requiresRow: false,
  },
  {
    code: 'safeDXF',
    successMsg: '操作成功!',
    gridRef: xGrid,
    requiresRow: false,
    showMessage: () => {
      ElMessage.info('此功能暂未完善,暂时无法执行图形另存为DXF操作。');
    }
  },
  {
    code: 'exportOPTIMA',
    successMsg: '操作成功!',
    gridRef: xGrid,
    requiresRow: false,
    showMessage: () => {
      ElMessage.info('此功能暂未完善,暂时无法执行导出数据到OPTIMA操作。');
    }
  },
]
// 右键菜单点击逻辑
const gridEvents = {
  menuClick({menu}) {
    const $grid = xGrid.value;
    if ($grid) {
      const config = operationConfigs.find(c => c.code === menu.code);
      if (config) {
        if (config.code === 'Export') {
          config.gridRef.value.exportData();
          ElMessage.success(config.successMsg);
          return;
        }
        // 添加确认提示弹窗,询问用户是否进行当前操作
        ElMessageBox.confirm('是否进行当前操作?', '确认操作', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          if (config.code === 'setAmount') {
            config.openAmount();
            ElMessage.success(config.successMsg);
          } else if (config.code === 'displayProcessCard') {
            config.displayProcess();
            ElMessage.success(config.successMsg);
          } else if (config.code === 'hideProcessCard') {
            config.hideProcess();
            ElMessage.success(config.successMsg);
          } else if (['setShape', 'safeDXF', 'exportOPTIMA'].includes(config.code)) {
            config.showMessage();
          }
        }).catch(() => {
          // 用户点击取消后执行的逻辑
          ElMessage.info('已取消操作');
        });
      } else {
        console.error(`未找到操作选项 ${menu.code} 对应的配置,请检查配置项`);
      }
    }
  },
};
const handleCommand = async (command) => {
  await emit('changeDialog', command)
}
let originalFilm=ref(true)
let surplusMaterial=ref(false)
//优化计算
const dialogVisible = ref({});
const openDialog = (index) => {
  dialogVisible.value[index] = true;
};
//关闭弹窗
const closeDialog = (index) => {
  dialogVisible.value[index] = false;
};
//右键菜单统一修边
const props = defineProps({
  TrimmingDialogVisible: Boolean
});
watch(() => props.TrimmingDialogVisible, (newValue) => {
  if (newValue === true) {
    dialogVisible.value[3] = newValue;
  }
});
const route = useRoute();
//工程号
const projectNo = ref(route.params.projectNo);
const projectName = ref('');
const thickNess = ref(route.params.thickNess);
const model = ref(route.params.model);
onBeforeUnmount(() => {
  localStorage.setItem('projectNo', projectNo.value);
});
const fetchData = () => {
  request.post(`/glassOptimize/projectInfoThirdParty/${projectNo.value}`).then((res) => {
    if ((Number(res.code) === 200)) {
      const data = res.data.data;
      const grindingTrimming = res.data.grindingTrimming;
      if(grindingTrimming!==null){
        const formattedData = grindingTrimming.map(item => {
          const formattedItem = {};
          for (const key in item) {
            if (typeof item[key] === 'string') {
              //去除字符串属性值开头和结尾的双引号
              formattedItem[key] = item[key].replace(/^\"|\"$/g, '');
            } else {
              formattedItem[key] = item[key];
            }
          }
          return formattedItem;
        });
      }
      xGrid.value.loadData(data);
      gridOptions.data = data;
      projectName.value = data[0].project_name;
    } else {
      ElMessage.warning(res.msg);
    }
  }).catch((error) => {
    console.error("获取数据出错:", error);
  });
};
onMounted(() => {
  if (projectNo.value) {
    fetchData();
  }
});
// 流程卡 宽度
const leftTableWidth = ref(100);
const showProcessCardTable = ref(false);
// 用于存储流程卡数据
const processCardData = ref(null);
//流程卡
const getProcessCard = () => {
  request.post(`/glassOptimize/getProcessCardMpThirdParty/${projectNo.value}`).then((res) => {
    if (Number(res.code) === 200) {
      processCardData.value = res.data.data;
    } else {
      ElMessage.warning(res.msg);
    }
  });
};
// 从子组件SetAmount获取磨量值,并更新表格数据
const Amount = (amountData) => {
  fetchData()
  /*nextTick(() => {
    const data = gridOptions.data;
    if (data) {
      try {
        const updatedData = [];
        for (let i = 0; i < data.length; i++) {
          const item = data[i];
          const updatedItem = {
            ...item,
            longGrind1: Number(amountData.quicksetTop),
            longGrind2: Number(amountData.quicksetRight),
            shortGrind1: Number(amountData.quicksetBottom),
            shortGrind2: Number(amountData.quicksetLeft)
          };
          updatedData.push(updatedItem);
        }
        gridOptions.data = updatedData;
        xGrid.value.loadData(updatedData);
      } catch (error) {
        console.error('更新表格数据时出错:', error);
        // 这里可以根据实际需求添加一些回滚操作或者提示用户的逻辑,比如显示一个错误提示框等
        ElMessage.error('更新磨量数据时出现错误,请检查输入或联系管理员');
      }
    } else {
      console.warn('表格数据为空,无法更新磨量值');
    }
  });*/
};
//中转站接受SetTrimming的值(设置修边)
const emit = defineEmits([
  'changeDialog',
  'forward-data-to-grandparent',
  'send-inventory-to-op'
]);
const handleTrimmingData = (data) => {
  emit('forward-data-to-grandparent', data);
};
//中转站接受CheckInventory的值(查询库存)
const handleInventory = (selectedLabel1, selectedLabel2) => {
  let type=0;
  if(originalFilm.value===true&&surplusMaterial.value===true){
    type=3
  }else if(originalFilm.value===true&&surplusMaterial.value===false){
    type=1
  }else if(originalFilm.value===false&&surplusMaterial.value===true){
    type=2
  }else{
    type=1
  }
  emit('send-inventory-to-op', selectedLabel1, selectedLabel2,type);
}
</script>
<template>
  <div style="width: 100%;height: 85%;">
    <!-- 头部 -->
    <div id="header" >
      <!--工程文件菜单-->
      <el-dropdown @command="handleCommand">
        <el-button type="primary" :icon="Folder" style="margin-top: 8px; margin-left: 5px">
          工程文件
        </el-button>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item :command="2" :icon="Setting">工程管理</el-dropdown-item>
            <el-dropdown-item :command="3" :icon="Operation">模拟计算</el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
      <div id="title">
        <span>工程编号:</span>
        <el-input readonly placeholder="" style="width: 150px" v-model="projectNo"></el-input>&nbsp;
        <span>工程名称:</span>
        <el-input readonly placeholder="" style="width: 150px; margin-right: 140px;" v-model="projectName" ></el-input>
      </div>
    </div>
    <!-- 表格容器 -->
    <div class="table-container">
      <vxe-grid
          class="left-table"
          @filter-change="filterChanged"
          height="100%"
          ref="xGrid"
          v-bind="gridOptions"
          v-on="gridEvents"
          v-bind:style="{ width: leftTableWidth + '%' }"
      >
        <template #num2_filter="{ column, $panel }">
          <div>
            <div v-for="(option, index) in column.filters" :key="index">
              <vxe-select v-model="option.data" :placeholder="$t('processCard.pleaseSelect')"
                          @change="changeFilterEvent($event, option, $panel)">
                <vxe-option value="0" :label="$t('basicData.unchecked')"></vxe-option>
                <vxe-option value="1" :label="$t('basicData.selected')"></vxe-option>
              </vxe-select>
            </div>
          </div>
        </template>
        <template #num1_filter="{ column, $panel }">
          <div>
            <div v-for="(option, index) in column.filters" :key="index">
              <input
                  type="type"
                  v-model="option.data"
                  @keyup.enter.native="$panel.confirmFilter()"
                  @input="changeFilterEvent($event, option, $panel)"/>
            </div>
          </div>
        </template>
      </vxe-grid>
      <!-- 流程卡表格 -->
      <vxe-grid
          height="100%"
          class="right-table"
          :data="processCardData"
          v-bind="processCardColumns"
          v-if="showProcessCardTable"
          :header-cell-style="{'height': '51.9px'}"
      >
      </vxe-grid>
    </div>
  </div>
</template>
<style scoped>
.table-container {
  width: 100%;
  height: 100%;
  flex: 1;
  display: flex;
}
.left-table {
  float: left;
}
.right-table {
  width: 40%;
}
:deep(.vxe-toolbar){
  height: 40px;
}
#header {
  height: 50px;
  display: flex;
}
#title {
  margin: 8px 5px;
  width: 1240px;
}
#button {
  margin-left: 10px;
}
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/ProjectList.vue
New file
@@ -0,0 +1,281 @@
<script setup>
import {nextTick, onMounted, reactive, ref, watch} from "vue";
import {useI18n} from "vue-i18n";
import request from "@/utils/request";
import {ElMessage, ElMessageBox} from "element-plus";
import {defineEmits} from 'vue';
const { t } = useI18n()
const xGrid = ref()
const gridOptions = reactive({
  height:'100%',
  loading: false,
  border:  "full",//表格加边框
  keepSource: true,//保持源数据
  align: 'center',//文字居中
  stripe:true,//斑马纹
  rowConfig: {isCurrent: true, isHover: true,height: 30, useKey: true},//鼠标移动或选择高亮
  id: 'ProjectList',
  scrollX:{enabled: true},
  scrollY:{ enabled: true ,gt:0},//开启虚拟滚动
  showOverflow:true,
  columnConfig: {
    resizable: true,
    useKey: true
  },
  filterConfig: {   //筛选配置项
    remote: true
  },
  customConfig: {
    storage: true
  },
  editConfig: {
    trigger: 'click',
    mode: 'row',
    showStatus: true
  },
  columns:[
    {field: 'id',width: 150, title: 'ID',filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true},
    {field: 'project_no',width: 150, title: '工程号',filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true},
    {field: 'project_name',width: 150, title: '项目名称',filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true},
    {field: 'glass_type',width: 150, title: '膜系',filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true},
    {field: 'glass_thickness',width: 150, title: '厚度',filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true},
    {field: 'type',width: 150, title: '类型',filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true},
    {field: 'state',width: 150, title: '状态',filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true},
    {field: 'glass_total',width: 150, title: t('order.quantity'),filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true},
    {field: 'glass_total_area',width: 150, title: t('order.grossArea'),filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true},
    {field: 'process_qty',width: 150, title: '流程数量',filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true},
    {field: 'process_cards',width: 150, title: '流程卡号',filters:[{ data: '' }],slots: { filter: 'num1_filter' }, sortable: true},
  ],//表头参数
  data:null,//表格数据
  toolbarConfig: {
    buttons: [
    ],
    import: false,
    // export: true,
    // print: true,
    zoom: true,
    custom: true
  },
  //右键菜单选项
  menuConfig: {
    body: {
      options: [
        [
          {code: 'openProject', name: '打开工程', prefixIcon: 'vxe-icon-folder-open'},
          {code: 'compute', name: '模拟计算', prefixIcon: 'vxe-icon-subtable'},
          {code: 'delProject', name: '删除工程', prefixIcon: 'vxe-icon-delete'},
        ],
        []
      ]
    }
  }
})
// 定义操作配置对象数组,集中管理不同操作选项对应的参数
const operationConfigs = [
  {
    code: 'openProject', // 打开工程
    initialState: ['10', '20', '100', '200'], //
    targetState: null,
    successMsg: '已打开!',
    checkMessage: '当前工程状态不符合条件,请确认工程状态后再操作!',
    requiresRow: true,
    openFile: async ({row}) => {
      const projectNumber = row.projectNumber;
      const thickness = row.thickness;
      const glassType = row.glassType;
      await router.push({
        name: 'optimizeInfo',
        params: {
          projectNo: projectNumber,
          thickNess: thickness,
          model: glassType
        }
      });
    }
  },
  {
    code: 'compute', // 打开模拟计算操作
    initialState: ['1', '2'], //
    targetState: null,
    successMsg: '模拟计算已启动!',
    checkMessage: '当前工程状态不符合模拟计算条件,请确认工程状态后再操作!',
    requiresRow: true,
    actionFunction: async ({row}) => {
      const projectNo = row.projectNumber;
      emit('switch-dialog', row);
    }
  },
  {
    code: 'delProject',
    initialState: ['1', '2', '10', '20', '100'],
    targetState: null,
    successMsg: '工程删除成功!',
    checkMessage: '当前工程状态不符合删除条件,请确认工程状态后再操作!',
  }
];
//定义切换模拟计算弹窗
const emit = defineEmits(['switch-dialog']);
onMounted(async () => {
  getProject();
})
const getProject = ()=>{
  request.post(`/glassOptimize/getProjectList`).then((res) => {
    if(res.code==200){
      xGrid.value.loadData(res.data.data)
    }else{
      ElMessage.warning(res.msg)
    }
  })
}
const gridEvents = {
  menuClick({menu, row}) {
    const $grid = xGrid.value;
    if ($grid) {
      const config = operationConfigs.find(c => c.code === menu.code);
      if (config) {
        if (config.requiresRow && !row) {
          ElMessage.warning('未选中工程,请选中工程后再进行当前操作!');
          return;
        }
        if (config.code === 'compute') {
          config.actionFunction({row});
          return;
        }
        // 添加确认提示弹窗,询问用户是否进行当前操作
        ElMessageBox.confirm('是否进行当前操作?', '确认操作', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          if (config.code === 'viewTempered') {
            ElMessageBox.alert('当前点击的是查看钢化版图功能,目前暂时仅做提示,暂无实际查看操作!', '功能提示', {
              confirmButtonText: '我知道了'
            });
            return;
          }
          if (config.code === 'viewOptimize') {
            ElMessageBox.alert('当前点击的是查看钢化版图功能,目前暂时仅做提示,暂无实际查看操作!', '功能提示', {
              confirmButtonText: '我知道了'
            });
            return;
          }
          if (config.code === 'delProject') {
            if (!row) {
              ElMessage.warning(config.checkMessage);
              return;
            }
            const isInitialStateMatched = config.initialState.includes(String(row.state));
            if (!isInitialStateMatched) {
              ElMessage.warning(config.checkMessage);
              return;
            }
            deleteProject(row.projectNumber, config);
          } else if (!checkOperationCondition(config, row)) {
            ElMessage.warning(config.checkMessage);
            return;
          } else {
            if (config.code === 'openProject') {
              handleSameDataOperation(row).then(({isRoutesEqual}) => {
                if (!isRoutesEqual) {
                  config.openFile({row});
                  ElMessage.success(config.successMsg);
                }
              });
            } else if (config.code === 'compute') {
              config.actionFunction({row});
            } else  if (config.code === 'optimizeTypography') {
              handleSameDataOperation(row).then(({isRoutesEqual}) => {
                if (!isRoutesEqual) {
                  config.Typography({row});
                  ElMessage.success(config.successMsg);
                }
              });
            }
            else {
              row.state = config.targetState;
              const index = produceList.value.findIndex(item => item === row);
              if (index !== -1) {
                produceList.value.splice(index, 1, {...row});
                xGrid.value.reloadData(produceList.value);
              }
              updateProjectStateAndHandleResponse(row, row.projectNumber, config.targetState, config.successMsg);
            }
          }
        }).catch(() => {
          // 用户点击取消后执行的逻辑
          ElMessage.info('已取消操作');
        });
      } else {
        console.error(`未找到操作选项 ${menu.code} 对应的配置,请检查配置项`);
      }
    }
  },
  cellDblclick: ({row}) => {
    const menu = {code: 'openProject'};
    nextTick(() => {
      handleSameDataOperation(row).then(({isRoutesEqual}) => {
        if (!isRoutesEqual) {
          gridEvents.menuClick({menu, row});
        }
      });
    });
  },
};
</script>
<template>
  <div style="width: 100%;height: 100%">
    <h1>工程列表</h1>
    <vxe-grid
        size="small"
        @filter-change="filterChanged"
        height="100%"
        class="mytable-scrollbar"
        ref="xGrid"
        v-bind="gridOptions"
        v-on="gridEvents"
    >
      <template #num2_filter="{ column, $panel }">
        <div>
          <div v-for="(option, index) in column.filters" :key="index">
            <vxe-select v-model="option.data" :placeholder="$t('processCard.pleaseSelect')"  @change="changeFilterEvent($event, option, $panel)">
              <vxe-option value="0" :label="$t('basicData.unchecked')"></vxe-option>
              <vxe-option value="1" :label="$t('basicData.selected')"></vxe-option>
            </vxe-select>
          </div>
        </div>
      </template>
      <template #num1_filter="{ column, $panel }">
        <div>
          <div v-for="(option, index) in column.filters" :key="index">
            <input
                type="type"
                v-model="option.data"
                @keyup.enter.native="$panel.confirmFilter()"
                @input="changeFilterEvent($event, option, $panel)"/>
          </div>
        </div>
      </template>
    </vxe-grid>
  </div>
</template>
<style scoped>
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/RectRenderer.vue
New file
@@ -0,0 +1,341 @@
<template>
  <div ref="layoutPanel" :class="panelClass" :style="panelStyle">
    <div id="printFlowCard">
      <div v-for="(layout, layoutIndex) in layouts" :key="layoutIndex" class="layout-wrapper">
        <div class="header" :style="headerStyle(layoutIndex)">
          工程号{{ processId }}
          {{ getCurrentRectInfo(layoutIndex) }}
        </div>
        <div class="layout-container" :style="layoutContainerStyle(layoutIndex)">
          <div class="grid-container" :class="`cols-${printColumns}`">
            <div
              v-for="(rect, rectIndex) in layout.rects"
              :key="rectIndex"
              :ref="(el) => { if (el) rectsElements[layoutIndex + '-' + rectIndex] = el }"
              :class="rectClass"
              :style="rectStyle(rect, layoutIndex)"
              @click="handleRectClick(layoutIndex, rectIndex)"
            >
              <div v-if="!rect.isRemain" class="rect-content">
                <div class="size">{{ rect.w }}×{{ rect.h }}</div>
                <div class="jia-hao">{{ rect.JiaHao }}</div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted, watch, nextTick } from 'vue';
import request from "@/utils/request";
const props = defineProps({
  layoutData: { type: Object, required: true },
  gw: { type: Number, default: 1400 },
  gh: { type: Number, default: 1100 },
  style: { type: String, default: 'width:100%;height:800px;display:block;background:gray' },
  printLayout: { type: String, default: '2rows-2cols' }, // 可选值:4rows-2cols, 3rows-2cols, 3rows-1col, 2rows-2cols
  fixedPageHeight: { type: Number, default: 1100 } // 固定页面高度
});
const emit = defineEmits(['rectClicked']);
const layoutPanel = ref(null);
const rectsElements = ref({});
const focusIndex = ref(null);
const layouts = ref([]);
const panelClass = ref('');
const panelStyle = ref(props.style);
const rectClass = ref('layout-rect');
const processId = localStorage.getItem('projectNo');
const printColumns = ref(2); // 初始化为2列
const layoutsPerPage = ref(4); // 默认每页显示4个布局(2行×2列)
// 定义不同布局的放大比例
const layoutScales = {
  '4rows-2cols': 0.8, // 四行两列,较小的放大比例
  '3rows-2cols': 0.9, // 三行两列,适中的放大比例
  '3rows-1col': 1.0,  // 三行一列,较大的放大比例
  '2rows-2cols': 1   // 两行两列,较大的放大比例
};
// 监听printLayout变化
watch(() => props.printLayout, (newVal) => {
  adjustPrintLayout();
  updateLayout();
});
const layoutContainerStyle = (layoutIndex) => {
  const containerWidth = (props.gw - 20) / printColumns.value; // 减少边距
  const containerHeight = (props.gh - 20) / Math.ceil(layoutsPerPage.value / printColumns.value);
  const x = (layoutIndex % printColumns.value) * containerWidth;
  const y = Math.floor(layoutIndex / printColumns.value) * containerHeight;
  return {
    position: 'absolute',
    left: `${x}px`,
    top: `${y}px`,
    width: `${containerWidth}px`,
    height: `${containerHeight}px`,
    overflow: 'visible',
    padding: '10px' // 添加内边距
  };
};
const headerStyle = (layoutIndex) => {
  const containerWidth = (props.gw - 20) / printColumns.value;
  const containerHeight = (props.gh - 20) / Math.ceil(layoutsPerPage.value / printColumns.value);
  const x = (layoutIndex % printColumns.value) * containerWidth;
  const y = Math.floor(layoutIndex / printColumns.value) * containerHeight;
  const scale = Math.min(
    containerWidth,
    containerHeight
  ) * 1.2; // 放大1.2倍
  return {
    position: 'absolute',
    left: `${x}px`,
    top: `${y - 45}px`,
    width: `${scale}px`,
    textAlign: 'center',
    zIndex: 1000,
    background: '#ffffff',
    padding: '5px',
    fontSize: '12px'
  };
};
const rectStyle = (rect, layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const containerWidth = (props.gw - 100) / printColumns.value;
  const containerHeight = (props.gh - 100) / Math.ceil(layoutsPerPage.value / printColumns.value);
  // 根据当前打印布局获取放大比例
  const currentScale = layoutScales[props.printLayout] || 1.0;
  const scale = Math.min(
    containerWidth / layout.width,
    containerHeight / layout.height
  ) * currentScale; // 应用当前布局的放大比例
  return {
    position: 'absolute',
    left: `${rect.x * scale}px`,
    top: `${rect.y * scale}px`,
    width: `${rect.w * scale}px`,
    height: `${rect.h * scale}px`,
    backgroundColor: rect.isRemain ? '#f0f0f0' : '#a0d8ef',
    border: '1px solid #000',
    cursor: 'pointer'
  };
};
const handleRectClick = (layoutIndex, rectIndex) => {
  focusIndex.value = { layoutIndex, rectIndex };
  emit('rectClicked', layoutIndex, rectIndex);
};
const getCurrentRectInfo = (layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[focusIndex.value?.rectIndex || 0];
  if (!rect) return '';
  const totalRects = layouts.value.length;
  const currentRectIndex = layoutIndex + 1;
  const width = layout.width;
  const height = layout.height;
  const sum = layout.rects.reduce((sum, r) => sum + (r.w * r.h), 0);
  const areaUtilization = ((sum / (width * height)) * 100).toFixed(2);
  return `${currentRectIndex}/${totalRects} ${height}X${width}X1 ${areaUtilization}%`;
};
const adjustPrintLayout = () => {
  switch (props.printLayout) {
    case '4rows-2cols':
      printColumns.value = 2;
      layoutsPerPage.value = 8; // 4行×2列
      break;
    case '3rows-2cols':
      printColumns.value = 2;
      layoutsPerPage.value = 6; // 3行×2列
      break;
    case '3rows-1col':
      printColumns.value = 1;
      layoutsPerPage.value = 3; // 3行×1列
      break;
    case '2rows-2cols':
      printColumns.value = 2;
      layoutsPerPage.value = 4; // 2行×2列
      break;
    default:
      printColumns.value = 2;
      layoutsPerPage.value = 4;
  }
};
const updateLayout = () => {
  if (!layoutPanel.value) return;
  layouts.value = props.layoutData.Layouts;
  adjustPrintLayout();
  // 强制重新渲染
  layoutPanel.value.offsetHeight; // 触发布局更新
};
onMounted(() => {
updateLayout();
});
onUnmounted(() => {
  rectsElements.value = {};
});
const print = () => {
  const el = document.getElementById('printFlowCard');
  const doc = document;
  const body = doc.body || doc.getElementsByTagName("body")[0];
  const printId = "print-" + Date.now();
  // 创建一个克隆的节点
  const content = document.createElement("div");
  content.id = printId;
  content.appendChild(el.cloneNode(true)); // 克隆节点并保留所有属性和子节点
  const style = document.createElement("style");
  style.innerHTML =
    "body>#" +
    printId +
    "{display:none}@media print{" +
    "@page {" +
    "    size: auto; " +
    "    margin: 13mm 4mm 0mm 4mm; " +
    "  }body>:not(#" +
    printId +
    "){display:none !important}body>#" +
    printId +
    "{display:block;padding-top:1px}}";
  body.appendChild(style);
  body.appendChild(content);
  // 优化分页逻辑
  const layoutWrappers = content.querySelectorAll('.layout-wrapper');
  let currentPageHeight = 0;
  let currentWrapperIndex = 0;
  layoutWrappers.forEach((wrapper, index) => {
    const wrapperHeight = wrapper.offsetHeight;
    if (currentPageHeight + wrapperHeight > props.fixedPageHeight) {
      const pageBreak = document.createElement('div');
      pageBreak.className = 'element-to-break-after';
      layoutWrappers[currentWrapperIndex - 1].appendChild(pageBreak);
      currentPageHeight = wrapperHeight;
    } else {
      currentPageHeight += wrapperHeight;
    }
    currentWrapperIndex = index + 1;
  });
  setTimeout(() => {
    window.print();
    body.removeChild(content);
    body.removeChild(style);
  }, 200);
};
defineExpose({
  print,
  updateLayout
});
</script>
<style scoped>
@media print {
  .layout-wrapper {
    page-break-inside: avoid;
    margin-bottom: 20px;
  }
  .element-to-break-after {
    page-break-after: always;
  }
  .header {
    position: static;
    width: 100%;
  }
  .layout-container {
    position: static;
    width: 100%;
    height: auto;
  }
  .grid-container {
    display: grid;
    gap: 10px; /* 减少打印时的网格间距 */
  }
  .cols-1 {
    grid-template-columns: 1fr;
  }
  .cols-2 {
    grid-template-columns: repeat(2, 1fr);
  }
  .cols-3 {
    grid-template-columns: repeat(3, 1fr);
  }
  .cols-4 {
    grid-template-columns: repeat(4, 1fr);
  }
}
.element-to-break-after {
  page-break-after: always;
}
.layout-wrapper {
  position: relative;
  margin-top: 50px;
}
.header {
  position: absolute;
  top: -45px;
  left: 0;
  width: 100%;
  text-align: center;
  z-index: 1000;
  background-color: #ffffff;
  padding: 5px;
  font-size: 12px;
}
.layout-container {
  position: relative;
  overflow: visible;
}
.rect-content {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
  padding: 5px;
}
.size {
  grid-row: 1;
  grid-column: 1;
  color: #444;
  font-size: 12px;
}
.jia-hao {
  grid-row: 2;
  grid-column: 1;
  margin: auto;
  font-size: 14px;
  font-weight: bold;
}
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/SetAmount.vue
New file
@@ -0,0 +1,217 @@
<script setup>
import {onMounted, ref} from "vue";
import {defineEmits} from 'vue';
import request from "@/utils/request";
import {ElMessage} from "element-plus";
onMounted(async() => {
  await firstLoading()
})
const firstLoading = async() => {
  request.post(`/glassOptimize/getConfiguration/磨量`).then((res) => {
    if (res.code == "200") {
      const rawData = res.data.data;
      if (Array.isArray(rawData) && rawData.length > 0) {
        const formattedData = rawData.map(item => {
          const formattedItem = {};
          for (const key in item) {
            if (typeof item[key] === 'string') {
              //去除字符串属性值开头和结尾的双引号
              formattedItem[key] = item[key].replace(/^\"|\"$/g, '');
            } else {
              formattedItem[key] = item[key];
            }
          }
          return formattedItem;
        });
        quicksetLeft.value=formattedData[0].leftEdge
        quicksetTop.value=formattedData[0].upEdge
        quicksetRight.value=formattedData[0].rightEdge
        quicksetBottom.value=formattedData[0].downEdge
        controlValue.value=formattedData[0].quickEdge
        if(formattedData[0].autoFillEdge=="true"){
          check.value=true
        }else{
          check.value=false
        }
        minAutoLenght.value=formattedData[0].minAutoLenght
      }
    } else {
      ElMessage.warning(res.msg)
    }
  });
};
function setupComponent() {
  // 各个输入框绑定的值,初始化为0
  const quicksetTop = ref('0');
  const quicksetRight = ref('0');
  const quicksetBottom = ref('0');
  const quicksetLeft = ref('0');
  // 用于控制的输入值,初始化为1
  const controlValue = ref('1');
  const minAutoLenght = ref('0');
  const syncValues = () => {
    // 将控制值赋给其他四个组件
    quicksetTop.value = controlValue.value;
    quicksetRight.value = controlValue.value;
    quicksetBottom.value = controlValue.value;
    quicksetLeft.value = controlValue.value;
  };
  return {
    quicksetTop,
    quicksetRight,
    quicksetBottom,
    quicksetLeft,
    controlValue,
    minAutoLenght,
    syncValues
  };
}
const {
  quicksetTop,
  quicksetRight,
  quicksetBottom,
  quicksetLeft,
  controlValue,
  minAutoLenght,
  syncValues
} = setupComponent();
const check = ref()
const emit = defineEmits(['set-amount']);
const props = defineProps({
  closeDialog: Function
});
const setAmount = () => {
  let json = "{"
  json+='"left_edge":"'+quicksetLeft.value+'",'
  json+='"up_edge":"'+quicksetTop.value+'",'
  json+='"right_edge":"'+quicksetRight.value+'",'
  json+='"down_edge":"'+quicksetBottom.value+'",'
  json+='"auto_fill_edge":"'+check.value+'",'
  json+='"quick_edge":"'+controlValue.value+'",'
  json+='"min_auto_lenght":"'+minAutoLenght.value+'"'
  json+="}"
  let configuration={
    json:json,
  }
  request.post(`/glassOptimize/saveConfiguration/磨量`,configuration).then((res) => {
    if(res.code==200 && res.data===true){
      const amountData = {
        quicksetTop: quicksetTop.value,
        quicksetRight: quicksetRight.value,
        quicksetBottom: quicksetBottom.value,
        quicksetLeft: quicksetLeft.value,
        controlValue:controlValue.value,
        minAutoLenght:minAutoLenght.value,
      };
      emit('set-amount', amountData);
      props.closeDialog(2);
    } else {
      ElMessage.warning(res.msg)
    }
  });
};
</script>
<template>
  <div id="box1">
     <div style="display: flex; justify-content: center">
      <div class="square-container">
        <div class="square" @click="syncValues"></div>
        <el-input-number v-model="quicksetTop" class="top" placeholder="0"
                         controls-position="right" :step="0.1" :min="0"></el-input-number>
        <el-input-number v-model="quicksetRight" class="right" placeholder="0"
                         controls-position="right" :step="0.1" :min="0"></el-input-number>
        <el-input-number v-model="quicksetBottom" class="bottom" placeholder="0"
                         controls-position="right" :step="0.1" :min="0"></el-input-number>
        <el-input-number v-model="quicksetLeft" class="left" placeholder="0"
                         controls-position="right" :step="0.1" :min="0"></el-input-number>
      </div>
      <el-button type="primary" style="float: right; margin: -75px 0;" @click="setAmount">应用</el-button>
    </div>
    <div style="margin-top: 20px">
      <span>鼠标点击蓝色图形区域可快速设置磨量</span>
      <el-input-number v-model="controlValue" class="quickset" placeholder="0"
                       controls-position="right" :step="0.1" :min="0"></el-input-number>
      <br>
      <div style="margin-top: 30px">
      <span>成品边长大于等于多少时自动填充(单位: mm)</span>
      <el-checkbox v-model="check" style="margin: 5px 0 0 12px;"/>
      <vxe-input style="width: 100px; height: 40px; margin-left: 5px" v-model="minAutoLenght" placeholder=""/>
      </div>
    </div>
  </div>
</template>
<style scoped>
#box1 {
  width: 100%;
  height: 100%;
  padding: 10px;
  border-radius: 5px;
}
.square-container {
  width: 300px;
  height: 250px;
  display: flex;
  position: relative;
  justify-content: center;
  align-items: center;
}
.square {
  width: 100px;
  height: 100px;
  background-color: #5cadfe;
}
.top,
.right,
.bottom,
.left {
  width: 100px;
  height: 50px;
  position: absolute;
}
.top {
  top: 20px;
  left: 50%;
  transform: translateX(-50%);
}
.right {
  top: 50%;
  right: -5px;
  transform: translateY(-50%);
}
.bottom {
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
}
.left {
  top: 50%;
  left: -5px;
  transform: translateY(-50%);
}
.quickset {
  margin-left: 60px;
  width: 100px;
  height: 40px;
}
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimizeThirdParty/page/SetTrimming.vue
New file
@@ -0,0 +1,205 @@
<script setup>
import {onMounted, ref} from "vue";
import {defineEmits} from 'vue';
import request from "@/utils/request";
import {ElMessage} from "element-plus";
onMounted(async() => {
  await firstLoading()
})
const firstLoading = async() => {
  request.post(`/glassOptimize/getConfiguration/修边`).then((res) => {
    if (res.code == "200") {
      const rawData = res.data.data;
      if (Array.isArray(rawData) && rawData.length > 0) {
        const formattedData = rawData.map(item => {
          const formattedItem = {};
          for (const key in item) {
            if (typeof item[key] === 'string') {
              //去除字符串属性值开头和结尾的双引号
              formattedItem[key] = item[key].replace(/^\"|\"$/g, '');
            } else {
              formattedItem[key] = item[key];
            }
          }
          return formattedItem;
        });
        quicksetLeft.value=formattedData[0].leftTrim
        quicksetTop.value=formattedData[0].upTrim
        quicksetRight.value=formattedData[0].rightTrim
        quicksetBottom.value=formattedData[0].downTrim
      }
    } else {
      ElMessage.warning(res.msg)
    }
  });
};
function setupComponent() {
  // 各个输入框绑定的值,初始化为0
  const quicksetTop = ref('0');
  const quicksetRight = ref('0');
  const quicksetBottom = ref('0');
  const quicksetLeft = ref('0');
  // 用于控制的输入值,初始化为1
  const controlValue = ref('1');
  const syncValues = () => {
    // 将控制值赋给其他四个组件
    quicksetTop.value = controlValue.value;
    quicksetRight.value = controlValue.value;
    quicksetBottom.value = controlValue.value;
    quicksetLeft.value = controlValue.value;
  };
  return {
    quicksetTop,
    quicksetRight,
    quicksetBottom,
    quicksetLeft,
    controlValue,
    syncValues
  };
}
const {
  quicksetTop,
  quicksetRight,
  quicksetBottom,
  quicksetLeft,
  controlValue,
  syncValues
} = setupComponent();
const check = ref(true)
const emit = defineEmits(['send-data-event',]);
const props = defineProps({
  closeDialog: Function
});
const setTrimming = () => {
  let json = "{"
  json+='"left_trim":"'+quicksetLeft.value+'",'
  json+='"up_trim":"'+quicksetTop.value+'",'
  json+='"right_trim":"'+quicksetRight.value+'",'
  json+='"down_trim":"'+quicksetBottom.value+'",'
  json+='"auto_fill_trim":"'+true+'",'
  json+='"quick_trim":"'+15+'"'
  json+="}"
  let configuration={
      json:json,
    }
  request.post(`/glassOptimize/saveConfiguration/修边`,configuration).then((res) => {
    if(res.code==200 && res.data===true){
      const dataToSend = {
        quicksetTop: quicksetTop.value,
        quicksetRight: quicksetRight.value,
        quicksetBottom: quicksetBottom.value,
        quicksetLeft: quicksetLeft.value
      };
      emit('send-data-event', dataToSend);
      props.closeDialog(3);
    } else {
      ElMessage.warning(res.msg)
    }
  });
};
</script>
<template>
  <div id="box1">
    <div style="display: flex; margin-top: -20px; justify-content: center">
      <div class="square-container">
        <div class="square" @click="syncValues"></div>
        <el-input-number v-model="quicksetTop" class="top" placeholder="0"
                         controls-position="right" :step="0.1" :min="0"></el-input-number>
        <el-input-number v-model="quicksetRight" class="right" placeholder="0"
                         controls-position="right" :step="0.1" :min="0"></el-input-number>
        <el-input-number v-model="quicksetBottom" class="bottom" placeholder="0"
                         controls-position="right" :step="0.1" :min="0"></el-input-number>
        <el-input-number v-model="quicksetLeft" class="left" placeholder="0"
                         controls-position="right" :step="0.1" :min="0"></el-input-number>
      </div>
      <el-button type="primary" style="float: right; margin: -55px 0;" @click="setTrimming">应用</el-button>
    </div>
    <div>
      <span>鼠标点击蓝色图形区域可快速设置修边量</span>
      <el-input-number v-model="controlValue" class="quickset" placeholder="0"
                       controls-position="right" :step="0.1" :min="0"></el-input-number>
      <br>
      <span>查询原片时自动填充(单位: mm)</span>
      <el-checkbox v-model="check" style="margin: 20px 0 0 117px;"/>
    </div>
  </div>
</template>
<style scoped>
#box1 {
  width: 100%;
  height: 100%;
  padding: 10px;
  border-radius: 5px;
}
.square-container {
  width: 300px;
  height: 300px;
  display: flex;
  position: relative;
  justify-content: center;
  align-items: center;
}
.square {
  width: 100px;
  height: 100px;
  background-color: #5cadfe;
}
.top,
.right,
.bottom,
.left {
  width: 100px;
  height: 50px;
  position: absolute;
}
.top {
  top: 45px;
  left: 50%;
  transform: translateX(-50%);
}
.right {
  top: 50%;
  right: -5px;
  transform: translateY(-50%);
}
.bottom {
  bottom: 45px;
  left: 50%;
  transform: translateX(-50%);
}
.left {
  top: 50%;
  left: -5px;
  transform: translateY(-50%);
}
.quickset {
  margin-left: 60px;
  width: 100px;
  height: 45px;
}
</style>
north-glass-erp/src/main/java/com/example/erp/controller/pp/GlassOptimizeController.java
@@ -289,4 +289,75 @@
    //模拟计算工程号查询第三方
    @ApiOperation("模拟计算工程号查询")
    @PostMapping  ("/selectProjectComputeMpThirdParty/{projectNumber}")
    public Result selectProjectComputeMpThirdParty( @PathVariable String projectNumber){
        return Result.seccess(glassOptimizeService.selectProjectComputeMpThirdParty(projectNumber));
    }
    //模拟计算流程卡详情第三方
    @ApiOperation("模拟计算流程卡详情")
    @PostMapping  ("/selectComputeDetailThirdParty/{processId}/{technologyNumber}")
    public Result selectComputeDetailThirdParty(
            @PathVariable String processId, @PathVariable Integer technologyNumber){
        return Result.seccess(glassOptimizeService.selectComputeDetailThirdParty(processId,technologyNumber));
    }
    @ApiOperation("模拟计算保存")
    @PostMapping("/simulationSaveThirdParty")
    public Result simulationSaveThirdParty(@RequestBody Map<String, Object> object) {
        try {
            return Result.seccess(glassOptimizeService.simulationSaveThirdParty(object));
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error();
        }
    }
    //删除工程
    @ApiOperation("工程管理删除接口")
    @PostMapping("/deleteProjectThirdParty/{projectNumber}")
    public Result deleteProjectThirdParty(
            @PathVariable String projectNumber){
        return Result.seccess(glassOptimizeService.deleteProjectThirdParty(projectNumber));
    }
    @ApiOperation("工程信息接口")
    @PostMapping  ("/projectInfoThirdParty/{projectNo}")
    public Result projectInfoThirdParty(
            @PathVariable String projectNo){
        return Result.seccess(glassOptimizeService.projectInfoThirdParty(projectNo));
    }
    //库存信息
    @ApiOperation("库存信息接口")
    @GetMapping  ("/materialStoreSvThirdParty/{projectNumber}")
    public Result materialStoreSvThirdParty(@PathVariable  String projectNumber ){
        return Result.seccess(glassOptimizeService.materialStoreSvThirdParty(projectNumber));
    }
    //工程信息流程卡
    @ApiOperation("工程信息流程卡接口")
    @PostMapping  ("/getProcessCardMpThirdParty/{projectNo}")
    public Result getProcessCardMpThirdParty(
            @PathVariable String projectNo){
        return Result.seccess(glassOptimizeService.getProcessCardMpThirdParty(projectNo));
    }
}
north-glass-erp/src/main/java/com/example/erp/entity/pp/OptimizeDetail.java
New file
@@ -0,0 +1,29 @@
package com.example.erp.entity.pp;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDate;
@Data
@TableName("pp.`optimize_detail`")
public class OptimizeDetail {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String projectNo;
    private String processId;
    private Integer total_layer;
    private Integer layer;
    private Integer orderSort;
    private Integer stock_id;
    private Integer stock_number;
    private String glass_id;
    private Double pWidth;
    private Double pHeight;
    private Integer heatLayoutId;
    private Integer heatLayoutSort;
}
north-glass-erp/src/main/java/com/example/erp/entity/pp/OptimizeHeatDetail.java
New file
@@ -0,0 +1,28 @@
package com.example.erp.entity.pp;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDate;
@Data
@TableName("pp.`optimize_heat_detail`")
public class OptimizeHeatDetail {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String projectNo;
    private String processId;
    private Integer patchState;
    private Integer layer;
    private Integer orderSort;
    private Integer layoutId;
    private Integer sort;
    private Integer glass_id;
    private Double width;
    private Double height;
    private LocalDate createTime;
}
north-glass-erp/src/main/java/com/example/erp/mapper/pp/GlassOptimizeMapper.java
@@ -1,10 +1,7 @@
package com.example.erp.mapper.pp;
import com.example.erp.entity.pp.FlowCard;
import com.example.erp.entity.pp.OptimizeProjectMange;
import com.example.erp.entity.pp.OptimizeUse;
import com.example.erp.entity.pp.PatchLog;
import com.example.erp.entity.pp.*;
import com.example.erp.entity.sd.OrderGlassDetail;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -67,6 +64,10 @@
    int deleteOptimizeDetail(String projectNumber);
    List<OptimizeHeatDetail> selectOptimizeHeatDetail(String projectNumber);
    OptimizeDetail selectOptimizeDetailById(String projectNumber,String processId,Integer orderSort,Integer layer);
    //模拟计算工程号查询
    List<Map<String, Object>> selectProjectComputeMp(@Param("projectNo")String projectNumber);
@@ -93,6 +94,8 @@
    List<Map<String, Object>> selectOptimizeResult(String processId);
    //优化结果调整
    void updateOptimizeResult(String jsonString,String processId);
    void updateOptimizeDetail(Long id,Integer layoutId,Integer sort);
    List<Map<String, Object>> getProcessCardDetailmMp(String processId, Integer technologyNumber);
@@ -125,4 +128,23 @@
    List<Map<String, Object>> getEdgeTrimming();
    Boolean updateOptimizeConfig(String json,Integer type);
    //模拟计算工程号查询
    List<Map<String, Object>> selectProjectComputeMpThirdParty(@Param("projectNo")String projectNumber);
    //模拟计算流程卡详情
    List<Map<String, Object>> selectComputeDetailMpThirdParty(String processId,Integer technologyNumber);
    int deleteOptimizeDetailThirdParty(String projectNumber);
    List<Map<String, Object>> firstOptimizationThirdParty(String projectNo);
    List<Map<String, Object>> analogComputationOptimizationThirdParty(String projectNo);
    //工程信息流程卡
    List<Map<String, Object>> getProcessCardMpThirdParty(String projectNo);
}
north-glass-erp/src/main/java/com/example/erp/service/pp/GlassOptimizeService.java
@@ -5,9 +5,9 @@
import com.alibaba.fastjson.JSONObject;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.example.erp.common.RabbitMQUtil;
import com.example.erp.entity.pp.FlowCard;
import com.example.erp.entity.pp.OptimizeProjectMange;
import com.example.erp.entity.pp.PatchLog;
import com.example.erp.entity.pp.*;
import com.example.erp.entity.sd.Delivery;
import com.example.erp.entity.sd.OrderDetail;
import com.example.erp.entity.userInfo.Log;
import com.example.erp.entity.userInfo.SysError;
import com.example.erp.mapper.pp.GlassOptimizeMapper;
@@ -71,6 +71,9 @@
//模拟计算保存
    public Boolean addSimulation(Map<String, Object> object) {
        try {
            Map<String, Object> objectMap = (Map<String, Object>) object.get("inputValues");
            String projectNo = objectMap.get("project_no").toString();
            Map<String, Object> optimizeProjectMap = glassOptimizeMapper.selectProjectCount(projectNo);
            glassOptimizeMapper.addSimulation(object);
            glassOptimizeMapper.addratioResult(object);
            glassOptimizeMapper.addratioProjectResult(object);
@@ -610,4 +613,101 @@
    }
    //模拟计算工程号查询
    public Map<String, Object>selectProjectComputeMpThirdParty(String projectNumber) {
        Map<String, Object> map = new HashMap<>();
        map.put("data", glassOptimizeMapper.selectProjectComputeMpThirdParty(projectNumber));
        return map;
    }
    //模拟计算流程卡详情
    public Map<String, Object>selectComputeDetailThirdParty(String processId,Integer technologyNumber) {
        Map<String, Object> map = new HashMap<>();
        map.put("data", glassOptimizeMapper.selectComputeDetailMpThirdParty(processId,technologyNumber));
        return map;
    }
    public Boolean simulationSaveThirdParty(Map<String, Object> object) {
        try {
            Map<String, Object> objectMap = (Map<String, Object>) object.get("inputValues");
            String projectNo = objectMap.get("project_no").toString();
            Map<String, Object> optimizeProjectMap = glassOptimizeMapper.selectProjectCount(projectNo);
            glassOptimizeMapper.addSimulation(object);
            glassOptimizeMapper.addratioResult(object);
            glassOptimizeMapper.addratioProjectResult(object);
            List<OptimizeHeatDetail> optimizeHeatDetail = glassOptimizeMapper.selectOptimizeHeatDetail(projectNo);
            for (OptimizeHeatDetail projectdetail:optimizeHeatDetail){
                OptimizeDetail optimizeDetail=glassOptimizeMapper.selectOptimizeDetailById(projectNo,projectdetail.getProcessId(),
                        projectdetail.getOrderSort(),projectdetail.getLayer());
                glassOptimizeMapper.updateOptimizeDetail(optimizeDetail.getId(),projectdetail.getLayoutId(),projectdetail.getSort());
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    //删除工程
    public boolean deleteProjectThirdParty(String projectNumber) {
        Map<String, Object> stringObjectMap = glassOptimizeMapper.selectProjectCount(projectNumber);
        if(stringObjectMap.get("state").equals(2)){
            glassOptimizeMapper.deleteOptimizeDetailThirdParty(projectNumber);
            glassOptimizeMapper.deleteOptimizeDetail(projectNumber);
        }else if(stringObjectMap.get("state").equals(10)){
            glassOptimizeMapper.deleteOptimizeDetailThirdParty(projectNumber);
            glassOptimizeMapper.deleteOptimizeHeatDetail(projectNumber);
            glassOptimizeMapper.deleteOptimizeHeatLayout(projectNumber);
        }
        glassOptimizeMapper.deleteProjectMp(projectNumber);
        return true;
    }
    //工程信息
    public Map<String, Object> projectInfoThirdParty(String projectNo) {
        Map<String, Object> stringObjectMap = glassOptimizeMapper.selectProjectCount(projectNo);
        Map<String, Object> map = new HashMap<>();
        if(stringObjectMap.get("state").equals(2)){
            map.put("data", glassOptimizeMapper.firstOptimizationThirdParty(projectNo));
            map.put("grindingTrimming", null);
        }else{
            map.put("data", glassOptimizeMapper.firstOptimizationThirdParty(projectNo));
            map.put("grindingTrimming", null);
        }
        return map;
    }
    //库存信息
    public Map<String, Object> materialStoreSvThirdParty(String projectNumber) {
        Map<String, Object> stringObjectMap = glassOptimizeMapper.selectProjectCount(projectNumber);
        Map<String, Object> map = new HashMap<>();
        map.put("data", glassOptimizeMapper.materialStoreOptimizeUse(projectNumber));
        map.put("edgeTrimming", null);
        map.put("state", Integer.parseInt(stringObjectMap.get("state").toString()));
        return map;
    }
    //工程信息流程卡
    public Map<String, Object> getProcessCardMpThirdParty(String projectNo) {
        Map<String, Object> map = new HashMap<>();
        map.put("data", glassOptimizeMapper.getProcessCardMpThirdParty(projectNo));
        return map;
    }
}
north-glass-erp/src/main/resources/mapper/pp/GlassOptimize.xml
@@ -223,7 +223,7 @@
            fc.process_id,
            fc.order_number,
            fc.quantity,
            CONCAT(fc.technology_number, '/', COUNT(fc.technology_number)) AS layer,
            CONCAT(fc.technology_number, '/', COUNT(fc.layers_number)) AS layer,
            so.project,
            CONCAT(sd.child_width, ' × ', sd.child_height) AS sizes
        FROM
@@ -587,6 +587,17 @@
        where project_no = #{projectNumber}
    </delete>
    <select id="selectOptimizeDetailById">
        select * from pp.optimize_detail
        where project_no = #{projectNumber} and process_id= #{processId} and layer= #{layer} and order_sort= #{orderSort}
          and heat_layout_id is null limit 0,1;
    </select>
    <select id="selectOptimizeHeatDetail">
        select * from pp.optimize_heat_detail
        where project_no = #{projectNumber}
    </select>
    <select id="getProjectListMp">
        SELECT
            p.id,
@@ -847,7 +858,7 @@
    <insert id="addSimulation" parameterType="map">
        <foreach collection="projectdetail.data[0].glass_details" item="glass">
        <foreach collection="projectdetail" item="glass">
            INSERT INTO pp.optimize_heat_detail (
            project_no,
            process_id,
@@ -895,7 +906,7 @@
    </insert>
    <!--模拟计算结果保存-->
    <insert id="addratioResult" parameterType="map">
        <foreach collection="projectdetail.data[0].ratioResult" item="glass">
        <foreach collection="ratioResult" item="glass">
            INSERT INTO pp.optimize_heat_layout (
            project_no,
            layout_id,
@@ -925,6 +936,8 @@
        UPDATE pp.optimize_project
        SET
            state = 10,
            furnaces_qty=#{resultSum[0]},
            load_rate=#{resultSum[1]},
            chaos_pct = #{inputValues.chaos_pct},
            max_load_pct = #{inputValues.max_load_pct},
            max_area=#{inputValues.max_area},
@@ -932,8 +945,7 @@
            load_width=#{inputValues.load_width},
            load_length=#{inputValues.load_length},
            x_space=#{inputValues.x_space},
            y_space=#{inputValues.y_space},
            load_rate=#{inputValues.load_rate}
            y_space=#{inputValues.y_space}
        WHERE
            project_no = #{inputValues.project_no}
@@ -948,6 +960,12 @@
        update pp.optimize_project_file as u
        set u.content = #{jsonString}
        where  u.project_no=#{processId} and type='优化结果'
    </update>
    <update id="updateOptimizeDetail">
        update pp.optimize_detail
        set heat_layout_id=#{layoutId},heat_layout_sort=#{sort},glass_id=concat(process_id,'|',#{layoutId},'|',#{sort})
        where id=#{id};
    </update>
@@ -1035,4 +1053,116 @@
              c.process_id,
              c.order_number
    </select>
    <!--模拟计算查询流程卡第三方-->
    <select id="selectProjectComputeMpThirdParty">
        select
            process_id as processId,
            layer as technologyNumber,
            total_layer as total_layers,
            filmsid as TotalNumber,
            sum(glass_total) as total_num,
            product_name as glass_child,
            0 as tempering,
            0 as allow_rotate,
            0 as curtain_wall,
            0 as priority_level,
            1 as is_must
        from pp.other_flow_card where project_no = #{projectNo} group by process_id,layer
    </select>
    <!--模拟计算流程卡详情第三方-->
    <select id="selectComputeDetailMpThirdParty">
        select
            process_id as process_id,
            width as width,
            height as height,
            width as maxwidth,
            height as maxheight,
            order_number as order_number,
            glass_total as quantity,
            layer as technology_number,
            0 as patch_state
        from pp.other_flow_card where process_id = #{processId} and layer=#{technologyNumber}
    </select>
    <!--第一次优化查询-->
    <select id="firstOptimizationThirdParty">
        select
            project_no,
            concat( process_id, '-', layer ) AS process_id,
            width,
            height,
            layer,
            glass_total as quantity,
            product_name,
            order_number
        from pp.other_flow_card where project_no = #{projectNo}
    </select>
    <select id="analogComputationOptimizationThirdParty">
        SELECT
            h.project_no,
            h.layout_id,
            h.width AS width,
            h.height AS height,
            count( 1 ) AS quantity,
            concat( h.process_id, '-', h.layer ) AS 'process_id',
            h.layer,
            c.order_number
        from
            pp.`optimize_heat_detail` h
                LEFT JOIN pp.other_flow_card c ON h.process_id = c.process_id
                AND h.layer = c.technology_number
                AND h.order_sort = c.order_number
        WHERE
            h.project_no = #{projectNo}
        GROUP BY
            h.project_no,
            h.layout_id,
            h.width,
            h.height,
            h.process_id,
            h.layer,
            c.quantity,
            c.order_number
        ORDER BY
            LENGTH( h.layout_id ),
            h.layout_id;
    </select>
    <!--工程信息流程卡-->
    <select id="getProcessCardMpThirdParty">
        SELECT
            fc.process_id,
            fc.order_number,
            fc.glass_total as quantity,
            CONCAT(fc.layer, '/', COUNT(fc.total_layer)) AS layer,
            CONCAT(fc.width, ' × ', fc.height) AS sizes
        FROM
            pp.other_flow_card AS fc
        WHERE
            fc.project_no = #{projectNo}
        GROUP BY
            fc.layer,
            fc.order_number
    </select>
    <delete id="deleteOptimizeDetailThirdParty">
        delete from pp.other_flow_card
        where project_no = #{projectNumber}
    </delete>
</mapper>