feat: save work-order tasks as a production task set

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-08-18 16:16:28 +08:00
1 parent 893ff58441
commit 7645ac77d2
3 files changed
+212 -12

No files matched your search

@@ -12,10 +12,7 @@
<div class="production-work-order-card__title-band">
<div class="production-work-order-card__title-row">
<div class="production-work-order-card__title-left">
<ProductionStepBadge
v-if="tasksBatchReady"
:count="resolveStepBadgeCount(row)"
/>
<ProductionStepBadge v-if="tasksBatchReady" :count="resolveStepBadgeCount(row)" />
<el-checkbox
v-if="!isMineMode"
:model-value="isSelected(row.id)"
@@ -35,7 +32,9 @@
<span
v-else
class="production-work-order-card__no"
:class="{ 'production-work-order-card__no--remark': temporaryCardTitleUsesRemark(row) }"
:class="{
'production-work-order-card__no--remark': temporaryCardTitleUsesRemark(row)
}"
>{{ cardTitleLabel(row) }}</span
>
<el-tag
@@ -119,6 +118,9 @@
<el-dropdown-item v-if="canDispatchTask(row)" command="view">
<el-icon><View /></el-icon>查看
</el-dropdown-item>
<el-dropdown-item v-if="canSaveTaskSet(row)" command="save-set">
<el-icon><FolderAdd /></el-icon>存为任务集合
</el-dropdown-item>
<el-dropdown-item v-if="canEditWorkOrder(row)" command="edit">
<el-icon><EditPen /></el-icon>编辑
</el-dropdown-item>
@@ -159,7 +161,10 @@
</div>
</template>
</dl>
<section v-if="rowCardMeta(row).showCellProducts" class="production-work-order-card__cells">
<section
v-if="rowCardMeta(row).showCellProducts"
class="production-work-order-card__cells"
>
<div v-if="cellProducts(row).length" class="production-work-order-card__cell-list">
<div
v-for="(product, pi) in cellProducts(row)"
@@ -284,7 +289,19 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { View, Position, Document, Check, More, EditPen, Delete, ArrowDown, ArrowUp, DataLine } from '@element-plus/icons-vue'
import {
View,
Position,
Document,
Check,
More,
EditPen,
Delete,
ArrowDown,
ArrowUp,
DataLine,
FolderAdd
} from '@element-plus/icons-vue'
import type { ProductionWorkOrderItem } from '@/api/production-work-order'
import type { OutboundQcDetailProduct } from '@/api/qc-work-order'
import type { ProductionWorkOrderCardMetaConfig } from '@/utils/production-work-order-card-meta'
@@ -301,6 +318,7 @@
import type { TagType } from '@/utils/status-tag-registry'
import { productStatusLabel, productStatusTagType } from '@/utils/status-tag-registry'
import { useUserStore } from '@/store/modules/user'
import { useMenuPerm } from '@/hooks/core/useMenuPerm'
import { sumProductionWorkOrderStepBadgeCount } from '@/utils/production-work-order-step-badge'
import WorkOrderTaskSubList from './WorkOrderTaskSubList.vue'
import ProductionStepBadge from './ProductionStepBadge.vue'
@@ -337,6 +355,7 @@
)
const userStore = useUserStore()
const { hasMenuPerm } = useMenuPerm()
const isMineMode = computed(() => props.pageMode === 'mine')
function resolveCurrentUserId() {
@@ -369,9 +388,7 @@
}
const cardMeta = computed(
() =>
props.cardMetaConfig ??
getCardMetaConfigForLegacyTab(props.activeTypeTab || 'inbound')
() => props.cardMetaConfig ?? getCardMetaConfigForLegacyTab(props.activeTypeTab || 'inbound')
)
const listTabKey = computed(() => cardMeta.value.tabKey)
@@ -508,7 +525,8 @@
function dispatchQcTooltip(row: ProductionWorkOrderItem) {
if (!rowCardMeta(row).allowDispatchQc) return ''
if (row.workOrderStatus === 'dpz') return '请先派发生产任务,并在步骤中录入有效物产出及条码后再派发质检'
if (row.workOrderStatus === 'dpz')
return '请先派发生产任务,并在步骤中录入有效物产出及条码后再派发质检'
if (row.workOrderStatus === 'yzf') return '已作废工单不可派发质检'
return '请先在生产任务步骤中录入类型为有效物且含条码的产出物'
}
@@ -531,8 +549,21 @@
return row.workOrderStatus !== 'ywc'
}
function canSaveTaskSet(row: ProductionWorkOrderItem) {
if (isMineMode.value) return false
if (!hasMenuPerm('production:task-set:add')) return false
const tasks = resolvePresetTasks(row.id)
return Array.isArray(tasks) && tasks.some((t) => String(t?.taskName || '').trim())
}
function hasMoreActions(row: ProductionWorkOrderItem) {
return canDispatchTask(row) || canEditWorkOrder(row) || canDeleteWorkOrder(row) || true
return (
canDispatchTask(row) ||
canSaveTaskSet(row) ||
canEditWorkOrder(row) ||
canDeleteWorkOrder(row) ||
true
)
}
</script>
@@ -0,0 +1,151 @@
<!-- 将当前工单已派发任务存为生产任务集合 -->
<!-- eslint-disable vue/no-v-model-argument -->
<template>
<el-dialog
v-model="dialogVisible"
title="存为任务集合"
width="720px"
class="dialog-form-layout"
align-center
append-to-body
modal-class="protocol-dialog-mask"
destroy-on-close
:close-on-click-modal="false"
@close="handleClose"
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-position="top"
class="biz-dialog-form"
>
<div class="form-grid-2">
<el-form-item label="集合名称" prop="setName" class="form-col-span-2">
<el-input v-model="form.setName" placeholder="请输入集合名称" maxlength="200" />
</el-form-item>
<el-form-item label="备注" class="form-col-span-2">
<el-input
v-model="form.remark"
type="textarea"
:rows="2"
placeholder="可选"
maxlength="512"
/>
</el-form-item>
<el-form-item label="将保存的任务" class="form-col-span-2">
<p v-if="!preview.items.length" class="hint-muted">没有可保存的任务名称</p>
<p v-else-if="preview.skippedEmptyNameCount" class="hint-muted">
将跳过 {{ preview.skippedEmptyNameCount }} 条空名称任务
</p>
<el-table v-if="preview.items.length" class="list-table" :data="preview.items" stripe>
<el-table-column
prop="taskName"
label="任务名称"
min-width="140"
show-overflow-tooltip
/>
<el-table-column label="工艺路线" min-width="140" show-overflow-tooltip>
<template #default="{ row }">{{ row.routeName || '—' }}</template>
</el-table-column>
<el-table-column
prop="description"
label="描述"
min-width="140"
show-overflow-tooltip
/>
<el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip />
</el-table>
</el-form-item>
</div>
</el-form>
<template #footer>
<el-button class="btn-ghost" @click="handleClose">
<el-icon><Close /></el-icon>取消
</el-button>
<el-button
class="btn-main"
type="primary"
:loading="submitting"
:disabled="!preview.items.length"
@click="handleSubmit"
>
<el-icon><Check /></el-icon>确定
</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { Close, Check } from '@element-plus/icons-vue'
import type { FormInstance, FormRules } from 'element-plus'
import { fetchCreateProductionTaskSetFromWorkOrder } from '@/api/production-task-set'
import { mapWorkOrderTasksToSetItems } from '@/utils/production-task-set'
defineOptions({ name: 'SaveProductionTaskSetDialog' })
const props = defineProps<{
visible: boolean
workOrderId: string
tasks?: Record<string, any>[]
}>()
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void
(e: 'success'): void
}>()
const dialogVisible = computed({
get: () => props.visible,
set: (val) => emit('update:visible', val)
})
const formRef = ref<FormInstance>()
const submitting = ref(false)
const form = reactive({ setName: '', remark: '' })
const rules: FormRules = {
setName: [{ required: true, message: '请输入集合名称', trigger: 'blur' }]
}
const preview = computed(() => mapWorkOrderTasksToSetItems(props.tasks))
function handleClose() {
dialogVisible.value = false
form.setName = ''
form.remark = ''
}
async function handleSubmit() {
if (!preview.value.items.length) {
ElMessage.warning('没有可保存的任务名称')
return
}
const valid = await formRef.value?.validate?.().catch(() => false)
if (!valid) return
submitting.value = true
try {
const result = await fetchCreateProductionTaskSetFromWorkOrder({
workOrderId: props.workOrderId,
setName: form.setName.trim(),
remark: form.remark || undefined
})
const skipped = Number(result?.skippedEmptyNameCount || 0)
ElMessage.success(skipped > 0 ? `保存成功,已跳过 ${skipped} 条空名称任务` : '保存成功')
emit('success')
handleClose()
} catch {
// 错误由请求拦截器提示
} finally {
submitting.value = false
}
}
</script>
<style scoped lang="scss">
.hint-muted {
margin: 0 0 8px;
font-size: 12px;
color: var(--mint-text-muted, #94a3b8);
}
</style>
@@ -220,6 +220,12 @@
:source-type="sampleReceptionSourceType"
@success="handleSampleReceptionSuccess"
/>
<SaveProductionTaskSetDialog
v-model:visible="saveSetVisible"
:work-order-id="saveSetRow?.id || ''"
:tasks="saveSetTasks"
/>
</div>
</template>
@@ -234,6 +240,7 @@
import ProductionWorkOrderCardList from '../ProductionWorkOrderCardList.vue'
import ProductionWorkOrderQcViewDialog from '../ProductionWorkOrderQcViewDialog.vue'
import SampleReceptionDialog from '../SampleReceptionDialog.vue'
import SaveProductionTaskSetDialog from '../SaveProductionTaskSetDialog.vue'
import { PRODUCTION_WORK_ORDER_VIEW_DIALOG_COMPONENTS } from '../view-dialogs/view-dialog-registry'
import {
useProductionWorkOrderTabPage,
@@ -266,6 +273,8 @@
const sampleReceptionSourceType = ref<string | undefined>(undefined)
const qcViewVisible = ref(false)
const qcViewRow = ref<ProductionWorkOrderItem | null>(null)
const saveSetVisible = ref(false)
const saveSetRow = ref<ProductionWorkOrderItem | null>(null)
const {
cardMetaConfig,
@@ -297,6 +306,10 @@
taskRefreshKey
} = useProductionWorkOrderTabPage(tabKeyRef, pageModeComputed)
const saveSetTasks = computed(
() => presetTasksByWorkOrderId.value[saveSetRow.value?.id || ''] || []
)
const viewDialogComponent = computed(
() => PRODUCTION_WORK_ORDER_VIEW_DIALOG_COMPONENTS[viewTabKey.value]
)
@@ -322,6 +335,11 @@
handleView(row)
return
}
if (command === 'save-set') {
saveSetRow.value = row
saveSetVisible.value = true
return
}
emit('command', command, row)
}