张诺
7 小时以前 60530101d70fd5887ae1152962882919fceab502
feat(库存管理): 重构库存分类并新增原材料库存页面

- 将库存管理从“合格/不合格”重构为“成品/辅材/原材料”分类
- 新增原材料库存页面 (rawMaterialRecord.vue),使用统一API接口
- 统一库存数据显示格式,增加存货编码、仓库信息等字段
- 在生产报工页面添加机台、计划时间等必填字段
- 移除调试日志,优化代码结构
已添加1个文件
已修改5个文件
364 ■■■■■ 文件已修改
src/pages/inventoryManagement/stockManagement/Qualified.vue 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inventoryManagement/stockManagement/Unqualified.vue 60 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inventoryManagement/stockManagement/index.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inventoryManagement/stockManagement/rawMaterialRecord.vue 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/productionReport/index.vue 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/works.vue 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inventoryManagement/stockManagement/Qualified.vue
@@ -27,7 +27,7 @@
            <text class="item-id">{{ item.productName }}</text>
          </view>
          <view class="item-right">
            <text class="item-tag tag-type">合格库存</text>
            <text class="item-tag tag-type">成品库存</text>
          </view>
        </view>
        
@@ -35,28 +35,28 @@
        <view class="item-details">
          <view class="detail-row">
            <text class="detail-label">规格型号</text>
            <text class="detail-value">{{ item.model }}</text>
            <text class="detail-label">存货/规格</text>
            <text class="detail-value">{{item.productName || "无" }} / {{ item.model || "无"  }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">库存数量</text>
            <text class="detail-value">{{ item.qualitity }} {{ item.unit }}</text>
            <text class="detail-label">主数量/主单位</text>
            <text class="detail-value">{{ item.qualitity || "无"  }} / {{ item.unit || "无"  }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">锁定/冻结</text>
            <text class="detail-value">{{ item.lockedQuantity }} {{ item.unit }}</text>
            <text class="detail-label">辅数量/辅单位</text>
            <text class="detail-value">{{ item.subQualitity || "无"  }} / {{ item.subUnit || "无"  }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">可用库存</text>
            <text class="detail-value" style="color: #2979ff; font-weight: bold;">{{ item.unLockedQuantity }} {{ item.unit }}</text>
            <text class="detail-label">仓库编码/仓库名称</text>
            <text class="detail-value">{{ item.warehouseCode || "无"  }} / {{ item.warehouseName || "无"  }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">库存预警</text>
            <text class="detail-value">{{ item.warnNum }}</text>
            <text class="detail-label">存货编码/库存预警</text>
            <text class="detail-value">{{ item.productCode || "无" }} / {{ item.warnNum || "无" }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">更新时间</text>
            <text class="detail-value">{{ item.updateTime }}</text>
            <text class="detail-value">{{ item.updateTime || "无"  }}</text>
          </view>
        </view>
      </view>
@@ -77,7 +77,7 @@
const loadStatus = ref('loadmore');
const page = reactive({ current: 1, size: 10 });
const total = ref(0);
const searchForm = reactive({ productName: '' });
const searchForm = reactive({ productName: '', rootName: '成品' });
const handleQuery = () => {
  page.current = 1;
@@ -89,7 +89,7 @@
  if (loading.value) return;
  loading.value = true;
  loadStatus.value = 'loading';
  getStockInventoryListPage({ ...searchForm, ...page, type: 'qualified' }).then(res => {
  getStockInventoryListPage({ ...searchForm, ...page }).then(res => {
    loading.value = false;
    const records = res.data.records || [];
    tableData.value = page.current === 1 ? records : [...tableData.value, ...records];
@@ -148,4 +148,26 @@
.no-data {
  padding-top: 100px;
}
.item-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  padding: 5px 0;
}
.fab-button {
  position: fixed;
  right: 20px;
  bottom: 80px;
  width: 50px;
  height: 50px;
  background-color: #2979ff;
  border-radius: 25px;
  display: flex;
  justify-content: center;
  align-items: center;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  z-index: 100;
}
</style>
src/pages/inventoryManagement/stockManagement/Unqualified.vue
@@ -27,7 +27,7 @@
            <text class="item-id">{{ item.productName }}</text>
          </view>
          <view class="item-right">
            <text class="item-tag tag-type" style="background-color: #fde2e2; color: #f56c6c;">不合格库存</text>
           <text class="item-tag tag-type">辅材库存</text>
          </view>
        </view>
        
@@ -35,24 +35,28 @@
        <view class="item-details">
          <view class="detail-row">
            <text class="detail-label">规格型号</text>
            <text class="detail-value">{{ item.model }}</text>
            <text class="detail-label">存货/规格</text>
            <text class="detail-value">{{item.productName || "无" }} / {{ item.model || "无"  }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">库存数量</text>
            <text class="detail-value">{{ item.qualitity }} {{ item.unit }}</text>
            <text class="detail-label">主数量/主单位</text>
            <text class="detail-value">{{ item.qualitity || "无"  }} / {{ item.unit || "无"  }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">锁定/冻结</text>
            <text class="detail-value">{{ item.lockedQuantity }} {{ item.unit }}</text>
            <text class="detail-label">辅数量/辅单位</text>
            <text class="detail-value">{{ item.subQualitity || "无"  }} / {{ item.subUnit || "无"  }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">可用库存</text>
            <text class="detail-value" style="color: #f56c6c; font-weight: bold;">{{ item.unLockedQuantity }} {{ item.unit }}</text>
            <text class="detail-label">仓库编码/仓库名称</text>
            <text class="detail-value">{{ item.warehouseCode || "无"  }} / {{ item.warehouseName || "无"  }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">存货编码/库存预警</text>
            <text class="detail-value">{{ item.productCode || "无" }} / {{ item.warnNum || "无" }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">更新时间</text>
            <text class="detail-value">{{ item.updateTime }}</text>
            <text class="detail-value">{{ item.updateTime || "无"  }}</text>
          </view>
        </view>
      </view>
@@ -66,14 +70,14 @@
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { getStockUninventoryListPage } from "@/api/inventoryManagement/stockUninventory.js";
import { getStockInventoryListPage } from "@/api/inventoryManagement/stockInventory.js";
const tableData = ref([]);
const loading = ref(false);
const loadStatus = ref('loadmore');
const page = reactive({ current: 1, size: 10 });
const total = ref(0);
const searchForm = reactive({ productName: '' });
const searchForm = reactive({ productName: '', rootName: '辅材' });
const handleQuery = () => {
  page.current = 1;
@@ -85,7 +89,7 @@
  if (loading.value) return;
  loading.value = true;
  loadStatus.value = 'loading';
  getStockUninventoryListPage({ ...searchForm, ...page, type: 'unqualified' }).then(res => {
  getStockInventoryListPage({ ...searchForm, ...page }).then(res => {
    loading.value = false;
    const records = res.data.records || [];
    tableData.value = page.current === 1 ? records : [...tableData.value, ...records];
@@ -131,4 +135,34 @@
.no-data {
  padding-top: 100px;
}
.tag-type {
  background-color: #e8f5e9;
  color: #4caf50;
  padding: 2px 8px;
  border-radius: 4px;
  font-size: 12px;
}
.item-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  padding: 5px 0;
}
.fab-button {
  position: fixed;
  right: 20px;
  bottom: 80px;
  width: 50px;
  height: 50px;
  background-color: #2979ff;
  border-radius: 25px;
  display: flex;
  justify-content: center;
  align-items: center;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  z-index: 100;
}
</style>
src/pages/inventoryManagement/stockManagement/index.vue
@@ -9,6 +9,9 @@
      <swiper-item class="swiper-item">
        <unqualified-record />
      </swiper-item>
      <swiper-item class="swiper-item">
        <raw-material-record />
      </swiper-item>
    </swiper>
  </view>
</template>
@@ -18,11 +21,13 @@
import PageHeader from "@/components/PageHeader.vue";
import QualifiedRecord from "./Qualified.vue";
import UnqualifiedRecord from "./Unqualified.vue";
import RawMaterialRecord from "./rawMaterialRecord.vue";
const activeTab = ref(0);
const tabs = ref([
  { name: '合格库存' },
  { name: '不合格库存' }
  { name: '成品库存' },
  { name: '辅材库存' },
  { name: '原材料库存' }
]);
const handleTabClick = (item) => {
src/pages/inventoryManagement/stockManagement/rawMaterialRecord.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,151 @@
<template>
  <view class="raw-material-record-container">
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input
            class="search-text"
            placeholder="请输入产品大类"
            v-model="searchForm.productName"
            @confirm="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="ledger-list" v-if="tableData.length > 0" @scrolltolower="loadMore">
      <view v-for="item in tableData" :key="item.id" class="ledger-item" :class="{ 'low-stock': isLowStock(item) }">
        <view class="item-header">
          <view class="item-left">
            <view class="document-icon">
              <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
            </view>
            <text class="item-id">{{ item.productName }}</text>
          </view>
          <view class="item-right">
            <text class="item-tag tag-type">原材料库存</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.productName || "无" }} / {{ item.model || "无"  }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">主数量/主单位</text>
            <text class="detail-value">{{ item.qualitity || "无"  }} / {{ item.unit || "无"  }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">辅数量/辅单位</text>
            <text class="detail-value">{{ item.subQualitity || "无"  }} / {{ item.subUnit || "无"  }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">仓库编码/仓库名称</text>
            <text class="detail-value">{{ item.warehouseCode || "无"  }} / {{ item.warehouseName || "无"  }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">存货编码/库存预警</text>
            <text class="detail-value">{{ item.productCode || "无" }} / {{ item.warnNum || "无" }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">更新时间</text>
            <text class="detail-value">{{ item.updateTime || "无"  }}</text>
          </view>
        </view>
      </view>
      <up-loadmore :status="loadStatus" />
    </scroll-view>
    <view v-else-if="!loading" class="no-data">
      <up-empty mode="data" text="暂无原材料库存数据"></up-empty>
    </view>
  </view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { getStockInventoryListPage } from "@/api/inventoryManagement/stockInventory.js";
const tableData = ref([]);
const loading = ref(false);
const loadStatus = ref('loadmore');
const page = reactive({ current: 1, size: 10 });
const total = ref(0);
const searchForm = reactive({ productName: '', rootName: '原材料' });
const handleQuery = () => {
  page.current = 1;
  tableData.value = [];
  getList();
};
const getList = () => {
  if (loading.value) return;
  loading.value = true;
  loadStatus.value = 'loading';
  getStockInventoryListPage({ ...searchForm, ...page }).then(res => {
    loading.value = false;
    const records = res.data.records || [];
    tableData.value = page.current === 1 ? records : [...tableData.value, ...records];
    total.value = res.data.total;
    loadStatus.value = tableData.value.length >= total.value ? 'nomore' : 'loadmore';
  }).catch(() => {
    loading.value = false;
    loadStatus.value = 'loadmore';
  });
};
const loadMore = () => {
  if (loadStatus.value === 'nomore' || loading.value) return;
  page.current++;
  getList();
};
const isLowStock = (row) => {
  const stock = Number(row?.unLockedQuantity ?? 0);
  const warn = Number(row?.warnNum ?? 0);
  return Number.isFinite(stock) && Number.isFinite(warn) && stock < warn;
};
onMounted(() => {
  getList();
});
</script>
<style scoped lang="scss">
@import '@/styles/sales-common.scss';
.raw-material-record-container {
  height: 100%;
  display: flex;
  flex-direction: column;
}
.tag-type {
  background-color: #e8f5e9;
  color: #4caf50;
  padding: 2px 8px;
  border-radius: 4px;
  font-size: 12px;
}
.ledger-list {
  flex: 1;
  overflow-y: auto;
}
.low-stock {
  background-color: #fde2e2;
  color: #c45656;
}
.no-data {
  padding-top: 100px;
}
</style>
src/pages/productionManagement/productionReport/index.vue
@@ -11,6 +11,31 @@
            error-message-align="right">
      <!-- åŸºæœ¬ä¿¡æ¯ -->
      <view class="form-section">
        <u-form-item label="机台"
                     prop="deviceName"
                     required>
          <u-input v-model="form.deviceName"
          disabled
                   placeholder="请输入机台" />
        </u-form-item>
        <u-form-item label="计划开始时间"
                     prop="planStartTime"
                     required>
          <u-input v-model="form.planStartTime"
                   placeholder="请选择计划开始时间"
                   readonly
                   @click="showStartTimePicker = true"
                   suffix-icon="calendar" />
        </u-form-item>
        <u-form-item label="计划结束时间"
                     prop="planEndTime"
                     required>
          <u-input v-model="form.planEndTime"
                   placeholder="请选择计划结束时间"
                   readonly
                   @click="showEndTimePicker = true"
                   suffix-icon="calendar" />
        </u-form-item>
        <u-form-item label="待生产数量"
                     prop="planQuantity"
                     required>
@@ -57,6 +82,20 @@
                     title="选择生产人"
                     @select="onProducerConfirm"
                     @close="showProducerPicker = false" />
    <!-- å¼€å§‹æ—¶é—´é€‰æ‹©å™¨ -->
    <up-datetime-picker :show="showStartTimePicker"
                        v-model="startTimeValue"
                        mode="datetime"
                        @confirm="onStartTimeConfirm"
                        @cancel="showStartTimePicker = false" />
    <!-- ç»“束时间选择器 -->
    <up-datetime-picker :show="showEndTimePicker"
                        v-model="endTimeValue"
                        mode="datetime"
                        @confirm="onEndTimeConfirm"
                        @cancel="showEndTimePicker = false" />
  </view>
</template>
@@ -80,6 +119,9 @@
  // è¡¨å•数据
  const form = ref({
    deviceName: "",
    planStartTime: "",
    planEndTime: "",
    planQuantity: "",
    quantity: "",
    scrapQty: "",
@@ -97,6 +139,12 @@
  // ç”Ÿäº§äººé€‰æ‹©å™¨çŠ¶æ€
  const showProducerPicker = ref(false);
  // æ—¶é—´é€‰æ‹©å™¨çŠ¶æ€
  const showStartTimePicker = ref(false);
  const showEndTimePicker = ref(false);
  const startTimeValue = ref(Number(new Date()));
  const endTimeValue = ref(Number(new Date()));
  const producerList = ref([]);
  const currentField = ref(""); // å½“前选择的字段
@@ -131,6 +179,30 @@
    showProducerPicker.value = false;
  };
  // æ ¼å¼åŒ–日期
  const formatDateTime = (timestamp) => {
    const date = new Date(timestamp);
    const y = date.getFullYear();
    const m = (date.getMonth() + 1).toString().padStart(2, '0');
    const d = date.getDate().toString().padStart(2, '0');
    const h = date.getHours().toString().padStart(2, '0');
    const min = date.getMinutes().toString().padStart(2, '0');
    const s = date.getSeconds().toString().padStart(2, '0');
    return `${y}-${m}-${d} ${h}:${min}:${s}`;
  };
  // å¼€å§‹æ—¶é—´ç¡®è®¤
  const onStartTimeConfirm = (e) => {
    form.value.planStartTime = formatDateTime(e.value);
    showStartTimePicker.value = false;
  };
  // ç»“束时间确认
  const onEndTimeConfirm = (e) => {
    form.value.planEndTime = formatDateTime(e.value);
    showEndTimePicker.value = false;
  };
  // æäº¤çŠ¶æ€
  const submitting = ref(false);
@@ -142,6 +214,21 @@
  const submitForm = async () => {
    submitting.value = true;
    // æ ¡éªŒè¡¨å•
    if (!form.value.deviceName) {
      submitting.value = false;
      showToast("请输入机台");
      return;
    }
    if (!form.value.planStartTime) {
      submitting.value = false;
      showToast("请选择计划开始时间");
      return;
    }
    if (!form.value.planEndTime) {
      submitting.value = false;
      showToast("请选择计划结束时间");
      return;
    }
    if (!form.value.quantity) {
      submitting.value = false;
      showToast("请输入本次生产数量");
@@ -202,10 +289,14 @@
  onLoad(options => {
    try {
      const orderRow = JSON.parse(options.orderRow);
      console.log(orderRow, "orderRow");
      // ç¡®ä¿ planQuantity è½¬æ¢ä¸ºå­—符串,以便在 u-input ä¸­æ­£ç¡®æ˜¾ç¤º
      form.value.planQuantity = orderRow.planQuantity != null ? String(orderRow.planQuantity) : "";
      form.value.productProcessRouteItemId = orderRow.productProcessRouteItemId || "";
      form.value.workOrderId = orderRow.id || "";
      form.value.deviceName = orderRow.deviceName || "";
      form.value.planStartTime = orderRow.planStartTime || "";
      form.value.planEndTime = orderRow.planEndTime || "";
      getInfo().then(res => {
        // é»˜è®¤ä½¿ç”¨å½“前登录用户,但允许用户修改
        form.value.userId.value = res.user.userId;
src/pages/works.vue
@@ -958,13 +958,12 @@
              orderRow = JSON.stringify({
                id: workData.id || workOrderId,
                planQuantity: workData.planQuantity - workData.completeQuantity,
                deviceName:workData.deviceName,
                productProcessRouteItemId:
                  workData.productProcessRouteItemId ||
                  workData.产品工艺路线项ID ||
                  "",
              });
              console.log("构造的orderRow:", orderRow);
            } else {
              modal.msgError("未找到对应的工单信息");
              return;