| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search-bar"> |
| | | <el-form :model="searchForm" |
| | | inline> |
| | | <el-form-item label="æ¥æåºé´:"> |
| | | <el-date-picker v-model="searchForm.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 240px" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | icon="Search" |
| | | @click="handleQuery">æç´¢</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | <div class="stats-grid" |
| | | v-loading="loading"> |
| | | <el-row :gutter="16" |
| | | v-if="statsData.length > 0"> |
| | | <el-col v-for="(item, index) in statsData" |
| | | :key="index" |
| | | :xs="24" |
| | | :sm="12" |
| | | :md="8" |
| | | :lg="4.8" |
| | | :xl="4.8" |
| | | class="mb-16"> |
| | | <div class="stats-card"> |
| | | <div class="card-header"> |
| | | <span class="process-name">{{ item.name }}</span> |
| | | <div class="header-stats"> |
| | | <div class="stat-row"> |
| | | <span class="label">è®¡åæ°</span> |
| | | <span class="value">{{ item.planned }}</span> |
| | | </div> |
| | | <div class="stat-row"> |
| | | <span class="label">è¯åæ°</span> |
| | | <span class="value">{{ item.good }}</span> |
| | | </div> |
| | | <div class="stat-row"> |
| | | <span class="label">ä¸è¯åæ°</span> |
| | | <span class="value">{{ item.bad }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="card-body"> |
| | | <div class="main-stat"> |
| | | <div class="big-number">{{ item.total }}</div> |
| | | <div class="sub-label">çäº§ä»»å¡æ°</div> |
| | | </div> |
| | | </div> |
| | | <div class="card-footer"> |
| | | <div class="progress-info"> |
| | | <span class="progress-label">è¿åº¦:</span> |
| | | <el-progress :percentage="Math.min(item.percentage, 100)" |
| | | :color="getProgressColor(item.percentage)" |
| | | :stroke-width="10" |
| | | :show-text="false" |
| | | class="flex-1" /> |
| | | <span class="percentage-text">{{ item.percentage }}%</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | <el-empty v-else |
| | | description="ææ æ°æ®" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { reactive, ref, onMounted } from "vue"; |
| | | import dayjs from "dayjs"; |
| | | import { getOperationStatistics } from "@/api/productionManagement/workOrder.js"; |
| | | |
| | | const loading = ref(false); |
| | | const searchForm = reactive({ |
| | | dateRange: [], |
| | | }); |
| | | |
| | | const statsData = ref([]); |
| | | |
| | | const getProgressColor = percentage => { |
| | | if (percentage >= 100) return "#67c23a"; |
| | | if (percentage >= 50) return "#409eff"; |
| | | if (percentage >= 25) return "#e6a23c"; |
| | | return "red"; |
| | | }; |
| | | |
| | | const getList = () => { |
| | | loading.value = true; |
| | | const params = { |
| | | startDate: searchForm.dateRange?.[0] || "", |
| | | endDate: searchForm.dateRange?.[1] || "", |
| | | }; |
| | | 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(); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .app-container { |
| | | padding: 20px; |
| | | background-color: #f0f2f5; |
| | | min-height: calc(100vh - 84px); |
| | | } |
| | | |
| | | .search-bar { |
| | | background: #fff; |
| | | padding: 15px 20px 0; |
| | | border-radius: 4px; |
| | | margin-bottom: 20px; |
| | | box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05); |
| | | } |
| | | |
| | | .mb-16 { |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | // 模æ lg="4.8" å 为 element 䏿¯æ 24/5 |
| | | @media only screen and (min-width: 1200px) { |
| | | .el-col-lg-4-8 { |
| | | width: 20%; |
| | | max-width: 20%; |
| | | flex: 0 0 20%; |
| | | } |
| | | } |
| | | |
| | | .stats-card { |
| | | background: #fff; |
| | | border-radius: 8px; |
| | | padding: 16px; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); |
| | | transition: transform 0.3s; |
| | | |
| | | &:hover { |
| | | transform: translateY(-2px); |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | margin-bottom: 12px; |
| | | |
| | | .process-name { |
| | | background-color: #e6f7ff; |
| | | color: #1890ff; |
| | | padding: 2px 8px; |
| | | border-radius: 4px; |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .header-stats { |
| | | text-align: right; |
| | | |
| | | .stat-row { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | align-items: center; |
| | | gap: 8px; |
| | | margin-bottom: 2px; |
| | | |
| | | .label { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .value { |
| | | font-size: 13px; |
| | | color: #303133; |
| | | font-weight: bold; |
| | | min-width: 24px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .card-body { |
| | | padding: 10px 0; |
| | | |
| | | .main-stat { |
| | | .big-number { |
| | | font-size: 28px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .sub-label { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | margin-top: 8px; |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .card-footer { |
| | | margin-top: 16px; |
| | | |
| | | .progress-info { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | |
| | | .progress-label { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .flex-1 { |
| | | flex: 1; |
| | | } |
| | | |
| | | .percentage-text { |
| | | font-size: 12px; |
| | | color: #606266; |
| | | min-width: 45px; |
| | | text-align: right; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // ä¿®æ£ el-col å¸å±éé
5 å |
| | | :deep(.el-row) { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | @media only screen and (min-width: 1200px) { |
| | | .el-col-lg-4\.8 { |
| | | flex: 0 0 20%; |
| | | max-width: 20%; |
| | | } |
| | | } |
| | | </style> |