feat(mes): 新增工艺路线模块,支持路线、工序链、产品及 BOM 管理

从 ktg-mes 迁移工艺路线(pro-route)模块,包含:
- 4 个 DO:MesProRoute / RouteProcess / RouteProduct / RouteProductBom
- 4 个 Mapper 及 9 个 VO(含工序链 findPre/findNext 查询)
- 4 对 Service 接口+实现(链表重建、启用校验、级联删除)
- 4 个 Controller(路线 CRUD+导出,子资源复用父权限)
pull/871/MERGE
YunaiV 2026-02-19 16:39:43 +08:00
parent 20c6876f2c
commit e95d613965
12 changed files with 1191 additions and 12 deletions

View File

@ -0,0 +1,36 @@
import request from '@/config/axios'
// TODO @AI参考别的 api 文件,需要有一些注释
export interface ProRouteVO {
id?: number
code: string
name: string
description?: string
status: number
remark?: string
createTime?: Date
}
export const ProRouteApi = {
getRoutePage: async (params: any) => {
return await request.get({ url: `/mes/pro/route/page`, params })
},
getRouteSimpleList: async () => {
return await request.get({ url: `/mes/pro/route/simple-list` })
},
getRoute: async (id: number) => {
return await request.get({ url: `/mes/pro/route/get?id=` + id })
},
createRoute: async (data: ProRouteVO) => {
return await request.post({ url: `/mes/pro/route/create`, data })
},
updateRoute: async (data: ProRouteVO) => {
return await request.put({ url: `/mes/pro/route/update`, data })
},
deleteRoute: async (id: number) => {
return await request.delete({ url: `/mes/pro/route/delete?id=` + id })
},
exportRoute: async (params: any) => {
return await request.download({ url: `/mes/pro/route/export-excel`, params })
}
}

View File

@ -0,0 +1,39 @@
import request from '@/config/axios'
// TODO @AI参考别的 api 文件,需要有一些注释
export interface ProRouteProcessVO {
id?: number
routeId: number
processId: number
processCode?: string
processName?: string
sort: number
nextProcessId?: number
nextProcessName?: string
linkType: number
prepareTime?: number
waitTime?: number
colorCode?: string
keyFlag?: number
checkFlag?: number
remark?: string
createTime?: Date
}
export const ProRouteProcessApi = {
getRouteProcessListByRoute: async (routeId: number) => {
return await request.get({ url: `/mes/pro/route-process/list-by-route?routeId=` + routeId })
},
getRouteProcess: async (id: number) => {
return await request.get({ url: `/mes/pro/route-process/get?id=` + id })
},
createRouteProcess: async (data: ProRouteProcessVO) => {
return await request.post({ url: `/mes/pro/route-process/create`, data })
},
updateRouteProcess: async (data: ProRouteProcessVO) => {
return await request.put({ url: `/mes/pro/route-process/update`, data })
},
deleteRouteProcess: async (id: number) => {
return await request.delete({ url: `/mes/pro/route-process/delete?id=` + id })
}
}

View File

@ -0,0 +1,35 @@
import request from '@/config/axios'
// TODO @AI参考别的 api 文件,需要有一些注释
export interface ProRouteProductVO {
id?: number
routeId: number
itemId: number
itemCode?: string
itemName?: string
specification?: string
unitName?: string
quantity?: number
productionTime?: number
timeUnitType?: number
remark?: string
createTime?: Date
}
export const ProRouteProductApi = {
getRouteProductListByRoute: async (routeId: number) => {
return await request.get({ url: `/mes/pro/route-product/list-by-route?routeId=` + routeId })
},
getRouteProduct: async (id: number) => {
return await request.get({ url: `/mes/pro/route-product/get?id=` + id })
},
createRouteProduct: async (data: ProRouteProductVO) => {
return await request.post({ url: `/mes/pro/route-product/create`, data })
},
updateRouteProduct: async (data: ProRouteProductVO) => {
return await request.put({ url: `/mes/pro/route-product/update`, data })
},
deleteRouteProduct: async (id: number) => {
return await request.delete({ url: `/mes/pro/route-product/delete?id=` + id })
}
}

View File

@ -0,0 +1,35 @@
import request from '@/config/axios'
// TODO @AI参考别的 api 文件,需要有一些注释
export interface ProRouteProductBomVO {
id?: number
routeId: number
processId: number
productId: number
itemId: number
itemCode?: string
itemName?: string
specification?: string
unitName?: string
quantity?: number
remark?: string
createTime?: Date
}
export const ProRouteProductBomApi = {
getRouteProductBomList: async (params: { routeId: number; processId?: number; productId?: number }) => {
return await request.get({ url: `/mes/pro/route-product-bom/list`, params })
},
getRouteProductBom: async (id: number) => {
return await request.get({ url: `/mes/pro/route-product-bom/get?id=` + id })
},
createRouteProductBom: async (data: ProRouteProductBomVO) => {
return await request.post({ url: `/mes/pro/route-product-bom/create`, data })
},
updateRouteProductBom: async (data: ProRouteProductBomVO) => {
return await request.put({ url: `/mes/pro/route-product-bom/update`, data })
},
deleteRouteProductBom: async (id: number) => {
return await request.delete({ url: `/mes/pro/route-product-bom/delete?id=` + id })
}
}

View File

@ -19,12 +19,14 @@
<Dialog :title="dialogTitle" v-model="dialogVisible" width="500px">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px" v-loading="formLoading">
<el-form-item label="班组" prop="teamId">
<!-- TODO @芋艿对接班组下拉列表 cal_team 迁移后对接 -->
<el-input-number
v-model="formData.teamId"
placeholder="请输入班组编号"
class="!w-1/1"
/>
<el-select v-model="formData.teamId" placeholder="请选择班组" class="!w-1/1">
<el-option
v-for="team in teamList"
:key="team.id"
:label="team.name"
:value="team.id"
/>
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" />

View File

@ -0,0 +1,164 @@
<!-- MES 工艺路线表单 -->
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="960px">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="编码" prop="code">
<el-input v-model="formData.code" placeholder="请输入编码">
<template #append>
<el-button @click="generateCode" :disabled="formType === 'update'">
生成
</el-button>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="说明" prop="description">
<el-input
v-model="formData.description"
type="textarea"
:rows="3"
placeholder="请输入工艺路线说明"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
<!-- 编辑时展示 Tab -->
<template v-if="formData.id">
<el-divider content-position="left">详细信息</el-divider>
<el-tabs v-model="activeTab">
<el-tab-pane label="组成工序" name="process">
<RouteProcessList :routeId="formData.id" />
</el-tab-pane>
<el-tab-pane label="关联产品" name="product">
<RouteProductList :routeId="formData.id" />
</el-tab-pane>
</el-tabs>
</template>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="submitForm" :loading="formLoading"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { generateRandomStr } from '@/utils'
import { ProRouteApi, ProRouteVO } from '@/api/mes/pro/route'
import RouteProcessList from './RouteProcessList.vue'
import RouteProductList from './RouteProductList.vue'
defineOptions({ name: 'RouteForm' })
// TODO @AI /Users/yunai/Java/yudao-all-in-one/yudao-ui-admin-vue3/src/views/mes/qc/template/TemplateForm.vue
const { t } = useI18n()
const message = useMessage()
const dialogVisible = ref(false)
const dialogTitle = ref('')
const formLoading = ref(false)
const formType = ref('')
const activeTab = ref('process')
const formData = ref<ProRouteVO>({
id: undefined,
code: '',
name: '',
description: '',
status: 0, // TODO @AI
remark: ''
})
const formRules = reactive({
code: [{ required: true, message: '编码不能为空', trigger: 'blur' }],
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
})
const formRef = ref()
/** 生成编码 */
const generateCode = () => {
formData.value.code = 'ROUTE' + generateRandomStr(8)
}
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = type === 'create' ? '新增工艺路线' : '编辑工艺路线'
formType.value = type
activeTab.value = 'process'
resetForm()
if (id) {
formLoading.value = true
try {
formData.value = await ProRouteApi.getRoute(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open })
/** 提交表单 */
const emit = defineEmits(['success'])
const submitForm = async () => {
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
formLoading.value = true
try {
const data = { ...formData.value }
if (formType.value === 'create') {
await ProRouteApi.createRoute(data)
message.success(t('common.createSuccess'))
} else {
await ProRouteApi.updateRoute(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
code: '',
name: '',
description: '',
status: 0,
remark: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,262 @@
<!-- MES 工艺路线工序列表 -->
<template>
<div>
<!-- 操作栏 -->
<el-row class="mb-10px">
<el-button type="primary" plain @click="openForm('create')">
<Icon icon="ep:plus" class="mr-5px" /> 添加工序
</el-button>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="序号" align="center" prop="sort" width="70" />
<el-table-column label="工序编码" align="center" prop="processCode" width="120" />
<el-table-column label="工序名称" align="center" prop="processName" width="120" />
<el-table-column label="下一道工序" align="center" prop="nextProcessName" width="120" />
<el-table-column label="工序关系" align="center" prop="linkType" width="130">
<template #default="scope">
<dict-tag :type="DICT_TYPE.MES_PRO_LINK_TYPE" :value="scope.row.linkType" />
</template>
</el-table-column>
<el-table-column label="关键工序" align="center" prop="keyFlag" width="80">
<template #default="scope">
<el-tag :type="scope.row.keyFlag ? 'danger' : 'info'" size="small">
{{ scope.row.keyFlag ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="质检工序" align="center" prop="checkFlag" width="80">
<template #default="scope">
<el-tag :type="scope.row.checkFlag ? 'warning' : 'info'" size="small">
{{ scope.row.checkFlag ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="准备时间" align="center" prop="prepareTime" width="90">
<template #default="scope">
{{ scope.row.prepareTime ? scope.row.prepareTime + '分钟' : '' }}
</template>
</el-table-column>
<el-table-column label="等待时间" align="center" prop="waitTime" width="90">
<template #default="scope">
{{ scope.row.waitTime ? scope.row.waitTime + '分钟' : '' }}
</template>
</el-table-column>
<el-table-column label="颜色" align="center" prop="colorCode" width="70">
<template #default="scope">
<div
v-if="scope.row.colorCode"
:style="{
backgroundColor: scope.row.colorCode,
width: '20px',
height: '20px',
borderRadius: '4px',
margin: '0 auto'
}"
></div>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="130" fixed="right">
<template #default="scope">
<el-button link type="primary" @click="openForm('update', scope.row)">编辑</el-button>
<el-button link type="danger" @click="handleDelete(scope.row.id)"></el-button>
</template>
</el-table-column>
</el-table>
<!-- 表单弹窗添加/修改 -->
<Dialog :title="formTitle" v-model="formVisible" width="600px">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="序号" prop="sort">
<el-input-number v-model="formData.sort" :min="1" controls-position="right" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="工序" prop="processId">
<el-select v-model="formData.processId" placeholder="请选择工序" filterable>
<el-option
v-for="item in processList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="工序关系" prop="linkType">
<el-select v-model="formData.linkType" placeholder="请选择">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.MES_PRO_LINK_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="颜色" prop="colorCode">
<el-color-picker v-model="formData.colorCode" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="关键工序" prop="keyFlag">
<el-switch v-model="formData.keyFlag" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="质检工序" prop="checkFlag">
<el-switch v-model="formData.checkFlag" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="准备时间" prop="prepareTime">
<el-input-number v-model="formData.prepareTime" :min="0" controls-position="right" />
<span class="ml-5px">分钟</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="等待时间" prop="waitTime">
<el-input-number v-model="formData.waitTime" :min="0" controls-position="right" />
<span class="ml-5px">分钟</span>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="formVisible = false"> </el-button>
<el-button type="primary" @click="submitForm" :loading="formLoading"> </el-button>
</template>
</Dialog>
</div>
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { ProRouteProcessApi, ProRouteProcessVO } from '@/api/mes/pro/route/process'
import { ProProcessApi } from '@/api/mes/pro/process'
// TODO @AI /Users/yunai/Java/yudao-all-in-one/yudao-ui-admin-vue3/src/views/mes/qc/template/TemplateIndicatorList.vue
defineOptions({ name: 'RouteProcessList' })
const props = defineProps<{
routeId: number
}>()
const message = useMessage()
const { t } = useI18n()
const loading = ref(false)
const list = ref<ProRouteProcessVO[]>([])
const processList = ref<any[]>([])
//
const formVisible = ref(false)
const formTitle = ref('')
const formLoading = ref(false)
const formType = ref('')
const formRef = ref()
const formData = ref<any>({})
const formRules = reactive({
sort: [{ required: true, message: '序号不能为空', trigger: 'blur' }],
processId: [{ required: true, message: '工序不能为空', trigger: 'change' }],
linkType: [{ required: true, message: '工序关系不能为空', trigger: 'change' }]
})
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
list.value = await ProRouteProcessApi.getRouteProcessListByRoute(props.routeId)
} finally {
loading.value = false
}
}
/** 加载工序列表 */
const loadProcessList = async () => {
processList.value = await ProProcessApi.getProcessSimpleList()
}
/** 添加/修改操作 */
const openForm = (type: string, row?: ProRouteProcessVO) => {
formVisible.value = true
formTitle.value = type === 'create' ? '添加工序' : '编辑工序'
formType.value = type
if (type === 'create') {
const maxSort = list.value.reduce((max, item) => Math.max(max, item.sort || 0), 0)
formData.value = {
routeId: props.routeId,
sort: maxSort + 1,
linkType: 3,
colorCode: '#00AEF3',
keyFlag: false,
checkFlag: false,
prepareTime: 0,
waitTime: 0
}
} else {
formData.value = { ...row }
}
formRef.value?.resetFields()
}
/** 提交表单 */
const submitForm = async () => {
const valid = await formRef.value.validate()
if (!valid) return
formLoading.value = true
try {
if (formType.value === 'create') {
await ProRouteProcessApi.createRouteProcess(formData.value)
message.success(t('common.createSuccess'))
} else {
await ProRouteProcessApi.updateRouteProcess(formData.value)
message.success(t('common.updateSuccess'))
}
formVisible.value = false
await getList()
} finally {
formLoading.value = false
}
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
await message.delConfirm()
await ProRouteProcessApi.deleteRouteProcess(id)
message.success(t('common.delSuccess'))
await getList()
} catch {}
}
/** 监听路线编号变化 */
watch(
() => props.routeId,
(val) => {
if (val) {
getList()
}
},
{ immediate: true }
)
/** 初始化 */
onMounted(() => {
loadProcessList()
})
</script>

View File

@ -0,0 +1,191 @@
<!-- MES 工艺路线产品 BOM 列表 -->
<template>
<div>
<el-tabs v-model="activeProcessId" @tab-change="handleTabChange">
<el-tab-pane
v-for="item in processList"
:key="item.processId"
:label="item.processName"
:name="String(item.processId)"
/>
</el-tabs>
<!-- 操作栏 -->
<el-row class="mb-10px">
<el-button type="primary" plain @click="openForm('create')" :disabled="!activeProcessId">
<Icon icon="ep:plus" class="mr-5px" /> 添加 BOM 物料
</el-button>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="bomList" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="物料编码" align="center" prop="itemCode" width="150" />
<el-table-column label="物料名称" align="center" prop="itemName" width="150" />
<el-table-column label="规格型号" align="center" prop="specification" width="150" />
<el-table-column label="单位" align="center" prop="unitName" width="80" />
<el-table-column label="用料比例" align="center" prop="quantity" width="100" />
<el-table-column label="备注" align="center" prop="remark" min-width="120" />
<el-table-column label="操作" align="center" width="130" fixed="right">
<template #default="scope">
<el-button link type="primary" @click="openForm('update', scope.row)">编辑</el-button>
<el-button link type="danger" @click="handleDelete(scope.row.id)"></el-button>
</template>
</el-table-column>
</el-table>
<!-- 表单弹窗 -->
<Dialog :title="formTitle" v-model="formVisible" width="500px">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
<el-form-item label="BOM 物料" prop="itemId">
<el-select v-model="formData.itemId" placeholder="请选择 BOM 物料" filterable>
<el-option
v-for="item in itemList"
:key="item.id"
:label="item.name + (item.code ? ' (' + item.code + ')' : '')"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="用料比例" prop="quantity">
<el-input-number
v-model="formData.quantity"
:min="0"
:precision="2"
controls-position="right"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="formVisible = false"> </el-button>
<el-button type="primary" @click="submitForm" :loading="formLoading"> </el-button>
</template>
</Dialog>
</div>
</template>
<script setup lang="ts">
import { ProRouteProductBomApi, ProRouteProductBomVO } from '@/api/mes/pro/route/productbom'
import { ProRouteProcessApi } from '@/api/mes/pro/route/process'
import { MdItemApi } from '@/api/mes/md/item'
defineOptions({ name: 'RouteProductBomList' })
// TODO @AI /Users/yunai/Java/yudao-all-in-one/yudao-ui-admin-vue3/src/views/mes/qc/template/TemplateIndicatorList.vue
const props = defineProps<{
routeId: number
productId: number
productName?: string
}>()
const message = useMessage()
const { t } = useI18n()
const loading = ref(false)
const bomList = ref<ProRouteProductBomVO[]>([])
const processList = ref<any[]>([])
const activeProcessId = ref('')
const itemList = ref<any[]>([])
//
const formVisible = ref(false)
const formTitle = ref('')
const formLoading = ref(false)
const formType = ref('')
const formRef = ref()
const formData = ref<any>({})
const formRules = reactive({
itemId: [{ required: true, message: 'BOM 物料不能为空', trigger: 'change' }],
quantity: [{ required: true, message: '用料比例不能为空', trigger: 'blur' }]
})
/** 查询工序列表 */
const loadProcessList = async () => {
const list = await ProRouteProcessApi.getRouteProcessListByRoute(props.routeId)
processList.value = list
if (list.length > 0) {
activeProcessId.value = String(list[0].processId)
await getBomList()
}
}
/** 查询 BOM 列表 */
const getBomList = async () => {
if (!activeProcessId.value) return
loading.value = true
try {
bomList.value = await ProRouteProductBomApi.getRouteProductBomList({
routeId: props.routeId,
processId: activeProcessId.value,
productId: props.productId
})
} finally {
loading.value = false
}
}
/** 切换工序 Tab */
const handleTabChange = () => {
getBomList()
}
/** 加载物料列表 */
const loadItemList = async () => {
itemList.value = await MdItemApi.getItemSimpleList()
}
/** 添加/修改操作 */
const openForm = (type: string, row?: ProRouteProductBomVO) => {
formVisible.value = true
formTitle.value = type === 'create' ? '添加 BOM 物料' : '编辑 BOM 物料'
formType.value = type
if (type === 'create') {
formData.value = {
routeId: props.routeId,
processId: Number(activeProcessId.value),
productId: props.productId,
quantity: 1
}
} else {
formData.value = { ...row }
}
formRef.value?.resetFields()
}
/** 提交表单 */
const submitForm = async () => {
const valid = await formRef.value.validate()
if (!valid) return
formLoading.value = true
try {
if (formType.value === 'create') {
await ProRouteProductBomApi.createRouteProductBom(formData.value)
message.success(t('common.createSuccess'))
} else {
await ProRouteProductBomApi.updateRouteProductBom(formData.value)
message.success(t('common.updateSuccess'))
}
formVisible.value = false
await getBomList()
} finally {
formLoading.value = false
}
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
await message.delConfirm()
await ProRouteProductBomApi.deleteRouteProductBom(id)
message.success(t('common.delSuccess'))
await getBomList()
} catch {}
}
/** 初始化 */
onMounted(() => {
loadProcessList()
loadItemList()
})
</script>

View File

@ -0,0 +1,217 @@
<!-- MES 工艺路线产品列表 -->
<template>
<div>
<!-- 操作栏 -->
<el-row class="mb-10px">
<el-button type="primary" plain @click="openForm('create')">
<Icon icon="ep:plus" class="mr-5px" /> 关联产品
</el-button>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="产品编码" align="center" prop="itemCode" width="150" />
<el-table-column label="产品名称" align="center" prop="itemName" width="150" />
<el-table-column label="规格型号" align="center" prop="specification" width="150" />
<el-table-column label="单位" align="center" prop="unitName" width="80" />
<el-table-column label="生产数量" align="center" prop="quantity" width="100" />
<el-table-column label="生产用时" align="center" width="120">
<template #default="scope">
<span v-if="scope.row.productionTime">
{{ scope.row.productionTime }}
<dict-tag :type="DICT_TYPE.MES_TIME_UNIT_TYPE" :value="scope.row.timeUnitType" />
</span>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" min-width="120" />
<el-table-column label="操作" align="center" width="180" fixed="right">
<template #default="scope">
<el-button link type="primary" @click="openBomDialog(scope.row)">BOM</el-button>
<el-button link type="primary" @click="openForm('update', scope.row)">编辑</el-button>
<el-button link type="danger" @click="handleDelete(scope.row.id)"></el-button>
</template>
</el-table-column>
</el-table>
<!-- 表单弹窗添加/修改 -->
<Dialog :title="formTitle" v-model="formVisible" width="500px">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
<el-form-item label="产品" prop="itemId">
<el-select v-model="formData.itemId" placeholder="请选择产品" filterable>
<el-option
v-for="item in itemList"
:key="item.id"
:label="item.name + (item.code ? ' (' + item.code + ')' : '')"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="生产数量" prop="quantity">
<el-input-number v-model="formData.quantity" :min="1" controls-position="right" />
</el-form-item>
<el-row :gutter="20">
<el-col :span="14">
<el-form-item label="生产用时" prop="productionTime">
<el-input-number
v-model="formData.productionTime"
:min="0"
:precision="2"
controls-position="right"
/>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="时间单位" prop="timeUnitType">
<el-select v-model="formData.timeUnitType" placeholder="请选择">
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.MES_TIME_UNIT_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="formVisible = false"> </el-button>
<el-button type="primary" @click="submitForm" :loading="formLoading"> </el-button>
</template>
</Dialog>
<!-- 产品 BOM 弹窗 -->
<Dialog title="产品 BOM 配置" v-model="bomDialogVisible" width="800px">
<RouteProductBomList
v-if="bomDialogVisible"
:routeId="routeId"
:productId="currentProduct.itemId"
:productName="currentProduct.itemName"
/>
</Dialog>
</div>
</template>
<script setup lang="ts">
import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
import { ProRouteProductApi, ProRouteProductVO } from '@/api/mes/pro/route/product'
import { MdItemApi } from '@/api/mes/md/item'
import RouteProductBomList from './RouteProductBomList.vue'
defineOptions({ name: 'RouteProductList' })
const props = defineProps<{
routeId: number
}>()
// TODO @AI /Users/yunai/Java/yudao-all-in-one/yudao-ui-admin-vue3/src/views/mes/qc/template/TemplateIndicatorList.vue
const message = useMessage()
const { t } = useI18n()
const loading = ref(false)
const list = ref<ProRouteProductVO[]>([])
const itemList = ref<any[]>([])
//
const formVisible = ref(false)
const formTitle = ref('')
const formLoading = ref(false)
const formType = ref('')
const formRef = ref()
const formData = ref<any>({})
const formRules = reactive({
itemId: [{ required: true, message: '产品不能为空', trigger: 'change' }]
})
// BOM
const bomDialogVisible = ref(false)
const currentProduct = ref<any>({})
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
list.value = await ProRouteProductApi.getRouteProductListByRoute(props.routeId)
} finally {
loading.value = false
}
}
/** 加载物料列表 */
const loadItemList = async () => {
itemList.value = await MdItemApi.getItemSimpleList()
}
/** 添加/修改操作 */
const openForm = (type: string, row?: ProRouteProductVO) => {
formVisible.value = true
formTitle.value = type === 'create' ? '关联产品' : '编辑产品'
formType.value = type
if (type === 'create') {
formData.value = {
routeId: props.routeId,
quantity: 1,
productionTime: 1,
timeUnitType: 'MINUTE'
}
} else {
formData.value = { ...row }
}
formRef.value?.resetFields()
}
/** 提交表单 */
const submitForm = async () => {
const valid = await formRef.value.validate()
if (!valid) return
formLoading.value = true
try {
if (formType.value === 'create') {
await ProRouteProductApi.createRouteProduct(formData.value)
message.success(t('common.createSuccess'))
} else {
await ProRouteProductApi.updateRouteProduct(formData.value)
message.success(t('common.updateSuccess'))
}
formVisible.value = false
await getList()
} finally {
formLoading.value = false
}
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
await message.delConfirm()
await ProRouteProductApi.deleteRouteProduct(id)
message.success(t('common.delSuccess'))
await getList()
} catch {}
}
/** 打开 BOM 弹窗 */
const openBomDialog = (row: ProRouteProductVO) => {
currentProduct.value = row
bomDialogVisible.value = true
}
/** 监听路线编号变化 */
watch(
() => props.routeId,
(val) => {
if (val) {
getList()
}
},
{ immediate: true }
)
/** 初始化 */
onMounted(() => {
loadItemList()
})
</script>

View File

@ -0,0 +1,201 @@
<!-- MES 工艺路线列表 -->
<!-- TODO @AI对齐下字段前缀需要加工艺路线不限于编码名称说明 -->
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="编码" prop="code">
<el-input
v-model="queryParams.code"
placeholder="请输入编码"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"> <Icon icon="ep:search" class="mr-5px" /> 搜索 </el-button>
<el-button @click="resetQuery"> <Icon icon="ep:refresh" class="mr-5px" /> 重置 </el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['mes:pro-route:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['mes:pro-route:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="编码" align="center" prop="code" width="180" />
<el-table-column label="名称" align="center" prop="name" width="200" />
<el-table-column label="说明" align="center" prop="description" min-width="200" />
<el-table-column label="状态" align="center" prop="status" width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" min-width="120" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center" width="150">
<template #default="scope">
<!-- TODO @AI增加一个启用禁用操作 -->
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['mes:pro-route:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['mes:pro-route:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<RouteForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { ProRouteApi, ProRouteVO } from '@/api/mes/pro/route'
import RouteForm from './RouteForm.vue'
defineOptions({ name: 'MesProRoute' })
const message = useMessage()
const { t } = useI18n()
const loading = ref(true)
const list = ref<ProRouteVO[]>([])
const total = ref(0)
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
code: undefined,
name: undefined,
status: undefined
})
const queryFormRef = ref()
const exportLoading = ref(false)
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ProRouteApi.getRoutePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
await message.delConfirm()
await ProRouteApi.deleteRoute(id)
message.success(t('common.delSuccess'))
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
await message.exportConfirm()
exportLoading.value = true
const data = await ProRouteApi.exportRoute(queryParams)
download.excel(data, '工艺路线.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(async () => {
await getList()
})
</script>

View File

@ -2,6 +2,7 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="900px">
<!-- 基本信息表单 -->
<!-- TODO @AIrules 有linter报错 -->
<el-form
ref="formRef"
:model="formData"
@ -142,6 +143,7 @@ const open = async (type: string, id?: number) => {
const data = await QcTemplateApi.getTemplate(id)
formData.value = {
...data,
// TODO @AI types
types: data.types ?? []
}
} finally {

View File

@ -83,12 +83,7 @@
<!-- 列表 -->
<ContentWrap>
<el-table
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="方案编号" align="center" prop="code" width="150" />
<el-table-column label="方案名称" align="center" prop="name" min-width="200" />
<el-table-column label="检测种类" align="center" prop="types" min-width="200">