<template>
|
<view class="process-statistics">
|
<PageHeader title="工序生产实况"
|
@back="goBack" />
|
<!-- 搜索区域 -->
|
<view class="search-section">
|
<view class="date-picker-container"
|
@click="showCalendar = true">
|
<view class="date-input">
|
<up-icon name="calendar"
|
size="20"
|
color="#999"></up-icon>
|
<text class="date-text"
|
:class="{ 'placeholder': !searchForm.startDate }">{{ dateRangeText }}</text>
|
<view v-if="searchForm.startDate"
|
class="clear-icon-wrapper"
|
@click.stop="handleClearDate">
|
<up-icon name="close-circle-fill"
|
size="18"
|
color="#c0c4cc"></up-icon>
|
</view>
|
</view>
|
<view class="search-btn-wrapper">
|
<up-button type="primary"
|
size="small"
|
text="搜索"
|
@click.stop="handleQuery"></up-button>
|
</view>
|
</view>
|
</view>
|
<!-- 统计卡片列表 -->
|
<scroll-view scroll-y
|
class="stats-list">
|
<view v-if="loading"
|
class="loading-box">
|
<up-loading-icon text="加载中..."></up-loading-icon>
|
</view>
|
<view v-else-if="statsData.length > 0"
|
class="card-grid">
|
<view v-for="(item, index) in statsData"
|
:key="index"
|
class="stats-card">
|
<view class="card-header">
|
<text class="process-tag">{{ item.name }}</text>
|
<view class="header-details">
|
<view class="detail-row">
|
<text class="label">计划数</text>
|
<text class="value">{{ item.planned }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="label">良品数</text>
|
<text class="value good">{{ item.good }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="label">不良品</text>
|
<text class="value bad">{{ item.bad }}</text>
|
</view>
|
</view>
|
</view>
|
<view class="card-body">
|
<view class="main-stat">
|
<text class="big-number">{{ item.total }}</text>
|
<text class="sub-label">生产任务数</text>
|
</view>
|
</view>
|
<view class="card-footer">
|
<view class="progress-section">
|
<view class="progress-header">
|
<text class="progress-label">生产进度</text>
|
<text class="percentage-text">{{ item.percentage }}%</text>
|
</view>
|
<up-line-progress :percentage="Math.min(item.percentage, 100)"
|
:activeColor="getProgressColor(item.percentage)"
|
:show-text="false"
|
height="8"></up-line-progress>
|
</view>
|
</view>
|
</view>
|
</view>
|
<view v-else
|
class="no-data">
|
<up-empty mode="data"
|
text="暂无工序统计数据"></up-empty>
|
</view>
|
</scroll-view>
|
<!-- 日历选择器 -->
|
<up-calendar :show="showCalendar"
|
mode="range"
|
:maxDate="maxDate"
|
minDate="2026-01-01"
|
:monthNum="monthNum"
|
@confirm="onDateConfirm"
|
@close="showCalendar = false"></up-calendar>
|
</view>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted, computed } from "vue";
|
import { getOperationStatistics } from "@/api/productionManagement/workOrder.js";
|
import PageHeader from "@/components/PageHeader.vue";
|
import dayjs from "dayjs";
|
|
const loading = ref(false);
|
const showCalendar = ref(false);
|
const dateRange = ref([]);
|
const maxDate = dayjs().format("YYYY-MM-DD");
|
const monthNum = computed(() => {
|
const min = dayjs("2022-02-01");
|
const max = dayjs(maxDate);
|
return max.diff(min, "month") + 1;
|
});
|
// const minDate = dayjs().subtract(7, "day").format("YYYY-MM-DD");
|
|
const searchForm = reactive({
|
startDate: "",
|
endDate: "",
|
});
|
|
const statsData = ref([]);
|
|
const dateRangeText = computed(() => {
|
if (searchForm.startDate && searchForm.endDate) {
|
return `${searchForm.startDate} 至 ${searchForm.endDate}`;
|
}
|
return "请选择日期区间";
|
});
|
|
const goBack = () => {
|
uni.navigateBack();
|
};
|
|
const getProgressColor = percentage => {
|
if (percentage >= 100) return "#67c23a";
|
if (percentage >= 50) return "#3c9cff";
|
if (percentage >= 25) return "#e6a23c";
|
return "#f56c6c";
|
};
|
|
const onDateConfirm = e => {
|
searchForm.startDate = e[0];
|
searchForm.endDate = e[e.length - 1];
|
showCalendar.value = false;
|
handleQuery();
|
};
|
|
const getList = () => {
|
loading.value = true;
|
const params = {
|
startDate: searchForm.startDate,
|
endDate: searchForm.endDate,
|
};
|
getOperationStatistics(params)
|
.then(res => {
|
statsData.value = (res.data || []).map(item => ({
|
name: item.operationName || "-",
|
total: item.productionTaskCount || 0,
|
planned: item.planQuantity || 0,
|
good: item.goodQuantity || 0,
|
bad: item.scrapQty || 0,
|
percentage: Number(item.completionStatus || 0),
|
}));
|
})
|
.finally(() => {
|
loading.value = false;
|
});
|
};
|
|
const handleQuery = () => {
|
getList();
|
};
|
|
const handleClearDate = () => {
|
searchForm.startDate = "";
|
searchForm.endDate = "";
|
handleQuery();
|
};
|
|
onMounted(() => {
|
// 默认时间置空
|
searchForm.startDate = "";
|
searchForm.endDate = "";
|
getList();
|
});
|
</script>
|
|
<style scoped lang="scss">
|
@import "@/styles/procurement-common.scss";
|
|
.process-statistics {
|
min-height: 100vh;
|
background-color: #f5f7fa;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.search-section {
|
background-color: #fff;
|
padding: 24rpx 30rpx;
|
margin-bottom: 20rpx;
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.02);
|
}
|
|
.date-picker-container {
|
display: flex;
|
align-items: center;
|
width: 100%;
|
|
.date-input {
|
flex: 1;
|
height: 80rpx;
|
background-color: #f5f7fa;
|
border: 1rpx solid #e4e7ed;
|
border-radius: 12rpx;
|
display: flex;
|
align-items: center;
|
padding: 0 24rpx;
|
margin-right: 20rpx;
|
transition: all 0.3s;
|
|
&:active {
|
background-color: #ebedf0;
|
}
|
|
.date-text {
|
font-size: 28rpx;
|
color: #303133;
|
margin-left: 16rpx;
|
flex: 1;
|
|
&.placeholder {
|
color: #c0c4cc;
|
}
|
}
|
|
.clear-icon {
|
padding: 10rpx;
|
margin-right: -10rpx;
|
}
|
}
|
|
.search-btn-wrapper {
|
width: 140rpx;
|
}
|
}
|
|
.stats-list {
|
flex: 1;
|
height: 0;
|
padding: 0 24rpx 40rpx;
|
}
|
|
.loading-box {
|
display: flex;
|
justify-content: center;
|
padding-top: 100rpx;
|
}
|
|
.card-grid {
|
display: flex;
|
flex-direction: column;
|
gap: 24rpx;
|
}
|
|
.stats-card {
|
background: #fff;
|
border-radius: 16rpx;
|
padding: 24rpx;
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: flex-start;
|
margin-bottom: 30rpx;
|
|
.process-tag {
|
background-color: #e6f7ff;
|
color: #1890ff;
|
padding: 6rpx 16rpx;
|
border-radius: 8rpx;
|
font-size: 26rpx;
|
font-weight: bold;
|
}
|
|
.header-details {
|
display: flex;
|
flex-direction: column;
|
gap: 4rpx;
|
|
.detail-row {
|
display: flex;
|
justify-content: flex-end;
|
align-items: center;
|
gap: 12rpx;
|
|
.label {
|
font-size: 22rpx;
|
color: #999;
|
}
|
|
.value {
|
font-size: 24rpx;
|
color: #333;
|
font-weight: bold;
|
min-width: 60rpx;
|
text-align: right;
|
|
&.good {
|
color: #52c41a;
|
}
|
&.bad {
|
color: #f56c6c;
|
}
|
}
|
}
|
}
|
}
|
|
.card-body {
|
padding-bottom: 30rpx;
|
border-bottom: 1rpx solid #f0f0f0;
|
|
.main-stat {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
|
.big-number {
|
font-size: 56rpx;
|
font-weight: bold;
|
color: #333;
|
line-height: 1;
|
}
|
|
.sub-label {
|
font-size: 26rpx;
|
color: #666;
|
margin-top: 12rpx;
|
}
|
}
|
}
|
|
.card-footer {
|
padding-top: 24rpx;
|
|
.progress-section {
|
.progress-header {
|
display: flex;
|
justify-content: space-between;
|
margin-bottom: 12rpx;
|
|
.progress-label {
|
font-size: 24rpx;
|
color: #999;
|
}
|
|
.percentage-text {
|
font-size: 24rpx;
|
font-weight: bold;
|
color: #333;
|
}
|
}
|
}
|
}
|
}
|
|
.no-data {
|
padding-top: 100rpx;
|
}
|
</style>
|