zhoushihao
2025-10-27 636bd027a92d4bf669410ee550a5ca6324f72c88
UI-Project/src/views/EngineerScheduling/engineerScheduling.vue
@@ -16,29 +16,51 @@
    projectName: string
}
// 所有可选数据(左侧)
const dataSource = ref<TransferDataItem[]>([])
// 已选数据的keys(右侧)
const targetKeys = ref<string[]>([])
// 根据当前标签页获取右侧列表标题
const getRightListTitle = computed(() => {
    switch (activeTab.value) {
        case 'cutting1':
            return t('large.countOutOne')
        case 'cutting2':
            return t('large.countOutTwo')
        case 'tempered':
            return t('large.temp')
        default:
            return '已排产'
// Transfer组件所需的数据格式 - 完善字段定义
interface TransferDataItem {
  key: string
  projectNo: string
  projectName: string
  state?: number
  type?: number
  glassThickness?: string  // 玻璃厚度
  glassType?: string       // 玻璃类型
  glassTotal?: number      // 玻璃总数
  glassTotalArea?: number  // 玻璃总面积
    }
})
// 获取左侧数据源
// 所有可选数据(左侧表格)
const dataSource = ref<TransferDataItem[]>([])
// 已选数据(右侧表格)
const rightDataSource = ref<TransferDataItem[]>([])
// 左侧表格选中项
const selectedLeft = ref<TransferDataItem[]>([])
// 右侧表格选中项
const selectedRight = ref<TransferDataItem[]>([])
// 详情数据状态(改为数组类型,适应列表返回)
const detailData = ref<any[]>([]);
const showDetail = ref(false);
// 获取项目详情接口(更新为实际接口)
const fetchProjectDetail = async (projectNo: string) => {
  try {
    const response = await request.post('/loadGlass/optimizeProject/selectProgress', {
      projectNo: projectNo  // 传入工程ID(projectNo)
    });
    if (response.code === 200) {
      detailData.value = response.data;  // 假设返回数组
      showDetail.value = true;
    } else {
      ElMessage.error(`获取详情失败: ${response.message || '未知错误'}`);
    }
  } catch (error) {
    console.error('获取项目详情失败:', error);
    ElMessage.error('获取详情失败,请稍后重试');
  }
};
// 获取左侧数据源 - 更新映射逻辑(包含所有字段)
const fetchDataSource = async () => {
    try {
        // 根据当前标签页选择不同的接口
        const apiUrl = activeTab.value === 'tempered'
            ? '/cacheVerticalGlass/bigStorageCageDetails/queryEngineer'
            : '/loadGlass/optimizeProject/queryEngineer';
@@ -46,17 +68,17 @@
        const response = await request.post(apiUrl)
        if (response.code === 200) {
            dataSource.value = response.data.map((item: any) => {
                const projectNo = activeTab.value === 'tempered' ? item.engineerId : item.projectNo
                const name = activeTab.value === 'tempered' ? item.engineerId : (item.projectNo + "-" + item.projectName)
                return {
                    key: projectNo,
                    label: name,
                    projectNo: projectNo,
                    projectName: activeTab.value === 'tempered' ? '' : item.projectName
                }
            })
      dataSource.value = response.data.map((item: any) => ({
        key: activeTab.value === 'tempered' ? item.engineerId : item.projectNo,
        projectNo: activeTab.value === 'tempered' ? item.engineerId : item.projectNo,
        projectName: item.projectName || '',
        state: item.state,
        type: item.type,
        glassThickness: item.glassThickness || '',
        glassType: item.glassType || '',
        glassTotal: item.glassTotal || 0,
        glassTotalArea: item.glassTotalArea || 0
      }))
        } else {
            ElMessage.error(`获取数据失败: ${response.message || '未知错误'}`)
        }
@@ -66,40 +88,38 @@
    }
}
// 获取右侧已选数据
// 获取右侧已选数据 - 直接填充右侧表格
const fetchTargetKeys = async () => {
    try {
        // 根据当前标签页确定type参数
        let type = 1; // 默认切割一线
    let type = 1;
    let response;
        if (activeTab.value === 'cutting2') {
            type = 2; // 切割二线
      type = 2;
      response = await request.post('/loadGlass/optimizeProject/engineerScheduling', {type})
        } else if (activeTab.value === 'tempered') {
            type = 3; // 钢化
      type = 3;
      response = await request.post('/cacheVerticalGlass/bigStorageCageDetails/queryTemperingOrder')
    } else {
      response = await request.post('/loadGlass/optimizeProject/engineerScheduling', {type})
        }
        const response = await request.post('/loadGlass/optimizeProject/engineerScheduling', {
            type: type
        })
        if (response.code === 200) {
            // 对于所有标签页,使用projectNo作为key
            targetKeys.value = response.data.map((item: any) => item.projectNo)
            // 将右侧数据添加到dataSource中,确保Transfer组件能找到对应的项
            response.data.forEach((item: any) => {
                // 检查dataSource中是否已存在该项
                const exists = dataSource.value.some(dataItem => dataItem.key === item.projectNo)
                if (!exists) {
                    // 如果不存在,添加到dataSource中
                    const newItem: TransferDataItem = {
      // 右侧表格数据
      rightDataSource.value = response.data.map((item: any) => ({
                        key: item.projectNo,
                        label: `${item.projectNo}-${item.projectName || 'null'}`,
                        projectNo: item.projectNo,
                        projectName: item.projectName || ''
                    }
                    dataSource.value.push(newItem)
                }
            })
        projectName: item.projectName || '',
        state: item.state,
        type: item.type,
        glassThickness: item.glassThickness || '',
        glassType: item.glassType || '',
        glassTotal: item.glassTotal || 0,
        glassTotalArea: item.glassTotalArea || 0
      }))
      // 从左侧数据源移除右侧已选数据
      dataSource.value = dataSource.value.filter(
          item => !rightDataSource.value.some(rightItem => rightItem.key === item.key)
      )
        } else {
            ElMessage.error(`获取数据失败: ${response.message || '未知错误'}`)
        }
@@ -109,30 +129,50 @@
    }
}
// 保存排产信息
// 修改:支持单行数据移动到右侧表格末尾
const moveToRight = (row: TransferDataItem) => {
  if (!row) return;
  // 从左侧表格移除当前行
  dataSource.value = dataSource.value.filter(item => item.key !== row.key);
  // 添加到右侧表格末尾
  rightDataSource.value.push(row);
  ElMessage.success(`已添加项目 ${row.projectNo}`);
}
// 修改:支持单行数据从右侧表格移回左侧表格
const moveToLeft = (row: TransferDataItem) => {
  if (!row) return;
  // 从右侧表格移除当前行
  rightDataSource.value = rightDataSource.value.filter(item => item.key !== row.key);
  // 添加到左侧表格
  dataSource.value.push(row);
  ElMessage.success(`已移除项目 ${row.projectNo}`);
}
// 更新保存逻辑 - 使用右侧表格数据
const saveScheduling = async () => {
    try {
        // 根据当前标签页确定type参数
        let type = 1; // 默认切割一线
        if (activeTab.value === 'cutting2') {
            type = 2; // 切割二线
        } else if (activeTab.value === 'tempered') {
            type = 3; // 钢化
        }
    let type = 1;
    if (activeTab.value === 'cutting2') type = 2;
    else if (activeTab.value === 'tempered') type = 3;
        const engineerList = targetKeys.value.map(projectNo => {
            const dataItem = dataSource.value.find(item => item.key === projectNo)
            return {
                projectNo: projectNo,
                projectName: dataItem ? dataItem.projectName : ''
            }
        })
    // 右侧表格数据即为需要保存的排产数据
    const engineerList = rightDataSource.value.map(item => ({
      projectNo: item.projectNo,
      projectName: item.projectName
    }))
        const response = await request.post(`/loadGlass/optimizeProject/updateEngineerScheduling?type=${type}`, engineerList)
    const response = await request.post(
        `/loadGlass/optimizeProject/updateEngineerScheduling?type=${type}`,
        engineerList
    )
        if (response.code === 200) {
            ElMessage.success('保存成功')
            // 重新加载数据
            await fetchDataSource()
            await fetchTargetKeys()
        } else {
@@ -148,7 +188,7 @@
const resetScheduling = async () => {
    await fetchDataSource()
    await fetchTargetKeys()
    ElMessage.info(t('已取消'))
  ElMessage.info(t('scheduling.cancelled'))
}
// 监听标签页切换,根据不同标签页加载对应的数据
@@ -167,6 +207,128 @@
    await fetchDataSource()
    await fetchTargetKeys()
})
// 判断是否为第一行
const isFirstRow = (row: TransferDataItem) => {
  const index = rightDataSource.value.findIndex(item => item.key === row.key);
  return index === 0;
};
// 判断是否为最后一行
const isLastRow = (row: TransferDataItem) => {
  const index = rightDataSource.value.findIndex(item => item.key === row.key);
  return index === rightDataSource.value.length - 1;
};
// 判断上方是否有进行中的行(修复:使用正确的状态值1)
const hasInProgressAbove = (row: TransferDataItem) => {
  const index = rightDataSource.value.findIndex(item => item.key === row.key);
  // 查找当前行上方是否有进行中的任务
  return rightDataSource.value.some((item, i) => i < index && item.state === 1);
};
// 向上移动一行
const moveUp = (row: TransferDataItem) => {
  const index = rightDataSource.value.findIndex(item => item.key === row.key);
  if (index > 0) {
    // 检查是否会超过进行中的任务
    const prevItem = rightDataSource.value[index - 1];
    if (prevItem.state === 1) {
      // 直接上方是进行中任务,提示用户不可超过
      ElMessage.warning('操作不可超过进行中的任务');
      return;
    }
    // 交换位置
    [rightDataSource.value[index], rightDataSource.value[index - 1]] =
        [rightDataSource.value[index - 1], rightDataSource.value[index]];
    // 触发响应式更新
    rightDataSource.value = [...rightDataSource.value];
  }
};
// 向下移动一行
const moveDown = (row: TransferDataItem) => {
  const index = rightDataSource.value.findIndex(item => item.key === row.key);
  if (index < rightDataSource.value.length - 1) {
    // 交换位置
    [rightDataSource.value[index], rightDataSource.value[index + 1]] =
        [rightDataSource.value[index + 1], rightDataSource.value[index]];
    // 触发响应式更新
    rightDataSource.value = [...rightDataSource.value];
  }
};
// 判断是否可以置顶(即是否有可置顶的位置)
const canMoveToTop = (row: TransferDataItem) => {
  // 进行中任务不能置顶
  if (row.state === 1) return false;
  const index = rightDataSource.value.findIndex(item => item.key === row.key);
  // 已经是第一行的不能置顶
  if (index === 0) return false;
  // 查找第一个非进行中任务的位置
  const firstNonProgressIndex = rightDataSource.value.findIndex(item => item.state !== 1);
  // 如果当前行已经在第一个非进行中任务的位置或之前,则不能置顶
  return index > firstNonProgressIndex;
};
// ... existing code (moveUp和moveDown函数保持不变)
// 置顶(移动到所有非进行中任务的最前面)
const moveToTop = (row: TransferDataItem) => {
  const index = rightDataSource.value.findIndex(item => item.key === row.key);
  if (index > 0) {
    // 移除当前行
    const newList = rightDataSource.value.filter(item => item.key !== row.key);
    // 查找所有非进行中任务的位置
    const nonProgressIndices = newList
        .map((item, i) => ({ item, index: i }))
        .filter(item => item.item.state !== 1)
        .map(item => item.index);
    if (nonProgressIndices.length > 0) {
      // 有非进行中任务,添加到第一个非进行中任务的位置
      newList.splice(nonProgressIndices[0], 0, row);
    } else {
      // 全部都是进行中任务,添加到开头
      newList.unshift(row);
    }
    // 更新数据源
    rightDataSource.value = newList;
  }
};
// 创建计算属性,根据标签页返回不同的表头配置
const tableHeaders = computed(() => {
  const baseHeaders = {
    serial: t('scheduling.serial'),
    projectNo: t('scheduling.projectNo'),
    thickness: t('scheduling.thickness'),
    glassType: t('scheduling.glassType'),
    totalCount: t('scheduling.totalCount'),
    totalArea: t('scheduling.totalArea'),
    operate: t('scheduling.operate')
  };
  // 根据不同标签页返回不同的表头配置
  if (activeTab.value === 'tempered') {
    return {
      ...baseHeaders,
      // 更改钢化标签页的特定表头
      totalCount: t('scheduling.totalFireCount'),
      totalArea: t('scheduling.fullFireCount')
    };
  }
  return baseHeaders;
});
// 状态格式化函数
const stateFormatter = (row: any) => {
  return row.state === 1 ? '进行中' : '未开始';
};
</script>
<template>
@@ -177,15 +339,106 @@
            <el-tab-pane :label="t('large.temp')" name="tempered" />
        </el-tabs>
        <div class="transfer-wrapper">
            <el-transfer v-model="targetKeys" class="custom-transfer" filterable :data="dataSource"
                :titles="['待排产', getRightListTitle]" :button-texts="['', '']">
                <template #right-empty>
                    <el-empty :image-size="80" description="No data" />
    <!-- 表格布局容器 -->
    <div class="table-container">
      <!-- 左侧表格:待排产 -->
      <div class="table-wrapper" v-if="activeTab !== 'tempered'">
        <h3 class="table-title">{{ t('scheduling.unfinished') }} ({{ dataSource.length }})</h3>
        <el-table
            :data="dataSource"
            border
            class="custom-table"
            height="400"
            row-key="key"
        >
          <el-table-column type="index" :label="$t('scheduling.serial')" width="55"/>
          <el-table-column prop="projectNo" :label="$t('scheduling.projectNo')" width="150"/>
          <el-table-column prop="glassThickness" :label="$t('scheduling.thickness')" width="120"/>
          <el-table-column prop="glassType" :label="$t('scheduling.glassType')" width="120"/>
          <el-table-column prop="glassTotal" :label="$t('scheduling.totalCount')" width="90"/>
          <el-table-column prop="glassTotalArea" :label="$t('scheduling.totalArea')" width="100"/>
          <el-table-column :label="$t('scheduling.operate')" width="90" align="center">
            <template #default="{ row }">
              <el-button type="primary" @click="moveToRight(row)">
                {{ t('scheduling.add') }}
              </el-button>
                </template>
            </el-transfer>
          </el-table-column>
        </el-table>
      </div>
      <!-- 右侧表格:已排产 -->
      <div class="table-wrapper">
        <h3 class="table-title">{{ t('scheduling.completed') }} ({{ rightDataSource.length }})</h3>
        <el-table
            :data="rightDataSource"
            border
            class="custom-table"
            height="400"
            row-key="key"
            @row-click="(row) => fetchProjectDetail(row.key)"
            highlight-current-row
        >
          <el-table-column type="index" :label="tableHeaders.serial" width="55"/>
          <el-table-column prop="projectNo" :label="tableHeaders.projectNo" width="120"/>
          <el-table-column prop="glassThickness" :label="tableHeaders.thickness" width="120"/>
          <el-table-column prop="glassType" :label="tableHeaders.glassType" width="120"/>
          <el-table-column prop="glassTotal" :label="tableHeaders.totalCount" width="90"/>
          <el-table-column prop="glassTotalArea" :label="tableHeaders.totalArea" width="120"/>
          <el-table-column
              prop="state"
              :label="t('scheduling.state')"
              width="90"
              :formatter="stateFormatter"
          />
          <el-table-column :label="t('scheduling.operate')" :width="activeTab === 'tempered' ? 200 : 300" :align="center">
            <template #default="{ row }">
              <div style="display: flex; gap: 5px; align-items: center;">
                <el-button
                    v-if="activeTab !== 'tempered'"
                    type="default"
                    size="small"
                    @click="moveToLeft(row)"
                    :disabled="row.state === 1"
                    style="background: #ff4d4f; color: white; border-radius: 8px; min-width: 60px; display: inline-flex; justify-content: center; align-items: center;"
                >
                  {{ t('scheduling.remove') }}
                </el-button>
                <el-button
                    type="default"
                    size="small"
                    @click="moveUp(row)"
                    :disabled="row.state === 1 || isFirstRow(row)"
                    style="background: #E6F4FF; color: #1890FF; border-radius: 8px; min-width: 40px; display: inline-flex; justify-content: center; align-items: center;"
                >
                  ↑
                </el-button>
                <el-button
                    type="default"
                    size="small"
                    @click="moveDown(row)"
                    :disabled="row.state === 1 || isLastRow(row)"
                    style="background: #E6F4FF; color: #1890FF; border-radius: 8px; min-width: 40px; display: inline-flex; justify-content: center; align-items: center;"
                >
                  ↓
                </el-button>
                <el-button
                    size="small"
                    @click="moveToTop(row)"
                    :disabled="row.state === 1 || isFirstRow(row) || !canMoveToTop(row)"
                    style="background: #E6F4FF; color: #1890FF; border-radius: 8px; min-width: 60px; display: inline-flex; justify-content: center; align-items: center;"
                >
                  {{ t('scheduling.top') }}
                </el-button>
              </div>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </div>
    <!-- 保存和重置按钮 -->
            <div class="transfer-save">
                <el-button type="primary" @click="saveScheduling">
                    {{ t('searchOrder.makesure') }}
@@ -195,10 +448,101 @@
                </el-button>
            </div>
        </div>
  <!-- 详情展示区域(改为表格展示列表数据) -->
  <div v-if="showDetail" class="project-detail">
    <!--    <h3 class="detail-title">工程详情列表 ({{ detailData.length }})</h3>-->
    <el-table
        :data="detailData"
        border
        class="detail-table"
        height="230"
    >
      <el-table-column prop="engineerId" :label="$t('scheduling.projectNo')" width="120"/>
      <el-table-column prop="flowCardId" :label="$t('scheduling.flowCardId')" width="150"/>
      <el-table-column prop="layer" :label="$t('scheduling.layerCount')" width="80"/>
      <el-table-column prop="glassType" :label="$t('scheduling.serial')" width="80"/>
      <el-table-column prop="thickness" :label="$t('scheduling.thickness')" width="100"/>
      <el-table-column prop="filmsid" :label="$t('scheduling.coatingType')" width="120"/>
      <el-table-column prop="width" :label="$t('scheduling.width')" width="90"/>
      <el-table-column prop="height" :label="$t('scheduling.height')" width="90"/>
      <el-table-column prop="glassIdCount" :label="$t('scheduling.glassCount')" width="80"/>
      <el-table-column prop="cuttingCount" :label="$t('scheduling.cuttingCount')" width="80"/>
      <el-table-column prop="edgingCount" :label="$t('scheduling.edgingCount')" width="80"/>
      <el-table-column prop="temperingCount" :label="$t('scheduling.temperingCount')" width="80"/>
      <el-table-column prop="insulatingCount" :label="$t('scheduling.insulatingCount')" width="80"/>
    </el-table>
    </div>
</template>
<style scoped>
/* 表格布局容器 */
.table-container {
  display: flex;
  align-items: center;
  gap: 20px;
  margin-bottom: 20px;
}
/* 详情表格样式 */
.project-detail {
  margin-top: 0px;
  padding: 15px;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  width: 100%;
  box-sizing: border-box;
  height: auto; /* 确保容器高度自适应 */
}
.detail-title {
  margin-bottom: 15px;
  font-size: 16px;
  font-weight: bold;
  color: #303133;
}
.detail-table {
  width: 100%;
  font-size: 14px;
}
/* 强制表格内容区域滚动 */
::v-deep(.detail-table .el-table__body-wrapper) {
  overflow-y: auto !important; /* 优先确保垂直滚动 */
  max-height: calc(230px - 50px); /* 适配表格高度(需与el-table height匹配) */
}
.table-wrapper {
  flex: 1;
}
.table-title {
  margin-bottom: 10px;
  font-size: 18px;
  font-weight: bold;
  color: #303133;
}
.custom-table {
  width: 100%;
  font-size: 14px;
}
/* 转移按钮样式 */
.transfer-buttons {
  display: flex;
  flex-direction: column;
}
/* 调整按钮区域位置 */
.transfer-save {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin-top: 20px;
}
.engineer-scheduling-container {
    padding: 20px;
    border: 1px solid #dcdfe6;