zhangwencui
3 天以前 6f32ae6d2545c8279032105bf263eb2552fe6a09
1.生产工单做流转单2.生产订单工艺路线做排序删除3.生产工单加报工
已修改7个文件
2520 ■■■■■ 文件已修改
src/api/productionManagement/productProcessRoute.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/workOrder.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/ProcessRouteItemForm.vue 864 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/index.vue 335 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/index.vue 824 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrder/index.vue 452 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productProcessRoute.js
@@ -12,8 +12,17 @@
export function addOrUpdateProductProcessRouteItem(data) {
    return request({
        url: "/productProcessRoute",
        url: "/productProcessRoute/updateRouteItem",
        method: "post",
        data: data,
    });
}
// 删除客户档案
export function deleteRouteItem(ids) {
    return request({
        url: '/productProcessRoute/deleteRouteItem',
        method: 'delete',
        data: ids
    })
}
src/api/productionManagement/workOrder.js
@@ -7,3 +7,19 @@
    params: query,
  });
}
export function updateProductWorkOrder(data) {
  return request({
    url: "/productWorkOrder/updateProductWorkOrder",
    method: "post",
    data: data,
  });
}
export function addProductMain(data) {
  return request({
    url: "/productionProductMain/addProductMain",
    method: "post",
    data: data,
  });
}
src/views/productionManagement/productionOrder/ProcessRouteItemForm.vue
@@ -1,81 +1,65 @@
<template>
  <div>
    <el-dialog
        v-model="isShow"
        title="工艺路线项目"
        width="800px"
        @close="closeModal"
    >
    <el-dialog v-model="isShow"
               title="工艺路线项目"
               width="800px"
               @close="closeModal">
      <div class="operate-button">
        <el-button
            type="primary"
            @click="isShowProductSelectDialog = true"
            class="mb5"
            style="margin-bottom: 10px;"
        >
        <el-button type="primary"
                   @click="isShowProductSelectDialog = true"
                   class="mb5"
                   style="margin-bottom: 10px;">
          选择产品
        </el-button>
        <el-switch
            v-model="isTable"
            inline-prompt
            active-text="表格"
            inactive-text="列表"
            @change="handleViewChange"
        />
        <el-switch v-model="isTable"
                   inline-prompt
                   active-text="表格"
                   inactive-text="列表"
                   @change="handleViewChange" />
      </div>
      <el-table
          v-if="isTable"
          ref="multipleTable"
          v-loading="tableLoading"
          border
          :data="routeItems"
          :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
          row-key="id"
          tooltip-effect="dark"
          class="lims-table"
          style="cursor: move;"
      >
        <el-table-column align="center" label="序号" width="60">
      <el-table v-if="isTable"
                ref="multipleTable"
                v-loading="tableLoading"
                border
                :data="routeItems"
                :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
                row-key="id"
                tooltip-effect="dark"
                class="lims-table"
                style="cursor: move;">
        <el-table-column align="center"
                         label="序号"
                         width="60">
          <template #default="scope">
            {{ scope.$index + 1 }}
          </template>
        </el-table-column>
        <el-table-column
            v-for="(item, index) in tableColumn"
            :key="index"
            :label="item.label"
            :width="item.width"
            show-overflow-tooltip
        >
          <template #default="scope" v-if="item.dataType === 'action'">
            <el-button
                v-for="(op, opIndex) in item.operation"
                :key="opIndex"
                :type="op.type"
                :link="op.link"
                size="small"
                @click.stop="op.clickFun(scope.row)"
            >
        <el-table-column v-for="(item, index) in tableColumn"
                         :key="index"
                         :label="item.label"
                         :width="item.width"
                         show-overflow-tooltip>
          <template #default="scope"
                    v-if="item.dataType === 'action'">
            <el-button v-for="(op, opIndex) in item.operation"
                       :key="opIndex"
                       :type="op.type"
                       :link="op.link"
                       size="small"
                       @click.stop="op.clickFun(scope.row)">
              {{ op.name }}
            </el-button>
          </template>
          <template #default="scope" v-else>
          <template #default="scope"
                    v-else>
            <template v-if="item.prop === 'processId'">
              <el-select
                  v-model="scope.row[item.prop]"
                  style="width: 100%;"
                  @mousedown.stop
              >
                <el-option
                    v-for="process in processOptions"
                    :key="process.id"
                    :label="process.name"
                    :value="process.id"
                />
              <el-select v-model="scope.row[item.prop]"
                         style="width: 100%;"
                         @mousedown.stop>
                <el-option v-for="process in processOptions"
                           :key="process.id"
                           :label="process.name"
                           :value="process.id" />
              </el-select>
            </template>
            <template v-else>
@@ -84,194 +68,229 @@
          </template>
        </el-table-column>
      </el-table>
      <!-- 使用普通div替代el-steps -->
      <div
          v-else
          ref="stepsContainer"
          class="mb5 custom-steps"
          style="padding: 10px 0; display: flex; flex-wrap: nowrap; gap: 20px; align-items: flex-start;"
      >
        <div
            v-for="(item, index) in routeItems"
            :key="item.id"
            class="custom-step draggable-step"
            :data-id="item.id"
            style="cursor: move; flex: 0 0 auto; min-width: 220px;"
        >
      <div v-else
           ref="stepsContainer"
           class="mb5 custom-steps"
           style="padding: 10px 0; display: flex; flex-wrap: nowrap; gap: 20px; align-items: flex-start;">
        <div v-for="(item, index) in routeItems"
             :key="item.id"
             class="custom-step draggable-step"
             :data-id="item.id"
             style="cursor: move; flex: 0 0 auto; min-width: 220px;">
          <div class="step-content">
            <div class="step-number">{{ index + 1 }}</div>
            <el-card
                :header="item.productName"
                class="step-card"
                style="cursor: move;"
            >
            <el-card :header="item.productName"
                     class="step-card"
                     style="cursor: move;">
              <div class="step-card-content">
                <p>{{ item.model }}</p>
                <p>{{ item.unit }}</p>
                <el-select
                    v-model="item.processId"
                    style="width: 100%;"
                    @mousedown.stop
                >
                  <el-option
                      v-for="process in processOptions"
                      :key="process.id"
                      :label="process.name"
                      :value="process.id"
                  />
                <el-select v-model="item.processId"
                           style="width: 100%;"
                           @mousedown.stop>
                  <el-option v-for="process in processOptions"
                             :key="process.id"
                             :label="process.name"
                             :value="process.id" />
                </el-select>
              </div>
              <template #footer>
                <div class="step-card-footer">
                  <el-button type="danger" link size="small" @click.stop="removeItemByID(item.id)">删除</el-button>
                  <el-button type="danger"
                             link
                             size="small"
                             @click.stop="removeItemByID(item.id)">删除</el-button>
                </div>
              </template>
            </el-card>
          </div>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleSubmit">确认</el-button>
          <el-button type="primary"
                     @click="handleSubmit">确认</el-button>
          <el-button @click="closeModal">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <ProductSelectDialog
        v-model="isShowProductSelectDialog"
        @confirm="handelSelectProducts"
    />
    <ProductSelectDialog v-model="isShowProductSelectDialog"
                         @confirm="handelSelectProducts" />
  </div>
</template>
<script setup>
import { ref, computed, getCurrentInstance, onMounted, onUnmounted, nextTick } from "vue";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import { findProductProcessRouteItemList, addOrUpdateProductProcessRouteItem } from "@/api/productionManagement/productProcessRoute.js";
import { processList } from "@/api/productionManagement/productionProcess.js";
import Sortable from 'sortablejs';
  import {
    ref,
    computed,
    getCurrentInstance,
    onMounted,
    onUnmounted,
    nextTick,
  } from "vue";
  import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
  import {
    findProductProcessRouteItemList,
    addOrUpdateProductProcessRouteItem,
    deleteRouteItem,
  } from "@/api/productionManagement/productProcessRoute.js";
  import { processList } from "@/api/productionManagement/productionProcess.js";
  import Sortable from "sortablejs";
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
    default: false
  },
  record: {
    type: Object,
    required: true,
    default: () => ({})
  }
});
const emit = defineEmits(['update:visible', 'completed']);
const processOptions = ref([]);
const tableLoading = ref(false);
const isShowProductSelectDialog = ref(false);
const routeItems = ref([]);
let tableSortable = null;
let stepsSortable = null;
const multipleTable = ref(null);
const stepsContainer = ref(null);
const isTable = ref(true);
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
  }
});
const tableColumn = ref([
  { label: "产品名称", prop: "productName", width: 180 },
  { label: "规格名称", prop: "model", width: 150 },
  { label: "单位", prop: "unit", width: 80 },
  { label: "工序名称", prop: "processId", width: 180 },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 100,
    operation: [
      {
        name: "删除",
        type: "danger",
        link: true,
        clickFun: (row) => {
          const idx = routeItems.value.findIndex(item => item.id === row.id);
          if (idx > -1) {
            removeItem(idx)
          }
        }
      }
    ]
  }
]);
const removeItem = (index) => {
  routeItems.value.splice(index, 1);
  nextTick(() => initSortable());
};
const removeItemByID = (id) => {
  const idx = routeItems.value.findIndex(item => item.id === id);
  if (idx > -1) {
    routeItems.value.splice(idx, 1);
    nextTick(() => initSortable());
  }
};
const closeModal = () => {
  isShow.value = false;
};
const handelSelectProducts = (products) => {
  destroySortable();
  const newData = products.map(({ id, ...product }) => ({
    ...product,
    productModelId: id,
    routeId: props.record.id,
    id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
    processId: undefined
  }));
  console.log('选择产品前数组:', routeItems.value);
  routeItems.value.push(...newData);
  routeItems.value = [...routeItems.value];
  console.log('选择产品后数组:', routeItems.value);
  // 延迟初始化,确保DOM完全渲染
  nextTick(() => {
    // 强制重新渲染组件
    if (proxy?.$forceUpdate) {
      proxy.$forceUpdate();
    }
    const temp = [...routeItems.value];
    routeItems.value = [];
    nextTick(() => {
      routeItems.value = temp;
      initSortable();
    });
  const props = defineProps({
    visible: {
      type: Boolean,
      required: true,
      default: false,
    },
    record: {
      type: Object,
      required: true,
      default: () => ({}),
    },
  });
};
const findProcessRouteItems = () => {
  tableLoading.value = true;
  findProductProcessRouteItemList({ orderId: props.record.id })
  const emit = defineEmits(["update:visible", "completed"]);
  const processOptions = ref([]);
  const tableLoading = ref(false);
  const isShowProductSelectDialog = ref(false);
  const routeItems = ref([]);
  let tableSortable = null;
  let stepsSortable = null;
  const multipleTable = ref(null);
  const stepsContainer = ref(null);
  const isTable = ref(true);
  const isShow = computed({
    get() {
      return props.visible;
    },
    set(val) {
      emit("update:visible", val);
    },
  });
  const tableColumn = ref([
    { label: "产品名称", prop: "productName", width: 180 },
    { label: "规格名称", prop: "model", width: 150 },
    { label: "单位", prop: "unit", width: 80 },
    { label: "工序名称", prop: "processId", width: 180 },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 100,
      operation: [
        {
          name: "删除",
          type: "danger",
          link: true,
          clickFun: row => {
            console.log(row.id, "删除");
            const dragSortx = routeItems.value.findIndex(
              item => item.dragSort === row.dragSort
            );
            const idx = routeItems.value.findIndex(item => item.id === row.id);
            console.log(idx, "idx");
            if (row.id) {
              deleteRouteItemByIds({ id: row.id }, idx);
            } else {
              removeItem(dragSortx);
            }
          },
        },
      ],
    },
  ]);
  const removeItem = index => {
    console.log("软删除", index);
    routeItems.value.splice(index, 1);
    updateDragSort();
    nextTick(() => initSortable());
  };
  const removeItemByID = id => {
    const idx = routeItems.value.findIndex(item => item.id === id);
    if (idx > -1) {
      routeItems.value.splice(idx, 1);
      updateDragSort();
      nextTick(() => initSortable());
    }
  };
  const deleteRouteItemByIds = (ids, index) => {
    deleteRouteItem(ids).then(res => {
      routeItems.value.splice(index, 1);
      updateDragSort();
      nextTick(() => initSortable());
    });
  };
  const closeModal = () => {
    isShow.value = false;
  };
  const updateDragSort = () => {
    routeItems.value.forEach((item, index) => {
      item.dragSort = index + 1;
    });
    routeItems.value = [...routeItems.value];
    console.log("更新后的数组:", routeItems.value);
  };
  const handelSelectProducts = products => {
    destroySortable();
    // 计算新的dragSort值起始点
    const maxDragSort =
      routeItems.value.length > 0
        ? Math.max(...routeItems.value.map(item => item.dragSort || 0))
        : 0;
    const newData = products.map(({ id, ...product }, index) => ({
      ...product,
      productModelId: id,
      routeId: props.record.id,
      // id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
      processId: undefined,
      dragSort: maxDragSort + index + 1,
    }));
    console.log("选择产品前数组:", routeItems.value);
    routeItems.value.push(...newData);
    updateDragSort();
    console.log("选择产品后数组:", routeItems.value);
    // 延迟初始化,确保DOM完全渲染
    nextTick(() => {
      // 强制重新渲染组件
      if (proxy?.$forceUpdate) {
        proxy.$forceUpdate();
      }
      const temp = [...routeItems.value];
      routeItems.value = [];
      nextTick(() => {
        routeItems.value = temp;
        initSortable();
      });
    });
  };
  const findProcessRouteItems = () => {
    tableLoading.value = true;
    findProductProcessRouteItemList({ orderId: props.record.id })
      .then(res => {
        tableLoading.value = false;
        routeItems.value = res.data.map(item => ({
          ...item,
          processId: item.processId === 0 ? undefined : item.processId
          processId: item.processId === 0 ? undefined : item.processId,
        }));
        // 延迟初始化,确保DOM完全渲染
        nextTick(() => {
@@ -282,250 +301,255 @@
        tableLoading.value = false;
        console.error("获取列表失败:", err);
      });
};
  };
const findProcessList = () => {
  processList({})
  const findProcessList = () => {
    processList({})
      .then(res => {
        processOptions.value = res.data;
      })
      .catch(err => {
        console.error("获取工序失败:", err);
      });
};
  };
const { proxy } = getCurrentInstance() || {};
  const { proxy } = getCurrentInstance() || {};
const handleSubmit = () => {
  const hasEmptyProcess = routeItems.value.some(item => !item.processId);
  if (hasEmptyProcess) {
    proxy?.$modal?.msgError("请为所有项目选择工序");
    return;
  }
  const handleSubmit = () => {
    const hasEmptyProcess = routeItems.value.some(item => !item.processId);
    if (hasEmptyProcess) {
      proxy?.$modal?.msgError("请为所有项目选择工序");
      return;
    }
  addOrUpdateProductProcessRouteItem({
    routeId: props.record.id,
    processRouteItem: routeItems.value.map(({ id, ...item }) => item)
  })
    addOrUpdateProductProcessRouteItem({
      routeId: props.record.id,
      processRouteItem: routeItems.value,
    })
      .then(res => {
        isShow.value = false;
        emit('completed');
        emit("completed");
        proxy?.$modal?.msgSuccess("提交成功");
      })
      .catch(err => {
        proxy?.$modal?.msgError(`提交失败:${err.msg || "网络异常"}`);
      });
};
  };
const destroySortable = () => {
  if (tableSortable) {
    tableSortable.destroy();
    tableSortable = null;
  }
  if (stepsSortable) {
    stepsSortable.destroy();
    stepsSortable = null;
  }
};
const initSortable = () => {
  destroySortable();
  if (isTable.value) {
    if (!multipleTable.value) return;
    const tbody = multipleTable.value.$el.querySelector('.el-table__body tbody') ||
        multipleTable.value.$el.querySelector('.el-table__body-wrapper > table > tbody');
    if (!tbody) return;
    tableSortable = new Sortable(tbody, {
      animation: 150,
      ghostClass: 'sortable-ghost',
      handle: '.el-table__row',
      filter: '.el-button, .el-select',
      onEnd: (evt) => {
        if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex]) return;
        // 使用数组 splice 方法重新排序,与表格模式保持一致
        const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0];
        routeItems.value.splice(evt.newIndex, 0, moveItem);
        routeItems.value = [...routeItems.value];
        console.log('排序后数组:', routeItems.value);
      }
    });
  } else {
    if (!stepsContainer.value) return;
    // 修改:直接使用stepsContainer.value作为拖拽容器
    const stepsList = stepsContainer.value;
    if (!stepsList) {
      console.warn('未找到步骤条拖拽容器');
      return;
  const destroySortable = () => {
    if (tableSortable) {
      tableSortable.destroy();
      tableSortable = null;
    }
    if (stepsSortable) {
      stepsSortable.destroy();
      stepsSortable = null;
    }
  };
    // 修改:简化拖拽配置
    stepsSortable = new Sortable(stepsList, {
      animation: 150,
      ghostClass: 'sortable-ghost',
      draggable: '.draggable-step', // 可拖拽元素
      handle: '.draggable-step, .step-card', // 拖拽手柄
      filter: '.el-button, .el-select, .el-input', // 过滤按钮/选择器
      forceFallback: true,
      fallbackClass: 'sortable-fallback',
      preventOnFilter: true,
      scroll: true,
      scrollSensitivity: 30,
      scrollSpeed: 10,
      bubbleScroll: true,
      onEnd: (evt) => {
        if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex]) return;
  const initSortable = () => {
    destroySortable();
        // 使用数组 splice 方法重新排序
        const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0];
        routeItems.value.splice(evt.newIndex, 0, moveItem);
        routeItems.value = [...routeItems.value];
    if (isTable.value) {
      if (!multipleTable.value) return;
      const tbody =
        multipleTable.value.$el.querySelector(".el-table__body tbody") ||
        multipleTable.value.$el.querySelector(
          ".el-table__body-wrapper > table > tbody"
        );
      if (!tbody) return;
      tableSortable = new Sortable(tbody, {
        animation: 150,
        ghostClass: "sortable-ghost",
        handle: ".el-table__row",
        filter: ".el-button, .el-select",
        onEnd: evt => {
          if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex])
            return;
          // 使用数组 splice 方法重新排序,与表格模式保持一致
          const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0];
          routeItems.value.splice(evt.newIndex, 0, moveItem);
          updateDragSort();
          console.log("排序后数组:", routeItems.value);
        },
      });
    } else {
      if (!stepsContainer.value) return;
      // 修改:直接使用stepsContainer.value作为拖拽容器
      const stepsList = stepsContainer.value;
      if (!stepsList) {
        console.warn("未找到步骤条拖拽容器");
        return;
      }
      // 修改:简化拖拽配置
      stepsSortable = new Sortable(stepsList, {
        animation: 150,
        ghostClass: "sortable-ghost",
        draggable: ".draggable-step", // 可拖拽元素
        handle: ".draggable-step, .step-card", // 拖拽手柄
        filter: ".el-button, .el-select, .el-input", // 过滤按钮/选择器
        forceFallback: true,
        fallbackClass: "sortable-fallback",
        preventOnFilter: true,
        scroll: true,
        scrollSensitivity: 30,
        scrollSpeed: 10,
        bubbleScroll: true,
        onEnd: evt => {
          if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex])
            return;
          // 使用数组 splice 方法重新排序
          const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0];
          routeItems.value.splice(evt.newIndex, 0, moveItem);
          updateDragSort();
        },
      });
      // 调试:打印容器和实例,确认绑定成功
      console.log("步骤条拖拽容器:", stepsList);
      console.log("Sortable实例:", stepsSortable);
    }
  };
  const handleViewChange = () => {
    destroySortable();
    // 延迟初始化,确保视图切换后DOM完全渲染
    nextTick(() => {
      setTimeout(() => initSortable(), 100);
    });
  };
    // 调试:打印容器和实例,确认绑定成功
    console.log('步骤条拖拽容器:', stepsList);
    console.log('Sortable实例:', stepsSortable);
  }
};
const handleViewChange = () => {
  destroySortable();
  // 延迟初始化,确保视图切换后DOM完全渲染
  nextTick(() => {
    setTimeout(() => initSortable(), 100);
  onMounted(() => {
    findProcessRouteItems();
    findProcessList();
  });
};
onMounted(() => {
  findProcessRouteItems();
  findProcessList();
});
  onUnmounted(() => {
    destroySortable();
  });
onUnmounted(() => {
  destroySortable();
});
defineExpose({
  closeModal,
  handleSubmit,
  isShow
});
  defineExpose({
    closeModal,
    handleSubmit,
    isShow,
  });
</script>
<style scoped>
:deep(.sortable-ghost) {
  opacity: 0.6;
  background-color: #f5f7fa !important;
}
  :deep(.sortable-ghost) {
    opacity: 0.6;
    background-color: #f5f7fa !important;
  }
:deep(.el-table__row) {
  transition: background-color 0.2s;
}
  :deep(.el-table__row) {
    transition: background-color 0.2s;
  }
:deep(.el-table__row:hover) {
  background-color: #f9fafc !important;
}
  :deep(.el-table__row:hover) {
    background-color: #f9fafc !important;
  }
:deep(.el-card__footer){
  padding: 0 !important;
}
  :deep(.el-card__footer) {
    padding: 0 !important;
  }
.operate-button {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
  .operate-button {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
/* 修改:自定义步骤条容器样式 */
.custom-steps {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 20px;
  min-height: 100px;
}
  /* 修改:自定义步骤条容器样式 */
  .custom-steps {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 20px;
    min-height: 100px;
  }
/* 修改:自定义步骤项样式 */
.custom-step {
  cursor: move !important;
  padding: 8px;
  position: relative;
  transition: all 0.2s ease;
  flex: 0 0 auto;
  min-width: 220px;
  touch-action: none;
}
  /* 修改:自定义步骤项样式 */
  .custom-step {
    cursor: move !important;
    padding: 8px;
    position: relative;
    transition: all 0.2s ease;
    flex: 0 0 auto;
    min-width: 220px;
    touch-action: none;
  }
/* 拖拽悬浮样式,提示可拖拽 */
.custom-step:hover {
  background-color: rgba(64, 158, 255, 0.05);
  transform: translateY(-2px);
}
  /* 拖拽悬浮样式,提示可拖拽 */
  .custom-step:hover {
    background-color: rgba(64, 158, 255, 0.05);
    transform: translateY(-2px);
  }
.sortable-ghost {
  opacity: 0.4;
  background-color: #f5f7fa !important;
  border: 2px dashed #409eff;
  margin: 10px;
  transform: scale(1.02);
}
  .sortable-ghost {
    opacity: 0.4;
    background-color: #f5f7fa !important;
    border: 2px dashed #409eff;
    margin: 10px;
    transform: scale(1.02);
  }
.sortable-fallback {
  opacity: 0.9;
  background-color: #f5f7fa;
  border: 1px solid #409eff;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  transform: rotate(2deg);
  margin: 10px;
}
  .sortable-fallback {
    opacity: 0.9;
    background-color: #f5f7fa;
    border: 1px solid #409eff;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    transform: rotate(2deg);
    margin: 10px;
  }
.step-card {
  cursor: move !important;
  transition: box-shadow 0.2s ease;
  user-select: none;
  -webkit-user-select: none;
  pointer-events: auto;
  margin: 10px;
  height: 240px;
}
  .step-card {
    cursor: move !important;
    transition: box-shadow 0.2s ease;
    user-select: none;
    -webkit-user-select: none;
    pointer-events: auto;
    margin: 10px;
    height: 240px;
  }
.step-card:hover {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
  .step-card:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  }
.step-content {
  width: 220px;
  user-select: none;
}
  .step-content {
    width: 220px;
    user-select: none;
  }
.step-card-content {
  display: flex;
  flex-direction: column;
  align-items: center;
}
  .step-card-content {
    display: flex;
    flex-direction: column;
    align-items: center;
  }
.step-card-footer {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  padding: 10px;
}
  .step-card-footer {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    padding: 10px;
  }
/* 自定义序号样式优化 */
.step-number {
  font-weight: bold;
  text-align: center;
  width: 36px;
  height: 36px;
  line-height: 36px;
  margin: 0 auto 10px;
  background: #409eff;
  color: #fff;
  border-radius: 50%;
  font-size: 14px;
}
  /* 自定义序号样式优化 */
  .step-number {
    font-weight: bold;
    text-align: center;
    width: 36px;
    height: 36px;
    line-height: 36px;
    margin: 0 auto 10px;
    background: #409eff;
    color: #fff;
    border-radius: 50%;
    font-size: 14px;
  }
</style>
src/views/productionManagement/productionOrder/index.vue
@@ -1,198 +1,211 @@
<template>
    <div class="app-container">
        <div class="search_form">
      <el-form :model="searchForm" :inline="true">
  <div class="app-container">
    <div class="search_form">
      <el-form :model="searchForm"
               :inline="true">
        <el-form-item label="客户名称:">
          <el-input v-model="searchForm.customerName" placeholder="请输入" clearable prefix-icon="Search"
          <el-input v-model="searchForm.customerName"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 200px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="合同号:">
          <el-input v-model="searchForm.salesContractNo" placeholder="请输入" clearable prefix-icon="Search"
          <el-input v-model="searchForm.salesContractNo"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 200px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="项目名称:">
          <el-input v-model="searchForm.projectName" placeholder="请输入" clearable prefix-icon="Search"
          <el-input v-model="searchForm.projectName"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 200px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="产品名称:">
          <el-input v-model="searchForm.productCategory" placeholder="请输入" clearable prefix-icon="Search"
          <el-input v-model="searchForm.productCategory"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 200px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="规格:">
          <el-input v-model="searchForm.specificationModel" placeholder="请输入" clearable prefix-icon="Search"
          <el-input v-model="searchForm.specificationModel"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 200px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleQuery">搜索</el-button>
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
        </el-form-item>
      </el-form>
            <div>
                <el-button @click="handleOut">导出</el-button>
            </div>
        </div>
        <div class="table_list">
            <PIMTable
                rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :tableLoading="tableLoading"
                @pagination="pagination"
            ></PIMTable>
        </div>
    <process-route-item-form
      v-if="isShowItemModal"
      v-model:visible="isShowItemModal"
      :record="record"
      @completed="getList"
    />
    </div>
      <div>
        <el-button @click="handleOut">导出</el-button>
      </div>
    </div>
    <div class="table_list">
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :tableLoading="tableLoading"
                @pagination="pagination"></PIMTable>
    </div>
    <process-route-item-form v-if="isShowItemModal"
                             v-model:visible="isShowItemModal"
                             :record="record"
                             @completed="getList" />
  </div>
</template>
<script setup>
import {onMounted, ref} from "vue";
import { ElMessageBox } from "element-plus";
import dayjs from "dayjs";
import {productOrderListPage} from "@/api/productionManagement/productionOrder.js";
const { proxy } = getCurrentInstance();
import ProcessRouteItemForm from "@/views/productionManagement/productionOrder/ProcessRouteItemForm.vue";
  import { onMounted, ref } from "vue";
  import { ElMessageBox } from "element-plus";
  import dayjs from "dayjs";
  import { productOrderListPage } from "@/api/productionManagement/productionOrder.js";
  const { proxy } = getCurrentInstance();
  import ProcessRouteItemForm from "@/views/productionManagement/productionOrder/ProcessRouteItemForm.vue";
const tableColumn = ref([
    {
        label: "生产订单号",
        prop: "npsNo",
        width: 120,
    },
    {
        label: "销售合同号",
        prop: "salesContractNo",
        width: 220,
    },
  {
    label: "项目名称",
    prop: "projectName",
    width:300
  },
    {
        label: "客户名称",
        prop: "customerName",
        width: 250,
    },
  {
    label: "产品名称",
    prop: "productCategory",
    width: 250,
  },
  {
    label: "规格",
    prop: "specificationModel",
    width: 250,
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 200,
    operation: [
      {
        name: "工艺路线",
        type: "text",
        clickFun: (row) => {
          showRouteItemModal(row);
        }
      }
    ]
  }
]);
const tableData = ref([]);
const tableLoading = ref(false);
const page = reactive({
    current: 1,
    size: 100,
    total: 0,
});
  const tableColumn = ref([
    {
      label: "生产订单号",
      prop: "npsNo",
      width: 120,
    },
    {
      label: "销售合同号",
      prop: "salesContractNo",
      width: 220,
    },
    {
      label: "项目名称",
      prop: "projectName",
      width: 300,
    },
    {
      label: "客户名称",
      prop: "customerName",
      width: 250,
    },
    {
      label: "产品名称",
      prop: "productCategory",
      width: 250,
    },
    {
      label: "规格",
      prop: "specificationModel",
      width: 250,
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 200,
      operation: [
        {
          name: "工艺路线",
          type: "text",
          clickFun: row => {
            showRouteItemModal(row);
          },
        },
      ],
    },
  ]);
  const tableData = ref([]);
  const tableLoading = ref(false);
  const page = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
const data = reactive({
    searchForm: {
        customerName: "",
        salesContractNo: "",
        projectName: "",
    productCategory: "",
    specificationModel: "",
    },
});
const { searchForm } = toRefs(data);
  const data = reactive({
    searchForm: {
      customerName: "",
      salesContractNo: "",
      projectName: "",
      productCategory: "",
      specificationModel: "",
    },
  });
  const { searchForm } = toRefs(data);
// 查询列表
/** 搜索按钮操作 */
const handleQuery = () => {
    page.current = 1;
    getList();
};
const pagination = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
};
const changeDaterange = (value) => {
    if (value) {
        searchForm.value.entryDateStart = value[0];
        searchForm.value.entryDateEnd = value[1];
    } else {
        searchForm.value.entryDateStart = undefined;
        searchForm.value.entryDateEnd = undefined;
    }
    handleQuery();
};
const getList = () => {
    tableLoading.value = true;
    // 构造一个新的对象,不包含entryDate字段
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined
    productOrderListPage(params).then((res) => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        page.total = res.data.total;
    }).catch(() => {
        tableLoading.value = false;
    })
};
  // 查询列表
  /** 搜索按钮操作 */
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  };
  const changeDaterange = value => {
    if (value) {
      searchForm.value.entryDateStart = value[0];
      searchForm.value.entryDateEnd = value[1];
    } else {
      searchForm.value.entryDateStart = undefined;
      searchForm.value.entryDateEnd = undefined;
    }
    handleQuery();
  };
  const getList = () => {
    tableLoading.value = true;
    // 构造一个新的对象,不包含entryDate字段
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined;
    productOrderListPage(params)
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        page.total = res.data.total;
      })
      .catch(() => {
        tableLoading.value = false;
      });
  };
const isShowItemModal = ref(false);
const record = ref({});
const showRouteItemModal = (row) => {
  isShowItemModal.value = true
  record.value = row
};
  const isShowItemModal = ref(false);
  const record = ref({});
  const showRouteItemModal = row => {
    isShowItemModal.value = true;
    record.value = row;
  };
// 导出
const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            proxy.download("/salesLedger/scheduling/export", {}, "生产订单.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
  // 导出
  const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        proxy.download("/salesLedger/scheduling/export", {}, "生产订单.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
const handleConfirmRoute = () => {
}
  const handleConfirmRoute = () => {};
onMounted(() => {
    getList();
});
  onMounted(() => {
    getList();
  });
</script>
<style scoped lang="scss"></style>
src/views/productionManagement/productionReporting/index.vue
@@ -1,426 +1,438 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <el-form :model="searchForm" :inline="true">
                <el-form-item label="报工人员名称:">
                    <el-input v-model="searchForm.nickName" placeholder="请输入" clearable prefix-icon="Search"
                                        style="width: 200px;"
                                        @change="handleQuery" />
                </el-form-item>
                <el-form-item label="工单号:">
                    <el-input v-model="searchForm.workOrderNo" placeholder="请输入" clearable prefix-icon="Search"
                                        style="width: 200px;"
                                        @change="handleQuery" />
                </el-form-item>
                <el-form-item label="工单状态:">
                    <el-select v-model="searchForm.workOrderStatus" placeholder="请选择工单状态" style="width: 140px" clearable>
                        <el-option label="待确认" :value="1"></el-option>
            <el-option label="待生产" :value="2"></el-option>
            <el-option label="生产中" :value="3"></el-option>
            <el-option label="已生产" :value="4"></el-option>
                    </el-select>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="handleQuery">搜索</el-button>
                </el-form-item>
            </el-form>
        </div>
        <div class="table_list">
            <div style="text-align: right" class="mb10">
                <el-button type="primary" @click="openForm('add')">生产报工</el-button>
                <el-button @click="handleOut">导出</el-button>
            </div>
            <PIMTable
                rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                :expandRowKeys="expandedRowKeys"
                @expand-change="expandChange"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
                :total="page.total"
            >
                <template #expand="{ row }">
                    <el-table
                        :data="expandData"
                        border
                        show-summary
                        :summary-method="summarizeMainTable"
                        v-loading="childrenLoading"
                    >
                        <el-table-column
                            align="center"
                            label="序号"
                            type="index"
                            width="60"
                        />
                        <el-table-column label="本次生产数量" prop="finishedNum" align="center" width="400">
                            <template #default="scope">
                                <el-input-number :step="0.01" :min="0" style="width: 100%"
                                                                 v-model="scope.row.finishedNum"
                                                                 :disabled="!scope.row.editType"
                                                                 :precision="2"
                                                                 placeholder="请输入"
                                                                 clearable
                                                                 @change="changeNum(scope.row)"
                                />
                            </template>
                        </el-table-column>
<!--                        <el-table-column label="待生产数量" prop="pendingNum" width="240" align="center"></el-table-column>-->
                        <el-table-column label="生产人" prop="schedulingUserId" width="400">
                            <template #default="scope">
                                <el-select
                                    v-model="scope.row.schedulingUserId"
                                    placeholder="选择人员"
                                    :disabled="!scope.row.editType"
                                    style="width: 100%;"
                                >
                                    <el-option
                                        v-for="user in userList"
                                        :key="user.userId"
                                        :label="user.nickName"
                                        :value="user.userId"
                                    />
                                </el-select>
                            </template>
                        </el-table-column>
                        <el-table-column label="生产日期" prop="schedulingDate" width="400">
                            <template #default="scope">
                                <el-date-picker
                                    v-model="scope.row.schedulingDate"
                                    type="date"
                                    :disabled="!scope.row.editType"
                                    placeholder="请选择日期"
                                    value-format="YYYY-MM-DD"
                                    format="YYYY-MM-DD"
                                    clearable
                                    style="width: 100%"
                                />
                            </template>
                        </el-table-column>
                        <el-table-column label="操作" width="60">
                            <template #default="scope">
                                <el-button
                                    link
                                    type="primary"
                                    size="small"
                                    @click="changeEditType(scope.row)"
                                    v-if="!scope.row.editType"
                                    :disabled="scope.row.parentStatus === 3"
                                >编辑</el-button
                                >
                                <el-button
                                    link
                                    type="primary"
                                    size="small"
                                    @click="saveReceiptPayment(scope.row)"
                                    v-if="scope.row.editType"
                                >保存</el-button
                                >
                            </template>
                        </el-table-column>
                    </el-table>
                </template>
            </PIMTable>
        </div>
        <form-dia ref="formDia" @close="handleQuery"></form-dia>
    <input-modal
        v-if="isShowInput"
        v-model:visible="isShowInput"
    />
    <output-modal
        v-if="isShowOutput"
        v-model:visible="isShowOutput"
    />
    </div>
  <div class="app-container">
    <div class="search_form">
      <el-form :model="searchForm"
               :inline="true">
        <el-form-item label="报工人员名称:">
          <el-input v-model="searchForm.nickName"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 200px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="工单号:">
          <el-input v-model="searchForm.workOrderNo"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 200px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="工单状态:">
          <el-select v-model="searchForm.workOrderStatus"
                     placeholder="请选择工单状态"
                     style="width: 140px"
                     clearable>
            <el-option label="待确认"
                       :value="1"></el-option>
            <el-option label="待生产"
                       :value="2"></el-option>
            <el-option label="生产中"
                       :value="3"></el-option>
            <el-option label="已生产"
                       :value="4"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
        </el-form-item>
      </el-form>
    </div>
    <div class="table_list">
      <div style="text-align: right"
           class="mb10">
        <el-button type="primary"
                   @click="openForm('add')">生产报工</el-button>
        <el-button @click="handleOut">导出</el-button>
      </div>
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                :expandRowKeys="expandedRowKeys"
                @expand-change="expandChange"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
                :total="page.total">
        <template #expand="{ row }">
          <el-table :data="expandData"
                    border
                    show-summary
                    :summary-method="summarizeMainTable"
                    v-loading="childrenLoading">
            <el-table-column align="center"
                             label="序号"
                             type="index"
                             width="60" />
            <el-table-column label="本次生产数量"
                             prop="finishedNum"
                             align="center"
                             width="400">
              <template #default="scope">
                <el-input-number :step="0.01"
                                 :min="0"
                                 style="width: 100%"
                                 v-model="scope.row.finishedNum"
                                 :disabled="!scope.row.editType"
                                 :precision="2"
                                 placeholder="请输入"
                                 clearable
                                 @change="changeNum(scope.row)" />
              </template>
            </el-table-column>
            <!--                        <el-table-column label="待生产数量" prop="pendingNum" width="240" align="center"></el-table-column>-->
            <el-table-column label="生产人"
                             prop="schedulingUserId"
                             width="400">
              <template #default="scope">
                <el-select v-model="scope.row.schedulingUserId"
                           placeholder="选择人员"
                           :disabled="!scope.row.editType"
                           style="width: 100%;">
                  <el-option v-for="user in userList"
                             :key="user.userId"
                             :label="user.nickName"
                             :value="user.userId" />
                </el-select>
              </template>
            </el-table-column>
            <el-table-column label="生产日期"
                             prop="schedulingDate"
                             width="400">
              <template #default="scope">
                <el-date-picker v-model="scope.row.schedulingDate"
                                type="date"
                                :disabled="!scope.row.editType"
                                placeholder="请选择日期"
                                value-format="YYYY-MM-DD"
                                format="YYYY-MM-DD"
                                clearable
                                style="width: 100%" />
              </template>
            </el-table-column>
            <el-table-column label="操作"
                             width="60">
              <template #default="scope">
                <el-button link
                           type="primary"
                           size="small"
                           @click="changeEditType(scope.row)"
                           v-if="!scope.row.editType"
                           :disabled="scope.row.parentStatus === 3">编辑</el-button>
                <el-button link
                           type="primary"
                           size="small"
                           @click="saveReceiptPayment(scope.row)"
                           v-if="scope.row.editType">保存</el-button>
              </template>
            </el-table-column>
          </el-table>
        </template>
      </PIMTable>
    </div>
    <form-dia ref="formDia"
              @close="handleQuery"></form-dia>
    <input-modal v-if="isShowInput"
                 v-model:visible="isShowInput" />
    <output-modal v-if="isShowOutput"
                  v-model:visible="isShowOutput" />
  </div>
</template>
<script setup>
import {onMounted, ref} from "vue";
import FormDia from "@/views/productionManagement/productionReporting/components/formDia.vue";
import {ElMessageBox} from "element-plus";
import {
    productionReportUpdate,
    workListPageById
} from "@/api/productionManagement/productionReporting.js";
import {
  productionProductMainListPage,
} from "@/api/productionManagement/production_product_main.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import InputModal from "@/views/productionManagement/productionReporting/Input.vue";
import OutputModal from "@/views/productionManagement/productionReporting/Output.vue";
  import { onMounted, ref } from "vue";
  import FormDia from "@/views/productionManagement/productionReporting/components/formDia.vue";
  import { ElMessageBox } from "element-plus";
  import {
    productionReportUpdate,
    workListPageById,
  } from "@/api/productionManagement/productionReporting.js";
  import { productionProductMainListPage } from "@/api/productionManagement/production_product_main.js";
  import { userListNoPageByTenantId } from "@/api/system/user.js";
  import InputModal from "@/views/productionManagement/productionReporting/Input.vue";
  import OutputModal from "@/views/productionManagement/productionReporting/Output.vue";
const data = reactive({
    searchForm: {
    nickName: "",
    workOrderNo: "",
    workOrderStatus: "",
    },
});
const { searchForm } = toRefs(data);
const expandedRowKeys = ref([]);
const expandData = ref([]);
const userList = ref([])
const tableColumn = ref([
  {
    label: "报工单号",
    prop: "productNo",
    width: 120,
  },
  {
    label: "报工人员",
    prop: "nickName",
    width: 120,
  },
  {
    label: "工单编号",
    prop: "workOrderNo",
    width: 120,
  },
    {
        label: "报工状态",
        prop: "status",
        dataType: "tag",
        formatData: (params) => {
            if (params == 3) {
                return "已报工";
            } else if (params == 1) {
                return "待生产";
            } else {
                return '生产中';
            }
        },
        formatType: (params) => {
            if (params == 3) {
                return "success";
            } else if (params == 1) {
                return "primary";
            } else {
                return 'warning';
            }
        },
    },
  {
    label: "工单状态",
    prop: "workOrderStatus",
    dataType: "tag",
    formatData: (params) => {
      switch (params) {
        case "1":
          return "待确认";
        case "2":
  const data = reactive({
    searchForm: {
      nickName: "",
      workOrderNo: "",
      workOrderStatus: "",
    },
  });
  const { searchForm } = toRefs(data);
  const expandedRowKeys = ref([]);
  const expandData = ref([]);
  const userList = ref([]);
  const tableColumn = ref([
    {
      label: "报工单号",
      prop: "productNo",
      width: 120,
    },
    {
      label: "报工人员",
      prop: "nickName",
      width: 120,
    },
    {
      label: "工单编号",
      prop: "workOrderNo",
      width: 120,
    },
    {
      label: "报工状态",
      prop: "status",
      dataType: "tag",
      formatData: params => {
        if (params == 3) {
          return "已报工";
        } else if (params == 1) {
          return "待生产";
        case "3":
        } else {
          return "生产中";
        case "4":
          return "已生产";
        default:
          return "";
      }
    },
    formatType: (params) => {
      switch (params) {
        case "1":
          return "primary";
        case "2":
          return "info";
        case "3":
          return "warning";
        case "4":
        }
      },
      formatType: params => {
        if (params == 3) {
          return "success";
        default:
          return "";
      }
        } else if (params == 1) {
          return "primary";
        } else {
          return "warning";
        }
      },
    },
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 200,
    operation: [
      {
        name: "查看投入",
        type: "text",
        clickFun: (row) => {
          showInput(row)
    {
      label: "工单状态",
      prop: "workOrderStatus",
      dataType: "tag",
      formatData: params => {
        switch (params) {
          case "1":
            return "待确认";
          case "2":
            return "待生产";
          case "3":
            return "生产中";
          case "4":
            return "已生产";
          default:
            return "";
        }
      },
      {
        name: "查看产出",
        type: "text",
        clickFun: (row) => {
          showOutput(row)
      formatType: params => {
        switch (params) {
          case "1":
            return "primary";
          case "2":
            return "info";
          case "3":
            return "warning";
          case "4":
            return "success";
          default:
            return "";
        }
      },
    ]
  }
]);
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const childrenLoading = ref(false);
const page = reactive({
    current: 1,
    size: 100,
    total: 0,
});
const formDia = ref()
const { proxy } = getCurrentInstance()
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 200,
      operation: [
        {
          name: "查看投入",
          type: "text",
          clickFun: row => {
            showInput(row);
          },
        },
        {
          name: "查看产出",
          type: "text",
          clickFun: row => {
            showOutput(row);
          },
        },
      ],
    },
  ]);
  const tableData = ref([]);
  const selectedRows = ref([]);
  const tableLoading = ref(false);
  const childrenLoading = ref(false);
  const page = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
  const formDia = ref();
  const { proxy } = getCurrentInstance();
// 查询列表
/** 搜索按钮操作 */
const handleQuery = () => {
    page.current = 1;
    getList();
};
const changeDaterange = (value) => {
    if (value) {
        searchForm.value.entryDateStart = value[0];
        searchForm.value.entryDateEnd = value[1];
    } else {
        searchForm.value.entryDateStart = undefined;
        searchForm.value.entryDateEnd = undefined;
    }
    handleQuery();
};
const pagination = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
};
const getList = () => {
    tableLoading.value = true;
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined
    expandedRowKeys.value = []
    productionProductMainListPage(params).then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records.map(item => ({
            ...item,
            pendingFinishNum: (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0)
        }));
        page.total = res.data.total;
    }).catch(err => {
        tableLoading.value = false;
    })
};
// 展开行
const expandChange = (row, expandedRows) => {
    userListNoPageByTenantId().then((res) => {
        userList.value = res.data;
    });
    if (expandedRows.length > 0) {
        nextTick(() => {
            expandedRowKeys.value = [];
            try {
                childrenLoading.value = true;
                workListPageById({ id: row.id }).then((res) => {
                    childrenLoading.value = false;
                    const index = tableData.value.findIndex((item) => item.id === row.id);
                    if (index > -1) {
                        expandData.value = res.data.map(item => ({
                            ...item,
                            pendingNum: (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0),
                            parentStatus: row.status // 新增父表状态
                        }));
                    }
                    expandedRowKeys.value.push(row.id);
                });
            } catch (error) {
                childrenLoading.value = false;
                console.log(error);
            }
        })
    } else {
        expandedRowKeys.value = [];
    }
};
const changeNum = (row) => {
    // 找到父表格数据
    const parentRow = tableData.value.find(item => item.id === expandedRowKeys.value[0]);
    // 计算所有子表格 finishedNum 的总和
    const totalFinishedNum = expandData.value.reduce((sum, item) => sum + (Number(item.finishedNum) || 0), 0);
    // 父表格的排产数量
    const schedulingNum = parentRow ? Number(parentRow.schedulingNum) : 0;
    if (totalFinishedNum > schedulingNum) {
        // 回退本次输入
        row.finishedNum = schedulingNum - (totalFinishedNum - Number(row.finishedNum));
        proxy.$modal.msgWarning('所有本次生产数量之和不可大于排产数量');
    }
    row.pendingNum = row.schedulingNum - row.finishedNum;
}
// 编辑修改状态
const changeEditType = (row) => {
    row.editType = !row.editType;
};
// 保存记录
const saveReceiptPayment = (row) => {
    productionReportUpdate(row).then((res) => {
        row.editType = !row.editType;
        getList();
        proxy.$modal.msgSuccess("提交成功");
    });
};
// 表格选择数据
const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
};
const summarizeMainTable = (param) => {
    return proxy.summarizeTable(param, [
        "finishedNum"
    ]);
};
// 打开弹框
const openForm = (type, row) => {
    if (selectedRows.value.length !== 1) {
        proxy.$message.error("请选择一条数据");
        return;
    }
    if (selectedRows.value[0].pendingFinishNum == 0) {
        proxy.$message.warning("无需再报工");
        return;
    }
    nextTick(() => {
        const rowInfo = type === 'add' ? selectedRows.value[0] : row
        formDia.value?.openDialog(type, rowInfo)
    })
};
  // 查询列表
  /** 搜索按钮操作 */
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
  const changeDaterange = value => {
    if (value) {
      searchForm.value.entryDateStart = value[0];
      searchForm.value.entryDateEnd = value[1];
    } else {
      searchForm.value.entryDateStart = undefined;
      searchForm.value.entryDateEnd = undefined;
    }
    handleQuery();
  };
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  };
  const getList = () => {
    tableLoading.value = true;
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined;
    expandedRowKeys.value = [];
    productionProductMainListPage(params)
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records.map(item => ({
          ...item,
          pendingFinishNum:
            (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0),
        }));
        page.total = res.data.total;
      })
      .catch(err => {
        tableLoading.value = false;
      });
  };
  // 展开行
  const expandChange = (row, expandedRows) => {
    userListNoPageByTenantId().then(res => {
      userList.value = res.data;
    });
    if (expandedRows.length > 0) {
      nextTick(() => {
        expandedRowKeys.value = [];
        try {
          childrenLoading.value = true;
          workListPageById({ id: row.id }).then(res => {
            childrenLoading.value = false;
            const index = tableData.value.findIndex(item => item.id === row.id);
            if (index > -1) {
              expandData.value = res.data.map(item => ({
                ...item,
                pendingNum:
                  (Number(item.schedulingNum) || 0) -
                  (Number(item.finishedNum) || 0),
                parentStatus: row.status, // 新增父表状态
              }));
            }
            expandedRowKeys.value.push(row.id);
          });
        } catch (error) {
          childrenLoading.value = false;
          console.log(error);
        }
      });
    } else {
      expandedRowKeys.value = [];
    }
  };
  const changeNum = row => {
    // 找到父表格数据
    const parentRow = tableData.value.find(
      item => item.id === expandedRowKeys.value[0]
    );
    // 计算所有子表格 finishedNum 的总和
    const totalFinishedNum = expandData.value.reduce(
      (sum, item) => sum + (Number(item.finishedNum) || 0),
      0
    );
    // 父表格的排产数量
    const schedulingNum = parentRow ? Number(parentRow.schedulingNum) : 0;
// 打开投入模态框
const isShowInput = ref(false);
const showInput = (row) => {
    isShowInput.value = true;
}
    if (totalFinishedNum > schedulingNum) {
      // 回退本次输入
      row.finishedNum =
        schedulingNum - (totalFinishedNum - Number(row.finishedNum));
      proxy.$modal.msgWarning("所有本次生产数量之和不可大于排产数量");
    }
    row.pendingNum = row.schedulingNum - row.finishedNum;
  };
  // 编辑修改状态
  const changeEditType = row => {
    row.editType = !row.editType;
  };
  // 保存记录
  const saveReceiptPayment = row => {
    productionReportUpdate(row).then(res => {
      row.editType = !row.editType;
      getList();
      proxy.$modal.msgSuccess("提交成功");
    });
  };
  // 表格选择数据
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
  };
  const summarizeMainTable = param => {
    return proxy.summarizeTable(param, ["finishedNum"]);
  };
  // 打开弹框
  const openForm = (type, row) => {
    if (selectedRows.value.length !== 1) {
      proxy.$message.error("请选择一条数据");
      return;
    }
    if (selectedRows.value[0].pendingFinishNum == 0) {
      proxy.$message.warning("无需再报工");
      return;
    }
    nextTick(() => {
      const rowInfo = type === "add" ? selectedRows.value[0] : row;
      formDia.value?.openDialog(type, rowInfo);
    });
  };
// 打开产出模态框
const isShowOutput = ref(false);
const showOutput = (row) => {
    isShowOutput.value = true;
}
  // 打开投入模态框
  const isShowInput = ref(false);
  const showInput = row => {
    isShowInput.value = true;
  };
// 导出
const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            proxy.download("/salesLedger/work/export", {}, "生产报工.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
    getList();
});
  // 打开产出模态框
  const isShowOutput = ref(false);
  const showOutput = row => {
    isShowOutput.value = true;
  };
  // 导出
  const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        proxy.download("/salesLedger/work/export", {}, "生产报工.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
  onMounted(() => {
    getList();
  });
</script>
<style scoped></style>
src/views/productionManagement/workOrder/index.vue
@@ -1,19 +1,20 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title">工单编号:</span>
        <el-input v-model="searchForm.workOrderNo"
                  style="width: 240px"
                  placeholder="请输入"
                  @change="handleQuery"
                  clearable
                  prefix-icon="Search" />
        <el-button type="primary"
                   @click="handleQuery"
                   style="margin-left: 10px">搜索</el-button>
      </div>
      <div>
      <div class="search-row">
        <div class="search-item">
          <span class="search_title">工单编号:</span>
          <el-input v-model="searchForm.workOrderNo"
                    style="width: 240px"
                    placeholder="请输入"
                    @change="handleQuery"
                    clearable
                    prefix-icon="Search" />
        </div>
        <div class="search-item">
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
        </div>
      </div>
    </div>
    <div class="table_list">
@@ -37,6 +38,171 @@
        </div>
      </div>
    </el-dialog>
    <el-dialog v-model="editDialogVisible"
               title="编辑时间"
               width="500px">
      <el-form :model="editrow"
               label-width="120px">
        <el-form-item label="计划开始时间">
          <el-date-picker v-model="editrow.planStartTime"
                          type="date"
                          placeholder="请选择"
                          value-format="YYYY-MM-DD"
                          style="width: 300px" />
        </el-form-item>
        <el-form-item label="计划结束时间">
          <el-date-picker v-model="editrow.planEndTime"
                          type="date"
                          placeholder="请选择"
                          value-format="YYYY-MM-DD"
                          style="width: 300px" />
        </el-form-item>
        <el-form-item label="实际开始时间">
          <el-date-picker v-model="editrow.actualStartTime"
                          type="date"
                          placeholder="请选择"
                          value-format="YYYY-MM-DD"
                          style="width: 300px" />
        </el-form-item>
        <el-form-item label="实际结束时间">
          <el-date-picker v-model="editrow.actualEndTime"
                          type="date"
                          placeholder="请选择"
                          value-format="YYYY-MM-DD"
                          style="width: 300px" />
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="editDialogVisible = false">取消</el-button>
          <el-button type="primary"
                     @click="handleUpdate">确定</el-button>
        </span>
      </template>
    </el-dialog>
    <el-dialog v-model="transferCardVisible"
               title="流转卡"
               width="1000px">
      <div class="transfer-card-title">工单流转卡</div>
      <div class="transfer-card-container">
        <div class="transfer-card-info">
          <div class="info-group">
            <div class="info-item">
              <span class="info-label">工单编号</span>
              <span class="info-value">{{ transferCardRowData.workOrderNo }}</span>
            </div>
            <!-- <div class="info-item">
              <span class="info-label">产品编号</span>
              <span class="info-value">{{ transferCardRowData.productNo }}</span>
            </div> -->
            <div class="info-item">
              <span class="info-label">产品名称</span>
              <span class="info-value">{{ transferCardRowData.productName }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">产品规格</span>
              <span class="info-value">{{ transferCardRowData.model }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">工单状态</span>
              <span class="info-value">{{
                transferCardRowData.status === 1 ? '待确认' :
                transferCardRowData.status === 2 ? '待生产' :
                transferCardRowData.status === 3 ? '生产中' :
                transferCardRowData.status === 4 ? '已生产' :
                transferCardRowData.status
              }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">计划开始时间</span>
              <span class="info-value">{{ transferCardRowData.planStartTime }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">计划结束时间</span>
              <span class="info-value">{{ transferCardRowData.planEndTime }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">备注</span>
              <span class="info-value">{{ transferCardRowData.remark }}</span>
            </div>
          </div>
          <div class="info-group">
            <div class="info-item">
              <span class="info-label">&nbsp;</span>
              <span class="info-value">&nbsp;</span>
            </div>
            <div class="info-item">
              <span class="info-label">计划数量</span>
              <span class="info-value">{{ transferCardRowData.planQuantity }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">良品数量</span>
              <span class="info-value">0</span>
            </div>
            <div class="info-item">
              <span class="info-label">不良品数</span>
              <span class="info-value">0</span>
            </div>
            <div class="info-item">
              <span class="info-label">实际开始时间</span>
              <span class="info-value">{{ transferCardRowData.actualStartTime }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">实际结束时间</span>
              <span class="info-value">{{ transferCardRowData.actualEndTime }}</span>
            </div>
          </div>
        </div>
        <div class="transfer-card-qr">
          <div class="qr-container">
            <img :src="transferCardQrUrl"
                 alt="流转卡二维码"
                 style="width: 200px; height: 200px;" />
            <!-- <div class="qr-tip"
                 style="margin-top: 10px; text-align: center;">流转卡二维码</div> -->
          </div>
        </div>
      </div>
      <div class="print-button-container"
           style=" text-align: center;
      margin-bottom: 40px;">
        <el-button type="primary"
                   style="margin-top: 20px;"
                   @click="printTransferCard">打印流转卡</el-button>
      </div>
    </el-dialog>
    <el-dialog v-model="reportDialogVisible"
               title="报工"
               width="500px">
      <el-form :model="reportForm"
               label-width="120px">
        <el-form-item label="待生产数量">
          <el-input v-model="reportForm.remainingQuantity"
                    readonly
                    style="width: 300px" />
        </el-form-item>
        <el-form-item label="本次生产数量">
          <el-input v-model.number="reportForm.quantity"
                    type="number"
                    min="1"
                    style="width: 300px"
                    placeholder="请输入本次生产数量" />
        </el-form-item>
        <el-form-item label="班组信息">
          <el-input v-model="reportForm.userName"
                    style="width: 300px"
                    readonly
                    placeholder="请输入班组信息" />
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="reportDialogVisible = false">取消</el-button>
          <el-button type="primary"
                     @click="handleReport">确定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
@@ -44,7 +210,12 @@
  import { onMounted, ref } from "vue";
  import { ElMessageBox } from "element-plus";
  import dayjs from "dayjs";
  import { productWorkOrderPage } from "@/api/productionManagement/workOrder.js";
  import {
    productWorkOrderPage,
    updateProductWorkOrder,
    addProductMain,
  } from "@/api/productionManagement/workOrder.js";
  import { getUserProfile } from "@/api/system/user.js";
  import QRCode from "qrcode";
  import { getCurrentInstance, reactive, toRefs } from "vue";
  const { proxy } = getCurrentInstance();
@@ -86,12 +257,50 @@
      label: "实际结束时间",
      prop: "actualEndTime",
    },
    {
      label: "操作",
      width: "200",
      align: "center",
      dataType: "action",
      operation: [
        {
          name: "编辑",
          clickFun: row => {
            handleEdit(row);
          },
        },
        {
          name: "流转卡",
          clickFun: row => {
            showTransferCard(row);
          },
        },
        {
          name: "报工",
          clickFun: row => {
            showReportDialog(row);
          },
        },
      ],
    },
  ]);
  const tableData = ref([]);
  const tableLoading = ref(false);
  const qrDialogVisible = ref(false);
  const qrCodeUrl = ref("");
  const qrRowData = ref(null);
  const editDialogVisible = ref(false);
  const transferCardVisible = ref(false);
  const transferCardData = ref([]);
  const transferCardQrUrl = ref("");
  const transferCardRowData = ref(null);
  const reportDialogVisible = ref(false);
  const reportForm = reactive({
    remainingQuantity: 0,
    quantity: 0,
    userName: "",
  });
  const currentReportRowData = ref(null);
  const page = reactive({
    current: 1,
    size: 100,
@@ -104,6 +313,7 @@
    },
  });
  const { searchForm } = toRefs(data);
  let editrow = ref(null);
  // 查询列表
  /** 搜索按钮操作 */
@@ -145,9 +355,221 @@
    document.body.removeChild(link);
  };
  const showTransferCard = async row => {
    transferCardRowData.value = row;
    const qrContent = proxy.javaApi + "/work-order?orderId=" + row.id;
    transferCardQrUrl.value = await QRCode.toDataURL(qrContent);
    transferCardVisible.value = true;
  };
  const printTransferCard = () => {
    window.print();
  };
  const handleEdit = row => {
    editrow.value = JSON.parse(JSON.stringify(row));
    editDialogVisible.value = true;
  };
  const handleUpdate = () => {
    updateProductWorkOrder(editrow.value)
      .then(res => {
        proxy.$modal.msgSuccess("提交成功");
        editDialogVisible.value = false;
        getList();
      })
      .catch(() => {
        ElMessageBox.alert("修改失败", "提示", {
          confirmButtonText: "确定",
        });
      });
  };
  const showReportDialog = row => {
    currentReportRowData.value = row;
    reportForm.remainingQuantity = 1;
    reportForm.quantity = 0;
    reportForm.productProcessRouteItemId = row.productProcessRouteItemId;
    // 获取当前登录用户信息
    reportDialogVisible.value = true;
  };
  const handleReport = () => {
    if (!reportForm.quantity || reportForm.quantity <= 0) {
      ElMessageBox.alert("请输入有效的本次生产数量", "提示", {
        confirmButtonText: "确定",
      });
      return;
    }
    if (reportForm.quantity > reportForm.remainingQuantity) {
      ElMessageBox.alert("本次生产数量不能超过待生产数量", "提示", {
        confirmButtonText: "确定",
      });
      return;
    }
    addProductMain(reportForm).then(res => {
      if (res.code === 200) {
        proxy.$modal.msgSuccess("报工成功");
        reportDialogVisible.value = false;
        getList();
      } else {
        ElMessageBox.alert(res.msg || "报工失败", "提示", {
          confirmButtonText: "确定",
        });
      }
    });
  };
  onMounted(() => {
    getList();
    getUserProfile()
      .then(res => {
        if (res.code === 200) {
          reportForm.userName = res.data.userName;
          reportForm.userId = res.data.userId;
        }
      })
      .catch(err => {
        console.error("获取用户信息失败", err);
      });
  });
</script>
<style scoped lang="scss"></style>
<style scoped lang="scss">
  .search_form {
    margin-bottom: 20px;
    .search-row {
      display: flex;
      gap: 20px;
      align-items: center;
      .search-item {
        display: flex;
        align-items: center;
        gap: 10px;
      }
    }
  }
  .transfer-card-title {
    font-size: 24px;
    font-weight: bold;
    text-align: center;
    margin-bottom: 20px;
  }
  .transfer-card-container {
    display: flex;
    gap: 20px;
    height: 350px;
    .transfer-card-info {
      flex: 1;
      overflow: auto;
      .info-group {
        width: 50%;
        float: left;
      }
      .info-item {
        display: flex;
        margin-bottom: 15px;
        .info-label {
          width: 120px;
          font-weight: bold;
          margin-right: 20px;
        }
        .info-value {
          flex: 1;
        }
      }
    }
    .transfer-card-qr {
      width: 240px;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: flex-start;
    }
  }
</style>
<style  lang="scss">
  @media print {
    @page {
      size: landscape;
    }
    body * {
      visibility: hidden;
    }
    .el-dialog__wrapper,
    .el-dialog,
    .el-dialog__body,
    .transfer-card-title,
    .transfer-card-container,
    .transfer-card-container *,
    .info-item,
    .info-label,
    .info-value {
      visibility: visible;
    }
    .print-button-container {
      visibility: hidden;
    }
    .el-dialog__wrapper {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      margin: 0;
    }
    .el-dialog {
      width: 100% !important;
      max-width: 800px;
      margin: 0 auto !important;
    }
    .el-dialog__header,
    .el-dialog__footer {
      display: none;
    }
    .el-dialog__body {
      padding: 20px;
    }
    .transfer-card-container {
      height: auto;
      display: flex;
      gap: 20px;
    }
    .transfer-card-info {
      flex: 1;
      .info-group {
        width: 100%;
        float: none;
        margin-bottom: 20px;
      }
      .info-item {
        display: flex;
        margin-bottom: 10px;
        .info-label {
          width: 100px;
          font-weight: bold;
          margin-right: 15px;
          white-space: nowrap;
        }
        .info-value {
          flex: 1;
          word-break: break-word;
        }
      }
    }
    .transfer-card-qr {
      width: 160px;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: flex-start;
    }
    .qr-container img {
      width: 140px !important;
      height: 140px !important;
    }
  }
</style>
vite.config.js
@@ -6,17 +6,11 @@
export default defineConfig(({ mode, command }) => {
  const env = loadEnv(mode, process.cwd());
  const { VITE_APP_ENV } = env;
  const baseUrl =
      env.VITE_APP_ENV === "development"
          ? "http://192.168.1.147:7003"
          : env.VITE_BASE_API;
  const javaUrl =
      env.VITE_APP_ENV === "development"
          ? "http://114.132.189.42:9037"
          : env.VITE_JAVA_API;
  const baseUrl = env.VITE_APP_ENV === "development" ? "http://192.168.1.35:7003" : env.VITE_BASE_API;
  const javaUrl = env.VITE_APP_ENV === "development" ? "http://114.132.189.42:9037" : env.VITE_JAVA_API;
  return {
    define:{
      __BASE_API__: JSON.stringify(javaUrl)
    define: {
      __BASE_API__: JSON.stringify(javaUrl),
    },
    // 部署生产环境和开发环境下的URL。
    // 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上
@@ -59,7 +53,7 @@
        "/dev-api": {
          target: baseUrl,
          changeOrigin: true,
          rewrite: (p) => p.replace(/^\/dev-api/, ""),
          rewrite: p => p.replace(/^\/dev-api/, ""),
        },
        // springdoc proxy
        "^/v3/api-docs/(.*)": {
@@ -74,7 +68,7 @@
          {
            postcssPlugin: "internal:charset-removal",
            AtRule: {
              charset: (atRule) => {
              charset: atRule => {
                if (atRule.name === "charset") {
                  atRule.remove();
                }