<!DOCTYPE html>
|
<html lang="zh-CN">
|
<head>
|
<meta charset="UTF-8">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<title>任务执行分析</title>
|
<link rel="stylesheet" href="../../static/vue/element/element.css">
|
<style>
|
[v-cloak] { display: none; }
|
* { box-sizing: border-box; }
|
html, body {
|
margin: 0;
|
min-height: 100%;
|
font-family: "Avenir Next", "PingFang SC", "Microsoft YaHei", sans-serif;
|
color: #22364a;
|
background:
|
radial-gradient(960px 400px at 0% -10%, rgba(33, 91, 168, 0.10), transparent 60%),
|
radial-gradient(880px 360px at 100% 0%, rgba(16, 142, 120, 0.10), transparent 60%),
|
linear-gradient(180deg, #f3f7fb 0%, #edf3f8 100%);
|
}
|
.page-shell {
|
max-width: 1720px;
|
margin: 0 auto;
|
padding: 16px;
|
}
|
.page-head {
|
display: flex;
|
align-items: flex-end;
|
justify-content: space-between;
|
gap: 12px;
|
margin-bottom: 14px;
|
flex-wrap: wrap;
|
}
|
.page-title {
|
font-size: 30px;
|
font-weight: 700;
|
color: #1e3245;
|
}
|
.page-subtitle {
|
margin-top: 6px;
|
font-size: 13px;
|
color: #7b8ca0;
|
}
|
.panel {
|
margin-top: 16px;
|
border-radius: 24px;
|
border: 1px solid rgba(216, 226, 235, 0.96);
|
background: rgba(255, 255, 255, 0.92);
|
box-shadow: 0 16px 36px rgba(38, 60, 88, 0.08);
|
overflow: hidden;
|
}
|
.panel-head {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
gap: 12px;
|
padding: 18px 20px 0;
|
flex-wrap: wrap;
|
}
|
.panel-title {
|
font-size: 18px;
|
font-weight: 700;
|
color: #22364a;
|
}
|
.panel-body {
|
padding: 16px 20px 20px;
|
}
|
.filter-grid {
|
display: grid;
|
grid-template-columns: repeat(6, minmax(0, 1fr));
|
gap: 12px;
|
}
|
.filter-item.span-2 {
|
grid-column: span 2;
|
}
|
.toolbar-row {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
gap: 12px;
|
margin-bottom: 14px;
|
flex-wrap: wrap;
|
}
|
.toolbar-actions {
|
display: flex;
|
gap: 10px;
|
flex-wrap: wrap;
|
}
|
.selection-meta {
|
font-size: 13px;
|
color: #698096;
|
}
|
.summary-grid {
|
display: grid;
|
grid-template-columns: repeat(6, minmax(0, 1fr));
|
gap: 12px;
|
}
|
.summary-card {
|
min-height: 92px;
|
padding: 14px 16px;
|
border-radius: 18px;
|
border: 1px solid #dfe8f2;
|
background: linear-gradient(180deg, #fcfdff 0%, #f5f9fd 100%);
|
}
|
.summary-label {
|
font-size: 12px;
|
color: #7d8ea2;
|
}
|
.summary-value {
|
margin-top: 8px;
|
font-size: 28px;
|
line-height: 1.1;
|
font-weight: 700;
|
color: #22364a;
|
}
|
.summary-value.datetime {
|
font-size: 20px;
|
line-height: 1.25;
|
word-break: break-word;
|
}
|
.summary-sub {
|
margin-top: 8px;
|
font-size: 12px;
|
color: #8a99ab;
|
}
|
.quality-banner {
|
margin-bottom: 14px;
|
padding: 10px 14px;
|
border-radius: 14px;
|
border: 1px solid rgba(230, 187, 88, 0.35);
|
background: rgba(255, 246, 226, 0.92);
|
color: #8b5d10;
|
font-size: 13px;
|
}
|
.chart-grid {
|
display: grid;
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
gap: 16px;
|
}
|
.chart-card {
|
padding: 16px;
|
border-radius: 20px;
|
border: 1px solid #dfe7f0;
|
background: #fbfdff;
|
}
|
.chart-title {
|
margin-bottom: 12px;
|
font-size: 16px;
|
font-weight: 700;
|
color: #22364a;
|
}
|
.chart-box {
|
width: 100%;
|
height: 320px;
|
}
|
.empty-shell {
|
padding: 52px 16px;
|
border-radius: 18px;
|
border: 1px dashed #d5e0ec;
|
background: #fafcff;
|
text-align: center;
|
color: #8a99ab;
|
font-size: 14px;
|
}
|
.tag-complete {
|
color: #187c5a;
|
background: rgba(24, 162, 105, 0.16);
|
border-color: rgba(24, 162, 105, 0.18);
|
}
|
.tag-partial {
|
color: #b56c05;
|
background: rgba(245, 163, 74, 0.16);
|
border-color: rgba(245, 163, 74, 0.18);
|
}
|
.export-mode {
|
background: #ffffff;
|
}
|
.export-detail-table {
|
width: 100%;
|
border-collapse: collapse;
|
table-layout: fixed;
|
font-size: 12px;
|
color: #22364a;
|
background: #ffffff;
|
}
|
.export-detail-table th,
|
.export-detail-table td {
|
border: 1px solid #dfe7f0;
|
padding: 8px 6px;
|
text-align: center;
|
vertical-align: middle;
|
word-break: break-all;
|
}
|
.export-detail-table th {
|
background: #f5f8fc;
|
font-weight: 700;
|
}
|
.export-section-title {
|
margin: 0 0 10px;
|
font-size: 16px;
|
font-weight: 700;
|
color: #22364a;
|
}
|
@media (max-width: 1460px) {
|
.filter-grid,
|
.summary-grid {
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
}
|
.chart-grid {
|
grid-template-columns: 1fr;
|
}
|
}
|
@media (max-width: 860px) {
|
.page-shell { padding: 12px; }
|
.filter-grid,
|
.summary-grid {
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
}
|
.filter-item.span-2 {
|
grid-column: span 2;
|
}
|
.page-title {
|
font-size: 24px;
|
}
|
}
|
</style>
|
</head>
|
<body>
|
<div id="app" v-cloak class="page-shell">
|
<div class="page-head">
|
<div>
|
<div class="page-title">任务执行分析</div>
|
<div class="page-subtitle">支持按任务批量分析和按时间范围分析,统一查看站点耗时、堆垛机耗时、总耗时和故障耗时。</div>
|
</div>
|
</div>
|
|
<section class="panel">
|
<div class="panel-head">
|
<div class="panel-title">筛选条件</div>
|
</div>
|
<div class="panel-body">
|
<div class="toolbar-row">
|
<el-radio-group v-model="filters.mode" size="small">
|
<el-radio-button label="TASK">按任务批量分析</el-radio-button>
|
<el-radio-button label="TIME">按时间范围分析</el-radio-button>
|
</el-radio-group>
|
<div class="toolbar-actions">
|
<el-button size="small" @click="handleReset">重置</el-button>
|
<el-button size="small" type="primary" @click="handleSearch">查询历史任务</el-button>
|
<el-button size="small" type="success" :loading="analyzeLoading" @click="runAnalysis">开始分析</el-button>
|
</div>
|
</div>
|
|
<div class="filter-grid">
|
<div class="filter-item span-2">
|
<el-input v-model.trim="filters.keyword" clearable placeholder="关键字:工作号 / WMS任务号 / 库位 / 条码"></el-input>
|
</div>
|
<div class="filter-item">
|
<el-select v-model="filters.ioType" clearable placeholder="任务类型" style="width: 100%;">
|
<el-option label="全部" value=""></el-option>
|
<el-option v-for="item in options.ioTypes" :key="item.code" :label="item.label" :value="item.value"></el-option>
|
</el-select>
|
</div>
|
<div class="filter-item">
|
<el-select v-model="filters.finalWrkSts" clearable placeholder="最终状态" style="width: 100%;">
|
<el-option label="默认完成态" value=""></el-option>
|
<el-option v-for="item in options.statuses" :key="item.code" :label="item.label" :value="item.value"></el-option>
|
</el-select>
|
</div>
|
<div class="filter-item">
|
<el-select v-model="filters.sourceStaNo" clearable placeholder="源站" style="width: 100%;">
|
<el-option label="全部源站" value=""></el-option>
|
<el-option v-for="item in options.stations" :key="'source-' + item.code" :label="item.label" :value="item.value"></el-option>
|
</el-select>
|
</div>
|
<div class="filter-item">
|
<el-select v-model="filters.staNo" clearable placeholder="目标站" style="width: 100%;">
|
<el-option label="全部目标站" value=""></el-option>
|
<el-option v-for="item in options.stations" :key="'target-' + item.code" :label="item.label" :value="item.value"></el-option>
|
</el-select>
|
</div>
|
<div class="filter-item">
|
<el-select v-model="filters.deviceType" clearable placeholder="设备类型" style="width: 100%;">
|
<el-option label="全部设备" value=""></el-option>
|
<el-option v-for="item in options.deviceTypes" :key="item.code" :label="item.label" :value="item.value"></el-option>
|
</el-select>
|
</div>
|
<div class="filter-item">
|
<el-select v-model="filters.timeField" placeholder="时间字段" style="width: 100%;">
|
<el-option v-for="item in options.timeFields" :key="item.code" :label="item.label" :value="item.value"></el-option>
|
</el-select>
|
</div>
|
<div class="filter-item span-2">
|
<el-date-picker
|
v-model="filters.timeRange"
|
type="datetimerange"
|
unlink-panels
|
range-separator="至"
|
start-placeholder="开始时间"
|
end-placeholder="结束时间"
|
style="width: 100%;">
|
</el-date-picker>
|
</div>
|
</div>
|
</div>
|
</section>
|
|
<section class="panel">
|
<div class="panel-head">
|
<div class="panel-title">历史任务列表</div>
|
<div class="selection-meta">已选任务 {{ selectedWrkNos.length }} 条</div>
|
</div>
|
<div class="panel-body">
|
<el-table
|
ref="historyTable"
|
:data="tableData"
|
border
|
stripe
|
size="mini"
|
row-key="wrkNo"
|
@selection-change="syncCurrentPageSelection"
|
v-loading="listLoading"
|
style="width: 100%;">
|
<el-table-column type="selection" width="48" reserve-selection></el-table-column>
|
<el-table-column prop="wrkNo" label="工作号" width="100" align="center"></el-table-column>
|
<el-table-column prop="wmsWrkNo" label="WMS任务号" min-width="150"></el-table-column>
|
<el-table-column prop="ioType$" label="任务类型" width="90" align="center"></el-table-column>
|
<el-table-column prop="wrkSts$" label="最终状态" min-width="120"></el-table-column>
|
<el-table-column prop="sourceStaNo" label="源站" width="90" align="center"></el-table-column>
|
<el-table-column prop="staNo" label="目标站" width="90" align="center"></el-table-column>
|
<el-table-column prop="appeTime$" label="创建时间" width="165"></el-table-column>
|
<el-table-column prop="finishTime$" label="完成时间" width="165"></el-table-column>
|
<el-table-column label="总耗时" width="130" align="right">
|
<template slot-scope="scope">{{ formatDuration(scope.row.totalDurationMs) }}</template>
|
</el-table-column>
|
<el-table-column label="站点耗时" width="130" align="right">
|
<template slot-scope="scope">{{ formatDuration(scope.row.stationDurationMs) }}</template>
|
</el-table-column>
|
<el-table-column label="堆垛机耗时" width="130" align="right">
|
<template slot-scope="scope">{{ formatDuration(scope.row.craneDurationMs) }}</template>
|
</el-table-column>
|
<el-table-column prop="faultCount" label="故障次数" width="90" align="center"></el-table-column>
|
<el-table-column label="故障耗时" width="130" align="right">
|
<template slot-scope="scope">{{ formatDuration(scope.row.faultDurationMs) }}</template>
|
</el-table-column>
|
<el-table-column label="数据完整性" width="110" align="center">
|
<template slot-scope="scope">
|
<el-tag size="mini" :class="scope.row.metricCompleteness === 'COMPLETE' ? 'tag-complete' : 'tag-partial'">
|
{{ scope.row.metricCompleteness === 'COMPLETE' ? 'COMPLETE' : 'PARTIAL' }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<div style="margin-top: 14px; text-align: right;">
|
<el-pagination
|
background
|
layout="total, sizes, prev, pager, next"
|
:current-page="currentPage"
|
:page-size="pageSize"
|
:page-sizes="[20, 50, 100, 200]"
|
:total="pageTotal"
|
@size-change="handleSizeChange"
|
@current-change="handleCurrentChange">
|
</el-pagination>
|
</div>
|
</div>
|
</section>
|
|
<section ref="analysisExportRoot" class="panel" :class="{ 'export-mode': exportingPdf }">
|
<div class="panel-head">
|
<div class="panel-title">分析结果</div>
|
<el-button
|
data-html2canvas-ignore="true"
|
size="small"
|
type="primary"
|
plain
|
:disabled="!analysisReady"
|
:loading="exportingPdf"
|
@click="exportAnalysisPdf">导出PDF</el-button>
|
</div>
|
<div class="panel-body">
|
<div v-if="!analysisReady" class="empty-shell">先从上方筛选任务或时间范围,然后执行分析。</div>
|
<template v-else>
|
<div ref="analysisVisualRoot">
|
<div v-if="analysis.summary.partialTaskCount > 0" class="quality-banner">
|
当前结果中有 {{ analysis.summary.partialTaskCount }} 条历史任务缺少阶段采集,仅参与总耗时和故障统计。
|
</div>
|
|
<div class="summary-grid">
|
<div class="summary-card">
|
<div class="summary-label">任务数</div>
|
<div class="summary-value">{{ formatNumber(analysis.summary.taskCount) }}</div>
|
<div class="summary-sub">本次分析命中任务总数</div>
|
</div>
|
<div class="summary-card">
|
<div class="summary-label">总任务开始</div>
|
<div class="summary-value datetime">{{ analysis.summary.taskStartTime$ || '--' }}</div>
|
<div class="summary-sub">命中任务最早创建时间</div>
|
</div>
|
<div class="summary-card">
|
<div class="summary-label">总任务结束</div>
|
<div class="summary-value datetime">{{ analysis.summary.taskEndTime$ || '--' }}</div>
|
<div class="summary-sub">命中任务最晚完成时间</div>
|
</div>
|
<div class="summary-card">
|
<div class="summary-label">总任务总耗时</div>
|
<div class="summary-value">{{ formatDuration(analysis.summary.taskDurationMs) }}</div>
|
<div class="summary-sub">最早创建到最晚完成</div>
|
</div>
|
<div class="summary-card">
|
<div class="summary-label">平均总耗时</div>
|
<div class="summary-value">{{ formatDuration(analysis.summary.avgTotalDurationMs) }}</div>
|
<div class="summary-sub">创建到完成的平均耗时</div>
|
</div>
|
<div class="summary-card">
|
<div class="summary-label">平均站点耗时</div>
|
<div class="summary-value">{{ formatDuration(analysis.summary.avgStationDurationMs) }}</div>
|
<div class="summary-sub">仅统计完整阶段数据</div>
|
</div>
|
<div class="summary-card">
|
<div class="summary-label">平均堆垛机耗时</div>
|
<div class="summary-value">{{ formatDuration(analysis.summary.avgCraneDurationMs) }}</div>
|
<div class="summary-sub">仅统计完整阶段数据</div>
|
</div>
|
<div class="summary-card">
|
<div class="summary-label">故障任务数</div>
|
<div class="summary-value">{{ formatNumber(analysis.summary.faultTaskCount) }}</div>
|
<div class="summary-sub">出现设备故障的任务数</div>
|
</div>
|
<div class="summary-card">
|
<div class="summary-label">总故障耗时</div>
|
<div class="summary-value">{{ formatDuration(analysis.summary.faultDurationMs) }}</div>
|
<div class="summary-sub">单堆垛机 / 双工位 / RGV</div>
|
</div>
|
</div>
|
|
<div class="chart-grid" style="margin-top: 16px;">
|
<div class="chart-card">
|
<div class="chart-title">任务耗时对比</div>
|
<div ref="durationChart" class="chart-box"></div>
|
</div>
|
<div class="chart-card">
|
<div class="chart-title">耗时趋势</div>
|
<div ref="trendChart" class="chart-box"></div>
|
</div>
|
<div class="chart-card">
|
<div class="chart-title">故障任务占比</div>
|
<div ref="faultPieChart" class="chart-box"></div>
|
</div>
|
<div class="chart-card">
|
<div class="chart-title">故障耗时分布</div>
|
<div ref="faultDurationChart" class="chart-box"></div>
|
</div>
|
</div>
|
</div>
|
|
<div style="margin-top: 16px;">
|
<div v-if="exportingPdf" ref="exportDetailRoot">
|
<div class="export-section-title">任务明细</div>
|
<table ref="exportDetailTable" class="export-detail-table">
|
<thead>
|
<tr>
|
<th>工作号</th>
|
<th>WMS任务号</th>
|
<th>任务类型</th>
|
<th>最终状态</th>
|
<th>源站</th>
|
<th>目标站</th>
|
<th>创建时间</th>
|
<th>完成时间</th>
|
<th>总耗时</th>
|
<th>站点耗时</th>
|
<th>堆垛机耗时</th>
|
<th>故障次数</th>
|
<th>故障耗时</th>
|
<th>数据完整性</th>
|
</tr>
|
</thead>
|
<tbody>
|
<tr v-for="row in analysis.detail" :key="'pdf-' + row.wrkNo">
|
<td>{{ row.wrkNo }}</td>
|
<td>{{ row.wmsWrkNo }}</td>
|
<td>{{ row.ioType$ }}</td>
|
<td>{{ row.finalWrkSts$ }}</td>
|
<td>{{ row.sourceStaNo }}</td>
|
<td>{{ row.staNo }}</td>
|
<td>{{ row.appeTime$ }}</td>
|
<td>{{ row.finishTime$ }}</td>
|
<td>{{ formatDuration(row.totalDurationMs) }}</td>
|
<td>{{ formatDuration(row.stationDurationMs) }}</td>
|
<td>{{ formatDuration(row.craneDurationMs) }}</td>
|
<td>{{ row.faultCount }}</td>
|
<td>{{ formatDuration(row.faultDurationMs) }}</td>
|
<td>{{ row.metricCompleteness === 'COMPLETE' ? 'COMPLETE' : 'PARTIAL' }}</td>
|
</tr>
|
</tbody>
|
</table>
|
</div>
|
<el-table v-if="!exportingPdf" :data="analysis.detail" border stripe size="mini" max-height="360" style="width: 100%;">
|
<el-table-column prop="wrkNo" label="工作号" width="100" align="center"></el-table-column>
|
<el-table-column prop="wmsWrkNo" label="WMS任务号" min-width="150"></el-table-column>
|
<el-table-column prop="ioType$" label="任务类型" width="90" align="center"></el-table-column>
|
<el-table-column prop="finalWrkSts$" label="最终状态" min-width="120"></el-table-column>
|
<el-table-column prop="sourceStaNo" label="源站" width="90" align="center"></el-table-column>
|
<el-table-column prop="staNo" label="目标站" width="90" align="center"></el-table-column>
|
<el-table-column prop="appeTime$" label="创建时间" width="165"></el-table-column>
|
<el-table-column prop="finishTime$" label="完成时间" width="165"></el-table-column>
|
<el-table-column label="总耗时" width="130" align="right">
|
<template slot-scope="scope">{{ formatDuration(scope.row.totalDurationMs) }}</template>
|
</el-table-column>
|
<el-table-column label="站点耗时" width="130" align="right">
|
<template slot-scope="scope">{{ formatDuration(scope.row.stationDurationMs) }}</template>
|
</el-table-column>
|
<el-table-column label="堆垛机耗时" width="130" align="right">
|
<template slot-scope="scope">{{ formatDuration(scope.row.craneDurationMs) }}</template>
|
</el-table-column>
|
<el-table-column prop="faultCount" label="故障次数" width="90" align="center"></el-table-column>
|
<el-table-column label="故障耗时" width="130" align="right">
|
<template slot-scope="scope">{{ formatDuration(scope.row.faultDurationMs) }}</template>
|
</el-table-column>
|
<el-table-column label="数据完整性" width="110" align="center">
|
<template slot-scope="scope">
|
<el-tag size="mini" :class="scope.row.metricCompleteness === 'COMPLETE' ? 'tag-complete' : 'tag-partial'">
|
{{ scope.row.metricCompleteness === 'COMPLETE' ? 'COMPLETE' : 'PARTIAL' }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
</el-table>
|
</div>
|
</template>
|
</div>
|
</section>
|
</div>
|
|
<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
|
<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
|
<script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
|
<script type="text/javascript" src="../../static/vue/element/element.js"></script>
|
<script type="text/javascript" src="../../static/js/echarts/echarts.min.js"></script>
|
<script type="text/javascript" src="../../static/lib/pdf/html2canvas.min.js"></script>
|
<script type="text/javascript" src="../../static/lib/pdf/jspdf.umd.min.js"></script>
|
<script type="text/javascript" src="../../static/js/wrkAnalysis/wrkAnalysis.js?v=20260322_03" charset="utf-8"></script>
|
</body>
|
</html>
|