提交
This commit is contained in:
commit
db556f469e
100
mes-ui/src/api/outsourcing.ts
Normal file
100
mes-ui/src/api/outsourcing.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { request } from '@/utils/request'
|
||||||
|
|
||||||
|
// 委外记录表 类型定义
|
||||||
|
export interface Outsourcing {
|
||||||
|
id?: number
|
||||||
|
|
||||||
|
name?: string
|
||||||
|
|
||||||
|
code?: string
|
||||||
|
|
||||||
|
businessType?: number
|
||||||
|
|
||||||
|
businessId?: number
|
||||||
|
|
||||||
|
businessCode?: string
|
||||||
|
|
||||||
|
orderItemId?: number
|
||||||
|
|
||||||
|
quantity?: number
|
||||||
|
|
||||||
|
supplierId?: number
|
||||||
|
|
||||||
|
supplierName?: string
|
||||||
|
|
||||||
|
unitPrice?: string
|
||||||
|
|
||||||
|
totalAmount?: string
|
||||||
|
|
||||||
|
beginPreparationTime?: string
|
||||||
|
|
||||||
|
endPreparationTime?: string
|
||||||
|
|
||||||
|
beginTime?: string
|
||||||
|
|
||||||
|
endTime?: string
|
||||||
|
|
||||||
|
remark?: string
|
||||||
|
|
||||||
|
createTime?: string
|
||||||
|
|
||||||
|
createBy?: string
|
||||||
|
|
||||||
|
overdue?: number
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 委外记录表 API
|
||||||
|
export const outsourcingApi = {
|
||||||
|
// 分页查询
|
||||||
|
page(params: { page: number; pageSize: number; id?: number; name?: string; businessType?: number }) {
|
||||||
|
return request({ url: '/biz/outsourcing/page', method: 'get', params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取详情
|
||||||
|
detail(id: string) {
|
||||||
|
return request({ url: `/biz/outsourcing/${id}`, method: 'get' })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
create(data: Outsourcing) {
|
||||||
|
return request({ url: '/biz/outsourcing', method: 'post', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 修改
|
||||||
|
update(data: Outsourcing) {
|
||||||
|
return request({ url: '/biz/outsourcing', method: 'put', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
delete(ids: string[]) {
|
||||||
|
return request({ url: `/biz/outsourcing/${ids.join(',')}`, method: 'delete' })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 导出
|
||||||
|
export(params?: { ids?: string[]; id?: number; name?: string; businessType?: number }) {
|
||||||
|
const p: Record<string, any> = {}
|
||||||
|
if (params?.ids?.length) p.ids = params.ids.join(',')
|
||||||
|
if (params?.id !== undefined && params?.id !== null) p.id = params.id
|
||||||
|
if (params?.name !== undefined && params?.name !== null) p.name = params.name
|
||||||
|
if (params?.businessType !== undefined && params?.businessType !== null) p.businessType = params.businessType
|
||||||
|
return request({ url: `/biz/outsourcing/export`, method: 'get', params: p, responseType: 'blob' })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 导入
|
||||||
|
importData(file: File) {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
return request<{ success: number; fail: number; errors: string[] }>({
|
||||||
|
url: `/biz/outsourcing/import`,
|
||||||
|
method: 'post',
|
||||||
|
data: formData,
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 下载导入模板
|
||||||
|
downloadTemplate() {
|
||||||
|
return request({ url: `/biz/outsourcing/template`, method: 'get', responseType: 'blob' })
|
||||||
|
}
|
||||||
|
}
|
||||||
492
mes-ui/src/views/biz/outsourcing/index.vue
Normal file
492
mes-ui/src/views/biz/outsourcing/index.vue
Normal file
@ -0,0 +1,492 @@
|
|||||||
|
<template>
|
||||||
|
<div class="page-container">
|
||||||
|
<n-card>
|
||||||
|
<!-- 搜索表单 -->
|
||||||
|
<div class="search-form">
|
||||||
|
<n-form inline :model="searchForm" label-placement="left">
|
||||||
|
<n-form-item label="主键id">
|
||||||
|
<n-input v-model:value="searchForm.id" placeholder="请输入主键id" clearable />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="委外产品名称">
|
||||||
|
<n-input v-model:value="searchForm.name" placeholder="请输入委外产品名称" clearable />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="业务类型 1工序委外">
|
||||||
|
<n-select v-model:value="searchForm.businessType" placeholder="请选择业务类型 1工序委外" clearable style="width: 150px" :options="[]" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item>
|
||||||
|
<n-space>
|
||||||
|
<n-button type="primary" @click="handleSearch">
|
||||||
|
<template #icon><n-icon><SearchOutline /></n-icon></template>
|
||||||
|
搜索
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="handleReset">
|
||||||
|
<template #icon><n-icon><RefreshOutline /></n-icon></template>
|
||||||
|
重置
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 工具栏 -->
|
||||||
|
<div class="table-toolbar">
|
||||||
|
<n-space>
|
||||||
|
<n-button type="primary" @click="handleAdd">
|
||||||
|
<template #icon><n-icon><AddOutline /></n-icon></template>
|
||||||
|
新增
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="importModalVisible = true">
|
||||||
|
<template #icon><n-icon><CloudUploadOutline /></n-icon></template>
|
||||||
|
导入
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="handleExport">
|
||||||
|
<template #icon><n-icon><DownloadOutline /></n-icon></template>
|
||||||
|
导出{{ selectedIds.length > 0 ? `(${selectedIds.length})` : '' }}
|
||||||
|
</n-button>
|
||||||
|
<n-button type="error" :disabled="selectedIds.length === 0" @click="handleBatchDelete">
|
||||||
|
<template #icon><n-icon><TrashOutline /></n-icon></template>
|
||||||
|
删除
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格 -->
|
||||||
|
<n-data-table
|
||||||
|
:columns="columns"
|
||||||
|
:data="tableData"
|
||||||
|
:loading="loading"
|
||||||
|
:pagination="pagination"
|
||||||
|
:row-key="(row) => row.id"
|
||||||
|
:scroll-x="1200"
|
||||||
|
@update:page="handlePageChange"
|
||||||
|
@update:page-size="handlePageSizeChange"
|
||||||
|
@update:checked-row-keys="handleCheck"
|
||||||
|
/>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<!-- 新增/编辑弹窗 -->
|
||||||
|
<n-modal v-model:show="modalVisible" preset="card" :title="modalTitle" style="width: 800px">
|
||||||
|
<n-form ref="formRef" :model="formData" :rules="formRules" label-placement="left" label-width="100px">
|
||||||
|
<n-grid :cols="2" :x-gap="24">
|
||||||
|
<n-form-item-gi label="委外产品名称" path="name">
|
||||||
|
<n-input v-model:value="formData.name" placeholder="请输入委外产品名称" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="委外产品编码" path="code">
|
||||||
|
<n-input v-model:value="formData.code" placeholder="请输入委外产品编码" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="业务类型 1工序委外" path="businessType">
|
||||||
|
<n-select v-model:value="formData.businessType" placeholder="请选择业务类型 1工序委外" :options="[]" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="业务id" path="businessId">
|
||||||
|
<n-input v-model:value="formData.businessId" placeholder="请输入业务id" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="业务编码" path="businessCode">
|
||||||
|
<n-input v-model:value="formData.businessCode" placeholder="请输入业务编码" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="生产订单id" path="orderItemId">
|
||||||
|
<n-input v-model:value="formData.orderItemId" placeholder="请输入生产订单id" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="委外数量" path="quantity">
|
||||||
|
<n-input v-model:value="formData.quantity" placeholder="请输入委外数量" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="供应商id" path="supplierId">
|
||||||
|
<n-input v-model:value="formData.supplierId" placeholder="请输入供应商id" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="供应商名称" path="supplierName">
|
||||||
|
<n-input v-model:value="formData.supplierName" placeholder="请输入供应商名称" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="单价" path="unitPrice">
|
||||||
|
<n-input v-model:value="formData.unitPrice" placeholder="请输入单价" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="委外金额(数量*单价)" path="totalAmount">
|
||||||
|
<n-input v-model:value="formData.totalAmount" placeholder="请输入委外金额(数量*单价)" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="预计委外开始时间" path="beginPreparationTime">
|
||||||
|
<n-date-picker v-model:value="formData.beginPreparationTime" type="datetime" clearable style="width: 100%" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="预计委外结束时间" path="endPreparationTime">
|
||||||
|
<n-date-picker v-model:value="formData.endPreparationTime" type="datetime" clearable style="width: 100%" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="实际开始时间" path="beginTime">
|
||||||
|
<n-date-picker v-model:value="formData.beginTime" type="datetime" clearable style="width: 100%" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="实际结束时间" path="endTime">
|
||||||
|
<n-date-picker v-model:value="formData.endTime" type="datetime" clearable style="width: 100%" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="是否超期" path="overdue">
|
||||||
|
<n-input v-model:value="formData.overdue" placeholder="请输入是否超期" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
</n-grid>
|
||||||
|
</n-form>
|
||||||
|
<template #footer>
|
||||||
|
<n-space justify="end">
|
||||||
|
<n-button @click="modalVisible = false">取消</n-button>
|
||||||
|
<n-button type="primary" @click="handleSubmit">确定</n-button>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
|
||||||
|
<!-- 导入弹窗 -->
|
||||||
|
<n-modal v-model:show="importModalVisible" preset="card" title="导入委外记录表" style="width: 500px">
|
||||||
|
<n-space vertical>
|
||||||
|
<n-alert type="info">
|
||||||
|
<template #header>导入说明</template>
|
||||||
|
<ul style="margin: 0; padding-left: 16px; line-height: 1.8">
|
||||||
|
<li>请先下载导入模板,按模板格式填写数据</li>
|
||||||
|
<li>支持 .xlsx 或 .xls 格式</li>
|
||||||
|
</ul>
|
||||||
|
</n-alert>
|
||||||
|
<n-space>
|
||||||
|
<n-button type="primary" @click="handleDownloadTemplate">
|
||||||
|
<template #icon><n-icon><DownloadOutline /></n-icon></template>
|
||||||
|
下载模板
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
<n-upload :max="1" accept=".xlsx,.xls" :show-file-list="true" :custom-request="handleImportUpload">
|
||||||
|
<n-upload-dragger>
|
||||||
|
<div style="margin-bottom: 12px">
|
||||||
|
<n-icon size="48" :depth="3"><CloudUploadOutline /></n-icon>
|
||||||
|
</div>
|
||||||
|
<n-text style="font-size: 16px">点击或拖拽文件到此处上传</n-text>
|
||||||
|
<n-p depth="3" style="margin: 8px 0 0 0">支持 .xlsx 或 .xls 格式</n-p>
|
||||||
|
</n-upload-dragger>
|
||||||
|
</n-upload>
|
||||||
|
</n-space>
|
||||||
|
<template #footer>
|
||||||
|
<n-button @click="importModalVisible = false">关闭</n-button>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, h, onMounted } from 'vue'
|
||||||
|
import { NButton, NSpace, NIcon, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
|
||||||
|
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline } from '@vicons/ionicons5'
|
||||||
|
import { outsourcingApi, type Outsourcing } from '@/api/outsourcing'
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const dialog = useDialog()
|
||||||
|
|
||||||
|
// 搜索表单
|
||||||
|
const searchForm = reactive({
|
||||||
|
id: null as number | null,
|
||||||
|
name: '',
|
||||||
|
businessType: null as number | null,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表格数据
|
||||||
|
const tableData = ref<Outsourcing[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const selectedIds = ref<number[]>([])
|
||||||
|
const pagination = reactive({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
itemCount: 0,
|
||||||
|
showSizePicker: true,
|
||||||
|
pageSizes: [10, 20, 50]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 弹窗
|
||||||
|
const modalVisible = ref(false)
|
||||||
|
const modalTitle = ref('')
|
||||||
|
const importModalVisible = ref(false)
|
||||||
|
const formRef = ref()
|
||||||
|
const defaultFormData: Outsourcing = {
|
||||||
|
name: '',
|
||||||
|
code: '',
|
||||||
|
businessType: undefined,
|
||||||
|
businessId: undefined,
|
||||||
|
businessCode: '',
|
||||||
|
orderItemId: undefined,
|
||||||
|
quantity: undefined,
|
||||||
|
supplierId: undefined,
|
||||||
|
supplierName: '',
|
||||||
|
unitPrice: undefined,
|
||||||
|
totalAmount: undefined,
|
||||||
|
beginPreparationTime: undefined,
|
||||||
|
endPreparationTime: undefined,
|
||||||
|
beginTime: undefined,
|
||||||
|
endTime: undefined,
|
||||||
|
overdue: undefined,
|
||||||
|
}
|
||||||
|
const formData = reactive<Outsourcing>({ ...defaultFormData })
|
||||||
|
|
||||||
|
// 字典选项(下拉框/单选框/复选框关联字典时使用)
|
||||||
|
|
||||||
|
// 表单校验规则
|
||||||
|
const formRules = {
|
||||||
|
businessType: { required: true, message: '请输入业务类型 1工序委外', trigger: 'blur' },
|
||||||
|
supplierName: { required: true, message: '请输入供应商名称', trigger: 'blur' },
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格列
|
||||||
|
const columns: DataTableColumns<Outsourcing> = [
|
||||||
|
{ type: 'selection' },
|
||||||
|
{ title: '主键id', key: 'id' },
|
||||||
|
{ title: '委外产品名称', key: 'name' },
|
||||||
|
{ title: '委外产品编码', key: 'code' },
|
||||||
|
{ title: '业务类型 1工序委外', key: 'businessType' },
|
||||||
|
{ title: '业务id', key: 'businessId' },
|
||||||
|
{ title: '业务编码', key: 'businessCode' },
|
||||||
|
{ title: '生产订单id', key: 'orderItemId' },
|
||||||
|
{ title: '委外数量', key: 'quantity' },
|
||||||
|
{ title: '供应商id', key: 'supplierId' },
|
||||||
|
{ title: '供应商名称', key: 'supplierName' },
|
||||||
|
{ title: '单价', key: 'unitPrice' },
|
||||||
|
{ title: '委外金额(数量*单价)', key: 'totalAmount' },
|
||||||
|
{ title: '预计委外开始时间', key: 'beginPreparationTime' },
|
||||||
|
{ title: '预计委外结束时间', key: 'endPreparationTime' },
|
||||||
|
{ title: '实际开始时间', key: 'beginTime' },
|
||||||
|
{ title: '实际结束时间', key: 'endTime' },
|
||||||
|
{ title: '备注', key: 'remark' },
|
||||||
|
{ title: '创建时间', key: 'createTime', width: 180 },
|
||||||
|
{ title: '创建人', key: 'createBy' },
|
||||||
|
{ title: '是否超期', key: 'overdue' },
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 140,
|
||||||
|
fixed: 'right',
|
||||||
|
render(row) {
|
||||||
|
return h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'nowrap' } }, [
|
||||||
|
h(NButton, { size: 'small', quaternary: true, onClick: () => handleEdit(row) }, {
|
||||||
|
default: () => [h(NIcon, null, { default: () => h(CreateOutline) }), ' 编辑']
|
||||||
|
}),
|
||||||
|
h(NButton, { size: 'small', quaternary: true, type: 'error', onClick: () => handleDelete(row) }, {
|
||||||
|
default: () => [h(NIcon, null, { default: () => h(TrashOutline) }), ' 删除']
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 加载数据
|
||||||
|
async function loadData() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await outsourcingApi.page({
|
||||||
|
page: pagination.page,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
id: searchForm.id || undefined,
|
||||||
|
name: searchForm.name || undefined,
|
||||||
|
businessType: searchForm.businessType || undefined,
|
||||||
|
})
|
||||||
|
tableData.value = res.list
|
||||||
|
pagination.itemCount = res.total
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
function handleSearch() {
|
||||||
|
pagination.page = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
function handleReset() {
|
||||||
|
searchForm.id = null
|
||||||
|
|
||||||
|
searchForm.name = ''
|
||||||
|
|
||||||
|
searchForm.businessType = null
|
||||||
|
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
function handlePageChange(page: number) {
|
||||||
|
pagination.page = page
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePageSizeChange(pageSize: number) {
|
||||||
|
pagination.pageSize = pageSize
|
||||||
|
pagination.page = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择
|
||||||
|
function handleCheck(keys: Array<string | number>) {
|
||||||
|
selectedIds.value = keys as number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
function handleAdd() {
|
||||||
|
modalTitle.value = '新增委外记录表'
|
||||||
|
Object.assign(formData, defaultFormData)
|
||||||
|
modalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
function handleEdit(row: Outsourcing) {
|
||||||
|
modalTitle.value = '编辑委外记录表'
|
||||||
|
Object.assign(formData, row)
|
||||||
|
if (formData.beginPreparationTime && typeof formData.beginPreparationTime === 'string') {
|
||||||
|
formData.beginPreparationTime = new Date(formData.beginPreparationTime.replace(' ', 'T')).getTime()
|
||||||
|
}
|
||||||
|
if (formData.endPreparationTime && typeof formData.endPreparationTime === 'string') {
|
||||||
|
formData.endPreparationTime = new Date(formData.endPreparationTime.replace(' ', 'T')).getTime()
|
||||||
|
}
|
||||||
|
if (formData.beginTime && typeof formData.beginTime === 'string') {
|
||||||
|
formData.beginTime = new Date(formData.beginTime.replace(' ', 'T')).getTime()
|
||||||
|
}
|
||||||
|
if (formData.endTime && typeof formData.endTime === 'string') {
|
||||||
|
formData.endTime = new Date(formData.endTime.replace(' ', 'T')).getTime()
|
||||||
|
}
|
||||||
|
if (formData.createTime && typeof formData.createTime === 'string') {
|
||||||
|
formData.createTime = new Date(formData.createTime.replace(' ', 'T')).getTime()
|
||||||
|
}
|
||||||
|
modalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
async function handleSubmit() {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
try {
|
||||||
|
const submitData = { ...formData } as Outsourcing
|
||||||
|
if (typeof submitData.beginPreparationTime === 'number') {
|
||||||
|
submitData.beginPreparationTime = new Date(submitData.beginPreparationTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
}
|
||||||
|
if (typeof submitData.endPreparationTime === 'number') {
|
||||||
|
submitData.endPreparationTime = new Date(submitData.endPreparationTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
}
|
||||||
|
if (typeof submitData.beginTime === 'number') {
|
||||||
|
submitData.beginTime = new Date(submitData.beginTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
}
|
||||||
|
if (typeof submitData.endTime === 'number') {
|
||||||
|
submitData.endTime = new Date(submitData.endTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
}
|
||||||
|
if (typeof submitData.createTime === 'number') {
|
||||||
|
submitData.createTime = new Date(submitData.createTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
}
|
||||||
|
if (submitData.id) {
|
||||||
|
await outsourcingApi.update(submitData)
|
||||||
|
message.success('修改成功')
|
||||||
|
} else {
|
||||||
|
await outsourcingApi.create(submitData)
|
||||||
|
message.success('新增成功')
|
||||||
|
}
|
||||||
|
modalVisible.value = false
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
function handleDelete(row: Outsourcing) {
|
||||||
|
dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定要删除该记录吗?',
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
await outsourcingApi.delete([row.id!])
|
||||||
|
message.success('删除成功')
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
function handleBatchDelete() {
|
||||||
|
dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: `确定要删除选中的 ${selectedIds.value.length} 条记录吗?`,
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
await outsourcingApi.delete(selectedIds.value)
|
||||||
|
message.success('删除成功')
|
||||||
|
selectedIds.value = []
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出
|
||||||
|
async function handleExport() {
|
||||||
|
try {
|
||||||
|
const params: Record<string, any> = {}
|
||||||
|
if (selectedIds.value.length > 0) params.ids = selectedIds.value
|
||||||
|
if (searchForm.id != null) params.id = searchForm.id
|
||||||
|
if (searchForm.name) params.name = searchForm.name
|
||||||
|
if (searchForm.businessType != null) params.businessType = searchForm.businessType
|
||||||
|
const blob = await outsourcingApi.export(params)
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = '委外记录表数据.xlsx'
|
||||||
|
link.click()
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载导入模板
|
||||||
|
async function handleDownloadTemplate() {
|
||||||
|
try {
|
||||||
|
const blob = await outsourcingApi.downloadTemplate()
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = '委外记录表导入模板.xlsx'
|
||||||
|
link.click()
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导入上传
|
||||||
|
async function handleImportUpload({ file }: UploadCustomRequestOptions) {
|
||||||
|
if (!file.file) return
|
||||||
|
try {
|
||||||
|
const result = await outsourcingApi.importData(file.file)
|
||||||
|
if (result.fail > 0) {
|
||||||
|
dialog.warning({
|
||||||
|
title: '导入结果',
|
||||||
|
content: `成功: ${result.success} 条,失败: ${result.fail} 条\n错误信息: ${(result.errors || []).join('\n') || '无'}`,
|
||||||
|
positiveText: '确定'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
message.success(`导入成功,共 ${result.success} 条数据`)
|
||||||
|
importModalVisible.value = false
|
||||||
|
}
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载字典选项
|
||||||
|
async function loadDictOptions() {
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
|
loadDictOptions()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.search-form {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-toolbar {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -8,16 +8,37 @@ export interface Device{
|
|||||||
deviceTypeId:number,
|
deviceTypeId:number,
|
||||||
deviceType:string,
|
deviceType:string,
|
||||||
sectionId:number,
|
sectionId:number,
|
||||||
|
sectionName?:string,
|
||||||
spec:string,
|
spec:string,
|
||||||
manufacturer:string,
|
manufacturer:string,
|
||||||
manufactureDate:string,
|
manufactureDate:string,
|
||||||
workshopId:number,
|
workshopId:number,
|
||||||
remark:string,
|
remark:string,
|
||||||
status:number,
|
status:number,
|
||||||
|
deviceFlag?:string,
|
||||||
|
/** 新代 SynFactory equipID */
|
||||||
|
synEquipId?: number | null,
|
||||||
createTime:string,
|
createTime:string,
|
||||||
updateTime:string
|
updateTime:string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DeviceRealtimeVO {
|
||||||
|
deviceId?: number
|
||||||
|
deviceCode?: string
|
||||||
|
displayName: string
|
||||||
|
synEquipId: number
|
||||||
|
bound: boolean
|
||||||
|
connected?: number | null
|
||||||
|
cncStatus?: number | null
|
||||||
|
cncStatusText?: string
|
||||||
|
partCount?: number | null
|
||||||
|
mainProgram?: string | null
|
||||||
|
alarmCount?: number
|
||||||
|
alarmMessages?: string[]
|
||||||
|
dataTimestamp?: number
|
||||||
|
sectionName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export const deviceApi = {
|
export const deviceApi = {
|
||||||
|
|
||||||
@ -25,6 +46,7 @@ export const deviceApi = {
|
|||||||
page:number,
|
page:number,
|
||||||
pageSize:number,
|
pageSize:number,
|
||||||
deviceCode:any
|
deviceCode:any
|
||||||
|
deviceFlag?: string
|
||||||
}) {
|
}) {
|
||||||
return request({
|
return request({
|
||||||
url:"/biz/device/page",
|
url:"/biz/device/page",
|
||||||
@ -33,6 +55,21 @@ export const deviceApi = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** 设备机联网看板:MES名称 + 新代即时状态 */
|
||||||
|
realtimeBoard(equipIds?: string | number[]) {
|
||||||
|
const params: Record<string, any> = {}
|
||||||
|
if (Array.isArray(equipIds) && equipIds.length) {
|
||||||
|
params.equipIds = equipIds.join(',')
|
||||||
|
} else if (typeof equipIds === 'string' && equipIds) {
|
||||||
|
params.equipIds = equipIds
|
||||||
|
}
|
||||||
|
return request<DeviceRealtimeVO[]>({
|
||||||
|
url: '/biz/device/realtime-board',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
create(data:Device) {
|
create(data:Device) {
|
||||||
return request({
|
return request({
|
||||||
url:"/biz/device",
|
url:"/biz/device",
|
||||||
|
|||||||
@ -64,6 +64,8 @@ export interface ProcessPlanItemVO {
|
|||||||
|
|
||||||
planId?: number
|
planId?: number
|
||||||
|
|
||||||
|
workOrderCode?: string
|
||||||
|
|
||||||
processCode?: string
|
processCode?: string
|
||||||
|
|
||||||
operNumber?: number
|
operNumber?: number
|
||||||
@ -442,5 +444,60 @@ export const orderProcessPlanApi = {
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** 委外第一步:获取可选工序 */
|
||||||
|
getOutsourcingOptions(processPlanId: number) {
|
||||||
|
return request<OutsourcingOptions>({
|
||||||
|
url: `/biz/orderProcessPlan/outsourcing/options/${processPlanId}`,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 委外第二步:创建委外单 */
|
||||||
|
createOutsourcing(data: OutsourcingCreate) {
|
||||||
|
return request<number>({
|
||||||
|
url: '/biz/orderProcessPlan/outsourcing',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 委外可选工序项 */
|
||||||
|
export interface OutsourcingProcessOption {
|
||||||
|
planId?: number
|
||||||
|
workOrderCode?: string
|
||||||
|
processCode?: string
|
||||||
|
operNumber?: number
|
||||||
|
processName?: string
|
||||||
|
quantity?: number
|
||||||
|
outsource?: number
|
||||||
|
planStartTime?: string
|
||||||
|
planFinishTime?: string
|
||||||
|
current?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 委外可选工序(第一步) */
|
||||||
|
export interface OutsourcingOptions {
|
||||||
|
orderItemId?: number
|
||||||
|
mainCode?: string
|
||||||
|
materialCode?: string
|
||||||
|
materialName?: string
|
||||||
|
orderCode?: string
|
||||||
|
quantity?: number
|
||||||
|
currentProcessPlanId?: number
|
||||||
|
processList?: OutsourcingProcessOption[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建委外单(第二步) */
|
||||||
|
export interface OutsourcingCreate {
|
||||||
|
processPlanId: number
|
||||||
|
supplierId?: number
|
||||||
|
supplierName: string
|
||||||
|
quantity: number
|
||||||
|
unitPrice?: number
|
||||||
|
planStartTime?: string
|
||||||
|
planEndTime?: string
|
||||||
|
remark?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
105
src/api/outsourcing.ts
Normal file
105
src/api/outsourcing.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { request } from '@/utils/request'
|
||||||
|
|
||||||
|
/** 委外工单 */
|
||||||
|
export interface Outsourcing {
|
||||||
|
id?: number
|
||||||
|
outsourceCode?: string
|
||||||
|
name?: string
|
||||||
|
code?: string
|
||||||
|
businessType?: number
|
||||||
|
businessId?: number
|
||||||
|
businessCode?: string
|
||||||
|
orderItemId?: number
|
||||||
|
quantity?: number
|
||||||
|
supplierId?: number
|
||||||
|
supplierName?: string
|
||||||
|
unitPrice?: number | string
|
||||||
|
totalAmount?: number | string
|
||||||
|
beginPreparationTime?: string
|
||||||
|
endPreparationTime?: string
|
||||||
|
beginTime?: string
|
||||||
|
endTime?: string
|
||||||
|
remark?: string
|
||||||
|
createTime?: string
|
||||||
|
createBy?: string
|
||||||
|
overdue?: number
|
||||||
|
/** 0草稿 1执行中 2已完成 3已关闭 */
|
||||||
|
status?: number
|
||||||
|
/** 1正常 2紧急 3低 */
|
||||||
|
priority?: number
|
||||||
|
issuedQuantity?: number | string
|
||||||
|
receivedQuantity?: number | string
|
||||||
|
qualifiedQuantity?: number | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OUTSOURCE_STATUS_MAP: Record<number, { label: string; type: 'default' | 'info' | 'success' | 'warning' | 'error' }> = {
|
||||||
|
0: { label: '草稿', type: 'default' },
|
||||||
|
1: { label: '执行中', type: 'info' },
|
||||||
|
2: { label: '已完成', type: 'success' },
|
||||||
|
3: { label: '已关闭', type: 'default' },
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OUTSOURCE_PRIORITY_MAP: Record<number, { label: string; type: 'default' | 'info' | 'success' | 'warning' | 'error' }> = {
|
||||||
|
1: { label: '正常', type: 'info' },
|
||||||
|
2: { label: '紧急', type: 'error' },
|
||||||
|
3: { label: '低', type: 'default' },
|
||||||
|
}
|
||||||
|
|
||||||
|
export const outsourcingApi = {
|
||||||
|
page(params: {
|
||||||
|
page: number
|
||||||
|
pageSize: number
|
||||||
|
id?: number
|
||||||
|
name?: string
|
||||||
|
businessType?: number
|
||||||
|
outsourceCode?: string
|
||||||
|
supplierName?: string
|
||||||
|
status?: number
|
||||||
|
}) {
|
||||||
|
return request<{ list: Outsourcing[]; total: number; page: number; pageSize: number }>({
|
||||||
|
url: '/biz/outsourcing/page',
|
||||||
|
method: 'get',
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
detail(id: number | string) {
|
||||||
|
return request<Outsourcing>({ url: `/biz/outsourcing/${id}`, method: 'get' })
|
||||||
|
},
|
||||||
|
|
||||||
|
create(data: Outsourcing) {
|
||||||
|
return request({ url: '/biz/outsourcing', method: 'post', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
update(data: Outsourcing) {
|
||||||
|
return request({ url: '/biz/outsourcing', method: 'put', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
delete(ids: number[]) {
|
||||||
|
return request({ url: `/biz/outsourcing/${ids.join(',')}`, method: 'delete' })
|
||||||
|
},
|
||||||
|
|
||||||
|
export(params?: { ids?: number[]; id?: number; name?: string; businessType?: number }) {
|
||||||
|
const p: Record<string, unknown> = {}
|
||||||
|
if (params?.ids?.length) p.ids = params.ids.join(',')
|
||||||
|
if (params?.id != null) p.id = params.id
|
||||||
|
if (params?.name) p.name = params.name
|
||||||
|
if (params?.businessType != null) p.businessType = params.businessType
|
||||||
|
return request({ url: '/biz/outsourcing/export', method: 'get', params: p, responseType: 'blob' })
|
||||||
|
},
|
||||||
|
|
||||||
|
importData(file: File) {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
return request<{ success: number; fail: number; errors: string[] }>({
|
||||||
|
url: '/biz/outsourcing/import',
|
||||||
|
method: 'post',
|
||||||
|
data: formData,
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
downloadTemplate() {
|
||||||
|
return request({ url: '/biz/outsourcing/template', method: 'get', responseType: 'blob' })
|
||||||
|
},
|
||||||
|
}
|
||||||
@ -82,5 +82,12 @@ export const qcResultDetailApi = {
|
|||||||
// 下载导入模板
|
// 下载导入模板
|
||||||
downloadTemplate() {
|
downloadTemplate() {
|
||||||
return request({ url: `/biz/qcResultDetail/template`, method: 'get', responseType: 'blob' })
|
return request({ url: `/biz/qcResultDetail/template`, method: 'get', responseType: 'blob' })
|
||||||
|
},
|
||||||
|
|
||||||
|
selectlistByqcId(qcId:any) {
|
||||||
|
return request({
|
||||||
|
url:`/biz/qcResultDetail/selectlistByqcId/${qcId}`,
|
||||||
|
method:'get'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,8 +73,10 @@ export const qualityTestingApi = {
|
|||||||
return request({ url: '/mes/qc/partial', method: 'post', data })
|
return request({ url: '/mes/qc/partial', method: 'post', data })
|
||||||
},
|
},
|
||||||
//质检撤回
|
//质检撤回
|
||||||
cancel(id: number) {
|
cancel(data:{assingId:number,remark:string}) {
|
||||||
return request({ url: `/mes/dispatch/revoke/${id}`, method: 'put' })
|
console.log(data);
|
||||||
|
|
||||||
|
return request({ url: `/mes/dispatch/revoke`, method: 'put',data })
|
||||||
},
|
},
|
||||||
|
|
||||||
// 导出
|
// 导出
|
||||||
|
|||||||
@ -22,12 +22,14 @@ export interface SubmitLog {
|
|||||||
|
|
||||||
assingStatus?: number
|
assingStatus?: number
|
||||||
|
|
||||||
|
remark?: string
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 派工工单汇报记录 API
|
// 派工工单汇报记录 API
|
||||||
export const submitLogApi = {
|
export const submitLogApi = {
|
||||||
// 分页查询
|
// 分页查询
|
||||||
page(params: { page: number; pageSize: number; assingStatus?: number }) {
|
page(params: { page: number; pageSize: number; submitStatus?: number }) {
|
||||||
return request({ url: '/mes/report/page', method: 'get', params })
|
return request({ url: '/mes/report/page', method: 'get', params })
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -56,7 +58,7 @@ export const submitLogApi = {
|
|||||||
const p: Record<string, any> = {}
|
const p: Record<string, any> = {}
|
||||||
if (params?.ids?.length) p.ids = params.ids.join(',')
|
if (params?.ids?.length) p.ids = params.ids.join(',')
|
||||||
if (params?.id !== undefined && params?.id !== null) p.id = params.id
|
if (params?.id !== undefined && params?.id !== null) p.id = params.id
|
||||||
return request({ url: `/biz/submitLog/export`, method: 'get', params: p, responseType: 'blob' })
|
return request({ url: `/mes/report/export`, method: 'get', params: p, responseType: 'blob' })
|
||||||
},
|
},
|
||||||
|
|
||||||
// 导入
|
// 导入
|
||||||
|
|||||||
84
src/components/SubmitDetailPage.vue
Normal file
84
src/components/SubmitDetailPage.vue
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<template>
|
||||||
|
<n-form ref="formRef" :model="formDetailData" label-placement="left" label-width="80">
|
||||||
|
<n-form-item label="批次号/SN码" path="traceCode" label-width="100" v-show="showTraceCode">
|
||||||
|
<n-input v-model:value="formDetailData.traceCode" disabled />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="判定数量" path="qty" label-width="100" >
|
||||||
|
<n-input-number v-model:value="formDetailData.qty"
|
||||||
|
placeholder="请输入判定数量" :min="0"
|
||||||
|
:disabled="disabled"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="备注" label-width="100">
|
||||||
|
<n-input
|
||||||
|
v-model:value="formDetailData.remark"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="输入备注原因"
|
||||||
|
:disabled="disabled"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
|
||||||
|
</n-form>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import {qcResultDetailApi,QcResultDetail} from "@/api/qcResultDetail.ts";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:formDetailData'])
|
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
id:Number,
|
||||||
|
formDetailData:Object as () => QcResultDetail,
|
||||||
|
disabled:Boolean,
|
||||||
|
showTraceCode:Boolean
|
||||||
|
})
|
||||||
|
|
||||||
|
let formDetailData = ref({...props.formDetailData})
|
||||||
|
let isInitLoading = true
|
||||||
|
|
||||||
|
|
||||||
|
watch(
|
||||||
|
()=> props.formDetailData,
|
||||||
|
(newVal)=>{
|
||||||
|
formDetailData.value = {...newVal}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
formDetailData,
|
||||||
|
(newVal)=>{
|
||||||
|
// if (isInitLoading) {
|
||||||
|
// isInitLoading = false
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
emit('update:formDetailData',{...newVal})
|
||||||
|
},
|
||||||
|
{ deep: true, flush: 'post' }
|
||||||
|
)
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
if(!props.id) return
|
||||||
|
qcResultDetailApi.detail(props.id).then((res:any)=>{
|
||||||
|
formDetailData.value = { ...formDetailData.value, ...res }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// watch(() => props.id, (val: Number)=>{
|
||||||
|
// loadData();
|
||||||
|
// })
|
||||||
|
|
||||||
|
watch(() => props.id, (val: number)=>{
|
||||||
|
loadData();
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(()=>{
|
||||||
|
loadData()
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
169
src/components/SumbitDetailCard.vue
Normal file
169
src/components/SumbitDetailCard.vue
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
<template>
|
||||||
|
<n-tabs type="card" animated>
|
||||||
|
<n-tab-pane name="合格" tab="合格" v-if="showOkTab">
|
||||||
|
<SubmitDetailPage
|
||||||
|
key="ok"
|
||||||
|
:disabled="false"
|
||||||
|
:formDetailData="formDetailOKData"
|
||||||
|
:showTraceCode="showTraceCode"
|
||||||
|
@update:formDetailData= "handleFormDetailOKData"
|
||||||
|
/>
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="报废" tab="报废" v-if="showScrapTab">
|
||||||
|
<SubmitDetailPage
|
||||||
|
key="scrap"
|
||||||
|
:disabled="false"
|
||||||
|
:formDetailData="formDetailScrapData"
|
||||||
|
:showTraceCode="showTraceCode"
|
||||||
|
@update:formDetailData= "handleFormDetailScrapData"
|
||||||
|
/>
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="返工" tab="返工" v-if="showReworkTab">
|
||||||
|
<SubmitDetailPage
|
||||||
|
key="rework"
|
||||||
|
:disabled="false"
|
||||||
|
:formDetailData="formDetailReworkData"
|
||||||
|
:showTraceCode="showTraceCode"
|
||||||
|
@update:formDetailData= "handleFormDetailReworkData"
|
||||||
|
/>
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="让步接收" tab="让步接收" v-if="showConnessionTab">
|
||||||
|
|
||||||
|
<SubmitDetailPage
|
||||||
|
key="conession"
|
||||||
|
:disabled="false"
|
||||||
|
:formDetailData="formDetailConcessionData"
|
||||||
|
:showTraceCode="showTraceCode"
|
||||||
|
@update:formDetailData= "handleFormDetailConcessionData"
|
||||||
|
/>
|
||||||
|
</n-tab-pane>
|
||||||
|
</n-tabs>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, watch, onMounted } from 'vue'
|
||||||
|
import {
|
||||||
|
NButton, NSpace, useMessage
|
||||||
|
} from 'naive-ui'
|
||||||
|
|
||||||
|
import {useRoute} from "vue-router";
|
||||||
|
|
||||||
|
import {qcResultDetailApi, type QcResultDetail} from "@/api/qcResultDetail.ts";
|
||||||
|
import { qualityTestingApi} from '@/api/qualityTesting'
|
||||||
|
import router from '@/router';
|
||||||
|
import SubmitDetailPage from '@/components/SubmitDetailPage.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
showOkTab:Boolean,
|
||||||
|
showScrapTab:Boolean,
|
||||||
|
showReworkTab:Boolean,
|
||||||
|
showConnessionTab:Boolean,
|
||||||
|
showTraceCode:Boolean,
|
||||||
|
qcId:Number
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
|
||||||
|
const traceCode = ref(String(route.params.traceCode))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const emit = defineEmits(['updateSubmitDetail','qcSumitDetail'])
|
||||||
|
|
||||||
|
const submitDetail = reactive<any>({})
|
||||||
|
|
||||||
|
const defaultFormDetailData:QcResultDetail = {
|
||||||
|
qcId: undefined,
|
||||||
|
resultType: undefined,
|
||||||
|
qty: 0,
|
||||||
|
traceCode: undefined,
|
||||||
|
defectCode: undefined,
|
||||||
|
handleAction: undefined,
|
||||||
|
actionStatus: undefined,
|
||||||
|
remark: '',
|
||||||
|
title:undefined,
|
||||||
|
images:[],
|
||||||
|
attachments:[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const formDetailOKData = reactive<QcResultDetail>({ ...defaultFormDetailData,traceCode:traceCode.value, resultType: 1,handleAction:'下一道工序',actionStatus:1 })
|
||||||
|
|
||||||
|
const formDetailScrapData = reactive<QcResultDetail>({ ...defaultFormDetailData,traceCode:traceCode.value,resultType: 2,handleAction:'报废次品',actionStatus:1 })
|
||||||
|
const formDetailReworkData = reactive<QcResultDetail>({ ...defaultFormDetailData,traceCode:traceCode.value,resultType: 3,handleAction:'返工',actionStatus:1 })
|
||||||
|
const formDetailConcessionData = reactive<QcResultDetail>({ ...defaultFormDetailData,traceCode:traceCode.value,resultType: 4,handleAction:'让步接收',actionStatus:1 })
|
||||||
|
|
||||||
|
function handleFormDetailOKData(formDetailData:QcResultDetail) {
|
||||||
|
console.log('===OK表单触发更新===', formDetailData)
|
||||||
|
Object.assign(formDetailOKData, formDetailData)
|
||||||
|
submitDetail.okData = formDetailOKData
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFormDetailScrapData(formDetailData:QcResultDetail) {
|
||||||
|
console.log('===报废表单触发更新===', formDetailData)
|
||||||
|
Object.assign(formDetailScrapData, formDetailData)
|
||||||
|
submitDetail.scrapData = formDetailScrapData
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFormDetailReworkData(formDetailData:QcResultDetail) {
|
||||||
|
Object.assign(formDetailReworkData, formDetailData)
|
||||||
|
submitDetail.reworkData = formDetailReworkData
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFormDetailConcessionData(formDetailData:QcResultDetail){
|
||||||
|
Object.assign(formDetailConcessionData, formDetailData)
|
||||||
|
submitDetail.concessionData = formDetailConcessionData
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
watch(
|
||||||
|
submitDetail,
|
||||||
|
(newVal)=>{
|
||||||
|
|
||||||
|
emit('updateSubmitDetail',{...newVal})
|
||||||
|
},{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
watch(
|
||||||
|
submitDetail,
|
||||||
|
(newVal)=>{
|
||||||
|
emit('qcSumitDetail',{...newVal})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
function handleReset() {
|
||||||
|
Object.assign(formDetailOKData, { ...defaultFormDetailData, resultType: 1, handleAction: '下一道工序', actionStatus: 1 })
|
||||||
|
Object.assign(formDetailScrapData, { ...defaultFormDetailData, resultType: 2, handleAction: '报废次品', actionStatus: 1 })
|
||||||
|
Object.assign(formDetailReworkData, { ...defaultFormDetailData, resultType: 3, handleAction: '返工', actionStatus: 1 })
|
||||||
|
Object.assign(formDetailConcessionData, { ...defaultFormDetailData, resultType: 4, handleAction: '让步接收', actionStatus: 1 })
|
||||||
|
|
||||||
|
message.info("表单已重置")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
if(props.qcId == -1) return
|
||||||
|
const resultList = await qcResultDetailApi.selectlistByqcId(props.qcId);
|
||||||
|
Object.assign(formDetailOKData,{...resultList.formDetailOKData})
|
||||||
|
Object.assign(formDetailScrapData,{...resultList.formDetailScrapData})
|
||||||
|
Object.assign(formDetailReworkData, { ...resultList.formDetailReworkData})
|
||||||
|
Object.assign(formDetailConcessionData, { ...resultList.formDetailConsecctionData })
|
||||||
|
|
||||||
|
submitDetail.okData = resultList.formDetailOKData ?? { ...defaultFormDetailData, resultType: 1, handleAction: '下一道工序', actionStatus: 1 }
|
||||||
|
submitDetail.scrapData = resultList.formDetailScrapData ?? { ...defaultFormDetailData, resultType: 2, handleAction: '报废次品', actionStatus: 1 }
|
||||||
|
submitDetail.reworkData = resultList.formDetailReworkData ?? { ...defaultFormDetailData, resultType: 3, handleAction: '返工', actionStatus: 1 }
|
||||||
|
submitDetail.concessionData = resultList.formDetailConsecctionData ?? { ...defaultFormDetailData, resultType: 4, handleAction: '让步接收', actionStatus: 1 }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(()=>{
|
||||||
|
loadData()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
@ -148,6 +148,24 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: () => import('@/views/biz/orderProcessPlan/index.vue'),
|
component: () => import('@/views/biz/orderProcessPlan/index.vue'),
|
||||||
meta: { title: '工序计划', icon: 'ListOutline' }
|
meta: { title: '工序计划', icon: 'ListOutline' }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'biz/orderProcessPlan/board',
|
||||||
|
name: 'orderProcessPlanBoard',
|
||||||
|
component: () => import('@/views/biz/orderProcessPlan/board.vue'),
|
||||||
|
meta: { title: '工序计划看板', icon: 'ListOutline', activeMenu: '/biz/orderProcessPlan' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'biz/device',
|
||||||
|
name: 'device',
|
||||||
|
component: () => import('@/views/biz/device/index.vue'),
|
||||||
|
meta: { title: '设备管理', icon: 'HardwareChipOutline' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'biz/device/board',
|
||||||
|
name: 'deviceRealtimeBoard',
|
||||||
|
component: () => import('@/views/biz/device/board.vue'),
|
||||||
|
meta: { title: '设备状态看板', icon: 'GridOutline', activeMenu: '/biz/device' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'biz/user',
|
path: 'biz/user',
|
||||||
name: 'user',
|
name: 'user',
|
||||||
@ -264,6 +282,12 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: () => import('@/views/biz/flowingAround/index.vue'),
|
component: () => import('@/views/biz/flowingAround/index.vue'),
|
||||||
meta: { title: '流转记录表', icon: 'ListOutline' }
|
meta: { title: '流转记录表', icon: 'ListOutline' }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'biz/outsourcing',
|
||||||
|
name: 'outsourcing',
|
||||||
|
component: () => import('@/views/biz/outsourcing/index.vue'),
|
||||||
|
meta: { title: '工序委外', icon: 'ListOutline' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// 开发工具
|
// 开发工具
|
||||||
path: 'tool/gen',
|
path: 'tool/gen',
|
||||||
|
|||||||
438
src/views/biz/device/board.vue
Normal file
438
src/views/biz/device/board.vue
Normal file
@ -0,0 +1,438 @@
|
|||||||
|
<template>
|
||||||
|
<div class="page-container">
|
||||||
|
<n-card>
|
||||||
|
<div class="table-toolbar">
|
||||||
|
<n-space align="center">
|
||||||
|
<n-button type="primary" :loading="loading" @click="loadBoard">
|
||||||
|
<template #icon><n-icon><RefreshOutline /></n-icon></template>
|
||||||
|
刷新
|
||||||
|
</n-button>
|
||||||
|
<n-switch v-model:value="autoRefresh" @update:value="onAutoRefreshChange">
|
||||||
|
<template #checked>自动刷新 15s</template>
|
||||||
|
<template #unchecked>手动刷新</template>
|
||||||
|
</n-switch>
|
||||||
|
</n-space>
|
||||||
|
<n-button quaternary @click="router.push('/biz/device')">
|
||||||
|
返回设备管理
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-row">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-num">{{ list.length }}</span>
|
||||||
|
<span class="stat-label">设备总数</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item running">
|
||||||
|
<span class="stat-dot running" />
|
||||||
|
<span class="stat-num">{{ countByStatus(1) }}</span>
|
||||||
|
<span class="stat-label">加工中</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item idle">
|
||||||
|
<span class="stat-dot idle" />
|
||||||
|
<span class="stat-num">{{ countByStatus(0) }}</span>
|
||||||
|
<span class="stat-label">闲置</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item alarm">
|
||||||
|
<span class="stat-dot alarm" />
|
||||||
|
<span class="stat-num">{{ countByStatus(2) }}</span>
|
||||||
|
<span class="stat-label">警报</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item offline">
|
||||||
|
<span class="stat-dot offline" />
|
||||||
|
<span class="stat-num">{{ offlineCount }}</span>
|
||||||
|
<span class="stat-label">离线</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<n-spin :show="loading">
|
||||||
|
<n-empty v-if="!loading && list.length === 0" description="暂无设备状态数据" />
|
||||||
|
<div v-else class="card-grid">
|
||||||
|
<div
|
||||||
|
v-for="item in list"
|
||||||
|
:key="item.synEquipId"
|
||||||
|
class="device-card"
|
||||||
|
:class="cardClass(item)"
|
||||||
|
>
|
||||||
|
<div class="card-head">
|
||||||
|
<div class="head-left">
|
||||||
|
<div class="status-lamp" :class="lampClass(item)" />
|
||||||
|
<div class="name-block">
|
||||||
|
<div class="name">{{ item.displayName }}</div>
|
||||||
|
<div class="meta">
|
||||||
|
<span v-if="item.deviceCode">{{ item.deviceCode }}</span>
|
||||||
|
<span v-if="item.deviceCode && item.sectionName"> · </span>
|
||||||
|
<span v-if="item.sectionName">{{ item.sectionName }}</span>
|
||||||
|
<span v-if="!item.deviceCode && !item.sectionName">设备监控</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<n-tag :type="statusTagType(item)" size="small">
|
||||||
|
{{ item.cncStatusText || '未知' }}
|
||||||
|
</n-tag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="metric-grid">
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">当前程序</div>
|
||||||
|
<div class="metric-value program">{{ item.mainProgram || '—' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">累计产量</div>
|
||||||
|
<div class="metric-value">{{ item.partCount ?? '—' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">通讯状态</div>
|
||||||
|
<div class="metric-value">
|
||||||
|
<span class="link-status" :class="item.connected === 1 ? 'on' : 'off'">
|
||||||
|
{{ item.connected === 1 ? '在线' : item.connected === 0 ? '离线' : '—' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">运行状态</div>
|
||||||
|
<div class="metric-value">{{ item.cncStatusText || '—' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="item.alarmCount" class="alarm-box">
|
||||||
|
<n-icon size="14"><WarningOutline /></n-icon>
|
||||||
|
<span>{{ (item.alarmMessages || []).slice(0, 2).join(';') || `当前警报 ${item.alarmCount} 条` }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-spin>
|
||||||
|
</n-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useMessage } from 'naive-ui'
|
||||||
|
import { RefreshOutline, WarningOutline } from '@vicons/ionicons5'
|
||||||
|
import { deviceApi, type DeviceRealtimeVO } from '@/api/device'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const list = ref<DeviceRealtimeVO[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const autoRefresh = ref(true)
|
||||||
|
let timer: ReturnType<typeof setInterval> | null = null
|
||||||
|
|
||||||
|
const offlineCount = computed(() => list.value.filter((i) => i.connected === 0).length)
|
||||||
|
|
||||||
|
function countByStatus(status: number) {
|
||||||
|
return list.value.filter((i) => i.connected !== 0 && i.cncStatus === status).length
|
||||||
|
}
|
||||||
|
|
||||||
|
function cardClass(item: DeviceRealtimeVO) {
|
||||||
|
if (item.connected === 0) return 'is-offline'
|
||||||
|
if (item.cncStatus === 2) return 'is-alarm'
|
||||||
|
if (item.cncStatus === 1) return 'is-running'
|
||||||
|
if (item.cncStatus === 0) return 'is-idle'
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function lampClass(item: DeviceRealtimeVO) {
|
||||||
|
if (item.connected === 0) return 'offline'
|
||||||
|
if (item.cncStatus === 2) return 'alarm'
|
||||||
|
if (item.cncStatus === 1) return 'running'
|
||||||
|
if (item.cncStatus === 0) return 'idle'
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusTagType(item: DeviceRealtimeVO) {
|
||||||
|
if (item.connected === 0) return 'default'
|
||||||
|
if (item.cncStatus === 2) return 'error'
|
||||||
|
if (item.cncStatus === 1) return 'success'
|
||||||
|
if (item.cncStatus === 0) return 'info'
|
||||||
|
return 'warning'
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadBoard() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
list.value = ((await deviceApi.realtimeBoard()) || []).filter((i) => i.bound)
|
||||||
|
} catch (e: any) {
|
||||||
|
message.error(e?.message || '加载设备状态失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearTimer() {
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer)
|
||||||
|
timer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAutoRefreshChange(val: boolean) {
|
||||||
|
clearTimer()
|
||||||
|
if (val) {
|
||||||
|
timer = setInterval(loadBoard, 15000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadBoard()
|
||||||
|
onAutoRefreshChange(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearTimer()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.table-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
min-width: 100px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item.running { background: #e8f8ef; border-color: #b7ebc6; }
|
||||||
|
.stat-item.idle { background: #eef5ff; border-color: #b3d4ff; }
|
||||||
|
.stat-item.alarm { background: #ffecec; border-color: #ffc2c8; }
|
||||||
|
.stat-item.offline { background: #f5f5f5; border-color: #e5e7eb; }
|
||||||
|
|
||||||
|
.stat-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
right: 12px;
|
||||||
|
}
|
||||||
|
.stat-dot.running { background: #18a058; box-shadow: 0 0 0 3px rgba(24, 160, 88, 0.2); }
|
||||||
|
.stat-dot.idle { background: #2080f0; }
|
||||||
|
.stat-dot.alarm { background: #d03050; }
|
||||||
|
.stat-dot.offline { background: #9aa0a6; }
|
||||||
|
|
||||||
|
.stat-num {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2329;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #646a73;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-card {
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-left: 3px solid #d0d3d9;
|
||||||
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-card:hover {
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-card.is-running { border-left-color: #18a058; }
|
||||||
|
.device-card.is-idle { border-left-color: #2080f0; }
|
||||||
|
.device-card.is-alarm { border-left-color: #d03050; }
|
||||||
|
.device-card.is-offline {
|
||||||
|
border-left-color: #c2c2c2;
|
||||||
|
opacity: 0.92;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-head {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.head-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-lamp {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-top: 5px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: #c2c2c2;
|
||||||
|
}
|
||||||
|
.status-lamp.running {
|
||||||
|
background: #18a058;
|
||||||
|
box-shadow: 0 0 0 3px rgba(24, 160, 88, 0.22);
|
||||||
|
animation: pulse 1.6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.status-lamp.idle { background: #2080f0; }
|
||||||
|
.status-lamp.alarm {
|
||||||
|
background: #d03050;
|
||||||
|
box-shadow: 0 0 0 3px rgba(208, 48, 80, 0.22);
|
||||||
|
animation: pulse 1s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.status-lamp.offline { background: #9aa0a6; }
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.55; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2329;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta {
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #8a8f98;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #f7f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #8a8f98;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-value {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2329;
|
||||||
|
word-break: break-all;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-value.program {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-status.on { color: #18a058; }
|
||||||
|
.link-status.off { color: #8a8f98; }
|
||||||
|
|
||||||
|
.alarm-box {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #fff1f0;
|
||||||
|
color: #d03050;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 暗色主题:自定义区域不吃 Naive 组件变量,单独提亮 */
|
||||||
|
body.dark-theme .stat-item {
|
||||||
|
background: #27272a !important;
|
||||||
|
border-color: #3f3f46 !important;
|
||||||
|
}
|
||||||
|
body.dark-theme .stat-item.running {
|
||||||
|
background: rgba(24, 160, 88, 0.18) !important;
|
||||||
|
border-color: rgba(52, 211, 153, 0.4) !important;
|
||||||
|
}
|
||||||
|
body.dark-theme .stat-item.idle {
|
||||||
|
background: rgba(32, 128, 240, 0.18) !important;
|
||||||
|
border-color: rgba(96, 165, 250, 0.4) !important;
|
||||||
|
}
|
||||||
|
body.dark-theme .stat-item.alarm {
|
||||||
|
background: rgba(208, 48, 80, 0.18) !important;
|
||||||
|
border-color: rgba(251, 113, 133, 0.4) !important;
|
||||||
|
}
|
||||||
|
body.dark-theme .stat-item.offline {
|
||||||
|
background: #27272a !important;
|
||||||
|
border-color: #3f3f46 !important;
|
||||||
|
}
|
||||||
|
body.dark-theme .stat-num {
|
||||||
|
color: #f4f4f5 !important;
|
||||||
|
}
|
||||||
|
body.dark-theme .stat-label {
|
||||||
|
color: #a1a1aa !important;
|
||||||
|
}
|
||||||
|
body.dark-theme .device-card {
|
||||||
|
background: #18181c !important;
|
||||||
|
border-color: #3f3f46 !important;
|
||||||
|
}
|
||||||
|
body.dark-theme .device-card.is-offline {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
body.dark-theme .name {
|
||||||
|
color: #f4f4f5 !important;
|
||||||
|
}
|
||||||
|
body.dark-theme .meta {
|
||||||
|
color: #a1a1aa !important;
|
||||||
|
}
|
||||||
|
body.dark-theme .metric {
|
||||||
|
background: #27272a !important;
|
||||||
|
}
|
||||||
|
body.dark-theme .metric-label {
|
||||||
|
color: #a1a1aa !important;
|
||||||
|
}
|
||||||
|
body.dark-theme .metric-value {
|
||||||
|
color: #f4f4f5 !important;
|
||||||
|
}
|
||||||
|
body.dark-theme .link-status.on {
|
||||||
|
color: #34d399 !important;
|
||||||
|
}
|
||||||
|
body.dark-theme .link-status.off {
|
||||||
|
color: #a1a1aa !important;
|
||||||
|
}
|
||||||
|
body.dark-theme .alarm-box {
|
||||||
|
background: rgba(208, 48, 80, 0.2) !important;
|
||||||
|
color: #fb7185 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -29,6 +29,9 @@
|
|||||||
<template #icon><n-icon><AddOutline /></n-icon></template>
|
<template #icon><n-icon><AddOutline /></n-icon></template>
|
||||||
新增设备
|
新增设备
|
||||||
</n-button>
|
</n-button>
|
||||||
|
<n-button @click="goRealtimeBoard">
|
||||||
|
设备状态看板
|
||||||
|
</n-button>
|
||||||
<!-- <n-button @click="importModalVisible = true">
|
<!-- <n-button @click="importModalVisible = true">
|
||||||
<template #icon><n-icon><CloudUploadOutline /></n-icon></template>
|
<template #icon><n-icon><CloudUploadOutline /></n-icon></template>
|
||||||
导入
|
导入
|
||||||
@ -64,14 +67,15 @@
|
|||||||
<n-form-item label="设备名称" path="deviceName">
|
<n-form-item label="设备名称" path="deviceName">
|
||||||
<n-input v-model:value="formData.deviceName" placeholder="请输入设备名称" />
|
<n-input v-model:value="formData.deviceName" placeholder="请输入设备名称" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="设备类型" path="deviceTypeId">
|
<n-form-item label="新代设备ID" path="synEquipId">
|
||||||
<n-select
|
<n-input-number
|
||||||
v-model:value="formData.deviceTypeId"
|
v-model:value="formData.synEquipId"
|
||||||
:options="deviceTypeList"
|
placeholder="SynFactory equipID,如 1、2、3"
|
||||||
placeholder="请选择设备类型"
|
:min="1"
|
||||||
clearable
|
:show-button="false"
|
||||||
style="width: 200px"
|
style="width: 100%"
|
||||||
/>
|
clearable
|
||||||
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="所属工段" path="sectionId">
|
<n-form-item label="所属工段" path="sectionId">
|
||||||
<n-select
|
<n-select
|
||||||
@ -82,8 +86,14 @@
|
|||||||
style="width: 200px"
|
style="width: 200px"
|
||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="设备规格型号" path="spec">
|
<n-form-item label="设备类型" path="deviceTypeId">
|
||||||
<n-input v-model:value="formData.spec" placeholder="请输入设备规格型号" />
|
<n-select
|
||||||
|
v-model:value="formData.deviceTypeId"
|
||||||
|
:options="deviceTypeList"
|
||||||
|
placeholder="请选择设备类型"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="生产厂家" path="manufacturer">
|
<n-form-item label="生产厂家" path="manufacturer">
|
||||||
<n-input v-model:value="formData.manufacturer" placeholder="请输入生产厂家" />
|
<n-input v-model:value="formData.manufacturer" placeholder="请输入生产厂家" />
|
||||||
@ -144,17 +154,18 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, h, onMounted } from 'vue'
|
import { ref, reactive, h, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import { NButton, NSpace,NTag, NIcon, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
|
import { NButton, NSpace,NTag, NIcon, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
|
||||||
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline } from '@vicons/ionicons5'
|
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline } from '@vicons/ionicons5'
|
||||||
import { deviceApi, type Device } from '@/api/device'
|
import { deviceApi, type Device } from '@/api/device'
|
||||||
|
|
||||||
import { stationApi, type Station } from '@/api/station'
|
|
||||||
import { dictDataApi } from '@/api/org'
|
import { dictDataApi } from '@/api/org'
|
||||||
import {sectionApi, type Section} from '@/api/section'
|
import {sectionApi, type Section} from '@/api/section'
|
||||||
|
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
let sectionList = reactive<{label:string,value:any}[]>([])
|
let sectionList = reactive<{label:string,value:any}[]>([])
|
||||||
|
|
||||||
@ -193,7 +204,9 @@ const defaultFormData: Device = {
|
|||||||
manufactureDate: undefined,
|
manufactureDate: undefined,
|
||||||
workshopId: '',
|
workshopId: '',
|
||||||
remark: '',
|
remark: '',
|
||||||
status:''
|
status:0,
|
||||||
|
deviceFlag:'mes_equipment',
|
||||||
|
synEquipId: null as number | null,
|
||||||
}
|
}
|
||||||
const formData = reactive<Device>({ ...defaultFormData })
|
const formData = reactive<Device>({ ...defaultFormData })
|
||||||
|
|
||||||
@ -201,6 +214,8 @@ const formData = reactive<Device>({ ...defaultFormData })
|
|||||||
const deviceTypeList = ref<{ label: string; value: any ;class:any}[]>([])
|
const deviceTypeList = ref<{ label: string; value: any ;class:any}[]>([])
|
||||||
const statusList = ref<{ label: string; value: any ;class:any}[]>([])
|
const statusList = ref<{ label: string; value: any ;class:any}[]>([])
|
||||||
|
|
||||||
|
const deviceFlagList = ref<{ label: string; value: any ;class:any}[]>([])
|
||||||
|
|
||||||
// 表单校验规则
|
// 表单校验规则
|
||||||
const formRules = {
|
const formRules = {
|
||||||
}
|
}
|
||||||
@ -210,6 +225,9 @@ const columns: DataTableColumns<Device> = [
|
|||||||
{ type: 'selection' },
|
{ type: 'selection' },
|
||||||
{ title: '设备编码', key: 'deviceCode' },
|
{ title: '设备编码', key: 'deviceCode' },
|
||||||
{ title: '设备名称', key: 'deviceName' },
|
{ title: '设备名称', key: 'deviceName' },
|
||||||
|
{ title: '新代ID', key: 'synEquipId', width: 90,
|
||||||
|
render: (row) => row.synEquipId ?? '-'
|
||||||
|
},
|
||||||
{ title: '设备类型', key: 'deviceType' },
|
{ title: '设备类型', key: 'deviceType' },
|
||||||
{ title: '所属工段', key: 'sectionName' },
|
{ title: '所属工段', key: 'sectionName' },
|
||||||
{ title: '设备型号', key: 'spec' },
|
{ title: '设备型号', key: 'spec' },
|
||||||
@ -248,7 +266,8 @@ async function loadData() {
|
|||||||
const res = await deviceApi.page({
|
const res = await deviceApi.page({
|
||||||
page: pagination.page,
|
page: pagination.page,
|
||||||
pageSize: pagination.pageSize,
|
pageSize: pagination.pageSize,
|
||||||
deviceCode:searchForm.deviceCode
|
deviceCode:searchForm.deviceCode,
|
||||||
|
deviceFlag:"mes_equipment"
|
||||||
})
|
})
|
||||||
tableData.value = res.list
|
tableData.value = res.list
|
||||||
pagination.itemCount = res.total
|
pagination.itemCount = res.total
|
||||||
@ -294,15 +313,14 @@ function handleAdd() {
|
|||||||
modalVisible.value = true
|
modalVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goRealtimeBoard() {
|
||||||
|
router.push('/biz/device/board')
|
||||||
|
}
|
||||||
|
|
||||||
// 编辑
|
// 编辑
|
||||||
function handleEdit(row: Device) {
|
function handleEdit(row: Device) {
|
||||||
modalTitle.value = '编辑设备表'
|
modalTitle.value = '编辑设备表'
|
||||||
console.log(row);
|
|
||||||
|
|
||||||
Object.assign(formData, row)
|
Object.assign(formData, row)
|
||||||
if (formData.manufactureDate && typeof formData.manufactureDate === 'string') {
|
|
||||||
formData.manufactureDate = new Date(formData.manufactureDate.replace(' ', 'T')).getTime()
|
|
||||||
}
|
|
||||||
modalVisible.value = true
|
modalVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -432,6 +450,10 @@ async function loadDictOptions() {
|
|||||||
const data = await dictDataApi.listByType("sys_status")
|
const data = await dictDataApi.listByType("sys_status")
|
||||||
statusList.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
statusList.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||||
}catch {}
|
}catch {}
|
||||||
|
try {
|
||||||
|
const data = await dictDataApi.listByType("mes_device_flag")
|
||||||
|
deviceFlagList.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||||
|
}catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,10 @@
|
|||||||
<n-input v-model:value="pauseform.processName" disabled />
|
<n-input v-model:value="pauseform.processName" disabled />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="报检数量" path="qcNum">
|
<n-form-item label="报检数量" path="qcNum">
|
||||||
<n-input v-model:value="pauseform.qcNum" disabled />
|
<n-input v-model:value="pauseform.qcNum"
|
||||||
|
:disabled = "qcNumDisabled"
|
||||||
|
@input="handleQcNumChange"
|
||||||
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
</template>
|
</template>
|
||||||
@ -17,12 +20,14 @@
|
|||||||
import { ref, reactive, h, defineProps, onMounted, watch} from 'vue';
|
import { ref, reactive, h, defineProps, onMounted, watch} from 'vue';
|
||||||
import { info } from '@/api/production'
|
import { info } from '@/api/production'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
id: number;
|
id: number;
|
||||||
num: number
|
num: number;
|
||||||
|
qcNumDisabled:boolean
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits(['updateWidth'])
|
const emit = defineEmits(['updateWidth','updateQcNum'])
|
||||||
|
|
||||||
|
|
||||||
let pauseform = reactive<any>({
|
let pauseform = reactive<any>({
|
||||||
id:'',
|
id:'',
|
||||||
@ -38,6 +43,10 @@ async function loadData(){
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleQcNumChange(){
|
||||||
|
emit('updateQcNum', pauseform.qcNum)
|
||||||
|
}
|
||||||
|
|
||||||
watch(() => props.id, (val: number)=>{
|
watch(() => props.id, (val: number)=>{
|
||||||
loadData();
|
loadData();
|
||||||
})
|
})
|
||||||
|
|||||||
@ -95,7 +95,16 @@
|
|||||||
</n-modal>
|
</n-modal>
|
||||||
|
|
||||||
<n-modal v-model:show="modalCheckVisible" preset="card" :title="modalCheckTitle" :style="parentWidth" >
|
<n-modal v-model:show="modalCheckVisible" preset="card" :title="modalCheckTitle" :style="parentWidth" >
|
||||||
<AssingWorkVue v-if="modalCheckData?.businessCode === 'mes_assing_work'" :id="modalCheckData.businessId" :num="modalCheckData.quantity" @updateWidth="handleUpdateWidth"/>
|
<AssingWorkVue v-if="modalCheckData?.businessCode === 'mes_assing_work'"
|
||||||
|
:id="modalCheckData.businessId"
|
||||||
|
:num="modalCheckData.quantity"
|
||||||
|
:qcNumDisabled ="true"
|
||||||
|
@updateWidth="handleUpdateWidth"/>
|
||||||
|
<SubmitDetailPage
|
||||||
|
v-if="modalCheckData?.businessCode === 'mes_quality_testing'"
|
||||||
|
:id="modalCheckData.businessId"
|
||||||
|
:disabled="true"
|
||||||
|
/>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
|
|
||||||
<!-- 导入弹窗 -->
|
<!-- 导入弹窗 -->
|
||||||
@ -103,12 +112,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, h, onMounted } from 'vue'
|
import { ref, reactive, h, onMounted, computed } from 'vue'
|
||||||
import { NButton, NSpace, NIcon, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
|
import { NButton, NSpace, NIcon, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
|
||||||
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, EyeOutline, DownloadOutline } from '@vicons/ionicons5'
|
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, EyeOutline, DownloadOutline } from '@vicons/ionicons5'
|
||||||
import { flowingAroundApi, type FlowingAround } from '@/api/flowingAround'
|
import { flowingAroundApi, type FlowingAround } from '@/api/flowingAround'
|
||||||
import { dictDataApi } from '@/api/org'
|
import { dictDataApi } from '@/api/org'
|
||||||
import AssingWorkVue from '@/views/biz/flowingAround/assingWork.vue'
|
import AssingWorkVue from '@/views/biz/flowingAround/assingWork.vue'
|
||||||
|
import SubmitDetailPage from '@/components/SubmitDetailPage.vue'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
@ -226,7 +236,7 @@ async function loadData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let parentWidth = ref('width: 40%;')
|
let parentWidth = ref('width: 20%;')
|
||||||
|
|
||||||
|
|
||||||
function handleUpdateWidth(width:string){
|
function handleUpdateWidth(width:string){
|
||||||
@ -273,6 +283,8 @@ const modalCheckData = ref<FlowingAround>()
|
|||||||
const modalCheckTitle = ref('')
|
const modalCheckTitle = ref('')
|
||||||
//查看提交数据
|
//查看提交数据
|
||||||
function handleView(row: FlowingAround){
|
function handleView(row: FlowingAround){
|
||||||
|
console.log(row);
|
||||||
|
|
||||||
modalCheckTitle.value = '提交详情'
|
modalCheckTitle.value = '提交详情'
|
||||||
modalCheckData.value = row
|
modalCheckData.value = row
|
||||||
modalCheckVisible.value = true
|
modalCheckVisible.value = true
|
||||||
@ -409,11 +421,39 @@ async function handleImportUpload({ file }: UploadCustomRequestOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const flowingAroundTypeOptions = ref<{ label: string; value: any }[]>([])
|
const flowingAroundTypeOptions = ref<{ label: string; value: any }[]>([])
|
||||||
const confirmResultOptions = ref<{ label: string; value: any }[]>([
|
|
||||||
{label: "待确认", value: "待确认"},
|
const confirmResultOptions = computed<any[]>(() => {
|
||||||
{label: "同意", value: "同意"},
|
const baseOptions: any[] = [
|
||||||
{label: "拒绝", value: "拒绝"},
|
{label: "待确认", value: "待确认"},
|
||||||
])
|
{label: "同意", value: "同意"},
|
||||||
|
]
|
||||||
|
|
||||||
|
//当 formData.businessCode !== 'mes_quality_testing' 时,才追加“拒绝”选项
|
||||||
|
console.log(formData.businessCode)
|
||||||
|
if (formData.businessCode !== 'mes_quality_testing') {
|
||||||
|
baseOptions.push( {label: "拒绝", value: "拒绝"},)
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseOptions
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// const directionOptions = computed<any[]>(() => {
|
||||||
|
// const baseOptions: any[] = [
|
||||||
|
// { label: t('account.进账'), value: '进账' },
|
||||||
|
// { label: t('account.出账'), value: '出账' },
|
||||||
|
// ]
|
||||||
|
|
||||||
|
// // 当 accountType === 'currency' 时,才追加“冻结”选项
|
||||||
|
// // if (formRegulationData.accountType === 'currency') {
|
||||||
|
// // baseOptions.push({ label: t('account.冻结'), value: '冻结' })
|
||||||
|
// // baseOptions.push({ label: t('account.解冻'), value: '解冻' })
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// return baseOptions
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 加载字典选项
|
// 加载字典选项
|
||||||
async function loadDictOptions() {
|
async function loadDictOptions() {
|
||||||
|
|||||||
@ -80,7 +80,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-for="order in tableData" :key="orderRowKey(order)" class="order-block">
|
<div v-for="order in tableData" :key="orderRowKey(order)" class="order-block">
|
||||||
<div class="order-row" :class="{ expanded: isExpanded(order) }">
|
<div class="order-row" :class="{ expanded: isExpanded(order), 'derived-expanded': isDerivedExpanded(order) }">
|
||||||
<div class="col-check">
|
<div class="col-check">
|
||||||
<n-checkbox
|
<n-checkbox
|
||||||
:checked="selectedOrderIds.includes(order.orderItemId!)"
|
:checked="selectedOrderIds.includes(order.orderItemId!)"
|
||||||
@ -92,11 +92,12 @@
|
|||||||
quaternary
|
quaternary
|
||||||
circle
|
circle
|
||||||
size="tiny"
|
size="tiny"
|
||||||
@click="toggleExpand(order)"
|
title="展开返工单 / 拆分工单"
|
||||||
|
@click="toggleDerivedExpand(order)"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon>
|
<n-icon>
|
||||||
<RemoveOutline v-if="isExpanded(order)" />
|
<RemoveOutline v-if="isDerivedExpanded(order)" />
|
||||||
<AddOutline v-else />
|
<AddOutline v-else />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</template>
|
</template>
|
||||||
@ -105,7 +106,7 @@
|
|||||||
<div class="col-product">
|
<div class="col-product">
|
||||||
<div class="product-name">{{ order.materialName || '-' }}</div>
|
<div class="product-name">{{ order.materialName || '-' }}</div>
|
||||||
<div class="product-code">
|
<div class="product-code">
|
||||||
<span>{{ order.orderCode || order.mainCode || '-' }}</span>
|
<span>{{ formatWorkOrderCodes(order) }}</span>
|
||||||
<n-tag v-if="isOverdue(order)" size="tiny" type="error" :bordered="false">逾期</n-tag>
|
<n-tag v-if="isOverdue(order)" size="tiny" type="error" :bordered="false">逾期</n-tag>
|
||||||
<n-tag v-if="hasOutsource(order)" size="tiny" type="warning" :bordered="false">委外</n-tag>
|
<n-tag v-if="hasOutsource(order)" size="tiny" type="warning" :bordered="false">委外</n-tag>
|
||||||
</div>
|
</div>
|
||||||
@ -116,8 +117,12 @@
|
|||||||
<n-tag v-if="order.proWorkshop" size="tiny" type="info" :bordered="false">{{ order.proWorkshop }}</n-tag>
|
<n-tag v-if="order.proWorkshop" size="tiny" type="info" :bordered="false">{{ order.proWorkshop }}</n-tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-kit">-</div>
|
<div class="col-kit">-</div>
|
||||||
<div class="col-flow">
|
<div class="col-flow" :class="{ 'flow-expanded': isExpanded(order) }">
|
||||||
<ProcessFlowStepper :list="order.processList" />
|
<ProcessFlowStepper
|
||||||
|
:list="order.processList"
|
||||||
|
clickable
|
||||||
|
@click="toggleExpand(order)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-plan">
|
<div class="col-plan">
|
||||||
<div>{{ formatShortTime(order.beginTime) }}</div>
|
<div>{{ formatShortTime(order.beginTime) }}</div>
|
||||||
@ -141,6 +146,43 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="isDerivedExpanded(order)" class="order-derived-expand">
|
||||||
|
<div class="derived-section">
|
||||||
|
<div class="derived-section-head">
|
||||||
|
<span class="derived-section-title">返工单</span>
|
||||||
|
<n-button text type="primary" size="tiny" @click="onMoreSelect('createRework', order)">创建返工单</n-button>
|
||||||
|
</div>
|
||||||
|
<div v-if="!getDerivedOrders(order).rework.length" class="derived-empty">暂无返工单</div>
|
||||||
|
<div v-else class="derived-list">
|
||||||
|
<div
|
||||||
|
v-for="item in getDerivedOrders(order).rework"
|
||||||
|
:key="item.id"
|
||||||
|
class="derived-item"
|
||||||
|
>
|
||||||
|
<span class="derived-code">{{ item.code }}</span>
|
||||||
|
<span class="derived-meta">{{ item.quantity ?? '-' }} · {{ item.statusLabel }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="derived-section">
|
||||||
|
<div class="derived-section-head">
|
||||||
|
<span class="derived-section-title">拆分工单</span>
|
||||||
|
<n-button text type="primary" size="tiny" @click="onMoreSelect('splitWorkOrder', order)">拆分工单</n-button>
|
||||||
|
</div>
|
||||||
|
<div v-if="!getDerivedOrders(order).split.length" class="derived-empty">暂无拆分工单</div>
|
||||||
|
<div v-else class="derived-list">
|
||||||
|
<div
|
||||||
|
v-for="item in getDerivedOrders(order).split"
|
||||||
|
:key="item.id"
|
||||||
|
class="derived-item"
|
||||||
|
>
|
||||||
|
<span class="derived-code">{{ item.code }}</span>
|
||||||
|
<span class="derived-meta">{{ item.quantity ?? '-' }} · {{ item.statusLabel }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="isExpanded(order)" class="order-expand">
|
<div v-if="isExpanded(order)" class="order-expand">
|
||||||
<div v-if="!(order.processList?.length)" class="expand-empty">暂无工序明细</div>
|
<div v-if="!(order.processList?.length)" class="expand-empty">暂无工序明细</div>
|
||||||
<div v-else class="card-flow-scroll">
|
<div v-else class="card-flow-scroll">
|
||||||
@ -151,8 +193,10 @@
|
|||||||
:is-last="idx === (order.processList?.length ?? 0) - 1"
|
:is-last="idx === (order.processList?.length ?? 0) - 1"
|
||||||
:can-edit="hasPermission('biz:orderItem:edit')"
|
:can-edit="hasPermission('biz:orderItem:edit')"
|
||||||
:can-assign="hasPermission('biz:orderItem:assign')"
|
:can-assign="hasPermission('biz:orderItem:assign')"
|
||||||
|
:can-outsource="hasPermission('biz:orderProcessPlan:add')"
|
||||||
@edit="(p) => handleEdit(p, order)"
|
@edit="(p) => handleEdit(p, order)"
|
||||||
@assign="(p) => disphand(p, order)"
|
@assign="(p) => disphand(p, order)"
|
||||||
|
@outsource="(p) => openOutsource(order, p)"
|
||||||
@view-model="(p) => openPlmModel(order, p)"
|
@view-model="(p) => openPlmModel(order, p)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -184,7 +228,7 @@
|
|||||||
<template v-if="detailData">
|
<template v-if="detailData">
|
||||||
<n-descriptions :column="3" bordered size="small" label-placement="left">
|
<n-descriptions :column="3" bordered size="small" label-placement="left">
|
||||||
<n-descriptions-item label="生产令号">{{ detailData.mainCode || '-' }}</n-descriptions-item>
|
<n-descriptions-item label="生产令号">{{ detailData.mainCode || '-' }}</n-descriptions-item>
|
||||||
<n-descriptions-item label="订单编码">{{ detailData.orderCode || '-' }}</n-descriptions-item>
|
<n-descriptions-item label="工单号">{{ formatWorkOrderCodes(detailData) }}</n-descriptions-item>
|
||||||
<n-descriptions-item label="物料">{{ detailData.materialName || '-' }}</n-descriptions-item>
|
<n-descriptions-item label="物料">{{ detailData.materialName || '-' }}</n-descriptions-item>
|
||||||
<n-descriptions-item label="数量">{{ detailData.quantity ?? '-' }}</n-descriptions-item>
|
<n-descriptions-item label="数量">{{ detailData.quantity ?? '-' }}</n-descriptions-item>
|
||||||
<n-descriptions-item label="工艺路线">{{ detailData.routeCode || '-' }}</n-descriptions-item>
|
<n-descriptions-item label="工艺路线">{{ detailData.routeCode || '-' }}</n-descriptions-item>
|
||||||
@ -195,14 +239,16 @@
|
|||||||
</n-descriptions>
|
</n-descriptions>
|
||||||
<div class="detail-cards">
|
<div class="detail-cards">
|
||||||
<ProcessCard
|
<ProcessCard
|
||||||
v-for="(proc, idx) in sortedProcessList(detailData.processList)"
|
v-for="(proc, idx) in (detailData.processList)"
|
||||||
:key="proc.planId ?? idx"
|
:key="proc.planId ?? idx"
|
||||||
:process="proc"
|
:process="proc"
|
||||||
:is-last="idx === (detailData.processList?.length ?? 0) - 1"
|
:is-last="idx === (detailData.processList?.length ?? 0) - 1"
|
||||||
:can-edit="hasPermission('biz:orderItem:edit')"
|
:can-edit="hasPermission('biz:orderItem:edit')"
|
||||||
:can-assign="hasPermission('biz:orderItem:assign')"
|
:can-assign="hasPermission('biz:orderItem:assign')"
|
||||||
|
:can-outsource="hasPermission('biz:orderProcessPlan:add')"
|
||||||
@edit="(p) => handleEdit(p, detailData!)"
|
@edit="(p) => handleEdit(p, detailData!)"
|
||||||
@assign="(p) => disphand(p, detailData!)"
|
@assign="(p) => disphand(p, detailData!)"
|
||||||
|
@outsource="(p) => openOutsource(detailData!, p)"
|
||||||
@view-model="(p) => openPlmModel(detailData!, p)"
|
@view-model="(p) => openPlmModel(detailData!, p)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -316,6 +362,148 @@
|
|||||||
</template>
|
</template>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
|
|
||||||
|
<!-- 工序委外 -->
|
||||||
|
<n-modal
|
||||||
|
v-model:show="outsourceModalVisible"
|
||||||
|
preset="card"
|
||||||
|
:title="outsourceStep === 1 ? '创建工序委外 - 选择工序' : '创建工序委外 - 填写信息'"
|
||||||
|
style="width: 720px"
|
||||||
|
:mask-closable="false"
|
||||||
|
@after-leave="resetOutsourceForm"
|
||||||
|
>
|
||||||
|
<n-spin :show="outsourceOptionsLoading">
|
||||||
|
<template v-if="outsourceStep === 1 && outsourceOptions">
|
||||||
|
<n-descriptions :column="2" bordered size="small" label-placement="left" style="margin-bottom: 16px">
|
||||||
|
<n-descriptions-item label="生产令号">{{ outsourceOptions.mainCode || '-' }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="订单编码">{{ outsourceOptions.orderCode || '-' }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="物料编码">{{ outsourceOptions.materialCode || '-' }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="物料名称">{{ outsourceOptions.materialName || '-' }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="生产数量">{{ outsourceOptions.quantity ?? '-' }}</n-descriptions-item>
|
||||||
|
</n-descriptions>
|
||||||
|
<n-radio-group v-model:value="outsourceSelectedPlanId" style="width: 100%">
|
||||||
|
<n-space vertical :size="8" style="width: 100%">
|
||||||
|
<div
|
||||||
|
v-for="proc in outsourceOptions.processList"
|
||||||
|
:key="proc.planId"
|
||||||
|
class="outsource-process-item"
|
||||||
|
:class="{ active: outsourceSelectedPlanId === proc.planId }"
|
||||||
|
@click="outsourceSelectedPlanId = proc.planId!"
|
||||||
|
>
|
||||||
|
<n-radio :value="proc.planId" />
|
||||||
|
<div class="outsource-process-body">
|
||||||
|
<div class="outsource-process-title">
|
||||||
|
<span>{{ proc.processName || '-' }}</span>
|
||||||
|
<n-tag v-if="proc.current" size="tiny" type="info" :bordered="false">当前</n-tag>
|
||||||
|
<n-tag v-if="proc.outsource === 1" size="tiny" type="warning" :bordered="false">已委外</n-tag>
|
||||||
|
</div>
|
||||||
|
<div class="outsource-process-meta">
|
||||||
|
<span>工单号:{{ proc.workOrderCode || '-' }}</span>
|
||||||
|
<span>工序号:{{ proc.operNumber ?? '-' }}</span>
|
||||||
|
<span>数量:{{ proc.quantity ?? '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-space>
|
||||||
|
</n-radio-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="outsourceStep === 2">
|
||||||
|
<n-card size="small" style="margin-bottom: 16px">
|
||||||
|
<n-grid :cols="2" :x-gap="12">
|
||||||
|
<n-gi>
|
||||||
|
<span class="pgClass">委外工序:</span>
|
||||||
|
<span>{{ selectedOutsourceProcess?.processName || '-' }}</span>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<span class="pgClass">工单号:</span>
|
||||||
|
<span>{{ selectedOutsourceProcess?.workOrderCode || '-' }}</span>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<span class="pgClass">物料:</span>
|
||||||
|
<span>{{ outsourceOptions?.materialName || '-' }}</span>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<span class="pgClass">工序数量:</span>
|
||||||
|
<span>{{ selectedOutsourceProcess?.quantity ?? '-' }}</span>
|
||||||
|
</n-gi>
|
||||||
|
</n-grid>
|
||||||
|
</n-card>
|
||||||
|
<n-form
|
||||||
|
ref="outsourceFormRef"
|
||||||
|
:model="outsourceForm"
|
||||||
|
:rules="outsourceRules"
|
||||||
|
label-placement="left"
|
||||||
|
label-width="110"
|
||||||
|
>
|
||||||
|
<n-form-item label="供应商" path="supplierName">
|
||||||
|
<n-input v-model:value="outsourceForm.supplierName" placeholder="请输入供应商名称" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="委外数量" path="quantity">
|
||||||
|
<n-input-number
|
||||||
|
v-model:value="outsourceForm.quantity"
|
||||||
|
:min="1"
|
||||||
|
:max="selectedOutsourceProcess?.quantity ?? undefined"
|
||||||
|
:precision="0"
|
||||||
|
placeholder="请输入委外数量"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="单价" path="unitPrice">
|
||||||
|
<n-input-number
|
||||||
|
v-model:value="outsourceForm.unitPrice"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
placeholder="请输入单价(选填)"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="计划开始时间" path="planStartTime">
|
||||||
|
<n-date-picker
|
||||||
|
v-model:value="outsourceForm.planStartTime"
|
||||||
|
type="datetime"
|
||||||
|
clearable
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="计划结束时间" path="planEndTime">
|
||||||
|
<n-date-picker
|
||||||
|
v-model:value="outsourceForm.planEndTime"
|
||||||
|
type="datetime"
|
||||||
|
clearable
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="备注" path="remark">
|
||||||
|
<n-input
|
||||||
|
v-model:value="outsourceForm.remark"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="请输入备注(选填)"
|
||||||
|
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
</template>
|
||||||
|
</n-spin>
|
||||||
|
<template #footer>
|
||||||
|
<n-space justify="end">
|
||||||
|
<n-button @click="outsourceModalVisible = false">取消</n-button>
|
||||||
|
<n-button v-if="outsourceStep === 2" @click="outsourceStep = 1">上一步</n-button>
|
||||||
|
<n-button
|
||||||
|
v-if="outsourceStep === 1"
|
||||||
|
type="primary"
|
||||||
|
:disabled="!outsourceSelectedPlanId"
|
||||||
|
@click="goOutsourceStep2"
|
||||||
|
>下一步</n-button>
|
||||||
|
<n-button
|
||||||
|
v-else
|
||||||
|
type="primary"
|
||||||
|
:loading="outsourceSubmitLoading"
|
||||||
|
@click="submitOutsource"
|
||||||
|
>确认创建</n-button>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
|
||||||
<!-- 导入 -->
|
<!-- 导入 -->
|
||||||
<n-modal v-model:show="importModalVisible" preset="card" title="导入工序计划" style="width: 500px">
|
<n-modal v-model:show="importModalVisible" preset="card" title="导入工序计划" style="width: 500px">
|
||||||
<n-space vertical>
|
<n-space vertical>
|
||||||
@ -338,17 +526,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, onMounted } from 'vue'
|
import { ref, reactive, onMounted, h, computed, type Component } from 'vue'
|
||||||
import {
|
import {
|
||||||
NButton, NSpace, NIcon, NTag, NSpin, NModal, NForm, NFormItem, NGrid, NGi,
|
NButton, NSpace, NIcon, NTag, NSpin, NModal, NForm, NFormItem, NGrid, NGi,
|
||||||
NInput, NDatePicker, NCheckbox, NEmpty, NPagination, NDropdown, NDescriptions,
|
NInput, NInputNumber, NDatePicker, NCheckbox, NEmpty, NPagination, NDropdown, NDescriptions,
|
||||||
NDescriptionsItem, NAlert, NUpload, NText, useMessage, useDialog,
|
NDescriptionsItem, NAlert, NUpload, NText, NCard, NRadioGroup, NRadio, useMessage, useDialog,
|
||||||
type UploadCustomRequestOptions,
|
type UploadCustomRequestOptions, type FormRules,
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import {
|
import {
|
||||||
AddOutline, RemoveOutline, SearchOutline, RefreshOutline, CloudUploadOutline,
|
AddOutline, RemoveOutline, SearchOutline, RefreshOutline, CloudUploadOutline,
|
||||||
DownloadOutline, ChevronDownOutline, CheckmarkCircleOutline, CheckmarkCircle,
|
DownloadOutline, ChevronDownOutline, CheckmarkCircleOutline, CheckmarkCircle,
|
||||||
EllipseOutline, TimeOutline,
|
EllipseOutline, TimeOutline, PrintOutline, SyncOutline, PeopleOutline,
|
||||||
|
GitBranchOutline, CloseCircleOutline, LockClosedOutline, TrashOutline,
|
||||||
} from '@vicons/ionicons5'
|
} from '@vicons/ionicons5'
|
||||||
import {
|
import {
|
||||||
orderProcessPlanApi,
|
orderProcessPlanApi,
|
||||||
@ -357,6 +546,8 @@ import {
|
|||||||
ORDER_STATUS_MAP,
|
ORDER_STATUS_MAP,
|
||||||
type OrderProcessPlanVO,
|
type OrderProcessPlanVO,
|
||||||
type ProcessPlanItemVO,
|
type ProcessPlanItemVO,
|
||||||
|
type OutsourcingOptions,
|
||||||
|
type OutsourcingProcessOption,
|
||||||
} from '@/api/orderProcessPlan'
|
} from '@/api/orderProcessPlan'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import ProcessFlowStepper from './components/ProcessFlowStepper.vue'
|
import ProcessFlowStepper from './components/ProcessFlowStepper.vue'
|
||||||
@ -377,6 +568,7 @@ const searchForm = reactive({
|
|||||||
|
|
||||||
const tableData = ref<OrderProcessPlanVO[]>([])
|
const tableData = ref<OrderProcessPlanVO[]>([])
|
||||||
const expandedIds = ref<Set<number>>(new Set())
|
const expandedIds = ref<Set<number>>(new Set())
|
||||||
|
const derivedExpandedIds = ref<Set<number>>(new Set())
|
||||||
const selectedOrderIds = ref<number[]>([])
|
const selectedOrderIds = ref<number[]>([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const pagination = reactive({ page: 1, pageSize: 10, itemCount: 0 })
|
const pagination = reactive({ page: 1, pageSize: 10, itemCount: 0 })
|
||||||
@ -402,13 +594,38 @@ const dispatchLoading = ref(false)
|
|||||||
const dispatchformRef = ref()
|
const dispatchformRef = ref()
|
||||||
const dispatchform = reactive<any>({
|
const dispatchform = reactive<any>({
|
||||||
id: '', name: '', beginTime: '', endTime: '', quantity: '',
|
id: '', name: '', beginTime: '', endTime: '', quantity: '',
|
||||||
list: [{ sectionId:'',deviceId:'',userId: '', quantity: '' }],
|
list: [{ sectionId:'',deviceId:'',userList: '', quantity: '' }],
|
||||||
})
|
})
|
||||||
const dispatchrules = {}
|
const dispatchrules = {}
|
||||||
|
|
||||||
const importModalVisible = ref(false)
|
const importModalVisible = ref(false)
|
||||||
const plmModelDrawerRef = ref<InstanceType<typeof PlmModelDrawer> | null>(null)
|
const plmModelDrawerRef = ref<InstanceType<typeof PlmModelDrawer> | null>(null)
|
||||||
|
|
||||||
|
const outsourceModalVisible = ref(false)
|
||||||
|
const outsourceOptionsLoading = ref(false)
|
||||||
|
const outsourceSubmitLoading = ref(false)
|
||||||
|
const outsourceStep = ref(1)
|
||||||
|
const outsourceOptions = ref<OutsourcingOptions | null>(null)
|
||||||
|
const outsourceSelectedPlanId = ref<number | null>(null)
|
||||||
|
const outsourceFormRef = ref()
|
||||||
|
const outsourceForm = reactive({
|
||||||
|
supplierName: '',
|
||||||
|
quantity: null as number | null,
|
||||||
|
unitPrice: null as number | null,
|
||||||
|
planStartTime: null as number | null,
|
||||||
|
planEndTime: null as number | null,
|
||||||
|
remark: '',
|
||||||
|
})
|
||||||
|
const outsourceRules: FormRules = {
|
||||||
|
supplierName: [{ required: true, message: '请输入供应商', trigger: 'blur' }],
|
||||||
|
quantity: [{ required: true, type: 'number', message: '请输入委外数量', trigger: ['blur', 'change'] }],
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedOutsourceProcess = computed<OutsourcingProcessOption | null>(() => {
|
||||||
|
if (!outsourceSelectedPlanId.value) return null
|
||||||
|
return outsourceOptions.value?.processList?.find(p => p.planId === outsourceSelectedPlanId.value) ?? null
|
||||||
|
})
|
||||||
|
|
||||||
function openPlmModel(order: OrderProcessPlanVO, process?: ProcessPlanItemVO) {
|
function openPlmModel(order: OrderProcessPlanVO, process?: ProcessPlanItemVO) {
|
||||||
const ctx: PlmModelOpenContext = {
|
const ctx: PlmModelOpenContext = {
|
||||||
orderItemId: order.orderItemId,
|
orderItemId: order.orderItemId,
|
||||||
@ -427,13 +644,66 @@ const batchOptions = [
|
|||||||
{ label: '全部展开', key: 'expandAll' },
|
{ label: '全部展开', key: 'expandAll' },
|
||||||
{ label: '全部收起', key: 'collapseAll' },
|
{ label: '全部收起', key: 'collapseAll' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
function dropdownIcon(icon: Component) {
|
||||||
|
return () => h(NIcon, null, { default: () => h(icon) })
|
||||||
|
}
|
||||||
|
|
||||||
|
function dangerDropdownItem(label: string, key: string, icon: Component) {
|
||||||
|
return {
|
||||||
|
label,
|
||||||
|
key,
|
||||||
|
icon: () => h(NIcon, { color: '#d03050' }, { default: () => h(icon) }),
|
||||||
|
props: { style: { color: '#d03050' } },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const moreOptions = [
|
const moreOptions = [
|
||||||
{ label: '展开工序', key: 'expand' },
|
{
|
||||||
{ label: '收起工序', key: 'collapse' },
|
type: 'group',
|
||||||
|
label: '查看与编辑',
|
||||||
|
key: 'group-view-edit',
|
||||||
|
children: [
|
||||||
|
{ label: '打印', key: 'print', icon: dropdownIcon(PrintOutline) },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'group',
|
||||||
|
label: '派生操作',
|
||||||
|
key: 'group-derived',
|
||||||
|
children: [
|
||||||
|
{ label: '创建返工单', key: 'createRework', icon: dropdownIcon(SyncOutline) },
|
||||||
|
{ label: '创建工序委外', key: 'createOutsource', icon: dropdownIcon(PeopleOutline) },
|
||||||
|
{ label: '拆分工单', key: 'splitWorkOrder', icon: dropdownIcon(GitBranchOutline) },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'group',
|
||||||
|
label: '状态控制',
|
||||||
|
key: 'group-status',
|
||||||
|
children: [
|
||||||
|
dangerDropdownItem('撤回', 'withdraw', CloseCircleOutline),
|
||||||
|
dangerDropdownItem('冻结工单', 'freeze', LockClosedOutline),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'group',
|
||||||
|
label: '危险操作',
|
||||||
|
key: 'group-danger',
|
||||||
|
children: [
|
||||||
|
dangerDropdownItem('删除', 'delete', TrashOutline),
|
||||||
|
],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
function orderRowKey(row: OrderProcessPlanVO) {
|
function orderRowKey(row: OrderProcessPlanVO) {
|
||||||
return row.orderItemId ?? `order-${row.orderCode}`
|
return row.orderItemId ?? `order-${row.mainCode}-${formatWorkOrderCodes(row)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 汇总展示订单下各道工序的主工单号 */
|
||||||
|
function formatWorkOrderCodes(order?: OrderProcessPlanVO | null) {
|
||||||
|
const codes = (order?.processList ?? []).map((p) => p.workOrderCode).filter(Boolean)
|
||||||
|
return codes.length ? codes.join('、') : '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortedProcessList(list?: ProcessPlanItemVO[]) {
|
function sortedProcessList(list?: ProcessPlanItemVO[]) {
|
||||||
@ -444,6 +714,10 @@ function isExpanded(order: OrderProcessPlanVO) {
|
|||||||
return order.orderItemId != null && expandedIds.value.has(order.orderItemId)
|
return order.orderItemId != null && expandedIds.value.has(order.orderItemId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDerivedExpanded(order: OrderProcessPlanVO) {
|
||||||
|
return order.orderItemId != null && derivedExpandedIds.value.has(order.orderItemId)
|
||||||
|
}
|
||||||
|
|
||||||
function toggleExpand(order: OrderProcessPlanVO) {
|
function toggleExpand(order: OrderProcessPlanVO) {
|
||||||
if (order.orderItemId == null) return
|
if (order.orderItemId == null) return
|
||||||
const next = new Set(expandedIds.value)
|
const next = new Set(expandedIds.value)
|
||||||
@ -452,6 +726,26 @@ function toggleExpand(order: OrderProcessPlanVO) {
|
|||||||
expandedIds.value = next
|
expandedIds.value = next
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleDerivedExpand(order: OrderProcessPlanVO) {
|
||||||
|
if (order.orderItemId == null) return
|
||||||
|
const next = new Set(derivedExpandedIds.value)
|
||||||
|
if (next.has(order.orderItemId)) next.delete(order.orderItemId)
|
||||||
|
else next.add(order.orderItemId)
|
||||||
|
derivedExpandedIds.value = next
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 派生工单(返工 / 拆分),待后端字段接入后替换占位逻辑 */
|
||||||
|
function getDerivedOrders(order: OrderProcessPlanVO) {
|
||||||
|
const raw = order as OrderProcessPlanVO & {
|
||||||
|
reworkOrders?: Array<{ id: string | number; code?: string; quantity?: number; statusLabel?: string }>
|
||||||
|
splitOrders?: Array<{ id: string | number; code?: string; quantity?: number; statusLabel?: string }>
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
rework: raw.reworkOrders ?? [],
|
||||||
|
split: raw.splitOrders ?? [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function toggleSelect(id: number, checked: boolean) {
|
function toggleSelect(id: number, checked: boolean) {
|
||||||
if (checked) selectedOrderIds.value = [...selectedOrderIds.value, id]
|
if (checked) selectedOrderIds.value = [...selectedOrderIds.value, id]
|
||||||
else selectedOrderIds.value = selectedOrderIds.value.filter(v => v !== id)
|
else selectedOrderIds.value = selectedOrderIds.value.filter(v => v !== id)
|
||||||
@ -531,8 +825,121 @@ function onBatchSelect(key: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onMoreSelect(key: string, order: OrderProcessPlanVO) {
|
function onMoreSelect(key: string, order: OrderProcessPlanVO) {
|
||||||
if (key === 'expand') toggleExpand(order)
|
if (key === 'createOutsource') {
|
||||||
if (key === 'collapse' && isExpanded(order)) toggleExpand(order)
|
openOutsource(order)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const labels: Record<string, string> = {
|
||||||
|
print: '打印',
|
||||||
|
createRework: '创建返工单',
|
||||||
|
splitWorkOrder: '拆分工单',
|
||||||
|
withdraw: '撤回',
|
||||||
|
freeze: '冻结工单',
|
||||||
|
delete: '删除',
|
||||||
|
}
|
||||||
|
if (labels[key]) message.info(`${labels[key]}功能待后端接口支持`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveDefaultPlanId(order: OrderProcessPlanVO, process?: ProcessPlanItemVO) {
|
||||||
|
if (process?.planId) return process.planId
|
||||||
|
const list = sortedProcessList(order.processList)
|
||||||
|
if (!list.length) return null
|
||||||
|
const current = list.find(p => p.operNumber === order.currentOperNumber)
|
||||||
|
return current?.planId ?? list[0].planId ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetOutsourceForm() {
|
||||||
|
outsourceStep.value = 1
|
||||||
|
outsourceOptions.value = null
|
||||||
|
outsourceSelectedPlanId.value = null
|
||||||
|
outsourceForm.supplierName = ''
|
||||||
|
outsourceForm.quantity = null
|
||||||
|
outsourceForm.unitPrice = null
|
||||||
|
outsourceForm.planStartTime = null
|
||||||
|
outsourceForm.planEndTime = null
|
||||||
|
outsourceForm.remark = ''
|
||||||
|
outsourceFormRef.value?.restoreValidation()
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillOutsourceFormFromProcess(proc?: OutsourcingProcessOption | null) {
|
||||||
|
if (!proc) return
|
||||||
|
outsourceForm.quantity = proc.quantity ?? null
|
||||||
|
outsourceForm.planStartTime = parseDateTime(proc.planStartTime)
|
||||||
|
outsourceForm.planEndTime = parseDateTime(proc.planFinishTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openOutsource(order: OrderProcessPlanVO, process?: ProcessPlanItemVO) {
|
||||||
|
const planId = resolveDefaultPlanId(order, process)
|
||||||
|
if (!planId) {
|
||||||
|
message.warning('当前订单暂无可委外工序')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resetOutsourceForm()
|
||||||
|
outsourceModalVisible.value = true
|
||||||
|
outsourceOptionsLoading.value = true
|
||||||
|
try {
|
||||||
|
const options = await orderProcessPlanApi.getOutsourcingOptions(planId)
|
||||||
|
outsourceOptions.value = options
|
||||||
|
const preferredId = process?.planId ?? options.currentProcessPlanId ?? options.processList?.[0]?.planId ?? null
|
||||||
|
outsourceSelectedPlanId.value = preferredId
|
||||||
|
if (process?.planId) {
|
||||||
|
outsourceStep.value = 2
|
||||||
|
fillOutsourceFormFromProcess(selectedOutsourceProcess.value)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
outsourceModalVisible.value = false
|
||||||
|
} finally {
|
||||||
|
outsourceOptionsLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goOutsourceStep2() {
|
||||||
|
if (!outsourceSelectedPlanId.value) {
|
||||||
|
message.warning('请选择委外工序')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fillOutsourceFormFromProcess(selectedOutsourceProcess.value)
|
||||||
|
outsourceStep.value = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatSubmitDateTime(ts: number | null) {
|
||||||
|
if (!ts) return undefined
|
||||||
|
return new Date(ts).toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitOutsource() {
|
||||||
|
await outsourceFormRef.value?.validate()
|
||||||
|
if (!outsourceSelectedPlanId.value) {
|
||||||
|
message.warning('请选择委外工序')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const maxQty = selectedOutsourceProcess.value?.quantity
|
||||||
|
if (maxQty != null && (outsourceForm.quantity ?? 0) > maxQty) {
|
||||||
|
message.error(`委外数量不能大于工序数量 ${maxQty}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (outsourceForm.planStartTime && outsourceForm.planEndTime
|
||||||
|
&& outsourceForm.planEndTime < outsourceForm.planStartTime) {
|
||||||
|
message.error('计划结束时间不能早于计划开始时间')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
outsourceSubmitLoading.value = true
|
||||||
|
try {
|
||||||
|
await orderProcessPlanApi.createOutsourcing({
|
||||||
|
processPlanId: outsourceSelectedPlanId.value,
|
||||||
|
supplierName: outsourceForm.supplierName.trim(),
|
||||||
|
quantity: outsourceForm.quantity!,
|
||||||
|
unitPrice: outsourceForm.unitPrice ?? undefined,
|
||||||
|
planStartTime: formatSubmitDateTime(outsourceForm.planStartTime),
|
||||||
|
planEndTime: formatSubmitDateTime(outsourceForm.planEndTime),
|
||||||
|
remark: outsourceForm.remark || undefined,
|
||||||
|
})
|
||||||
|
message.success('委外单创建成功')
|
||||||
|
outsourceModalVisible.value = false
|
||||||
|
loadData()
|
||||||
|
} finally {
|
||||||
|
outsourceSubmitLoading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openDetail(order: OrderProcessPlanVO) {
|
async function openDetail(order: OrderProcessPlanVO) {
|
||||||
@ -606,11 +1013,13 @@ function disphand(process: ProcessPlanItemVO, order?: OrderProcessPlanVO) {
|
|||||||
dispatchform.beginTime = entity.beginTime
|
dispatchform.beginTime = entity.beginTime
|
||||||
dispatchform.endTime = entity.endTime
|
dispatchform.endTime = entity.endTime
|
||||||
dispatchform.quantity = entity.quantity
|
dispatchform.quantity = entity.quantity
|
||||||
dispatchform.list = [{ sectionId:'',deviceId:'',userId: '', quantity: '' }]
|
dispatchform.orderItemId = entity.orderItemId
|
||||||
|
dispatchform.sort = entity.sort
|
||||||
|
dispatchform.list = [{ sectionId:'',deviceId:'',userList: '', quantity: '' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
function addpgnum() {
|
function addpgnum() {
|
||||||
dispatchform.list.push({ sectionId:'',deviceId:'',userId: '', quantity: '' })
|
dispatchform.list.push({ sectionId:'',deviceId:'',userList: '', quantity: '' })
|
||||||
}
|
}
|
||||||
|
|
||||||
function deletenum(index: number) {
|
function deletenum(index: number) {
|
||||||
@ -619,13 +1028,17 @@ function deletenum(index: number) {
|
|||||||
|
|
||||||
function dispatchSubmit() {
|
function dispatchSubmit() {
|
||||||
let total = 0
|
let total = 0
|
||||||
dispatchform.list.forEach((n: any) => { total += Number(n.quantity) || 0 })
|
dispatchform.list.forEach((n: any) => {
|
||||||
|
total += Number(n.quantity) || 0
|
||||||
|
})
|
||||||
|
|
||||||
dispatchformRef.value?.validate((v: boolean) => {
|
dispatchformRef.value?.validate((v: boolean) => {
|
||||||
if (v) return
|
if (v) return
|
||||||
if (total !== Number(dispatchform.quantity)) {
|
if (total !== Number(dispatchform.quantity)) {
|
||||||
message.error('派工总数量需等于生产总数', { duration: 4000 })
|
message.error('派工总数量需等于生产总数', { duration: 4000 })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchLoading.value = true
|
dispatchLoading.value = true
|
||||||
orderProcessPlanApi.assignWork(dispatchform).then(() => {
|
orderProcessPlanApi.assignWork(dispatchform).then(() => {
|
||||||
message.success('派工成功')
|
message.success('派工成功')
|
||||||
@ -755,6 +1168,14 @@ onMounted(loadData)
|
|||||||
background: rgba(32, 128, 240, 0.04);
|
background: rgba(32, 128, 240, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.order-row.derived-expanded {
|
||||||
|
background: rgba(24, 160, 88, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-row.expanded.derived-expanded {
|
||||||
|
background: linear-gradient(90deg, rgba(24, 160, 88, 0.04) 0%, rgba(32, 128, 240, 0.04) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
.col-check,
|
.col-check,
|
||||||
.col-expand {
|
.col-expand {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -819,6 +1240,10 @@ onMounted(loadData)
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.col-flow.flow-expanded :deep(.flow-stepper) {
|
||||||
|
background: rgba(32, 128, 240, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
.life-cell {
|
.life-cell {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -840,6 +1265,69 @@ onMounted(loadData)
|
|||||||
border-top: 1px dashed #e8eaed;
|
border-top: 1px dashed #e8eaed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.order-derived-expand {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 16px 12px 68px;
|
||||||
|
background: linear-gradient(180deg, rgba(24, 160, 88, 0.03) 0%, #fff 100%);
|
||||||
|
border-top: 1px dashed #e8eaed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.derived-section {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #eef0f3;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.derived-section-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.derived-section-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.derived-empty {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #8c8c8c;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.derived-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.derived-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
background: #fafbfc;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.derived-code {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.derived-meta {
|
||||||
|
color: #8c8c8c;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.card-flow-scroll,
|
.card-flow-scroll,
|
||||||
.detail-cards {
|
.detail-cards {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -876,4 +1364,44 @@ onMounted(loadData)
|
|||||||
.pgClass {
|
.pgClass {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.outsource-process-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s, background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.outsource-process-item:hover,
|
||||||
|
.outsource-process-item.active {
|
||||||
|
border-color: #2080f0;
|
||||||
|
background: rgba(32, 128, 240, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.outsource-process-body {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.outsource-process-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.outsource-process-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #8c8c8c;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -50,6 +50,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-meta">
|
<div class="card-meta">
|
||||||
|
<div class="meta-line"><span>工单号</span><em>{{ process.workOrderCode || '-' }}</em></div>
|
||||||
<div class="meta-line"><span>人员</span><em>{{ personnel }}</em></div>
|
<div class="meta-line"><span>人员</span><em>{{ personnel }}</em></div>
|
||||||
<div class="meta-line"><span>车间</span><em>{{ process.departmentName || '-' }}</em></div>
|
<div class="meta-line"><span>车间</span><em>{{ process.departmentName || '-' }}</em></div>
|
||||||
<div class="meta-line"><span>工位</span><em>{{ process.workCenterName || '-' }}</em></div>
|
<div class="meta-line"><span>工位</span><em>{{ process.workCenterName || '-' }}</em></div>
|
||||||
@ -74,6 +75,13 @@
|
|||||||
>编辑</n-button>
|
>编辑</n-button>
|
||||||
<n-button size="tiny" type="info" ghost @click="emit('viewModel', process)">模型</n-button>
|
<n-button size="tiny" type="info" ghost @click="emit('viewModel', process)">模型</n-button>
|
||||||
<n-button size="tiny" quaternary disabled>返工</n-button>
|
<n-button size="tiny" quaternary disabled>返工</n-button>
|
||||||
|
<n-button
|
||||||
|
v-if="canOutsource"
|
||||||
|
size="tiny"
|
||||||
|
type="warning"
|
||||||
|
ghost
|
||||||
|
@click="emit('outsource', process)"
|
||||||
|
>委外</n-button>
|
||||||
<n-button
|
<n-button
|
||||||
size="tiny"
|
size="tiny"
|
||||||
:type="process.processStatus === 4 ? 'success' : 'default'"
|
:type="process.processStatus === 4 ? 'success' : 'default'"
|
||||||
@ -99,12 +107,14 @@ const props = defineProps<{
|
|||||||
isLast?: boolean
|
isLast?: boolean
|
||||||
canEdit?: boolean
|
canEdit?: boolean
|
||||||
canAssign?: boolean
|
canAssign?: boolean
|
||||||
|
canOutsource?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
edit: [process: ProcessPlanItemVO]
|
edit: [process: ProcessPlanItemVO]
|
||||||
assign: [process: ProcessPlanItemVO]
|
assign: [process: ProcessPlanItemVO]
|
||||||
viewModel: [process: ProcessPlanItemVO]
|
viewModel: [process: ProcessPlanItemVO]
|
||||||
|
outsource: [process: ProcessPlanItemVO]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const totalQty = computed(() => props.process.quantity ?? 0)
|
const totalQty = computed(() => props.process.quantity ?? 0)
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flow-stepper">
|
<div
|
||||||
|
class="flow-stepper"
|
||||||
|
:class="{ clickable }"
|
||||||
|
@click="onClick"
|
||||||
|
>
|
||||||
<template v-for="(step, idx) in steps" :key="step.key">
|
<template v-for="(step, idx) in steps" :key="step.key">
|
||||||
<div
|
<div
|
||||||
class="flow-step"
|
class="flow-step"
|
||||||
@ -23,8 +27,15 @@ import { NIcon } from 'naive-ui'
|
|||||||
import { CheckmarkOutline } from '@vicons/ionicons5'
|
import { CheckmarkOutline } from '@vicons/ionicons5'
|
||||||
import type { ProcessPlanItemVO } from '@/api/orderProcessPlan'
|
import type { ProcessPlanItemVO } from '@/api/orderProcessPlan'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
list?: ProcessPlanItemVO[]
|
list?: ProcessPlanItemVO[]
|
||||||
|
clickable?: boolean
|
||||||
|
}>(), {
|
||||||
|
clickable: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
click: []
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const steps = computed(() => {
|
const steps = computed(() => {
|
||||||
@ -40,6 +51,10 @@ const steps = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function onClick() {
|
||||||
|
if (props.clickable) emit('click')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -51,6 +66,18 @@ const steps = computed(() => {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flow-stepper.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
margin: -4px -6px;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-stepper.clickable:hover {
|
||||||
|
background: rgba(32, 128, 240, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
.flow-step {
|
.flow-step {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@ -437,7 +437,7 @@ const expandedRowKeys = ref<Array<string | number>>([])
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const selectedIds = ref<number[]>([])
|
const selectedIds = ref<number[]>([])
|
||||||
const orderScrollX = 2090
|
const orderScrollX = 2090
|
||||||
const processScrollX = 2620
|
const processScrollX = 2788
|
||||||
|
|
||||||
function orderRowKey(row: OrderProcessPlanVO) {
|
function orderRowKey(row: OrderProcessPlanVO) {
|
||||||
return row.orderItemId ?? `order-${row.mainCode}-${row.orderCode}`
|
return row.orderItemId ?? `order-${row.mainCode}-${row.orderCode}`
|
||||||
@ -585,7 +585,17 @@ const orderColumns: DataTableColumns<OrderProcessPlanVO> = [
|
|||||||
{ title: '生产令号', key: 'mainCode', width: 140, align: 'center', ellipsis: { tooltip: true } },
|
{ title: '生产令号', key: 'mainCode', width: 140, align: 'center', ellipsis: { tooltip: true } },
|
||||||
{ title: '物料编码', key: 'materialCode', width: 130, align: 'center', ellipsis: { tooltip: true } },
|
{ title: '物料编码', key: 'materialCode', width: 130, align: 'center', ellipsis: { tooltip: true } },
|
||||||
{ title: '物料名称', key: 'materialName', width: 168, ellipsis: { tooltip: true } },
|
{ title: '物料名称', key: 'materialName', width: 168, ellipsis: { tooltip: true } },
|
||||||
{ title: '生产订单编码', key: 'orderCode', width: 150, align: 'center', ellipsis: { tooltip: true } },
|
{
|
||||||
|
title: '工单号',
|
||||||
|
key: 'workOrderCode',
|
||||||
|
width: 168,
|
||||||
|
align: 'center',
|
||||||
|
ellipsis: { tooltip: true },
|
||||||
|
render: (row) => {
|
||||||
|
const codes = (row.processList ?? []).map((p) => p.workOrderCode).filter(Boolean)
|
||||||
|
return codes.length ? codes.join('、') : '-'
|
||||||
|
},
|
||||||
|
},
|
||||||
{ title: '数量', key: 'quantity', width: 72, align: 'center' },
|
{ title: '数量', key: 'quantity', width: 72, align: 'center' },
|
||||||
{ title: '工艺路线', key: 'routeCode', width: 108, align: 'center', ellipsis: { tooltip: true } },
|
{ title: '工艺路线', key: 'routeCode', width: 108, align: 'center', ellipsis: { tooltip: true } },
|
||||||
{ title: '生产车间', key: 'proWorkshop', width: 108, align: 'center', ellipsis: { tooltip: true } },
|
{ title: '生产车间', key: 'proWorkshop', width: 108, align: 'center', ellipsis: { tooltip: true } },
|
||||||
@ -614,6 +624,7 @@ const processColumns: DataTableColumns<ProcessPlanItemVO> = [
|
|||||||
align: 'center',
|
align: 'center',
|
||||||
render: () => h(NTag, { size: 'small', type: 'warning', bordered: false }, { default: () => '工序' }),
|
render: () => h(NTag, { size: 'small', type: 'warning', bordered: false }, { default: () => '工序' }),
|
||||||
},
|
},
|
||||||
|
{ title: '工单号', key: 'workOrderCode', width: 168, align: 'center', ellipsis: { tooltip: true }, render: (row) => row.workOrderCode || '-' },
|
||||||
{ title: '工序号', key: 'operNumber', width: 72, align: 'center' },
|
{ title: '工序号', key: 'operNumber', width: 72, align: 'center' },
|
||||||
{ title: '工序编码', key: 'processCode', width: 96, align: 'center', ellipsis: { tooltip: true } },
|
{ title: '工序编码', key: 'processCode', width: 96, align: 'center', ellipsis: { tooltip: true } },
|
||||||
{ title: '工序名称', key: 'processName', width: 140, ellipsis: { tooltip: true } },
|
{ title: '工序名称', key: 'processName', width: 140, ellipsis: { tooltip: true } },
|
||||||
|
|||||||
@ -53,21 +53,30 @@
|
|||||||
<n-gi :span="10">
|
<n-gi :span="10">
|
||||||
<n-form-item
|
<n-form-item
|
||||||
label="分配人员"
|
label="分配人员"
|
||||||
:path="`${listname}[${index}].userId`"
|
:path="`${listname}[${index}].userList`"
|
||||||
:rule="{
|
:rule="{
|
||||||
required: true,
|
required: true,
|
||||||
message: '请选择员工',
|
message: '请选择员工',
|
||||||
trigger: 'blur'
|
trigger: 'blur',
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
// value 多选为数组,单选是单个值
|
||||||
|
if (!Array.isArray(value) || value.length === 0) {
|
||||||
|
callback(new Error('请选择员工'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<!--multiple-->
|
<!--multiple-->
|
||||||
<n-select
|
<n-select
|
||||||
v-model:value="obj.userId"
|
v-model:value="obj.userList"
|
||||||
filterable
|
filterable
|
||||||
placeholder="请输入用户名称搜索"
|
placeholder="请输入用户名称搜索"
|
||||||
:options="yglist"
|
:options="yglist"
|
||||||
:loading="loadingRef"
|
:loading="loadingRef"
|
||||||
clearable
|
clearable
|
||||||
|
multiple
|
||||||
remote
|
remote
|
||||||
:clear-filter-after-select="false"
|
:clear-filter-after-select="false"
|
||||||
@search="slehand"
|
@search="slehand"
|
||||||
|
|||||||
539
src/views/biz/outsourcing/index.vue
Normal file
539
src/views/biz/outsourcing/index.vue
Normal file
@ -0,0 +1,539 @@
|
|||||||
|
<template>
|
||||||
|
<div class="page-container">
|
||||||
|
<n-card>
|
||||||
|
<div class="table-toolbar">
|
||||||
|
<n-space>
|
||||||
|
<n-button type="primary" @click="handleGoCreate">
|
||||||
|
<template #icon><n-icon><AddOutline /></n-icon></template>
|
||||||
|
新建工序委外
|
||||||
|
</n-button>
|
||||||
|
<n-button disabled title="待后端接口支持">下推</n-button>
|
||||||
|
<n-button type="error" :disabled="selectedIds.length === 0" @click="handleBatchDelete">
|
||||||
|
<template #icon><n-icon><TrashOutline /></n-icon></template>
|
||||||
|
批量删除
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
<n-space>
|
||||||
|
<n-button quaternary circle @click="loadData">
|
||||||
|
<template #icon><n-icon><RefreshOutline /></n-icon></template>
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search-form">
|
||||||
|
<n-form inline :model="searchForm" label-placement="left" size="small">
|
||||||
|
<n-form-item label="委外编号">
|
||||||
|
<n-input v-model:value="searchForm.outsourceCode" placeholder="工单委外编号" clearable style="width: 160px" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="供应商">
|
||||||
|
<n-input v-model:value="searchForm.supplierName" placeholder="委外供应商" clearable style="width: 140px" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="工序">
|
||||||
|
<n-input v-model:value="searchForm.name" placeholder="委外工序" clearable style="width: 140px" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="状态">
|
||||||
|
<n-select
|
||||||
|
v-model:value="searchForm.status"
|
||||||
|
placeholder="当前阶段"
|
||||||
|
clearable
|
||||||
|
style="width: 120px"
|
||||||
|
:options="statusOptions"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item>
|
||||||
|
<n-space>
|
||||||
|
<n-button type="primary" size="small" @click="handleSearch">
|
||||||
|
<template #icon><n-icon><SearchOutline /></n-icon></template>
|
||||||
|
搜索
|
||||||
|
</n-button>
|
||||||
|
<n-button size="small" @click="handleReset">重置</n-button>
|
||||||
|
</n-space>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<n-data-table
|
||||||
|
:columns="columns"
|
||||||
|
:data="tableData"
|
||||||
|
:loading="loading"
|
||||||
|
:pagination="pagination"
|
||||||
|
:row-key="(row) => row.id!"
|
||||||
|
:scroll-x="1520"
|
||||||
|
@update:page="handlePageChange"
|
||||||
|
@update:page-size="handlePageSizeChange"
|
||||||
|
@update:checked-row-keys="handleCheck"
|
||||||
|
/>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<!-- 详情 -->
|
||||||
|
<n-modal v-model:show="detailVisible" preset="card" title="委外工单详情" style="width: 760px">
|
||||||
|
<n-descriptions v-if="detailData" :column="2" bordered size="small" label-placement="left">
|
||||||
|
<n-descriptions-item label="工单委外编号">{{ detailData.outsourceCode || '-' }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="当前阶段">{{ statusLabel(detailData.status) }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="委外工序">{{ detailData.name || '-' }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="工序工单号">{{ detailData.businessCode || '-' }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="委外供应商">{{ detailData.supplierName || '-' }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="优先级">{{ priorityLabel(detailData.priority) }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="委外数量">{{ formatQty(detailData.quantity) }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="委外单价">{{ formatMoney(detailData.unitPrice) }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="委外总金额">{{ formatMoney(detailData.totalAmount) }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="已发料数量">{{ formatDecimal(detailData.issuedQuantity) }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="已收货数量">{{ formatDecimal(detailData.receivedQuantity) }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="合格数量">{{ formatDecimal(detailData.qualifiedQuantity) }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="预计开始">{{ detailData.beginPreparationTime || '-' }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="预计结束">{{ detailData.endPreparationTime || '-' }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="备注" :span="2">{{ detailData.remark || '-' }}</n-descriptions-item>
|
||||||
|
</n-descriptions>
|
||||||
|
</n-modal>
|
||||||
|
|
||||||
|
<!-- 编辑 -->
|
||||||
|
<n-modal v-model:show="modalVisible" preset="card" :title="modalTitle" style="width: 720px">
|
||||||
|
<n-form ref="formRef" :model="formData" :rules="formRules" label-placement="left" label-width="110px">
|
||||||
|
<n-grid :cols="2" :x-gap="16">
|
||||||
|
<n-form-item-gi label="工单委外编号">
|
||||||
|
<n-input :value="formData.outsourceCode" disabled />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="委外工序">
|
||||||
|
<n-input :value="formData.name" disabled />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="供应商" path="supplierName">
|
||||||
|
<n-input v-model:value="formData.supplierName" placeholder="请输入供应商" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="委外数量" path="quantity">
|
||||||
|
<n-input-number v-model:value="formData.quantity" :min="1" :precision="0" style="width: 100%" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="单价" path="unitPrice">
|
||||||
|
<n-input-number v-model:value="formData.unitPrice" :min="0" :precision="2" style="width: 100%" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="优先级" path="priority">
|
||||||
|
<n-select v-model:value="formData.priority" :options="priorityOptions" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="当前阶段" path="status">
|
||||||
|
<n-select v-model:value="formData.status" :options="statusOptions" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="预计开始" path="beginPreparationTime">
|
||||||
|
<n-date-picker v-model:value="formData.beginPreparationTime" type="datetime" clearable style="width: 100%" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="预计结束" path="endPreparationTime">
|
||||||
|
<n-date-picker v-model:value="formData.endPreparationTime" type="datetime" clearable style="width: 100%" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi :span="2" label="备注" path="remark">
|
||||||
|
<n-input v-model:value="formData.remark" type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
</n-grid>
|
||||||
|
</n-form>
|
||||||
|
<template #footer>
|
||||||
|
<n-space justify="end">
|
||||||
|
<n-button @click="modalVisible = false">取消</n-button>
|
||||||
|
<n-button type="primary" :loading="submitLoading" @click="handleSubmit">保存</n-button>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, h, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import {
|
||||||
|
NButton, NSpace, NIcon, NTag, NModal, NForm, NFormItem, NFormItemGi, NGrid,
|
||||||
|
NInput, NInputNumber, NSelect, NDatePicker, NDescriptions, NDescriptionsItem,
|
||||||
|
useMessage, useDialog, type DataTableColumns, type FormRules,
|
||||||
|
} from 'naive-ui'
|
||||||
|
import {
|
||||||
|
SearchOutline, RefreshOutline, AddOutline, TrashOutline,
|
||||||
|
CopyOutline, EllipseOutline, TimeOutline,
|
||||||
|
} from '@vicons/ionicons5'
|
||||||
|
import {
|
||||||
|
outsourcingApi,
|
||||||
|
OUTSOURCE_STATUS_MAP,
|
||||||
|
OUTSOURCE_PRIORITY_MAP,
|
||||||
|
type Outsourcing,
|
||||||
|
} from '@/api/outsourcing'
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const dialog = useDialog()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const searchForm = reactive({
|
||||||
|
outsourceCode: '',
|
||||||
|
supplierName: '',
|
||||||
|
name: '',
|
||||||
|
status: null as number | null,
|
||||||
|
})
|
||||||
|
|
||||||
|
const statusOptions = Object.entries(OUTSOURCE_STATUS_MAP).map(([value, item]) => ({
|
||||||
|
label: item.label,
|
||||||
|
value: Number(value),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const priorityOptions = Object.entries(OUTSOURCE_PRIORITY_MAP).map(([value, item]) => ({
|
||||||
|
label: item.label,
|
||||||
|
value: Number(value),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const tableData = ref<Outsourcing[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const submitLoading = ref(false)
|
||||||
|
const selectedIds = ref<number[]>([])
|
||||||
|
const pagination = reactive({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
itemCount: 0,
|
||||||
|
showSizePicker: true,
|
||||||
|
pageSizes: [10, 20, 50],
|
||||||
|
})
|
||||||
|
|
||||||
|
const detailVisible = ref(false)
|
||||||
|
const detailData = ref<Outsourcing | null>(null)
|
||||||
|
const modalVisible = ref(false)
|
||||||
|
const modalTitle = ref('')
|
||||||
|
const formRef = ref()
|
||||||
|
|
||||||
|
const defaultFormData = (): Record<string, unknown> => ({
|
||||||
|
id: undefined,
|
||||||
|
outsourceCode: '',
|
||||||
|
name: '',
|
||||||
|
supplierName: '',
|
||||||
|
quantity: null,
|
||||||
|
unitPrice: null,
|
||||||
|
priority: 1,
|
||||||
|
status: 1,
|
||||||
|
beginPreparationTime: null,
|
||||||
|
endPreparationTime: null,
|
||||||
|
remark: '',
|
||||||
|
})
|
||||||
|
const formData = reactive<Record<string, unknown>>(defaultFormData())
|
||||||
|
|
||||||
|
const formRules: FormRules = {
|
||||||
|
supplierName: [{ required: true, message: '请输入供应商', trigger: 'blur' }],
|
||||||
|
quantity: [{ required: true, type: 'number', message: '请输入委外数量', trigger: ['blur', 'change'] }],
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatQty(val?: number | null) {
|
||||||
|
if (val == null) return '0.00'
|
||||||
|
return Number(val).toFixed(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDecimal(val?: number | string | null) {
|
||||||
|
if (val == null || val === '') return '0.00'
|
||||||
|
return Number(val).toFixed(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMoney(val?: number | string | null, mask = false) {
|
||||||
|
if (mask) return '¥***'
|
||||||
|
if (val == null || val === '') return '-'
|
||||||
|
return `¥${Number(val).toFixed(2)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusLabel(status?: number) {
|
||||||
|
if (status == null) return '-'
|
||||||
|
return OUTSOURCE_STATUS_MAP[status]?.label ?? String(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
function priorityLabel(priority?: number) {
|
||||||
|
if (priority == null) return '-'
|
||||||
|
return OUTSOURCE_PRIORITY_MAP[priority]?.label ?? String(priority)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyText(text: string) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text)
|
||||||
|
message.success('已复制')
|
||||||
|
} catch {
|
||||||
|
message.warning('复制失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns: DataTableColumns<Outsourcing> = [
|
||||||
|
{ type: 'selection', width: 48, align: 'center', fixed: 'left' },
|
||||||
|
{
|
||||||
|
title: '工单委外编号',
|
||||||
|
key: 'outsourceCode',
|
||||||
|
width: 200,
|
||||||
|
align: 'center',
|
||||||
|
fixed: 'left',
|
||||||
|
ellipsis: { tooltip: true },
|
||||||
|
render(row) {
|
||||||
|
const code = row.outsourceCode || (row.id ? `WW${row.id}` : '-')
|
||||||
|
return h('div', { class: 'cell-center code-cell' }, [
|
||||||
|
h(NButton, {
|
||||||
|
text: true,
|
||||||
|
type: 'primary',
|
||||||
|
size: 'small',
|
||||||
|
onClick: () => openDetail(row),
|
||||||
|
}, { default: () => code }),
|
||||||
|
h(NButton, {
|
||||||
|
text: true,
|
||||||
|
size: 'tiny',
|
||||||
|
onClick: () => copyText(code),
|
||||||
|
}, {
|
||||||
|
default: () => h(NIcon, { size: 14 }, { default: () => h(CopyOutline) }),
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '委外数量',
|
||||||
|
key: 'quantity',
|
||||||
|
width: 96,
|
||||||
|
align: 'center',
|
||||||
|
render: (row) => formatQty(row.quantity),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '委外供应商',
|
||||||
|
key: 'supplierName',
|
||||||
|
width: 140,
|
||||||
|
align: 'center',
|
||||||
|
ellipsis: { tooltip: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '委外工序',
|
||||||
|
key: 'name',
|
||||||
|
width: 120,
|
||||||
|
align: 'center',
|
||||||
|
ellipsis: { tooltip: true },
|
||||||
|
render: (row) => row.name || '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '委外单价',
|
||||||
|
key: 'unitPrice',
|
||||||
|
width: 96,
|
||||||
|
align: 'center',
|
||||||
|
render: (row) => formatMoney(row.unitPrice, true),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '委外总金额',
|
||||||
|
key: 'totalAmount',
|
||||||
|
width: 108,
|
||||||
|
align: 'center',
|
||||||
|
render: (row) => formatMoney(row.totalAmount, true),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '优先级',
|
||||||
|
key: 'priority',
|
||||||
|
width: 88,
|
||||||
|
align: 'center',
|
||||||
|
render(row) {
|
||||||
|
const item = OUTSOURCE_PRIORITY_MAP[row.priority ?? 1] ?? OUTSOURCE_PRIORITY_MAP[1]
|
||||||
|
return h('div', { class: 'cell-center' }, [
|
||||||
|
h(NTag, { size: 'small', type: item.type, bordered: false }, { default: () => item.label }),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '已发料数量',
|
||||||
|
key: 'issuedQuantity',
|
||||||
|
width: 104,
|
||||||
|
align: 'center',
|
||||||
|
render: (row) => formatDecimal(row.issuedQuantity),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '已收货数量',
|
||||||
|
key: 'receivedQuantity',
|
||||||
|
width: 104,
|
||||||
|
align: 'center',
|
||||||
|
render: (row) => formatDecimal(row.receivedQuantity),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '合格数量',
|
||||||
|
key: 'qualifiedQuantity',
|
||||||
|
width: 96,
|
||||||
|
align: 'center',
|
||||||
|
render: (row) => formatDecimal(row.qualifiedQuantity),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '当前阶段',
|
||||||
|
key: 'status',
|
||||||
|
width: 100,
|
||||||
|
align: 'center',
|
||||||
|
render(row) {
|
||||||
|
const status = row.status ?? 1
|
||||||
|
const item = OUTSOURCE_STATUS_MAP[status] ?? OUTSOURCE_STATUS_MAP[1]
|
||||||
|
const icon = status === 1 ? TimeOutline : EllipseOutline
|
||||||
|
const color = status === 1 ? '#2080f0' : '#bfbfbf'
|
||||||
|
return h('div', { class: 'cell-center status-cell' }, [
|
||||||
|
h(NIcon, { color, size: 16 }, { default: () => h(icon) }),
|
||||||
|
h('span', item.label),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 120,
|
||||||
|
align: 'center',
|
||||||
|
fixed: 'right',
|
||||||
|
render(row) {
|
||||||
|
return h('div', { class: 'cell-center' }, [
|
||||||
|
h(NSpace, { size: 4, justify: 'center' }, {
|
||||||
|
default: () => [
|
||||||
|
h(NButton, { text: true, type: 'primary', size: 'small', onClick: () => openDetail(row) }, { default: () => '详情' }),
|
||||||
|
h(NButton, { text: true, type: 'primary', size: 'small', onClick: () => handleEdit(row) }, { default: () => '编辑' }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await outsourcingApi.page({
|
||||||
|
page: pagination.page,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
name: searchForm.name || undefined,
|
||||||
|
outsourceCode: searchForm.outsourceCode || undefined,
|
||||||
|
supplierName: searchForm.supplierName || undefined,
|
||||||
|
status: searchForm.status ?? undefined,
|
||||||
|
})
|
||||||
|
tableData.value = res.list ?? []
|
||||||
|
pagination.itemCount = res.total ?? 0
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSearch() {
|
||||||
|
pagination.page = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleReset() {
|
||||||
|
searchForm.outsourceCode = ''
|
||||||
|
searchForm.supplierName = ''
|
||||||
|
searchForm.name = ''
|
||||||
|
searchForm.status = null
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePageChange(page: number) {
|
||||||
|
pagination.page = page
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePageSizeChange(pageSize: number) {
|
||||||
|
pagination.pageSize = pageSize
|
||||||
|
pagination.page = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCheck(keys: Array<string | number>) {
|
||||||
|
selectedIds.value = keys as number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGoCreate() {
|
||||||
|
router.push('/biz/orderProcessPlan/board')
|
||||||
|
message.info('请在看板中选择工序后创建委外')
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDateTime(val?: string | null) {
|
||||||
|
if (!val) return null
|
||||||
|
const ts = new Date(val.replace(' ', 'T')).getTime()
|
||||||
|
return Number.isNaN(ts) ? null : ts
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openDetail(row: Outsourcing) {
|
||||||
|
if (!row.id) return
|
||||||
|
detailData.value = await outsourcingApi.detail(row.id)
|
||||||
|
detailVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEdit(row: Outsourcing) {
|
||||||
|
modalTitle.value = '编辑委外工单'
|
||||||
|
Object.assign(formData, defaultFormData(), row, {
|
||||||
|
quantity: row.quantity ?? null,
|
||||||
|
unitPrice: row.unitPrice != null ? Number(row.unitPrice) : null,
|
||||||
|
beginPreparationTime: parseDateTime(row.beginPreparationTime),
|
||||||
|
endPreparationTime: parseDateTime(row.endPreparationTime),
|
||||||
|
})
|
||||||
|
modalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatSubmitDateTime(ts: number | null) {
|
||||||
|
if (!ts) return undefined
|
||||||
|
return new Date(ts).toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
submitLoading.value = true
|
||||||
|
try {
|
||||||
|
const qty = formData.quantity as number
|
||||||
|
const price = formData.unitPrice as number | null
|
||||||
|
const payload: Outsourcing = {
|
||||||
|
id: formData.id as number,
|
||||||
|
supplierName: formData.supplierName as string,
|
||||||
|
quantity: qty,
|
||||||
|
unitPrice: price ?? undefined,
|
||||||
|
totalAmount: price != null ? Number((qty * price).toFixed(2)) : undefined,
|
||||||
|
priority: formData.priority as number,
|
||||||
|
status: formData.status as number,
|
||||||
|
beginPreparationTime: formatSubmitDateTime(formData.beginPreparationTime as number | null),
|
||||||
|
endPreparationTime: formatSubmitDateTime(formData.endPreparationTime as number | null),
|
||||||
|
remark: (formData.remark as string) || undefined,
|
||||||
|
}
|
||||||
|
await outsourcingApi.update(payload)
|
||||||
|
message.success('保存成功')
|
||||||
|
modalVisible.value = false
|
||||||
|
loadData()
|
||||||
|
} finally {
|
||||||
|
submitLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBatchDelete() {
|
||||||
|
dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: `确定删除选中的 ${selectedIds.value.length} 条委外工单吗?`,
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
await outsourcingApi.delete(selectedIds.value)
|
||||||
|
message.success('删除成功')
|
||||||
|
selectedIds.value = []
|
||||||
|
loadData()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(loadData)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.search-form {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-center {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-cell {
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-cell {
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-data-table-th) {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-data-table-td) {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -54,8 +54,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, onMounted } from 'vue'
|
import { ref, reactive,h, onMounted } from 'vue'
|
||||||
import { NButton, NSpace, NIcon, type DataTableColumns} from 'naive-ui'
|
import { NButton, NSpace,NTag, NIcon, type DataTableColumns} from 'naive-ui'
|
||||||
import { SearchOutline, RefreshOutline } from '@vicons/ionicons5'
|
import { SearchOutline, RefreshOutline } from '@vicons/ionicons5'
|
||||||
import { qcResultDetailApi, type QcResultDetail } from '@/api/qcResultDetail'
|
import { qcResultDetailApi, type QcResultDetail } from '@/api/qcResultDetail'
|
||||||
import { dictDataApi } from '@/api/org'
|
import { dictDataApi } from '@/api/org'
|
||||||
@ -92,7 +92,7 @@ const actionStatusOptions = ref<{ label: string; value: any }[]>([])
|
|||||||
//判定类型
|
//判定类型
|
||||||
const qcResultTypeOptions = ref<{ label: string; value: any }[]>([])
|
const qcResultTypeOptions = ref<{ label: string; value: any }[]>([])
|
||||||
//执行状态
|
//执行状态
|
||||||
const qcActionStatusOptions = ref<{ label: string; value: any }[]>([])
|
const qcActionStatusOptions = ref<{ label: string; value: any;class:any }[]>([])
|
||||||
|
|
||||||
|
|
||||||
// 表格列
|
// 表格列
|
||||||
@ -113,7 +113,8 @@ const columns: DataTableColumns<QcResultDetail> = [
|
|||||||
render(row) {
|
render(row) {
|
||||||
const val = row.actionStatus
|
const val = row.actionStatus
|
||||||
const opt = qcActionStatusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
const opt = qcActionStatusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
return opt ? opt.label : (val ?? '-')
|
if (!opt) return val ?? '-'
|
||||||
|
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ title: '备注/原因描述', key: 'remark' },
|
{ title: '备注/原因描述', key: 'remark' },
|
||||||
@ -195,7 +196,7 @@ async function loadDictOptions() {
|
|||||||
}catch {}
|
}catch {}
|
||||||
try {
|
try {
|
||||||
const data = await dictDataApi.listByType('qc_action_status')
|
const data = await dictDataApi.listByType('qc_action_status')
|
||||||
qcActionStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
|
qcActionStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class: d.listClass }))
|
||||||
}catch {}
|
}catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -224,7 +224,23 @@
|
|||||||
<!-- </n-card>-->
|
<!-- </n-card>-->
|
||||||
</n-modal>
|
</n-modal>
|
||||||
|
|
||||||
|
<!-- 提交判定明细抽屉 -->
|
||||||
|
<n-drawer v-model:show="showSubmitOuter" :width="800">
|
||||||
|
<SumbitDetailCard
|
||||||
|
:showOkTab="true"
|
||||||
|
:showScrapTab="true"
|
||||||
|
:showReworkTab = true
|
||||||
|
:showConnessionTab="true"
|
||||||
|
:showTraceCode = "true"
|
||||||
|
:qcId="qcId"
|
||||||
|
@qcSumitDetail="handleSubmitDetail"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<n-space justify="center" style="width: 100%; margin-top: 16px;">
|
||||||
|
<n-button size="small" @click="handleReset">重置</n-button>
|
||||||
|
<n-button type="primary" size="small" @click="handleSubmitResultDetail">提交</n-button>
|
||||||
|
</n-space>
|
||||||
|
</n-drawer>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -245,13 +261,18 @@ import { qualityTestingApi, type QualityTesting } from '@/api/qualityTesting'
|
|||||||
import {assingReworkApi} from '@/api/assingRework'
|
import {assingReworkApi} from '@/api/assingRework'
|
||||||
import { dictDataApi } from '@/api/org'
|
import { dictDataApi } from '@/api/org'
|
||||||
|
|
||||||
import {QcResultDetail} from "@/api/qcResultDetail.ts";
|
import {qcResultDetailApi, type QcResultDetail} from "@/api/qcResultDetail.ts";
|
||||||
|
import SumbitDetailCard from '@/components/SumbitDetailCard.vue'
|
||||||
|
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
|
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
|
|
||||||
|
//提交明细抽屉
|
||||||
|
const showSubmitOuter = ref<Boolean>(false)
|
||||||
|
const qcId = ref<number>(-1)
|
||||||
|
|
||||||
|
|
||||||
// 表格数据
|
// 表格数据
|
||||||
@ -287,7 +308,7 @@ const detailIsCollapse = ref(true)
|
|||||||
// const submitDetailModalVisible = ref(false)
|
// const submitDetailModalVisible = ref(false)
|
||||||
const traceCodeShow = ref(false)
|
const traceCodeShow = ref(false)
|
||||||
//提交明细/部分提交明细标识
|
//提交明细/部分提交明细标识
|
||||||
const submitFlag = ref<Boolean>()
|
const showTraceFlag = ref<Boolean>()
|
||||||
|
|
||||||
|
|
||||||
const customOrderKeys = [
|
const customOrderKeys = [
|
||||||
@ -411,21 +432,25 @@ const defaultFormData: QualityTesting = {
|
|||||||
}
|
}
|
||||||
const formData = reactive<QualityTesting>({ ...defaultFormData })
|
const formData = reactive<QualityTesting>({ ...defaultFormData })
|
||||||
|
|
||||||
// const defaultFormDetailData:QcResultDetail = {
|
const defaultFormDetailData:QcResultDetail = {
|
||||||
// qcId: undefined,
|
qcId: undefined,
|
||||||
// resultType: undefined,
|
resultType: undefined,
|
||||||
// qty: 0,
|
qty: 0,
|
||||||
// traceCode: undefined,
|
traceCode: undefined,
|
||||||
// defectCode: undefined,
|
defectCode: undefined,
|
||||||
// handleAction: undefined,
|
handleAction: undefined,
|
||||||
// actionStatus: undefined
|
actionStatus: undefined,
|
||||||
// }
|
remark: '',
|
||||||
|
title:undefined,
|
||||||
|
images:[],
|
||||||
|
attachments:[]
|
||||||
|
}
|
||||||
|
|
||||||
|
let formDetailOKData = reactive<QcResultDetail>({ ...defaultFormDetailData, resultType: 1,handleAction:'下一道工序',actionStatus:1 })
|
||||||
|
|
||||||
// const formDetailOKData = reactive<QcResultDetail>({ ...defaultFormDetailData, resultType: 1,handleAction:'下一道工序',actionStatus:1 })
|
let formDetailScrapData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 2,handleAction:'报废次品',actionStatus:1 })
|
||||||
// const formDetailScrapData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 2,handleAction:'报废次品',actionStatus:1 })
|
let formDetailReworkData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 3,handleAction:'返工',actionStatus:1 })
|
||||||
// const formDetailReworkData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 3,handleAction:'返工',actionStatus:1 })
|
let formDetailConcessionData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 4,handleAction:'让步接收',actionStatus:1 })
|
||||||
// const formDetailConcessionData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 4,handleAction:'让步接收',actionStatus:1 })
|
|
||||||
|
|
||||||
const assingWorkDisabled = ref(true)
|
const assingWorkDisabled = ref(true)
|
||||||
|
|
||||||
@ -466,6 +491,7 @@ const columns: DataTableColumns<QualityTesting> = [
|
|||||||
// { title: '关联生产订单ID', key: 'orderId' },
|
// { title: '关联生产订单ID', key: 'orderId' },
|
||||||
// { title: '关联工序计划ID', key: 'processId' },
|
// { title: '关联工序计划ID', key: 'processId' },
|
||||||
// { title: '物料ID', key: 'itemId' },
|
// { title: '物料ID', key: 'itemId' },
|
||||||
|
{ title: '追溯码', key: 'traceCode',align: 'center' },
|
||||||
{ title: '报检总数', key: 'totalQty',align: 'center' },
|
{ title: '报检总数', key: 'totalQty',align: 'center' },
|
||||||
{ title: '已检验数量', key: 'inspectQty',align: 'center' },
|
{ title: '已检验数量', key: 'inspectQty',align: 'center' },
|
||||||
{ title: '质检状态', key: 'status',align: 'center',
|
{ title: '质检状态', key: 'status',align: 'center',
|
||||||
@ -508,12 +534,12 @@ const columns: DataTableColumns<QualityTesting> = [
|
|||||||
{ label: '查看详情', key: 'view' },
|
{ label: '查看详情', key: 'view' },
|
||||||
{ type: 'divider' }, // 分割线
|
{ type: 'divider' }, // 分割线
|
||||||
{ label: '提交判定明细', key: 'submitResultDetail',show:row.status == 1 },
|
{ label: '提交判定明细', key: 'submitResultDetail',show:row.status == 1 },
|
||||||
{ type: 'divider',show:row.status == 1 }, // 分割线
|
// { type: 'divider',show:row.status == 1 }, // 分割线
|
||||||
{ label: '提交部分判定明细', key: 'submitBFResultDetail',show:(row.status == 1 || row.status == 2) },
|
// { label: '提交部分判定明细', key: 'submitBFResultDetail',show:(row.status == 1 || row.status == 2) },
|
||||||
{ type: 'divider',show:(row.status == 1 || row.status == 2) }, // 分割线
|
{ type: 'divider',show:(row.status == 1 || row.status == 2) }, // 分割线
|
||||||
{ label: '查询判定明细', key: 'queryResultDetail' },
|
{ label: '查询判定明细', key: 'queryResultDetail' },
|
||||||
{ type: 'divider' }, // 分割线
|
// { type: 'divider' }, // 分割线
|
||||||
{ label: '质检撤回', key: 'cancelQuality' },
|
// { label: '质检撤回', key: 'cancelQuality' },
|
||||||
],
|
],
|
||||||
// 下拉菜单项点击事件
|
// 下拉菜单项点击事件
|
||||||
onSelect: (key: string) => {
|
onSelect: (key: string) => {
|
||||||
@ -524,15 +550,15 @@ const columns: DataTableColumns<QualityTesting> = [
|
|||||||
case 'submitResultDetail':
|
case 'submitResultDetail':
|
||||||
submitResultDetail(row)
|
submitResultDetail(row)
|
||||||
break
|
break
|
||||||
case 'submitBFResultDetail':
|
// case 'submitBFResultDetail':
|
||||||
submitBFResultDetail(row)
|
// submitBFResultDetail(row)
|
||||||
break
|
// break
|
||||||
case 'queryResultDetail':
|
case 'queryResultDetail':
|
||||||
queryResultDetail(row)
|
queryResultDetail(row)
|
||||||
break
|
break
|
||||||
case 'cancelQuality':
|
// case 'cancelQuality':
|
||||||
handleCancelQuality(row)
|
// handleCancelQuality(row)
|
||||||
break
|
// break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
@ -608,6 +634,8 @@ function handleAdd() {
|
|||||||
function handleEdit(row: QualityTesting) {
|
function handleEdit(row: QualityTesting) {
|
||||||
modalTitle.value = '编辑质检单表'
|
modalTitle.value = '编辑质检单表'
|
||||||
assingWorkDisabled.value = true
|
assingWorkDisabled.value = true
|
||||||
|
console.log(row);
|
||||||
|
|
||||||
Object.assign(formData, row)
|
Object.assign(formData, row)
|
||||||
if (formData.inspectTime && typeof formData.inspectTime === 'string') {
|
if (formData.inspectTime && typeof formData.inspectTime === 'string') {
|
||||||
formData.inspectTime = new Date(formData.inspectTime.replace(' ', 'T')).getTime()
|
formData.inspectTime = new Date(formData.inspectTime.replace(' ', 'T')).getTime()
|
||||||
@ -836,30 +864,43 @@ async function loadAssignWorkOrder() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//提交判定明细
|
//提交判定明细
|
||||||
function submitResultDetail(row:any) {
|
async function submitResultDetail(row:any) {
|
||||||
|
showSubmitOuter.value = true
|
||||||
if (row.traceType != 1) {
|
qcId.value = row.id
|
||||||
traceCodeShow.value = true
|
}
|
||||||
}
|
|
||||||
submitFlag.value = true
|
|
||||||
|
|
||||||
|
|
||||||
let path = `/qc/submitDetail/${row.qcNo}/${row.id}/${submitFlag.value}/${traceCodeShow.value}`
|
function handleSubmitDetail(submitDetail:any){
|
||||||
router.push(path)
|
formDetailOKData = submitDetail.okData ? submitDetail.okData : formDetailOKData ;
|
||||||
|
formDetailScrapData = submitDetail.scrapData ? submitDetail.scrapData : formDetailScrapData
|
||||||
|
formDetailReworkData = submitDetail.reworkData ? submitDetail.reworkData : formDetailReworkData
|
||||||
|
formDetailConcessionData = submitDetail.concessionData ? submitDetail.concessionData : formDetailConcessionData
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//提交部分判定明细
|
async function handleSubmitResultDetail() {
|
||||||
function submitBFResultDetail(row:any) {
|
|
||||||
|
|
||||||
if (row.traceType != 1) {
|
formDetailOKData.qcId = qcId.value
|
||||||
traceCodeShow.value = true
|
formDetailScrapData.qcId = qcId.value
|
||||||
}
|
formDetailReworkData.qcId = qcId.value
|
||||||
submitFlag.value = false
|
formDetailConcessionData.qcId =qcId.value
|
||||||
|
|
||||||
let path = `/qc/submitDetail/${row.qcNo}/${row.id}/${submitFlag.value}/${traceCodeShow.value}`
|
|
||||||
router.push(path)
|
const submitParam = [
|
||||||
|
formDetailOKData,
|
||||||
|
formDetailScrapData,
|
||||||
|
formDetailReworkData,
|
||||||
|
formDetailConcessionData]
|
||||||
|
|
||||||
|
console.log(submitParam);
|
||||||
|
|
||||||
|
|
||||||
|
await qualityTestingApi.submit(submitParam)
|
||||||
|
|
||||||
|
message.success('提交成功')
|
||||||
|
router.go(-1)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -870,25 +911,6 @@ function queryResultDetail(row:any) {
|
|||||||
router.push(path)
|
router.push(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
//质检撤回
|
|
||||||
function handleCancelQuality(row:any) {
|
|
||||||
dialog.warning({
|
|
||||||
title: '提示',
|
|
||||||
content: '确定要撤回ID:'+row.id+'质检记录吗?',
|
|
||||||
positiveText: '确定',
|
|
||||||
negativeText: '取消',
|
|
||||||
onPositiveClick: async () => {
|
|
||||||
try {
|
|
||||||
await qualityTestingApi.cancel(row.id)
|
|
||||||
message.success('撤回成功')
|
|
||||||
loadData()
|
|
||||||
} catch (error) {
|
|
||||||
console.log("撤回失败"+error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,164 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<n-card :title="title" style="margin-bottom: 16px">
|
<n-card :title="title" >
|
||||||
<n-tabs type="card" animated>
|
<n-tabs type="card" animated>
|
||||||
<n-tab-pane name="合格" tab="合格">
|
<n-tab-pane name="合格" tab="合格" style="width: 50%;">
|
||||||
<n-form ref="formRef" :model="formDetailOKData" label-placement="left" label-width="100px">
|
<SubmitDetailPage
|
||||||
<n-grid x-gap="12" :cols="2">
|
key="ok"
|
||||||
<n-gi>
|
:disabled="false"
|
||||||
<n-form-item label="判定数量" path="qty">
|
:formDetailData="formDetailOKData"
|
||||||
<n-input-number v-model:value="formDetailOKData.qty" placeholder="请输入判定数量" :min="0" />
|
:showTraceCode="true"
|
||||||
</n-form-item>
|
@update:formDetailData= handleFormDetailOKData
|
||||||
</n-gi>
|
/>
|
||||||
<n-gi>
|
|
||||||
<n-form-item label="批次号或SN码" path="traceCode" v-show="traceCodeShow">
|
|
||||||
<n-input v-model:value="formDetailOKData.traceCode" placeholder="请输入批次号或SN码(启用追溯时必填)" />
|
|
||||||
</n-form-item>
|
|
||||||
</n-gi>
|
|
||||||
</n-grid>
|
|
||||||
<n-grid x-gap="8" :cols="3">
|
|
||||||
<n-gi>
|
|
||||||
<n-form-item label="备注" >
|
|
||||||
<n-input
|
|
||||||
v-model:value="formDetailOKData.remark"
|
|
||||||
type="textarea"
|
|
||||||
placeholder="输入备注原因"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
</n-gi>
|
|
||||||
</n-grid>
|
|
||||||
|
|
||||||
</n-form>
|
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane name="报废" tab="报废">
|
<n-tab-pane name="报废" tab="报废" style="width: 50%;">
|
||||||
<n-form ref="formRef" :model="formDetailScrapData" label-placement="left" label-width="100px">
|
<SubmitDetailPage
|
||||||
<n-grid x-gap="12" :cols="2">
|
key="scrap"
|
||||||
<n-gi>
|
:disabled="false"
|
||||||
<n-form-item label="判定数量" path="qty">
|
:formDetailData="formDetailScrapData"
|
||||||
<n-input-number v-model:value="formDetailScrapData.qty" placeholder="请输入判定数量" :min="0" />
|
:showTraceCode="true"
|
||||||
</n-form-item>
|
@update:formDetailData= handleFormDetailScrapData
|
||||||
</n-gi>
|
/>
|
||||||
<n-gi>
|
|
||||||
<n-form-item label="批次号或SN码" path="traceCode" v-show="traceCodeShow">
|
|
||||||
<n-input v-model:value="formDetailScrapData.traceCode" placeholder="请输入批次号或SN码(启用追溯时必填)" />
|
|
||||||
</n-form-item>
|
|
||||||
</n-gi>
|
|
||||||
</n-grid>
|
|
||||||
<n-grid x-gap="8" :cols="3">
|
|
||||||
<n-gi>
|
|
||||||
<n-form-item label="备注" >
|
|
||||||
<n-input
|
|
||||||
v-model:value="formDetailScrapData.remark"
|
|
||||||
type="textarea"
|
|
||||||
placeholder="输入备注原因"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
</n-gi>
|
|
||||||
</n-grid>
|
|
||||||
|
|
||||||
</n-form>
|
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane name="返工" tab="返工">
|
<n-tab-pane name="返工" tab="返工" style="width: 50%;">
|
||||||
<n-form ref="formRef" :model="formDetailReworkData" label-placement="left" label-width="100px">
|
<SubmitDetailPage
|
||||||
<n-grid x-gap="12" :cols="2">
|
key="rework"
|
||||||
<n-gi>
|
:disabled="false"
|
||||||
<n-form-item label="判定数量" path="qty">
|
:formDetailData="formDetailReworkData"
|
||||||
<n-input-number v-model:value="formDetailReworkData.qty" placeholder="请输入判定数量" :min="0" />
|
:showTraceCode="true"
|
||||||
</n-form-item>
|
@update:formDetailData= handleFormDetailReworkData
|
||||||
</n-gi>
|
/>
|
||||||
<n-gi>
|
|
||||||
<n-form-item label="批次号或SN码" path="traceCode" v-show="traceCodeShow">
|
|
||||||
<n-input v-model:value="formDetailReworkData.traceCode" placeholder="请输入批次号或SN码(启用追溯时必填)" />
|
|
||||||
</n-form-item>
|
|
||||||
</n-gi>
|
|
||||||
</n-grid>
|
|
||||||
<n-grid x-gap="8" :cols="3">
|
|
||||||
<n-gi>
|
|
||||||
<n-form-item label="备注" >
|
|
||||||
<n-input
|
|
||||||
v-model:value="formDetailReworkData.remark"
|
|
||||||
type="textarea"
|
|
||||||
placeholder="输入备注原因"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
</n-gi>
|
|
||||||
</n-grid>
|
|
||||||
</n-form>
|
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane name="让步接收" tab="让步接收">
|
<n-tab-pane name="让步接收" tab="让步接收" style="width: 50%;">
|
||||||
<n-form ref="formRef" :model="formDetailConcessionData" label-placement="left" label-width="100px">
|
|
||||||
|
|
||||||
<n-grid x-gap="12" :cols="2">
|
<SubmitDetailPage
|
||||||
<n-gi>
|
key="conession"
|
||||||
<n-form-item label="判定数量" path="qty">
|
:disabled="false"
|
||||||
<n-input-number v-model:value="formDetailConcessionData.qty" placeholder="请输入判定数量" :min="0" />
|
:formDetailData="formDetailConcessionData"
|
||||||
</n-form-item>
|
:showTraceCode="true"
|
||||||
</n-gi>
|
@update:formDetailData= handleFormDetailConcessionData
|
||||||
<n-gi>
|
/>
|
||||||
<n-form-item label="批次号或SN码" path="traceCode" v-show="traceCodeShow">
|
|
||||||
<n-input v-model:value="formDetailConcessionData.traceCode" placeholder="请输入批次号或SN码(启用追溯时必填)" />
|
|
||||||
</n-form-item>
|
|
||||||
</n-gi>
|
|
||||||
</n-grid>
|
|
||||||
<n-grid x-gap="8" :cols="3">
|
|
||||||
<n-gi>
|
|
||||||
<n-form-item label="备注" >
|
|
||||||
<n-input
|
|
||||||
v-model:value="formDetailConcessionData.remark"
|
|
||||||
type="textarea"
|
|
||||||
placeholder="输入备注原因"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
</n-gi>
|
|
||||||
</n-grid>
|
|
||||||
<n-grid x-gap="8" :cols="3">
|
|
||||||
<n-gi>
|
|
||||||
<n-form-item label="图片">
|
|
||||||
<n-upload
|
|
||||||
action="null"
|
|
||||||
:max="5"
|
|
||||||
:custom-request="()=>{}"
|
|
||||||
@change="handleUploadImageChange"
|
|
||||||
v-model:file-list="imageList"
|
|
||||||
list-type="image-card"
|
|
||||||
>
|
|
||||||
点击上传
|
|
||||||
</n-upload>
|
|
||||||
</n-form-item>
|
|
||||||
</n-gi>
|
|
||||||
</n-grid>
|
|
||||||
<n-grid x-gap="8" :cols="3">
|
|
||||||
<n-gi>
|
|
||||||
<n-form-item label="附件">
|
|
||||||
<n-upload
|
|
||||||
multiple
|
|
||||||
directory-dnd
|
|
||||||
:max="5"
|
|
||||||
:custom-request="()=>{}"
|
|
||||||
@change="handleUploadChange"
|
|
||||||
v-model:file-list="fileList"
|
|
||||||
accept=".pdf,.doc,.docx,.xls,.xlsx"
|
|
||||||
:max-size="10*1024*1024"
|
|
||||||
>
|
|
||||||
<n-upload-dragger>
|
|
||||||
<div style="margin-bottom: 12px">
|
|
||||||
<n-icon size="48" :depth="3">
|
|
||||||
<ArchiveIcon />
|
|
||||||
</n-icon>
|
|
||||||
</div>
|
|
||||||
<n-text style="font-size: 16px">
|
|
||||||
点击或者拖动文件到该区域来上传
|
|
||||||
</n-text>
|
|
||||||
<n-p depth="3" style="margin: 8px 0 0 0">
|
|
||||||
仅支持 PDF、DOC、DOCX、XLS、XLSX、JPG、PNG、GIF,单文件最大10MB
|
|
||||||
</n-p>
|
|
||||||
</n-upload-dragger>
|
|
||||||
</n-upload>
|
|
||||||
</n-form-item>
|
|
||||||
</n-gi>
|
|
||||||
</n-grid>
|
|
||||||
|
|
||||||
</n-form>
|
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
|
|
||||||
@ -179,33 +58,22 @@ import {
|
|||||||
|
|
||||||
import {useRoute} from "vue-router";
|
import {useRoute} from "vue-router";
|
||||||
|
|
||||||
import {QcResultDetail} from "@/api/qcResultDetail.ts";
|
import {qcResultDetailApi, type QcResultDetail} from "@/api/qcResultDetail.ts";
|
||||||
import {fileApi} from '@/api/system1'
|
import {fileApi} from '@/api/system1'
|
||||||
import { qualityTestingApi} from '@/api/qualityTesting'
|
import { qualityTestingApi} from '@/api/qualityTesting'
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
|
import SubmitDetailPage from '@/components/SubmitDetailPage.vue';
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const qcId =ref( Number(route.params.id))
|
const qcId =ref( Number(route.params.id))
|
||||||
|
const traceCode = ref(String(route.params.traceCode))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const traceCodeShow = ref(route.params.traceCodeShow === 'true')
|
|
||||||
const submitFlag = ref(route.params.submitFlag === 'true')
|
const submitFlag = ref(route.params.submitFlag === 'true')
|
||||||
|
|
||||||
|
|
||||||
const MAX_FILE_SIZE = 10 * 1024 * 1024
|
|
||||||
//提交图片
|
|
||||||
const imageList = ref<any[]>([])
|
|
||||||
const uploadImages = ref<File[]>([])
|
|
||||||
const imageExts = ['jpg', 'jpeg', 'png']
|
|
||||||
|
|
||||||
|
|
||||||
//提交附件
|
|
||||||
const fileList = ref<any[]>([])
|
|
||||||
const uploadFiles = ref<File[]>([])
|
|
||||||
const fileExts = ['pdf', 'doc', 'docx', 'xls', 'xlsx']
|
|
||||||
|
|
||||||
|
|
||||||
const title = ref(`质检编号: ${route.params.qcNo}`)
|
const title = ref(`质检编号: ${route.params.qcNo}`)
|
||||||
|
|
||||||
|
|
||||||
@ -223,10 +91,29 @@ const defaultFormDetailData:QcResultDetail = {
|
|||||||
attachments:[]
|
attachments:[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const formDetailOKData = reactive<QcResultDetail>({ ...defaultFormDetailData, resultType: 1,handleAction:'下一道工序',actionStatus:1 })
|
const formDetailOKData = reactive<QcResultDetail>({ ...defaultFormDetailData,traceCode:traceCode.value, resultType: 1,handleAction:'下一道工序',actionStatus:1 })
|
||||||
const formDetailScrapData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 2,handleAction:'报废次品',actionStatus:1 })
|
|
||||||
const formDetailReworkData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 3,handleAction:'返工',actionStatus:1 })
|
const formDetailScrapData = reactive<QcResultDetail>({ ...defaultFormDetailData,traceCode:traceCode.value,resultType: 2,handleAction:'报废次品',actionStatus:1 })
|
||||||
const formDetailConcessionData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 4,handleAction:'让步接收',actionStatus:1 })
|
const formDetailReworkData = reactive<QcResultDetail>({ ...defaultFormDetailData,traceCode:traceCode.value,resultType: 3,handleAction:'返工',actionStatus:1 })
|
||||||
|
const formDetailConcessionData = reactive<QcResultDetail>({ ...defaultFormDetailData,traceCode:traceCode.value,resultType: 4,handleAction:'让步接收',actionStatus:1 })
|
||||||
|
|
||||||
|
function handleFormDetailOKData(formDetailData:QcResultDetail) {
|
||||||
|
Object.assign(formDetailOKData, formDetailData)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFormDetailScrapData(formDetailData:QcResultDetail) {
|
||||||
|
Object.assign(formDetailScrapData, formDetailData)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFormDetailReworkData(formDetailData:QcResultDetail) {
|
||||||
|
Object.assign(formDetailReworkData, formDetailData)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFormDetailConcessionData(formDetailData:QcResultDetail){
|
||||||
|
Object.assign(formDetailConcessionData, formDetailData)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function handleSubmitResultDetail() {
|
async function handleSubmitResultDetail() {
|
||||||
@ -236,19 +123,17 @@ async function handleSubmitResultDetail() {
|
|||||||
formDetailReworkData.qcId = qcId.value
|
formDetailReworkData.qcId = qcId.value
|
||||||
formDetailConcessionData.qcId =qcId.value
|
formDetailConcessionData.qcId =qcId.value
|
||||||
|
|
||||||
formDetailConcessionData.images = uploadImages.value
|
|
||||||
formDetailConcessionData.attachments = uploadFiles.value
|
|
||||||
|
|
||||||
const submitParam = [
|
const submitParam = [
|
||||||
formDetailOKData,
|
formDetailOKData,
|
||||||
formDetailScrapData,
|
formDetailScrapData,
|
||||||
formDetailReworkData,
|
formDetailReworkData,
|
||||||
formDetailConcessionData]
|
formDetailConcessionData]
|
||||||
|
|
||||||
console.log(submitParam);
|
console.log(submitParam);
|
||||||
|
|
||||||
|
|
||||||
submitFlag.value? await qualityTestingApi.submit(submitParam) : await qualityTestingApi.submitBF(submitParam)
|
await qualityTestingApi.submit(submitParam)
|
||||||
|
|
||||||
message.success('提交成功')
|
message.success('提交成功')
|
||||||
router.go(-1)
|
router.go(-1)
|
||||||
@ -257,120 +142,28 @@ async function handleSubmitResultDetail() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function handleUploadChange ({ file, fileList }: { file: any, fileList: any[] }) {
|
|
||||||
const invalidNames: string[] = []
|
|
||||||
|
|
||||||
fileList.forEach(item => {
|
|
||||||
if (!item.file) return
|
|
||||||
const f = item.file
|
|
||||||
const ext = f.name.toLowerCase().split('.').pop() || ''
|
|
||||||
|
|
||||||
if (!fileExts.includes(ext)) {
|
|
||||||
invalidNames.push(f.name)
|
|
||||||
item.status = 'error'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (invalidNames.length) {
|
|
||||||
message.error(`格式不支持:${invalidNames.join('、')}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const pendingList = fileList.filter(
|
|
||||||
item => item.status === 'pending' && item.file && item.status !== 'error'
|
|
||||||
)
|
|
||||||
if (pendingList.length === 0) return
|
|
||||||
|
|
||||||
|
|
||||||
const promises = pendingList.map(async (item) => {
|
|
||||||
try {
|
|
||||||
const res = await fileApi.uploadDingAttachment(item.file)
|
|
||||||
uploadFiles.value.push({ uid: item.id, ...res })
|
|
||||||
item.status = 'success'
|
|
||||||
item.percentage = 100
|
|
||||||
message.success("附件上传成功")
|
|
||||||
} catch (err) {
|
|
||||||
console.error('附件上传失败', err)
|
|
||||||
item.status = 'error'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// await Promise.all(promises)
|
|
||||||
|
|
||||||
|
|
||||||
// const successIds = fileList.filter(x => x.status === 'success').map(x => x.id)
|
|
||||||
// uploadFiles.value = uploadFiles.value.filter(att => successIds.includes(att.uid))
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function handleUploadImageChange({ file, fileList }: { file: any, fileList: any[] }) {
|
|
||||||
|
|
||||||
const invalidNames: string[] = []
|
|
||||||
fileList.forEach(item => {
|
|
||||||
if (!item.file) return
|
|
||||||
const f = item.file
|
|
||||||
const ext = f.name.toLowerCase().split('.').pop() || ''
|
|
||||||
|
|
||||||
if (!imageExts.includes(ext)) {
|
|
||||||
invalidNames.push(f.name)
|
|
||||||
item.status = 'error'
|
|
||||||
}
|
|
||||||
if (f.size > MAX_FILE_SIZE) {
|
|
||||||
message.error(`${f.name} 超出10MB大小限制`)
|
|
||||||
item.status = 'error'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (invalidNames.length) {
|
|
||||||
message.error(`格式不支持:${invalidNames.join('、')}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 筛选待上传合法文件
|
|
||||||
const pendingList = fileList.filter(
|
|
||||||
item => item.status === 'pending' && item.file && item.status !== 'error'
|
|
||||||
)
|
|
||||||
if (pendingList.length === 0) return
|
|
||||||
|
|
||||||
// 3. 批量上传
|
|
||||||
const promises = pendingList.map(async (item) => {
|
|
||||||
try {
|
|
||||||
const res = await fileApi.uploadDingImage(item.file)
|
|
||||||
uploadImages.value.push({ uid: item.id, ...res })
|
|
||||||
item.status = 'success'
|
|
||||||
item.percentage = 100
|
|
||||||
message.success("上传成功")
|
|
||||||
} catch (err) {
|
|
||||||
console.error('图片上传失败', err)
|
|
||||||
item.status = 'error'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// await Promise.all(promises)
|
|
||||||
|
|
||||||
|
|
||||||
// const successIds = fileList.filter(x => x.status === 'success').map(x => x.id)
|
|
||||||
// uploadImages.value = uploadImages.value.filter(img => successIds.includes(img.uid))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleReset() {
|
function handleReset() {
|
||||||
Object.assign(formDetailOKData, { ...defaultFormDetailData, resultType: 1, handleAction: '下一道工序', actionStatus: 1 })
|
Object.assign(formDetailOKData, { ...defaultFormDetailData, resultType: 1, handleAction: '下一道工序', actionStatus: 1 })
|
||||||
Object.assign(formDetailScrapData, { ...defaultFormDetailData, resultType: 2, handleAction: '报废次品', actionStatus: 1 })
|
Object.assign(formDetailScrapData, { ...defaultFormDetailData, resultType: 2, handleAction: '报废次品', actionStatus: 1 })
|
||||||
Object.assign(formDetailReworkData, { ...defaultFormDetailData, resultType: 3, handleAction: '返工', actionStatus: 1 })
|
Object.assign(formDetailReworkData, { ...defaultFormDetailData, resultType: 3, handleAction: '返工', actionStatus: 1 })
|
||||||
Object.assign(formDetailConcessionData, { ...defaultFormDetailData, resultType: 4, handleAction: '让步接收', actionStatus: 1 })
|
Object.assign(formDetailConcessionData, { ...defaultFormDetailData, resultType: 4, handleAction: '让步接收', actionStatus: 1 })
|
||||||
|
|
||||||
imageList.value = []
|
|
||||||
uploadImages.value = []
|
|
||||||
fileList.value = []
|
|
||||||
uploadFiles.value = []
|
|
||||||
|
|
||||||
message.info("表单已重置")
|
message.info("表单已重置")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onMounted(()=>{
|
async function loadData() {
|
||||||
|
|
||||||
|
if(qcId.value != null || qcId.value != '') {
|
||||||
|
//根据qcId查询明细
|
||||||
|
const resultList = await qcResultDetailApi.selectlistByqcId(qcId.value);
|
||||||
|
Object.assign(formDetailOKData,{...resultList.formDetailOKData})
|
||||||
|
Object.assign(formDetailScrapData,{...resultList.formDetailScrapData})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(()=>{
|
||||||
|
loadData()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@ -4,8 +4,8 @@
|
|||||||
<!-- 搜索表单 -->
|
<!-- 搜索表单 -->
|
||||||
<div class="search-form">
|
<div class="search-form">
|
||||||
<n-form inline :model="searchForm" label-placement="left">
|
<n-form inline :model="searchForm" label-placement="left">
|
||||||
<n-form-item label="工位编码">
|
<n-form-item label="主键ID">
|
||||||
<n-input v-model:value="searchForm.stationCode" placeholder="请输入工位编码" clearable />
|
<n-input v-model:value="searchForm.deviceCode" placeholder="请输入工位编码" clearable />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item>
|
<n-form-item>
|
||||||
<n-space>
|
<n-space>
|
||||||
@ -61,10 +61,10 @@
|
|||||||
<!-- 新增/编辑弹窗 -->
|
<!-- 新增/编辑弹窗 -->
|
||||||
<n-modal v-model:show="modalVisible" preset="card" :title="modalTitle" style="width: 600px">
|
<n-modal v-model:show="modalVisible" preset="card" :title="modalTitle" style="width: 600px">
|
||||||
<n-form ref="formRef" :model="formData" :rules="formRules" label-placement="left" label-width="100px">
|
<n-form ref="formRef" :model="formData" :rules="formRules" label-placement="left" label-width="100px">
|
||||||
<n-form-item label="工位名称" path="stationName">
|
<n-form-item label="工位名称" path="deviceName">
|
||||||
<n-input v-model:value="formData.stationName" placeholder="请输入工位名称" />
|
<n-input v-model:value="formData.deviceName" placeholder="请输入设备名称" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="所属工段" path="sectionId">
|
<n-form-item label="所属工段" path="sectionId">
|
||||||
<n-select
|
<n-select
|
||||||
v-model:value="formData.sectionId"
|
v-model:value="formData.sectionId"
|
||||||
:options="sectionList"
|
:options="sectionList"
|
||||||
@ -92,7 +92,7 @@
|
|||||||
</n-modal>
|
</n-modal>
|
||||||
|
|
||||||
<!-- 导入弹窗 -->
|
<!-- 导入弹窗 -->
|
||||||
<n-modal v-model:show="importModalVisible" preset="card" title="导入工位表" style="width: 500px">
|
<n-modal v-model:show="importModalVisible" preset="card" title="导入设备表" style="width: 500px">
|
||||||
<n-space vertical>
|
<n-space vertical>
|
||||||
<n-alert type="info">
|
<n-alert type="info">
|
||||||
<template #header>导入说明</template>
|
<template #header>导入说明</template>
|
||||||
@ -128,20 +128,24 @@
|
|||||||
import { ref, reactive, h, onMounted } from 'vue'
|
import { ref, reactive, h, onMounted } from 'vue'
|
||||||
import { NButton, NSpace,NTag, NIcon, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
|
import { NButton, NSpace,NTag, NIcon, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
|
||||||
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline } from '@vicons/ionicons5'
|
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline } from '@vicons/ionicons5'
|
||||||
import { stationApi, type Station } from '@/api/station'
|
import { deviceApi, type Device } from '@/api/device'
|
||||||
|
|
||||||
import { dictDataApi } from '@/api/org'
|
import { dictDataApi } from '@/api/org'
|
||||||
import {sectionApi, type Section} from '@/api/section'
|
import {sectionApi, type Section} from '@/api/section'
|
||||||
|
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
|
|
||||||
|
let sectionList = reactive<{label:string,value:any}[]>([])
|
||||||
|
|
||||||
// 搜索表单
|
// 搜索表单
|
||||||
const searchForm = reactive({
|
const searchForm = reactive({
|
||||||
stationCode: null as string | null,
|
deviceCode: null as number | null,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 表格数据
|
// 表格数据
|
||||||
const tableData = ref<Station[]>([])
|
const tableData = ref<Device[]>([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const selectedIds = ref<number[]>([])
|
const selectedIds = ref<number[]>([])
|
||||||
const pagination = reactive({
|
const pagination = reactive({
|
||||||
@ -157,42 +161,48 @@ const modalVisible = ref(false)
|
|||||||
const modalTitle = ref('')
|
const modalTitle = ref('')
|
||||||
const importModalVisible = ref(false)
|
const importModalVisible = ref(false)
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const defaultFormData: Station = {
|
const defaultFormData: Device = {
|
||||||
id:'',
|
id:"",
|
||||||
stationCode: '',
|
deviceCode: '',
|
||||||
stationName: '',
|
deviceName: '',
|
||||||
sectionId: undefined,
|
deviceTypeId: undefined,
|
||||||
status:undefined,
|
deviceType:"",
|
||||||
remark:undefined
|
sectionId: "",
|
||||||
|
sectionName:"",
|
||||||
|
spec: '',
|
||||||
|
manufacturer: '',
|
||||||
|
manufactureDate: undefined,
|
||||||
|
workshopId: '',
|
||||||
|
remark: '',
|
||||||
|
status:0,
|
||||||
|
deviceFlag:'mes_station'
|
||||||
}
|
}
|
||||||
const formData = reactive<Station>({ ...defaultFormData })
|
const formData = reactive<Device>({ ...defaultFormData })
|
||||||
|
|
||||||
let sectionList = reactive<{label:string,value:any}[]>([])
|
|
||||||
|
|
||||||
// 字典选项(下拉框/单选框/复选框关联字典时使用)
|
// 字典选项(下拉框/单选框/复选框关联字典时使用)
|
||||||
|
const deviceTypeList = ref<{ label: string; value: any ;class:any}[]>([])
|
||||||
const statusList = ref<{ label: string; value: any ;class:any}[]>([])
|
const statusList = ref<{ label: string; value: any ;class:any}[]>([])
|
||||||
|
|
||||||
|
const deviceFlagList = ref<{ label: string; value: any ;class:any}[]>([])
|
||||||
|
|
||||||
// 表单校验规则
|
// 表单校验规则
|
||||||
const formRules = {
|
const formRules = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表格列
|
// 表格列
|
||||||
const columns: DataTableColumns<Station> = [
|
const columns: DataTableColumns<Device> = [
|
||||||
{ type: 'selection' },
|
{ type: 'selection' },
|
||||||
{ title: '工位编码', key: 'stationCode' },
|
{ title: '工位编码', key: 'deviceCode' },
|
||||||
{ title: '工位名称', key: 'stationName' },
|
{ title: '工位名称', key: 'deviceName' },
|
||||||
{ title: '所属工段', key: 'sectionName' },
|
{ title: '所属工段', key: 'sectionName' },
|
||||||
{ title: '备注', key: 'remark' },
|
{ title: '状态', key: 'status',
|
||||||
{ title: '启用状态', key: 'status',
|
|
||||||
render:(row) =>{
|
render:(row) =>{
|
||||||
const val = row.status
|
const val = row.status
|
||||||
const opt = statusList.value.find(o => o.value === val || String(o.value) === String(val))
|
const opt = statusList.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
if (!opt) return val ?? '-'
|
if (!opt) return val ?? '-'
|
||||||
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ title: '创建时间', key: 'createTime' },
|
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
@ -215,10 +225,11 @@ const columns: DataTableColumns<Station> = [
|
|||||||
async function loadData() {
|
async function loadData() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await stationApi.page({
|
const res = await deviceApi.page({
|
||||||
page: pagination.page,
|
page: pagination.page,
|
||||||
pageSize: pagination.pageSize,
|
pageSize: pagination.pageSize,
|
||||||
stationCode: searchForm.stationCode || undefined,
|
deviceCode:searchForm.deviceCode,
|
||||||
|
deviceFlag:"mes_station"
|
||||||
})
|
})
|
||||||
tableData.value = res.list
|
tableData.value = res.list
|
||||||
pagination.itemCount = res.total
|
pagination.itemCount = res.total
|
||||||
@ -235,7 +246,7 @@ function handleSearch() {
|
|||||||
|
|
||||||
// 重置
|
// 重置
|
||||||
function handleReset() {
|
function handleReset() {
|
||||||
searchForm.stationCode = null
|
searchForm.deviceCode = null
|
||||||
|
|
||||||
handleSearch()
|
handleSearch()
|
||||||
}
|
}
|
||||||
@ -259,14 +270,16 @@ function handleCheck(keys: Array<string | number>) {
|
|||||||
|
|
||||||
// 新增
|
// 新增
|
||||||
function handleAdd() {
|
function handleAdd() {
|
||||||
modalTitle.value = '新增工位表'
|
modalTitle.value = '新增设备表'
|
||||||
Object.assign(formData, defaultFormData)
|
Object.assign(formData, defaultFormData)
|
||||||
modalVisible.value = true
|
modalVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 编辑
|
// 编辑
|
||||||
function handleEdit(row: Station) {
|
function handleEdit(row: Device) {
|
||||||
modalTitle.value = '编辑工位表'
|
modalTitle.value = '编辑设备表'
|
||||||
|
console.log(row);
|
||||||
|
|
||||||
Object.assign(formData, row)
|
Object.assign(formData, row)
|
||||||
modalVisible.value = true
|
modalVisible.value = true
|
||||||
}
|
}
|
||||||
@ -275,12 +288,12 @@ function handleEdit(row: Station) {
|
|||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
await formRef.value?.validate()
|
await formRef.value?.validate()
|
||||||
try {
|
try {
|
||||||
const submitData = { ...formData } as Station
|
const submitData = { ...formData } as Device
|
||||||
if (submitData.id) {
|
if (submitData.id) {
|
||||||
await stationApi.update(submitData)
|
await deviceApi.update(submitData)
|
||||||
message.success('修改成功')
|
message.success('修改成功')
|
||||||
} else {
|
} else {
|
||||||
await stationApi.create(submitData)
|
await deviceApi.create(submitData)
|
||||||
message.success('新增成功')
|
message.success('新增成功')
|
||||||
}
|
}
|
||||||
modalVisible.value = false
|
modalVisible.value = false
|
||||||
@ -291,7 +304,7 @@ async function handleSubmit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 删除
|
// 删除
|
||||||
function handleDelete(row: Station) {
|
function handleDelete(row: Device) {
|
||||||
dialog.warning({
|
dialog.warning({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
content: '确定要删除该记录吗?',
|
content: '确定要删除该记录吗?',
|
||||||
@ -299,7 +312,7 @@ function handleDelete(row: Station) {
|
|||||||
negativeText: '取消',
|
negativeText: '取消',
|
||||||
onPositiveClick: async () => {
|
onPositiveClick: async () => {
|
||||||
try {
|
try {
|
||||||
await stationApi.delete([row.id!])
|
await deviceApi.delete([row.id!])
|
||||||
message.success('删除成功')
|
message.success('删除成功')
|
||||||
loadData()
|
loadData()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -318,7 +331,7 @@ function handleBatchDelete() {
|
|||||||
negativeText: '取消',
|
negativeText: '取消',
|
||||||
onPositiveClick: async () => {
|
onPositiveClick: async () => {
|
||||||
try {
|
try {
|
||||||
await stationApi.delete(selectedIds.value)
|
await deviceApi.delete(selectedIds.value)
|
||||||
message.success('删除成功')
|
message.success('删除成功')
|
||||||
selectedIds.value = []
|
selectedIds.value = []
|
||||||
loadData()
|
loadData()
|
||||||
@ -334,12 +347,12 @@ async function handleExport() {
|
|||||||
try {
|
try {
|
||||||
const params: Record<string, any> = {}
|
const params: Record<string, any> = {}
|
||||||
if (selectedIds.value.length > 0) params.ids = selectedIds.value
|
if (selectedIds.value.length > 0) params.ids = selectedIds.value
|
||||||
if (searchForm.stationCode != null) params.stationCode = searchForm.stationCode
|
if (searchForm.deviceCode != null) params.deviceCode = searchForm.deviceCode
|
||||||
const blob = await stationApi.export(params)
|
const blob = await deviceApi.export(params)
|
||||||
const url = window.URL.createObjectURL(blob)
|
const url = window.URL.createObjectURL(blob)
|
||||||
const link = document.createElement('a')
|
const link = document.createElement('a')
|
||||||
link.href = url
|
link.href = url
|
||||||
link.download = '工位表数据.xlsx'
|
link.download = '设备表数据.xlsx'
|
||||||
link.click()
|
link.click()
|
||||||
window.URL.revokeObjectURL(url)
|
window.URL.revokeObjectURL(url)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -350,11 +363,11 @@ async function handleExport() {
|
|||||||
// 下载导入模板
|
// 下载导入模板
|
||||||
async function handleDownloadTemplate() {
|
async function handleDownloadTemplate() {
|
||||||
try {
|
try {
|
||||||
const blob = await stationApi.downloadTemplate()
|
const blob = await deviceApi.downloadTemplate()
|
||||||
const url = window.URL.createObjectURL(blob)
|
const url = window.URL.createObjectURL(blob)
|
||||||
const link = document.createElement('a')
|
const link = document.createElement('a')
|
||||||
link.href = url
|
link.href = url
|
||||||
link.download = '工位表导入模板.xlsx'
|
link.download = '设备表导入模板.xlsx'
|
||||||
link.click()
|
link.click()
|
||||||
window.URL.revokeObjectURL(url)
|
window.URL.revokeObjectURL(url)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -366,7 +379,7 @@ async function handleDownloadTemplate() {
|
|||||||
async function handleImportUpload({ file }: UploadCustomRequestOptions) {
|
async function handleImportUpload({ file }: UploadCustomRequestOptions) {
|
||||||
if (!file.file) return
|
if (!file.file) return
|
||||||
try {
|
try {
|
||||||
const result = await stationApi.importData(file.file)
|
const result = await deviceApi.importData(file.file)
|
||||||
if (result.fail > 0) {
|
if (result.fail > 0) {
|
||||||
dialog.warning({
|
dialog.warning({
|
||||||
title: '导入结果',
|
title: '导入结果',
|
||||||
@ -385,10 +398,19 @@ async function handleImportUpload({ file }: UploadCustomRequestOptions) {
|
|||||||
|
|
||||||
// 加载字典选项
|
// 加载字典选项
|
||||||
async function loadDictOptions() {
|
async function loadDictOptions() {
|
||||||
try {
|
try {
|
||||||
const data = await dictDataApi.listByType("sys_status")
|
const data = await dictDataApi.listByType("device_type")
|
||||||
statusList.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
deviceTypeList.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||||
}catch {}
|
}catch {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await dictDataApi.listByType("sys_status")
|
||||||
|
statusList.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||||
|
}catch {}
|
||||||
|
try {
|
||||||
|
const data = await dictDataApi.listByType("mes_device_flag")
|
||||||
|
deviceFlagList.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||||
|
}catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -4,10 +4,10 @@
|
|||||||
<!-- 搜索表单 -->
|
<!-- 搜索表单 -->
|
||||||
<div class="search-form">
|
<div class="search-form">
|
||||||
<n-form inline :model="searchForm" label-placement="left">
|
<n-form inline :model="searchForm" label-placement="left">
|
||||||
<n-form-item label="派工状态">
|
<n-form-item label="汇报状态">
|
||||||
<n-select
|
<n-select
|
||||||
v-model:value="searchForm.assingStatus"
|
v-model:value="searchForm.submitStatus"
|
||||||
:options="qcAssingStatusOptions"
|
:options="submitStatusOptions"
|
||||||
placeholder="请选择派工状态"
|
placeholder="请选择派工状态"
|
||||||
clearable
|
clearable
|
||||||
style="width: 200px"
|
style="width: 200px"
|
||||||
@ -31,18 +31,19 @@
|
|||||||
<!-- 工具栏 -->
|
<!-- 工具栏 -->
|
||||||
<div class="table-toolbar">
|
<div class="table-toolbar">
|
||||||
<n-space>
|
<n-space>
|
||||||
<n-button type="primary" @click="handleStatistics">
|
<n-button type="primary" @click="handleStatistics" disabled>
|
||||||
<template #icon></template>
|
<template #icon></template>
|
||||||
汇报统计
|
汇报统计
|
||||||
</n-button>
|
</n-button>
|
||||||
<!-- <n-button @click="importModalVisible = true">
|
|
||||||
<template #icon><n-icon><CloudUploadOutline /></n-icon></template>
|
|
||||||
导入
|
|
||||||
</n-button>
|
|
||||||
<n-button @click="handleExport">
|
<n-button @click="handleExport">
|
||||||
<template #icon><n-icon><DownloadOutline /></n-icon></template>
|
<template #icon><n-icon><DownloadOutline /></n-icon></template>
|
||||||
导出{{ selectedIds.length > 0 ? `(${selectedIds.length})` : '' }}
|
导出{{ selectedIds.length > 0 ? `(${selectedIds.length})` : '' }}
|
||||||
</n-button>
|
</n-button>
|
||||||
|
<!-- <n-button @click="importModalVisible = true">
|
||||||
|
<template #icon><n-icon><CloudUploadOutline /></n-icon></template>
|
||||||
|
导入
|
||||||
|
</n-button>
|
||||||
|
|
||||||
<n-button type="error" :disabled="selectedIds.length === 0" @click="handleBatchDelete">
|
<n-button type="error" :disabled="selectedIds.length === 0" @click="handleBatchDelete">
|
||||||
<template #icon><n-icon><TrashOutline /></n-icon></template>
|
<template #icon><n-icon><TrashOutline /></n-icon></template>
|
||||||
删除
|
删除
|
||||||
@ -211,7 +212,7 @@ const pagination = reactive({
|
|||||||
// 搜索表单
|
// 搜索表单
|
||||||
const searchForm = reactive({
|
const searchForm = reactive({
|
||||||
page:pagination.page as number,
|
page:pagination.page as number,
|
||||||
assingStatus:undefined
|
submitStatus:undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
// 弹窗
|
// 弹窗
|
||||||
@ -235,15 +236,14 @@ const qcResultTypeOptions = ref<{ label: string; value: any;class:any }[]>([])
|
|||||||
const qcSourceTypeOptions = ref<{ label: string; value: any;class:any }[]>([])
|
const qcSourceTypeOptions = ref<{ label: string; value: any;class:any }[]>([])
|
||||||
//质检状态字典
|
//质检状态字典
|
||||||
const qcStatusOptions = ref<{ label: string; value: any;class:any }[]>([])
|
const qcStatusOptions = ref<{ label: string; value: any;class:any }[]>([])
|
||||||
//派工状态字典
|
//汇报状态字典
|
||||||
const qcAssingStatusOptions = ref<{ label: string; value: any ;class:any}[]>([])
|
const submitStatusOptions = ref<{ label: string; value: any ;class:any}[]>([])
|
||||||
// 表单校验规则
|
// 表单校验规则
|
||||||
const formRules = {
|
const formRules = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表格列
|
// 表格列
|
||||||
const columns: DataTableColumns<SubmitLog> = [
|
const columns: DataTableColumns<SubmitLog> = [
|
||||||
{ type: 'selection' },
|
|
||||||
{ title: '物料名称', key: 'materialName',align: 'center',
|
{ title: '物料名称', key: 'materialName',align: 'center',
|
||||||
render(rowData, rowIndex) {
|
render(rowData, rowIndex) {
|
||||||
return rowData.materialName? rowData.materialName: '-'
|
return rowData.materialName? rowData.materialName: '-'
|
||||||
@ -258,40 +258,22 @@ const columns: DataTableColumns<SubmitLog> = [
|
|||||||
render(rowData, rowIndex) {
|
render(rowData, rowIndex) {
|
||||||
return rowData.materialQty? rowData.materialQty: '-'
|
return rowData.materialQty? rowData.materialQty: '-'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ title: '工序名称', key: 'processName',align: 'center' },
|
{ title: '工序名称', key: 'processName',align: 'center' },
|
||||||
{ title: '工序编号', key: 'processCode',align: 'center' },
|
{ title: '工序编号', key: 'processCode',align: 'center' },
|
||||||
|
{ title: '汇报数量', key: 'quantity',align: 'center'},
|
||||||
{ title: '派工编号', key: 'assingCode',align: 'center' },
|
{ title: '派工编号', key: 'assingCode',align: 'center' },
|
||||||
{ title: '派工人', key: 'userName',align: 'center' },
|
{ title: '汇报人', key: 'userName',align: 'center' },
|
||||||
{ title: '派工状态', key: 'assingStatus',align: 'center',
|
{ title: '派工状态', key: 'submitStatus',align: 'center',
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
const val = row.assingStatus
|
const val = row.submitStatus
|
||||||
const opt = qcAssingStatusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
const opt = submitStatusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
if (!opt) return val ?? '-'
|
if (!opt) return val ?? '-'
|
||||||
|
|
||||||
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ title: '派工完成时间', key: 'completedTime',align: 'center'},
|
{ title: '撤回原因', key: 'remark',align: 'center' },
|
||||||
{
|
|
||||||
title: '操作',
|
|
||||||
key: 'actions',
|
|
||||||
width: 140,
|
|
||||||
fixed: 'right',
|
|
||||||
render(row) {
|
|
||||||
return h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'nowrap' } }, [
|
|
||||||
h(NButton, { size: 'small', quaternary: true, onClick: () => handleViewReport(row) }, {
|
|
||||||
default: () => ['汇报信息']
|
|
||||||
}),
|
|
||||||
// h(NButton, { size: 'small', quaternary: true, onClick: () => handleEdit(row) }, {
|
|
||||||
// default: () => [h(NIcon, null, { default: () => h(CreateOutline) }), ' 编辑']
|
|
||||||
// }),
|
|
||||||
// h(NButton, { size: 'small', quaternary: true, type: 'error', onClick: () => handleDelete(row) }, {
|
|
||||||
// default: () => [h(NIcon, null, { default: () => h(TrashOutline) }), ' 删除']
|
|
||||||
// })
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
//汇报详情属性
|
//汇报详情属性
|
||||||
@ -317,7 +299,7 @@ async function loadData() {
|
|||||||
const res = await submitLogApi.page({
|
const res = await submitLogApi.page({
|
||||||
page: pagination.page,
|
page: pagination.page,
|
||||||
pageSize: pagination.pageSize,
|
pageSize: pagination.pageSize,
|
||||||
assingStatus: searchForm.assingStatus
|
submitStatus: searchForm.submitStatus
|
||||||
})
|
})
|
||||||
tableData.value = res.list
|
tableData.value = res.list
|
||||||
pagination.itemCount = res.total
|
pagination.itemCount = res.total
|
||||||
@ -336,7 +318,7 @@ function handleSearch() {
|
|||||||
|
|
||||||
// 重置
|
// 重置
|
||||||
function handleReset() {
|
function handleReset() {
|
||||||
searchForm.assingStatus = undefined
|
searchForm.submitStatus = undefined
|
||||||
|
|
||||||
handleSearch()
|
handleSearch()
|
||||||
}
|
}
|
||||||
@ -441,7 +423,7 @@ async function handleExport() {
|
|||||||
try {
|
try {
|
||||||
const params: Record<string, any> = {}
|
const params: Record<string, any> = {}
|
||||||
if (selectedIds.value.length > 0) params.ids = selectedIds.value
|
if (selectedIds.value.length > 0) params.ids = selectedIds.value
|
||||||
if (searchForm.assingStatus != null) params.id = searchForm.assingStatus
|
if (searchForm.submitStatus != null) params.id = searchForm.submitStatus
|
||||||
const blob = await submitLogApi.export(params)
|
const blob = await submitLogApi.export(params)
|
||||||
const url = window.URL.createObjectURL(blob)
|
const url = window.URL.createObjectURL(blob)
|
||||||
const link = document.createElement('a')
|
const link = document.createElement('a')
|
||||||
@ -505,8 +487,8 @@ async function loadDictOptions() {
|
|||||||
qcStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
qcStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||||
}catch {}
|
}catch {}
|
||||||
try {
|
try {
|
||||||
const data = await dictDataApi.listByType('assing_status')
|
const data = await dictDataApi.listByType('submit_status')
|
||||||
qcAssingStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
submitStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||||
}catch {}
|
}catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -496,6 +496,24 @@ const columns = [
|
|||||||
key: 'processName',
|
key: 'processName',
|
||||||
minWidth: 200
|
minWidth: 200
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
align:'center',
|
||||||
|
title:"追溯类型",
|
||||||
|
key:"traceType",
|
||||||
|
render(row:any) {
|
||||||
|
const val = row.traceType
|
||||||
|
if(val == 1) return '-'
|
||||||
|
if(val == 2) return '追溯码'
|
||||||
|
if(val == 3) return '批次码'
|
||||||
|
},
|
||||||
|
minWidth:80
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// align:'center',
|
||||||
|
// title:"追溯码/批次号",
|
||||||
|
// key:"traceCode",
|
||||||
|
// minWidth:200
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
align:'center',
|
align:'center',
|
||||||
title: '派工状态',
|
title: '派工状态',
|
||||||
@ -557,6 +575,26 @@ const columns = [
|
|||||||
key: 'reworkNum',
|
key: 'reworkNum',
|
||||||
minWidth: 150
|
minWidth: 150
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
align:'center',
|
||||||
|
title:"是否质检",
|
||||||
|
key:"isQual",
|
||||||
|
minWidth:100,
|
||||||
|
render(row:any) {
|
||||||
|
const qual = row.isQual === 1
|
||||||
|
return h(NTag, {type: qual ? 'error' : 'success', size: 'small'}, {default: () => (qual ? '是' : '否')})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align:'center',
|
||||||
|
title:"是否直接入库",
|
||||||
|
key:"isStorage",
|
||||||
|
minWidth:120,
|
||||||
|
render(row:any) {
|
||||||
|
const storage = row.isQual === 1
|
||||||
|
return h(NTag, {type: storage ? 'error' : 'success', size: 'small'}, {default: () => (storage ? '是' : '否')})
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
align:'center',
|
align:'center',
|
||||||
title: '完成数量',
|
title: '完成数量',
|
||||||
|
|||||||
@ -120,24 +120,21 @@
|
|||||||
style="width: 400px"
|
style="width: 400px"
|
||||||
:mask-closable="false"
|
:mask-closable="false"
|
||||||
>
|
>
|
||||||
|
<AssingWorkVue
|
||||||
|
:id="gxobg.id"
|
||||||
|
:qcNumDisabled ="false"
|
||||||
|
@updateQcNum="handleUpdateQcNum"
|
||||||
|
/>
|
||||||
|
<SumbitDetailCard
|
||||||
|
:showOkTab="true"
|
||||||
|
:showScrapTab="true"
|
||||||
|
:showReworkTab = false
|
||||||
|
:showTraceCode="false"
|
||||||
|
:qcId="-1"
|
||||||
|
@updateSubmitDetail="handleSubmitDetail"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
<n-form
|
|
||||||
ref="pauseformRef"
|
|
||||||
:model="pauseform"
|
|
||||||
:rules="pauserules"
|
|
||||||
label-placement="top"
|
|
||||||
label-width="80"
|
|
||||||
>
|
|
||||||
<n-form-item label="工序编号">
|
|
||||||
<n-input v-model:value="gxobg.id" disabled />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="工序名称">
|
|
||||||
<n-input v-model:value="gxobg.name" disabled />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="报检数量" path="qcNum">
|
|
||||||
<n-input v-model:value="pauseform.qcNum" placeholder="请输入报检数量" />
|
|
||||||
</n-form-item>
|
|
||||||
</n-form>
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<n-space justify="end">
|
<n-space justify="end">
|
||||||
<n-button @click="pausemodal = false">取消</n-button>
|
<n-button @click="pausemodal = false">取消</n-button>
|
||||||
@ -146,11 +143,43 @@
|
|||||||
:loading="pauseLoading"
|
:loading="pauseLoading"
|
||||||
@click="pauseSubmit"
|
@click="pauseSubmit"
|
||||||
:disabled="pauseisedbtn"
|
:disabled="pauseisedbtn"
|
||||||
>保存</n-button>
|
>确认</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
|
|
||||||
|
<!-- 质检撤回弹框 -->
|
||||||
|
<n-modal
|
||||||
|
v-model:show="qcCancelModel"
|
||||||
|
title="提示"
|
||||||
|
preset="card"
|
||||||
|
style="width: 400px"
|
||||||
|
:mask-closable="false"
|
||||||
|
>
|
||||||
|
<n-card title="确定要撤回质检记录吗?">
|
||||||
|
<n-form ref="formRef" :model="qcCancelFormData" label-placement="left" label-width="100px">
|
||||||
|
<n-form-item label="备注" label-width="100">
|
||||||
|
<n-input
|
||||||
|
v-model:value="qcCancelFormData.remark"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="输入撤回原因"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
<template #footer>
|
||||||
|
<n-space justify="end">
|
||||||
|
<n-button @click="qcCancelModel = false">取消</n-button>
|
||||||
|
<n-button
|
||||||
|
type="primary"
|
||||||
|
|
||||||
|
@click="cancelSubmit"
|
||||||
|
>确认</n-button>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
</n-modal>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -174,7 +203,7 @@ import {
|
|||||||
// NGi,
|
// NGi,
|
||||||
NTag,
|
NTag,
|
||||||
useMessage,
|
useMessage,
|
||||||
//useDialog,
|
useDialog,
|
||||||
//type FormInst,
|
//type FormInst,
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
|
|
||||||
@ -191,8 +220,12 @@ import {
|
|||||||
} from '@/api/production'
|
} from '@/api/production'
|
||||||
import PlmModelDrawer from '@/components/PlmModelDrawer.vue'
|
import PlmModelDrawer from '@/components/PlmModelDrawer.vue'
|
||||||
import type { PlmModelOpenContext } from '@/api/plmModel'
|
import type { PlmModelOpenContext } from '@/api/plmModel'
|
||||||
|
import AssingWorkVue from '@/views/biz/flowingAround/assingWork.vue'
|
||||||
|
import { qualityTestingApi, type QualityTesting } from '@/api/qualityTesting'
|
||||||
|
import SumbitDetailCard from '@/components/SumbitDetailCard.vue'
|
||||||
|
import { SubmitLog } from '@/api/submitLog'
|
||||||
|
|
||||||
//const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
@ -431,7 +464,7 @@ const columns = [
|
|||||||
align:'center',
|
align:'center',
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
width: 100,
|
width: 250,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
render(row:any) {
|
render(row:any) {
|
||||||
const buttons:any = []
|
const buttons:any = []
|
||||||
@ -442,7 +475,7 @@ const columns = [
|
|||||||
type:'primary',
|
type:'primary',
|
||||||
ghost:true,
|
ghost:true,
|
||||||
onClick: () => { handlpause(row) } },
|
onClick: () => { handlpause(row) } },
|
||||||
{ default: () => '报检'}
|
{ default: () => '完工汇报'}
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
buttons.push(h(NButton, {
|
buttons.push(h(NButton, {
|
||||||
@ -452,6 +485,13 @@ const columns = [
|
|||||||
onClick: () => openPlmModel(row),
|
onClick: () => openPlmModel(row),
|
||||||
}, { default: () => '模型' }))
|
}, { default: () => '模型' }))
|
||||||
|
|
||||||
|
buttons.push(h(NButton, {
|
||||||
|
size: 'small',
|
||||||
|
type: 'info',
|
||||||
|
ghost: true,
|
||||||
|
onClick: ()=> cancelQuality(row)
|
||||||
|
}, { default: () => '质检撤回' }))
|
||||||
|
|
||||||
return buttons.length > 0 ? h(NSpace, {justify:'center'}, { default: () => buttons }) : '-'
|
return buttons.length > 0 ? h(NSpace, {justify:'center'}, { default: () => buttons }) : '-'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -506,7 +546,29 @@ let gxobg = reactive<any>({})
|
|||||||
let pausemodal = ref(false)
|
let pausemodal = ref(false)
|
||||||
let pauseform = reactive<any>({
|
let pauseform = reactive<any>({
|
||||||
id:'',
|
id:'',
|
||||||
qcNum:''
|
qcNum:'',
|
||||||
|
okData:{
|
||||||
|
id:undefined,
|
||||||
|
qcId:undefined,
|
||||||
|
resultType:undefined,
|
||||||
|
qty:0,
|
||||||
|
traceCode:undefined,
|
||||||
|
defectCode:undefined,
|
||||||
|
handleAction:undefined,
|
||||||
|
actionStatus:undefined,
|
||||||
|
remark:undefined,
|
||||||
|
},
|
||||||
|
scrapData:{
|
||||||
|
id:undefined,
|
||||||
|
qcId:undefined,
|
||||||
|
resultType:undefined,
|
||||||
|
qty:0,
|
||||||
|
traceCode:undefined,
|
||||||
|
defectCode:undefined,
|
||||||
|
handleAction:undefined,
|
||||||
|
actionStatus:undefined,
|
||||||
|
remark:undefined,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
const pauseformRef = ref()
|
const pauseformRef = ref()
|
||||||
|
|
||||||
@ -532,6 +594,18 @@ const pauserules = {
|
|||||||
let pauseLoading = ref(false)
|
let pauseLoading = ref(false)
|
||||||
let pauseisedbtn = ref(false)
|
let pauseisedbtn = ref(false)
|
||||||
|
|
||||||
|
|
||||||
|
// 质检撤回
|
||||||
|
const qcCancelModel = ref<Boolean>(false)
|
||||||
|
const qcCancelFormData = ref<SubmitLog>({
|
||||||
|
assingWorkId:undefined,
|
||||||
|
remark:undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
let qcCancelLoading = ref<Boolean>(false)
|
||||||
|
let qcDisabledBtn = ref<Boolean>(false)
|
||||||
|
|
||||||
|
|
||||||
function handlpause(n:any) {
|
function handlpause(n:any) {
|
||||||
gxobg.id = n.id
|
gxobg.id = n.id
|
||||||
gxobg.name = n.name
|
gxobg.name = n.name
|
||||||
@ -542,13 +616,69 @@ function handlpause(n:any) {
|
|||||||
pauseformRef.value?.restoreValidation()
|
pauseformRef.value?.restoreValidation()
|
||||||
pauseform.id = n.id
|
pauseform.id = n.id
|
||||||
//pauseform.qcNum = `${n.quantity}`
|
//pauseform.qcNum = `${n.quantity}`
|
||||||
pauseform.qcNum = n.quantity-(n.completedQuantity ?? 0)
|
// pauseform.qcNum = n.quantity-(n.completedQuantity ?? 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkPauseSumbit():boolean {
|
||||||
|
if(pauseform.id == null) {
|
||||||
|
message.error("参数id为空")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pauseform.qcNum == null || pauseform.qcNum == '') {
|
||||||
|
message.error("质检数量不能为空")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pauseform.okData == null || pauseform.okData == '' ) {
|
||||||
|
pauseform.okData={
|
||||||
|
id:undefined,
|
||||||
|
qcId:undefined,
|
||||||
|
resultType:undefined,
|
||||||
|
qty:0,
|
||||||
|
traceCode:undefined,
|
||||||
|
defectCode:undefined,
|
||||||
|
handleAction:undefined,
|
||||||
|
actionStatus:undefined,
|
||||||
|
remark:undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pauseform.scrapData == null || pauseform.scrapData == '' ) {
|
||||||
|
pauseform.scrapData={
|
||||||
|
id:undefined,
|
||||||
|
qcId:undefined,
|
||||||
|
resultType:undefined,
|
||||||
|
qty:0,
|
||||||
|
traceCode:undefined,
|
||||||
|
defectCode:undefined,
|
||||||
|
handleAction:undefined,
|
||||||
|
actionStatus:undefined,
|
||||||
|
remark:undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//检查合格数量和报废数量总和是不是等于提交总数
|
||||||
|
const okQty = pauseform.okData?.qty ?? 0
|
||||||
|
const scrapQty = pauseform.scrapData?.qty ?? 0
|
||||||
|
const checkNum = okQty + scrapQty
|
||||||
|
|
||||||
|
if(checkNum != pauseform.qcNum) {
|
||||||
|
message.error("汇报数量总和不等于提交数量")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
function pauseSubmit() {
|
function pauseSubmit() {
|
||||||
pauseformRef.value?.validate((v:any) => {
|
|
||||||
if(!v){
|
var pass = checkPauseSumbit();
|
||||||
pauseLoading.value = true
|
|
||||||
|
console.log(pass);
|
||||||
|
|
||||||
|
if(!pass) return
|
||||||
|
|
||||||
|
pauseLoading.value = true
|
||||||
pauseisedbtn.value = true
|
pauseisedbtn.value = true
|
||||||
inspectio(Object.assign({},pauseform,{qcNum:pauseform.qcNum*1})).then(() => {
|
inspectio(Object.assign({},pauseform,{qcNum:pauseform.qcNum*1})).then(() => {
|
||||||
message.success('操作成功!')
|
message.success('操作成功!')
|
||||||
@ -561,13 +691,24 @@ function pauseSubmit() {
|
|||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
pauseLoading.value = false
|
pauseLoading.value = false
|
||||||
pauseisedbtn.value = false
|
pauseisedbtn.value = false
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleUpdateQcNum(qcNum:number) {
|
||||||
|
pauseform.qcNum = qcNum
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmitDetail(submitDetail:any){
|
||||||
|
pauseform.okData = submitDetail.okData
|
||||||
|
pauseform.scrapData = submitDetail.scrapData
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// 加载字典选项
|
// 加载字典选项
|
||||||
async function loadDictOptions() {
|
async function loadDictOptions() {
|
||||||
|
qcCancelLoading = true
|
||||||
try {
|
try {
|
||||||
const data = await dictDataApi.listByType('assing_status')
|
const data = await dictDataApi.listByType('assing_status')
|
||||||
qcAssingStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
qcAssingStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||||
@ -588,10 +729,43 @@ async function loadDictOptions() {
|
|||||||
// }
|
// }
|
||||||
// })
|
// })
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
function cancelQuality(row:any) {
|
||||||
|
qcCancelModel.value = true
|
||||||
|
qcCancelFormData.value.assingWorkId = row.id
|
||||||
|
qcCancelLoading.value = true
|
||||||
|
qcDisabledBtn.value = true
|
||||||
|
// dialog.warning({
|
||||||
|
// title: '提示',
|
||||||
|
// content: '确定要撤回质检记录吗?',
|
||||||
|
// positiveText: '确定',
|
||||||
|
// negativeText: '取消',
|
||||||
|
// onPositiveClick: async () => {
|
||||||
|
// try {
|
||||||
|
// await qualityTestingApi.cancel(row.id)
|
||||||
|
// message.success('撤回成功')
|
||||||
|
// getlist()
|
||||||
|
// } catch (error) {
|
||||||
|
// console.log("撤回失败"+error)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cancelSubmit() {
|
||||||
|
|
||||||
|
try{
|
||||||
|
await qualityTestingApi.cancel({"assingId":qcCancelFormData.value.assingWorkId,"remark":qcCancelFormData.value.remark})
|
||||||
|
message.success('撤回成功')
|
||||||
|
qcCancelModel.value = false
|
||||||
|
getlist()
|
||||||
|
}catch(error) {
|
||||||
|
console.log("撤回失败"+error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
getlist()
|
||||||
|
|
||||||
getlist()
|
|
||||||
loadDictOptions()
|
loadDictOptions()
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
@ -16,15 +16,13 @@ export default defineConfig({
|
|||||||
emptyOutDir: true
|
emptyOutDir: true
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
// host: "192.168.5.231",
|
|
||||||
host: true,
|
host: true,
|
||||||
port: 3000,
|
port: 3000,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
//target:'http://192.168.6.107:8080', //'http://192.168.5.230:8888',
|
//target:'http://192.168.6.107:8080', //'http://192.168.5.230:8888',
|
||||||
//target:'http://192.168.12.4:8888',
|
//target:'http://192.168.5.230:8888',
|
||||||
target:'http://192.168.12.230:8888',
|
target:'http://localhost:8888',
|
||||||
//target:'http://192.168.5.200:8888',
|
|
||||||
changeOrigin: true
|
changeOrigin: true
|
||||||
},
|
},
|
||||||
'/druid': {
|
'/druid': {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user