Merge branch 'master' of https://git.evo-techina.com/sgc/mes-vue
This commit is contained in:
commit
d80a5983d4
@ -1,82 +0,0 @@
|
||||
import { request } from '@/utils/request'
|
||||
|
||||
// 让步接收申请表 类型定义
|
||||
export interface ConcessionApply {
|
||||
id?: number
|
||||
|
||||
qcId?: number
|
||||
|
||||
concessionQty?: number
|
||||
|
||||
applyRemark?: string
|
||||
|
||||
applyContent?: string
|
||||
|
||||
applyFile?: string
|
||||
|
||||
applyUserId?: number
|
||||
|
||||
deptId?: number
|
||||
|
||||
instanceId?: string
|
||||
|
||||
approveStatus?: number
|
||||
|
||||
createTime?: string
|
||||
|
||||
updateTime?: string
|
||||
|
||||
}
|
||||
|
||||
// 让步接收申请表 API
|
||||
export const concessionApplyApi = {
|
||||
// 分页查询
|
||||
page(params: { page: number; pageSize: number; id?: number }) {
|
||||
return request({ url: '/biz/concessionApply/page', method: 'get', params })
|
||||
},
|
||||
|
||||
// 获取详情
|
||||
detail(id: string) {
|
||||
return request({ url: `/biz/concessionApply/${id}`, method: 'get' })
|
||||
},
|
||||
|
||||
// 新增
|
||||
create(data: ConcessionApply) {
|
||||
return request({ url: '/biz/concessionApply', method: 'post', data })
|
||||
},
|
||||
|
||||
// 修改
|
||||
update(data: ConcessionApply) {
|
||||
return request({ url: '/biz/concessionApply', method: 'put', data })
|
||||
},
|
||||
|
||||
// 删除
|
||||
delete(ids: string[]) {
|
||||
return request({ url: `/biz/concessionApply/${ids.join(',')}`, method: 'delete' })
|
||||
},
|
||||
|
||||
// 导出
|
||||
export(params?: { ids?: string[]; id?: 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
|
||||
return request({ url: `/biz/concessionApply/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/concessionApply/import`,
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
},
|
||||
|
||||
// 下载导入模板
|
||||
downloadTemplate() {
|
||||
return request({ url: `/biz/concessionApply/template`, method: 'get', responseType: 'blob' })
|
||||
}
|
||||
}
|
||||
@ -1,80 +0,0 @@
|
||||
import { request } from '@/utils/request'
|
||||
|
||||
// MES设备维修保养记录表 类型定义
|
||||
export interface MaintenanceRecord {
|
||||
recordId?: string
|
||||
|
||||
equipmentId?: string
|
||||
|
||||
recordType?: string
|
||||
|
||||
itemContent?: string
|
||||
|
||||
handleContent?: string
|
||||
|
||||
executorId?: number
|
||||
|
||||
startTime?: string
|
||||
|
||||
endTime?: string
|
||||
|
||||
recordStatus?: string
|
||||
|
||||
createTime?: string
|
||||
|
||||
updateTime?: string
|
||||
|
||||
}
|
||||
|
||||
// MES设备维修保养记录表 API
|
||||
export const maintenanceRecordApi = {
|
||||
// 分页查询
|
||||
page(params: { page: number; pageSize: number; recordId?: string }) {
|
||||
return request({ url: '/biz/maintenanceRecord/page', method: 'get', params })
|
||||
},
|
||||
|
||||
// 获取详情
|
||||
detail(recordId: string) {
|
||||
return request({ url: `/biz/maintenanceRecord/${recordId}`, method: 'get' })
|
||||
},
|
||||
|
||||
// 新增
|
||||
create(data: MaintenanceRecord) {
|
||||
return request({ url: '/biz/maintenanceRecord', method: 'post', data })
|
||||
},
|
||||
|
||||
// 修改
|
||||
update(data: MaintenanceRecord) {
|
||||
return request({ url: '/biz/maintenanceRecord', method: 'put', data })
|
||||
},
|
||||
|
||||
// 删除
|
||||
delete(recordIds: string[]) {
|
||||
return request({ url: `/biz/maintenanceRecord/${recordIds.join(',')}`, method: 'delete' })
|
||||
},
|
||||
|
||||
// 导出
|
||||
export(params?: { ids?: string[]; recordId?: string }) {
|
||||
const p: Record<string, any> = {}
|
||||
if (params?.ids?.length) p.ids = params.ids.join(',')
|
||||
if (params?.recordId !== undefined && params?.recordId !== null) p.recordId = params.recordId
|
||||
return request({ url: `/biz/maintenanceRecord/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/maintenanceRecord/import`,
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
},
|
||||
|
||||
// 下载导入模板
|
||||
downloadTemplate() {
|
||||
return request({ url: `/biz/maintenanceRecord/template`, method: 'get', responseType: 'blob' })
|
||||
}
|
||||
}
|
||||
@ -1,100 +0,0 @@
|
||||
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' })
|
||||
}
|
||||
}
|
||||
@ -1,441 +0,0 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<n-card>
|
||||
<!-- 搜索表单 -->
|
||||
<div class="search-form">
|
||||
<n-form inline :model="searchForm" label-placement="left">
|
||||
<n-form-item label="维保记录编号(后端代码自动生成,唯一主键)">
|
||||
<n-input v-model:value="searchForm.recordId" placeholder="请输入维保记录编号(后端代码自动生成,唯一主键)" clearable />
|
||||
</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.recordId"
|
||||
: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: 600px">
|
||||
<n-form ref="formRef" :model="formData" :rules="formRules" label-placement="left" label-width="100px">
|
||||
<n-form-item label="设备ID,关联mes_equipment设备表主键" path="equipmentId">
|
||||
<n-input v-model:value="formData.equipmentId" placeholder="请输入设备ID,关联mes_equipment设备表主键" />
|
||||
</n-form-item>
|
||||
<n-form-item label="记录类型:maintain=保养,repair=维修" path="recordType">
|
||||
<n-select v-model:value="formData.recordType" placeholder="请选择记录类型:maintain=保养,repair=维修" :options="[]" />
|
||||
</n-form-item>
|
||||
<n-form-item label="故障现象/保养项目详情" path="itemContent">
|
||||
<n-input v-model:value="formData.itemContent" type="textarea" placeholder="请输入故障现象/保养项目详情" />
|
||||
</n-form-item>
|
||||
<n-form-item label="维修处理措施/保养执行内容" path="handleContent">
|
||||
<n-input v-model:value="formData.handleContent" type="textarea" placeholder="请输入维修处理措施/保养执行内容" />
|
||||
</n-form-item>
|
||||
<n-form-item label="执行人ID,关联sys_user系统用户表主键" path="executorId">
|
||||
<n-input v-model:value="formData.executorId" placeholder="请输入执行人ID,关联sys_user系统用户表主键" />
|
||||
</n-form-item>
|
||||
<n-form-item label="维保作业开始时间" path="startTime">
|
||||
<n-date-picker v-model:value="formData.startTime" type="datetime" clearable style="width: 100%" />
|
||||
</n-form-item>
|
||||
<n-form-item label="维保作业结束时间,待处理单据为空" path="endTime">
|
||||
<n-date-picker v-model:value="formData.endTime" type="datetime" clearable style="width: 100%" />
|
||||
</n-form-item>
|
||||
<n-form-item label="单据状态:pending=待处理,finished=已完成" path="recordStatus">
|
||||
<n-select v-model:value="formData.recordStatus" placeholder="请选择单据状态:pending=待处理,finished=已完成" :options="recordStatusOptions" />
|
||||
</n-form-item>
|
||||
</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="导入MES设备维修保养记录表" 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 { maintenanceRecordApi, type MaintenanceRecord } from '@/api/maintenanceRecord'
|
||||
import { dictDataApi } from '@/api/org'
|
||||
|
||||
const message = useMessage()
|
||||
const dialog = useDialog()
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
recordId: '',
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref<MaintenanceRecord[]>([])
|
||||
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: MaintenanceRecord = {
|
||||
equipmentId: '',
|
||||
recordType: '',
|
||||
itemContent: '',
|
||||
handleContent: '',
|
||||
executorId: undefined,
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
recordStatus: '',
|
||||
}
|
||||
const formData = reactive<MaintenanceRecord>({ ...defaultFormData })
|
||||
|
||||
// 字典选项(下拉框/单选框/复选框关联字典时使用)
|
||||
const recordStatusOptions = ref<{ label: string; value: any }[]>([])
|
||||
|
||||
// 表单校验规则
|
||||
const formRules = {
|
||||
equipmentId: { required: true, message: '请输入设备ID,关联mes_equipment设备表主键', trigger: 'blur' },
|
||||
recordType: { required: true, message: '请输入记录类型:maintain=保养,repair=维修', trigger: 'blur' },
|
||||
itemContent: { required: true, message: '请输入故障现象/保养项目详情', trigger: 'blur' },
|
||||
handleContent: { required: true, message: '请输入维修处理措施/保养执行内容', trigger: 'blur' },
|
||||
}
|
||||
|
||||
// 表格列
|
||||
const columns: DataTableColumns<MaintenanceRecord> = [
|
||||
{ type: 'selection' },
|
||||
{ title: '维保记录编号(后端代码自动生成,唯一主键)', key: 'recordId' },
|
||||
{ title: '设备ID,关联mes_equipment设备表主键', key: 'equipmentId' },
|
||||
{ title: '记录类型:maintain=保养,repair=维修', key: 'recordType' },
|
||||
{ title: '故障现象/保养项目详情', key: 'itemContent' },
|
||||
{ title: '维修处理措施/保养执行内容', key: 'handleContent' },
|
||||
{ title: '执行人ID,关联sys_user系统用户表主键', key: 'executorId' },
|
||||
{ title: '维保作业开始时间', key: 'startTime' },
|
||||
{ title: '维保作业结束时间,待处理单据为空', key: 'endTime' },
|
||||
{ title: '单据状态:pending=待处理,finished=已完成', key: 'recordStatus',
|
||||
render(row) {
|
||||
const val = row.recordStatus
|
||||
const opt = recordStatusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||
return opt ? opt.label : (val ?? '-')
|
||||
}
|
||||
},
|
||||
{ title: '单据创建时间', key: 'createTime', width: 180 },
|
||||
{ title: '单据最后更新时间', key: 'updateTime', width: 180 },
|
||||
{
|
||||
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 maintenanceRecordApi.page({
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
recordId: searchForm.recordId || undefined,
|
||||
})
|
||||
tableData.value = res.list
|
||||
pagination.itemCount = res.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
function handleSearch() {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
// 重置
|
||||
function handleReset() {
|
||||
searchForm.recordId = ''
|
||||
|
||||
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 = '新增MES设备维修保养记录表'
|
||||
Object.assign(formData, defaultFormData)
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑
|
||||
function handleEdit(row: MaintenanceRecord) {
|
||||
modalTitle.value = '编辑MES设备维修保养记录表'
|
||||
Object.assign(formData, row)
|
||||
if (formData.startTime && typeof formData.startTime === 'string') {
|
||||
formData.startTime = new Date(formData.startTime.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()
|
||||
}
|
||||
if (formData.updateTime && typeof formData.updateTime === 'string') {
|
||||
formData.updateTime = new Date(formData.updateTime.replace(' ', 'T')).getTime()
|
||||
}
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
// 提交
|
||||
async function handleSubmit() {
|
||||
await formRef.value?.validate()
|
||||
try {
|
||||
const submitData = { ...formData } as MaintenanceRecord
|
||||
if (typeof submitData.startTime === 'number') {
|
||||
submitData.startTime = new Date(submitData.startTime).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 (typeof submitData.updateTime === 'number') {
|
||||
submitData.updateTime = new Date(submitData.updateTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||
}
|
||||
if (submitData.recordId) {
|
||||
await maintenanceRecordApi.update(submitData)
|
||||
message.success('修改成功')
|
||||
} else {
|
||||
await maintenanceRecordApi.create(submitData)
|
||||
message.success('新增成功')
|
||||
}
|
||||
modalVisible.value = false
|
||||
loadData()
|
||||
} catch (error) {
|
||||
// 错误已在拦截器处理
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
function handleDelete(row: MaintenanceRecord) {
|
||||
dialog.warning({
|
||||
title: '提示',
|
||||
content: '确定要删除该记录吗?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
await maintenanceRecordApi.delete([row.recordId!])
|
||||
message.success('删除成功')
|
||||
loadData()
|
||||
} catch (error) {
|
||||
// 错误已在拦截器处理
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
function handleBatchDelete() {
|
||||
dialog.warning({
|
||||
title: '提示',
|
||||
content: `确定要删除选中的 ${selectedIds.value.length} 条记录吗?`,
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
await maintenanceRecordApi.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.recordId) params.recordId = searchForm.recordId
|
||||
const blob = await maintenanceRecordApi.export(params)
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = 'MES设备维修保养记录表数据.xlsx'
|
||||
link.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
} catch (error) {
|
||||
// 错误已在拦截器处理
|
||||
}
|
||||
}
|
||||
|
||||
// 下载导入模板
|
||||
async function handleDownloadTemplate() {
|
||||
try {
|
||||
const blob = await maintenanceRecordApi.downloadTemplate()
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = 'MES设备维修保养记录表导入模板.xlsx'
|
||||
link.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
} catch (error) {
|
||||
// 错误已在拦截器处理
|
||||
}
|
||||
}
|
||||
|
||||
// 导入上传
|
||||
async function handleImportUpload({ file }: UploadCustomRequestOptions) {
|
||||
if (!file.file) return
|
||||
try {
|
||||
const result = await maintenanceRecordApi.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() {
|
||||
try {
|
||||
const data = await dictDataApi.listByType('sys_status')
|
||||
recordStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: d.dictValue }))
|
||||
} catch {}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
loadDictOptions()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search-form {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.table-toolbar {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
@ -1,492 +0,0 @@
|
||||
<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>
|
||||
71
src/api/exceptionFilter.ts
Normal file
71
src/api/exceptionFilter.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { request } from '@/utils/request'
|
||||
|
||||
// 异常过滤表 类型定义
|
||||
export interface ExceptionFilter {
|
||||
id?: number
|
||||
|
||||
title?: string
|
||||
|
||||
filterCondition?: string
|
||||
|
||||
description?: string
|
||||
|
||||
status?: 0
|
||||
|
||||
deleted?: number
|
||||
|
||||
}
|
||||
|
||||
// 异常过滤表 API
|
||||
export const exceptionFilterApi = {
|
||||
// 分页查询
|
||||
page(params: { page: number; pageSize: number; id?: number; status?: number }) {
|
||||
return request({ url: '/biz/exceptionFilter/page', method: 'get', params })
|
||||
},
|
||||
|
||||
// 获取详情
|
||||
detail(id: string) {
|
||||
return request({ url: `/biz/exceptionFilter/${id}`, method: 'get' })
|
||||
},
|
||||
|
||||
// 新增
|
||||
create(data: ExceptionFilter) {
|
||||
return request({ url: '/biz/exceptionFilter', method: 'post', data })
|
||||
},
|
||||
|
||||
// 修改
|
||||
update(data: ExceptionFilter) {
|
||||
return request({ url: '/biz/exceptionFilter', method: 'put', data })
|
||||
},
|
||||
|
||||
// 删除
|
||||
delete(ids: string[]) {
|
||||
return request({ url: `/biz/exceptionFilter/${ids.join(',')}`, method: 'delete' })
|
||||
},
|
||||
|
||||
// 导出
|
||||
export(params?: { ids?: string[]; id?: number; status?: 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?.status !== undefined && params?.status !== null) p.status = params.status
|
||||
return request({ url: `/biz/exceptionFilter/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/exceptionFilter/import`,
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
},
|
||||
|
||||
// 下载导入模板
|
||||
downloadTemplate() {
|
||||
return request({ url: `/biz/exceptionFilter/template`, method: 'get', responseType: 'blob' })
|
||||
}
|
||||
}
|
||||
@ -19,6 +19,7 @@ export interface SysNotice {
|
||||
errorNotificationId?:number,
|
||||
filler?:number,
|
||||
filingStatus?:number,
|
||||
deviceName?:string
|
||||
}
|
||||
|
||||
export interface NoticeChannelOption {
|
||||
|
||||
@ -45,6 +45,8 @@ export interface OrderItem {
|
||||
|
||||
starter?: number
|
||||
|
||||
mainCode?: string
|
||||
|
||||
orderCode?: string
|
||||
|
||||
routeCode?: string
|
||||
@ -124,6 +126,10 @@ export const orderItemApi = {
|
||||
assignWork(data: any) {
|
||||
return request({ url: '/biz/orderItem/assignWork', method: 'post', data })
|
||||
},
|
||||
// 开工
|
||||
startWork(id: number) {
|
||||
return request({ url: `/biz/orderItem/${id}/startWork`, method: 'post' })
|
||||
},
|
||||
//根据生产编号查询预生产订单
|
||||
preCreation(data: any) {
|
||||
return request({ url: '/biz/orderItem/preCreation', method: 'post', data })
|
||||
|
||||
@ -88,6 +88,12 @@ export interface ProcessPlanItemVO {
|
||||
|
||||
planFinishTime?: string
|
||||
|
||||
/** 计划开始(部分接口直接返回 beginTime) */
|
||||
beginTime?: string
|
||||
|
||||
/** 计划结束(部分接口直接返回 endTime) */
|
||||
endTime?: string
|
||||
|
||||
actualStartTime?: string | null
|
||||
|
||||
actualFinishTime?: string | null
|
||||
@ -242,9 +248,9 @@ export function processItemToEntity(
|
||||
|
||||
orderItemId,
|
||||
|
||||
beginTime: item.planStartTime,
|
||||
beginTime: item.planStartTime || item.beginTime,
|
||||
|
||||
endTime: item.planFinishTime,
|
||||
endTime: item.planFinishTime || item.endTime,
|
||||
|
||||
procurement: item.procurement,
|
||||
|
||||
@ -469,6 +475,18 @@ export const orderProcessPlanApi = {
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 订单级工序顺序重排。
|
||||
* 只提交 orderItemId + 按新顺序排列的 planId,后端重写 sort(10/20/30…)。
|
||||
*/
|
||||
reorder(data: ProcessPlanReorderPayload) {
|
||||
return request({
|
||||
url: '/biz/orderProcessPlan/reorder',
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
/** 委外可选工序项 */
|
||||
@ -509,3 +527,16 @@ export interface OutsourcingCreate {
|
||||
remark?: string
|
||||
}
|
||||
|
||||
/** 工序重排明细 */
|
||||
export interface ProcessPlanReorderItem {
|
||||
planId: number
|
||||
/** 可选;不传则按数组顺序由后端分配 sort */
|
||||
sort?: number
|
||||
}
|
||||
|
||||
/** 订单级工序重排请求 */
|
||||
export interface ProcessPlanReorderPayload {
|
||||
orderItemId: number
|
||||
items: ProcessPlanReorderItem[]
|
||||
}
|
||||
|
||||
|
||||
@ -89,6 +89,10 @@ export interface KingdeeProcessRoute {
|
||||
planStartTime: string | null
|
||||
/** 计划结束时间 yyyy-MM-dd HH:mm:ss */
|
||||
planFinishTime: string | null
|
||||
/** 是否质检 1是 0否(后端按控制码推导后返回) */
|
||||
qualityInspection?: number | null
|
||||
/** 是否入库 1是 0否(后端:仅末序为 1) */
|
||||
storageEntry?: number | null
|
||||
}
|
||||
|
||||
/** 解析工序名称,兼容旧字段及后端误映射 */
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
import {onMounted, ref, watch} from "vue";
|
||||
import { Notifications } from '@vicons/ionicons5'
|
||||
import { ErrorNotification } from '@/api/errorNotification.ts'
|
||||
import {} from '@/api/system.ts'
|
||||
import {noticeApi, SysNotice} from '@/api/message'
|
||||
import type {FormInst} from "naive-ui";
|
||||
@ -12,7 +11,7 @@ const emit = defineEmits<{
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
errorNotifiaction:ErrorNotification,
|
||||
|
||||
notice:SysNotice,
|
||||
}>()
|
||||
|
||||
@ -72,7 +71,7 @@ watch(showModal, (val) => {
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
formData.value.id = props.errorNotifiaction.noticeId
|
||||
formData.value.id = props.notice.id
|
||||
formData.value.filingStatus = 1
|
||||
//修改通知表
|
||||
noticeApi.update({
|
||||
@ -114,7 +113,7 @@ async function handleSubmit() {
|
||||
<template #1>
|
||||
<n-grid x-gap="12" :cols="1">
|
||||
<n-gi>
|
||||
<h2>异常内容</h2>
|
||||
<h2>通知内容</h2>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-form
|
||||
@ -122,21 +121,21 @@ async function handleSubmit() {
|
||||
label-width="100"
|
||||
style="margin: 10px"
|
||||
>
|
||||
<n-form-item label="异常名称" path="title">
|
||||
<n-form-item label="通知标题" path="title">
|
||||
<n-input
|
||||
v-model:value="props.errorNotifiaction.title"
|
||||
v-model:value="props.notice.title"
|
||||
disabled
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="异常设备" path="devices">
|
||||
<n-form-item label="异常设备" path="title">
|
||||
<n-input
|
||||
v-model:value="props.errorNotifiaction.devices"
|
||||
v-model:value="props.notice.deviceName"
|
||||
disabled
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="通知内容" path="notifierContent">
|
||||
<n-input
|
||||
v-model:value="errorNotifiaction.notifierContent"
|
||||
v-model:value="props.notice.content"
|
||||
type="textarea"
|
||||
disabled
|
||||
/>
|
||||
@ -172,7 +171,7 @@ async function handleSubmit() {
|
||||
<div v-if="!isFili">
|
||||
<n-grid x-gap="12" :cols="1">
|
||||
<n-gi>
|
||||
<h2>异常内容</h2>
|
||||
<h2>通知内容</h2>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-form
|
||||
@ -180,21 +179,21 @@ async function handleSubmit() {
|
||||
label-width="100"
|
||||
style="margin: 10px"
|
||||
>
|
||||
<n-form-item label="异常名称" path="title">
|
||||
<n-form-item label="通知标题" path="title">
|
||||
<n-input
|
||||
v-model:value="props.errorNotifiaction.title"
|
||||
v-model:value="props.notice.title"
|
||||
disabled
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="异常设备" path="devices">
|
||||
<n-form-item label="异常设备" path="title">
|
||||
<n-input
|
||||
v-model:value="props.errorNotifiaction.devices"
|
||||
v-model:value="props.notice.deviceName"
|
||||
disabled
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="通知内容" path="notifierContent">
|
||||
<n-input
|
||||
v-model:value="errorNotifiaction.notifierContent"
|
||||
v-model:value="props.notice.content"
|
||||
type="textarea"
|
||||
disabled
|
||||
/>
|
||||
|
||||
@ -335,7 +335,6 @@
|
||||
<!--异常填报弹窗-->
|
||||
<ErrorReportModal
|
||||
v-model:show="showErrorReportModal"
|
||||
:errorNotifiaction ="errorNotifiaction"
|
||||
:notice = "notice"
|
||||
/>
|
||||
</n-layout>
|
||||
@ -609,40 +608,22 @@ function stripHtml(html: string | undefined): string {
|
||||
}
|
||||
|
||||
// 点击通知
|
||||
//异常订阅内容
|
||||
const errorNotifiaction = ref({
|
||||
title: '',
|
||||
devices: '',
|
||||
sendingMethodName: '',
|
||||
notifierName: '',
|
||||
notifierContent: ''
|
||||
})
|
||||
//是否需要填报
|
||||
const notice = ref<SysNotice>( {})
|
||||
async function handleNoticeClick(item: SysNotice) {
|
||||
|
||||
|
||||
const res = await errorNotificationApi.detail(item.errorNotificationId);
|
||||
errorNotifiaction.value = {...res}
|
||||
errorNotifiaction.value.noticeId = item.id
|
||||
notice.value = item
|
||||
//异常填报直接弹窗
|
||||
showErrorReportModal.value = true
|
||||
// if (item.abnormalReporting) {
|
||||
//
|
||||
// }else {
|
||||
// // 标记已读
|
||||
// if (item.id) {
|
||||
// try {
|
||||
// await noticeApi.markAsRead(item.id)
|
||||
// // 刷新未读数量
|
||||
// loadUnreadCount()
|
||||
// } catch (error) {
|
||||
// // 忽略
|
||||
// }
|
||||
// }
|
||||
// router.push({ path: '/message/notice', query: { id: item.id?.toString() } })
|
||||
// }
|
||||
// 标记已读
|
||||
if (item.id) {
|
||||
try {
|
||||
await noticeApi.markAsRead(item.id)
|
||||
// 刷新未读数量
|
||||
loadUnreadCount()
|
||||
} catch (error) {
|
||||
// 忽略
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 点击聊天
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
import { normalizeKingdeeProcessRoute, type KingdeePrdMo, type KingdeeProcessRoute } from '@/api/orderProject'
|
||||
import {
|
||||
normalizeKingdeeProcessRoute,
|
||||
type KingdeePrdMo,
|
||||
type KingdeeProcessRoute,
|
||||
} from '@/api/orderProject'
|
||||
|
||||
export type KingdeeMoTableRow = Omit<KingdeePrdMo, 'children'> & { processRoutes: KingdeeProcessRoute[] }
|
||||
|
||||
@ -11,6 +15,9 @@ export function mapKingdeeMoListToTableRows(data: KingdeePrdMo[]): KingdeeMoTabl
|
||||
...r,
|
||||
planStartTime: r.planStartTime ?? null,
|
||||
planFinishTime: r.planFinishTime ?? null,
|
||||
// 质检/入库由后端查询金蝶时已写入,前端原样带出
|
||||
qualityInspection: r.qualityInspection ?? null,
|
||||
storageEntry: r.storageEntry ?? null,
|
||||
}))
|
||||
return { ...rest, processRoutes: routes }
|
||||
})
|
||||
|
||||
@ -327,8 +327,8 @@ const columns: DataTableColumns<BasicProcessPlan> = [
|
||||
return h(NTag,{type: opt ? 'success' : 'error', size: 'small'},{default: () => (opt ? '是' : '否')})
|
||||
}
|
||||
},
|
||||
{ title: '工作中心', key: 'workCenterName' },
|
||||
{ title: '生产车间', key: 'departmentName' },
|
||||
{ title: '工作中心', key: 'sectionName' },
|
||||
{ title: '生产车间', key: 'workShopName' },
|
||||
{ title: '工序说明', key: 'operDescription' },
|
||||
{ title: '状态', key: 'status',
|
||||
render(row){
|
||||
|
||||
@ -112,12 +112,18 @@
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="所属工段" path="sectionId">
|
||||
<n-select
|
||||
<!-- <n-select
|
||||
v-model:value="formData.sectionId"
|
||||
:options="sectionList"
|
||||
placeholder="请选择所属工段"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/> -->
|
||||
<n-tree-select
|
||||
v-model:value="formData.sectionId"
|
||||
cascade
|
||||
checkable
|
||||
:options="sectionList"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="设备类型" path="deviceTypeId">
|
||||
@ -267,12 +273,14 @@ import {deviceApi, type Device} from '@/api/device'
|
||||
|
||||
import {dictDataApi} from '@/api/org'
|
||||
import {sectionApi} from '@/api/section'
|
||||
import {SysUser, userApi} from '@/api/system'
|
||||
import {deptApi, type SysDept , userApi} from '@/api/system'
|
||||
import {DeviceAbnormalRecord, deviceAbnormalRecordApi} from "@/api/deviceAbnormalRecord.ts";
|
||||
|
||||
import DeviceAbnormalRecordPage from '@/views/biz/device/DeviceAbnomarlRecord.vue'
|
||||
import DeviceOperationLogPage from "@/views/biz/device/DeviceOperationLog.vue";
|
||||
|
||||
import DeviceDispatchLogPage from "@/views/biz/device/DeviceDispatchLog.vue";
|
||||
import { S } from 'vue-router/dist/router-CWoNjPRp.mjs'
|
||||
|
||||
const message = useMessage()
|
||||
const dialog = useDialog()
|
||||
const router = useRouter()
|
||||
@ -283,8 +291,15 @@ const deviceId = ref<number>(undefined)
|
||||
//设备负责人列表
|
||||
const deviceManagerList = ref<{ label: string, value: any }[]>([])
|
||||
|
||||
let sectionList = reactive<{ label: string, value: any }[]>([])
|
||||
// 树形下拉选项递归接口
|
||||
interface TreeNode {
|
||||
label: string;
|
||||
key: number | string;
|
||||
children?: TreeNode[];
|
||||
}
|
||||
|
||||
let sectionList = reactive<TreeNode[]>([])
|
||||
// const sectionList = ref<SysDept[]>([]) // 树形数据
|
||||
//设备异常记录
|
||||
const deviceErrorLogList = ref<DeviceAbnormalRecord[]>([])
|
||||
const deviceAbnormalRecordDrawerVisible = ref<Boolean>(false) //异常记录抽屉显示
|
||||
@ -664,11 +679,26 @@ async function loadDictOptions() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//加载工段
|
||||
async function loadSection() {
|
||||
const data = await sectionApi.listNoParam()
|
||||
sectionList = data.map((d: any) => ({label: d.sectionName, value: (Number(d.id) || d.id)}))
|
||||
const data = await deptApi.tree()
|
||||
sectionList = buildOptions(data);
|
||||
}
|
||||
|
||||
function buildOptions(d: any[]): TreeNode[] {
|
||||
return d.map(item => {
|
||||
// 安全id转换:0也能正常转为数字,不会被||过滤
|
||||
const idNum = Number(item.id);
|
||||
const key = !isNaN(idNum) ? idNum : item.id;
|
||||
return {
|
||||
label: item.deptName,
|
||||
key,
|
||||
// 子节点存在且为数组才递归处理
|
||||
children: Array.isArray(item.children) && item.children.length
|
||||
? buildOptions(item.children)
|
||||
: undefined
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
//加载人员
|
||||
|
||||
@ -4,8 +4,11 @@
|
||||
<!-- 搜索表单 -->
|
||||
<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 label="主键ID">
|
||||
<n-input v-model:value="searchForm.id" placeholder="请输入主键ID" clearable />
|
||||
</n-form-item>
|
||||
<n-form-item label="状态">
|
||||
<n-select v-model:value="searchForm.status" placeholder="请选择状态" clearable style="width: 150px" :options="statusOptions" />
|
||||
</n-form-item>
|
||||
<n-form-item>
|
||||
<n-space>
|
||||
@ -61,29 +64,26 @@
|
||||
<!-- 新增/编辑弹窗 -->
|
||||
<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="qcId">
|
||||
<n-input v-model:value="formData.qcId" placeholder="请输入质检编号" />
|
||||
<n-form-item label="异常过滤标题" path="title">
|
||||
<n-input v-model:value="formData.title" placeholder="请输入异常过滤标题" />
|
||||
</n-form-item>
|
||||
<n-form-item label="让步接收数量" path="concessionQty">
|
||||
<n-input v-model:value="formData.concessionQty" placeholder="请输入让步接收数量" />
|
||||
<n-form-item label="过滤条件" path="filterCondition">
|
||||
<n-input v-model:value="formData.filterCondition" type="textarea" placeholder="请输入过滤条件" />
|
||||
</n-form-item>
|
||||
<n-form-item label="申请原因" path="applyRemark">
|
||||
<n-input v-model:value="formData.applyRemark" type="textarea" placeholder="请输入申请原因" />
|
||||
<n-form-item label="描述" path="description">
|
||||
<n-input v-model:value="formData.description" type="textarea" placeholder="请输入描述" />
|
||||
</n-form-item>
|
||||
<n-form-item label="申请内容" path="applyContent">
|
||||
<n-input v-model:value="formData.applyContent" type="textarea" placeholder="请输入申请内容" />
|
||||
</n-form-item>
|
||||
<n-form-item label="申请人id(发去人id)" path="applyUserId">
|
||||
<n-input v-model:value="formData.applyUserId" placeholder="请输入申请人id(发去人id)" />
|
||||
</n-form-item>
|
||||
<n-form-item label="部门id" path="deptId">
|
||||
<n-input v-model:value="formData.deptId" placeholder="请输入部门id" />
|
||||
</n-form-item>
|
||||
<n-form-item label="钉钉单据id" path="instanceId">
|
||||
<n-input v-model:value="formData.instanceId" placeholder="请输入钉钉单据id" />
|
||||
</n-form-item>
|
||||
<n-form-item label="审批状态 1待审批 2审批通过 3 审批驳回" path="approveStatus">
|
||||
<n-select v-model:value="formData.approveStatus" placeholder="请选择审批状态 1待审批 2审批通过 3 审批驳回" :options="approveStatusOptions" />
|
||||
<n-form-item label="状态">
|
||||
<n-radio-group v-model:value="formData.status" :default-value="0" >
|
||||
<n-space>
|
||||
<n-radio :value="1">
|
||||
启用
|
||||
</n-radio>
|
||||
<n-radio :value="0">
|
||||
禁用
|
||||
</n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #footer>
|
||||
@ -95,7 +95,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>
|
||||
@ -131,7 +131,7 @@
|
||||
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 { concessionApplyApi, type ConcessionApply } from '@/api/concessionApply'
|
||||
import { exceptionFilterApi, type ExceptionFilter } from '@/api/exceptionFilter'
|
||||
import { dictDataApi } from '@/api/org'
|
||||
|
||||
const message = useMessage()
|
||||
@ -140,10 +140,11 @@ const dialog = useDialog()
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
id: null as number | null,
|
||||
status: null as number | null,
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref<ConcessionApply[]>([])
|
||||
const tableData = ref<ExceptionFilter[]>([])
|
||||
const loading = ref(false)
|
||||
const selectedIds = ref<number[]>([])
|
||||
const pagination = reactive({
|
||||
@ -159,47 +160,35 @@ const modalVisible = ref(false)
|
||||
const modalTitle = ref('')
|
||||
const importModalVisible = ref(false)
|
||||
const formRef = ref()
|
||||
const defaultFormData: ConcessionApply = {
|
||||
qcId: undefined,
|
||||
concessionQty: undefined,
|
||||
applyRemark: '',
|
||||
applyContent: '',
|
||||
applyFile: '',
|
||||
applyUserId: undefined,
|
||||
deptId: undefined,
|
||||
instanceId: '',
|
||||
approveStatus: undefined,
|
||||
const defaultFormData: ExceptionFilter = {
|
||||
title: '',
|
||||
filterCondition: '',
|
||||
description: '',
|
||||
status: undefined,
|
||||
}
|
||||
const formData = reactive<ConcessionApply>({ ...defaultFormData })
|
||||
const formData = reactive<ExceptionFilter>({ ...defaultFormData })
|
||||
|
||||
// 字典选项(下拉框/单选框/复选框关联字典时使用)
|
||||
const approveStatusOptions = ref<{ label: string; value: any }[]>([])
|
||||
const statusOptions = ref<{ label: string; value: any }[]>([])
|
||||
|
||||
// 表单校验规则
|
||||
const formRules = {
|
||||
}
|
||||
|
||||
// 表格列
|
||||
const columns: DataTableColumns<ConcessionApply> = [
|
||||
const columns: DataTableColumns<ExceptionFilter> = [
|
||||
{ type: 'selection' },
|
||||
{ title: '主键id', key: 'id' },
|
||||
{ title: '质检编号', key: 'qcId' },
|
||||
{ title: '让步接收数量', key: 'concessionQty' },
|
||||
{ title: '申请原因', key: 'applyRemark' },
|
||||
{ title: '申请内容', key: 'applyContent' },
|
||||
{ title: '申请附件', key: 'applyFile' },
|
||||
{ title: '申请人id(发去人id)', key: 'applyUserId' },
|
||||
{ title: '部门id', key: 'deptId' },
|
||||
{ title: '钉钉单据id', key: 'instanceId' },
|
||||
{ title: '审批状态 1待审批 2审批通过 3 审批驳回', key: 'approveStatus',
|
||||
{ title: '主键ID', key: 'id' },
|
||||
{ title: '异常过滤标题', key: 'title' },
|
||||
{ title: '过滤条件', key: 'filterCondition' },
|
||||
{ title: '描述', key: 'description' },
|
||||
{ title: '状态', key: 'status',
|
||||
render(row) {
|
||||
const val = row.approveStatus
|
||||
const opt = approveStatusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||
const val = row.status
|
||||
const opt = statusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||
return opt ? opt.label : (val ?? '-')
|
||||
}
|
||||
},
|
||||
{ title: '创建时间', key: 'createTime', width: 180 },
|
||||
{ title: '更新时间', key: 'updateTime', width: 180 },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
@ -222,10 +211,11 @@ const columns: DataTableColumns<ConcessionApply> = [
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await concessionApplyApi.page({
|
||||
const res = await exceptionFilterApi.page({
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
id: searchForm.id || undefined,
|
||||
status: searchForm.status || undefined,
|
||||
})
|
||||
tableData.value = res.list
|
||||
pagination.itemCount = res.total
|
||||
@ -244,6 +234,8 @@ function handleSearch() {
|
||||
function handleReset() {
|
||||
searchForm.id = null
|
||||
|
||||
searchForm.status = null
|
||||
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
@ -266,21 +258,15 @@ function handleCheck(keys: Array<string | number>) {
|
||||
|
||||
// 新增
|
||||
function handleAdd() {
|
||||
modalTitle.value = '新增让步接收申请表'
|
||||
modalTitle.value = '新增异常过滤表'
|
||||
Object.assign(formData, defaultFormData)
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑
|
||||
function handleEdit(row: ConcessionApply) {
|
||||
modalTitle.value = '编辑让步接收申请表'
|
||||
function handleEdit(row: ExceptionFilter) {
|
||||
modalTitle.value = '编辑异常过滤表'
|
||||
Object.assign(formData, row)
|
||||
if (formData.createTime && typeof formData.createTime === 'string') {
|
||||
formData.createTime = new Date(formData.createTime.replace(' ', 'T')).getTime()
|
||||
}
|
||||
if (formData.updateTime && typeof formData.updateTime === 'string') {
|
||||
formData.updateTime = new Date(formData.updateTime.replace(' ', 'T')).getTime()
|
||||
}
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
@ -288,18 +274,12 @@ function handleEdit(row: ConcessionApply) {
|
||||
async function handleSubmit() {
|
||||
await formRef.value?.validate()
|
||||
try {
|
||||
const submitData = { ...formData } as ConcessionApply
|
||||
if (typeof submitData.createTime === 'number') {
|
||||
submitData.createTime = new Date(submitData.createTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||
}
|
||||
if (typeof submitData.updateTime === 'number') {
|
||||
submitData.updateTime = new Date(submitData.updateTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||
}
|
||||
const submitData = { ...formData } as ExceptionFilter
|
||||
if (submitData.id) {
|
||||
await concessionApplyApi.update(submitData)
|
||||
await exceptionFilterApi.update(submitData)
|
||||
message.success('修改成功')
|
||||
} else {
|
||||
await concessionApplyApi.create(submitData)
|
||||
await exceptionFilterApi.create(submitData)
|
||||
message.success('新增成功')
|
||||
}
|
||||
modalVisible.value = false
|
||||
@ -310,7 +290,7 @@ async function handleSubmit() {
|
||||
}
|
||||
|
||||
// 删除
|
||||
function handleDelete(row: ConcessionApply) {
|
||||
function handleDelete(row: ExceptionFilter) {
|
||||
dialog.warning({
|
||||
title: '提示',
|
||||
content: '确定要删除该记录吗?',
|
||||
@ -318,7 +298,7 @@ function handleDelete(row: ConcessionApply) {
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
await concessionApplyApi.delete([row.id!])
|
||||
await exceptionFilterApi.delete([row.id!])
|
||||
message.success('删除成功')
|
||||
loadData()
|
||||
} catch (error) {
|
||||
@ -337,7 +317,7 @@ function handleBatchDelete() {
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
await concessionApplyApi.delete(selectedIds.value)
|
||||
await exceptionFilterApi.delete(selectedIds.value)
|
||||
message.success('删除成功')
|
||||
selectedIds.value = []
|
||||
loadData()
|
||||
@ -354,11 +334,12 @@ async function handleExport() {
|
||||
const params: Record<string, any> = {}
|
||||
if (selectedIds.value.length > 0) params.ids = selectedIds.value
|
||||
if (searchForm.id != null) params.id = searchForm.id
|
||||
const blob = await concessionApplyApi.export(params)
|
||||
if (searchForm.status != null) params.status = searchForm.status
|
||||
const blob = await exceptionFilterApi.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) {
|
||||
@ -369,11 +350,11 @@ async function handleExport() {
|
||||
// 下载导入模板
|
||||
async function handleDownloadTemplate() {
|
||||
try {
|
||||
const blob = await concessionApplyApi.downloadTemplate()
|
||||
const blob = await exceptionFilterApi.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) {
|
||||
@ -385,7 +366,7 @@ async function handleDownloadTemplate() {
|
||||
async function handleImportUpload({ file }: UploadCustomRequestOptions) {
|
||||
if (!file.file) return
|
||||
try {
|
||||
const result = await concessionApplyApi.importData(file.file)
|
||||
const result = await exceptionFilterApi.importData(file.file)
|
||||
if (result.fail > 0) {
|
||||
dialog.warning({
|
||||
title: '导入结果',
|
||||
@ -406,7 +387,7 @@ async function handleImportUpload({ file }: UploadCustomRequestOptions) {
|
||||
async function loadDictOptions() {
|
||||
try {
|
||||
const data = await dictDataApi.listByType('sys_status')
|
||||
approveStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
|
||||
statusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@ -37,10 +37,10 @@
|
||||
<n-button type="error" :disabled="kingdeeLoading" @click="reCrawlReorderForm">
|
||||
重新读取数据
|
||||
</n-button>
|
||||
<n-button type="primary" :disabled="kingdeeLoading" @click="handleSaveDraft">
|
||||
<n-button type="primary" :disabled="kingdeeLoading" :loading="actionLoading" @click="handleSaveDraft">
|
||||
保存草稿
|
||||
</n-button>
|
||||
<n-button type="info" :disabled="kingdeeLoading" @click="handleSyncOrderAndPlan">
|
||||
<n-button type="info" :disabled="kingdeeLoading" :loading="actionLoading" @click="handleSyncOrderAndPlan">
|
||||
同步订单计划
|
||||
</n-button>
|
||||
<n-button @click="$emit('close-modal')">关闭</n-button>
|
||||
@ -54,7 +54,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, h, computed, defineProps, onMounted, watch, defineEmits} from 'vue'
|
||||
import { NButton, NSpace, NIcon, NTag, NDatePicker, NDataTable, useMessage, useDialog, type DataTableColumns } from 'naive-ui'
|
||||
import { type KingdeePrdMo, type KingdeeProcessRoute } from '@/api/orderProject'
|
||||
import { type KingdeePrdMo, type KingdeeProcessRoute, resolveProcessName } from '@/api/orderProject'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { CalendarOutline } from '@vicons/ionicons5'
|
||||
|
||||
@ -72,20 +72,27 @@ const props = defineProps({
|
||||
selectedId:{
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
/** 父页面查询/重读数据时的加载状态 */
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
kingdeeTableData.value = props.kingdeeTableData as KingdeeMoTableRow[]
|
||||
kingdeeTableData.value = (props.kingdeeTableData as KingdeeMoTableRow[]) || []
|
||||
})
|
||||
|
||||
watch(()=>props.kingdeeTableData,()=>{
|
||||
kingdeeTableData.value = props.kingdeeTableData as KingdeeMoTableRow[]
|
||||
},{ deep: true } )
|
||||
|
||||
watch(() => props.kingdeeTableData, () => {
|
||||
kingdeeTableData.value = (props.kingdeeTableData as KingdeeMoTableRow[]) || []
|
||||
}, { deep: true })
|
||||
|
||||
const kingdeeModalVisible = ref(false)
|
||||
const kingdeeLoading = ref(false)
|
||||
/** 子组件自身操作(保存/同步)的加载状态 */
|
||||
const actionLoading = ref(false)
|
||||
/** 表格与按钮共用:父级查询 loading 或本地操作 loading */
|
||||
const kingdeeLoading = computed(() => props.loading || actionLoading.value)
|
||||
/** 主表行:工序仅存 processRoutes,避免 children 触发树形重复行 */
|
||||
type KingdeeMoTableRow = Omit<KingdeePrdMo, 'children'> & { processRoutes: KingdeeProcessRoute[] }
|
||||
const kingdeeTableData = ref<KingdeeMoTableRow[]>([])
|
||||
@ -93,7 +100,7 @@ const kingdeeExpandedKeys = ref<Array<string | number>>([])
|
||||
// const kingdeeCurrentProjectId = ref<number | null>(null) // 记录当前正在查询的订单ID
|
||||
const kingdeeTableMaxHeight = 'min(78vh, 720px)'
|
||||
const kingdeeMoScrollX = 1820
|
||||
const kingdeeProcessScrollX = 1360
|
||||
const kingdeeProcessScrollX = 1540
|
||||
|
||||
const kingdeeMoColumns: DataTableColumns<KingdeeMoTableRow> = [
|
||||
{
|
||||
@ -155,7 +162,12 @@ const kingdeeProcessColumns: DataTableColumns<KingdeeProcessRoute> = [
|
||||
return row.operNumber != null ? String(row.operNumber) : '-'
|
||||
}
|
||||
},
|
||||
{ title: '工序名称', key: 'processProperty', width: 100, align: 'center' },
|
||||
{
|
||||
title: '工序名称', key: 'processName', width: 100, align: 'center',
|
||||
render(row) {
|
||||
return resolveProcessName(row) || '-'
|
||||
}
|
||||
},
|
||||
{ title: '工序说明', key: 'operDescription', width: 140, ellipsis: { tooltip: true } },
|
||||
{ title: '工作中心', key: 'workCenterName', width: 110, align: 'center', ellipsis: { tooltip: true } },
|
||||
{ title: '生产车间', key: 'departmentName', width: 110, align: 'center', ellipsis: { tooltip: true } },
|
||||
@ -179,6 +191,28 @@ const kingdeeProcessColumns: DataTableColumns<KingdeeProcessRoute> = [
|
||||
},
|
||||
{ title: '活动单位', key: 'activityUnit', width: 80, align: 'center' },
|
||||
{ title: '控制码', key: 'optCtrlCodeName', width: 120, align: 'center', ellipsis: { tooltip: true } },
|
||||
{
|
||||
title: '是否质检', key: 'qualityInspection', width: 88, align: 'center',
|
||||
render(row) {
|
||||
const yes = Number(row.qualityInspection) === 1
|
||||
return h(NTag, { size: 'small', type: yes ? 'success' : 'default', bordered: false }, {
|
||||
default: () => (yes ? '是' : '否'),
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '是否入库', key: 'storageEntry', width: 88, align: 'center',
|
||||
render(row) {
|
||||
const yes = Number(row.storageEntry) === 1
|
||||
return h(NTag, {
|
||||
size: 'small',
|
||||
type: yes ? 'warning' : 'default',
|
||||
bordered: false,
|
||||
}, {
|
||||
default: () => (yes ? '是' : '否'),
|
||||
})
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@ -298,29 +332,32 @@ function handleGanttSchedule() {
|
||||
router.push('/biz/orderProject/gantt')
|
||||
}
|
||||
|
||||
// 同步订单和计划表
|
||||
// 同步订单和计划表(等待父组件接口完成后再关 loading,避免第一次点击“没反应”)
|
||||
async function handleSyncOrderAndPlan() {
|
||||
if (!props.selectedId) return
|
||||
if (!props.selectedId || actionLoading.value) return
|
||||
|
||||
kingdeeLoading.value = true
|
||||
actionLoading.value = true
|
||||
try {
|
||||
// 恢复数据结构,将 processRoutes 转换为 children
|
||||
const draftData = kingdeeTableData.value.map(mo => {
|
||||
const { processRoutes, ...rest } = mo
|
||||
return { ...rest, children: processRoutes } as KingdeePrdMo
|
||||
})
|
||||
|
||||
emit('synchronizationCallback', props.selectedId, draftData, (res) => {
|
||||
if("success" === res){
|
||||
message.success('同步订单计划成功')
|
||||
emit('close-modal')
|
||||
}
|
||||
const res = await new Promise<unknown>((resolve, reject) => {
|
||||
emit('synchronizationCallback', props.selectedId, draftData, (result: unknown, err?: unknown) => {
|
||||
if (err) reject(err)
|
||||
else resolve(result)
|
||||
})
|
||||
kingdeeModalVisible.value = false
|
||||
} catch (error) {
|
||||
})
|
||||
|
||||
if (res === 'success') {
|
||||
message.success('同步订单计划成功')
|
||||
emit('close-modal')
|
||||
}
|
||||
} catch {
|
||||
// 错误拦截器已处理
|
||||
} finally {
|
||||
kingdeeLoading.value = false
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -336,15 +373,8 @@ const reCrawlReorderForm = () => {
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
kingdeeLoading.value = true
|
||||
try {
|
||||
emit('reCrawlCallback', props.selectedId, true)
|
||||
message.success('重启读取成功')
|
||||
} catch (error) {
|
||||
// 错误拦截器已处理
|
||||
} finally {
|
||||
kingdeeLoading.value = false
|
||||
}
|
||||
// 加载态由父组件 reCrawlReorderForm 的 kingdeeLoading 控制
|
||||
emit('reCrawlCallback', props.selectedId, true)
|
||||
}
|
||||
})
|
||||
|
||||
@ -356,7 +386,7 @@ const handleSaveDraft = () => {
|
||||
// // 暂存金蝶生产订单数据(草稿)
|
||||
if (!props.selectedId) return
|
||||
|
||||
kingdeeLoading.value = true
|
||||
actionLoading.value = true
|
||||
try {
|
||||
const draftData = kingdeeTableData.value.map(mo => {
|
||||
const { processRoutes, ...rest } = mo
|
||||
@ -367,7 +397,7 @@ const handleSaveDraft = () => {
|
||||
} catch (error) {
|
||||
// 错误拦截器已处理
|
||||
} finally {
|
||||
kingdeeLoading.value = false
|
||||
actionLoading.value = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,99 +1,95 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<n-card>
|
||||
<!-- 搜索表单 -->
|
||||
<div class="search-form">
|
||||
<n-form inline :model="searchForm" label-placement="left">
|
||||
<n-form-item label="物料编码">
|
||||
<n-input v-model:value="searchForm.code" placeholder="请输入物料编码" clearable />
|
||||
</n-form-item>
|
||||
<n-form-item label="项目名称">
|
||||
<n-input v-model:value="searchForm.projectName" placeholder="请输入产品名称" clearable />
|
||||
</n-form-item>
|
||||
<n-form-item label="开始时间">
|
||||
<n-date-picker v-model:value="searchForm.beginTime" type="datetime" clearable />
|
||||
</n-form-item>
|
||||
<n-form-item label="结束时间">
|
||||
<n-date-picker v-model:value="searchForm.endTime" type="datetime" clearable />
|
||||
</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="order-layout">
|
||||
<!-- 左侧生产令号 -->
|
||||
<n-card class="order-tree-card" size="small">
|
||||
<template #header>
|
||||
<div class="order-tree-header">生产令号</div>
|
||||
</template>
|
||||
<div class="order-search">
|
||||
<n-input v-model:value="projectTreeSearch" placeholder="搜索生产令号" clearable size="small">
|
||||
<template #prefix>
|
||||
<n-icon><SearchOutline /></n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
</div>
|
||||
<div class="order-tree-wrapper">
|
||||
<n-tree
|
||||
:data="projectTreeData"
|
||||
:pattern="projectTreeSearch"
|
||||
:selected-keys="selectedProjectKeys"
|
||||
:node-props="projectNodeProps"
|
||||
key-field="id"
|
||||
label-field="label"
|
||||
selectable
|
||||
block-line
|
||||
/>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 工具栏 -->
|
||||
<div class="table-toolbar">
|
||||
<n-space>
|
||||
<!--<n-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="handleAdd"
|
||||
<!-- 右侧生产订单列表 -->
|
||||
<n-card class="order-list-card" size="small">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ selectedProjectLabel ? `【${selectedProjectLabel}】生产订单` : '全部生产订单' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<div class="search-form">
|
||||
<n-form inline :model="searchForm" label-placement="left">
|
||||
<n-form-item label="物料编码">
|
||||
<n-input v-model:value="searchForm.code" placeholder="请输入物料编码" clearable />
|
||||
</n-form-item>
|
||||
<n-form-item label="开始时间">
|
||||
<n-date-picker v-model:value="searchForm.beginTime" type="datetime" clearable />
|
||||
</n-form-item>
|
||||
<n-form-item label="结束时间">
|
||||
<n-date-picker v-model:value="searchForm.endTime" type="datetime" clearable />
|
||||
</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>
|
||||
|
||||
<n-data-table
|
||||
size="small"
|
||||
remote
|
||||
striped
|
||||
:border="false"
|
||||
:single-line="false"
|
||||
:columns="columns"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
/>
|
||||
<div class="pagination-container" style="display: flex; justify-content: flex-end; margin-top: 12px">
|
||||
<n-pagination
|
||||
v-model:page="pagination.page"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:item-count="pagination.itemCount"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
show-size-picker
|
||||
show-quick-jumper
|
||||
@update:page="handlePageChange"
|
||||
@update:page-size="handlePageSizeChange"
|
||||
>
|
||||
<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>
|
||||
|
||||
<!-- 表格 -->
|
||||
<!--
|
||||
:row-key="(row) => row.id"
|
||||
:scroll-x="1200"
|
||||
@update:page="handlePageChange"
|
||||
@update:page-size="handlePageSizeChange"
|
||||
@update:checked-row-keys="handleCheck"
|
||||
:pagination="pagination"
|
||||
-->
|
||||
<!--flex-height-->
|
||||
<n-data-table
|
||||
size="small"
|
||||
remote
|
||||
striped
|
||||
:border="false"
|
||||
:single-line="false"
|
||||
:columns="columns"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
/>
|
||||
<div class="pagination-container" style="display: flex; justify-content: flex-end; margin-top: 12px">
|
||||
<n-pagination
|
||||
v-model:page="pagination.page"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:item-count="pagination.itemCount"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
show-size-picker
|
||||
show-quick-jumper
|
||||
@update:page="handlePageChange"
|
||||
@update:page-size="handlePageSizeChange"
|
||||
>
|
||||
<template #prefix>
|
||||
共 {{ pagination.itemCount }} 条
|
||||
</template>
|
||||
</n-pagination>
|
||||
</div>
|
||||
</n-card>
|
||||
<template #prefix>
|
||||
共 {{ pagination.itemCount }} 条
|
||||
</template>
|
||||
</n-pagination>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
|
||||
<!-- 新增/编辑弹窗 -->
|
||||
<n-modal
|
||||
@ -371,6 +367,7 @@
|
||||
<KingdeeVue
|
||||
:kingdeeTableData="kingdeeTableData"
|
||||
:selectedId="kingdeeOrderOtemId"
|
||||
:loading="kingdeeLoading"
|
||||
@close-modal="kingdeeModalVisible = false"
|
||||
@holdTemporarilyCallback="holdTemporarilySave"
|
||||
@synchronizationCallback="synchronizationSave"
|
||||
@ -381,16 +378,19 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, h, onMounted } from 'vue'
|
||||
import { NButton, NSpace, NIcon, useMessage, useDialog, type UploadCustomRequestOptions} from 'naive-ui'
|
||||
import { ref, reactive, h, computed, onMounted, type HTMLAttributes } from 'vue'
|
||||
import { NButton, NSpace, NIcon, NTag, useMessage, useDialog, type UploadCustomRequestOptions, type TreeOption } from 'naive-ui'
|
||||
import { SearchOutline, RefreshOutline, CreateOutline, PlayOutline, DocumentTextOutline } from '@vicons/ionicons5'
|
||||
import { orderItemApi, type OrderItem } from '@/api/orderItem'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { VueDraggable } from 'vue-draggable-plus'
|
||||
|
||||
import KingdeeVue from '@/views/biz/kingdee/index.vue'
|
||||
|
||||
import { type KingdeePrdMo, type KingdeeProcessRoute } from '@/api/orderProject'
|
||||
import { type KingdeePrdMo } from '@/api/orderProject'
|
||||
import { mapKingdeeMoListToTableRows, type KingdeeMoTableRow } from '@/utils/kingdeeSchedule'
|
||||
import { userApi } from '@/api/system'
|
||||
import { dictDataApi } from '@/api/org'
|
||||
|
||||
const message = useMessage()
|
||||
const dialog = useDialog()
|
||||
@ -416,6 +416,56 @@ const searchForm = reactive<any>({
|
||||
endTime: null as number | null,
|
||||
})
|
||||
|
||||
// 左侧生产令号树
|
||||
type ProjectTreeNode = {
|
||||
id: number
|
||||
label: string
|
||||
projectCode: string
|
||||
projectName: string
|
||||
}
|
||||
const projectTreeSearch = ref('')
|
||||
const projectTreeData = ref<ProjectTreeNode[]>([])
|
||||
const selectedProjectKeys = ref<Array<string | number>>([])
|
||||
const selectedProjectId = ref<number | undefined>(undefined)
|
||||
const selectedProjectLabel = ref('')
|
||||
|
||||
const projectNodeProps = ({ option }: { option: TreeOption }): HTMLAttributes => {
|
||||
return {
|
||||
onClick() {
|
||||
const id = option.id as number
|
||||
if (selectedProjectId.value === id) {
|
||||
selectedProjectId.value = undefined
|
||||
selectedProjectKeys.value = []
|
||||
selectedProjectLabel.value = ''
|
||||
} else {
|
||||
selectedProjectId.value = id
|
||||
selectedProjectKeys.value = [id]
|
||||
selectedProjectLabel.value = (option.projectCode as string) || (option.label as string) || ''
|
||||
}
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProjectTree() {
|
||||
try {
|
||||
const data = await orderItemApi.xmoptions({})
|
||||
projectTreeData.value = (Array.isArray(data) ? data : []).map((item: any) => {
|
||||
const projectCode = item?.additionParams?.projectCode || ''
|
||||
const projectName = item?.label || ''
|
||||
return {
|
||||
id: Number(item.value),
|
||||
label: projectCode || projectName,
|
||||
projectCode,
|
||||
projectName,
|
||||
}
|
||||
}).filter((n: ProjectTreeNode) => n.id && n.label)
|
||||
} catch {
|
||||
projectTreeData.value = []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 表格数据
|
||||
@ -466,25 +516,30 @@ const defaultFormData = {
|
||||
const formData = reactive<any>({ ...defaultFormData })
|
||||
|
||||
// 字典选项(下拉框/单选框/复选框关联字典时使用)
|
||||
const workshopList = ref<{ label: string; value: any; class: any }[]>([])
|
||||
|
||||
// 表单校验规则
|
||||
const formRules = {
|
||||
}
|
||||
|
||||
// 表格列
|
||||
const columns = [
|
||||
// 表格列(computed:字典异步加载后能触发表格重渲染)
|
||||
const columns = computed(() => [
|
||||
{
|
||||
type: 'selection'
|
||||
},
|
||||
{
|
||||
align:'center',
|
||||
title: '生产令号',
|
||||
key: 'mainCode',
|
||||
width: '140'
|
||||
},
|
||||
{
|
||||
align:'center',
|
||||
title: '产品名称',
|
||||
key: 'projectName',
|
||||
// ellipsis: {
|
||||
// tooltip: true
|
||||
// }
|
||||
minWidth: 150,
|
||||
title: '订单编号',
|
||||
key: 'orderCode',
|
||||
width:"120"
|
||||
},
|
||||
|
||||
{
|
||||
align:'center',
|
||||
title: '物料名称',
|
||||
@ -497,12 +552,7 @@ const columns = [
|
||||
key: 'code',
|
||||
width:"120"
|
||||
},
|
||||
{
|
||||
align:'center',
|
||||
title: '订单编号',
|
||||
key: 'orderCode',
|
||||
width:"120"
|
||||
},
|
||||
|
||||
{
|
||||
align:'center',
|
||||
title: '工艺路线编码',
|
||||
@ -513,43 +563,58 @@ const columns = [
|
||||
align:'center',
|
||||
title: '生产车间',
|
||||
key: 'proWorkshop',
|
||||
width:"120"
|
||||
},
|
||||
{
|
||||
align:'center',
|
||||
title: '产品序列',
|
||||
key: 'sort',
|
||||
width:"120"
|
||||
width:"120",
|
||||
render(row: any) {
|
||||
const val = row.proWorkshop
|
||||
// 兼容存 dictValue 或车间名称(金蝶同步写入的是 WorkShopName)
|
||||
const opt = workshopList.value.find(o =>
|
||||
o.value === val ||
|
||||
String(o.value) === String(val) ||
|
||||
o.label === val ||
|
||||
String(o.label) === String(val)
|
||||
)
|
||||
if (!opt) return val ?? '-'
|
||||
const tagType = opt.class && ['default', 'primary', 'info', 'success', 'warning', 'error'].includes(opt.class)
|
||||
? opt.class
|
||||
: 'info'
|
||||
return h(NTag, { type: tagType, size: 'small' }, { default: () => opt.label })
|
||||
}
|
||||
},
|
||||
// {
|
||||
// align:'center',
|
||||
// title: '产品序列',
|
||||
// key: 'sort',
|
||||
// width:"120"
|
||||
// },
|
||||
{
|
||||
align:'center',
|
||||
title: '生产数量',
|
||||
key: 'quantity',
|
||||
width:"120"
|
||||
},
|
||||
{
|
||||
align:'center',
|
||||
title: '是否半成品',
|
||||
key: 'sfProduct',
|
||||
width:"120"
|
||||
},
|
||||
{
|
||||
align:'center',
|
||||
title: '是否采购',
|
||||
key: 'procurement',
|
||||
width:"120"
|
||||
},
|
||||
// {
|
||||
// align:'center',
|
||||
// title: '是否半成品',
|
||||
// key: 'sfProduct',
|
||||
// width:"120"
|
||||
// },
|
||||
// {
|
||||
// align:'center',
|
||||
// title: '是否采购',
|
||||
// key: 'procurement',
|
||||
// width:"120"
|
||||
// },
|
||||
{
|
||||
align:'center',
|
||||
title: '开始时间',
|
||||
key: 'beginTime',
|
||||
width:"120"
|
||||
width:"160"
|
||||
},
|
||||
{
|
||||
align:'center',
|
||||
title: '结束时间',
|
||||
key: 'endTime',
|
||||
width:"120"
|
||||
width:"160"
|
||||
},
|
||||
{
|
||||
align:'center',
|
||||
@ -585,7 +650,10 @@ const columns = [
|
||||
align:'center',
|
||||
title: '是否开工',
|
||||
key: 'starter',
|
||||
width:"120"
|
||||
width:"120",
|
||||
render(row: any) {
|
||||
return row.starter === 1 ? '已开工' : '未开工'
|
||||
}
|
||||
},
|
||||
{
|
||||
align:'center',
|
||||
@ -597,54 +665,48 @@ const columns = [
|
||||
align:'center',
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 190,
|
||||
width: 260,
|
||||
fixed:"right",
|
||||
render(row:any) {
|
||||
const buttons: ReturnType<typeof h>[] = []
|
||||
|
||||
const buttons:any = []
|
||||
if (hasPermission('biz:orderItem:edit')) {
|
||||
buttons.push(h(NButton, {
|
||||
size: 'small',
|
||||
quaternary: true,
|
||||
onClick: () => handleEdit(row),
|
||||
}, {
|
||||
default: () => [h(NIcon, null, { default: () => h(CreateOutline) }), ' 编辑'],
|
||||
}))
|
||||
}
|
||||
// 未开工且有工艺路线的子订单可开工
|
||||
if (hasPermission('biz:orderItem:startWork') && row.starter !== 1 && row.parentId && row.routeCode) {
|
||||
buttons.push(h(NButton, {
|
||||
size: 'small',
|
||||
quaternary: true,
|
||||
type: 'warning',
|
||||
onClick: () => handleStartWork(row),
|
||||
}, {
|
||||
default: () => [h(NIcon, null, { default: () => h(PlayOutline) }), ' 开工'],
|
||||
}))
|
||||
}
|
||||
if (hasPermission('biz:orderItem:reorderForm')) {
|
||||
buttons.push(h(NButton, {
|
||||
size: 'small',
|
||||
quaternary: true,
|
||||
type: 'info',
|
||||
onClick: () => handleReorderForm(row, false),
|
||||
}, {
|
||||
default: () => [h(NIcon, null, { default: () => h(DocumentTextOutline) }), ' 补单'],
|
||||
}))
|
||||
}
|
||||
|
||||
if(hasPermission('biz:orderItem:edit')){
|
||||
buttons.push(h(NButton, {
|
||||
size: 'small',
|
||||
type:'primary',
|
||||
ghost:true,
|
||||
onClick: () => { handleEdit(row) } },
|
||||
{ default: () => '编辑'}
|
||||
))
|
||||
}
|
||||
if(hasPermission('biz:orderItem:assign')){
|
||||
buttons.push(h(NButton, {
|
||||
size: 'small',
|
||||
type:'success',
|
||||
ghost:true,
|
||||
onClick: () => { disphand(row) } },
|
||||
{ default: () => '派工'}
|
||||
))
|
||||
}
|
||||
if(hasPermission('biz:orderItem:reorderForm')){
|
||||
buttons.push(h(NButton, {
|
||||
size: 'small',
|
||||
type:'info',
|
||||
ghost:true,
|
||||
onClick: () => handleReorderForm(row, false) }, //, disabled: row.starter !==1
|
||||
{ default: () => '补单'}
|
||||
))
|
||||
}
|
||||
|
||||
return buttons.length > 0 ? h(NSpace, {justify:'center'}, { default: () => buttons }) : '-'
|
||||
|
||||
|
||||
// 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) }), ' 派工']
|
||||
// })
|
||||
// ])
|
||||
return buttons.length > 0
|
||||
? h('div', { style: { display: 'flex', alignItems: 'center', gap: '4px', flexWrap: 'nowrap', justifyContent: 'center' } }, buttons)
|
||||
: '-'
|
||||
}
|
||||
}
|
||||
]
|
||||
])
|
||||
|
||||
//根据内容设置表格滚动条
|
||||
let scrollX = ref(0)
|
||||
@ -666,11 +728,9 @@ async function loadData() {
|
||||
const res = await orderItemApi.page({
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
code: searchForm.code,
|
||||
projectName: searchForm.projectName,
|
||||
orderCode: searchForm.orderCode,
|
||||
routeCode: searchForm.routeCode,
|
||||
proWorkshop: searchForm.proWorkshop,
|
||||
projectId: selectedProjectId.value,
|
||||
code: searchForm.code || undefined,
|
||||
projectName: searchForm.projectName || undefined,
|
||||
beginTime: searchForm.beginTime,
|
||||
endTime: searchForm.endTime
|
||||
})
|
||||
@ -696,6 +756,10 @@ function handleReset() {
|
||||
searchForm.proWorkshop = ''
|
||||
searchForm.beginTime = null
|
||||
searchForm.endTime = null
|
||||
selectedProjectId.value = undefined
|
||||
selectedProjectKeys.value = []
|
||||
selectedProjectLabel.value = ''
|
||||
projectTreeSearch.value = ''
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
@ -800,22 +864,14 @@ const kingdeeModalVisible = ref(false)
|
||||
const kingdeeModalTitle = ref('金蝶生产订单')
|
||||
const kingdeeLoading = ref(false)
|
||||
|
||||
type KingdeeMoTableRow = Omit<KingdeePrdMo, 'children'> & { processRoutes: KingdeeProcessRoute[] }
|
||||
const kingdeeTableData = ref<KingdeeMoTableRow[]>([])
|
||||
// const kingdeeExpandedKeys = ref<Array<string | number>>([])
|
||||
|
||||
async function reCrawlReorderForm(id:number,reCrawl:Boolean){
|
||||
kingdeeLoading.value = true
|
||||
try {
|
||||
const data = await orderItemApi.preCreationInfo(id, {reCrawl:reCrawl})
|
||||
kingdeeTableData.value = (Array.isArray(data) ? data : []).map((mo) => {
|
||||
const { children, ...rest } = mo
|
||||
const routes = [...(children ?? [])].sort((a, b) => (a.operNumber ?? 0) - (b.operNumber ?? 0)).map((r) => ({
|
||||
...r,
|
||||
planStartTime: r.planStartTime ?? null,
|
||||
planFinishTime: r.planFinishTime ?? null,
|
||||
}))
|
||||
return { ...rest, processRoutes: routes }
|
||||
})
|
||||
kingdeeTableData.value = mapKingdeeMoListToTableRows(Array.isArray(data) ? data : [])
|
||||
} finally {
|
||||
kingdeeLoading.value = false
|
||||
}
|
||||
@ -829,9 +885,8 @@ async function handleReorderForm(row:OrderItem, reCrawl:Boolean){
|
||||
kingdeeOrderOtemId.value = row.id
|
||||
kingdeeModalTitle.value = `金蝶生产订单${row.code ? ` - ${row.code}` : ''}`
|
||||
kingdeeModalVisible.value = true
|
||||
kingdeeLoading.value = true
|
||||
reCrawlReorderForm(row.id, false)
|
||||
kingdeeLoading.value = false
|
||||
kingdeeTableData.value = []
|
||||
await reCrawlReorderForm(row.id, reCrawl)
|
||||
}
|
||||
|
||||
// 暂存金蝶生产订单数据(草稿)
|
||||
@ -857,18 +912,13 @@ async function holdTemporarilySave(id: number, data: KingdeePrdMo[]) {
|
||||
// }
|
||||
// }
|
||||
|
||||
// 同步订单和计划表
|
||||
async function synchronizationSave(id: number, data: KingdeePrdMo[], callback:Function) {
|
||||
// 同步订单和计划表(通过 callback 回传结果,供子弹等待接口完成)
|
||||
async function synchronizationSave(id: number, data: KingdeePrdMo[], callback: Function) {
|
||||
try {
|
||||
// 恢复数据结构,将 processRoutes 转换为 children
|
||||
await orderItemApi.synchronizationSave(id, data)
|
||||
.then((rps:any) =>{
|
||||
callback(rps)
|
||||
}).catch((res) => {
|
||||
callback(res)
|
||||
})
|
||||
const rps = await orderItemApi.synchronizationSave(id, data)
|
||||
callback(rps)
|
||||
} catch (error) {
|
||||
// 错误拦截器已处理
|
||||
callback(null, error)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1077,6 +1127,25 @@ function handleDelete(row: OrderItem) {
|
||||
})
|
||||
}
|
||||
|
||||
// 开工:生成工单
|
||||
function handleStartWork(row: OrderItem) {
|
||||
dialog.warning({
|
||||
title: '提示',
|
||||
content: `确定开工订单【${row.orderCode || row.name}】?开工后将生成工单。`,
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
await orderItemApi.startWork(row.id!)
|
||||
message.success('开工成功')
|
||||
loadData()
|
||||
} catch (error) {
|
||||
// 错误已在拦截器处理
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
function handleBatchDelete() {
|
||||
dialog.warning({
|
||||
@ -1102,6 +1171,7 @@ async function handleExport() {
|
||||
try {
|
||||
const params: Record<string, any> = {}
|
||||
if (selectedIds.value.length > 0) params.ids = selectedIds.value
|
||||
if (searchForm.productionCode) params.productionCode = searchForm.productionCode
|
||||
if (searchForm.code) params.code = searchForm.code
|
||||
if (searchForm.projectName) params.projectName = searchForm.projectName
|
||||
if (searchForm.orderCode) params.orderCode = searchForm.orderCode
|
||||
@ -1159,9 +1229,14 @@ async function handleImportUpload({ file }: UploadCustomRequestOptions) {
|
||||
|
||||
// 加载字典选项
|
||||
async function loadDictOptions() {
|
||||
try {
|
||||
const data = await dictDataApi.listByType('work_shop')
|
||||
workshopList.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue), class: d.listClass }))
|
||||
} catch {}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadProjectTree()
|
||||
loadData()
|
||||
loadDictOptions()
|
||||
|
||||
@ -1172,6 +1247,41 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.order-layout {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.order-tree-card {
|
||||
width: 280px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.order-tree-header {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.order-search {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.order-tree-wrapper {
|
||||
height: calc(100vh - 280px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.order-list-card {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@ -139,7 +139,14 @@
|
||||
<div class="col-actions">
|
||||
<n-button text type="primary" size="tiny" @click="openDetail(order)">详情</n-button>
|
||||
<n-button text type="info" size="tiny" @click="openPlmModel(order)">模型</n-button>
|
||||
<n-button text type="primary" size="tiny" disabled title="订单级编辑待支持">编辑</n-button>
|
||||
<!-- 订单级编辑:打开工序顺序重排弹窗(不进单工序编辑) -->
|
||||
<n-button
|
||||
text
|
||||
type="primary"
|
||||
size="tiny"
|
||||
:disabled="!hasPermission('biz:orderProcessPlan:edit')"
|
||||
@click="openReorder(order)"
|
||||
>编辑</n-button>
|
||||
<n-dropdown :options="moreOptions" trigger="click" @select="(k) => onMoreSelect(k, order)">
|
||||
<n-button text size="tiny">更多</n-button>
|
||||
</n-dropdown>
|
||||
@ -190,6 +197,11 @@
|
||||
v-for="(proc, idx) in sortedProcessList(order.processList)"
|
||||
:key="proc.planId ?? idx"
|
||||
:process="proc"
|
||||
:material-code="order.materialCode"
|
||||
:order-code="order.orderCode"
|
||||
:order-begin-time="order.beginTime"
|
||||
:order-end-time="order.endTime"
|
||||
:pro-workshop="order.proWorkshop"
|
||||
:is-last="idx === (order.processList?.length ?? 0) - 1"
|
||||
:can-edit="hasPermission('biz:orderItem:edit')"
|
||||
:can-assign="hasPermission('biz:orderItem:assign')"
|
||||
@ -246,6 +258,11 @@
|
||||
v-for="(proc, idx) in (detailData.processList)"
|
||||
:key="proc.planId ?? idx"
|
||||
:process="proc"
|
||||
:material-code="detailData.materialCode"
|
||||
:order-code="detailData.orderCode"
|
||||
:order-begin-time="detailData.beginTime"
|
||||
:order-end-time="detailData.endTime"
|
||||
:pro-workshop="detailData.proWorkshop"
|
||||
:is-last="idx === (detailData.processList?.length ?? 0) - 1"
|
||||
:can-edit="hasPermission('biz:orderItem:edit')"
|
||||
:can-assign="hasPermission('biz:orderItem:assign')"
|
||||
@ -261,7 +278,81 @@
|
||||
</n-spin>
|
||||
</n-modal>
|
||||
|
||||
<!-- 新增/编辑 -->
|
||||
<!-- 订单级工序顺序重排:拖拽 / 上移下移,只提交 planId 顺序 -->
|
||||
<n-modal
|
||||
v-model:show="reorderVisible"
|
||||
preset="card"
|
||||
title="调整工序顺序"
|
||||
style="width: 640px"
|
||||
:mask-closable="false"
|
||||
@after-leave="resetReorder"
|
||||
>
|
||||
<n-spin :show="reorderLoading">
|
||||
<n-alert type="info" style="margin-bottom: 12px">
|
||||
拖拽行或使用上移/下移调整顺序;保存后按 10、20、30… 重写工序号。存在派工工单或已有转出数量的工序不可调整。
|
||||
</n-alert>
|
||||
<div v-if="reorderOrderMeta" class="reorder-meta">
|
||||
<span>令号:{{ reorderOrderMeta.mainCode || '-' }}</span>
|
||||
<span>物料:{{ reorderOrderMeta.materialName || '-' }}</span>
|
||||
<span>数量:{{ reorderOrderMeta.quantity ?? '-' }}</span>
|
||||
</div>
|
||||
<div v-if="!reorderList.length && !reorderLoading" class="reorder-empty">暂无工序可调整</div>
|
||||
<VueDraggable
|
||||
v-else
|
||||
v-model="reorderList"
|
||||
:animation="150"
|
||||
handle=".reorder-drag-handle"
|
||||
class="reorder-list"
|
||||
>
|
||||
<div
|
||||
v-for="(proc, idx) in reorderList"
|
||||
:key="proc.planId ?? idx"
|
||||
class="reorder-item"
|
||||
>
|
||||
<n-icon class="reorder-drag-handle" size="18" :depth="3">
|
||||
<MenuOutline />
|
||||
</n-icon>
|
||||
<span class="reorder-index">{{ idx + 1 }}</span>
|
||||
<div class="reorder-body">
|
||||
<div class="reorder-title">
|
||||
<em v-if="proc.operNumber != null">{{ proc.operNumber }}</em>
|
||||
{{ proc.processName || '-' }}
|
||||
</div>
|
||||
<div class="reorder-sub">
|
||||
{{ proc.processCode || '-' }} · {{ proc.workOrderCode || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<n-space :size="4">
|
||||
<n-button
|
||||
size="tiny"
|
||||
quaternary
|
||||
:disabled="idx === 0"
|
||||
@click="moveReorderItem(idx, -1)"
|
||||
>上移</n-button>
|
||||
<n-button
|
||||
size="tiny"
|
||||
quaternary
|
||||
:disabled="idx === reorderList.length - 1"
|
||||
@click="moveReorderItem(idx, 1)"
|
||||
>下移</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
</VueDraggable>
|
||||
</n-spin>
|
||||
<template #footer>
|
||||
<n-space justify="end">
|
||||
<n-button @click="reorderVisible = false">取消</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
:loading="reorderSubmitLoading"
|
||||
:disabled="!reorderList.length"
|
||||
@click="submitReorder"
|
||||
>保存顺序</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
|
||||
<!-- 新增/编辑(单工序字段;顺序请用上方重排弹窗) -->
|
||||
<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="120px">
|
||||
<n-grid :cols="2">
|
||||
@ -276,8 +367,9 @@
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<!-- 顺序请走订单行「编辑」重排,避免单条改 sort 撞号断链 -->
|
||||
<n-form-item label="工序顺序" path="sort">
|
||||
<n-input v-model:value="formData.sort" placeholder="请输入工序顺序" />
|
||||
<n-input v-model:value="formData.sort" placeholder="请通过订单编辑调整顺序" disabled />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
@ -329,6 +421,16 @@
|
||||
<span>{{dispatchform.quantity}}</span>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-grid :cols="2">
|
||||
<n-gi>
|
||||
<span class="pgClass">已完成:</span>
|
||||
<span>{{dispatchform.completedQty ?? 0}}</span>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<span class="pgClass">本次待派:</span>
|
||||
<span style="color: #2080f0; font-weight: 600">{{dispatchform.remainQty}}</span>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-grid :cols="2">
|
||||
<n-gi>
|
||||
<span class="pgClass">开始时间:</span>
|
||||
@ -570,7 +672,9 @@ import {
|
||||
DownloadOutline, ChevronDownOutline, CheckmarkCircleOutline, CheckmarkCircle,
|
||||
EllipseOutline, TimeOutline, PrintOutline, SyncOutline, PeopleOutline,
|
||||
GitBranchOutline, CloseCircleOutline, LockClosedOutline, TrashOutline, EyeSharp,
|
||||
MenuOutline,
|
||||
} from '@vicons/ionicons5'
|
||||
import { VueDraggable } from 'vue-draggable-plus'
|
||||
import {
|
||||
orderProcessPlanApi,
|
||||
toOrderProcessPlanPayload,
|
||||
@ -612,6 +716,15 @@ const detailVisible = ref(false)
|
||||
const detailLoading = ref(false)
|
||||
const detailData = ref<OrderProcessPlanVO | null>(null)
|
||||
|
||||
/** 订单级工序重排弹窗状态 */
|
||||
const reorderVisible = ref(false)
|
||||
const reorderLoading = ref(false)
|
||||
const reorderSubmitLoading = ref(false)
|
||||
const reorderOrderItemId = ref<number | null>(null)
|
||||
const reorderOrderMeta = ref<OrderProcessPlanVO | null>(null)
|
||||
/** 弹窗内可拖拽的工序列表(按当前展示顺序) */
|
||||
const reorderList = ref<ProcessPlanItemVO[]>([])
|
||||
|
||||
const modalVisible = ref(false)
|
||||
const modalTitle = ref('')
|
||||
const formRef = ref()
|
||||
@ -628,7 +741,7 @@ const dispatchmodal = ref(false)
|
||||
const dispatchLoading = ref(false)
|
||||
const dispatchformRef = ref()
|
||||
const dispatchform = reactive<any>({
|
||||
id: '', name: '', beginTime: '', endTime: '', quantity: '',workCenterName:'',proWorkshop:'',
|
||||
id: '', name: '', beginTime: '', endTime: '', quantity: '',workCenterName:'',
|
||||
list: [{ sectionId:'',deviceId:'', quantity: '' }],
|
||||
})
|
||||
const dispatchrules = {}
|
||||
@ -1012,6 +1125,79 @@ async function openDetail(order: OrderProcessPlanVO) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开订单级工序顺序调整(拉详情,按 operNumber 排序后供拖拽) */
|
||||
async function openReorder(order: OrderProcessPlanVO) {
|
||||
if (!order.orderItemId) {
|
||||
message.warning('缺少生产订单id')
|
||||
return
|
||||
}
|
||||
reorderVisible.value = true
|
||||
reorderLoading.value = true
|
||||
reorderOrderItemId.value = order.orderItemId
|
||||
reorderOrderMeta.value = order
|
||||
reorderList.value = []
|
||||
try {
|
||||
const detail = await orderProcessPlanApi.detail(order.orderItemId)
|
||||
reorderOrderMeta.value = detail
|
||||
// 与看板展开区一致:按 operNumber(后端 sort)升序
|
||||
reorderList.value = sortedProcessList(detail.processList).map((p) => ({ ...p }))
|
||||
if (!reorderList.value.length) {
|
||||
message.warning('当前订单暂无工序计划')
|
||||
}
|
||||
} catch {
|
||||
// 详情失败时降级用列表行数据,仍可本地排序后提交
|
||||
reorderList.value = sortedProcessList(order.processList).map((p) => ({ ...p }))
|
||||
} finally {
|
||||
reorderLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function resetReorder() {
|
||||
reorderOrderItemId.value = null
|
||||
reorderOrderMeta.value = null
|
||||
reorderList.value = []
|
||||
reorderSubmitLoading.value = false
|
||||
}
|
||||
|
||||
/** 上移 / 下移(delta: -1 上移,+1 下移) */
|
||||
function moveReorderItem(index: number, delta: number) {
|
||||
const next = index + delta
|
||||
if (next < 0 || next >= reorderList.value.length) return
|
||||
const list = [...reorderList.value]
|
||||
const [item] = list.splice(index, 1)
|
||||
list.splice(next, 0, item)
|
||||
reorderList.value = list
|
||||
}
|
||||
|
||||
/** 提交重排:只传 orderItemId + planId 顺序 */
|
||||
async function submitReorder() {
|
||||
if (!reorderOrderItemId.value) {
|
||||
message.warning('缺少生产订单id')
|
||||
return
|
||||
}
|
||||
if (!reorderList.value.length) {
|
||||
message.warning('暂无工序可保存')
|
||||
return
|
||||
}
|
||||
const missing = reorderList.value.some((p) => p.planId == null)
|
||||
if (missing) {
|
||||
message.error('存在缺少 planId 的工序,请刷新后重试')
|
||||
return
|
||||
}
|
||||
reorderSubmitLoading.value = true
|
||||
try {
|
||||
await orderProcessPlanApi.reorder({
|
||||
orderItemId: reorderOrderItemId.value,
|
||||
items: reorderList.value.map((p) => ({ planId: p.planId! })),
|
||||
})
|
||||
message.success('工序顺序已更新')
|
||||
reorderVisible.value = false
|
||||
loadData()
|
||||
} finally {
|
||||
reorderSubmitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
modalTitle.value = '新增工序计划'
|
||||
Object.assign(formData, defaultFormData)
|
||||
@ -1063,26 +1249,33 @@ async function handleSubmit() {
|
||||
function disphand(process: ProcessPlanItemVO, order?: OrderProcessPlanVO) {
|
||||
const entity = processItemToEntity(process, order?.orderItemId,order?.proWorkshop)
|
||||
|
||||
const planQty = Number(entity.quantity) || 0
|
||||
const completedQty = Number(process.completedQty) || 0
|
||||
const remainQty = Math.max(0, planQty - completedQty)
|
||||
dispatchmodal.value = true
|
||||
dispatchformRef.value?.restoreValidation()
|
||||
dispatchform.id = entity.id
|
||||
dispatchform.name = entity.name
|
||||
dispatchform.beginTime = entity.beginTime
|
||||
dispatchform.endTime = entity.endTime
|
||||
// 工序计划时间优先,没有则用订单头 beginTime/endTime
|
||||
dispatchform.beginTime = entity.beginTime || order?.beginTime || ''
|
||||
dispatchform.endTime = entity.endTime || order?.endTime || ''
|
||||
// quantity 保持工序计划总数,提交时不能改掉计划数量
|
||||
dispatchform.quantity = entity.quantity
|
||||
dispatchform.completedQty = completedQty
|
||||
dispatchform.remainQty = remainQty
|
||||
dispatchform.orderItemId = entity.orderItemId
|
||||
dispatchform.sort = entity.sort
|
||||
dispatchform.workCenterName = entity.workCenterName
|
||||
dispatchform.workShop = entity.proWorkshop
|
||||
dispatchform.list = [{ sectionId:'',deviceId:'', quantity: '' }]
|
||||
|
||||
console.log(dispatchform)
|
||||
|
||||
|
||||
dispatchform.list = [{ sectionId: '', deviceId: '', quantity: remainQty || '' }]
|
||||
}
|
||||
|
||||
function addpgnum() {
|
||||
dispatchform.list.push({ sectionId:'',deviceId:'', quantity: '' })
|
||||
dispatchform.list.push({ sectionId: '', deviceId: '', quantity: '' })
|
||||
}
|
||||
|
||||
function deletenum(index: number) {
|
||||
@ -1094,15 +1287,24 @@ function dispatchSubmit() {
|
||||
dispatchform.list.forEach((n: any) => {
|
||||
total += Number(n.quantity) || 0
|
||||
})
|
||||
const remainQty = Number(dispatchform.remainQty)
|
||||
if (remainQty <= 0) {
|
||||
message.warning('当前工序已全部完成,无需再派工')
|
||||
return
|
||||
}
|
||||
|
||||
dispatchformRef.value?.validate((v: boolean) => {
|
||||
if (v) return
|
||||
if (total !== Number(dispatchform.quantity)) {
|
||||
message.error('派工总数量需等于生产总数', { duration: 4000 })
|
||||
dispatchformRef.value?.validate((errors: any) => {
|
||||
// Naive UI:有 errors 表示校验失败
|
||||
if (errors) return
|
||||
if (total !== remainQty) {
|
||||
message.error(`派工总数量需等于待派数量 ${remainQty}(计划 ${dispatchform.quantity},已完成 ${dispatchform.completedQty ?? 0})`, {
|
||||
duration: 4000,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
dispatchLoading.value = true
|
||||
// 提交时 quantity 仍传计划总数,避免把工序计划数量改成待派数
|
||||
orderProcessPlanApi.assignWork(dispatchform).then(() => {
|
||||
message.success('派工成功')
|
||||
dispatchmodal.value = false
|
||||
@ -1476,4 +1678,83 @@ onMounted(loadData)
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
|
||||
.reorder-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
margin-bottom: 12px;
|
||||
font-size: 13px;
|
||||
color: #595959;
|
||||
}
|
||||
|
||||
.reorder-empty {
|
||||
padding: 24px 0;
|
||||
text-align: center;
|
||||
color: #8c8c8c;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.reorder-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
max-height: 420px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.reorder-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
background: #fafbfc;
|
||||
border: 1px solid #eef0f3;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.reorder-drag-handle {
|
||||
cursor: grab;
|
||||
flex-shrink: 0;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
|
||||
.reorder-drag-handle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.reorder-index {
|
||||
width: 22px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #2080f0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.reorder-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.reorder-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.reorder-title em {
|
||||
font-style: normal;
|
||||
margin-right: 6px;
|
||||
color: #2080f0;
|
||||
}
|
||||
|
||||
.reorder-sub {
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -2,7 +2,13 @@
|
||||
<div class="process-card-wrap">
|
||||
<div class="process-card" :class="themeClass">
|
||||
<div class="card-head">
|
||||
<span class="card-title">{{ process.processName || '-' }}</span>
|
||||
<div class="card-title-block">
|
||||
<span class="card-title">
|
||||
<em v-if="process.operNumber != null" class="card-oper">{{ process.operNumber }}</em>
|
||||
{{ process.processName || '-' }}
|
||||
</span>
|
||||
<span v-if="materialCode" class="card-material">{{ materialCode }}</span>
|
||||
</div>
|
||||
<n-tag size="small" :bordered="false" :type="statusTag.type">{{ statusTag.label }}</n-tag>
|
||||
</div>
|
||||
|
||||
@ -39,22 +45,28 @@
|
||||
</div>
|
||||
|
||||
<div class="card-flow">
|
||||
<div class="flow-item">
|
||||
<span>转入</span>
|
||||
<strong>{{ process.transferInQty ?? 0 }}</strong>
|
||||
</div>
|
||||
<div class="flow-item">
|
||||
<span>剩余物料</span>
|
||||
<strong>{{ remainQty }}</strong>
|
||||
</div>
|
||||
|
||||
<div class="flow-item">
|
||||
<span>转下道</span>
|
||||
<span>转出</span>
|
||||
<strong>{{ process.transferOutQty ?? 0 }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-meta">
|
||||
<div class="meta-line"><span>生产订单</span><em>{{ orderCode || '-' }}</em></div>
|
||||
<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>{{ workshopText }}</em></div>
|
||||
<div class="meta-line"><span>工位</span><em>{{ process.workCenterName || '-' }}</em></div>
|
||||
<div class="meta-line"><span>设备</span><em>-</em></div>
|
||||
<div class="meta-line"><span>设备</span><em>{{ deviceText }}</em></div>
|
||||
<div class="meta-line"><span>计划时间</span><em>{{ planTimeText }}</em></div>
|
||||
<div class="meta-line"><span>实际开始</span><em>{{ process.actualStartTime || '-' }}</em></div>
|
||||
</div>
|
||||
@ -110,6 +122,16 @@ import { PROCESS_STATUS_MAP, type ProcessPlanItemVO } from '@/api/orderProcessPl
|
||||
|
||||
const props = defineProps<{
|
||||
process: ProcessPlanItemVO
|
||||
/** 物料编码(订单头) */
|
||||
materialCode?: string
|
||||
/** 生产订单编号(订单头 orderCode) */
|
||||
orderCode?: string
|
||||
/** 订单计划开始(订单头 beginTime,工序无计划时间时回退) */
|
||||
orderBeginTime?: string
|
||||
/** 订单计划结束(订单头 endTime) */
|
||||
orderEndTime?: string
|
||||
/** 生产车间(订单头 proWorkshop) */
|
||||
proWorkshop?: string
|
||||
isLast?: boolean
|
||||
canEdit?: boolean
|
||||
canAssign?: boolean
|
||||
@ -171,15 +193,39 @@ const railColor = computed(() => {
|
||||
const personnel = computed(() => {
|
||||
if (props.process.assignByName) return props.process.assignByName
|
||||
const list = props.process.assignWorkList
|
||||
if (list?.length) return `${list.length} 人派工`
|
||||
if (list?.length) {
|
||||
const names = list
|
||||
.map((a: any) => a.userName || a.userId)
|
||||
.filter(Boolean)
|
||||
if (names.length) return names.join('、')
|
||||
return `${list.length} 人派工`
|
||||
}
|
||||
return '-'
|
||||
})
|
||||
|
||||
const workshopText = computed(() => {
|
||||
return props.process.departmentName || props.proWorkshop || '-'
|
||||
})
|
||||
|
||||
const deviceText = computed(() => {
|
||||
const list = props.process.assignWorkList as any[] | undefined
|
||||
if (!list?.length) return '-'
|
||||
const devices = list
|
||||
.map((a) => a.deviceName || a.deviceId)
|
||||
.filter(Boolean)
|
||||
return devices.length ? [...new Set(devices)].join('、') : '-'
|
||||
})
|
||||
|
||||
const planTimeText = computed(() => {
|
||||
const start = props.process.planStartTime
|
||||
const end = props.process.planFinishTime
|
||||
// 工序计划时间优先;没有则回退订单头 beginTime/endTime
|
||||
const start = props.process.planStartTime || props.process.beginTime || props.orderBeginTime
|
||||
const end = props.process.planFinishTime || props.process.endTime || props.orderEndTime
|
||||
if (!start && !end) return '-'
|
||||
if (start && end) return `${start.slice(0, 16)} ~ ${end.slice(11, 16)}`
|
||||
if (start && end) {
|
||||
const startText = start.length >= 16 ? start.slice(0, 16) : start
|
||||
const endText = end.length >= 16 ? end.slice(11, 16) : end
|
||||
return `${startText} ~ ${endText}`
|
||||
}
|
||||
return start || end || '-'
|
||||
})
|
||||
</script>
|
||||
@ -252,6 +298,29 @@ const planTimeText = computed(() => {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.card-oper {
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
margin-right: 4px;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.card-title-block {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.card-material {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
opacity: 0.85;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.card-progress {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@ -233,6 +233,16 @@
|
||||
<n-input v-model:value="dispatchform.quantity" disabled />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="已完成">
|
||||
<n-input :value="String(dispatchform.completedQty ?? 0)" disabled />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="本次待派">
|
||||
<n-input :value="String(dispatchform.remainQty ?? 0)" disabled />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="开始时间">
|
||||
<n-input v-model:value="dispatchform.beginTime" disabled />
|
||||
@ -816,6 +826,8 @@ let dispatchform = reactive<any>({
|
||||
endTime:'',
|
||||
userid:'',
|
||||
quantity:'',
|
||||
completedQty: 0,
|
||||
remainQty: 0,
|
||||
list:[{
|
||||
userId:'',
|
||||
quantity:''
|
||||
@ -866,17 +878,22 @@ function deletenum(index:any) {
|
||||
|
||||
function disphand(process: ProcessPlanItemVO, order?: OrderProcessPlanVO) {
|
||||
const entity = processItemToEntity(process, order?.orderItemId)
|
||||
const planQty = Number(entity.quantity) || 0
|
||||
const completedQty = Number(process.completedQty) || 0
|
||||
const remainQty = Math.max(0, planQty - completedQty)
|
||||
dispatchmodal.value = true
|
||||
dispatchformRef.value?.restoreValidation()
|
||||
dispatchform.id = entity.id
|
||||
dispatchform.name = entity.name
|
||||
dispatchform.beginTime = entity.beginTime
|
||||
dispatchform.endTime = entity.endTime
|
||||
dispatchform.beginTime = entity.beginTime || order?.beginTime || ''
|
||||
dispatchform.endTime = entity.endTime || order?.endTime || ''
|
||||
dispatchform.quantity = entity.quantity
|
||||
dispatchform.completedQty = completedQty
|
||||
dispatchform.remainQty = remainQty
|
||||
dispatchform.userid = ''
|
||||
dispatchform.list = [{
|
||||
userId: '',
|
||||
quantity: '',
|
||||
quantity: remainQty || '',
|
||||
}]
|
||||
}
|
||||
|
||||
@ -885,10 +902,15 @@ function dispatchSubmit() {
|
||||
dispatchform.list.forEach((n:any) => {
|
||||
total = n.quantity*1+total
|
||||
})
|
||||
const remainQty = Number(dispatchform.remainQty)
|
||||
if (remainQty <= 0) {
|
||||
message.warning('当前工序已全部完成,无需再派工')
|
||||
return
|
||||
}
|
||||
dispatchformRef.value?.validate((v:any) => {
|
||||
if(!v){
|
||||
if(total != dispatchform.quantity){
|
||||
message.error('派工总数量需等于生产总数',{
|
||||
if(total != remainQty){
|
||||
message.error(`派工总数量需等于待派数量 ${remainQty}(计划 ${dispatchform.quantity},已完成 ${dispatchform.completedQty ?? 0})`,{
|
||||
duration:4000
|
||||
})
|
||||
}else{
|
||||
|
||||
@ -159,10 +159,9 @@
|
||||
const res = await sectionApi.list({sectionName:props.section,workShop:props.workShop})
|
||||
const mesSection = res.MesSection;
|
||||
const mesDevice = res.MesDevice
|
||||
console.log(mesSection);
|
||||
|
||||
sectionList.push({
|
||||
label:mesSection.sectionName,
|
||||
label:mesSection.deptName,
|
||||
value:mesSection.id
|
||||
})
|
||||
props.obj.sectionId = mesSection.id
|
||||
|
||||
@ -151,6 +151,7 @@
|
||||
<KingdeeVue
|
||||
:kingdeeTableData="kingdeeTableData"
|
||||
:selectedId="kingdeeCurrentProjectId"
|
||||
:loading="kingdeeLoading"
|
||||
@close-modal="kingdeeModalVisible = false"
|
||||
@holdTemporarilyCallback="handleSaveDraft"
|
||||
@synchronizationCallback="handleSyncOrderAndPlan"
|
||||
@ -194,7 +195,8 @@
|
||||
import { ref, reactive, h, computed, onMounted, watch } from 'vue'
|
||||
import { NButton, NSpace, NIcon, NTag, NDatePicker, NDataTable, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
|
||||
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline, EyeOutline, CalendarOutline } from '@vicons/ionicons5'
|
||||
import { orderProjectApi, type OrderProject, type KingdeePrdMo, type KingdeeProcessRoute, normalizeKingdeeProcessRoute } from '@/api/orderProject'
|
||||
import { orderProjectApi, type OrderProject, type KingdeePrdMo, type KingdeeProcessRoute } from '@/api/orderProject'
|
||||
import { mapKingdeeMoListToTableRows } from '@/utils/kingdeeSchedule'
|
||||
import { dictDataApi } from '@/api/org'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
@ -660,21 +662,14 @@ function handleGanttSchedule() {
|
||||
}
|
||||
|
||||
async function reCrawlReorderForm(id:number,reCrawl:Boolean){
|
||||
kingdeeLoading.value = true
|
||||
try {
|
||||
const data = await orderProjectApi.getKingdeeOrder(id,{reCrawl:reCrawl})
|
||||
kingdeeTableData.value = (Array.isArray(data) ? data : []).map((mo) => {
|
||||
const { children, ...rest } = mo
|
||||
const routes = [...(children ?? [])]
|
||||
.sort((a, b) => (a.operNumber ?? 0) - (b.operNumber ?? 0))
|
||||
.map((r) => normalizeKingdeeProcessRoute({
|
||||
...r,
|
||||
planStartTime: r.planStartTime ?? null,
|
||||
planFinishTime: r.planFinishTime ?? null,
|
||||
}))
|
||||
return { ...rest, processRoutes: routes }
|
||||
})
|
||||
kingdeeTableData.value = mapKingdeeMoListToTableRows(Array.isArray(data) ? data : [])
|
||||
if (kingdeeTableData.value.length === 0) {
|
||||
message.info('未查询到金蝶生产订单数据')
|
||||
} else if (reCrawl) {
|
||||
message.success('重新读取成功')
|
||||
}
|
||||
} finally {
|
||||
kingdeeLoading.value = false
|
||||
|
||||
@ -311,7 +311,7 @@ const defaultFormData: QcItem = {
|
||||
plans: '',
|
||||
description: '',
|
||||
createby: undefined,
|
||||
status:undefined
|
||||
status:0
|
||||
}
|
||||
const formData = reactive<QcItem>({ ...defaultFormData })
|
||||
|
||||
@ -319,6 +319,8 @@ const formData = reactive<QcItem>({ ...defaultFormData })
|
||||
|
||||
// 表单校验规则
|
||||
const formRules = {
|
||||
name: [{ required: true, message: '请输入质检项名称', trigger: 'blur' }],
|
||||
spec: [{ required: true, message: '请输入质检项规格', trigger: 'blur' }],
|
||||
}
|
||||
|
||||
// 表格列
|
||||
@ -497,15 +499,16 @@ async function handleSubmit() {
|
||||
if (typeof submitData.updateTime === 'number') {
|
||||
submitData.updateTime = new Date(submitData.updateTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||
}
|
||||
if (submitData.id) {
|
||||
await qcItemApi.update(submitData)
|
||||
message.success('修改成功')
|
||||
} else {
|
||||
await qcItemApi.create(submitData)
|
||||
message.success('新增成功')
|
||||
}
|
||||
modalVisible.value = false
|
||||
loadData()
|
||||
console.log(submitData)
|
||||
// if (submitData.id) {
|
||||
// await qcItemApi.update(submitData)
|
||||
// message.success('修改成功')
|
||||
// } else {
|
||||
// await qcItemApi.create(submitData)
|
||||
// message.success('新增成功')
|
||||
// }
|
||||
// modalVisible.value = false
|
||||
// loadData()
|
||||
} catch (error) {
|
||||
// 错误已在拦截器处理
|
||||
}
|
||||
|
||||
@ -79,13 +79,19 @@
|
||||
<n-input v-model:value="formData.deviceName" placeholder="请输入设备名称" />
|
||||
</n-form-item>
|
||||
<n-form-item label="所属工段" path="sectionId">
|
||||
<n-select
|
||||
v-model:value="formData.sectionId"
|
||||
:options="sectionList"
|
||||
placeholder="请选择所属工段"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
<!-- <n-select-->
|
||||
<!-- v-model:value="formData.sectionId"-->
|
||||
<!-- :options="sectionList"-->
|
||||
<!-- placeholder="请选择所属工段"-->
|
||||
<!-- clearable-->
|
||||
<!-- style="width: 200px"-->
|
||||
<!-- />-->
|
||||
<n-tree-select
|
||||
v-model:value="formData.sectionId"
|
||||
cascade
|
||||
checkable
|
||||
:options="sectionList"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="备注" path="remark">
|
||||
<n-input v-model:value="formData.remark" type="textarea" placeholder="请输入备注" />
|
||||
@ -144,8 +150,9 @@ import { NButton, NSpace,NTag, NIcon, NUpload, useMessage, useDialog, type DataT
|
||||
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline } from '@vicons/ionicons5'
|
||||
import { deviceApi, type Device } from '@/api/device'
|
||||
|
||||
import { dictDataApi } from '@/api/org'
|
||||
import {deptApi, dictDataApi} from '@/api/org'
|
||||
import {sectionApi, type Section} from '@/api/section'
|
||||
import {TreeNode} from "echarts/types/src/data/Tree";
|
||||
|
||||
|
||||
const message = useMessage()
|
||||
@ -208,6 +215,7 @@ const columns: DataTableColumns<Device> = [
|
||||
{ type: 'selection' },
|
||||
{ title: '工位编码', key: 'deviceCode' },
|
||||
{ title: '工位名称', key: 'deviceName' },
|
||||
{ title: '工位类型', key: 'deviceType' },
|
||||
{ title: '所属工段', key: 'sectionName' },
|
||||
{ title: '状态', key: 'status',
|
||||
render:(row) =>{
|
||||
@ -431,8 +439,24 @@ async function loadDictOptions() {
|
||||
|
||||
//加载工段
|
||||
async function loadSection() {
|
||||
const data = await sectionApi.listNoParam()
|
||||
sectionList = data.map((d:any)=> ({ label: d.sectionName, value: (Number(d.id) || d.id) }))
|
||||
const data = await deptApi.tree()
|
||||
sectionList = buildOptions(data);
|
||||
}
|
||||
|
||||
function buildOptions(d: any[]): TreeNode[] {
|
||||
return d.map(item => {
|
||||
// 安全id转换:0也能正常转为数字,不会被||过滤
|
||||
const idNum = Number(item.id);
|
||||
const key = !isNaN(idNum) ? idNum : item.id;
|
||||
return {
|
||||
label: item.deptName,
|
||||
key,
|
||||
// 子节点存在且为数组才递归处理
|
||||
children: Array.isArray(item.children) && item.children.length
|
||||
? buildOptions(item.children)
|
||||
: undefined
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
:data="AssingWorkTableData"
|
||||
:loading="tableLoading"
|
||||
:row-key="RowKey"
|
||||
:scroll-x="600"
|
||||
:scroll-x="1800"
|
||||
:max-height="1000"
|
||||
size="small"
|
||||
style="margin-bottom:15px"
|
||||
|
||||
@ -507,7 +507,7 @@ async function loadData() {
|
||||
|
||||
async function loadRoles() {
|
||||
try {
|
||||
const roles = await roleApi.roleList()
|
||||
const roles = await roleApi.list()
|
||||
roleOptions.value = roles.map((role: SysRole) => ({
|
||||
label: role.name,
|
||||
value: role.id!
|
||||
@ -519,7 +519,7 @@ async function loadRoles() {
|
||||
|
||||
async function loadPostOptions() {
|
||||
try {
|
||||
const posts = await postApi.postList()
|
||||
const posts = await postApi.list()
|
||||
postOptions.value = posts.map(p => ({
|
||||
label: p.postName,
|
||||
value: p.id!
|
||||
|
||||
@ -20,7 +20,8 @@ export default defineConfig({
|
||||
port: 3000,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target:'http://192.168.12.4:8888/',
|
||||
target:'http://192.168.12.12:8888/', //'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.230:8888',
|
||||
//target:'http://192.168.5.232:8888/',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user