1、供应商管理新增修改删除功能2、库存管理增加添加功能、详情、领用功能
| | |
| | | // 产åç»´æ¤é¡µé¢æ¥å£ |
| | | 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, |
| | | }); |
| | | } |
| | |
| | | }); |
| | | }; |
| | | |
| | | export const getStockInventoryBatchNoQty = params => { |
| | | return request({ |
| | | url: "/stockInventory/getBatchNoQty", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // å建åºåè®°å½ |
| | | export const createStockInventory = params => { |
| | | return request({ |
| | |
| | | }); |
| | | }; |
| | | |
| | | 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({ |
| | |
| | | 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, |
| | | }); |
| | | }; |
| | | |
| | | // è§£å»åºåè®°å½ |
| | | 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, |
| | | }); |
| | | }; |
| | |
| | | } |
| | | }, |
| | | { |
| | | "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": "è§ç¨ä¸èµè´¨", |
| | |
| | | "navigationBarTitleText": "RuoYi", |
| | | "navigationBarBackgroundColor": "#FFFFFF" |
| | | } |
| | | } |
| | | } |
| | |
| | | <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> |
| | |
| | | </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({ |
| | |
| | | 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, |
| | |
| | | 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; |
| | | }); |
| | | }; |
| | | |
| | |
| | | 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; |
| | |
| | | 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 { |
| | |
| | | .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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |