zhangwencui
2026-05-06 c5842658c8bc44bd604090192c0e8857bb22e596
生产追溯模块开发,及生产订单跳转生产追溯
已添加1个文件
已修改4个文件
990 ■■■■■ 文件已修改
src/api/productionManagement/productionOrder.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/productionOrder/index.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/productionTraceability/index.vue 947 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/works.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionOrder.js
@@ -10,6 +10,15 @@
  });
}
// ç”Ÿäº§è®¢å•溯源详情
export function getOrderDetail(npsNo) {
  return request({
    url: "/productionOrder/ordeDetail",
    method: "get",
    params: { npsNo },
  });
}
// èŽ·å–ç”Ÿäº§è®¢å•æ¥æºæ•°æ®
export function getProductOrderSource(id) {
  return request({
src/pages.json
@@ -866,6 +866,13 @@
      }
    },
    {
      "path": "pages/productionManagement/productionTraceability/index",
      "style": {
        "navigationBarTitleText": "生产追溯",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/inventoryManagement/receiptManagement/index",
      "style": {
        "navigationBarTitleText": "自定义入库",
src/pages/productionManagement/productionOrder/index.vue
@@ -100,17 +100,22 @@
          <view class="item-footer">
            <view class="action-btns">
              <up-button type="info"
                         size="mini"
                         size="small"
                         plain
                         text="生产追溯"
                         @click="goTraceability(item)"></up-button>
              <up-button type="info"
                         size="small"
                         plain
                         text="工艺路线"
                         @click="goProcessRoute(item)"></up-button>
              <up-button type="primary"
                         size="mini"
                         size="small"
                         plain
                         text="来源"
                         @click="goSource(item)"></up-button>
              <up-button type="success"
                         size="mini"
                         size="small"
                         plain
                         text="领料详情"
                         @click="goPickingDetail(item)"></up-button>
@@ -340,6 +345,13 @@
    });
  };
  // è·³è½¬ç”Ÿäº§è¿½æº¯
  const goTraceability = item => {
    uni.navigateTo({
      url: `/pages/productionManagement/productionTraceability/index?npsNo=${item.npsNo}`,
    });
  };
  // é¡µé¢æ˜¾ç¤ºæ—¶åŠ è½½æ•°æ®
  onShow(() => {
    handleQuery();
src/pages/productionManagement/productionTraceability/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,947 @@
<template>
  <view class="production-traceability">
    <PageHeader title="生产追溯"
                @back="goBack" />
    <!-- æœç´¢åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar"
            @click="openNpsNoSelector">
        <view class="search-input">
          <text v-if="!selectedNpsNo"
                class="placeholder">请选择生产订单号</text>
          <text v-else
                class="value">{{ selectedNpsNoLabel }}</text>
        </view>
        <view class="search-button">
          <up-icon name="arrow-down"
                   size="20"
                   color="#999"></up-icon>
        </view>
      </view>
    </view>
    <!-- å†…容区域 -->
    <view class="content-container"
          v-if="rowData.productionOrderDto">
      <!-- åŸºç¡€ä¿¡æ¯ -->
      <view class="info-card">
        <view class="card-title">基础信息</view>
        <view class="info-grid">
          <view class="info-item">
            <text class="label">生产订单号</text>
            <text class="value">{{ rowData.productionOrderDto.npsNo || '-' }}</text>
          </view>
          <view class="info-item">
            <text class="label">产品名称</text>
            <text class="value">{{ rowData.productionOrderDto.productName || '-' }}</text>
          </view>
          <view class="info-item">
            <text class="label">产品规格</text>
            <text class="value">{{ rowData.productionOrderDto.model || '-' }}</text>
          </view>
          <view class="info-item">
            <text class="label">计划数量</text>
            <text class="value">{{ rowData.productionOrderDto.quantity || 0 }} {{ rowData.productionOrderDto.unit || '' }}</text>
          </view>
          <view class="info-item">
            <text class="label">当前状态</text>
            <up-tag :text="getStatusText(rowData.productionOrderDto.status)"
                    style="width:100rpx"
                    :type="getStatusType(rowData.productionOrderDto.status)"
                    size="mini" />
          </view>
          <view class="info-item">
            <text class="label">客户名称</text>
            <text class="value">{{ rowData.productionOrderDto.customerName || '-' }}</text>
          </view>
          <view class="info-item">
            <text class="label">开始日期</text>
            <text class="value">{{ formatDate(rowData.productionOrderDto.startTime) }}</text>
          </view>
          <view class="info-item full-width">
            <text class="label">完成进度</text>
            <view class="progress-container">
              <up-line-progress :percentage="formatProgress(rowData.productionOrderDto.completionStatus)"
                                :activeColor="progressColor(rowData.productionOrderDto.completionStatus)"
                                height="8"></up-line-progress>
              <text class="progress-text">{{ formatProgress(rowData.productionOrderDto.completionStatus) }}%</text>
            </view>
          </view>
        </view>
      </view>
      <!-- å·¥å•信息 -->
      <view class="work-order-section"
            v-if="rowData.productionRecords && rowData.productionRecords.length > 0">
        <view class="section-title">工单信息</view>
        <view v-for="(item, index) in rowData.productionRecords"
              :key="index"
              class="work-order-card">
          <view class="card-header">
            <text class="work-order-no">{{ item.workOrder.workOrderNo }}</text>
            <text class="progress-tag"
                  :style="{ color: progressColor(item.workOrder.completionStatus) }">{{ item.workOrder.completionStatus || 0 }}%</text>
          </view>
          <view class="card-content">
            <view class="content-row">
              <text class="label">产品/规格:</text>
              <text class="value">{{ item.workOrder.productName }} / {{ item.workOrder.model }}</text>
            </view>
            <view class="content-row">
              <text class="label">需求/完成:</text>
              <text class="value">{{ item.workOrder.planQuantity }} / {{ item.workOrder.completeQuantity }}</text>
            </view>
          </view>
          <view class="card-footer">
            <up-button type="primary"
                       size="small"
                       plain
                       text="报工记录"
                       @click="handleShowReports(item)"></up-button>
            <up-button type="success"
                       size="small"
                       plain
                       text="质检信息"
                       @click="handleShowQuality(item)"></up-button>
          </view>
        </view>
      </view>
      <view v-else
            class="no-data-minor">
        <up-empty mode="data"
                  text="暂无工单信息"
                  icon-size="40"></up-empty>
      </view>
    </view>
    <view v-else
          class="no-data">
      <up-empty mode="search"
                text="请选择生产订单号查看追溯信息"></up-empty>
    </view>
    <!-- ç”Ÿäº§è®¢å•号选择弹窗 -->
    <up-popup :show="showNpsNoSelector"
              mode="bottom"
              @close="showNpsNoSelector = false"
              round="10">
      <view class="selector-popup">
        <view class="popup-header">
          <text class="popup-title">选择生产订单号</text>
          <up-icon name="close"
                   size="20"
                   @click="showNpsNoSelector = false"></up-icon>
        </view>
        <view class="search-box">
          <up-search placeholder="输入关键字搜索"
                     v-model="npsNoQuery"
                     :show-action="false"
                     @change="handleNpsNoSearch"
                     @search="handleNpsNoSearch"
                     :loading="npsNoLoading"></up-search>
        </view>
        <scroll-view scroll-y
                     class="options-list">
          <view v-for="item in npsNoOptions"
                :key="item.id"
                class="option-item"
                @click="onSelectNpsNo(item)">
            <view class="option-main">
              <text class="nps-no">{{ item.npsNo }}</text>
              <text class="product-info">{{ item.productName }} / {{ item.model }}</text>
            </view>
            <up-icon v-if="selectedNpsNo === item.id"
                     name="checkbox-mark"
                     color="#3c9cff"
                     size="20"></up-icon>
          </view>
          <view v-if="npsNoOptions.length === 0"
                class="no-options">
            <text>{{ npsNoLoading ? '加载中...' : '暂无选项' }}</text>
          </view>
        </scroll-view>
      </view>
    </up-popup>
    <!-- æŠ¥å·¥è¯¦æƒ…弹窗 -->
    <up-popup :show="reportPopupVisible"
              mode="bottom"
              @close="reportPopupVisible = false"
              round="10">
      <view class="popup-content">
        <view class="popup-header">
          <text class="popup-title">生产报工详情</text>
          <up-icon name="close"
                   size="20"
                   @click="reportPopupVisible = false"></up-icon>
        </view>
        <scroll-view scroll-y
                     class="popup-scroll">
          <view class="detail-info">
            <view class="info-row"><text class="label">工单号:</text><text class="value">{{ detailData.workOrder.workOrderNo }}</text></view>
            <view class="info-row"><text class="label">计划/完成:</text><text class="value">{{ detailData.workOrder.planQuantity }} / {{ detailData.workOrder.completeQuantity }}</text></view>
            <view class="info-row"><text class="label">实际时间:</text><text class="value">{{ formatDate(detailData.workOrder.actualStartTime) }} è‡³ {{ formatDate(detailData.workOrder.actualEndTime) }}</text></view>
          </view>
          <view class="list-title">报工明细</view>
          <view v-for="(report, idx) in detailData.reports"
                :key="idx"
                class="detail-item">
            <view class="item-main">
              <view class="item-row"><text class="label">报工单号:</text><text class="value">{{ report.productNo }}</text></view>
              <view class="item-row"><text class="label">创建人:</text><text class="value">{{ report.userName }}</text></view>
              <view class="item-row"><text class="label">创建时间:</text><text class="value">{{ formatDate(report.createTime, '{y}-{m}-{d} {h}:{i}') }}</text></view>
            </view>
            <view class="item-actions">
              <text class="action-link"
                    @click="showParams(report.productionOperationParamList)">参数详情</text>
              <text class="action-link green"
                    @click="handleShowInput(report.id)">投入详情</text>
            </view>
          </view>
          <view v-if="!detailData.reports || detailData.reports.length === 0"
                class="no-data-minor">暂无报工明细</view>
        </scroll-view>
      </view>
    </up-popup>
    <!-- æŠ•入详情弹窗 -->
    <up-popup :show="inputPopupVisible"
              mode="bottom"
              @close="inputPopupVisible = false"
              round="10">
      <view class="popup-content">
        <view class="popup-header">
          <text class="popup-title">投入信息详情</text>
          <up-icon name="close"
                   size="20"
                   @click="inputPopupVisible = false"></up-icon>
        </view>
        <scroll-view scroll-y
                     class="popup-scroll">
          <view class="input-list-popup">
            <view v-for="(item, idx) in inputListData"
                  :key="idx"
                  class="input-item">
              <view class="input-row"><text class="label">物料名称:</text><text class="value">{{ item.materialName }}</text></view>
              <view class="input-row"><text class="label">规格型号:</text><text class="value">{{ item.model }}</text></view>
              <view class="input-row"><text class="label">投入数量:</text><text class="value">{{ item.quantity }} {{ item.unit }}</text></view>
              <view class="input-row"><text class="label">批次号:</text><text class="value">{{ item.batchNo }}</text></view>
            </view>
            <view v-if="!inputListData || inputListData.length === 0"
                  class="no-data-minor">{{ inputLoading ? '加载中...' : '暂无投入记录' }}</view>
          </view>
        </scroll-view>
      </view>
    </up-popup>
    <!-- è´¨æ£€è¯¦æƒ…弹窗 -->
    <up-popup :show="qualityPopupVisible"
              mode="bottom"
              @close="qualityPopupVisible = false"
              round="10">
      <view class="popup-content">
        <view class="popup-header">
          <text class="popup-title">质检详情</text>
          <up-icon name="close"
                   size="20"
                   @click="qualityPopupVisible = false"></up-icon>
        </view>
        <scroll-view scroll-y
                     class="popup-scroll">
          <view v-for="(record, idx) in qualityRecords"
                :key="idx"
                class="quality-record">
            <view class="record-title">检测记录 {{ idx + 1 }}</view>
            <view class="info-grid">
              <view class="info-item"><text class="label">检测日期</text><text class="value">{{ formatDate(record.createTime) }}</text></view>
              <view class="info-item"><text class="label">检测结果</text><up-tag style="width:100rpx"
                        :text="record.checkResult || '待检测'"
                        :type="record.checkResult === '合格' ? 'success' : 'error'"
                        size="mini" /></view>
              <view class="info-item"><text class="label">检验员</text><text class="value">{{ record.userName }}</text></view>
              <view class="info-item"><text class="label">数量</text><text class="value">{{ record.quantity }} {{ record.unit }}</text></view>
            </view>
            <view class="params-table">
              <view class="table-header">
                <text class="col">指标</text>
                <text class="col">标准值</text>
                <text class="col">实际值</text>
              </view>
              <view v-for="(param, pIdx) in record.inspectParamList"
                    :key="pIdx"
                    class="table-row">
                <text class="col">{{ param.parameterItem }}</text>
                <text class="col">{{ param.standardValue }}</text>
                <text class="col"
                      :class="{ 'error-text': param.testValue != param.standardValue }">{{ param.testValue }}</text>
              </view>
            </view>
          </view>
          <view v-if="!qualityRecords || qualityRecords.length === 0"
                class="no-data-minor">暂无质检记录</view>
        </scroll-view>
      </view>
    </up-popup>
    <!-- å‚数详情弹窗 -->
    <up-modal :show="paramModalVisible"
              title="参数详情"
              @confirm="paramModalVisible = false">
      <view class="modal-content">
        <view v-for="(param, idx) in currentParams"
              :key="idx"
              class="param-row">
          <text class="label">{{ param.paramName }}:</text>
          <text class="value">{{ param.inputValue }} {{ param.unit && param.unit !== '/' ? param.unit : '' }}</text>
        </view>
        <view v-if="!currentParams || currentParams.length === 0"
              class="no-data-minor">暂无参数数据</view>
      </view>
    </up-modal>
  </view>
</template>
<script setup>
  import { ref, reactive, computed } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import {
    getOrderDetail,
    productOrderListPage,
  } from "@/api/productionManagement/productionOrder";
  import { productionProductInputListPage } from "@/api/productionManagement/productionProductMain";
  import PageHeader from "@/components/PageHeader.vue";
  import { parseTime } from "@/utils/ruoyi";
  // é€‰æ‹©å™¨ç›¸å…³
  const showNpsNoSelector = ref(false);
  const npsNoQuery = ref("");
  const npsNoOptions = ref([]);
  const npsNoLoading = ref(false);
  const selectedNpsNo = ref(null);
  const selectedNpsNoLabel = ref("");
  const rowData = reactive({
    productionOrderDto: null,
    productionRecords: [],
  });
  // æŠ¥å·¥è¯¦æƒ…
  const reportPopupVisible = ref(false);
  const detailData = ref({ workOrder: {}, reports: [] });
  // æŠ•入详情
  const inputPopupVisible = ref(false);
  const inputListData = ref([]);
  const inputLoading = ref(false);
  // è´¨æ£€è¯¦æƒ…
  const qualityPopupVisible = ref(false);
  const qualityRecords = ref([]);
  // å‚数详情
  const paramModalVisible = ref(false);
  const currentParams = ref([]);
  const goBack = () => {
    uni.navigateBack();
  };
  const openNpsNoSelector = () => {
    showNpsNoSelector.value = true;
    if (npsNoOptions.value.length === 0) {
      handleNpsNoSearch();
    }
  };
  const handleNpsNoSearch = async () => {
    npsNoLoading.value = true;
    try {
      const res = await productOrderListPage({
        npsNo: npsNoQuery.value || "",
        pageNum: 1,
        pageSize: 50,
      });
      npsNoOptions.value = res.data?.records || res.rows || [];
    } catch (error) {
      console.error(error);
    } finally {
      npsNoLoading.value = false;
    }
  };
  const onSelectNpsNo = async item => {
    selectedNpsNo.value = item.id;
    selectedNpsNoLabel.value = item.npsNo;
    showNpsNoSelector.value = false;
    uni.showLoading({ title: "加载中..." });
    try {
      const res = await getOrderDetail(item.npsNo);
      if (res.code === 200 && res.data) {
        const { productionOrder, workOrderList } = res.data;
        rowData.productionOrderDto = productionOrder || item;
        rowData.productionRecords = workOrderList || [];
      } else {
        rowData.productionOrderDto = item;
        rowData.productionRecords = [];
      }
    } catch (error) {
      console.error(error);
      rowData.productionOrderDto = item;
      rowData.productionRecords = [];
      uni.showToast({ title: "获取详情失败", icon: "none" });
    } finally {
      uni.hideLoading();
    }
  };
  onLoad(async options => {
    if (options.npsNo) {
      uni.showLoading({ title: "加载中..." });
      try {
        const res = await productOrderListPage({
          npsNo: options.npsNo,
          pageNum: 1,
          pageSize: 10,
        });
        const records = res.data?.records || res.rows || [];
        if (records.length > 0) {
          onSelectNpsNo(records[0]);
        } else {
          uni.showToast({ title: "未找到相关订单", icon: "none" });
        }
      } catch (error) {
        console.error(error);
      } finally {
        uni.hideLoading();
      }
    }
  });
  const getStatusText = status => {
    const statusMap = { 1: "待开始", 2: "进行中", 3: "已完成", 5: "已结束" };
    return statusMap[status] || "已取消";
  };
  const getStatusType = status => {
    const typeMap = { 1: "primary", 2: "warning", 3: "success", 5: "error" };
    return typeMap[status] || "info";
  };
  const formatDate = (date, pattern = "{y}-{m}-{d}") => {
    return parseTime(date, pattern) || "-";
  };
  const formatProgress = val => {
    const p = parseFloat(val || 0);
    return p >= 100 ? 100 : p;
  };
  const progressColor = percentage => {
    if (percentage < 30) return "#f56c6c";
    if (percentage < 70) return "#e6a23c";
    return "#67c23a";
  };
  const handleShowReports = row => {
    detailData.value = {
      workOrder: row.workOrder || {},
      reports: (row.reportList || []).map(r => ({
        ...r.reportMain,
        id: r.reportMain.id,
        productionOperationParamList: r.reportParamList || [],
      })),
    };
    reportPopupVisible.value = true;
  };
  const handleShowInput = async reportId => {
    inputPopupVisible.value = true;
    inputLoading.value = true;
    inputListData.value = [];
    try {
      const res = await productionProductInputListPage({
        productMainId: reportId,
        pageNum: 1,
        pageSize: 100,
      });
      inputListData.value = res.data?.records || res.rows || [];
    } catch (error) {
      console.error(error);
      uni.showToast({ title: "获取投入信息失败", icon: "none" });
    } finally {
      inputLoading.value = false;
    }
  };
  const handleShowQuality = row => {
    const inspects = row.inspectList || [];
    qualityRecords.value = inspects.map(i => ({
      ...i.inspect,
      reportNo: i.reportNo,
      userName: i.reportMain?.userName || "-",
      inspectParamList: i.inspectParamList || [],
    }));
    qualityPopupVisible.value = true;
  };
  const showParams = params => {
    currentParams.value = params || [];
    paramModalVisible.value = true;
  };
</script>
<style scoped lang="scss">
  @import "@/styles/procurement-common.scss";
  .production-traceability {
    min-height: 100vh;
    background-color: #f5f7fa;
  }
  .search-section {
    background-color: #fff;
    padding: 20rpx 24rpx;
    margin-bottom: 20rpx;
  }
  .search-bar {
    display: flex;
    align-items: center;
    background-color: #f2f2f2;
    border-radius: 8rpx;
    padding: 0 20rpx;
    height: 80rpx;
    .search-input {
      flex: 1;
      display: flex;
      align-items: center;
      .placeholder {
        font-size: 28rpx;
        color: #999;
      }
      .value {
        font-size: 28rpx;
        color: #333;
        font-weight: 500;
      }
    }
    .search-button {
      padding: 0 10rpx;
    }
  }
  .selector-popup {
    background: #fff;
    padding: 30rpx;
    max-height: 70vh;
    display: flex;
    flex-direction: column;
    .popup-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 24rpx;
      .popup-title {
        font-size: 32rpx;
        font-weight: bold;
      }
    }
    .search-box {
      margin-bottom: 20rpx;
    }
    .options-list {
      flex: 1;
      overflow: hidden;
    }
    .option-item {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 24rpx 0;
      border-bottom: 1rpx solid #f0f0f0;
      .option-main {
        flex: 1;
        .nps-no {
          font-size: 28rpx;
          font-weight: bold;
          color: #333;
          display: block;
          margin-bottom: 4rpx;
        }
        .product-info {
          font-size: 24rpx;
          color: #999;
        }
      }
    }
    .no-options {
      text-align: center;
      padding: 40rpx;
      color: #999;
      font-size: 26rpx;
    }
  }
  .content-container {
    padding: 0 24rpx 40rpx;
  }
  .info-card {
    background: #fff;
    border-radius: 16rpx;
    padding: 24rpx;
    margin-bottom: 24rpx;
    box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
    .card-title {
      font-size: 32rpx;
      font-weight: bold;
      color: #333;
      margin-bottom: 24rpx;
      padding-left: 16rpx;
      border-left: 8rpx solid #3c9cff;
    }
  }
  .info-grid {
    display: flex;
    flex-wrap: wrap;
    .info-item {
      width: 50%;
      margin-bottom: 20rpx;
      display: flex;
      flex-direction: column;
      &.full-width {
        width: 100%;
      }
      .label {
        font-size: 24rpx;
        color: #999;
        margin-bottom: 8rpx;
      }
      .value {
        font-size: 28rpx;
        color: #333;
        word-break: break-all;
      }
    }
  }
  .progress-container {
    display: flex;
    align-items: center;
    gap: 20rpx;
    up-line-progress {
      flex: 1;
    }
    .progress-text {
      font-size: 24rpx;
      color: #666;
      min-width: 60rpx;
    }
  }
  .section-title {
    font-size: 32rpx;
    font-weight: bold;
    color: #333;
    margin: 32rpx 0 20rpx;
  }
  .work-order-card {
    background: #fff;
    border-radius: 16rpx;
    padding: 24rpx;
    margin-bottom: 20rpx;
    box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
    .card-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 20rpx;
      padding-bottom: 16rpx;
      border-bottom: 1rpx solid #f0f0f0;
      .work-order-no {
        font-size: 28rpx;
        font-weight: bold;
        color: #3c9cff;
      }
      .progress-tag {
        font-size: 28rpx;
        font-weight: bold;
      }
    }
    .card-content {
      .content-row {
        margin-bottom: 12rpx;
        font-size: 26rpx;
        .label {
          color: #999;
        }
        .value {
          color: #333;
        }
      }
    }
    .card-footer {
      display: flex;
      justify-content: flex-end;
      gap: 20rpx;
      margin-top: 20rpx;
    }
  }
  .popup-content {
    background: #fff;
    padding: 30rpx;
    max-height: 80vh;
    display: flex;
    flex-direction: column;
    border-radius: 20rpx 20rpx 0 0;
    .popup-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 30rpx;
      .popup-title {
        font-size: 34rpx;
        font-weight: bold;
        color: #333;
      }
    }
    .popup-scroll {
      flex: 1;
      overflow: hidden;
    }
  }
  .detail-info {
    background: #f8f9fa;
    padding: 24rpx;
    border-radius: 16rpx;
    margin-bottom: 30rpx;
    flex-direction: column;
    .info-row {
      margin-bottom: 12rpx;
      font-size: 28rpx;
      display: flex;
      &:last-child {
        margin-bottom: 0;
      }
      .label {
        color: #999;
        min-width: 140rpx;
      }
      .value {
        color: #333;
        flex: 1;
        font-weight: 500;
      }
    }
  }
  .list-title {
    font-size: 30rpx;
    font-weight: bold;
    margin-bottom: 20rpx;
    color: #333;
    display: flex;
    align-items: center;
    &::before {
      content: "";
      width: 6rpx;
      height: 28rpx;
      background: #3c9cff;
      margin-right: 12rpx;
      border-radius: 4rpx;
    }
  }
  .detail-item {
    background: #fff;
    border: 1rpx solid #f0f0f0;
    border-radius: 12rpx;
    padding: 24rpx;
    margin-bottom: 20rpx;
    display: flex;
    justify-content: space-between;
    align-items: center;
    .item-main {
      flex: 1;
      .item-row {
        font-size: 26rpx;
        margin-bottom: 8rpx;
        display: flex;
        &:last-child {
          margin-bottom: 0;
        }
        .label {
          color: #999;
          min-width: 130rpx;
        }
        .value {
          color: #333;
          flex: 1;
        }
      }
    }
    .item-actions {
      display: flex;
      flex-direction: column;
      gap: 16rpx;
      padding-left: 20rpx;
      border-left: 1rpx solid #f0f0f0;
      .action-link {
        font-size: 26rpx;
        color: #3c9cff;
        white-space: nowrap;
        &.green {
          color: #52c41a;
        }
      }
    }
  }
  .quality-record {
    background: #fff;
    border: 1rpx solid #f0f0f0;
    border-radius: 16rpx;
    padding: 24rpx;
    margin-bottom: 30rpx;
    .record-title {
      font-size: 30rpx;
      font-weight: bold;
      color: #3c9cff;
      margin-bottom: 24rpx;
      display: flex;
      justify-content: space-between;
    }
  }
  .params-table {
    margin-top: 24rpx;
    border: 1rpx solid #f0f0f0;
    border-radius: 12rpx;
    overflow: hidden;
    .table-header {
      display: flex;
      background: #f8f9fa;
      padding: 20rpx 16rpx;
      font-size: 26rpx;
      font-weight: bold;
      color: #666;
    }
    .table-row {
      display: flex;
      padding: 20rpx 16rpx;
      font-size: 26rpx;
      border-top: 1rpx solid #f0f0f0;
      color: #333;
      &:nth-child(even) {
        background: #fafafa;
      }
    }
    .col {
      flex: 1;
      text-align: center;
      word-break: break-all;
    }
  }
  .modal-content {
    padding: 30rpx;
    .param-row {
      margin-bottom: 20rpx;
      font-size: 28rpx;
      display: flex;
      &:last-child {
        margin-bottom: 0;
      }
      .label {
        color: #666;
        min-width: 160rpx;
      }
      .value {
        color: #333;
        font-weight: 500;
        flex: 1;
      }
    }
  }
  .input-list-popup {
    .input-item {
      background: #fff;
      border: 1rpx solid #f0f0f0;
      border-radius: 12rpx;
      padding: 20rpx;
      margin-bottom: 20rpx;
      .input-row {
        display: flex;
        font-size: 26rpx;
        margin-bottom: 8rpx;
        &:last-child {
          margin-bottom: 0;
        }
        .label {
          color: #999;
          min-width: 160rpx;
        }
        .value {
          color: #333;
          flex: 1;
        }
      }
    }
  }
  .error-text {
    color: #f56c6c;
    font-weight: bold;
  }
  .no-data-minor {
    text-align: center;
    padding: 60rpx 40rpx;
    color: #999;
    font-size: 28rpx;
  }
</style>
src/pages/works.vue
@@ -597,6 +597,10 @@
      icon: "/static/images/icon/shengchanbaogong1.svg",
      label: "生产核算",
    },
    {
      icon: "/static/images/icon/shengchanbaogong1.svg",
      label: "生产追溯",
    },
  ]);
  // è®¾å¤‡ç®¡ç†åŠŸèƒ½æ•°æ®
@@ -870,6 +874,11 @@
          url: "/pages/productionManagement/productionAccounting/index",
        });
        break;
      case "生产追溯":
        uni.navigateTo({
          url: "/pages/productionManagement/productionTraceability/index",
        });
        break;
      case "设备台账":
        uni.navigateTo({
          url: "/pages/equipmentManagement/ledger/index",