feat(mes): 报工表单只填写报工数量并展示可报工数量,工作台报工支持工艺参数填写
- 报工表单移除合格品/不良品/废品数量输入,只保留报工数量
- 工作台报工表单修复可报工数量不显示(绑定 reportableQuantity)
- 工作台报工/快速报工加载并提交工艺参数(paramValues),修复必填参数校验报错
- 快速报工/批量报工请求类型移除合格/不良字段,响应类型新增 reportableQuantity
- 修复工作台 tabs 错误导入路径
Co-Authored-By: Claude <noreply@anthropic.com>
| | |
| | | taskId: number; |
| | | paramValues?: MesProFeedbackApi.ParamValue[]; |
| | | feedbackQuantity: number; |
| | | qualifiedQuantity?: number; |
| | | unqualifiedQuantity?: number; |
| | | remark?: string; |
| | | } |
| | | |
| | |
| | | feedbackId?: number; |
| | | taskStatus?: number; |
| | | workOrderStatus?: number; |
| | | /** 报工后剩余可报工数量 */ |
| | | reportableQuantity?: number; |
| | | } |
| | | |
| | | /** 批量报工请求 */ |
| | | export interface BatchCreateReq { |
| | | taskIds: number[]; |
| | | feedbackQuantity: number; |
| | | qualifiedQuantity?: number; |
| | | unqualifiedQuantity?: number; |
| | | remark?: string; |
| | | } |
| | | |
| | |
| | | pauseRecords?: PauseRecord[]; // 停工记录 |
| | | reportableQuantity?: number; // 可报工数量 |
| | | assigneeUserIds?: number[]; // 报工人 |
| | | feedbackApprove?: boolean; // 是否报工审批(派生自工单) |
| | | } |
| | | |
| | | /** MES 生产任务分页查询参数 */ |
| | |
| | | * - create / update:录入报工主体信息和数量 |
| | | * - submit / approve / detail:主体字段只读 |
| | | * |
| | | * 数量区域根据 `checkFlag` 和 `unqualifiedQuantity` 动态显示: |
| | | * - 非质检工序:报工数量 = 合格 + 不良;不良 > 0 时再展开工废/料废/其他废品 |
| | | * - 质检工序:只填报工数量(视为待检数量) |
| | | * 报工只填写"报工数量";合格品/不良品数量由质检确定,报工环节不采集。 |
| | | * 质检工序报工后进入待检验(uncheckQuantity = 报工数量),非质检工序直接完结。 |
| | | */ |
| | | export function useFormSchema( |
| | | formType: FormType, |
| | |
| | | class: 'w-full', |
| | | min: 0, |
| | | precision: 2, |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['checkFlag'], |
| | | // 非质检工序时,报工数量 = 合格 + 不良,禁用直接编辑 |
| | | componentProps: (values) => ({ |
| | | class: 'w-full', |
| | | disabled: !values.checkFlag, |
| | | min: 0, |
| | | placeholder: '请输入报工数量', |
| | | precision: 2, |
| | | }), |
| | | placeholder: '请输入报工数量', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'qualifiedQuantity', |
| | | label: '合格品数量', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: 'w-full', |
| | | min: 0, |
| | | precision: 2, |
| | | // 合格/不良变更,自动累计为报工数量 |
| | | onChange: async () => { |
| | | const values = await formApi?.getValues(); |
| | | await formApi?.setFieldValue( |
| | | 'feedbackQuantity', |
| | | (values?.qualifiedQuantity || 0) + |
| | | (values?.unqualifiedQuantity || 0), |
| | | ); |
| | | }, |
| | | }, |
| | | defaultValue: 0, |
| | | dependencies: { |
| | | triggerFields: ['checkFlag'], |
| | | show: (values) => !values.checkFlag, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'unqualifiedQuantity', |
| | | label: '不良品数量', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: 'w-full', |
| | | min: 0, |
| | | precision: 2, |
| | | // 合格/不良变更,自动累计为报工数量 |
| | | onChange: async () => { |
| | | const values = await formApi?.getValues(); |
| | | await formApi?.setFieldValue( |
| | | 'feedbackQuantity', |
| | | (values?.qualifiedQuantity || 0) + |
| | | (values?.unqualifiedQuantity || 0), |
| | | ); |
| | | }, |
| | | }, |
| | | defaultValue: 0, |
| | | dependencies: { |
| | | triggerFields: ['checkFlag'], |
| | | show: (values) => !values.checkFlag, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'laborScrapQuantity', |
| | | label: '工废数量', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: 'w-full', |
| | | min: 0, |
| | | precision: 2, |
| | | // 废品分类变更,自动累计为不良品数量及报工数量 |
| | | onChange: async () => { |
| | | const values = await formApi?.getValues(); |
| | | const unqualified = |
| | | (values?.laborScrapQuantity || 0) + |
| | | (values?.materialScrapQuantity || 0) + |
| | | (values?.otherScrapQuantity || 0); |
| | | await formApi?.setValues({ |
| | | feedbackQuantity: (values?.qualifiedQuantity || 0) + unqualified, |
| | | unqualifiedQuantity: unqualified, |
| | | }); |
| | | }, |
| | | }, |
| | | defaultValue: 0, |
| | | dependencies: { |
| | | triggerFields: ['checkFlag', 'unqualifiedQuantity'], |
| | | show: (values) => |
| | | !values.checkFlag && (values.unqualifiedQuantity || 0) > 0, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'materialScrapQuantity', |
| | | label: '料废数量', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: 'w-full', |
| | | min: 0, |
| | | precision: 2, |
| | | // 废品分类变更,自动累计为不良品数量及报工数量 |
| | | onChange: async () => { |
| | | const values = await formApi?.getValues(); |
| | | const unqualified = |
| | | (values?.laborScrapQuantity || 0) + |
| | | (values?.materialScrapQuantity || 0) + |
| | | (values?.otherScrapQuantity || 0); |
| | | await formApi?.setValues({ |
| | | feedbackQuantity: (values?.qualifiedQuantity || 0) + unqualified, |
| | | unqualifiedQuantity: unqualified, |
| | | }); |
| | | }, |
| | | }, |
| | | defaultValue: 0, |
| | | dependencies: { |
| | | triggerFields: ['checkFlag', 'unqualifiedQuantity'], |
| | | show: (values) => |
| | | !values.checkFlag && (values.unqualifiedQuantity || 0) > 0, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'otherScrapQuantity', |
| | | label: '其他废品', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: 'w-full', |
| | | min: 0, |
| | | precision: 2, |
| | | // 废品分类变更,自动累计为不良品数量及报工数量 |
| | | onChange: async () => { |
| | | const values = await formApi?.getValues(); |
| | | const unqualified = |
| | | (values?.laborScrapQuantity || 0) + |
| | | (values?.materialScrapQuantity || 0) + |
| | | (values?.otherScrapQuantity || 0); |
| | | await formApi?.setValues({ |
| | | feedbackQuantity: (values?.qualifiedQuantity || 0) + unqualified, |
| | | unqualifiedQuantity: unqualified, |
| | | }); |
| | | }, |
| | | }, |
| | | defaultValue: 0, |
| | | dependencies: { |
| | | triggerFields: ['checkFlag', 'unqualifiedQuantity'], |
| | | show: (values) => |
| | | !values.checkFlag && (values.unqualifiedQuantity || 0) > 0, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'feedbackUserId', |
| | |
| | | render: (val) => |
| | | h(DictTag, { type: DICT_TYPE.MES_PRO_FEEDBACK_TYPE, value: val }), |
| | | }, |
| | | { field: 'workOrderId', label: '生产订单', render: (_val, data) => data.workOrderCode }, |
| | | { field: 'taskId', label: '生产任务', render: (_val, data) => data.taskCode }, |
| | | { field: 'workOrderId', label: '生产订单', render: (_val, data) => data?.workOrderCode }, |
| | | { field: 'taskId', label: '生产任务', render: (_val, data) => data?.taskCode }, |
| | | { field: 'workstationName', label: '工作站' }, |
| | | { field: 'itemCode', label: '产品编码' }, |
| | | { field: 'itemName', label: '产品名称' }, |
| | |
| | | selectedTasks.value = []; |
| | | formData.value = { |
| | | feedbackQuantity: undefined, |
| | | qualifiedQuantity: undefined, |
| | | unqualifiedQuantity: undefined, |
| | | remark: '', |
| | | }; |
| | | } |
| | |
| | | const selectedTasks = ref<MesProTaskApi.Task[]>([]); |
| | | const formData = ref({ |
| | | feedbackQuantity: undefined as number | undefined, |
| | | qualifiedQuantity: undefined as number | undefined, |
| | | unqualifiedQuantity: undefined as number | undefined, |
| | | remark: '', |
| | | }); |
| | | const submitting = ref(false); |
| | |
| | | const result = await batchCreateFeedback({ |
| | | taskIds: selectedTasks.value.map((t) => t.id!), |
| | | feedbackQuantity: formData.value.feedbackQuantity, |
| | | qualifiedQuantity: formData.value.qualifiedQuantity, |
| | | unqualifiedQuantity: formData.value.unqualifiedQuantity, |
| | | remark: formData.value.remark || undefined, |
| | | }); |
| | | if (result.failCount) { |
| | |
| | | :min="0.01" |
| | | :precision="2" |
| | | placeholder="请输入报工数量(应用到所有选中任务)" |
| | | class="!w-full" |
| | | /> |
| | | </a-form-item> |
| | | <a-form-item label="合格品数量"> |
| | | <a-input-number |
| | | v-model:value="formData.qualifiedQuantity" |
| | | :min="0" |
| | | :precision="2" |
| | | placeholder="请输入合格品数量" |
| | | class="!w-full" |
| | | /> |
| | | </a-form-item> |
| | | <a-form-item label="不良品数量"> |
| | | <a-input-number |
| | | v-model:value="formData.unqualifiedQuantity" |
| | | :min="0" |
| | | :precision="2" |
| | | placeholder="请输入不良品数量" |
| | | class="!w-full" |
| | | /> |
| | | </a-form-item> |
| | |
| | | wrapperClass: 'grid-cols-3', |
| | | }); |
| | | |
| | | /** 提交前对齐数量:根据 checkFlag 决定 uncheck/合格/不良归零策略 */ |
| | | /** 提交前对齐数量:报工只填写报工数量,合格/不良/废品数量由质检确定,报工环节不采集 */ |
| | | function alignQuantity(data: MesProFeedbackApi.Feedback) { |
| | | if (data.checkFlag) { |
| | | data.uncheckQuantity = data.feedbackQuantity; |
| | | data.qualifiedQuantity = 0; |
| | | data.unqualifiedQuantity = 0; |
| | | data.laborScrapQuantity = 0; |
| | | data.materialScrapQuantity = 0; |
| | | data.otherScrapQuantity = 0; |
| | | } else { |
| | | data.feedbackQuantity = |
| | | (data.qualifiedQuantity || 0) + (data.unqualifiedQuantity || 0); |
| | | data.uncheckQuantity = 0; |
| | | } |
| | | data.uncheckQuantity = data.checkFlag ? data.feedbackQuantity : 0; |
| | | delete data.qualifiedQuantity; |
| | | delete data.unqualifiedQuantity; |
| | | delete data.laborScrapQuantity; |
| | | delete data.materialScrapQuantity; |
| | | delete data.otherScrapQuantity; |
| | | } |
| | | |
| | | /** 读取并按提交规则对齐后的表单数据,用于快照和保存 */ |
| | |
| | | } |
| | | |
| | | const batchDialogRef = ref<InstanceType<typeof BatchInputDialog>>(); |
| | | const batchActionType = ref<'submit' | 'approve'>(''); |
| | | const batchActionType = ref<'' | 'submit' | 'approve'>(''); |
| | | |
| | | function syncParamValues(data: MesProFeedbackApi.Feedback) { |
| | | data.paramValues = paramDetails.value.map((detail) => ({ |
| | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { quickCreateFeedback } from '#/api/mes/pro/feedback'; |
| | | import { |
| | | loadParamDetails, |
| | | paramDetails, |
| | | } from '#/views/mes/pro/feedback/param-state'; |
| | | import ProTaskSelect from '#/views/mes/pro/task/components/select.vue'; |
| | | |
| | | defineOptions({ name: 'QuickFeedbackForm' }); |
| | |
| | | selectedTask.value = undefined; |
| | | formData.value = { |
| | | feedbackQuantity: undefined, |
| | | qualifiedQuantity: undefined, |
| | | unqualifiedQuantity: undefined, |
| | | remark: '', |
| | | }; |
| | | // 预设任务时加载其工艺参数;否则等待选择任务后再加载 |
| | | if (props.taskId) { |
| | | loadParamDetails(props.taskId); |
| | | } else { |
| | | paramDetails.value = []; |
| | | } |
| | | } |
| | | }, |
| | | }); |
| | |
| | | const selectedTask = ref<MesProTaskApi.Task>(); |
| | | const formData = ref({ |
| | | feedbackQuantity: undefined as number | undefined, |
| | | qualifiedQuantity: undefined as number | undefined, |
| | | unqualifiedQuantity: undefined as number | undefined, |
| | | remark: '', |
| | | }); |
| | | const submitting = ref(false); |
| | |
| | | function handleTaskChange(task: MesProTaskApi.Task | undefined) { |
| | | selectedTask.value = task; |
| | | selectedTaskId.value = task?.id; |
| | | loadParamDetails(task?.id); |
| | | } |
| | | |
| | | async function handleSubmit() { |
| | |
| | | await quickCreateFeedback({ |
| | | taskId, |
| | | feedbackQuantity: formData.value.feedbackQuantity, |
| | | qualifiedQuantity: formData.value.qualifiedQuantity, |
| | | unqualifiedQuantity: formData.value.unqualifiedQuantity, |
| | | paramValues: paramDetails.value.map((detail) => ({ |
| | | detailId: detail.id, |
| | | value: detail.paramValue, |
| | | })), |
| | | remark: formData.value.remark || undefined, |
| | | }); |
| | | message.success('报工成功'); |
| | |
| | | class="!w-full" |
| | | /> |
| | | </a-form-item> |
| | | <a-form-item label="合格品数量"> |
| | | <a-input-number |
| | | v-model:value="formData.qualifiedQuantity" |
| | | :min="0" |
| | | :precision="2" |
| | | placeholder="请输入合格品数量" |
| | | class="!w-full" |
| | | /> |
| | | </a-form-item> |
| | | <a-form-item label="不良品数量"> |
| | | <a-input-number |
| | | v-model:value="formData.unqualifiedQuantity" |
| | | :min="0" |
| | | :precision="2" |
| | | placeholder="请输入不良品数量" |
| | | class="!w-full" |
| | | /> |
| | | </a-form-item> |
| | | <div v-if="paramDetails.length" class="mb-4"> |
| | | <div class="mb-1 font-medium">工艺参数</div> |
| | | <div |
| | | v-for="detail in paramDetails" |
| | | :key="detail.id" |
| | | class="mb-2 flex items-center gap-2" |
| | | > |
| | | <span class="w-40 flex-shrink-0 text-right text-sm text-gray-600"> |
| | | <span v-if="detail.required" class="text-red-500">* </span> |
| | | {{ detail.paramName || detail.paramCode |
| | | }}{{ |
| | | detail.unitMeasureName ? `(${detail.unitMeasureName})` : '' |
| | | }} |
| | | </span> |
| | | <a-input |
| | | v-model:value="detail.paramValue" |
| | | placeholder="请输入参数值" |
| | | class="!w-full" |
| | | /> |
| | | </div> |
| | | </div> |
| | | <a-form-item label="备注"> |
| | | <a-textarea |
| | | v-model:value="formData.remark" |
| | |
| | | <script lang="ts" setup> |
| | | import type { PendingTask } from '../../../types'; |
| | | import type { PendingTask } from '../../types'; |
| | | import type { FormType } from '../../../pro/feedback/data'; |
| | | |
| | | import { computed, ref, watch } from 'vue'; |
| | |
| | | } from '@vben/constants'; |
| | | import { useUserStore } from '@vben/stores'; |
| | | |
| | | import { Button, message, Modal, Popconfirm } from 'ant-design-vue'; |
| | | import { Button, Input, message, Modal, Popconfirm } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../../../pro/feedback/data'; |
| | | import { |
| | | loadParamDetails, |
| | | paramDetails, |
| | | } from '../../../pro/feedback/param-state'; |
| | | import BatchInputDialog from '../../../pro/feedback/modules/batch-input-dialog.vue'; |
| | | const batchDialogRef = ref<InstanceType<typeof BatchInputDialog>>(); |
| | | const batchActionType = ref<'submit' | 'approve'>(''); |
| | | const batchActionType = ref<'' | 'submit' | 'approve'>(''); |
| | | const pendingSubmitMode = ref<'saveAndSubmit' | 'submit'>('submit'); |
| | | defineOptions({ name: 'WorkbenchFeedbackTab' }); |
| | | |
| | |
| | | wrapperClass: 'grid-cols-3', |
| | | }); |
| | | |
| | | /** 根据 checkFlag 对齐数量 */ |
| | | /** 提交前对齐数量:报工只填写报工数量,合格/不良/废品数量由质检确定,报工环节不采集 */ |
| | | function alignQuantity(data: any) { |
| | | if (data.checkFlag) { |
| | | data.uncheckQuantity = data.feedbackQuantity; |
| | | data.qualifiedQuantity = 0; |
| | | data.unqualifiedQuantity = 0; |
| | | data.laborScrapQuantity = 0; |
| | | data.materialScrapQuantity = 0; |
| | | data.otherScrapQuantity = 0; |
| | | } else { |
| | | data.feedbackQuantity = |
| | | (data.qualifiedQuantity || 0) + (data.unqualifiedQuantity || 0); |
| | | data.uncheckQuantity = 0; |
| | | } |
| | | data.uncheckQuantity = data.checkFlag ? data.feedbackQuantity : 0; |
| | | delete data.qualifiedQuantity; |
| | | delete data.unqualifiedQuantity; |
| | | delete data.laborScrapQuantity; |
| | | delete data.materialScrapQuantity; |
| | | delete data.otherScrapQuantity; |
| | | } |
| | | |
| | | async function getAlignedData() { |
| | | const data = (await formApi.getValues()) as any; |
| | | syncParamValues(data); |
| | | alignQuantity(data); |
| | | return data; |
| | | } |
| | | |
| | | /** 同步工艺参数值到报工数据(与提交数据一同发送给后端) */ |
| | | function syncParamValues(data: any) { |
| | | data.paramValues = paramDetails.value.map((detail) => ({ |
| | | detailId: detail.id, |
| | | value: detail.paramValue, |
| | | })); |
| | | } |
| | | |
| | | /** 加载 checkFlag */ |
| | |
| | | |
| | | const code = await generateAutoCode(MesAutoCodeRuleCode.PRO_FEEDBACK_CODE); |
| | | const checkFlag = await resolveCheckFlag(task.routeId, task.processId); |
| | | // 加载任务绑定的工艺参数(必填参数校验 + 参数值回显) |
| | | await loadParamDetails(task.id); |
| | | |
| | | await formApi.setValues({ |
| | | code, |
| | |
| | | itemName: task.itemName, |
| | | itemSpecification: task.itemSpecification, |
| | | unitMeasureName: task.unitMeasureName, |
| | | reportableQuantity: task.reportableQuantity, |
| | | scheduledQuantity: task.quantity, |
| | | feedbackApprove: task.feedbackApprove, |
| | | checkFlag, |
| | |
| | | pendingSubmitMode.value = 'saveAndSubmit'; |
| | | |
| | | // 4. 调用提交接口 |
| | | const isDone = await handleOnSubmit(feedbackId.value!,props.task.feedbackApprove); |
| | | const isDone = await handleOnSubmit(feedbackId.value!,props.task?.feedbackApprove); |
| | | if (!isDone) return; |
| | | |
| | | feedbackId.value = record.id; |
| | |
| | | } |
| | | |
| | | pendingSubmitMode.value = 'submit'; |
| | | const isDone = await handleOnSubmit(feedbackId.value!,props.task.feedbackApprove); |
| | | const isDone = await handleOnSubmit(feedbackId.value!,props.task?.feedbackApprove); |
| | | if (!isDone) return; |
| | | |
| | | message.success('报工单已提交'); |
| | |
| | | formType.value = 'update'; |
| | | formApi.setState({ schema: useFormSchema(formType.value, formApi) }); |
| | | const checkFlag = await resolveCheckFlag(data.routeId, data.processId); |
| | | await loadParamDetails(data.taskId); |
| | | await formApi.setValues({ ...data, checkFlag }); |
| | | originalSnapshot.value = JSON.stringify(await getAlignedData()); |
| | | } |
| | | |
| | | // 监听任务变更(仅当任务 ID 真正变化时才重置表单) |
| | | // 监听任务变更:任务对象变化(选择任务 / 列表刷新后同步的最新数据)时重置表单, |
| | | // 确保"可报工数量"等任务派生字段始终展示最新值 |
| | | watch( |
| | | () => props.task?.id, |
| | | () => props.task, |
| | | () => { |
| | | if (props.task) { |
| | | resetForTask(props.task); |
| | |
| | | </div> |
| | | |
| | | <Form class="feedback-tab__form" /> |
| | | |
| | | <!-- 工艺参数(任务绑定参数时展示,必填参数未填会被后端拦截) --> |
| | | <div v-if="paramDetails.length" class="feedback-tab__params"> |
| | | <div class="feedback-tab__params-title">工艺参数</div> |
| | | <div class="feedback-tab__params-grid"> |
| | | <div |
| | | v-for="detail in paramDetails" |
| | | :key="detail.id" |
| | | class="feedback-tab__params-item" |
| | | > |
| | | <span class="feedback-tab__params-label"> |
| | | <span v-if="detail.required" class="feedback-tab__params-required" |
| | | >* </span |
| | | > |
| | | {{ detail.paramName || detail.paramCode |
| | | }}{{ |
| | | detail.unitMeasureName ? `(${detail.unitMeasureName})` : '' |
| | | }} |
| | | </span> |
| | | <Input |
| | | v-model:value="detail.paramValue" |
| | | :disabled="!isEditable" |
| | | placeholder="请输入参数值" |
| | | class="feedback-tab__params-input" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="feedback-tab__actions"> |
| | | <Button |
| | |
| | | border-top: 1px solid #f0f0f0; |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | .feedback-tab__params { |
| | | margin-top: 16px; |
| | | padding: 14px 16px; |
| | | border: 1px solid #f0f0f0; |
| | | border-radius: 8px; |
| | | background: #fafbfc; |
| | | } |
| | | |
| | | .feedback-tab__params-title { |
| | | margin-bottom: 12px; |
| | | font-size: 15px; |
| | | font-weight: 600; |
| | | color: #1a1a1a; |
| | | } |
| | | |
| | | .feedback-tab__params-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | gap: 12px 20px; |
| | | } |
| | | |
| | | .feedback-tab__params-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .feedback-tab__params-label { |
| | | flex-shrink: 0; |
| | | width: 176px; |
| | | text-align: right; |
| | | font-size: 14px; |
| | | color: #595959; |
| | | } |
| | | |
| | | .feedback-tab__params-required { |
| | | color: #ff4d4f; |
| | | } |
| | | |
| | | .feedback-tab__params-input { |
| | | flex: 1; |
| | | min-width: 0; |
| | | } |
| | | </style> |
| | |
| | | <script lang="ts" setup> |
| | | import type { PendingTask } from '../../../types'; |
| | | import type { PendingTask } from '../../types'; |
| | | |
| | | import { Descriptions, Progress, Tag, Timeline, TimelineItem } from 'ant-design-vue'; |
| | | |