提交
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,
|
||||
deviceType:string,
|
||||
sectionId:number,
|
||||
sectionName?:string,
|
||||
spec:string,
|
||||
manufacturer:string,
|
||||
manufactureDate:string,
|
||||
workshopId:number,
|
||||
remark:string,
|
||||
status:number,
|
||||
deviceFlag?:string,
|
||||
/** 新代 SynFactory equipID */
|
||||
synEquipId?: number | null,
|
||||
createTime: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 = {
|
||||
|
||||
@ -25,6 +46,7 @@ export const deviceApi = {
|
||||
page:number,
|
||||
pageSize:number,
|
||||
deviceCode:any
|
||||
deviceFlag?: string
|
||||
}) {
|
||||
return request({
|
||||
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) {
|
||||
return request({
|
||||
url:"/biz/device",
|
||||
|
||||
@ -64,6 +64,8 @@ export interface ProcessPlanItemVO {
|
||||
|
||||
planId?: number
|
||||
|
||||
workOrderCode?: string
|
||||
|
||||
processCode?: string
|
||||
|
||||
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() {
|
||||
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 })
|
||||
},
|
||||
//质检撤回
|
||||
cancel(id: number) {
|
||||
return request({ url: `/mes/dispatch/revoke/${id}`, method: 'put' })
|
||||
cancel(data:{assingId:number,remark:string}) {
|
||||
console.log(data);
|
||||
|
||||
return request({ url: `/mes/dispatch/revoke`, method: 'put',data })
|
||||
},
|
||||
|
||||
// 导出
|
||||
|
||||
@ -22,12 +22,14 @@ export interface SubmitLog {
|
||||
|
||||
assingStatus?: number
|
||||
|
||||
remark?: string
|
||||
|
||||
}
|
||||
|
||||
// 派工工单汇报记录 API
|
||||
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 })
|
||||
},
|
||||
|
||||
@ -56,7 +58,7 @@ export const submitLogApi = {
|
||||
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
|
||||
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'),
|
||||
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',
|
||||
name: 'user',
|
||||
@ -264,6 +282,12 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('@/views/biz/flowingAround/index.vue'),
|
||||
meta: { title: '流转记录表', icon: 'ListOutline' }
|
||||
},
|
||||
{
|
||||
path: 'biz/outsourcing',
|
||||
name: 'outsourcing',
|
||||
component: () => import('@/views/biz/outsourcing/index.vue'),
|
||||
meta: { title: '工序委外', icon: 'ListOutline' }
|
||||
},
|
||||
{
|
||||
// 开发工具
|
||||
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>
|
||||
新增设备
|
||||
</n-button>
|
||||
<n-button @click="goRealtimeBoard">
|
||||
设备状态看板
|
||||
</n-button>
|
||||
<!-- <n-button @click="importModalVisible = true">
|
||||
<template #icon><n-icon><CloudUploadOutline /></n-icon></template>
|
||||
导入
|
||||
@ -64,13 +67,14 @@
|
||||
<n-form-item label="设备名称" path="deviceName">
|
||||
<n-input v-model:value="formData.deviceName" placeholder="请输入设备名称" />
|
||||
</n-form-item>
|
||||
<n-form-item label="设备类型" path="deviceTypeId">
|
||||
<n-select
|
||||
v-model:value="formData.deviceTypeId"
|
||||
:options="deviceTypeList"
|
||||
placeholder="请选择设备类型"
|
||||
<n-form-item label="新代设备ID" path="synEquipId">
|
||||
<n-input-number
|
||||
v-model:value="formData.synEquipId"
|
||||
placeholder="SynFactory equipID,如 1、2、3"
|
||||
:min="1"
|
||||
:show-button="false"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="所属工段" path="sectionId">
|
||||
@ -82,8 +86,14 @@
|
||||
style="width: 200px"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="设备规格型号" path="spec">
|
||||
<n-input v-model:value="formData.spec" placeholder="请输入设备规格型号" />
|
||||
<n-form-item label="设备类型" path="deviceTypeId">
|
||||
<n-select
|
||||
v-model:value="formData.deviceTypeId"
|
||||
:options="deviceTypeList"
|
||||
placeholder="请选择设备类型"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="生产厂家" path="manufacturer">
|
||||
<n-input v-model:value="formData.manufacturer" placeholder="请输入生产厂家" />
|
||||
@ -144,17 +154,18 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
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 { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline } from '@vicons/ionicons5'
|
||||
import { deviceApi, type Device } from '@/api/device'
|
||||
|
||||
import { stationApi, type Station } from '@/api/station'
|
||||
import { dictDataApi } from '@/api/org'
|
||||
import {sectionApi, type Section} from '@/api/section'
|
||||
|
||||
|
||||
const message = useMessage()
|
||||
const dialog = useDialog()
|
||||
const router = useRouter()
|
||||
|
||||
let sectionList = reactive<{label:string,value:any}[]>([])
|
||||
|
||||
@ -193,7 +204,9 @@ const defaultFormData: Device = {
|
||||
manufactureDate: undefined,
|
||||
workshopId: '',
|
||||
remark: '',
|
||||
status:''
|
||||
status:0,
|
||||
deviceFlag:'mes_equipment',
|
||||
synEquipId: null as number | null,
|
||||
}
|
||||
const formData = reactive<Device>({ ...defaultFormData })
|
||||
|
||||
@ -201,6 +214,8 @@ const formData = reactive<Device>({ ...defaultFormData })
|
||||
const deviceTypeList = 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 = {
|
||||
}
|
||||
@ -210,6 +225,9 @@ const columns: DataTableColumns<Device> = [
|
||||
{ type: 'selection' },
|
||||
{ title: '设备编码', key: 'deviceCode' },
|
||||
{ title: '设备名称', key: 'deviceName' },
|
||||
{ title: '新代ID', key: 'synEquipId', width: 90,
|
||||
render: (row) => row.synEquipId ?? '-'
|
||||
},
|
||||
{ title: '设备类型', key: 'deviceType' },
|
||||
{ title: '所属工段', key: 'sectionName' },
|
||||
{ title: '设备型号', key: 'spec' },
|
||||
@ -248,7 +266,8 @@ async function loadData() {
|
||||
const res = await deviceApi.page({
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
deviceCode:searchForm.deviceCode
|
||||
deviceCode:searchForm.deviceCode,
|
||||
deviceFlag:"mes_equipment"
|
||||
})
|
||||
tableData.value = res.list
|
||||
pagination.itemCount = res.total
|
||||
@ -294,15 +313,14 @@ function handleAdd() {
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
function goRealtimeBoard() {
|
||||
router.push('/biz/device/board')
|
||||
}
|
||||
|
||||
// 编辑
|
||||
function handleEdit(row: Device) {
|
||||
modalTitle.value = '编辑设备表'
|
||||
console.log(row);
|
||||
|
||||
Object.assign(formData, row)
|
||||
if (formData.manufactureDate && typeof formData.manufactureDate === 'string') {
|
||||
formData.manufactureDate = new Date(formData.manufactureDate.replace(' ', 'T')).getTime()
|
||||
}
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
@ -432,6 +450,10 @@ async function loadDictOptions() {
|
||||
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 {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -7,7 +7,10 @@
|
||||
<n-input v-model:value="pauseform.processName" disabled />
|
||||
</n-form-item>
|
||||
<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>
|
||||
</template>
|
||||
@ -17,12 +20,14 @@
|
||||
import { ref, reactive, h, defineProps, onMounted, watch} from 'vue';
|
||||
import { info } from '@/api/production'
|
||||
|
||||
const props = defineProps<{
|
||||
const props = defineProps<{
|
||||
id: number;
|
||||
num: number
|
||||
num: number;
|
||||
qcNumDisabled:boolean
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['updateWidth'])
|
||||
const emit = defineEmits(['updateWidth','updateQcNum'])
|
||||
|
||||
|
||||
let pauseform = reactive<any>({
|
||||
id:'',
|
||||
@ -38,6 +43,10 @@ async function loadData(){
|
||||
|
||||
}
|
||||
|
||||
function handleQcNumChange(){
|
||||
emit('updateQcNum', pauseform.qcNum)
|
||||
}
|
||||
|
||||
watch(() => props.id, (val: number)=>{
|
||||
loadData();
|
||||
})
|
||||
|
||||
@ -95,7 +95,16 @@
|
||||
</n-modal>
|
||||
|
||||
<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>
|
||||
|
||||
<!-- 导入弹窗 -->
|
||||
@ -103,12 +112,13 @@
|
||||
</template>
|
||||
|
||||
<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 { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, EyeOutline, DownloadOutline } from '@vicons/ionicons5'
|
||||
import { flowingAroundApi, type FlowingAround } from '@/api/flowingAround'
|
||||
import { dictDataApi } from '@/api/org'
|
||||
import AssingWorkVue from '@/views/biz/flowingAround/assingWork.vue'
|
||||
import SubmitDetailPage from '@/components/SubmitDetailPage.vue'
|
||||
|
||||
const message = useMessage()
|
||||
const dialog = useDialog()
|
||||
@ -226,7 +236,7 @@ async function loadData() {
|
||||
}
|
||||
|
||||
|
||||
let parentWidth = ref('width: 40%;')
|
||||
let parentWidth = ref('width: 20%;')
|
||||
|
||||
|
||||
function handleUpdateWidth(width:string){
|
||||
@ -273,6 +283,8 @@ const modalCheckData = ref<FlowingAround>()
|
||||
const modalCheckTitle = ref('')
|
||||
//查看提交数据
|
||||
function handleView(row: FlowingAround){
|
||||
console.log(row);
|
||||
|
||||
modalCheckTitle.value = '提交详情'
|
||||
modalCheckData.value = row
|
||||
modalCheckVisible.value = true
|
||||
@ -409,11 +421,39 @@ async function handleImportUpload({ file }: UploadCustomRequestOptions) {
|
||||
}
|
||||
|
||||
const flowingAroundTypeOptions = ref<{ label: string; value: any }[]>([])
|
||||
const confirmResultOptions = ref<{ label: string; value: any }[]>([
|
||||
|
||||
const confirmResultOptions = computed<any[]>(() => {
|
||||
const baseOptions: any[] = [
|
||||
{label: "待确认", value: "待确认"},
|
||||
{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() {
|
||||
|
||||
@ -80,7 +80,7 @@
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<n-checkbox
|
||||
:checked="selectedOrderIds.includes(order.orderItemId!)"
|
||||
@ -92,11 +92,12 @@
|
||||
quaternary
|
||||
circle
|
||||
size="tiny"
|
||||
@click="toggleExpand(order)"
|
||||
title="展开返工单 / 拆分工单"
|
||||
@click="toggleDerivedExpand(order)"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<RemoveOutline v-if="isExpanded(order)" />
|
||||
<RemoveOutline v-if="isDerivedExpanded(order)" />
|
||||
<AddOutline v-else />
|
||||
</n-icon>
|
||||
</template>
|
||||
@ -105,7 +106,7 @@
|
||||
<div class="col-product">
|
||||
<div class="product-name">{{ order.materialName || '-' }}</div>
|
||||
<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="hasOutsource(order)" size="tiny" type="warning" :bordered="false">委外</n-tag>
|
||||
</div>
|
||||
@ -116,8 +117,12 @@
|
||||
<n-tag v-if="order.proWorkshop" size="tiny" type="info" :bordered="false">{{ order.proWorkshop }}</n-tag>
|
||||
</div>
|
||||
<div class="col-kit">-</div>
|
||||
<div class="col-flow">
|
||||
<ProcessFlowStepper :list="order.processList" />
|
||||
<div class="col-flow" :class="{ 'flow-expanded': isExpanded(order) }">
|
||||
<ProcessFlowStepper
|
||||
:list="order.processList"
|
||||
clickable
|
||||
@click="toggleExpand(order)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-plan">
|
||||
<div>{{ formatShortTime(order.beginTime) }}</div>
|
||||
@ -141,6 +146,43 @@
|
||||
</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="!(order.processList?.length)" class="expand-empty">暂无工序明细</div>
|
||||
<div v-else class="card-flow-scroll">
|
||||
@ -151,8 +193,10 @@
|
||||
:is-last="idx === (order.processList?.length ?? 0) - 1"
|
||||
:can-edit="hasPermission('biz:orderItem:edit')"
|
||||
:can-assign="hasPermission('biz:orderItem:assign')"
|
||||
:can-outsource="hasPermission('biz:orderProcessPlan:add')"
|
||||
@edit="(p) => handleEdit(p, order)"
|
||||
@assign="(p) => disphand(p, order)"
|
||||
@outsource="(p) => openOutsource(order, p)"
|
||||
@view-model="(p) => openPlmModel(order, p)"
|
||||
/>
|
||||
</div>
|
||||
@ -184,7 +228,7 @@
|
||||
<template v-if="detailData">
|
||||
<n-descriptions :column="3" bordered size="small" label-placement="left">
|
||||
<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.quantity ?? '-' }}</n-descriptions-item>
|
||||
<n-descriptions-item label="工艺路线">{{ detailData.routeCode || '-' }}</n-descriptions-item>
|
||||
@ -195,14 +239,16 @@
|
||||
</n-descriptions>
|
||||
<div class="detail-cards">
|
||||
<ProcessCard
|
||||
v-for="(proc, idx) in sortedProcessList(detailData.processList)"
|
||||
v-for="(proc, idx) in (detailData.processList)"
|
||||
:key="proc.planId ?? idx"
|
||||
:process="proc"
|
||||
:is-last="idx === (detailData.processList?.length ?? 0) - 1"
|
||||
:can-edit="hasPermission('biz:orderItem:edit')"
|
||||
:can-assign="hasPermission('biz:orderItem:assign')"
|
||||
:can-outsource="hasPermission('biz:orderProcessPlan:add')"
|
||||
@edit="(p) => handleEdit(p, detailData!)"
|
||||
@assign="(p) => disphand(p, detailData!)"
|
||||
@outsource="(p) => openOutsource(detailData!, p)"
|
||||
@view-model="(p) => openPlmModel(detailData!, p)"
|
||||
/>
|
||||
</div>
|
||||
@ -316,6 +362,148 @@
|
||||
</template>
|
||||
</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-space vertical>
|
||||
@ -338,17 +526,18 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ref, reactive, onMounted, h, computed, type Component } from 'vue'
|
||||
import {
|
||||
NButton, NSpace, NIcon, NTag, NSpin, NModal, NForm, NFormItem, NGrid, NGi,
|
||||
NInput, NDatePicker, NCheckbox, NEmpty, NPagination, NDropdown, NDescriptions,
|
||||
NDescriptionsItem, NAlert, NUpload, NText, useMessage, useDialog,
|
||||
type UploadCustomRequestOptions,
|
||||
NInput, NInputNumber, NDatePicker, NCheckbox, NEmpty, NPagination, NDropdown, NDescriptions,
|
||||
NDescriptionsItem, NAlert, NUpload, NText, NCard, NRadioGroup, NRadio, useMessage, useDialog,
|
||||
type UploadCustomRequestOptions, type FormRules,
|
||||
} from 'naive-ui'
|
||||
import {
|
||||
AddOutline, RemoveOutline, SearchOutline, RefreshOutline, CloudUploadOutline,
|
||||
DownloadOutline, ChevronDownOutline, CheckmarkCircleOutline, CheckmarkCircle,
|
||||
EllipseOutline, TimeOutline,
|
||||
EllipseOutline, TimeOutline, PrintOutline, SyncOutline, PeopleOutline,
|
||||
GitBranchOutline, CloseCircleOutline, LockClosedOutline, TrashOutline,
|
||||
} from '@vicons/ionicons5'
|
||||
import {
|
||||
orderProcessPlanApi,
|
||||
@ -357,6 +546,8 @@ import {
|
||||
ORDER_STATUS_MAP,
|
||||
type OrderProcessPlanVO,
|
||||
type ProcessPlanItemVO,
|
||||
type OutsourcingOptions,
|
||||
type OutsourcingProcessOption,
|
||||
} from '@/api/orderProcessPlan'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import ProcessFlowStepper from './components/ProcessFlowStepper.vue'
|
||||
@ -377,6 +568,7 @@ const searchForm = reactive({
|
||||
|
||||
const tableData = ref<OrderProcessPlanVO[]>([])
|
||||
const expandedIds = ref<Set<number>>(new Set())
|
||||
const derivedExpandedIds = ref<Set<number>>(new Set())
|
||||
const selectedOrderIds = ref<number[]>([])
|
||||
const loading = ref(false)
|
||||
const pagination = reactive({ page: 1, pageSize: 10, itemCount: 0 })
|
||||
@ -402,13 +594,38 @@ const dispatchLoading = ref(false)
|
||||
const dispatchformRef = ref()
|
||||
const dispatchform = reactive<any>({
|
||||
id: '', name: '', beginTime: '', endTime: '', quantity: '',
|
||||
list: [{ sectionId:'',deviceId:'',userId: '', quantity: '' }],
|
||||
list: [{ sectionId:'',deviceId:'',userList: '', quantity: '' }],
|
||||
})
|
||||
const dispatchrules = {}
|
||||
|
||||
const importModalVisible = ref(false)
|
||||
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) {
|
||||
const ctx: PlmModelOpenContext = {
|
||||
orderItemId: order.orderItemId,
|
||||
@ -427,13 +644,66 @@ const batchOptions = [
|
||||
{ label: '全部展开', key: 'expandAll' },
|
||||
{ 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 = [
|
||||
{ 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) {
|
||||
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[]) {
|
||||
@ -444,6 +714,10 @@ function isExpanded(order: OrderProcessPlanVO) {
|
||||
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) {
|
||||
if (order.orderItemId == null) return
|
||||
const next = new Set(expandedIds.value)
|
||||
@ -452,6 +726,26 @@ function toggleExpand(order: OrderProcessPlanVO) {
|
||||
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) {
|
||||
if (checked) selectedOrderIds.value = [...selectedOrderIds.value, id]
|
||||
else selectedOrderIds.value = selectedOrderIds.value.filter(v => v !== id)
|
||||
@ -531,8 +825,121 @@ function onBatchSelect(key: string) {
|
||||
}
|
||||
|
||||
function onMoreSelect(key: string, order: OrderProcessPlanVO) {
|
||||
if (key === 'expand') toggleExpand(order)
|
||||
if (key === 'collapse' && isExpanded(order)) toggleExpand(order)
|
||||
if (key === 'createOutsource') {
|
||||
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) {
|
||||
@ -606,11 +1013,13 @@ function disphand(process: ProcessPlanItemVO, order?: OrderProcessPlanVO) {
|
||||
dispatchform.beginTime = entity.beginTime
|
||||
dispatchform.endTime = entity.endTime
|
||||
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() {
|
||||
dispatchform.list.push({ sectionId:'',deviceId:'',userId: '', quantity: '' })
|
||||
dispatchform.list.push({ sectionId:'',deviceId:'',userList: '', quantity: '' })
|
||||
}
|
||||
|
||||
function deletenum(index: number) {
|
||||
@ -619,13 +1028,17 @@ function deletenum(index: number) {
|
||||
|
||||
function dispatchSubmit() {
|
||||
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) => {
|
||||
if (v) return
|
||||
if (total !== Number(dispatchform.quantity)) {
|
||||
message.error('派工总数量需等于生产总数', { duration: 4000 })
|
||||
return
|
||||
}
|
||||
|
||||
dispatchLoading.value = true
|
||||
orderProcessPlanApi.assignWork(dispatchform).then(() => {
|
||||
message.success('派工成功')
|
||||
@ -755,6 +1168,14 @@ onMounted(loadData)
|
||||
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-expand {
|
||||
display: flex;
|
||||
@ -819,6 +1240,10 @@ onMounted(loadData)
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.col-flow.flow-expanded :deep(.flow-stepper) {
|
||||
background: rgba(32, 128, 240, 0.1);
|
||||
}
|
||||
|
||||
.life-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -840,6 +1265,69 @@ onMounted(loadData)
|
||||
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,
|
||||
.detail-cards {
|
||||
display: flex;
|
||||
@ -876,4 +1364,44 @@ onMounted(loadData)
|
||||
.pgClass {
|
||||
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>
|
||||
|
||||
@ -50,6 +50,7 @@
|
||||
</div>
|
||||
|
||||
<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>{{ process.departmentName || '-' }}</em></div>
|
||||
<div class="meta-line"><span>工位</span><em>{{ process.workCenterName || '-' }}</em></div>
|
||||
@ -74,6 +75,13 @@
|
||||
>编辑</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
|
||||
v-if="canOutsource"
|
||||
size="tiny"
|
||||
type="warning"
|
||||
ghost
|
||||
@click="emit('outsource', process)"
|
||||
>委外</n-button>
|
||||
<n-button
|
||||
size="tiny"
|
||||
:type="process.processStatus === 4 ? 'success' : 'default'"
|
||||
@ -99,12 +107,14 @@ const props = defineProps<{
|
||||
isLast?: boolean
|
||||
canEdit?: boolean
|
||||
canAssign?: boolean
|
||||
canOutsource?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
edit: [process: ProcessPlanItemVO]
|
||||
assign: [process: ProcessPlanItemVO]
|
||||
viewModel: [process: ProcessPlanItemVO]
|
||||
outsource: [process: ProcessPlanItemVO]
|
||||
}>()
|
||||
|
||||
const totalQty = computed(() => props.process.quantity ?? 0)
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<div class="flow-stepper">
|
||||
<div
|
||||
class="flow-stepper"
|
||||
:class="{ clickable }"
|
||||
@click="onClick"
|
||||
>
|
||||
<template v-for="(step, idx) in steps" :key="step.key">
|
||||
<div
|
||||
class="flow-step"
|
||||
@ -23,8 +27,15 @@ import { NIcon } from 'naive-ui'
|
||||
import { CheckmarkOutline } from '@vicons/ionicons5'
|
||||
import type { ProcessPlanItemVO } from '@/api/orderProcessPlan'
|
||||
|
||||
const props = defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
list?: ProcessPlanItemVO[]
|
||||
clickable?: boolean
|
||||
}>(), {
|
||||
clickable: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: []
|
||||
}>()
|
||||
|
||||
const steps = computed(() => {
|
||||
@ -40,6 +51,10 @@ const steps = computed(() => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
function onClick() {
|
||||
if (props.clickable) emit('click')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@ -51,6 +66,18 @@ const steps = computed(() => {
|
||||
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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -437,7 +437,7 @@ const expandedRowKeys = ref<Array<string | number>>([])
|
||||
const loading = ref(false)
|
||||
const selectedIds = ref<number[]>([])
|
||||
const orderScrollX = 2090
|
||||
const processScrollX = 2620
|
||||
const processScrollX = 2788
|
||||
|
||||
function orderRowKey(row: OrderProcessPlanVO) {
|
||||
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: 'materialCode', width: 130, align: 'center', 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: 'routeCode', 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',
|
||||
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: 'processCode', width: 96, align: 'center', ellipsis: { tooltip: true } },
|
||||
{ title: '工序名称', key: 'processName', width: 140, ellipsis: { tooltip: true } },
|
||||
|
||||
@ -53,21 +53,30 @@
|
||||
<n-gi :span="10">
|
||||
<n-form-item
|
||||
label="分配人员"
|
||||
:path="`${listname}[${index}].userId`"
|
||||
:path="`${listname}[${index}].userList`"
|
||||
:rule="{
|
||||
required: true,
|
||||
message: '请选择员工',
|
||||
trigger: 'blur'
|
||||
trigger: 'blur',
|
||||
validator: (rule, value, callback) => {
|
||||
// value 多选为数组,单选是单个值
|
||||
if (!Array.isArray(value) || value.length === 0) {
|
||||
callback(new Error('请选择员工'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}"
|
||||
>
|
||||
<!--multiple-->
|
||||
<n-select
|
||||
v-model:value="obj.userId"
|
||||
v-model:value="obj.userList"
|
||||
filterable
|
||||
placeholder="请输入用户名称搜索"
|
||||
:options="yglist"
|
||||
:loading="loadingRef"
|
||||
clearable
|
||||
multiple
|
||||
remote
|
||||
:clear-filter-after-select="false"
|
||||
@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>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { NButton, NSpace, NIcon, type DataTableColumns} from 'naive-ui'
|
||||
import { ref, reactive,h, onMounted } from 'vue'
|
||||
import { NButton, NSpace,NTag, NIcon, type DataTableColumns} from 'naive-ui'
|
||||
import { SearchOutline, RefreshOutline } from '@vicons/ionicons5'
|
||||
import { qcResultDetailApi, type QcResultDetail } from '@/api/qcResultDetail'
|
||||
import { dictDataApi } from '@/api/org'
|
||||
@ -92,7 +92,7 @@ const actionStatusOptions = 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) {
|
||||
const val = row.actionStatus
|
||||
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' },
|
||||
@ -195,7 +196,7 @@ async function loadDictOptions() {
|
||||
}catch {}
|
||||
try {
|
||||
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 {}
|
||||
}
|
||||
|
||||
|
||||
@ -224,7 +224,23 @@
|
||||
<!-- </n-card>-->
|
||||
</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>
|
||||
@ -245,13 +261,18 @@ import { qualityTestingApi, type QualityTesting } from '@/api/qualityTesting'
|
||||
import {assingReworkApi} from '@/api/assingRework'
|
||||
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";
|
||||
|
||||
|
||||
const message = useMessage()
|
||||
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 traceCodeShow = ref(false)
|
||||
//提交明细/部分提交明细标识
|
||||
const submitFlag = ref<Boolean>()
|
||||
const showTraceFlag = ref<Boolean>()
|
||||
|
||||
|
||||
const customOrderKeys = [
|
||||
@ -411,21 +432,25 @@ const defaultFormData: QualityTesting = {
|
||||
}
|
||||
const formData = reactive<QualityTesting>({ ...defaultFormData })
|
||||
|
||||
// const defaultFormDetailData:QcResultDetail = {
|
||||
// qcId: undefined,
|
||||
// resultType: undefined,
|
||||
// qty: 0,
|
||||
// traceCode: undefined,
|
||||
// defectCode: undefined,
|
||||
// handleAction: undefined,
|
||||
// actionStatus: undefined
|
||||
// }
|
||||
const defaultFormDetailData:QcResultDetail = {
|
||||
qcId: undefined,
|
||||
resultType: undefined,
|
||||
qty: 0,
|
||||
traceCode: undefined,
|
||||
defectCode: undefined,
|
||||
handleAction: 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 })
|
||||
// const formDetailScrapData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 2,handleAction:'报废次品',actionStatus:1 })
|
||||
// const formDetailReworkData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 3,handleAction:'返工',actionStatus:1 })
|
||||
// const formDetailConcessionData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 4,handleAction:'让步接收',actionStatus:1 })
|
||||
let formDetailScrapData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 2,handleAction:'报废次品',actionStatus:1 })
|
||||
let formDetailReworkData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 3,handleAction:'返工',actionStatus:1 })
|
||||
let formDetailConcessionData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 4,handleAction:'让步接收',actionStatus:1 })
|
||||
|
||||
const assingWorkDisabled = ref(true)
|
||||
|
||||
@ -466,6 +491,7 @@ const columns: DataTableColumns<QualityTesting> = [
|
||||
// { title: '关联生产订单ID', key: 'orderId' },
|
||||
// { title: '关联工序计划ID', key: 'processId' },
|
||||
// { title: '物料ID', key: 'itemId' },
|
||||
{ title: '追溯码', key: 'traceCode',align: 'center' },
|
||||
{ title: '报检总数', key: 'totalQty',align: 'center' },
|
||||
{ title: '已检验数量', key: 'inspectQty',align: 'center' },
|
||||
{ title: '质检状态', key: 'status',align: 'center',
|
||||
@ -508,12 +534,12 @@ const columns: DataTableColumns<QualityTesting> = [
|
||||
{ label: '查看详情', key: 'view' },
|
||||
{ type: 'divider' }, // 分割线
|
||||
{ label: '提交判定明细', key: 'submitResultDetail',show:row.status == 1 },
|
||||
{ type: 'divider',show:row.status == 1 }, // 分割线
|
||||
{ label: '提交部分判定明细', key: 'submitBFResultDetail',show:(row.status == 1 || row.status == 2) },
|
||||
// { type: 'divider',show:row.status == 1 }, // 分割线
|
||||
// { label: '提交部分判定明细', key: 'submitBFResultDetail',show:(row.status == 1 || row.status == 2) },
|
||||
{ type: 'divider',show:(row.status == 1 || row.status == 2) }, // 分割线
|
||||
{ label: '查询判定明细', key: 'queryResultDetail' },
|
||||
{ type: 'divider' }, // 分割线
|
||||
{ label: '质检撤回', key: 'cancelQuality' },
|
||||
// { type: 'divider' }, // 分割线
|
||||
// { label: '质检撤回', key: 'cancelQuality' },
|
||||
],
|
||||
// 下拉菜单项点击事件
|
||||
onSelect: (key: string) => {
|
||||
@ -524,15 +550,15 @@ const columns: DataTableColumns<QualityTesting> = [
|
||||
case 'submitResultDetail':
|
||||
submitResultDetail(row)
|
||||
break
|
||||
case 'submitBFResultDetail':
|
||||
submitBFResultDetail(row)
|
||||
break
|
||||
// case 'submitBFResultDetail':
|
||||
// submitBFResultDetail(row)
|
||||
// break
|
||||
case 'queryResultDetail':
|
||||
queryResultDetail(row)
|
||||
break
|
||||
case 'cancelQuality':
|
||||
handleCancelQuality(row)
|
||||
break
|
||||
// case 'cancelQuality':
|
||||
// handleCancelQuality(row)
|
||||
// break
|
||||
}
|
||||
}
|
||||
}, {
|
||||
@ -608,6 +634,8 @@ function handleAdd() {
|
||||
function handleEdit(row: QualityTesting) {
|
||||
modalTitle.value = '编辑质检单表'
|
||||
assingWorkDisabled.value = true
|
||||
console.log(row);
|
||||
|
||||
Object.assign(formData, row)
|
||||
if (formData.inspectTime && typeof formData.inspectTime === 'string') {
|
||||
formData.inspectTime = new Date(formData.inspectTime.replace(' ', 'T')).getTime()
|
||||
@ -836,30 +864,43 @@ async function loadAssignWorkOrder() {
|
||||
}
|
||||
|
||||
//提交判定明细
|
||||
function submitResultDetail(row:any) {
|
||||
|
||||
if (row.traceType != 1) {
|
||||
traceCodeShow.value = true
|
||||
}
|
||||
submitFlag.value = true
|
||||
async function submitResultDetail(row:any) {
|
||||
showSubmitOuter.value = true
|
||||
qcId.value = row.id
|
||||
}
|
||||
|
||||
|
||||
let path = `/qc/submitDetail/${row.qcNo}/${row.id}/${submitFlag.value}/${traceCodeShow.value}`
|
||||
router.push(path)
|
||||
function handleSubmitDetail(submitDetail:any){
|
||||
formDetailOKData = submitDetail.okData ? submitDetail.okData : formDetailOKData ;
|
||||
formDetailScrapData = submitDetail.scrapData ? submitDetail.scrapData : formDetailScrapData
|
||||
formDetailReworkData = submitDetail.reworkData ? submitDetail.reworkData : formDetailReworkData
|
||||
formDetailConcessionData = submitDetail.concessionData ? submitDetail.concessionData : formDetailConcessionData
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//提交部分判定明细
|
||||
function submitBFResultDetail(row:any) {
|
||||
async function handleSubmitResultDetail() {
|
||||
|
||||
if (row.traceType != 1) {
|
||||
traceCodeShow.value = true
|
||||
}
|
||||
submitFlag.value = false
|
||||
formDetailOKData.qcId = qcId.value
|
||||
formDetailScrapData.qcId = qcId.value
|
||||
formDetailReworkData.qcId = qcId.value
|
||||
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)
|
||||
}
|
||||
|
||||
//质检撤回
|
||||
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>
|
||||
<div class="page-container">
|
||||
<n-card :title="title" style="margin-bottom: 16px">
|
||||
<n-card :title="title" >
|
||||
<n-tabs type="card" animated>
|
||||
<n-tab-pane name="合格" tab="合格">
|
||||
<n-form ref="formRef" :model="formDetailOKData" label-placement="left" label-width="100px">
|
||||
<n-grid x-gap="12" :cols="2">
|
||||
<n-gi>
|
||||
<n-form-item label="判定数量" path="qty">
|
||||
<n-input-number v-model:value="formDetailOKData.qty" placeholder="请输入判定数量" :min="0" />
|
||||
</n-form-item>
|
||||
</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-tab-pane name="合格" tab="合格" style="width: 50%;">
|
||||
<SubmitDetailPage
|
||||
key="ok"
|
||||
:disabled="false"
|
||||
:formDetailData="formDetailOKData"
|
||||
:showTraceCode="true"
|
||||
@update:formDetailData= handleFormDetailOKData
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
</n-form>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="报废" tab="报废">
|
||||
<n-form ref="formRef" :model="formDetailScrapData" label-placement="left" label-width="100px">
|
||||
<n-grid x-gap="12" :cols="2">
|
||||
<n-gi>
|
||||
<n-form-item label="判定数量" path="qty">
|
||||
<n-input-number v-model:value="formDetailScrapData.qty" placeholder="请输入判定数量" :min="0" />
|
||||
</n-form-item>
|
||||
</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-tab-pane name="报废" tab="报废" style="width: 50%;">
|
||||
<SubmitDetailPage
|
||||
key="scrap"
|
||||
:disabled="false"
|
||||
:formDetailData="formDetailScrapData"
|
||||
:showTraceCode="true"
|
||||
@update:formDetailData= handleFormDetailScrapData
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
</n-form>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="返工" tab="返工">
|
||||
<n-form ref="formRef" :model="formDetailReworkData" label-placement="left" label-width="100px">
|
||||
<n-grid x-gap="12" :cols="2">
|
||||
<n-gi>
|
||||
<n-form-item label="判定数量" path="qty">
|
||||
<n-input-number v-model:value="formDetailReworkData.qty" placeholder="请输入判定数量" :min="0" />
|
||||
</n-form-item>
|
||||
</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-tab-pane name="返工" tab="返工" style="width: 50%;">
|
||||
<SubmitDetailPage
|
||||
key="rework"
|
||||
:disabled="false"
|
||||
:formDetailData="formDetailReworkData"
|
||||
:showTraceCode="true"
|
||||
@update:formDetailData= handleFormDetailReworkData
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</n-form>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="让步接收" tab="让步接收">
|
||||
<n-form ref="formRef" :model="formDetailConcessionData" label-placement="left" label-width="100px">
|
||||
<n-tab-pane name="让步接收" tab="让步接收" style="width: 50%;">
|
||||
|
||||
<n-grid x-gap="12" :cols="2">
|
||||
<n-gi>
|
||||
<n-form-item label="判定数量" path="qty">
|
||||
<n-input-number v-model:value="formDetailConcessionData.qty" placeholder="请输入判定数量" :min="0" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<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="输入备注原因"
|
||||
<SubmitDetailPage
|
||||
key="conession"
|
||||
:disabled="false"
|
||||
:formDetailData="formDetailConcessionData"
|
||||
:showTraceCode="true"
|
||||
@update:formDetailData= handleFormDetailConcessionData
|
||||
/>
|
||||
</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-tabs>
|
||||
|
||||
@ -179,33 +58,22 @@ import {
|
||||
|
||||
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 { qualityTestingApi} from '@/api/qualityTesting'
|
||||
import router from '@/router';
|
||||
|
||||
import SubmitDetailPage from '@/components/SubmitDetailPage.vue';
|
||||
|
||||
const message = useMessage()
|
||||
const route = useRoute()
|
||||
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 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}`)
|
||||
|
||||
|
||||
@ -223,10 +91,29 @@ const defaultFormDetailData:QcResultDetail = {
|
||||
attachments:[]
|
||||
}
|
||||
|
||||
const formDetailOKData = reactive<QcResultDetail>({ ...defaultFormDetailData, 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 formDetailConcessionData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 4,handleAction:'让步接收',actionStatus:1 })
|
||||
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) {
|
||||
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() {
|
||||
@ -236,8 +123,6 @@ async function handleSubmitResultDetail() {
|
||||
formDetailReworkData.qcId = qcId.value
|
||||
formDetailConcessionData.qcId =qcId.value
|
||||
|
||||
formDetailConcessionData.images = uploadImages.value
|
||||
formDetailConcessionData.attachments = uploadFiles.value
|
||||
|
||||
const submitParam = [
|
||||
formDetailOKData,
|
||||
@ -248,7 +133,7 @@ async function handleSubmitResultDetail() {
|
||||
console.log(submitParam);
|
||||
|
||||
|
||||
submitFlag.value? await qualityTestingApi.submit(submitParam) : await qualityTestingApi.submitBF(submitParam)
|
||||
await qualityTestingApi.submit(submitParam)
|
||||
|
||||
message.success('提交成功')
|
||||
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() {
|
||||
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 })
|
||||
|
||||
imageList.value = []
|
||||
uploadImages.value = []
|
||||
fileList.value = []
|
||||
uploadFiles.value = []
|
||||
|
||||
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>
|
||||
<style>
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
<!-- 搜索表单 -->
|
||||
<div class="search-form">
|
||||
<n-form inline :model="searchForm" label-placement="left">
|
||||
<n-form-item label="工位编码">
|
||||
<n-input v-model:value="searchForm.stationCode" placeholder="请输入工位编码" clearable />
|
||||
<n-form-item label="主键ID">
|
||||
<n-input v-model:value="searchForm.deviceCode" placeholder="请输入工位编码" clearable />
|
||||
</n-form-item>
|
||||
<n-form-item>
|
||||
<n-space>
|
||||
@ -61,8 +61,8 @@
|
||||
<!-- 新增/编辑弹窗 -->
|
||||
<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-item label="工位名称" path="stationName">
|
||||
<n-input v-model:value="formData.stationName" placeholder="请输入工位名称" />
|
||||
<n-form-item label="工位名称" path="deviceName">
|
||||
<n-input v-model:value="formData.deviceName" placeholder="请输入设备名称" />
|
||||
</n-form-item>
|
||||
<n-form-item label="所属工段" path="sectionId">
|
||||
<n-select
|
||||
@ -92,7 +92,7 @@
|
||||
</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-alert type="info">
|
||||
<template #header>导入说明</template>
|
||||
@ -128,20 +128,24 @@
|
||||
import { ref, reactive, h, onMounted } from 'vue'
|
||||
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 { stationApi, type Station } from '@/api/station'
|
||||
import { deviceApi, type Device } from '@/api/device'
|
||||
|
||||
import { dictDataApi } from '@/api/org'
|
||||
import {sectionApi, type Section} from '@/api/section'
|
||||
|
||||
|
||||
const message = useMessage()
|
||||
const dialog = useDialog()
|
||||
|
||||
let sectionList = reactive<{label:string,value:any}[]>([])
|
||||
|
||||
// 搜索表单
|
||||
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 selectedIds = ref<number[]>([])
|
||||
const pagination = reactive({
|
||||
@ -157,42 +161,48 @@ const modalVisible = ref(false)
|
||||
const modalTitle = ref('')
|
||||
const importModalVisible = ref(false)
|
||||
const formRef = ref()
|
||||
const defaultFormData: Station = {
|
||||
id:'',
|
||||
stationCode: '',
|
||||
stationName: '',
|
||||
sectionId: undefined,
|
||||
status:undefined,
|
||||
remark:undefined
|
||||
const defaultFormData: Device = {
|
||||
id:"",
|
||||
deviceCode: '',
|
||||
deviceName: '',
|
||||
deviceTypeId: undefined,
|
||||
deviceType:"",
|
||||
sectionId: "",
|
||||
sectionName:"",
|
||||
spec: '',
|
||||
manufacturer: '',
|
||||
manufactureDate: undefined,
|
||||
workshopId: '',
|
||||
remark: '',
|
||||
status:0,
|
||||
deviceFlag:'mes_station'
|
||||
}
|
||||
const formData = reactive<Station>({ ...defaultFormData })
|
||||
|
||||
let sectionList = reactive<{label:string,value:any}[]>([])
|
||||
const formData = reactive<Device>({ ...defaultFormData })
|
||||
|
||||
// 字典选项(下拉框/单选框/复选框关联字典时使用)
|
||||
const deviceTypeList = 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 columns: DataTableColumns<Station> = [
|
||||
const columns: DataTableColumns<Device> = [
|
||||
{ type: 'selection' },
|
||||
{ title: '工位编码', key: 'stationCode' },
|
||||
{ title: '工位名称', key: 'stationName' },
|
||||
{ title: '工位编码', key: 'deviceCode' },
|
||||
{ title: '工位名称', key: 'deviceName' },
|
||||
{ title: '所属工段', key: 'sectionName' },
|
||||
{ title: '备注', key: 'remark' },
|
||||
{ title: '启用状态', key: 'status',
|
||||
{ title: '状态', key: 'status',
|
||||
render:(row) =>{
|
||||
const val = row.status
|
||||
const opt = statusList.value.find(o => o.value === val || String(o.value) === String(val))
|
||||
if (!opt) return val ?? '-'
|
||||
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
||||
}
|
||||
},
|
||||
{ title: '创建时间', key: 'createTime' },
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
@ -215,10 +225,11 @@ const columns: DataTableColumns<Station> = [
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await stationApi.page({
|
||||
const res = await deviceApi.page({
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
stationCode: searchForm.stationCode || undefined,
|
||||
deviceCode:searchForm.deviceCode,
|
||||
deviceFlag:"mes_station"
|
||||
})
|
||||
tableData.value = res.list
|
||||
pagination.itemCount = res.total
|
||||
@ -235,7 +246,7 @@ function handleSearch() {
|
||||
|
||||
// 重置
|
||||
function handleReset() {
|
||||
searchForm.stationCode = null
|
||||
searchForm.deviceCode = null
|
||||
|
||||
handleSearch()
|
||||
}
|
||||
@ -259,14 +270,16 @@ function handleCheck(keys: Array<string | number>) {
|
||||
|
||||
// 新增
|
||||
function handleAdd() {
|
||||
modalTitle.value = '新增工位表'
|
||||
modalTitle.value = '新增设备表'
|
||||
Object.assign(formData, defaultFormData)
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑
|
||||
function handleEdit(row: Station) {
|
||||
modalTitle.value = '编辑工位表'
|
||||
function handleEdit(row: Device) {
|
||||
modalTitle.value = '编辑设备表'
|
||||
console.log(row);
|
||||
|
||||
Object.assign(formData, row)
|
||||
modalVisible.value = true
|
||||
}
|
||||
@ -275,12 +288,12 @@ function handleEdit(row: Station) {
|
||||
async function handleSubmit() {
|
||||
await formRef.value?.validate()
|
||||
try {
|
||||
const submitData = { ...formData } as Station
|
||||
const submitData = { ...formData } as Device
|
||||
if (submitData.id) {
|
||||
await stationApi.update(submitData)
|
||||
await deviceApi.update(submitData)
|
||||
message.success('修改成功')
|
||||
} else {
|
||||
await stationApi.create(submitData)
|
||||
await deviceApi.create(submitData)
|
||||
message.success('新增成功')
|
||||
}
|
||||
modalVisible.value = false
|
||||
@ -291,7 +304,7 @@ async function handleSubmit() {
|
||||
}
|
||||
|
||||
// 删除
|
||||
function handleDelete(row: Station) {
|
||||
function handleDelete(row: Device) {
|
||||
dialog.warning({
|
||||
title: '提示',
|
||||
content: '确定要删除该记录吗?',
|
||||
@ -299,7 +312,7 @@ function handleDelete(row: Station) {
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
await stationApi.delete([row.id!])
|
||||
await deviceApi.delete([row.id!])
|
||||
message.success('删除成功')
|
||||
loadData()
|
||||
} catch (error) {
|
||||
@ -318,7 +331,7 @@ function handleBatchDelete() {
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
await stationApi.delete(selectedIds.value)
|
||||
await deviceApi.delete(selectedIds.value)
|
||||
message.success('删除成功')
|
||||
selectedIds.value = []
|
||||
loadData()
|
||||
@ -334,12 +347,12 @@ async function handleExport() {
|
||||
try {
|
||||
const params: Record<string, any> = {}
|
||||
if (selectedIds.value.length > 0) params.ids = selectedIds.value
|
||||
if (searchForm.stationCode != null) params.stationCode = searchForm.stationCode
|
||||
const blob = await stationApi.export(params)
|
||||
if (searchForm.deviceCode != null) params.deviceCode = searchForm.deviceCode
|
||||
const blob = await deviceApi.export(params)
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = '工位表数据.xlsx'
|
||||
link.download = '设备表数据.xlsx'
|
||||
link.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
} catch (error) {
|
||||
@ -350,11 +363,11 @@ async function handleExport() {
|
||||
// 下载导入模板
|
||||
async function handleDownloadTemplate() {
|
||||
try {
|
||||
const blob = await stationApi.downloadTemplate()
|
||||
const blob = await deviceApi.downloadTemplate()
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = '工位表导入模板.xlsx'
|
||||
link.download = '设备表导入模板.xlsx'
|
||||
link.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
} catch (error) {
|
||||
@ -366,7 +379,7 @@ async function handleDownloadTemplate() {
|
||||
async function handleImportUpload({ file }: UploadCustomRequestOptions) {
|
||||
if (!file.file) return
|
||||
try {
|
||||
const result = await stationApi.importData(file.file)
|
||||
const result = await deviceApi.importData(file.file)
|
||||
if (result.fail > 0) {
|
||||
dialog.warning({
|
||||
title: '导入结果',
|
||||
@ -385,10 +398,19 @@ async function handleImportUpload({ file }: UploadCustomRequestOptions) {
|
||||
|
||||
// 加载字典选项
|
||||
async function loadDictOptions() {
|
||||
try {
|
||||
const data = await dictDataApi.listByType("device_type")
|
||||
deviceTypeList.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||
}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">
|
||||
<n-form inline :model="searchForm" label-placement="left">
|
||||
<n-form-item label="派工状态">
|
||||
<n-form-item label="汇报状态">
|
||||
<n-select
|
||||
v-model:value="searchForm.assingStatus"
|
||||
:options="qcAssingStatusOptions"
|
||||
v-model:value="searchForm.submitStatus"
|
||||
:options="submitStatusOptions"
|
||||
placeholder="请选择派工状态"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
@ -31,18 +31,19 @@
|
||||
<!-- 工具栏 -->
|
||||
<div class="table-toolbar">
|
||||
<n-space>
|
||||
<n-button type="primary" @click="handleStatistics">
|
||||
<n-button type="primary" @click="handleStatistics" disabled>
|
||||
<template #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 @click="importModalVisible = true">
|
||||
<template #icon><n-icon><CloudUploadOutline /></n-icon></template>
|
||||
导入
|
||||
</n-button>
|
||||
|
||||
<n-button type="error" :disabled="selectedIds.length === 0" @click="handleBatchDelete">
|
||||
<template #icon><n-icon><TrashOutline /></n-icon></template>
|
||||
删除
|
||||
@ -211,7 +212,7 @@ const pagination = reactive({
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
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 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 columns: DataTableColumns<SubmitLog> = [
|
||||
{ type: 'selection' },
|
||||
{ title: '物料名称', key: 'materialName',align: 'center',
|
||||
render(rowData, rowIndex) {
|
||||
return rowData.materialName? rowData.materialName: '-'
|
||||
@ -261,37 +261,19 @@ const columns: DataTableColumns<SubmitLog> = [
|
||||
},
|
||||
{ title: '工序名称', key: 'processName',align: 'center' },
|
||||
{ title: '工序编号', key: 'processCode',align: 'center' },
|
||||
{ title: '汇报数量', key: 'quantity',align: 'center'},
|
||||
{ title: '派工编号', key: 'assingCode',align: 'center' },
|
||||
{ title: '派工人', key: 'userName',align: 'center' },
|
||||
{ title: '派工状态', key: 'assingStatus',align: 'center',
|
||||
{ title: '汇报人', key: 'userName',align: 'center' },
|
||||
{ title: '派工状态', key: 'submitStatus',align: 'center',
|
||||
render: (row) => {
|
||||
const val = row.assingStatus
|
||||
const opt = qcAssingStatusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||
const val = row.submitStatus
|
||||
const opt = submitStatusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||
if (!opt) return val ?? '-'
|
||||
|
||||
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
||||
}
|
||||
},
|
||||
{ title: '派工完成时间', key: 'completedTime',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) }), ' 删除']
|
||||
// })
|
||||
])
|
||||
}
|
||||
}
|
||||
{ title: '撤回原因', key: 'remark',align: 'center' },
|
||||
]
|
||||
|
||||
//汇报详情属性
|
||||
@ -317,7 +299,7 @@ async function loadData() {
|
||||
const res = await submitLogApi.page({
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
assingStatus: searchForm.assingStatus
|
||||
submitStatus: searchForm.submitStatus
|
||||
})
|
||||
tableData.value = res.list
|
||||
pagination.itemCount = res.total
|
||||
@ -336,7 +318,7 @@ function handleSearch() {
|
||||
|
||||
// 重置
|
||||
function handleReset() {
|
||||
searchForm.assingStatus = undefined
|
||||
searchForm.submitStatus = undefined
|
||||
|
||||
handleSearch()
|
||||
}
|
||||
@ -441,7 +423,7 @@ async function handleExport() {
|
||||
try {
|
||||
const params: Record<string, any> = {}
|
||||
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 url = window.URL.createObjectURL(blob)
|
||||
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 }))
|
||||
}catch {}
|
||||
try {
|
||||
const data = await dictDataApi.listByType('assing_status')
|
||||
qcAssingStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||
const data = await dictDataApi.listByType('submit_status')
|
||||
submitStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||
}catch {}
|
||||
}
|
||||
|
||||
|
||||
@ -496,6 +496,24 @@ const columns = [
|
||||
key: 'processName',
|
||||
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',
|
||||
title: '派工状态',
|
||||
@ -557,6 +575,26 @@ const columns = [
|
||||
key: 'reworkNum',
|
||||
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',
|
||||
title: '完成数量',
|
||||
|
||||
@ -120,24 +120,21 @@
|
||||
style="width: 400px"
|
||||
: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>
|
||||
<n-space justify="end">
|
||||
<n-button @click="pausemodal = false">取消</n-button>
|
||||
@ -146,11 +143,43 @@
|
||||
:loading="pauseLoading"
|
||||
@click="pauseSubmit"
|
||||
:disabled="pauseisedbtn"
|
||||
>保存</n-button>
|
||||
>确认</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</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>
|
||||
</template>
|
||||
@ -174,7 +203,7 @@ import {
|
||||
// NGi,
|
||||
NTag,
|
||||
useMessage,
|
||||
//useDialog,
|
||||
useDialog,
|
||||
//type FormInst,
|
||||
} from 'naive-ui'
|
||||
|
||||
@ -191,8 +220,12 @@ import {
|
||||
} from '@/api/production'
|
||||
import PlmModelDrawer from '@/components/PlmModelDrawer.vue'
|
||||
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()
|
||||
|
||||
@ -431,7 +464,7 @@ const columns = [
|
||||
align:'center',
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 100,
|
||||
width: 250,
|
||||
fixed: 'right',
|
||||
render(row:any) {
|
||||
const buttons:any = []
|
||||
@ -442,7 +475,7 @@ const columns = [
|
||||
type:'primary',
|
||||
ghost:true,
|
||||
onClick: () => { handlpause(row) } },
|
||||
{ default: () => '报检'}
|
||||
{ default: () => '完工汇报'}
|
||||
))
|
||||
}
|
||||
buttons.push(h(NButton, {
|
||||
@ -452,6 +485,13 @@ const columns = [
|
||||
onClick: () => openPlmModel(row),
|
||||
}, { 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 }) : '-'
|
||||
}
|
||||
}
|
||||
@ -506,7 +546,29 @@ let gxobg = reactive<any>({})
|
||||
let pausemodal = ref(false)
|
||||
let pauseform = reactive<any>({
|
||||
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()
|
||||
|
||||
@ -532,6 +594,18 @@ const pauserules = {
|
||||
let pauseLoading = 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) {
|
||||
gxobg.id = n.id
|
||||
gxobg.name = n.name
|
||||
@ -542,12 +616,68 @@ function handlpause(n:any) {
|
||||
pauseformRef.value?.restoreValidation()
|
||||
pauseform.id = n.id
|
||||
//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() {
|
||||
pauseformRef.value?.validate((v:any) => {
|
||||
if(!v){
|
||||
|
||||
var pass = checkPauseSumbit();
|
||||
|
||||
console.log(pass);
|
||||
|
||||
if(!pass) return
|
||||
|
||||
pauseLoading.value = true
|
||||
pauseisedbtn.value = true
|
||||
inspectio(Object.assign({},pauseform,{qcNum:pauseform.qcNum*1})).then(() => {
|
||||
@ -562,12 +692,23 @@ function pauseSubmit() {
|
||||
pauseLoading.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() {
|
||||
qcCancelLoading = true
|
||||
try {
|
||||
const data = await dictDataApi.listByType('assing_status')
|
||||
qcAssingStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
||||
@ -588,9 +729,42 @@ 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(() => {
|
||||
|
||||
|
||||
getlist()
|
||||
loadDictOptions()
|
||||
|
||||
|
||||
@ -16,15 +16,13 @@ export default defineConfig({
|
||||
emptyOutDir: true
|
||||
},
|
||||
server: {
|
||||
// host: "192.168.5.231",
|
||||
host: true,
|
||||
port: 3000,
|
||||
proxy: {
|
||||
'/api': {
|
||||
//target:'http://192.168.6.107:8080', //'http://192.168.5.230:8888',
|
||||
//target:'http://192.168.12.4:8888',
|
||||
target:'http://192.168.12.230:8888',
|
||||
//target:'http://192.168.5.200:8888',
|
||||
//target:'http://192.168.5.230:8888',
|
||||
target:'http://localhost:8888',
|
||||
changeOrigin: true
|
||||
},
|
||||
'/druid': {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user