feat(mes): 新增生产排产(pro_task)前端页面

- 新增生产任务 API(task + task_issue)和 VO 定义
- 新增排产主页面:甘特图预览 + 待排产工单列表 + 排产/进度 Drawer
- 新增任务 CRUD 列表(ProTaskList)、任务表单(ProTaskForm)
- 新增 dhtmlx-gantt 甘特图组件封装(GanttChart)和全屏编辑(GanttEdit)
- 新增生产进度圆形进度条展示(ProTaskProgress)
- 新增任务投料列表(ProTaskIssueList,预留)
- 新增工作站下拉选择器(MdWorkstationSelect)
- 新增 MES_PRO_TASK_STATUS 字典和 MesProTaskStatusEnum 枚举
- 安装 dhtmlx-gantt 依赖
pull/871/MERGE
YunaiV 2026-02-20 08:57:56 +08:00
parent 2740f3d26b
commit 3c3bf1b13b
3 changed files with 32 additions and 75 deletions

View File

@ -1,46 +0,0 @@
<!-- 库位详情弹窗只读 -->
<!-- TODO @AI复用 /Users/yunai/Java/yudao-all-in-one/yudao-ui-admin-vue3/src/views/mes/wm/warehouse/area/AreaForm.vue需要对方组件增加一个 detail 模式 -->
<template>
<Dialog v-model="dialogVisible" title="库位详情" width="600px">
<el-descriptions :column="2" border>
<el-descriptions-item label="库位编码">{{ detailData.code }}</el-descriptions-item>
<el-descriptions-item label="库位名称">{{ detailData.name }}</el-descriptions-item>
<el-descriptions-item label="面积(㎡)">{{ detailData.area }}</el-descriptions-item>
<el-descriptions-item label="最大载重kg">{{ detailData.maxLoad }}</el-descriptions-item>
<el-descriptions-item label="允许混放物料">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="detailData.allowItemMixing" />
</el-descriptions-item>
<el-descriptions-item label="允许混放批次">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="detailData.allowBatchMixing" />
</el-descriptions-item>
<el-descriptions-item label="位置X">{{ detailData.positionX }}</el-descriptions-item>
<el-descriptions-item label="位置Y">{{ detailData.positionY }}</el-descriptions-item>
<el-descriptions-item label="位置Z">{{ detailData.positionZ }}</el-descriptions-item>
<el-descriptions-item label="状态">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="detailData.status" />
</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ detailData.remark }}</el-descriptions-item>
</el-descriptions>
<template #footer>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE } from '@/utils/dict'
import { WmWarehouseAreaApi, WmWarehouseAreaVO } from '@/api/mes/wm/warehouse/area'
defineOptions({ name: 'MesWmMaterialStockAreaDetailDialog' })
const dialogVisible = ref(false)
const detailData = ref<Partial<WmWarehouseAreaVO>>({})
/** 打开弹窗 */
const open = async (areaId: number) => {
dialogVisible.value = true
detailData.value = await WmWarehouseAreaApi.getWarehouseArea(areaId)
}
defineExpose({ open })
</script>

View File

@ -1,5 +1,4 @@
<!-- MES 库存台账列表 -->
<!-- TODO @AI包名应该小写 -->
<template>
<el-row :gutter="20">
<!-- 左侧分类树 -->
@ -98,8 +97,6 @@
</el-form>
</ContentWrap>
<!-- TODO @AI左侧分类看看能不能复用下 md item 那的组件 -->
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
@ -156,26 +153,25 @@
</el-row>
<!-- 库位详情弹窗 -->
<AreaDetailDialog ref="areaDetailDialogRef" />
<AreaForm ref="areaFormRef" />
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { WmMaterialStockApi, WmMaterialStockVO } from '@/api/mes/wm/materialStock'
import { WmMaterialStockApi, WmMaterialStockVO } from '@/api/mes/wm/materialstock'
import { WmWarehouseApi } from '@/api/mes/wm/warehouse'
import { WmWarehouseLocationApi } from '@/api/mes/wm/warehouse/location'
import ItemTypeTree from '@/views/mes/md/item/ItemTypeTree.vue'
import AreaDetailDialog from './AreaDetailDialog.vue'
import AreaForm from '@/views/mes/wm/warehouse/area/AreaForm.vue'
defineOptions({ name: 'MesWmMaterialStock' })
// TODO @AI system user index.vue
const message = useMessage()
const message = useMessage() //
const loading = ref(true)
const list = ref<WmMaterialStockVO[]>([])
const total = ref(0)
const loading = ref(true) //
const list = ref<WmMaterialStockVO[]>([]) //
const total = ref(0) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
@ -186,8 +182,8 @@ const queryParams = reactive({
locationId: undefined,
frozen: undefined
})
const queryFormRef = ref()
const exportLoading = ref(false)
const queryFormRef = ref() //
const exportLoading = ref(false) //
//
const warehouseList = ref<any[]>([])
@ -252,9 +248,9 @@ const handleFrozenChange = async (row: WmMaterialStockVO) => {
}
/** 打开库位详情弹窗 */
const areaDetailDialogRef = ref()
const areaFormRef = ref()
const openAreaDetail = (areaId: number) => {
areaDetailDialogRef.value.open(areaId)
areaFormRef.value.open('detail', areaId)
}
/** 导出按钮操作 */

View File

@ -3,7 +3,7 @@
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
:rules="isDetail ? {} : formRules"
label-width="120px"
v-loading="formLoading"
>
@ -14,6 +14,7 @@
v-model="selectedWarehouseId"
placeholder="请选择仓库"
class="!w-1/1"
:disabled="isDetail"
@change="handleWarehouseChange"
>
<el-option
@ -31,7 +32,7 @@
v-model="formData.locationId"
placeholder="请选择库区"
class="!w-1/1"
:disabled="!selectedWarehouseId"
:disabled="isDetail || !selectedWarehouseId"
>
<el-option
v-for="location in locationList"
@ -44,14 +45,14 @@
</el-col>
<el-col :span="8">
<el-form-item label="库位编码" prop="code">
<el-input v-model="formData.code" placeholder="请输入库位编码" />
<el-input v-model="formData.code" placeholder="请输入库位编码" :disabled="isDetail" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="库位名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入库位名称" />
<el-input v-model="formData.name" placeholder="请输入库位名称" :disabled="isDetail" />
</el-form-item>
</el-col>
<el-col :span="8">
@ -62,6 +63,7 @@
:min="0"
controls-position="right"
class="!w-1/1"
:disabled="isDetail"
/>
</el-form-item>
</el-col>
@ -73,6 +75,7 @@
:min="0"
controls-position="right"
class="!w-1/1"
:disabled="isDetail"
/>
</el-form-item>
</el-col>
@ -85,6 +88,7 @@
:min="0"
controls-position="right"
class="!w-1/1"
:disabled="isDetail"
/>
</el-form-item>
</el-col>
@ -95,6 +99,7 @@
:min="0"
controls-position="right"
class="!w-1/1"
:disabled="isDetail"
/>
</el-form-item>
</el-col>
@ -105,6 +110,7 @@
:min="0"
controls-position="right"
class="!w-1/1"
:disabled="isDetail"
/>
</el-form-item>
</el-col>
@ -112,7 +118,7 @@
<el-row>
<el-col :span="8">
<el-form-item label="状态" prop="status">
<el-select v-model="formData.status" placeholder="请选择" class="!w-1/1">
<el-select v-model="formData.status" placeholder="请选择" class="!w-1/1" :disabled="isDetail">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
@ -124,34 +130,34 @@
</el-col>
<el-col :span="8">
<el-form-item label="是否冻结" prop="frozen">
<el-switch v-model="formData.frozen" />
<el-switch v-model="formData.frozen" :disabled="isDetail" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="允许物料混放" prop="allowItemMixing">
<el-switch v-model="formData.allowItemMixing" />
<el-switch v-model="formData.allowItemMixing" :disabled="isDetail" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="允许批次混放" prop="allowBatchMixing">
<el-switch v-model="formData.allowBatchMixing" />
<el-switch v-model="formData.allowBatchMixing" :disabled="isDetail" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" />
<el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" :disabled="isDetail" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<!-- TODO @芋艿barcodeimg -->
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
<el-button v-if="!isDetail" @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false">{{ isDetail ? '关 闭' : '取 消' }}</el-button>
</template>
</Dialog>
</template>
@ -171,7 +177,8 @@ const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formType = ref('') // create - update - detail -
const isDetail = computed(() => formType.value === 'detail') //
const selectedWarehouseId = ref<number | undefined>(undefined) // ID
const warehouseList = ref<WmWarehouseVO[]>([]) //
const locationList = ref<WmWarehouseLocationVO[]>([]) //
@ -225,11 +232,11 @@ const open = async (
defaultWarehouseId?: number
) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
dialogTitle.value = type === 'detail' ? '库位详情' : t('action.' + type)
formType.value = type
resetForm()
warehouseList.value = await WmWarehouseApi.getWarehouseSimpleList()
//
// /
if (id) {
formLoading.value = true
try {