| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="production-traceability"> |
| | | <PageHeader title="ç产追溯" |
| | | @back="goBack" /> |
| | | <!-- æç´¢åºå --> |
| | | <view class="search-section"> |
| | | <view class="search-bar" |
| | | @click="openNpsNoSelector"> |
| | | <view class="search-input"> |
| | | <text v-if="!selectedNpsNo" |
| | | class="placeholder">è¯·éæ©ç产订åå·</text> |
| | | <text v-else |
| | | class="value">{{ selectedNpsNoLabel }}</text> |
| | | </view> |
| | | <view class="search-button"> |
| | | <up-icon name="arrow-down" |
| | | size="20" |
| | | color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- å
容åºå --> |
| | | <view class="content-container" |
| | | v-if="rowData.productionOrderDto"> |
| | | <!-- åºç¡ä¿¡æ¯ --> |
| | | <view class="info-card"> |
| | | <view class="card-title">åºç¡ä¿¡æ¯</view> |
| | | <view class="info-grid"> |
| | | <view class="info-item"> |
| | | <text class="label">ç产订åå·</text> |
| | | <text class="value">{{ rowData.productionOrderDto.npsNo || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">产ååç§°</text> |
| | | <text class="value">{{ rowData.productionOrderDto.productName || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">产åè§æ ¼</text> |
| | | <text class="value">{{ rowData.productionOrderDto.model || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">è®¡åæ°é</text> |
| | | <text class="value">{{ rowData.productionOrderDto.quantity || 0 }} {{ rowData.productionOrderDto.unit || '' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">å½åç¶æ</text> |
| | | <up-tag :text="getStatusText(rowData.productionOrderDto.status)" |
| | | style="width:100rpx" |
| | | :type="getStatusType(rowData.productionOrderDto.status)" |
| | | size="mini" /> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">客æ·åç§°</text> |
| | | <text class="value">{{ rowData.productionOrderDto.customerName || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">å¼å§æ¥æ</text> |
| | | <text class="value">{{ formatDate(rowData.productionOrderDto.startTime) }}</text> |
| | | </view> |
| | | <view class="info-item full-width"> |
| | | <text class="label">宿è¿åº¦</text> |
| | | <view class="progress-container"> |
| | | <up-line-progress :percentage="formatProgress(rowData.productionOrderDto.completionStatus)" |
| | | :activeColor="progressColor(rowData.productionOrderDto.completionStatus)" |
| | | height="8"></up-line-progress> |
| | | <text class="progress-text">{{ formatProgress(rowData.productionOrderDto.completionStatus) }}%</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- å·¥åä¿¡æ¯ --> |
| | | <view class="work-order-section" |
| | | v-if="rowData.productionRecords && rowData.productionRecords.length > 0"> |
| | | <view class="section-title">å·¥åä¿¡æ¯</view> |
| | | <view v-for="(item, index) in rowData.productionRecords" |
| | | :key="index" |
| | | class="work-order-card"> |
| | | <view class="card-header"> |
| | | <text class="work-order-no">{{ item.workOrder.workOrderNo }}</text> |
| | | <text class="progress-tag" |
| | | :style="{ color: progressColor(item.workOrder.completionStatus) }">{{ item.workOrder.completionStatus || 0 }}%</text> |
| | | </view> |
| | | <view class="card-content"> |
| | | <view class="content-row"> |
| | | <text class="label">产å/è§æ ¼ï¼</text> |
| | | <text class="value">{{ item.workOrder.productName }} / {{ item.workOrder.model }}</text> |
| | | </view> |
| | | <view class="content-row"> |
| | | <text class="label">éæ±/宿ï¼</text> |
| | | <text class="value">{{ item.workOrder.planQuantity }} / {{ item.workOrder.completeQuantity }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="card-footer"> |
| | | <up-button type="primary" |
| | | size="small" |
| | | plain |
| | | text="æ¥å·¥è®°å½" |
| | | @click="handleShowReports(item)"></up-button> |
| | | <up-button type="success" |
| | | size="small" |
| | | plain |
| | | text="è´¨æ£ä¿¡æ¯" |
| | | @click="handleShowQuality(item)"></up-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else |
| | | class="no-data-minor"> |
| | | <up-empty mode="data" |
| | | text="ææ å·¥åä¿¡æ¯" |
| | | icon-size="40"></up-empty> |
| | | </view> |
| | | </view> |
| | | <view v-else |
| | | class="no-data"> |
| | | <up-empty mode="search" |
| | | text="è¯·éæ©ç产订åå·æ¥ç追溯信æ¯"></up-empty> |
| | | </view> |
| | | <!-- ç产订åå·éæ©å¼¹çª --> |
| | | <up-popup :show="showNpsNoSelector" |
| | | mode="bottom" |
| | | @close="showNpsNoSelector = false" |
| | | round="10"> |
| | | <view class="selector-popup"> |
| | | <view class="popup-header"> |
| | | <text class="popup-title">éæ©ç产订åå·</text> |
| | | <up-icon name="close" |
| | | size="20" |
| | | @click="showNpsNoSelector = false"></up-icon> |
| | | </view> |
| | | <view class="search-box"> |
| | | <up-search placeholder="è¾å
¥å
³é®åæç´¢" |
| | | v-model="npsNoQuery" |
| | | :show-action="false" |
| | | @change="handleNpsNoSearch" |
| | | @search="handleNpsNoSearch" |
| | | :loading="npsNoLoading"></up-search> |
| | | </view> |
| | | <scroll-view scroll-y |
| | | class="options-list"> |
| | | <view v-for="item in npsNoOptions" |
| | | :key="item.id" |
| | | class="option-item" |
| | | @click="onSelectNpsNo(item)"> |
| | | <view class="option-main"> |
| | | <text class="nps-no">{{ item.npsNo }}</text> |
| | | <text class="product-info">{{ item.productName }} / {{ item.model }}</text> |
| | | </view> |
| | | <up-icon v-if="selectedNpsNo === item.id" |
| | | name="checkbox-mark" |
| | | color="#3c9cff" |
| | | size="20"></up-icon> |
| | | </view> |
| | | <view v-if="npsNoOptions.length === 0" |
| | | class="no-options"> |
| | | <text>{{ npsNoLoading ? 'å è½½ä¸...' : 'ææ é项' }}</text> |
| | | </view> |
| | | </scroll-view> |
| | | </view> |
| | | </up-popup> |
| | | <!-- æ¥å·¥è¯¦æ
å¼¹çª --> |
| | | <up-popup :show="reportPopupVisible" |
| | | mode="bottom" |
| | | @close="reportPopupVisible = false" |
| | | round="10"> |
| | | <view class="popup-content"> |
| | | <view class="popup-header"> |
| | | <text class="popup-title">ç产æ¥å·¥è¯¦æ
</text> |
| | | <up-icon name="close" |
| | | size="20" |
| | | @click="reportPopupVisible = false"></up-icon> |
| | | </view> |
| | | <scroll-view scroll-y |
| | | class="popup-scroll"> |
| | | <view class="detail-info"> |
| | | <view class="info-row"><text class="label">å·¥åå·ï¼</text><text class="value">{{ detailData.workOrder.workOrderNo }}</text></view> |
| | | <view class="info-row"><text class="label">计å/宿ï¼</text><text class="value">{{ detailData.workOrder.planQuantity }} / {{ detailData.workOrder.completeQuantity }}</text></view> |
| | | <view class="info-row"><text class="label">å®é
æ¶é´ï¼</text><text class="value">{{ formatDate(detailData.workOrder.actualStartTime) }} è³ {{ formatDate(detailData.workOrder.actualEndTime) }}</text></view> |
| | | </view> |
| | | <view class="list-title">æ¥å·¥æç»</view> |
| | | <view v-for="(report, idx) in detailData.reports" |
| | | :key="idx" |
| | | class="detail-item"> |
| | | <view class="item-main"> |
| | | <view class="item-row"><text class="label">æ¥å·¥åå·ï¼</text><text class="value">{{ report.productNo }}</text></view> |
| | | <view class="item-row"><text class="label">å建人ï¼</text><text class="value">{{ report.userName }}</text></view> |
| | | <view class="item-row"><text class="label">å建æ¶é´ï¼</text><text class="value">{{ formatDate(report.createTime, '{y}-{m}-{d} {h}:{i}') }}</text></view> |
| | | </view> |
| | | <view class="item-actions"> |
| | | <text class="action-link" |
| | | @click="showParams(report.productionOperationParamList)">åæ°è¯¦æ
</text> |
| | | <text class="action-link green" |
| | | @click="handleShowInput(report.id)">æå
¥è¯¦æ
</text> |
| | | </view> |
| | | </view> |
| | | <view v-if="!detailData.reports || detailData.reports.length === 0" |
| | | class="no-data-minor">ææ æ¥å·¥æç»</view> |
| | | </scroll-view> |
| | | </view> |
| | | </up-popup> |
| | | <!-- æå
¥è¯¦æ
å¼¹çª --> |
| | | <up-popup :show="inputPopupVisible" |
| | | mode="bottom" |
| | | @close="inputPopupVisible = false" |
| | | round="10"> |
| | | <view class="popup-content"> |
| | | <view class="popup-header"> |
| | | <text class="popup-title">æå
¥ä¿¡æ¯è¯¦æ
</text> |
| | | <up-icon name="close" |
| | | size="20" |
| | | @click="inputPopupVisible = false"></up-icon> |
| | | </view> |
| | | <scroll-view scroll-y |
| | | class="popup-scroll"> |
| | | <view class="input-list-popup"> |
| | | <view v-for="(item, idx) in inputListData" |
| | | :key="idx" |
| | | class="input-item"> |
| | | <view class="input-row"><text class="label">ç©æåç§°ï¼</text><text class="value">{{ item.materialName }}</text></view> |
| | | <view class="input-row"><text class="label">è§æ ¼åå·ï¼</text><text class="value">{{ item.model }}</text></view> |
| | | <view class="input-row"><text class="label">æå
¥æ°éï¼</text><text class="value">{{ item.quantity }} {{ item.unit }}</text></view> |
| | | <view class="input-row"><text class="label">æ¹æ¬¡å·ï¼</text><text class="value">{{ item.batchNo }}</text></view> |
| | | </view> |
| | | <view v-if="!inputListData || inputListData.length === 0" |
| | | class="no-data-minor">{{ inputLoading ? 'å è½½ä¸...' : 'ææ æå
¥è®°å½' }}</view> |
| | | </view> |
| | | </scroll-view> |
| | | </view> |
| | | </up-popup> |
| | | <!-- è´¨æ£è¯¦æ
å¼¹çª --> |
| | | <up-popup :show="qualityPopupVisible" |
| | | mode="bottom" |
| | | @close="qualityPopupVisible = false" |
| | | round="10"> |
| | | <view class="popup-content"> |
| | | <view class="popup-header"> |
| | | <text class="popup-title">è´¨æ£è¯¦æ
</text> |
| | | <up-icon name="close" |
| | | size="20" |
| | | @click="qualityPopupVisible = false"></up-icon> |
| | | </view> |
| | | <scroll-view scroll-y |
| | | class="popup-scroll"> |
| | | <view v-for="(record, idx) in qualityRecords" |
| | | :key="idx" |
| | | class="quality-record"> |
| | | <view class="record-title">æ£æµè®°å½ {{ idx + 1 }}</view> |
| | | <view class="info-grid"> |
| | | <view class="info-item"><text class="label">æ£æµæ¥æ</text><text class="value">{{ formatDate(record.createTime) }}</text></view> |
| | | <view class="info-item"><text class="label">æ£æµç»æ</text><up-tag style="width:100rpx" |
| | | :text="record.checkResult || 'å¾
æ£æµ'" |
| | | :type="record.checkResult === 'åæ ¼' ? 'success' : 'error'" |
| | | size="mini" /></view> |
| | | <view class="info-item"><text class="label">æ£éªå</text><text class="value">{{ record.userName }}</text></view> |
| | | <view class="info-item"><text class="label">æ°é</text><text class="value">{{ record.quantity }} {{ record.unit }}</text></view> |
| | | </view> |
| | | <view class="params-table"> |
| | | <view class="table-header"> |
| | | <text class="col">ææ </text> |
| | | <text class="col">æ åå¼</text> |
| | | <text class="col">å®é
å¼</text> |
| | | </view> |
| | | <view v-for="(param, pIdx) in record.inspectParamList" |
| | | :key="pIdx" |
| | | class="table-row"> |
| | | <text class="col">{{ param.parameterItem }}</text> |
| | | <text class="col">{{ param.standardValue }}</text> |
| | | <text class="col" |
| | | :class="{ 'error-text': param.testValue != param.standardValue }">{{ param.testValue }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-if="!qualityRecords || qualityRecords.length === 0" |
| | | class="no-data-minor">ææ è´¨æ£è®°å½</view> |
| | | </scroll-view> |
| | | </view> |
| | | </up-popup> |
| | | <!-- åæ°è¯¦æ
å¼¹çª --> |
| | | <up-modal :show="paramModalVisible" |
| | | title="åæ°è¯¦æ
" |
| | | @confirm="paramModalVisible = false"> |
| | | <view class="modal-content"> |
| | | <view v-for="(param, idx) in currentParams" |
| | | :key="idx" |
| | | class="param-row"> |
| | | <text class="label">{{ param.paramName }}ï¼</text> |
| | | <text class="value">{{ param.inputValue }} {{ param.unit && param.unit !== '/' ? param.unit : '' }}</text> |
| | | </view> |
| | | <view v-if="!currentParams || currentParams.length === 0" |
| | | class="no-data-minor">ææ åæ°æ°æ®</view> |
| | | </view> |
| | | </up-modal> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed } from "vue"; |
| | | import { onLoad } from "@dcloudio/uni-app"; |
| | | import { |
| | | getOrderDetail, |
| | | productOrderListPage, |
| | | } from "@/api/productionManagement/productionOrder"; |
| | | import { productionProductInputListPage } from "@/api/productionManagement/productionProductMain"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { parseTime } from "@/utils/ruoyi"; |
| | | |
| | | // éæ©å¨ç¸å
³ |
| | | const showNpsNoSelector = ref(false); |
| | | const npsNoQuery = ref(""); |
| | | const npsNoOptions = ref([]); |
| | | const npsNoLoading = ref(false); |
| | | const selectedNpsNo = ref(null); |
| | | const selectedNpsNoLabel = ref(""); |
| | | |
| | | const rowData = reactive({ |
| | | productionOrderDto: null, |
| | | productionRecords: [], |
| | | }); |
| | | |
| | | // æ¥å·¥è¯¦æ
|
| | | const reportPopupVisible = ref(false); |
| | | const detailData = ref({ workOrder: {}, reports: [] }); |
| | | |
| | | // æå
¥è¯¦æ
|
| | | const inputPopupVisible = ref(false); |
| | | const inputListData = ref([]); |
| | | const inputLoading = ref(false); |
| | | |
| | | // è´¨æ£è¯¦æ
|
| | | const qualityPopupVisible = ref(false); |
| | | const qualityRecords = ref([]); |
| | | |
| | | // åæ°è¯¦æ
|
| | | const paramModalVisible = ref(false); |
| | | const currentParams = ref([]); |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const openNpsNoSelector = () => { |
| | | showNpsNoSelector.value = true; |
| | | if (npsNoOptions.value.length === 0) { |
| | | handleNpsNoSearch(); |
| | | } |
| | | }; |
| | | |
| | | const handleNpsNoSearch = async () => { |
| | | npsNoLoading.value = true; |
| | | try { |
| | | const res = await productOrderListPage({ |
| | | npsNo: npsNoQuery.value || "", |
| | | pageNum: 1, |
| | | pageSize: 50, |
| | | }); |
| | | npsNoOptions.value = res.data?.records || res.rows || []; |
| | | } catch (error) { |
| | | console.error(error); |
| | | } finally { |
| | | npsNoLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const onSelectNpsNo = async item => { |
| | | selectedNpsNo.value = item.id; |
| | | selectedNpsNoLabel.value = item.npsNo; |
| | | showNpsNoSelector.value = false; |
| | | |
| | | uni.showLoading({ title: "å è½½ä¸..." }); |
| | | try { |
| | | const res = await getOrderDetail(item.npsNo); |
| | | if (res.code === 200 && res.data) { |
| | | const { productionOrder, workOrderList } = res.data; |
| | | rowData.productionOrderDto = productionOrder || item; |
| | | rowData.productionRecords = workOrderList || []; |
| | | } else { |
| | | rowData.productionOrderDto = item; |
| | | rowData.productionRecords = []; |
| | | } |
| | | } catch (error) { |
| | | console.error(error); |
| | | rowData.productionOrderDto = item; |
| | | rowData.productionRecords = []; |
| | | uni.showToast({ title: "è·å详æ
失败", icon: "none" }); |
| | | } finally { |
| | | uni.hideLoading(); |
| | | } |
| | | }; |
| | | |
| | | onLoad(async options => { |
| | | if (options.npsNo) { |
| | | uni.showLoading({ title: "å è½½ä¸..." }); |
| | | try { |
| | | const res = await productOrderListPage({ |
| | | npsNo: options.npsNo, |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | }); |
| | | const records = res.data?.records || res.rows || []; |
| | | if (records.length > 0) { |
| | | onSelectNpsNo(records[0]); |
| | | } else { |
| | | uni.showToast({ title: "æªæ¾å°ç¸å
³è®¢å", icon: "none" }); |
| | | } |
| | | } catch (error) { |
| | | console.error(error); |
| | | } finally { |
| | | uni.hideLoading(); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | const getStatusText = status => { |
| | | const statusMap = { 1: "å¾
å¼å§", 2: "è¿è¡ä¸", 3: "已宿", 5: "å·²ç»æ" }; |
| | | return statusMap[status] || "已忶"; |
| | | }; |
| | | |
| | | const getStatusType = status => { |
| | | const typeMap = { 1: "primary", 2: "warning", 3: "success", 5: "error" }; |
| | | return typeMap[status] || "info"; |
| | | }; |
| | | |
| | | const formatDate = (date, pattern = "{y}-{m}-{d}") => { |
| | | return parseTime(date, pattern) || "-"; |
| | | }; |
| | | |
| | | const formatProgress = val => { |
| | | const p = parseFloat(val || 0); |
| | | return p >= 100 ? 100 : p; |
| | | }; |
| | | |
| | | const progressColor = percentage => { |
| | | if (percentage < 30) return "#f56c6c"; |
| | | if (percentage < 70) return "#e6a23c"; |
| | | return "#67c23a"; |
| | | }; |
| | | |
| | | const handleShowReports = row => { |
| | | detailData.value = { |
| | | workOrder: row.workOrder || {}, |
| | | reports: (row.reportList || []).map(r => ({ |
| | | ...r.reportMain, |
| | | id: r.reportMain.id, |
| | | productionOperationParamList: r.reportParamList || [], |
| | | })), |
| | | }; |
| | | reportPopupVisible.value = true; |
| | | }; |
| | | |
| | | const handleShowInput = async reportId => { |
| | | inputPopupVisible.value = true; |
| | | inputLoading.value = true; |
| | | inputListData.value = []; |
| | | try { |
| | | const res = await productionProductInputListPage({ |
| | | productMainId: reportId, |
| | | pageNum: 1, |
| | | pageSize: 100, |
| | | }); |
| | | inputListData.value = res.data?.records || res.rows || []; |
| | | } catch (error) { |
| | | console.error(error); |
| | | uni.showToast({ title: "è·åæå
¥ä¿¡æ¯å¤±è´¥", icon: "none" }); |
| | | } finally { |
| | | inputLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const handleShowQuality = row => { |
| | | const inspects = row.inspectList || []; |
| | | qualityRecords.value = inspects.map(i => ({ |
| | | ...i.inspect, |
| | | reportNo: i.reportNo, |
| | | userName: i.reportMain?.userName || "-", |
| | | inspectParamList: i.inspectParamList || [], |
| | | })); |
| | | qualityPopupVisible.value = true; |
| | | }; |
| | | |
| | | const showParams = params => { |
| | | currentParams.value = params || []; |
| | | paramModalVisible.value = true; |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/styles/procurement-common.scss"; |
| | | |
| | | .production-traceability { |
| | | min-height: 100vh; |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | .search-section { |
| | | background-color: #fff; |
| | | padding: 20rpx 24rpx; |
| | | margin-bottom: 20rpx; |
| | | } |
| | | |
| | | .search-bar { |
| | | display: flex; |
| | | align-items: center; |
| | | background-color: #f2f2f2; |
| | | border-radius: 8rpx; |
| | | padding: 0 20rpx; |
| | | height: 80rpx; |
| | | |
| | | .search-input { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | .placeholder { |
| | | font-size: 28rpx; |
| | | color: #999; |
| | | } |
| | | |
| | | .value { |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | |
| | | .search-button { |
| | | padding: 0 10rpx; |
| | | } |
| | | } |
| | | |
| | | .selector-popup { |
| | | background: #fff; |
| | | padding: 30rpx; |
| | | max-height: 70vh; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .popup-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 24rpx; |
| | | |
| | | .popup-title { |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | |
| | | .search-box { |
| | | margin-bottom: 20rpx; |
| | | } |
| | | |
| | | .options-list { |
| | | flex: 1; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .option-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 24rpx 0; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | |
| | | .option-main { |
| | | flex: 1; |
| | | .nps-no { |
| | | font-size: 28rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | display: block; |
| | | margin-bottom: 4rpx; |
| | | } |
| | | .product-info { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .no-options { |
| | | text-align: center; |
| | | padding: 40rpx; |
| | | color: #999; |
| | | font-size: 26rpx; |
| | | } |
| | | } |
| | | |
| | | .content-container { |
| | | padding: 0 24rpx 40rpx; |
| | | } |
| | | |
| | | .info-card { |
| | | background: #fff; |
| | | border-radius: 16rpx; |
| | | padding: 24rpx; |
| | | margin-bottom: 24rpx; |
| | | box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); |
| | | |
| | | .card-title { |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | margin-bottom: 24rpx; |
| | | padding-left: 16rpx; |
| | | border-left: 8rpx solid #3c9cff; |
| | | } |
| | | } |
| | | |
| | | .info-grid { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | |
| | | .info-item { |
| | | width: 50%; |
| | | margin-bottom: 20rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | &.full-width { |
| | | width: 100%; |
| | | } |
| | | |
| | | .label { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | margin-bottom: 8rpx; |
| | | } |
| | | |
| | | .value { |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | word-break: break-all; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .progress-container { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 20rpx; |
| | | |
| | | up-line-progress { |
| | | flex: 1; |
| | | } |
| | | |
| | | .progress-text { |
| | | font-size: 24rpx; |
| | | color: #666; |
| | | min-width: 60rpx; |
| | | } |
| | | } |
| | | |
| | | .section-title { |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | margin: 32rpx 0 20rpx; |
| | | } |
| | | |
| | | .work-order-card { |
| | | background: #fff; |
| | | border-radius: 16rpx; |
| | | padding: 24rpx; |
| | | margin-bottom: 20rpx; |
| | | box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20rpx; |
| | | padding-bottom: 16rpx; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | |
| | | .work-order-no { |
| | | font-size: 28rpx; |
| | | font-weight: bold; |
| | | color: #3c9cff; |
| | | } |
| | | |
| | | .progress-tag { |
| | | font-size: 28rpx; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | |
| | | .card-content { |
| | | .content-row { |
| | | margin-bottom: 12rpx; |
| | | font-size: 26rpx; |
| | | |
| | | .label { |
| | | color: #999; |
| | | } |
| | | |
| | | .value { |
| | | color: #333; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .card-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 20rpx; |
| | | margin-top: 20rpx; |
| | | } |
| | | } |
| | | |
| | | .popup-content { |
| | | background: #fff; |
| | | padding: 30rpx; |
| | | max-height: 80vh; |
| | | display: flex; |
| | | flex-direction: column; |
| | | border-radius: 20rpx 20rpx 0 0; |
| | | |
| | | .popup-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 30rpx; |
| | | |
| | | .popup-title { |
| | | font-size: 34rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | } |
| | | |
| | | .popup-scroll { |
| | | flex: 1; |
| | | overflow: hidden; |
| | | } |
| | | } |
| | | |
| | | .detail-info { |
| | | background: #f8f9fa; |
| | | padding: 24rpx; |
| | | border-radius: 16rpx; |
| | | margin-bottom: 30rpx; |
| | | flex-direction: column; |
| | | |
| | | .info-row { |
| | | margin-bottom: 12rpx; |
| | | font-size: 28rpx; |
| | | display: flex; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .label { |
| | | color: #999; |
| | | min-width: 140rpx; |
| | | } |
| | | .value { |
| | | color: #333; |
| | | flex: 1; |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .list-title { |
| | | font-size: 30rpx; |
| | | font-weight: bold; |
| | | margin-bottom: 20rpx; |
| | | color: #333; |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | width: 6rpx; |
| | | height: 28rpx; |
| | | background: #3c9cff; |
| | | margin-right: 12rpx; |
| | | border-radius: 4rpx; |
| | | } |
| | | } |
| | | |
| | | .detail-item { |
| | | background: #fff; |
| | | border: 1rpx solid #f0f0f0; |
| | | border-radius: 12rpx; |
| | | padding: 24rpx; |
| | | margin-bottom: 20rpx; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .item-main { |
| | | flex: 1; |
| | | .item-row { |
| | | font-size: 26rpx; |
| | | margin-bottom: 8rpx; |
| | | display: flex; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .label { |
| | | color: #999; |
| | | min-width: 130rpx; |
| | | } |
| | | .value { |
| | | color: #333; |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .item-actions { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 16rpx; |
| | | padding-left: 20rpx; |
| | | border-left: 1rpx solid #f0f0f0; |
| | | |
| | | .action-link { |
| | | font-size: 26rpx; |
| | | color: #3c9cff; |
| | | white-space: nowrap; |
| | | |
| | | &.green { |
| | | color: #52c41a; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .quality-record { |
| | | background: #fff; |
| | | border: 1rpx solid #f0f0f0; |
| | | border-radius: 16rpx; |
| | | padding: 24rpx; |
| | | margin-bottom: 30rpx; |
| | | |
| | | .record-title { |
| | | font-size: 30rpx; |
| | | font-weight: bold; |
| | | color: #3c9cff; |
| | | margin-bottom: 24rpx; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | } |
| | | } |
| | | |
| | | .params-table { |
| | | margin-top: 24rpx; |
| | | border: 1rpx solid #f0f0f0; |
| | | border-radius: 12rpx; |
| | | overflow: hidden; |
| | | |
| | | .table-header { |
| | | display: flex; |
| | | background: #f8f9fa; |
| | | padding: 20rpx 16rpx; |
| | | font-size: 26rpx; |
| | | font-weight: bold; |
| | | color: #666; |
| | | } |
| | | |
| | | .table-row { |
| | | display: flex; |
| | | padding: 20rpx 16rpx; |
| | | font-size: 26rpx; |
| | | border-top: 1rpx solid #f0f0f0; |
| | | color: #333; |
| | | |
| | | &:nth-child(even) { |
| | | background: #fafafa; |
| | | } |
| | | } |
| | | |
| | | .col { |
| | | flex: 1; |
| | | text-align: center; |
| | | word-break: break-all; |
| | | } |
| | | } |
| | | |
| | | .modal-content { |
| | | padding: 30rpx; |
| | | .param-row { |
| | | margin-bottom: 20rpx; |
| | | font-size: 28rpx; |
| | | display: flex; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .label { |
| | | color: #666; |
| | | min-width: 160rpx; |
| | | } |
| | | .value { |
| | | color: #333; |
| | | font-weight: 500; |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .input-list-popup { |
| | | .input-item { |
| | | background: #fff; |
| | | border: 1rpx solid #f0f0f0; |
| | | border-radius: 12rpx; |
| | | padding: 20rpx; |
| | | margin-bottom: 20rpx; |
| | | |
| | | .input-row { |
| | | display: flex; |
| | | font-size: 26rpx; |
| | | margin-bottom: 8rpx; |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | .label { |
| | | color: #999; |
| | | min-width: 160rpx; |
| | | } |
| | | .value { |
| | | color: #333; |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .error-text { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .no-data-minor { |
| | | text-align: center; |
| | | padding: 60rpx 40rpx; |
| | | color: #999; |
| | | font-size: 28rpx; |
| | | } |
| | | </style> |