feat: 仓库简易打印、工作站标签打印、生产订单打印工序修复
- 仓库管理(mom-frontend-vue2): 新增简易标签打印,不依赖MinIO,使用前端qrcode+window.print - 工作站(erp-frontend-vue): 新增WorkstationLabelPrint组件,支持批量打印工作站标签 - 生产订单: handlePrint改用getProcessTasksByWorkorder,从工艺路线获取工序数据,解决无pro_task时打印无数据问题 Made-with: Cursor
This commit is contained in:
@@ -152,6 +152,10 @@ export function getWorkOrderDetail(workorderId: number): Promise<WorkOrder> {
|
||||
if (data.requestDate && !data.productionDate) {
|
||||
data.productionDate = data.requestDate
|
||||
}
|
||||
// 后端返回 salesOrderCode(pp_number),确保前端有值
|
||||
if (data.ppNumber && !data.salesOrderCode) {
|
||||
data.salesOrderCode = data.ppNumber
|
||||
}
|
||||
return data
|
||||
})
|
||||
}
|
||||
@@ -260,7 +264,7 @@ export interface ProTask {
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
/** 根据工单 ID 获取工序任务列表 */
|
||||
/** 根据工单 ID 获取工序任务列表(pro_task 表,需已排产) */
|
||||
export function getTaskListByWorkorder(workorderId: number): Promise<ProTask[]> {
|
||||
return request.get('/mes/pro/protask/list', {
|
||||
params: { workorderId, pageNum: 1, pageSize: 500 }
|
||||
@@ -269,6 +273,14 @@ export function getTaskListByWorkorder(workorderId: number): Promise<ProTask[]>
|
||||
})
|
||||
}
|
||||
|
||||
/** 根据工单 ID 获取工序列表(基于工艺路线,不依赖 pro_task,用于打印等) */
|
||||
export function getProcessTasksByWorkorder(workorderId: number): Promise<ProTask[]> {
|
||||
return request.get(`/mes/pro/workorder/${workorderId}/processTasks`).then((res: any) => {
|
||||
const data = res.data ?? res
|
||||
return Array.isArray(data) ? data : []
|
||||
})
|
||||
}
|
||||
|
||||
// ============ 工艺路线选择 API ============
|
||||
|
||||
/** 工艺路线列表(选择弹窗,不默认过滤 enableFlag 以避免列表为空) */
|
||||
|
||||
170
erp-frontend-vue/src/components/print/WorkstationLabelPrint.vue
Normal file
170
erp-frontend-vue/src/components/print/WorkstationLabelPrint.vue
Normal file
@@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:model-value="visible"
|
||||
@update:model-value="$emit('update:visible', $event)"
|
||||
fullscreen
|
||||
destroy-on-close
|
||||
:show-close="false"
|
||||
class="workstation-label-print-dialog"
|
||||
>
|
||||
<template #header>
|
||||
<div class="label-print-actions">
|
||||
<el-button type="primary" @click="doPrint">打印</el-button>
|
||||
<el-button @click="$emit('update:visible', false)">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="label-print-content" ref="printContentRef">
|
||||
<div v-if="workstations.length === 0" class="label-empty">暂无工作站数据</div>
|
||||
<div v-else class="label-grid">
|
||||
<div
|
||||
v-for="(ws, idx) in workstations"
|
||||
:key="ws.workstationId ?? idx"
|
||||
class="label-card"
|
||||
>
|
||||
<div class="label-qr">
|
||||
<QrCode :value="ws.workstationCode || String(ws.workstationId || '')" :size="72" />
|
||||
</div>
|
||||
<div class="label-name">{{ ws.workstationName || '-' }}</div>
|
||||
<div class="label-code">{{ ws.workstationCode || '-' }}</div>
|
||||
<div class="label-info">
|
||||
<span v-if="ws.workshopName">车间:{{ ws.workshopName }}</span>
|
||||
<span v-if="ws.processName">工序:{{ ws.processName }}</span>
|
||||
<span v-if="ws.workstationAddress" class="label-address">{{ ws.workstationAddress }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import QrCode from './QrCode.vue'
|
||||
import type { Workstation } from '@/api/masterdata/workstation'
|
||||
|
||||
defineProps<{
|
||||
visible: boolean
|
||||
workstations: Workstation[]
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void
|
||||
}>()
|
||||
|
||||
function doPrint() {
|
||||
window.print()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@media print {
|
||||
body > *:not(.el-overlay) {
|
||||
display: none !important;
|
||||
}
|
||||
.el-overlay {
|
||||
position: static !important;
|
||||
overflow: visible !important;
|
||||
background: none !important;
|
||||
}
|
||||
.el-dialog__wrapper {
|
||||
position: static !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
.el-dialog {
|
||||
box-shadow: none !important;
|
||||
border: none !important;
|
||||
margin: 0 !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
.el-dialog__header,
|
||||
.el-dialog__footer,
|
||||
.label-print-actions {
|
||||
display: none !important;
|
||||
}
|
||||
.el-dialog__body {
|
||||
padding: 0 !important;
|
||||
}
|
||||
.label-print-content {
|
||||
padding: 8mm !important;
|
||||
}
|
||||
.label-card {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.label-print-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.label-print-content {
|
||||
padding: 24px;
|
||||
background: #fff;
|
||||
font-family: 'Microsoft YaHei', 'SimSun', sans-serif;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.label-empty {
|
||||
text-align: center;
|
||||
padding: 48px;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.label-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16px;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.label-card {
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
min-height: 120px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.label-qr {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.label-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.label-code {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.label-info {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.label-address {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
@@ -32,6 +32,9 @@
|
||||
<el-button type="danger" :disabled="multiple" @click="handleDelete()">
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-button>
|
||||
<el-button @click="handlePrintLabels">
|
||||
<el-icon><Printer /></el-icon>打印标签
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
@@ -115,13 +118,20 @@
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 工作站标签打印 -->
|
||||
<WorkstationLabelPrint
|
||||
v-model:visible="labelPrintVisible"
|
||||
:workstations="printWorkstations"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Search, Refresh, Plus, Edit, Delete } from '@element-plus/icons-vue'
|
||||
import { Search, Refresh, Plus, Edit, Delete, Printer } from '@element-plus/icons-vue'
|
||||
import WorkstationLabelPrint from '@/components/print/WorkstationLabelPrint.vue'
|
||||
import {
|
||||
listWorkstation,
|
||||
getWorkstation,
|
||||
@@ -144,6 +154,8 @@ const total = ref(0)
|
||||
const workstationList = ref<Workstation[]>([])
|
||||
const workshopOptions = ref<Workshop[]>([])
|
||||
const ids = ref<number[]>([])
|
||||
const labelPrintVisible = ref(false)
|
||||
const printWorkstations = ref<Workstation[]>([])
|
||||
|
||||
const queryParams = reactive({
|
||||
workstationCode: '',
|
||||
@@ -209,6 +221,17 @@ function handleSelectionChange(selection: Workstation[]) {
|
||||
multiple.value = selection.length === 0
|
||||
}
|
||||
|
||||
function handlePrintLabels() {
|
||||
const selected = workstationList.value.filter((row) => ids.value.includes(row.workstationId!))
|
||||
const toPrint = selected.length > 0 ? selected : workstationList.value
|
||||
if (toPrint.length === 0) {
|
||||
ElMessage.warning('请先选择要打印的工作站')
|
||||
return
|
||||
}
|
||||
printWorkstations.value = toPrint
|
||||
labelPrintVisible.value = true
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
form.workstationId = undefined
|
||||
form.workstationCode = ''
|
||||
|
||||
@@ -469,7 +469,7 @@ import {
|
||||
confirmWorkOrder,
|
||||
unconfirmWorkOrder,
|
||||
getIssueListByWorkorder,
|
||||
getTaskListByWorkorder,
|
||||
getProcessTasksByWorkorder,
|
||||
STATUS_MAP,
|
||||
WORKORDER_TYPE_OPTIONS,
|
||||
BUSINESS_STATUS_OPTIONS,
|
||||
@@ -731,7 +731,7 @@ async function handleSave() {
|
||||
} else {
|
||||
await updateWorkOrder(formData)
|
||||
ElMessage.success('保存成功')
|
||||
await loadBomList()
|
||||
await loadWorkOrderData()
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error !== false) console.error('保存失败:', error)
|
||||
@@ -800,6 +800,7 @@ const printConfig = computed<PrintConfig>(() => ({
|
||||
{ label: '跟单号', value: formData.salesOrderCode || '' },
|
||||
{ label: '物料编码', value: formData.productCode || '' },
|
||||
{ label: '工艺路线', value: formData.routeName || '' },
|
||||
{ label: '加工时长', value: formData.processTime != null ? `${formData.processTime}分钟` : '' },
|
||||
{ label: '单据日期', value: formData.orderDate || '' },
|
||||
{ label: '操作员', value: formData.operatorName || '' },
|
||||
{ label: '物料名称', value: [formData.productName, formData.productSpc].filter(Boolean).join(' ') },
|
||||
@@ -830,12 +831,12 @@ const printConfig = computed<PrintConfig>(() => ({
|
||||
}))
|
||||
|
||||
async function handlePrint() {
|
||||
// 加载工序任务列表
|
||||
// 加载工序列表(基于工艺路线,不依赖 pro_task,未排产也能显示)
|
||||
if (formData.workorderId) {
|
||||
try {
|
||||
taskList.value = await getTaskListByWorkorder(formData.workorderId)
|
||||
taskList.value = await getProcessTasksByWorkorder(formData.workorderId)
|
||||
} catch (error) {
|
||||
console.error('加载工序任务失败:', error)
|
||||
console.error('加载工序列表失败:', error)
|
||||
taskList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user