You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

802 lines
31 KiB

3 weeks ago
3 weeks ago
1 month ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
  1. <template>
  2. <div class="mod-config">
  3. <el-form :inline="true" label-position="top" class="query-form">
  4. <el-form-item label="任务单号">
  5. <el-input v-model="searchData.taskNo" clearable placeholder="请输入任务单号" style="width: 150px"></el-input>
  6. </el-form-item>
  7. <el-form-item label="任务类型">
  8. <el-select v-model="searchData.taskType" clearable placeholder="全部" style="width: 120px">
  9. <el-option label="线缆自制" value="线缆自制"></el-option>
  10. <el-option label="COP自制" value="COP自制"></el-option>
  11. </el-select>
  12. </el-form-item>
  13. <el-form-item label="来源项目号">
  14. <el-input v-model="searchData.sourceProjectNo" clearable placeholder="请输入项目号" style="width: 140px"></el-input>
  15. </el-form-item>
  16. <el-form-item label="状态">
  17. <el-select v-model="searchData.status" clearable placeholder="全部" style="width: 120px">
  18. <el-option label="已排产" value="已排产"></el-option>
  19. <el-option label="进行中" value="进行中"></el-option>
  20. <el-option label="已完成" value="已完成"></el-option>
  21. </el-select>
  22. </el-form-item>
  23. <el-form-item label="计划完成日期">
  24. <el-date-picker v-model="searchData.planStartDate" type="date" value-format="yyyy-MM-dd" placeholder="开始" style="width: 130px"></el-date-picker>
  25. </el-form-item>
  26. <el-form-item label="至">
  27. <el-date-picker v-model="searchData.planEndDate" type="date" value-format="yyyy-MM-dd" placeholder="结束" style="width: 130px"></el-date-picker>
  28. </el-form-item>
  29. <el-form-item label=" " style="margin-top: -11px">
  30. <el-button @click="searchTable('Y')" plain class="search-btn">查询</el-button>
  31. <el-button @click="resetQuery()" plain class="reset-btn">重置</el-button>
  32. <el-button @click="openEditDialog()" plain class="add-btn">新增任务单</el-button>
  33. </el-form-item>
  34. </el-form>
  35. <el-table
  36. ref="orderTable"
  37. class="data-table"
  38. :data="dataList"
  39. :height="tableHeight"
  40. border
  41. highlight-current-row
  42. v-loading="dataListLoading"
  43. style="width: 100%"
  44. @current-change="onCurrentRowChange">
  45. <el-table-column type="index" label="#" width="50" align="center"></el-table-column>
  46. <el-table-column prop="taskNo" label="任务单号" width="140" align="center"></el-table-column>
  47. <el-table-column prop="taskType" label="任务类型" width="100" align="center">
  48. <template slot-scope="scope">
  49. <el-tag :type="scope.row.taskType === '线缆自制' ? 'primary' : 'success'" size="small">{{ scope.row.taskType }}</el-tag>
  50. </template>
  51. </el-table-column>
  52. <el-table-column prop="sourceProjectNo" label="来源项目号" width="120" align="center"></el-table-column>
  53. <el-table-column prop="modelNo" label="型号" width="110" align="center"></el-table-column>
  54. <el-table-column prop="taskQty" label="任务数量" width="90" align="center"></el-table-column>
  55. <el-table-column prop="reportQty" label="累计报工数量" width="110" align="center"></el-table-column>
  56. <el-table-column prop="currentNode" label="当前节点" width="110" align="center"></el-table-column>
  57. <el-table-column prop="assigneeSummary" label="节点负责人" min-width="160" show-overflow-tooltip></el-table-column>
  58. <el-table-column label="节点进度" width="100" align="center">
  59. <template slot-scope="scope">
  60. <el-tag size="small" type="info">{{ scope.row.nodeDoneCount }}/{{ scope.row.nodeTotalCount }}</el-tag>
  61. </template>
  62. </el-table-column>
  63. <el-table-column prop="status" label="状态" width="90" align="center">
  64. <template slot-scope="scope">
  65. <el-tag :type="getStatusType(scope.row.status)" size="small">{{ scope.row.status }}</el-tag>
  66. </template>
  67. </el-table-column>
  68. <el-table-column prop="planFinishDate" label="计划完成日期" width="110" align="center"></el-table-column>
  69. <el-table-column prop="finishDate" label="完工日期" width="110" align="center">
  70. <template slot-scope="scope">{{ scope.row.finishDate || '-' }}</template>
  71. </el-table-column>
  72. <el-table-column label="操作" width="300" align="center">
  73. <template slot-scope="scope">
  74. <a type="text" @click="openEditDialog(scope.row)" v-if="scope.row.status === '已排产'">修改</a>
  75. <a type="text" @click="openAssignDialog(scope.row)" v-if="scope.row.status !== '已完成'">分配人员</a>
  76. <a type="text" @click="finishTask(scope.row)" v-if="scope.row.status !== '已完成'">完工</a>
  77. <a type="text" style="color:#F56C6C" @click="deleteTask(scope.row)" v-if="scope.row.status !== '已完成'">删除</a>
  78. </template>
  79. </el-table-column>
  80. </el-table>
  81. <el-pagination
  82. @size-change="sizeChangeHandle"
  83. @current-change="currentChangeHandle"
  84. :current-page="pageIndex"
  85. :page-sizes="[10, 20, 50, 100]"
  86. :page-size="pageSize"
  87. :total="totalPage"
  88. layout="total, sizes, prev, pager, next, jumper"
  89. style="margin-top: 20px; text-align: right">
  90. </el-pagination>
  91. <div class="detail-tabs-wrap">
  92. <el-tabs v-model="detailTabName" type="border-card">
  93. <el-tab-pane label="节点状态和日志" name="statusLogs">
  94. <div v-if="selectedOrder.orderNo" class="two-column-layout">
  95. <div class="stages-column">
  96. <div class="column-header">
  97. <i class="el-icon-s-order"></i>
  98. <span>节点流程</span>
  99. <span class="progress-badge">{{ selectedOrderProgressPercent }}%</span>
  100. </div>
  101. <div class="stages-list">
  102. <div
  103. v-for="(stage, index) in selectedOrderNodeList"
  104. :key="stage.nodeCode || index"
  105. class="stage-item"
  106. :class="'stage-' + getStageClass(stage.status)">
  107. <div class="stage-icon">
  108. <i :class="getStageIcon(stage.status)"></i>
  109. </div>
  110. <div class="stage-content">
  111. <div class="stage-name">{{ stage.nodeName }}</div>
  112. <div class="stage-meta">
  113. <el-tag :type="getStageTagType(stage.status)" size="mini" effect="plain">{{ stage.status || '未开始' }}</el-tag>
  114. <span class="stage-owner">负责人{{ stage.assigneeUserName || '-' }}</span>
  115. </div>
  116. </div>
  117. </div>
  118. </div>
  119. </div>
  120. <div class="logs-column">
  121. <div class="column-header">
  122. <i class="el-icon-tickets"></i>
  123. <span>操作日志</span>
  124. <span class="logs-count">{{ selectedOrderLogList.length }}</span>
  125. </div>
  126. <div class="logs-table-wrapper">
  127. <el-table :data="selectedOrderLogList" border size="small" class="detail-table" v-loading="detailLogLoading" height="295px">
  128. <el-table-column prop="logTime" label="时间" width="160" align="center"></el-table-column>
  129. <el-table-column prop="action" label="操作" width="95" align="center"></el-table-column>
  130. <el-table-column prop="nodeName" label="节点" width="130" align="center">
  131. <template slot-scope="scope">{{ scope.row.nodeName || scope.row.nodeCode || '-' }}</template>
  132. </el-table-column>
  133. <el-table-column prop="operatorName" label="操作人" width="100" align="center"></el-table-column>
  134. <el-table-column prop="comment" label="备注" min-width="220" show-overflow-tooltip>
  135. <template slot-scope="scope">{{ scope.row.comment || '-' }}</template>
  136. </el-table-column>
  137. </el-table>
  138. </div>
  139. </div>
  140. </div>
  141. <el-empty v-else description="请先选择一条任务记录"></el-empty>
  142. </el-tab-pane>
  143. </el-tabs>
  144. </div>
  145. <el-dialog :title="saveHeaderData.orderNo ? '修改线缆/COP自制任务单' : '新增线缆/COP自制任务单'" :visible.sync="setUp.reviewFlag" width="550px" :close-on-click-modal="false" v-drag>
  146. <el-form
  147. ref="editForm"
  148. :model="saveHeaderData"
  149. label-position="top"
  150. class="edit-form">
  151. <el-row :gutter="20">
  152. <el-col :span="12"><el-form-item label="任务单号" required><el-input v-model="saveHeaderData.taskNo"></el-input></el-form-item></el-col>
  153. <el-col :span="12"><el-form-item label="任务类型" required><el-select v-model="saveHeaderData.taskType" style="width: 100%"><el-option label="线缆自制" value="线缆自制"></el-option><el-option label="COP自制" value="COP自制"></el-option></el-select></el-form-item></el-col>
  154. </el-row>
  155. <el-row :gutter="20">
  156. <el-col :span="12"><el-form-item label="来源项目号"><el-input v-model="saveHeaderData.sourceProjectNo"></el-input></el-form-item></el-col>
  157. <el-col :span="12"><el-form-item label="型号"><el-input v-model="saveHeaderData.modelNo"></el-input></el-form-item></el-col>
  158. </el-row>
  159. <el-row :gutter="20">
  160. <el-col :span="12"><el-form-item label="任务数量"><el-input v-model="saveHeaderData.taskQty" :min="1" :max="9999" style="width: 100%"></el-input></el-form-item></el-col>
  161. <el-col :span="12"><el-form-item label="计划完成日期"><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>
  162. </el-row>
  163. <el-row :gutter="20">
  164. <el-col :span="12">
  165. <el-form-item>
  166. <template slot="label">
  167. 人员分配策略
  168. <el-tooltip effect="dark" placement="top">
  169. <div slot="content">
  170. 默认分配创建订单后系统会按节点角色自动分配该角色下全部人员<br>
  171. 手动分配创建订单后不自动分配需要在分配人员里手工选择负责人
  172. </div>
  173. <i class="el-icon-question" style="margin-left:6px;color:#909399;cursor:pointer;"></i>
  174. </el-tooltip>
  175. </template>
  176. <el-radio-group v-model="saveHeaderData.autoAssignAllUsers">
  177. <el-radio :label="true">默认分配</el-radio>
  178. <el-radio :label="false">手动分配</el-radio>
  179. </el-radio-group>
  180. </el-form-item>
  181. </el-col>
  182. <el-col :span="12">
  183. <el-form-item label="节点报工模式">
  184. <el-radio-group v-model="saveHeaderData.nodeReportMode">
  185. <el-radio label="PARALLEL">并行</el-radio>
  186. <el-radio label="SEQUENTIAL">串行</el-radio>
  187. </el-radio-group>
  188. </el-form-item>
  189. </el-col>
  190. </el-row>
  191. </el-form>
  192. <el-footer style="height: 40px; margin-top: 50px; text-align: center">
  193. <el-button plain class="reset-btn" @click="setUp.reviewFlag = false">取消</el-button>
  194. <el-button plain class="add-btn" :loading="setUp.saveButton" @click="saveTask">保存</el-button>
  195. </el-footer>
  196. </el-dialog>
  197. <el-dialog title="车间节点报工" :visible.sync="setUp.reportFlag" width="460px" :close-on-click-modal="false" v-drag>
  198. <el-form :model="reportData" label-width="110px" label-position="top">
  199. <el-form-item label="任务单号"><el-input v-model="reportData.taskNo" disabled></el-input></el-form-item>
  200. <el-form-item label="报工节点">
  201. <el-select v-model="reportData.nodeCode" placeholder="请选择节点" style="width: 100%">
  202. <el-option v-for="item in reportNodeOptions" :key="item.nodeCode" :label="item.nodeName" :value="item.nodeCode"></el-option>
  203. </el-select>
  204. </el-form-item>
  205. <el-form-item label="本次报工数量"><el-input v-model="reportData.reportQty" :min="1" :max="9999" style="width: 100%"></el-input></el-form-item>
  206. <el-form-item label="报工备注"><el-input v-model="reportData.remark" type="textarea" :rows="2"></el-input></el-form-item>
  207. </el-form>
  208. <el-footer style="height: 40px; margin-top: 50px; text-align: center">
  209. <el-button plain class="reset-btn" @click="setUp.reportFlag = false">取消</el-button>
  210. <el-button plain class="search-btn" :loading="setUp.reportButton" @click="submitNodeReport">提交报工</el-button>
  211. </el-footer>
  212. </el-dialog>
  213. <el-dialog title="节点负责人分配" :visible.sync="setUp.assignFlag" width="500px" :close-on-click-modal="false" v-drag>
  214. <el-table :data="assignNodeList" border class="data-table">
  215. <el-table-column prop="nodeName" label="节点" width="180"></el-table-column>
  216. <el-table-column label="负责人">
  217. <template slot-scope="scope">
  218. <el-select v-model="scope.row.assigneeUserIdList" filterable clearable multiple collapse-tags placeholder="请选择负责人" style="width: 100%">
  219. <el-option v-for="user in scope.row.userOptions" :key="user.userId" :label="user.displayName || user.username" :value="user.userId"></el-option>
  220. </el-select>
  221. </template>
  222. </el-table-column>
  223. </el-table>
  224. <el-footer style="height: 40px; margin-top: 20px; text-align: center">
  225. <el-button plain class="reset-btn" @click="setUp.assignFlag = false">取消</el-button>
  226. <el-button plain class="add-btn" :loading="setUp.assignButton" @click="saveNodeAssigneeAction">保存分配</el-button>
  227. </el-footer>
  228. </el-dialog>
  229. </div>
  230. </template>
  231. <script>
  232. import { deleteCableCopTask, finishCableCopTask, getCableCopTaskList, getNodeAssigneeList, getNodeAssigneeUsers, getReportLogList, reportCableCopTaskNode, saveCableCopTask, saveNodeAssignee } from '@/api/longchuang/productionPlan'
  233. export default {
  234. name: 'ProductionPlanCableCopTask',
  235. data() {
  236. return {
  237. searchData: { taskNo: '', taskType: '', sourceProjectNo: '', status: '', planStartDate: '', planEndDate: '', page: 1, limit: 20 },
  238. saveHeaderData: {},
  239. reportData: { orderNo: '', taskNo: '', nodeCode: '', reportQty: 1, remark: '' },
  240. reportNodeOptions: [],
  241. setUp: { reviewFlag: false, reportFlag: false, assignFlag: false, saveButton: false, reportButton: false, assignButton: false },
  242. dataList: [],
  243. currentAssignOrder: { orderNo: '', orderType: 'CABLE_COP' },
  244. assignNodeList: [],
  245. selectedOrder: {},
  246. detailTabName: 'statusLogs',
  247. selectedOrderLogList: [],
  248. detailLogLoading: false,
  249. pageIndex: 1,
  250. pageSize: 20,
  251. totalPage: 0,
  252. dataListLoading: false,
  253. tableHeight: (window.innerHeight - 320) / 2
  254. }
  255. },
  256. activated() {
  257. this.searchTable()
  258. },
  259. methods: {
  260. searchTable(flag) {
  261. if (flag === 'Y') this.pageIndex = 1
  262. this.searchData.page = this.pageIndex
  263. this.searchData.limit = this.pageSize
  264. this.dataListLoading = true
  265. getCableCopTaskList(this.searchData).then(({data}) => {
  266. this.dataListLoading = false
  267. if (data && data.code === 0) {
  268. this.dataList = (data.page.list || []).map(this.normalizeRow)
  269. this.totalPage = data.page.totalCount || 0
  270. this.syncSelectedOrder()
  271. } else this.loadMockData()
  272. }).catch(() => {
  273. this.dataListLoading = false
  274. this.loadMockData()
  275. })
  276. },
  277. normalizeRow(row) {
  278. const list = row.nodeList || []
  279. const done = list.filter(item => item.status === '已完成').length
  280. const currentNode = (list.find(item => item.status !== '已完成') || {}).nodeName || '全部完成'
  281. const assigneeSummary = list.filter(item => item.assigneeUserName).map(item => `${item.nodeName}:${item.assigneeUserName}`).join(';')
  282. 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 || '-' }
  283. },
  284. loadMockData() {
  285. this.dataList = [
  286. {
  287. orderNo: 'MOCK-CABLE-001',
  288. taskNo: 'TASK-XL-202604-001',
  289. taskType: '线缆自制',
  290. sourceProjectNo: 'LC-202604-001',
  291. modelNo: 'LC-HOME-820',
  292. taskQty: 6,
  293. reportQty: 2,
  294. autoAssignAllUsers: true,
  295. nodeReportMode: 'PARALLEL',
  296. planFinishDate: '2026-04-20',
  297. finishDate: '',
  298. status: '进行中',
  299. nodeList: [
  300. { nodeCode: 'lineProduction', nodeName: '线缆生产', status: '已完成' },
  301. { nodeCode: 'copProduction', nodeName: 'COP生产', status: '进行中' }
  302. ]
  303. },
  304. {
  305. orderNo: 'MOCK-CABLE-002',
  306. taskNo: 'TASK-COP-202604-002',
  307. taskType: 'COP自制',
  308. sourceProjectNo: 'LC-202604-002',
  309. modelNo: 'LC-HOME-1050',
  310. taskQty: 8,
  311. reportQty: 0,
  312. autoAssignAllUsers: true,
  313. nodeReportMode: 'PARALLEL',
  314. planFinishDate: '2026-04-22',
  315. finishDate: '',
  316. status: '已排产',
  317. nodeList: [
  318. { nodeCode: 'lineProduction', nodeName: '线缆生产', status: '未开始' },
  319. { nodeCode: 'copProduction', nodeName: 'COP生产', status: '未开始' }
  320. ]
  321. }
  322. ].map(this.normalizeRow)
  323. this.totalPage = this.dataList.length
  324. this.syncSelectedOrder()
  325. },
  326. syncSelectedOrder() {
  327. if (!this.dataList.length) {
  328. this.selectedOrder = {}
  329. this.selectedOrderLogList = []
  330. return
  331. }
  332. const selectedOrderNo = this.selectedOrder && this.selectedOrder.orderNo
  333. const matched = selectedOrderNo ? this.dataList.find(item => item.orderNo === selectedOrderNo) : null
  334. const current = matched || this.dataList[0]
  335. this.selectedOrder = current
  336. this.$nextTick(() => {
  337. if (this.$refs.orderTable && current) {
  338. this.$refs.orderTable.setCurrentRow(current)
  339. }
  340. })
  341. this.loadSelectedOrderLogList(current)
  342. },
  343. onCurrentRowChange(row) {
  344. if (!row || !row.orderNo) {
  345. this.selectedOrder = {}
  346. this.selectedOrderLogList = []
  347. return
  348. }
  349. this.selectedOrder = row
  350. this.loadSelectedOrderLogList(row)
  351. },
  352. loadSelectedOrderLogList(row) {
  353. if (!row || !row.orderNo) {
  354. this.selectedOrderLogList = []
  355. return
  356. }
  357. this.detailLogLoading = true
  358. getReportLogList({ orderNo: row.orderNo, orderType: 'CABLE_COP' }).then(({ data }) => {
  359. this.detailLogLoading = false
  360. this.selectedOrderLogList = data && data.code === 0 ? (data.rows || []) : []
  361. }).catch(() => {
  362. this.detailLogLoading = false
  363. this.selectedOrderLogList = []
  364. })
  365. },
  366. resetQuery() {
  367. this.searchData = { taskNo: '', taskType: '', sourceProjectNo: '', status: '', planStartDate: '', planEndDate: '', page: 1, limit: 20 }
  368. this.searchTable('Y')
  369. },
  370. openEditDialog(row) {
  371. this.saveHeaderData = row ? { ...row, autoAssignAllUsers: !!row.autoAssignAllUsers, nodeReportMode: row.nodeReportMode || 'PARALLEL' } : { orderNo: '', taskNo: '', taskType: '线缆自制', sourceProjectNo: '', modelNo: '', taskQty: 1, reportQty: 0, planFinishDate: '', status: '已排产', autoAssignAllUsers: true, nodeReportMode: 'PARALLEL', nodeList: [] }
  372. this.setUp.reviewFlag = true
  373. },
  374. saveTask() {
  375. if (!this.saveHeaderData.taskNo || !this.saveHeaderData.taskType) return this.$message.warning('请先填写任务单号和任务类型')
  376. this.saveHeaderData.projectNo = this.saveHeaderData.taskNo
  377. this.setUp.saveButton = true
  378. saveCableCopTask(this.saveHeaderData).then(({data}) => {
  379. this.setUp.saveButton = false
  380. if (data && data.code === 0) {
  381. this.$message.success(data.msg || '保存成功')
  382. this.setUp.reviewFlag = false
  383. this.searchTable()
  384. } else this.$message.error(data.msg || '保存失败')
  385. }).catch(() => {
  386. this.setUp.saveButton = false
  387. const orderNo = this.saveHeaderData.orderNo || String(Date.now())
  388. const index = this.dataList.findIndex(item => item.orderNo === orderNo)
  389. const saveData = this.normalizeRow({
  390. ...this.saveHeaderData,
  391. orderNo: orderNo,
  392. nodeList: this.saveHeaderData.nodeList && this.saveHeaderData.nodeList.length ? this.saveHeaderData.nodeList : [
  393. { nodeCode: 'lineProduction', nodeName: '线缆生产', status: '未开始' },
  394. { nodeCode: 'copProduction', nodeName: 'COP生产', status: '未开始' }
  395. ]
  396. })
  397. if (index > -1) this.$set(this.dataList, index, saveData)
  398. else this.dataList.unshift(saveData)
  399. this.totalPage = this.dataList.length
  400. this.setUp.reviewFlag = false
  401. this.$message.success('后端未完成,已在前端演示保存')
  402. })
  403. },
  404. openReportDialog(row) {
  405. this.reportNodeOptions = (row.nodeList || []).filter(item => item.status !== '已完成')
  406. this.reportData = { orderNo: row.orderNo, taskNo: row.taskNo, nodeCode: '', reportQty: 1, remark: '' }
  407. this.setUp.reportFlag = true
  408. },
  409. openAssignDialog(row) {
  410. if (!row.orderNo) return this.$message.warning('请先保存任务单后再分配人员')
  411. this.currentAssignOrder = { orderNo: row.orderNo, orderType: 'CABLE_COP' }
  412. getNodeAssigneeList({ orderNo: row.orderNo, orderType: 'CABLE_COP' }).then(({ data }) => {
  413. const assignRows = data && data.code === 0 ? (data.rows || []) : []
  414. this.assignNodeList = assignRows.map(item => ({ ...item, assigneeUserIdList: item.assigneeUserIdList || [], userOptions: [] }))
  415. const requests = this.assignNodeList.map(item =>
  416. getNodeAssigneeUsers({ orderType: 'CABLE_COP', nodeCode: item.nodeCode }).then(({ data: userData }) => {
  417. item.userOptions = userData && userData.code === 0 ? (userData.rows || []) : []
  418. }).catch(() => { item.userOptions = [] })
  419. )
  420. Promise.all(requests).finally(() => { this.setUp.assignFlag = true })
  421. }).catch(() => {
  422. this.$message.error('加载节点分配信息失败')
  423. })
  424. },
  425. saveNodeAssigneeAction() {
  426. this.setUp.assignButton = true
  427. const assigneeList = this.assignNodeList.map(item => ({
  428. nodeCode: item.nodeCode,
  429. nodeName: item.nodeName,
  430. assigneeUserIdList: item.assigneeUserIdList || []
  431. }))
  432. saveNodeAssignee({
  433. orderNo: this.currentAssignOrder.orderNo,
  434. orderType: this.currentAssignOrder.orderType,
  435. assigneeList: assigneeList
  436. }).then(({ data }) => {
  437. this.setUp.assignButton = false
  438. if (data && data.code === 0) {
  439. this.$message.success(data.msg || '分配成功')
  440. this.setUp.assignFlag = false
  441. this.searchTable()
  442. } else this.$message.error(data.msg || '分配失败')
  443. }).catch(() => {
  444. this.setUp.assignButton = false
  445. this.$message.error('分配失败')
  446. })
  447. },
  448. submitNodeReport() {
  449. if (!this.reportData.nodeCode) return this.$message.warning('请选择报工节点')
  450. if (!this.reportData.reportQty || this.reportData.reportQty <= 0) return this.$message.warning('请输入有效报工数量')
  451. this.setUp.reportButton = true
  452. reportCableCopTaskNode(this.reportData).then(({data}) => {
  453. this.setUp.reportButton = false
  454. if (data && data.code === 0) {
  455. this.$message.success(data.msg || '报工成功')
  456. this.setUp.reportFlag = false
  457. this.searchTable()
  458. } else this.$message.error(data.msg || '报工失败')
  459. }).catch(() => {
  460. this.setUp.reportButton = false
  461. this.simulateNodeReport(this.reportData.orderNo, this.reportData.nodeCode, this.reportData.reportQty)
  462. this.setUp.reportFlag = false
  463. this.$message.success('后端未完成,已在前端演示节点报工')
  464. })
  465. },
  466. simulateNodeReport(orderNo, nodeCode, qty) {
  467. const row = this.dataList.find(item => item.orderNo === orderNo)
  468. if (!row) return
  469. const node = row.nodeList.find(item => item.nodeCode === nodeCode)
  470. if (!node) return
  471. node.status = '已完成'
  472. row.reportQty = Number(row.reportQty || 0) + Number(qty || 0)
  473. row.status = '进行中'
  474. const nextNode = row.nodeList.find(item => item.status !== '已完成')
  475. row.currentNode = nextNode ? nextNode.nodeName : '全部完成'
  476. row.nodeDoneCount = row.nodeList.filter(item => item.status === '已完成').length
  477. if (!nextNode) {
  478. row.status = '已完成'
  479. row.finishDate = this.dayjs().format('YYYY-MM-DD')
  480. }
  481. },
  482. finishTask(row) {
  483. finishCableCopTask({ orderNo: row.orderNo }).then(({data}) => {
  484. if (data && data.code === 0) {
  485. this.$message.success(data.msg || '完工成功')
  486. this.searchTable()
  487. } else this.$message.error(data.msg || '完工失败')
  488. }).catch(() => {
  489. row.status = '已完成'
  490. row.finishDate = this.dayjs().format('YYYY-MM-DD')
  491. row.reportQty = row.taskQty
  492. row.nodeList = row.nodeList.map(item => ({ ...item, status: '已完成' }))
  493. row.currentNode = '全部完成'
  494. row.nodeDoneCount = row.nodeTotalCount
  495. this.$message.success('后端未完成,已在前端演示完工')
  496. })
  497. },
  498. deleteTask(row) {
  499. this.$confirm('确定删除该任务单吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
  500. deleteCableCopTask({ orderNo: row.orderNo }).then(({data}) => {
  501. if (data && data.code === 0) {
  502. this.$message.success(data.msg || '删除成功')
  503. this.searchTable()
  504. } else this.$message.error(data.msg || '删除失败')
  505. }).catch(() => {
  506. this.dataList = this.dataList.filter(item => item.orderNo !== row.orderNo)
  507. this.totalPage = this.dataList.length
  508. this.$message.success('后端未完成,已在前端演示删除')
  509. })
  510. }).catch(() => {})
  511. },
  512. getStatusType(status) {
  513. const map = { 已排产: 'info', 进行中: 'warning', 已完成: 'success' }
  514. return map[status] || 'info'
  515. },
  516. getStageClass(status) {
  517. if (status === '已完成') return 'done'
  518. if (status === '进行中') return 'processing'
  519. return 'pending'
  520. },
  521. getStageIcon(status) {
  522. if (status === '已完成') return 'el-icon-check'
  523. if (status === '进行中') return 'el-icon-loading'
  524. return 'el-icon-time'
  525. },
  526. getStageTagType(status) {
  527. if (status === '已完成') return 'success'
  528. if (status === '进行中') return 'warning'
  529. return 'info'
  530. },
  531. sizeChangeHandle(val) {
  532. this.pageSize = val
  533. this.pageIndex = 1
  534. this.searchTable()
  535. },
  536. currentChangeHandle(val) {
  537. this.pageIndex = val
  538. this.searchTable()
  539. }
  540. },
  541. computed: {
  542. selectedOrderNodeList() {
  543. return (this.selectedOrder && this.selectedOrder.nodeList) ? this.selectedOrder.nodeList : []
  544. },
  545. selectedOrderProgressPercent() {
  546. const total = this.selectedOrderNodeList.length
  547. if (!total) {
  548. return 0
  549. }
  550. const done = this.selectedOrderNodeList.filter(item => item.status === '已完成').length
  551. return Math.round((done * 100) / total)
  552. }
  553. }
  554. }
  555. </script>
  556. <style scoped>
  557. .data-table {
  558. background-color: #fff;
  559. border-radius: 4px;
  560. }
  561. .data-table >>> .cell {
  562. line-height: 20px;
  563. height: 20px;
  564. }
  565. .data-table >>> .el-table__header-wrapper th,
  566. .data-table >>> .el-table__fixed-header-wrapper th {
  567. background-color: #f5f7fa !important;
  568. color: #333;
  569. font-weight: 600;
  570. border-color: #ebeef5;
  571. padding: 8px 0;
  572. }
  573. .data-table >>> .el-table__header-wrapper .cell,
  574. .data-table >>> .el-table__fixed-header-wrapper .cell,
  575. .data-table >>> .el-table__body-wrapper .cell,
  576. .data-table >>> .el-table__fixed-body-wrapper .cell {
  577. padding: 0 10px;
  578. overflow: hidden;
  579. text-overflow: ellipsis;
  580. white-space: nowrap;
  581. font-size: 13px !important;
  582. }
  583. .data-table >>> .el-table__body tr:hover > td {
  584. background-color: #f5f7fa !important;
  585. }
  586. .data-table >>> .el-table__body tr.current-row > td {
  587. background-color: #ecf5ff !important;
  588. }
  589. .query-form {
  590. background-color: #fff;
  591. padding: 15px 15px 5px 15px;
  592. border-radius: 4px;
  593. margin-bottom: 12px;
  594. }
  595. .query-form >>> .el-form-item__label {
  596. color: #333;
  597. font-size: 13px;
  598. padding-bottom: 5px;
  599. }
  600. .query-form >>> .el-input__inner {
  601. height: 32px;
  602. line-height: 32px;
  603. border-radius: 4px;
  604. font-size: 13px;
  605. }
  606. .query-form >>> .el-button {
  607. height: 32px;
  608. padding: 0 15px;
  609. font-size: 13px;
  610. border-radius: 4px;
  611. }
  612. .search-btn {
  613. background-color: #ecf5ff;
  614. border-color: #b3d8ff;
  615. color: #409eff;
  616. }
  617. .search-btn:hover {
  618. background-color: #409eff;
  619. border-color: #409eff;
  620. color: #fff;
  621. }
  622. .reset-btn {
  623. background-color: #f5f7fa;
  624. border-color: #d3d4d6;
  625. color: #606266;
  626. }
  627. .reset-btn:hover {
  628. background-color: #909399;
  629. border-color: #909399;
  630. color: #fff;
  631. }
  632. .add-btn {
  633. background-color: #f0f9eb;
  634. border-color: #c2e7b0;
  635. color: #67c23a;
  636. }
  637. .add-btn:hover {
  638. background-color: #67c23a;
  639. border-color: #67c23a;
  640. color: #fff;
  641. }
  642. .dialog-footer {
  643. text-align: right;
  644. }
  645. .edit-form {
  646. margin-left: 5px;
  647. margin-top: -5px;
  648. }
  649. .detail-tabs-wrap {
  650. margin-top: 12px;
  651. background-color: #fff;
  652. border-radius: 4px;
  653. }
  654. .detail-table {
  655. width: 100%;
  656. }
  657. .detail-table >>> .el-table__header-wrapper th,
  658. .detail-table >>> .el-table__fixed-header-wrapper th {
  659. background-color: #f5f7fa !important;
  660. color: #333;
  661. font-weight: 600;
  662. border-color: #ebeef5;
  663. }
  664. .two-column-layout {
  665. display: flex;
  666. gap: 12px;
  667. }
  668. .stages-column {
  669. width: 38%;
  670. min-width: 320px;
  671. border: 1px solid #ebeef5;
  672. border-radius: 4px;
  673. background: #fff;
  674. }
  675. .logs-column {
  676. flex: 1;
  677. border: 1px solid #ebeef5;
  678. border-radius: 4px;
  679. background: #fff;
  680. }
  681. .column-header {
  682. height: 24px;
  683. display: flex;
  684. align-items: center;
  685. gap: 6px;
  686. padding: 0 12px;
  687. border-bottom: 1px solid #ebeef5;
  688. font-weight: 600;
  689. color: #303133;
  690. }
  691. .progress-badge,
  692. .logs-count {
  693. margin-left: auto;
  694. color: #409eff;
  695. font-size: 12px;
  696. font-weight: 500;
  697. }
  698. .stages-list {
  699. max-height: 295px;
  700. overflow-y: auto;
  701. padding: 10px;
  702. }
  703. .stage-item {
  704. display: flex;
  705. align-items: flex-start;
  706. gap: 10px;
  707. padding: 3px 8px;
  708. border-radius: 4px;
  709. }
  710. .stage-item + .stage-item {
  711. margin-top: 2px;
  712. }
  713. .stage-item.stage-done {
  714. background: #f0f9eb;
  715. }
  716. .stage-item.stage-processing {
  717. background: #fdf6ec;
  718. }
  719. .stage-item.stage-pending {
  720. background: #f5f7fa;
  721. }
  722. .stage-icon {
  723. width: 22px;
  724. height: 22px;
  725. border-radius: 50%;
  726. display: flex;
  727. align-items: center;
  728. justify-content: center;
  729. font-size: 13px;
  730. background: #fff;
  731. border: 1px solid #dcdfe6;
  732. color: #606266;
  733. }
  734. .stage-content {
  735. flex: 1;
  736. }
  737. .stage-name {
  738. font-size: 13px;
  739. color: #303133;
  740. line-height: 20px;
  741. }
  742. .stage-meta {
  743. margin-top: 6px;
  744. display: flex;
  745. align-items: center;
  746. gap: 8px;
  747. }
  748. .stage-owner {
  749. color: #606266;
  750. font-size: 12px;
  751. }
  752. .logs-table-wrapper {
  753. padding: 8px;
  754. }
  755. .el-icon-check {
  756. color: #67c23a;
  757. font-weight: 1000;
  758. }
  759. </style>