zhangwencui
2026-04-30 300f0fd4a8148dab8915eb26ace72e187fa18e61
工艺路线模块开发
已添加3个文件
已修改4个文件
1058 ■■■■■ 文件已修改
src/api/productionManagement/processRoute.js 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionOrder.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/processRoute/index.vue 287 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/processRoute/items.vue 544 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/productionOrder/index.vue 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/works.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/processRoute.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
// å·¥è‰ºè·¯çº¿ç›¸å…³æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢å·¥è‰ºè·¯çº¿åˆ—表
export function listPage(query) {
  return request({
    url: "/technologyRouting/page",
    method: "get",
    params: query,
  });
}
// æŸ¥è¯¢å·¥è‰ºè·¯çº¿é¡¹ç›®åˆ—表
export function findProcessRouteItemList(query) {
  return request({
    url: "/technologyRoutingOperation/list",
    method: "get",
    params: query,
  });
}
// èŽ·å–å·¥åºå‚æ•°åˆ—è¡¨
export function getProcessParamList(query) {
  return request({
    url: "/technologyRoutingOperationParam/list",
    method: "get",
    params: query,
  });
}
// æŸ¥è¯¢BOM结构 (工艺路线)
export function queryBomList(bomId) {
  return request({
    url: "/technologyBomStructure/listByBomId/" + bomId,
    method: "get",
  });
}
src/api/productionManagement/productionOrder.js
@@ -42,3 +42,29 @@
    method: "get",
  });
}
// èŽ·å–ç”Ÿäº§è®¢å•å…³è”çš„å·¥è‰ºè·¯çº¿ä¸»ä¿¡æ¯
export function getOrderProcessRouteMain(orderId) {
  return request({
    url: "/productionOrderRouting/listMain",
    method: "get",
    params: { orderId },
  });
}
// æŸ¥è¯¢BOM结构 (生产订单)
export function queryOrderBomList(bomId) {
  return request({
    url: "/productionBomStructure/listByBomId/" + bomId,
    method: "get",
  });
}
// èŽ·å–å·¥åºå‚æ•°åˆ—è¡¨ (生产订单)
export function findProcessParamListOrder(query) {
  return request({
    url: "/productionOrderRoutingOperationParam/list",
    method: "get",
    params: query,
  });
}
src/pages.json
@@ -789,6 +789,20 @@
      }
    },
    {
      "path": "pages/productionManagement/processRoute/index",
      "style": {
        "navigationBarTitleText": "工艺路线",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/productionManagement/processRoute/items",
      "style": {
        "navigationBarTitleText": "路线项目",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/productionManagement/productionDispatching/index",
      "style": {
        "navigationBarTitleText": "生产派工",
src/pages/productionManagement/processRoute/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,287 @@
<template>
  <view class="process-route">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="工艺路线"
                @back="goBack" />
    <!-- æœç´¢åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input class="search-text"
                    placeholder="请输入规格名称搜索"
                    v-model="searchForm.model"
                    @change="handleQuery"
                    clearable />
        </view>
        <view class="filter-button"
              @click="handleQuery">
          <up-icon name="search"
                   size="24"
                   color="#999"></up-icon>
        </view>
      </view>
    </view>
    <!-- åˆ—表区域 -->
    <scroll-view scroll-y
                 class="list-container"
                 v-if="tableData.length > 0"
                 @scrolltolower="loadMore">
      <view v-for="(item, index) in tableData"
            :key="item.id || index"
            @click="goDetail(item)">
        <view class="ledger-item">
          <view class="item-header">
            <view class="item-left">
              <view class="document-icon">
                <up-icon name="share-square"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-id">{{ item.processRouteCode }}</text>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">产品名称</text>
              <text class="detail-value font-bold">{{ item.productName || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">规格名称</text>
              <text class="detail-value">{{ item.model || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">BOM编号</text>
              <text class="detail-value">{{ item.bomNo || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">描述</text>
              <text class="detail-value">{{ item.description || '-' }}</text>
            </view>
          </view>
          <view class="item-footer">
            <text class="more-detail">路线项目</text>
            <up-icon name="arrow-right"
                     size="14"
                     color="#999"></up-icon>
          </view>
        </view>
      </view>
      <up-loadmore :status="loadStatus"
                   v-if="tableData.length >= page.size" />
    </scroll-view>
    <view v-else
          class="no-data">
      <up-empty mode="data"
                text="暂无工艺路线数据"></up-empty>
    </view>
  </view>
</template>
<script setup>
  import { ref, reactive, toRefs, getCurrentInstance } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import { listPage } from "@/api/productionManagement/processRoute.js";
  import PageHeader from "@/components/PageHeader.vue";
  const { proxy } = getCurrentInstance();
  // åŠ è½½çŠ¶æ€
  const loading = ref(false);
  const loadStatus = ref("loadmore");
  // åˆ—表数据
  const tableData = ref([]);
  // åˆ†é¡µé…ç½®
  const page = reactive({
    current: 1,
    size: 10,
    total: 0,
  });
  // æœç´¢è¡¨å•数据
  const data = reactive({
    searchForm: {
      model: "",
    },
  });
  const { searchForm } = toRefs(data);
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // æŸ¥è¯¢åˆ—表
  const handleQuery = () => {
    page.current = 1;
    tableData.value = [];
    getList();
  };
  // åŠ è½½æ›´å¤š
  const loadMore = () => {
    if (loadStatus.value === "nomore" || loading.value) return;
    page.current++;
    getList();
  };
  // èŽ·å–åˆ—è¡¨æ•°æ®
  const getList = () => {
    loading.value = true;
    loadStatus.value = "loading";
    const params = {
      current: page.current,
      size: page.size,
      model: searchForm.value.model,
    };
    listPage(params)
      .then(res => {
        loading.value = false;
        const records = res.data.records || [];
        if (page.current === 1) {
          tableData.value = records;
        } else {
          tableData.value = [...tableData.value, ...records];
        }
        if (records.length < page.size) {
          loadStatus.value = "nomore";
        } else {
          loadStatus.value = "loadmore";
        }
        page.total = res.data.total || 0;
      })
      .catch(() => {
        loading.value = false;
        loadStatus.value = "loadmore";
        uni.showToast({
          title: "加载失败",
          icon: "error",
        });
      });
  };
  // è·³è½¬è·¯çº¿é¡¹ç›®
  const goDetail = item => {
    uni.navigateTo({
      url: `/pages/productionManagement/processRoute/items?id=${
        item.id
      }&processRouteCode=${
        item.processRouteCode
      }&productName=${encodeURIComponent(
        item.productName || ""
      )}&model=${encodeURIComponent(item.model || "")}&bomNo=${
        item.bomNo || ""
      }&bomId=${item.bomId || ""}&description=${encodeURIComponent(
        item.description || ""
      )}`,
    });
  };
  // é¡µé¢æ˜¾ç¤ºæ—¶åŠ è½½æ•°æ®
  onShow(() => {
    handleQuery();
  });
</script>
<style scoped lang="scss">
  @import "@/styles/sales-common.scss";
  .process-route {
    min-height: 100vh;
    background: #f8f9fa;
    display: flex;
    flex-direction: column;
  }
  .list-container {
    flex: 1;
    height: 0;
  }
  .ledger-item {
    background: #fff;
    margin: 20rpx;
    padding: 24rpx;
    border-radius: 16rpx;
    box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
    .item-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding-bottom: 12rpx;
      .item-left {
        display: flex;
        align-items: center;
        .document-icon {
          width: 44rpx;
          height: 44rpx;
          background: #3c9cff;
          border-radius: 10rpx;
          display: flex;
          justify-content: center;
          align-items: center;
          margin-right: 20rpx;
        }
        .item-id {
          font-size: 30rpx;
          font-weight: bold;
          color: #333;
        }
      }
    }
    .item-details {
      padding: 16rpx 0;
      .detail-row {
        display: flex;
        justify-content: space-between;
        align-items: flex-start;
        margin-bottom: 16rpx;
        .detail-label {
          font-size: 26rpx;
          color: #999;
          min-width: 140rpx;
        }
        .detail-value {
          font-size: 26rpx;
          color: #333;
          text-align: right;
          flex: 1;
          &.font-bold {
            font-weight: bold;
          }
        }
      }
    }
    .item-footer {
      display: flex;
      justify-content: flex-end;
      align-items: center;
      padding-top: 16rpx;
      border-top: 1rpx solid #f0f0f0;
      .more-detail {
        font-size: 24rpx;
        color: #3c9cff;
        margin-right: 8rpx;
      }
    }
  }
  .no-data {
    padding-top: 200rpx;
  }
</style>
src/pages/productionManagement/processRoute/items.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,544 @@
<template>
  <view class="process-route-items">
    <PageHeader title="路线项目"
                @back="goBack" />
    <!-- è·¯çº¿åŸºç¡€ä¿¡æ¯å¡ç‰‡ -->
    <view class="route-info-card">
      <view class="info-row">
        <text class="label">工艺路线编号</text>
        <text class="value">{{ routeInfo.processRouteCode || '-' }}</text>
      </view>
      <view class="info-row">
        <text class="label">产品名称</text>
        <text class="value">{{ routeInfo.productName || '-' }}</text>
      </view>
      <view class="info-row">
        <text class="label">规格名称</text>
        <text class="value">{{ routeInfo.model || '-' }}</text>
      </view>
      <view class="info-row">
        <text class="label">BOM编号</text>
        <text class="value">{{ routeInfo.bomNo || '-' }}</text>
      </view>
    </view>
    <!-- é€‰é¡¹å¡åˆ‡æ¢ -->
    <view class="tabs-box">
      <up-tabs :list="tabsList"
               @click="handleTabClick"
               :current="currentTab"></up-tabs>
    </view>
    <!-- å·¥åºé¡¹ç›®åˆ—表 -->
    <scroll-view scroll-y
                 class="content-scroll"
                 v-if="currentTab === 0">
      <view v-if="itemsList.length > 0">
        <view v-for="(item, index) in itemsList"
              :key="index"
              class="process-card">
          <view class="card-header">
            <view class="index-badge">{{ index + 1 }}</view>
            <text class="process-name">{{ item.technologyOperationName || item.operationName || '-' }}</text>
          </view>
          <view class="card-content">
            <view class="detail-row">
              <text class="detail-label">关联产品</text>
              <text class="detail-value">{{ item.productName || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">规格型号</text>
              <text class="detail-value">{{ item.model || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">单位</text>
              <text class="detail-value">{{ item.unit || '-' }}</text>
            </view>
            <view class="tag-row">
              <up-tag v-if="item.isQuality"
                      text="质检"
                      type="primary"
                      size="mini"
                      plain />
              <up-tag v-if="item.isProduction"
                      text="生产"
                      type="success"
                      size="mini"
                      plain />
            </view>
          </view>
          <view class="card-footer"
                @click="showParams(item)">
            <text class="action-text">查看参数列表</text>
            <up-icon name="arrow-right"
                     size="14"
                     color="#3c9cff"></up-icon>
          </view>
        </view>
      </view>
      <view v-else
            class="no-data">
        <up-empty mode="data"
                  text="暂无路线项目"></up-empty>
      </view>
    </scroll-view>
    <!-- BOM ç»“构展示 -->
    <scroll-view scroll-y
                 class="content-scroll"
                 v-if="currentTab === 1">
      <view v-if="bomList.length > 0"
            class="bom-tree">
        <view v-for="(node, nIndex) in flatBomList"
              :key="nIndex"
              class="bom-node"
              :style="{ paddingLeft: (node.level * 40) + 'rpx' }">
          <view class="bom-node-inner">
            <view class="bom-line"
                  v-if="node.level > 0"></view>
            <view class="bom-content">
              <view class="bom-header">
                <text class="bom-product">{{ node.productName }}</text>
                <text class="bom-model"
                      v-if="node.model">({{ node.model }})</text>
              </view>
              <view class="bom-details">
                <text class="bom-info">工序: {{ node.operationName || '-' }}</text>
                <text class="bom-info">所需: {{ node.unitQuantity || 0 }} {{ node.unit || '' }}</text>
              </view>
            </view>
          </view>
        </view>
      </view>
      <view v-else
            class="no-data">
        <up-empty mode="data"
                  text="暂无 BOM ç»“æž„"></up-empty>
      </view>
    </scroll-view>
    <!-- å‚数列表弹窗 -->
    <up-popup :show="showPopup"
              mode="bottom"
              @close="showPopup = false"
              round="10">
      <view class="popup-content">
        <view class="popup-header">
          <text class="title">参数列表 - {{ currentItem.technologyOperationName || currentItem.operationName }}</text>
          <up-icon name="close"
                   size="20"
                   @click="showPopup = false"></up-icon>
        </view>
        <scroll-view scroll-y
                     class="param-list">
          <view v-if="paramList.length > 0">
            <view v-for="(param, pIndex) in paramList"
                  :key="pIndex"
                  class="param-item">
              <view class="param-row">
                <text class="param-label">参数名称:</text>
                <text class="param-value">{{ param.paramName || '-' }}</text>
              </view>
              <view class="param-row">
                <text class="param-label">标准值:</text>
                <text class="param-value">{{ param.standardValue || '-' }}</text>
              </view>
              <view class="param-row">
                <text class="param-label">单位:</text>
                <text class="param-value">{{ param.unit || '-' }}</text>
              </view>
            </view>
          </view>
          <view v-else
                class="no-record">
            <text>暂无参数记录</text>
          </view>
        </scroll-view>
      </view>
    </up-popup>
  </view>
</template>
<script setup>
  import { ref, reactive, computed } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import {
    findProcessRouteItemList,
    getProcessParamList,
    queryBomList,
  } from "@/api/productionManagement/processRoute.js";
  import {
    queryOrderBomList,
    findProcessParamListOrder,
  } from "@/api/productionManagement/productionOrder.js";
  import PageHeader from "@/components/PageHeader.vue";
  const routeInfo = ref({});
  const itemsList = ref([]);
  const bomList = ref([]);
  const loading = ref(false);
  const pageType = ref("route"); // route | order
  // é€‰é¡¹å¡
  const tabsList = reactive([{ name: "路线项目" }, { name: "BOM结构" }]);
  const currentTab = ref(0);
  // å¼¹çª—相关
  const showPopup = ref(false);
  const currentItem = ref({});
  const paramList = ref([]);
  const paramLoading = ref(false);
  const goBack = () => {
    uni.navigateBack();
  };
  const handleTabClick = item => {
    currentTab.value = item.index;
    if (item.index === 1 && bomList.value.length === 0) {
      fetchBom();
    }
  };
  // æ‰å¹³åŒ– BOM æ ‘用于展示
  const flatBomList = computed(() => {
    const result = [];
    const flatten = (nodes, level = 0) => {
      nodes.forEach(node => {
        result.push({ ...node, level });
        if (node.children && node.children.length > 0) {
          flatten(node.children, level + 1);
        }
      });
    };
    flatten(bomList.value);
    return result;
  });
  onLoad(options => {
    if (options.id) {
      pageType.value = options.type || "route";
      routeInfo.value = {
        id: options.id,
        processRouteCode: options.processRouteCode || "",
        productName: decodeURIComponent(options.productName || ""),
        model: decodeURIComponent(options.model || ""),
        bomNo: options.bomNo || "",
        bomId: options.bomId || "",
        description: decodeURIComponent(options.description || ""),
        orderId: options.orderId || "",
      };
      fetchItems(options.id);
    }
  });
  const fetchItems = id => {
    loading.value = true;
    findProcessRouteItemList({ routeId: id, orderId: routeInfo.value.orderId })
      .then(res => {
        itemsList.value = res.data || [];
        loading.value = false;
      })
      .catch(() => {
        loading.value = false;
        uni.showToast({
          title: "获取项目失败",
          icon: "error",
        });
      });
  };
  const fetchBom = () => {
    console.log(routeInfo.value.bomId, "routeInfo.value.bomId");
    if (!routeInfo.value.bomId) return;
    loading.value = true;
    const api = pageType.value === "order" ? queryOrderBomList : queryBomList;
    api(routeInfo.value.bomId)
      .then(res => {
        bomList.value = res.data || [];
        loading.value = false;
      })
      .catch(() => {
        loading.value = false;
        uni.showToast({
          title: "获取 BOM å¤±è´¥",
          icon: "error",
        });
      });
  };
  const showParams = item => {
    currentItem.value = item;
    showPopup.value = true;
    paramLoading.value = true;
    paramList.value = [];
    const api =
      pageType.value === "order"
        ? findProcessParamListOrder
        : getProcessParamList;
    const params =
      pageType.value === "order"
        ? {
            productionOrderRoutingOperationId: item.id,
            productionOrderId: routeInfo.value.orderId,
          }
        : { technologyRoutingOperationId: item.id };
    api(params)
      .then(res => {
        paramList.value = res.data || [];
        paramLoading.value = false;
      })
      .catch(() => {
        paramLoading.value = false;
        uni.showToast({
          title: "获取参数失败",
          icon: "error",
        });
      });
  };
</script>
<style scoped lang="scss">
  .process-route-items {
    min-height: 100vh;
    background: #f8f9fa;
    display: flex;
    flex-direction: column;
  }
  .route-info-card {
    background: #fff;
    margin: 20rpx;
    padding: 24rpx;
    border-radius: 16rpx;
    box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
    .info-row {
      display: flex;
      justify-content: space-between;
      margin-bottom: 12rpx;
      &:last-child {
        margin-bottom: 0;
      }
      .label {
        font-size: 26rpx;
        color: #999;
      }
      .value {
        font-size: 26rpx;
        color: #333;
        font-weight: bold;
      }
    }
  }
  .tabs-box {
    background: #fff;
    margin-bottom: 10rpx;
  }
  .content-scroll {
    flex: 1;
    height: 0;
    padding: 0 20rpx;
  }
  .process-card {
    background: #fff;
    margin-bottom: 24rpx;
    border-radius: 16rpx;
    overflow: hidden;
    box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
    .card-header {
      display: flex;
      align-items: center;
      padding: 20rpx 24rpx;
      background: #fcfcfc;
      border-bottom: 1rpx solid #f5f5f5;
      .index-badge {
        width: 40rpx;
        height: 40rpx;
        background: #3c9cff;
        color: #fff;
        border-radius: 20rpx;
        display: flex;
        justify-content: center;
        align-items: center;
        font-size: 24rpx;
        margin-right: 20rpx;
      }
      .process-name {
        font-size: 28rpx;
        font-weight: bold;
        color: #333;
      }
    }
    .card-content {
      padding: 24rpx;
      .detail-row {
        display: flex;
        justify-content: space-between;
        margin-bottom: 12rpx;
        .detail-label {
          font-size: 24rpx;
          color: #999;
        }
        .detail-value {
          font-size: 24rpx;
          color: #666;
        }
      }
      .tag-row {
        display: flex;
        gap: 16rpx;
        margin-top: 10rpx;
      }
    }
    .card-footer {
      padding: 16rpx 24rpx;
      border-top: 1rpx dashed #eee;
      display: flex;
      justify-content: space-between;
      align-items: center;
      .action-text {
        font-size: 24rpx;
        color: #3c9cff;
      }
    }
  }
  /* BOM æ ‘样式 */
  .bom-tree {
    padding: 20rpx 0;
  }
  .bom-node {
    position: relative;
    margin-bottom: 20rpx;
  }
  .bom-node-inner {
    background: #fff;
    padding: 20rpx;
    border-radius: 12rpx;
    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
    display: flex;
    align-items: center;
  }
  .bom-line {
    position: absolute;
    left: -20rpx;
    top: 50%;
    width: 20rpx;
    height: 2rpx;
    background: #ddd;
  }
  .bom-content {
    flex: 1;
  }
  .bom-header {
    display: flex;
    align-items: center;
    margin-bottom: 8rpx;
    .bom-product {
      font-size: 28rpx;
      font-weight: bold;
      color: #333;
    }
    .bom-model {
      font-size: 24rpx;
      color: #999;
      margin-left: 10rpx;
    }
  }
  .bom-details {
    display: flex;
    justify-content: space-between;
    .bom-info {
      font-size: 24rpx;
      color: #666;
    }
  }
  .no-data {
    padding-top: 100rpx;
  }
  /* å¼¹çª—样式 */
  .popup-content {
    background: #fff;
    padding: 30rpx;
    max-height: 70vh;
    display: flex;
    flex-direction: column;
  }
  .popup-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding-bottom: 30rpx;
    border-bottom: 1rpx solid #eee;
    .title {
      font-size: 30rpx;
      font-weight: bold;
      color: #333;
    }
  }
  .param-list {
    flex: 1;
    height: 0;
    padding-top: 20rpx;
  }
  .param-item {
    padding: 20rpx;
    background: #f9f9f9;
    border-radius: 12rpx;
    margin-bottom: 16rpx;
    .param-row {
      display: flex;
      margin-bottom: 8rpx;
      &:last-child {
        margin-bottom: 0;
      }
      .param-label {
        font-size: 24rpx;
        color: #999;
        width: 140rpx;
      }
      .param-value {
        font-size: 24rpx;
        color: #333;
        flex: 1;
      }
    }
  }
  .no-record {
    padding: 100rpx 0;
    text-align: center;
    color: #999;
    font-size: 26rpx;
  }
</style>
src/pages/productionManagement/productionOrder/index.vue
@@ -1,43 +1,50 @@
<template>
    <view class="production-order">
        <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
        <PageHeader title="生产订单" @back="goBack" />
    <PageHeader title="生产订单"
                @back="goBack" />
        <!-- æœç´¢åŒºåŸŸ -->
        <view class="search-section">
            <view class="search-bar">
                <view class="search-input">
                    <up-input
                        class="search-text"
          <up-input class="search-text"
                        placeholder="请输入订单号或产品名称"
                        v-model="searchForm.keyword"
                        @change="handleQuery"
                        clearable
                    />
                    clearable />
                </view>
                <view class="filter-button" @click="handleQuery">
                    <up-icon name="search" size="24" color="#999"></up-icon>
        <view class="filter-button"
              @click="handleQuery">
          <up-icon name="search"
                   size="24"
                   color="#999"></up-icon>
                </view>
            </view>
        </view>
        <!-- åˆ—表区域 -->
        <scroll-view scroll-y class="list-container" v-if="tableData.length > 0" @scrolltolower="loadMore">
            <view v-for="(item, index) in tableData" :key="item.id || index">
    <scroll-view scroll-y
                 class="list-container"
                 v-if="tableData.length > 0"
                 @scrolltolower="loadMore">
      <view v-for="(item, index) in tableData"
            :key="item.id || index">
                <view class="ledger-item">
                    <view class="item-header">
                        <view class="item-left">
                            <view class="document-icon">
                                <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
                            </view>
                            <text class="item-id">{{ item.npsNo }}</text>
                        </view>
                        <view class="item-right">
                            <up-tag :text="getStatusText(item.status)" :type="getStatusType(item.status)" size="mini" />
              <up-tag :text="getStatusText(item.status)"
                      :type="getStatusType(item.status)"
                      size="mini" />
                        </view>
                    </view>
                    <up-divider></up-divider>
                    <view class="item-details">
                        <view class="detail-row">
                            <text class="detail-label">产品名称</text>
@@ -65,36 +72,53 @@
                            <text class="detail-value">{{ formatDate(item.planCompleteTime) }}</text>
                        </view>
                    </view>
                    <view class="item-footer">
                        <view class="action-btns">
                            <up-button type="primary" size="mini" plain text="来源" @click="goSource(item)"></up-button>
                            <up-button type="success" size="mini" plain text="领料详情" @click="goPickingDetail(item)"></up-button>
              <up-button type="info"
                         size="mini"
                         plain
                         text="工艺路线"
                         @click="goProcessRoute(item)"></up-button>
              <up-button type="primary"
                         size="mini"
                         plain
                         text="来源"
                         @click="goSource(item)"></up-button>
              <up-button type="success"
                         size="mini"
                         plain
                         text="领料详情"
                         @click="goPickingDetail(item)"></up-button>
                        </view>
                    </view>
                </view>
            </view>
            <up-loadmore :status="loadStatus" v-if="tableData.length >= page.size" />
      <up-loadmore :status="loadStatus"
                   v-if="tableData.length >= page.size" />
        </scroll-view>
        <view v-else class="no-data">
            <up-empty mode="data" text="暂无生产订单数据"></up-empty>
    <view v-else
          class="no-data">
      <up-empty mode="data"
                text="暂无生产订单数据"></up-empty>
        </view>
    </view>
</template>
<script setup>
import { ref, reactive, toRefs, getCurrentInstance } from "vue";
import { onShow } from '@dcloudio/uni-app';
  import { onShow } from "@dcloudio/uni-app";
import dayjs from "dayjs";
import { productOrderListPage } from "@/api/productionManagement/productionOrder.js";
  import {
    productOrderListPage,
    getOrderProcessRouteMain,
  } from "@/api/productionManagement/productionOrder.js";
import PageHeader from "@/components/PageHeader.vue";
const { proxy } = getCurrentInstance();
// åŠ è½½çŠ¶æ€
const loading = ref(false);
const loadStatus = ref('loadmore');
  const loadStatus = ref("loadmore");
// åˆ—表数据
const tableData = ref([]);
@@ -119,12 +143,12 @@
};
// æ ¼å¼åŒ–日期
const formatDate = (date) => {
    return date ? dayjs(date).format('YYYY-MM-DD') : '-';
  const formatDate = date => {
    return date ? dayjs(date).format("YYYY-MM-DD") : "-";
};
// èŽ·å–çŠ¶æ€æ–‡æœ¬
const getStatusText = (status) => {
  const getStatusText = status => {
    const statusMap = {
        1: "待开始",
        2: "进行中",
@@ -135,7 +159,7 @@
};
// èŽ·å–çŠ¶æ€ç±»åž‹
const getStatusType = (status) => {
  const getStatusType = status => {
    const typeMap = {
        1: "primary",
        2: "warning",
@@ -146,7 +170,7 @@
};
// å®Œæˆè¿›åº¦ç™¾åˆ†æ¯”
const toProgressPercentage = (val) => {
  const toProgressPercentage = val => {
    const n = Number(val);
    if (!Number.isFinite(n)) return 0;
    if (n <= 0) return 0;
@@ -155,7 +179,7 @@
};
// è¿›åº¦æ¡é¢œè‰²
const progressColor = (percentage) => {
  const progressColor = percentage => {
    const p = toProgressPercentage(percentage);
    if (p < 30) return "#f56c6c";
    if (p < 50) return "#e6a23c";
@@ -172,7 +196,7 @@
// åŠ è½½æ›´å¤š
const loadMore = () => {
    if (loadStatus.value === 'nomore' || loading.value) return;
    if (loadStatus.value === "nomore" || loading.value) return;
    page.current++;
    getList();
};
@@ -180,16 +204,17 @@
// èŽ·å–åˆ—è¡¨æ•°æ®
const getList = () => {
    loading.value = true;
    loadStatus.value = 'loading';
    loadStatus.value = "loading";
    
    const params = { 
        current: page.current,
        size: page.size,
        npsNo: searchForm.value.keyword,
        productName: searchForm.value.keyword
      productName: searchForm.value.keyword,
    };
    
    productOrderListPage(params).then((res) => {
    productOrderListPage(params)
      .then(res => {
        loading.value = false;
        const records = res.data.records || [];
        if (page.current === 1) {
@@ -199,32 +224,63 @@
        }
        
        if (records.length < page.size) {
            loadStatus.value = 'nomore';
          loadStatus.value = "nomore";
        } else {
            loadStatus.value = 'loadmore';
          loadStatus.value = "loadmore";
        }
        page.total = res.data.total || 0;
    }).catch(() => {
      })
      .catch(() => {
        loading.value = false;
        loadStatus.value = 'loadmore';
        loadStatus.value = "loadmore";
        uni.showToast({
            title: '加载失败',
            icon: 'error'
          title: "加载失败",
          icon: "error",
        });
      });
  };
  // è·³è½¬å·¥è‰ºè·¯çº¿ (BOM)
  const goProcessRoute = item => {
    getOrderProcessRouteMain(item.id)
      .then(res => {
        const data = res.data || {};
        if (!data.id) {
          uni.showToast({ title: "未找到工艺路线", icon: "none" });
          return;
        }
        uni.navigateTo({
          url: `/pages/productionManagement/processRoute/items?id=${
            data.id
          }&bomId=${data.orderBomId}&processRouteCode=${
            data.processRouteCode || ""
          }&productName=${encodeURIComponent(
            item.productName || ""
          )}&model=${encodeURIComponent(item.model || "")}&orderId=${
            item.id
          }&type=order`,
        });
      })
      .catch(() => {
        uni.showToast({ title: "获取路线失败", icon: "none" });
    });
};
// è·³è½¬æ¥æº
const goSource = (item) => {
  const goSource = item => {
    uni.navigateTo({
        url: `/pages/productionManagement/productionOrder/source?id=${item.id}&productName=${encodeURIComponent(item.productName)}&model=${encodeURIComponent(item.model)}&quantity=${item.quantity}`
      url: `/pages/productionManagement/productionOrder/source?id=${
        item.id
      }&productName=${encodeURIComponent(
        item.productName
      )}&model=${encodeURIComponent(item.model)}&quantity=${item.quantity}`,
    });
};
// è·³è½¬é¢†æ–™è¯¦æƒ…
const goPickingDetail = (item) => {
  const goPickingDetail = item => {
    uni.navigateTo({
        url: `/pages/productionManagement/productionOrder/pickingDetail?id=${item.id}&npsNo=${item.npsNo}`
      url: `/pages/productionManagement/productionOrder/pickingDetail?id=${item.id}&npsNo=${item.npsNo}`,
    });
};
@@ -235,7 +291,7 @@
</script>
<style scoped lang="scss">
@import '@/styles/sales-common.scss';
  @import "@/styles/sales-common.scss";
.production-order {
    min-height: 100vh;
src/pages/works.vue
@@ -837,9 +837,9 @@
          url: "/pages/productionManagement/productionDispatching/index",
        });
        break;
      case "工序排产":
      case "工艺路线":
        uni.navigateTo({
          url: "/pages/productionManagement/processScheduling/index",
          url: "/pages/productionManagement/processRoute/index",
        });
        break;
      case "生产工单":