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.

3352 lines
112 KiB

7 months ago
11 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
  1. <template>
  2. <div class="customer-css">
  3. <el-dialog :title="titleCon" v-drag v-bind="$attrs" v-on="$listeners" width="830px" style="height: 680px;" class="customer-dialog" :show-close="!exceptionRuleLocked" :close-on-press-escape="!exceptionRuleLocked" :before-close="handleMainDialogBeforeClose" @close="onDialogClosed">
  4. <el-form :inline="true" label-position="top" style="height: auto;">
  5. <!-- 时间和固定载具 -->
  6. <el-row style="margin-top: -10px;">
  7. <el-col :span="11">
  8. <el-form-item :label=labels.currentTime>
  9. <el-date-picker style="width: 110px;"
  10. v-model="pageData.reportDate"
  11. format="yyyy-MM-dd"
  12. value-format="yyyy-MM-dd"
  13. placeholder="">
  14. </el-date-picker>
  15. </el-form-item>
  16. <el-form-item :label="''" style="margin-top: 23px">
  17. <el-time-picker style="width: 100px;"
  18. v-model="pageData.reportTime"
  19. format="HH:mm:ss"
  20. value-format="HH:mm:ss"
  21. placeholder="">
  22. </el-time-picker>
  23. </el-form-item>
  24. </el-col>
  25. <el-col :span="6">
  26. <el-form-item>
  27. <span style="cursor: pointer" slot="label" @click="queryFixedCarrierList"><a>固定载具</a></span>
  28. <el-input v-model="pageData.fixture" readonly style="width: 120px;" placeholder="固定载具">
  29. <i v-if="pageData.fixture"
  30. slot="suffix"
  31. class="el-icon-circle-close fixture-clear-btn"
  32. @click="pageData.fixture = ''">
  33. </i>
  34. </el-input>
  35. </el-form-item>
  36. </el-col>
  37. <el-col :span="3">
  38. <el-form-item label="排数">
  39. <el-input-number :controls="false" :step="0" v-model="pageData.rowCount" @change="handleRowCountChange" style="width: 60px;"></el-input-number>
  40. </el-form-item>
  41. </el-col>
  42. <el-col :span="3">
  43. <el-form-item label="分切卷数">
  44. <el-input-number :controls="false" :step="0" v-model="pageData.rollCount" @change="handleRollCountChange" style="width: 60px;"></el-input-number>
  45. </el-form-item>
  46. </el-col>
  47. </el-row>
  48. <!-- 排数据表格 -->
  49. <div class="rq">
  50. <el-table
  51. :data="rowDataList"
  52. border
  53. style="width: 100%; margin-top: 10px;"
  54. max-height="350"
  55. :span-method="objectSpanMethod">
  56. <el-table-column prop="rowNumber" label="NO." width="50" align="center">
  57. </el-table-column>
  58. <el-table-column label="良品数" width="80" align="center">
  59. <template slot-scope="scope">
  60. <el-input-number
  61. :controls="false" :step="0"
  62. v-model="scope.row.goodQty"
  63. @change="handleQtyChange(scope.row)"
  64. style="width: 100%"
  65. class="good-qty-input"
  66. :style="{'color': '#67c23a', 'font-weight': 'bold'}">
  67. </el-input-number>
  68. </template>
  69. </el-table-column>
  70. <el-table-column label="面损" width="80" align="center">
  71. <template slot-scope="scope">
  72. <el-input-number
  73. :controls="false" :step="0"
  74. v-model="scope.row.surfaceLossQty"
  75. @change="handleQtyChange(scope.row)"
  76. style="width: 100%">
  77. </el-input-number>
  78. </template>
  79. </el-table-column>
  80. <el-table-column label="性能不良" width="80" align="center">
  81. <template slot-scope="scope">
  82. <el-input-number
  83. :controls="false" :step="0"
  84. v-model="scope.row.poorPerformanceQty"
  85. @change="handleQtyChange(scope.row)"
  86. style="width: 100%">
  87. </el-input-number>
  88. </template>
  89. </el-table-column>
  90. <el-table-column label="不良数" width="80" align="right">
  91. <template slot-scope="scope">
  92. <div class="defect-display" :style="{'color': '#f56c6c', 'font-weight': 'bold', 'text-align': 'right', 'padding-right': '10px'}">
  93. {{ scope.row.defectQty }}
  94. </div>
  95. </template>
  96. </el-table-column>
  97. <el-table-column label="良率" width="80" align="center">
  98. <template slot-scope="scope">
  99. <div class="yield-display" :style="{'color': '#409eff', 'font-weight': 'bold'}">
  100. {{ scope.row.yieldRate }}
  101. </div>
  102. </template>
  103. </el-table-column>
  104. <el-table-column label="总数" width="80" align="right">
  105. <template slot-scope="scope">
  106. <div class="total-display" :style="{'text-align': 'right', 'padding-right': '10px'}">
  107. {{ scope.row.totalQty }}
  108. </div>
  109. </template>
  110. </el-table-column>
  111. <el-table-column v-if="scheduleData.packingListFlag === 'Y'" label="Packing List" align="center" class-name="packing-list-column">
  112. <template slot-scope="scope">
  113. <div class="remark-wrapper">
  114. <el-input
  115. type="textarea"
  116. v-model="scope.row.packingList"
  117. resize="none"
  118. :autosize="false"
  119. class="remark-textarea">
  120. </el-input>
  121. </div>
  122. </template>
  123. </el-table-column>
  124. <el-table-column label="备注" align="center" class-name="remark-column">
  125. <template slot-scope="scope">
  126. <div class="remark-wrapper">
  127. <el-input
  128. type="textarea"
  129. v-model="scope.row.remark"
  130. resize="none"
  131. :autosize="false"
  132. class="remark-textarea">
  133. </el-input>
  134. </div>
  135. </template>
  136. </el-table-column>
  137. <el-table-column label="操作" align="center" width="110" class-name="operation-column">
  138. <template slot-scope="scope">
  139. <el-button type="text" size="mini" @click="openRollDefectDialog(scope.$index)">不良原因</el-button>
  140. </template>
  141. </el-table-column>
  142. </el-table>
  143. </div>
  144. </el-form>
  145. <span slot="footer" class="dialog-footer">
  146. <el-button type="primary" @click="checkCreateSeparateRolllBun" :loading="yieldReportLoading" :disabled="exceptionRuleLocked">产量报告</el-button>
  147. <el-button type="success" @click="saveShiftChangeTransfer" :loading="shiftChangeLoading" :disabled="exceptionRuleLocked">换班结转</el-button>
  148. <el-button type="warning" @click="executeAbnormalRollCut" :loading="abnormalCutLoading" :disabled="exceptionRuleLocked">异常截卷</el-button>
  149. <el-button @click="closeDialog" :disabled="exceptionRuleLocked">{{ buttons.closeButton }}</el-button>
  150. </span>
  151. <div
  152. v-if="exceptionRuleLocked"
  153. :class="['exception-rule-mask-screen', isWarningLevelStyle() ? 'warning' : 'serious']">
  154. <div class="exception-rule-bg-grid"></div>
  155. <div class="exception-rule-vignette"></div>
  156. <div class="exception-rule-scanline">
  157. <div class="lines"></div>
  158. <div class="beam"></div>
  159. </div>
  160. <div class="exception-rule-corners">
  161. <div class="corner tl">
  162. <svg viewBox="0 0 80 80">
  163. <path d="M4 40 L4 4 L40 4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" opacity="0.7" />
  164. <line x1="4" y1="16" x2="10" y2="16" stroke="currentColor" stroke-width="1" opacity="0.4" />
  165. <line x1="4" y1="28" x2="8" y2="28" stroke="currentColor" stroke-width="1" opacity="0.4" />
  166. <line x1="16" y1="4" x2="16" y2="10" stroke="currentColor" stroke-width="1" opacity="0.4" />
  167. <line x1="28" y1="4" x2="28" y2="8" stroke="currentColor" stroke-width="1" opacity="0.4" />
  168. <g class="arc">
  169. <circle cx="4" cy="4" r="14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-dasharray="14 74" opacity="0.6" />
  170. </g>
  171. <rect x="1" y="1" width="6" height="6" fill="currentColor" opacity="0.9" />
  172. </svg>
  173. </div>
  174. <div class="corner tr">
  175. <svg viewBox="0 0 80 80">
  176. <path d="M4 40 L4 4 L40 4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" opacity="0.7" />
  177. <line x1="4" y1="16" x2="10" y2="16" stroke="currentColor" stroke-width="1" opacity="0.4" />
  178. <line x1="4" y1="28" x2="8" y2="28" stroke="currentColor" stroke-width="1" opacity="0.4" />
  179. <line x1="16" y1="4" x2="16" y2="10" stroke="currentColor" stroke-width="1" opacity="0.4" />
  180. <line x1="28" y1="4" x2="28" y2="8" stroke="currentColor" stroke-width="1" opacity="0.4" />
  181. <g class="arc">
  182. <circle cx="4" cy="4" r="14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-dasharray="14 74" opacity="0.6" />
  183. </g>
  184. <rect x="1" y="1" width="6" height="6" fill="currentColor" opacity="0.9" />
  185. </svg>
  186. </div>
  187. <div class="corner bl">
  188. <svg viewBox="0 0 80 80">
  189. <path d="M4 40 L4 4 L40 4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" opacity="0.7" />
  190. <line x1="4" y1="16" x2="10" y2="16" stroke="currentColor" stroke-width="1" opacity="0.4" />
  191. <line x1="4" y1="28" x2="8" y2="28" stroke="currentColor" stroke-width="1" opacity="0.4" />
  192. <line x1="16" y1="4" x2="16" y2="10" stroke="currentColor" stroke-width="1" opacity="0.4" />
  193. <line x1="28" y1="4" x2="28" y2="8" stroke="currentColor" stroke-width="1" opacity="0.4" />
  194. <g class="arc">
  195. <circle cx="4" cy="4" r="14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-dasharray="14 74" opacity="0.6" />
  196. </g>
  197. <rect x="1" y="1" width="6" height="6" fill="currentColor" opacity="0.9" />
  198. </svg>
  199. </div>
  200. <div class="corner br">
  201. <svg viewBox="0 0 80 80">
  202. <path d="M4 40 L4 4 L40 4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" opacity="0.7" />
  203. <line x1="4" y1="16" x2="10" y2="16" stroke="currentColor" stroke-width="1" opacity="0.4" />
  204. <line x1="4" y1="28" x2="8" y2="28" stroke="currentColor" stroke-width="1" opacity="0.4" />
  205. <line x1="16" y1="4" x2="16" y2="10" stroke="currentColor" stroke-width="1" opacity="0.4" />
  206. <line x1="28" y1="4" x2="28" y2="8" stroke="currentColor" stroke-width="1" opacity="0.4" />
  207. <g class="arc">
  208. <circle cx="4" cy="4" r="14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-dasharray="14 74" opacity="0.6" />
  209. </g>
  210. <rect x="1" y="1" width="6" height="6" fill="currentColor" opacity="0.9" />
  211. </svg>
  212. </div>
  213. </div>
  214. <div class="exception-rule-statusbar">
  215. <div class="left">
  216. <span class="mes">Boing MES</span>
  217. <span>Site - {{ scheduleData.site || '--' }} / Bu - {{ scheduleData.buNo || '--' }}</span>
  218. </div>
  219. <div class="right">
  220. <span class="lockdown"><span class="dot"></span>{{ getExceptionRuleActiveText() }}</span>
  221. <span>{{ exceptionRuleClockDate || getNowDateText() }}</span>
  222. <span class="clock">{{ exceptionRuleClockTime || getNowTimeText() }}</span>
  223. </div>
  224. </div>
  225. <div class="exception-rule-main">
  226. <div class="exception-rule-alert-icon">
  227. <div class="pulse-ring d0"></div>
  228. <div class="pulse-ring d1"></div>
  229. <div class="pulse-ring d2"></div>
  230. <div class="alert-glow"></div>
  231. <div class="alert-core">
  232. <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
  233. <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/>
  234. <line x1="12" y1="9" x2="12" y2="13"/>
  235. <line x1="12" y1="17" x2="12.01" y2="17"/>
  236. </svg>
  237. </div>
  238. </div>
  239. <div class="exception-rule-title-block">
  240. <div class="exception-rule-title-cn">{{ getExceptionRuleTitleCn() }}</div>
  241. <div class="exception-rule-title-en">PROCESS QUALITY FAULT</div>
  242. <div class="exception-rule-title-rule"></div>
  243. <div class="exception-rule-title-lock">{{ getExceptionRuleTitleLock() }}</div>
  244. </div>
  245. <div class="exception-rule-info-cards">
  246. <div class="exception-rule-info-card">
  247. <span class="label">设备编号 / EQUIPMENT ID</span>
  248. <span class="value">{{ getExceptionRuleEquipmentText() }}</span>
  249. </div>
  250. <div class="exception-rule-info-card">
  251. <span class="label">锁定时间 / LOCKED AT</span>
  252. <span class="value">{{ getExceptionRuleLockedTimeText() }}</span>
  253. </div>
  254. <div class="exception-rule-info-card work-order-card">
  255. <span class="label">工单编号 / WORK ORDER</span>
  256. <div class="work-order-value-line">
  257. <span class="value hl">{{ getExceptionRuleWorkOrderText() }}</span>
  258. <span class="work-order-seq">{{ getExceptionRuleSeqNoText() }}</span>
  259. </div>
  260. </div>
  261. </div>
  262. <div class="exception-rule-flow-steps">
  263. <div class="flow-step done"><div class="dot"></div><div class="label">异常检测</div></div>
  264. <div class="flow-line done"></div>
  265. <div class="flow-step done"><div class="dot"></div><div class="label">工位锁定</div></div>
  266. <div class="flow-line"></div>
  267. <div class="flow-step active"><div class="dot"></div><div class="label">质量审核</div></div>
  268. <div class="flow-line"></div>
  269. <div class="flow-step"><div class="dot"></div><div class="label">解锁生产</div></div>
  270. </div>
  271. </div>
  272. <div class="exception-rule-bottombar">
  273. <div class="inner">
  274. <div class="left">
  275. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  276. <path d="M22 12h-4l-3 9L9 3l-3 9H2"/>
  277. </svg>
  278. <span class="ttl">生产质量指标 / QUALITY METRICS</span>
  279. </div>
  280. <div class="exception-rule-quality-cards">
  281. <div class="quality-card">
  282. <span class="qlabel">不良品率 / DEFECT RATE</span>
  283. <span class="qvalue hl">{{ formatExceptionRuleDefectRateText() }}</span>
  284. </div>
  285. <div class="quality-card">
  286. <span class="qlabel">影响数量 / AFFECTED</span>
  287. <span class="qvalue hl">{{ formatExceptionRuleAffectedQtyText() }}</span>
  288. </div>
  289. <div class="quality-card">
  290. <span class="qlabel">已生产数量 / PRODUCED</span>
  291. <span class="qvalue">{{ formatExceptionRuleProducedQtyText() }}</span>
  292. </div>
  293. </div>
  294. <div class="right">
  295. <div>实时监控中</div>
  296. <div class="auth">
  297. REAL-TIME MONITORING
  298. <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  299. <polyline points="9 18 15 12 9 6"/>
  300. </svg>
  301. </div>
  302. </div>
  303. </div>
  304. </div>
  305. </div>
  306. </el-dialog>
  307. <!-- 分卷不良原因对话框 -->
  308. <el-dialog
  309. title="不良原因"
  310. :close-on-click-modal="false"
  311. :visible.sync="rollDefectDialogVisible"
  312. width="700px"
  313. append-to-body>
  314. <div class="roll-defect-summary">
  315. <span>当前卷{{ currentRollDefectIndex }}</span>
  316. <span>面损合计{{ formatQty(getCurrentRollSurfaceLossQty()) }}</span>
  317. <span>已选不良合计{{ formatQty(getCurrentRollDefectTotal()) }}</span>
  318. </div>
  319. <el-form :inline="true" label-position="top" class="roll-defect-form">
  320. <el-form-item>
  321. <span slot="label" class="defect-code-label" @click="getBaseList(89)"><a>不良代码</a></span>
  322. <el-input
  323. v-model="rollDefectForm.defectCode"
  324. readonly
  325. class="defect-code-input"
  326. style="width: 130px"
  327. placeholder="请选择"
  328. @click.native="getBaseList(89)">
  329. </el-input>
  330. </el-form-item>
  331. <el-form-item label="不良数量">
  332. <el-input-number
  333. v-model="rollDefectForm.defectQty"
  334. :controls="false"
  335. :min="0"
  336. style="width: 120px">
  337. </el-input-number>
  338. </el-form-item>
  339. <el-form-item label="不良描述">
  340. <el-input
  341. v-model="rollDefectForm.defectDesc"
  342. readonly
  343. style="width: 250px">
  344. </el-input>
  345. </el-form-item>
  346. <el-form-item label=" ">
  347. <el-button type="primary" @click="addRollDefectItem">添加</el-button>
  348. </el-form-item>
  349. </el-form>
  350. <el-table
  351. :data="currentRollDefectList"
  352. border
  353. height="260"
  354. style="width: 100%;">
  355. <el-table-column prop="defectCode" label="不良代码" width="120" align="center"></el-table-column>
  356. <el-table-column prop="defectDesc" label="不良描述" min-width="260" align="left"></el-table-column>
  357. <el-table-column prop="defectQty" label="不良数量" width="110" align="right"></el-table-column>
  358. <el-table-column label="操作" width="80" align="center">
  359. <template slot-scope="scope">
  360. <el-button type="text" @click="removeRollDefectItem(scope.$index)">删除</el-button>
  361. </template>
  362. </el-table-column>
  363. </el-table>
  364. <span slot="footer" class="dialog-footer">
  365. <el-button type="primary" @click="rollDefectDialogVisible = false">确定</el-button>
  366. </span>
  367. </el-dialog>
  368. <!-- 固定载具选择对话框 -->
  369. <el-dialog title="固定载具清单" :close-on-click-modal="false" v-drag :visible.sync="carrierModelFlag" width="950px">
  370. <div class="rq">
  371. <el-form :inline="true" label-position="top" :model="carrierSearchData">
  372. <el-form-item label="标签条码">
  373. <el-input v-model="carrierSearchData.carrierNo" clearable style="width: 150px"></el-input>
  374. </el-form-item>
  375. <el-form-item label="载具类型名称">
  376. <el-input v-model="carrierSearchData.carrierTypeName" clearable style="width: 150px"></el-input>
  377. </el-form-item>
  378. <el-form-item label="规格描述">
  379. <el-input v-model="carrierSearchData.specification" clearable style="width: 150px"></el-input>
  380. </el-form-item>
  381. <el-form-item label=" ">
  382. <el-button type="primary" @click="queryFixedCarrierList">查询</el-button>
  383. <el-button v-if="!editBatchVisible" @click="editBatchModel" type="success" icon="el-icon-edit">批量编辑</el-button>
  384. <el-button v-if="editBatchVisible" @click="batchSaveCarrier" type="primary" icon="el-icon-check" :loading="saveLoading">批量保存</el-button>
  385. <el-button type="success" icon="el-icon-plus" @click="addCarrierModal">新增</el-button>
  386. </el-form-item>
  387. </el-form>
  388. <el-table
  389. :height="400"
  390. :data="carrierList"
  391. @row-dblclick="selectCarrier"
  392. border
  393. style="width: 100%;">
  394. <el-table-column
  395. prop="carrierNo"
  396. label="标签条码"
  397. header-align="center"
  398. align="left"
  399. min-width="120">
  400. </el-table-column>
  401. <el-table-column
  402. prop="carrierTypeCode"
  403. label="载具类型编码"
  404. header-align="center"
  405. align="left"
  406. min-width="120">
  407. </el-table-column>
  408. <el-table-column
  409. prop="carrierTypeName"
  410. label="载具类型名称"
  411. header-align="center"
  412. align="left"
  413. min-width="150">
  414. </el-table-column>
  415. <el-table-column
  416. prop="specification"
  417. label="规格描述"
  418. header-align="center"
  419. align="left"
  420. min-width="150">
  421. </el-table-column>
  422. <el-table-column
  423. prop="dimensions"
  424. label="尺寸"
  425. header-align="center"
  426. align="left"
  427. min-width="100">
  428. </el-table-column>
  429. <el-table-column
  430. label="可用数量"
  431. header-align="center"
  432. align="right"
  433. min-width="120">
  434. <template slot-scope="scope">
  435. <el-input-number
  436. v-if="editBatchVisible"
  437. v-model="scope.row.availableQty"
  438. :controls="false"
  439. :min="0"
  440. :precision="0"
  441. style="width: 100%">
  442. </el-input-number>
  443. <span v-else>{{ scope.row.availableQty || 0 }}</span>
  444. </template>
  445. </el-table-column>
  446. <el-table-column
  447. label="操作"
  448. header-align="center"
  449. align="center"
  450. width="80"
  451. fixed="right">
  452. <template slot-scope="scope">
  453. <el-button
  454. type="text"
  455. icon="el-icon-printer"
  456. @click="printCarrier(scope.row)"
  457. style="color: #409eff;">
  458. 打印
  459. </el-button>
  460. </template>
  461. </el-table-column>
  462. </el-table>
  463. </div>
  464. <el-footer style="height:40px;margin-top: 20px;text-align:center">
  465. <el-button type="primary" @click="carrierModelFlag=false">关闭</el-button>
  466. </el-footer>
  467. </el-dialog>
  468. <!-- 新增固定载具对话框 -->
  469. <el-dialog title="新增固定载具" :close-on-click-modal="false" v-drag :visible.sync="addCarrierFlag" width="487px">
  470. <el-form ref="addCarrierForm" :inline="true" label-position="top" :model="addCarrierData" :rules="addCarrierRules" style="margin-left: 7px;margin-top: -5px;">
  471. <el-form-item label="状态" prop="status">
  472. <el-select v-model="addCarrierData.status" placeholder="请选择" style="width: 120px">
  473. <el-option label="空闲" :value="1"></el-option>
  474. <el-option label="占用" :value="2"></el-option>
  475. <el-option label="维修" :value="3"></el-option>
  476. <el-option label="报废" :value="4"></el-option>
  477. <el-option label="外借" :value="5"></el-option>
  478. </el-select>
  479. </el-form-item>
  480. </el-form>
  481. <el-form :inline="true" label-position="top" :model="addCarrierData" :rules="addCarrierRules" style="margin-left: 7px">
  482. <el-form-item label="载具类型编码" prop="carrierTypeCode">
  483. <el-input v-model="addCarrierData.carrierTypeCode" style="width: 150px"></el-input>
  484. </el-form-item>
  485. <el-form-item label="载具类型名称" prop="carrierTypeName">
  486. <el-input v-model="addCarrierData.carrierTypeName" style="width: 285px"></el-input>
  487. </el-form-item>
  488. </el-form>
  489. <el-form :inline="true" label-position="top" :model="addCarrierData" style="margin-left: 7px">
  490. <el-form-item label="固定资产编号">
  491. <el-input v-model="addCarrierData.assetNo" style="width: 150px"></el-input>
  492. </el-form-item>
  493. <el-form-item label="规格描述">
  494. <el-input v-model="addCarrierData.specification" style="width: 285px"></el-input>
  495. </el-form-item>
  496. </el-form>
  497. <el-form :inline="true" label-position="top" :model="addCarrierData" style="margin-left: 7px">
  498. <el-form-item label="采购日期">
  499. <el-date-picker v-model="addCarrierData.purchaseDate" type="date" value-format="yyyy-MM-dd" style="width: 150px"></el-date-picker>
  500. </el-form-item>
  501. <el-form-item label="当前位置">
  502. <el-input v-model="addCarrierData.currentLocation" style="width: 150px"></el-input>
  503. </el-form-item>
  504. <el-form-item label="尺寸">
  505. <el-input v-model="addCarrierData.dimensions" style="width: 120px"></el-input>
  506. </el-form-item>
  507. </el-form>
  508. <el-form :inline="true" label-position="top" :model="addCarrierData" style="margin-left: 7px">
  509. <el-form-item label="可用数量">
  510. <el-input-number v-model="addCarrierData.availableQty" :controls="false" style="width: 102px"></el-input-number>
  511. </el-form-item>
  512. <el-form-item label="最大承重">
  513. <el-input-number v-model="addCarrierData.maxWeight" :controls="false" style="width: 102px"></el-input-number>
  514. </el-form-item>
  515. <el-form-item label="预期使用次数">
  516. <el-input-number v-model="addCarrierData.expectedLifeCycles" :controls="false" style="width: 102px"></el-input-number>
  517. </el-form-item>
  518. <el-form-item label="预期使用寿命">
  519. <el-input-number v-model="addCarrierData.expectedLifeDays" :controls="false" style="width: 101px"></el-input-number>
  520. </el-form-item>
  521. </el-form>
  522. <el-form :inline="true" label-position="top" :model="addCarrierData" style="margin-left: 7px">
  523. <el-form-item label="备注">
  524. <el-input type="textarea" v-model="addCarrierData.remark" :rows="3" resize='none' show-word-limit style="width: 450px;height: 30px"></el-input>
  525. </el-form-item>
  526. </el-form>
  527. <el-footer style="height:35px;margin-top: 55px;text-align:center">
  528. <el-button type="primary" :loading="addCarrierLoading" @click="saveAddCarrier">保存</el-button>
  529. <el-button @click="addCarrierFlag=false">关闭</el-button>
  530. </el-footer>
  531. </el-dialog>
  532. <Chooselist ref="baseList" @getBaseData="getBaseData"></Chooselist>
  533. </div>
  534. </template>
  535. <script>
  536. import {
  537. checkSplitRollExceptionRule,/*创建分卷前异常规则校验*/
  538. getSplitRuleLockStatus,/*查询创建分卷规则锁定状态*/
  539. completeSplitRuleTodoOperation,/*创建分卷完成后回写代办操作标识*/
  540. createSplitSfdcRoll,/*执行创建分卷的操作*/
  541. getFixedCarrierList,/*获取固定载具列表*/
  542. getUnprocessedShiftChangeData,/*查询未处理的换班结转数据*/
  543. saveShiftChangeData,/*保存换班结转数据*/
  544. markShiftChangeAsProcessed,/*更新换班结转数据为已处理*/
  545. saveAbnormalRollData,/*保存异常截卷数据*/
  546. } from '@/api/yieldReport/com_separate_roll.js';
  547. import { checkDefectCode } from '@/api/yieldReport/com_defect_roll.js';
  548. import { fixedCarrierSave } from '@/api/fixedCarrier/fixedCarrier.js';
  549. import Chooselist from '@/views/modules/common/Chooselist';
  550. /*打印标签专用的js*/
  551. import {
  552. printSfdcLabel,
  553. } from "@/views/modules/yieldReport/print_roll_label.js";
  554. import {
  555. searchSysLanguagePackList,
  556. searchSysLanguageParam,
  557. searchFunctionButtonList,
  558. saveButtonList,
  559. searchSysLanguage,
  560. searchLanguageListByLanguageCode,
  561. saveSysLanguageOne,
  562. searchPageLanguageData,
  563. removerLanguage,
  564. saveSysLanguageList
  565. } from "@/api/sysLanguage.js";
  566. import {
  567. getPartLabelTemplateList,
  568. callUspPartLabelTemplate
  569. } from '@/api/wms/wms';
  570. import getLodop from '@/utils/LodopFuncs.js';
  571. import labelPrintTemplates from '@/mixins/labelPrintTemplates.js';
  572. var functionId = 'C10000004';
  573. export default {
  574. name: "com_separate_roll",
  575. mixins: [labelPrintTemplates],
  576. components: {
  577. Chooselist
  578. },
  579. data() {
  580. return {
  581. titleCon: '创建分卷',
  582. showDefault: false,
  583. // 按钮loading状态
  584. yieldReportLoading: false, // 产量报告loading
  585. shiftChangeLoading: false, // 换班结转loading
  586. abnormalCutLoading: false, // 异常截卷loading
  587. exceptionRuleLocked: false, // 异常规则触发后的强制锁定
  588. exceptionRuleLockMessage: '', // 锁定提示文案
  589. exceptionRuleWarningLevel: '', // 预警等级(轻微/一般/严重)
  590. exceptionRuleLockedAt: '', // 锁定时间
  591. exceptionRuleMatchedRollIndex: null, // 触发规则卷序号
  592. exceptionRuleMatchedDefectRate: null, // 触发规则卷不良率
  593. exceptionRuleMatchedTotalQty: null, // 触发规则卷总数
  594. exceptionRuleClockDate: '', // 锁定页面日期
  595. exceptionRuleClockTime: '', // 锁定页面时间
  596. exceptionRuleClockTimer: null, // 锁定页面时钟定时器
  597. pendingRuleFollowupAction: '', // 提示类规则在成功后需打开的功能动作
  598. pendingRuleFollowupResultMap: null,
  599. scheduleData: {
  600. site: this.$store.state.user.site,
  601. username: this.$store.state.user.name,
  602. seqNo: '',
  603. orderNo: '',
  604. itemNo: 0,
  605. resourceId: '',
  606. scheduledDate: '',
  607. shiftNo: '',
  608. partNo: '',
  609. buNo: '',
  610. workCenterNo: '',
  611. workCenterDesc: '',
  612. resourceDesc: '',
  613. rollNo: '',
  614. partDesc: '',
  615. totalRollQty: 0,
  616. planStartTime: '',
  617. planFinishTime: '',
  618. qtyRequiredOriginal: 0,
  619. preItemDesc: '',
  620. nextItemDesc: '',
  621. nextItemNo: 0,
  622. operatorId: '',
  623. functionName: '',
  624. currentRollFlag: false
  625. },
  626. pageData: {
  627. site: this.$store.state.user.site,
  628. username: this.$store.state.user.name,
  629. orderNo: '',
  630. itemNo: '',
  631. seqNo: '',
  632. rollNo: '',
  633. reportDate: '',
  634. reportTime: '',
  635. operatorId: '',
  636. rollQty: 0,
  637. rollNums: 1,
  638. // 新增字段
  639. fixture: '', // 固定载具
  640. rowCount: 0, // 排数
  641. rollCount: 0, // 卷数
  642. carrierNo: ''
  643. },
  644. rowDataList: [], // 排数据列表
  645. // 分卷不良原因(前端暂存)
  646. rollDefectDialogVisible: false,
  647. currentRollDefectIndex: 1,
  648. tagNo: '',
  649. rollDefectForm: {
  650. defectCode: '',
  651. defectDesc: '',
  652. defectQty: null
  653. },
  654. rollDefectMap: {},
  655. currentRollDefectList: [],
  656. operatorData: {
  657. site: this.$store.state.user.site,
  658. username: this.$store.state.user.name,
  659. operatorId: '',
  660. operatorName: '',
  661. status: '',
  662. seqNo: '',
  663. showFlag: false
  664. },
  665. carrierModelFlag: false, // 固定载具对话框标记
  666. carrierList: [], // 固定载具列表
  667. carrierSearchData: { // 固定载具查询条件
  668. carrierNo: '',
  669. carrierTypeName: '',
  670. specification: ''
  671. },
  672. editBatchVisible: false, // 批量编辑状态
  673. saveLoading: false, // 保存loading状态
  674. // 新增固定载具相关
  675. addCarrierFlag: false,
  676. addCarrierLoading: false,
  677. addCarrierData: {
  678. bu: '',
  679. site: '',
  680. buNo: '',
  681. carrierTypeCode: '',
  682. carrierTypeName: '',
  683. assetNo: '',
  684. specification: '',
  685. purchaseDate: null,
  686. currentLocation: '',
  687. dimensions: '',
  688. availableQty: null,
  689. maxWeight: null,
  690. expectedLifeCycles: null,
  691. expectedLifeDays: null,
  692. status: 1,
  693. remark: ''
  694. },
  695. addCarrierRules: {
  696. carrierTypeCode: [{ required: true, message: '请输入载具类型编码', trigger: 'blur' }],
  697. carrierTypeName: [{ required: true, message: '请输入载具类型名称', trigger: 'blur' }]
  698. },
  699. hasCachedData: false, // 是否有缓存数据
  700. cachedDataCount: 0, // 缓存数据条数
  701. cachedRowDataList: [], // 缓存的原始行数据(用于校验)
  702. isLoadingCachedData: false, // 是否正在加载缓存数据(防止触发handleRowCountChange)
  703. lockStatusTimer: null, // 强制锁定状态轮询定时器
  704. buttons: {
  705. confirmButton: '确定',
  706. closeButton: '关闭',
  707. },
  708. buttonList: [
  709. {
  710. functionId: functionId,
  711. languageValue: '确定',
  712. objectId: 'confirmButton',
  713. objectType: 'button',
  714. tableId: '*'
  715. }, {
  716. functionId: functionId,
  717. languageValue: '关闭',
  718. objectId: 'closeButton',
  719. objectType: 'button',
  720. tableId: '*'
  721. },
  722. ],
  723. queryButton: {
  724. functionId: functionId,
  725. table_id: '*',
  726. languageCode: this.$i18n.locale,
  727. objectType: 'button'
  728. },
  729. labels: {
  730. componentTitle: '创建分卷',
  731. currentTime: '当前时间:',
  732. rollQty: '良品数量:',
  733. rollNums: '卷数:',
  734. approvedQtyMustBeInteger: '良品数量必须是正整数!',
  735. rollQtyMustBeInteger: '卷数必须是正整数!',
  736. pleaseSwitchOperator: '请先切换人员!',
  737. approvedQtyMustMoreTanZeroAndBeInteger: '良品数量必须大于零且是整数!',
  738. rollQtyMustMoreTanZeroAndBeInteger: '卷数必须是正整数!',
  739. confirmLabel: '确认',
  740. cancelLabel: '取消',
  741. },
  742. labelsList: [
  743. {
  744. functionId: functionId,
  745. languageValue: '创建分卷',
  746. objectId: 'componentTitle',
  747. objectType: 'label',
  748. tableId: '*'
  749. }, {
  750. functionId: functionId,
  751. languageValue: '当前时间:',
  752. objectId: 'currentTime',
  753. objectType: 'label',
  754. tableId: '*'
  755. }, {
  756. functionId: functionId,
  757. languageValue: '良品数量:',
  758. objectId: 'rollQty',
  759. objectType: 'label',
  760. tableId: '*'
  761. }, {
  762. functionId: functionId,
  763. languageValue: '卷数:',
  764. objectId: 'rollNums',
  765. objectType: 'label',
  766. tableId: '*'
  767. }, {
  768. functionId: functionId,
  769. languageValue: '良品数量必须是正整数!',
  770. objectId: 'approvedQtyMustBeInteger',
  771. objectType: 'label',
  772. tableId: '*'
  773. }, {
  774. functionId: functionId,
  775. languageValue: '卷数必须是正整数!',
  776. objectId: 'rollQtyMustBeInteger',
  777. objectType: 'label',
  778. tableId: '*'
  779. }, {
  780. functionId: functionId,
  781. languageValue: '良品数量必须大于零且是整数!',
  782. objectId: 'approvedQtyMustMoreTanZeroAndBeInteger',
  783. objectType: 'label',
  784. tableId: '*'
  785. }, {
  786. functionId: functionId,
  787. languageValue: '卷数必须大于零且是整数!',
  788. objectId: 'rollQtyMustMoreTanZeroAndBeInteger',
  789. objectType: 'label',
  790. tableId: '*'
  791. }, {
  792. functionId: functionId,
  793. languageValue: '请先切换人员!',
  794. objectId: 'pleaseSwitchOperator',
  795. objectType: 'label',
  796. tableId: '*'
  797. }, {
  798. functionId: functionId,
  799. languageValue: '确认',
  800. objectId: 'confirmLabel',
  801. objectType: 'label',
  802. tableId: '*'
  803. }, {
  804. functionId: functionId,
  805. languageValue: '取消',
  806. objectId: 'cancelLabel',
  807. objectType: 'label',
  808. tableId: '*'
  809. },
  810. ],
  811. queryLabel: {
  812. functionId: functionId,
  813. table_id: '*',
  814. languageCode: this.$i18n.locale,
  815. objectType: 'label'
  816. },
  817. }
  818. },
  819. methods: {
  820. //页面初始化是的方法
  821. async init(scheduleData, operatorData) {
  822. // 设置标记,防止初始化过程中触发handleRowCountChange
  823. this.isLoadingCachedData = true;
  824. //初始化参数
  825. this.scheduleData = scheduleData;
  826. //初始化操作员对象
  827. this.operatorData = JSON.parse(JSON.stringify(operatorData));
  828. //重置时间
  829. this.pageData.reportDate = this.dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss');
  830. this.pageData.reportTime = this.dayjs(new Date()).format('HH:mm:ss');
  831. //设置参数
  832. this.pageData.orderNo = scheduleData.orderNo;
  833. this.pageData.itemNo = scheduleData.itemNo;
  834. this.pageData.seqNo = scheduleData.seqNo;
  835. this.pageData.rollNo = scheduleData.rollNo;
  836. this.pageData.operatorId = operatorData.operatorId;
  837. //清空参数
  838. this.pageData.rollQty = 0;
  839. this.pageData.rollNums = 1;
  840. // 初始化新增字段(先设置默认值)
  841. this.pageData.rowCount = scheduleData.rowCount;
  842. this.pageData.rollCount = scheduleData.rollCount;
  843. this.pageData.fixture = scheduleData.carrierNo;
  844. // 重置异常规则锁定状态
  845. this.exceptionRuleLocked = false;
  846. this.exceptionRuleLockMessage = '';
  847. this.exceptionRuleWarningLevel = '';
  848. this.exceptionRuleLockedAt = '';
  849. this.exceptionRuleMatchedRollIndex = null;
  850. this.exceptionRuleMatchedDefectRate = null;
  851. this.exceptionRuleMatchedTotalQty = null;
  852. this.resetPendingRuleFollowup();
  853. // 重置缓存数据标记
  854. this.hasCachedData = false;
  855. this.cachedDataCount = 0;
  856. this.cachedRowDataList = []; // 重置缓存的原始行数据
  857. // 重置分卷不良原因暂存
  858. this.resetRollDefectData();
  859. // 清空行数据列表
  860. this.rowDataList = [];
  861. //判断是否启用多语言
  862. // this.getMultiLanguageList(); //刷新多语言的信息
  863. //获取焦点
  864. this.$nextTick(() => {
  865. this.$refs.rollQty && this.$refs.rollQty.focus();
  866. });
  867. this.titleCon = this.labels.componentTitle;//重置标题
  868. // 查询是否有缓存的换班结转数据(等待完成)
  869. await this.loadCachedShiftChangeData();
  870. // 如果没有缓存数据,初始化空白行
  871. if (!this.hasCachedData) {
  872. this.initRowDataList();
  873. }
  874. // 初始化后立即同步一次强制锁定状态
  875. try {
  876. await this.refreshSplitRuleLockStatus(false);
  877. } catch (error) {
  878. console.error('初始化锁定状态失败:', error);
  879. }
  880. if (this.exceptionRuleLocked) {
  881. this.startSplitRuleLockPolling();
  882. } else {
  883. this.stopSplitRuleLockPolling();
  884. }
  885. // 取消标记
  886. this.isLoadingCachedData = false;
  887. },
  888. // 查询缓存的换班结转数据
  889. async loadCachedShiftChangeData() {
  890. try {
  891. const params = {
  892. site: this.scheduleData.site,
  893. orderNo: this.scheduleData.orderNo,
  894. seqNo: parseInt(this.scheduleData.seqNo) || 0
  895. };
  896. console.log('查询缓存数据参数:', params);
  897. const {data} = await getUnprocessedShiftChangeData(params);
  898. console.log('查询缓存数据结果:', data);
  899. if (data && data.code === 0 && data.data && data.data.length > 0) {
  900. // 有缓存数据,恢复到界面
  901. this.hasCachedData = true;
  902. this.cachedDataCount = data.count;
  903. const cachedList = data.data;
  904. console.log('缓存数据列表:', cachedList);
  905. // 恢复排数和卷数
  906. if (cachedList.length > 0) {
  907. this.pageData.rowCount = cachedList[0].rowCount || 0;
  908. this.pageData.rollCount = cachedList[0].rollCount || 0;
  909. this.pageData.fixture = cachedList[0].fixtureNo || '';
  910. }
  911. // 恢复行数据列表
  912. this.rowDataList = cachedList.map(item => ({
  913. rowNumber: item.rowBumber,
  914. goodQty: Number(item.goodQty) || 0,
  915. surfaceLossQty: Number(item.surfaceLossQty) || 0,
  916. poorPerformanceQty: Number(item.poorPerformanceQty) || 0,
  917. defectQty: Number(item.defectQty) || 0,
  918. yieldRate: this.calculateYieldRate(Number(item.goodQty) || 0, Number(item.defectQty) || 0),
  919. totalQty: (Number(item.goodQty) || 0) + (Number(item.defectQty) || 0),
  920. packingList: item.packingList || '',
  921. remark: item.remark || ''
  922. }));
  923. // 保存缓存的原始数据(用于产量报告时校验)
  924. this.cachedRowDataList = JSON.parse(JSON.stringify(this.rowDataList));
  925. console.log('恢复后的行数据:', this.rowDataList);
  926. this.$message.info(`已加载上次换班结转的缓存数据(${this.cachedDataCount}条)`);
  927. }
  928. // 如果没有缓存数据,不做任何处理,由init方法负责初始化
  929. } catch (error) {
  930. console.error('加载缓存数据失败:', error);
  931. // 加载失败时不做处理,由init方法负责初始化
  932. }
  933. },
  934. // 计算良率
  935. calculateYieldRate(goodQty, defectQty) {
  936. const totalQty = goodQty + defectQty;
  937. if (totalQty > 0) {
  938. return ((goodQty / totalQty) * 100).toFixed(2) + '%';
  939. }
  940. return '0.00%';
  941. },
  942. // ===================== 新增分卷优化方法 =====================
  943. // 初始化排数据列表
  944. initRowDataList() {
  945. console.log('initRowDataList被调用, isLoadingCachedData=', this.isLoadingCachedData, ', 调用栈:', new Error().stack);
  946. this.rowDataList = []
  947. for (let i = 0; i < this.pageData.rowCount; i++) {
  948. this.rowDataList.push({
  949. rowNumber: i,
  950. goodQty: 0,
  951. surfaceLossQty: 0,
  952. poorPerformanceQty: 0,
  953. defectQty: 0,
  954. yieldRate: '0.00%',
  955. totalQty: 0,
  956. packingList: '',
  957. remark: ''
  958. })
  959. }
  960. },
  961. // 排数变化时重新初始化
  962. handleRowCountChange() {
  963. console.log('handleRowCountChange被调用, isLoadingCachedData=', this.isLoadingCachedData);
  964. // 如果是从缓存加载数据,不要重新初始化
  965. if (this.isLoadingCachedData) {
  966. console.log('跳过初始化,因为正在加载缓存数据');
  967. return;
  968. }
  969. if (this.pageData.rollCount > this.pageData.rowCount) {
  970. this.pageData.rollCount = this.pageData.rowCount
  971. }
  972. this.initRowDataList()
  973. this.resetRollDefectData()
  974. },
  975. // 卷数变化时校验并清理不良原因缓存
  976. handleRollCountChange() {
  977. if (this.pageData.rollCount > this.pageData.rowCount) {
  978. this.pageData.rollCount = this.pageData.rowCount
  979. }
  980. const hasDefectReason = Object.keys(this.rollDefectMap).some(key => {
  981. const list = this.rollDefectMap[key]
  982. return Array.isArray(list) && list.length > 0
  983. })
  984. if (hasDefectReason) {
  985. this.$message.warning('卷数已变更,原不良原因已清空,请重新维护')
  986. }
  987. this.resetRollDefectData()
  988. },
  989. // 数量变化时自动计算不良数、良率和总数
  990. handleQtyChange(row) {
  991. // 计算不良数 = 面损 + 性能不良
  992. row.defectQty = (row.surfaceLossQty || 0) + (row.poorPerformanceQty || 0)
  993. // 计算总数 = 良品数 + 不良数
  994. row.totalQty = (row.goodQty || 0) + row.defectQty
  995. // 计算良率 = 良品数 / 总数 * 100%
  996. if (row.totalQty > 0) {
  997. const yieldValue = ((row.goodQty || 0) / row.totalQty * 100).toFixed(2)
  998. row.yieldRate = yieldValue + '%'
  999. } else {
  1000. row.yieldRate = '0.00%'
  1001. }
  1002. },
  1003. resetRollDefectData() {
  1004. this.rollDefectDialogVisible = false
  1005. this.currentRollDefectIndex = 1
  1006. this.rollDefectMap = {}
  1007. this.currentRollDefectList = []
  1008. this.resetRollDefectForm()
  1009. },
  1010. resetRollDefectForm() {
  1011. this.rollDefectForm = {
  1012. defectCode: '',
  1013. defectDesc: '',
  1014. defectQty: null
  1015. }
  1016. },
  1017. normalizeRollDefectMap() {
  1018. const maxRollCount = Number(this.pageData.rollCount || 0)
  1019. Object.keys(this.rollDefectMap).forEach(key => {
  1020. if (Number(key) > maxRollCount) {
  1021. this.$delete(this.rollDefectMap, key)
  1022. }
  1023. })
  1024. },
  1025. getRollSegments() {
  1026. const totalRows = Array.isArray(this.rowDataList) ? this.rowDataList.length : 0
  1027. const rollCount = Number(this.pageData.rollCount || 0)
  1028. if (totalRows <= 0 || rollCount <= 0) {
  1029. return []
  1030. }
  1031. const rowsPerRoll = Math.floor(totalRows / rollCount)
  1032. const remainingRows = totalRows % rollCount
  1033. const segments = []
  1034. let startRow = 0
  1035. for (let i = 0; i < rollCount; i++) {
  1036. const currentRollRows = rowsPerRoll + (i < remainingRows ? 1 : 0)
  1037. if (currentRollRows <= 0) {
  1038. continue
  1039. }
  1040. segments.push({
  1041. rollIndex: i + 1,
  1042. startRow: startRow,
  1043. endRow: startRow + currentRollRows - 1,
  1044. rowCount: currentRollRows
  1045. })
  1046. startRow += currentRollRows
  1047. }
  1048. return segments
  1049. },
  1050. getRollSegmentByRowIndex(rowIndex) {
  1051. const segments = this.getRollSegments()
  1052. return segments.find(item => rowIndex >= item.startRow && rowIndex <= item.endRow) || null
  1053. },
  1054. getRollSegmentByRollIndex(rollIndex) {
  1055. const segments = this.getRollSegments()
  1056. return segments.find(item => item.rollIndex === rollIndex) || null
  1057. },
  1058. ensureRollDefectList(rollIndex) {
  1059. const key = String(rollIndex)
  1060. if (!Array.isArray(this.rollDefectMap[key])) {
  1061. this.$set(this.rollDefectMap, key, [])
  1062. }
  1063. },
  1064. openRollDefectDialog(rowIndex) {
  1065. const segment = this.getRollSegmentByRowIndex(rowIndex)
  1066. if (!segment) {
  1067. this.$message.warning('当前卷信息无效,请先确认排数和卷数')
  1068. return
  1069. }
  1070. this.currentRollDefectIndex = segment.rollIndex
  1071. this.ensureRollDefectList(segment.rollIndex)
  1072. this.currentRollDefectList = this.rollDefectMap[String(segment.rollIndex)]
  1073. this.resetRollDefectForm()
  1074. this.rollDefectDialogVisible = true
  1075. },
  1076. getRollSurfaceLossQty(rollIndex) {
  1077. const segment = this.getRollSegmentByRollIndex(rollIndex)
  1078. if (!segment) {
  1079. return 0
  1080. }
  1081. let total = 0
  1082. for (let i = segment.startRow; i <= segment.endRow; i++) {
  1083. const row = this.rowDataList[i]
  1084. total += Number(row && row.surfaceLossQty ? row.surfaceLossQty : 0)
  1085. }
  1086. return total
  1087. },
  1088. getCurrentRollSurfaceLossQty() {
  1089. return this.getRollSurfaceLossQty(this.currentRollDefectIndex)
  1090. },
  1091. getRollDefectTotal(rollIndex) {
  1092. const list = this.rollDefectMap[String(rollIndex)] || []
  1093. return list.reduce((sum, item) => sum + Number(item && item.defectQty ? item.defectQty : 0), 0)
  1094. },
  1095. getCurrentRollDefectTotal() {
  1096. return this.getRollDefectTotal(this.currentRollDefectIndex)
  1097. },
  1098. formatQty(value) {
  1099. const num = Number(value || 0)
  1100. return Number(num.toFixed(6)).toString()
  1101. },
  1102. getCurrentWorkCenterNo() {
  1103. return (this.scheduleData.workCenterNo || '').trim()
  1104. },
  1105. buildDefectCheckParams(defectCode, workCenterNo) {
  1106. return {
  1107. site: this.pageData.site,
  1108. orderNo: this.pageData.orderNo,
  1109. itemNo: this.pageData.itemNo,
  1110. seqNo: this.pageData.seqNo,
  1111. rollNo: this.pageData.rollNo,
  1112. defectCode: defectCode,
  1113. workCenterNo: workCenterNo
  1114. }
  1115. },
  1116. async addRollDefectItem() {
  1117. if (!this.currentRollDefectList) {
  1118. return
  1119. }
  1120. const workCenterNo = this.getCurrentWorkCenterNo()
  1121. if (!workCenterNo) {
  1122. this.$message.error('当前派工单未维护加工中心,不能选择不良原因')
  1123. return
  1124. }
  1125. this.rollDefectForm.defectCode = (this.rollDefectForm.defectCode || '').trim().toUpperCase()
  1126. if (!this.rollDefectForm.defectCode) {
  1127. this.$message.error('请先选择不良代码')
  1128. return
  1129. }
  1130. let checkResultMap = {}
  1131. try {
  1132. const { data } = await checkDefectCode(this.buildDefectCheckParams(this.rollDefectForm.defectCode, workCenterNo))
  1133. if (data.code == 500) {
  1134. this.$message.error(data.msg || '不良代码不属于当前加工中心')
  1135. return
  1136. }
  1137. checkResultMap = data.resultMap || {}
  1138. } catch (e) {
  1139. this.$message.error('不良代码校验失败,请稍后重试')
  1140. return
  1141. }
  1142. if (checkResultMap.defectDesc) {
  1143. this.rollDefectForm.defectDesc = checkResultMap.defectDesc
  1144. }
  1145. if (!this.rollDefectForm.defectDesc) {
  1146. this.$message.error('请选择有效的不良代码')
  1147. return
  1148. }
  1149. const defectQty = Number(this.rollDefectForm.defectQty)
  1150. if (!defectQty || defectQty <= 0) {
  1151. this.$message.error('不良数量必须大于0')
  1152. return
  1153. }
  1154. const existing = this.currentRollDefectList.find(item => item.defectCode === this.rollDefectForm.defectCode)
  1155. if (existing) {
  1156. existing.defectQty = Number(existing.defectQty || 0) + defectQty
  1157. existing.defectDesc = this.rollDefectForm.defectDesc
  1158. } else {
  1159. this.currentRollDefectList.push({
  1160. defectCode: this.rollDefectForm.defectCode,
  1161. defectDesc: this.rollDefectForm.defectDesc,
  1162. defectQty: defectQty
  1163. })
  1164. }
  1165. this.resetRollDefectForm()
  1166. },
  1167. removeRollDefectItem(index) {
  1168. if (!this.currentRollDefectList) {
  1169. return
  1170. }
  1171. this.currentRollDefectList.splice(index, 1)
  1172. },
  1173. getBaseData(val) {
  1174. if (this.tagNo === 89) {
  1175. this.rollDefectForm.defectCode = val.DefectCode || ''
  1176. this.rollDefectForm.defectDesc = val.DefectDesc || ''
  1177. }
  1178. },
  1179. getBaseList(val) {
  1180. this.tagNo = val
  1181. this.$nextTick(() => {
  1182. let strVal = ''
  1183. let conSql = ''
  1184. if (val === 89) {
  1185. const workCenterNo = this.getCurrentWorkCenterNo()
  1186. if (!workCenterNo) {
  1187. this.$message.error('当前派工单未维护加工中心,无法选择不良代码')
  1188. return
  1189. }
  1190. strVal = this.rollDefectForm.defectCode || ''
  1191. conSql = " and isnull(A.workCenter_no,'') = '" + workCenterNo + "'"
  1192. }
  1193. this.$refs.baseList.init(val, strVal, conSql)
  1194. })
  1195. },
  1196. getRollDefectDetails(rollIndex) {
  1197. const list = this.rollDefectMap[String(rollIndex)] || []
  1198. return list
  1199. .map(item => ({
  1200. defectCode: item.defectCode,
  1201. defectDesc: item.defectDesc || '',
  1202. defectQty: Number(item.defectQty || 0)
  1203. }))
  1204. .filter(item => item.defectCode && item.defectQty > 0)
  1205. },
  1206. // 校验每卷不良原因合计必须等于该卷面损合计
  1207. validateRollDefectQtyBySurfaceLoss() {
  1208. const segments = this.getRollSegments()
  1209. if (!segments.length) {
  1210. return true
  1211. }
  1212. this.normalizeRollDefectMap()
  1213. for (let i = 0; i < segments.length; i++) {
  1214. const rollIndex = segments[i].rollIndex
  1215. const surfaceLossQty = this.getRollSurfaceLossQty(rollIndex)
  1216. const defectReasonQty = this.getRollDefectTotal(rollIndex)
  1217. if (Math.abs(surfaceLossQty - defectReasonQty) > 0.000001) {
  1218. this.$message.error(`${rollIndex}卷不良原因数量合计(${this.formatQty(defectReasonQty)})必须等于该卷面损数合计(${this.formatQty(surfaceLossQty)})`)
  1219. return false
  1220. }
  1221. }
  1222. return true
  1223. },
  1224. // 验证排数据
  1225. validateRowData() {
  1226. if (this.pageData.rowCount <= 0) {
  1227. this.$message.warning('请输入有效的排数')
  1228. return false
  1229. }
  1230. if (this.pageData.rollCount <= 0) {
  1231. this.$message.warning('请输入有效的卷数')
  1232. return false
  1233. }
  1234. if (this.pageData.rollCount > this.pageData.rowCount) {
  1235. this.$message.warning('卷数不能大于排数')
  1236. return false
  1237. }
  1238. // 检查是否有数据
  1239. const hasData = this.rowDataList.some(row => (row.goodQty > 0 || row.defectQty > 0))
  1240. if (!hasData) {
  1241. this.$message.warning('请至少输入一行数据')
  1242. return false
  1243. }
  1244. // 检查每一行的良品数必须大于0
  1245. for (let i = 0; i < this.rowDataList.length; i++) {
  1246. const row = this.rowDataList[i]
  1247. if (row.goodQty === null || row.goodQty === undefined || row.goodQty <= 0) {
  1248. this.$message.error(`${i + 1} 排的良品数必须大于0`)
  1249. return false
  1250. }
  1251. }
  1252. return true
  1253. },
  1254. // Packing List 条件必填校验(仅在 packingListFlag = Y 时生效)
  1255. validatePackingListRequired() {
  1256. if (this.scheduleData.packingListFlag !== 'Y') {
  1257. return true
  1258. }
  1259. const segments = this.getRollSegments()
  1260. if (!segments.length) {
  1261. return true
  1262. }
  1263. for (let i = 0; i < segments.length; i++) {
  1264. const segment = segments[i]
  1265. let hasPackingList = false
  1266. for (let rowIndex = segment.startRow; rowIndex <= segment.endRow; rowIndex++) {
  1267. const row = this.rowDataList[rowIndex]
  1268. const packingList = row && row.packingList !== undefined && row.packingList !== null
  1269. ? String(row.packingList).trim()
  1270. : ''
  1271. if (packingList) {
  1272. hasPackingList = true
  1273. break
  1274. }
  1275. }
  1276. if (!hasPackingList) {
  1277. this.$message.error(`${segment.rollIndex}卷的Packing List不能为空`)
  1278. return false
  1279. }
  1280. }
  1281. return true
  1282. },
  1283. // 换班结转保存
  1284. async saveShiftChangeTransfer() {
  1285. // 人员判断
  1286. if (this.pageData.operatorId === '' || this.pageData.operatorId == null) {
  1287. this.$message.error(this.labels.pleaseSwitchOperator);
  1288. return false;
  1289. }
  1290. // 基础校验
  1291. if (this.pageData.rowCount <= 0) {
  1292. this.$message.warning('请输入有效的排数');
  1293. return false;
  1294. }
  1295. if (this.pageData.rollCount <= 0) {
  1296. this.$message.warning('请输入有效的卷数');
  1297. return false;
  1298. }
  1299. if (this.pageData.rollCount > this.pageData.rowCount) {
  1300. this.$message.warning('卷数不能大于排数');
  1301. return false;
  1302. }
  1303. // 如果有缓存数据,校验本次数据条数必须大于等于之前的数据
  1304. if (this.hasCachedData && this.rowDataList.length < this.cachedDataCount) {
  1305. this.$message.error(`数据条数不能减少!当前缓存数据${this.cachedDataCount}条,本次提交${this.rowDataList.length}`);
  1306. return false;
  1307. }
  1308. // 开启loading
  1309. this.shiftChangeLoading = true;
  1310. try {
  1311. // 构建保存数据
  1312. const saveData = {
  1313. site: this.scheduleData.site,
  1314. buNo: this.scheduleData.buNo,
  1315. orderNo: this.scheduleData.orderNo,
  1316. seqNo: parseInt(this.scheduleData.seqNo) || 0,
  1317. fixtureNo: this.pageData.fixture,
  1318. shiftFrom: this.scheduleData.shiftNo || '', // 当前班次
  1319. rowCount: this.pageData.rowCount,
  1320. rollCount: this.pageData.rollCount,
  1321. createdBy: this.pageData.operatorId,
  1322. rowDataList: this.rowDataList.map(row => ({
  1323. rowNumber: row.rowNumber,
  1324. goodQty: row.goodQty || 0,
  1325. surfaceLossQty: row.surfaceLossQty || 0,
  1326. poorPerformanceQty: row.poorPerformanceQty || 0,
  1327. defectQty: row.defectQty || 0,
  1328. packingList: row.packingList || '',
  1329. remark: row.remark || ''
  1330. }))
  1331. };
  1332. const {data} = await saveShiftChangeData(saveData);
  1333. if (data && data.code === 0) {
  1334. this.$message.success('换班结转数据保存成功!');
  1335. // 更新缓存状态
  1336. this.hasCachedData = true;
  1337. this.cachedDataCount = this.rowDataList.length;
  1338. // 关闭对话框
  1339. this.closeDialog();
  1340. } else {
  1341. this.$message.error(data.msg || '换班结转保存失败');
  1342. }
  1343. } catch (error) {
  1344. console.error('换班结转保存失败:', error);
  1345. this.$message.error('换班结转保存失败:' + error.message);
  1346. } finally {
  1347. this.shiftChangeLoading = false;
  1348. }
  1349. },
  1350. // 异常截卷操作(产量报告 + 换班结转)
  1351. async executeAbnormalRollCut() {
  1352. // 人员判断
  1353. if (this.pageData.operatorId === '' || this.pageData.operatorId == null) {
  1354. this.$message.error(this.labels.pleaseSwitchOperator);
  1355. return false;
  1356. }
  1357. // 基础校验
  1358. if (this.pageData.rowCount <= 0) {
  1359. this.$message.warning('请输入有效的排数');
  1360. return false;
  1361. }
  1362. if (this.pageData.rollCount <= 0) {
  1363. this.$message.warning('请输入有效的卷数');
  1364. return false;
  1365. }
  1366. if (this.pageData.rollCount > this.pageData.rowCount) {
  1367. this.$message.warning('卷数不能大于排数');
  1368. return false;
  1369. }
  1370. // 验证排数据
  1371. if (!this.validateRowData()) {
  1372. return false;
  1373. }
  1374. // Packing List 显示时要求必填
  1375. if (!this.validatePackingListRequired()) {
  1376. return false;
  1377. }
  1378. // 校验每卷不良原因数量是否等于该卷面损数量
  1379. if (!this.validateRollDefectQtyBySurfaceLoss()) {
  1380. return false;
  1381. }
  1382. // 确认框
  1383. try {
  1384. await this.$confirm('确定执行异常截卷操作吗?该操作将执行产量报告。', '提示', {
  1385. confirmButtonText: '确定',
  1386. cancelButtonText: '取消',
  1387. type: 'warning'
  1388. });
  1389. } catch (e) {
  1390. return false; // 用户取消
  1391. }
  1392. try {
  1393. const passRuleCheck = await this.checkSplitRuleBeforeAction('异常截卷')
  1394. if (!passRuleCheck) {
  1395. return false
  1396. }
  1397. } catch (error) {
  1398. this.$message.error(error.message || '异常规则校验失败')
  1399. return false
  1400. }
  1401. // 开启loading
  1402. this.abnormalCutLoading = true;
  1403. try {
  1404. // ========== 第一步:执行产量报告 + 保存缓存数据 ==========
  1405. // 计算分卷
  1406. const rowsPerRoll = Math.floor(this.pageData.rowCount / this.pageData.rollCount);
  1407. const remainingRows = this.pageData.rowCount % this.pageData.rollCount;
  1408. let currentRowIndex = 0;
  1409. const allPrintList = []; // 收集所有打印数据
  1410. // 循环创建每一卷
  1411. for (let rollIndex = 0; rollIndex < this.pageData.rollCount; rollIndex++) {
  1412. const currentRollRows = rowsPerRoll + (rollIndex < remainingRows ? 1 : 0);
  1413. // 计算当前卷的良品和不良总数
  1414. let totalGoodQty = 0;
  1415. let totalDefectQty = 0;
  1416. const rollRows = [];
  1417. // 获取当前卷的备注
  1418. let rollRemark = '';
  1419. let rollPackingList = '';
  1420. // 收集当前卷的行数据
  1421. const currentRollRowDataList = [];
  1422. for (let i = 0; i < currentRollRows; i++) {
  1423. const row = this.rowDataList[currentRowIndex + i];
  1424. totalGoodQty += row.goodQty || 0;
  1425. totalDefectQty += row.defectQty || 0;
  1426. if (i === 0) {
  1427. rollRemark = row.remark || '';
  1428. rollPackingList = row.packingList || '';
  1429. }
  1430. rollRows.push({
  1431. rowNumber: row.rowNumber,
  1432. goodQty: row.goodQty || 0,
  1433. surfaceLossQty: row.surfaceLossQty || 0,
  1434. poorPerformanceQty: row.poorPerformanceQty || 0,
  1435. defectQty: row.defectQty || 0,
  1436. totalQty: row.totalQty || 0,
  1437. packingList: row.packingList || '',
  1438. remark: row.remark || ''
  1439. });
  1440. // 收集当前卷的行数据用于保存缓存
  1441. currentRollRowDataList.push({
  1442. rowNumber: row.rowNumber,
  1443. goodQty: row.goodQty || 0,
  1444. surfaceLossQty: row.surfaceLossQty || 0,
  1445. poorPerformanceQty: row.poorPerformanceQty || 0,
  1446. defectQty: row.defectQty || 0,
  1447. packingList: row.packingList || '',
  1448. remark: row.remark || ''
  1449. });
  1450. }
  1451. // 构建请求数据
  1452. const requestData = {
  1453. ...this.pageData,
  1454. buNo: this.scheduleData.buNo,
  1455. rollQty: totalGoodQty,
  1456. rollNums: 1,
  1457. defectQty: totalDefectQty,
  1458. rollRows: rollRows,
  1459. rollDefectDetails: this.getRollDefectDetails(rollIndex + 1),
  1460. packingList: rollPackingList,
  1461. remark: rollRemark
  1462. };
  1463. // 调用后端创建单个卷
  1464. const {data} = await createSplitSfdcRoll(requestData);
  1465. if (data.code === 500) {
  1466. throw new Error(data.msg);
  1467. }
  1468. // 获取当前卷返回的新卷号
  1469. let currentNewRollNo = '';
  1470. if (data.printList && data.printList.length > 0) {
  1471. currentNewRollNo = data.printList[0].rollNo || '';
  1472. // 收集打印数据
  1473. allPrintList.push(...data.printList);
  1474. }
  1475. const abnormalRollData = {
  1476. site: this.scheduleData.site,
  1477. buNo: this.scheduleData.buNo,
  1478. orderNo: this.scheduleData.orderNo,
  1479. seqNo: parseInt(this.scheduleData.seqNo) || 0,
  1480. fixtureNo: this.pageData.fixture,
  1481. shiftFrom: this.scheduleData.shiftNo || '',
  1482. rowCount: currentRollRows,
  1483. rollCount: 1,
  1484. createdBy: this.pageData.operatorId,
  1485. currentRollNo: this.scheduleData.rollNo || '',
  1486. newRollNo: currentNewRollNo,
  1487. operatorId: this.pageData.operatorId,
  1488. rowDataList: currentRollRowDataList
  1489. };
  1490. const {data: abnormalData} = await saveAbnormalRollData(abnormalRollData);
  1491. if (!abnormalData || abnormalData.code !== 0) {
  1492. console.error(`${rollIndex + 1}卷缓存数据保存失败:`, abnormalData && abnormalData.msg);
  1493. }
  1494. this.$message.success(`${rollIndex + 1}/${this.pageData.rollCount}卷创建成功`);
  1495. currentRowIndex += currentRollRows;
  1496. }
  1497. // 如果有换班结转缓存数据,标记为已处理
  1498. if (this.hasCachedData) {
  1499. try {
  1500. await markShiftChangeAsProcessed({
  1501. site: this.scheduleData.site,
  1502. orderNo: this.scheduleData.orderNo,
  1503. seqNo: parseInt(this.scheduleData.seqNo) || 0,
  1504. processedBy: this.pageData.operatorId
  1505. });
  1506. this.hasCachedData = false;
  1507. this.cachedDataCount = 0;
  1508. } catch (err) {
  1509. console.error('标记换班结转数据为已处理失败:', err);
  1510. }
  1511. }
  1512. this.$message.success('异常截卷操作完成!数据已保存。');
  1513. // ========== 第二步:打印标签(和产量报告一样)==========
  1514. if (allPrintList.length > 0) {
  1515. await this.printLabelsWithTemplate(allPrintList);
  1516. }
  1517. // 放行执行完成后:回写operate_flag=Y并清理对应缓存
  1518. try {
  1519. await this.completeSplitRuleTodoOperationIfNeeded();
  1520. await this.refreshSplitRuleLockStatus(false);
  1521. } catch (completeError) {
  1522. console.error('回写代办操作状态失败:', completeError);
  1523. this.$message.warning(completeError.message || '回写代办操作状态失败,请联系管理员检查');
  1524. }
  1525. if (this.triggerPendingRuleFollowupIfNeeded()) {
  1526. this.abnormalCutLoading = false;
  1527. return true;
  1528. }
  1529. // 延时关闭弹窗
  1530. setTimeout(() => {
  1531. this.closeDialog();
  1532. this.abnormalCutLoading = false;
  1533. }, 1000);
  1534. } catch (error) {
  1535. console.error('异常截卷操作失败:', error);
  1536. this.$message.error('异常截卷操作失败:' + error.message);
  1537. this.abnormalCutLoading = false;
  1538. }
  1539. },
  1540. // 表格合并行方法(备注列根据卷数合并)
  1541. objectSpanMethod({ row, column, rowIndex, columnIndex }) {
  1542. // 对备注、Packing List和操作列进行按卷合并
  1543. if (column.label === '备注' || column.label === 'Packing List' || column.label === '操作') {
  1544. const segment = this.getRollSegmentByRowIndex(rowIndex)
  1545. if (!segment) {
  1546. return
  1547. }
  1548. if (rowIndex === segment.startRow) {
  1549. return {
  1550. rowspan: segment.rowCount,
  1551. colspan: 1
  1552. }
  1553. }
  1554. return {
  1555. rowspan: 0,
  1556. colspan: 0
  1557. }
  1558. }
  1559. },
  1560. // 计算备注wrapper的样式(使用padding-top实现垂直居中)
  1561. getRemarkWrapperStyle(rowIndex) {
  1562. if (this.pageData.rowCount <= 0 || this.pageData.rollCount <= 0) {
  1563. return {}
  1564. }
  1565. const segment = this.getRollSegmentByRowIndex(rowIndex)
  1566. if (!segment) {
  1567. return {}
  1568. }
  1569. const currentRollRows = segment.rowCount
  1570. // 每行高度约为32px
  1571. const rowHeight = 32
  1572. const totalHeight = currentRollRows * rowHeight
  1573. // textarea的实际高度约为24px(单行)
  1574. const textareaHeight = 24
  1575. // 计算需要的padding-top来实现垂直居中
  1576. const paddingTop = (totalHeight - textareaHeight) / 2
  1577. // 返回样式对象
  1578. return {
  1579. paddingTop: paddingTop + 'px'
  1580. }
  1581. },
  1582. // ===================== 分卷优化方法结束 =====================
  1583. // 查询固定载具列表
  1584. queryFixedCarrierList() {
  1585. const params = {
  1586. site: this.scheduleData.site,
  1587. carrierNo: this.carrierSearchData.carrierNo,
  1588. carrierTypeName: this.carrierSearchData.carrierTypeName,
  1589. specification: this.carrierSearchData.specification
  1590. }
  1591. getFixedCarrierList(params).then(({data}) => {
  1592. if (data && data.code === 0) {
  1593. this.carrierList = data.data
  1594. this.carrierModelFlag = true
  1595. } else {
  1596. this.$message.error(data.msg || '获取固定载具列表失败')
  1597. }
  1598. }).catch(error => {
  1599. this.$message.error('获取固定载具列表失败: ' + error.message)
  1600. })
  1601. },
  1602. // 选中固定载具
  1603. selectCarrier(row) {
  1604. this.pageData.fixture = row.carrierNo
  1605. this.carrierModelFlag = false
  1606. },
  1607. // 批量编辑模态框
  1608. editBatchModel() {
  1609. if (this.editBatchVisible) {
  1610. this.editBatchVisible = false
  1611. } else {
  1612. this.editBatchVisible = true
  1613. }
  1614. },
  1615. // 批量保存固定载具可用数量
  1616. batchSaveCarrier() {
  1617. this.saveLoading = true
  1618. // 调用API保存
  1619. import('@/api/yieldReport/com_separate_roll.js').then(module => {
  1620. const { updateFixedCarrierBatch } = module
  1621. updateFixedCarrierBatch(this.carrierList).then(({data}) => {
  1622. if (data && data.code === 0) {
  1623. this.$message.success('批量保存成功')
  1624. this.editBatchVisible = false
  1625. this.queryFixedCarrierList()
  1626. } else {
  1627. this.$message.error(data.msg || '批量保存失败')
  1628. }
  1629. }).catch(error => {
  1630. this.$message.error('批量保存失败: ' + error.message)
  1631. }).finally(() => {
  1632. this.saveLoading = false
  1633. })
  1634. })
  1635. },
  1636. // 打开新增固定载具对话框
  1637. addCarrierModal() {
  1638. console.log(this.scheduleData)
  1639. this.addCarrierData = {
  1640. bu: this.scheduleData.site + '_' + this.scheduleData.buNo || '',
  1641. site: this.scheduleData.site || '',
  1642. buNo: this.scheduleData.buNo || '',
  1643. carrierTypeCode: '',
  1644. carrierTypeName: '',
  1645. assetNo: '',
  1646. specification: '',
  1647. purchaseDate: null,
  1648. currentLocation: '',
  1649. dimensions: '',
  1650. availableQty: null,
  1651. maxWeight: null,
  1652. expectedLifeCycles: null,
  1653. expectedLifeDays: null,
  1654. status: 1,
  1655. remark: ''
  1656. };
  1657. this.addCarrierFlag = true;
  1658. },
  1659. // 保存新增固定载具
  1660. saveAddCarrier() {
  1661. this.$refs.addCarrierForm.validate((valid) => {
  1662. if (valid) {
  1663. this.addCarrierLoading = true;
  1664. fixedCarrierSave(this.addCarrierData).then(({data}) => {
  1665. if (data && data.code === 0) {
  1666. this.$message.success('新增成功');
  1667. this.addCarrierFlag = false;
  1668. this.queryFixedCarrierList();
  1669. } else {
  1670. this.$message.error(data.msg || '新增失败');
  1671. }
  1672. }).catch(error => {
  1673. this.$message.error('新增失败: ' + error.message);
  1674. }).finally(() => {
  1675. this.addCarrierLoading = false;
  1676. });
  1677. }
  1678. });
  1679. },
  1680. // 打印固定载具
  1681. printCarrier(row) {
  1682. const LODOP = getLodop()
  1683. if (!LODOP) {
  1684. this.$message.error('未检测到打印控件,请安装CLodop打印控件!')
  1685. return
  1686. }
  1687. try {
  1688. // 初始化打印
  1689. LODOP.PRINT_INIT('固定载具标签打印')
  1690. // 设置纸张尺寸:70mm x 20mm
  1691. LODOP.SET_PRINT_PAGESIZE(0, 700, 200, '')
  1692. // 绘制外边框
  1693. LODOP.ADD_PRINT_RECT('1mm', '1mm', '68mm', '18mm', 0, 1)
  1694. // 绘制中间竖线(左右分隔)
  1695. LODOP.ADD_PRINT_LINE('1mm', '50mm', '19mm', '50mm', 0, 1)
  1696. // 绘制左侧横线(三行文本之间的分隔)
  1697. LODOP.ADD_PRINT_LINE('7mm', '1mm', '7mm', '50mm', 0, 1)
  1698. LODOP.ADD_PRINT_LINE('13mm', '1mm', '13mm', '50mm', 0, 1)
  1699. // 左侧文字信息 - 标签条码(第一行)
  1700. const text1 = LODOP.ADD_PRINT_TEXT('2.5mm', '2mm', '46mm', '5mm', 'NO.:' + (row.carrierNo || ''))
  1701. LODOP.SET_PRINT_STYLEA(text1, "FontName", "Microsoft YaHei")
  1702. LODOP.SET_PRINT_STYLEA(text1, "FontSize", 5)
  1703. LODOP.SET_PRINT_STYLEA(text1, "Bold", 1)
  1704. // 左侧文字信息 - 载具类型编码(第二行)
  1705. const text2 = LODOP.ADD_PRINT_TEXT('8.5mm', '2mm', '46mm', '5mm', 'Type:' + (row.carrierTypeCode || ''))
  1706. LODOP.SET_PRINT_STYLEA(text2, "FontName", "Microsoft YaHei")
  1707. LODOP.SET_PRINT_STYLEA(text2, "FontSize", 5)
  1708. LODOP.SET_PRINT_STYLEA(text2, "Bold", 1)
  1709. // 左侧文字信息 - 载具类型名称(第三行)
  1710. const text3 = LODOP.ADD_PRINT_TEXT('14.5mm', '2mm', '46mm', '5mm', 'TypeDesc:' + (row.carrierTypeName || ''))
  1711. LODOP.SET_PRINT_STYLEA(text3, "FontName", "Microsoft YaHei")
  1712. LODOP.SET_PRINT_STYLEA(text3, "FontSize", 5)
  1713. LODOP.SET_PRINT_STYLEA(text3, "Bold", 1)
  1714. // 右侧二维码(使用 LODOP 原生二维码)
  1715. const qrIndex = LODOP.ADD_PRINT_BARCODE('1.5mm', '50mm', '18.5mm', '18.5mm', 'QRCode', row.carrierNo || '')
  1716. LODOP.SET_PRINT_STYLEA(qrIndex, "QRCodeVersion", 0)
  1717. LODOP.SET_PRINT_STYLEA(qrIndex, "QRCodeErrorLevel", "M")
  1718. // 执行打印
  1719. LODOP.PRINT()
  1720. } catch (error) {
  1721. console.error('打印失败:', error)
  1722. this.$message.error('打印失败: ' + error.message)
  1723. }
  1724. },
  1725. /*关闭modal*/
  1726. closeDialog(){
  1727. if (this.exceptionRuleLocked) {
  1728. return
  1729. }
  1730. this.stopSplitRuleLockPolling()
  1731. this.clearExceptionRuleVisualMeta()
  1732. this.resetPendingRuleFollowup()
  1733. //刷新报工的页面
  1734. this.$emit('refreshPageData');
  1735. //关闭当前的页面
  1736. this.$emit('update:visible', false);
  1737. },
  1738. handleMainDialogBeforeClose (done) {
  1739. if (this.exceptionRuleLocked) {
  1740. return
  1741. }
  1742. done()
  1743. },
  1744. onDialogClosed () {
  1745. this.stopSplitRuleLockPolling()
  1746. this.clearExceptionRuleVisualMeta()
  1747. },
  1748. normalizeWarningLevel (value) {
  1749. return String(value || '').trim()
  1750. },
  1751. isWarningLevelStyle () {
  1752. const warningLevel = this.normalizeWarningLevel(this.exceptionRuleWarningLevel)
  1753. return warningLevel === '轻微' || warningLevel === '一般'
  1754. },
  1755. getExceptionRuleTitleCn () {
  1756. return this.isWarningLevelStyle() ? '过程质量预警' : '过程质量异常'
  1757. },
  1758. getExceptionRuleTitleLock () {
  1759. return this.isWarningLevelStyle() ? '系统预警 / SYSTEM WARNING' : '系统锁定 / SYSTEM LOCKDOWN'
  1760. },
  1761. getExceptionRuleActiveText () {
  1762. return this.isWarningLevelStyle() ? 'WARNING ACTIVE' : 'LOCKDOWN ACTIVE'
  1763. },
  1764. getNowDateText () {
  1765. return this.dayjs(new Date()).format('YYYY-MM-DD')
  1766. },
  1767. getNowTimeText () {
  1768. return this.dayjs(new Date()).format('HH:mm:ss')
  1769. },
  1770. refreshExceptionRuleClock () {
  1771. const now = this.dayjs(new Date())
  1772. this.exceptionRuleClockDate = now.format('YYYY-MM-DD')
  1773. this.exceptionRuleClockTime = now.format('HH:mm:ss')
  1774. },
  1775. startExceptionRuleClock () {
  1776. this.refreshExceptionRuleClock()
  1777. if (this.exceptionRuleClockTimer) {
  1778. return
  1779. }
  1780. this.exceptionRuleClockTimer = setInterval(() => {
  1781. this.refreshExceptionRuleClock()
  1782. }, 1000)
  1783. },
  1784. stopExceptionRuleClock () {
  1785. if (this.exceptionRuleClockTimer) {
  1786. clearInterval(this.exceptionRuleClockTimer)
  1787. this.exceptionRuleClockTimer = null
  1788. }
  1789. },
  1790. toRuleNumber (value) {
  1791. const num = Number(value)
  1792. return Number.isFinite(num) ? num : null
  1793. },
  1794. formatRuleNumber (value, scale) {
  1795. const num = this.toRuleNumber(value)
  1796. if (num === null) {
  1797. return '--'
  1798. }
  1799. const digits = Number.isInteger(scale) && scale >= 0 ? scale : 2
  1800. const fixed = num.toFixed(digits)
  1801. return fixed.replace(/\.?0+$/, '')
  1802. },
  1803. getExceptionRuleEquipmentText () {
  1804. return this.scheduleData.resourceDesc || this.scheduleData.resourceId || '--'
  1805. },
  1806. getExceptionRuleLockedTimeText () {
  1807. return this.exceptionRuleLockedAt || `${this.getNowDateText()} ${this.getNowTimeText()}`
  1808. },
  1809. getExceptionRuleWorkOrderText () {
  1810. return this.scheduleData.orderNo || '--'
  1811. },
  1812. getExceptionRuleSeqNoText () {
  1813. return this.scheduleData.seqNo || '--'
  1814. },
  1815. formatExceptionRuleDefectRateText () {
  1816. const text = this.formatRuleNumber(this.exceptionRuleMatchedDefectRate, 2)
  1817. return text === '--' ? '--' : `${text}%`
  1818. },
  1819. formatExceptionRuleAffectedQtyText () {
  1820. const text = this.formatRuleNumber(this.exceptionRuleMatchedTotalQty, 2)
  1821. return text === '--' ? '--' : `${text} pcs`
  1822. },
  1823. formatExceptionRuleProducedQtyText () {
  1824. const text = this.formatRuleNumber(this.scheduleData.qtyApprove, 2)
  1825. return text === '--' ? '--' : `${text} pcs`
  1826. },
  1827. applyExceptionRuleVisualMeta (resultMap, refreshLockTime) {
  1828. this.startExceptionRuleClock()
  1829. const warningLevel = this.normalizeWarningLevel(resultMap && resultMap.warningLevel)
  1830. if (warningLevel) {
  1831. this.exceptionRuleWarningLevel = warningLevel
  1832. }
  1833. const lockedTime = String((resultMap && resultMap.lockedTime) || '').trim()
  1834. if (lockedTime) {
  1835. this.exceptionRuleLockedAt = lockedTime
  1836. } else if (refreshLockTime || !this.exceptionRuleLockedAt) {
  1837. this.exceptionRuleLockedAt = `${this.getNowDateText()} ${this.getNowTimeText()}`
  1838. }
  1839. if (resultMap && Object.prototype.hasOwnProperty.call(resultMap, 'matchedRollIndex')) {
  1840. this.exceptionRuleMatchedRollIndex = resultMap.matchedRollIndex
  1841. }
  1842. if (resultMap && Object.prototype.hasOwnProperty.call(resultMap, 'matchedDefectRate')) {
  1843. this.exceptionRuleMatchedDefectRate = resultMap.matchedDefectRate
  1844. }
  1845. if (resultMap && Object.prototype.hasOwnProperty.call(resultMap, 'matchedTotalQty')) {
  1846. this.exceptionRuleMatchedTotalQty = resultMap.matchedTotalQty
  1847. }
  1848. },
  1849. clearExceptionRuleVisualMeta () {
  1850. this.stopExceptionRuleClock()
  1851. this.exceptionRuleWarningLevel = ''
  1852. this.exceptionRuleLockedAt = ''
  1853. this.exceptionRuleMatchedRollIndex = null
  1854. this.exceptionRuleMatchedDefectRate = null
  1855. this.exceptionRuleMatchedTotalQty = null
  1856. this.exceptionRuleClockDate = ''
  1857. this.exceptionRuleClockTime = ''
  1858. },
  1859. buildSplitRuleStatusPayload () {
  1860. return {
  1861. site: this.scheduleData.site,
  1862. buNo: this.scheduleData.buNo,
  1863. orderNo: this.scheduleData.orderNo,
  1864. seqNo: this.scheduleData.seqNo
  1865. }
  1866. },
  1867. async refreshSplitRuleLockStatus (showUnlockHint) {
  1868. const payload = this.buildSplitRuleStatusPayload()
  1869. if (!payload.site || !payload.buNo || !payload.orderNo || !payload.seqNo) {
  1870. return
  1871. }
  1872. const wasLocked = this.exceptionRuleLocked
  1873. const { data } = await getSplitRuleLockStatus(payload)
  1874. if (!data || data.code === 500) {
  1875. return
  1876. }
  1877. const resultMap = data.resultMap || {}
  1878. const locked = !!resultMap.locked
  1879. this.exceptionRuleLocked = locked
  1880. if (locked) {
  1881. this.applyExceptionRuleVisualMeta(resultMap, !wasLocked)
  1882. this.exceptionRuleLockMessage = resultMap.message || '当前异常代办尚未审核,已锁定创建分卷操作。'
  1883. if (!wasLocked) {
  1884. this.$emit('lockPageByExceptionRule', resultMap)
  1885. }
  1886. if (!this.lockStatusTimer) {
  1887. this.startSplitRuleLockPolling()
  1888. }
  1889. return
  1890. }
  1891. this.stopSplitRuleLockPolling()
  1892. this.exceptionRuleLockMessage = ''
  1893. this.clearExceptionRuleVisualMeta()
  1894. this.$emit('unlockPageByExceptionRule', resultMap)
  1895. if (wasLocked && showUnlockHint && resultMap.bypassByAudit) {
  1896. this.$message.success('QC已审核,已解除锁定,可继续操作。')
  1897. }
  1898. },
  1899. startSplitRuleLockPolling () {
  1900. this.stopSplitRuleLockPolling()
  1901. this.lockStatusTimer = setInterval(() => {
  1902. this.refreshSplitRuleLockStatus(true).catch(err => {
  1903. console.error('轮询锁定状态失败:', err)
  1904. })
  1905. }, 10000)
  1906. },
  1907. stopSplitRuleLockPolling () {
  1908. if (this.lockStatusTimer) {
  1909. clearInterval(this.lockStatusTimer)
  1910. this.lockStatusTimer = null
  1911. }
  1912. },
  1913. resetPendingRuleFollowup () {
  1914. this.pendingRuleFollowupAction = ''
  1915. this.pendingRuleFollowupResultMap = null
  1916. },
  1917. triggerPendingRuleFollowupIfNeeded () {
  1918. const action = this.pendingRuleFollowupAction
  1919. if (!action) {
  1920. return false
  1921. }
  1922. const resultMap = this.pendingRuleFollowupResultMap || {}
  1923. this.resetPendingRuleFollowup()
  1924. this.$emit('refreshPageData')
  1925. this.$emit('update:visible', false)
  1926. if (action === 'OPEN_DEFECT') {
  1927. this.$emit('openDefectByExceptionRule', resultMap)
  1928. return true
  1929. }
  1930. if (action === 'OPEN_DOWNTIME') {
  1931. this.$emit('openDownTimeByExceptionRule', resultMap)
  1932. return true
  1933. }
  1934. return false
  1935. },
  1936. async completeSplitRuleTodoOperationIfNeeded () {
  1937. const payload = this.buildSplitRuleStatusPayload()
  1938. if (!payload.site || !payload.buNo || !payload.orderNo || !payload.seqNo) {
  1939. return
  1940. }
  1941. payload.operatorId = this.pageData.operatorId
  1942. payload.functionName = this.scheduleData.functionName || ''
  1943. const { data } = await completeSplitRuleTodoOperation(payload)
  1944. if (data && data.code === 500) {
  1945. throw new Error(data.msg || '回写代办操作状态失败')
  1946. }
  1947. },
  1948. buildSplitRuleCheckPayload (actionName) {
  1949. return {
  1950. site: this.scheduleData.site,
  1951. buNo: this.scheduleData.buNo,
  1952. orderNo: this.scheduleData.orderNo,
  1953. itemNo: this.scheduleData.itemNo,
  1954. seqNo: this.scheduleData.seqNo,
  1955. rollNo: this.scheduleData.rollNo,
  1956. operatorId: this.pageData.operatorId,
  1957. functionName: actionName || '',
  1958. fixture: this.pageData.fixture || '',
  1959. shiftNo: this.scheduleData.shiftNo || '',
  1960. rowCount: this.pageData.rowCount || 0,
  1961. rollCount: this.pageData.rollCount || 0,
  1962. totalRollQty: Number(this.scheduleData.totalRollQty || 0),
  1963. rollRows: (this.rowDataList || []).map(row => ({
  1964. rowNumber: row.rowNumber,
  1965. goodQty: row.goodQty || 0,
  1966. surfaceLossQty: row.surfaceLossQty || 0,
  1967. poorPerformanceQty: row.poorPerformanceQty || 0,
  1968. defectQty: row.defectQty || 0,
  1969. totalQty: row.totalQty || 0,
  1970. packingList: row.packingList || '',
  1971. remark: row.remark || ''
  1972. }))
  1973. }
  1974. },
  1975. async checkSplitRuleBeforeAction (actionName) {
  1976. this.resetPendingRuleFollowup()
  1977. const payload = this.buildSplitRuleCheckPayload(actionName)
  1978. const { data } = await checkSplitRollExceptionRule(payload)
  1979. if (!data || data.code === 500) {
  1980. throw new Error((data && data.msg) || '异常规则校验失败')
  1981. }
  1982. const resultMap = data.resultMap || {}
  1983. if (resultMap.bypassByAudit) {
  1984. this.exceptionRuleLocked = false
  1985. this.exceptionRuleLockMessage = ''
  1986. this.clearExceptionRuleVisualMeta()
  1987. this.stopSplitRuleLockPolling()
  1988. this.$emit('unlockPageByExceptionRule', resultMap)
  1989. }
  1990. if (!resultMap.matched) {
  1991. return true
  1992. }
  1993. const action = resultMap.action || 'NONE'
  1994. const message = resultMap.message || '触发异常规则,请先处理。'
  1995. if (action === 'OPEN_DEFECT') {
  1996. this.$message.warning(message)
  1997. this.pendingRuleFollowupAction = action
  1998. this.pendingRuleFollowupResultMap = resultMap
  1999. return true
  2000. }
  2001. if (action === 'OPEN_DOWNTIME') {
  2002. this.$message.warning(message)
  2003. this.pendingRuleFollowupAction = action
  2004. this.pendingRuleFollowupResultMap = resultMap
  2005. return true
  2006. }
  2007. if (action === 'LOCK_PAGE') {
  2008. this.exceptionRuleLocked = true
  2009. this.applyExceptionRuleVisualMeta(resultMap, true)
  2010. this.exceptionRuleLockMessage = message
  2011. this.$emit('lockPageByExceptionRule', resultMap)
  2012. this.startSplitRuleLockPolling()
  2013. return false
  2014. }
  2015. return true
  2016. },
  2017. /*检查材料卷号的数据*/
  2018. checkValidQty() {
  2019. //获取当前是的数量
  2020. let rollQty = parseFloat(this.pageData.rollQty);
  2021. //判断是否可以修改
  2022. if (rollQty <= 0 || !Number.isInteger(rollQty)){
  2023. this.$message.error(this.labels.approvedQtyMustBeInteger);
  2024. return false;
  2025. }
  2026. },
  2027. /*检查材料卷号的数据*/
  2028. checkRollNums() {
  2029. //获取当前是的数量
  2030. let rollNums = parseFloat(this.pageData.rollNums);
  2031. //判断是否是正整数
  2032. if (rollNums <= 0 || !Number.isInteger(rollNums)){
  2033. this.$message.error(this.labels.rollQtyMustBeInteger);
  2034. return false;
  2035. }
  2036. },
  2037. checkCreateSeparateRolllBun() {
  2038. //人员判断
  2039. if (this.pageData.operatorId === '' || this.pageData.operatorId == null) {
  2040. this.$message.error(this.labels.pleaseSwitchOperator);
  2041. return false;
  2042. }
  2043. // 如果有缓存数据,校验本次数据条数必须大于等于之前的数据
  2044. if (this.hasCachedData && this.rowDataList.length < this.cachedDataCount) {
  2045. this.$message.error(`数据条数不能减少!当前缓存数据${this.cachedDataCount}条,本次提交${this.rowDataList.length}`);
  2046. return false;
  2047. }
  2048. // 如果有缓存数据,校验缓存中带过来的数据的良品数、面损、性能不良不能小于缓存中的数据
  2049. if (this.hasCachedData && this.cachedRowDataList.length > 0) {
  2050. for (let i = 0; i < this.cachedRowDataList.length; i++) {
  2051. const cachedRow = this.cachedRowDataList[i];
  2052. const currentRow = this.rowDataList[i];
  2053. if (currentRow) {
  2054. // 校验良品数不能小于缓存值
  2055. if ((currentRow.goodQty || 0) < (cachedRow.goodQty || 0)) {
  2056. this.$message.error(`${i + 1}排的良品数量(${currentRow.goodQty})不能小于缓存值(${cachedRow.goodQty}`);
  2057. return false;
  2058. }
  2059. // 校验面损数量不能小于缓存值
  2060. if ((currentRow.surfaceLossQty || 0) < (cachedRow.surfaceLossQty || 0)) {
  2061. this.$message.error(`${i + 1}排的面损数量(${currentRow.surfaceLossQty})不能小于缓存值(${cachedRow.surfaceLossQty}`);
  2062. return false;
  2063. }
  2064. // 校验性能不良数量不能小于缓存值
  2065. if ((currentRow.poorPerformanceQty || 0) < (cachedRow.poorPerformanceQty || 0)) {
  2066. this.$message.error(`${i + 1}排的性能不良数量(${currentRow.poorPerformanceQty})不能小于缓存值(${cachedRow.poorPerformanceQty}`);
  2067. return false;
  2068. }
  2069. }
  2070. }
  2071. }
  2072. // 开启loading
  2073. this.yieldReportLoading = true;
  2074. this.createSeparateRolllOperation();
  2075. },
  2076. /*执行创建分卷的操作*/
  2077. async createSeparateRolllOperation() {
  2078. // 验证排数据
  2079. if (!this.validateRowData()) {
  2080. this.yieldReportLoading = false;
  2081. return false
  2082. }
  2083. // Packing List 显示时要求必填
  2084. if (!this.validatePackingListRequired()) {
  2085. this.yieldReportLoading = false;
  2086. return false
  2087. }
  2088. // 校验每卷不良原因数量是否等于该卷面损数量
  2089. if (!this.validateRollDefectQtyBySurfaceLoss()) {
  2090. this.yieldReportLoading = false;
  2091. return false
  2092. }
  2093. try {
  2094. const passRuleCheck = await this.checkSplitRuleBeforeAction('产量报告')
  2095. if (!passRuleCheck) {
  2096. this.yieldReportLoading = false
  2097. return false
  2098. }
  2099. } catch (error) {
  2100. this.$message.error(error.message || '异常规则校验失败')
  2101. this.yieldReportLoading = false
  2102. return false
  2103. }
  2104. try {
  2105. // 计算分卷
  2106. const rowsPerRoll = Math.floor(this.pageData.rowCount / this.pageData.rollCount)
  2107. const remainingRows = this.pageData.rowCount % this.pageData.rollCount
  2108. let currentRowIndex = 0
  2109. const allPrintList = []
  2110. // 循环创建每一卷
  2111. for (let rollIndex = 0; rollIndex < this.pageData.rollCount; rollIndex++) {
  2112. // 计算当前卷的排数
  2113. const currentRollRows = rowsPerRoll + (rollIndex < remainingRows ? 1 : 0)
  2114. // 计算当前卷的良品和不良总数
  2115. let totalGoodQty = 0
  2116. let totalDefectQty = 0
  2117. const rollRows = []
  2118. // 获取当前卷的备注(第一排的备注)
  2119. let rollRemark = ''
  2120. let rollPackingList = ''
  2121. for (let i = 0; i < currentRollRows; i++) {
  2122. const row = this.rowDataList[currentRowIndex + i]
  2123. totalGoodQty += row.goodQty || 0
  2124. totalDefectQty += row.defectQty || 0
  2125. // 第一排的备注作为整卷的备注
  2126. if (i === 0) {
  2127. rollRemark = row.remark || ''
  2128. rollPackingList = row.packingList || ''
  2129. }
  2130. rollRows.push({
  2131. rowNumber: row.rowNumber,
  2132. goodQty: row.goodQty || 0,
  2133. surfaceLossQty: row.surfaceLossQty || 0,
  2134. poorPerformanceQty: row.poorPerformanceQty || 0,
  2135. defectQty: row.defectQty || 0,
  2136. totalQty: row.totalQty || 0,
  2137. packingList: row.packingList || '',
  2138. remark: row.remark || ''
  2139. })
  2140. }
  2141. // 构建请求数据(每次调用rollNums固定为1)
  2142. const requestData = {
  2143. ...this.pageData,
  2144. buNo: this.scheduleData.buNo,
  2145. rollQty: totalGoodQty, // 该卷的良品总数
  2146. rollNums: 1, // 固定为1
  2147. defectQty: totalDefectQty, // 该卷的不良总数
  2148. rollRows: rollRows, // 该卷包含的排数据
  2149. rollDefectDetails: this.getRollDefectDetails(rollIndex + 1),
  2150. packingList: rollPackingList, // 该卷的Packing List
  2151. remark: rollRemark // 该卷的备注
  2152. }
  2153. // 调用后端创建单个卷
  2154. const {data} = await createSplitSfdcRoll(requestData)
  2155. if (data.code === 500) {
  2156. throw new Error(data.msg)
  2157. }
  2158. // 收集打印数据
  2159. if (data.printList && data.printList.length > 0) {
  2160. allPrintList.push(...data.printList)
  2161. }
  2162. // 进度提示
  2163. this.$message.success(`${rollIndex + 1}/${this.pageData.rollCount}卷创建成功`)
  2164. currentRowIndex += currentRollRows
  2165. }
  2166. // 所有卷创建完成
  2167. this.$message.success('创建分卷完成!')
  2168. // 如果有缓存数据,标记为已处理
  2169. if (this.hasCachedData) {
  2170. try {
  2171. await markShiftChangeAsProcessed({
  2172. site: this.scheduleData.site,
  2173. orderNo: this.scheduleData.orderNo,
  2174. seqNo: parseInt(this.scheduleData.seqNo) || 0,
  2175. processedBy: this.pageData.operatorId
  2176. });
  2177. // 重置缓存状态
  2178. this.hasCachedData = false;
  2179. this.cachedDataCount = 0;
  2180. } catch (err) {
  2181. console.error('标记换班结转数据为已处理失败:', err);
  2182. }
  2183. }
  2184. // 打印标签(优化版:使用模板打印)
  2185. if (allPrintList.length > 0) {
  2186. await this.printLabelsWithTemplate(allPrintList)
  2187. }
  2188. // 放行执行完成后:回写operate_flag=Y并清理对应缓存
  2189. try {
  2190. await this.completeSplitRuleTodoOperationIfNeeded()
  2191. await this.refreshSplitRuleLockStatus(false)
  2192. } catch (completeError) {
  2193. console.error('回写代办操作状态失败:', completeError)
  2194. this.$message.warning(completeError.message || '回写代办操作状态失败,请联系管理员检查')
  2195. }
  2196. if (this.triggerPendingRuleFollowupIfNeeded()) {
  2197. this.yieldReportLoading = false
  2198. return true
  2199. }
  2200. // 延时关闭弹窗
  2201. setTimeout(() =>{
  2202. //关闭当前的页面
  2203. this.closeDialog()
  2204. this.yieldReportLoading = false;
  2205. }, 1000)
  2206. } catch (error) {
  2207. this.$message.error('创建分卷失败: ' + error.message)
  2208. this.yieldReportLoading = false;
  2209. }
  2210. },
  2211. /**
  2212. * 使用模板打印标签优化版
  2213. * @param {Array} printList - 打印数据列表
  2214. */
  2215. async printLabelsWithTemplate(printList) {
  2216. try {
  2217. // 1. 查询物料的标签模板设置(找默认模板)
  2218. const templateParams = {
  2219. site: this.scheduleData.site,
  2220. buNo: this.scheduleData.buNo,
  2221. partNo: this.scheduleData.partNo
  2222. };
  2223. const {data: templateData} = await getPartLabelTemplateList(templateParams);
  2224. if (!templateData || templateData.code !== 0 || !templateData.list || templateData.list.length === 0) {
  2225. // 如果没有模板配置,使用原有的流转标签打印方式
  2226. console.warn('未找到物料标签模板配置!');
  2227. return;
  2228. }
  2229. // 找到默认模板(default_flag = '1')
  2230. let defaultTemplate = templateData.list.find(item => item.defaultFlag === '1');
  2231. if (!defaultTemplate && templateData.list.length > 0) {
  2232. // 如果没有设置默认模板,使用第一个
  2233. defaultTemplate = templateData.list[0];
  2234. }
  2235. if (!defaultTemplate) {
  2236. console.warn('未找到默认模板!');
  2237. return;
  2238. }
  2239. const labelNo = defaultTemplate.labelNo; // A001、A002、A003
  2240. // 2. 获取打印机(使用默认打印机)
  2241. const LODOP = getLodop();
  2242. if (!LODOP) {
  2243. this.$message.warning('无法连接到打印控件,跳过打印');
  2244. return;
  2245. }
  2246. // 获取默认打印机
  2247. const printerCount = LODOP.GET_PRINTER_COUNT();
  2248. if (printerCount === 0) {
  2249. this.$message.warning('未检测到打印机,跳过打印');
  2250. return;
  2251. }
  2252. const defaultPrinterName = LODOP.GET_PRINTER_NAME(0); // 使用第一个打印机作为默认打印机
  2253. // 3. 调用存储过程获取打印参数
  2254. const printDataList = [];
  2255. for (let i = 0; i < printList.length; i++) {
  2256. const item = printList[i];
  2257. const params = {
  2258. site: this.scheduleData.site,
  2259. buNo: this.scheduleData.buNo,
  2260. menuID: '104003006', // 过站采集功能ID
  2261. relatedOrderNo: this.scheduleData.orderNo,
  2262. relatedOrderLineNo: this.scheduleData.seqNo,
  2263. documentNo: '',
  2264. partNo: this.scheduleData.partNo,
  2265. labelNo: labelNo,
  2266. rollNo: item.rollNo
  2267. };
  2268. const {data: printData} = await callUspPartLabelTemplate(params);
  2269. if (printData && printData.code === 0 && printData.row) {
  2270. printDataList.push({
  2271. ...printData.row,
  2272. labelNo: labelNo
  2273. });
  2274. } else {
  2275. console.warn(`获取卷号 ${item.rollNo} 的打印参数失败,使用原始数据`);
  2276. // 如果获取打印参数失败,使用原始数据
  2277. printDataList.push({
  2278. ...item,
  2279. labelNo: labelNo
  2280. });
  2281. }
  2282. }
  2283. // 4. 执行打印
  2284. await this.executePrintWithTemplate(LODOP, printDataList, labelNo, defaultPrinterName);
  2285. this.$message.success('标签打印任务已发送!');
  2286. } catch (error) {
  2287. console.error('模板打印失败:', error);
  2288. // this.$message.warning('模板打印失败,使用默认流转标签打印');
  2289. // // 降级处理:使用原有的流转标签打印方式
  2290. // printSfdcLabel(printList);
  2291. }
  2292. },
  2293. /**
  2294. * 执行模板打印
  2295. * @param {Object} LODOP - 打印控件对象
  2296. * @param {Array} printDataList - 打印数据列表
  2297. * @param {String} labelNo - 标签模板编号 (A001/A002/A003)
  2298. * @param {String} printerName - 打印机名称
  2299. */
  2300. async executePrintWithTemplate(LODOP, printDataList, labelNo, printerName) {
  2301. console.log('开始打印,标签数量:', printDataList.length, '标签数据:', printDataList);
  2302. // 循环打印每个标签(每个标签单独打印一次)
  2303. for (let i = 0; i < printDataList.length; i++) {
  2304. const printData = printDataList[i];
  2305. // 每个标签单独初始化一个打印任务
  2306. LODOP.PRINT_INIT('分卷标签打印_' + (i + 1));
  2307. // 设置打印模式
  2308. LODOP.SET_PRINT_MODE("PRINT_NOCOLLATE", true);
  2309. // 根据 labelNo 调用不同的打印方法(来自 labelPrintTemplates mixin)
  2310. // 注意:不需要 NEWPAGE,因为每个标签是独立的打印任务
  2311. if (labelNo === 'A001') {
  2312. await this.printLabelA001(LODOP, printData, false);
  2313. } else if (labelNo === 'A002') {
  2314. this.printLabelA002(LODOP, printData, false);
  2315. } else if (labelNo === 'A003') {
  2316. this.printLabelA003(LODOP, printData, false);
  2317. } else if (labelNo === 'A004') {
  2318. this.printLabelA004(LODOP, printData, false);
  2319. } else {
  2320. console.warn(`未知的标签模板:${labelNo},跳过该标签`);
  2321. continue;
  2322. }
  2323. // 执行打印
  2324. LODOP.PRINT();
  2325. console.log(`${i + 1}张标签已发送打印, 卷号: ${printData.rollNo}`);
  2326. }
  2327. },
  2328. // 保存 默认配置 列
  2329. async saveMultiLanguage() {
  2330. // 保存页面 button label title 属性
  2331. let buttons = this.buttonList;
  2332. let labels = this.labelsList;
  2333. await saveButtonList(buttons)
  2334. await saveButtonList(labels)
  2335. },
  2336. getMultiLanguageList() {
  2337. //首先查询当前按钮的多语言
  2338. searchFunctionButtonList(this.queryButton).then(({data}) => {
  2339. if (data && data.code == 0 ) {
  2340. this.buttons = data.data
  2341. } else {
  2342. // saveButtonList(this.buttonList).then(({data}) => {
  2343. // })
  2344. }
  2345. });
  2346. //其次查询当前标签的多语言
  2347. searchFunctionButtonList(this.queryLabel).then(({data}) => {
  2348. if (data && data.code == 0 ) {
  2349. this.labels = data.data
  2350. } else {
  2351. // saveButtonList(this.buttonList).then(({data}) => {
  2352. // })
  2353. }
  2354. });
  2355. },
  2356. },
  2357. created() {
  2358. // this.factoryList()
  2359. // this.getLanguageList()
  2360. },
  2361. beforeDestroy() {
  2362. this.stopSplitRuleLockPolling()
  2363. this.stopExceptionRuleClock()
  2364. }
  2365. }
  2366. </script>
  2367. <style scoped lang="scss">
  2368. @import url('https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700&display=swap');
  2369. /*调节页面button和input的上下间距*/
  2370. .customer-css .customer-button{
  2371. margin-top: 25px;
  2372. }
  2373. /* 固定载具清除按钮 */
  2374. .fixture-clear-btn {
  2375. cursor: pointer;
  2376. color: #C0C4CC;
  2377. &:hover {
  2378. color: #909399;
  2379. }
  2380. }
  2381. /*调节fieldset下的样式*/
  2382. .customer-fieldset .customer-item{
  2383. margin-top: -15px;
  2384. }
  2385. /*fieldset下table的样式*/
  2386. .customer-fieldset /deep/ .el-table__header th.is-leaf{
  2387. line-height: 16px;
  2388. }
  2389. /deep/ .customer-tab .el-tabs__content{
  2390. padding: 0px !important;
  2391. }
  2392. /* 良品数输入框样式 - 绿色加粗 */
  2393. .good-qty-input >>> .el-input__inner {
  2394. font-weight: bold !important;
  2395. color: #67c23a !important;
  2396. font-size: 12px !important;
  2397. }
  2398. ::v-deep .good-qty-input .el-input__inner {
  2399. font-weight: bold !important;
  2400. color: #67c23a !important;
  2401. font-size: 12px !important;
  2402. }
  2403. /deep/ .good-qty-input .el-input__inner {
  2404. font-weight: bold !important;
  2405. color: #67c23a !important;
  2406. font-size: 12px !important;
  2407. }
  2408. /* 不良数输入框样式 - 红色加粗 */
  2409. .defect-qty-input >>> .el-input__inner {
  2410. font-weight: bold !important;
  2411. color: #f56c6c !important;
  2412. font-size: 12px !important;
  2413. }
  2414. ::v-deep .defect-qty-input .el-input__inner {
  2415. font-weight: bold !important;
  2416. color: #f56c6c !important;
  2417. font-size: 12px !important;
  2418. }
  2419. /deep/ .defect-qty-input .el-input__inner {
  2420. font-weight: bold !important;
  2421. color: #f56c6c !important;
  2422. font-size: 12px !important;
  2423. }
  2424. /* 总数显示样式 */
  2425. .total-display {
  2426. font-weight: bold;
  2427. color: #303133;
  2428. font-size: 12px;
  2429. }
  2430. /* 备注列单元格样式 - 使用绝对定位填满 */
  2431. .customer-dialog >>> .el-table td.remark-column,
  2432. .customer-dialog >>> .el-table td.packing-list-column {
  2433. padding: 0 !important;
  2434. position: relative !important;
  2435. overflow: hidden !important;
  2436. }
  2437. .customer-dialog /deep/ .el-table td.remark-column,
  2438. .customer-dialog /deep/ .el-table td.packing-list-column {
  2439. padding: 0 !important;
  2440. position: relative !important;
  2441. overflow: hidden !important;
  2442. }
  2443. /* 备注包装器 - 绝对定位填满单元格 */
  2444. .customer-dialog >>> .remark-wrapper {
  2445. position: absolute !important;
  2446. top: 1px !important;
  2447. left: 1px !important;
  2448. right: 1px !important;
  2449. bottom: 1px !important;
  2450. width: 100% !important;
  2451. height: 100% !important;
  2452. box-sizing: border-box !important;
  2453. }
  2454. .customer-dialog /deep/ .remark-wrapper {
  2455. position: absolute !important;
  2456. top: 1px !important;
  2457. left: 1px !important;
  2458. right: 1px !important;
  2459. bottom: 1px !important;
  2460. width: 100% !important;
  2461. height: 100% !important;
  2462. box-sizing: border-box !important;
  2463. }
  2464. /* 备注textarea */
  2465. .customer-dialog >>> .remark-textarea {
  2466. width: 100% !important;
  2467. height: 100% !important;
  2468. }
  2469. .customer-dialog >>> .remark-textarea .el-textarea__inner {
  2470. border: none !important;
  2471. padding: 8px 10px !important;
  2472. border-radius: 0 !important;
  2473. resize: none !important;
  2474. width: 100% !important;
  2475. height: 100% !important;
  2476. box-sizing: border-box !important;
  2477. overflow-y: auto !important;
  2478. }
  2479. .customer-dialog /deep/ .remark-textarea {
  2480. width: 100% !important;
  2481. height: 100% !important;
  2482. }
  2483. .customer-dialog /deep/ .remark-textarea .el-textarea__inner {
  2484. border: none !important;
  2485. padding: 8px 10px !important;
  2486. border-radius: 0 !important;
  2487. resize: none !important;
  2488. width: 100% !important;
  2489. height: 100% !important;
  2490. box-sizing: border-box !important;
  2491. overflow-y: auto !important;
  2492. }
  2493. .customer-dialog /deep/ .el-dialog {
  2494. position: relative;
  2495. }
  2496. .exception-rule-mask-screen {
  2497. --mask-accent: #e01020;
  2498. --mask-accent-rgb: 224, 16, 32;
  2499. --mask-subtitle: #7a1020;
  2500. --mask-accent-dark: #b3001b;
  2501. position: fixed;
  2502. inset: 0;
  2503. z-index: 4000;
  2504. background: #0d1317;
  2505. overflow: hidden;
  2506. pointer-events: all;
  2507. display: flex;
  2508. flex-direction: column;
  2509. font-family: 'Barlow Condensed', sans-serif;
  2510. }
  2511. .exception-rule-mask-screen.warning {
  2512. --mask-accent: #e6a817;
  2513. --mask-accent-rgb: 230, 168, 23;
  2514. --mask-subtitle: #7a5c10;
  2515. --mask-accent-dark: #a3780f;
  2516. }
  2517. .exception-rule-mask-screen.serious {
  2518. --mask-accent: #e01020;
  2519. --mask-accent-rgb: 224, 16, 32;
  2520. --mask-subtitle: #7a1020;
  2521. }
  2522. .exception-rule-bg-grid {
  2523. position: absolute;
  2524. inset: 0;
  2525. pointer-events: none;
  2526. background-image:
  2527. linear-gradient(rgba(var(--mask-accent-rgb), 0.03) 1px, transparent 1px),
  2528. linear-gradient(90deg, rgba(var(--mask-accent-rgb), 0.03) 1px, transparent 1px);
  2529. background-size: 48px 48px;
  2530. z-index: 0;
  2531. }
  2532. .exception-rule-vignette {
  2533. position: absolute;
  2534. inset: 0;
  2535. pointer-events: none;
  2536. background: radial-gradient(ellipse at center, transparent 40%, rgba(0, 0, 0, 0.75) 100%);
  2537. z-index: 0;
  2538. }
  2539. .exception-rule-scanline {
  2540. position: absolute;
  2541. inset: 0;
  2542. pointer-events: none;
  2543. overflow: hidden;
  2544. z-index: 1;
  2545. }
  2546. .exception-rule-scanline .lines {
  2547. position: absolute;
  2548. inset: 0;
  2549. background-image: repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0, 0, 0, 0.18) 2px, rgba(0, 0, 0, 0.18) 4px);
  2550. background-size: 100% 4px;
  2551. }
  2552. .exception-rule-scanline .beam {
  2553. position: absolute;
  2554. left: 0;
  2555. right: 0;
  2556. height: 128px;
  2557. background: linear-gradient(
  2558. 180deg,
  2559. transparent 0%,
  2560. rgba(var(--mask-accent-rgb), 0.035) 40%,
  2561. rgba(var(--mask-accent-rgb), 0.055) 50%,
  2562. rgba(var(--mask-accent-rgb), 0.035) 60%,
  2563. transparent 100%
  2564. );
  2565. animation: exception-rule-scan-beam 6s linear infinite;
  2566. }
  2567. .exception-rule-corners {
  2568. position: absolute;
  2569. inset: 0;
  2570. pointer-events: none;
  2571. z-index: 2;
  2572. color: var(--mask-accent);
  2573. }
  2574. .exception-rule-corners .corner {
  2575. position: absolute;
  2576. width: 80px;
  2577. height: 80px;
  2578. }
  2579. .exception-rule-corners .corner.tl { top: 0; left: 0; }
  2580. .exception-rule-corners .corner.tr { top: 0; right: 0; }
  2581. .exception-rule-corners .corner.bl { bottom: 0; left: 0; }
  2582. .exception-rule-corners .corner.br { bottom: 0; right: 0; }
  2583. .exception-rule-corners .corner svg {
  2584. width: 100%;
  2585. height: 100%;
  2586. overflow: visible;
  2587. }
  2588. .exception-rule-corners .corner.tr svg { transform: rotate(90deg); }
  2589. .exception-rule-corners .corner.bl svg { transform: rotate(-90deg); }
  2590. .exception-rule-corners .corner.br svg { transform: rotate(180deg); }
  2591. .exception-rule-corners .corner .arc {
  2592. transform-origin: 4px 4px;
  2593. animation: exception-rule-spin-cw 4s linear infinite;
  2594. }
  2595. .exception-rule-statusbar {
  2596. position: relative;
  2597. z-index: 10;
  2598. display: flex;
  2599. align-items: center;
  2600. justify-content: space-between;
  2601. padding: 8px 24px;
  2602. font-size: 12px;
  2603. line-height: 1.2;
  2604. border-bottom: 1px solid rgba(var(--mask-accent-rgb), 0.2);
  2605. color: #8aafc8;
  2606. background: rgba(8, 11, 13, 0.65);
  2607. font-family: 'JetBrains Mono', monospace;
  2608. }
  2609. .exception-rule-statusbar .left,
  2610. .exception-rule-statusbar .right {
  2611. display: flex;
  2612. align-items: center;
  2613. gap: 16px;
  2614. }
  2615. .exception-rule-statusbar .mes {
  2616. color: var(--mask-accent);
  2617. font-weight: 700;
  2618. letter-spacing: 0.05em;
  2619. }
  2620. .exception-rule-statusbar .lockdown {
  2621. color: var(--mask-accent);
  2622. display: flex;
  2623. align-items: center;
  2624. }
  2625. .exception-rule-statusbar .dot {
  2626. display: inline-block;
  2627. width: 8px;
  2628. height: 8px;
  2629. border-radius: 50%;
  2630. background: var(--mask-accent);
  2631. margin-right: 6px;
  2632. animation: exception-rule-blink 1s step-end infinite;
  2633. }
  2634. .exception-rule-statusbar .clock {
  2635. color: #dde3e8;
  2636. font-weight: 600;
  2637. }
  2638. .exception-rule-main {
  2639. position: relative;
  2640. z-index: 10;
  2641. flex: 1;
  2642. display: flex;
  2643. flex-direction: column;
  2644. align-items: center;
  2645. justify-content: flex-start;
  2646. padding: 148px 32px 0;
  2647. gap: 32px;
  2648. min-height: 0;
  2649. }
  2650. .exception-rule-alert-icon {
  2651. position: relative;
  2652. display: flex;
  2653. align-items: center;
  2654. justify-content: center;
  2655. width: 180px;
  2656. height: 180px;
  2657. margin-bottom: 30px;
  2658. flex-shrink: 0;
  2659. }
  2660. .exception-rule-alert-icon .pulse-ring {
  2661. position: absolute;
  2662. inset: 0;
  2663. border-radius: 50%;
  2664. border: 2px solid var(--mask-accent);
  2665. opacity: 0;
  2666. animation: exception-rule-pulse-ring 2s ease-out infinite;
  2667. }
  2668. .exception-rule-alert-icon .pulse-ring.d0 { animation-delay: 0s; }
  2669. .exception-rule-alert-icon .pulse-ring.d1 { animation-delay: 0.67s; }
  2670. .exception-rule-alert-icon .pulse-ring.d2 { animation-delay: 1.33s; }
  2671. .exception-rule-alert-icon .alert-glow {
  2672. position: absolute;
  2673. inset: 0;
  2674. border-radius: 50%;
  2675. box-shadow: 0 0 40px 8px rgba(var(--mask-accent-rgb), 0.35), 0 0 80px 20px rgba(var(--mask-accent-rgb), 0.12);
  2676. animation: exception-rule-glow-breathe 1.5s ease-in-out infinite;
  2677. }
  2678. .exception-rule-alert-icon .alert-core {
  2679. position: relative;
  2680. z-index: 10;
  2681. width: 110px;
  2682. height: 110px;
  2683. border-radius: 50%;
  2684. display: flex;
  2685. align-items: center;
  2686. justify-content: center;
  2687. background: radial-gradient(circle at 35% 35%, rgba(var(--mask-accent-rgb), 0.18), rgba(26, 22, 4, 0.95));
  2688. border: 2px solid rgba(var(--mask-accent-rgb), 0.8);
  2689. box-shadow: inset 0 0 20px rgba(var(--mask-accent-rgb), 0.3);
  2690. color: var(--mask-accent);
  2691. animation: exception-rule-glow-breathe 1.5s ease-in-out infinite;
  2692. }
  2693. .exception-rule-title-block {
  2694. text-align: center;
  2695. margin-top: 30px;
  2696. }
  2697. .exception-rule-title-cn {
  2698. font-family: 'Barlow Condensed', sans-serif;
  2699. font-size: 72px;
  2700. font-weight: 800;
  2701. letter-spacing: 0.12em;
  2702. line-height: 1;
  2703. color: var(--mask-accent);
  2704. text-shadow: 0 0 40px rgba(var(--mask-accent-rgb), 0.55), 0 0 80px rgba(var(--mask-accent-rgb), 0.22);
  2705. }
  2706. .exception-rule-title-en {
  2707. font-family: 'Barlow Condensed', sans-serif;
  2708. margin-top: 4px;
  2709. font-size: 30px;
  2710. font-weight: 700;
  2711. letter-spacing: 0.35em;
  2712. color: var(--mask-subtitle);
  2713. }
  2714. .exception-rule-title-rule {
  2715. margin: 12px auto 0;
  2716. height: 1px;
  2717. background: linear-gradient(90deg, transparent, rgba(var(--mask-accent-rgb), 0.6), transparent);
  2718. max-width: 360px;
  2719. }
  2720. .exception-rule-title-lock {
  2721. margin-top: 12px;
  2722. font-family: 'Barlow Condensed', sans-serif;
  2723. font-size: 20px;
  2724. font-weight: 700;
  2725. letter-spacing: 0.3em;
  2726. text-transform: uppercase;
  2727. color: #dde3e8;
  2728. }
  2729. .exception-rule-info-cards {
  2730. display: grid;
  2731. grid-template-columns: repeat(3, 1fr);
  2732. gap: 16px;
  2733. width: 100%;
  2734. max-width: 768px;
  2735. }
  2736. .exception-rule-info-card {
  2737. display: flex;
  2738. flex-direction: column;
  2739. gap: 4px;
  2740. padding: 12px 16px;
  2741. background: rgba(10, 16, 22, 0.7);
  2742. border: 1px solid rgba(var(--mask-accent-rgb), 0.18);
  2743. }
  2744. .exception-rule-info-card .label {
  2745. font-size: 12px;
  2746. line-height: 1.2;
  2747. letter-spacing: 0.05em;
  2748. text-transform: uppercase;
  2749. color: #8aafc8;
  2750. font-family: 'JetBrains Mono', monospace;
  2751. }
  2752. .exception-rule-info-card .value {
  2753. font-family: 'Barlow Condensed', sans-serif;
  2754. font-size: 20px;
  2755. line-height: 1.15;
  2756. font-weight: 700;
  2757. letter-spacing: 0.02em;
  2758. color: #dde3e8;
  2759. word-break: break-all;
  2760. }
  2761. .exception-rule-info-card .value.hl {
  2762. color: var(--mask-accent);
  2763. text-shadow: 0 0 12px rgba(var(--mask-accent-rgb), 0.5);
  2764. animation: exception-rule-glow-breathe 1.5s ease-in-out infinite;
  2765. }
  2766. .exception-rule-info-card.work-order-card .work-order-value-line {
  2767. display: flex;
  2768. align-items: flex-end;
  2769. justify-content: space-between;
  2770. gap: 10px;
  2771. width: 100%;
  2772. }
  2773. .exception-rule-info-card.work-order-card .work-order-value-line .value {
  2774. flex: 1;
  2775. min-width: 0;
  2776. text-align: left;
  2777. white-space: nowrap;
  2778. overflow: hidden;
  2779. text-overflow: ellipsis;
  2780. }
  2781. .exception-rule-info-card.work-order-card .work-order-seq {
  2782. display: inline-block;
  2783. min-width: 5ch;
  2784. text-align: right;
  2785. white-space: nowrap;
  2786. font-family: 'Barlow Condensed', sans-serif;
  2787. font-size: 20px;
  2788. line-height: 1.15;
  2789. font-weight: 700;
  2790. letter-spacing: 0.02em;
  2791. color: #dde3e8;
  2792. }
  2793. .exception-rule-flow-steps {
  2794. display: flex;
  2795. align-items: center;
  2796. gap: 0;
  2797. width: 100%;
  2798. max-width: 768px;
  2799. }
  2800. .exception-rule-flow-steps .flow-step {
  2801. flex: 1;
  2802. text-align: center;
  2803. position: relative;
  2804. padding: 10px 0;
  2805. }
  2806. .exception-rule-flow-steps .flow-step .dot {
  2807. width: 16px;
  2808. height: 16px;
  2809. border-radius: 50%;
  2810. margin: 0 auto 8px;
  2811. border: 2px solid rgba(var(--mask-accent-rgb), 0.5);
  2812. }
  2813. .exception-rule-flow-steps .flow-step.done .dot {
  2814. background: var(--mask-accent);
  2815. border-color: var(--mask-accent);
  2816. }
  2817. .exception-rule-flow-steps .flow-step.active .dot {
  2818. background: transparent;
  2819. border-color: var(--mask-accent);
  2820. box-shadow: 0 0 8px rgba(var(--mask-accent-rgb), 0.5);
  2821. animation: exception-rule-blink 1.2s ease-in-out infinite;
  2822. }
  2823. .exception-rule-flow-steps .flow-step .label {
  2824. font-size: 12px;
  2825. color: #5a7080;
  2826. letter-spacing: 0.05em;
  2827. font-family: 'JetBrains Mono', monospace;
  2828. }
  2829. .exception-rule-flow-steps .flow-step.done .label { color: #8aafc8; }
  2830. .exception-rule-flow-steps .flow-step.active .label {
  2831. color: var(--mask-accent);
  2832. font-weight: 600;
  2833. }
  2834. .exception-rule-flow-steps .flow-line {
  2835. width: 40px;
  2836. height: 1px;
  2837. background: rgba(var(--mask-accent-rgb), 0.2);
  2838. margin: 0 -4px;
  2839. align-self: flex-start;
  2840. margin-top: 6px;
  2841. }
  2842. .exception-rule-flow-steps .flow-line.done {
  2843. background: rgba(var(--mask-accent-rgb), 0.5);
  2844. }
  2845. .exception-rule-bottombar {
  2846. position: relative;
  2847. z-index: 10;
  2848. padding: 16px 32px;
  2849. border-top: 1px solid rgba(var(--mask-accent-rgb), 0.2);
  2850. background: rgba(8, 11, 13, 0.85);
  2851. }
  2852. .exception-rule-bottombar .inner {
  2853. display: flex;
  2854. align-items: center;
  2855. justify-content: space-between;
  2856. max-width: 1024px;
  2857. margin: 0 auto;
  2858. }
  2859. .exception-rule-bottombar .left {
  2860. display: flex;
  2861. align-items: center;
  2862. gap: 12px;
  2863. }
  2864. .exception-rule-bottombar .left svg {
  2865. color: var(--mask-accent-dark);
  2866. }
  2867. .exception-rule-bottombar .left .ttl {
  2868. font-size: 12px;
  2869. letter-spacing: 0.2em;
  2870. text-transform: uppercase;
  2871. color: #8aafc8;
  2872. font-family: 'JetBrains Mono', monospace;
  2873. }
  2874. .exception-rule-quality-cards {
  2875. display: flex;
  2876. align-items: center;
  2877. gap: 16px;
  2878. }
  2879. .exception-rule-quality-cards .quality-card {
  2880. display: flex;
  2881. flex-direction: column;
  2882. align-items: center;
  2883. gap: 4px;
  2884. padding: 12px 24px;
  2885. min-width: 140px;
  2886. border: 1px solid rgba(var(--mask-accent-rgb), 0.3);
  2887. background: rgba(var(--mask-accent-rgb), 0.04);
  2888. }
  2889. .exception-rule-quality-cards .quality-card .qlabel {
  2890. font-size: 11px;
  2891. line-height: 1.2;
  2892. font-weight: 600;
  2893. letter-spacing: 0.15em;
  2894. text-transform: uppercase;
  2895. color: #8aafc8;
  2896. font-family: 'JetBrains Mono', monospace;
  2897. }
  2898. .exception-rule-quality-cards .quality-card .qvalue {
  2899. font-size: 22px;
  2900. line-height: 1;
  2901. font-weight: 700;
  2902. color: #dde3e8;
  2903. letter-spacing: 0.04em;
  2904. }
  2905. .exception-rule-quality-cards .quality-card .qvalue.hl {
  2906. color: var(--mask-accent);
  2907. text-shadow: 0 0 12px rgba(var(--mask-accent-rgb), 0.4);
  2908. }
  2909. .exception-rule-bottombar .right {
  2910. text-align: right;
  2911. font-size: 12px;
  2912. line-height: 1.25;
  2913. color: #8aafc8;
  2914. font-family: 'JetBrains Mono', monospace;
  2915. }
  2916. .exception-rule-bottombar .right .auth {
  2917. display: flex;
  2918. align-items: center;
  2919. justify-content: flex-end;
  2920. gap: 4px;
  2921. margin-top: 2px;
  2922. color: #1e3040;
  2923. }
  2924. @keyframes exception-rule-spin-cw {
  2925. from { transform: rotate(0deg); }
  2926. to { transform: rotate(360deg); }
  2927. }
  2928. @keyframes exception-rule-scan-beam {
  2929. from { top: -128px; }
  2930. to { top: 100%; }
  2931. }
  2932. @keyframes exception-rule-pulse-ring {
  2933. 0% {
  2934. transform: scale(0.7);
  2935. opacity: 0.7;
  2936. }
  2937. 100% {
  2938. transform: scale(2.2);
  2939. opacity: 0;
  2940. }
  2941. }
  2942. @keyframes exception-rule-glow-breathe {
  2943. 0%, 100% { opacity: 0.8; }
  2944. 50% { opacity: 1; }
  2945. }
  2946. @keyframes exception-rule-blink {
  2947. 0%,
  2948. 100% {
  2949. opacity: 1;
  2950. }
  2951. 50% {
  2952. opacity: 0;
  2953. }
  2954. }
  2955. /* 分卷不良原因对话框 */
  2956. .roll-defect-summary {
  2957. display: flex;
  2958. justify-content: space-between;
  2959. align-items: center;
  2960. margin-bottom: 12px;
  2961. padding: 8px 12px;
  2962. background: #f5f7fa;
  2963. border-radius: 4px;
  2964. color: #606266;
  2965. }
  2966. .roll-defect-form /deep/ .el-form-item {
  2967. margin-right: 12px;
  2968. }
  2969. .roll-defect-form .defect-code-label a {
  2970. color: #409EFF;
  2971. font-weight: 600;
  2972. cursor: pointer;
  2973. }
  2974. .roll-defect-form /deep/ .defect-code-input .el-input__inner {
  2975. color: #409EFF;
  2976. font-weight: 600;
  2977. }
  2978. </style>
  2979. <style lang="scss">
  2980. /* 全局样式 - 良品数和不良数输入框颜色 */
  2981. .good-qty-input .el-input__inner {
  2982. font-weight: bold !important;
  2983. color: #67c23a !important;
  2984. font-size: 16px !important;
  2985. }
  2986. .defect-qty-input .el-input__inner {
  2987. font-weight: bold !important;
  2988. color: #f56c6c !important;
  2989. font-size: 16px !important;
  2990. }
  2991. /* 备注列样式 - 只针对创建分卷对话框 */
  2992. .customer-css .el-table td.remark-column,
  2993. .customer-css .el-table td.packing-list-column {
  2994. padding: 0 !important;
  2995. position: relative !important;
  2996. overflow: hidden !important;
  2997. }
  2998. /* 备注包装器 - 填满整个单元格 */
  2999. .customer-css .remark-wrapper {
  3000. position: absolute !important;
  3001. top: 1px !important;
  3002. left: 1px !important;
  3003. right: 1px !important;
  3004. bottom: 1px !important;
  3005. width: 100% !important;
  3006. height: 100% !important;
  3007. box-sizing: border-box !important;
  3008. }
  3009. /* 备注textarea容器 */
  3010. .customer-css .remark-textarea {
  3011. width: 100% !important;
  3012. height: 100% !important;
  3013. }
  3014. .customer-css .remark-textarea .el-textarea__inner {
  3015. border: none !important;
  3016. padding: 8px 10px !important;
  3017. border-radius: 0 !important;
  3018. resize: none !important;
  3019. width: 100% !important;
  3020. height: 100% !important;
  3021. box-sizing: border-box !important;
  3022. overflow-y: auto !important;
  3023. }
  3024. </style>