zhangwencui
2026-05-06 66a61e5084be68c8f7aa72f28607ef7d1664fb06
生产核算模块开发
已添加1个文件
已修改3个文件
550 ■■■■■ 文件已修改
src/api/productionManagement/productionCosting.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/productionAccounting/index.vue 498 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/works.vue 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionCosting.js
@@ -8,4 +8,22 @@
    method: "get",
    params: query,
  });
}
// å·¦è¾¹è¡¨æ ¼çš„æŽ¥å£ (汇总)
export function salesLedgerProductionAccountingList(query) {
  return request({
    url: "/productionAccount/listPage",
    method: "get",
    params: query,
  });
}
// å³è¾¹è¡¨æ ¼çš„æŽ¥å£ (明细)
export function salesLedgerProductionAccountingListProductionDetails(query) {
  return request({
    url: "/productionAccount/listProductionDetails",
    method: "get",
    params: query,
  });
}
src/pages.json
@@ -858,13 +858,13 @@
        "navigationStyle": "custom"
      }
    },
    // {
    //   "path": "pages/productionManagement/productionCosting/index",
    //   "style": {
    //     "navigationBarTitleText": "生产核算",
    //     "navigationStyle": "custom"
    //   }
    // },
    {
      "path": "pages/productionManagement/productionAccounting/index",
      "style": {
        "navigationBarTitleText": "生产核算",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/inventoryManagement/receiptManagement/index",
      "style": {
src/pages/productionManagement/productionAccounting/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,498 @@
<template>
  <view class="production-accounting">
    <PageHeader title="生产核算"
                @back="goBack" />
    <!-- ç­›é€‰åŒºåŸŸ -->
    <view class="filter-section">
      <view class="date-type-selector">
        <up-tabs :list="dateTypeList"
                 :current="currentDateTypeIndex"
                 @change="handleDateTypeChange"
                 :activeStyle="{ color: '#2979ff', fontWeight: 'bold' }"
                 lineWidth="30"
                 lineHeight="3" />
      </view>
      <view class="date-picker-bar"
            @click="showDatePicker = true">
        <view class="date-display">
          <up-icon name="calendar"
                   size="20"
                   color="#2979ff"></up-icon>
          <text class="date-text">{{ dateDisplayText }}</text>
        </view>
        <up-icon name="arrow-right"
                 size="16"
                 color="#999"></up-icon>
      </view>
    </view>
    <!-- æ±‡æ€»åˆ—表 -->
    <view class="summary-section"
          v-if="!showDetail">
      <view class="section-header">
        <text class="section-title">生产人员汇总</text>
      </view>
      <view class="ledger-list"
            v-if="summaryList.length > 0">
        <view v-for="(item, index) in summaryList"
              :key="index"
              class="ledger-item"
              @click="handleRowClick(item)">
          <view class="item-header">
            <view class="item-left">
              <view class="user-icon">
                <up-icon name="account"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-id">{{ item.schedulingUserName || '未知' }}</text>
            </view>
            <view class="item-right">
              <up-icon name="arrow-right"
                       size="16"
                       color="#999"></up-icon>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-grid">
              <view class="grid-item">
                <text class="grid-label">产量</text>
                <text class="grid-value">{{ item.finishedNum || 0 }}</text>
              </view>
              <view class="grid-item">
                <text class="grid-label">工资</text>
                <text class="grid-value highlight">Â¥{{ item.wages || 0 }}</text>
              </view>
              <view class="grid-item">
                <text class="grid-label">合格率</text>
                <text class="grid-value">{{ formatOutputRate(item.outputRate) }}</text>
              </view>
            </view>
          </view>
        </view>
      </view>
      <view v-else
            class="no-data">
        <up-empty mode="data"
                  text="暂无汇总数据" />
      </view>
    </view>
    <!-- æ˜Žç»†åˆ—表 (点击汇总行后显示) -->
    <view class="detail-section"
          v-else>
      <view class="section-header back-bar"
            @click="showDetail = false">
        <up-icon name="arrow-left"
                 size="16"
                 color="#2979ff"></up-icon>
        <text class="back-text">返回汇总 ({{ currentUserName }})</text>
      </view>
      <view class="ledger-list"
            v-if="detailList.length > 0">
        <view v-for="(item, index) in detailList"
              :key="index"
              class="ledger-item no-click">
          <view class="item-header">
            <view class="item-left">
              <view class="product-icon">
                <up-icon name="shopping-cart"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-id">{{ item.productName }}</text>
            </view>
            <view class="item-tag">
              <text class="tag-text">{{ item.schedulingDate }}</text>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">规格型号</text>
              <text class="detail-value">{{ item.productModelName }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">工序</text>
              <text class="detail-value">{{ item.process }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">生产数量</text>
              <text class="detail-value">{{ item.quantity }} {{ item.unit }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">工时定额</text>
              <text class="detail-value">{{ item.workHours }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">工资</text>
              <text class="detail-value highlight">Â¥{{ item.wages }}</text>
            </view>
          </view>
        </view>
        <up-loadmore :status="loadStatus"
                     @loadmore="getDetailList" />
      </view>
      <view v-else
            class="no-data">
        <up-empty mode="data"
                  text="暂无明细数据" />
      </view>
    </view>
    <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
    <up-datetime-picker :show="showDatePicker"
                        v-model="pickerValue"
                        :mode="currentDateType === 'day' ? 'date' : 'year-month'"
                        @confirm="handleDateConfirm"
                        @cancel="showDatePicker = false" />
  </view>
</template>
<script setup>
  import { ref, reactive, computed, onMounted } from "vue";
  import {
    salesLedgerProductionAccountingList,
    salesLedgerProductionAccountingListProductionDetails,
  } from "@/api/productionManagement/productionCosting";
  import PageHeader from "@/components/PageHeader.vue";
  import dayjs from "dayjs";
  // ç­›é€‰ç›¸å…³
  const dateTypeList = [{ name: "日" }, { name: "月" }];
  const currentDateTypeIndex = ref(0);
  const currentDateType = computed(() =>
    currentDateTypeIndex.value === 0 ? "day" : "month"
  );
  const showDatePicker = ref(false);
  const pickerValue = ref(Date.now());
  const selectedDate = ref(dayjs().format("YYYY-MM-DD"));
  const dateDisplayText = computed(() => {
    return currentDateType.value === "day"
      ? selectedDate.value
      : dayjs(selectedDate.value).format("YYYY-MM");
  });
  // æ•°æ®ç›¸å…³
  const summaryList = ref([]);
  const detailList = ref([]);
  const showDetail = ref(false);
  const currentUserName = ref("");
  const loadStatus = ref("loadmore");
  const page = reactive({
    current: 1,
    size: 20,
    total: 0,
  });
  const page1 = reactive({
    current: 1,
    size: 20,
    total: 0,
  });
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // åˆ‡æ¢æ—¥æœŸç±»åž‹
  const handleDateTypeChange = index => {
    currentDateTypeIndex.value = index.index;
    if (currentDateType.value === "day") {
      selectedDate.value = dayjs().format("YYYY-MM-DD");
    } else {
      selectedDate.value = dayjs().startOf("month").format("YYYY-MM-DD");
    }
    reloadData();
  };
  // ç¡®è®¤æ—¥æœŸé€‰æ‹©
  const handleDateConfirm = e => {
    selectedDate.value = dayjs(e.value).format("YYYY-MM-DD");
    showDatePicker.value = false;
    reloadData();
  };
  // æ ¼å¼åŒ–合格率
  const formatOutputRate = val => {
    if (val == null || val === "") return "-";
    return parseFloat(val).toFixed(2) + "%";
  };
  // åŠ è½½æ±‡æ€»åˆ—è¡¨
  const getSummaryList = () => {
    uni.showLoading({ title: "加载中..." });
    const params = {
      dateType: currentDateType.value,
      entryDate: currentDateType.value === "day" ? selectedDate.value : undefined,
      entryDateStart:
        currentDateType.value === "month"
          ? dayjs(selectedDate.value).startOf("month").format("YYYY-MM-DD")
          : undefined,
      entryDateEnd:
        currentDateType.value === "month"
          ? dayjs(selectedDate.value).endOf("month").format("YYYY-MM-DD")
          : undefined,
      pageNum: page.current,
      pageSize: page.size,
    };
    salesLedgerProductionAccountingList(params)
      .then(res => {
        summaryList.value = res.data.records || [];
        page.total = res.data.total || 0;
      })
      .finally(() => {
        uni.hideLoading();
      });
  };
  // åŠ è½½æ˜Žç»†åˆ—è¡¨
  const getDetailList = (isLoadMore = false) => {
    if (!isLoadMore) {
      page1.current = 1;
      detailList.value = [];
    }
    loadStatus.value = "loading";
    const params = {
      schedulingUserName: currentUserName.value,
      dateType: currentDateType.value,
      entryDate: currentDateType.value === "day" ? selectedDate.value : undefined,
      entryDateStart:
        currentDateType.value === "month"
          ? dayjs(selectedDate.value).startOf("month").format("YYYY-MM-DD")
          : undefined,
      entryDateEnd:
        currentDateType.value === "month"
          ? dayjs(selectedDate.value).endOf("month").format("YYYY-MM-DD")
          : undefined,
      pageNum: page1.current,
      pageSize: page1.size,
    };
    salesLedgerProductionAccountingListProductionDetails(params)
      .then(res => {
        const records = res.data.records || [];
        detailList.value = isLoadMore ? [...detailList.value, ...records] : records;
        page1.total = res.data.total || 0;
        if (detailList.value.length >= page1.total) {
          loadStatus.value = "nomore";
        } else {
          loadStatus.value = "loadmore";
          page1.current++;
        }
      })
      .catch(() => {
        loadStatus.value = "loadmore";
      });
  };
  // ç‚¹å‡»æ±‡æ€»è¡Œ
  const handleRowClick = item => {
    currentUserName.value = item.schedulingUserName;
    showDetail.value = true;
    getDetailList();
  };
  // é‡æ–°åŠ è½½æ•°æ®
  const reloadData = () => {
    page.current = 1;
    showDetail.value = false;
    getSummaryList();
  };
  onMounted(() => {
    getSummaryList();
  });
</script>
<style scoped lang="scss">
  .production-accounting {
    background-color: #f5f7fa;
    min-height: 100vh;
    padding-bottom: 30rpx;
    .filter-section {
      background-color: #ffffff;
      padding: 20rpx 30rpx;
      margin-bottom: 20rpx;
      .date-type-selector {
        margin-bottom: 20rpx;
      }
      .date-picker-bar {
        display: flex;
        justify-content: space-between;
        align-items: center;
        background-color: #f0f4ff;
        padding: 16rpx 24rpx;
        border-radius: 8rpx;
        .date-display {
          display: flex;
          align-items: center;
          gap: 12rpx;
          .date-text {
            font-size: 28rpx;
            color: #2979ff;
            font-weight: bold;
          }
        }
      }
    }
    .section-header {
      padding: 20rpx 30rpx;
      .section-title {
        font-size: 30rpx;
        font-weight: bold;
        color: #333;
        border-left: 8rpx solid #2979ff;
        padding-left: 16rpx;
      }
      &.back-bar {
        display: flex;
        align-items: center;
        gap: 10rpx;
        background-color: #ffffff;
        margin-bottom: 20rpx;
        .back-text {
          font-size: 28rpx;
          color: #2979ff;
        }
      }
    }
    .ledger-list {
      padding: 0 20rpx;
      .ledger-item {
        background-color: #ffffff;
        border-radius: 16rpx;
        padding: 24rpx;
        margin-bottom: 20rpx;
        box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
        &:active {
          background-color: #f9f9f9;
        }
        &.no-click:active {
          background-color: #ffffff;
        }
        .item-header {
          display: flex;
          justify-content: space-between;
          align-items: center;
          margin-bottom: 16rpx;
          .item-left {
            display: flex;
            align-items: center;
            gap: 12rpx;
            .user-icon,
            .product-icon {
              width: 48rpx;
              height: 48rpx;
              background: linear-gradient(135deg, #2979ff, #64a1ff);
              border-radius: 8rpx;
              display: flex;
              align-items: center;
              justify-content: center;
            }
            .item-id {
              font-size: 28rpx;
              font-weight: bold;
              color: #333;
            }
          }
          .item-tag {
            background-color: #f0f4ff;
            padding: 4rpx 12rpx;
            border-radius: 4rpx;
            .tag-text {
              font-size: 24rpx;
              color: #2979ff;
            }
          }
        }
        .item-details {
          padding-top: 10rpx;
          .detail-grid {
            display: flex;
            justify-content: space-between;
            .grid-item {
              display: flex;
              flex-direction: column;
              align-items: center;
              flex: 1;
              .grid-label {
                font-size: 24rpx;
                color: #999;
                margin-bottom: 8rpx;
              }
              .grid-value {
                font-size: 28rpx;
                color: #333;
                font-weight: 500;
                &.highlight {
                  color: #ff5a5f;
                }
              }
            }
          }
          .detail-row {
            display: flex;
            justify-content: space-between;
            margin-bottom: 12rpx;
            .detail-label {
              font-size: 26rpx;
              color: #999;
            }
            .detail-value {
              font-size: 26rpx;
              color: #333;
              &.highlight {
                color: #ff5a5f;
                font-weight: bold;
              }
            }
          }
        }
      }
    }
    .no-data {
      padding-top: 100rpx;
    }
  }
</style>
src/pages/works.vue
@@ -570,7 +570,7 @@
  // ç”Ÿäº§ç®¡æŽ§åŠŸèƒ½æ•°æ®
  const productionItems = reactive([
    {
      icon: "/static/images/icon/shengchanbaogong.svg",
      icon: "/static/images/icon/shengchanbaogong1.svg",
      label: "生产订单",
    },
    // {
@@ -578,11 +578,11 @@
    //   label: "生产派工",
    // },
    {
      icon: "/static/images/icon/shengchanbaogong.svg",
      icon: "/static/images/icon/shengchanbaogong1.svg",
      label: "生产排产",
    },
    {
      icon: "/static/images/icon/shengchanbaogong.svg",
      icon: "/static/images/icon/shengchanbaogong1.svg",
      label: "主生产计划",
    },
    {
@@ -590,17 +590,13 @@
      label: "生产报工",
    },
    {
      icon: "/static/images/icon/xiaoshoutaizhang.svg",
      icon: "/static/images/icon/shengchanbaogong1.svg",
      label: "报工台账",
    },
    // {
    //   icon: "/static/images/icon/shengchanbaogong.svg",
    //   label: "生产工单",
    // },
    // {
    //   icon: "/static/images/icon/shengchanhesuan@2x.svg",
    //   label: "生产核算",
    // },
    {
      icon: "/static/images/icon/shengchanbaogong1.svg",
      label: "生产核算",
    },
  ]);
  // è®¾å¤‡ç®¡ç†åŠŸèƒ½æ•°æ®