From 6ca03cc8ac229c13702ce699bab4db6e93f3f172 Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期二, 30 六月 2026 14:19:12 +0800
Subject: [PATCH] 1、供应商管理新增修改删除功能2、库存管理增加添加功能、详情、领用功能
---
src/pages.json | 30 +
src/pages/inventoryManagement/stockManagement/detail.vue | 294 +++++++++++++
src/pages/inventoryManagement/stockManagement/selectProductModel.vue | 228 ++++++++++
src/api/basicData/product.js | 80 ++-
src/api/inventoryManagement/stockUninventory.js | 68 +-
src/pages/inventoryManagement/stockManagement/add.vue | 155 +++++++
src/pages/inventoryManagement/stockManagement/use.vue | 183 ++++++++
src/api/inventoryManagement/stockInventory.js | 24 +
src/pages/inventoryManagement/stockManagement/Record.vue | 205 +++++++++
9 files changed, 1,193 insertions(+), 74 deletions(-)
diff --git a/src/api/basicData/product.js b/src/api/basicData/product.js
index 10f8fd1..fbd0e5b 100644
--- a/src/api/basicData/product.js
+++ b/src/api/basicData/product.js
@@ -1,58 +1,66 @@
// 浜у搧缁存姢椤甸潰鎺ュ彛
-import request from '@/utils/request'
+import request from "@/utils/request";
// 浜у搧鏍戞煡璇�
export function productTreeList(query) {
- return request({
- url: '/basic/product/list',
- method: 'get',
- params: query
- })
+ return request({
+ url: "/basic/product/list",
+ method: "get",
+ params: query,
+ });
}
// 浜у搧鏍戞柊澧炰慨鏀�
export function addOrEditProduct(query) {
- return request({
- url: '/basic/product/addOrEditProduct',
- method: 'post',
- data: query
- })
+ return request({
+ url: "/basic/product/addOrEditProduct",
+ method: "post",
+ data: query,
+ });
}
// 瑙勬牸鍨嬪彿鏂板淇敼
export function addOrEditProductModel(query) {
- return request({
- url: '/basic/product/addOrEditProductModel',
- method: 'post',
- data: query
- })
+ return request({
+ url: "/basic/product/addOrEditProductModel",
+ method: "post",
+ data: query,
+ });
}
// 浜у搧鏍戝垹闄�
export function delProduct(query) {
- return request({
- url: '/basic/product/delProduct',
- method: 'delete',
- data: query
- })
+ return request({
+ url: "/basic/product/delProduct",
+ method: "delete",
+ data: query,
+ });
}
// 瑙勬牸鍨嬪彿鍒犻櫎
export function delProductModel(query) {
- return request({
- url: '/basic/product/delProductModel',
- method: 'delete',
- data: query
- })
+ return request({
+ url: "/basic/product/delProductModel",
+ method: "delete",
+ data: query,
+ });
}
// 瑙勬牸鍨嬪彿鏌ヨ
export function modelList(query) {
- return request({
- url: '/basic/product/modelList',
- method: 'get',
- params: query
- })
+ return request({
+ url: "/basic/product/modelList",
+ method: "get",
+ params: query,
+ });
}
export function modelListPage(query) {
- return request({
- url: '/basic/product/modelListPage',
- method: 'get',
- params: query
- })
+ return request({
+ url: "/basic/product/modelListPage",
+ method: "get",
+ params: query,
+ });
+}
+
+export function pageModel(query) {
+ return request({
+ url: "/basic/product/pageModel",
+ method: "get",
+ params: query,
+ });
}
diff --git a/src/api/inventoryManagement/stockInventory.js b/src/api/inventoryManagement/stockInventory.js
index 328657c..5dda278 100644
--- a/src/api/inventoryManagement/stockInventory.js
+++ b/src/api/inventoryManagement/stockInventory.js
@@ -17,6 +17,14 @@
});
};
+export const getStockInventoryBatchNoQty = params => {
+ return request({
+ url: "/stockInventory/getBatchNoQty",
+ method: "get",
+ params,
+ });
+};
+
// 鍒涘缓搴撳瓨璁板綍
export const createStockInventory = params => {
return request({
@@ -26,6 +34,22 @@
});
};
+export const addStockInRecordOnly = params => {
+ return request({
+ url: "/stockInventory/addStockInRecordOnly",
+ method: "post",
+ data: params,
+ });
+};
+
+export const addStockOutRecordOnly = params => {
+ return request({
+ url: "/stockInventory/addStockOutRecordOnly",
+ method: "post",
+ data: params,
+ });
+};
+
// 鍑忓皯搴撳瓨璁板綍
export const subtractStockInventory = params => {
return request({
diff --git a/src/api/inventoryManagement/stockUninventory.js b/src/api/inventoryManagement/stockUninventory.js
index fee8908..529759b 100644
--- a/src/api/inventoryManagement/stockUninventory.js
+++ b/src/api/inventoryManagement/stockUninventory.js
@@ -1,45 +1,53 @@
import request from "@/utils/request";
// 鍒嗛〉鏌ヨ搴撳瓨璁板綍鍒楄〃
-export const getStockUninventoryListPage = (params) => {
- return request({
- url: "/stockUninventory/pagestockUninventory",
- method: "get",
- params,
- });
+export const getStockUninventoryListPage = params => {
+ return request({
+ url: "/stockUninventory/pagestockUninventory",
+ method: "get",
+ params,
+ });
};
// 鍒涘缓搴撳瓨璁板綍
-export const createStockUnInventory = (params) => {
- return request({
- url: "/stockUninventory/addstockUninventory",
- method: "post",
- data: params,
- });
+export const createStockUnInventory = params => {
+ return request({
+ url: "/stockUninventory/addstockUninventory",
+ method: "post",
+ data: params,
+ });
};
// 鍑忓皯搴撳瓨璁板綍
-export const subtractStockUnInventory = (params) => {
- return request({
- url: "/stockUninventory/subtractstockUninventory",
- method: "post",
- data: params,
- });
+export const subtractStockUnInventory = params => {
+ return request({
+ url: "/stockUninventory/subtractstockUninventory",
+ method: "post",
+ data: params,
+ });
};
// 鍐荤粨搴撳瓨璁板綍
-export const frozenStockUninventory = (params) => {
- return request({
- url: "/stockUninventory/frozenStock",
- method: "post",
- data: params,
- });
+export const frozenStockUninventory = params => {
+ return request({
+ url: "/stockUninventory/frozenStock",
+ method: "post",
+ data: params,
+ });
};
// 瑙e喕搴撳瓨璁板綍
-export const thawStockUninventory = (params) => {
- return request({
- url: "/stockUninventory/thawStock",
- method: "post",
- data: params,
- });
+export const thawStockUninventory = params => {
+ return request({
+ url: "/stockUninventory/thawStock",
+ method: "post",
+ data: params,
+ });
+};
+
+export const addUnqualifiedStockOutRecordOnly = params => {
+ return request({
+ url: "/stockUninventory/addStockOutRecordOnly",
+ method: "post",
+ data: params,
+ });
};
diff --git a/src/pages.json b/src/pages.json
index 30a1a63..71f446c 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -936,6 +936,34 @@
}
},
{
+ "path": "pages/inventoryManagement/stockManagement/add",
+ "style": {
+ "navigationBarTitleText": "鏂板搴撳瓨",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/inventoryManagement/stockManagement/detail",
+ "style": {
+ "navigationBarTitleText": "搴撳瓨璇︽儏",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/inventoryManagement/stockManagement/use",
+ "style": {
+ "navigationBarTitleText": "棰嗙敤",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/inventoryManagement/stockManagement/selectProductModel",
+ "style": {
+ "navigationBarTitleText": "閫夋嫨浜у搧",
+ "navigationStyle": "custom"
+ }
+ },
+ {
"path": "pages/safeProduction/safeQualifications/index",
"style": {
"navigationBarTitleText": "瑙勭▼涓庤祫璐�",
@@ -1554,4 +1582,4 @@
"navigationBarTitleText": "RuoYi",
"navigationBarBackgroundColor": "#FFFFFF"
}
-}
\ No newline at end of file
+}
diff --git a/src/pages/inventoryManagement/stockManagement/Record.vue b/src/pages/inventoryManagement/stockManagement/Record.vue
index e5d5ca2..087626f 100644
--- a/src/pages/inventoryManagement/stockManagement/Record.vue
+++ b/src/pages/inventoryManagement/stockManagement/Record.vue
@@ -91,6 +91,11 @@
<text class="detail-value">{{ item.updateTime }}</text>
</view>
</view>
+ <view class="action-buttons">
+ <u-button size="small"
+ class="action-btn"
+ @click="openDetail(item)">璇︽儏</u-button>
+ </view>
</view>
<up-loadmore :status="loadStatus" />
</scroll-view>
@@ -124,11 +129,17 @@
</scroll-view>
</view>
</up-popup>
+ <view class="fab-button"
+ @click="openAddPopup">
+ <up-icon name="plus"
+ size="24"
+ color="#ffffff"></up-icon>
+ </view>
</view>
</template>
<script setup>
- import { ref, reactive, onMounted } from "vue";
+ import { ref, reactive, onMounted, onUnmounted } from "vue";
import { getStockInventoryListPageCombined } from "@/api/inventoryManagement/stockInventory.js";
const props = defineProps({
@@ -142,7 +153,6 @@
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,
@@ -168,17 +178,18 @@
size: page.size,
})
.then(res => {
- loading.value = false;
- const records = res.data.records || [];
+ const records = res?.data?.records || [];
tableData.value =
page.current === 1 ? records : [...tableData.value, ...records];
- total.value = res.data.total;
+ const total = Number(res?.data?.total || 0);
loadStatus.value =
- tableData.value.length >= total.value ? "nomore" : "loadmore";
+ total > 0 && tableData.value.length >= total ? "nomore" : "loadmore";
})
.catch(() => {
- loading.value = false;
loadStatus.value = "loadmore";
+ })
+ .finally(() => {
+ loading.value = false;
});
};
@@ -213,12 +224,41 @@
return batchNo.length > 25 || batchNo.includes(",") || batchNo.includes("锛�");
};
+ const openDetail = row => {
+ if (!row?.productId || !row?.productModelId) return;
+ const productName = encodeURIComponent(row.productName || "");
+ const model = encodeURIComponent(row.model || "");
+ const unit = encodeURIComponent(row.unit || "");
+ uni.navigateTo({
+ url: `/pages/inventoryManagement/stockManagement/detail?topParentProductId=${props.productId}&productId=${row.productId}&productModelId=${row.productModelId}&productName=${productName}&model=${model}&unit=${unit}`,
+ });
+ };
+
+ const openAddPopup = () => {
+ uni.navigateTo({
+ url: `/pages/inventoryManagement/stockManagement/add?topParentProductId=${props.productId}`,
+ });
+ };
+
+ const onRefresh = payload => {
+ if (!payload) return;
+ if (String(payload.topParentProductId) !== String(props.productId)) return;
+ handleQuery();
+ };
+
onMounted(() => {
getList();
+ uni.$on("stockManagement:refresh", onRefresh);
+ });
+
+ onUnmounted(() => {
+ uni.$off("stockManagement:refresh", onRefresh);
});
</script>
<style scoped lang="scss">
+ @import "@/static/scss/form-common.scss";
+
.record-container {
height: 100%;
display: flex;
@@ -267,6 +307,19 @@
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
+ }
+
+ .action-buttons {
+ display: flex;
+ gap: 12px;
+ padding-top: 10px;
+ }
+
+ .action-btn {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
}
.item-header {
@@ -440,4 +493,142 @@
.no-data {
padding-top: 200rpx;
}
+
+ .fab-button {
+ position: fixed;
+ bottom: calc(30px + env(safe-area-inset-bottom));
+ right: 30px;
+ width: 56px;
+ height: 56px;
+ background: #2979ff;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 4px 16px rgba(41, 121, 255, 0.3);
+ z-index: 1000;
+ }
+
+ .detail-popup {
+ background-color: #fff;
+ max-height: 75vh;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .detail-summary {
+ padding: 0 30rpx 20rpx 30rpx;
+ }
+
+ .summary-row {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 16rpx;
+ }
+
+ .summary-label {
+ font-size: 24rpx;
+ color: #909399;
+ }
+
+ .summary-value {
+ font-size: 24rpx;
+ color: #303133;
+ text-align: right;
+ margin-left: 20rpx;
+ flex: 1;
+ }
+
+ .detail-list-scroll {
+ flex: 1;
+ min-height: 0;
+ }
+
+ .detail-list {
+ padding: 20rpx 30rpx 40rpx 30rpx;
+ }
+
+ .detail-card {
+ background-color: #f8f9fa;
+ border-radius: 16rpx;
+ padding: 24rpx;
+ margin-bottom: 20rpx;
+ }
+
+ .detail-card-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 16rpx;
+ }
+
+ .detail-card-title {
+ font-size: 28rpx;
+ color: #303133;
+ font-weight: 600;
+ }
+
+ .detail-card-body {
+ .detail-row {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 12rpx;
+ font-size: 24rpx;
+ }
+
+ .detail-label {
+ color: #909399;
+ }
+
+ .detail-value {
+ color: #303133;
+ font-weight: 500;
+ text-align: right;
+ margin-left: 20rpx;
+ flex: 1;
+ }
+ }
+
+ .product-select-popup {
+ background-color: #fff;
+ max-height: 75vh;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .product-search {
+ padding: 20rpx 30rpx;
+ display: flex;
+ gap: 16rpx;
+ }
+
+ .product-list-scroll {
+ flex: 1;
+ min-height: 0;
+ }
+
+ .product-model-list {
+ padding: 0 30rpx 40rpx 30rpx;
+ }
+
+ .product-model-item {
+ background-color: #f8f9fa;
+ border-radius: 16rpx;
+ padding: 24rpx;
+ margin-top: 20rpx;
+ }
+
+ .pm-name {
+ display: block;
+ font-size: 26rpx;
+ color: #303133;
+ font-weight: 600;
+ }
+
+ .pm-model {
+ display: block;
+ margin-top: 8rpx;
+ font-size: 24rpx;
+ color: #909399;
+ }
</style>
diff --git a/src/pages/inventoryManagement/stockManagement/add.vue b/src/pages/inventoryManagement/stockManagement/add.vue
new file mode 100644
index 0000000..e60a6e0
--- /dev/null
+++ b/src/pages/inventoryManagement/stockManagement/add.vue
@@ -0,0 +1,155 @@
+<template>
+ <view class="account-detail">
+ <PageHeader title="鏂板搴撳瓨"
+ @back="goBack" />
+ <up-form ref="formRef"
+ :model="form"
+ label-width="110">
+ <up-form-item label="浜у搧"
+ required>
+ <up-input v-model="form.productName"
+ placeholder="璇烽�夋嫨"
+ readonly
+ @click="goSelectProduct" />
+ <template #right>
+ <up-icon name="arrow-right"
+ @click="goSelectProduct"></up-icon>
+ </template>
+ </up-form-item>
+ <up-form-item label="瑙勬牸鍨嬪彿">
+ <up-input v-model="form.model"
+ disabled
+ placeholder="鑷姩濉厖" />
+ </up-form-item>
+ <up-form-item label="鍗曚綅">
+ <up-input v-model="form.unit"
+ disabled
+ placeholder="鑷姩濉厖" />
+ </up-form-item>
+ <up-form-item label="搴撳瓨绫诲瀷"
+ required>
+ <up-input v-model="stockTypeText"
+ disabled />
+ </up-form-item>
+ <up-form-item label="鏁伴噺"
+ required>
+ <up-number-box v-model="form.qualitity"
+ :min="1"
+ :step="1" />
+ </up-form-item>
+ <up-form-item label="鎵瑰彿">
+ <up-input v-model="form.batchNo"
+ placeholder="閫夊~"
+ clearable />
+ </up-form-item>
+ <up-form-item v-if="form.type === 'qualified'"
+ label="棰勮鏁伴噺">
+ <up-number-box v-model="form.warnNum"
+ :min="0"
+ :max="Number(form.qualitity || 0)"
+ :step="1" />
+ </up-form-item>
+ <up-form-item label="澶囨敞">
+ <up-textarea v-model="form.remark"
+ placeholder="閫夊~"
+ auto-height />
+ </up-form-item>
+ </up-form>
+ <FooterButtons cancelText="鍙栨秷"
+ confirmText="淇濆瓨"
+ :loading="submitting"
+ @cancel="goBack"
+ @confirm="handleSubmit" />
+ </view>
+</template>
+
+<script setup>
+ import { onMounted, ref } from "vue";
+ import PageHeader from "@/components/PageHeader.vue";
+ import FooterButtons from "@/components/FooterButtons.vue";
+ import { addStockInRecordOnly } from "@/api/inventoryManagement/stockInventory.js";
+
+ const submitting = ref(false);
+ const formRef = ref(null);
+ const topParentProductId = ref(undefined);
+
+ const form = ref({
+ productId: undefined,
+ productModelId: undefined,
+ productName: "",
+ model: "",
+ unit: "",
+ type: "qualified",
+ qualitity: 1,
+ batchNo: null,
+ warnNum: 0,
+ remark: "",
+ });
+
+ const stockTypeText = ref("鍚堟牸搴撳瓨");
+
+ const getRequestErrorText = err => {
+ if (!err || typeof err !== "object") return "";
+ const msg = err?.msg || err?.message || err?.data?.msg || err?.data?.message;
+ return msg ? String(msg) : "";
+ };
+
+ const goBack = () => {
+ uni.navigateBack();
+ };
+
+ const goSelectProduct = () => {
+ const onSelected = row => {
+ form.value.productId = row.productId;
+ form.value.productModelId = row.id;
+ form.value.productName = row.productName || "";
+ form.value.model = row.model || "";
+ form.value.unit = row.unit || "";
+ };
+ uni.$once("stockManagement:selectedProductModel", onSelected);
+ uni.navigateTo({
+ url: `/pages/inventoryManagement/stockManagement/selectProductModel?topParentProductId=${topParentProductId.value || ""}`,
+ events: {
+ selected: onSelected,
+ },
+ });
+ };
+
+ const handleSubmit = () => {
+ if (!form.value.productModelId) {
+ uni.showToast({ title: "璇烽�夋嫨浜у搧", icon: "none" });
+ return;
+ }
+ const qty = Number(form.value.qualitity);
+ if (!qty || qty < 1) {
+ uni.showToast({ title: "鏁伴噺蹇呴』澶т簬0", icon: "none" });
+ return;
+ }
+
+ submitting.value = true;
+ const payload = { ...form.value };
+ if (payload.batchNo === "") payload.batchNo = null;
+ addStockInRecordOnly(payload)
+ .then(() => {
+ uni.showToast({ title: "鎻愪氦鎴愬姛", icon: "success" });
+ uni.$emit("stockManagement:refresh", { topParentProductId: topParentProductId.value });
+ goBack();
+ })
+ .catch(err => {
+ const title = getRequestErrorText(err);
+ if (title) uni.showToast({ title, icon: "none" });
+ })
+ .finally(() => {
+ submitting.value = false;
+ });
+ };
+
+ onMounted(() => {
+ const options = getCurrentPages()?.slice(-1)?.[0]?.options || {};
+ topParentProductId.value = options.topParentProductId ? Number(options.topParentProductId) : undefined;
+ });
+</script>
+
+<style scoped lang="scss">
+ @import "@/static/scss/form-common.scss";
+</style>
diff --git a/src/pages/inventoryManagement/stockManagement/detail.vue b/src/pages/inventoryManagement/stockManagement/detail.vue
new file mode 100644
index 0000000..eb4b06e
--- /dev/null
+++ b/src/pages/inventoryManagement/stockManagement/detail.vue
@@ -0,0 +1,294 @@
+<template>
+ <view class="app-container">
+ <PageHeader title="搴撳瓨璇︽儏"
+ @back="goBack" />
+ <view class="summary-card">
+ <view class="summary-row">
+ <text class="label">浜у搧</text>
+ <text class="value">{{ productName || '-' }}</text>
+ </view>
+ <view class="summary-row">
+ <text class="label">瑙勬牸</text>
+ <text class="value">{{ model || '-' }}</text>
+ </view>
+ <view class="summary-row">
+ <text class="label">鍗曚綅</text>
+ <text class="value">{{ unit || '-' }}</text>
+ </view>
+ </view>
+
+ <scroll-view scroll-y
+ class="list-scroll"
+ @scrolltolower="loadMore">
+ <view v-if="loading"
+ class="loading-state">
+ <up-loading-icon text="鍔犺浇涓�..."></up-loading-icon>
+ </view>
+ <view v-else-if="rows.length === 0"
+ class="no-data">
+ <up-empty mode="data"
+ text="鏆傛棤鎵规鏁版嵁"></up-empty>
+ </view>
+ <view v-else
+ class="list">
+ <view v-for="row in rows"
+ :key="row.id || row.batchNo"
+ class="card">
+ <view class="card-header">
+ <text class="title">鎵瑰彿锛歿{ row.batchNo || '-' }}</text>
+ </view>
+ <view class="card-body">
+ <view class="kv-grid">
+ <view class="kv half">
+ <text class="k">鍚堟牸搴撳瓨</text>
+ <text class="v">{{ row.qualifiedQuantity ?? 0 }}</text>
+ </view>
+ <view class="kv half">
+ <text class="k">涓嶅悎鏍煎簱瀛�</text>
+ <text class="v">{{ row.unQualifiedQuantity ?? 0 }}</text>
+ </view>
+ <view class="kv half">
+ <text class="k">鍚堟牸鍙敤</text>
+ <text class="v">{{ calcQualifiedMax(row) }}</text>
+ </view>
+ <view class="kv half">
+ <text class="k">涓嶅悎鏍煎彲鐢�</text>
+ <text class="v">{{ calcUnqualifiedMax(row) }}</text>
+ </view>
+ <view class="kv full">
+ <text class="k">棰勮</text>
+ <text class="v">{{ row.warnNum ?? '-' }}</text>
+ </view>
+ <view class="kv full">
+ <text class="k">澶囨敞</text>
+ <text class="v">{{ row.remark || '-' }}</text>
+ </view>
+ </view>
+ </view>
+ <view class="card-footer">
+ <u-button type="primary"
+ :disabled="!canUseRow(row)"
+ :customStyle="{ width: '100%', height: '72rpx', lineHeight: '72rpx', borderRadius: '12rpx' }"
+ @click="goUse(row)">
+ 棰嗙敤
+ </u-button>
+ </view>
+ </view>
+ <up-loadmore :status="loadStatus" />
+ </view>
+ </scroll-view>
+ </view>
+</template>
+
+<script setup>
+ import { onMounted, reactive, ref } from "vue";
+ import PageHeader from "@/components/PageHeader.vue";
+ import { getStockInventoryBatchNoQty } from "@/api/inventoryManagement/stockInventory.js";
+
+ const loading = ref(false);
+ const loadStatus = ref("loadmore");
+ const rows = ref([]);
+ const total = ref(0);
+ const page = reactive({ current: 1, size: 20 });
+
+ const topParentProductId = ref(undefined);
+ const productId = ref(undefined);
+ const productModelId = ref(undefined);
+ const productName = ref("");
+ const model = ref("");
+ const unit = ref("");
+
+ const goBack = () => {
+ uni.navigateBack();
+ };
+
+ const calcQualifiedMax = row => {
+ return Math.max(
+ 0,
+ Number(row?.qualifiedUnLockedQuantity || 0) +
+ Number(row?.qualifiedPendingOutQuantity || 0)
+ );
+ };
+
+ const calcUnqualifiedMax = row => {
+ return Math.max(
+ 0,
+ Number(row?.unQualifiedUnLockedQuantity || 0) +
+ Number(row?.unQualifiedPendingOutQuantity || 0)
+ );
+ };
+
+ const canUseRow = row => {
+ return calcQualifiedMax(row) > 0 || calcUnqualifiedMax(row) > 0;
+ };
+
+ const fetchList = () => {
+ if (loading.value) return;
+ if (!productId.value || !productModelId.value) return;
+ loading.value = true;
+ loadStatus.value = "loading";
+
+ getStockInventoryBatchNoQty({
+ current: page.current,
+ size: page.size,
+ productId: productId.value,
+ productModelId: productModelId.value,
+ })
+ .then(res => {
+ const records = res?.records || res?.data?.records || res?.data || [];
+ const list = Array.isArray(records) ? records : [];
+ rows.value = page.current === 1 ? list : [...rows.value, ...list];
+ total.value = Number(res?.total ?? res?.data?.total ?? rows.value.length);
+ loadStatus.value = rows.value.length >= total.value ? "nomore" : "loadmore";
+ })
+ .catch(() => {
+ loadStatus.value = "loadmore";
+ })
+ .finally(() => {
+ loading.value = false;
+ });
+ };
+
+ const loadMore = () => {
+ if (loadStatus.value !== "loadmore") return;
+ page.current++;
+ fetchList();
+ };
+
+ const goUse = row => {
+ if (!row) return;
+ const qualifiedMax = calcQualifiedMax(row);
+ const unqualifiedMax = calcUnqualifiedMax(row);
+ const pn = encodeURIComponent(productName.value || "");
+ const md = encodeURIComponent(model.value || "");
+ const un = encodeURIComponent(unit.value || "");
+ const bn = encodeURIComponent(row.batchNo || "");
+ uni.navigateTo({
+ url: `/pages/inventoryManagement/stockManagement/use?topParentProductId=${topParentProductId.value || ""}&productId=${productId.value}&productModelId=${productModelId.value}&productName=${pn}&model=${md}&unit=${un}&batchNo=${bn}&qualifiedMax=${qualifiedMax}&unqualifiedMax=${unqualifiedMax}`,
+ });
+ };
+
+ onMounted(() => {
+ const options = getCurrentPages()?.slice(-1)?.[0]?.options || {};
+ topParentProductId.value = options.topParentProductId ? Number(options.topParentProductId) : undefined;
+ productId.value = options.productId ? Number(options.productId) : undefined;
+ productModelId.value = options.productModelId ? Number(options.productModelId) : undefined;
+ productName.value = decodeURIComponent(options.productName || "");
+ model.value = decodeURIComponent(options.model || "");
+ unit.value = decodeURIComponent(options.unit || "");
+ fetchList();
+ });
+</script>
+
+<style scoped lang="scss">
+ .app-container {
+ height: 100vh;
+ background-color: #f8f9fa;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .summary-card {
+ background: #fff;
+ margin: 20rpx;
+ border-radius: 16rpx;
+ padding: 24rpx;
+ box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.06);
+ }
+
+ .summary-row {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 12rpx;
+ font-size: 26rpx;
+ }
+
+ .label {
+ color: #909399;
+ }
+
+ .value {
+ color: #303133;
+ font-weight: 500;
+ text-align: right;
+ margin-left: 20rpx;
+ flex: 1;
+ }
+
+ .list-scroll {
+ flex: 1;
+ min-height: 0;
+ }
+
+ .list {
+ padding: 0 20rpx 20rpx 20rpx;
+ }
+
+ .card {
+ background: #fff;
+ border-radius: 16rpx;
+ padding: 24rpx;
+ margin-bottom: 20rpx;
+ box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.06);
+ }
+
+ .card-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 16rpx;
+ }
+
+ .title {
+ font-size: 28rpx;
+ font-weight: 600;
+ color: #303133;
+ }
+
+ .card-body {
+ .kv-grid {
+ display: flex;
+ flex-wrap: wrap;
+ margin-top: 8rpx;
+ }
+
+ .kv {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 26rpx;
+ padding-top: 12rpx;
+ box-sizing: border-box;
+ }
+
+ .kv.half {
+ width: 50%;
+ padding-right: 16rpx;
+ }
+
+ .kv.full {
+ width: 100%;
+ }
+
+ .k {
+ color: #909399;
+ }
+
+ .v {
+ color: #303133;
+ font-weight: 500;
+ text-align: right;
+ margin-left: 20rpx;
+ flex: 1;
+ }
+ }
+
+ .card-footer {
+ margin-top: 20rpx;
+ }
+
+ .loading-state,
+ .no-data {
+ padding-top: 200rpx;
+ }
+</style>
diff --git a/src/pages/inventoryManagement/stockManagement/selectProductModel.vue b/src/pages/inventoryManagement/stockManagement/selectProductModel.vue
new file mode 100644
index 0000000..50892cb
--- /dev/null
+++ b/src/pages/inventoryManagement/stockManagement/selectProductModel.vue
@@ -0,0 +1,228 @@
+<template>
+ <view class="account-detail">
+ <PageHeader title="閫夋嫨浜у搧"
+ @back="goBack" />
+ <view class="search-bar">
+ <view class="search-input">
+ <up-input v-model="query.productName"
+ placeholder="浜у搧鍚嶇О"
+ clearable
+ @confirm="handleQuery" />
+ </view>
+ <view class="search-input">
+ <up-input v-model="query.model"
+ placeholder="瑙勬牸鍨嬪彿"
+ clearable
+ @confirm="handleQuery" />
+ </view>
+ <view class="filter-button"
+ @click="handleQuery">
+ <up-icon name="search"
+ size="24"
+ color="#999"></up-icon>
+ </view>
+ </view>
+ <scroll-view scroll-y
+ class="list-scroll"
+ @scrolltolower="loadMore">
+ <view v-if="loading"
+ class="loading-state">
+ <up-loading-icon text="鍔犺浇涓�..."></up-loading-icon>
+ </view>
+ <view v-else-if="list.length === 0"
+ class="no-data">
+ <up-empty mode="data"
+ text="鏆傛棤鏁版嵁"></up-empty>
+ </view>
+ <view v-else
+ class="list">
+ <view v-for="row in list"
+ :key="row.id"
+ class="card"
+ @click="selectRow(row)">
+ <view class="card-title">
+ <text class="name">{{ row.productName }}</text>
+ </view>
+ <view class="card-sub">
+ <text class="label">瑙勬牸</text>
+ <text class="value">{{ row.model || '-' }}</text>
+ </view>
+ <view class="card-sub">
+ <text class="label">鍗曚綅</text>
+ <text class="value">{{ row.unit || '-' }}</text>
+ </view>
+ </view>
+ <up-loadmore :status="loadStatus" />
+ </view>
+ </scroll-view>
+ </view>
+</template>
+
+<script setup>
+ import { onMounted, reactive, ref, getCurrentInstance } from "vue";
+ import PageHeader from "@/components/PageHeader.vue";
+ import { pageModel } from "@/api/basicData/product.js";
+
+ const loading = ref(false);
+ const loadStatus = ref("loadmore");
+ const list = ref([]);
+ const total = ref(0);
+ const page = reactive({ current: 1, size: 20 });
+ const topParentProductId = ref(undefined);
+
+ const query = reactive({
+ productName: "",
+ model: "",
+ });
+
+ const goBack = () => {
+ uni.navigateBack();
+ };
+
+ const fetchList = () => {
+ if (loading.value) return;
+ loading.value = true;
+ loadStatus.value = "loading";
+ pageModel({
+ productName: query.productName,
+ model: query.model,
+ current: page.current,
+ size: page.size,
+ topProductParentId: topParentProductId.value,
+ })
+ .then(res => {
+ const records = res?.records || res?.data?.records || res?.data || [];
+ const rows = Array.isArray(records) ? records : [];
+ list.value = page.current === 1 ? rows : [...list.value, ...rows];
+ total.value = Number(res?.total ?? res?.data?.total ?? list.value.length);
+ loadStatus.value = list.value.length >= total.value ? "nomore" : "loadmore";
+ })
+ .catch(() => {
+ loadStatus.value = "loadmore";
+ })
+ .finally(() => {
+ loading.value = false;
+ });
+ };
+
+ const handleQuery = () => {
+ page.current = 1;
+ list.value = [];
+ fetchList();
+ };
+
+ const loadMore = () => {
+ if (loadStatus.value !== "loadmore") return;
+ page.current++;
+ fetchList();
+ };
+
+ const selectRow = row => {
+ const instance = getCurrentInstance();
+ const eventChannel = instance?.proxy?.getOpenerEventChannel?.();
+ if (eventChannel?.emit) {
+ eventChannel.emit("selected", row);
+ } else {
+ uni.$emit("stockManagement:selectedProductModel", row);
+ }
+ goBack();
+ };
+
+ onMounted(() => {
+ const options = getCurrentPages()?.slice(-1)?.[0]?.options || {};
+ topParentProductId.value = options.topParentProductId ? Number(options.topParentProductId) : undefined;
+ fetchList();
+ });
+</script>
+
+<style scoped lang="scss">
+ @import "@/static/scss/form-common.scss";
+
+ .search-bar {
+ margin: 20rpx;
+ background-color: #fff;
+ border-radius: 16rpx;
+ padding: 0 30rpx;
+ height: 80rpx;
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+
+ .search-input {
+ flex: 1;
+ min-width: 0;
+ }
+
+ :deep(.u-input) {
+ background-color: #f2f2f2;
+ border-radius: 40rpx;
+ padding: 0 30rpx;
+ height: 80rpx;
+ margin-bottom: 0;
+ }
+
+ :deep(.u-input__content__field-wrapper__field) {
+ height: 80rpx;
+ line-height: 80rpx;
+ }
+
+ .filter-button {
+ width: 80rpx;
+ height: 80rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ }
+ }
+
+ .list-scroll {
+ flex: 1;
+ min-height: 0;
+ }
+
+ .list {
+ padding: 0 20rpx 20rpx 20rpx;
+ }
+
+ .card {
+ background: #fff;
+ border-radius: 16rpx;
+ padding: 24rpx;
+ margin-bottom: 20rpx;
+ }
+
+ .card-title {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 10rpx;
+ }
+
+ .name {
+ font-size: 30rpx;
+ font-weight: 600;
+ color: #303133;
+ }
+
+ .card-sub {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 10rpx;
+ font-size: 26rpx;
+ }
+
+ .label {
+ color: #909399;
+ }
+
+ .value {
+ color: #303133;
+ font-weight: 500;
+ }
+
+ .loading-state,
+ .no-data {
+ padding-top: 200rpx;
+ }
+</style>
diff --git a/src/pages/inventoryManagement/stockManagement/use.vue b/src/pages/inventoryManagement/stockManagement/use.vue
new file mode 100644
index 0000000..023876c
--- /dev/null
+++ b/src/pages/inventoryManagement/stockManagement/use.vue
@@ -0,0 +1,183 @@
+<template>
+ <view class="account-detail">
+ <PageHeader title="棰嗙敤"
+ @back="goBack" />
+ <up-form ref="formRef"
+ :model="form"
+ label-width="110">
+ <up-form-item label="浜у搧">
+ <up-input v-model="form.productName"
+ disabled />
+ </up-form-item>
+ <up-form-item label="瑙勬牸鍨嬪彿">
+ <up-input v-model="form.model"
+ disabled />
+ </up-form-item>
+ <up-form-item label="鍗曚綅">
+ <up-input v-model="form.unit"
+ disabled />
+ </up-form-item>
+ <up-form-item label="鎵瑰彿"
+ v-if="form.batchNo">
+ <up-input v-model="form.batchNo"
+ disabled />
+ </up-form-item>
+ <up-form-item label="搴撳瓨绫诲瀷"
+ required>
+ <u-radio-group v-model="form.type"
+ @change="onTypeChange">
+ <u-radio :customStyle="{ marginRight: '40rpx' }"
+ label="鍚堟牸搴撳瓨"
+ :disabled="qualifiedAvailable <= 0"
+ name="qualified"></u-radio>
+ <u-radio label="涓嶅悎鏍煎簱瀛�"
+ :disabled="unqualifiedAvailable <= 0"
+ name="unqualified"></u-radio>
+ </u-radio-group>
+ </up-form-item>
+ <up-form-item label="鏁伴噺"
+ required>
+ <up-number-box v-model="form.qualitity"
+ :min="1"
+ :max="maxQty"
+ :step="1" />
+ </up-form-item>
+ <up-form-item label="澶囨敞">
+ <up-textarea v-model="form.remark"
+ placeholder="閫夊~"
+ auto-height />
+ </up-form-item>
+ </up-form>
+ <FooterButtons cancelText="鍙栨秷"
+ confirmText="鎻愪氦"
+ :loading="submitting"
+ @cancel="goBack"
+ @confirm="handleSubmit" />
+ </view>
+</template>
+
+<script setup>
+ import { onMounted, ref, computed } from "vue";
+ import PageHeader from "@/components/PageHeader.vue";
+ import FooterButtons from "@/components/FooterButtons.vue";
+ import { addStockOutRecordOnly } from "@/api/inventoryManagement/stockInventory.js";
+ import { addUnqualifiedStockOutRecordOnly } from "@/api/inventoryManagement/stockUninventory.js";
+
+ const submitting = ref(false);
+ const formRef = ref(null);
+ const maxQty = ref(1);
+ const qualifiedMax = ref(0);
+ const unqualifiedMax = ref(0);
+ const topParentProductId = ref(undefined);
+
+ const form = ref({
+ productId: undefined,
+ productModelId: undefined,
+ productName: "",
+ model: "",
+ unit: "",
+ batchNo: undefined,
+ type: "qualified",
+ qualitity: 1,
+ remark: "",
+ });
+
+ const qualifiedAvailable = computed(() => {
+ return Number(qualifiedMax.value || 0);
+ });
+ const unqualifiedAvailable = computed(() => {
+ return Number(unqualifiedMax.value || 0);
+ });
+ const currentAvailable = computed(() => {
+ return form.value.type === "qualified"
+ ? qualifiedAvailable.value
+ : unqualifiedAvailable.value;
+ });
+
+ const getRequestErrorText = err => {
+ if (!err || typeof err !== "object") return "";
+ const msg = err?.msg || err?.message || err?.data?.msg || err?.data?.message;
+ return msg ? String(msg) : "";
+ };
+
+ const goBack = () => {
+ uni.navigateBack();
+ };
+
+ const syncMax = () => {
+ if (form.value.type === "qualified" && qualifiedAvailable.value <= 0 && unqualifiedAvailable.value > 0) {
+ form.value.type = "unqualified";
+ }
+ if (form.value.type === "unqualified" && unqualifiedAvailable.value <= 0 && qualifiedAvailable.value > 0) {
+ form.value.type = "qualified";
+ }
+ maxQty.value = Math.max(1, Number(currentAvailable.value || 0));
+ if (Number(form.value.qualitity) > Number(maxQty.value)) {
+ form.value.qualitity = Number(maxQty.value);
+ }
+ };
+
+ const onTypeChange = () => {
+ form.value.qualitity = 1;
+ syncMax();
+ };
+
+ const handleSubmit = () => {
+ if (!form.value.productModelId) {
+ uni.showToast({ title: "鍙傛暟閿欒", icon: "none" });
+ return;
+ }
+ const qty = Number(form.value.qualitity);
+ const limit = form.value.type === "qualified" ? qualifiedMax.value : unqualifiedMax.value;
+ if (!qty || qty < 1) {
+ uni.showToast({ title: "鏁伴噺蹇呴』澶т簬0", icon: "none" });
+ return;
+ }
+ if (limit <= 0) {
+ uni.showToast({ title: "鏆傛棤鍙敤搴撳瓨", icon: "none" });
+ return;
+ }
+ if (qty > limit) {
+ uni.showToast({ title: "棰嗙敤鏁伴噺瓒呭嚭鍙敤鏁伴噺", icon: "none" });
+ return;
+ }
+
+ submitting.value = true;
+ const payload = { ...form.value };
+ const requestFn = payload.type === "qualified" ? addStockOutRecordOnly : addUnqualifiedStockOutRecordOnly;
+ requestFn(payload)
+ .then(() => {
+ uni.showToast({ title: "鎻愪氦鎴愬姛", icon: "success" });
+ uni.$emit("stockManagement:refresh", { topParentProductId: topParentProductId.value });
+ goBack();
+ })
+ .catch(err => {
+ const title = getRequestErrorText(err);
+ if (title) uni.showToast({ title, icon: "none" });
+ })
+ .finally(() => {
+ submitting.value = false;
+ });
+ };
+
+ onMounted(() => {
+ const options = getCurrentPages()?.slice(-1)?.[0]?.options || {};
+ topParentProductId.value = options.topParentProductId ? Number(options.topParentProductId) : undefined;
+ form.value.productId = options.productId ? Number(options.productId) : undefined;
+ form.value.productModelId = options.productModelId ? Number(options.productModelId) : undefined;
+ form.value.productName = decodeURIComponent(options.productName || "");
+ form.value.model = decodeURIComponent(options.model || "");
+ form.value.unit = decodeURIComponent(options.unit || "");
+ form.value.batchNo = options.batchNo ? decodeURIComponent(options.batchNo || "") : undefined;
+ qualifiedMax.value = options.qualifiedMax ? Number(options.qualifiedMax) : 0;
+ unqualifiedMax.value = options.unqualifiedMax ? Number(options.unqualifiedMax) : 0;
+
+ form.value.type = qualifiedMax.value > 0 ? "qualified" : "unqualified";
+ form.value.qualitity = 1;
+ syncMax();
+ });
+</script>
+
+<style scoped lang="scss">
+ @import "@/static/scss/form-common.scss";
+</style>
--
Gitblit v1.9.3