修改
This commit is contained in:
commit
1382a19d3d
@ -20,7 +20,7 @@ export interface ConcessionApply {
|
|||||||
|
|
||||||
instanceId?: string
|
instanceId?: string
|
||||||
|
|
||||||
approveStatus?: number
|
approveStatus?: string
|
||||||
|
|
||||||
createTime?: string
|
createTime?: string
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ export const concessionApplyApi = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 删除
|
// 删除
|
||||||
delete(ids: string[]) {
|
delete(ids: number[]) {
|
||||||
return request({ url: `/biz/concessionApply/${ids.join(',')}`, method: 'delete' })
|
return request({ url: `/biz/concessionApply/${ids.join(',')}`, method: 'delete' })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
import { request } from '@/utils/request'
|
import { request } from '@/utils/request'
|
||||||
|
|
||||||
// 工序计划 类型定义
|
|
||||||
|
|
||||||
|
/** 工序计划实体(新增/修改/派工) */
|
||||||
|
|
||||||
export interface OrderProcessPlan {
|
export interface OrderProcessPlan {
|
||||||
|
|
||||||
id?: number
|
id?: number
|
||||||
|
|
||||||
name?: string
|
name?: string
|
||||||
@ -36,63 +40,407 @@ export interface OrderProcessPlan {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** 派工明细 */
|
||||||
|
|
||||||
|
export interface AssignWorkItem {
|
||||||
|
|
||||||
|
userId?: number | string
|
||||||
|
|
||||||
|
quantity?: number | string
|
||||||
|
|
||||||
|
assingStatus?: number
|
||||||
|
|
||||||
|
[key: string]: unknown
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** 工序计划明细 VO(processList 项) */
|
||||||
|
|
||||||
|
export interface ProcessPlanItemVO {
|
||||||
|
|
||||||
|
planId?: number
|
||||||
|
|
||||||
|
processCode?: string
|
||||||
|
|
||||||
|
operNumber?: number
|
||||||
|
|
||||||
|
workCenterName?: string
|
||||||
|
|
||||||
|
departmentName?: string
|
||||||
|
|
||||||
|
processName?: string
|
||||||
|
|
||||||
|
operDescription?: string
|
||||||
|
|
||||||
|
activityUnit?: string
|
||||||
|
|
||||||
|
optCtrlCodeName?: string
|
||||||
|
|
||||||
|
activityQty?: number
|
||||||
|
|
||||||
|
planStartTime?: string
|
||||||
|
|
||||||
|
planFinishTime?: string
|
||||||
|
|
||||||
|
actualStartTime?: string | null
|
||||||
|
|
||||||
|
actualFinishTime?: string | null
|
||||||
|
|
||||||
|
quantity?: number
|
||||||
|
|
||||||
|
transferInQty?: number
|
||||||
|
|
||||||
|
transferOutQty?: number
|
||||||
|
|
||||||
|
completedQty?: number
|
||||||
|
|
||||||
|
scrapQty?: number
|
||||||
|
|
||||||
|
reworkQty?: number
|
||||||
|
|
||||||
|
procurement?: number
|
||||||
|
|
||||||
|
outsource?: number
|
||||||
|
|
||||||
|
qualityInspection?: number
|
||||||
|
|
||||||
|
storageEntry?: number
|
||||||
|
|
||||||
|
assign?: number
|
||||||
|
|
||||||
|
assignByName?: string | null
|
||||||
|
|
||||||
|
assignTime?: string | null
|
||||||
|
|
||||||
|
processStatus?: number
|
||||||
|
|
||||||
|
prevPlanId?: number | null
|
||||||
|
|
||||||
|
nextPlanId?: number | null
|
||||||
|
|
||||||
|
assignWorkList?: AssignWorkItem[]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** 按生产订单分组的工序计划 VO(分页/详情) */
|
||||||
|
|
||||||
|
export interface OrderProcessPlanVO {
|
||||||
|
|
||||||
|
orderItemId?: number
|
||||||
|
|
||||||
|
mainCode?: string
|
||||||
|
|
||||||
|
materialCode?: string
|
||||||
|
|
||||||
|
materialName?: string
|
||||||
|
|
||||||
|
orderCode?: string
|
||||||
|
|
||||||
|
quantity?: number
|
||||||
|
|
||||||
|
routeCode?: string
|
||||||
|
|
||||||
|
proWorkshop?: string
|
||||||
|
|
||||||
|
beginTime?: string
|
||||||
|
|
||||||
|
endTime?: string
|
||||||
|
|
||||||
|
actualStartTime?: string | null
|
||||||
|
|
||||||
|
actualEndTime?: string | null
|
||||||
|
|
||||||
|
completedQty?: number
|
||||||
|
|
||||||
|
orderStatus?: number
|
||||||
|
|
||||||
|
currentProcessName?: string
|
||||||
|
|
||||||
|
currentOperNumber?: number
|
||||||
|
|
||||||
|
processList?: ProcessPlanItemVO[]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const ORDER_STATUS_MAP: Record<number, { label: string; type: 'default' | 'info' | 'success' | 'warning' | 'error' }> = {
|
||||||
|
|
||||||
|
0: { label: '未开始', type: 'default' },
|
||||||
|
|
||||||
|
1: { label: '进行中', type: 'info' },
|
||||||
|
|
||||||
|
2: { label: '已完成', type: 'success' },
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const PROCESS_STATUS_MAP: Record<number, { label: string; type: 'default' | 'info' | 'success' | 'warning' | 'error' }> = {
|
||||||
|
|
||||||
|
0: { label: '待派工', type: 'default' },
|
||||||
|
|
||||||
|
1: { label: '待执行', type: 'info' },
|
||||||
|
|
||||||
|
2: { label: '执行中', type: 'warning' },
|
||||||
|
|
||||||
|
3: { label: '质检中', type: 'warning' },
|
||||||
|
|
||||||
|
4: { label: '已完成', type: 'success' },
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const ASSIGN_STATUS_MAP: Record<number, { label: string; type: 'default' | 'info' | 'success' | 'warning' | 'error' }> = {
|
||||||
|
|
||||||
|
1: { label: '待执行', type: 'info' },
|
||||||
|
|
||||||
|
2: { label: '执行中', type: 'warning' },
|
||||||
|
|
||||||
|
3: { label: '质检中', type: 'warning' },
|
||||||
|
|
||||||
|
4: { label: '已汇报', type: 'success' },
|
||||||
|
|
||||||
|
5: { label: '已关闭', type: 'default' },
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** 工序 VO 转实体(编辑/派工提交用) */
|
||||||
|
|
||||||
|
export function processItemToEntity(
|
||||||
|
|
||||||
|
item: ProcessPlanItemVO,
|
||||||
|
|
||||||
|
orderItemId?: number,
|
||||||
|
|
||||||
|
): OrderProcessPlan {
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
id: item.planId,
|
||||||
|
|
||||||
|
name: item.processName,
|
||||||
|
|
||||||
|
code: item.processCode,
|
||||||
|
|
||||||
|
sort: item.operNumber,
|
||||||
|
|
||||||
|
orderItemId,
|
||||||
|
|
||||||
|
beginTime: item.planStartTime,
|
||||||
|
|
||||||
|
endTime: item.planFinishTime,
|
||||||
|
|
||||||
|
procurement: item.procurement,
|
||||||
|
|
||||||
|
outsource: item.outsource,
|
||||||
|
|
||||||
|
quantity: item.quantity,
|
||||||
|
|
||||||
|
transferNum: item.transferInQty,
|
||||||
|
|
||||||
|
transferOutNum: item.transferOutQty,
|
||||||
|
|
||||||
|
qualityInspection: item.qualityInspection,
|
||||||
|
|
||||||
|
storageEntry: item.storageEntry,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** 提交前剔除展示字段,保留 OrderProcessPlan 实体 */
|
||||||
|
|
||||||
|
export function toOrderProcessPlanPayload(data: Record<string, unknown>): OrderProcessPlan {
|
||||||
|
|
||||||
|
const payload = { ...data }
|
||||||
|
|
||||||
|
const displayKeys = [
|
||||||
|
|
||||||
|
'mainCode', 'materialCode', 'materialName', 'orderCode', 'routeCode', 'proWorkshop',
|
||||||
|
|
||||||
|
'actualStartTime', 'actualEndTime', 'completedQty', 'orderStatus', 'currentProcessName',
|
||||||
|
|
||||||
|
'currentOperNumber', 'processList', 'planId', 'processName', 'processCode', 'operNumber',
|
||||||
|
|
||||||
|
'workCenterName', 'departmentName', 'operDescription', 'activityUnit', 'optCtrlCodeName',
|
||||||
|
|
||||||
|
'activityQty', 'planStartTime', 'planFinishTime', 'actualFinishTime', 'transferInQty',
|
||||||
|
|
||||||
|
'transferOutQty', 'scrapQty', 'reworkQty', 'assign', 'assignByName', 'assignTime',
|
||||||
|
|
||||||
|
'processStatus', 'prevPlanId', 'nextPlanId', 'assignWorkList',
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const key of displayKeys) {
|
||||||
|
|
||||||
|
delete payload[key]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload as OrderProcessPlan
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 工序计划 API
|
// 工序计划 API
|
||||||
|
|
||||||
export const orderProcessPlanApi = {
|
export const orderProcessPlanApi = {
|
||||||
// 分页查询
|
|
||||||
page(params:any) { //{ page: number; pageSize: number; id?: number; name?: string; code?: string; orderItemId?: number }
|
// 分页查询(按生产订单分组)
|
||||||
return request({ url: '/biz/orderProcessPlan/page', method: 'get', params })
|
|
||||||
|
page(params: {
|
||||||
|
|
||||||
|
page: number
|
||||||
|
|
||||||
|
pageSize: number
|
||||||
|
|
||||||
|
id?: number | string
|
||||||
|
|
||||||
|
name?: string
|
||||||
|
|
||||||
|
code?: string
|
||||||
|
|
||||||
|
orderItemId?: number | string
|
||||||
|
|
||||||
|
}) {
|
||||||
|
|
||||||
|
return request<{ list: OrderProcessPlanVO[]; total: number; page: number; pageSize: number }>({
|
||||||
|
|
||||||
|
url: '/biz/orderProcessPlan/page',
|
||||||
|
|
||||||
|
method: 'get',
|
||||||
|
|
||||||
|
params,
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取详情
|
|
||||||
detail(id: string) {
|
|
||||||
return request({ url: `/biz/orderProcessPlan/${id}`, method: 'get' })
|
// 获取详情(路径参数为生产订单 id)
|
||||||
|
|
||||||
|
detail(orderItemId: number | string) {
|
||||||
|
|
||||||
|
return request<OrderProcessPlanVO>({
|
||||||
|
|
||||||
|
url: `/biz/orderProcessPlan/${orderItemId}`,
|
||||||
|
|
||||||
|
method: 'get',
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 新增
|
// 新增
|
||||||
create(data: any) {
|
|
||||||
|
create(data: OrderProcessPlan) {
|
||||||
|
|
||||||
return request({ url: '/biz/orderProcessPlan', method: 'post', data })
|
return request({ url: '/biz/orderProcessPlan', method: 'post', data })
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 修改
|
// 修改
|
||||||
update(data: any) {
|
|
||||||
|
update(data: OrderProcessPlan) {
|
||||||
|
|
||||||
return request({ url: '/biz/orderProcessPlan', method: 'put', data })
|
return request({ url: '/biz/orderProcessPlan', method: 'put', data })
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 删除
|
|
||||||
delete(ids: string[]) {
|
|
||||||
|
// 删除(工序计划 id)
|
||||||
|
|
||||||
|
delete(ids: (number | string)[]) {
|
||||||
|
|
||||||
return request({ url: `/biz/orderProcessPlan/${ids.join(',')}`, method: 'delete' })
|
return request({ url: `/biz/orderProcessPlan/${ids.join(',')}`, method: 'delete' })
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 派工
|
// 派工
|
||||||
assignWork(data: any) {
|
|
||||||
|
assignWork(data: OrderProcessPlan & { list?: AssignWorkItem[] }) {
|
||||||
|
|
||||||
return request({ url: '/biz/orderProcessPlan/assignWork', method: 'put', data })
|
return request({ url: '/biz/orderProcessPlan/assignWork', method: 'put', data })
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 导出
|
// 导出
|
||||||
|
|
||||||
export(params?: { ids?: string[]; id?: number; name?: string; code?: string; orderItemId?: number }) {
|
export(params?: { ids?: string[]; id?: number; name?: string; code?: string; orderItemId?: number }) {
|
||||||
const p: Record<string, any> = {}
|
|
||||||
|
const p: Record<string, unknown> = {}
|
||||||
|
|
||||||
if (params?.ids?.length) p.ids = params.ids.join(',')
|
if (params?.ids?.length) p.ids = params.ids.join(',')
|
||||||
|
|
||||||
if (params?.id !== undefined && params?.id !== null) p.id = params.id
|
if (params?.id !== undefined && params?.id !== null) p.id = params.id
|
||||||
|
|
||||||
if (params?.name !== undefined && params?.name !== null) p.name = params.name
|
if (params?.name !== undefined && params?.name !== null) p.name = params.name
|
||||||
|
|
||||||
if (params?.code !== undefined && params?.code !== null) p.code = params.code
|
if (params?.code !== undefined && params?.code !== null) p.code = params.code
|
||||||
|
|
||||||
if (params?.orderItemId !== undefined && params?.orderItemId !== null) p.orderItemId = params.orderItemId
|
if (params?.orderItemId !== undefined && params?.orderItemId !== null) p.orderItemId = params.orderItemId
|
||||||
return request({ url: `/biz/orderProcessPlan/export`, method: 'get', params: p, responseType: 'blob' })
|
|
||||||
|
return request({ url: '/biz/orderProcessPlan/export', method: 'get', params: p, responseType: 'blob' })
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 导入
|
// 导入
|
||||||
|
|
||||||
importData(file: File) {
|
importData(file: File) {
|
||||||
|
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
|
|
||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
|
|
||||||
return request<{ success: number; fail: number; errors: string[] }>({
|
return request<{ success: number; fail: number; errors: string[] }>({
|
||||||
url: `/biz/orderProcessPlan/import`,
|
|
||||||
|
url: '/biz/orderProcessPlan/import',
|
||||||
|
|
||||||
method: 'post',
|
method: 'post',
|
||||||
|
|
||||||
data: formData,
|
data: formData,
|
||||||
headers: { 'Content-Type': 'multipart/form-data' }
|
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 下载导入模板
|
// 下载导入模板
|
||||||
|
|
||||||
downloadTemplate() {
|
downloadTemplate() {
|
||||||
return request({ url: `/biz/orderProcessPlan/template`, method: 'get', responseType: 'blob' })
|
|
||||||
}
|
return request({ url: '/biz/orderProcessPlan/template', method: 'get', responseType: 'blob' })
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
88
src/api/processRoute.ts
Normal file
88
src/api/processRoute.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { request } from '@/utils/request'
|
||||||
|
|
||||||
|
// 工艺路线主表 类型定义
|
||||||
|
export interface ProcessRoute {
|
||||||
|
id?: number
|
||||||
|
|
||||||
|
code?: string
|
||||||
|
|
||||||
|
name?: string
|
||||||
|
|
||||||
|
materialCode?: string
|
||||||
|
|
||||||
|
materialName?: string
|
||||||
|
|
||||||
|
version?: string
|
||||||
|
|
||||||
|
status?: number
|
||||||
|
|
||||||
|
source?: number
|
||||||
|
|
||||||
|
sourceId?: string
|
||||||
|
|
||||||
|
remark?: string
|
||||||
|
|
||||||
|
createTime?: string
|
||||||
|
|
||||||
|
createBy?: string
|
||||||
|
|
||||||
|
changeTime?: string
|
||||||
|
|
||||||
|
changeBy?: string
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工艺路线主表 API
|
||||||
|
export const processRouteApi = {
|
||||||
|
// 分页查询
|
||||||
|
page(params: { page: number; pageSize: number; id?: number; name?: string; status?: number }) {
|
||||||
|
return request({ url: '/biz/processRoute/page', method: 'get', params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取详情
|
||||||
|
detail(id: string) {
|
||||||
|
return request({ url: `/biz/processRoute/${id}`, method: 'get' })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
create(data: ProcessRoute) {
|
||||||
|
return request({ url: '/biz/processRoute', method: 'post', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 修改
|
||||||
|
update(data: ProcessRoute) {
|
||||||
|
return request({ url: '/biz/processRoute', method: 'put', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
delete(ids: string[]) {
|
||||||
|
return request({ url: `/biz/processRoute/${ids.join(',')}`, method: 'delete' })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 导出
|
||||||
|
export(params?: { ids?: string[]; id?: number; name?: string; 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?.name !== undefined && params?.name !== null) p.name = params.name
|
||||||
|
if (params?.status !== undefined && params?.status !== null) p.status = params.status
|
||||||
|
return request({ url: `/biz/processRoute/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/processRoute/import`,
|
||||||
|
method: 'post',
|
||||||
|
data: formData,
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 下载导入模板
|
||||||
|
downloadTemplate() {
|
||||||
|
return request({ url: `/biz/processRoute/template`, method: 'get', responseType: 'blob' })
|
||||||
|
}
|
||||||
|
}
|
||||||
89
src/api/processRouteStep.ts
Normal file
89
src/api/processRouteStep.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { request } from '@/utils/request'
|
||||||
|
|
||||||
|
// 工艺路线工序明细 类型定义
|
||||||
|
export interface ProcessRouteStep {
|
||||||
|
id?: number
|
||||||
|
|
||||||
|
routeId?: number
|
||||||
|
|
||||||
|
operNumber?: number
|
||||||
|
|
||||||
|
processName?: string
|
||||||
|
|
||||||
|
operDescription?: string
|
||||||
|
|
||||||
|
workCenterName?: string
|
||||||
|
|
||||||
|
departmentName?: string
|
||||||
|
|
||||||
|
activityUnit?: string
|
||||||
|
|
||||||
|
optCtrlCodeName?: string
|
||||||
|
|
||||||
|
activityQty?: string
|
||||||
|
|
||||||
|
sort?: number
|
||||||
|
|
||||||
|
isOutsource?: number
|
||||||
|
|
||||||
|
status?: number
|
||||||
|
|
||||||
|
createTime?: string
|
||||||
|
|
||||||
|
changeTime?: string
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工艺路线工序明细 API
|
||||||
|
export const processRouteStepApi = {
|
||||||
|
// 分页查询
|
||||||
|
page(params: { page: number; pageSize: number; id?: number; status?: number }) {
|
||||||
|
return request({ url: '/biz/processRouteStep/page', method: 'get', params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取详情
|
||||||
|
detail(id: number) {
|
||||||
|
return request({ url: `/biz/processRouteStep/${id}`, method: 'get' })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
create(data: ProcessRouteStep) {
|
||||||
|
return request({ url: '/biz/processRouteStep', method: 'post', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 修改
|
||||||
|
update(data: ProcessRouteStep) {
|
||||||
|
return request({ url: '/biz/processRouteStep', method: 'put', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
delete(ids: number[]) {
|
||||||
|
return request({ url: `/biz/processRouteStep/${ids.join(',')}`, method: 'delete' })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 导出
|
||||||
|
export(params?: { ids?: number[]; 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/processRouteStep/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/processRouteStep/import`,
|
||||||
|
method: 'post',
|
||||||
|
data: formData,
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 下载导入模板
|
||||||
|
downloadTemplate() {
|
||||||
|
return request({ url: `/biz/processRouteStep/template`, method: 'get', responseType: 'blob' })
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,7 +8,7 @@ export interface QcResultDetail {
|
|||||||
|
|
||||||
resultType?: number
|
resultType?: number
|
||||||
|
|
||||||
qty?: string
|
qty?: number
|
||||||
|
|
||||||
traceCode?: string
|
traceCode?: string
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,14 @@ export interface SubmitLog {
|
|||||||
|
|
||||||
createBy?: string
|
createBy?: string
|
||||||
|
|
||||||
|
materialName?:string
|
||||||
|
|
||||||
|
materialCode?:string
|
||||||
|
|
||||||
|
materialQty?:number
|
||||||
|
|
||||||
|
assingStatus?: number
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 派工工单汇报记录 API
|
// 派工工单汇报记录 API
|
||||||
@ -39,7 +47,7 @@ export const submitLogApi = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 删除
|
// 删除
|
||||||
delete(ids: string[]) {
|
delete(ids: number[]) {
|
||||||
return request({ url: `/biz/submitLog/${ids.join(',')}`, method: 'delete' })
|
return request({ url: `/biz/submitLog/${ids.join(',')}`, method: 'delete' })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
69
src/utils/kingdeeSchedule.ts
Normal file
69
src/utils/kingdeeSchedule.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { normalizeKingdeeProcessRoute, type KingdeePrdMo, type KingdeeProcessRoute } from '@/api/orderProject'
|
||||||
|
|
||||||
|
export type KingdeeMoTableRow = Omit<KingdeePrdMo, 'children'> & { processRoutes: KingdeeProcessRoute[] }
|
||||||
|
|
||||||
|
export function mapKingdeeMoListToTableRows(data: KingdeePrdMo[]): KingdeeMoTableRow[] {
|
||||||
|
return (Array.isArray(data) ? data : []).map((mo) => {
|
||||||
|
const { children, ...rest } = mo
|
||||||
|
const routes = [...(children ?? [])]
|
||||||
|
.sort((a, b) => (a.operNumber ?? 0) - (b.operNumber ?? 0))
|
||||||
|
.map((r) => normalizeKingdeeProcessRoute({
|
||||||
|
...r,
|
||||||
|
planStartTime: r.planStartTime ?? null,
|
||||||
|
planFinishTime: r.planFinishTime ?? null,
|
||||||
|
}))
|
||||||
|
return { ...rest, processRoutes: routes }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tableRowsToKingdeeDraft(rows: KingdeeMoTableRow[]): KingdeePrdMo[] {
|
||||||
|
return rows.map((mo) => {
|
||||||
|
const { processRoutes, ...rest } = mo
|
||||||
|
return { ...rest, children: processRoutes } as KingdeePrdMo
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function parsePlanTime(val: string | null | undefined): number | null {
|
||||||
|
if (!val) return null
|
||||||
|
const ts = new Date(val.replace(' ', 'T')).getTime()
|
||||||
|
return Number.isNaN(ts) ? null : ts
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateKingdeeSchedule(rows: KingdeeMoTableRow[]): string[] {
|
||||||
|
const errors: string[] = []
|
||||||
|
if (!rows.length) {
|
||||||
|
errors.push('没有可同步的生产订单数据')
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.forEach((mo, moIdx) => {
|
||||||
|
const moLabel = mo.billNo || mo.productionOrderNo || `第 ${moIdx + 1} 条`
|
||||||
|
const moStart = parsePlanTime(mo.planStartTime)
|
||||||
|
const moEnd = parsePlanTime(mo.planFinishTime)
|
||||||
|
if (moStart == null || moEnd == null) {
|
||||||
|
errors.push(`生产订单 ${moLabel}:计划开工/完工时间未填写`)
|
||||||
|
} else if (moEnd <= moStart) {
|
||||||
|
errors.push(`生产订单 ${moLabel}:计划完工须晚于计划开工`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const routes = mo.processRoutes ?? []
|
||||||
|
if (!routes.length) {
|
||||||
|
errors.push(`生产订单 ${moLabel}:没有工序计划`)
|
||||||
|
}
|
||||||
|
|
||||||
|
routes.forEach((route, ri) => {
|
||||||
|
const routeLabel = route.operNumber ?? ri + 1
|
||||||
|
const rStart = parsePlanTime(route.planStartTime)
|
||||||
|
const rEnd = parsePlanTime(route.planFinishTime)
|
||||||
|
if (rStart == null || rEnd == null) {
|
||||||
|
errors.push(`生产订单 ${moLabel} 工序 ${routeLabel}:计划开始/结束时间未填写`)
|
||||||
|
} else if (rEnd <= rStart) {
|
||||||
|
errors.push(`生产订单 ${moLabel} 工序 ${routeLabel}:结束时间须晚于开始时间`)
|
||||||
|
} else if (moStart != null && moEnd != null && (rStart < moStart || rEnd > moEnd)) {
|
||||||
|
errors.push(`生产订单 ${moLabel} 工序 ${routeLabel}:计划时间超出生产订单时间范围`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return errors
|
||||||
|
}
|
||||||
@ -395,8 +395,6 @@ const columns: DataTableColumns<ConcessionApply> = [
|
|||||||
{ title: '审批状态', key: 'approveStatus',align:"center",minWidth:"150" },
|
{ title: '审批状态', key: 'approveStatus',align:"center",minWidth:"150" },
|
||||||
{ title: '审批结果', key: 'approveResult',align:"center",minWidth:"150",
|
{ title: '审批结果', key: 'approveResult',align:"center",minWidth:"150",
|
||||||
render(row:any){
|
render(row:any){
|
||||||
console.log(row.approveResult);
|
|
||||||
|
|
||||||
if(row.approveResult == '待审批') return h(NTag, { type: 'default', size: 'small' }, { default: () => row.approveResult })
|
if(row.approveResult == '待审批') return h(NTag, { type: 'default', size: 'small' }, { default: () => row.approveResult })
|
||||||
if(row.approveResult == '已驳回') return h(NTag, { type: 'error', size: 'small' }, { default: () => row.approveResult })
|
if(row.approveResult == '已驳回') return h(NTag, { type: 'error', size: 'small' }, { default: () => row.approveResult })
|
||||||
if(row.approveResult == '已通过') return h(NTag, { type: 'success', size: 'small' }, { default: () => row.approveResult })
|
if(row.approveResult == '已通过') return h(NTag, { type: 'success', size: 'small' }, { default: () => row.approveResult })
|
||||||
@ -650,25 +648,8 @@ async function loadDictOptions() {
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTimelineType = (type) => {
|
|
||||||
console.log(type);
|
|
||||||
|
|
||||||
const map = {
|
function getApproveResultColor(status:string) {
|
||||||
'START_PROCESS_INSTANCE': 'info', //发起-蓝色
|
|
||||||
'AGREE': 'success',//通过-绿色
|
|
||||||
'REFUSE': 'error',//驳回-红色
|
|
||||||
'TERMINATE_PROCESS_INSTANCE':'warning',//撤回-橙黄
|
|
||||||
'grey':'default', //兼容grey
|
|
||||||
'info':'default'
|
|
||||||
}
|
|
||||||
return map[type] || 'default'
|
|
||||||
}
|
|
||||||
const getTitleAttribute = (item) =>{
|
|
||||||
return item.username +" "+item.operationResult
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getApproveResultColor(status) {
|
|
||||||
switch(status){
|
switch(status){
|
||||||
case '已通过':
|
case '已通过':
|
||||||
return "green"
|
return "green"
|
||||||
@ -679,11 +660,6 @@ function getApproveResultColor(status) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRemark=(item) =>{
|
|
||||||
console.log(item.remark);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async function downloadOutline() {
|
async function downloadOutline() {
|
||||||
const model = detailModel.value
|
const model = detailModel.value
|
||||||
if (!model) {
|
if (!model) {
|
||||||
@ -827,7 +803,7 @@ async function downloadOutline() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function handleCancel(row) {
|
async function handleCancel(row:any) {
|
||||||
|
|
||||||
dialog.warning({
|
dialog.warning({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
|
|||||||
@ -225,8 +225,20 @@
|
|||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
<n-gi>
|
<n-gi>
|
||||||
<n-form-item label="派工接收人" path="assingWorkId">
|
<n-form-item label="派工接收人" path="assingWorkList">
|
||||||
<n-input v-model:value="formData.assingWorkId" placeholder="请输入派工接收人" />
|
<!-- <n-input v-model:value="formData.assingWorkId" placeholder="请选择派工接收人" /> -->
|
||||||
|
<n-select
|
||||||
|
multiple
|
||||||
|
filterable
|
||||||
|
v-model:value="formData.assingWorkList"
|
||||||
|
placeholder="请输入用户名称搜索"
|
||||||
|
:options="yglist"
|
||||||
|
:loading="loadingRef"
|
||||||
|
clearable
|
||||||
|
remote
|
||||||
|
:clear-filter-after-select="false"
|
||||||
|
@search="slehand"
|
||||||
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
</n-grid>
|
</n-grid>
|
||||||
@ -314,17 +326,16 @@
|
|||||||
<n-form-item label="工单名称">
|
<n-form-item label="工单名称">
|
||||||
<n-input v-model:value="dispatchform.name" disabled />
|
<n-input v-model:value="dispatchform.name" disabled />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="指派员工" path="userid">
|
<n-form-item label="指派员工" path="assingWorkList">
|
||||||
<!-- <n-select
|
<!-- <n-select
|
||||||
v-model:value="dispatchform.userid"
|
v-model:value="dispatchform.userid"
|
||||||
:options="yglist"
|
:options="yglist"
|
||||||
clearable
|
clearable
|
||||||
placeholder="请选择员工"
|
placeholder="请选择员工"
|
||||||
/> -->
|
/> -->
|
||||||
|
<!--v-model:value="dispatchform.assingWorkList"-->
|
||||||
<n-select
|
<n-select
|
||||||
v-model:value="dispatchform.userid"
|
multiple
|
||||||
|
|
||||||
filterable
|
filterable
|
||||||
placeholder="请输入用户名称搜索"
|
placeholder="请输入用户名称搜索"
|
||||||
:options="yglist"
|
:options="yglist"
|
||||||
@ -333,6 +344,7 @@
|
|||||||
remote
|
remote
|
||||||
:clear-filter-after-select="false"
|
:clear-filter-after-select="false"
|
||||||
@search="slehand"
|
@search="slehand"
|
||||||
|
@update:value="updatesle"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- <n-input v-model:value="dispatchform.userid" placeholder="请选择员工" /> -->
|
<!-- <n-input v-model:value="dispatchform.userid" placeholder="请选择员工" /> -->
|
||||||
@ -441,13 +453,14 @@ const defaultFormData = {
|
|||||||
projectId: '',
|
projectId: '',
|
||||||
projectName:'',
|
projectName:'',
|
||||||
parentId: '',
|
parentId: '',
|
||||||
sfProduct: '',
|
sfProduct: 0,
|
||||||
procurement: '',
|
procurement: 0,
|
||||||
beginTime: null,
|
beginTime: null,
|
||||||
endTime: null,
|
endTime: null,
|
||||||
actualStartTime: null,
|
actualStartTime: null,
|
||||||
actualEndTime: null,
|
actualEndTime: null,
|
||||||
assingWorkId: '',
|
assingWorkId: '',
|
||||||
|
assingWorkList:[],
|
||||||
assingWorkTime: null,
|
assingWorkTime: null,
|
||||||
assingWorkOperationId: '',
|
assingWorkOperationId: '',
|
||||||
assingWorkOperationTime: null,
|
assingWorkOperationTime: null,
|
||||||
@ -881,6 +894,17 @@ function handleEdit(row: OrderItem) {
|
|||||||
modalVisible.value = true
|
modalVisible.value = true
|
||||||
planList.value.splice(0)
|
planList.value.splice(0)
|
||||||
Object.assign(formData, row)
|
Object.assign(formData, row)
|
||||||
|
if(formData.sfProduct == null){
|
||||||
|
formData.sfProduct = 0
|
||||||
|
}
|
||||||
|
if(formData.procurement == null){
|
||||||
|
formData.procurement = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if(formData.assingWorkId){
|
||||||
|
formData.assingWorkList = formData.assingWorkId.split(',')
|
||||||
|
}
|
||||||
|
|
||||||
if (formData.beginTime && typeof formData.beginTime === 'string') {
|
if (formData.beginTime && typeof formData.beginTime === 'string') {
|
||||||
formData.beginTime = new Date(formData.beginTime.replace(' ', 'T')).getTime()
|
formData.beginTime = new Date(formData.beginTime.replace(' ', 'T')).getTime()
|
||||||
}
|
}
|
||||||
@ -903,6 +927,8 @@ function handleEdit(row: OrderItem) {
|
|||||||
formData.assingWorkOperationTime = new Date(formData.assingWorkOperationTime.replace(' ', 'T')).getTime()
|
formData.assingWorkOperationTime = new Date(formData.assingWorkOperationTime.replace(' ', 'T')).getTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
slehand()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交
|
// 提交
|
||||||
@ -961,15 +987,22 @@ let dispatchmodal = ref(false)
|
|||||||
let dispatchform = reactive<any>({
|
let dispatchform = reactive<any>({
|
||||||
id:'',
|
id:'',
|
||||||
name:'',
|
name:'',
|
||||||
userid:'',
|
//userid:'',
|
||||||
username:''
|
username:'',
|
||||||
|
assingWorkList:''
|
||||||
})
|
})
|
||||||
|
|
||||||
let yglist = ref<any>([])
|
let yglist = ref<any>([])
|
||||||
|
|
||||||
const dispatchrules = {
|
const dispatchrules = {
|
||||||
userid:[{ required: true, message: '请选择员工', trigger: 'blur' }]
|
assingWorkList:[{ required: true, message: '请选择员工', trigger: 'blur' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updatesle(v:any) {
|
||||||
|
dispatchform.assingWorkList = v.join()
|
||||||
|
console.log(v)
|
||||||
|
}
|
||||||
|
|
||||||
let dispatchLoading = ref(false)
|
let dispatchLoading = ref(false)
|
||||||
let dispatchddbtn = ref(false)
|
let dispatchddbtn = ref(false)
|
||||||
|
|
||||||
@ -981,7 +1014,7 @@ function slehand(v?:any) {
|
|||||||
loadingRef.value = true
|
loadingRef.value = true
|
||||||
userApi.page({
|
userApi.page({
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 100,
|
pageSize: 200,
|
||||||
username: v,
|
username: v,
|
||||||
}).then((rps:any) =>{
|
}).then((rps:any) =>{
|
||||||
if(rps.list && rps.list.length > 0){
|
if(rps.list && rps.list.length > 0){
|
||||||
@ -1004,12 +1037,13 @@ function disphand(v:any){
|
|||||||
dispatchformRef.value?.restoreValidation()
|
dispatchformRef.value?.restoreValidation()
|
||||||
dispatchform.id = v.id
|
dispatchform.id = v.id
|
||||||
dispatchform.name = v.name
|
dispatchform.name = v.name
|
||||||
dispatchform.userid = ''
|
//dispatchform.userid = ''
|
||||||
|
dispatchform.assingWorkList = ''
|
||||||
slehand()
|
slehand()
|
||||||
}
|
}
|
||||||
|
|
||||||
function dispatchSubmit() {
|
function dispatchSubmit() {
|
||||||
console.log(dispatchform.userid,'dispatchform.userid')
|
|
||||||
dispatchformRef.value?.validate((v:any) => {
|
dispatchformRef.value?.validate((v:any) => {
|
||||||
if(!v){
|
if(!v){
|
||||||
// dispatchLoading.value = true
|
// dispatchLoading.value = true
|
||||||
@ -1022,7 +1056,7 @@ function dispatchSubmit() {
|
|||||||
// dispatchddbtn.value = false
|
// dispatchddbtn.value = false
|
||||||
// loadData()
|
// loadData()
|
||||||
// },1000)
|
// },1000)
|
||||||
orderItemApi.assignWork(dispatchform).then(() => {
|
orderItemApi.assignWork(Object.assign({},dispatchform,{assingWorkList:dispatchform.assingWorkList.split(',')}) ).then(() => {
|
||||||
message.success('操作成功!')
|
message.success('操作成功!')
|
||||||
setTimeout(()=> {
|
setTimeout(()=> {
|
||||||
dispatchmodal.value = false
|
dispatchmodal.value = false
|
||||||
|
|||||||
847
src/views/biz/orderProcessPlan/board.vue
Normal file
847
src/views/biz/orderProcessPlan/board.vue
Normal file
@ -0,0 +1,847 @@
|
|||||||
|
<template>
|
||||||
|
<div class="plan-board-page">
|
||||||
|
<!-- 顶部工具栏 -->
|
||||||
|
<div class="board-toolbar">
|
||||||
|
<n-space :size="8" align="center">
|
||||||
|
<n-button type="primary" size="small" @click="handleAdd">
|
||||||
|
<template #icon><n-icon><AddOutline /></n-icon></template>
|
||||||
|
新增工序
|
||||||
|
</n-button>
|
||||||
|
<n-button size="small" type="success" ghost disabled title="待后端接口支持">
|
||||||
|
<template #icon><n-icon><CheckmarkCircleOutline /></n-icon></template>
|
||||||
|
齐套自动下达
|
||||||
|
</n-button>
|
||||||
|
<n-button size="small" type="error" ghost disabled title="待后端接口支持">批量删除</n-button>
|
||||||
|
<n-button size="small" type="primary" ghost disabled title="待后端接口支持">批量下达</n-button>
|
||||||
|
<n-button size="small" quaternary disabled title="待后端接口支持">合并为组工单</n-button>
|
||||||
|
<n-dropdown :options="batchOptions" trigger="click" @select="onBatchSelect">
|
||||||
|
<n-button size="small" quaternary>
|
||||||
|
批量操作
|
||||||
|
<n-icon style="margin-left: 4px"><ChevronDownOutline /></n-icon>
|
||||||
|
</n-button>
|
||||||
|
</n-dropdown>
|
||||||
|
</n-space>
|
||||||
|
<n-space :size="8" align="center">
|
||||||
|
<n-button size="small" quaternary @click="importModalVisible = true">
|
||||||
|
<template #icon><n-icon><CloudUploadOutline /></n-icon></template>
|
||||||
|
导入
|
||||||
|
</n-button>
|
||||||
|
<n-button size="small" quaternary @click="handleExport">
|
||||||
|
<template #icon><n-icon><DownloadOutline /></n-icon></template>
|
||||||
|
导出
|
||||||
|
</n-button>
|
||||||
|
<n-button size="small" quaternary @click="loadData">
|
||||||
|
<template #icon><n-icon><RefreshOutline /></n-icon></template>
|
||||||
|
同步
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 筛选 -->
|
||||||
|
<div class="board-filter">
|
||||||
|
<n-form inline :model="searchForm" label-placement="left" size="small">
|
||||||
|
<n-form-item label="工序名称">
|
||||||
|
<n-input v-model:value="searchForm.name" placeholder="工序名称" clearable style="width: 140px" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="工序编码">
|
||||||
|
<n-input v-model:value="searchForm.code" placeholder="工序编码" clearable style="width: 140px" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item>
|
||||||
|
<n-space>
|
||||||
|
<n-button type="primary" size="small" @click="handleSearch">
|
||||||
|
<template #icon><n-icon><SearchOutline /></n-icon></template>
|
||||||
|
搜索
|
||||||
|
</n-button>
|
||||||
|
<n-button size="small" @click="handleReset">重置</n-button>
|
||||||
|
</n-space>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="board-table-wrap">
|
||||||
|
<!-- 表头 -->
|
||||||
|
<div class="board-table-head">
|
||||||
|
<div class="col-check" />
|
||||||
|
<div class="col-expand" />
|
||||||
|
<div class="col-product">产品 / 工单编号</div>
|
||||||
|
<div class="col-qty">数量</div>
|
||||||
|
<div class="col-mode">模式</div>
|
||||||
|
<div class="col-kit">齐套率</div>
|
||||||
|
<div class="col-flow">工序</div>
|
||||||
|
<div class="col-plan">计划开始 / 结束</div>
|
||||||
|
<div class="col-life">生命周期</div>
|
||||||
|
<div class="col-actions">操作</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<n-spin :show="loading">
|
||||||
|
<div v-if="!tableData.length && !loading" class="board-empty">
|
||||||
|
<n-empty description="暂无工序计划数据" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-for="order in tableData" :key="orderRowKey(order)" class="order-block">
|
||||||
|
<div class="order-row" :class="{ expanded: isExpanded(order) }">
|
||||||
|
<div class="col-check">
|
||||||
|
<n-checkbox
|
||||||
|
:checked="selectedOrderIds.includes(order.orderItemId!)"
|
||||||
|
@update:checked="(v: boolean) => toggleSelect(order.orderItemId!, v)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-expand">
|
||||||
|
<n-button
|
||||||
|
quaternary
|
||||||
|
circle
|
||||||
|
size="tiny"
|
||||||
|
@click="toggleExpand(order)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<RemoveOutline v-if="isExpanded(order)" />
|
||||||
|
<AddOutline v-else />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
<div class="col-product">
|
||||||
|
<div class="product-name">{{ order.materialName || '-' }}</div>
|
||||||
|
<div class="product-code">
|
||||||
|
<span>{{ order.orderCode || order.mainCode || '-' }}</span>
|
||||||
|
<n-tag v-if="isOverdue(order)" size="tiny" type="error" :bordered="false">逾期</n-tag>
|
||||||
|
<n-tag v-if="hasOutsource(order)" size="tiny" type="warning" :bordered="false">委外</n-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-qty">{{ order.quantity ?? '-' }}</div>
|
||||||
|
<div class="col-mode">
|
||||||
|
<n-tag v-if="order.routeCode" size="tiny" :bordered="false">工艺型</n-tag>
|
||||||
|
<n-tag v-if="order.proWorkshop" size="tiny" type="info" :bordered="false">{{ order.proWorkshop }}</n-tag>
|
||||||
|
</div>
|
||||||
|
<div class="col-kit">-</div>
|
||||||
|
<div class="col-flow">
|
||||||
|
<ProcessFlowStepper :list="order.processList" />
|
||||||
|
</div>
|
||||||
|
<div class="col-plan">
|
||||||
|
<div>{{ formatShortTime(order.beginTime) }}</div>
|
||||||
|
<div class="sub-text">{{ formatShortTime(order.endTime) }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-life">
|
||||||
|
<div class="life-cell">
|
||||||
|
<n-icon :color="lifeIcon(order).color" size="18">
|
||||||
|
<component :is="lifeIcon(order).icon" />
|
||||||
|
</n-icon>
|
||||||
|
<span>{{ lifeLabel(order) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-actions">
|
||||||
|
<n-button text type="primary" size="tiny" @click="openDetail(order)">详情</n-button>
|
||||||
|
<n-button text type="primary" size="tiny" disabled title="订单级编辑待支持">编辑</n-button>
|
||||||
|
<n-dropdown :options="moreOptions" trigger="click" @select="(k) => onMoreSelect(k, order)">
|
||||||
|
<n-button text size="tiny">更多</n-button>
|
||||||
|
</n-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="isExpanded(order)" class="order-expand">
|
||||||
|
<div v-if="!(order.processList?.length)" class="expand-empty">暂无工序明细</div>
|
||||||
|
<div v-else class="card-flow-scroll">
|
||||||
|
<ProcessCard
|
||||||
|
v-for="(proc, idx) in sortedProcessList(order.processList)"
|
||||||
|
:key="proc.planId ?? idx"
|
||||||
|
:process="proc"
|
||||||
|
:is-last="idx === (order.processList?.length ?? 0) - 1"
|
||||||
|
:can-edit="hasPermission('biz:orderItem:edit')"
|
||||||
|
:can-assign="hasPermission('biz:orderItem:assign')"
|
||||||
|
@edit="(p) => handleEdit(p, order)"
|
||||||
|
@assign="(p) => disphand(p, order)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-spin>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="board-pagination">
|
||||||
|
<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-modal v-model:show="detailVisible" preset="card" title="工序计划详情" style="width: 900px">
|
||||||
|
<n-spin :show="detailLoading">
|
||||||
|
<template v-if="detailData">
|
||||||
|
<n-descriptions :column="3" bordered size="small" label-placement="left">
|
||||||
|
<n-descriptions-item label="生产令号">{{ detailData.mainCode || '-' }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="订单编码">{{ detailData.orderCode || '-' }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="物料">{{ detailData.materialName || '-' }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="数量">{{ detailData.quantity ?? '-' }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="工艺路线">{{ detailData.routeCode || '-' }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="车间">{{ detailData.proWorkshop || '-' }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="计划开工">{{ detailData.beginTime || '-' }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="计划完工">{{ detailData.endTime || '-' }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="订单状态">{{ lifeLabel(detailData) }}</n-descriptions-item>
|
||||||
|
</n-descriptions>
|
||||||
|
<div class="detail-cards">
|
||||||
|
<ProcessCard
|
||||||
|
v-for="(proc, idx) in sortedProcessList(detailData.processList)"
|
||||||
|
:key="proc.planId ?? idx"
|
||||||
|
:process="proc"
|
||||||
|
:is-last="idx === (detailData.processList?.length ?? 0) - 1"
|
||||||
|
:can-edit="hasPermission('biz:orderItem:edit')"
|
||||||
|
:can-assign="hasPermission('biz:orderItem:assign')"
|
||||||
|
@edit="(p) => handleEdit(p, detailData!)"
|
||||||
|
@assign="(p) => disphand(p, detailData!)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-spin>
|
||||||
|
</n-modal>
|
||||||
|
|
||||||
|
<!-- 新增/编辑 -->
|
||||||
|
<n-modal v-model:show="modalVisible" preset="card" :title="modalTitle" style="width: 800px">
|
||||||
|
<n-form ref="formRef" :model="formData" :rules="formRules" label-placement="left" label-width="120px">
|
||||||
|
<n-grid :cols="2">
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="工序名称" path="name">
|
||||||
|
<n-input v-model:value="formData.name" placeholder="请输入工序名称" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="工序编码" path="code">
|
||||||
|
<n-input v-model:value="formData.code" placeholder="请输入工序编码" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="工序顺序" path="sort">
|
||||||
|
<n-input v-model:value="formData.sort" placeholder="请输入工序顺序" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="生产订单id" path="orderItemId">
|
||||||
|
<n-input v-model:value="formData.orderItemId" placeholder="请输入生产订单id" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="生产开始时间" path="beginTime">
|
||||||
|
<n-date-picker v-model:value="formData.beginTime" type="datetime" clearable style="width: 100%" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="生产结束时间" path="endTime">
|
||||||
|
<n-date-picker v-model:value="formData.endTime" type="datetime" clearable style="width: 100%" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="生产总数" path="quantity">
|
||||||
|
<n-input v-model:value="formData.quantity" placeholder="请输入生产总数" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="转入数量" path="transferNum">
|
||||||
|
<n-input v-model:value="formData.transferNum" placeholder="请输入转入数量" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-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="dispatchmodal" title="派工" preset="card" style="width: 800px" :mask-closable="false">
|
||||||
|
<n-form ref="dispatchformRef" :model="dispatchform" label-placement="left" :rules="dispatchrules" label-width="80">
|
||||||
|
<n-grid :cols="2">
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="工序名称">
|
||||||
|
<n-input v-model:value="dispatchform.name" disabled />
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="生产数量">
|
||||||
|
<n-input v-model:value="dispatchform.quantity" disabled />
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="开始时间">
|
||||||
|
<n-input v-model:value="dispatchform.beginTime" disabled />
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="结束时间">
|
||||||
|
<n-input v-model:value="dispatchform.endTime" disabled />
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi :span="24">
|
||||||
|
<Pgitem
|
||||||
|
v-for="(n, i) in dispatchform.list"
|
||||||
|
:key="i"
|
||||||
|
:obj="n"
|
||||||
|
:index="i"
|
||||||
|
listname="list"
|
||||||
|
@addhand="addpgnum"
|
||||||
|
@delehand="deletenum(i)"
|
||||||
|
/>
|
||||||
|
</n-gi>
|
||||||
|
</n-grid>
|
||||||
|
</n-form>
|
||||||
|
<template #footer>
|
||||||
|
<n-space justify="end">
|
||||||
|
<n-button size="small" @click="dispatchmodal = false">取消</n-button>
|
||||||
|
<n-button size="small" type="primary" :loading="dispatchLoading" @click="dispatchSubmit">确定</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>
|
||||||
|
请先下载模板,按格式填写后上传 .xlsx / .xls 文件。
|
||||||
|
</n-alert>
|
||||||
|
<n-button type="primary" @click="handleDownloadTemplate">
|
||||||
|
<template #icon><n-icon><DownloadOutline /></n-icon></template>
|
||||||
|
下载模板
|
||||||
|
</n-button>
|
||||||
|
<n-upload :max="1" accept=".xlsx,.xls" :custom-request="handleImportUpload">
|
||||||
|
<n-upload-dragger>
|
||||||
|
<n-text>点击或拖拽文件到此处上传</n-text>
|
||||||
|
</n-upload-dragger>
|
||||||
|
</n-upload>
|
||||||
|
</n-space>
|
||||||
|
</n-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import {
|
||||||
|
NButton, NSpace, NIcon, NTag, NSpin, NModal, NForm, NFormItem, NGrid, NGi,
|
||||||
|
NInput, NDatePicker, NCheckbox, NEmpty, NPagination, NDropdown, NDescriptions,
|
||||||
|
NDescriptionsItem, NAlert, NUpload, NText, useMessage, useDialog,
|
||||||
|
type UploadCustomRequestOptions,
|
||||||
|
} from 'naive-ui'
|
||||||
|
import {
|
||||||
|
AddOutline, RemoveOutline, SearchOutline, RefreshOutline, CloudUploadOutline,
|
||||||
|
DownloadOutline, ChevronDownOutline, CheckmarkCircleOutline, CheckmarkCircle,
|
||||||
|
EllipseOutline, TimeOutline,
|
||||||
|
} from '@vicons/ionicons5'
|
||||||
|
import {
|
||||||
|
orderProcessPlanApi,
|
||||||
|
toOrderProcessPlanPayload,
|
||||||
|
processItemToEntity,
|
||||||
|
ORDER_STATUS_MAP,
|
||||||
|
type OrderProcessPlanVO,
|
||||||
|
type ProcessPlanItemVO,
|
||||||
|
} from '@/api/orderProcessPlan'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import ProcessFlowStepper from './components/ProcessFlowStepper.vue'
|
||||||
|
import ProcessCard from './components/ProcessCard.vue'
|
||||||
|
import Pgitem from './pgitem.vue'
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const dialog = useDialog()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const hasPermission = (p: string) => userStore.hasPermission(p)
|
||||||
|
|
||||||
|
const searchForm = reactive({
|
||||||
|
name: '',
|
||||||
|
code: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const tableData = ref<OrderProcessPlanVO[]>([])
|
||||||
|
const expandedIds = ref<Set<number>>(new Set())
|
||||||
|
const selectedOrderIds = ref<number[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const pagination = reactive({ page: 1, pageSize: 10, itemCount: 0 })
|
||||||
|
|
||||||
|
const detailVisible = ref(false)
|
||||||
|
const detailLoading = ref(false)
|
||||||
|
const detailData = ref<OrderProcessPlanVO | null>(null)
|
||||||
|
|
||||||
|
const modalVisible = ref(false)
|
||||||
|
const modalTitle = ref('')
|
||||||
|
const formRef = ref()
|
||||||
|
const defaultFormData = {
|
||||||
|
name: '', code: '', sort: '', orderItemId: '',
|
||||||
|
beginTime: null as number | null, endTime: null as number | null,
|
||||||
|
quantity: '', transferNum: '', transferOutNum: '',
|
||||||
|
procurement: '', outsource: '', qualityInspection: '', storageEntry: '',
|
||||||
|
}
|
||||||
|
const formData = reactive<any>({ ...defaultFormData })
|
||||||
|
const formRules = {}
|
||||||
|
|
||||||
|
const dispatchmodal = ref(false)
|
||||||
|
const dispatchLoading = ref(false)
|
||||||
|
const dispatchformRef = ref()
|
||||||
|
const dispatchform = reactive<any>({
|
||||||
|
id: '', name: '', beginTime: '', endTime: '', quantity: '',
|
||||||
|
list: [{ userId: '', quantity: '' }],
|
||||||
|
})
|
||||||
|
const dispatchrules = {}
|
||||||
|
|
||||||
|
const importModalVisible = ref(false)
|
||||||
|
|
||||||
|
const batchOptions = [
|
||||||
|
{ label: '全部展开', key: 'expandAll' },
|
||||||
|
{ label: '全部收起', key: 'collapseAll' },
|
||||||
|
]
|
||||||
|
const moreOptions = [
|
||||||
|
{ label: '展开工序', key: 'expand' },
|
||||||
|
{ label: '收起工序', key: 'collapse' },
|
||||||
|
]
|
||||||
|
|
||||||
|
function orderRowKey(row: OrderProcessPlanVO) {
|
||||||
|
return row.orderItemId ?? `order-${row.orderCode}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortedProcessList(list?: ProcessPlanItemVO[]) {
|
||||||
|
return [...(list ?? [])].sort((a, b) => (a.operNumber ?? 0) - (b.operNumber ?? 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
function isExpanded(order: OrderProcessPlanVO) {
|
||||||
|
return order.orderItemId != null && expandedIds.value.has(order.orderItemId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleExpand(order: OrderProcessPlanVO) {
|
||||||
|
if (order.orderItemId == null) return
|
||||||
|
const next = new Set(expandedIds.value)
|
||||||
|
if (next.has(order.orderItemId)) next.delete(order.orderItemId)
|
||||||
|
else next.add(order.orderItemId)
|
||||||
|
expandedIds.value = next
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSelect(id: number, checked: boolean) {
|
||||||
|
if (checked) selectedOrderIds.value = [...selectedOrderIds.value, id]
|
||||||
|
else selectedOrderIds.value = selectedOrderIds.value.filter(v => v !== id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatShortTime(val?: string | null) {
|
||||||
|
if (!val) return '-'
|
||||||
|
return val.length > 16 ? val.slice(0, 16) : val
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOverdue(order: OrderProcessPlanVO) {
|
||||||
|
if (order.orderStatus === 2 || !order.endTime) return false
|
||||||
|
return new Date(order.endTime.replace(' ', 'T')).getTime() < Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasOutsource(order: OrderProcessPlanVO) {
|
||||||
|
return order.processList?.some(p => p.outsource === 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function lifeLabel(order: OrderProcessPlanVO) {
|
||||||
|
const s = order.orderStatus
|
||||||
|
if (s == null) return '-'
|
||||||
|
return ORDER_STATUS_MAP[s]?.label ?? String(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
function lifeIcon(order: OrderProcessPlanVO) {
|
||||||
|
const s = order.orderStatus
|
||||||
|
if (s === 2) return { icon: CheckmarkCircle, color: '#18a058' }
|
||||||
|
if (s === 1) return { icon: TimeOutline, color: '#2080f0' }
|
||||||
|
return { icon: EllipseOutline, color: '#8c8c8c' }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await orderProcessPlanApi.page({
|
||||||
|
page: pagination.page,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
name: searchForm.name || undefined,
|
||||||
|
code: searchForm.code || undefined,
|
||||||
|
})
|
||||||
|
tableData.value = res.list ?? []
|
||||||
|
pagination.itemCount = res.total
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSearch() {
|
||||||
|
pagination.page = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleReset() {
|
||||||
|
searchForm.name = ''
|
||||||
|
searchForm.code = ''
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePageChange(page: number) {
|
||||||
|
pagination.page = page
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePageSizeChange(pageSize: number) {
|
||||||
|
pagination.pageSize = pageSize
|
||||||
|
pagination.page = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBatchSelect(key: string) {
|
||||||
|
if (key === 'expandAll') {
|
||||||
|
expandedIds.value = new Set(tableData.value.map(o => o.orderItemId!).filter(Boolean))
|
||||||
|
} else if (key === 'collapseAll') {
|
||||||
|
expandedIds.value = new Set()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMoreSelect(key: string, order: OrderProcessPlanVO) {
|
||||||
|
if (key === 'expand') toggleExpand(order)
|
||||||
|
if (key === 'collapse' && isExpanded(order)) toggleExpand(order)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openDetail(order: OrderProcessPlanVO) {
|
||||||
|
if (!order.orderItemId) return
|
||||||
|
detailVisible.value = true
|
||||||
|
detailLoading.value = true
|
||||||
|
detailData.value = null
|
||||||
|
try {
|
||||||
|
detailData.value = await orderProcessPlanApi.detail(order.orderItemId)
|
||||||
|
} catch {
|
||||||
|
detailData.value = order
|
||||||
|
} finally {
|
||||||
|
detailLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAdd() {
|
||||||
|
modalTitle.value = '新增工序计划'
|
||||||
|
Object.assign(formData, defaultFormData)
|
||||||
|
modalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDateTime(val: string | null | undefined) {
|
||||||
|
if (!val) return null
|
||||||
|
const ts = new Date(val.replace(' ', 'T')).getTime()
|
||||||
|
return Number.isNaN(ts) ? null : ts
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEdit(process: ProcessPlanItemVO, order?: OrderProcessPlanVO) {
|
||||||
|
modalTitle.value = '编辑工序计划'
|
||||||
|
const entity = processItemToEntity(process, order?.orderItemId)
|
||||||
|
Object.assign(formData, {
|
||||||
|
...defaultFormData,
|
||||||
|
...entity,
|
||||||
|
sort: entity.sort != null ? String(entity.sort) : '',
|
||||||
|
orderItemId: entity.orderItemId != null ? String(entity.orderItemId) : '',
|
||||||
|
quantity: entity.quantity != null ? String(entity.quantity) : '',
|
||||||
|
transferNum: entity.transferNum != null ? String(entity.transferNum) : '',
|
||||||
|
beginTime: parseDateTime(entity.beginTime),
|
||||||
|
endTime: parseDateTime(entity.endTime),
|
||||||
|
})
|
||||||
|
modalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
const submitData = toOrderProcessPlanPayload({ ...formData })
|
||||||
|
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 (submitData.id) {
|
||||||
|
await orderProcessPlanApi.update(submitData)
|
||||||
|
message.success('修改成功')
|
||||||
|
} else {
|
||||||
|
await orderProcessPlanApi.create(submitData)
|
||||||
|
message.success('新增成功')
|
||||||
|
}
|
||||||
|
modalVisible.value = false
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
function disphand(process: ProcessPlanItemVO, order?: OrderProcessPlanVO) {
|
||||||
|
const entity = processItemToEntity(process, order?.orderItemId)
|
||||||
|
dispatchmodal.value = true
|
||||||
|
dispatchformRef.value?.restoreValidation()
|
||||||
|
dispatchform.id = entity.id
|
||||||
|
dispatchform.name = entity.name
|
||||||
|
dispatchform.beginTime = entity.beginTime
|
||||||
|
dispatchform.endTime = entity.endTime
|
||||||
|
dispatchform.quantity = entity.quantity
|
||||||
|
dispatchform.list = [{ userId: '', quantity: '' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
function addpgnum() {
|
||||||
|
dispatchform.list.push({ userId: '', quantity: '' })
|
||||||
|
}
|
||||||
|
|
||||||
|
function deletenum(index: number) {
|
||||||
|
dispatchform.list.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function dispatchSubmit() {
|
||||||
|
let total = 0
|
||||||
|
dispatchform.list.forEach((n: any) => { total += Number(n.quantity) || 0 })
|
||||||
|
dispatchformRef.value?.validate((v: boolean) => {
|
||||||
|
if (v) return
|
||||||
|
if (total !== Number(dispatchform.quantity)) {
|
||||||
|
message.error('派工总数量需等于生产总数', { duration: 4000 })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dispatchLoading.value = true
|
||||||
|
orderProcessPlanApi.assignWork(dispatchform).then(() => {
|
||||||
|
message.success('派工成功')
|
||||||
|
dispatchmodal.value = false
|
||||||
|
loadData()
|
||||||
|
}).finally(() => { dispatchLoading.value = false })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleExport() {
|
||||||
|
const blob = await orderProcessPlanApi.export({
|
||||||
|
name: searchForm.name || undefined,
|
||||||
|
code: searchForm.code || undefined,
|
||||||
|
})
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = '工序计划数据.xlsx'
|
||||||
|
link.click()
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDownloadTemplate() {
|
||||||
|
const blob = await orderProcessPlanApi.downloadTemplate()
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = '工序计划导入模板.xlsx'
|
||||||
|
link.click()
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleImportUpload({ file }: UploadCustomRequestOptions) {
|
||||||
|
if (!file.file) return
|
||||||
|
const result = await orderProcessPlanApi.importData(file.file)
|
||||||
|
if (result.fail > 0) {
|
||||||
|
dialog.warning({
|
||||||
|
title: '导入结果',
|
||||||
|
content: `成功 ${result.success} 条,失败 ${result.fail} 条`,
|
||||||
|
positiveText: '确定',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
message.success(`导入成功,共 ${result.success} 条`)
|
||||||
|
importModalVisible.value = false
|
||||||
|
}
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(loadData)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.plan-board-page {
|
||||||
|
padding: 0;
|
||||||
|
background: #f5f7fa;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
border-bottom: 1px solid #eef0f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-filter {
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid #eef0f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-table-wrap {
|
||||||
|
overflow-x: auto;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-table-head,
|
||||||
|
.order-row {
|
||||||
|
display: grid;
|
||||||
|
/* 勾选 | 展开 | 产品工单 | 数量 | 模式 | 齐套率 | 工序轴 | 计划时间 | 生命周期 | 操作 */
|
||||||
|
grid-template-columns:
|
||||||
|
40px
|
||||||
|
40px
|
||||||
|
minmax(220px, 1.6fr)
|
||||||
|
64px
|
||||||
|
108px
|
||||||
|
72px
|
||||||
|
minmax(200px, 1.4fr)
|
||||||
|
168px
|
||||||
|
96px
|
||||||
|
140px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 0 16px;
|
||||||
|
min-width: 1320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-table-head {
|
||||||
|
height: 40px;
|
||||||
|
background: #fafbfc;
|
||||||
|
border-bottom: 1px solid #e8eaed;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #8c8c8c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-block {
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid #eef0f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-row {
|
||||||
|
min-height: 64px;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-row:hover {
|
||||||
|
background: #f8faff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-row.expanded {
|
||||||
|
background: rgba(32, 128, 240, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-check,
|
||||||
|
.col-expand {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-product {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-qty,
|
||||||
|
.col-kit {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 13px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1a1a1a;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-code {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #8c8c8c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-mode {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-plan {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-plan .sub-text {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #8c8c8c;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-life {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-flow {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.life-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-expand {
|
||||||
|
padding: 12px 16px 16px 68px;
|
||||||
|
background: linear-gradient(180deg, rgba(32, 128, 240, 0.03) 0%, #fff 100%);
|
||||||
|
border-top: 1px dashed #e8eaed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-flow-scroll,
|
||||||
|
.detail-cards {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-cards {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-empty {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #8c8c8c;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-empty {
|
||||||
|
padding: 48px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
353
src/views/biz/orderProcessPlan/components/ProcessCard.vue
Normal file
353
src/views/biz/orderProcessPlan/components/ProcessCard.vue
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
<template>
|
||||||
|
<div class="process-card-wrap">
|
||||||
|
<div class="process-card" :class="themeClass">
|
||||||
|
<div class="card-head">
|
||||||
|
<span class="card-title">{{ process.processName || '-' }}</span>
|
||||||
|
<n-tag size="small" :bordered="false" :type="statusTag.type">{{ statusTag.label }}</n-tag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-progress">
|
||||||
|
<n-progress
|
||||||
|
type="circle"
|
||||||
|
:percentage="progressPct"
|
||||||
|
:stroke-width="10"
|
||||||
|
:color="progressColor"
|
||||||
|
:rail-color="railColor"
|
||||||
|
style="width: 88px"
|
||||||
|
>
|
||||||
|
<span class="progress-text">{{ progressPct }}%</span>
|
||||||
|
</n-progress>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-stats">
|
||||||
|
<div class="stat-row">
|
||||||
|
<span class="stat-label">完成</span>
|
||||||
|
<span class="stat-value">{{ completedQty }} / {{ totalQty }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-row">
|
||||||
|
<span class="stat-label">合格 / 不合格</span>
|
||||||
|
<span class="stat-value">
|
||||||
|
<em class="ok">{{ qualifiedQty }}</em>
|
||||||
|
<span class="sep">/</span>
|
||||||
|
<em class="bad">{{ scrapQty }}</em>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-row">
|
||||||
|
<span class="stat-label">合格率</span>
|
||||||
|
<span class="stat-value highlight">{{ passRate }}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-flow">
|
||||||
|
<div class="flow-item">
|
||||||
|
<span>剩余物料</span>
|
||||||
|
<strong>{{ remainQty }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="flow-item">
|
||||||
|
<span>转下道</span>
|
||||||
|
<strong>{{ process.transferOutQty ?? 0 }}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-meta">
|
||||||
|
<div class="meta-line"><span>人员</span><em>{{ personnel }}</em></div>
|
||||||
|
<div class="meta-line"><span>车间</span><em>{{ process.departmentName || '-' }}</em></div>
|
||||||
|
<div class="meta-line"><span>工位</span><em>{{ process.workCenterName || '-' }}</em></div>
|
||||||
|
<div class="meta-line"><span>设备</span><em>-</em></div>
|
||||||
|
<div class="meta-line"><span>计划时间</span><em>{{ planTimeText }}</em></div>
|
||||||
|
<div class="meta-line"><span>实际开始</span><em>{{ process.actualStartTime || '-' }}</em></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-actions">
|
||||||
|
<n-button
|
||||||
|
v-if="canAssign"
|
||||||
|
size="tiny"
|
||||||
|
type="warning"
|
||||||
|
ghost
|
||||||
|
@click="emit('assign', process)"
|
||||||
|
>派工</n-button>
|
||||||
|
<n-button
|
||||||
|
v-if="canEdit"
|
||||||
|
size="tiny"
|
||||||
|
ghost
|
||||||
|
@click="emit('edit', process)"
|
||||||
|
>编辑</n-button>
|
||||||
|
<n-button size="tiny" quaternary disabled>返工</n-button>
|
||||||
|
<n-button
|
||||||
|
size="tiny"
|
||||||
|
:type="process.processStatus === 4 ? 'success' : 'default'"
|
||||||
|
:ghost="process.processStatus !== 4"
|
||||||
|
disabled
|
||||||
|
>{{ process.processStatus === 4 ? '已完成' : '进行中' }}</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!isLast" class="card-chevron">
|
||||||
|
<n-icon size="28" color="#c0c4cc"><ChevronForwardOutline /></n-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { NButton, NIcon, NProgress, NTag } from 'naive-ui'
|
||||||
|
import { ChevronForwardOutline } from '@vicons/ionicons5'
|
||||||
|
import { PROCESS_STATUS_MAP, type ProcessPlanItemVO } from '@/api/orderProcessPlan'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
process: ProcessPlanItemVO
|
||||||
|
isLast?: boolean
|
||||||
|
canEdit?: boolean
|
||||||
|
canAssign?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
edit: [process: ProcessPlanItemVO]
|
||||||
|
assign: [process: ProcessPlanItemVO]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const totalQty = computed(() => props.process.quantity ?? 0)
|
||||||
|
const completedQty = computed(() => props.process.completedQty ?? 0)
|
||||||
|
const scrapQty = computed(() => props.process.scrapQty ?? 0)
|
||||||
|
const qualifiedQty = computed(() => Math.max(0, completedQty.value - scrapQty.value))
|
||||||
|
|
||||||
|
const progressPct = computed(() => {
|
||||||
|
if (!totalQty.value) return 0
|
||||||
|
return Math.min(100, Math.round((completedQty.value / totalQty.value) * 100))
|
||||||
|
})
|
||||||
|
|
||||||
|
const passRate = computed(() => {
|
||||||
|
if (!completedQty.value) return progressPct.value
|
||||||
|
return Math.round((qualifiedQty.value / completedQty.value) * 100)
|
||||||
|
})
|
||||||
|
|
||||||
|
const remainQty = computed(() => Math.max(0, totalQty.value - completedQty.value))
|
||||||
|
|
||||||
|
const statusTag = computed(() => {
|
||||||
|
const s = props.process.processStatus
|
||||||
|
if (s == null) return { label: '-', type: 'default' as const }
|
||||||
|
const m = PROCESS_STATUS_MAP[s]
|
||||||
|
return m ? { label: m.label, type: m.type } : { label: String(s), type: 'default' as const }
|
||||||
|
})
|
||||||
|
|
||||||
|
const themeClass = computed(() => {
|
||||||
|
const s = props.process.processStatus
|
||||||
|
if (s === 4) return 'theme-done'
|
||||||
|
if (s != null && s >= 2) return 'theme-active'
|
||||||
|
return 'theme-pending'
|
||||||
|
})
|
||||||
|
|
||||||
|
const progressColor = computed(() => {
|
||||||
|
if (themeClass.value === 'theme-done') return '#18a058'
|
||||||
|
if (themeClass.value === 'theme-active') return '#2080f0'
|
||||||
|
return '#d9d9d9'
|
||||||
|
})
|
||||||
|
|
||||||
|
const railColor = computed(() => {
|
||||||
|
if (themeClass.value === 'theme-done') return 'rgba(24,160,88,0.15)'
|
||||||
|
if (themeClass.value === 'theme-active') return 'rgba(32,128,240,0.12)'
|
||||||
|
return '#f0f0f0'
|
||||||
|
})
|
||||||
|
|
||||||
|
const personnel = computed(() => {
|
||||||
|
if (props.process.assignByName) return props.process.assignByName
|
||||||
|
const list = props.process.assignWorkList
|
||||||
|
if (list?.length) return `${list.length} 人派工`
|
||||||
|
return '-'
|
||||||
|
})
|
||||||
|
|
||||||
|
const planTimeText = computed(() => {
|
||||||
|
const start = props.process.planStartTime
|
||||||
|
const end = props.process.planFinishTime
|
||||||
|
if (!start && !end) return '-'
|
||||||
|
if (start && end) return `${start.slice(0, 16)} ~ ${end.slice(11, 16)}`
|
||||||
|
return start || end || '-'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.process-card-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-card {
|
||||||
|
width: 220px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
background: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
|
||||||
|
transition: box-shadow 0.2s, transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-card:hover {
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-done {
|
||||||
|
border-color: rgba(24, 160, 88, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-done .card-head {
|
||||||
|
background: linear-gradient(135deg, #18a058 0%, #36ad6a 100%);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-done .card-head :deep(.n-tag) {
|
||||||
|
background: rgba(255, 255, 255, 0.25) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-active .card-head {
|
||||||
|
background: linear-gradient(135deg, #2080f0 0%, #4098fc 100%);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-active .card-head :deep(.n-tag) {
|
||||||
|
background: rgba(255, 255, 255, 0.25) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-pending .card-head {
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #595959;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
min-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-progress {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 12px 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-text {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-stats {
|
||||||
|
padding: 0 12px 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
color: #8c8c8c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value .ok {
|
||||||
|
color: #18a058;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value .bad {
|
||||||
|
color: #d03050;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value .sep {
|
||||||
|
margin: 0 2px;
|
||||||
|
color: #bfbfbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value.highlight {
|
||||||
|
color: #18a058;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-flow {
|
||||||
|
display: flex;
|
||||||
|
margin: 0 12px 8px;
|
||||||
|
padding: 8px;
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 6px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-item {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #8c8c8c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-item strong {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-meta {
|
||||||
|
padding: 0 12px 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-line {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 11px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-line span {
|
||||||
|
color: #8c8c8c;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-line em {
|
||||||
|
font-style: normal;
|
||||||
|
color: #595959;
|
||||||
|
text-align: right;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 8px 10px 10px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-chevron {
|
||||||
|
padding: 0 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
121
src/views/biz/orderProcessPlan/components/ProcessFlowStepper.vue
Normal file
121
src/views/biz/orderProcessPlan/components/ProcessFlowStepper.vue
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flow-stepper">
|
||||||
|
<template v-for="(step, idx) in steps" :key="step.key">
|
||||||
|
<div
|
||||||
|
class="flow-step"
|
||||||
|
:class="step.state"
|
||||||
|
:title="step.label"
|
||||||
|
>
|
||||||
|
<span class="flow-dot">
|
||||||
|
<n-icon v-if="step.state === 'done'" size="12"><CheckmarkOutline /></n-icon>
|
||||||
|
<span v-else class="flow-num">{{ idx + 1 }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="flow-label">{{ step.shortLabel }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="idx < steps.length - 1" class="flow-line" :class="step.state" />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { NIcon } from 'naive-ui'
|
||||||
|
import { CheckmarkOutline } from '@vicons/ionicons5'
|
||||||
|
import type { ProcessPlanItemVO } from '@/api/orderProcessPlan'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
list?: ProcessPlanItemVO[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const steps = computed(() => {
|
||||||
|
const list = [...(props.list ?? [])].sort((a, b) => (a.operNumber ?? 0) - (b.operNumber ?? 0))
|
||||||
|
return list.map((item, idx) => {
|
||||||
|
const label = item.processName || item.processCode || `工序${idx + 1}`
|
||||||
|
const state = item.processStatus === 4 ? 'done' : item.processStatus != null && item.processStatus >= 2 ? 'active' : 'pending'
|
||||||
|
return {
|
||||||
|
key: item.planId ?? `${item.processCode}-${idx}`,
|
||||||
|
label,
|
||||||
|
shortLabel: label.length > 5 ? `${label.slice(0, 5)}…` : label,
|
||||||
|
state,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.flow-stepper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
max-width: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-dot {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
border: 2px solid #d9d9d9;
|
||||||
|
background: #fff;
|
||||||
|
color: #8c8c8c;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step.done .flow-dot {
|
||||||
|
background: #18a058;
|
||||||
|
border-color: #18a058;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step.active .flow-dot {
|
||||||
|
background: #2080f0;
|
||||||
|
border-color: #2080f0;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-label {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #8c8c8c;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.2;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step.done .flow-label,
|
||||||
|
.flow-step.active .flow-label {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-line {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 12px;
|
||||||
|
max-width: 28px;
|
||||||
|
height: 2px;
|
||||||
|
background: #e8e8e8;
|
||||||
|
margin: 0 2px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-line.done,
|
||||||
|
.flow-line.active {
|
||||||
|
background: #18a058;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-num {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -55,16 +55,25 @@
|
|||||||
</n-button-group> -->
|
</n-button-group> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 表格 -->
|
<div class="table-toolbar" style="margin-top: -8px">
|
||||||
|
<n-button size="small" quaternary @click="toggleExpandAll">
|
||||||
|
{{ allExpanded ? '全部收起' : '全部展开' }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格:主行生产订单,展开显示工序明细 -->
|
||||||
<n-data-table
|
<n-data-table
|
||||||
size="small"
|
size="small"
|
||||||
remote
|
remote
|
||||||
:border="false"
|
:border="false"
|
||||||
:single-line="false"
|
:single-line="false"
|
||||||
striped
|
striped
|
||||||
:columns="columns"
|
:columns="orderColumns"
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
:row-key="orderRowKey"
|
||||||
|
:scroll-x="orderScrollX"
|
||||||
|
v-model:expanded-row-keys="expandedRowKeys"
|
||||||
/>
|
/>
|
||||||
<div class="pagination-container" style="display: flex; justify-content: flex-end; margin-top: 12px">
|
<div class="pagination-container" style="display: flex; justify-content: flex-end; margin-top: 12px">
|
||||||
<n-pagination
|
<n-pagination
|
||||||
@ -367,16 +376,19 @@ import {
|
|||||||
ref,
|
ref,
|
||||||
reactive,
|
reactive,
|
||||||
h,
|
h,
|
||||||
|
computed,
|
||||||
onMounted
|
onMounted
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
import {
|
import {
|
||||||
NButton,
|
NButton,
|
||||||
NSpace,
|
NSpace,
|
||||||
NIcon,
|
NIcon,
|
||||||
|
NTag,
|
||||||
|
NDataTable,
|
||||||
NUpload,
|
NUpload,
|
||||||
useMessage,
|
useMessage,
|
||||||
useDialog,
|
useDialog,
|
||||||
//type DataTableColumns,
|
type DataTableColumns,
|
||||||
type UploadCustomRequestOptions
|
type UploadCustomRequestOptions
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import {
|
import {
|
||||||
@ -390,7 +402,12 @@ import {
|
|||||||
} from '@vicons/ionicons5'
|
} from '@vicons/ionicons5'
|
||||||
import {
|
import {
|
||||||
orderProcessPlanApi,
|
orderProcessPlanApi,
|
||||||
type OrderProcessPlan
|
toOrderProcessPlanPayload,
|
||||||
|
processItemToEntity,
|
||||||
|
ORDER_STATUS_MAP,
|
||||||
|
PROCESS_STATUS_MAP,
|
||||||
|
type OrderProcessPlanVO,
|
||||||
|
type ProcessPlanItemVO,
|
||||||
} from '@/api/orderProcessPlan'
|
} from '@/api/orderProcessPlan'
|
||||||
import { dictDataApi } from '@/api/org'
|
import { dictDataApi } from '@/api/org'
|
||||||
|
|
||||||
@ -415,9 +432,102 @@ const searchForm = reactive<any>({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 表格数据
|
// 表格数据
|
||||||
const tableData = ref<OrderProcessPlan[]>([])
|
const tableData = ref<OrderProcessPlanVO[]>([])
|
||||||
|
const expandedRowKeys = ref<Array<string | number>>([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const selectedIds = ref<number[]>([])
|
const selectedIds = ref<number[]>([])
|
||||||
|
const orderScrollX = 2090
|
||||||
|
const processScrollX = 2620
|
||||||
|
|
||||||
|
function orderRowKey(row: OrderProcessPlanVO) {
|
||||||
|
return row.orderItemId ?? `order-${row.mainCode}-${row.orderCode}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function processRowKey(row: ProcessPlanItemVO) {
|
||||||
|
return row.planId ?? `proc-${row.processCode}-${row.operNumber}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const allExpanded = computed(() => {
|
||||||
|
const total = tableData.value.length
|
||||||
|
if (!total) return false
|
||||||
|
return expandedRowKeys.value.length >= total
|
||||||
|
})
|
||||||
|
|
||||||
|
function toggleExpandAll() {
|
||||||
|
if (allExpanded.value) {
|
||||||
|
expandedRowKeys.value = []
|
||||||
|
} else {
|
||||||
|
expandedRowKeys.value = tableData.value.map(orderRowKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderStatusTag(
|
||||||
|
val: number | null | undefined,
|
||||||
|
map: Record<number, { label: string; type: 'default' | 'info' | 'success' | 'warning' | 'error' }>,
|
||||||
|
) {
|
||||||
|
if (val == null) return '-'
|
||||||
|
const opt = map[val]
|
||||||
|
if (!opt) return String(val)
|
||||||
|
return h(NTag, { size: 'small', type: opt.type, bordered: false }, { default: () => opt.label })
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderYesNo(val: number | null | undefined, options: { label: string; value: unknown }[]) {
|
||||||
|
const opt = options.find(o => o.value === val || String(o.value) === String(val))
|
||||||
|
return opt ? opt.label : (val ?? '-')
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildProcessActionColumn(order: OrderProcessPlanVO): DataTableColumns<ProcessPlanItemVO>[number] {
|
||||||
|
return {
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 140,
|
||||||
|
fixed: 'right',
|
||||||
|
align: 'center',
|
||||||
|
render(row) {
|
||||||
|
const buttons: ReturnType<typeof h>[] = []
|
||||||
|
if (hasPermission('biz:orderItem:edit')) {
|
||||||
|
buttons.push(h(NButton, {
|
||||||
|
size: 'small',
|
||||||
|
type: 'primary',
|
||||||
|
ghost: true,
|
||||||
|
onClick: () => handleEdit(row, order),
|
||||||
|
}, { default: () => '编辑' }))
|
||||||
|
}
|
||||||
|
if (hasPermission('biz:orderItem:assign')) {
|
||||||
|
buttons.push(h(NButton, {
|
||||||
|
size: 'small',
|
||||||
|
type: 'success',
|
||||||
|
ghost: true,
|
||||||
|
onClick: () => disphand(row, order),
|
||||||
|
}, { default: () => '派工' }))
|
||||||
|
}
|
||||||
|
return buttons.length > 0 ? h(NSpace, { justify: 'center' }, { default: () => buttons }) : '-'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildProcessColumns(order: OrderProcessPlanVO): DataTableColumns<ProcessPlanItemVO> {
|
||||||
|
return [...processColumns.slice(0, -1), buildProcessActionColumn(order)]
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderProcessExpand(row: OrderProcessPlanVO) {
|
||||||
|
const list = row.processList ?? []
|
||||||
|
if (!list.length) {
|
||||||
|
return h('div', { class: 'process-expand-empty' }, '暂无工序计划')
|
||||||
|
}
|
||||||
|
return h('div', { class: 'process-expand-wrap' }, [
|
||||||
|
h(NDataTable, {
|
||||||
|
class: 'process-plan-table',
|
||||||
|
columns: buildProcessColumns(row),
|
||||||
|
data: list,
|
||||||
|
rowKey: processRowKey,
|
||||||
|
size: 'small',
|
||||||
|
bordered: true,
|
||||||
|
striped: true,
|
||||||
|
scrollX: processScrollX,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
}
|
||||||
const pagination = reactive({
|
const pagination = reactive({
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
@ -458,149 +568,113 @@ const storageEntryOptions = ref<{ label: string; value: any }[]>([])
|
|||||||
const formRules = {
|
const formRules = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表格列
|
// 生产订单主表列(宽度按内容:编码/名称偏宽,数量/状态偏窄)
|
||||||
const columns = [
|
const orderColumns: DataTableColumns<OrderProcessPlanVO> = [
|
||||||
// { type: 'selection' },
|
|
||||||
{
|
{
|
||||||
align:'center',
|
type: 'expand',
|
||||||
minWidth: 240,
|
expandable: (row) => (row.processList?.length ?? 0) > 0,
|
||||||
title: '工序名称',
|
renderExpand: (row) => renderProcessExpand(row),
|
||||||
key: 'name'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
title: '类型',
|
||||||
|
key: 'rowType',
|
||||||
|
width: 88,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
minWidth: 150,
|
render: () => h(NTag, { size: 'small', type: 'info', bordered: false }, { default: () => '生产订单' }),
|
||||||
title: '工序编码',
|
|
||||||
key: 'code'
|
|
||||||
},
|
},
|
||||||
|
{ title: '生产令号', key: 'mainCode', width: 140, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
{ title: '物料编码', key: 'materialCode', width: 130, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
{ title: '物料名称', key: 'materialName', width: 168, ellipsis: { tooltip: true } },
|
||||||
|
{ title: '生产订单编码', key: 'orderCode', width: 150, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
{ title: '数量', key: 'quantity', width: 72, align: 'center' },
|
||||||
|
{ title: '工艺路线', key: 'routeCode', width: 108, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
{ title: '生产车间', key: 'proWorkshop', width: 108, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
{ title: '计划开工', key: 'beginTime', width: 168, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
{ title: '计划完工', key: 'endTime', width: 168, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
{ title: '实际开工', key: 'actualStartTime', width: 168, align: 'center', ellipsis: { tooltip: true }, render: (row) => row.actualStartTime || '-' },
|
||||||
|
{ title: '实际完工', key: 'actualEndTime', width: 168, align: 'center', ellipsis: { tooltip: true }, render: (row) => row.actualEndTime || '-' },
|
||||||
|
{ title: '完成数量', key: 'completedQty', width: 88, align: 'center' },
|
||||||
{
|
{
|
||||||
|
title: '订单状态',
|
||||||
|
key: 'orderStatus',
|
||||||
|
width: 96,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
minWidth: 150,
|
render: (row) => renderStatusTag(row.orderStatus, ORDER_STATUS_MAP),
|
||||||
title: '工序顺序',
|
|
||||||
key: 'sort'
|
|
||||||
},
|
},
|
||||||
// {
|
{ title: '当前工序', key: 'currentProcessName', width: 132, align: 'center', ellipsis: { tooltip: true } },
|
||||||
// align:'center',
|
{ title: '当前工序号', key: 'currentOperNumber', width: 96, align: 'center' },
|
||||||
// minWidth: 150,
|
]
|
||||||
// title: '生产订单id',
|
|
||||||
// key: 'orderItemId'
|
// 工序明细列(时间列 168,数量列 72,布尔/状态列 64~96)
|
||||||
// },
|
const processColumns: DataTableColumns<ProcessPlanItemVO> = [
|
||||||
{
|
{
|
||||||
|
title: '类型',
|
||||||
|
key: 'rowType',
|
||||||
|
width: 68,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
minWidth: 150,
|
render: () => h(NTag, { size: 'small', type: 'warning', bordered: false }, { default: () => '工序' }),
|
||||||
title: '生产开始时间',
|
|
||||||
key: 'beginTime'
|
|
||||||
},
|
},
|
||||||
|
{ title: '工序号', key: 'operNumber', width: 72, align: 'center' },
|
||||||
|
{ title: '工序编码', key: 'processCode', width: 96, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
{ title: '工序名称', key: 'processName', width: 140, ellipsis: { tooltip: true } },
|
||||||
|
{ title: '工序说明', key: 'operDescription', width: 160, ellipsis: { tooltip: true } },
|
||||||
|
{ title: '工作中心', key: 'workCenterName', width: 112, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
{ title: '部门', key: 'departmentName', width: 112, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
{ title: '计划开工', key: 'planStartTime', width: 168, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
{ title: '计划完工', key: 'planFinishTime', width: 168, align: 'center', ellipsis: { tooltip: true } },
|
||||||
|
{ title: '实际开工', key: 'actualStartTime', width: 168, align: 'center', ellipsis: { tooltip: true }, render: (row) => row.actualStartTime || '-' },
|
||||||
|
{ title: '实际完工', key: 'actualFinishTime', width: 168, align: 'center', ellipsis: { tooltip: true }, render: (row) => row.actualFinishTime || '-' },
|
||||||
|
{ title: '数量', key: 'quantity', width: 72, align: 'center' },
|
||||||
|
{ title: '转入', key: 'transferInQty', width: 72, align: 'center' },
|
||||||
|
{ title: '转出', key: 'transferOutQty', width: 72, align: 'center' },
|
||||||
|
{ title: '完成', key: 'completedQty', width: 72, align: 'center' },
|
||||||
|
{ title: '报废', key: 'scrapQty', width: 72, align: 'center' },
|
||||||
|
{ title: '返工', key: 'reworkQty', width: 72, align: 'center' },
|
||||||
{
|
{
|
||||||
align:'center',
|
title: '采购',
|
||||||
minWidth: 150,
|
|
||||||
title: '生产结束时间',
|
|
||||||
key: 'endTime'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align:'center',
|
|
||||||
minWidth: 150,
|
|
||||||
title: '是否采购',
|
|
||||||
key: 'procurement',
|
key: 'procurement',
|
||||||
render(row) {
|
width: 64,
|
||||||
const val = row.procurement
|
align: 'center',
|
||||||
const opt = procurementOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
render: (row) => renderYesNo(row.procurement, procurementOptions.value),
|
||||||
return opt ? opt.label : (val ?? '-')
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
align:'center',
|
title: '委外',
|
||||||
minWidth: 150,
|
|
||||||
title: '是否委外',
|
|
||||||
key: 'outsource',
|
key: 'outsource',
|
||||||
render(row) {
|
width: 64,
|
||||||
const val = row.outsource
|
align: 'center',
|
||||||
const opt = outsourceOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
render: (row) => renderYesNo(row.outsource, outsourceOptions.value),
|
||||||
return opt ? opt.label : (val ?? '-')
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
align:'center',
|
title: '质检',
|
||||||
minWidth: 150,
|
|
||||||
title: '生产总数',
|
|
||||||
key: 'quantity'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align:'center',
|
|
||||||
minWidth: 150,
|
|
||||||
title: '转入数量',
|
|
||||||
key: 'transferNum'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align:'center',
|
|
||||||
minWidth: 150,
|
|
||||||
title: '转出数量',
|
|
||||||
key: 'transferOutNum'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align:'center',
|
|
||||||
minWidth: 150,
|
|
||||||
title: '是否质检',
|
|
||||||
key: 'qualityInspection',
|
key: 'qualityInspection',
|
||||||
render(row) {
|
width: 64,
|
||||||
const val = row.qualityInspection
|
align: 'center',
|
||||||
const opt = qualityInspectionOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
render: (row) => renderYesNo(row.qualityInspection, qualityInspectionOptions.value),
|
||||||
return opt ? opt.label : (val ?? '-')
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
align:'center',
|
title: '入库',
|
||||||
minWidth: 150,
|
|
||||||
title: '是否直接入库',
|
|
||||||
key: 'storageEntry',
|
key: 'storageEntry',
|
||||||
render(row) {
|
width: 64,
|
||||||
const val = row.storageEntry
|
align: 'center',
|
||||||
const opt = storageEntryOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
render: (row) => renderYesNo(row.storageEntry, storageEntryOptions.value),
|
||||||
return opt ? opt.label : (val ?? '-')
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
title: '工序状态',
|
||||||
|
key: 'processStatus',
|
||||||
|
width: 96,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
render: (row) => renderStatusTag(row.processStatus, PROCESS_STATUS_MAP),
|
||||||
|
},
|
||||||
|
{ title: '派工人', key: 'assignByName', width: 96, align: 'center', ellipsis: { tooltip: true }, render: (row) => row.assignByName || '-' },
|
||||||
|
{ title: '派工时间', key: 'assignTime', width: 168, align: 'center', ellipsis: { tooltip: true }, render: (row) => row.assignTime || '-' },
|
||||||
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
width: 140,
|
width: 136,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
render(row) {
|
align: 'center',
|
||||||
|
render: () => '-',
|
||||||
const buttons:any = []
|
},
|
||||||
|
|
||||||
if(hasPermission('biz:orderItem:edit')){
|
|
||||||
buttons.push(h(NButton, {
|
|
||||||
size: 'small',
|
|
||||||
type:'primary',
|
|
||||||
ghost:true,
|
|
||||||
onClick: () => { handleEdit(row) } },
|
|
||||||
//{ default: () => [h(NIcon, null, { default: () => h(CreateOutline) }), ' 编辑' ]}
|
|
||||||
{ default: () => '编辑'}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
if(hasPermission('biz:orderItem:assign')){
|
|
||||||
buttons.push(h(NButton, {
|
|
||||||
size: 'small',
|
|
||||||
type:'success',
|
|
||||||
ghost:true,
|
|
||||||
onClick: () => { disphand(row) } },
|
|
||||||
{ default: () => '派工'}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
return buttons.length > 0 ? h(NSpace, {justify:'center'}, { default: () => buttons }) : '-'
|
|
||||||
|
|
||||||
// return h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'nowrap' } }, [
|
|
||||||
// h(NButton, { size: 'small', quaternary: true, onClick: () => handleEdit(row) }, {
|
|
||||||
// default: () => [h(NIcon, null, { default: () => h(CreateOutline) }), ' 编辑']
|
|
||||||
// }),
|
|
||||||
// h(NButton, { size: 'small', quaternary: true, type: 'error', onClick: () => handleDelete(row) }, {
|
|
||||||
// default: () => [h(NIcon, null, { default: () => h(TrashOutline) }), ' 删除']
|
|
||||||
// })
|
|
||||||
// ])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
// 加载数据
|
// 加载数据
|
||||||
@ -665,19 +739,31 @@ function handleAdd() {
|
|||||||
modalVisible.value = true
|
modalVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 编辑
|
function parseDateTime(val: string | null | undefined) {
|
||||||
function handleEdit(row:any) {
|
if (!val) return null
|
||||||
|
const ts = new Date(val.replace(' ', 'T')).getTime()
|
||||||
|
return Number.isNaN(ts) ? null : ts
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑(工序明细 -> 实体字段)
|
||||||
|
function handleEdit(process: ProcessPlanItemVO, order?: OrderProcessPlanVO) {
|
||||||
modalTitle.value = '编辑工序计划'
|
modalTitle.value = '编辑工序计划'
|
||||||
Object.assign(formData, row)
|
const entity = processItemToEntity(process, order?.orderItemId)
|
||||||
if (formData.beginTime && typeof formData.beginTime === 'string') {
|
Object.assign(formData, {
|
||||||
formData.beginTime = new Date(formData.beginTime.replace(' ', 'T')).getTime()
|
...defaultFormData,
|
||||||
}
|
...entity,
|
||||||
if (formData.endTime && typeof formData.endTime === 'string') {
|
sort: entity.sort != null ? String(entity.sort) : '',
|
||||||
formData.endTime = new Date(formData.endTime.replace(' ', 'T')).getTime()
|
orderItemId: entity.orderItemId != null ? String(entity.orderItemId) : '',
|
||||||
}
|
quantity: entity.quantity != null ? String(entity.quantity) : '',
|
||||||
if (formData.createTime && typeof formData.createTime === 'string') {
|
transferNum: entity.transferNum != null ? String(entity.transferNum) : '',
|
||||||
formData.createTime = new Date(formData.createTime.replace(' ', 'T')).getTime()
|
transferOutNum: entity.transferOutNum != null ? String(entity.transferOutNum) : '',
|
||||||
}
|
procurement: entity.procurement != null ? entity.procurement : '',
|
||||||
|
outsource: entity.outsource != null ? entity.outsource : '',
|
||||||
|
qualityInspection: entity.qualityInspection != null ? entity.qualityInspection : '',
|
||||||
|
storageEntry: entity.storageEntry != null ? entity.storageEntry : '',
|
||||||
|
beginTime: parseDateTime(entity.beginTime),
|
||||||
|
endTime: parseDateTime(entity.endTime),
|
||||||
|
})
|
||||||
modalVisible.value = true
|
modalVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -685,7 +771,7 @@ function handleEdit(row:any) {
|
|||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
await formRef.value?.validate()
|
await formRef.value?.validate()
|
||||||
try {
|
try {
|
||||||
const submitData = { ...formData }
|
const submitData = toOrderProcessPlanPayload({ ...formData })
|
||||||
if (typeof submitData.beginTime === 'number') {
|
if (typeof submitData.beginTime === 'number') {
|
||||||
submitData.beginTime = new Date(submitData.beginTime).toISOString().slice(0, 19).replace('T', ' ')
|
submitData.beginTime = new Date(submitData.beginTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||||
}
|
}
|
||||||
@ -767,20 +853,20 @@ function deletenum(index:any) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
function disphand(v:any){
|
function disphand(process: ProcessPlanItemVO, order?: OrderProcessPlanVO) {
|
||||||
|
const entity = processItemToEntity(process, order?.orderItemId)
|
||||||
dispatchmodal.value = true
|
dispatchmodal.value = true
|
||||||
dispatchformRef.value?.restoreValidation()
|
dispatchformRef.value?.restoreValidation()
|
||||||
dispatchform.id = v.id
|
dispatchform.id = entity.id
|
||||||
dispatchform.name = v.name
|
dispatchform.name = entity.name
|
||||||
dispatchform.beginTime = v.beginTime
|
dispatchform.beginTime = entity.beginTime
|
||||||
dispatchform.endTime = v.endTime
|
dispatchform.endTime = entity.endTime
|
||||||
dispatchform.quantity = v.quantity
|
dispatchform.quantity = entity.quantity
|
||||||
dispatchform.userid = ''
|
dispatchform.userid = ''
|
||||||
dispatchform.list = [{
|
dispatchform.list = [{
|
||||||
userId: '',
|
userId: '',
|
||||||
quantity:''
|
quantity: '',
|
||||||
}]
|
}]
|
||||||
//slehand()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function dispatchSubmit() {
|
function dispatchSubmit() {
|
||||||
@ -813,16 +899,16 @@ function dispatchSubmit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 删除
|
// 删除(工序计划 id)
|
||||||
function handleDelete(row: OrderProcessPlan) {
|
function handleDelete(process: ProcessPlanItemVO) {
|
||||||
dialog.warning({
|
dialog.warning({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
content: '确定要删除该记录吗?',
|
content: '确定要删除该工序计划吗?',
|
||||||
positiveText: '确定',
|
positiveText: '确定',
|
||||||
negativeText: '取消',
|
negativeText: '取消',
|
||||||
onPositiveClick: async () => {
|
onPositiveClick: async () => {
|
||||||
try {
|
try {
|
||||||
await orderProcessPlanApi.delete([row.id!])
|
await orderProcessPlanApi.delete([process.planId!])
|
||||||
message.success('删除成功')
|
message.success('删除成功')
|
||||||
loadData()
|
loadData()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -943,4 +1029,31 @@ onMounted(() => {
|
|||||||
.table-toolbar {
|
.table-toolbar {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.n-data-table-tr--expanded td) {
|
||||||
|
background-color: rgba(32, 128, 240, 0.06) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-expand-wrap {
|
||||||
|
margin: 4px 8px 8px 36px;
|
||||||
|
padding: 12px 14px 14px;
|
||||||
|
border: 1px solid var(--n-border-color);
|
||||||
|
border-left: 4px solid #18a058;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--n-action-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.process-plan-table .n-data-table-th) {
|
||||||
|
background: rgba(24, 160, 88, 0.12) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-expand-empty {
|
||||||
|
margin: 4px 8px 8px 36px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--n-text-color-3);
|
||||||
|
border: 1px dashed var(--n-border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--n-action-color);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
455
src/views/biz/processRoute/index.vue
Normal file
455
src/views/biz/processRoute/index.vue
Normal file
@ -0,0 +1,455 @@
|
|||||||
|
<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.id" placeholder="请输入主键" 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启用 0禁用">
|
||||||
|
<n-select v-model:value="searchForm.status" placeholder="请选择状态:1启用 0禁用" clearable style="width: 150px" :options="statusOptions" />
|
||||||
|
</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="工艺路线编码(金蝶 FNumber)" path="code">
|
||||||
|
<n-input v-model:value="formData.code" placeholder="请输入工艺路线编码(金蝶 FNumber)" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<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="materialCode">
|
||||||
|
<n-input v-model:value="formData.materialCode" placeholder="请输入物料编码" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="物料名称" path="materialName">
|
||||||
|
<n-input v-model:value="formData.materialName" placeholder="请输入物料名称" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="版本号" path="version">
|
||||||
|
<n-input v-model:value="formData.version" placeholder="请输入版本号" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="状态:1启用 0禁用" path="status">
|
||||||
|
<n-select v-model:value="formData.status" placeholder="请选择状态:1启用 0禁用" :options="statusOptions" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="来源:1金蝶 9手工" path="source">
|
||||||
|
<n-input v-model:value="formData.source" placeholder="请输入来源:1金蝶 9手工" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="来源系统" path="sourceId">
|
||||||
|
<n-input v-model:value="formData.sourceId" placeholder="请输入来源系统" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="变更时间" path="changeTime">
|
||||||
|
<n-date-picker v-model:value="formData.changeTime" type="datetime" clearable style="width: 100%" />
|
||||||
|
</n-form-item-gi>
|
||||||
|
<n-form-item-gi label="变更人" path="changeBy">
|
||||||
|
<n-input v-model:value="formData.changeBy" 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 { processRouteApi, type ProcessRoute } from '@/api/processRoute'
|
||||||
|
import { dictDataApi } from '@/api/org'
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const dialog = useDialog()
|
||||||
|
|
||||||
|
// 搜索表单
|
||||||
|
const searchForm = reactive({
|
||||||
|
id: null as number | null,
|
||||||
|
name: '',
|
||||||
|
status: null as number | null,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表格数据
|
||||||
|
const tableData = ref<ProcessRoute[]>([])
|
||||||
|
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: ProcessRoute = {
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
materialCode: '',
|
||||||
|
materialName: '',
|
||||||
|
version: '',
|
||||||
|
status: undefined,
|
||||||
|
source: undefined,
|
||||||
|
sourceId: '',
|
||||||
|
changeTime: undefined,
|
||||||
|
changeBy: '',
|
||||||
|
}
|
||||||
|
const formData = reactive<ProcessRoute>({ ...defaultFormData })
|
||||||
|
|
||||||
|
// 字典选项(下拉框/单选框/复选框关联字典时使用)
|
||||||
|
const statusOptions = ref<{ label: string; value: any }[]>([])
|
||||||
|
|
||||||
|
// 表单校验规则
|
||||||
|
const formRules = {
|
||||||
|
code: { required: true, message: '请输入工艺路线编码(金蝶 FNumber)', trigger: 'blur' },
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格列
|
||||||
|
const columns: DataTableColumns<ProcessRoute> = [
|
||||||
|
{ type: 'selection' },
|
||||||
|
{ title: '主键', key: 'id' },
|
||||||
|
{ title: '工艺路线编码(金蝶 FNumber)', key: 'code' },
|
||||||
|
{ title: '工艺路线名称', key: 'name' },
|
||||||
|
{ title: '物料编码', key: 'materialCode' },
|
||||||
|
{ title: '物料名称', key: 'materialName' },
|
||||||
|
{ title: '版本号', key: 'version' },
|
||||||
|
{ title: '状态:1启用 0禁用', key: 'status',
|
||||||
|
render(row) {
|
||||||
|
const val = row.status
|
||||||
|
const opt = statusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
|
return opt ? opt.label : (val ?? '-')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ title: '来源:1金蝶 9手工', key: 'source' },
|
||||||
|
{ title: '来源系统', key: 'sourceId' },
|
||||||
|
{ title: '备注', key: 'remark' },
|
||||||
|
{ title: '创建时间', key: 'createTime', width: 180 },
|
||||||
|
{ title: '创建人', key: 'createBy' },
|
||||||
|
{ title: '变更时间', key: 'changeTime' },
|
||||||
|
{ title: '变更人', key: 'changeBy' },
|
||||||
|
{
|
||||||
|
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 processRouteApi.page({
|
||||||
|
page: pagination.page,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
id: searchForm.id || undefined,
|
||||||
|
name: searchForm.name || undefined,
|
||||||
|
status: searchForm.status || 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.status = null
|
||||||
|
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
function handlePageChange(page: number) {
|
||||||
|
pagination.page = page
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePageSizeChange(pageSize: number) {
|
||||||
|
pagination.pageSize = pageSize
|
||||||
|
pagination.page = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择
|
||||||
|
function handleCheck(keys: Array<string | number>) {
|
||||||
|
selectedIds.value = keys as number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
function handleAdd() {
|
||||||
|
modalTitle.value = '新增工艺路线主表'
|
||||||
|
Object.assign(formData, defaultFormData)
|
||||||
|
modalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
function handleEdit(row: ProcessRoute) {
|
||||||
|
modalTitle.value = '编辑工艺路线主表'
|
||||||
|
Object.assign(formData, row)
|
||||||
|
if (formData.createTime && typeof formData.createTime === 'string') {
|
||||||
|
formData.createTime = new Date(formData.createTime.replace(' ', 'T')).getTime()
|
||||||
|
}
|
||||||
|
if (formData.changeTime && typeof formData.changeTime === 'string') {
|
||||||
|
formData.changeTime = new Date(formData.changeTime.replace(' ', 'T')).getTime()
|
||||||
|
}
|
||||||
|
modalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
async function handleSubmit() {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
try {
|
||||||
|
const submitData = { ...formData } as ProcessRoute
|
||||||
|
if (typeof submitData.createTime === 'number') {
|
||||||
|
submitData.createTime = new Date(submitData.createTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
}
|
||||||
|
if (typeof submitData.changeTime === 'number') {
|
||||||
|
submitData.changeTime = new Date(submitData.changeTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
}
|
||||||
|
if (submitData.id) {
|
||||||
|
await processRouteApi.update(submitData)
|
||||||
|
message.success('修改成功')
|
||||||
|
} else {
|
||||||
|
await processRouteApi.create(submitData)
|
||||||
|
message.success('新增成功')
|
||||||
|
}
|
||||||
|
modalVisible.value = false
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
function handleDelete(row: ProcessRoute) {
|
||||||
|
dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定要删除该记录吗?',
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
await processRouteApi.delete([row.id!])
|
||||||
|
message.success('删除成功')
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
function handleBatchDelete() {
|
||||||
|
dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: `确定要删除选中的 ${selectedIds.value.length} 条记录吗?`,
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
await processRouteApi.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.status != null) params.status = searchForm.status
|
||||||
|
const blob = await processRouteApi.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 processRouteApi.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 processRouteApi.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('pro_status')
|
||||||
|
statusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
|
loadDictOptions()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.search-form {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-toolbar {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
459
src/views/biz/processRouteStep/index.vue
Normal file
459
src/views/biz/processRouteStep/index.vue
Normal file
@ -0,0 +1,459 @@
|
|||||||
|
<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.id" placeholder="请输入主键" clearable />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="状态">
|
||||||
|
<n-select v-model:value="searchForm.status" placeholder="请选择状态" clearable style="width: 150px" :options="statusOptions" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item>
|
||||||
|
<n-space>
|
||||||
|
<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: 600px">
|
||||||
|
<n-form ref="formRef" :model="formData" :rules="formRules" label-placement="left" label-width="100px">
|
||||||
|
<n-form-item label="工艺路线id" path="routeId">
|
||||||
|
<n-input v-model:value="formData.routeId" placeholder="请输入工艺路线id" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="工序号" path="operNumber">
|
||||||
|
<n-input v-model:value="formData.operNumber" placeholder="请输入工序号" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="工序名称" path="processName">
|
||||||
|
<n-input v-model:value="formData.processName" placeholder="请输入工序名称" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="工序说明" path="operDescription">
|
||||||
|
<n-input v-model:value="formData.operDescription" type="textarea" placeholder="请输入工序说明" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="工作中心" path="workCenterName">
|
||||||
|
<n-input v-model:value="formData.workCenterName" placeholder="请输入工作中心" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="生产车间" path="departmentName">
|
||||||
|
<n-input v-model:value="formData.departmentName" placeholder="请输入生产车间" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="活动单位" path="activityUnit">
|
||||||
|
<n-input v-model:value="formData.activityUnit" placeholder="请输入活动单位" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="工序控制码" path="optCtrlCodeName">
|
||||||
|
<n-input v-model:value="formData.optCtrlCodeName" placeholder="请输入工序控制码" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="活动量" path="activityQty">
|
||||||
|
<n-input v-model:value="formData.activityQty" placeholder="请输入活动量" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="排序" path="sort">
|
||||||
|
<n-input v-model:value="formData.sort" placeholder="请输入排序" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="是否委外" path="isOutsource">
|
||||||
|
<n-input v-model:value="formData.isOutsource" placeholder="请输入是否委外" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="状态" path="status">
|
||||||
|
<n-select v-model:value="formData.status" placeholder="请选择状态" :options="statusOptions" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="变更时间" path="changeTime">
|
||||||
|
<n-date-picker v-model:value="formData.changeTime" type="datetime" clearable style="width: 100%" />
|
||||||
|
</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="导入工艺路线工序明细" 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 { processRouteStepApi, type ProcessRouteStep } from '@/api/processRouteStep'
|
||||||
|
import { dictDataApi } from '@/api/org'
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const dialog = useDialog()
|
||||||
|
|
||||||
|
// 搜索表单
|
||||||
|
const searchForm = reactive({
|
||||||
|
id: null as number | null,
|
||||||
|
status: null as number | null,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表格数据
|
||||||
|
const tableData = ref<ProcessRouteStep[]>([])
|
||||||
|
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: ProcessRouteStep = {
|
||||||
|
routeId: undefined,
|
||||||
|
operNumber: undefined,
|
||||||
|
processName: '',
|
||||||
|
operDescription: '',
|
||||||
|
workCenterName: '',
|
||||||
|
departmentName: '',
|
||||||
|
activityUnit: '',
|
||||||
|
optCtrlCodeName: '',
|
||||||
|
activityQty: undefined,
|
||||||
|
sort: undefined,
|
||||||
|
isOutsource: undefined,
|
||||||
|
status: undefined,
|
||||||
|
changeTime: undefined,
|
||||||
|
}
|
||||||
|
const formData = reactive<ProcessRouteStep>({ ...defaultFormData })
|
||||||
|
|
||||||
|
// 字典选项(下拉框/单选框/复选框关联字典时使用)
|
||||||
|
const statusOptions = ref<{ label: string; value: any }[]>([])
|
||||||
|
|
||||||
|
// 表单校验规则
|
||||||
|
const formRules = {
|
||||||
|
routeId: { required: true, message: '请输入工艺路线id', trigger: 'blur' },
|
||||||
|
operNumber: { required: true, message: '请输入工序号', trigger: 'blur' },
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格列
|
||||||
|
const columns: DataTableColumns<ProcessRouteStep> = [
|
||||||
|
{ type: 'selection' },
|
||||||
|
{ title: '主键', key: 'id' },
|
||||||
|
{ title: '工艺路线id', key: 'routeId' },
|
||||||
|
{ title: '工序号', key: 'operNumber' },
|
||||||
|
{ title: '工序名称', key: 'processName' },
|
||||||
|
{ title: '工序说明', key: 'operDescription' },
|
||||||
|
{ title: '工作中心', key: 'workCenterName' },
|
||||||
|
{ title: '生产车间', key: 'departmentName' },
|
||||||
|
{ title: '活动单位', key: 'activityUnit' },
|
||||||
|
{ title: '工序控制码', key: 'optCtrlCodeName' },
|
||||||
|
{ title: '活动量', key: 'activityQty' },
|
||||||
|
{ title: '排序', key: 'sort' },
|
||||||
|
{ title: '是否委外', key: 'isOutsource' },
|
||||||
|
{ title: '状态', key: 'status',
|
||||||
|
render(row) {
|
||||||
|
const val = row.status
|
||||||
|
const opt = statusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
|
return opt ? opt.label : (val ?? '-')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ title: '创建时间', key: 'createTime', width: 180 },
|
||||||
|
{ title: '变更时间', key: 'changeTime' },
|
||||||
|
{
|
||||||
|
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 processRouteStepApi.page({
|
||||||
|
page: pagination.page,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
id: searchForm.id || undefined,
|
||||||
|
status: searchForm.status || 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.status = null
|
||||||
|
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
function handlePageChange(page: number) {
|
||||||
|
pagination.page = page
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePageSizeChange(pageSize: number) {
|
||||||
|
pagination.pageSize = pageSize
|
||||||
|
pagination.page = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择
|
||||||
|
function handleCheck(keys: Array<string | number>) {
|
||||||
|
selectedIds.value = keys as number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
function handleAdd() {
|
||||||
|
modalTitle.value = '新增工艺路线工序明细'
|
||||||
|
Object.assign(formData, defaultFormData)
|
||||||
|
modalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
function handleEdit(row: ProcessRouteStep) {
|
||||||
|
modalTitle.value = '编辑工艺路线工序明细'
|
||||||
|
Object.assign(formData, row)
|
||||||
|
if (formData.createTime && typeof formData.createTime === 'string') {
|
||||||
|
formData.createTime = new Date(formData.createTime.replace(' ', 'T')).getTime()
|
||||||
|
}
|
||||||
|
if (formData.changeTime && typeof formData.changeTime === 'string') {
|
||||||
|
formData.changeTime = new Date(formData.changeTime.replace(' ', 'T')).getTime()
|
||||||
|
}
|
||||||
|
modalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
async function handleSubmit() {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
try {
|
||||||
|
const submitData = { ...formData } as ProcessRouteStep
|
||||||
|
if (typeof submitData.createTime === 'number') {
|
||||||
|
submitData.createTime = new Date(submitData.createTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
}
|
||||||
|
if (typeof submitData.changeTime === 'number') {
|
||||||
|
submitData.changeTime = new Date(submitData.changeTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
}
|
||||||
|
if (submitData.id) {
|
||||||
|
await processRouteStepApi.update(submitData)
|
||||||
|
message.success('修改成功')
|
||||||
|
} else {
|
||||||
|
await processRouteStepApi.create(submitData)
|
||||||
|
message.success('新增成功')
|
||||||
|
}
|
||||||
|
modalVisible.value = false
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
function handleDelete(row: ProcessRouteStep) {
|
||||||
|
dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定要删除该记录吗?',
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
await processRouteStepApi.delete([row.id!])
|
||||||
|
message.success('删除成功')
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已在拦截器处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
function handleBatchDelete() {
|
||||||
|
dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: `确定要删除选中的 ${selectedIds.value.length} 条记录吗?`,
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
await processRouteStepApi.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.status != null) params.status = searchForm.status
|
||||||
|
const blob = await processRouteStepApi.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 processRouteStepApi.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 processRouteStepApi.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')
|
||||||
|
statusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
|
loadDictOptions()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.search-form {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-toolbar {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -40,7 +40,7 @@
|
|||||||
:data="tableData"
|
:data="tableData"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:pagination="pagination"
|
:pagination="pagination"
|
||||||
:row-key="(row) => row.id"
|
:row-key="(row:any) => row.id"
|
||||||
:scroll-x="1200"
|
:scroll-x="1200"
|
||||||
@update:page="handlePageChange"
|
@update:page="handlePageChange"
|
||||||
@update:page-size="handlePageSizeChange"
|
@update:page-size="handlePageSizeChange"
|
||||||
@ -54,9 +54,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, h, onMounted } from 'vue'
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
import { NButton, NSpace, NIcon, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
|
import { NButton, NSpace, NIcon, type DataTableColumns} from 'naive-ui'
|
||||||
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline } from '@vicons/ionicons5'
|
import { SearchOutline, RefreshOutline } from '@vicons/ionicons5'
|
||||||
import { qcResultDetailApi, type QcResultDetail } from '@/api/qcResultDetail'
|
import { qcResultDetailApi, type QcResultDetail } from '@/api/qcResultDetail'
|
||||||
import { dictDataApi } from '@/api/org'
|
import { dictDataApi } from '@/api/org'
|
||||||
|
|
||||||
|
|||||||
@ -213,7 +213,7 @@
|
|||||||
:columns="detailColumns"
|
:columns="detailColumns"
|
||||||
:data="detailTableData"
|
:data="detailTableData"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:row-key="(row) => row.id"
|
:row-key="(row:any) => row.id"
|
||||||
:scroll-x="700"
|
:scroll-x="700"
|
||||||
max-height="320"
|
max-height="320"
|
||||||
:pagination="{ pageSize:4 }"
|
:pagination="{ pageSize:4 }"
|
||||||
@ -245,15 +245,9 @@ import { qualityTestingApi, type QualityTesting } from '@/api/qualityTesting'
|
|||||||
import {assingReworkApi} from '@/api/assingRework'
|
import {assingReworkApi} from '@/api/assingRework'
|
||||||
import { dictDataApi } from '@/api/org'
|
import { dictDataApi } from '@/api/org'
|
||||||
|
|
||||||
import {Language} from "@xterm/xterm/src/vs/base/common/platform.ts";
|
|
||||||
|
|
||||||
import {fileApi} from '@/api/system1'
|
|
||||||
|
|
||||||
import value = Language.value;
|
|
||||||
import {QcResultDetail} from "@/api/qcResultDetail.ts";
|
import {QcResultDetail} from "@/api/qcResultDetail.ts";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
import dd from 'dingtalk-jsapi'
|
|
||||||
import { env } from 'echarts'
|
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
@ -274,7 +268,7 @@ const pagination = reactive({
|
|||||||
|
|
||||||
// 搜索表单
|
// 搜索表单
|
||||||
const searchForm = reactive({
|
const searchForm = reactive({
|
||||||
qcNo: null as number | null,
|
qcNo: null as string | null,
|
||||||
status: null as number | null,
|
status: null as number | null,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -290,7 +284,7 @@ const infoIsCollapse = ref(false)
|
|||||||
const detailIsCollapse = ref(true)
|
const detailIsCollapse = ref(true)
|
||||||
|
|
||||||
//提交判定明细
|
//提交判定明细
|
||||||
const submitDetailModalVisible = ref(false)
|
// const submitDetailModalVisible = ref(false)
|
||||||
const traceCodeShow = ref(false)
|
const traceCodeShow = ref(false)
|
||||||
//提交明细/部分提交明细标识
|
//提交明细/部分提交明细标识
|
||||||
const submitFlag = ref<Boolean>()
|
const submitFlag = ref<Boolean>()
|
||||||
@ -334,10 +328,10 @@ const infoDataMapping = {
|
|||||||
inspectTime: '质检时间',
|
inspectTime: '质检时间',
|
||||||
traceType: '追溯类型'
|
traceType: '追溯类型'
|
||||||
}
|
}
|
||||||
const infoDataMap = new Map(Object.entries(infoDataMapping))
|
|
||||||
const mappedInfoList = ref<Array<{ label: string; key: string; value: any }>>([])
|
const mappedInfoList = ref<Array<{ label: string; key: string; value: any }>>([])
|
||||||
//详情列表名
|
//详情列表名
|
||||||
const detailColumns: DataTableColumns<Object> = [
|
const detailColumns: DataTableColumns<QcResultDetail> = [
|
||||||
{
|
{
|
||||||
title: '判定类型',
|
title: '判定类型',
|
||||||
key: 'resultType',
|
key: 'resultType',
|
||||||
@ -403,7 +397,7 @@ const detailTableData = ref<Object[]>([])
|
|||||||
|
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const defaultFormData: QualityTesting = {
|
const defaultFormData: QualityTesting = {
|
||||||
qcNo: '',
|
qcNo: undefined,
|
||||||
sourceType: undefined,
|
sourceType: undefined,
|
||||||
sourceId: undefined,
|
sourceId: undefined,
|
||||||
orderId: undefined,
|
orderId: undefined,
|
||||||
@ -417,38 +411,38 @@ const defaultFormData: QualityTesting = {
|
|||||||
}
|
}
|
||||||
const formData = reactive<QualityTesting>({ ...defaultFormData })
|
const formData = reactive<QualityTesting>({ ...defaultFormData })
|
||||||
|
|
||||||
const defaultFormDetailData:QcResultDetail = {
|
// const defaultFormDetailData:QcResultDetail = {
|
||||||
qcId: undefined,
|
// qcId: undefined,
|
||||||
resultType: undefined,
|
// resultType: undefined,
|
||||||
qty: 0,
|
// qty: 0,
|
||||||
traceCode: undefined,
|
// traceCode: undefined,
|
||||||
defectCode: undefined,
|
// defectCode: undefined,
|
||||||
handleAction: undefined,
|
// handleAction: undefined,
|
||||||
actionStatus: undefined
|
// actionStatus: undefined
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
const formDetailOKData = reactive<QcResultDetail>({ ...defaultFormDetailData, resultType: 1,handleAction:'下一道工序',actionStatus:1 })
|
// const formDetailOKData = reactive<QcResultDetail>({ ...defaultFormDetailData, resultType: 1,handleAction:'下一道工序',actionStatus:1 })
|
||||||
const formDetailScrapData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 2,handleAction:'报废次品',actionStatus:1 })
|
// const formDetailScrapData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 2,handleAction:'报废次品',actionStatus:1 })
|
||||||
const formDetailReworkData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 3,handleAction:'返工',actionStatus:1 })
|
// const formDetailReworkData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 3,handleAction:'返工',actionStatus:1 })
|
||||||
const formDetailConcessionData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 4,handleAction:'让步接收',actionStatus:1 })
|
// const formDetailConcessionData = reactive<QcResultDetail>({ ...defaultFormDetailData,resultType: 4,handleAction:'让步接收',actionStatus:1 })
|
||||||
|
|
||||||
const assingWorkDisabled = ref(true)
|
const assingWorkDisabled = ref(true)
|
||||||
|
|
||||||
const assingWork = ref<{ label: string; value: any }[]>([])
|
const assingWork = ref<{ label: string; value: any }[]>([])
|
||||||
|
|
||||||
// 字典选项(下拉框/单选框/复选框关联字典时使用)
|
// 字典选项(下拉框/单选框/复选框关联字典时使用)
|
||||||
const statusOptions = ref<{ label: string; value: any }[]>([])
|
const statusOptions = ref<{ label: string; value: any;class:any}[]>([])
|
||||||
//质检状态字典
|
//质检状态字典
|
||||||
const qcStatusOptions = ref<{ label: string; value: any }[]>([])
|
const qcStatusOptions = ref<{ label: string; value: any;class:any }[]>([])
|
||||||
//业务来源字典
|
//业务来源字典
|
||||||
const qcSourceTypeOptions = ref<{ label: string; value: any }[]>([])
|
const qcSourceTypeOptions = ref<{ label: string; value: any;class:any }[]>([])
|
||||||
//追溯状态字段
|
//追溯状态字段
|
||||||
const qcTraceTypeOptions = ref<{ label: string; value: any }[]>([])
|
const qcTraceTypeOptions = ref<{ label: string; value: any;class:any }[]>([])
|
||||||
//判定类型
|
//判定类型
|
||||||
const qcResultTypeOptions = ref<{ label: string; value: any }[]>([])
|
const qcResultTypeOptions = ref<{ label: string; value: any;class:any }[]>([])
|
||||||
//动作执行状态
|
//动作执行状态
|
||||||
const qcActionStatusOptions = ref<{ label: string; value: any }[]>([])
|
const qcActionStatusOptions = ref<{ label: string; value: any;class:any }[]>([])
|
||||||
// 表单校验规则
|
// 表单校验规则
|
||||||
const formRules = {
|
const formRules = {
|
||||||
sourceType: [{ required: true, type: 'number', message: '请选择来源类型:1工序汇报/2委外/3手工', trigger: 'change' }],
|
sourceType: [{ required: true, type: 'number', message: '请选择来源类型:1工序汇报/2委外/3手工', trigger: 'change' }],
|
||||||
@ -577,7 +571,7 @@ function handleSearch() {
|
|||||||
|
|
||||||
// 重置
|
// 重置
|
||||||
function handleReset() {
|
function handleReset() {
|
||||||
searchForm.id = null
|
searchForm.qcNo = null
|
||||||
searchForm.status = null
|
searchForm.status = null
|
||||||
|
|
||||||
handleSearch()
|
handleSearch()
|
||||||
@ -699,7 +693,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.id != null) params.id = searchForm.id
|
if (searchForm.qcNo != null) params.id = searchForm.qcNo
|
||||||
if (searchForm.status != null) params.status = searchForm.status
|
if (searchForm.status != null) params.status = searchForm.status
|
||||||
const blob = await qualityTestingApi.export(params)
|
const blob = await qualityTestingApi.export(params)
|
||||||
const url = window.URL.createObjectURL(blob)
|
const url = window.URL.createObjectURL(blob)
|
||||||
@ -837,12 +831,12 @@ function formatDisplayValue(key: string, value: any): string {
|
|||||||
async function loadAssignWorkOrder() {
|
async function loadAssignWorkOrder() {
|
||||||
const data = await assingReworkApi.allList()
|
const data = await assingReworkApi.allList()
|
||||||
console.log(data)
|
console.log(data)
|
||||||
assingWork.value = data.map(d => ({ label: d.assingCode, value: (Number(d.id) || d.id) }))
|
assingWork.value = data.map((d:any) => ({ label: d.assingCode, value: (Number(d.id) || d.id) }))
|
||||||
console.log(assingWork)
|
console.log(assingWork)
|
||||||
}
|
}
|
||||||
|
|
||||||
//提交判定明细
|
//提交判定明细
|
||||||
function submitResultDetail(row) {
|
function submitResultDetail(row:any) {
|
||||||
|
|
||||||
if (row.traceType != 1) {
|
if (row.traceType != 1) {
|
||||||
traceCodeShow.value = true
|
traceCodeShow.value = true
|
||||||
@ -856,36 +850,14 @@ function submitResultDetail(row) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function handleSubmitResultDetail() {
|
|
||||||
|
|
||||||
formDetailConcessionData.files = uploadFiles.value
|
|
||||||
|
|
||||||
const submitParam = [
|
|
||||||
formDetailOKData,
|
|
||||||
formDetailScrapData,
|
|
||||||
formDetailReworkData,
|
|
||||||
formDetailConcessionData]
|
|
||||||
console.log(submitParam);
|
|
||||||
|
|
||||||
submitFlag.value? await qualityTestingApi.submit(submitParam) : await qualityTestingApi.submitBF(submitParam)
|
|
||||||
|
|
||||||
message.success('提交成功')
|
|
||||||
submitDetailModalVisible.value = false
|
|
||||||
loadData()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//提交部分判定明细
|
//提交部分判定明细
|
||||||
function submitBFResultDetail(row) {
|
function submitBFResultDetail(row:any) {
|
||||||
|
|
||||||
|
|
||||||
if (row.traceType != 1) {
|
if (row.traceType != 1) {
|
||||||
traceCodeShow.value = true
|
traceCodeShow.value = true
|
||||||
}
|
}
|
||||||
submitFlag.value = false
|
submitFlag.value = false
|
||||||
|
|
||||||
|
|
||||||
//version-2
|
|
||||||
let path = `/qc/submitDetail/${row.qcNo}/${row.id}/${submitFlag.value}/${traceCodeShow.value}`
|
let path = `/qc/submitDetail/${row.qcNo}/${row.id}/${submitFlag.value}/${traceCodeShow.value}`
|
||||||
router.push(path)
|
router.push(path)
|
||||||
|
|
||||||
@ -893,13 +865,13 @@ function submitBFResultDetail(row) {
|
|||||||
|
|
||||||
|
|
||||||
//查询判定明细
|
//查询判定明细
|
||||||
function queryResultDetail(row) {
|
function queryResultDetail(row:any) {
|
||||||
let path = `detail/${row.id}`;
|
let path = `detail/${row.id}`;
|
||||||
router.push(path)
|
router.push(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
//质检撤回
|
//质检撤回
|
||||||
function handleCancelQuality(row) {
|
function handleCancelQuality(row:any) {
|
||||||
dialog.warning({
|
dialog.warning({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
content: '确定要撤回ID:'+row.id+'质检记录吗?',
|
content: '确定要撤回ID:'+row.id+'质检记录吗?',
|
||||||
|
|||||||
@ -134,7 +134,6 @@
|
|||||||
multiple
|
multiple
|
||||||
directory-dnd
|
directory-dnd
|
||||||
:max="5"
|
:max="5"
|
||||||
:action="null"
|
|
||||||
:custom-request="()=>{}"
|
:custom-request="()=>{}"
|
||||||
@change="handleUploadChange"
|
@change="handleUploadChange"
|
||||||
v-model:file-list="fileList"
|
v-model:file-list="fileList"
|
||||||
@ -173,19 +172,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, h, onMounted } from 'vue'
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
import {
|
import {
|
||||||
NButton, NSpace, NIcon, NUpload,NTag, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions,
|
NButton, NSpace, NIcon, NUpload, useMessage
|
||||||
NDropdown
|
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
|
|
||||||
import {useRoute} from "vue-router";
|
import {useRoute} from "vue-router";
|
||||||
|
|
||||||
import {QcResultDetail} from "@/api/qcResultDetail.ts";
|
import {QcResultDetail} from "@/api/qcResultDetail.ts";
|
||||||
import {fileApi} from '@/api/system1'
|
import {fileApi} from '@/api/system1'
|
||||||
import { qualityTestingApi, type QualityTesting } from '@/api/qualityTesting'
|
import { qualityTestingApi} from '@/api/qualityTesting'
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
|
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const qcId =ref( Number(route.params.id))
|
const qcId =ref( Number(route.params.id))
|
||||||
|
|||||||
@ -211,6 +211,7 @@ const pagination = reactive({
|
|||||||
// 搜索表单
|
// 搜索表单
|
||||||
const searchForm = reactive({
|
const searchForm = reactive({
|
||||||
page:pagination.page as number,
|
page:pagination.page as number,
|
||||||
|
assingStatus:undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
// 弹窗
|
// 弹窗
|
||||||
@ -229,13 +230,13 @@ const reportDetailVisible = ref(false)
|
|||||||
|
|
||||||
// 字典选项(下拉框/单选框/复选框关联字典时使用)
|
// 字典选项(下拉框/单选框/复选框关联字典时使用)
|
||||||
//判定状态字典
|
//判定状态字典
|
||||||
const qcResultTypeOptions = ref<{ label: string; value: any }[]>([])
|
const qcResultTypeOptions = ref<{ label: string; value: any;class:any }[]>([])
|
||||||
//业务来源字典
|
//业务来源字典
|
||||||
const qcSourceTypeOptions = ref<{ label: string; value: any }[]>([])
|
const qcSourceTypeOptions = ref<{ label: string; value: any;class:any }[]>([])
|
||||||
//质检状态字典
|
//质检状态字典
|
||||||
const qcStatusOptions = ref<{ label: string; value: any }[]>([])
|
const qcStatusOptions = ref<{ label: string; value: any;class:any }[]>([])
|
||||||
//派工状态字典
|
//派工状态字典
|
||||||
const qcAssingStatusOptions = ref<{ label: string; value: any }[]>([])
|
const qcAssingStatusOptions = ref<{ label: string; value: any ;class:any}[]>([])
|
||||||
// 表单校验规则
|
// 表单校验规则
|
||||||
const formRules = {
|
const formRules = {
|
||||||
}
|
}
|
||||||
@ -280,7 +281,7 @@ const columns: DataTableColumns<SubmitLog> = [
|
|||||||
render(row) {
|
render(row) {
|
||||||
return h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'nowrap' } }, [
|
return h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'nowrap' } }, [
|
||||||
h(NButton, { size: 'small', quaternary: true, onClick: () => handleViewReport(row) }, {
|
h(NButton, { size: 'small', quaternary: true, onClick: () => handleViewReport(row) }, {
|
||||||
default: () => [h(NIcon, null, { default: () => h(null) }), ' 汇报信息']
|
default: () => ['汇报信息']
|
||||||
}),
|
}),
|
||||||
// h(NButton, { size: 'small', quaternary: true, onClick: () => handleEdit(row) }, {
|
// h(NButton, { size: 'small', quaternary: true, onClick: () => handleEdit(row) }, {
|
||||||
// default: () => [h(NIcon, null, { default: () => h(CreateOutline) }), ' 编辑']
|
// default: () => [h(NIcon, null, { default: () => h(CreateOutline) }), ' 编辑']
|
||||||
@ -300,7 +301,7 @@ const submitColumns = ref([
|
|||||||
{ title: "提交时间", key: "submitCreateTime" },
|
{ title: "提交时间", key: "submitCreateTime" },
|
||||||
{ title: "提交数量", key: "quantity" },
|
{ title: "提交数量", key: "quantity" },
|
||||||
{ title: "单据状态", key: "submitStatus",
|
{ title: "单据状态", key: "submitStatus",
|
||||||
render: (row) => {
|
render: (row:any) => {
|
||||||
const val = row.submitStatus
|
const val = row.submitStatus
|
||||||
const opt = qcResultTypeOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
const opt = qcResultTypeOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
if (!opt) return val ?? '-'
|
if (!opt) return val ?? '-'
|
||||||
@ -335,7 +336,7 @@ function handleSearch() {
|
|||||||
|
|
||||||
// 重置
|
// 重置
|
||||||
function handleReset() {
|
function handleReset() {
|
||||||
searchForm.assingStatus = null
|
searchForm.assingStatus = undefined
|
||||||
|
|
||||||
handleSearch()
|
handleSearch()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -92,8 +92,8 @@ const statisticTypeOptions = ref<{ label: string; value: any }[]>([])
|
|||||||
|
|
||||||
//统计属性
|
//统计属性
|
||||||
const searchForm =reactive({
|
const searchForm =reactive({
|
||||||
type: null as string,
|
type:undefined as any,
|
||||||
timestamp: null as string
|
timestamp: undefined as any
|
||||||
})
|
})
|
||||||
|
|
||||||
const stats = reactive({
|
const stats = reactive({
|
||||||
|
|||||||
@ -396,13 +396,13 @@ const userStore = useUserStore()
|
|||||||
const hasPermission = (permission: string) => userStore.hasPermission(permission)
|
const hasPermission = (permission: string) => userStore.hasPermission(permission)
|
||||||
|
|
||||||
//判定状态字典
|
//判定状态字典
|
||||||
const qcResultTypeOptions = ref<{ label: string; value: any }[]>([])
|
const qcResultTypeOptions = ref<{ label: string; value: any;class:any }[]>([])
|
||||||
//业务来源字典
|
//业务来源字典
|
||||||
const qcSourceTypeOptions = ref<{ label: string; value: any }[]>([])
|
const qcSourceTypeOptions = ref<{ label: string; value: any;class:any }[]>([])
|
||||||
//质检状态字典
|
//质检状态字典
|
||||||
const qcStatusOptions = ref<{ label: string; value: any }[]>([])
|
const qcStatusOptions = ref<{ label: string; value: any;class:any }[]>([])
|
||||||
//派工状态字典
|
//派工状态字典
|
||||||
const qcAssingStatusOptions = ref<{ label: string; value: any }[]>([])
|
const qcAssingStatusOptions = ref<{ label: string; value: any;class:any }[]>([])
|
||||||
|
|
||||||
//汇报详情
|
//汇报详情
|
||||||
const infoIsCollapse = ref(false)
|
const infoIsCollapse = ref(false)
|
||||||
@ -418,7 +418,7 @@ const submitColumns = ref([
|
|||||||
{ title: "提交时间", key: "submitCreateTime" },
|
{ title: "提交时间", key: "submitCreateTime" },
|
||||||
{ title: "提交数量", key: "quantity" },
|
{ title: "提交数量", key: "quantity" },
|
||||||
{ title: "单据状态", key: "submitStatus",
|
{ title: "单据状态", key: "submitStatus",
|
||||||
render: (row) => {
|
render: (row:any) => {
|
||||||
const val = row.submitStatus
|
const val = row.submitStatus
|
||||||
const opt = qcResultTypeOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
const opt = qcResultTypeOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
if (!opt) return val ?? '-'
|
if (!opt) return val ?? '-'
|
||||||
|
|||||||
@ -199,7 +199,7 @@ const hasPermission = (permission: string) => userStore.hasPermission(permission
|
|||||||
|
|
||||||
|
|
||||||
//派工状态字典
|
//派工状态字典
|
||||||
const qcAssingStatusOptions = ref<{ label: string; value: any }[]>([])
|
const qcAssingStatusOptions = ref<{ label: string; value: any;class:any }[]>([])
|
||||||
|
|
||||||
// ==================== 搜索表单 ====================
|
// ==================== 搜索表单 ====================
|
||||||
const searchForm = reactive({
|
const searchForm = reactive({
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user