This commit is contained in:
andy 2026-08-03 13:34:16 +08:00
commit d80a5983d4
28 changed files with 1089 additions and 1639 deletions

View File

@ -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' })
}
}

View File

@ -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' })
}
}

View File

@ -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' })
}
}

View File

@ -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>

View File

@ -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>

View 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' })
}
}

View File

@ -19,6 +19,7 @@ export interface SysNotice {
errorNotificationId?:number, errorNotificationId?:number,
filler?:number, filler?:number,
filingStatus?:number, filingStatus?:number,
deviceName?:string
} }
export interface NoticeChannelOption { export interface NoticeChannelOption {

View File

@ -44,6 +44,8 @@ export interface OrderItem {
assingWorkOperationTime?: string assingWorkOperationTime?: string
starter?: number starter?: number
mainCode?: string
orderCode?: string orderCode?: string
@ -123,7 +125,11 @@ export const orderItemApi = {
//派工 //派工
assignWork(data: any) { assignWork(data: any) {
return request({ url: '/biz/orderItem/assignWork', method: 'post', data }) return request({ url: '/biz/orderItem/assignWork', method: 'post', data })
}, },
// 开工
startWork(id: number) {
return request({ url: `/biz/orderItem/${id}/startWork`, method: 'post' })
},
//根据生产编号查询预生产订单 //根据生产编号查询预生产订单
preCreation(data: any) { preCreation(data: any) {
return request({ url: '/biz/orderItem/preCreation', method: 'post', data }) return request({ url: '/biz/orderItem/preCreation', method: 'post', data })

View File

@ -88,6 +88,12 @@ export interface ProcessPlanItemVO {
planFinishTime?: string planFinishTime?: string
/** 计划开始(部分接口直接返回 beginTime */
beginTime?: string
/** 计划结束(部分接口直接返回 endTime */
endTime?: string
actualStartTime?: string | null actualStartTime?: string | null
actualFinishTime?: string | null actualFinishTime?: string | null
@ -242,9 +248,9 @@ export function processItemToEntity(
orderItemId, orderItemId,
beginTime: item.planStartTime, beginTime: item.planStartTime || item.beginTime,
endTime: item.planFinishTime, endTime: item.planFinishTime || item.endTime,
procurement: item.procurement, procurement: item.procurement,
@ -469,6 +475,18 @@ export const orderProcessPlanApi = {
}) })
}, },
/**
*
* orderItemId + planId sort10/20/30
*/
reorder(data: ProcessPlanReorderPayload) {
return request({
url: '/biz/orderProcessPlan/reorder',
method: 'put',
data,
})
},
} }
/** 委外可选工序项 */ /** 委外可选工序项 */
@ -509,3 +527,16 @@ export interface OutsourcingCreate {
remark?: string remark?: string
} }
/** 工序重排明细 */
export interface ProcessPlanReorderItem {
planId: number
/** 可选;不传则按数组顺序由后端分配 sort */
sort?: number
}
/** 订单级工序重排请求 */
export interface ProcessPlanReorderPayload {
orderItemId: number
items: ProcessPlanReorderItem[]
}

View File

@ -89,6 +89,10 @@ export interface KingdeeProcessRoute {
planStartTime: string | null planStartTime: string | null
/** 计划结束时间 yyyy-MM-dd HH:mm:ss */ /** 计划结束时间 yyyy-MM-dd HH:mm:ss */
planFinishTime: string | null planFinishTime: string | null
/** 是否质检 1是 0否后端按控制码推导后返回 */
qualityInspection?: number | null
/** 是否入库 1是 0否后端仅末序为 1 */
storageEntry?: number | null
} }
/** 解析工序名称,兼容旧字段及后端误映射 */ /** 解析工序名称,兼容旧字段及后端误映射 */

View File

@ -2,7 +2,6 @@
import {onMounted, ref, watch} from "vue"; import {onMounted, ref, watch} from "vue";
import { Notifications } from '@vicons/ionicons5' import { Notifications } from '@vicons/ionicons5'
import { ErrorNotification } from '@/api/errorNotification.ts'
import {} from '@/api/system.ts' import {} from '@/api/system.ts'
import {noticeApi, SysNotice} from '@/api/message' import {noticeApi, SysNotice} from '@/api/message'
import type {FormInst} from "naive-ui"; import type {FormInst} from "naive-ui";
@ -12,7 +11,7 @@ const emit = defineEmits<{
const props = defineProps<{ const props = defineProps<{
show: boolean show: boolean
errorNotifiaction:ErrorNotification,
notice:SysNotice, notice:SysNotice,
}>() }>()
@ -72,7 +71,7 @@ watch(showModal, (val) => {
async function handleSubmit() { async function handleSubmit() {
try { try {
await formRef.value?.validate() await formRef.value?.validate()
formData.value.id = props.errorNotifiaction.noticeId formData.value.id = props.notice.id
formData.value.filingStatus = 1 formData.value.filingStatus = 1
// //
noticeApi.update({ noticeApi.update({
@ -114,7 +113,7 @@ async function handleSubmit() {
<template #1> <template #1>
<n-grid x-gap="12" :cols="1"> <n-grid x-gap="12" :cols="1">
<n-gi> <n-gi>
<h2>异常内容</h2> <h2>通知内容</h2>
</n-gi> </n-gi>
</n-grid> </n-grid>
<n-form <n-form
@ -122,21 +121,21 @@ async function handleSubmit() {
label-width="100" label-width="100"
style="margin: 10px" style="margin: 10px"
> >
<n-form-item label="异常名称" path="title"> <n-form-item label="通知标题" path="title">
<n-input <n-input
v-model:value="props.errorNotifiaction.title" v-model:value="props.notice.title"
disabled disabled
/> />
</n-form-item> </n-form-item>
<n-form-item label="异常设备" path="devices"> <n-form-item label="异常设备" path="title">
<n-input <n-input
v-model:value="props.errorNotifiaction.devices" v-model:value="props.notice.deviceName"
disabled disabled
/> />
</n-form-item> </n-form-item>
<n-form-item label="通知内容" path="notifierContent"> <n-form-item label="通知内容" path="notifierContent">
<n-input <n-input
v-model:value="errorNotifiaction.notifierContent" v-model:value="props.notice.content"
type="textarea" type="textarea"
disabled disabled
/> />
@ -172,7 +171,7 @@ async function handleSubmit() {
<div v-if="!isFili"> <div v-if="!isFili">
<n-grid x-gap="12" :cols="1"> <n-grid x-gap="12" :cols="1">
<n-gi> <n-gi>
<h2>异常内容</h2> <h2>通知内容</h2>
</n-gi> </n-gi>
</n-grid> </n-grid>
<n-form <n-form
@ -180,21 +179,21 @@ async function handleSubmit() {
label-width="100" label-width="100"
style="margin: 10px" style="margin: 10px"
> >
<n-form-item label="异常名称" path="title"> <n-form-item label="通知标题" path="title">
<n-input <n-input
v-model:value="props.errorNotifiaction.title" v-model:value="props.notice.title"
disabled disabled
/> />
</n-form-item> </n-form-item>
<n-form-item label="异常设备" path="devices"> <n-form-item label="异常设备" path="title">
<n-input <n-input
v-model:value="props.errorNotifiaction.devices" v-model:value="props.notice.deviceName"
disabled disabled
/> />
</n-form-item> </n-form-item>
<n-form-item label="通知内容" path="notifierContent"> <n-form-item label="通知内容" path="notifierContent">
<n-input <n-input
v-model:value="errorNotifiaction.notifierContent" v-model:value="props.notice.content"
type="textarea" type="textarea"
disabled disabled
/> />

View File

@ -335,7 +335,6 @@
<!--异常填报弹窗--> <!--异常填报弹窗-->
<ErrorReportModal <ErrorReportModal
v-model:show="showErrorReportModal" v-model:show="showErrorReportModal"
:errorNotifiaction ="errorNotifiaction"
:notice = "notice" :notice = "notice"
/> />
</n-layout> </n-layout>
@ -609,40 +608,22 @@ function stripHtml(html: string | undefined): string {
} }
// //
//
const errorNotifiaction = ref({
title: '',
devices: '',
sendingMethodName: '',
notifierName: '',
notifierContent: ''
})
// //
const notice = ref<SysNotice>( {}) const notice = ref<SysNotice>( {})
async function handleNoticeClick(item: SysNotice) { async function handleNoticeClick(item: SysNotice) {
const res = await errorNotificationApi.detail(item.errorNotificationId);
errorNotifiaction.value = {...res}
errorNotifiaction.value.noticeId = item.id
notice.value = item notice.value = item
// //
showErrorReportModal.value = true showErrorReportModal.value = true
// if (item.abnormalReporting) { //
// if (item.id) {
// }else { try {
// // await noticeApi.markAsRead(item.id)
// if (item.id) { //
// try { loadUnreadCount()
// await noticeApi.markAsRead(item.id) } catch (error) {
// // //
// loadUnreadCount() }
// } catch (error) { }
// //
// }
// }
// router.push({ path: '/message/notice', query: { id: item.id?.toString() } })
// }
} }
// //

View File

@ -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[] } export type KingdeeMoTableRow = Omit<KingdeePrdMo, 'children'> & { processRoutes: KingdeeProcessRoute[] }
@ -11,6 +15,9 @@ export function mapKingdeeMoListToTableRows(data: KingdeePrdMo[]): KingdeeMoTabl
...r, ...r,
planStartTime: r.planStartTime ?? null, planStartTime: r.planStartTime ?? null,
planFinishTime: r.planFinishTime ?? null, planFinishTime: r.planFinishTime ?? null,
// 质检/入库由后端查询金蝶时已写入,前端原样带出
qualityInspection: r.qualityInspection ?? null,
storageEntry: r.storageEntry ?? null,
})) }))
return { ...rest, processRoutes: routes } return { ...rest, processRoutes: routes }
}) })

View File

@ -327,8 +327,8 @@ const columns: DataTableColumns<BasicProcessPlan> = [
return h(NTag,{type: opt ? 'success' : 'error', size: 'small'},{default: () => (opt ? '是' : '否')}) return h(NTag,{type: opt ? 'success' : 'error', size: 'small'},{default: () => (opt ? '是' : '否')})
} }
}, },
{ title: '工作中心', key: 'workCenterName' }, { title: '工作中心', key: 'sectionName' },
{ title: '生产车间', key: 'departmentName' }, { title: '生产车间', key: 'workShopName' },
{ title: '工序说明', key: 'operDescription' }, { title: '工序说明', key: 'operDescription' },
{ title: '状态', key: 'status', { title: '状态', key: 'status',
render(row){ render(row){

View File

@ -112,12 +112,18 @@
/> />
</n-form-item> </n-form-item>
<n-form-item label="所属工段" path="sectionId"> <n-form-item label="所属工段" path="sectionId">
<n-select <!-- <n-select
v-model:value="formData.sectionId" v-model:value="formData.sectionId"
:options="sectionList" :options="sectionList"
placeholder="请选择所属工段" placeholder="请选择所属工段"
clearable clearable
style="width: 200px" style="width: 200px"
/> -->
<n-tree-select
v-model:value="formData.sectionId"
cascade
checkable
:options="sectionList"
/> />
</n-form-item> </n-form-item>
<n-form-item label="设备类型" path="deviceTypeId"> <n-form-item label="设备类型" path="deviceTypeId">
@ -267,12 +273,14 @@ import {deviceApi, type Device} from '@/api/device'
import {dictDataApi} from '@/api/org' import {dictDataApi} from '@/api/org'
import {sectionApi} from '@/api/section' 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 {DeviceAbnormalRecord, deviceAbnormalRecordApi} from "@/api/deviceAbnormalRecord.ts";
import DeviceAbnormalRecordPage from '@/views/biz/device/DeviceAbnomarlRecord.vue' import DeviceAbnormalRecordPage from '@/views/biz/device/DeviceAbnomarlRecord.vue'
import DeviceOperationLogPage from "@/views/biz/device/DeviceOperationLog.vue"; import DeviceOperationLogPage from "@/views/biz/device/DeviceOperationLog.vue";
import DeviceDispatchLogPage from "@/views/biz/device/DeviceDispatchLog.vue"; import DeviceDispatchLogPage from "@/views/biz/device/DeviceDispatchLog.vue";
import { S } from 'vue-router/dist/router-CWoNjPRp.mjs'
const message = useMessage() const message = useMessage()
const dialog = useDialog() const dialog = useDialog()
const router = useRouter() const router = useRouter()
@ -283,8 +291,15 @@ const deviceId = ref<number>(undefined)
// //
const deviceManagerList = ref<{ label: string, value: any }[]>([]) 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 deviceErrorLogList = ref<DeviceAbnormalRecord[]>([])
const deviceAbnormalRecordDrawerVisible = ref<Boolean>(false) // const deviceAbnormalRecordDrawerVisible = ref<Boolean>(false) //
@ -664,11 +679,26 @@ async function loadDictOptions() {
} }
} }
// //
async function loadSection() { async function loadSection() {
const data = await sectionApi.listNoParam() const data = await deptApi.tree()
sectionList = data.map((d: any) => ({label: d.sectionName, value: (Number(d.id) || d.id)})) sectionList = buildOptions(data);
}
function buildOptions(d: any[]): TreeNode[] {
return d.map(item => {
// id0||
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
};
});
} }
// //

View File

@ -4,8 +4,11 @@
<!-- 搜索表单 --> <!-- 搜索表单 -->
<div class="search-form"> <div class="search-form">
<n-form inline :model="searchForm" label-placement="left"> <n-form inline :model="searchForm" label-placement="left">
<n-form-item label="主键id"> <n-form-item label="主键ID">
<n-input v-model:value="searchForm.id" placeholder="请输入主键id" clearable /> <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-form-item> <n-form-item>
<n-space> <n-space>
@ -61,29 +64,26 @@
<!-- 新增/编辑弹窗 --> <!-- 新增/编辑弹窗 -->
<n-modal v-model:show="modalVisible" preset="card" :title="modalTitle" style="width: 600px"> <n-modal v-model:show="modalVisible" preset="card" :title="modalTitle" style="width: 600px">
<n-form ref="formRef" :model="formData" :rules="formRules" label-placement="left" label-width="100px"> <n-form ref="formRef" :model="formData" :rules="formRules" label-placement="left" label-width="100px">
<n-form-item label="质检编号" path="qcId"> <n-form-item label="异常过滤标题" path="title">
<n-input v-model:value="formData.qcId" placeholder="请输入质检编号" /> <n-input v-model:value="formData.title" placeholder="请输入异常过滤标题" />
</n-form-item> </n-form-item>
<n-form-item label="让步接收数量" path="concessionQty"> <n-form-item label="过滤条件" path="filterCondition">
<n-input v-model:value="formData.concessionQty" placeholder="请输入让步接收数量" /> <n-input v-model:value="formData.filterCondition" type="textarea" placeholder="请输入过滤条件" />
</n-form-item> </n-form-item>
<n-form-item label="申请原因" path="applyRemark"> <n-form-item label="描述" path="description">
<n-input v-model:value="formData.applyRemark" type="textarea" placeholder="请输入申请原因" /> <n-input v-model:value="formData.description" type="textarea" placeholder="请输入描述" />
</n-form-item> </n-form-item>
<n-form-item label="申请内容" path="applyContent"> <n-form-item label="状态">
<n-input v-model:value="formData.applyContent" type="textarea" placeholder="请输入申请内容" /> <n-radio-group v-model:value="formData.status" :default-value="0" >
</n-form-item> <n-space>
<n-form-item label="申请人id(发去人id)" path="applyUserId"> <n-radio :value="1">
<n-input v-model:value="formData.applyUserId" placeholder="请输入申请人id(发去人id)" /> 启用
</n-form-item> </n-radio>
<n-form-item label="部门id" path="deptId"> <n-radio :value="0">
<n-input v-model:value="formData.deptId" placeholder="请输入部门id" /> 禁用
</n-form-item> </n-radio>
<n-form-item label="钉钉单据id" path="instanceId"> </n-space>
<n-input v-model:value="formData.instanceId" placeholder="请输入钉钉单据id" /> </n-radio-group>
</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> </n-form-item>
</n-form> </n-form>
<template #footer> <template #footer>
@ -95,7 +95,7 @@
</n-modal> </n-modal>
<!-- 导入弹窗 --> <!-- 导入弹窗 -->
<n-modal v-model:show="importModalVisible" preset="card" title="导入让步接收申请表" style="width: 500px"> <n-modal v-model:show="importModalVisible" preset="card" title="导入异常过滤表" style="width: 500px">
<n-space vertical> <n-space vertical>
<n-alert type="info"> <n-alert type="info">
<template #header>导入说明</template> <template #header>导入说明</template>
@ -131,7 +131,7 @@
import { ref, reactive, h, onMounted } from 'vue' import { ref, reactive, h, onMounted } from 'vue'
import { NButton, NSpace, NIcon, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui' import { NButton, NSpace, NIcon, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline } from '@vicons/ionicons5' 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' import { dictDataApi } from '@/api/org'
const message = useMessage() const message = useMessage()
@ -140,10 +140,11 @@ const dialog = useDialog()
// //
const searchForm = reactive({ const searchForm = reactive({
id: null as number | null, id: null as number | null,
status: null as number | null,
}) })
// //
const tableData = ref<ConcessionApply[]>([]) const tableData = ref<ExceptionFilter[]>([])
const loading = ref(false) const loading = ref(false)
const selectedIds = ref<number[]>([]) const selectedIds = ref<number[]>([])
const pagination = reactive({ const pagination = reactive({
@ -159,47 +160,35 @@ const modalVisible = ref(false)
const modalTitle = ref('') const modalTitle = ref('')
const importModalVisible = ref(false) const importModalVisible = ref(false)
const formRef = ref() const formRef = ref()
const defaultFormData: ConcessionApply = { const defaultFormData: ExceptionFilter = {
qcId: undefined, title: '',
concessionQty: undefined, filterCondition: '',
applyRemark: '', description: '',
applyContent: '', status: undefined,
applyFile: '',
applyUserId: undefined,
deptId: undefined,
instanceId: '',
approveStatus: 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 formRules = {
} }
// //
const columns: DataTableColumns<ConcessionApply> = [ const columns: DataTableColumns<ExceptionFilter> = [
{ type: 'selection' }, { type: 'selection' },
{ title: '主键id', key: 'id' }, { title: '主键ID', key: 'id' },
{ title: '质检编号', key: 'qcId' }, { title: '异常过滤标题', key: 'title' },
{ title: '让步接收数量', key: 'concessionQty' }, { title: '过滤条件', key: 'filterCondition' },
{ title: '申请原因', key: 'applyRemark' }, { title: '描述', key: 'description' },
{ title: '申请内容', key: 'applyContent' }, { title: '状态', key: 'status',
{ title: '申请附件', key: 'applyFile' },
{ title: '申请人id(发去人id)', key: 'applyUserId' },
{ title: '部门id', key: 'deptId' },
{ title: '钉钉单据id', key: 'instanceId' },
{ title: '审批状态 1待审批 2审批通过 3 审批驳回', key: 'approveStatus',
render(row) { render(row) {
const val = row.approveStatus const val = row.status
const opt = approveStatusOptions.value.find(o => o.value === val || String(o.value) === String(val)) const opt = statusOptions.value.find(o => o.value === val || String(o.value) === String(val))
return opt ? opt.label : (val ?? '-') return opt ? opt.label : (val ?? '-')
} }
}, },
{ title: '创建时间', key: 'createTime', width: 180 },
{ title: '更新时间', key: 'updateTime', width: 180 },
{ {
title: '操作', title: '操作',
key: 'actions', key: 'actions',
@ -222,10 +211,11 @@ const columns: DataTableColumns<ConcessionApply> = [
async function loadData() { async function loadData() {
loading.value = true loading.value = true
try { try {
const res = await concessionApplyApi.page({ const res = await exceptionFilterApi.page({
page: pagination.page, page: pagination.page,
pageSize: pagination.pageSize, pageSize: pagination.pageSize,
id: searchForm.id || undefined, id: searchForm.id || undefined,
status: searchForm.status || undefined,
}) })
tableData.value = res.list tableData.value = res.list
pagination.itemCount = res.total pagination.itemCount = res.total
@ -244,6 +234,8 @@ function handleSearch() {
function handleReset() { function handleReset() {
searchForm.id = null searchForm.id = null
searchForm.status = null
handleSearch() handleSearch()
} }
@ -266,21 +258,15 @@ function handleCheck(keys: Array<string | number>) {
// //
function handleAdd() { function handleAdd() {
modalTitle.value = '新增让步接收申请表' modalTitle.value = '新增异常过滤表'
Object.assign(formData, defaultFormData) Object.assign(formData, defaultFormData)
modalVisible.value = true modalVisible.value = true
} }
// //
function handleEdit(row: ConcessionApply) { function handleEdit(row: ExceptionFilter) {
modalTitle.value = '编辑让步接收申请表' modalTitle.value = '编辑异常过滤表'
Object.assign(formData, row) 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 modalVisible.value = true
} }
@ -288,18 +274,12 @@ function handleEdit(row: ConcessionApply) {
async function handleSubmit() { async function handleSubmit() {
await formRef.value?.validate() await formRef.value?.validate()
try { try {
const submitData = { ...formData } as ConcessionApply const submitData = { ...formData } as ExceptionFilter
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.id) { if (submitData.id) {
await concessionApplyApi.update(submitData) await exceptionFilterApi.update(submitData)
message.success('修改成功') message.success('修改成功')
} else { } else {
await concessionApplyApi.create(submitData) await exceptionFilterApi.create(submitData)
message.success('新增成功') message.success('新增成功')
} }
modalVisible.value = false modalVisible.value = false
@ -310,7 +290,7 @@ async function handleSubmit() {
} }
// //
function handleDelete(row: ConcessionApply) { function handleDelete(row: ExceptionFilter) {
dialog.warning({ dialog.warning({
title: '提示', title: '提示',
content: '确定要删除该记录吗?', content: '确定要删除该记录吗?',
@ -318,7 +298,7 @@ function handleDelete(row: ConcessionApply) {
negativeText: '取消', negativeText: '取消',
onPositiveClick: async () => { onPositiveClick: async () => {
try { try {
await concessionApplyApi.delete([row.id!]) await exceptionFilterApi.delete([row.id!])
message.success('删除成功') message.success('删除成功')
loadData() loadData()
} catch (error) { } catch (error) {
@ -337,7 +317,7 @@ function handleBatchDelete() {
negativeText: '取消', negativeText: '取消',
onPositiveClick: async () => { onPositiveClick: async () => {
try { try {
await concessionApplyApi.delete(selectedIds.value) await exceptionFilterApi.delete(selectedIds.value)
message.success('删除成功') message.success('删除成功')
selectedIds.value = [] selectedIds.value = []
loadData() loadData()
@ -354,11 +334,12 @@ async function handleExport() {
const params: Record<string, any> = {} const params: Record<string, any> = {}
if (selectedIds.value.length > 0) params.ids = selectedIds.value if (selectedIds.value.length > 0) params.ids = selectedIds.value
if (searchForm.id != null) params.id = searchForm.id 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 url = window.URL.createObjectURL(blob)
const link = document.createElement('a') const link = document.createElement('a')
link.href = url link.href = url
link.download = '让步接收申请表数据.xlsx' link.download = '异常过滤表数据.xlsx'
link.click() link.click()
window.URL.revokeObjectURL(url) window.URL.revokeObjectURL(url)
} catch (error) { } catch (error) {
@ -369,11 +350,11 @@ async function handleExport() {
// //
async function handleDownloadTemplate() { async function handleDownloadTemplate() {
try { try {
const blob = await concessionApplyApi.downloadTemplate() const blob = await exceptionFilterApi.downloadTemplate()
const url = window.URL.createObjectURL(blob) const url = window.URL.createObjectURL(blob)
const link = document.createElement('a') const link = document.createElement('a')
link.href = url link.href = url
link.download = '让步接收申请表导入模板.xlsx' link.download = '异常过滤表导入模板.xlsx'
link.click() link.click()
window.URL.revokeObjectURL(url) window.URL.revokeObjectURL(url)
} catch (error) { } catch (error) {
@ -385,7 +366,7 @@ async function handleDownloadTemplate() {
async function handleImportUpload({ file }: UploadCustomRequestOptions) { async function handleImportUpload({ file }: UploadCustomRequestOptions) {
if (!file.file) return if (!file.file) return
try { try {
const result = await concessionApplyApi.importData(file.file) const result = await exceptionFilterApi.importData(file.file)
if (result.fail > 0) { if (result.fail > 0) {
dialog.warning({ dialog.warning({
title: '导入结果', title: '导入结果',
@ -406,7 +387,7 @@ async function handleImportUpload({ file }: UploadCustomRequestOptions) {
async function loadDictOptions() { async function loadDictOptions() {
try { try {
const data = await dictDataApi.listByType('sys_status') const data = await dictDataApi.listByType('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 {} } catch {}
} }

View File

@ -37,10 +37,10 @@
<n-button type="error" :disabled="kingdeeLoading" @click="reCrawlReorderForm"> <n-button type="error" :disabled="kingdeeLoading" @click="reCrawlReorderForm">
重新读取数据 重新读取数据
</n-button> </n-button>
<n-button type="primary" :disabled="kingdeeLoading" @click="handleSaveDraft"> <n-button type="primary" :disabled="kingdeeLoading" :loading="actionLoading" @click="handleSaveDraft">
保存草稿 保存草稿
</n-button> </n-button>
<n-button type="info" :disabled="kingdeeLoading" @click="handleSyncOrderAndPlan"> <n-button type="info" :disabled="kingdeeLoading" :loading="actionLoading" @click="handleSyncOrderAndPlan">
同步订单计划 同步订单计划
</n-button> </n-button>
<n-button @click="$emit('close-modal')">关闭</n-button> <n-button @click="$emit('close-modal')">关闭</n-button>
@ -54,7 +54,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, h, computed, defineProps, onMounted, watch, defineEmits} from 'vue' 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 { 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 { useRouter } from 'vue-router'
import { CalendarOutline } from '@vicons/ionicons5' import { CalendarOutline } from '@vicons/ionicons5'
@ -72,20 +72,27 @@ const props = defineProps({
selectedId:{ selectedId:{
type: Number, type: Number,
default: null default: null
},
/** 父页面查询/重读数据时的加载状态 */
loading: {
type: Boolean,
default: false
} }
}) })
onMounted(() => { onMounted(() => {
kingdeeTableData.value = props.kingdeeTableData as KingdeeMoTableRow[] kingdeeTableData.value = (props.kingdeeTableData as KingdeeMoTableRow[]) || []
}) })
watch(()=>props.kingdeeTableData,()=>{ watch(() => props.kingdeeTableData, () => {
kingdeeTableData.value = props.kingdeeTableData as KingdeeMoTableRow[] kingdeeTableData.value = (props.kingdeeTableData as KingdeeMoTableRow[]) || []
},{ deep: true } ) }, { deep: true })
const kingdeeModalVisible = ref(false) const kingdeeModalVisible = ref(false)
const kingdeeLoading = ref(false) /** 子组件自身操作(保存/同步)的加载状态 */
const actionLoading = ref(false)
/** 表格与按钮共用:父级查询 loading 或本地操作 loading */
const kingdeeLoading = computed(() => props.loading || actionLoading.value)
/** 主表行:工序仅存 processRoutes避免 children 触发树形重复行 */ /** 主表行:工序仅存 processRoutes避免 children 触发树形重复行 */
type KingdeeMoTableRow = Omit<KingdeePrdMo, 'children'> & { processRoutes: KingdeeProcessRoute[] } type KingdeeMoTableRow = Omit<KingdeePrdMo, 'children'> & { processRoutes: KingdeeProcessRoute[] }
const kingdeeTableData = ref<KingdeeMoTableRow[]>([]) const kingdeeTableData = ref<KingdeeMoTableRow[]>([])
@ -93,7 +100,7 @@ const kingdeeExpandedKeys = ref<Array<string | number>>([])
// const kingdeeCurrentProjectId = ref<number | null>(null) // ID // const kingdeeCurrentProjectId = ref<number | null>(null) // ID
const kingdeeTableMaxHeight = 'min(78vh, 720px)' const kingdeeTableMaxHeight = 'min(78vh, 720px)'
const kingdeeMoScrollX = 1820 const kingdeeMoScrollX = 1820
const kingdeeProcessScrollX = 1360 const kingdeeProcessScrollX = 1540
const kingdeeMoColumns: DataTableColumns<KingdeeMoTableRow> = [ const kingdeeMoColumns: DataTableColumns<KingdeeMoTableRow> = [
{ {
@ -155,7 +162,12 @@ const kingdeeProcessColumns: DataTableColumns<KingdeeProcessRoute> = [
return row.operNumber != null ? String(row.operNumber) : '-' 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: 'operDescription', width: 140, ellipsis: { tooltip: true } },
{ title: '工作中心', key: 'workCenterName', width: 110, align: 'center', ellipsis: { tooltip: true } }, { title: '工作中心', key: 'workCenterName', width: 110, align: 'center', ellipsis: { tooltip: true } },
{ title: '生产车间', key: 'departmentName', 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: 'activityUnit', width: 80, align: 'center' },
{ title: '控制码', key: 'optCtrlCodeName', width: 120, align: 'center', ellipsis: { tooltip: true } }, { 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') router.push('/biz/orderProject/gantt')
} }
// // loading
async function handleSyncOrderAndPlan() { async function handleSyncOrderAndPlan() {
if (!props.selectedId) return if (!props.selectedId || actionLoading.value) return
kingdeeLoading.value = true actionLoading.value = true
try { try {
// processRoutes children
const draftData = kingdeeTableData.value.map(mo => { const draftData = kingdeeTableData.value.map(mo => {
const { processRoutes, ...rest } = mo const { processRoutes, ...rest } = mo
return { ...rest, children: processRoutes } as KingdeePrdMo return { ...rest, children: processRoutes } as KingdeePrdMo
}) })
emit('synchronizationCallback', props.selectedId, draftData, (res) => { const res = await new Promise<unknown>((resolve, reject) => {
if("success" === res){ emit('synchronizationCallback', props.selectedId, draftData, (result: unknown, err?: unknown) => {
message.success('同步订单计划成功') if (err) reject(err)
emit('close-modal') else resolve(result)
}
}) })
kingdeeModalVisible.value = false })
} catch (error) {
if (res === 'success') {
message.success('同步订单计划成功')
emit('close-modal')
}
} catch {
// //
} finally { } finally {
kingdeeLoading.value = false actionLoading.value = false
} }
} }
@ -336,15 +373,8 @@ const reCrawlReorderForm = () => {
positiveText: '确定', positiveText: '确定',
negativeText: '取消', negativeText: '取消',
onPositiveClick: () => { onPositiveClick: () => {
kingdeeLoading.value = true // reCrawlReorderForm kingdeeLoading
try { emit('reCrawlCallback', props.selectedId, true)
emit('reCrawlCallback', props.selectedId, true)
message.success('重启读取成功')
} catch (error) {
//
} finally {
kingdeeLoading.value = false
}
} }
}) })
@ -356,7 +386,7 @@ const handleSaveDraft = () => {
// // 稿 // // 稿
if (!props.selectedId) return if (!props.selectedId) return
kingdeeLoading.value = true actionLoading.value = true
try { try {
const draftData = kingdeeTableData.value.map(mo => { const draftData = kingdeeTableData.value.map(mo => {
const { processRoutes, ...rest } = mo const { processRoutes, ...rest } = mo
@ -367,7 +397,7 @@ const handleSaveDraft = () => {
} catch (error) { } catch (error) {
// //
} finally { } finally {
kingdeeLoading.value = false actionLoading.value = false
} }
} }

View File

@ -1,99 +1,95 @@
<template> <template>
<div class="page-container"> <div class="page-container">
<n-card> <div class="order-layout">
<!-- 搜索表单 --> <!-- 左侧生产令号 -->
<div class="search-form"> <n-card class="order-tree-card" size="small">
<n-form inline :model="searchForm" label-placement="left"> <template #header>
<n-form-item label="物料编码"> <div class="order-tree-header">生产令号</div>
<n-input v-model:value="searchForm.code" placeholder="请输入物料编码" clearable /> </template>
</n-form-item> <div class="order-search">
<n-form-item label="项目名称"> <n-input v-model:value="projectTreeSearch" placeholder="搜索生产令号" clearable size="small">
<n-input v-model:value="searchForm.projectName" placeholder="请输入产品名称" clearable /> <template #prefix>
</n-form-item> <n-icon><SearchOutline /></n-icon>
<n-form-item label="开始时间"> </template>
<n-date-picker v-model:value="searchForm.beginTime" type="datetime" clearable /> </n-input>
</n-form-item> </div>
<n-form-item label="结束时间"> <div class="order-tree-wrapper">
<n-date-picker v-model:value="searchForm.endTime" type="datetime" clearable /> <n-tree
</n-form-item> :data="projectTreeData"
<n-form-item> :pattern="projectTreeSearch"
<n-space> :selected-keys="selectedProjectKeys"
<n-button type="primary" @click="handleSearch"> :node-props="projectNodeProps"
<template #icon><n-icon><SearchOutline /></n-icon></template> key-field="id"
搜索 label-field="label"
</n-button> selectable
<n-button @click="handleReset"> block-line
<template #icon><n-icon><RefreshOutline /></n-icon></template> />
重置 </div>
</n-button> </n-card>
</n-space>
</n-form-item>
</n-form>
</div>
<!-- 工具栏 --> <!-- 右侧生产订单列表 -->
<div class="table-toolbar"> <n-card class="order-list-card" size="small">
<n-space> <template #header>
<!--<n-button <div class="card-header">
size="small" <span>{{ selectedProjectLabel ? `${selectedProjectLabel}】生产订单` : '全部生产订单' }}</span>
type="primary" </div>
@click="handleAdd" </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> <template #prefix>
新增/补单 {{ pagination.itemCount }}
</n-button> </template>
<n-button @click="importModalVisible = true"> </n-pagination>
<template #icon><n-icon><CloudUploadOutline /></n-icon></template> </div>
导入 </n-card>
</n-button> </div>
<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>
<!-- 新增/编辑弹窗 --> <!-- 新增/编辑弹窗 -->
<n-modal <n-modal
@ -370,7 +366,8 @@
> >
<KingdeeVue <KingdeeVue
:kingdeeTableData="kingdeeTableData" :kingdeeTableData="kingdeeTableData"
:selectedId="kingdeeOrderOtemId" :selectedId="kingdeeOrderOtemId"
:loading="kingdeeLoading"
@close-modal="kingdeeModalVisible = false" @close-modal="kingdeeModalVisible = false"
@holdTemporarilyCallback="holdTemporarilySave" @holdTemporarilyCallback="holdTemporarilySave"
@synchronizationCallback="synchronizationSave" @synchronizationCallback="synchronizationSave"
@ -381,16 +378,19 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, h, onMounted } from 'vue' import { ref, reactive, h, computed, onMounted, type HTMLAttributes } from 'vue'
import { NButton, NSpace, NIcon, useMessage, useDialog, type UploadCustomRequestOptions} from 'naive-ui' 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 { orderItemApi, type OrderItem } from '@/api/orderItem'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { VueDraggable } from 'vue-draggable-plus' import { VueDraggable } from 'vue-draggable-plus'
import KingdeeVue from '@/views/biz/kingdee/index.vue' 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 { userApi } from '@/api/system'
import { dictDataApi } from '@/api/org'
const message = useMessage() const message = useMessage()
const dialog = useDialog() const dialog = useDialog()
@ -416,6 +416,56 @@ const searchForm = reactive<any>({
endTime: null as number | null, 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 formData = reactive<any>({ ...defaultFormData })
// //使 // //使
const workshopList = ref<{ label: string; value: any; class: any }[]>([])
// //
const formRules = { const formRules = {
} }
// // computed
const columns = [ const columns = computed(() => [
{ {
type: 'selection' type: 'selection'
}, },
{ {
align:'center', align:'center',
title: '产品名称', title: '生产令号',
key: 'projectName', key: 'mainCode',
// ellipsis: { width: '140'
// tooltip: true
// }
minWidth: 150,
}, },
{
align:'center',
title: '订单编号',
key: 'orderCode',
width:"120"
},
{ {
align:'center', align:'center',
title: '物料名称', title: '物料名称',
@ -497,12 +552,7 @@ const columns = [
key: 'code', key: 'code',
width:"120" width:"120"
}, },
{
align:'center',
title: '订单编号',
key: 'orderCode',
width:"120"
},
{ {
align:'center', align:'center',
title: '工艺路线编码', title: '工艺路线编码',
@ -513,43 +563,58 @@ const columns = [
align:'center', align:'center',
title: '生产车间', title: '生产车间',
key: 'proWorkshop', key: 'proWorkshop',
width:"120" width:"120",
}, render(row: any) {
{ const val = row.proWorkshop
align:'center', // dictValue WorkShopName
title: '产品序列', const opt = workshopList.value.find(o =>
key: 'sort', o.value === val ||
width:"120" 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', align:'center',
title: '生产数量', title: '生产数量',
key: 'quantity', key: 'quantity',
width:"120" width:"120"
}, },
{ // {
align:'center', // align:'center',
title: '是否半成品', // title: '',
key: 'sfProduct', // key: 'sfProduct',
width:"120" // width:"120"
}, // },
{ // {
align:'center', // align:'center',
title: '是否采购', // title: '',
key: 'procurement', // key: 'procurement',
width:"120" // width:"120"
}, // },
{ {
align:'center', align:'center',
title: '开始时间', title: '开始时间',
key: 'beginTime', key: 'beginTime',
width:"120" width:"160"
}, },
{ {
align:'center', align:'center',
title: '结束时间', title: '结束时间',
key: 'endTime', key: 'endTime',
width:"120" width:"160"
}, },
{ {
align:'center', align:'center',
@ -585,7 +650,10 @@ const columns = [
align:'center', align:'center',
title: '是否开工', title: '是否开工',
key: 'starter', key: 'starter',
width:"120" width:"120",
render(row: any) {
return row.starter === 1 ? '已开工' : '未开工'
}
}, },
{ {
align:'center', align:'center',
@ -597,54 +665,48 @@ const columns = [
align:'center', align:'center',
title: '操作', title: '操作',
key: 'actions', key: 'actions',
width: 190, width: 260,
fixed:"right", fixed:"right",
render(row:any) { render(row:any) {
const buttons: ReturnType<typeof h>[] = []
const buttons:any = [] if (hasPermission('biz:orderItem:edit')) {
buttons.push(h(NButton, {
if(hasPermission('biz:orderItem:edit')){ size: 'small',
buttons.push(h(NButton, { quaternary: true,
size: 'small', onClick: () => handleEdit(row),
type:'primary', }, {
ghost:true, default: () => [h(NIcon, null, { default: () => h(CreateOutline) }), ' 编辑'],
onClick: () => { handleEdit(row) } }, }))
{ default: () => '编辑'} }
)) // 线
} if (hasPermission('biz:orderItem:startWork') && row.starter !== 1 && row.parentId && row.routeCode) {
if(hasPermission('biz:orderItem:assign')){ buttons.push(h(NButton, {
buttons.push(h(NButton, { size: 'small',
size: 'small', quaternary: true,
type:'success', type: 'warning',
ghost:true, onClick: () => handleStartWork(row),
onClick: () => { disphand(row) } }, }, {
{ default: () => '派工'} default: () => [h(NIcon, null, { default: () => h(PlayOutline) }), ' 开工'],
)) }))
} }
if(hasPermission('biz:orderItem:reorderForm')){ if (hasPermission('biz:orderItem:reorderForm')) {
buttons.push(h(NButton, { buttons.push(h(NButton, {
size: 'small', size: 'small',
type:'info', quaternary: true,
ghost:true, type: 'info',
onClick: () => handleReorderForm(row, false) }, //, disabled: row.starter !==1 onClick: () => handleReorderForm(row, false),
{ default: () => '补单'} }, {
)) default: () => [h(NIcon, null, { default: () => h(DocumentTextOutline) }), ' 补单'],
} }))
}
return buttons.length > 0 ? h(NSpace, {justify:'center'}, { default: () => buttons }) : '-' return buttons.length > 0
? h('div', { style: { display: 'flex', alignItems: 'center', gap: '4px', flexWrap: 'nowrap', justifyContent: 'center' } }, 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) }), ' ']
// })
// ])
} }
} }
] ])
// //
let scrollX = ref(0) let scrollX = ref(0)
@ -666,11 +728,9 @@ async function loadData() {
const res = await orderItemApi.page({ const res = await orderItemApi.page({
page: pagination.page, page: pagination.page,
pageSize: pagination.pageSize, pageSize: pagination.pageSize,
code: searchForm.code, projectId: selectedProjectId.value,
projectName: searchForm.projectName, code: searchForm.code || undefined,
orderCode: searchForm.orderCode, projectName: searchForm.projectName || undefined,
routeCode: searchForm.routeCode,
proWorkshop: searchForm.proWorkshop,
beginTime: searchForm.beginTime, beginTime: searchForm.beginTime,
endTime: searchForm.endTime endTime: searchForm.endTime
}) })
@ -696,6 +756,10 @@ function handleReset() {
searchForm.proWorkshop = '' searchForm.proWorkshop = ''
searchForm.beginTime = null searchForm.beginTime = null
searchForm.endTime = null searchForm.endTime = null
selectedProjectId.value = undefined
selectedProjectKeys.value = []
selectedProjectLabel.value = ''
projectTreeSearch.value = ''
handleSearch() handleSearch()
} }
@ -800,22 +864,14 @@ const kingdeeModalVisible = ref(false)
const kingdeeModalTitle = ref('金蝶生产订单') const kingdeeModalTitle = ref('金蝶生产订单')
const kingdeeLoading = ref(false) const kingdeeLoading = ref(false)
type KingdeeMoTableRow = Omit<KingdeePrdMo, 'children'> & { processRoutes: KingdeeProcessRoute[] }
const kingdeeTableData = ref<KingdeeMoTableRow[]>([]) const kingdeeTableData = ref<KingdeeMoTableRow[]>([])
// const kingdeeExpandedKeys = ref<Array<string | number>>([]) // const kingdeeExpandedKeys = ref<Array<string | number>>([])
async function reCrawlReorderForm(id:number,reCrawl:Boolean){ async function reCrawlReorderForm(id:number,reCrawl:Boolean){
kingdeeLoading.value = true
try { try {
const data = await orderItemApi.preCreationInfo(id, {reCrawl:reCrawl}) const data = await orderItemApi.preCreationInfo(id, {reCrawl:reCrawl})
kingdeeTableData.value = (Array.isArray(data) ? data : []).map((mo) => { kingdeeTableData.value = mapKingdeeMoListToTableRows(Array.isArray(data) ? data : [])
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 }
})
} finally { } finally {
kingdeeLoading.value = false kingdeeLoading.value = false
} }
@ -829,9 +885,8 @@ async function handleReorderForm(row:OrderItem, reCrawl:Boolean){
kingdeeOrderOtemId.value = row.id kingdeeOrderOtemId.value = row.id
kingdeeModalTitle.value = `金蝶生产订单${row.code ? ` - ${row.code}` : ''}` kingdeeModalTitle.value = `金蝶生产订单${row.code ? ` - ${row.code}` : ''}`
kingdeeModalVisible.value = true kingdeeModalVisible.value = true
kingdeeLoading.value = true kingdeeTableData.value = []
reCrawlReorderForm(row.id, false) await reCrawlReorderForm(row.id, reCrawl)
kingdeeLoading.value = false
} }
// 稿 // 稿
@ -857,19 +912,14 @@ async function holdTemporarilySave(id: number, data: KingdeePrdMo[]) {
// } // }
// } // }
// // callback
async function synchronizationSave(id: number, data: KingdeePrdMo[], callback:Function) { async function synchronizationSave(id: number, data: KingdeePrdMo[], callback: Function) {
try { try {
// processRoutes children const rps = await orderItemApi.synchronizationSave(id, data)
await orderItemApi.synchronizationSave(id, data) callback(rps)
.then((rps:any) =>{
callback(rps)
}).catch((res) => {
callback(res)
})
} catch (error) { } 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() { function handleBatchDelete() {
dialog.warning({ dialog.warning({
@ -1102,6 +1171,7 @@ async function handleExport() {
try { try {
const params: Record<string, any> = {} const params: Record<string, any> = {}
if (selectedIds.value.length > 0) params.ids = selectedIds.value if (selectedIds.value.length > 0) params.ids = selectedIds.value
if (searchForm.productionCode) params.productionCode = searchForm.productionCode
if (searchForm.code) params.code = searchForm.code if (searchForm.code) params.code = searchForm.code
if (searchForm.projectName) params.projectName = searchForm.projectName if (searchForm.projectName) params.projectName = searchForm.projectName
if (searchForm.orderCode) params.orderCode = searchForm.orderCode if (searchForm.orderCode) params.orderCode = searchForm.orderCode
@ -1159,9 +1229,14 @@ async function handleImportUpload({ file }: UploadCustomRequestOptions) {
// //
async function loadDictOptions() { 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(() => { onMounted(() => {
loadProjectTree()
loadData() loadData()
loadDictOptions() loadDictOptions()
@ -1172,6 +1247,41 @@ onMounted(() => {
</script> </script>
<style scoped> <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 { .search-form {
margin-bottom: 16px; margin-bottom: 16px;
} }

View File

@ -139,7 +139,14 @@
<div class="col-actions"> <div class="col-actions">
<n-button text type="primary" size="tiny" @click="openDetail(order)">详情</n-button> <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="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-dropdown :options="moreOptions" trigger="click" @select="(k) => onMoreSelect(k, order)">
<n-button text size="tiny">更多</n-button> <n-button text size="tiny">更多</n-button>
</n-dropdown> </n-dropdown>
@ -190,6 +197,11 @@
v-for="(proc, idx) in sortedProcessList(order.processList)" v-for="(proc, idx) in sortedProcessList(order.processList)"
:key="proc.planId ?? idx" :key="proc.planId ?? idx"
:process="proc" :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" :is-last="idx === (order.processList?.length ?? 0) - 1"
:can-edit="hasPermission('biz:orderItem:edit')" :can-edit="hasPermission('biz:orderItem:edit')"
:can-assign="hasPermission('biz:orderItem:assign')" :can-assign="hasPermission('biz:orderItem:assign')"
@ -246,6 +258,11 @@
v-for="(proc, idx) in (detailData.processList)" v-for="(proc, idx) in (detailData.processList)"
:key="proc.planId ?? idx" :key="proc.planId ?? idx"
:process="proc" :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" :is-last="idx === (detailData.processList?.length ?? 0) - 1"
:can-edit="hasPermission('biz:orderItem:edit')" :can-edit="hasPermission('biz:orderItem:edit')"
:can-assign="hasPermission('biz:orderItem:assign')" :can-assign="hasPermission('biz:orderItem:assign')"
@ -261,7 +278,81 @@
</n-spin> </n-spin>
</n-modal> </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">
拖拽行或使用上移/下移调整顺序保存后按 102030 重写工序号存在派工工单或已有转出数量的工序不可调整
</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-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-form ref="formRef" :model="formData" :rules="formRules" label-placement="left" label-width="120px">
<n-grid :cols="2"> <n-grid :cols="2">
@ -276,8 +367,9 @@
</n-form-item> </n-form-item>
</n-gi> </n-gi>
<n-gi> <n-gi>
<!-- 顺序请走订单行编辑重排避免单条改 sort 撞号断链 -->
<n-form-item label="工序顺序" path="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-form-item>
</n-gi> </n-gi>
<n-gi> <n-gi>
@ -329,6 +421,16 @@
<span>{{dispatchform.quantity}}</span> <span>{{dispatchform.quantity}}</span>
</n-gi> </n-gi>
</n-grid> </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-grid :cols="2">
<n-gi> <n-gi>
<span class="pgClass">开始时间</span> <span class="pgClass">开始时间</span>
@ -570,7 +672,9 @@ import {
DownloadOutline, ChevronDownOutline, CheckmarkCircleOutline, CheckmarkCircle, DownloadOutline, ChevronDownOutline, CheckmarkCircleOutline, CheckmarkCircle,
EllipseOutline, TimeOutline, PrintOutline, SyncOutline, PeopleOutline, EllipseOutline, TimeOutline, PrintOutline, SyncOutline, PeopleOutline,
GitBranchOutline, CloseCircleOutline, LockClosedOutline, TrashOutline, EyeSharp, GitBranchOutline, CloseCircleOutline, LockClosedOutline, TrashOutline, EyeSharp,
MenuOutline,
} from '@vicons/ionicons5' } from '@vicons/ionicons5'
import { VueDraggable } from 'vue-draggable-plus'
import { import {
orderProcessPlanApi, orderProcessPlanApi,
toOrderProcessPlanPayload, toOrderProcessPlanPayload,
@ -612,6 +716,15 @@ const detailVisible = ref(false)
const detailLoading = ref(false) const detailLoading = ref(false)
const detailData = ref<OrderProcessPlanVO | null>(null) 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 modalVisible = ref(false)
const modalTitle = ref('') const modalTitle = ref('')
const formRef = ref() const formRef = ref()
@ -628,7 +741,7 @@ const dispatchmodal = ref(false)
const dispatchLoading = ref(false) const dispatchLoading = ref(false)
const dispatchformRef = ref() const dispatchformRef = ref()
const dispatchform = reactive<any>({ const dispatchform = reactive<any>({
id: '', name: '', beginTime: '', endTime: '', quantity: '',workCenterName:'',proWorkshop:'', id: '', name: '', beginTime: '', endTime: '', quantity: '',workCenterName:'',
list: [{ sectionId:'',deviceId:'', quantity: '' }], list: [{ sectionId:'',deviceId:'', quantity: '' }],
}) })
const dispatchrules = {} 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() { function handleAdd() {
modalTitle.value = '新增工序计划' modalTitle.value = '新增工序计划'
Object.assign(formData, defaultFormData) Object.assign(formData, defaultFormData)
@ -1063,26 +1249,33 @@ async function handleSubmit() {
function disphand(process: ProcessPlanItemVO, order?: OrderProcessPlanVO) { function disphand(process: ProcessPlanItemVO, order?: OrderProcessPlanVO) {
const entity = processItemToEntity(process, order?.orderItemId,order?.proWorkshop) 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 dispatchmodal.value = true
dispatchformRef.value?.restoreValidation() dispatchformRef.value?.restoreValidation()
dispatchform.id = entity.id dispatchform.id = entity.id
dispatchform.name = entity.name dispatchform.name = entity.name
dispatchform.beginTime = entity.beginTime // beginTime/endTime
dispatchform.endTime = entity.endTime dispatchform.beginTime = entity.beginTime || order?.beginTime || ''
dispatchform.endTime = entity.endTime || order?.endTime || ''
// quantity
dispatchform.quantity = entity.quantity dispatchform.quantity = entity.quantity
dispatchform.completedQty = completedQty
dispatchform.remainQty = remainQty
dispatchform.orderItemId = entity.orderItemId dispatchform.orderItemId = entity.orderItemId
dispatchform.sort = entity.sort dispatchform.sort = entity.sort
dispatchform.workCenterName = entity.workCenterName dispatchform.workCenterName = entity.workCenterName
dispatchform.workShop = entity.proWorkshop dispatchform.workShop = entity.proWorkshop
dispatchform.list = [{ sectionId:'',deviceId:'', quantity: '' }] dispatchform.list = [{ sectionId:'',deviceId:'', quantity: '' }]
console.log(dispatchform)
dispatchform.list = [{ sectionId: '', deviceId: '', quantity: remainQty || '' }]
} }
function addpgnum() { function addpgnum() {
dispatchform.list.push({ sectionId:'',deviceId:'', quantity: '' }) dispatchform.list.push({ sectionId: '', deviceId: '', quantity: '' })
} }
function deletenum(index: number) { function deletenum(index: number) {
@ -1091,18 +1284,27 @@ function deletenum(index: number) {
function dispatchSubmit() { function dispatchSubmit() {
let total = 0 let total = 0
dispatchform.list.forEach((n: any) => { dispatchform.list.forEach((n: any) => {
total += Number(n.quantity) || 0 total += Number(n.quantity) || 0
}) })
const remainQty = Number(dispatchform.remainQty)
dispatchformRef.value?.validate((v: boolean) => { if (remainQty <= 0) {
if (v) return message.warning('当前工序已全部完成,无需再派工')
if (total !== Number(dispatchform.quantity)) { return
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 return
} }
dispatchLoading.value = true dispatchLoading.value = true
// quantity
orderProcessPlanApi.assignWork(dispatchform).then(() => { orderProcessPlanApi.assignWork(dispatchform).then(() => {
message.success('派工成功') message.success('派工成功')
dispatchmodal.value = false dispatchmodal.value = false
@ -1476,4 +1678,83 @@ onMounted(loadData)
font-size: 12px; font-size: 12px;
color: #8c8c8c; 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> </style>

View File

@ -2,7 +2,13 @@
<div class="process-card-wrap"> <div class="process-card-wrap">
<div class="process-card" :class="themeClass"> <div class="process-card" :class="themeClass">
<div class="card-head"> <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> <n-tag size="small" :bordered="false" :type="statusTag.type">{{ statusTag.label }}</n-tag>
</div> </div>
@ -39,22 +45,28 @@
</div> </div>
<div class="card-flow"> <div class="card-flow">
<div class="flow-item">
<span>转入</span>
<strong>{{ process.transferInQty ?? 0 }}</strong>
</div>
<div class="flow-item"> <div class="flow-item">
<span>剩余物料</span> <span>剩余物料</span>
<strong>{{ remainQty }}</strong> <strong>{{ remainQty }}</strong>
</div> </div>
<div class="flow-item"> <div class="flow-item">
<span>下道</span> <span></span>
<strong>{{ process.transferOutQty ?? 0 }}</strong> <strong>{{ process.transferOutQty ?? 0 }}</strong>
</div> </div>
</div> </div>
<div class="card-meta"> <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>{{ process.workOrderCode || '-' }}</em></div>
<div class="meta-line"><span>人员</span><em>{{ personnel }}</em></div> <div class="meta-line"><span>人员</span><em>{{ personnel }}</em></div>
<div class="meta-line"><span>车间</span><em>{{ process.departmentName || '-' }}</em></div> <div class="meta-line"><span>车间</span><em>{{ workshopText }}</em></div>
<div class="meta-line"><span>工位</span><em>{{ process.workCenterName || '-' }}</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>{{ planTimeText }}</em></div>
<div class="meta-line"><span>实际开始</span><em>{{ process.actualStartTime || '-' }}</em></div> <div class="meta-line"><span>实际开始</span><em>{{ process.actualStartTime || '-' }}</em></div>
</div> </div>
@ -110,6 +122,16 @@ import { PROCESS_STATUS_MAP, type ProcessPlanItemVO } from '@/api/orderProcessPl
const props = defineProps<{ const props = defineProps<{
process: ProcessPlanItemVO process: ProcessPlanItemVO
/** 物料编码(订单头) */
materialCode?: string
/** 生产订单编号(订单头 orderCode */
orderCode?: string
/** 订单计划开始(订单头 beginTime工序无计划时间时回退 */
orderBeginTime?: string
/** 订单计划结束(订单头 endTime */
orderEndTime?: string
/** 生产车间(订单头 proWorkshop */
proWorkshop?: string
isLast?: boolean isLast?: boolean
canEdit?: boolean canEdit?: boolean
canAssign?: boolean canAssign?: boolean
@ -171,15 +193,39 @@ const railColor = computed(() => {
const personnel = computed(() => { const personnel = computed(() => {
if (props.process.assignByName) return props.process.assignByName if (props.process.assignByName) return props.process.assignByName
const list = props.process.assignWorkList 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 '-' 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 planTimeText = computed(() => {
const start = props.process.planStartTime // 退 beginTime/endTime
const end = props.process.planFinishTime 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 '-'
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 || '-' return start || end || '-'
}) })
</script> </script>
@ -252,6 +298,29 @@ const planTimeText = computed(() => {
white-space: nowrap; 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 { .card-progress {
display: flex; display: flex;
justify-content: center; justify-content: center;

View File

@ -233,6 +233,16 @@
<n-input v-model:value="dispatchform.quantity" disabled /> <n-input v-model:value="dispatchform.quantity" disabled />
</n-form-item> </n-form-item>
</n-gi> </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-gi>
<n-form-item label="开始时间"> <n-form-item label="开始时间">
<n-input v-model:value="dispatchform.beginTime" disabled /> <n-input v-model:value="dispatchform.beginTime" disabled />
@ -816,6 +826,8 @@ let dispatchform = reactive<any>({
endTime:'', endTime:'',
userid:'', userid:'',
quantity:'', quantity:'',
completedQty: 0,
remainQty: 0,
list:[{ list:[{
userId:'', userId:'',
quantity:'' quantity:''
@ -866,17 +878,22 @@ function deletenum(index:any) {
function disphand(process: ProcessPlanItemVO, order?: OrderProcessPlanVO) { function disphand(process: ProcessPlanItemVO, order?: OrderProcessPlanVO) {
const entity = processItemToEntity(process, order?.orderItemId) 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 dispatchmodal.value = true
dispatchformRef.value?.restoreValidation() dispatchformRef.value?.restoreValidation()
dispatchform.id = entity.id dispatchform.id = entity.id
dispatchform.name = entity.name dispatchform.name = entity.name
dispatchform.beginTime = entity.beginTime dispatchform.beginTime = entity.beginTime || order?.beginTime || ''
dispatchform.endTime = entity.endTime dispatchform.endTime = entity.endTime || order?.endTime || ''
dispatchform.quantity = entity.quantity dispatchform.quantity = entity.quantity
dispatchform.completedQty = completedQty
dispatchform.remainQty = remainQty
dispatchform.userid = '' dispatchform.userid = ''
dispatchform.list = [{ dispatchform.list = [{
userId: '', userId: '',
quantity: '', quantity: remainQty || '',
}] }]
} }
@ -885,10 +902,15 @@ function dispatchSubmit() {
dispatchform.list.forEach((n:any) => { dispatchform.list.forEach((n:any) => {
total = n.quantity*1+total total = n.quantity*1+total
}) })
const remainQty = Number(dispatchform.remainQty)
if (remainQty <= 0) {
message.warning('当前工序已全部完成,无需再派工')
return
}
dispatchformRef.value?.validate((v:any) => { dispatchformRef.value?.validate((v:any) => {
if(!v){ if(!v){
if(total != dispatchform.quantity){ if(total != remainQty){
message.error('派工总数量需等于生产总数',{ message.error(`派工总数量需等于待派数量 ${remainQty}(计划 ${dispatchform.quantity},已完成 ${dispatchform.completedQty ?? 0}`,{
duration:4000 duration:4000
}) })
}else{ }else{

View File

@ -159,10 +159,9 @@
const res = await sectionApi.list({sectionName:props.section,workShop:props.workShop}) const res = await sectionApi.list({sectionName:props.section,workShop:props.workShop})
const mesSection = res.MesSection; const mesSection = res.MesSection;
const mesDevice = res.MesDevice const mesDevice = res.MesDevice
console.log(mesSection);
sectionList.push({ sectionList.push({
label:mesSection.sectionName, label:mesSection.deptName,
value:mesSection.id value:mesSection.id
}) })
props.obj.sectionId = mesSection.id props.obj.sectionId = mesSection.id

View File

@ -150,7 +150,8 @@
> >
<KingdeeVue <KingdeeVue
:kingdeeTableData="kingdeeTableData" :kingdeeTableData="kingdeeTableData"
:selectedId="kingdeeCurrentProjectId" :selectedId="kingdeeCurrentProjectId"
:loading="kingdeeLoading"
@close-modal="kingdeeModalVisible = false" @close-modal="kingdeeModalVisible = false"
@holdTemporarilyCallback="handleSaveDraft" @holdTemporarilyCallback="handleSaveDraft"
@synchronizationCallback="handleSyncOrderAndPlan" @synchronizationCallback="handleSyncOrderAndPlan"
@ -194,7 +195,8 @@
import { ref, reactive, h, computed, onMounted, watch } from 'vue' 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 { 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 { 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 { dictDataApi } from '@/api/org'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
@ -660,21 +662,14 @@ function handleGanttSchedule() {
} }
async function reCrawlReorderForm(id:number,reCrawl:Boolean){ async function reCrawlReorderForm(id:number,reCrawl:Boolean){
kingdeeLoading.value = true
try { try {
const data = await orderProjectApi.getKingdeeOrder(id,{reCrawl:reCrawl}) const data = await orderProjectApi.getKingdeeOrder(id,{reCrawl:reCrawl})
kingdeeTableData.value = (Array.isArray(data) ? data : []).map((mo) => { kingdeeTableData.value = mapKingdeeMoListToTableRows(Array.isArray(data) ? data : [])
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 }
})
if (kingdeeTableData.value.length === 0) { if (kingdeeTableData.value.length === 0) {
message.info('未查询到金蝶生产订单数据') message.info('未查询到金蝶生产订单数据')
} else if (reCrawl) {
message.success('重新读取成功')
} }
} finally { } finally {
kingdeeLoading.value = false kingdeeLoading.value = false

View File

@ -311,7 +311,7 @@ const defaultFormData: QcItem = {
plans: '', plans: '',
description: '', description: '',
createby: undefined, createby: undefined,
status:undefined status:0
} }
const formData = reactive<QcItem>({ ...defaultFormData }) const formData = reactive<QcItem>({ ...defaultFormData })
@ -319,6 +319,8 @@ const formData = reactive<QcItem>({ ...defaultFormData })
// //
const formRules = { 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') { if (typeof submitData.updateTime === 'number') {
submitData.updateTime = new Date(submitData.updateTime).toISOString().slice(0, 19).replace('T', ' ') submitData.updateTime = new Date(submitData.updateTime).toISOString().slice(0, 19).replace('T', ' ')
} }
if (submitData.id) { console.log(submitData)
await qcItemApi.update(submitData) // if (submitData.id) {
message.success('修改成功') // await qcItemApi.update(submitData)
} else { // message.success('')
await qcItemApi.create(submitData) // } else {
message.success('新增成功') // await qcItemApi.create(submitData)
} // message.success('')
modalVisible.value = false // }
loadData() // modalVisible.value = false
// loadData()
} catch (error) { } catch (error) {
// //
} }

View File

@ -79,13 +79,19 @@
<n-input v-model:value="formData.deviceName" placeholder="请输入设备名称" /> <n-input v-model:value="formData.deviceName" placeholder="请输入设备名称" />
</n-form-item> </n-form-item>
<n-form-item label="所属工段" path="sectionId"> <n-form-item label="所属工段" path="sectionId">
<n-select <!-- <n-select-->
v-model:value="formData.sectionId" <!-- v-model:value="formData.sectionId"-->
:options="sectionList" <!-- :options="sectionList"-->
placeholder="请选择所属工段" <!-- placeholder="请选择所属工段"-->
clearable <!-- clearable-->
style="width: 200px" <!-- style="width: 200px"-->
/> <!-- />-->
<n-tree-select
v-model:value="formData.sectionId"
cascade
checkable
:options="sectionList"
/>
</n-form-item> </n-form-item>
<n-form-item label="备注" path="remark"> <n-form-item label="备注" path="remark">
<n-input v-model:value="formData.remark" type="textarea" placeholder="请输入备注" /> <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 { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline } from '@vicons/ionicons5'
import { deviceApi, type Device } from '@/api/device' import { deviceApi, type Device } from '@/api/device'
import { dictDataApi } from '@/api/org' import {deptApi, dictDataApi} from '@/api/org'
import {sectionApi, type Section} from '@/api/section' import {sectionApi, type Section} from '@/api/section'
import {TreeNode} from "echarts/types/src/data/Tree";
const message = useMessage() const message = useMessage()
@ -208,6 +215,7 @@ const columns: DataTableColumns<Device> = [
{ type: 'selection' }, { type: 'selection' },
{ title: '工位编码', key: 'deviceCode' }, { title: '工位编码', key: 'deviceCode' },
{ title: '工位名称', key: 'deviceName' }, { title: '工位名称', key: 'deviceName' },
{ title: '工位类型', key: 'deviceType' },
{ title: '所属工段', key: 'sectionName' }, { title: '所属工段', key: 'sectionName' },
{ title: '状态', key: 'status', { title: '状态', key: 'status',
render:(row) =>{ render:(row) =>{
@ -431,8 +439,24 @@ async function loadDictOptions() {
// //
async function loadSection() { async function loadSection() {
const data = await sectionApi.listNoParam() const data = await deptApi.tree()
sectionList = data.map((d:any)=> ({ label: d.sectionName, value: (Number(d.id) || d.id) })) sectionList = buildOptions(data);
}
function buildOptions(d: any[]): TreeNode[] {
return d.map(item => {
// id0||
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(() => { onMounted(() => {

View File

@ -18,7 +18,7 @@
:data="AssingWorkTableData" :data="AssingWorkTableData"
:loading="tableLoading" :loading="tableLoading"
:row-key="RowKey" :row-key="RowKey"
:scroll-x="600" :scroll-x="1800"
:max-height="1000" :max-height="1000"
size="small" size="small"
style="margin-bottom:15px" style="margin-bottom:15px"

View File

@ -507,7 +507,7 @@ async function loadData() {
async function loadRoles() { async function loadRoles() {
try { try {
const roles = await roleApi.roleList() const roles = await roleApi.list()
roleOptions.value = roles.map((role: SysRole) => ({ roleOptions.value = roles.map((role: SysRole) => ({
label: role.name, label: role.name,
value: role.id! value: role.id!
@ -519,7 +519,7 @@ async function loadRoles() {
async function loadPostOptions() { async function loadPostOptions() {
try { try {
const posts = await postApi.postList() const posts = await postApi.list()
postOptions.value = posts.map(p => ({ postOptions.value = posts.map(p => ({
label: p.postName, label: p.postName,
value: p.id! value: p.id!

View File

@ -20,7 +20,8 @@ export default defineConfig({
port: 3000, port: 3000,
proxy: { proxy: {
'/api': { '/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.12.230:8888',
//target:'http://192.168.5.230:8888', //target:'http://192.168.5.230:8888',
//target:'http://192.168.5.232:8888/', //target:'http://192.168.5.232:8888/',