zhangwencui
5 天以前 88022fc18a3dddb2e1181eaf08e0e155cdb3094b
库存管理完善
已修改1个文件
567 ■■■■■ 文件已修改
src/pages/inventoryManagement/stockManagement/Record.vue 567 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inventoryManagement/stockManagement/Record.vue
@@ -3,33 +3,38 @@
    <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
          />
          <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 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">
    <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>
              <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>
@@ -39,11 +44,20 @@
            <text class="detail-label">单位</text>
            <text class="detail-value">{{ item.unit }}</text>
          </view>
          <view class="detail-row">
          <view class="detail-row"
                @click="handleShowBatch(item.batchNo)">
            <text class="detail-label">批号</text>
            <text class="detail-value">{{ item.batchNo }}</text>
            <view class="detail-value batch-no-wrapper">
              <text class="batch-no-text"
                    :class="{ 'clickable': isBatchClickable(item.batchNo) }">
                {{ formatBatchNo(item.batchNo) }}
              </text>
              <up-icon v-if="isBatchClickable(item.batchNo)"
                       name="arrow-right"
                       size="14"
                       color="#2979ff"></up-icon>
            </view>
          </view>
          <view class="quantity-section">
            <view class="quantity-box qualified">
              <text class="q-label">合格库存</text>
@@ -54,7 +68,6 @@
              <text class="q-value">{{ item.unQualifiedQuantity }}</text>
            </view>
          </view>
          <view class="quantity-section">
            <view class="quantity-box locked">
              <text class="q-label">合格冻结</text>
@@ -65,7 +78,6 @@
              <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>
@@ -82,211 +94,350 @@
      </view>
      <up-loadmore :status="loadStatus" />
    </scroll-view>
    <view v-else-if="!loading" class="no-data">
      <up-empty mode="data" text="暂无库存数据"></up-empty>
    <view v-else-if="!loading"
          class="no-data">
      <up-empty mode="data"
                text="暂无库存数据"></up-empty>
    </view>
    <!-- 批号列表弹窗 -->
    <up-popup v-model:show="showBatchPopup"
              @close="showBatchPopup = false"
              mode="bottom"
              round="20"
              closeable>
      <view class="batch-popup-content">
        <view class="popup-header">
          <text class="popup-title">批号详情</text>
        </view>
        <scroll-view scroll-y
                     class="batch-list-scroll">
          <view class="batch-list">
            <view v-for="(batch, index) in currentBatchList"
                  :key="index"
                  class="batch-item">
              <view class="batch-index-box">
                <text class="batch-index">{{ index + 1 }}</text>
              </view>
              <text class="batch-text">{{ batch }}</text>
            </view>
          </view>
        </scroll-view>
      </view>
    </up-popup>
  </view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { getStockInventoryListPageCombined } from "@/api/inventoryManagement/stockInventory.js";
  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 props = defineProps({
    productId: {
      type: Number,
      required: true,
    },
  });
};
const loadMore = () => {
  if (loadStatus.value === 'loadmore') {
    page.current++;
  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 showBatchPopup = ref(false);
  const currentBatchList = ref([]);
  const handleQuery = () => {
    page.current = 1;
    tableData.value = [];
    getList();
  }
};
  };
onMounted(() => {
  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();
    }
  };
  const handleShowBatch = batchNo => {
    if (!batchNo) return;
    // 支持逗号、空格或换行分隔
    currentBatchList.value = batchNo
      .split(/[,,\s\n]+/)
      .filter(item => item.trim() !== "");
    if (currentBatchList.value.length > 0) {
      showBatchPopup.value = true;
    }
  };
  const formatBatchNo = batchNo => {
    if (!batchNo) return "-";
    if (batchNo.length > 25) {
      return batchNo.substring(0, 25) + "...";
    }
    return batchNo;
  };
  const isBatchClickable = batchNo => {
    if (!batchNo) return false;
    return batchNo.length > 25 || batchNo.includes(",") || batchNo.includes(",");
  };
  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;
  .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;
    .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;
    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 {
    padding: 20rpx;
    box-sizing: border-box;
    overflow: auto;
  }
  .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;
      }
      .batch-no-wrapper {
        display: flex;
        align-items: center;
        max-width: 70%;
        .batch-no-text {
          overflow: hidden;
          text-overflow: ellipsis;
          white-space: nowrap;
          &.clickable {
            color: #2979ff;
            text-decoration: underline;
          }
        }
      }
    }
  }
}
.no-data {
  padding-top: 200rpx;
}
  .batch-popup-content {
    background-color: #fff;
    padding: 30rpx;
    max-height: 70vh;
    display: flex;
    flex-direction: column;
    .popup-header {
      padding-bottom: 30rpx;
      border-bottom: 1rpx solid #ebeef5;
      margin-bottom: 20rpx;
      text-align: center;
      .popup-title {
        font-size: 32rpx;
        font-weight: bold;
        color: #303133;
      }
    }
    .batch-list-scroll {
      flex: 1;
      overflow: hidden;
    }
    .batch-list {
      padding: 10rpx 0;
      .batch-item {
        display: flex;
        align-items: center;
        padding: 24rpx 0;
        border-bottom: 1rpx solid #f2f6fc;
        &:last-child {
          border-bottom: none;
        }
        .batch-index-box {
          width: 40rpx;
          height: 40rpx;
          background-color: #f0f2f5;
          border-radius: 50%;
          display: flex;
          align-items: center;
          justify-content: center;
          margin-right: 20rpx;
          .batch-index {
            font-size: 20rpx;
            color: #909399;
          }
        }
        .batch-text {
          font-size: 28rpx;
          color: #303133;
          flex: 1;
          word-break: break-all;
        }
      }
    }
  }
  .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>