You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
besure_web/src/views/mes/plan/index.vue

793 lines
31 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="auto">
<el-form-item :label="t('ProductionPlan.Plan.searchTaskLabel')" prop="taskId">
<el-select v-model="queryParams.taskId" @change="handleQuery" clearable filterable
:placeholder="t('ProductionPlan.Plan.searchTaskPlaceholder')" class="!w-180px">
<el-option v-for="item in taskList" :key="item.id" :label="item.code" :value="String(item.id)" />
</el-select>
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.searchCodeLabel')" prop="code">
<el-input v-model="queryParams.code" :placeholder="t('ProductionPlan.Plan.searchCodePlaceholder')" clearable
@keyup.enter="handleQuery" class="!w-180px" />
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.searchProductLabel')" prop="productId">
<el-select v-model="queryParams.productId" @change="handleQuery" clearable filterable
:placeholder="t('ProductionPlan.Plan.searchProductPlaceholder')" class="!w-180px">
<el-option v-for="item in productList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.searchRemarkLabel')" prop="remark" v-show="showAllFilters">
<el-input v-model="queryParams.remark" :placeholder="t('ProductionPlan.Plan.searchRemarkPlaceholder')" clearable
@keyup.enter="handleQuery" class="!w-180px" />
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.searchPlanStartLabel')" prop="planStartTime" v-show="showAllFilters">
<el-date-picker v-model="queryParams.planStartTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
@change="handleQuery" :start-placeholder="t('ProductionPlan.Plan.searchPlanStartStartPlaceholder')"
:end-placeholder="t('ProductionPlan.Plan.searchPlanStartEndPlaceholder')"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" />
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.searchPlanEndLabel')" prop="planEndTime" v-show="showAllFilters">
<el-date-picker v-model="queryParams.planEndTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
@change="handleQuery" :start-placeholder="t('ProductionPlan.Plan.searchPlanEndStartPlaceholder')"
:end-placeholder="t('ProductionPlan.Plan.searchPlanEndEndPlaceholder')"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" />
</el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') :
t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> {{ t('ProductionPlan.Plan.buttonSearchText') }}
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" /> {{ t('ProductionPlan.Plan.buttonResetText') }}
</el-button>
<!-- <el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['mes:plan:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button> -->
<el-button type="success" plain @click="handleExport" :loading="exportLoading" v-hasPermi="['mes:plan:export']">
<Icon icon="ep:download" class="mr-5px" /> {{ t('ProductionPlan.Plan.buttonExportText') }}
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-tabs v-model="activeName" @tab-click="handleTabClick">
<el-tab-pane v-for="item in planStatusTabs" :key="item.value" :label="item.label" :name="item.value" />
</el-tabs>
<el-table ref="tableRef" v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"
:max-height="planTableMaxHeight" row-key="id">
<el-table-column :label="t('ProductionPlan.Plan.tableCodeColumn')" align="center" prop="code" min-width="180px"
sortable />
<el-table-column :label="t('ProductionPlan.Plan.tableTaskCodeColumn')" align="center" prop="taskCode"
min-width="150px" sortable />
<el-table-column :label="t('ProductionPlan.Plan.tableProductColumn')" align="center" prop="productName"
min-width="200px" sortable />
<el-table-column :label="t('ProductionPlan.Plan.dialogProcessRouteLabel')" align="left"
sortable min-width="200px">
<template #default="scope">
<div class="process-device-cell">
<div v-for="(item, index) in getProcessDeviceLines(scope.row)" :key="index">
{{ item }}
</div>
</div>
</template>
</el-table-column>
<el-table-column :label="t('ProductionPlan.Plan.tablePlanNumberColumn')" align="center" prop="planNumber"
sortable min-width="100px"/>
<el-table-column :label="t('ProductionPlan.Plan.tableFinishNumberColumn')" align="center" prop="passNumber"
sortable min-width="100px"/>
<el-table-column :label="t('ProductionPlan.Plan.tablePassRateColumn')" align="center" min-width="110px"
sortable="custom">
<template #default="scope">
<div class="pass-rate-cell">
<el-progress type="circle" :percentage="Number(scope.row.passRate ?? 0)" :width="40" :stroke-width="4"
:show-text="false" :color="Number(scope.row.passRate ?? 0) === 100 ? '#67c23a' : undefined" />
<span class="pass-rate-text">{{ Number(scope.row.passRate ?? 0) }}%</span>
</div>
</template>
</el-table-column>
<!-- <el-table-column label="热压数量" align="center" prop="reyaNumber" /> -->
<el-table-column :label="t('ProductionPlan.Plan.tableStatusColumn')" align="center" prop="status" sortable min-width="90px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.MES_PLAN_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<!-- <el-table-column label="班别" align="center" prop="groupType" sortable>
<template #default="scope">
<dict-tag :type="DICT_TYPE.MES_GROUP_TYPE" :value="scope.row.groupType" />
</template>
</el-table-column> -->
<!-- <el-table-column label="领料人" align="center" prop="productionManagerName" sortable /> -->
<el-table-column :label="t('ProductionPlan.Plan.tablePlanStartTimeColumn')" align="center" prop="planStartTime"
:formatter="dateFormatter2" width="150px" sortable />
<el-table-column :label="t('ProductionPlan.Plan.tablePlanEndTimeColumn')" align="center" prop="planEndTime"
:formatter="dateFormatter2" width="150px" sortable />
<el-table-column :label="t('ProductionPlan.Plan.tableIsZjColumn')" align="center" prop="isZj"
v-if="activeName === '6'">
<template #default="scope">
<dict-tag :type="DICT_TYPE.MES_ZJ_PRODUCT" :value="scope.row.isZj" />
</template>
</el-table-column>
<!-- <el-table-column :label="t('ProductionPlan.Plan.tableRemarkColumn')" align="center" prop="remark" /> -->
<el-table-column :label="t('ProductionPlan.Plan.tableOperateColumn')" align="center" fixed="right" min-width="260">
<template #default="scope">
<el-button link type="info" @click="openDetail(scope.row.id)" v-hasPermi="['mes:plan:query']">
{{ t('ProductionPlan.Plan.actionDetailLabel') }}
</el-button>
<el-button link type="primary" @click="openChangeLineDialog(scope.row)" v-hasPermi="['mes:plan:update']" v-if="String(scope.row.status) !== '9'">
换线
</el-button>
<el-button link type="primary" @click="openForm('update', scope.row.id)" v-hasPermi="['mes:plan:update']"
v-if="scope.row.status === 0">
{{ t('ProductionPlan.Plan.actionEditLabel') }}
</el-button>
<!-- <el-button link type="info" @click="openItemNeed(scope.row.code, scope.row.id)"
v-hasPermi="['mes:task:query']">
{{ t('ProductionPlan.Plan.actionMaterialLabel') }}
</el-button> -->
<el-button link type="primary" @click="openPaiForm(scope.row.code, scope.row.id)"
v-hasPermi="['mes:plan:update']" v-if="scope.row.status === 0">
{{ t('ProductionPlan.Plan.actionDispatchFeedingLabel') }}
</el-button>
<el-button link type="primary" @click="openTypePaiForm(scope.row.code, scope.row.id)"
v-hasPermi="['mes:plan:update']" v-if="scope.row.status === 0">
{{ t('ProductionPlan.Plan.actionDispatchProcessLabel') }}
</el-button>
<el-button link type="primary"
@click="handleStatus(scope.row.code, scope.row.id, 'pre', 6, t('ProductionPlan.Plan.actionTrialLabel'), scope.row.isZj)"
v-hasPermi="['mes:plan:update']" v-if="scope.row.status === 1 && scope.row.isPreProduction === 1">
{{ t('ProductionPlan.Plan.actionTrialLabel') }}
</el-button>
<!-- <el-button link type="primary" @click.stop="openZjTaskForm(scope.row)"
v-if="scope.row.status === 6 && inspectableMap[scope.row.id] !== false || scope.row.status === 8">
{{ t('ProductionPlan.Plan.actionInspectLabel') }}
</el-button> -->
<!-- <el-button
link
type="primary"
@click="handleStatus(scope.row.code, scope.row.id, 'start',2, t('ProductionPlan.Plan.actionMassLabel'), scope.row.isZj)"
v-hasPermi="['mes:plan:update']"
v-if="(scope.row.status === 1 && scope.row.isPreProduction === 0) || scope.row.status === 6"
>
{{ t('ProductionPlan.Plan.actionMassLabel') }}
</el-button> -->
<el-button link type="primary"
@click="handleStatus(scope.row.code, scope.row.id, 'commence', 8, t('ProductionPlan.Plan.actionStartLabel'), scope.row.isZj)"
v-hasPermi="['mes:plan:update']"
v-if="(scope.row.status === 1 && scope.row.isPreProduction === 0) || scope.row.status === 6 || scope.row.status === 3">
{{ t('ProductionPlan.Plan.actionStartLabel') }}
</el-button>
<el-button link type="primary" v-hasPermi="['mes:plan:update']"
@click="openBaogongForm(scope.row.code, scope.row.id)"
v-if="scope.row.status === 2 || scope.row.status === 8">
{{ t('ProductionPlan.Plan.actionBaogongLabel') }}
</el-button>
<el-button link type="primary"
@click="handleStatus(scope.row.code, scope.row.id, 'pause', 3, t('ProductionPlan.Plan.actionPauseLabel'), scope.row.isZj)"
v-hasPermi="['mes:plan:update']" v-if="scope.row.status === 2 || scope.row.status === 8">
{{ t('ProductionPlan.Plan.actionPauseLabel') }}
</el-button>
<el-button link type="primary"
@click="handleStatus(scope.row.code, scope.row.id, 'end', 9, t('ProductionPlan.Plan.actionFinishLabel'), scope.row.isZj)"
v-hasPermi="['mes:plan:update']"
v-if="scope.row.status === 2 || scope.row.status === 3 || scope.row.status === 8">
{{ t('ProductionPlan.Plan.actionFinishLabel') }}
</el-button>
<!-- <el-button link type="primary" @click="openStoreDialog(scope.row.code, scope.row.id, scope.row.isZj)"
v-hasPermi="['mes:plan:update']" v-if="scope.row.status === 4">
{{ t('ProductionPlan.Plan.actionStoreLabel') }}
</el-button> -->
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['mes:plan:delete']"
v-if="scope.row.status === 0">
{{ t('ProductionPlan.Plan.actionDeleteLabel') }}
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
@pagination="getList" />
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<PlanForm ref="formRef" @success="getList" />
<!-- 物料列表 -->
<ItemNeedIndex ref="itemFormRef" @success="getList" />
<!-- 派工弹出 -->
<Paigong ref="paiFormRef" @success="getList" />
<!-- 工序派工弹出 -->
<TypePaigong ref="typePaiFormRef" @success="getList" />
<!-- 报工弹出 -->
<Baogong ref="baogongFormRef" @success="handleBaogongSuccess" />
<!-- 换线弹窗 -->
<Dialog v-model="lineChangeDialogVisible" :title="lineChangeDialogTitle" width="1250px">
<el-table
v-loading="lineChangeLoading"
:data="lineChangeItems"
border
:stripe="true"
:show-overflow-tooltip="true"
row-key="id"
empty-text="暂无工艺路线明细"
>
<el-table-column label="排序" prop="sort" width="70" align="center" />
<el-table-column label="工艺参数编码" prop="processParameterCode" min-width="140" align="center" />
<el-table-column label="工艺参数名称" prop="processParameterName" min-width="160" align="center" />
<el-table-column label="备注" prop="remark" min-width="140" align="center" />
<el-table-column label="当前设备" min-width="170" align="center">
<template #default="scope">
{{ scope.row.originalDeviceName || scope.row.originalDeviceId || '' }}
</template>
</el-table-column>
<el-table-column label="换线后设备" min-width="260" align="center">
<template #default="scope">
<el-input
:model-value="getLineChangeDeviceDisplay(scope.row)"
placeholder="请选择设备"
readonly
@click="openLineDeviceSelectDialog(scope.row)"
>
<template #append>
<el-button @click.stop="openLineDeviceSelectDialog(scope.row)">选择</el-button>
</template>
</el-input>
</template>
</el-table-column>
<el-table-column label="换线备注" min-width="180" align="center">
<template #default="scope">
<el-input v-model="scope.row.lineChangeRemark" placeholder="请输入备注" clearable />
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="lineChangeDialogVisible = false">关闭</el-button>
<el-button type="primary" :loading="lineChangeSaving" :disabled="!hasLineChangeChanges || lineChangeLoading" @click="submitLineChangeBatch">保存</el-button>
</template>
</Dialog>
<TableSelectDialog
ref="lineDeviceSelectDialogRef"
title="选择设备"
:columns="lineDeviceColumns"
:fetch-api="fetchLineDeviceLedgerPage"
selection-type="single"
row-key="id"
@confirm="handleLineDeviceSelectConfirm"
/>
<!-- 详情侧边栏 -->
<PlanDetail ref="detailRef" />
<!-- 入库仓库选择弹窗 -->
<Dialog v-model="storeDialogVisible" title="入库" width="400px">
<el-form label-width="80px">
<el-form-item label="选择仓库">
<el-select v-model="storeWarehouseId" placeholder="请选择仓库" filterable style="width: 100%">
<el-option v-for="item in warehouseList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="storeDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmStore" :disabled="!storeWarehouseId">确定</el-button>
</template>
</Dialog>
<!-- 检验任务弹窗 -->
<ZjTaskForm ref="zjTaskFormRef" @success="handleZjTaskSuccess" />
</template>
<script setup lang="ts">
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
import { dateFormatter2 } from '@/utils/formatTime'
import download from '@/utils/download'
import { PlanApi, PlanProcessRouteItemVO, PlanVO } from '@/api/mes/plan'
import PlanForm from './PlanForm.vue'
import { ProductApi, ProductVO } from "@/api/erp/product/product";
import { TaskApi, TaskVO } from "@/api/mes/task";
import ItemNeedIndex from "@/views/mes/bom/ItemNeedIndex.vue";
import Paigong from "./components/Paigong.vue";
import TypePaigong from "./components/TypePaigong.vue";
import PlanDetail from "./components/PlanDetail.vue";
import Baogong from "./components/Baogong.vue";
import ZjTaskForm from "@/views/mes/zjTask/ZjTaskForm.vue";
import TableSelectDialog from '@/components/TableSelectDialog/TableSelectDialog.vue'
import { DeviceLedgerApi } from '@/api/mes/deviceledger'
import { ZjTaskApi } from '@/api/mes/zjtask'
import { WarehouseApi, WarehouseVO } from '@/api/erp/stock/warehouse'
const productList = ref<ProductVO[]>([]) // 产品列表
const taskList = ref<TaskVO[]>([]) // 列表
/** 生产计划 列表 */
defineOptions({ name: 'Plan' })
const activeName = ref('') // 列表 tab
const planStatusTabs = computed(() => [
{ label: '所有', value: '' },
...getStrDictOptions(DICT_TYPE.MES_PLAN_STATUS).map((item) => ({
label: item.label,
value: item.value
}))
])
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const { query } = useRoute() // 查询参数
const loading = ref(true) // 列表的加载中
const list = ref<PlanVO[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
const tableRef = ref()
const planTableMaxHeight = ref(520)
const planPaginationRef = ref<HTMLElement>()
const updatePlanTableMaxHeight = async () => {
await nextTick()
const tableTop = tableRef.value?.$el?.getBoundingClientRect().top ?? 0
const paginationHeight = planPaginationRef.value?.getBoundingClientRect().height ?? 72
const pageBottomGap = 56
const availableHeight = window.innerHeight - tableTop - paginationHeight - pageBottomGap
planTableMaxHeight.value = Math.min(window.innerHeight, Math.max(240, Math.floor(availableHeight)))
}
const inspectableMap = reactive<Record<number, boolean | undefined>>({})
type LineChangeRouteItem = PlanProcessRouteItemVO & {
planProcessRouteItemId?: number | string
originalDeviceId?: number | string
originalDeviceName?: string
selectedDeviceId?: number | string
selectedDeviceName?: string
lineChangeRemark?: string
}
const lineChangeDialogVisible = ref(false)
const lineChangeDialogTitle = ref('')
const lineChangeLoading = ref(false)
const lineChangeSaving = ref(false)
const lineChangePlanId = ref<number>()
const lineChangeItems = ref<LineChangeRouteItem[]>([])
const activeLineChangeItem = ref<LineChangeRouteItem>()
const lineDeviceSelectDialogRef = ref()
const lineDeviceColumns = [
{ label: t('ProductionPlan.Plan.deviceColumnCode'), prop: 'deviceCode', minWidth: 140 },
{ label: t('ProductionPlan.Plan.deviceColumnName'), prop: 'deviceName', minWidth: 160 },
{ label: t('ProductionPlan.Plan.deviceColumnModel'), prop: 'deviceModel', minWidth: 140 },
{ label: t('ProductionPlan.Plan.deviceColumnWorkshop'), prop: 'workshopName', minWidth: 140 }
]
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
code: undefined,
productId: undefined,
taskDetailId: undefined,
taskId: undefined,
planNumber: undefined,
finishNumber: undefined,
status: undefined,
planStartTime: [],
planEndTime: [],
startTime: [],
endTime: [],
productionManagerId: undefined,
remark: undefined,
isEnable: undefined,
feedingPipeline: undefined,
requisitionId: undefined,
createTime: []
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
const showAllFilters = ref(false) // 是否显示所有筛选框
const filterCount = 4 // 筛选框数量
const chineseNumberText = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
const formatChineseNumber = (value: number) => {
if (value <= 10) return chineseNumberText[value] || String(value)
if (value < 20) return `${chineseNumberText[value - 10]}`
if (value < 100) {
const ten = Math.floor(value / 10)
const one = value % 10
return `${chineseNumberText[ten]}${one ? chineseNumberText[one] : ''}`
}
return String(value)
}
const getProcessDeviceLines = (row: PlanVO) => {
const items = Array.isArray(row.processRouteItems) ? row.processRouteItems : []
if (!items.length) return [row.deviceName || '-']
return items.map((item, index) => {
const processName = item.processParameterName || '-'
const deviceName = item.deviceName || '-'
return `工序${formatChineseNumber(index + 1)}${processName}${deviceName}`
})
}
/** 切换筛选框展开/折叠 */
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
updatePlanTableMaxHeight()
}
const warehouseList = ref<WarehouseVO[]>([]) // 仓库列表
const storeDialogVisible = ref(false) // 入库仓库选择弹窗
const storeWarehouseId = ref<number>() // 选中的仓库ID
const storePlanInfo = ref<{ planCode: string; id: number; isZj: number }>() // 入库计划信息
/** 详情操作 */
const detailRef = ref()
const openDetail = (id: number) => {
detailRef.value.open(id)
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await PlanApi.getPlanPage(queryParams)
list.value = data.list
total.value = data.total
await refreshInspectableMap(list.value)
} finally {
loading.value = false
updatePlanTableMaxHeight()
}
}
/** 搜索按钮操作 */
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 PlanApi.deletePlan(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch { }
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await PlanApi.exportPlan(queryParams)
download.excel(data, t('ProductionPlan.Plan.exportFilename'))
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(async () => {
//处理其他页面跳转过来的参数
if (query.taskId)
queryParams.taskId = String(query.taskId)
if (query.productId)
queryParams.productId = String(query.productId)
// 加载产品、任务单
productList.value = await ProductApi.getMesProductSimpleList()
taskList.value = await TaskApi.getTaskList()
await getList()
updatePlanTableMaxHeight()
window.addEventListener('resize', updatePlanTableMaxHeight)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', updatePlanTableMaxHeight)
})
/** 物料需求 */
const itemFormRef = ref()
const openItemNeed = (planCode: string, planId: number) => {
itemFormRef.value.open('plan', t('ProductionPlan.Plan.itemNeedDialogPlanTitlePrefix') + planCode, planId)
}
/** 派工 */
const paiFormRef = ref()
const openPaiForm = (planCode: string, planId: number) => {
paiFormRef.value.open(planCode, planId)
}
/** 工序派工 */
const typePaiFormRef = ref()
const openTypePaiForm = (planCode: string, planId: number) => {
typePaiFormRef.value.open(planCode, planId)
}
/** 报工 */
const baogongFormRef = ref()
const openBaogongForm = (planCode: string, planId: number) => {
baogongFormRef.value.open(planCode, planId)
}
const fetchLineDeviceLedgerPage = (params: Record<string, any>) => {
return DeviceLedgerApi.getDeviceLedgerPage({
...params,
isScheduled: 1
})
}
const getPlanProcessRouteItemId = (row: LineChangeRouteItem) => {
return row.planProcessRouteItemId ?? row.id ?? row.processRouteItemId
}
const getLineChangeDeviceDisplay = (row: LineChangeRouteItem) => {
if (row.selectedDeviceName) return row.selectedDeviceName
if (row.selectedDeviceId) return String(row.selectedDeviceId)
return ''
}
const isLineChangeRowChanged = (row: LineChangeRouteItem) => {
if (!row.selectedDeviceId) return false
return String(row.selectedDeviceId) !== String(row.originalDeviceId ?? '')
}
const changedLineChangeItems = computed(() => lineChangeItems.value.filter(isLineChangeRowChanged))
const hasLineChangeChanges = computed(() => changedLineChangeItems.value.length > 0)
const openChangeLineDialog = async (row: PlanVO) => {
if (!row?.id) return
if (String(row.status) === '9') {
message.warning('完工状态不能换线')
return
}
lineChangePlanId.value = row.id
lineChangeDialogTitle.value = row.code ? `换线 - ${row.code}` : '换线'
lineChangeDialogVisible.value = true
lineChangeLoading.value = true
lineChangeSaving.value = false
lineChangeItems.value = []
try {
const data = await PlanApi.getPlan(row.id)
const items = Array.isArray(data?.processRouteItems) ? data.processRouteItems : []
lineChangeItems.value = items.map((item: PlanProcessRouteItemVO, index: number) => ({
...item,
sort: item.sort || index + 1,
originalDeviceId: item.deviceId,
originalDeviceName: item.deviceName,
selectedDeviceId: item.deviceId,
selectedDeviceName: item.deviceName,
lineChangeRemark: ''
}))
} finally {
lineChangeLoading.value = false
}
}
const openLineDeviceSelectDialog = (row: LineChangeRouteItem) => {
activeLineChangeItem.value = row
const rows = row.selectedDeviceId
? [
{
id: String(row.selectedDeviceId),
deviceName: row.selectedDeviceName
}
]
: []
lineDeviceSelectDialogRef.value?.open(rows)
}
const handleLineDeviceSelectConfirm = (payload: { ids: (number | string)[]; rows: any[] }) => {
const row = payload.rows?.[0]
if (!row || !activeLineChangeItem.value) return
const deviceId = String(row.id)
if (!deviceId) return
activeLineChangeItem.value.selectedDeviceId = deviceId
activeLineChangeItem.value.selectedDeviceName = row.deviceName || row.name || row.code || `ID:${deviceId}`
}
const submitLineChangeBatch = async () => {
const planId = lineChangePlanId.value
if (!planId) {
message.warning('缺少生产计划 ID')
return
}
const items = changedLineChangeItems.value.reduce<
Array<{ planProcessRouteItemId: number | string; deviceId: number | string; remark: string }>
>((result, row) => {
const planProcessRouteItemId = getPlanProcessRouteItemId(row)
const deviceId = row.selectedDeviceId
if (!planProcessRouteItemId || !deviceId) return result
result.push({
planProcessRouteItemId,
deviceId,
remark: row.lineChangeRemark || ''
})
return result
}, [])
if (!items.length) return
lineChangeSaving.value = true
try {
await PlanApi.changeLineBatch({ planId, items })
const changedItemMap = new Map(items.map((item) => [String(item.planProcessRouteItemId), item]))
lineChangeItems.value.forEach((row) => {
const item = changedItemMap.get(String(getPlanProcessRouteItemId(row)))
if (!item) return
row.deviceId = item.deviceId
row.deviceName = row.selectedDeviceName
row.originalDeviceId = item.deviceId
row.originalDeviceName = row.selectedDeviceName || String(item.deviceId)
row.lineChangeRemark = ''
})
message.success('换线成功')
await getList()
} finally {
lineChangeSaving.value = false
}
}
const handleBaogongSuccess = async () => {
await getList()
}
const handleZjTaskSuccess = async () => {
await getList()
}
const zjTaskFormRef = ref()
const openZjTaskForm = (row: PlanVO) => {
zjTaskFormRef.value.open('create', undefined, { ticketType: 1, ticket: row.id, ticketName: row.code, productId: row.productId })
}
const refreshInspectableMap = async (rows: PlanVO[]) => {
const ids = rows
.filter((row) => row?.id && String(row.status) === '6')
.map((row) => row.id as number)
const idSet = new Set(ids)
for (const key of Object.keys(inspectableMap)) {
const id = Number(key)
if (!idSet.has(id)) delete inspectableMap[id]
}
await Promise.all(
ids.map(async (id) => {
try {
const data = await ZjTaskApi.getZjTaskList({ ticket: String(id) })
const arr = Array.isArray(data) ? data : data?.list ?? data?.data ?? []
const hasPending = arr.some((item: any) => String(item?.status) === '0')
inspectableMap[id] = hasPending
} catch {
inspectableMap[id] = true
}
})
)
}
/** 入库 - 打开仓库选择弹窗 */
const openStoreDialog = async (planCode: string, id: number, isZj: number) => {
storePlanInfo.value = { planCode, id, isZj }
storeWarehouseId.value = undefined
if (!warehouseList.value.length) {
warehouseList.value = await WarehouseApi.getWarehouseSimpleList()
}
storeDialogVisible.value = true
}
/** 入库 - 确认入库 */
const confirmStore = async () => {
if (!storePlanInfo.value || !storeWarehouseId.value) return
storeDialogVisible.value = false
await handleStatus(
storePlanInfo.value.planCode,
storePlanInfo.value.id,
'store',
5,
t('ProductionPlan.Plan.actionStoreLabel'),
storePlanInfo.value.isZj,
storeWarehouseId.value
)
}
/** 开工 */
const handleStatus = async (planCode: string, id: number,
type: string, status: number, tip: string, isZj: number, warehouseId?: number) => {
try {
// if((isZj === 0 || isZj === null) && tip === "量产") {
// // message.alertError("请先完成检验")
// // 二次确认
// await message.confirm("计划:"+planCode+"确定"+tip+"吗?",planCode)
// }
// 二次确认
await message.confirm(
t('ProductionPlan.Plan.statusConfirmMessage', { code: planCode, action: tip }),
t('ProductionPlan.Plan.statusConfirmTitle')
)
// 发起
const data: any = {
id: id,
code: type,
status: status
}
if (warehouseId) {
data.warehouseId = warehouseId
}
await PlanApi.updatePlanStatus(data)
message.success(t('common.success'))
// 刷新列表
await getList()
} catch { }
}
/** 更新质检 */
const handleZjStatus = async (planCode: string, id: number) => {
try {
// 二次确认
await message.confirm(
t('ProductionPlan.Plan.inspectConfirmMessage', { code: planCode }),
t('ProductionPlan.Plan.inspectConfirmTitle')
)
// 发起
const data = {
id: id
}
await PlanApi.updatePlanZjStatus(data)
message.success(t('common.success'))
// 刷新列表
await getList()
} catch { }
}
/** tab 切换 */
const handleTabClick = (tab: TabsPaneContext) => {
queryParams.status = tab.paneName === '' || tab.paneName === undefined ? undefined : String(tab.paneName)
handleQuery()
}
</script>
<style scoped>
.pass-rate-cell {
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
}
.pass-rate-cell :deep(.el-progress-circle) {
display: flex;
align-items: center;
justify-content: center;
}
.pass-rate-text {
font-size: 12px;
white-space: nowrap;
}
</style>