| | |
| | | <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> |
| | |
| | | <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> |
| | |
| | | <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.unQualifiedLockedQuantity }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="detail-row"> |
| | | <text class="detail-label">库存预警</text> |
| | | <text class="detail-value">{{ item.warnNum }}</text> |
| | |
| | | </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> |