zhangwencui
2026-05-15 2379443c468dadd3edb9184be4e9119359e6b996
库存管理重构
已添加1个文件
已修改2个文件
已删除2个文件
719 ■■■■ 文件已修改
src/api/inventoryManagement/stockInventory.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inventoryManagement/stockManagement/Qualified.vue 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inventoryManagement/stockManagement/Record.vue 292 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inventoryManagement/stockManagement/Unqualified.vue 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inventoryManagement/stockManagement/index.vue 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockInventory.js
@@ -8,6 +8,15 @@
    });
};
// åˆ†é¡µæŸ¥è¯¢è”合库存记录列表(包含商品信息)
export const getStockInventoryListPageCombined = (params) => {
    return request({
        url: "/stockInventory/pageListCombinedStockInventory",
        method: "get",
        params,
    });
};
// åˆ›å»ºåº“存记录
export const createStockInventory = (params) => {
    return request({
src/pages/inventoryManagement/stockManagement/Qualified.vue
ÎļþÒÑɾ³ý
src/pages/inventoryManagement/stockManagement/Record.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,292 @@
<template>
  <view class="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">
        <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>
        <up-divider></up-divider>
        <view class="item-details">
          <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="detail-row">
            <text class="detail-label">批号</text>
            <text class="detail-value">{{ item.batchNo }}</text>
          </view>
          <view class="quantity-section">
            <view class="quantity-box qualified">
              <text class="q-label">合格库存</text>
              <text class="q-value">{{ item.qualifiedQuantity }}</text>
            </view>
            <view class="quantity-box unqualified">
              <text class="q-label">不合格库存</text>
              <text class="q-value">{{ item.unQualifiedQuantity }}</text>
            </view>
          </view>
          <view class="quantity-section">
            <view class="quantity-box locked">
              <text class="q-label">合格冻结</text>
              <text class="q-value">{{ item.qualifiedLockedQuantity }}</text>
            </view>
            <view class="quantity-box locked">
              <text class="q-label">不合格冻结</text>
              <text class="q-value">{{ item.unQualifiedLockedQuantity }}</text>
            </view>
          </view>
          <view class="detail-row">
            <text class="detail-label">库存预警</text>
            <text class="detail-value">{{ item.warnNum }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">备注</text>
            <text class="detail-value">{{ item.remark || '-' }}</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 { getStockInventoryListPageCombined } from "@/api/inventoryManagement/stockInventory.js";
const props = defineProps({
  productId: {
    type: Number,
    required: true
  }
});
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: '',
  topParentProductId: props.productId
});
const handleQuery = () => {
  page.current = 1;
  tableData.value = [];
  getList();
};
const getList = () => {
  if (loading.value) return;
  loading.value = true;
  loadStatus.value = 'loading';
  getStockInventoryListPageCombined({
    ...searchForm,
    current: page.current,
    size: page.size
  }).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 === 'loadmore') {
    page.current++;
    getList();
  }
};
onMounted(() => {
  getList();
});
</script>
<style scoped lang="scss">
.record-container {
  height: 100%;
  display: flex;
  flex-direction: column;
  background-color: #f5f7fa;
}
.search-section {
  padding: 20rpx;
  background-color: #ffffff;
  position: sticky;
  top: 0;
  z-index: 10;
}
.search-bar {
  display: flex;
  align-items: center;
  background-color: #f2f2f2;
  border-radius: 40rpx;
  padding: 0 30rpx;
  height: 80rpx;
}
.search-input {
  flex: 1;
}
.search-text {
  font-size: 28rpx;
}
.filter-button {
  padding-left: 20rpx;
}
.ledger-list {
  flex: 1;
  padding: 20rpx;
  box-sizing: border-box;
}
.ledger-item {
  background-color: #ffffff;
  border-radius: 16rpx;
  padding: 30rpx;
  margin-bottom: 20rpx;
  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
}
.item-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20rpx;
}
.item-left {
  display: flex;
  align-items: center;
}
.document-icon {
  width: 40rpx;
  height: 40rpx;
  background: linear-gradient(135deg, #2979ff, #1565c0);
  border-radius: 8rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: 16rpx;
}
.item-id {
  font-size: 30rpx;
  font-weight: bold;
  color: #303133;
}
.item-details {
  .detail-row {
    display: flex;
    justify-content: space-between;
    margin-bottom: 16rpx;
    font-size: 26rpx;
    .detail-label {
      color: #909399;
    }
    .detail-value {
      color: #303133;
      font-weight: 500;
    }
  }
}
.quantity-section {
  display: flex;
  gap: 20rpx;
  margin: 20rpx 0;
  .quantity-box {
    flex: 1;
    padding: 16rpx;
    border-radius: 8rpx;
    display: flex;
    flex-direction: column;
    align-items: center;
    .q-label {
      font-size: 22rpx;
      margin-bottom: 8rpx;
    }
    .q-value {
      font-size: 32rpx;
      font-weight: bold;
    }
    &.qualified {
      background-color: #ecf5ff;
      color: #409eff;
    }
    &.unqualified {
      background-color: #fef0f0;
      color: #f56c6c;
    }
    &.locked {
      background-color: #f4f4f5;
      color: #909399;
    }
  }
}
.no-data {
  padding-top: 200rpx;
}
</style>
src/pages/inventoryManagement/stockManagement/Unqualified.vue
ÎļþÒÑɾ³ý
src/pages/inventoryManagement/stockManagement/index.vue
@@ -1,57 +1,104 @@
<template>
  <view class="app-container">
    <PageHeader title="库存管理" @back="goBack" />
    <up-tabs :list="tabs" @click="handleTabClick" :current="activeTab"/>
    <swiper class="swiper-box" :current="activeTab" @change="handleSwiperChange">
      <swiper-item class="swiper-item">
        <qualified-record />
      </swiper-item>
      <swiper-item class="swiper-item">
        <unqualified-record />
      </swiper-item>
    </swiper>
    <PageHeader title="库存管理"
                @back="goBack" />
    <view v-if="loading"
          class="loading-state">
      <up-loading-icon text="加载中..."></up-loading-icon>
    </view>
    <template v-else>
      <up-tabs :list="tabs"
               @click="handleTabClick"
               :current="activeTab" />
      <swiper class="swiper-box"
              :current="activeTab"
              @change="handleSwiperChange">
        <swiper-item class="swiper-item"
                     v-for="tab in products"
                     :key="tab.id">
          <record :product-id="tab.id"
                  v-if="activeTab === products.indexOf(tab) || initializedTabs.includes(tab.id)" />
        </swiper-item>
      </swiper>
    </template>
  </view>
</template>
<script setup>
import { ref } from 'vue';
import PageHeader from "@/components/PageHeader.vue";
import QualifiedRecord from "./Qualified.vue";
import UnqualifiedRecord from "./Unqualified.vue";
  import { ref, onMounted } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import Record from "./Record.vue";
  import { productTreeList } from "@/api/basicData/product.js";
const activeTab = ref(0);
const tabs = ref([
  { name: '合格库存' },
  { name: '不合格库存' }
]);
  const activeTab = ref(0);
  const tabs = ref([]);
  const products = ref([]);
  const loading = ref(false);
  const initializedTabs = ref([]);
const handleTabClick = (item) => {
  activeTab.value = item.index;
};
  const handleTabClick = item => {
    activeTab.value = item.index;
    if (!initializedTabs.value.includes(products.value[item.index].id)) {
      initializedTabs.value.push(products.value[item.index].id);
    }
  };
const handleSwiperChange = (e) => {
  activeTab.value = e.detail.current;
};
  const handleSwiperChange = e => {
    const index = e.detail.current;
    activeTab.value = index;
    if (!initializedTabs.value.includes(products.value[index].id)) {
      initializedTabs.value.push(products.value[index].id);
    }
  };
const goBack = () => {
  uni.navigateBack();
};
  const fetchProducts = async () => {
    loading.value = true;
    try {
      const res = await productTreeList();
      // è¿‡æ»¤æ ¹èŠ‚ç‚¹äº§å“
      products.value = res
        .filter(item => item.parentId === null)
        .map(({ id, productName }) => ({ id, productName }));
      tabs.value = products.value.map(p => ({ name: p.productName }));
      if (products.value.length > 0) {
        activeTab.value = 0;
        initializedTabs.value = [products.value[0].id];
      }
    } finally {
      loading.value = false;
    }
  };
  const goBack = () => {
    uni.navigateBack();
  };
  onMounted(() => {
    fetchProducts();
  });
</script>
<style scoped lang="scss">
.app-container {
  display: flex;
  flex-direction: column;
  height: 100vh;
  background-color: #f8f9fa;
}
.swiper-box {
  flex: 1;
}
.swiper-item {
  height: 100%;
}
:deep(.up-tabs) {
  background-color: #fff;
}
  .app-container {
    display: flex;
    flex-direction: column;
    height: 100vh;
    background-color: #f8f9fa;
  }
  .loading-state {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .swiper-box {
    flex: 1;
  }
  .swiper-item {
    height: 100%;
  }
  :deep(.up-tabs) {
    background-color: #fff;
  }
</style>