| src/api/productionManagement/productionOrder.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/api/productionManagement/productionPlan.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages.json | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/productionManagement/mainProductionPlan/detail.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/productionManagement/mainProductionPlan/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/productionManagement/productionOrder/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/productionManagement/productionOrder/pickingDetail.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/productionManagement/productionOrder/source.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/works.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/api/productionManagement/productionOrder.js
@@ -1,19 +1,44 @@ // ç产订å页颿¥å£ import request from "@/utils/request"; // å页æ¥è¯¢ export function schedulingListPage(query) { // å页æ¥è¯¢ç产订å export function productOrderListPage(query) { return request({ url: "/salesLedger/scheduling/listPage", url: "/productionOrder/page", method: "get", params: query, }); } // ç产派工 export function productionDispatch(query) { // è·åçäº§è®¢åæ¥æºæ°æ® export function getProductOrderSource(id) { return request({ url: "/salesLedger/scheduling/productionDispatch", method: "post", data: query, url: `/productionOrder/source/${id}`, method: "get", }); } // é¢æè¯¦æ å表 export function listMaterialPickingDetail(productionOrderId) { return request({ url: "/productionOrderPick/detail/" + productionOrderId, method: "get", }); } // è¡¥æè®°å½å表 export function listMaterialSupplementRecord(query) { return request({ url: "/productionOrderPickRecord/feeding", method: "get", params: query, }); } // è·å颿BOMä¿¡æ¯ (å¯éï¼å¤ç¨) export function listMaterialPickingBom(productionOrderId) { return request({ url: "/productionOrder/pick/" + productionOrderId, method: "get", }); } src/api/productionManagement/productionPlan.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,29 @@ // 主çäº§è®¡åæ¥å£ import request from "@/utils/request"; // å页å表 export function productionPlanListPage(query) { return request({ url: "/productionPlan/listPage", method: "get", params: query, }); } // æåæ°æ® export function loadProdData(query) { return request({ url: "/productionPlan/loadProdData", method: "get", params: query, }); } // æ±æ»ç»è®¡ export function summaryByProductType(query) { return request({ url: "/productionPlan/summaryByProductType", method: "get", params: query, }); } src/pages.json
@@ -775,6 +775,20 @@ } }, { "path": "pages/productionManagement/productionOrder/source", "style": { "navigationBarTitleText": "æ¥æºæ°æ®", "navigationStyle": "custom" } }, { "path": "pages/productionManagement/productionOrder/pickingDetail", "style": { "navigationBarTitleText": "é¢æè¯¦æ ", "navigationStyle": "custom" } }, { "path": "pages/productionManagement/productionDispatching/index", "style": { "navigationBarTitleText": "ç产派工", @@ -802,6 +816,20 @@ "navigationStyle": "custom" } }, { "path": "pages/productionManagement/mainProductionPlan/index", "style": { "navigationBarTitleText": "主ç产计å", "navigationStyle": "custom" } }, { "path": "pages/productionManagement/mainProductionPlan/detail", "style": { "navigationBarTitleText": "计å详æ ", "navigationStyle": "custom" } }, // { // "path": "pages/productionManagement/productionCosting/index", // "style": { src/pages/productionManagement/mainProductionPlan/detail.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,252 @@ <template> <view class="production-plan-detail"> <PageHeader title="计å详æ " @back="goBack" /> <view class="detail-container" v-if="detailData"> <!-- åºæ¬ä¿¡æ¯å¡ç --> <view class="detail-card"> <view class="card-title"> <up-icon name="info-circle" size="18" color="#3c9cff"></up-icon> <text class="title-text">åºæ¬ä¿¡æ¯</text> </view> <view class="card-content"> <view class="info-item"> <text class="label">主ç产计åå·</text> <text class="value">{{ detailData.mpsNo || '-' }}</text> </view> <view class="info-item"> <text class="label">æ¥æº</text> <up-tag :text="detailData.source === 'éå®' ? 'éå®' : 'å é¨'" :type="detailData.source === 'éå®' ? 'primary' : 'info'" size="mini" /> </view> <view class="info-item"> <text class="label">ä¸åç¶æ</text> <up-tag :text="getStatusText(detailData.status)" :type="getStatusType(detailData.status)" size="mini" /> </view> </view> </view> <!-- 产åä¿¡æ¯å¡ç --> <view class="detail-card"> <view class="card-title"> <up-icon name="order" size="18" color="#3c9cff"></up-icon> <text class="title-text">产åä¿¡æ¯</text> </view> <view class="card-content"> <view class="info-item"> <text class="label">产ååç§°</text> <text class="value font-bold">{{ detailData.productName || '-' }}</text> </view> <view class="info-item"> <text class="label">è§æ ¼åå·</text> <text class="value">{{ detailData.model || '-' }}</text> </view> <view class="info-item"> <text class="label">æéæ°é</text> <text class="value highlight">{{ detailData.qtyRequired || 0 }} {{ detailData.unit || 'æ¹' }}</text> </view> <view class="info-item"> <text class="label">å·²ä¸åæ°é</text> <text class="value">{{ detailData.quantityIssued || 0 }} {{ detailData.unit || 'æ¹' }}</text> </view> </view> </view> <!-- æ¥æä¸å ³èå¡ç --> <view class="detail-card"> <view class="card-title"> <up-icon name="calendar" size="18" color="#3c9cff"></up-icon> <text class="title-text">æ¥æä¸å ³è</text> </view> <view class="card-content"> <view class="info-item"> <text class="label">éæ±æ¥æ</text> <text class="value">{{ formatDate(detailData.requiredDate) }}</text> </view> <view class="info-item"> <text class="label">æ¿è¯ºæ¥æ</text> <text class="value">{{ formatDate(detailData.promisedDeliveryDate) }}</text> </view> <view class="info-item"> <text class="label">éå®ååå·</text> <text class="value">{{ detailData.salesContractNo || '-' }}</text> </view> <view class="info-item"> <text class="label">客æ·åç§°</text> <text class="value">{{ detailData.customerName || '-' }}</text> </view> <view class="info-item"> <text class="label">项ç®åç§°</text> <text class="value">{{ detailData.projectName || '-' }}</text> </view> </view> </view> <!-- 夿³¨ä¿¡æ¯ --> <view class="detail-card"> <view class="card-title"> <up-icon name="edit-pen" size="18" color="#3c9cff"></up-icon> <text class="title-text">夿³¨</text> </view> <view class="card-content"> <view class="remark-box"> <text class="remark-text">{{ detailData.remark || 'æ 夿³¨' }}</text> </view> </view> </view> </view> <view v-else class="no-data"> <up-empty mode="data" text="ææ è¯¦æ æ°æ®"></up-empty> </view> </view> </template> <script setup> import { ref, reactive, onMounted } from "vue"; import { onLoad } from "@dcloudio/uni-app"; import dayjs from "dayjs"; import PageHeader from "@/components/PageHeader.vue"; const detailData = ref(null); // è¿åä¸ä¸é¡µ const goBack = () => { uni.navigateBack(); }; // æ ¼å¼åæ¥æ const formatDate = date => { return date ? dayjs(date).format("YYYY-MM-DD") : "-"; }; // è·åç¶æææ¬ const getStatusText = status => { const statusMap = { 0: "å¾ ä¸å", 1: "é¨åä¸å", 2: "å·²ä¸å", }; return statusMap[status] || "æªç¥"; }; // è·åç¶æç±»å const getStatusType = status => { const typeMap = { 0: "warning", 1: "primary", 2: "info", }; return typeMap[status] || "info"; }; onLoad(options => { if (options.data) { try { detailData.value = JSON.parse(decodeURIComponent(options.data)); } catch (e) { console.error("è§£ææ°æ®å¤±è´¥", e); uni.showToast({ title: "æ°æ®å 载失败", icon: "error", }); } } }); </script> <style scoped lang="scss"> .production-plan-detail { min-height: 100vh; background: #f8f9fa; padding-bottom: 40rpx; } .detail-container { padding: 20rpx; } .detail-card { background: #fff; border-radius: 16rpx; margin-bottom: 24rpx; overflow: hidden; box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.03); .card-title { display: flex; align-items: center; padding: 24rpx; border-bottom: 1rpx solid #f0f0f0; background: #fafafa; .title-text { font-size: 28rpx; font-weight: bold; color: #333; margin-left: 12rpx; } } .card-content { padding: 10rpx 24rpx; .info-item { display: flex; justify-content: space-between; align-items: center; padding: 20rpx 0; border-bottom: 1rpx solid #f9f9f9; &:last-child { border-bottom: none; } .label { font-size: 26rpx; color: #999; } .value { font-size: 26rpx; color: #333; text-align: right; max-width: 70%; &.font-bold { font-weight: bold; } &.highlight { color: #f56c6c; font-weight: bold; } } } .remark-box { padding: 20rpx 0; .remark-text { font-size: 26rpx; color: #666; line-height: 1.5; } } } } .no-data { padding-top: 200rpx; } </style> src/pages/productionManagement/mainProductionPlan/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,300 @@ <template> <view class="main-production-plan"> <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> <PageHeader title="主ç产计å" @back="goBack" /> <!-- æç´¢åºå --> <view class="search-section"> <view class="search-bar"> <view class="search-input"> <up-input class="search-text" placeholder="请è¾å ¥è®¡åå·æäº§ååç§°" v-model="searchForm.keyword" @change="handleQuery" clearable /> </view> <view class="filter-button" @click="handleQuery"> <up-icon name="search" size="24" color="#999"></up-icon> </view> </view> </view> <!-- å表åºå --> <scroll-view scroll-y class="list-container" v-if="tableData.length > 0" @scrolltolower="loadMore"> <view v-for="(item, index) in tableData" :key="item.id || index" @click="goDetail(item)"> <view class="ledger-item"> <view class="item-header"> <view class="item-left"> <view class="document-icon"> <up-icon name="file-text" size="16" color="#ffffff"></up-icon> </view> <text class="item-id">{{ item.mpsNo }}</text> </view> <view class="item-right"> <up-tag :text="getStatusText(item.status)" :type="getStatusType(item.status)" size="mini" /> </view> </view> <up-divider></up-divider> <view class="item-details"> <view class="detail-row"> <text class="detail-label">产ååç§°</text> <text class="detail-value">{{ item.productName || '-' }}</text> </view> <view class="detail-row"> <text class="detail-label">è§æ ¼åå·</text> <text class="detail-value">{{ item.model || '-' }}</text> </view> <view class="detail-row"> <text class="detail-label">æéæ°é</text> <text class="detail-value highlight">{{ item.qtyRequired || 0 }} {{ item.unit || 'æ¹' }}</text> </view> <view class="detail-row"> <text class="detail-label">éæ±æ¥æ</text> <text class="detail-value">{{ formatDate(item.requiredDate) }}</text> </view> <view class="detail-row"> <text class="detail-label">æ¥æº</text> <text class="detail-value">{{ item.source === 'éå®' ? 'éå®' : 'å é¨' }}</text> </view> </view> <view class="item-footer"> <text class="more-detail">æ¥ç详æ </text> <up-icon name="arrow-right" size="14" color="#999"></up-icon> </view> </view> </view> <up-loadmore :status="loadStatus" v-if="tableData.length >= page.size" /> </scroll-view> <view v-else class="no-data"> <up-empty mode="data" text="ææ ä¸»çäº§è®¡åæ°æ®"></up-empty> </view> </view> </template> <script setup> import { ref, reactive, toRefs, getCurrentInstance } from "vue"; import { onShow } from '@dcloudio/uni-app'; import dayjs from "dayjs"; import { productionPlanListPage } from "@/api/productionManagement/productionPlan.js"; import PageHeader from "@/components/PageHeader.vue"; const { proxy } = getCurrentInstance(); // å è½½ç¶æ const loading = ref(false); const loadStatus = ref('loadmore'); // åè¡¨æ°æ® const tableData = ref([]); // å页é ç½® const page = reactive({ current: 1, size: 10, total: 0, }); // æç´¢è¡¨åæ°æ® const data = reactive({ searchForm: { keyword: "", mpsNo: "", productName: "" }, }); const { searchForm } = toRefs(data); // è¿åä¸ä¸é¡µ const goBack = () => { uni.navigateBack(); }; // æ ¼å¼åæ¥æ const formatDate = (date) => { return date ? dayjs(date).format('YYYY-MM-DD') : '-'; }; // è·åç¶æææ¬ const getStatusText = (status) => { const statusMap = { 0: "å¾ ä¸å", 1: "é¨åä¸å", 2: "å·²ä¸å", }; return statusMap[status] || "æªç¥"; }; // è·åç¶æç±»å (uView tag type) const getStatusType = (status) => { const typeMap = { 0: "warning", 1: "primary", 2: "info", }; return typeMap[status] || "info"; }; // æ¥è¯¢å表 const handleQuery = () => { page.current = 1; tableData.value = []; getList(); }; // å è½½æ´å¤ const loadMore = () => { if (loadStatus.value === 'nomore' || loading.value) return; page.current++; getList(); }; // è·ååè¡¨æ°æ® const getList = () => { loading.value = true; loadStatus.value = 'loading'; // æé 请æ±åæ° // PC端æ¥å£æ¯æ mpsNo, productName çï¼è¿éç®åå¤çï¼å¦æ keyword åå¨ï¼åå°è¯å¹é const params = { current: page.current, size: page.size, mpsNo: searchForm.value.keyword, // ç®åå¤çï¼æç´¢å· productName: searchForm.value.keyword // ç®åå¤çï¼æç´¢åç§° }; productionPlanListPage(params).then((res) => { loading.value = false; const records = res.data.records || []; if (page.current === 1) { tableData.value = records; } else { tableData.value = [...tableData.value, ...records]; } if (records.length < page.size) { loadStatus.value = 'nomore'; } else { loadStatus.value = 'loadmore'; } page.total = res.data.total || 0; }).catch(() => { loading.value = false; loadStatus.value = 'loadmore'; uni.showToast({ title: 'å 载失败', icon: 'error' }); }); }; // 跳转详æ const goDetail = (item) => { uni.navigateTo({ url: `/pages/productionManagement/mainProductionPlan/detail?data=${encodeURIComponent(JSON.stringify(item))}` }); }; // 页颿¾ç¤ºæ¶å è½½æ°æ® onShow(() => { handleQuery(); }); </script> <style scoped lang="scss"> @import '@/styles/sales-common.scss'; .main-production-plan { min-height: 100vh; background: #f8f9fa; display: flex; flex-direction: column; } .list-container { flex: 1; height: 0; } .ledger-item { background: #fff; margin: 20rpx; padding: 20rpx; border-radius: 12rpx; box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); .item-header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 10rpx; .item-left { display: flex; align-items: center; .document-icon { width: 40rpx; height: 40rpx; background: #3c9cff; border-radius: 8rpx; display: flex; justify-content: center; align-items: center; margin-right: 16rpx; } .item-id { font-size: 28rpx; font-weight: bold; color: #333; } } } .item-details { padding: 10rpx 0; .detail-row { display: flex; justify-content: space-between; margin-bottom: 12rpx; .detail-label { font-size: 26rpx; color: #999; } .detail-value { font-size: 26rpx; color: #333; &.highlight { color: #f56c6c; font-weight: bold; } } } } .item-footer { display: flex; justify-content: flex-end; align-items: center; padding-top: 16rpx; border-top: 1rpx solid #f0f0f0; .more-detail { font-size: 24rpx; color: #999; margin-right: 8rpx; } } } .no-data { padding-top: 200rpx; } </style> src/pages/productionManagement/productionOrder/index.vue
@@ -9,8 +9,8 @@ <view class="search-input"> <up-input class="search-text" placeholder="请è¾å ¥å®¢æ·åç§°æç´¢" v-model="searchForm.customerName" placeholder="请è¾å ¥è®¢åå·æäº§ååç§°" v-model="searchForm.keyword" @change="handleQuery" clearable /> @@ -21,8 +21,8 @@ </view> </view> <!-- ç产订åå表 --> <view class="ledger-list" v-if="tableData.length > 0"> <!-- å表åºå --> <scroll-view scroll-y class="list-container" v-if="tableData.length > 0" @scrolltolower="loadMore"> <view v-for="(item, index) in tableData" :key="item.id || index"> <view class="ledger-item"> <view class="item-header"> @@ -30,54 +30,55 @@ <view class="document-icon"> <up-icon name="file-text" size="16" color="#ffffff"></up-icon> </view> <text class="item-id">{{ item.salesContractNo }}</text> <text class="item-id">{{ item.npsNo }}</text> </view> <view class="item-right"> <up-tag :text="getStatusText(item.status)" :type="getStatusType(item.status)" size="mini" /> </view> </view> <up-divider></up-divider> <view class="item-details"> <view class="detail-row"> <text class="detail-label">å½å ¥æ¥æ</text> <text class="detail-value">{{ item.entryDate }}</text> </view> <view class="detail-row"> <text class="detail-label">客æ·ååå·</text> <text class="detail-value">{{ item.customerContractNo }}</text> </view> <view class="detail-row"> <text class="detail-label">客æ·åç§°</text> <text class="detail-value">{{ item.customerName }}</text> </view> <view class="detail-row"> <text class="detail-label">项ç®åç§°</text> <text class="detail-value">{{ item.projectName }}</text> </view> <view class="detail-row"> <text class="detail-label">产å大类</text> <text class="detail-value">{{ item.productCategory }}</text> <text class="detail-label">产ååç§°</text> <text class="detail-value font-bold">{{ item.productName || '-' }}</text> </view> <view class="detail-row"> <text class="detail-label">è§æ ¼åå·</text> <text class="detail-value">{{ item.specificationModel }}</text> <text class="detail-value">{{ item.model || '-' }}</text> </view> <view class="detail-row"> <text class="detail-label">æ°é</text> <text class="detail-value">{{ item.quantity }} {{ item.unit }}</text> <text class="detail-label">è®¢åæ°é</text> <text class="detail-value">{{ item.quantity || 0 }}</text> </view> <view class="detail-row"> <text class="detail-label">æäº§æ°é</text> <text class="detail-value highlight">{{ item.schedulingNum }}</text> <text class="detail-label">宿è¿åº¦</text> <view class="progress-box"> <up-line-progress :percentage="toProgressPercentage(item.completionStatus)" :activeColor="progressColor(item.completionStatus)" height="10"></up-line-progress> <text class="progress-text">{{ item.completeQuantity || 0 }} / {{ item.quantity || 0 }}</text> </view> </view> <view class="detail-row"> <text class="detail-label">å®å·¥æ°é</text> <text class="detail-value highlight">{{ item.successNum }}</text> <text class="detail-label">计å宿</text> <text class="detail-value">{{ formatDate(item.planCompleteTime) }}</text> </view> </view> <view class="item-footer"> <view class="action-btns"> <up-button type="primary" size="mini" plain text="æ¥æº" @click="goSource(item)"></up-button> <up-button type="success" size="mini" plain text="é¢æè¯¦æ " @click="goPickingDetail(item)"></up-button> </view> </view> </view> </view> </view> <up-loadmore :status="loadStatus" v-if="tableData.length >= page.size" /> </scroll-view> <view v-else class="no-data"> <text>ææ çäº§è®¢åæ°æ®</text> <up-empty mode="data" text="ææ çäº§è®¢åæ°æ®"></up-empty> </view> </view> </template> @@ -86,70 +87,126 @@ import { ref, reactive, toRefs, getCurrentInstance } from "vue"; import { onShow } from '@dcloudio/uni-app'; import dayjs from "dayjs"; import {schedulingListPage} from "@/api/productionManagement/productionOrder.js"; import { productOrderListPage } from "@/api/productionManagement/productionOrder.js"; import PageHeader from "@/components/PageHeader.vue"; const { proxy } = getCurrentInstance(); // å è½½ç¶æ const loading = ref(false); const loadStatus = ref('loadmore'); // åè¡¨æ°æ® const tableData = ref([]); // å页é ç½® const page = reactive({ current: -1, size: -1, current: 1, size: 10, total: 0, }); // æç´¢è¡¨åæ°æ® const data = reactive({ searchForm: { customerName: "", keyword: "", }, }); const { searchForm } = toRefs(data); // éç¨æç¤ºå½æ° const showLoadingToast = (message) => { uni.showLoading({ title: message, mask: true }); }; const closeToast = () => { uni.hideLoading(); }; // è¿åä¸ä¸é¡µ const goBack = () => { uni.navigateBack(); }; // æ ¼å¼åæ¥æ const formatDate = (date) => { return date ? dayjs(date).format('YYYY-MM-DD') : '-'; }; // è·åç¶æææ¬ const getStatusText = (status) => { const statusMap = { 1: "å¾ å¼å§", 2: "è¿è¡ä¸", 3: "已宿", 4: "已忶", }; return statusMap[status] || "æªç¥"; }; // è·åç¶æç±»å const getStatusType = (status) => { const typeMap = { 1: "primary", 2: "warning", 3: "success", 4: "danger", }; return typeMap[status] || "info"; }; // 宿è¿åº¦ç¾åæ¯ const toProgressPercentage = (val) => { const n = Number(val); if (!Number.isFinite(n)) return 0; if (n <= 0) return 0; if (n >= 100) return 100; return Math.round(n); }; // è¿åº¦æ¡é¢è² const progressColor = (percentage) => { const p = toProgressPercentage(percentage); if (p < 30) return "#f56c6c"; if (p < 50) return "#e6a23c"; if (p < 80) return "#409eff"; return "#67c23a"; }; // æ¥è¯¢å表 const handleQuery = () => { page.current = 1; tableData.value = []; // éç½®åè¡¨æ°æ® tableData.value = []; getList(); }; // å è½½æ´å¤ const loadMore = () => { if (loadStatus.value === 'nomore' || loading.value) return; page.current++; getList(); }; // è·ååè¡¨æ°æ® const getList = () => { loading.value = true; showLoadingToast('å è½½ä¸...'); loadStatus.value = 'loading'; // æé 请æ±åæ° const params = { ...searchForm.value, ...page }; const params = { current: page.current, size: page.size, npsNo: searchForm.value.keyword, productName: searchForm.value.keyword }; schedulingListPage(params).then((res) => { productOrderListPage(params).then((res) => { loading.value = false; closeToast(); const records = res.data.records || []; if (page.current === 1) { tableData.value = records; } else { tableData.value = [...tableData.value, ...records]; } tableData.value = res.data.records || []; if (records.length < page.size) { loadStatus.value = 'nomore'; } else { loadStatus.value = 'loadmore'; } page.total = res.data.total || 0; }).catch(() => { loading.value = false; closeToast(); loadStatus.value = 'loadmore'; uni.showToast({ title: 'å 载失败', icon: 'error' @@ -157,37 +214,131 @@ }); }; // è·³è½¬æ¥æº const goSource = (item) => { uni.navigateTo({ url: `/pages/productionManagement/productionOrder/source?id=${item.id}&productName=${encodeURIComponent(item.productName)}&model=${encodeURIComponent(item.model)}&quantity=${item.quantity}` }); }; // è·³è½¬é¢æè¯¦æ const goPickingDetail = (item) => { uni.navigateTo({ url: `/pages/productionManagement/productionOrder/pickingDetail?id=${item.id}&npsNo=${item.npsNo}` }); }; // 页颿¾ç¤ºæ¶å è½½æ°æ® onShow(() => { // å è½½åè¡¨æ°æ® getList(); handleQuery(); }); </script> <style scoped lang="scss"> @import '@/styles/sales-common.scss'; // ç产订å页颿 ·å¼ .production-order { min-height: 100vh; background: #f8f9fa; position: relative; display: flex; flex-direction: column; } // éåé¨åæ ·å¼ä»¥éé ç产订å .list-container { flex: 1; height: 0; } .ledger-item { .detail-value.highlight { color: #ff6b35; font-weight: 600; background: #fff; margin: 20rpx; padding: 24rpx; border-radius: 16rpx; box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06); .item-header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 12rpx; .item-left { display: flex; align-items: center; .document-icon { width: 44rpx; height: 44rpx; background: #3c9cff; border-radius: 10rpx; display: flex; justify-content: center; align-items: center; margin-right: 20rpx; } .item-id { font-size: 30rpx; font-weight: bold; color: #333; } } } // éé uView ç»ä»¶æ ·å¼ :deep(.up-input) { background: transparent; .item-details { padding: 16rpx 0; .detail-row { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 16rpx; .detail-label { font-size: 26rpx; color: #999; min-width: 140rpx; } :deep(.up-datetime-picker) { width: 100%; .detail-value { font-size: 26rpx; color: #333; text-align: right; &.font-bold { font-weight: bold; } } .progress-box { flex: 1; margin-left: 40rpx; .progress-text { font-size: 22rpx; color: #999; margin-top: 4rpx; display: block; text-align: right; } } } } .item-footer { padding-top: 20rpx; border-top: 1rpx solid #f0f0f0; display: flex; justify-content: flex-end; .action-btns { display: flex; gap: 20rpx; } } } .no-data { padding-top: 200rpx; } </style> src/pages/productionManagement/productionOrder/pickingDetail.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,350 @@ <template> <view class="picking-detail"> <PageHeader :title="é¢æè¯¦æ " @back="goBack" /> <scroll-view scroll-y class="detail-list" v-if="detailList.length > 0"> <view v-for="(item, index) in detailList" :key="index" class="material-card"> <view class="card-header"> <text class="material-name">{{ item.materialName || item.productName || '-' }}</text> <up-tag :text="item.operationName || '-'" type="info" size="mini" plain /> </view> <view class="card-content"> <view class="info-grid"> <view class="info-item"> <text class="label">è§æ ¼åå·</text> <text class="value">{{ item.model || '-' }}</text> </view> <view class="info-item"> <text class="label">åä½</text> <text class="value">{{ item.unit || '-' }}</text> </view> <view class="info-item"> <text class="label">é颿°é</text> <text class="value highlight">{{ item.qtyRequired || item.demandedQuantity || 0 }}</text> </view> <view class="info-item"> <text class="label">已颿°é</text> <text class="value success">{{ item.qtyPicked || item.pickQuantity || 0 }}</text> </view> <view class="info-item"> <text class="label">è¡¥ææ°é</text> <view class="value link" @click="showSupplementDetail(item)"> {{ item.qtySupplement || item.feedingQty || 0 }} </view> </view> <view class="info-item"> <text class="label">éææ°é</text> <text class="value">{{ item.returnQty || 0 }}</text> </view> </view> <view class="remark-row" v-if="item.remark"> <text class="label">夿³¨ï¼</text> <text class="value">{{ item.remark }}</text> </view> </view> </view> </scroll-view> <view v-else class="no-data"> <up-empty mode="data" text="ææ é¢æè¯¦æ "></up-empty> </view> <!-- è¡¥æè®°å½å¼¹çª --> <up-popup :show="showPopup" mode="bottom" @close="showPopup = false" round="10"> <view class="popup-content"> <view class="popup-header"> <text class="title">è¡¥æè®°å½</text> <up-icon name="close" size="20" @click="showPopup = false"></up-icon> </view> <scroll-view scroll-y class="record-list"> <view v-if="supplementRecords.length > 0"> <view v-for="(record, rIndex) in supplementRecords" :key="rIndex" class="record-item"> <view class="record-row"> <text class="record-label">è¡¥ææ°éï¼</text> <text class="record-value highlight">{{ record.pickQuantity || 0 }}</text> </view> <view class="record-row"> <text class="record-label">è¡¥æäººï¼</text> <text class="record-value">{{ record.supplementUserName || '-' }}</text> </view> <view class="record-row"> <text class="record-label">è¡¥ææ¥æï¼</text> <text class="record-value">{{ record.supplementTime || '-' }}</text> </view> <view class="record-row"> <text class="record-label">è¡¥æåå ï¼</text> <text class="record-value">{{ record.feedingReason || '-' }}</text> </view> </view> </view> <view v-else class="no-record"> <text>ææ è¡¥æè®°å½</text> </view> </scroll-view> </view> </up-popup> </view> </template> <script setup> import { ref, reactive } from "vue"; import { onLoad } from "@dcloudio/uni-app"; import { listMaterialPickingDetail, listMaterialSupplementRecord, } from "@/api/productionManagement/productionOrder.js"; import PageHeader from "@/components/PageHeader.vue"; const npsNo = ref(""); const productionOrderId = ref(""); const detailList = ref([]); const loading = ref(false); // å¼¹çªç¸å ³ const showPopup = ref(false); const supplementRecords = ref([]); const recordLoading = ref(false); const goBack = () => { uni.navigateBack(); }; const calculatePending = item => { const required = Number(item.qtyRequired || item.demandedQuantity || 0); const picked = Number(item.qtyPicked || item.pickQuantity || 0); return Math.max(0, required - picked); }; onLoad(options => { if (options.id) { productionOrderId.value = options.id; npsNo.value = options.npsNo || ""; fetchDetail(options.id); } }); const fetchDetail = id => { loading.value = true; listMaterialPickingDetail(id) .then(res => { detailList.value = res.data?.records || res.data || []; loading.value = false; }) .catch(() => { loading.value = false; uni.showToast({ title: "è·å详æ 失败", icon: "error", }); }); }; const showSupplementDetail = item => { const qty = Number(item.qtySupplement || item.feedingQty || 0); if (qty <= 0) return; showPopup.value = true; recordLoading.value = true; supplementRecords.value = []; listMaterialSupplementRecord({ pickId: item.id, productionOrderId: productionOrderId.value, }) .then(res => { supplementRecords.value = res.data || []; recordLoading.value = false; }) .catch(() => { recordLoading.value = false; uni.showToast({ title: "è·åè¡¥æè®°å½å¤±è´¥", icon: "error", }); }); }; </script> <style scoped lang="scss"> .picking-detail { min-height: 100vh; background: #f8f9fa; display: flex; flex-direction: column; } .detail-list { flex: 1; height: 0; padding: 20rpx; } .material-card { background: #fff; margin-bottom: 24rpx; padding: 24rpx; border-radius: 16rpx; box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05); .card-header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 20rpx; border-bottom: 1rpx solid #f5f5f5; margin-bottom: 20rpx; .material-name { font-size: 30rpx; font-weight: bold; color: #333; } } .info-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20rpx; .info-item { display: flex; flex-direction: column; .label { font-size: 24rpx; color: #999; margin-bottom: 4rpx; } .value { font-size: 26rpx; color: #333; &.highlight { color: #f56c6c; font-weight: bold; } &.success { color: #67c23a; font-weight: bold; } &.warning { color: #e6a23c; font-weight: bold; } &.link { color: #3c9cff; text-decoration: underline; } } } } .remark-row { margin-top: 20rpx; padding-top: 16rpx; border-top: 1rpx dashed #eee; display: flex; .label { font-size: 24rpx; color: #999; } .value { font-size: 24rpx; color: #666; flex: 1; } } } .no-data { padding-top: 200rpx; } /* å¼¹çªæ ·å¼ */ .popup-content { background: #fff; padding: 30rpx; max-height: 70vh; display: flex; flex-direction: column; } .popup-header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 30rpx; border-bottom: 1rpx solid #eee; .title { font-size: 32rpx; font-weight: bold; color: #333; } } .record-list { flex: 1; height: 0; padding-top: 20rpx; } .record-item { padding: 24rpx; background: #f9f9f9; border-radius: 12rpx; margin-bottom: 20rpx; .record-row { display: flex; margin-bottom: 10rpx; &:last-child { margin-bottom: 0; } .record-label { font-size: 26rpx; color: #999; width: 140rpx; } .record-value { font-size: 26rpx; color: #333; flex: 1; &.highlight { color: #f56c6c; font-weight: bold; } } } } .no-record { padding: 100rpx 0; text-align: center; color: #999; font-size: 28rpx; } </style> src/pages/productionManagement/productionOrder/source.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,166 @@ <template> <view class="production-order-source"> <PageHeader title="æ¥æºæ°æ®" @back="goBack" /> <view class="summary-card" v-if="summary"> <view class="summary-item"> <text class="label">产ååç§°</text> <up-tag :text="summary.productName || '-'" type="primary" size="mini" /> </view> <view class="summary-item"> <text class="label">è§æ ¼åå·</text> <text class="value">{{ summary.model || '-' }}</text> </view> <view class="summary-item"> <text class="label">éæ±æ°é</text> <text class="value highlight">{{ summary.quantity || 0 }}</text> </view> </view> <scroll-view scroll-y class="source-list" v-if="sourceList.length > 0"> <view v-for="(item, index) in sourceList" :key="index" class="source-card"> <view class="card-header"> <text class="plan-no">计åå·: {{ item.mpsNo || '-' }}</text> <up-tag :text="item.source || 'æªç¥'" :type="item.source === 'éå®' ? 'primary' : 'warning'" size="mini" /> </view> <view class="card-content"> <view class="info-row"> <text class="info-label">ååå·</text> <text class="info-value">{{ item.salesContractNo || '-' }}</text> </view> <view class="info-row"> <text class="info-label">客æ·åç§°</text> <text class="info-value">{{ item.customerName || '-' }}</text> </view> <view class="info-row"> <text class="info-label">项ç®åç§°</text> <text class="info-value">{{ item.projectName || '-' }}</text> </view> <view class="info-row"> <text class="info-label">éæ±æ°é</text> <text class="info-value">{{ item.qtyRequired || 0 }} {{ item.unit || '' }}</text> </view> <view class="info-row"> <text class="info-label">éæ±æ¥æ</text> <text class="info-value">{{ formatDate(item.requiredDate) }}</text> </view> </view> </view> </scroll-view> <view v-else class="no-data"> <up-empty mode="data" text="ææ æ¥æºæ°æ®"></up-empty> </view> </view> </template> <script setup> import { ref, reactive, onMounted } from "vue"; import { onLoad } from '@dcloudio/uni-app'; import dayjs from "dayjs"; import { getProductOrderSource } from "@/api/productionManagement/productionOrder.js"; import PageHeader from "@/components/PageHeader.vue"; const summary = ref(null); const sourceList = ref([]); const loading = ref(false); const goBack = () => { uni.navigateBack(); }; const formatDate = (date) => { return date ? dayjs(date).format('YYYY-MM-DD') : '-'; }; onLoad((options) => { if (options.id) { summary.value = { productName: decodeURIComponent(options.productName || ''), model: decodeURIComponent(options.model || ''), quantity: options.quantity || 0 }; fetchSource(options.id); } }); const fetchSource = (id) => { loading.value = true; getProductOrderSource(id).then(res => { sourceList.value = res.data || []; loading.value = false; }).catch(() => { loading.value = false; uni.showToast({ title: 'è·åæ¥æºå¤±è´¥', icon: 'error' }); }); }; </script> <style scoped lang="scss"> .production-order-source { min-height: 100vh; background: #f8f9fa; display: flex; flex-direction: column; } .summary-card { background: #fff; margin: 20rpx; padding: 24rpx; border-radius: 16rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05); .summary-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12rpx; &:last-child { margin-bottom: 0; } .label { font-size: 26rpx; color: #999; } .value { font-size: 26rpx; color: #333; } .highlight { color: #f56c6c; font-weight: bold; } } } .source-list { flex: 1; height: 0; padding: 0 20rpx; } .source-card { background: #fff; margin-bottom: 20rpx; padding: 24rpx; border-radius: 16rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.03); .card-header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 16rpx; border-bottom: 1rpx solid #f9f9f9; margin-bottom: 16rpx; .plan-no { font-size: 28rpx; font-weight: bold; color: #333; } } .info-row { display: flex; justify-content: space-between; margin-bottom: 12rpx; &:last-child { margin-bottom: 0; } .info-label { font-size: 24rpx; color: #999; } .info-value { font-size: 24rpx; color: #666; } } } .no-data { padding-top: 100rpx; } </style> src/pages/works.vue
@@ -569,10 +569,10 @@ // ç产管æ§åè½æ°æ® const productionItems = reactive([ // { // icon: "/static/images/icon/shengchandingdan@2x.svg", // label: "ç产订å", // }, { icon: "/static/images/icon/shengchandingdan@2x.svg", label: "ç产订å", }, // { // icon: "/static/images/icon/shengchanpaigong@2x.svg", // label: "ç产派工", @@ -581,6 +581,10 @@ // icon: "/static/images/icon/shengchanpaichan@2x.svg", // label: "å·¥åºæäº§", // }, { icon: "/static/images/icon/shengchanbaogong.svg", label: "主ç产计å", }, { icon: "/static/images/icon/shengchanbaogong.svg", label: "ç产æ¥å·¥", @@ -843,6 +847,11 @@ url: "/pages/productionManagement/workOrder/index", }); break; case "主ç产计å": uni.navigateTo({ url: "/pages/productionManagement/mainProductionPlan/index", }); break; case "ç产æ¥å·¥": getcode(); break;