周宾
5 天以前 8845ac3d32217b6cf765934dc29dffe0065232d8
src/pages/productionManagement/productionDispatching/index.vue
@@ -2,7 +2,7 @@
   <view class="production-dispatching">
      <!-- 使用通用页面头部组件 -->
      <PageHeader title="生产派工" @back="goBack" />
      <!-- 炒机状态展示 -->
      <view class="machines-section">
         <view class="section-title">炒机状态</view>
@@ -33,6 +33,28 @@
               </view>
            </view>
         </view>
         <!-- 损耗率设置 -->
      <view class="loss-rate-section">
         <view class="section-title">损耗率设置</view>
         <view class="loss-rate-content">
            <view class="loss-rate-item">
               <up-button
                  class="loss-rate-btn"
                  type="primary"
                  plain
                  size="small"
                  @click="showLossRateSheet = true"
               >{{ lossRate ? `损耗率: ${lossRate}%` : '请选择损耗率' }}</up-button>
               <up-action-sheet
               :show="showLossRateSheet"
               :actions="lossRateOptions"
               @select="onLossRateSelect"
               title="选择损耗率"
               @close="showLossRateSheet = false"
            />
            </view>
         </view>
      </view>
         <view class="save-section">
            <up-button type="primary" @click="saveMachineTotals" size="normal" class="save-btn">保存炒机设置</up-button>
         </view>
@@ -71,46 +93,41 @@
      </view>
      
      <!-- 批量操作区域 -->
      <view v-if="showBatchActions" class="batch-actions-section">
      <view class="batch-actions-section" v-if="showBatchActions">
         <view class="batch-info">
            <text class="batch-count">已选择 {{ selectedItems.length }} 个项目</text>
            <text class="batch-text">已选择 {{ selectedItems.length }} 个项目</text>
         </view>
         <view class="batch-buttons">
            <up-button type="primary" size="small" @click="handleAutoDispatch" class="batch-btn">
               <up-icon name="play-circle" size="16" color="#ffffff"></up-icon>
               自动派单
            </up-button>
            <up-button type="default" size="small" @click="clearSelection" class="batch-btn">
               <up-icon name="close-circle" size="16" color="#6c757d"></up-icon>
               取消选择
            </up-button>
            <up-button type="primary" size="small" @click="handleAutoDispatch" class="batch-btn">自动派单</up-button>
            <up-button type="default" size="small" @click="clearSelection" class="batch-btn">取消选择</up-button>
         </view>
      </view>
      <!-- 全选操作区域 -->
      <view v-if="tableData.length > 0" class="select-all-section">
         <view class="select-all-checkbox" @click="toggleAllSelection">
            <up-icon
               :name="isAllSelected ? 'checkbox-mark' : 'circle'"
               :color="isAllSelected ? '#409eff' : '#c0c4cc'"
               size="18"
            ></up-icon>
            <text class="select-all-text">{{ isAllSelected ? '取消全选' : '全选' }}</text>
      <view class="select-all-section" v-if="tableData.length > 0">
         <view class="select-all-content">
            <up-checkbox
            v-model="isAllSelected"
            @change="toggleAllSelection"
            label="全选"
            class="select-all-checkbox"
            :disabled="tableData.length === 0 || tableData.filter(item => item.pendingQuantity > 0 && item.speculativeTradingName).length === 0"
         />
         </view>
         <text class="select-all-hint">(仅选择待排数量大于0的项目)</text>
      </view>
      <!-- 生产派工列表 -->
      <view class="ledger-list" v-if="tableData.length > 0">
         <view v-for="(item, index) in tableData" :key="item.id || index" class="list-item">
            <view class="ledger-item">
               <!-- 选择复选框 -->
               <view class="item-checkbox" @click="toggleItemSelection(item)">
                  <up-icon
                     :name="selectedItems.includes(item.id) ? 'checkbox-mark' : 'circle'"
                     :color="selectedItems.includes(item.id) ? '#409eff' : '#c0c4cc'"
                     size="18"
                  ></up-icon>
               <view class="item-checkbox">
                  <up-checkbox
               :model-value="selectedItems.some(selected => selected.id === item.id)"
               @change="(checked) => toggleItemSelection(item, checked)"
               :disabled="item.pendingQuantity <= 0 || !item.speculativeTradingName"
               shape="circle"
            />
               </view>
               
               <view class="item-content">
@@ -125,18 +142,18 @@
                  </view>
               
                  <view class="item-details">
                     <view class="detail-row">
                     <!-- <view class="detail-row">
                        <text class="detail-label">客户合同号</text>
                        <text class="detail-value">{{ item.customerContractNo }}</text>
                     </view>
                     </view> -->
                     <view class="detail-row">
                        <text class="detail-label">客户名称</text>
                        <text class="detail-value">{{ item.customerName }}</text>
                     </view>
                     <view class="detail-row">
                     <!-- <view class="detail-row">
                        <text class="detail-label">项目名称</text>
                        <text class="detail-value">{{ item.projectName }}</text>
                     </view>
                     </view> -->
                     <view class="detail-row">
                        <text class="detail-label">产品大类</text>
                        <text class="detail-value">{{ item.productCategory }}</text>
@@ -146,11 +163,19 @@
                        <text class="detail-value">{{ item.specificationModel }}</text>
                     </view>
                     <view class="detail-row">
                        <text class="detail-label">绑定机器</text>
                        <text class="detail-value">{{ item.speculativeTradingName }}</text>
                     </view>
                     <view class="detail-row">
                        <text class="detail-label">单位</text>
                        <text class="detail-value">{{ item.unit }}</text>
                     </view>
                     <view class="detail-row">
                        <text class="detail-label">总数量</text>
                        <text class="detail-label">录入日期</text>
                        <text class="detail-value">{{ item.entryDate }}</text>
                     </view>
                     <view class="detail-row">
                        <text class="detail-label">数量</text>
                        <text class="detail-value">{{ item.quantity }}</text>
                     </view>
                     <view class="detail-row">
@@ -165,14 +190,14 @@
                     <!-- 操作按钮区域 -->
                     <view class="action-buttons">
                        <up-button
                           type="primary"
                           size="small"
                           @click="handleDispatch(item)"
                           class="action-btn"
                           :disabled="item.pendingQuantity <= 0"
                        >
                           {{ item.pendingQuantity <= 0 ? '无需派工' : '生产派工' }}
                        </up-button>
                  type="primary"
                  size="small"
                  @click="handleDispatch(item)"
                  class="action-btn"
                  :disabled="item.pendingQuantity <= 0 || !item.speculativeTradingName"
               >
                  {{ item.pendingQuantity <= 0 ? '无需派工' : !item.speculativeTradingName ? '未绑定机器' : '生产派工' }}
               </up-button>
                     </view>
                  </view>
               </view>
@@ -188,16 +213,20 @@
      
      <!-- 派工弹窗 -->
      <DispatchModal ref="dispatchModalRef" @confirm="handleDispatchConfirm" />
      <!-- 自动派单弹窗 -->
      <AutoDispatchDia ref="autoDispatchDia" />
   </view>
</template>
<script setup>
import { ref, reactive, toRefs, getCurrentInstance } from "vue";
import { ref, reactive, toRefs, getCurrentInstance, nextTick } from "vue";
import { onShow } from '@dcloudio/uni-app';
import dayjs from "dayjs";
import {schedulingListPage, schedulingList, addSpeculatTrading, updateSpeculatTrading} from "@/api/productionManagement/productionOrder.js";
import {schedulingListPage, schedulingList, addSpeculatTrading, updateSpeculatTrading, getLossRate, addLossRate, updateLossRate} from "@/api/productionManagement/productionOrder.js";
import PageHeader from "@/components/PageHeader.vue";
import DispatchModal from "./components/DispatchModal.vue";
import AutoDispatchDia from "./components/autoDispatchDia.vue";
const { proxy } = getCurrentInstance();
@@ -207,10 +236,10 @@
// 列表数据
const tableData = ref([]);
// 批量选择相关数据
const selectedItems = ref([]); // 选中的项目ID数组
const isAllSelected = ref(false); // 是否全选
const showBatchActions = ref(false); // 是否显示批量操作区域
// 选择相关数据
const selectedItems = ref([]);
const isAllSelected = ref(false);
const showBatchActions = ref(false);
// 搜索表单数据
const data = reactive({
@@ -261,8 +290,23 @@
// 是否有查询数据(用于判断是新增还是修改)
const hasQueryData = ref(false);
// 损耗率相关数据
const lossRate = ref(""); // 当前选择的损耗率
const showLossRateSheet = ref(false); // 控制损耗率选择面板显示
const lossRateOptions = ref([
   { name: "6%", value: "6" },
   { name: "7%", value: "7" },
   { name: "8%", value: "8" },
   { name: "9%", value: "9" },
   { name: "10%", value: "10" }
]);
const lossRateData = ref(null); // 损耗率查询返回的数据
// 派工弹窗引用
const dispatchModalRef = ref();
// 自动派单弹窗引用
const autoDispatchDia = ref();
// 通用提示函数
const showLoadingToast = (message) => {
@@ -328,6 +372,28 @@
   });
};
// 损耗率选择事件
const onLossRateSelect = (action) => {
   lossRate.value = action.value;
   showLossRateSheet.value = false;
   console.log('选择了损耗率:', action.name, '值:', action.value);
};
// 获取损耗率数据
const getLossRateData = () => {
   getLossRate().then((res) => {
      if (res.data) {
         lossRateData.value = res.data;
         // 设置当前选择的损耗率
         if (res.data.rate !== null && res.data.rate !== undefined) {
            lossRate.value = res.data.rate.toString();
         }
      }
   }).catch(err => {
      console.error('获取损耗率失败:', err);
   });
};
// 获取列表数据
const getList = () => {
   loading.value = true;
@@ -340,14 +406,17 @@
      closeToast();
      
      tableData.value = (res.data.records || []).map(item => ({
         ...item,
         pendingQuantity: (Number(item.quantity) || 0) - (Number(item.schedulingNum) || 0)
      }));
            ...item,
            pendingQuantity: (Number(item.quantity) || 0) - (Number(item.schedulingNum) || 0)
         })).filter(item => item.pendingQuantity > 0);
      
      page.total = res.data.total || 0;
      
      // 获取炒机数据
      getMachineProductionData();
      // 获取损耗率数据
      getLossRateData();
      
   }).catch(() => {
      loading.value = false;
@@ -364,6 +433,14 @@
   if (item.pendingQuantity <= 0) {
      uni.showToast({
         title: '该项目无需再派工',
         icon: 'none'
      });
      return;
   }
   if (!item.speculativeTradingName) {
      uni.showToast({
         title: '该项目未绑定机器,无法派工',
         icon: 'none'
      });
      return;
@@ -392,6 +469,51 @@
      workLoad: machineTotal[`m${machineId}`] || 0,
      currentWorkLoad: machineInProduction[`m${machineId}`] || 0
   };
};
// 保存损耗率设置
const saveLossRate = () => {
   if (!lossRate.value) {
      console.log('未选择损耗率,跳过保存');
      return Promise.resolve();
   }
   const lossRateDataToSave = {
      rate: parseFloat(lossRate.value) || 0
   };
   // 如果有查询到的损耗率数据,说明是修改操作,需要传递id
   if (lossRateData.value && lossRateData.value.id) {
      lossRateDataToSave.id = lossRateData.value.id;
   }
   console.log('保存损耗率数据:', lossRateDataToSave);
   // 根据是否有损耗率数据决定调用新增接口还是修改接口
   const saveLossApi = lossRateData.value && lossRateData.value.id ? updateLossRate : addLossRate;
   const successMessage = lossRateData.value && lossRateData.value.id ? '损耗率修改成功' : '损耗率新增成功';
   return saveLossApi(lossRateDataToSave).then(res => {
      console.log('损耗率保存成功:', res);
      uni.showToast({
         title: successMessage,
         icon: 'success'
      });
      // 更新损耗率数据
      if (res.data) {
         lossRateData.value = res.data;
      }
      return res;
   }).catch(err => {
      console.error('损耗率保存失败:', err);
      uni.showToast({
         title: '损耗率保存失败',
         icon: 'none'
      });
      throw err;
   });
};
// 保存炒机总量设置
@@ -425,9 +547,15 @@
   
   console.log(`调用接口: ${hasQueryData.value ? '修改' : '新增'}`);
   
   // 调用后端API保存
   saveApi(saveData).then(res => {
      proxy.$message.success(successMessage);
   // 先保存损耗率,再保存炒机设置
   saveLossRate().then(() => {
      // 调用后端API保存炒机设置
      return saveApi(saveData);
   }).then(res => {
      uni.showToast({
         title: successMessage,
         icon: 'success'
      });
      console.log('保存成功:', res);
      
      // 保存成功后,设置hasQueryData为true,下次保存将调用修改接口
@@ -435,61 +563,69 @@
         hasQueryData.value = true;
      }
   }).catch(err => {
      proxy.$message.error('保存失败');
      uni.showToast({
         title: '保存失败',
         icon: 'none'
      });
      console.error('保存失败:', err);
   });
};
// 批量选择相关函数
// 切换单个项目的选择状态
const toggleItemSelection = (item) => {
   const itemId = item.id;
   const index = selectedItems.value.indexOf(itemId);
// 切换单个项目选择状态
const toggleItemSelection = (item, checked) => {
   // 仅允许选择已绑定机器且待派数量>0的项目
   if (!item.speculativeTradingName || item.pendingQuantity <= 0) return;
   
   if (index > -1) {
      // 如果已选中,则取消选择
      selectedItems.value.splice(index, 1);
   console.log('切换选择状态:', item.id, checked);
   // 使用更严格的比较逻辑,确保ID唯一性
   const index = selectedItems.value.findIndex(selected => {
      // 深度比较对象,确保是同一个项目
      return JSON.stringify(selected) === JSON.stringify(item);
   });
   if (checked) {
      // 如果选中且不在选中列表中,则添加
      if (index === -1) {
         selectedItems.value.push({...item}); // 创建新对象,避免引用问题
         console.log('添加项目后选中数量:', selectedItems.value.length);
      }
   } else {
      // 如果未选中,则添加选择
      selectedItems.value.push(itemId);
      // 如果取消选中且在选中列表中,则移除
      if (index > -1) {
         selectedItems.value.splice(index, 1);
         console.log('移除项目后选中数量:', selectedItems.value.length);
      }
   }
   
   // 更新全选状态
   console.log('当前选中项目列表:', selectedItems.value.map(s => s.id));
   updateAllSelectedStatus();
   // 更新批量操作区域显示状态
   updateBatchActionsVisibility();
};
// 切换全选状态
const toggleAllSelection = () => {
   if (isAllSelected.value) {
      // 取消全选
      selectedItems.value = [];
   } else {
      // 全选
      selectedItems.value = tableData.value
         .filter(item => item.pendingQuantity > 0) // 只选择待排数量大于0的项目
         .map(item => item.id);
      selectedItems.value = tableData.value.filter(item => item.pendingQuantity > 0 && item.speculativeTradingName).map(item => ({ ...item }));
   }
   isAllSelected.value = !isAllSelected.value;
   updateBatchActionsVisibility();
};
// 更新全选状态
const updateAllSelectedStatus = () => {
   const selectableItems = tableData.value.filter(item => item.pendingQuantity > 0);
   if (selectableItems.length === 0) {
   const selectableItems = tableData.value.filter(item => item.pendingQuantity > 0 && item.speculativeTradingName);
   if (selectableItems.length > 0 && selectedItems.value.length === selectableItems.length &&
      selectableItems.every(item => selectedItems.value.some(selected => selected.id === item.id))) {
      isAllSelected.value = true;
   } else {
      isAllSelected.value = false;
      return;
   }
   isAllSelected.value = selectedItems.value.length === selectableItems.length &&
      selectableItems.every(item => selectedItems.value.includes(item.id));
};
// 更新批量操作区域显示状态
// 更新批量操作显示状态
const updateBatchActionsVisibility = () => {
   showBatchActions.value = selectedItems.value.length > 0;
};
@@ -503,80 +639,36 @@
// 获取选中的项目
const getSelectedItems = () => {
   return tableData.value.filter(item => selectedItems.value.includes(item.id));
   return selectedItems.value;
};
// 自动派单功能
// 处理自动派单
const handleAutoDispatch = () => {
   const selectedItemsList = getSelectedItems();
   if (selectedItemsList.length === 0) {
   if (selectedItems.value.length === 0) {
      uni.showToast({
         title: '请先选择要派工的项目',
         title: '请选择要派工的项目',
         icon: 'none'
      });
      return;
   }
   
   // 检查是否有项目待排数量不足
   const invalidItems = selectedItemsList.filter(item => item.pendingQuantity <= 0);
   if (invalidItems.length > 0) {
   // 检查是否所有选中项目都有绑定机器
   const unboundItems = selectedItems.value.filter(item => !item.speculativeTradingName);
   if (unboundItems.length > 0) {
      uni.showToast({
         title: `有${invalidItems.length}个项目无需派工,已自动过滤`,
         icon: 'none'
      });
   }
   // 过滤掉待排数量不足的项目
   const validItems = selectedItemsList.filter(item => item.pendingQuantity > 0);
   if (validItems.length === 0) {
      uni.showToast({
         title: '没有可派工的项目',
         title: '所选项目中有未绑定机器的项目,无法自动派单',
         icon: 'none'
      });
      return;
   }
   
   uni.showModal({
      title: '确认自动派单',
      content: `确定要对选中的${validItems.length}个项目进行自动派单吗?`,
      success: (res) => {
         if (res.confirm) {
            executeAutoDispatch(validItems);
         }
      }
   });
};
// 执行自动派单
const executeAutoDispatch = (items) => {
   showLoadingToast('自动派单中...');
   // 模拟自动派单过程
   setTimeout(() => {
      closeToast();
      // 这里应该调用实际的自动派单API
      // 暂时使用模拟成功
      uni.showToast({
         title: `成功为${items.length}个项目完成自动派单`,
         icon: 'success'
      });
      // 清空选择
      clearSelection();
      // 刷新列表
      getList();
      console.log('自动派单项目:', items);
   }, 1500);
   // 确保传递的是完整的选中项目数组
   autoDispatchDia.value?.openDialog([...selectedItems.value]);
};
// 页面显示时加载数据
onShow(() => {
   getList();
   // 清空选择状态
   clearSelection();
});
</script>
@@ -588,16 +680,63 @@
   padding: 20rpx;
}
// 损耗率设置区域
.loss-rate-section {
   background: #ffffff;
   border: 1rpx solid #e4e7ed;
   border-radius: 12rpx;
   padding: 32rpx;
   margin-top: 24rpx;
   margin-bottom: 32rpx;
   box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
}
.loss-rate-section .section-title {
   font-size: 32rpx;
   font-weight: 600;
   color: #303133;
   margin-bottom: 20rpx;
}
.loss-rate-section .loss-rate-content {
   display: flex;
   flex-direction: column;
   gap: 24rpx;
}
.loss-rate-section .loss-rate-content .loss-rate-item {
   display: flex;
   align-items: center;
   gap: 24rpx;
}
.loss-rate-section .loss-rate-content .loss-rate-label {
   font-size: 30rpx;
   font-weight: 500;
   color: #303133;
   min-width: 140rpx;
   white-space: nowrap;
}
.loss-rate-section .loss-rate-content .loss-rate-btn {
   min-width: 260rpx;
   font-size: 28rpx;
   height: 64rpx;
   line-height: 64rpx;
   border-radius: 8rpx;
   font-weight: 500;
}
// 炒机状态区域
.machines-section {
   margin-bottom: 30rpx;
   .section-title {
      font-size: 32rpx;
      font-weight: 600;
      color: #303133;
      margin-bottom: 20rpx;
   }
}
.machines-section .section-title {
   font-size: 32rpx;
   font-weight: 600;
   color: #303133;
   margin-bottom: 20rpx;
}
.machines-grid {
@@ -771,10 +910,10 @@
   align-items: center;
   padding: 12rpx 0;
   border-bottom: 1rpx solid #f5f5f5;
   &:last-child {
      border-bottom: none;
   }
}
.detail-row:last-child {
   border-bottom: none;
}
.detail-label {
@@ -786,16 +925,16 @@
   font-size: 26rpx;
   color: #303133;
   font-weight: 500;
   &.highlight {
      color: #ff6b35;
      font-weight: 600;
   }
   &.danger {
      color: #ee0a24;
      font-weight: 600;
   }
}
.detail-value.highlight {
   color: #ff6b35;
   font-weight: 600;
}
.detail-value.danger {
   color: #ee0a24;
   font-weight: 600;
}
.action-buttons {
@@ -808,6 +947,69 @@
   min-width: 180rpx;
}
// 批量操作区域样式
.batch-actions-section {
   background: #e8f4ff;
   border: 1rpx solid #409eff;
   border-radius: 12rpx;
   padding: 20rpx 24rpx;
   margin-bottom: 24rpx;
   display: flex;
   justify-content: space-between;
   align-items: center;
}
.batch-actions-section .batch-text {
   font-size: 28rpx;
   font-weight: 600;
   color: #409eff;
}
.batch-actions-section .batch-buttons {
   display: flex;
   gap: 16rpx;
}
.batch-actions-section .batch-btn {
   min-width: 140rpx;
}
// 全选操作区域样式
.select-all-section {
   background: #ffffff;
   border-radius: 12rpx;
   padding: 20rpx 24rpx;
   margin-bottom: 16rpx;
   border: 1rpx solid #e4e7ed;
}
.select-all-section .select-all-content {
   display: flex;
   align-items: center;
}
.select-all-section .select-all-checkbox {
   font-size: 28rpx;
   font-weight: 500;
}
// 列表项选择框样式
.ledger-item {
   display: flex;
   align-items: flex-start;
   padding: 0;
}
.item-checkbox {
   padding: 24rpx 16rpx 0 24rpx;
   display: flex;
   align-items: center;
}
.item-content {
   flex: 1;
}
// 空状态
.no-data {
   padding: 100rpx 0;
@@ -818,88 +1020,6 @@
   font-size: 28rpx;
   color: #909399;
   margin-top: 20rpx;
}
// 批量操作区域样式
.batch-actions-section {
   background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
   border-radius: 16rpx;
   padding: 24rpx;
   margin-bottom: 24rpx;
   box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3);
   color: #ffffff;
}
.batch-info {
   display: flex;
   justify-content: space-between;
   align-items: center;
   margin-bottom: 20rpx;
}
.batch-count {
   font-size: 28rpx;
   font-weight: 600;
}
.batch-buttons {
   display: flex;
   gap: 20rpx;
   justify-content: flex-end;
}
.batch-btn {
   min-width: 180rpx;
}
// 全选操作区域样式
.select-all-section {
   display: flex;
   justify-content: space-between;
   align-items: center;
   background: #f8f9fa;
   border-radius: 12rpx;
   padding: 20rpx 24rpx;
   margin-bottom: 20rpx;
   border: 1rpx solid #e9ecef;
}
.select-all-checkbox {
   display: flex;
   align-items: center;
   gap: 12rpx;
   cursor: pointer;
}
.select-all-text {
   font-size: 26rpx;
   color: #606266;
   font-weight: 500;
}
.select-all-hint {
   font-size: 22rpx;
   color: #909399;
}
// 列表项选择样式
.ledger-item {
   display: flex;
   align-items: flex-start;
   padding: 0;
}
.item-checkbox {
   padding: 24rpx 16rpx 0 24rpx;
   cursor: pointer;
   display: flex;
   align-items: center;
   min-height: 48rpx;
}
.item-content {
   flex: 1;
   padding: 0;
}
// 点击编辑区域样式