8 changed files with 978 additions and 40 deletions
-
7src/api/longchuang/productionPlan.js
-
1src/router/index.js
-
7src/views/modules/longtron/production-plan-cable-cop-task.vue
-
7src/views/modules/longtron/production-plan-home-order.vue
-
780src/views/modules/longtron/production-plan-machining-task.vue
-
9src/views/modules/longtron/production-plan-renovation-order.vue
-
75src/views/modules/longtron/production-work-report.vue
-
132src/views/modules/longtron/screen-machining-progress.vue
@ -0,0 +1,780 @@ |
|||
<template> |
|||
<div class="mod-config"> |
|||
<el-form :inline="true" label-position="top" class="query-form"> |
|||
<el-form-item label="物料号"> |
|||
<el-input v-model="searchData.modelNo" clearable placeholder="请输入物料号" style="width: 160px"></el-input> |
|||
</el-form-item> |
|||
<el-form-item label="状态"> |
|||
<el-select v-model="searchData.status" clearable placeholder="全部" style="width: 120px"> |
|||
<el-option label="已排产" value="已排产"></el-option> |
|||
<el-option label="进行中" value="进行中"></el-option> |
|||
<el-option label="已完成" value="已完成"></el-option> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="计划完工日期"> |
|||
<el-date-picker v-model="searchData.planStartDate" type="date" value-format="yyyy-MM-dd" placeholder="开始" style="width: 130px"></el-date-picker> |
|||
</el-form-item> |
|||
<el-form-item label="至"> |
|||
<el-date-picker v-model="searchData.planEndDate" type="date" value-format="yyyy-MM-dd" placeholder="结束" style="width: 130px"></el-date-picker> |
|||
</el-form-item> |
|||
<el-form-item label=" " style="margin-top: -11px"> |
|||
<el-button @click="searchTable('Y')" plain class="search-btn">查询</el-button> |
|||
<el-button @click="resetQuery()" plain class="reset-btn">重置</el-button> |
|||
<el-button @click="openEditDialog()" plain class="add-btn">新增任务单</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
|
|||
<el-table |
|||
ref="orderTable" |
|||
class="data-table" |
|||
:data="dataList" |
|||
:height="tableHeight" |
|||
border |
|||
highlight-current-row |
|||
v-loading="dataListLoading" |
|||
style="width: 100%" |
|||
@current-change="onCurrentRowChange"> |
|||
<el-table-column type="index" label="#" width="50" align="center"></el-table-column> |
|||
<el-table-column prop="orderNo" label="任务单号" width="180" align="center"></el-table-column> |
|||
<el-table-column prop="modelNo" label="物料号" width="160" align="center"></el-table-column> |
|||
<el-table-column prop="taskQty" label="计划数量" width="100" align="center"></el-table-column> |
|||
<el-table-column prop="reportQty" label="累计报工数量" width="120" align="center"></el-table-column> |
|||
<el-table-column prop="currentNode" label="当前节点" width="120" align="center"></el-table-column> |
|||
<el-table-column prop="assigneeSummary" label="节点负责人" min-width="160" show-overflow-tooltip></el-table-column> |
|||
<el-table-column label="节点进度" width="100" align="center"> |
|||
<template slot-scope="scope"> |
|||
<el-tag size="small" type="info">{{ scope.row.nodeDoneCount }}/{{ scope.row.nodeTotalCount }}</el-tag> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column prop="status" label="状态" width="90" align="center"> |
|||
<template slot-scope="scope"> |
|||
<el-tag :type="getStatusType(scope.row.status)" size="small">{{ scope.row.status }}</el-tag> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column prop="planFinishDate" label="计划完工日期" width="120" align="center"></el-table-column> |
|||
<el-table-column prop="finishDate" label="完工日期" width="120" align="center"> |
|||
<template slot-scope="scope">{{ scope.row.finishDate || '-' }}</template> |
|||
</el-table-column> |
|||
<el-table-column label="操作" width="300" align="center"> |
|||
<template slot-scope="scope"> |
|||
<a type="text" @click="openEditDialog(scope.row)" v-if="scope.row.status === '已排产'">修改</a> |
|||
<a type="text" @click="openAssignDialog(scope.row)" v-if="scope.row.status !== '已完成' && scope.row.currentNode!=='全部完成'">分配人员</a> |
|||
<a type="text" @click="finishTask(scope.row)" v-if="scope.row.status !== '已完成'">完工</a> |
|||
<a type="text" style="color:#F56C6C" @click="deleteTask(scope.row)" v-if="scope.row.status !== '已完成'">删除</a> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
|
|||
<el-pagination |
|||
@size-change="sizeChangeHandle" |
|||
@current-change="currentChangeHandle" |
|||
:current-page="pageIndex" |
|||
:page-sizes="[10, 20, 50, 100]" |
|||
:page-size="pageSize" |
|||
:total="totalPage" |
|||
layout="total, sizes, prev, pager, next, jumper" |
|||
style="margin-top: 20px; text-align: right"> |
|||
</el-pagination> |
|||
|
|||
<div class="detail-tabs-wrap"> |
|||
<el-tabs v-model="detailTabName" type="border-card"> |
|||
<el-tab-pane label="节点状态和日志" name="statusLogs"> |
|||
<div v-if="selectedOrder.orderNo" class="two-column-layout"> |
|||
<div class="stages-column"> |
|||
<div class="column-header"> |
|||
<i class="el-icon-s-order"></i> |
|||
<span>节点流程</span> |
|||
<span class="progress-badge">{{ selectedOrderProgressPercent }}%</span> |
|||
</div> |
|||
<div class="stages-list"> |
|||
<div |
|||
v-for="(stage, index) in selectedOrderNodeList" |
|||
:key="stage.nodeCode || index" |
|||
class="stage-item" |
|||
:class="'stage-' + getStageClass(stage.status)"> |
|||
<div class="stage-icon"> |
|||
<i :class="getStageIcon(stage.status)"></i> |
|||
</div> |
|||
<div class="stage-content"> |
|||
<div class="stage-name">{{ stage.nodeName }}</div> |
|||
<div class="stage-meta"> |
|||
<el-tag :type="getStageTagType(stage.status)" size="mini" effect="plain">{{ stage.status || '未开始' }}</el-tag> |
|||
<span class="stage-owner">负责人:{{ stage.assigneeUserName || '-' }}</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="logs-column"> |
|||
<div class="column-header"> |
|||
<i class="el-icon-tickets"></i> |
|||
<span>操作日志</span> |
|||
<span class="logs-count">{{ selectedOrderLogList.length }}条</span> |
|||
</div> |
|||
<div class="logs-table-wrapper"> |
|||
<el-table :data="selectedOrderLogList" border size="small" class="detail-table" v-loading="detailLogLoading" height="295px"> |
|||
<el-table-column prop="logTime" label="时间" width="160" align="center"></el-table-column> |
|||
<el-table-column prop="action" label="操作" width="95" align="center"></el-table-column> |
|||
<el-table-column prop="nodeName" label="节点" width="130" align="center"> |
|||
<template slot-scope="scope">{{ scope.row.nodeName || scope.row.nodeCode || '-' }}</template> |
|||
</el-table-column> |
|||
<el-table-column prop="operatorName" label="操作人" width="100" align="center"></el-table-column> |
|||
<el-table-column prop="comment" label="备注" min-width="220" show-overflow-tooltip> |
|||
<template slot-scope="scope">{{ scope.row.comment || '-' }}</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<el-empty v-else description="请先选择一条任务记录"></el-empty> |
|||
</el-tab-pane> |
|||
</el-tabs> |
|||
</div> |
|||
|
|||
<el-dialog :title="saveHeaderData.orderNo ? '修改机加工生产任务单' : '新增机加工生产任务单'" :visible.sync="setUp.reviewFlag" width="550px" :close-on-click-modal="false" v-drag> |
|||
<el-form |
|||
ref="editForm" |
|||
:model="saveHeaderData" |
|||
label-position="top" |
|||
class="edit-form"> |
|||
<el-row :gutter="20"> |
|||
<el-col :span="12"><el-form-item label="物料号" required><el-input v-model="saveHeaderData.modelNo"></el-input></el-form-item></el-col> |
|||
<el-col :span="12"><el-form-item label="计划数量" required><el-input v-model="saveHeaderData.taskQty" :min="1" :max="999999" style="width: 100%"></el-input></el-form-item></el-col> |
|||
</el-row> |
|||
<el-row :gutter="20"> |
|||
<el-col :span="12"><el-form-item label="计划完工日期" required><el-date-picker v-model="saveHeaderData.planFinishDate" type="date" value-format="yyyy-MM-dd" style="width: 100%"></el-date-picker></el-form-item></el-col> |
|||
<el-col :span="12"><el-form-item label="状态"><el-select disabled v-model="saveHeaderData.status" style="width: 100%"><el-option label="已排产" value="已排产"></el-option><el-option label="进行中" value="进行中"></el-option><el-option label="已完成" value="已完成"></el-option></el-select></el-form-item></el-col> |
|||
</el-row> |
|||
<el-row :gutter="20"> |
|||
<el-col :span="12"> |
|||
<el-form-item> |
|||
<template slot="label"> |
|||
人员分配策略 |
|||
<el-tooltip effect="dark" placement="top"> |
|||
<div slot="content"> |
|||
默认分配:创建订单后,系统会按节点角色自动分配该角色下全部人员。<br> |
|||
手动分配:创建订单后不自动分配,需要在“分配人员”里手工选择负责人。 |
|||
</div> |
|||
<i class="el-icon-question" style="margin-left:6px;color:#909399;cursor:pointer;"></i> |
|||
</el-tooltip> |
|||
</template> |
|||
<el-radio-group v-model="saveHeaderData.autoAssignAllUsers"> |
|||
<el-radio :label="true">默认分配</el-radio> |
|||
<el-radio :label="false">手动分配</el-radio> |
|||
</el-radio-group> |
|||
</el-form-item> |
|||
</el-col> |
|||
<el-col :span="12"> |
|||
<el-form-item label="节点报工模式"> |
|||
<el-radio-group v-model="saveHeaderData.nodeReportMode"> |
|||
<el-radio label="PARALLEL">并行</el-radio> |
|||
<el-radio label="SEQUENTIAL">串行</el-radio> |
|||
</el-radio-group> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
</el-form> |
|||
<el-footer style="height: 40px; margin-top: 50px; text-align: center"> |
|||
<el-button plain class="reset-btn" @click="setUp.reviewFlag = false">取消</el-button> |
|||
<el-button plain class="add-btn" :loading="setUp.saveButton" @click="saveTask">保存</el-button> |
|||
</el-footer> |
|||
</el-dialog> |
|||
|
|||
<el-dialog title="机加工生产报工" :visible.sync="setUp.reportFlag" width="460px" :close-on-click-modal="false" v-drag> |
|||
<el-form :model="reportData" label-width="110px" label-position="top"> |
|||
<el-form-item label="任务单号"><el-input v-model="reportData.taskNo" disabled></el-input></el-form-item> |
|||
<el-form-item label="本次报工数量"><el-input v-model="reportData.reportQty" :min="1" :max="999999" style="width: 100%"></el-input></el-form-item> |
|||
<el-form-item label="报工备注"><el-input v-model="reportData.remark" type="textarea" :rows="2"></el-input></el-form-item> |
|||
</el-form> |
|||
<el-footer style="height: 40px; margin-top: 50px; text-align: center"> |
|||
<el-button plain class="reset-btn" @click="setUp.reportFlag = false">取消</el-button> |
|||
<el-button plain class="search-btn" :loading="setUp.reportButton" @click="submitNodeReport">提交报工</el-button> |
|||
</el-footer> |
|||
</el-dialog> |
|||
|
|||
<el-dialog title="节点负责人分配" :visible.sync="setUp.assignFlag" width="550px" :close-on-click-modal="false" v-drag> |
|||
<el-table :data="assignNodeList" border class="data-table"> |
|||
<el-table-column prop="nodeName" label="节点" width="150"></el-table-column> |
|||
<el-table-column label="负责人" width="370"> |
|||
<template slot-scope="scope"> |
|||
<el-select v-model="scope.row.assigneeUserIdList" filterable clearable multiple placeholder="请选择负责人" style="width: 100%"> |
|||
<el-option v-for="user in scope.row.userOptions" :key="user.userId" :label="user.displayName || user.username" :value="user.userId"></el-option> |
|||
</el-select> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
<el-footer style="height: 40px; margin-top: 20px; text-align: center"> |
|||
<el-button plain class="reset-btn" @click="setUp.assignFlag = false">取消</el-button> |
|||
<el-button plain class="add-btn" :loading="setUp.assignButton" @click="saveNodeAssigneeAction">保存分配</el-button> |
|||
</el-footer> |
|||
</el-dialog> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { deleteMachiningTask, finishMachiningTask, getMachiningTaskList, getNodeAssigneeList, getNodeAssigneeUsers, getReportLogList, reportMachiningTaskNode, saveMachiningTask, saveNodeAssignee } from '@/api/longchuang/productionPlan' |
|||
|
|||
export default { |
|||
name: 'ProductionPlanMachiningTask', |
|||
data() { |
|||
return { |
|||
searchData: { modelNo: '', status: '', planStartDate: '', planEndDate: '', page: 1, limit: 20 }, |
|||
saveHeaderData: {}, |
|||
reportData: { orderNo: '', taskNo: '', nodeCode: 'machiningProduction', reportQty: '', remark: '' }, |
|||
setUp: { reviewFlag: false, reportFlag: false, assignFlag: false, saveButton: false, reportButton: false, assignButton: false }, |
|||
dataList: [], |
|||
currentAssignOrder: { orderNo: '', orderType: 'MACHINING' }, |
|||
assignNodeList: [], |
|||
selectedOrder: {}, |
|||
detailTabName: 'statusLogs', |
|||
selectedOrderLogList: [], |
|||
detailLogLoading: false, |
|||
pageIndex: 1, |
|||
pageSize: 20, |
|||
totalPage: 0, |
|||
dataListLoading: false, |
|||
tableHeight: (window.innerHeight - 320) / 2 |
|||
} |
|||
}, |
|||
activated() { |
|||
this.searchTable() |
|||
}, |
|||
methods: { |
|||
searchTable(flag) { |
|||
if (flag === 'Y') this.pageIndex = 1 |
|||
this.searchData.page = this.pageIndex |
|||
this.searchData.limit = this.pageSize |
|||
this.dataListLoading = true |
|||
getMachiningTaskList(this.searchData).then(({ data }) => { |
|||
this.dataListLoading = false |
|||
if (data && data.code === 0) { |
|||
this.dataList = (data.page.list || []).map(this.normalizeRow) |
|||
this.totalPage = data.page.totalCount || 0 |
|||
this.syncSelectedOrder() |
|||
} else this.loadMockData() |
|||
}).catch(() => { |
|||
this.dataListLoading = false |
|||
this.loadMockData() |
|||
}) |
|||
}, |
|||
normalizeRow(row) { |
|||
const list = row.nodeList || [] |
|||
const done = list.filter(item => item.status === '已完成').length |
|||
const currentNode = (list.find(item => item.status !== '已完成') || {}).nodeName || '全部完成' |
|||
const assigneeSummary = list.filter(item => item.assigneeUserName).map(item => `${item.nodeName}:${item.assigneeUserName}`).join(';') |
|||
return { ...row, autoAssignAllUsers: !!row.autoAssignAllUsers, nodeReportMode: row.nodeReportMode || 'PARALLEL', nodeList: list, nodeDoneCount: done, nodeTotalCount: list.length, currentNode: row.currentNode || currentNode, reportQty: row.reportQty || 0, assigneeSummary: assigneeSummary || '-' } |
|||
}, |
|||
loadMockData() { |
|||
this.dataList = [ |
|||
{ |
|||
orderNo: 'MOCK-MACH-001', |
|||
projectNo: 'MAT-120001', |
|||
modelNo: 'MAT-120001', |
|||
taskQty: 120, |
|||
reportQty: 30, |
|||
autoAssignAllUsers: true, |
|||
nodeReportMode: 'PARALLEL', |
|||
planFinishDate: '2026-05-25', |
|||
finishDate: '', |
|||
status: '进行中', |
|||
nodeList: [ |
|||
{ nodeCode: 'machiningProduction', nodeName: '机加工生产', status: '进行中' } |
|||
] |
|||
}, |
|||
{ |
|||
orderNo: 'MOCK-MACH-002', |
|||
projectNo: 'MAT-120188', |
|||
modelNo: 'MAT-120188', |
|||
taskQty: 240, |
|||
reportQty: 0, |
|||
autoAssignAllUsers: true, |
|||
nodeReportMode: 'PARALLEL', |
|||
planFinishDate: '2026-05-27', |
|||
finishDate: '', |
|||
status: '已排产', |
|||
nodeList: [ |
|||
{ nodeCode: 'machiningProduction', nodeName: '机加工生产', status: '未开始' } |
|||
] |
|||
} |
|||
].map(this.normalizeRow) |
|||
this.totalPage = this.dataList.length |
|||
this.syncSelectedOrder() |
|||
}, |
|||
syncSelectedOrder() { |
|||
if (!this.dataList.length) { |
|||
this.selectedOrder = {} |
|||
this.selectedOrderLogList = [] |
|||
return |
|||
} |
|||
const selectedOrderNo = this.selectedOrder && this.selectedOrder.orderNo |
|||
const matched = selectedOrderNo ? this.dataList.find(item => item.orderNo === selectedOrderNo) : null |
|||
const current = matched || this.dataList[0] |
|||
this.selectedOrder = current |
|||
this.$nextTick(() => { |
|||
if (this.$refs.orderTable && current) { |
|||
this.$refs.orderTable.setCurrentRow(current) |
|||
} |
|||
}) |
|||
this.loadSelectedOrderLogList(current) |
|||
}, |
|||
onCurrentRowChange(row) { |
|||
if (!row || !row.orderNo) { |
|||
this.selectedOrder = {} |
|||
this.selectedOrderLogList = [] |
|||
return |
|||
} |
|||
this.selectedOrder = row |
|||
this.loadSelectedOrderLogList(row) |
|||
}, |
|||
loadSelectedOrderLogList(row) { |
|||
if (!row || !row.orderNo) { |
|||
this.selectedOrderLogList = [] |
|||
return |
|||
} |
|||
this.detailLogLoading = true |
|||
getReportLogList({ orderNo: row.orderNo, orderType: 'MACHINING' }).then(({ data }) => { |
|||
this.detailLogLoading = false |
|||
this.selectedOrderLogList = data && data.code === 0 ? (data.rows || []) : [] |
|||
}).catch(() => { |
|||
this.detailLogLoading = false |
|||
this.selectedOrderLogList = [] |
|||
}) |
|||
}, |
|||
resetQuery() { |
|||
this.searchData = { modelNo: '', status: '', planStartDate: '', planEndDate: '', page: 1, limit: 20 } |
|||
this.searchTable('Y') |
|||
}, |
|||
openEditDialog(row) { |
|||
this.saveHeaderData = row |
|||
? { ...row, autoAssignAllUsers: !!row.autoAssignAllUsers, nodeReportMode: row.nodeReportMode || 'PARALLEL' } |
|||
: { orderNo: '', modelNo: '', taskQty: 1, reportQty: 0, planFinishDate: '', status: '已排产', autoAssignAllUsers: true, nodeReportMode: 'PARALLEL', nodeList: [] } |
|||
this.setUp.reviewFlag = true |
|||
}, |
|||
saveTask() { |
|||
if (!this.saveHeaderData.modelNo) return this.$message.warning('请先填写物料号') |
|||
if (!this.saveHeaderData.taskQty || Number(this.saveHeaderData.taskQty) <= 0) return this.$message.warning('请输入有效的计划数量') |
|||
if (!this.saveHeaderData.planFinishDate) return this.$message.warning('请选择计划完工日期') |
|||
const payload = { |
|||
...this.saveHeaderData, |
|||
projectNo: this.saveHeaderData.modelNo, |
|||
nodeList: this.saveHeaderData.nodeList && this.saveHeaderData.nodeList.length |
|||
? this.saveHeaderData.nodeList |
|||
: [{ nodeCode: 'machiningProduction', nodeName: '机加工生产', status: '未开始' }] |
|||
} |
|||
this.setUp.saveButton = true |
|||
saveMachiningTask(payload).then(({ data }) => { |
|||
this.setUp.saveButton = false |
|||
if (data && data.code === 0) { |
|||
this.$message.success(data.msg || '保存成功') |
|||
this.setUp.reviewFlag = false |
|||
this.searchTable() |
|||
} else this.$message.error(data.msg || '保存失败') |
|||
}).catch(() => { |
|||
this.setUp.saveButton = false |
|||
const orderNo = this.saveHeaderData.orderNo || String(Date.now()) |
|||
const index = this.dataList.findIndex(item => item.orderNo === orderNo) |
|||
const saveData = this.normalizeRow({ |
|||
...payload, |
|||
orderNo: orderNo |
|||
}) |
|||
if (index > -1) this.$set(this.dataList, index, saveData) |
|||
else this.dataList.unshift(saveData) |
|||
this.totalPage = this.dataList.length |
|||
this.setUp.reviewFlag = false |
|||
this.$message.success('后端未完成,已在前端演示保存') |
|||
}) |
|||
}, |
|||
openReportDialog(row) { |
|||
const defaultNode = (row.nodeList || []).find(item => item.status !== '已完成') || {} |
|||
this.reportData = { |
|||
orderNo: row.orderNo, |
|||
taskNo: row.orderNo, |
|||
nodeCode: defaultNode.nodeCode || 'machiningProduction', |
|||
reportQty: '', |
|||
remark: '' |
|||
} |
|||
this.setUp.reportFlag = true |
|||
}, |
|||
openAssignDialog(row) { |
|||
if (!row.orderNo) return this.$message.warning('请先保存任务单后再分配人员') |
|||
this.currentAssignOrder = { orderNo: row.orderNo, orderType: 'MACHINING' } |
|||
getNodeAssigneeList({ orderNo: row.orderNo, orderType: 'MACHINING' }).then(({ data }) => { |
|||
const assignRows = data && data.code === 0 ? (data.rows || []) : [] |
|||
this.assignNodeList = assignRows.map(item => ({ ...item, assigneeUserIdList: item.assigneeUserIdList || [], userOptions: [] })) |
|||
const requests = this.assignNodeList.map(item => |
|||
getNodeAssigneeUsers({ orderType: 'MACHINING', nodeCode: item.nodeCode }).then(({ data: userData }) => { |
|||
item.userOptions = userData && userData.code === 0 ? (userData.rows || []) : [] |
|||
}).catch(() => { item.userOptions = [] }) |
|||
) |
|||
Promise.all(requests).finally(() => { this.setUp.assignFlag = true }) |
|||
}).catch(() => { |
|||
this.$message.error('加载节点分配信息失败') |
|||
}) |
|||
}, |
|||
saveNodeAssigneeAction() { |
|||
this.setUp.assignButton = true |
|||
const assigneeList = this.assignNodeList.map(item => ({ |
|||
nodeCode: item.nodeCode, |
|||
nodeName: item.nodeName, |
|||
assigneeUserIdList: item.assigneeUserIdList || [] |
|||
})) |
|||
saveNodeAssignee({ |
|||
orderNo: this.currentAssignOrder.orderNo, |
|||
orderType: this.currentAssignOrder.orderType, |
|||
assigneeList: assigneeList |
|||
}).then(({ data }) => { |
|||
this.setUp.assignButton = false |
|||
if (data && data.code === 0) { |
|||
this.$message.success(data.msg || '分配成功') |
|||
this.setUp.assignFlag = false |
|||
this.searchTable() |
|||
} else this.$message.error(data.msg || '分配失败') |
|||
}).catch(() => { |
|||
this.setUp.assignButton = false |
|||
this.$message.error('分配失败') |
|||
}) |
|||
}, |
|||
submitNodeReport() { |
|||
if (!this.reportData.reportQty || Number(this.reportData.reportQty) <= 0) return this.$message.warning('请输入有效报工数量') |
|||
if (!this.reportData.nodeCode) this.reportData.nodeCode = 'machiningProduction' |
|||
this.setUp.reportButton = true |
|||
reportMachiningTaskNode(this.reportData).then(({ data }) => { |
|||
this.setUp.reportButton = false |
|||
if (data && data.code === 0) { |
|||
this.$message.success(data.msg || '报工成功') |
|||
this.setUp.reportFlag = false |
|||
this.searchTable() |
|||
} else this.$message.error(data.msg || '报工失败') |
|||
}).catch(() => { |
|||
this.setUp.reportButton = false |
|||
this.simulateNodeReport(this.reportData.orderNo, this.reportData.nodeCode, this.reportData.reportQty) |
|||
this.setUp.reportFlag = false |
|||
this.$message.success('后端未完成,已在前端演示节点报工') |
|||
}) |
|||
}, |
|||
simulateNodeReport(orderNo, nodeCode, qty) { |
|||
const row = this.dataList.find(item => item.orderNo === orderNo) |
|||
if (!row) return |
|||
const node = row.nodeList.find(item => item.nodeCode === nodeCode) |
|||
if (!node) return |
|||
node.status = '已完成' |
|||
row.reportQty = Number(row.reportQty || 0) + Number(qty || 0) |
|||
row.status = '进行中' |
|||
const nextNode = row.nodeList.find(item => item.status !== '已完成') |
|||
row.currentNode = nextNode ? nextNode.nodeName : '全部完成' |
|||
row.nodeDoneCount = row.nodeList.filter(item => item.status === '已完成').length |
|||
row.finishDate = '' |
|||
}, |
|||
finishTask(row) { |
|||
finishMachiningTask({ orderNo: row.orderNo }).then(({ data }) => { |
|||
if (data && data.code === 0) { |
|||
this.$message.success(data.msg || '完工成功') |
|||
this.searchTable() |
|||
} else this.$message.error(data.msg || '完工失败') |
|||
}).catch(() => { |
|||
row.status = '已完成' |
|||
row.finishDate = this.dayjs().format('YYYY-MM-DD') |
|||
row.nodeList = row.nodeList.map(item => ({ ...item, status: '已完成' })) |
|||
row.currentNode = '全部完成' |
|||
row.nodeDoneCount = row.nodeTotalCount |
|||
this.$message.success('后端未完成,已在前端演示完工') |
|||
}) |
|||
}, |
|||
deleteTask(row) { |
|||
this.$confirm('确定删除该任务单吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { |
|||
deleteMachiningTask({ orderNo: row.orderNo }).then(({ data }) => { |
|||
if (data && data.code === 0) { |
|||
this.$message.success(data.msg || '删除成功') |
|||
this.searchTable() |
|||
} else this.$message.error(data.msg || '删除失败') |
|||
}).catch(() => { |
|||
this.dataList = this.dataList.filter(item => item.orderNo !== row.orderNo) |
|||
this.totalPage = this.dataList.length |
|||
this.$message.success('后端未完成,已在前端演示删除') |
|||
}) |
|||
}).catch(() => {}) |
|||
}, |
|||
getStatusType(status) { |
|||
const map = { 已排产: 'info', 进行中: 'warning', 已完成: 'success' } |
|||
return map[status] || 'info' |
|||
}, |
|||
getStageClass(status) { |
|||
if (status === '已完成') return 'done' |
|||
if (status === '进行中') return 'processing' |
|||
return 'pending' |
|||
}, |
|||
getStageIcon(status) { |
|||
if (status === '已完成') return 'el-icon-check' |
|||
if (status === '进行中') return 'el-icon-loading' |
|||
return 'el-icon-time' |
|||
}, |
|||
getStageTagType(status) { |
|||
if (status === '已完成') return 'success' |
|||
if (status === '进行中') return 'warning' |
|||
return 'info' |
|||
}, |
|||
sizeChangeHandle(val) { |
|||
this.pageSize = val |
|||
this.pageIndex = 1 |
|||
this.searchTable() |
|||
}, |
|||
currentChangeHandle(val) { |
|||
this.pageIndex = val |
|||
this.searchTable() |
|||
} |
|||
}, |
|||
computed: { |
|||
selectedOrderNodeList() { |
|||
return (this.selectedOrder && this.selectedOrder.nodeList) ? this.selectedOrder.nodeList : [] |
|||
}, |
|||
selectedOrderProgressPercent() { |
|||
const total = this.selectedOrderNodeList.length |
|||
if (!total) { |
|||
return 0 |
|||
} |
|||
const done = this.selectedOrderNodeList.filter(item => item.status === '已完成').length |
|||
return Math.round((done * 100) / total) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.data-table { |
|||
background-color: #fff; |
|||
border-radius: 4px; |
|||
} |
|||
.data-table >>> .cell { |
|||
line-height: 20px; |
|||
height: 20px; |
|||
} |
|||
.data-table >>> .el-table__header-wrapper th, |
|||
.data-table >>> .el-table__fixed-header-wrapper th { |
|||
background-color: #f5f7fa !important; |
|||
color: #333; |
|||
font-weight: 600; |
|||
border-color: #ebeef5; |
|||
padding: 8px 0; |
|||
} |
|||
|
|||
.data-table >>> .el-table__header-wrapper .cell, |
|||
.data-table >>> .el-table__fixed-header-wrapper .cell, |
|||
.data-table >>> .el-table__body-wrapper .cell, |
|||
.data-table >>> .el-table__fixed-body-wrapper .cell { |
|||
padding: 0 10px; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
font-size: 13px !important; |
|||
} |
|||
|
|||
.data-table >>> .el-table__body tr:hover > td { |
|||
background-color: #f5f7fa !important; |
|||
} |
|||
|
|||
.data-table >>> .el-table__body tr.current-row > td { |
|||
background-color: #ecf5ff !important; |
|||
} |
|||
|
|||
.query-form { |
|||
background-color: #fff; |
|||
padding: 15px 15px 5px 15px; |
|||
border-radius: 4px; |
|||
margin-bottom: 12px; |
|||
} |
|||
|
|||
.query-form >>> .el-form-item__label { |
|||
color: #333; |
|||
font-size: 13px; |
|||
padding-bottom: 5px; |
|||
} |
|||
|
|||
.query-form >>> .el-input__inner { |
|||
height: 32px; |
|||
line-height: 32px; |
|||
border-radius: 4px; |
|||
font-size: 13px; |
|||
} |
|||
|
|||
.query-form >>> .el-button { |
|||
height: 32px; |
|||
padding: 0 15px; |
|||
font-size: 13px; |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
.search-btn { |
|||
background-color: #ecf5ff; |
|||
border-color: #b3d8ff; |
|||
color: #409eff; |
|||
} |
|||
|
|||
.search-btn:hover { |
|||
background-color: #409eff; |
|||
border-color: #409eff; |
|||
color: #fff; |
|||
} |
|||
|
|||
.reset-btn { |
|||
background-color: #f5f7fa; |
|||
border-color: #d3d4d6; |
|||
color: #606266; |
|||
} |
|||
|
|||
.reset-btn:hover { |
|||
background-color: #909399; |
|||
border-color: #909399; |
|||
color: #fff; |
|||
} |
|||
|
|||
.add-btn { |
|||
background-color: #f0f9eb; |
|||
border-color: #c2e7b0; |
|||
color: #67c23a; |
|||
} |
|||
|
|||
.add-btn:hover { |
|||
background-color: #67c23a; |
|||
border-color: #67c23a; |
|||
color: #fff; |
|||
} |
|||
|
|||
.dialog-footer { |
|||
text-align: right; |
|||
} |
|||
|
|||
.edit-form { |
|||
margin-left: 5px; |
|||
margin-top: -5px; |
|||
} |
|||
|
|||
.detail-tabs-wrap { |
|||
margin-top: 12px; |
|||
background-color: #fff; |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
.detail-table { |
|||
width: 100%; |
|||
} |
|||
|
|||
.detail-table >>> .el-table__header-wrapper th, |
|||
.detail-table >>> .el-table__fixed-header-wrapper th { |
|||
background-color: #f5f7fa !important; |
|||
color: #333; |
|||
font-weight: 600; |
|||
border-color: #ebeef5; |
|||
} |
|||
|
|||
.two-column-layout { |
|||
display: flex; |
|||
gap: 12px; |
|||
} |
|||
|
|||
.stages-column { |
|||
width: 38%; |
|||
min-width: 320px; |
|||
border: 1px solid #ebeef5; |
|||
border-radius: 4px; |
|||
background: #fff; |
|||
} |
|||
|
|||
.logs-column { |
|||
flex: 1; |
|||
border: 1px solid #ebeef5; |
|||
border-radius: 4px; |
|||
background: #fff; |
|||
} |
|||
|
|||
.column-header { |
|||
height: 24px; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 6px; |
|||
padding: 0 12px; |
|||
border-bottom: 1px solid #ebeef5; |
|||
font-weight: 600; |
|||
color: #303133; |
|||
} |
|||
|
|||
.progress-badge, |
|||
.logs-count { |
|||
margin-left: auto; |
|||
color: #409eff; |
|||
font-size: 12px; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.stages-list { |
|||
max-height: 295px; |
|||
overflow-y: auto; |
|||
padding: 10px; |
|||
} |
|||
|
|||
.stage-item { |
|||
display: flex; |
|||
align-items: flex-start; |
|||
gap: 10px; |
|||
padding: 3px 8px; |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
.stage-item + .stage-item { |
|||
margin-top: 2px; |
|||
} |
|||
|
|||
.stage-item.stage-done { |
|||
background: #f0f9eb; |
|||
} |
|||
|
|||
.stage-item.stage-processing { |
|||
background: #fdf6ec; |
|||
} |
|||
|
|||
.stage-item.stage-pending { |
|||
background: #f5f7fa; |
|||
} |
|||
|
|||
.stage-icon { |
|||
width: 22px; |
|||
height: 22px; |
|||
border-radius: 50%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
font-size: 13px; |
|||
background: #fff; |
|||
border: 1px solid #dcdfe6; |
|||
color: #606266; |
|||
} |
|||
|
|||
.stage-content { |
|||
flex: 1; |
|||
} |
|||
|
|||
.stage-name { |
|||
font-size: 13px; |
|||
color: #303133; |
|||
line-height: 20px; |
|||
} |
|||
|
|||
.stage-meta { |
|||
margin-top: 6px; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 8px; |
|||
} |
|||
|
|||
.stage-owner { |
|||
color: #606266; |
|||
font-size: 12px; |
|||
} |
|||
|
|||
.logs-table-wrapper { |
|||
padding: 8px; |
|||
} |
|||
.el-icon-check { |
|||
color: #67c23a; |
|||
font-weight: 1000; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,132 @@ |
|||
<template> |
|||
<div class="screen-wrap"> |
|||
<div class="top-bar"> |
|||
<div class="header-left"><img class="site-navbar__brand-logo" src="~@/assets/img/lc.png" alt="龙闯电梯"></div> |
|||
<div class="header-center"> |
|||
<div class="title">机加工生产进度看板</div> |
|||
</div> |
|||
<div class="tools"> |
|||
<span class="time">{{ currentTime }}</span> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="kpi-row"> |
|||
<div class="kpi-card"><div class="kpi-label">排产任务</div><div class="kpi-value">{{ kpi.total }}</div></div> |
|||
<div class="kpi-card"><div class="kpi-label">进行中</div><div class="kpi-value warning">{{ kpi.processing }}</div></div> |
|||
<div class="kpi-card"><div class="kpi-label">已完成</div><div class="kpi-value success">{{ kpi.finished }}</div></div> |
|||
<div class="kpi-card"><div class="kpi-label">完工达成率</div><div class="kpi-value highlight">{{ kpi.finishRate }}%</div></div> |
|||
</div> |
|||
|
|||
<el-table class="board-table" :data="boardList" :height="tableHeight" border stripe> |
|||
<el-table-column type="index" label="序号" width="70" align="center"></el-table-column> |
|||
<el-table-column prop="modelNo" label="物料号" min-width="180" align="center"></el-table-column> |
|||
<el-table-column prop="taskQty" label="计划数量" min-width="130" align="center"></el-table-column> |
|||
<el-table-column prop="reportQty" label="实际数量" min-width="130" align="center"></el-table-column> |
|||
<el-table-column prop="planFinishDate" label="计划完工日期" min-width="170" align="center"></el-table-column> |
|||
<el-table-column prop="finishDate" label="实际完工日期" min-width="170" align="center"> |
|||
<template slot-scope="scope">{{ scope.row.finishDate || '-' }}</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { getMachiningTaskList } from '@/api/longchuang/productionPlan' |
|||
|
|||
export default { |
|||
name: 'ScreenMachiningProgress', |
|||
data() { |
|||
return { |
|||
loading: false, |
|||
boardTimerId: null, |
|||
tableHeight: 640, |
|||
currentTime: '', |
|||
timerId: null, |
|||
boardList: [], |
|||
kpi: { total: 0, processing: 0, finished: 0, finishRate: 0 } |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.setTableHeight() |
|||
this.loadBoardData() |
|||
this.updateTime() |
|||
this.timerId = setInterval(this.updateTime, 1000) |
|||
this.boardTimerId = setInterval(() => { |
|||
this.loadBoardData() |
|||
}, 10000) |
|||
window.addEventListener('resize', this.setTableHeight) |
|||
}, |
|||
beforeDestroy() { |
|||
if (this.timerId) clearInterval(this.timerId) |
|||
if (this.boardTimerId) clearInterval(this.boardTimerId) |
|||
window.removeEventListener('resize', this.setTableHeight) |
|||
}, |
|||
methods: { |
|||
setTableHeight() { |
|||
this.tableHeight = Math.max(420, window.innerHeight - 220) |
|||
}, |
|||
loadBoardData() { |
|||
if (this.loading) return |
|||
this.loading = true |
|||
getMachiningTaskList({ page: 1, limit: 300, statusList: ['已排产', '进行中', '已完成'] }).then(({ data }) => { |
|||
this.loading = false |
|||
const source = (data && data.code === 0 && data.page && data.page.list) ? data.page.list : this.getMockList() |
|||
this.boardList = this.buildBoardList(source) |
|||
this.buildKpi() |
|||
}).catch(() => { |
|||
this.loading = false |
|||
this.boardList = this.buildBoardList(this.getMockList()) |
|||
this.buildKpi() |
|||
}) |
|||
}, |
|||
buildBoardList(sourceList) { |
|||
const statusAllow = ['已排产', '进行中', '已完成'] |
|||
return (sourceList || []).filter(item => statusAllow.includes(item.status)).map(item => ({ |
|||
...item, |
|||
taskQty: item.taskQty == null ? 0 : item.taskQty, |
|||
reportQty: item.reportQty == null ? 0 : item.reportQty |
|||
})) |
|||
}, |
|||
buildKpi() { |
|||
const total = this.boardList.length |
|||
const processing = this.boardList.filter(item => item.status === '进行中').length |
|||
const finished = this.boardList.filter(item => item.status === '已完成').length |
|||
this.kpi = { total, processing, finished, finishRate: total ? Math.round((finished / total) * 100) : 0 } |
|||
}, |
|||
updateTime() { |
|||
const now = new Date() |
|||
const weekList = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'] |
|||
const d = this.dayjs(now) |
|||
this.currentTime = `${d.format('YYYY/MM/DD')} ${weekList[now.getDay()]} ${d.format('HH:mm:ss')}` |
|||
}, |
|||
getMockList() { |
|||
return [] |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
.screen-wrap{height:100vh;padding:16px;background:linear-gradient(165deg,#0a1f36 0%,#0f2b47 50%,#0a2037 100%);display:flex;flex-direction:column;box-sizing:border-box;overflow:hidden} |
|||
.top-bar{display:flex;justify-content:space-between;align-items:center;padding:14px 18px;border-radius:12px;border:1px solid rgba(109,167,219,.24);background:rgba(9,29,49,.82)} |
|||
.top-bar{position:relative} |
|||
.header-left{display:flex;align-items:center;min-width:110px;z-index:2} |
|||
.header-center{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);text-align:center;pointer-events:none;max-width:70%;width:auto;padding:6px 24px 8px;border-radius:8px;border:1px solid rgba(96,170,232,.34);background:linear-gradient(180deg,rgba(33,73,116,.52),rgba(16,44,77,.42))} |
|||
.header-center::before{content:'';position:absolute;left:50%;transform:translateX(-50%);top:-8px;width:68%;height:1px;background:linear-gradient(90deg,rgba(87,164,230,0),rgba(87,164,230,.9),rgba(87,164,230,0))} |
|||
.top-bar .tools{margin-left:auto;z-index:2} |
|||
.title{color:#8fe7ff;font-size:30px;font-weight:800;letter-spacing:3px;line-height:1.05;text-shadow:0 0 14px rgba(79,179,255,.36)} |
|||
.tools{display:flex;align-items:center;gap:12px}.time{color:#89d7ff;font-size:20px;font-weight:600} |
|||
.kpi-row{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin:12px 0 10px} |
|||
.kpi-card{border-radius:10px;padding:12px 14px;background:rgba(14,43,70,.82);border:1px solid rgba(101,157,209,.22)} |
|||
.kpi-label{color:#b7d3ee;font-size:13px}.kpi-value{margin-top:6px;font-size:30px;font-weight:700;color:#edf6ff} |
|||
.kpi-value.warning{color:#ffd166}.kpi-value.success{color:#74dfa3}.kpi-value.highlight{color:#79d5ff} |
|||
.board-table{border-radius:10px;overflow:hidden;border:1px solid rgba(86,140,190,.35);flex:1;min-height:0} |
|||
.board-table .cell{line-height:30px;font-size:16px;height:30px} |
|||
.screen-wrap .board-table .el-table,.screen-wrap .board-table .el-table__expanded-cell,.screen-wrap .board-table .el-table__body-wrapper,.screen-wrap .board-table .el-table__empty-block,.screen-wrap .board-table .el-table__fixed-body-wrapper{background:rgba(12,39,64,.96)!important;color:#fff!important} |
|||
.screen-wrap .board-table .el-table__header-wrapper th,.screen-wrap .board-table .el-table__fixed-header-wrapper th{background:#123a5e!important;color:#d9e9f8!important;border-color:rgba(80,133,181,.6)!important;font-size:14px!important;padding:12px 0!important} |
|||
.screen-wrap .board-table .el-table__body tr>td,.screen-wrap .board-table .el-table__fixed-body-wrapper tr>td,.screen-wrap .board-table .el-table__fixed-right .el-table__fixed-body-wrapper tr>td{background:rgba(20,52,83,.96)!important;border-color:rgba(88,139,187,.4)!important;color:#fff!important;height:46px!important;vertical-align:middle!important} |
|||
.screen-wrap .board-table .el-table--striped .el-table__body tr.el-table__row--striped>td,.screen-wrap .board-table .el-table__fixed-body-wrapper tr.el-table__row--striped>td{background:rgba(29,66,102,.96)!important} |
|||
.screen-wrap .board-table .el-table--enable-row-hover .el-table__body tr:hover>td,.screen-wrap .board-table .el-table__fixed-body-wrapper tr:hover>td{background:rgba(39,81,123,.96)!important} |
|||
.screen-wrap .board-table .el-table .cell{color:#fff!important;line-height:24px!important;overflow:visible!important} |
|||
.screen-wrap .board-table .el-table__empty-text{color:#9ac2e7!important} |
|||
</style> |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue