<script lang="ts" setup>
|
import type { PendingTask } from '../../../types';
|
|
import { Descriptions, Progress, Tag, Timeline, TimelineItem } from 'ant-design-vue';
|
|
defineOptions({ name: 'WorkbenchTaskDetailTab' });
|
|
defineProps<{
|
task?: PendingTask | null;
|
}>();
|
|
function getProgressStatus(rate?: number): 'exception' | 'normal' | 'success' {
|
if (rate === undefined || rate === null) return 'normal';
|
if (rate >= 100) return 'success';
|
if (rate < 0) return 'exception';
|
return 'normal';
|
}
|
</script>
|
|
<template>
|
<div class="task-detail-tab">
|
<div v-if="!task" class="task-detail-tab__empty">
|
请从左侧工单列表选择一条任务
|
</div>
|
<template v-else>
|
<!-- 进度概览 -->
|
<div class="detail-progress">
|
<div class="detail-progress__head">
|
<span class="detail-progress__title">报工进度</span>
|
<span class="detail-progress__rate">{{ task.feedbackRate ?? 0 }}%</span>
|
<span class="detail-progress__qty">
|
{{ task.producedQuantity ?? 0 }} / {{ task.quantity ?? 0 }}
|
{{ task.unitMeasureName }}
|
</span>
|
</div>
|
<Progress
|
:percent="task.feedbackRate ?? 0"
|
:status="getProgressStatus(task.feedbackRate)"
|
:stroke-color="{ from: '#108ee9', to: '#87d068' }"
|
:show-info="false"
|
:stroke-width="14"
|
/>
|
<div class="detail-progress__stats">
|
<div class="stat-item">
|
<span class="stat-item__label">排产数量</span>
|
<span class="stat-item__value">{{ task.quantity ?? 0 }}</span>
|
</div>
|
<div class="stat-item">
|
<span class="stat-item__label">已生产</span>
|
<span class="stat-item__value">{{ task.producedQuantity ?? 0 }}</span>
|
</div>
|
<div class="stat-item">
|
<span class="stat-item__label">在途报工</span>
|
<span class="stat-item__value">{{ task.inTransitQuantity ?? 0 }}</span>
|
</div>
|
<div class="stat-item">
|
<span class="stat-item__label">可报工</span>
|
<span class="stat-item__value stat-item__value--primary">
|
{{ task.reportableQuantity ?? 0 }}
|
</span>
|
</div>
|
</div>
|
</div>
|
|
<!-- 详细信息 -->
|
<Descriptions
|
bordered
|
:column="2"
|
size="middle"
|
class="detail-desc"
|
>
|
<Descriptions.Item label="任务编码">{{ task.code ?? '—' }}</Descriptions.Item>
|
<Descriptions.Item label="任务名称">{{ task.name ?? '—' }}</Descriptions.Item>
|
<Descriptions.Item label="工单编码">{{ task.workOrderCode ?? '—' }}</Descriptions.Item>
|
<Descriptions.Item label="工单名称">{{ task.workOrderName ?? '—' }}</Descriptions.Item>
|
<Descriptions.Item label="工作站">{{ task.workstationName ?? '—' }}</Descriptions.Item>
|
<Descriptions.Item label="工序">{{ task.processName ?? '—' }}</Descriptions.Item>
|
<Descriptions.Item label="产品编码">{{ task.itemCode ?? '—' }}</Descriptions.Item>
|
<Descriptions.Item label="产品名称">{{ task.itemName ?? '—' }}</Descriptions.Item>
|
<Descriptions.Item label="规格型号">{{ task.itemSpecification ?? '—' }}</Descriptions.Item>
|
<Descriptions.Item label="计量单位">{{ task.unitMeasureName ?? '—' }}</Descriptions.Item>
|
<Descriptions.Item label="合格品数量">{{ task.qualifyQuantity ?? 0 }}</Descriptions.Item>
|
<Descriptions.Item label="不良品数量">{{ task.unqualifyQuantity ?? 0 }}</Descriptions.Item>
|
<Descriptions.Item label="客户名称">{{ task.clientName ?? '—' }}</Descriptions.Item>
|
<Descriptions.Item label="是否质检工序">
|
<Tag v-if="task.checkFlag" color="orange">是</Tag>
|
<Tag v-else>否</Tag>
|
</Descriptions.Item>
|
<Descriptions.Item label="需求日期" :span="2">
|
{{ task.requestDate ?? '—' }}
|
</Descriptions.Item>
|
<Descriptions.Item label="备注" :span="2">{{ task.remark ?? '—' }}</Descriptions.Item>
|
</Descriptions>
|
|
<!-- 停工记录 -->
|
<div v-if="task.pauseRecords && task.pauseRecords.length > 0" class="detail-pause-records">
|
<div class="detail-pause-records__title">停工历史记录</div>
|
<Timeline class="pause-timeline">
|
<TimelineItem
|
v-for="record in task.pauseRecords"
|
:key="record.id"
|
:color="record.resumeTime ? 'green' : 'red'"
|
>
|
<div class="pause-timeline-item">
|
<div class="pause-timeline-time">
|
<span class="time-label">{{ record.pauseTime }}</span>
|
<span class="time-separator">至</span>
|
<span class="time-label" :class="{ 'is-ongoing': !record.resumeTime }">
|
{{ record.resumeTime || '正在停工' }}
|
</span>
|
</div>
|
<div class="pause-timeline-content">
|
<div class="pause-info-row">
|
<span class="info-label">操作人员:</span>
|
<span class="info-value">
|
{{ record.pauseUserName }} (停工)
|
<template v-if="record.resumeTime">
|
<span style="margin: 0 4px; color: #d9d9d9">|</span>
|
{{ record.resumeUserName }} (复工)
|
</template>
|
</span>
|
</div>
|
<div class="pause-info-row">
|
<span class="info-label">停工原因:</span>
|
<span class="info-value">{{ record.reason }}</span>
|
</div>
|
<div class="pause-info-row" v-if="record.remark">
|
<span class="info-label">备注:</span>
|
<span class="info-value">{{ record.remark }}</span>
|
</div>
|
</div>
|
</div>
|
</TimelineItem>
|
</Timeline>
|
</div>
|
</template>
|
</div>
|
</template>
|
|
<style lang="scss" scoped>
|
.task-detail-tab {
|
padding: 20px 24px;
|
min-height: 100%;
|
height: 100%;
|
overflow: auto;
|
}
|
|
.task-detail-tab__empty {
|
padding: 64px 0;
|
text-align: center;
|
color: #8c8c8c;
|
font-size: 16px;
|
}
|
|
.detail-progress {
|
margin-bottom: 20px;
|
padding: 16px 20px;
|
border-radius: 8px;
|
background: #f6f8fa;
|
border: 1px solid #e8e8e8;
|
}
|
|
.detail-progress__head {
|
display: flex;
|
align-items: baseline;
|
flex-wrap: wrap;
|
gap: 8px 16px;
|
margin-bottom: 12px;
|
}
|
|
.detail-progress__title {
|
font-size: 16px;
|
font-weight: 600;
|
color: #1a1a1a;
|
}
|
|
.detail-progress__rate {
|
font-size: 22px;
|
font-weight: 700;
|
color: var(--ant-primary-color, #1890ff);
|
font-variant-numeric: tabular-nums;
|
}
|
|
.detail-progress__qty {
|
margin-left: auto;
|
font-size: 14px;
|
font-weight: 600;
|
color: #595959;
|
}
|
|
.detail-progress__stats {
|
display: flex;
|
gap: 24px;
|
margin-top: 14px;
|
flex-wrap: wrap;
|
}
|
|
.stat-item {
|
display: flex;
|
flex-direction: column;
|
gap: 4px;
|
|
&__label {
|
font-size: 12px;
|
color: #8c8c8c;
|
font-weight: 500;
|
}
|
|
&__value {
|
font-size: 18px;
|
font-weight: 700;
|
color: #1a1a1a;
|
font-variant-numeric: tabular-nums;
|
|
&--primary {
|
color: var(--ant-primary-color, #1890ff);
|
}
|
}
|
}
|
|
.detail-desc {
|
:deep(.ant-descriptions-item-label) {
|
width: 140px;
|
font-weight: 600;
|
background: #fafafa;
|
}
|
|
:deep(.ant-descriptions-item-content) {
|
font-size: 14px;
|
}
|
}
|
|
.detail-pause-records {
|
margin-top: 24px;
|
}
|
|
.detail-pause-records__title {
|
font-size: 16px;
|
font-weight: 600;
|
color: #1a1a1a;
|
margin-bottom: 20px;
|
}
|
|
.pause-timeline {
|
padding-top: 6px;
|
padding-left: 4px;
|
}
|
|
.pause-timeline-item {
|
background: #fafafa;
|
border: 1px solid #f0f0f0;
|
border-radius: 8px;
|
padding: 14px 18px;
|
margin-top: -6px;
|
box-shadow: 0 1px 2px rgb(0 0 0 / 2%);
|
transition: all 0.3s ease;
|
|
&:hover {
|
box-shadow: 0 4px 12px rgb(0 0 0 / 5%);
|
border-color: #e6d8d8;
|
}
|
}
|
|
.pause-timeline-time {
|
font-size: 13px;
|
color: #595959;
|
font-weight: 500;
|
margin-bottom: 10px;
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
}
|
|
.time-label {
|
background: #e6f7ff;
|
color: #1890ff;
|
padding: 2px 8px;
|
border-radius: 4px;
|
font-variant-numeric: tabular-nums;
|
}
|
|
.time-label.is-ongoing {
|
background: #fff1f0;
|
color: #f5222d;
|
}
|
|
.time-separator {
|
color: #bfbfbf;
|
font-size: 12px;
|
}
|
|
.pause-timeline-content {
|
display: flex;
|
flex-direction: column;
|
gap: 6px;
|
}
|
|
.pause-info-row {
|
font-size: 13px;
|
display: flex;
|
line-height: 1.5;
|
}
|
|
.pause-info-row .info-label {
|
color: #8c8c8c;
|
width: 70px;
|
flex-shrink: 0;
|
}
|
|
.pause-info-row .info-value {
|
color: #262626;
|
flex: 1;
|
}
|
</style>
|