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.

6217 lines
236 KiB

3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
3 months ago
2 months ago
2 months ago
1 month ago
4 weeks ago
3 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
4 weeks ago
1 month ago
2 months ago
3 months ago
3 months ago
1 month ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
4 weeks ago
3 months ago
3 months ago
4 weeks ago
3 months ago
4 weeks ago
3 months ago
4 weeks ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
2 months ago
2 months ago
3 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
3 months ago
1 month ago
3 months ago
3 months ago
1 month ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
3 months ago
2 months ago
2 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
2 months ago
2 months ago
3 months ago
2 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
2 months ago
3 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
3 months ago
2 months ago
3 months ago
2 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
1 month ago
3 months ago
3 months ago
3 months ago
1 month ago
1 month ago
1 month ago
1 month ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
1 month ago
4 weeks ago
1 month ago
3 months ago
2 months ago
3 months ago
3 months ago
2 months ago
2 months ago
2 months ago
3 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
1 month ago
4 weeks ago
1 month ago
3 months ago
1 month ago
4 weeks ago
1 month ago
4 weeks ago
1 month ago
4 weeks ago
1 month ago
4 weeks ago
1 month ago
4 weeks ago
1 month ago
4 weeks ago
1 month ago
4 weeks ago
1 month ago
4 weeks ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
3 months ago
3 months ago
3 months ago
3 months ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
3 months ago
3 months ago
2 months ago
3 months ago
2 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
2 months ago
2 months ago
2 months ago
3 months ago
1 month ago
1 month ago
3 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
3 months ago
2 months ago
2 months ago
2 months ago
2 months ago
3 months ago
3 months ago
2 months ago
3 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
3 months ago
2 months ago
2 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
4 weeks ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
2 months ago
2 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
  1. <template>
  2. <div class="mod-config">
  3. <el-form :inline="true" :model="searchData" label-position="top" class="search-form">
  4. <el-form-item label="BU">
  5. <el-select v-model="searchData.buNo" filterable placeholder="请选择" style="width: 130px" @change="handleSearchBuChange">
  6. <el-option
  7. v-for="item in userBuList"
  8. :key="`searchBu_${item.buNo}`"
  9. :label="item.buDesc ? `${normalizeBuNo(item.buNo, searchData.site)}-${item.buDesc}` : normalizeBuNo(item.buNo, searchData.site)"
  10. :value="normalizeBuNo(item.buNo, searchData.site)"
  11. />
  12. </el-select>
  13. </el-form-item>
  14. <el-form-item label="项目编码">
  15. <el-input v-model="searchData.projectNo" clearable placeholder="请输入项目编码" style="width: 140px" />
  16. </el-form-item>
  17. <el-form-item label="项目描述">
  18. <el-input v-model="searchData.projectDesc" clearable placeholder="请输入项目描述" style="width: 160px" />
  19. </el-form-item>
  20. <el-form-item label="项目物料">
  21. <el-input v-model="searchData.testPartNo" clearable placeholder="请输入项目物料" style="width: 140px" />
  22. </el-form-item>
  23. <el-form-item label="料号描述">
  24. <el-input v-model="searchData.partDesc" clearable placeholder="请输入料号描述" style="width: 160px" />
  25. </el-form-item>
  26. <el-form-item v-if="searchData.buNo==='03-RFID'" label="分类">
  27. <el-select v-model="searchData.dryWetType" clearable placeholder="全部" style="width: 100px">
  28. <el-option v-for="item in dryWetTypeList" :key="`searchDryWet_${item}`" :label="item" :value="item" />
  29. </el-select>
  30. </el-form-item>
  31. <el-form-item label="客户编码">
  32. <el-input v-model="searchData.customerNo" clearable placeholder="请输入客户编码" style="width: 120px" />
  33. </el-form-item>
  34. <el-form-item label="打样单号">
  35. <el-input v-model="searchData.proofingNo" clearable placeholder="请输入打样单号" style="width: 150px" />
  36. </el-form-item>
  37. <el-form-item label="项目/物料同步">
  38. <el-select v-model="searchData.projectPartSyncFlag" clearable placeholder="全部" style="width: 120px">
  39. <el-option label="已同步" value="Y" />
  40. <el-option label="未同步" value="N" />
  41. </el-select>
  42. </el-form-item>
  43. <el-form-item label="打样单同步">
  44. <el-select v-model="searchData.proofSyncFlag" clearable placeholder="全部" style="width: 120px">
  45. <el-option label="已同步" value="Y" />
  46. <el-option label="未同步" value="N" />
  47. </el-select>
  48. </el-form-item>
  49. <el-form-item label="打样状态">
  50. <el-select v-model="searchData.proofingStatus" clearable placeholder="全部" style="width: 120px">
  51. <el-option label="进行中" value="进行中" />
  52. <el-option label="打样完成" value="打样完成" />
  53. <el-option label="草稿" value="草稿" />
  54. </el-select>
  55. <el-button type="primary" @click="getDataList('Y')">查询</el-button>
  56. <el-button @click="resetSearch">重置</el-button>
  57. <el-button type="success" :loading="exportLoading" @click="exportTrackingData">导出</el-button>
  58. </el-form-item>
  59. <el-form-item class="button-group-col">
  60. <el-row :gutter="8" type="flex" class="button-row" >
  61. <el-button type="primary" @click="openOneKeyDialog">一键创建</el-button>
  62. <!-- <el-button type="warning" @click="toggleBatchEdit">{{ batchEditMode ? '取消修改' : '批量修改' }}</el-button>
  63. <el-button v-if="batchEditMode" type="success" @click="saveBatchPlan">保存修改</el-button>-->
  64. <el-button type="warning" @click="openCreateProofDialog()">新增打样</el-button>
  65. <el-button type="success" plain :loading="syncBatchNpiLoading" @click="syncBatchProjectPartToNpi()">同步项目物料到NPI</el-button>
  66. <el-button type="success" plain :loading="syncBatchProofLoading" @click="syncBatchProofToNpi()">同步打样到NPI</el-button>
  67. <el-button type="info" v-if="isAuth('833001005:set')" plain @click="openBuProcessConfigDialog">事业部进度设置</el-button>
  68. <el-button type="info" v-if="isAuth('833001005:set2')" plain @click="openDefaultProcessConfigDialog">默认进度设置</el-button>
  69. <el-tooltip style="margin-top: 2px;margin-left: 10px"
  70. content="已完成后,双击日期选择框可回退为未完成"
  71. placement="top">
  72. <i class="el-icon-question default-process-tip-icon"></i>
  73. </el-tooltip>
  74. </el-row>
  75. </el-form-item>
  76. </el-form>
  77. <el-table
  78. ref="trackingTable"
  79. :data="dataList"
  80. border class="data-table"
  81. stripe highlight-current-row
  82. height="700"
  83. v-loading="dataListLoading"
  84. @selection-change="selectionChange"
  85. @row-click="rowClick"
  86. style="width: 100%; margin-top: 8px"
  87. >
  88. <el-table-column type="selection" width="45" />
  89. <el-table-column
  90. v-for="(item, trackingColumnIndex) in visibleTrackingColumns"
  91. :key="`${item.columnProp}_${trackingColumnIndex}`"
  92. :prop="item.columnProp"
  93. :label="item.columnLabel"
  94. :width="item.columnWidth"
  95. :header-align="item.headerAlign || 'center'"
  96. :align="item.align || 'left'"
  97. :fixed="item.fixed === '' ? false : item.fixed"
  98. :show-overflow-tooltip="item.showOverflowTooltip"
  99. >
  100. <template slot-scope="scope">
  101. <span
  102. v-if="isProjectPartSyncColumn(item.columnProp)"
  103. :class="getProjectPartSyncTextClass(scope.row)"
  104. >{{ getTrackingColumnDisplayValue(scope.row, item.columnProp) }}</span>
  105. <span
  106. v-else-if="item.columnProp === 'proofingNo'"
  107. :class="getProofSyncTextClass(scope.row)"
  108. >{{ getTrackingColumnDisplayValue(scope.row, item.columnProp) }}</span>
  109. <a
  110. v-else-if="item.columnProp === 'proofingStatus'"
  111. :style="{ color: (scope.row.proofingStatus === '打样完成' || scope.row.proofingStatus === '打样结束') ? '#67c23a' : '#046e97' }"
  112. >{{ scope.row.proofingStatus || '进行中' }}</a>
  113. <div
  114. v-else-if="item.columnProp === 'trackingStatus'"
  115. :class="getTrackingStatusClass(scope.row)"
  116. >
  117. {{ getTrackingStatusText(scope.row) }}
  118. </div>
  119. <span v-else-if="item.columnProp === 'engineer'">
  120. {{ getTrackingRoleDisplayValue(scope.row, 'engineerName', 'engineer') }}
  121. </span>
  122. <span v-else-if="item.columnProp === 'projectManager'">
  123. {{ getTrackingRoleDisplayValue(scope.row, 'projectManagerName', 'projectManager') }}
  124. </span>
  125. <span v-else-if="item.columnProp === 'pic'">
  126. {{ getTrackingRoleDisplayValue(scope.row, 'projectOwnerName', 'pic') }}
  127. </span>
  128. <span v-else>{{ getTrackingColumnDisplayValue(scope.row, item.columnProp) }}</span>
  129. </template>
  130. </el-table-column>
  131. <el-table-column
  132. v-for="(item, processColumnIndex) in visibleProcessColumns"
  133. :key="`${item.code}_${item.sortNo || ''}_${processColumnIndex}`"
  134. :label="item.label"
  135. :width="getProcessColumnWidth(item)"
  136. align="center"
  137. header-align="center"
  138. >
  139. <template slot-scope="scope">
  140. <div
  141. v-if="isProcessColumnVisibleForRow(scope.row, item)"
  142. :class="['process-cell', isProcessStatusComplete(scope.row, item) ? 'is-complete' : '']"
  143. >
  144. <el-date-picker
  145. v-if="batchEditMode"
  146. v-model="scope.row[item.actualField]"
  147. type="date"
  148. value-format="yyyy-MM-dd"
  149. format="yyyy-MM-dd"
  150. placeholder="实际完成日期"
  151. style="width: 120px"
  152. />
  153. <div
  154. v-else
  155. :class="['process-content', isProcessStatusComplete(scope.row, item) ? 'is-complete' : '']"
  156. @mouseenter="handleProcessTooltipMouseEnter(scope.row, item, $event)"
  157. @mousemove="handleProcessTooltipMouseMove($event)"
  158. @mouseleave="handleProcessTooltipMouseLeave"
  159. >
  160. <el-date-picker
  161. v-model="scope.row[item.actualField]"
  162. type="date"
  163. value-format="yyyy-MM-dd"
  164. format="yyyy-MM-dd"
  165. placeholder=""
  166. style="width: 122px"
  167. @change="handleProcessDateChange(scope.row, item, $event)"
  168. @dblclick.native.stop="handleProcessDateDoubleClick(scope.row, item)"
  169. />
  170. <el-button
  171. v-if="!isProcessStatusComplete(scope.row, item)"
  172. class="process-complete-btn"
  173. type="success"
  174. size="mini"
  175. plain
  176. title="标记为已完成"
  177. @click.stop="markProcessComplete(scope.row, item)"
  178. ></el-button>
  179. </div>
  180. </div>
  181. <div v-else class="process-cell process-cell--masked"></div>
  182. </template>
  183. </el-table-column>
  184. <el-table-column label="Comments" width="220">
  185. <template slot-scope="scope">
  186. <div class="comments-cell" @dblclick.stop="startCommentsEdit(scope.row)">
  187. <template v-if="batchEditMode">
  188. <el-input
  189. v-model="scope.row.comments"
  190. class="comments-edit-input"
  191. size="mini"
  192. clearable
  193. placeholder="请输入Comments"
  194. />
  195. </template>
  196. <template v-else-if="isCommentsEditing(scope.row)">
  197. <el-input
  198. v-model="commentsDraft"
  199. v-focus-comments-input="isCommentsEditing(scope.row)"
  200. :ref="'commentsInput_' + scope.row.trackingId"
  201. :id="'commentsInput_' + scope.row.trackingId"
  202. class="comments-edit-input"
  203. size="mini"
  204. clearable
  205. placeholder="请输入,回车也可保存"
  206. @keydown.enter.native.prevent="saveCommentsEdit(scope.row)"
  207. @keyup.esc.native="cancelCommentsEdit"
  208. @blur="handleCommentsBlur(scope.row)"
  209. />
  210. <el-button
  211. class="comments-save-btn"
  212. type="success"
  213. size="mini"
  214. plain
  215. :loading="commentsSaveLoading"
  216. @mousedown.native.prevent
  217. @click.stop="saveCommentsEdit(scope.row)"
  218. ></el-button>
  219. </template>
  220. <span v-else class="comments-text" :title="scope.row.comments || ''">{{ scope.row.comments || '-' }}</span>
  221. </div>
  222. </template>
  223. </el-table-column>
  224. <el-table-column label="操作" fixed="right" width="150" align="center">
  225. <template slot-scope="scope">
  226. <a @click="openOneKeyDialog('edit', scope.row)">修改</a>
  227. <a class="action-link delete-link" @click="deleteProof(scope.row)">删除</a>
  228. <a class="action-link end-link" v-if="scope.row.proofingNo && scope.row.proofingStatus !== '打样完成'" @click="finishProof(scope.row)">打样完成</a>
  229. </template>
  230. </el-table-column>
  231. </el-table>
  232. <el-pagination
  233. @size-change="sizeChangeHandle"
  234. @current-change="currentChangeHandle"
  235. :current-page="pageIndex"
  236. :page-sizes="[10, 20, 50, 100]"
  237. :page-size="pageSize"
  238. :total="totalPage"
  239. layout="total, sizes, prev, pager, next, jumper"
  240. style="margin-top: 12px; text-align: right"
  241. />
  242. <div
  243. v-show="processTooltip.visible"
  244. class="process-history-tooltip"
  245. :style="{
  246. left: `${processTooltip.left}px`,
  247. top: `${processTooltip.top}px`
  248. }"
  249. >{{ processTooltip.content }}</div>
  250. <el-dialog
  251. title="进度展示设置"
  252. :visible.sync="buProcessConfigDialogVisible"
  253. width="1180px"
  254. :close-on-click-modal="false"
  255. custom-class="bu-process-config-dialog"
  256. >
  257. <div class="bu-process-config-tip">
  258. <i class="el-icon-info"></i>
  259. <span>拖动可调整顺序按序号从左到右勾选控制可见列工序分类按 BU 独立维护</span>
  260. </div>
  261. <el-table :data="buProcessConfigRows" border stripe max-height="560">
  262. <el-table-column prop="buDesc" label="BU" width="120" show-overflow-tooltip />
  263. <el-table-column label="可见进度列表 / 工序分类" min-width="760" class-name="bu-process-list-column">
  264. <template slot-scope="scope">
  265. <el-checkbox-group
  266. v-model="scope.row.processCodes"
  267. class="bu-process-order-list"
  268. @change="handleBuProcessCodesChange(scope.row)"
  269. >
  270. <div
  271. v-for="(processCode, processCodeIndex) in scope.row.processOrderCodes"
  272. :key="`${scope.row.buNo}_${processCode}_${processCodeIndex}`"
  273. :class="['bu-process-order-item', isProcessOrderDraggingItem(scope.row, processCodeIndex) ? 'is-dragging' : '']"
  274. draggable="true"
  275. @dragstart="handleProcessOrderDragStart(scope.row, processCodeIndex, $event)"
  276. @dragover.prevent
  277. @dragenter.prevent
  278. @drop.prevent="handleProcessOrderDrop(scope.row, processCodeIndex, $event)"
  279. @dragend="handleProcessOrderDragEnd"
  280. >
  281. <span class="process-order-index">{{ processCodeIndex + 1 }}.</span>
  282. <i class="el-icon-rank process-order-handle"></i>
  283. <el-checkbox :label="processCode">{{ getProcessLabelByCode(processCode) }}</el-checkbox>
  284. <el-select
  285. class="bu-process-category-select"
  286. :value="getBuProcessCategoryCode(scope.row, processCode)"
  287. size="mini"
  288. clearable
  289. filterable
  290. placeholder="未分类"
  291. @change="handleBuProcessCategoryChange(scope.row, processCode, $event)"
  292. @mousedown.native.stop
  293. @click.native.stop
  294. >
  295. <el-option
  296. v-for="categoryItem in getProcessCategoryOptionsByBuNo(scope.row.buNo)"
  297. :key="`buProcessCategory_${scope.row.buNo}_${categoryItem.categoryCode}`"
  298. :label="`${categoryItem.categoryName}`"
  299. :value="categoryItem.categoryCode"
  300. />
  301. </el-select>
  302. </div>
  303. </el-checkbox-group>
  304. </template>
  305. </el-table-column>
  306. <el-table-column label="分类维护" width="100" align="center">
  307. <template slot-scope="scope">
  308. <a type="text" @click="openProcessCategoryDialog(scope.row)">维护分类</a>
  309. </template>
  310. </el-table-column>
  311. </el-table>
  312. <div slot="footer">
  313. <el-button @click="buProcessConfigDialogVisible = false">关闭</el-button>
  314. <el-button type="primary" @click="saveBuProcessConfig">保存设置</el-button>
  315. </div>
  316. </el-dialog>
  317. <el-dialog
  318. title="默认进度设置"
  319. :visible.sync="defaultProcessConfigDialogVisible"
  320. width="600px" class="zxClass"
  321. :close-on-click-modal="false"
  322. custom-class="default-process-config-dialog"
  323. @opened="initDefaultProcessConfigSortable"
  324. @closed="handleDefaultProcessConfigDialogClosed"
  325. >
  326. <div class="default-process-config-actions">
  327. <el-button type="success" plain size="mini" @click="addDefaultProcessConfigRow">新增一行</el-button>
  328. <el-button
  329. type="danger"
  330. plain
  331. size="mini"
  332. :disabled="defaultProcessConfigSelectionRows.length === 0 || defaultProcessConfigDeleteLoading"
  333. :loading="defaultProcessConfigDeleteLoading"
  334. @click="deleteDefaultProcessConfigRow"
  335. >删除选中</el-button>
  336. </div>
  337. <el-table
  338. ref="defaultProcessConfigTable"
  339. :data="defaultProcessConfigRows"
  340. row-key="_rowKey"
  341. border
  342. stripe
  343. max-height="640"
  344. @selection-change="handleDefaultProcessConfigSelectionChange"
  345. >
  346. <el-table-column type="selection" width="50" />
  347. <el-table-column label="排序" width="90" align="center">
  348. <template slot-scope="scope">
  349. <i class="el-icon-rank default-process-drag-handle"></i>
  350. <span class="default-process-order-index">{{ scope.$index + 1 }}</span>
  351. </template>
  352. </el-table-column>
  353. <el-table-column label="工序编码" min-width="160">
  354. <template slot-scope="scope">
  355. <el-input v-model.trim="scope.row.code" size="mini" clearable placeholder="如: coating" />
  356. </template>
  357. </el-table-column>
  358. <el-table-column label="工序名称" min-width="180">
  359. <template slot-scope="scope">
  360. <el-input v-model.trim="scope.row.label" size="mini" clearable placeholder="如: Coating" />
  361. </template>
  362. </el-table-column>
  363. <!-- <el-table-column label="启用" width="100" align="center">
  364. <template slot-scope="scope">
  365. <el-select v-model="scope.row.activeFlag" size="mini" style="width: 80px;">
  366. <el-option label="是" value="Y" />
  367. <el-option label="否" value="N" />
  368. </el-select>
  369. </template>
  370. </el-table-column>-->
  371. </el-table>
  372. <div v-if="defaultProcessConfigRows.length === 0" class="default-process-config-empty">
  373. 当前没有默认行请点击新增一行后填写并保存
  374. </div>
  375. <div slot="footer">
  376. <el-button @click="defaultProcessConfigDialogVisible = false">关闭</el-button>
  377. <el-button type="primary" @click="saveDefaultProcessConfig">保存设置</el-button>
  378. </div>
  379. </el-dialog>
  380. <el-dialog
  381. :title="`工序分类维护(${processCategoryDialogBuDesc || processCategoryDialogBuNo || '-'})`"
  382. :visible.sync="processCategoryDialogVisible"
  383. width="450px"
  384. :close-on-click-modal="false"
  385. custom-class="process-category-config-dialog"
  386. @closed="handleProcessCategoryDialogClosed"
  387. >
  388. <div class="default-process-config-actions">
  389. <el-button type="success" plain size="mini" @click="addProcessCategoryRow">新增一行</el-button>
  390. <el-button
  391. type="danger"
  392. plain
  393. size="mini"
  394. :disabled="processCategorySelectionRows.length === 0 || processCategorySaveLoading"
  395. :loading="processCategorySaveLoading"
  396. @click="deleteProcessCategoryRows"
  397. >删除选中</el-button>
  398. </div>
  399. <el-table
  400. ref="processCategoryTable"
  401. :data="processCategoryRows"
  402. row-key="_rowKey"
  403. border
  404. stripe class="zxClass"
  405. max-height="520"
  406. @selection-change="handleProcessCategorySelectionChange"
  407. >
  408. <el-table-column type="selection" width="50" />
  409. <el-table-column label="分类名称" min-width="280">
  410. <template slot-scope="scope">
  411. <el-input v-model.trim="scope.row.categoryName" size="mini" clearable placeholder="如: 制前工序" />
  412. </template>
  413. </el-table-column>
  414. </el-table>
  415. <div v-if="processCategoryRows.length === 0" class="default-process-config-empty">
  416. 当前没有分类请点击新增一行后填写并保存
  417. </div>
  418. <div slot="footer">
  419. <el-button @click="processCategoryDialogVisible = false">关闭</el-button>
  420. <el-button type="primary" :loading="processCategorySaveLoading" @click="saveProcessCategoryConfig">保存分类</el-button>
  421. </div>
  422. </el-dialog>
  423. <el-dialog :title="oneKeyDialogMode === 'edit' ? '修改项目/物料/打样' : '一键创建项目/物料/打样'" :visible.sync="oneKeyDialogVisible" :close-on-click-modal="false" width="1000px">
  424. <el-form :model="oneKeyForm" label-position="top" class="one-key-grid-form">
  425. <el-row :gutter="16">
  426. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('buNo')">
  427. <el-form-item label="BU" required>
  428. <el-select v-model="oneKeyForm.buNo" placeholder="请选择" style="width: 100%" @change="handleOneKeyBuChange">
  429. <el-option
  430. v-for="item in userBuList"
  431. :key="item.buNo"
  432. :label="item.buDesc ? `${normalizeBuNo(item.buNo, searchData.site)}-${item.buDesc}` : normalizeBuNo(item.buNo, searchData.site)"
  433. :value="item.buNo">
  434. </el-option>
  435. </el-select>
  436. </el-form-item>
  437. </el-col>
  438. <el-col :span="9" v-show="isOneKeyCreateFieldVisible('projectNo')">
  439. <el-form-item label="项目编码">
  440. <el-autocomplete
  441. v-model.trim="oneKeyForm.projectNo"
  442. :fetch-suggestions="queryOneKeyProjectSuggestions"
  443. :trigger-on-focus="true"
  444. placeholder="手工输入或按BU选择NPI项目"
  445. style="width: 100%"
  446. @select="handleOneKeyProjectSelect"
  447. @blur="handleOneKeyProjectNoBlur">
  448. <template slot-scope="{ item }">
  449. <div class="one-key-suggest-main">{{ item.value }}</div>
  450. <div class="one-key-suggest-sub">
  451. {{ item.projectDesc || '-' }} | {{ item.customerDesc || '-' }}
  452. </div>
  453. </template>
  454. </el-autocomplete>
  455. </el-form-item>
  456. </el-col>
  457. <el-col :span="9" v-show="isOneKeyCreateFieldVisible('projectDesc')">
  458. <el-form-item label="项目名称">
  459. <el-input v-model="oneKeyForm.projectDesc" style="width: 100%"></el-input>
  460. </el-form-item>
  461. </el-col>
  462. </el-row>
  463. <el-row :gutter="16">
  464. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('customerNo')">
  465. <el-form-item>
  466. <span slot="label" class="big-label">
  467. <a href="javascript:void(0)" @click="getBaseList(509)">客户编码</a>
  468. <a href="javascript:void(0)" @click="newCustomer">新客户</a>
  469. </span>
  470. <el-input v-model="oneKeyForm.customerNo" @blur="customerNoBlur" style="width: 100%"></el-input>
  471. </el-form-item>
  472. </el-col>
  473. <el-col :span="12" v-show="isOneKeyCreateFieldVisible('customerDesc')">
  474. <el-form-item label="客户名称">
  475. <el-input v-model="oneKeyForm.customerDesc" disabled style="width: 100%"></el-input>
  476. </el-form-item>
  477. </el-col>
  478. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cProjectRegion')">
  479. <el-form-item label="区域">
  480. <el-select v-model="oneKeyForm.cProjectRegion" placeholder="请选择" clearable style="width: 100%">
  481. <el-option
  482. v-for="i in cProjectRegionList"
  483. :key="i.cProjectRegion"
  484. :label="i.cProjectRegion"
  485. :value="i.cProjectRegion">
  486. </el-option>
  487. </el-select>
  488. </el-form-item>
  489. </el-col>
  490. </el-row>
  491. <el-row :gutter="16">
  492. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectCreationDate')">
  493. <el-form-item label="立项日期">
  494. <el-date-picker
  495. v-model="oneKeyForm.projectCreationDate"
  496. type="date"
  497. value-format="yyyy-MM-dd"
  498. placeholder="选择日期"
  499. style="width: 100%">
  500. </el-date-picker>
  501. </el-form-item>
  502. </el-col>
  503. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('needDate')">
  504. <el-form-item label="预计完成日期">
  505. <el-date-picker
  506. v-model="oneKeyForm.needDate"
  507. type="date"
  508. value-format="yyyy-MM-dd"
  509. placeholder="选择日期"
  510. style="width: 100%">
  511. </el-date-picker>
  512. </el-form-item>
  513. </el-col>
  514. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectCategory')">
  515. <el-form-item label="项目分类">
  516. <el-select v-model="oneKeyForm.projectCategory" placeholder="请选择" clearable style="width: 100%">
  517. <el-option v-for="item in projectCategoryList" :key="item" :label="item" :value="item"></el-option>
  518. </el-select>
  519. </el-form-item>
  520. </el-col>
  521. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('priorityLevel')">
  522. <el-form-item label="优先级">
  523. <el-select v-model="oneKeyForm.priorityLevel" placeholder="请选择" clearable style="width: 100%">
  524. <el-option v-for="item in priorityList" :key="item" :label="item" :value="item"></el-option>
  525. </el-select>
  526. </el-form-item>
  527. </el-col>
  528. </el-row>
  529. <el-row :gutter="16">
  530. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('testPartNo')">
  531. <el-form-item label="项目料号">
  532. <el-autocomplete
  533. v-model.trim="oneKeyForm.testPartNo"
  534. :fetch-suggestions="queryOneKeyPartSuggestions"
  535. :trigger-on-focus="true"
  536. placeholder="手工输入或按项目编码选择料号"
  537. style="width: 100%"
  538. @select="handleOneKeyPartSelect"
  539. @blur="handleOneKeyPartNoBlur">
  540. <template slot-scope="{ item }">
  541. <div class="one-key-suggest-main">{{ item.value }}</div>
  542. <div class="one-key-suggest-sub">{{ item.partDesc || '-' }} | 创建: {{ item.createDateText || '-' }}</div>
  543. </template>
  544. </el-autocomplete>
  545. </el-form-item>
  546. </el-col>
  547. <el-col :span="12" v-show="isOneKeyCreateFieldVisible('partDesc')">
  548. <el-form-item label="料号描述" required>
  549. <el-input v-model="oneKeyForm.partDesc" style="width: 100%"></el-input>
  550. </el-form-item>
  551. </el-col>
  552. <el-col :span="isOneKeyDryWetTypeVisible()?3:6" v-show="isOneKeyCreateFieldVisible('partType')">
  553. <el-form-item label="料号状态">
  554. <el-select v-model="oneKeyForm.partType" placeholder="请选择" clearable style="width: 100%">
  555. <el-option v-for="item in partTypeList" :key="item" :label="item" :value="item"></el-option>
  556. </el-select>
  557. </el-form-item>
  558. </el-col>
  559. <el-col :span="3" v-show="isOneKeyCreateFieldVisible('dryWetType') && isOneKeyDryWetTypeVisible()">
  560. <el-form-item label="分类">
  561. <el-select v-model="oneKeyForm.dry_wet_type" placeholder="请选择" clearable style="width: 100%">
  562. <el-option v-for="item in dryWetTypeList" :key="item" :label="item" :value="item"></el-option>
  563. </el-select>
  564. </el-form-item>
  565. </el-col>
  566. </el-row>
  567. <el-row :gutter="16">
  568. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectManager')">
  569. <el-form-item>
  570. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('projectManager')">PM/Sales</a></span>
  571. <el-input v-model="oneKeyForm.projectManagerName" disabled style="width: 100%"></el-input>
  572. </el-form-item>
  573. </el-col>
  574. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectOwner')">
  575. <el-form-item required>
  576. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('projectOwner')">PjM</a></span>
  577. <el-input v-model="oneKeyForm.projectOwnerName" disabled style="width: 100%"></el-input>
  578. </el-form-item>
  579. </el-col>
  580. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('engineer')">
  581. <el-form-item required>
  582. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('engineer')">Engineer</a></span>
  583. <el-input v-model="oneKeyForm.engineerName" disabled style="width: 100%"></el-input>
  584. </el-form-item>
  585. </el-col>
  586. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cManufactureEngineer')">
  587. <el-form-item>
  588. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cManufactureEngineer')">MFG</a></span>
  589. <el-input v-model="oneKeyForm.cManufactureEngineerName" disabled style="width: 100%"></el-input>
  590. </el-form-item>
  591. </el-col>
  592. </el-row>
  593. <el-row :gutter="16">
  594. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer6')">
  595. <el-form-item>
  596. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer6')">IQC</a></span>
  597. <el-input v-model="oneKeyForm.cQualityEngineer6Name" disabled style="width: 100%"></el-input>
  598. </el-form-item>
  599. </el-col>
  600. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer1')">
  601. <el-form-item>
  602. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer1')">IPQC-Lam/Pri/Etch/Slit</a></span>
  603. <el-input v-model="oneKeyForm.cQualityEngineer1Name" disabled style="width: 100%"></el-input>
  604. </el-form-item>
  605. </el-col>
  606. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer2')">
  607. <el-form-item>
  608. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer2')">IPQC-Converting</a></span>
  609. <el-input v-model="oneKeyForm.cQualityEngineer2Name" disabled style="width: 100%"></el-input>
  610. </el-form-item>
  611. </el-col>
  612. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer4')">
  613. <el-form-item>
  614. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer4')">SQE</a></span>
  615. <el-input v-model="oneKeyForm.cQualityEngineer4Name" disabled style="width: 100%"></el-input>
  616. </el-form-item>
  617. </el-col>
  618. </el-row>
  619. <el-row :gutter="16">
  620. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer3')">
  621. <el-form-item>
  622. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer3')">FQC1</a></span>
  623. <el-input v-model="oneKeyForm.cQualityEngineer3Name" disabled style="width: 100%"></el-input>
  624. </el-form-item>
  625. </el-col>
  626. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer5')">
  627. <el-form-item>
  628. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer5')">FQC2</a></span>
  629. <el-input v-model="oneKeyForm.cQualityEngineer5Name" disabled style="width: 100%"></el-input>
  630. </el-form-item>
  631. </el-col>
  632. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('ipqcHardTag')">
  633. <el-form-item>
  634. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('ipqcHardTag')">IPQC-Hardtag</a></span>
  635. <el-input v-model="oneKeyForm.ipqcHardTagName" disabled style="width: 100%"></el-input>
  636. </el-form-item>
  637. </el-col>
  638. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer7')">
  639. <el-form-item>
  640. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer7')">前道工程师</a></span>
  641. <el-input v-model="oneKeyForm.cQualityEngineer7Name" disabled style="width: 100%"></el-input>
  642. </el-form-item>
  643. </el-col>
  644. </el-row>
  645. <el-row :gutter="16">
  646. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('docEngineer')">
  647. <el-form-item>
  648. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('docEngineer')">文档工程师</a></span>
  649. <el-input v-model="oneKeyForm.docEngineerName" disabled style="width: 100%"></el-input>
  650. </el-form-item>
  651. </el-col>
  652. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('docEngineer2')">
  653. <el-form-item>
  654. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('docEngineer2')">文档工程师2</a></span>
  655. <el-input v-model="oneKeyForm.docEngineer2Name" disabled style="width: 100%"></el-input>
  656. </el-form-item>
  657. </el-col>
  658. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectStatus')">
  659. <el-form-item label="项目状态">
  660. <el-select v-model="oneKeyForm.projectStatus" clearable style="width: 100%">
  661. <el-option v-for="item in projectStatusList" :key="item" :label="item" :value="item" />
  662. </el-select>
  663. </el-form-item>
  664. </el-col>
  665. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectPhase')">
  666. <el-form-item label="项目阶段">
  667. <el-select v-model="oneKeyForm.projectPhase" clearable style="width: 100%">
  668. <el-option
  669. v-for="item in proofProjectPhaseList"
  670. :key="item.projectPhase"
  671. :label="item.projectPhase"
  672. :value="item.projectPhase"
  673. />
  674. </el-select>
  675. </el-form-item>
  676. </el-col>
  677. </el-row>
  678. <el-row :gutter="16">
  679. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('proofingNo')">
  680. <el-form-item label="打样单号">
  681. <el-select
  682. v-model="oneKeyForm.proofingNo"
  683. placeholder="请选择打样单号"
  684. clearable
  685. filterable
  686. :loading="oneKeyProofingApplyLoading"
  687. :disabled="isBlankValue(oneKeyForm.projectNo)"
  688. style="width: 100%"
  689. @change="handleOneKeyProofingNoChange">
  690. <el-option
  691. v-for="item in oneKeyProofingApplyOptions"
  692. :key="item.applyNo"
  693. :label="item.applyNo"
  694. :value="item.applyNo"
  695. />
  696. </el-select>
  697. </el-form-item>
  698. </el-col>
  699. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('proofingStatus')">
  700. <el-form-item label="打样状态">
  701. <el-select v-model="oneKeyForm.proofingStatus" clearable style="width: 100%">
  702. <el-option label="草稿" value="草稿" />
  703. <el-option label="进行中" value="进行中" />
  704. <el-option label="打样完成" value="打样完成" />
  705. <el-option label="打样结束" value="打样结束" />
  706. </el-select>
  707. </el-form-item>
  708. </el-col>
  709. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('requiredDeliveryDate')">
  710. <el-form-item label="预计完成日期">
  711. <el-date-picker
  712. v-model="oneKeyForm.requiredDeliveryDate"
  713. type="date"
  714. value-format="yyyy-MM-dd"
  715. placeholder="选择日期"
  716. style="width: 100%"
  717. @change="handleOneKeyRequiredDeliveryDateChange">
  718. </el-date-picker>
  719. </el-form-item>
  720. </el-col>
  721. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('planStartDate')">
  722. <el-form-item label="打样开始日期">
  723. <el-date-picker
  724. v-model="oneKeyForm.planStartDate"
  725. type="date"
  726. value-format="yyyy-MM-dd"
  727. placeholder="选择日期"
  728. style="width: 100%">
  729. </el-date-picker>
  730. </el-form-item>
  731. </el-col>
  732. </el-row>
  733. <el-row :gutter="16">
  734. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('proofingNumber')">
  735. <el-form-item label="数量">
  736. <el-input v-model.number="oneKeyForm.proofingNumber" style="width: 100%"></el-input>
  737. <div v-if="oneKeySelectedApplyOption" class="apply-qty-reference">
  738. 试验申请数量{{ getApplyQuantityDisplayText(oneKeySelectedApplyOption.applyQuantity) }}
  739. </div>
  740. </el-form-item>
  741. </el-col>
  742. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('baseline')">
  743. <el-form-item label="Baseline">
  744. <el-date-picker
  745. v-model="oneKeyForm.baseline"
  746. type="date"
  747. value-format="yyyy-MM-dd"
  748. placeholder="选择日期"
  749. style="width: 100%">
  750. </el-date-picker>
  751. </el-form-item>
  752. </el-col>
  753. <el-col :span="(isOneKeyCreateFieldVisible('requiredDeliveryDate') ? 18 : 24) - (isOneKeyCreateFieldVisible('baseline') ? 6 : 0)" v-show="isOneKeyCreateFieldVisible('remark')">
  754. <el-form-item label="备注">
  755. <el-input v-model="oneKeyForm.remark" style="width: 100%"></el-input>
  756. </el-form-item>
  757. </el-col>
  758. </el-row>
  759. </el-form>
  760. <el-footer style="height: 40px; margin-top: 40px; text-align: center">
  761. <el-button @click="oneKeyDialogVisible = false">取消</el-button>
  762. <el-button type="primary" :loading="saveOneKeyLoading" @click="submitOneKey">{{ oneKeyDialogMode === 'edit' ? '保存修改' : '保存' }}</el-button>
  763. </el-footer>
  764. </el-dialog>
  765. <el-dialog title="新增打样" :visible.sync="proofDialogVisible" width="768px" :close-on-click-modal="false">
  766. <el-form :model="proofDialogData" label-position="top" class="proof-grid-form">
  767. <el-row :gutter="16">
  768. <el-col :span="6">
  769. <el-form-item label="项目编码">
  770. <el-input v-model="proofDialogData.projectNo" disabled style="width: 100%" />
  771. </el-form-item>
  772. </el-col>
  773. <el-col :span="12">
  774. <el-form-item label="项目名称">
  775. <el-input v-model="proofDialogData.projectDesc" disabled style="width: 100%" />
  776. </el-form-item>
  777. </el-col>
  778. <el-col :span="6">
  779. <el-form-item label="BU">
  780. <el-input v-model="proofDialogData.buNo" disabled style="width: 100%" />
  781. </el-form-item>
  782. </el-col>
  783. </el-row>
  784. <el-row :gutter="16">
  785. <el-col :span="6">
  786. <el-form-item label="客户编码">
  787. <el-input v-model="proofDialogData.customerNo" disabled style="width: 100%" />
  788. </el-form-item>
  789. </el-col>
  790. <el-col :span="12">
  791. <el-form-item label="客户名称">
  792. <el-input v-model="proofDialogData.customerDesc" disabled style="width: 100%" />
  793. </el-form-item>
  794. </el-col>
  795. <el-col :span="6">
  796. <el-form-item label="项目料号">
  797. <el-input v-model="proofDialogData.testPartNo" disabled style="width: 100%" />
  798. </el-form-item>
  799. </el-col>
  800. </el-row>
  801. <el-row :gutter="16">
  802. <el-col :span="12">
  803. <el-form-item label="料号描述">
  804. <el-input v-model="proofDialogData.partDesc" disabled style="width: 100%" />
  805. </el-form-item>
  806. </el-col>
  807. <el-col :span="6">
  808. <el-form-item label="项目分类" required>
  809. <el-select v-model="proofDialogData.cProjectTypeDb" placeholder="请选择" clearable style="width: 100%">
  810. <el-option
  811. v-for="item in cProjectTypeDbList"
  812. :key="item.cProjectTypeDb"
  813. :label="item.cProjectTypeDb"
  814. :value="item.cProjectTypeDb">
  815. </el-option>
  816. </el-select>
  817. </el-form-item>
  818. </el-col>
  819. <el-col :span="6">
  820. <el-form-item label="项目阶段" required>
  821. <el-select v-model="proofDialogData.projectPhase" placeholder="请选择" clearable style="width: 100%">
  822. <el-option
  823. v-for="item in proofProjectPhaseList"
  824. :key="item.projectPhase"
  825. :label="item.projectPhase"
  826. :value="item.projectPhase">
  827. </el-option>
  828. </el-select>
  829. </el-form-item>
  830. </el-col>
  831. </el-row>
  832. <el-row :gutter="16">
  833. <el-col :span="6">
  834. <el-form-item label="打样单号" required>
  835. <el-select
  836. v-model="proofDialogData.proofingNo"
  837. placeholder="请选择试验单号"
  838. clearable
  839. filterable
  840. :loading="proofDialogApplyLoading"
  841. :disabled="isBlankValue(proofDialogData.projectNo)"
  842. style="width: 100%"
  843. @change="handleProofDialogProofingNoChange">
  844. <el-option
  845. v-for="item in proofDialogApplyOptions"
  846. :key="item.applyNo"
  847. :label="item.applyNo"
  848. :value="item.applyNo"
  849. />
  850. </el-select>
  851. </el-form-item>
  852. </el-col>
  853. <el-col :span="6">
  854. <el-form-item label="打样开始日期" required>
  855. <el-date-picker
  856. v-model="proofDialogData.planStartDate"
  857. type="date"
  858. value-format="yyyy-MM-dd"
  859. placeholder="选择日期"
  860. style="width: 100%">
  861. </el-date-picker>
  862. </el-form-item>
  863. </el-col>
  864. <el-col :span="6">
  865. <el-form-item label="打样状态">
  866. <el-select v-model="proofDialogData.proofingStatus" placeholder="请选择" clearable style="width: 100%">
  867. <el-option label="草稿" value="草稿"></el-option>
  868. <el-option label="进行中" value="进行中"></el-option>
  869. </el-select>
  870. </el-form-item>
  871. </el-col>
  872. <el-col :span="6">
  873. <el-form-item label="实际完成日期">
  874. <el-date-picker
  875. v-model="proofDialogData.actualityDeliveryDate"
  876. type="date"
  877. value-format="yyyy-MM-dd"
  878. placeholder="选择日期"
  879. style="width: 100%">
  880. </el-date-picker>
  881. </el-form-item>
  882. </el-col>
  883. </el-row>
  884. <el-row :gutter="16">
  885. <el-col :span="6">
  886. <el-form-item label="数量" required>
  887. <el-input v-model="proofDialogData.proofingNumber" style="width: 100%" />
  888. <div v-if="proofDialogSelectedApplyOption" class="apply-qty-reference">
  889. 试验申请数量{{ getApplyQuantityDisplayText(proofDialogSelectedApplyOption.applyQuantity) }}
  890. </div>
  891. </el-form-item>
  892. </el-col>
  893. <el-col :span="6">
  894. <el-form-item label="预计完成日期" required>
  895. <el-date-picker
  896. v-model="proofDialogData.requiredDeliveryDate"
  897. type="date"
  898. value-format="yyyy-MM-dd"
  899. placeholder="选择日期"
  900. style="width: 100%">
  901. </el-date-picker>
  902. </el-form-item>
  903. </el-col>
  904. <el-col :span="12">
  905. <el-form-item label="备注">
  906. <el-input v-model="proofDialogData.remark" style="width: 100%" ></el-input>
  907. </el-form-item>
  908. </el-col>
  909. </el-row>
  910. </el-form>
  911. <el-footer style="height: 40px; margin-top: 40px; text-align: center">
  912. <el-button @click="proofDialogVisible = false"> </el-button>
  913. <el-button type="primary" :loading="proofSaveLoading" @click="saveProofRecord"> </el-button>
  914. </el-footer>
  915. </el-dialog>
  916. <el-dialog title="更新工序状态" :visible.sync="processDialogVisible" width="620px" :close-on-click-modal="false">
  917. <el-form :model="processForm" label-position="top" label-width="120px" :inline="true">
  918. <el-form-item label="工序">
  919. <el-input v-model="processForm.processName" disabled style="width: 180px" />
  920. </el-form-item>
  921. <el-form-item label="状态" required>
  922. <el-select v-model="processForm.status" style="width: 180px">
  923. <el-option label="未完成" value="未完成" />
  924. <el-option label="进行中" value="进行中" />
  925. <el-option label="已完成" value="已完成" />
  926. </el-select>
  927. </el-form-item>
  928. <el-form-item label="实际完成日期">
  929. <el-date-picker v-model="processForm.actualDate" type="date" value-format="yyyy-MM-dd" format="yyyy-MM-dd" style="width: 180px" />
  930. </el-form-item>
  931. <el-form-item label="备注">
  932. <el-input type="textarea" :rows="2" v-model="processForm.remark" style="width: 570px" />
  933. </el-form-item>
  934. </el-form>
  935. <el-table :data="processHistoryRows" border stripe height="220" style="margin-top: 32px">
  936. <el-table-column prop="changedDate" label="修改时间" width="120" />
  937. <el-table-column prop="changedBy" label="修改人" width="70" />
  938. <el-table-column prop="oldStatus" label="原状态" width="60" />
  939. <el-table-column prop="newStatus" label="新状态" width="60" />
  940. <el-table-column prop="oldActualDate" label="原实际" width="90" />
  941. <el-table-column prop="newActualDate" label="新实际" width="90" />
  942. <el-table-column prop="remark" label="备注" show-overflow-tooltip />
  943. </el-table>
  944. <div slot="footer">
  945. <el-button @click="processDialogVisible = false">取消</el-button>
  946. <el-button type="primary" :loading="saveProcessLoading" @click="submitProcessUpdate">保存</el-button>
  947. </div>
  948. </el-dialog>
  949. <el-dialog
  950. title="打样完成"
  951. :visible.sync="finishDialogVisible"
  952. width="260px"
  953. :close-on-click-modal="false"
  954. custom-class="finish-proof-dialog">
  955. <div class="finish-dialog-tip">
  956. <i class="el-icon-info"></i>
  957. <span>确认后将直接标记为<strong class="finish-tip-highlight">打样完成</strong></span>
  958. </div>
  959. <el-form :model="finishForm" label-width="100px" class="finish-form">
  960. <el-form-item label="实际完成日期" required>
  961. <el-date-picker
  962. v-model="finishForm.actualityDeliveryDate"
  963. type="date"
  964. value-format="yyyy-MM-dd"
  965. format="yyyy-MM-dd"
  966. placeholder="选择日期"
  967. style="width: 100%"
  968. />
  969. </el-form-item>
  970. </el-form>
  971. <div slot="footer">
  972. <el-button @click="finishDialogVisible = false">取消</el-button>
  973. <el-button type="primary" :loading="finishSaveLoading" @click="submitFinishProof">确认完成</el-button>
  974. </div>
  975. </el-dialog>
  976. <el-dialog :title="roleDialogTitle" :visible.sync="roleDialogVisible" width="559px" :close-on-click-modal="false">
  977. <el-form :inline="true" label-position="top" :model="roleSearchData" style="margin-left: 7px; margin-top: -5px;">
  978. <el-form-item label="用户账号">
  979. <el-input v-model="roleSearchData.username" clearable style="width: 110px"></el-input>
  980. </el-form-item>
  981. <el-form-item label="用户名">
  982. <el-input v-model="roleSearchData.userDisplay" clearable style="width: 110px"></el-input>
  983. </el-form-item>
  984. <el-form-item label="是否在用">
  985. <el-select filterable v-model="roleSearchData.active" clearable style="width: 140px">
  986. <el-option label="是" value="Y"></el-option>
  987. <el-option label="否" value="N"></el-option>
  988. </el-select>
  989. </el-form-item>
  990. <el-form-item label=" ">
  991. <el-button type="primary" style="padding: 3px 12px" @click="queryRoleDialogList">查询</el-button>
  992. </el-form-item>
  993. </el-form>
  994. <el-table
  995. :data="roleDialogList"
  996. stripe
  997. border
  998. highlight-current-row
  999. @row-dblclick="pickRole"
  1000. style="width: 100%">
  1001. <el-table-column prop="username" header-align="center" align="center" label="用户账号"></el-table-column>
  1002. <el-table-column prop="userDisplay" header-align="center" align="center" label="用户名"></el-table-column>
  1003. <el-table-column prop="active" header-align="center" align="center" label="是否在用"></el-table-column>
  1004. </el-table>
  1005. <el-footer style="height: 40px; margin-top: 10px; text-align: center">
  1006. <el-button @click="roleDialogVisible = false">关闭</el-button>
  1007. </el-footer>
  1008. </el-dialog>
  1009. <el-dialog title="新客户" :visible.sync="newCustomerFlag" width="348px" :close-on-click-modal="false" @close="closeNewCustomer">
  1010. <el-form :inline="true" label-position="top" :model="newCustomerData" style="margin-left: 7px; margin-top: -5px;">
  1011. <el-form-item label="客户名称">
  1012. <el-input v-model="newCustomerData.customerDesc" style="width: 313px"></el-input>
  1013. </el-form-item>
  1014. </el-form>
  1015. <el-footer style="height: 40px; margin-top: 10px; text-align: center">
  1016. <el-button type="primary" @click="saveNewCustomer">保存</el-button>
  1017. <el-button @click="newCustomerFlag = false">关闭</el-button>
  1018. </el-footer>
  1019. </el-dialog>
  1020. <Chooselist ref="baseList" @getBaseData="getBaseData"></Chooselist>
  1021. </div>
  1022. </template>
  1023. <script>
  1024. import {
  1025. batchUpdateProofTrackingPlan,
  1026. createProofTrackingRecord,
  1027. deleteProofTracking,
  1028. exportProofTracking,
  1029. finishProofTracking,
  1030. oneKeyCreateProofTracking,
  1031. oneKeyUpdateProofTracking,
  1032. queryProofTrackingBuProcessConfig,
  1033. queryProofTrackingProcessColumns,
  1034. queryProofTrackingProcessCategories,
  1035. deleteProofTrackingProcessColumnsManage,
  1036. queryProofTrackingProcessColumnsManage,
  1037. queryProofTrackingProcessHistory,
  1038. saveProofTrackingBuProcessConfig,
  1039. saveProofTrackingProcessCategoriesManage,
  1040. saveProofTrackingProcessColumnsManage,
  1041. searchProjectInfoTracking,
  1042. searchProjectPartTracking,
  1043. searchProofTracking,
  1044. batchSyncProofTracking,
  1045. batchSyncProjectPartTracking,
  1046. updateProofTrackingComments,
  1047. updateProofTrackingProcess
  1048. } from '@/api/sampleTracking/sampleTracking'
  1049. import { searchExpApplyList } from '@/api/erf/erf'
  1050. import { eamProjectInfoSearch, eamProjectPartSearch, getCustomerNo, saveNewCustomer } from '@/api/eam/eamProject.js'
  1051. import { getSiteAndBuByUserName } from '@/api/eam/eam.js'
  1052. import {
  1053. searchBusinessInfo,
  1054. searchBusinessInfo1,
  1055. searchBusinessInfo2,
  1056. searchBusinessInfo3,
  1057. searchBusinessInfo4,
  1058. searchBusinessInfo5,
  1059. searchBusinessInfo6,
  1060. searchBusinessInfo7,
  1061. searchBusinessInfo8
  1062. } from '@/api/factory/site.js'
  1063. import { queryCustomerList } from '@/api/customer/customer'
  1064. import { getTableDefaultListLanguage, getTableUserListLanguage } from '@/api/table.js'
  1065. import Chooselist from '@/views/modules/common/Chooselist_eam'
  1066. import Sortable from 'sortablejs'
  1067. function focusCommentsNativeInput (el, vnode) {
  1068. const doFocus = () => {
  1069. const componentInput = vnode && vnode.componentInstance && vnode.componentInstance.$refs
  1070. ? vnode.componentInstance.$refs.input
  1071. : null
  1072. const inputEl = componentInput || (el && el.querySelector ? el.querySelector('input') : null)
  1073. if (!inputEl || typeof inputEl.focus !== 'function') {
  1074. return false
  1075. }
  1076. inputEl.focus()
  1077. const textLength = inputEl.value ? String(inputEl.value).length : 0
  1078. if (typeof inputEl.setSelectionRange === 'function') {
  1079. inputEl.setSelectionRange(textLength, textLength)
  1080. }
  1081. return true
  1082. }
  1083. if (doFocus()) {
  1084. return
  1085. }
  1086. setTimeout(doFocus, 0)
  1087. setTimeout(doFocus, 50)
  1088. setTimeout(doFocus, 100)
  1089. }
  1090. const DEFAULT_PROCESS_COLUMNS = [
  1091. { label: 'Raw Material', code: 'rawMaterial', planField: 'rawMaterialPlanDate', actualField: 'rawMaterialActualDate', statusField: 'rawMaterialStatus', processCategoryCode: '', processCategoryName: '' },
  1092. { label: 'Cylinder/Cutter', code: 'cylinderCutter', planField: 'cylinderCutterPlanDate', actualField: 'cylinderCutterActualDate', statusField: 'cylinderCutterStatus', processCategoryCode: '', processCategoryName: '' },
  1093. { label: 'Lamination', code: 'lamination', planField: 'laminationPlanDate', actualField: 'laminationActualDate', statusField: 'laminationStatus', processCategoryCode: '', processCategoryName: '' },
  1094. { label: 'Printing', code: 'printing', planField: 'printingPlanDate', actualField: 'printingActualDate', statusField: 'printingStatus', processCategoryCode: '', processCategoryName: '' },
  1095. { label: 'Etching', code: 'etching', planField: 'etchingPlanDate', actualField: 'etchingActualDate', statusField: 'etchingStatus', processCategoryCode: '', processCategoryName: '' },
  1096. { label: 'Slitting&Packing', code: 'slittingPacking', planField: 'slittingPackingPlanDate', actualField: 'slittingPackingActualDate', statusField: 'slittingPackingStatus', processCategoryCode: '', processCategoryName: '' },
  1097. { label: 'Bonding', code: 'bonding', planField: 'bondingPlanDate', actualField: 'bondingActualDate', statusField: 'bondingStatus', processCategoryCode: '', processCategoryName: '' },
  1098. { label: 'CL60/CL822', code: 'cl60Cl822', planField: 'cl60Cl822PlanDate', actualField: 'cl60Cl822ActualDate', statusField: 'cl60Cl822Status', processCategoryCode: '', processCategoryName: '' },
  1099. { label: 'Spotting', code: 'spotting', planField: 'spottingPlanDate', actualField: 'spottingActualDate', statusField: 'spottingStatus', processCategoryCode: '', processCategoryName: '' },
  1100. { label: 'Encoding', code: 'encoding', planField: 'encodingPlanDate', actualField: 'encodingActualDate', statusField: 'encodingStatus', processCategoryCode: '', processCategoryName: '' },
  1101. { label: 'Die cut', code: 'dieCut', planField: 'dieCutPlanDate', actualField: 'dieCutActualDate', statusField: 'dieCutStatus', processCategoryCode: '', processCategoryName: '' },
  1102. { label: 'Inspection', code: 'inspection', planField: 'inspectionPlanDate', actualField: 'inspectionActualDate', statusField: 'inspectionStatus', processCategoryCode: '', processCategoryName: '' },
  1103. { label: 'Delivery&Package', code: 'deliveryPackage', planField: 'deliveryPackagePlanDate', actualField: 'deliveryPackageActualDate', statusField: 'deliveryPackageStatus', processCategoryCode: '', processCategoryName: '' }
  1104. ]
  1105. function cloneDefaultProcessColumns () {
  1106. return DEFAULT_PROCESS_COLUMNS.map(item => Object.assign({}, item))
  1107. }
  1108. function buildProcessCodeLabelMapByRows (rows) {
  1109. const nextMap = {}
  1110. if (!Array.isArray(rows)) {
  1111. return nextMap
  1112. }
  1113. rows.forEach(item => {
  1114. if (!item) {
  1115. return
  1116. }
  1117. const code = item.code == null ? '' : String(item.code).trim()
  1118. const label = item.label == null ? '' : String(item.label).trim()
  1119. if (!code || !label) {
  1120. return
  1121. }
  1122. // 同时缓存原始编码与小写编码,兼容历史配置大小写不一致的场景。
  1123. nextMap[code] = label
  1124. nextMap[code.toLowerCase()] = label
  1125. })
  1126. return nextMap
  1127. }
  1128. const ONE_KEY_ROLE_FIELDS = [
  1129. 'projectManager',
  1130. 'projectOwner',
  1131. 'engineer',
  1132. 'cManufactureEngineer',
  1133. 'cQualityEngineer1',
  1134. 'cQualityEngineer2',
  1135. 'cQualityEngineer3',
  1136. 'cQualityEngineer4',
  1137. 'cQualityEngineer5',
  1138. 'cQualityEngineer6',
  1139. 'cQualityEngineer7',
  1140. 'ipqcHardTag',
  1141. 'docEngineer',
  1142. 'docEngineer2'
  1143. ]
  1144. const DEFAULT_TRACKING_COLUMNS = [
  1145. {
  1146. serialNumber: 'proofTrackingTable1ProjectNo',
  1147. columnProp: 'projectNo',
  1148. headerAlign: 'center',
  1149. align: 'left',
  1150. columnLabel: '项目编码',
  1151. columnHidden: false,
  1152. columnImage: false,
  1153. columnSortable: false,
  1154. status: true,
  1155. fixed: '',
  1156. columnWidth: 90,
  1157. showOverflowTooltip: true
  1158. },
  1159. {
  1160. serialNumber: 'proofTrackingTable1ProjectDesc',
  1161. columnProp: 'projectDesc',
  1162. headerAlign: 'center',
  1163. align: 'left',
  1164. columnLabel: '项目描述',
  1165. columnHidden: false,
  1166. columnImage: false,
  1167. columnSortable: false,
  1168. status: true,
  1169. fixed: '',
  1170. columnWidth: 120,
  1171. showOverflowTooltip: true
  1172. },
  1173. {
  1174. serialNumber: 'proofTrackingTable1TestPartNo',
  1175. columnProp: 'testPartNo',
  1176. headerAlign: 'center',
  1177. align: 'left',
  1178. columnLabel: '项目物料',
  1179. columnHidden: false,
  1180. columnImage: false,
  1181. columnSortable: false,
  1182. status: true,
  1183. fixed: '',
  1184. columnWidth: 90,
  1185. showOverflowTooltip: true
  1186. },
  1187. {
  1188. serialNumber: 'proofTrackingTable1PartDesc',
  1189. columnProp: 'partDesc',
  1190. headerAlign: 'center',
  1191. align: 'left',
  1192. columnLabel: '物料描述',
  1193. columnHidden: false,
  1194. columnImage: false,
  1195. columnSortable: false,
  1196. status: true,
  1197. fixed: 'left',
  1198. columnWidth: 150,
  1199. showOverflowTooltip: true
  1200. },
  1201. {
  1202. serialNumber: 'proofTrackingTable1DryWetType',
  1203. columnProp: 'dryWetType',
  1204. headerAlign: 'center',
  1205. align: 'center',
  1206. columnLabel: '分类',
  1207. columnHidden: false,
  1208. columnImage: false,
  1209. columnSortable: false,
  1210. status: true,
  1211. fixed: '',
  1212. columnWidth: 70,
  1213. showOverflowTooltip: true
  1214. },
  1215. {
  1216. serialNumber: 'proofTrackingTable1ProjectRegion',
  1217. columnProp: 'cProjectRegion',
  1218. headerAlign: 'center',
  1219. align: 'left',
  1220. columnLabel: '区域',
  1221. columnHidden: false,
  1222. columnImage: false,
  1223. columnSortable: false,
  1224. status: true,
  1225. fixed: '',
  1226. columnWidth: 90,
  1227. showOverflowTooltip: true
  1228. },
  1229. {
  1230. serialNumber: 'proofTrackingTable1CustomerDesc',
  1231. columnProp: 'customerDesc',
  1232. headerAlign: 'center',
  1233. align: 'left',
  1234. columnLabel: '客户名称',
  1235. columnHidden: false,
  1236. columnImage: false,
  1237. columnSortable: false,
  1238. status: true,
  1239. fixed: '',
  1240. columnWidth: 130,
  1241. showOverflowTooltip: true
  1242. },
  1243. {
  1244. serialNumber: 'proofTrackingTable1Engineer',
  1245. columnProp: 'engineer',
  1246. headerAlign: 'center',
  1247. align: 'left',
  1248. columnLabel: 'Engineer',
  1249. columnHidden: false,
  1250. columnImage: false,
  1251. columnSortable: false,
  1252. status: true,
  1253. fixed: '',
  1254. columnWidth: 120,
  1255. showOverflowTooltip: true
  1256. },
  1257. {
  1258. serialNumber: 'proofTrackingTable1ProjectManager',
  1259. columnProp: 'projectManager',
  1260. headerAlign: 'center',
  1261. align: 'left',
  1262. columnLabel: 'PM/Sales',
  1263. columnHidden: false,
  1264. columnImage: false,
  1265. columnSortable: false,
  1266. status: true,
  1267. fixed: '',
  1268. columnWidth: 120,
  1269. showOverflowTooltip: true
  1270. },
  1271. {
  1272. serialNumber: 'proofTrackingTable1ProofingNo',
  1273. columnProp: 'proofingNo',
  1274. headerAlign: 'center',
  1275. align: 'left',
  1276. columnLabel: '打样单号',
  1277. columnHidden: false,
  1278. columnImage: false,
  1279. columnSortable: false,
  1280. status: true,
  1281. fixed: '',
  1282. columnWidth: 90,
  1283. showOverflowTooltip: true
  1284. },
  1285. {
  1286. serialNumber: 'proofTrackingTable1ProofingStatus',
  1287. columnProp: 'proofingStatus',
  1288. headerAlign: 'center',
  1289. align: 'center',
  1290. columnLabel: '打样状态',
  1291. columnHidden: false,
  1292. columnImage: false,
  1293. columnSortable: false,
  1294. status: true,
  1295. fixed: '',
  1296. columnWidth: 80,
  1297. showOverflowTooltip: false
  1298. },
  1299. {
  1300. serialNumber: 'proofTrackingTable1Baseline',
  1301. columnProp: 'baseline',
  1302. headerAlign: 'center',
  1303. align: 'center',
  1304. columnLabel: 'Baseline',
  1305. columnHidden: false,
  1306. columnImage: false,
  1307. columnSortable: false,
  1308. status: true,
  1309. fixed: '',
  1310. columnWidth: 100,
  1311. showOverflowTooltip: false
  1312. },
  1313. {
  1314. serialNumber: 'proofTrackingTable1DeliveryVariance',
  1315. columnProp: 'deliveryVariance',
  1316. headerAlign: 'center',
  1317. align: 'center',
  1318. columnLabel: 'Delivery Variance',
  1319. columnHidden: false,
  1320. columnImage: false,
  1321. columnSortable: false,
  1322. status: true,
  1323. fixed: '',
  1324. columnWidth: 110,
  1325. showOverflowTooltip: false
  1326. },
  1327. {
  1328. serialNumber: 'proofTrackingTable1TrackingStatus',
  1329. columnProp: 'trackingStatus',
  1330. headerAlign: 'center',
  1331. align: 'center',
  1332. columnLabel: 'Tracking Status',
  1333. columnHidden: false,
  1334. columnImage: false,
  1335. columnSortable: false,
  1336. status: true,
  1337. fixed: '',
  1338. columnWidth: 150,
  1339. showOverflowTooltip: true
  1340. }
  1341. ]
  1342. function cloneDefaultTrackingColumns () {
  1343. return DEFAULT_TRACKING_COLUMNS.map(item => Object.assign({}, item))
  1344. }
  1345. export default {
  1346. name: 'ProjectProofTracking',
  1347. directives: {
  1348. focusCommentsInput: {
  1349. inserted (el, binding, vnode) {
  1350. if (binding.value) {
  1351. focusCommentsNativeInput(el, vnode)
  1352. }
  1353. },
  1354. componentUpdated (el, binding, vnode) {
  1355. if (binding.value && !binding.oldValue) {
  1356. focusCommentsNativeInput(el, vnode)
  1357. }
  1358. }
  1359. }
  1360. },
  1361. components: {
  1362. Chooselist
  1363. },
  1364. data () {
  1365. return {
  1366. searchData: {
  1367. site: this.$store.state.user.site,
  1368. userName: this.$store.state.user.name,
  1369. projectNo: '',
  1370. projectDesc: '',
  1371. buNo: '',
  1372. testPartNo: '',
  1373. partDesc: '',
  1374. dryWetType: '',
  1375. customerNo: '',
  1376. proofingNo: '',
  1377. projectPartSyncFlag: '',
  1378. proofSyncFlag: '',
  1379. proofingStatus: ''
  1380. },
  1381. trackingColumnList: cloneDefaultTrackingColumns(),
  1382. hasUserTrackingColumnConfig: false,
  1383. processColumns: cloneDefaultProcessColumns(),
  1384. processCodeLabelMap: buildProcessCodeLabelMapByRows(cloneDefaultProcessColumns()),
  1385. buProcessCategoryOptionsMap: {},
  1386. buProcessConfigDialogVisible: false,
  1387. buProcessConfigRows: [],
  1388. buProcessConfigMap: {},
  1389. defaultProcessConfigDialogVisible: false,
  1390. defaultProcessConfigRows: [],
  1391. defaultProcessConfigSelectionRows: [],
  1392. defaultProcessConfigDeleteLoading: false,
  1393. defaultProcessConfigRowSeed: 0,
  1394. defaultProcessConfigSortable: null,
  1395. processCategoryDialogVisible: false,
  1396. processCategoryDialogBuNo: '',
  1397. processCategoryDialogBuDesc: '',
  1398. processCategoryRows: [],
  1399. processCategorySelectionRows: [],
  1400. processCategorySaveLoading: false,
  1401. processCategoryRowSeed: 0,
  1402. processOrderDragState: {
  1403. buNo: '',
  1404. fromIndex: -1
  1405. },
  1406. projectCategoryList: ['Low Risk', 'High Risk', 'Sustaining'],
  1407. cProjectRegionList: [
  1408. { cProjectRegion: 'Global' },
  1409. { cProjectRegion: 'APAC' },
  1410. { cProjectRegion: 'CHINA' },
  1411. { cProjectRegion: 'EU' },
  1412. { cProjectRegion: 'US' },
  1413. { cProjectRegion: 'Mexico' },
  1414. { cProjectRegion: 'Other' }
  1415. ],
  1416. priorityList: ['Low', 'Middle', 'High'],
  1417. projectStatusList: ['草稿', '进行中', '已关闭'],
  1418. partTypeList: ['Active', 'On hold', 'Cancel', 'EOL'],
  1419. dryWetTypeList: ['Dry', 'Wet'],
  1420. partStatusList: ['草稿', '进行中', '已量产', '正式量产'],
  1421. cProjectTypeDbList: [
  1422. { cProjectTypeDb: 'Sustaining' },
  1423. { cProjectTypeDb: 'Low Risk' },
  1424. { cProjectTypeDb: 'High Risk' }
  1425. ],
  1426. proofProjectPhaseList: [
  1427. { projectPhase: 'Prototype' },
  1428. { projectPhase: 'Alpha' },
  1429. { projectPhase: 'Beta' },
  1430. { projectPhase: 'Pre-launch' }
  1431. ],
  1432. // 一键创建字段白名单(仅 create 模式生效):后续确认要隐藏哪些字段时,只改这里即可。
  1433. oneKeyCreateVisibleFieldKeys: [
  1434. 'buNo',
  1435. 'projectNo',
  1436. 'projectDesc',
  1437. 'customerNo',
  1438. 'customerDesc',
  1439. 'cProjectRegion',
  1440. 'projectCreationDate',
  1441. 'needDate',
  1442. 'projectCategory',
  1443. 'priorityLevel',
  1444. 'testPartNo',
  1445. 'partDesc',
  1446. 'partType',
  1447. 'dryWetType',
  1448. 'projectManager',
  1449. 'projectOwner',
  1450. 'engineer',
  1451. 'cManufactureEngineer',
  1452. 'cQualityEngineer6',
  1453. 'cQualityEngineer1',
  1454. 'cQualityEngineer2',
  1455. 'cQualityEngineer4',
  1456. 'cQualityEngineer3',
  1457. 'cQualityEngineer5',
  1458. 'ipqcHardTag',
  1459. 'cQualityEngineer7',
  1460. 'docEngineer',
  1461. 'docEngineer2',
  1462. 'projectStatus',
  1463. 'projectPhase',
  1464. 'proofingNo',
  1465. 'proofingStatus',
  1466. 'proofingNumber',
  1467. 'baseline',
  1468. 'planStartDate',
  1469. 'requiredDeliveryDate',
  1470. 'remark'
  1471. ],
  1472. userBuList: [],
  1473. tagNo: '',
  1474. newCustomerFlag: false,
  1475. newCustomerData: {
  1476. customerDesc: ''
  1477. },
  1478. roleDialogVisible: false,
  1479. roleDialogTitle: '',
  1480. roleDialogField: '',
  1481. roleDialogApiKey: '0',
  1482. roleDialogList: [],
  1483. roleSearchData: {
  1484. site: this.$store.state.user.site,
  1485. username: '',
  1486. userDisplay: '',
  1487. active: '',
  1488. page: 1,
  1489. limit: 50
  1490. },
  1491. roleConfig: {
  1492. projectManager: { apiKey: '0', title: 'PM/Sales', nameField: 'projectManagerName' },
  1493. projectOwner: { apiKey: '1', title: 'PjM', nameField: 'projectOwnerName' },
  1494. cQualityEngineer1: { apiKey: '2', title: 'IPQC-Lam/Pri/Etch/Slit', nameField: 'cQualityEngineer1Name' },
  1495. cQualityEngineer2: { apiKey: '3', title: 'IPQC-Converting', nameField: 'cQualityEngineer2Name' },
  1496. cQualityEngineer3: { apiKey: '4', title: 'FQC1', nameField: 'cQualityEngineer3Name' },
  1497. cManufactureEngineer: { apiKey: '5', title: 'MFG', nameField: 'cManufactureEngineerName' },
  1498. engineer: { apiKey: '6', title: 'Engineer', nameField: 'engineerName' },
  1499. cQualityEngineer4: { apiKey: '7', title: 'SQE', nameField: 'cQualityEngineer4Name' },
  1500. cQualityEngineer5: { apiKey: '7', title: 'FQC2', nameField: 'cQualityEngineer5Name' },
  1501. cQualityEngineer6: { apiKey: '7', title: 'IQC', nameField: 'cQualityEngineer6Name' },
  1502. docEngineer: { apiKey: '7', title: '文档工程师', nameField: 'docEngineerName' },
  1503. ipqcHardTag: { apiKey: '7', title: 'IPQC-Hardtag', nameField: 'ipqcHardTagName' },
  1504. cQualityEngineer7: { apiKey: '7', title: '前道工程师', nameField: 'cQualityEngineer7Name' },
  1505. docEngineer2: { apiKey: '8', title: '文档工程师2', nameField: 'docEngineer2Name' }
  1506. },
  1507. dataList: [],
  1508. dataListLoading: false,
  1509. exportLoading: false,
  1510. syncBatchNpiLoading: false,
  1511. syncBatchProofLoading: false,
  1512. pageIndex: 1,
  1513. pageSize: 20,
  1514. totalPage: 0,
  1515. currentRow: null,
  1516. manualSelectedTrackingId: null,
  1517. selectionRows: [],
  1518. batchEditMode: false,
  1519. processHistoryMap: {},
  1520. processTooltipHoverKey: '',
  1521. processTooltip: {
  1522. visible: false,
  1523. content: '',
  1524. left: 0,
  1525. top: 0
  1526. },
  1527. processHistoryRows: [],
  1528. commentsEditingTrackingId: null,
  1529. commentsDraft: '',
  1530. commentsSaveLoading: false,
  1531. oneKeyDialogVisible: false,
  1532. oneKeyDialogMode: 'create',
  1533. saveOneKeyLoading: false,
  1534. oneKeyForm: {},
  1535. oneKeyProofingApplyOptions: [],
  1536. oneKeyProofingApplyLoading: false,
  1537. oneKeyProofingApplyQueryToken: 0,
  1538. oneKeyProjectQueryToken: 0,
  1539. oneKeyPartQueryToken: 0,
  1540. oneKeyProjectNoSnapshot: '',
  1541. oneKeyProjectRoleBaseline: {},
  1542. proofDialogVisible: false,
  1543. proofSaveLoading: false,
  1544. proofDialogData: this.getDefaultProofDialogData(),
  1545. proofDialogApplyOptions: [],
  1546. proofDialogApplyLoading: false,
  1547. proofDialogApplyQueryToken: 0,
  1548. processDialogVisible: false,
  1549. saveProcessLoading: false,
  1550. processForm: {},
  1551. finishDialogVisible: false,
  1552. finishSaveLoading: false,
  1553. finishForm: {
  1554. trackingId: null,
  1555. actualityDeliveryDate: ''
  1556. }
  1557. }
  1558. },
  1559. activated () {
  1560. this.loadUserBuList().then(() => {
  1561. return Promise.all([this.queryProcessColumns(), this.loadTrackingColumns()])
  1562. }).then(() => {
  1563. this.queryBuProcessConfig()
  1564. this.getDataList()
  1565. })
  1566. },
  1567. deactivated () {
  1568. this.destroyDefaultProcessConfigSortable()
  1569. },
  1570. computed: {
  1571. visibleTrackingColumns () {
  1572. const sourceColumns = this.hasUserTrackingColumnConfig
  1573. ? (Array.isArray(this.trackingColumnList) ? this.trackingColumnList : [])
  1574. : ((Array.isArray(this.trackingColumnList) && this.trackingColumnList.length > 0)
  1575. ? this.trackingColumnList
  1576. : this.buildDefaultTrackingColumns())
  1577. return sourceColumns.filter(item => this.isTrackingColumnVisible(item))
  1578. },
  1579. visibleProcessColumns () {
  1580. const searchBuNo = this.getSelectedSearchBuNo()
  1581. // 指定了查询BU时,整表进度列按该BU配置展示。
  1582. if (!searchBuNo) {
  1583. return this.processColumns
  1584. }
  1585. const visibleCodes = this.getProcessVisibleCodesByBuNo(searchBuNo)
  1586. if (!visibleCodes) {
  1587. return this.processColumns
  1588. }
  1589. const visibleColumns = this.processColumns.filter(item => visibleCodes.indexOf(item.code) > -1)
  1590. return visibleColumns.length > 0 ? visibleColumns : this.processColumns
  1591. },
  1592. oneKeySelectedApplyOption () {
  1593. const selectedApplyNo = this.oneKeyForm && !this.isBlankValue(this.oneKeyForm.proofingNo)
  1594. ? String(this.oneKeyForm.proofingNo).trim()
  1595. : ''
  1596. if (!selectedApplyNo) {
  1597. return null
  1598. }
  1599. return this.oneKeyProofingApplyOptions.find(item => item && item.applyNo === selectedApplyNo) || null
  1600. },
  1601. proofDialogSelectedApplyOption () {
  1602. const selectedApplyNo = this.proofDialogData && !this.isBlankValue(this.proofDialogData.proofingNo)
  1603. ? String(this.proofDialogData.proofingNo).trim()
  1604. : ''
  1605. if (!selectedApplyNo) {
  1606. return null
  1607. }
  1608. return this.proofDialogApplyOptions.find(item => item && item.applyNo === selectedApplyNo) || null
  1609. }
  1610. },
  1611. methods: {
  1612. getTrackingColumnFunctionId () {
  1613. return this.$route && this.$route.meta && this.$route.meta.menuId
  1614. ? this.$route.meta.menuId
  1615. : ''
  1616. },
  1617. getTrackingColumnTableId () {
  1618. const functionId = this.getTrackingColumnFunctionId()
  1619. return functionId ? `${functionId}table1` : 'projectProofTrackingTable1'
  1620. },
  1621. buildDefaultTrackingColumns () {
  1622. const functionId = this.getTrackingColumnFunctionId()
  1623. const tableId = this.getTrackingColumnTableId()
  1624. const userId = this.$store.state.user.name
  1625. return cloneDefaultTrackingColumns().map(item => Object.assign({}, item, {
  1626. userId: userId,
  1627. functionId: functionId,
  1628. tableId: tableId,
  1629. tableName: '项目打样跟踪'
  1630. }))
  1631. },
  1632. normalizeTrackingColumnWidth (value, fallbackValue) {
  1633. const parsedValue = parseInt(value, 10)
  1634. if (!Number.isNaN(parsedValue) && parsedValue > 0) {
  1635. return parsedValue
  1636. }
  1637. const parsedFallback = parseInt(fallbackValue, 10)
  1638. return !Number.isNaN(parsedFallback) && parsedFallback > 0 ? parsedFallback : 120
  1639. },
  1640. normalizeTrackingColumnBoolean (value, fallbackValue) {
  1641. if (value === undefined || value === null || value === '') {
  1642. return fallbackValue
  1643. }
  1644. if (typeof value === 'boolean') {
  1645. return value
  1646. }
  1647. if (typeof value === 'number') {
  1648. return value !== 0
  1649. }
  1650. const normalizedValue = String(value).trim().toLowerCase()
  1651. if (normalizedValue === 'true' || normalizedValue === '1' || normalizedValue === 'y' || normalizedValue === 'yes') {
  1652. return true
  1653. }
  1654. if (normalizedValue === 'false' || normalizedValue === '0' || normalizedValue === 'n' || normalizedValue === 'no') {
  1655. return false
  1656. }
  1657. return fallbackValue
  1658. },
  1659. normalizeTrackingColumnItem (item, fallbackItem) {
  1660. const defaultItem = fallbackItem || {}
  1661. const mergedItem = Object.assign({}, defaultItem, item || {})
  1662. const rawColumnProp = mergedItem.columnProp == null ? '' : String(mergedItem.columnProp).trim()
  1663. const columnProp = rawColumnProp.toLowerCase() === 'pic' ? 'pic' : rawColumnProp
  1664. const normalizedLabel = mergedItem.columnLabel == null ? '' : String(mergedItem.columnLabel).trim()
  1665. return Object.assign({}, mergedItem, {
  1666. columnProp: columnProp,
  1667. columnLabel: normalizedLabel || defaultItem.columnLabel || columnProp,
  1668. headerAlign: mergedItem.headerAlign || defaultItem.headerAlign || 'center',
  1669. align: mergedItem.align || defaultItem.align || 'left',
  1670. fixed: mergedItem.fixed == null ? (defaultItem.fixed || '') : mergedItem.fixed,
  1671. columnWidth: this.normalizeTrackingColumnWidth(mergedItem.columnWidth, defaultItem.columnWidth),
  1672. showOverflowTooltip: this.normalizeTrackingColumnBoolean(mergedItem.showOverflowTooltip, defaultItem.showOverflowTooltip !== false),
  1673. status: this.normalizeTrackingColumnBoolean(mergedItem.status, true),
  1674. columnHidden: this.normalizeTrackingColumnBoolean(mergedItem.columnHidden, false)
  1675. })
  1676. },
  1677. normalizeTrackingColumns (rows, appendDefaultColumns) {
  1678. const defaultColumns = this.buildDefaultTrackingColumns()
  1679. const defaultColumnMap = {}
  1680. defaultColumns.forEach(item => {
  1681. defaultColumnMap[item.columnProp] = item
  1682. })
  1683. const mergedRows = []
  1684. const usedColumnPropMap = {}
  1685. if (Array.isArray(rows)) {
  1686. rows.forEach(item => {
  1687. const columnProp = item && item.columnProp != null ? String(item.columnProp).trim() : ''
  1688. if (!columnProp || usedColumnPropMap[columnProp]) {
  1689. return
  1690. }
  1691. usedColumnPropMap[columnProp] = true
  1692. mergedRows.push(this.normalizeTrackingColumnItem(item, defaultColumnMap[columnProp]))
  1693. })
  1694. }
  1695. if (appendDefaultColumns !== true) {
  1696. return mergedRows
  1697. }
  1698. // 用户旧配置可能缺少新加列,这里按系统默认顺序补齐,避免新增列永远看不到。
  1699. defaultColumns.forEach((defaultItem, defaultIndex) => {
  1700. if (usedColumnPropMap[defaultItem.columnProp]) {
  1701. return
  1702. }
  1703. let insertIndex = mergedRows.length
  1704. for (let i = defaultIndex + 1; i < defaultColumns.length; i++) {
  1705. const nextProp = defaultColumns[i].columnProp
  1706. const matchedIndex = mergedRows.findIndex(item => item.columnProp === nextProp)
  1707. if (matchedIndex > -1) {
  1708. insertIndex = matchedIndex
  1709. break
  1710. }
  1711. }
  1712. if (insertIndex === mergedRows.length) {
  1713. for (let i = defaultIndex - 1; i >= 0; i--) {
  1714. const prevProp = defaultColumns[i].columnProp
  1715. const matchedIndex = mergedRows.findIndex(item => item.columnProp === prevProp)
  1716. if (matchedIndex > -1) {
  1717. insertIndex = matchedIndex + 1
  1718. break
  1719. }
  1720. }
  1721. }
  1722. mergedRows.splice(insertIndex, 0, Object.assign({}, defaultItem))
  1723. usedColumnPropMap[defaultItem.columnProp] = true
  1724. })
  1725. return mergedRows
  1726. },
  1727. loadTrackingDefaultColumns (tableId) {
  1728. const queryTable = {
  1729. functionId: this.getTrackingColumnFunctionId(),
  1730. tableId: tableId,
  1731. languageCode: this.$i18n && this.$i18n.locale ? this.$i18n.locale : ''
  1732. }
  1733. return getTableDefaultListLanguage(queryTable).then(({ data }) => {
  1734. const rows = data && Array.isArray(data.rows) ? data.rows : []
  1735. this.hasUserTrackingColumnConfig = false
  1736. this.trackingColumnList = rows.length > 0
  1737. ? this.normalizeTrackingColumns(rows, true)
  1738. : this.buildDefaultTrackingColumns()
  1739. this.refreshTrackingTableLayout()
  1740. return this.trackingColumnList
  1741. }).catch(() => {
  1742. this.hasUserTrackingColumnConfig = false
  1743. this.trackingColumnList = this.buildDefaultTrackingColumns()
  1744. this.refreshTrackingTableLayout()
  1745. return this.trackingColumnList
  1746. })
  1747. },
  1748. loadTrackingColumns () {
  1749. const tableId = this.getTrackingColumnTableId()
  1750. const queryTableUser = {
  1751. userId: this.$store.state.user.name,
  1752. functionId: this.getTrackingColumnFunctionId(),
  1753. tableId: tableId,
  1754. status: true,
  1755. languageCode: this.$i18n && this.$i18n.locale ? this.$i18n.locale : ''
  1756. }
  1757. return getTableUserListLanguage(queryTableUser).then(({ data }) => {
  1758. const rows = data && Array.isArray(data.rows) ? data.rows : []
  1759. if (rows.length > 0) {
  1760. this.hasUserTrackingColumnConfig = true
  1761. this.trackingColumnList = this.normalizeTrackingColumns(rows, false)
  1762. this.refreshTrackingTableLayout()
  1763. return this.trackingColumnList
  1764. }
  1765. return this.loadTrackingDefaultColumns(tableId)
  1766. }).catch(() => {
  1767. this.hasUserTrackingColumnConfig = false
  1768. this.trackingColumnList = this.buildDefaultTrackingColumns()
  1769. this.refreshTrackingTableLayout()
  1770. return this.trackingColumnList
  1771. })
  1772. },
  1773. isTrackingColumnVisible (column) {
  1774. if (!column) {
  1775. return false
  1776. }
  1777. if (this.normalizeTrackingColumnBoolean(column.status, true) === false) {
  1778. return false
  1779. }
  1780. return this.normalizeTrackingColumnBoolean(column.columnHidden, false) === false
  1781. },
  1782. isProjectPartSyncColumn (columnProp) {
  1783. return ['projectNo', 'projectDesc', 'testPartNo', 'partDesc'].indexOf(columnProp) > -1
  1784. },
  1785. getTrackingColumnDisplayValue (row, columnProp) {
  1786. if (!row || !columnProp) {
  1787. return ''
  1788. }
  1789. const value = row[columnProp]
  1790. return this.isBlankValue(value) ? '' : String(value)
  1791. },
  1792. getTrackingRoleDisplayValue (row, nameField, valueField) {
  1793. if (!row) {
  1794. return ''
  1795. }
  1796. const roleNameValue = row[nameField]
  1797. if (!this.isBlankValue(roleNameValue)) {
  1798. return String(roleNameValue)
  1799. }
  1800. const roleValue = row[valueField]
  1801. if (this.isBlankValue(roleValue)) {
  1802. return ''
  1803. }
  1804. const displayName = this.getRoleDisplayName(roleValue)
  1805. return this.isBlankValue(displayName) ? '' : displayName
  1806. },
  1807. getSearchBuOptionList () {
  1808. return this.userBuList
  1809. .map(item => this.normalizeBuNo(item && item.buNo ? item.buNo : '', this.searchData.site))
  1810. .filter(item => !!item)
  1811. },
  1812. ensureSearchBuNoSelected () {
  1813. const buOptionList = this.getSearchBuOptionList()
  1814. if (buOptionList.length === 0) {
  1815. this.searchData.buNo = ''
  1816. return ''
  1817. }
  1818. const currentBuNo = this.normalizeBuNo(this.searchData.buNo, this.searchData.site)
  1819. if (currentBuNo && buOptionList.indexOf(currentBuNo) > -1) {
  1820. this.searchData.buNo = currentBuNo
  1821. return currentBuNo
  1822. }
  1823. // 需求要求查询BU必须有值:为空或无效时默认回退到首个可用BU。
  1824. this.searchData.buNo = buOptionList[0]
  1825. return this.searchData.buNo
  1826. },
  1827. loadUserBuList () {
  1828. if (this.userBuList.length > 0) {
  1829. this.ensureSearchBuNoSelected()
  1830. this.ensureBuProcessConfigRows()
  1831. return Promise.resolve(this.userBuList)
  1832. }
  1833. const params = { username: this.$store.state.user.name }
  1834. return getSiteAndBuByUserName(params).then(({ data }) => {
  1835. if (data && data.code === 0) {
  1836. const excludedBuNoList = ['3_02-Hardtag', '4_04-MHM', '5_05-Alpha']
  1837. this.userBuList = (data.rows || []).filter(item => {
  1838. const buNo = item && item.buNo ? String(item.buNo).trim() : ''
  1839. return excludedBuNoList.indexOf(buNo) === -1
  1840. })
  1841. this.ensureSearchBuNoSelected()
  1842. this.ensureBuProcessConfigRows()
  1843. }
  1844. return this.userBuList
  1845. }).catch(() => {
  1846. this.userBuList = []
  1847. this.ensureSearchBuNoSelected()
  1848. this.ensureBuProcessConfigRows()
  1849. return []
  1850. })
  1851. },
  1852. queryProcessColumns () {
  1853. const selectedBuNo = this.getSelectedSearchBuNo()
  1854. const queryBuNo = selectedBuNo || '*'
  1855. // 列顺序来源规则:
  1856. // 1) 未选择查询 BU:固定按 bu='*' 读取默认工序顺序;
  1857. // 2) 已选择查询 BU:按该 BU 读取;若无配置由后端回退到 bu='*'。
  1858. const inData = {
  1859. site: this.$store.state.user.site,
  1860. userName: this.$store.state.user.name,
  1861. buNo: queryBuNo
  1862. }
  1863. return queryProofTrackingProcessColumns(inData).then(({ data }) => {
  1864. if (!(data && data.code === 0)) {
  1865. this.$message.error((data && data.msg) || '加载工序列配置失败,已使用默认配置')
  1866. this.processColumns = cloneDefaultProcessColumns()
  1867. this.mergeProcessCodeLabelMapByRows(this.processColumns)
  1868. this.ensureBuProcessConfigRows()
  1869. this.refreshTrackingTableLayout()
  1870. return this.processColumns
  1871. }
  1872. const rows = Array.isArray(data.rows) ? data.rows : []
  1873. // 防御性排序:即使后端 SQL 已 ORDER BY sort_no,前端仍按 sortNo 二次排序,确保展示稳定。
  1874. const sortedRows = rows
  1875. .map((item, index) => {
  1876. const parsedSortNo = parseInt(item && item.sortNo, 10)
  1877. return {
  1878. item: item,
  1879. sourceIndex: index,
  1880. sortNo: Number.isNaN(parsedSortNo) ? Number.MAX_SAFE_INTEGER : parsedSortNo
  1881. }
  1882. })
  1883. .sort((a, b) => {
  1884. if (a.sortNo !== b.sortNo) {
  1885. return a.sortNo - b.sortNo
  1886. }
  1887. return a.sourceIndex - b.sourceIndex
  1888. })
  1889. const nextColumns = []
  1890. const usedCodeMap = {}
  1891. sortedRows.forEach(({ item, sortNo }) => {
  1892. if (!item) {
  1893. return
  1894. }
  1895. const code = item.code == null ? '' : String(item.code).trim()
  1896. const label = item.label == null ? '' : String(item.label).trim()
  1897. const planField = item.planField == null ? '' : String(item.planField).trim()
  1898. const actualField = item.actualField == null ? '' : String(item.actualField).trim()
  1899. const statusField = item.statusField == null ? '' : String(item.statusField).trim()
  1900. const processCategoryCode = item.processCategoryCode == null ? '' : String(item.processCategoryCode).trim()
  1901. const processCategoryName = item.processCategoryName == null ? '' : String(item.processCategoryName).trim()
  1902. if (!code || !label || !planField || !actualField || !statusField || usedCodeMap[code]) {
  1903. return
  1904. }
  1905. usedCodeMap[code] = true
  1906. nextColumns.push({
  1907. label: label,
  1908. code: code,
  1909. planField: planField,
  1910. actualField: actualField,
  1911. statusField: statusField,
  1912. processCategoryCode: processCategoryCode,
  1913. processCategoryName: processCategoryName,
  1914. sortNo: sortNo === Number.MAX_SAFE_INTEGER ? null : sortNo
  1915. })
  1916. })
  1917. // 保障切换期可用性:当表里无有效配置时继续使用前端兜底工序定义。
  1918. this.processColumns = nextColumns.length > 0 ? nextColumns : cloneDefaultProcessColumns()
  1919. this.mergeProcessCodeLabelMapByRows(this.processColumns)
  1920. this.syncDataListProcessValues()
  1921. this.ensureBuProcessConfigRows()
  1922. this.refreshTrackingTableLayout()
  1923. return this.processColumns
  1924. }).catch(() => {
  1925. this.$message.error('加载工序列配置异常,已使用默认配置')
  1926. this.processColumns = cloneDefaultProcessColumns()
  1927. this.mergeProcessCodeLabelMapByRows(this.processColumns)
  1928. this.syncDataListProcessValues()
  1929. this.ensureBuProcessConfigRows()
  1930. this.refreshTrackingTableLayout()
  1931. return this.processColumns
  1932. })
  1933. },
  1934. getRowProcessValue (row, processCode) {
  1935. if (!row || !processCode) {
  1936. return null
  1937. }
  1938. const processMap = row.processValueMap && typeof row.processValueMap === 'object'
  1939. ? row.processValueMap
  1940. : null
  1941. if (!processMap) {
  1942. return null
  1943. }
  1944. return processMap[processCode] || null
  1945. },
  1946. applyProcessValuesToRow (row) {
  1947. if (!row || !this.processColumns || this.processColumns.length === 0) {
  1948. return
  1949. }
  1950. this.processColumns.forEach(item => {
  1951. if (!item) {
  1952. return
  1953. }
  1954. const processValue = this.getRowProcessValue(row, item.code)
  1955. const planDate = processValue ? this.formatDate(processValue.planDate) : ''
  1956. const actualDate = processValue ? this.formatDate(processValue.actualDate) : ''
  1957. const status = processValue && processValue.status ? String(processValue.status).trim() : ''
  1958. this.$set(row, item.planField, planDate)
  1959. this.$set(row, item.actualField, actualDate)
  1960. this.$set(row, item.statusField, status)
  1961. })
  1962. },
  1963. syncDataListProcessValues () {
  1964. if (!Array.isArray(this.dataList) || this.dataList.length === 0) {
  1965. return
  1966. }
  1967. this.dataList.forEach(row => {
  1968. this.applyProcessValuesToRow(row)
  1969. })
  1970. },
  1971. updateRowProcessValue (row, processCol, status, actualDate) {
  1972. if (!row || !processCol) {
  1973. return
  1974. }
  1975. if (!row.processValueMap || typeof row.processValueMap !== 'object') {
  1976. this.$set(row, 'processValueMap', {})
  1977. }
  1978. const prevValue = this.getRowProcessValue(row, processCol.code) || {}
  1979. this.$set(row.processValueMap, processCol.code, Object.assign({}, prevValue, {
  1980. processCode: processCol.code,
  1981. status: status,
  1982. actualDate: actualDate || null
  1983. }))
  1984. this.$set(row, processCol.statusField, status)
  1985. this.$set(row, processCol.actualField, actualDate)
  1986. if (processCol.code === 'deliveryPackage') {
  1987. // Delivery&Package 工序日期与打样预计完成日期使用同一口径,前端先行回写避免用户感知延迟。
  1988. this.$set(row, 'requiredDeliveryDate', actualDate || '')
  1989. // baseline 仅在首次维护 Delivery&Package 日期时自动带入,后续允许独立维护。
  1990. if (!this.formatDate(row.baseline) && actualDate) {
  1991. this.$set(row, 'baseline', actualDate)
  1992. }
  1993. }
  1994. },
  1995. getAllProcessCodes () {
  1996. return this.processColumns.map(item => item.code)
  1997. },
  1998. mergeProcessCodeLabelMapByRows (rows) {
  1999. if (!Array.isArray(rows) || rows.length === 0) {
  2000. return
  2001. }
  2002. const nextMap = Object.assign({}, this.processCodeLabelMap || {})
  2003. const rowLabelMap = buildProcessCodeLabelMapByRows(rows)
  2004. Object.keys(rowLabelMap).forEach(code => {
  2005. nextMap[code] = rowLabelMap[code]
  2006. })
  2007. this.processCodeLabelMap = nextMap
  2008. },
  2009. mergeProcessCodeLabelMapByObject (labelMap) {
  2010. if (!labelMap || typeof labelMap !== 'object') {
  2011. return
  2012. }
  2013. const nextMap = Object.assign({}, this.processCodeLabelMap || {})
  2014. Object.keys(labelMap).forEach(rawCode => {
  2015. const code = rawCode == null ? '' : String(rawCode).trim()
  2016. const label = labelMap[rawCode] == null ? '' : String(labelMap[rawCode]).trim()
  2017. if (!code || !label) {
  2018. return
  2019. }
  2020. // 兼容后端已保存大小写不一致的编码。
  2021. nextMap[code] = label
  2022. nextMap[code.toLowerCase()] = label
  2023. })
  2024. this.processCodeLabelMap = nextMap
  2025. },
  2026. buildProcessCategoryOptionsByRows (rows) {
  2027. const sourceRows = Array.isArray(rows) ? rows : []
  2028. const nextOptions = []
  2029. const usedCodeMap = {}
  2030. sourceRows.forEach((item, index) => {
  2031. const categoryCode = item && item.categoryCode != null ? String(item.categoryCode).trim() : ''
  2032. const categoryName = item && item.categoryName != null ? String(item.categoryName).trim() : ''
  2033. if (!categoryCode || !categoryName) {
  2034. return
  2035. }
  2036. const codeKey = categoryCode.toLowerCase()
  2037. if (usedCodeMap[codeKey]) {
  2038. return
  2039. }
  2040. usedCodeMap[codeKey] = true
  2041. const parsedSortNo = parseInt(item && item.sortNo, 10)
  2042. nextOptions.push({
  2043. categoryCode: categoryCode,
  2044. categoryName: categoryName,
  2045. sortNo: Number.isNaN(parsedSortNo) ? (index + 1) * 10 : parsedSortNo
  2046. })
  2047. })
  2048. nextOptions.sort((a, b) => {
  2049. if (a.sortNo !== b.sortNo) {
  2050. return a.sortNo - b.sortNo
  2051. }
  2052. return String(a.categoryCode).localeCompare(String(b.categoryCode))
  2053. })
  2054. return nextOptions
  2055. },
  2056. getProcessCategoryOptionsByBuNo (buNo) {
  2057. const normalizedBuNo = this.normalizeBuNo(buNo, this.$store.state.user.site)
  2058. if (!normalizedBuNo) {
  2059. return []
  2060. }
  2061. const optionMap = this.buProcessCategoryOptionsMap || {}
  2062. return Array.isArray(optionMap[normalizedBuNo]) ? optionMap[normalizedBuNo] : []
  2063. },
  2064. getBuProcessCategoryNameByCode (buNo, categoryCode) {
  2065. if (!categoryCode) {
  2066. return ''
  2067. }
  2068. const normalizedCategoryCode = String(categoryCode).trim()
  2069. if (!normalizedCategoryCode) {
  2070. return ''
  2071. }
  2072. const lowerCategoryCode = normalizedCategoryCode.toLowerCase()
  2073. const configEntry = this.getBuProcessConfigEntry(buNo, this.$store.state.user.site)
  2074. const categoryNameMap = configEntry && configEntry.config && configEntry.config.processCategoryNameMap
  2075. ? configEntry.config.processCategoryNameMap
  2076. : {}
  2077. if (categoryNameMap && typeof categoryNameMap === 'object') {
  2078. if (categoryNameMap[normalizedCategoryCode]) {
  2079. return categoryNameMap[normalizedCategoryCode]
  2080. }
  2081. if (categoryNameMap[lowerCategoryCode]) {
  2082. return categoryNameMap[lowerCategoryCode]
  2083. }
  2084. const matchedNameCode = Object.keys(categoryNameMap).find(code => {
  2085. return String(code).trim().toLowerCase() === lowerCategoryCode
  2086. })
  2087. if (matchedNameCode) {
  2088. return categoryNameMap[matchedNameCode] || ''
  2089. }
  2090. }
  2091. const options = this.getProcessCategoryOptionsByBuNo(buNo)
  2092. const matched = options.find(item => {
  2093. if (!item || !item.categoryCode) {
  2094. return false
  2095. }
  2096. return String(item.categoryCode).trim().toLowerCase() === lowerCategoryCode
  2097. })
  2098. return matched && matched.categoryName ? matched.categoryName : ''
  2099. },
  2100. getBuProcessCategoryCodeByProcessCode (buNo, processCode) {
  2101. const configEntry = this.getBuProcessConfigEntry(buNo, this.$store.state.user.site)
  2102. const config = configEntry ? configEntry.config : null
  2103. if (!config || !config.processCategoryMap || typeof config.processCategoryMap !== 'object' || !processCode) {
  2104. return ''
  2105. }
  2106. const sourceMap = config.processCategoryMap
  2107. const normalizedProcessCode = String(processCode).trim()
  2108. if (!normalizedProcessCode) {
  2109. return ''
  2110. }
  2111. if (sourceMap[normalizedProcessCode] != null) {
  2112. return String(sourceMap[normalizedProcessCode]).trim()
  2113. }
  2114. const lowerProcessCode = normalizedProcessCode.toLowerCase()
  2115. const matchedKey = Object.keys(sourceMap).find(code => {
  2116. return String(code).trim().toLowerCase() === lowerProcessCode
  2117. })
  2118. return matchedKey ? String(sourceMap[matchedKey] || '').trim() : ''
  2119. },
  2120. getBuProcessCategoryCode (row, processCode) {
  2121. if (!row || !processCode) {
  2122. return ''
  2123. }
  2124. const map = row.processCategoryMap && typeof row.processCategoryMap === 'object'
  2125. ? row.processCategoryMap
  2126. : {}
  2127. const directValue = map[processCode]
  2128. if (directValue != null) {
  2129. return String(directValue).trim()
  2130. }
  2131. const normalizedCode = String(processCode).trim().toLowerCase()
  2132. const matchedKey = Object.keys(map).find(code => String(code).trim().toLowerCase() === normalizedCode)
  2133. if (!matchedKey) {
  2134. return ''
  2135. }
  2136. const matchedValue = map[matchedKey]
  2137. return matchedValue == null ? '' : String(matchedValue).trim()
  2138. },
  2139. normalizeProcessCategoryMapForRow (row) {
  2140. const resultMap = {}
  2141. if (!row) {
  2142. return resultMap
  2143. }
  2144. const validOrderCodes = this.getValidProcessOrderCodes(row.processOrderCodes)
  2145. validOrderCodes.forEach(code => {
  2146. const categoryCode = this.getBuProcessCategoryCode(row, code)
  2147. resultMap[code] = categoryCode || ''
  2148. })
  2149. return resultMap
  2150. },
  2151. handleBuProcessCategoryChange (row, processCode, categoryCode) {
  2152. if (!row || !processCode) {
  2153. return
  2154. }
  2155. const normalizedCode = String(processCode).trim()
  2156. if (!normalizedCode) {
  2157. return
  2158. }
  2159. if (!row.processCategoryMap || typeof row.processCategoryMap !== 'object') {
  2160. this.$set(row, 'processCategoryMap', {})
  2161. }
  2162. const categoryCodeVal = categoryCode == null ? '' : String(categoryCode).trim()
  2163. this.$set(row.processCategoryMap, normalizedCode, categoryCodeVal)
  2164. },
  2165. loadBuProcessCategoryOptionsByBuNo (buNo, silent = true) {
  2166. const normalizedBuNo = this.normalizeBuNo(buNo, this.$store.state.user.site)
  2167. if (!normalizedBuNo) {
  2168. return Promise.resolve([])
  2169. }
  2170. const inData = {
  2171. site: this.$store.state.user.site,
  2172. buNo: normalizedBuNo,
  2173. userName: this.$store.state.user.name
  2174. }
  2175. return queryProofTrackingProcessCategories(inData).then(({ data }) => {
  2176. if (!(data && data.code === 0)) {
  2177. if (!silent) {
  2178. this.$message.error((data && data.msg) || `加载事业部【${normalizedBuNo}】工序分类失败`)
  2179. }
  2180. this.$set(this.buProcessCategoryOptionsMap, normalizedBuNo, [])
  2181. if (this.buProcessConfigMap && this.buProcessConfigMap[normalizedBuNo]) {
  2182. this.$set(this.buProcessConfigMap, normalizedBuNo, Object.assign({}, this.buProcessConfigMap[normalizedBuNo], {
  2183. processCategoryNameMap: {}
  2184. }))
  2185. }
  2186. return []
  2187. }
  2188. const options = this.buildProcessCategoryOptionsByRows(data.rows || [])
  2189. this.$set(this.buProcessCategoryOptionsMap, normalizedBuNo, options)
  2190. if (this.buProcessConfigMap && this.buProcessConfigMap[normalizedBuNo]) {
  2191. const processCategoryNameMap = {}
  2192. options.forEach(item => {
  2193. const categoryCode = item && item.categoryCode ? String(item.categoryCode).trim() : ''
  2194. const categoryName = item && item.categoryName ? String(item.categoryName).trim() : ''
  2195. if (!categoryCode || !categoryName) {
  2196. return
  2197. }
  2198. processCategoryNameMap[categoryCode] = categoryName
  2199. processCategoryNameMap[categoryCode.toLowerCase()] = categoryName
  2200. })
  2201. this.$set(this.buProcessConfigMap, normalizedBuNo, Object.assign({}, this.buProcessConfigMap[normalizedBuNo], {
  2202. processCategoryNameMap: processCategoryNameMap
  2203. }))
  2204. }
  2205. return options
  2206. }).catch(() => {
  2207. if (!silent) {
  2208. this.$message.error(`加载事业部【${normalizedBuNo}】工序分类异常`)
  2209. }
  2210. this.$set(this.buProcessCategoryOptionsMap, normalizedBuNo, [])
  2211. if (this.buProcessConfigMap && this.buProcessConfigMap[normalizedBuNo]) {
  2212. this.$set(this.buProcessConfigMap, normalizedBuNo, Object.assign({}, this.buProcessConfigMap[normalizedBuNo], {
  2213. processCategoryNameMap: {}
  2214. }))
  2215. }
  2216. return []
  2217. })
  2218. },
  2219. loadAllBuProcessCategoryOptions (sourceRows, silent = true) {
  2220. const rows = Array.isArray(sourceRows) ? sourceRows : []
  2221. const requestBuNos = []
  2222. const usedBuMap = {}
  2223. rows.forEach(item => {
  2224. const buNo = this.normalizeBuNo(item && item.buNo ? item.buNo : '', this.$store.state.user.site)
  2225. if (!buNo || usedBuMap[buNo]) {
  2226. return
  2227. }
  2228. usedBuMap[buNo] = true
  2229. requestBuNos.push(buNo)
  2230. })
  2231. if (requestBuNos.length === 0) {
  2232. return Promise.resolve([])
  2233. }
  2234. return Promise.all(requestBuNos.map(buNo => this.loadBuProcessCategoryOptionsByBuNo(buNo, silent)))
  2235. },
  2236. getProcessLabelByCode (processCode) {
  2237. if (!processCode) {
  2238. return ''
  2239. }
  2240. const normalizedCode = String(processCode).trim()
  2241. const lowerCode = normalizedCode.toLowerCase()
  2242. const matched = this.processColumns.find(item => item && item.code === normalizedCode)
  2243. if (matched && matched.label) {
  2244. return matched.label
  2245. }
  2246. const caseInsensitiveMatched = this.processColumns.find(item => {
  2247. if (!item || !item.code) {
  2248. return false
  2249. }
  2250. return String(item.code).trim().toLowerCase() === lowerCode
  2251. })
  2252. if (caseInsensitiveMatched && caseInsensitiveMatched.label) {
  2253. return caseInsensitiveMatched.label
  2254. }
  2255. const codeLabelMap = this.processCodeLabelMap || {}
  2256. if (codeLabelMap[normalizedCode]) {
  2257. return codeLabelMap[normalizedCode]
  2258. }
  2259. if (codeLabelMap[lowerCode]) {
  2260. return codeLabelMap[lowerCode]
  2261. }
  2262. const defaultMatched = DEFAULT_PROCESS_COLUMNS.find(item => {
  2263. if (!item || !item.code) {
  2264. return false
  2265. }
  2266. return item.code.toLowerCase() === lowerCode
  2267. })
  2268. return defaultMatched && defaultMatched.label ? defaultMatched.label : normalizedCode
  2269. },
  2270. getValidProcessOrderCodes (codes) {
  2271. // 顺序列表不能因为“当前 BU 不可见”而丢项:先保留后端返回顺序,再补齐当前已知工序。
  2272. const orderedCodes = []
  2273. if (Array.isArray(codes)) {
  2274. codes.forEach(code => {
  2275. const codeVal = code == null ? '' : String(code).trim()
  2276. if (!codeVal || orderedCodes.indexOf(codeVal) > -1) {
  2277. return
  2278. }
  2279. orderedCodes.push(codeVal)
  2280. })
  2281. }
  2282. const allCodes = this.getAllProcessCodes()
  2283. allCodes.forEach(code => {
  2284. if (orderedCodes.indexOf(code) === -1) {
  2285. orderedCodes.push(code)
  2286. }
  2287. })
  2288. return orderedCodes
  2289. },
  2290. normalizeBuNo (buNo, site) {
  2291. const rawBuNo = buNo == null ? '' : String(buNo).trim()
  2292. if (!rawBuNo) {
  2293. return ''
  2294. }
  2295. const splitIdx = rawBuNo.indexOf('_')
  2296. if (splitIdx < 1 || splitIdx >= rawBuNo.length - 1) {
  2297. return rawBuNo
  2298. }
  2299. // BU统一按“去掉site前缀”展示与保存,例如 2_01-Label -> 01-Label。
  2300. return rawBuNo.substring(splitIdx + 1)
  2301. },
  2302. isRfidBuNo (buNo) {
  2303. const normalizedBuNo = this.normalizeBuNo(buNo, this.$store.state.user.site)
  2304. return normalizedBuNo.toUpperCase() === '03-RFID'
  2305. },
  2306. getValidProcessCodes (codes, allowedCodes) {
  2307. if (!Array.isArray(codes) || codes.length === 0) {
  2308. return []
  2309. }
  2310. const allCodes = Array.isArray(allowedCodes) && allowedCodes.length > 0
  2311. ? allowedCodes
  2312. .map(code => (code == null ? '' : String(code).trim()))
  2313. .filter(code => !!code)
  2314. : this.getAllProcessCodes()
  2315. const normalizedCodes = []
  2316. codes.forEach(code => {
  2317. const codeVal = code == null ? '' : String(code).trim()
  2318. if (!codeVal || allCodes.indexOf(codeVal) === -1) {
  2319. return
  2320. }
  2321. if (normalizedCodes.indexOf(codeVal) === -1) {
  2322. normalizedCodes.push(codeVal)
  2323. }
  2324. })
  2325. return normalizedCodes
  2326. },
  2327. getBuProcessConfigEntry (buNo, site) {
  2328. const normalizedBuNo = this.normalizeBuNo(buNo, site)
  2329. if (!normalizedBuNo) {
  2330. return null
  2331. }
  2332. const config = this.buProcessConfigMap[normalizedBuNo]
  2333. if (config) {
  2334. return { key: normalizedBuNo, config: config }
  2335. }
  2336. return null
  2337. },
  2338. getSelectedSearchBuNo () {
  2339. return this.normalizeBuNo(this.searchData.buNo, this.searchData.site)
  2340. },
  2341. handleSearchBuChange () {
  2342. this.ensureSearchBuNoSelected()
  2343. // BU筛选变化后立即重新查询,并回到第一页。
  2344. this.queryProcessColumns().then(() => {
  2345. this.getDataList('Y')
  2346. })
  2347. },
  2348. getProcessVisibleCodesByBuNo (buNo, site) {
  2349. const configEntry = this.getBuProcessConfigEntry(buNo, site)
  2350. const config = configEntry ? configEntry.config : null
  2351. // 没有配置或配置为全部显示时,返回 null 表示该 BU 工序列不过滤。
  2352. if (!config || config.showAll !== false) {
  2353. return null
  2354. }
  2355. const validCodes = this.getValidProcessCodes(config.processCodes)
  2356. if (validCodes.length === 0 || validCodes.length >= this.processColumns.length) {
  2357. return null
  2358. }
  2359. return validCodes
  2360. },
  2361. getProcessColumnWidth (item) {
  2362. const rows = this.dataList.filter(row =>
  2363. this.isProcessColumnVisibleForRow(row, item)
  2364. )
  2365. // 没有数据
  2366. if (rows.length === 0) {
  2367. return 110
  2368. }
  2369. // 有任何一个未完成
  2370. if (rows.some(row => !this.isProcessStatusComplete(row, item))) {
  2371. return 134
  2372. }
  2373. // 全部完成
  2374. return 110
  2375. },
  2376. isProcessColumnVisibleForRow (row, processCol) {
  2377. if (!row || !processCol) {
  2378. return true
  2379. }
  2380. // 已指定查询BU时,列集合已经按查询BU过滤,不再按行二次隐藏。
  2381. if (this.getSelectedSearchBuNo()) {
  2382. return true
  2383. }
  2384. const visibleCodes = this.getProcessVisibleCodesByBuNo(row.buNo, row.site)
  2385. if (!visibleCodes) {
  2386. return true
  2387. }
  2388. return visibleCodes.indexOf(processCol.code) > -1
  2389. },
  2390. queryBuProcessConfig () {
  2391. const inData = {
  2392. site: this.$store.state.user.site,
  2393. userName: this.$store.state.user.name
  2394. }
  2395. return queryProofTrackingBuProcessConfig(inData).then(({ data }) => {
  2396. if (!(data && data.code === 0)) {
  2397. this.$message.error((data && data.msg) || '加载事业部进度展示配置失败')
  2398. return
  2399. }
  2400. const rows = data.rows || []
  2401. const nextMap = {}
  2402. const processLabelMap = {}
  2403. rows.forEach(item => {
  2404. if (item && item.processLabelMap && typeof item.processLabelMap === 'object') {
  2405. Object.keys(item.processLabelMap).forEach(rawCode => {
  2406. const code = rawCode == null ? '' : String(rawCode).trim()
  2407. const label = item.processLabelMap[rawCode] == null ? '' : String(item.processLabelMap[rawCode]).trim()
  2408. if (!code || !label) {
  2409. return
  2410. }
  2411. processLabelMap[code] = label
  2412. })
  2413. }
  2414. const buNo = this.normalizeBuNo(item && item.buNo ? item.buNo : '', this.$store.state.user.site)
  2415. if (!buNo) {
  2416. return
  2417. }
  2418. const processOrderCodes = this.getValidProcessOrderCodes(item.processOrderCodes)
  2419. const processCodes = this.getValidProcessCodes(item.processCodes, processOrderCodes)
  2420. const showAll = item && item.showAll === true
  2421. ? true
  2422. : processCodes.length >= processOrderCodes.length
  2423. const selectedCodeMap = {}
  2424. processCodes.forEach(code => {
  2425. selectedCodeMap[code] = true
  2426. })
  2427. const processCategoryMap = {}
  2428. const sourceCategoryMap = item && item.processCategoryMap && typeof item.processCategoryMap === 'object'
  2429. ? item.processCategoryMap
  2430. : {}
  2431. const processCategoryNameMap = {}
  2432. const sourceCategoryNameMap = item && item.processCategoryNameMap && typeof item.processCategoryNameMap === 'object'
  2433. ? item.processCategoryNameMap
  2434. : {}
  2435. Object.keys(sourceCategoryNameMap).forEach(rawCategoryCode => {
  2436. const categoryCode = rawCategoryCode == null ? '' : String(rawCategoryCode).trim()
  2437. const categoryName = sourceCategoryNameMap[rawCategoryCode] == null
  2438. ? ''
  2439. : String(sourceCategoryNameMap[rawCategoryCode]).trim()
  2440. if (!categoryCode || !categoryName) {
  2441. return
  2442. }
  2443. processCategoryNameMap[categoryCode] = categoryName
  2444. processCategoryNameMap[categoryCode.toLowerCase()] = categoryName
  2445. })
  2446. processOrderCodes.forEach(code => {
  2447. if (!code) {
  2448. return
  2449. }
  2450. const directVal = sourceCategoryMap[code]
  2451. if (directVal != null) {
  2452. processCategoryMap[code] = String(directVal).trim()
  2453. return
  2454. }
  2455. const lowerCode = String(code).trim().toLowerCase()
  2456. const matchedKey = Object.keys(sourceCategoryMap).find(rawCode => {
  2457. return String(rawCode).trim().toLowerCase() === lowerCode
  2458. })
  2459. processCategoryMap[code] = matchedKey ? String(sourceCategoryMap[matchedKey] || '').trim() : ''
  2460. })
  2461. nextMap[buNo] = {
  2462. showAll: showAll,
  2463. processOrderCodes: processOrderCodes,
  2464. processCodes: showAll ? processOrderCodes.slice() : processOrderCodes.filter(code => selectedCodeMap[code]),
  2465. processCategoryMap: processCategoryMap,
  2466. processCategoryNameMap: processCategoryNameMap
  2467. }
  2468. })
  2469. this.mergeProcessCodeLabelMapByObject(processLabelMap)
  2470. this.buProcessConfigMap = nextMap
  2471. this.ensureBuProcessConfigRows()
  2472. this.loadAllBuProcessCategoryOptions(this.buProcessConfigRows, true)
  2473. this.refreshTrackingTableLayout()
  2474. }).catch(() => {
  2475. this.$message.error('加载事业部进度展示配置异常')
  2476. })
  2477. },
  2478. ensureBuProcessConfigRows () {
  2479. const allCodes = this.getAllProcessCodes()
  2480. const builtinCodeMap = {}
  2481. DEFAULT_PROCESS_COLUMNS.forEach(item => {
  2482. if (item && item.code) {
  2483. builtinCodeMap[item.code] = true
  2484. }
  2485. })
  2486. const defaultSelectedCodes = allCodes.filter(code => !!builtinCodeMap[code])
  2487. const rows = []
  2488. const usedBuMap = {}
  2489. const appendRow = (buNo, buDesc, site) => {
  2490. const key = this.normalizeBuNo(buNo, site || this.$store.state.user.site)
  2491. if (!key || usedBuMap[key]) {
  2492. return
  2493. }
  2494. usedBuMap[key] = true
  2495. const cfg = this.buProcessConfigMap[key]
  2496. const row = {
  2497. buNo: key,
  2498. buDesc: buDesc || key,
  2499. processOrderCodes: allCodes.slice(),
  2500. // 新增默认工序默认不勾选:仅预勾选系统内置工序,自定义新增项需事业部手工勾选。
  2501. processCodes: (defaultSelectedCodes.length > 0 ? defaultSelectedCodes : allCodes).slice(),
  2502. processCategoryMap: {}
  2503. }
  2504. if (cfg) {
  2505. const validOrderCodes = this.getValidProcessOrderCodes(cfg.processOrderCodes)
  2506. row.processOrderCodes = validOrderCodes
  2507. const validCodes = this.getValidProcessCodes(cfg.processCodes, validOrderCodes)
  2508. if (validCodes.length > 0) {
  2509. const selectedCodeMap = {}
  2510. validCodes.forEach(code => {
  2511. selectedCodeMap[code] = true
  2512. })
  2513. row.processCodes = validOrderCodes.filter(code => selectedCodeMap[code])
  2514. } else {
  2515. const defaultSelectedCodeMap = {}
  2516. row.processCodes.forEach(code => {
  2517. defaultSelectedCodeMap[code] = true
  2518. })
  2519. row.processCodes = validOrderCodes.filter(code => defaultSelectedCodeMap[code])
  2520. }
  2521. row.processCategoryMap = cfg.processCategoryMap && typeof cfg.processCategoryMap === 'object'
  2522. ? Object.assign({}, cfg.processCategoryMap)
  2523. : {}
  2524. }
  2525. row.processCategoryMap = this.normalizeProcessCategoryMapForRow(row)
  2526. rows.push(row)
  2527. }
  2528. this.userBuList.forEach(item => {
  2529. if (!item || !item.buNo) {
  2530. return
  2531. }
  2532. appendRow(item.buNo, item.buDesc || this.normalizeBuNo(item.buNo, this.$store.state.user.site), this.$store.state.user.site)
  2533. })
  2534. Object.keys(this.buProcessConfigMap).forEach(buNo => {
  2535. appendRow(buNo, buNo, this.$store.state.user.site)
  2536. })
  2537. this.buProcessConfigRows = rows
  2538. },
  2539. openBuProcessConfigDialog () {
  2540. this.ensureBuProcessConfigRows()
  2541. this.processOrderDragState = {
  2542. buNo: '',
  2543. fromIndex: -1
  2544. }
  2545. this.buProcessConfigDialogVisible = true
  2546. this.loadAllBuProcessCategoryOptions(this.buProcessConfigRows, false)
  2547. },
  2548. buildDefaultProcessConfigRow (item) {
  2549. const parsedSortNo = parseInt(item && item.sortNo, 10)
  2550. const activeFlag = item && item.activeFlag ? String(item.activeFlag).trim().toUpperCase() : 'Y'
  2551. const code = item && item.code ? String(item.code).trim() : ''
  2552. return {
  2553. _rowKey: `default_process_${Date.now()}_${this.defaultProcessConfigRowSeed++}`,
  2554. // 记录后端原始编码,删除时可避免“修改了编码但尚未保存”导致删不到原记录。
  2555. _persistedCode: code,
  2556. sortNo: Number.isNaN(parsedSortNo) || parsedSortNo <= 0 ? this.getNextDefaultProcessSortNo() : parsedSortNo,
  2557. code: code,
  2558. label: item && item.label ? String(item.label).trim() : '',
  2559. activeFlag: activeFlag === 'N' ? 'N' : 'Y'
  2560. }
  2561. },
  2562. getNextDefaultProcessSortNo () {
  2563. if (!Array.isArray(this.defaultProcessConfigRows) || this.defaultProcessConfigRows.length === 0) {
  2564. return 10
  2565. }
  2566. let maxSortNo = 0
  2567. this.defaultProcessConfigRows.forEach(row => {
  2568. const parsedSortNo = parseInt(row && row.sortNo, 10)
  2569. if (!Number.isNaN(parsedSortNo) && parsedSortNo > maxSortNo) {
  2570. maxSortNo = parsedSortNo
  2571. }
  2572. })
  2573. return maxSortNo > 0 ? maxSortNo + 10 : (this.defaultProcessConfigRows.length + 1) * 10
  2574. },
  2575. resetDefaultProcessConfigSortNoByOrder () {
  2576. if (!Array.isArray(this.defaultProcessConfigRows)) {
  2577. return
  2578. }
  2579. this.defaultProcessConfigRows.forEach((row, index) => {
  2580. if (!row) {
  2581. return
  2582. }
  2583. this.$set(row, 'sortNo', (index + 1) * 10)
  2584. })
  2585. },
  2586. clearDefaultProcessConfigSelection () {
  2587. this.defaultProcessConfigSelectionRows = []
  2588. this.$nextTick(() => {
  2589. const tableRef = this.$refs.defaultProcessConfigTable
  2590. if (tableRef && tableRef.clearSelection) {
  2591. tableRef.clearSelection()
  2592. }
  2593. })
  2594. },
  2595. destroyDefaultProcessConfigSortable () {
  2596. if (this.defaultProcessConfigSortable && typeof this.defaultProcessConfigSortable.destroy === 'function') {
  2597. this.defaultProcessConfigSortable.destroy()
  2598. }
  2599. this.defaultProcessConfigSortable = null
  2600. },
  2601. initDefaultProcessConfigSortable () {
  2602. this.$nextTick(() => {
  2603. this.destroyDefaultProcessConfigSortable()
  2604. if (!this.defaultProcessConfigDialogVisible || !Array.isArray(this.defaultProcessConfigRows) || this.defaultProcessConfigRows.length === 0) {
  2605. return
  2606. }
  2607. const tableRef = this.$refs.defaultProcessConfigTable
  2608. const tableBody = tableRef && tableRef.$el
  2609. ? tableRef.$el.querySelector('.el-table__body-wrapper tbody')
  2610. : null
  2611. if (!tableBody) {
  2612. return
  2613. }
  2614. this.defaultProcessConfigSortable = Sortable.create(tableBody, {
  2615. animation: 150,
  2616. // 整行可拖拽,避免必须精确悬浮到小图标才能排序。
  2617. handle: 'tr',
  2618. draggable: 'tr',
  2619. ghostClass: 'default-process-sortable-ghost',
  2620. chosenClass: 'default-process-sortable-chosen',
  2621. dragClass: 'default-process-sortable-drag',
  2622. onEnd: (evt) => {
  2623. const oldIndex = evt && typeof evt.oldIndex === 'number' ? evt.oldIndex : -1
  2624. const newIndex = evt && typeof evt.newIndex === 'number' ? evt.newIndex : -1
  2625. if (oldIndex < 0 || newIndex < 0 || oldIndex === newIndex) {
  2626. return
  2627. }
  2628. const movedRow = this.defaultProcessConfigRows.splice(oldIndex, 1)[0]
  2629. this.defaultProcessConfigRows.splice(newIndex, 0, movedRow)
  2630. this.resetDefaultProcessConfigSortNoByOrder()
  2631. this.clearDefaultProcessConfigSelection()
  2632. }
  2633. })
  2634. })
  2635. },
  2636. handleDefaultProcessConfigDialogClosed () {
  2637. this.defaultProcessConfigDeleteLoading = false
  2638. this.clearDefaultProcessConfigSelection()
  2639. this.destroyDefaultProcessConfigSortable()
  2640. },
  2641. handleDefaultProcessConfigSelectionChange (rows) {
  2642. this.defaultProcessConfigSelectionRows = Array.isArray(rows) ? rows : []
  2643. },
  2644. normalizeDefaultProcessFieldBaseByCode (code) {
  2645. const rawCode = code == null ? '' : String(code).trim()
  2646. if (!rawCode) {
  2647. return ''
  2648. }
  2649. if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(rawCode)) {
  2650. return rawCode
  2651. }
  2652. const parts = rawCode.split(/[^A-Za-z0-9]+/).filter(item => !!item)
  2653. if (parts.length === 0) {
  2654. return ''
  2655. }
  2656. const firstPart = parts[0].toLowerCase()
  2657. const remained = parts.slice(1).map(item => {
  2658. const lower = item.toLowerCase()
  2659. return `${lower.charAt(0).toUpperCase()}${lower.substring(1)}`
  2660. }).join('')
  2661. let fieldBase = `${firstPart}${remained}`
  2662. if (/^[0-9]/.test(fieldBase)) {
  2663. fieldBase = `p${fieldBase}`
  2664. }
  2665. return fieldBase
  2666. },
  2667. buildDefaultProcessFieldsByCode (code) {
  2668. const fieldBase = this.normalizeDefaultProcessFieldBaseByCode(code)
  2669. if (!fieldBase) {
  2670. return {
  2671. planField: '',
  2672. actualField: '',
  2673. statusField: ''
  2674. }
  2675. }
  2676. return {
  2677. planField: `${fieldBase}PlanDate`,
  2678. actualField: `${fieldBase}ActualDate`,
  2679. statusField: `${fieldBase}Status`
  2680. }
  2681. },
  2682. queryDefaultProcessConfig () {
  2683. const inData = {
  2684. site: '*',
  2685. buNo: '*',
  2686. userName: this.$store.state.user.name
  2687. }
  2688. return queryProofTrackingProcessColumnsManage(inData).then(({ data }) => {
  2689. if (!(data && data.code === 0)) {
  2690. this.$message.error((data && data.msg) || '加载默认工序配置失败')
  2691. this.defaultProcessConfigRows = []
  2692. this.defaultProcessConfigSelectionRows = []
  2693. return []
  2694. }
  2695. const rows = Array.isArray(data.rows) ? data.rows : []
  2696. const sortedRows = rows
  2697. .map((item, index) => {
  2698. const parsedSortNo = parseInt(item && item.sortNo, 10)
  2699. return {
  2700. sourceIndex: index,
  2701. sortNo: Number.isNaN(parsedSortNo) ? Number.MAX_SAFE_INTEGER : parsedSortNo,
  2702. item: item
  2703. }
  2704. })
  2705. .sort((a, b) => {
  2706. if (a.sortNo !== b.sortNo) {
  2707. return a.sortNo - b.sortNo
  2708. }
  2709. return a.sourceIndex - b.sourceIndex
  2710. })
  2711. this.defaultProcessConfigRows = sortedRows.map(({ item }) => this.buildDefaultProcessConfigRow(item))
  2712. this.resetDefaultProcessConfigSortNoByOrder()
  2713. this.clearDefaultProcessConfigSelection()
  2714. this.initDefaultProcessConfigSortable()
  2715. return this.defaultProcessConfigRows
  2716. }).catch(() => {
  2717. this.$message.error('加载默认工序配置异常')
  2718. this.defaultProcessConfigRows = []
  2719. this.defaultProcessConfigSelectionRows = []
  2720. this.destroyDefaultProcessConfigSortable()
  2721. return []
  2722. })
  2723. },
  2724. openDefaultProcessConfigDialog () {
  2725. this.defaultProcessConfigDeleteLoading = false
  2726. this.defaultProcessConfigDialogVisible = true
  2727. this.queryDefaultProcessConfig()
  2728. },
  2729. buildProcessCategoryRow (item) {
  2730. const parsedSortNo = parseInt(item && item.sortNo, 10)
  2731. const categoryCode = item && item.categoryCode ? String(item.categoryCode).trim() : ''
  2732. return {
  2733. _rowKey: `process_category_${Date.now()}_${this.processCategoryRowSeed++}`,
  2734. _persistedCode: categoryCode,
  2735. sortNo: Number.isNaN(parsedSortNo) || parsedSortNo <= 0 ? (this.processCategoryRows.length + 1) * 10 : parsedSortNo,
  2736. categoryCode: categoryCode,
  2737. categoryName: item && item.categoryName ? String(item.categoryName).trim() : ''
  2738. }
  2739. },
  2740. queryProcessCategoryConfig (buNo, silent = false) {
  2741. const normalizedBuNo = this.normalizeBuNo(buNo, this.$store.state.user.site)
  2742. if (!normalizedBuNo) {
  2743. return Promise.resolve([])
  2744. }
  2745. const inData = {
  2746. site: this.$store.state.user.site,
  2747. buNo: normalizedBuNo,
  2748. userName: this.$store.state.user.name
  2749. }
  2750. return queryProofTrackingProcessCategories(inData).then(({ data }) => {
  2751. if (!(data && data.code === 0)) {
  2752. if (!silent) {
  2753. this.$message.error((data && data.msg) || '加载工序分类失败')
  2754. }
  2755. this.processCategoryRows = []
  2756. this.processCategorySelectionRows = []
  2757. this.$set(this.buProcessCategoryOptionsMap, normalizedBuNo, [])
  2758. if (this.buProcessConfigMap && this.buProcessConfigMap[normalizedBuNo]) {
  2759. this.$set(this.buProcessConfigMap, normalizedBuNo, Object.assign({}, this.buProcessConfigMap[normalizedBuNo], {
  2760. processCategoryNameMap: {}
  2761. }))
  2762. }
  2763. return []
  2764. }
  2765. const rows = Array.isArray(data.rows) ? data.rows : []
  2766. const categoryOptions = this.buildProcessCategoryOptionsByRows(rows)
  2767. this.$set(this.buProcessCategoryOptionsMap, normalizedBuNo, categoryOptions)
  2768. if (this.buProcessConfigMap && this.buProcessConfigMap[normalizedBuNo]) {
  2769. const processCategoryNameMap = {}
  2770. categoryOptions.forEach(item => {
  2771. const categoryCode = item && item.categoryCode ? String(item.categoryCode).trim() : ''
  2772. const categoryName = item && item.categoryName ? String(item.categoryName).trim() : ''
  2773. if (!categoryCode || !categoryName) {
  2774. return
  2775. }
  2776. processCategoryNameMap[categoryCode] = categoryName
  2777. processCategoryNameMap[categoryCode.toLowerCase()] = categoryName
  2778. })
  2779. this.$set(this.buProcessConfigMap, normalizedBuNo, Object.assign({}, this.buProcessConfigMap[normalizedBuNo], {
  2780. processCategoryNameMap: processCategoryNameMap
  2781. }))
  2782. }
  2783. const sortedRows = rows
  2784. .map((item, index) => {
  2785. const parsedSortNo = parseInt(item && item.sortNo, 10)
  2786. return {
  2787. sourceIndex: index,
  2788. sortNo: Number.isNaN(parsedSortNo) ? Number.MAX_SAFE_INTEGER : parsedSortNo,
  2789. item: item
  2790. }
  2791. })
  2792. .sort((a, b) => {
  2793. if (a.sortNo !== b.sortNo) {
  2794. return a.sortNo - b.sortNo
  2795. }
  2796. return a.sourceIndex - b.sourceIndex
  2797. })
  2798. this.processCategoryRows = sortedRows.map(({ item }) => this.buildProcessCategoryRow(item))
  2799. this.processCategorySelectionRows = []
  2800. return this.processCategoryRows
  2801. }).catch(() => {
  2802. if (!silent) {
  2803. this.$message.error('加载工序分类异常')
  2804. }
  2805. this.processCategoryRows = []
  2806. this.processCategorySelectionRows = []
  2807. this.$set(this.buProcessCategoryOptionsMap, normalizedBuNo, [])
  2808. if (this.buProcessConfigMap && this.buProcessConfigMap[normalizedBuNo]) {
  2809. this.$set(this.buProcessConfigMap, normalizedBuNo, Object.assign({}, this.buProcessConfigMap[normalizedBuNo], {
  2810. processCategoryNameMap: {}
  2811. }))
  2812. }
  2813. return []
  2814. })
  2815. },
  2816. openProcessCategoryDialog (row) {
  2817. const buNo = this.normalizeBuNo(row && row.buNo ? row.buNo : '', this.$store.state.user.site)
  2818. if (!buNo) {
  2819. this.$message.warning('请在事业部进度设置中指定事业部后再维护分类')
  2820. return
  2821. }
  2822. this.processCategoryDialogBuNo = buNo
  2823. this.processCategoryDialogBuDesc = row && row.buDesc ? row.buDesc : buNo
  2824. this.processCategoryDialogVisible = true
  2825. this.processCategorySaveLoading = false
  2826. this.queryProcessCategoryConfig(buNo)
  2827. },
  2828. handleProcessCategoryDialogClosed () {
  2829. this.processCategoryRows = []
  2830. this.processCategorySelectionRows = []
  2831. this.processCategoryDialogBuNo = ''
  2832. this.processCategoryDialogBuDesc = ''
  2833. this.processCategorySaveLoading = false
  2834. },
  2835. handleProcessCategorySelectionChange (rows) {
  2836. this.processCategorySelectionRows = Array.isArray(rows) ? rows : []
  2837. },
  2838. addProcessCategoryRow () {
  2839. this.processCategoryRows.push(this.buildProcessCategoryRow({
  2840. sortNo: (this.processCategoryRows.length + 1) * 10
  2841. }))
  2842. },
  2843. deleteProcessCategoryRows () {
  2844. if (!Array.isArray(this.processCategorySelectionRows) || this.processCategorySelectionRows.length === 0) {
  2845. this.$message.warning('请先勾选要删除的分类')
  2846. return
  2847. }
  2848. const selectedRows = this.processCategorySelectionRows.slice()
  2849. this.$confirm(`确认删除选中的 ${selectedRows.length} 条分类吗?`, '提示', {
  2850. confirmButtonText: '确定',
  2851. cancelButtonText: '取消',
  2852. type: 'warning'
  2853. }).then(() => {
  2854. const selectedKeyMap = {}
  2855. selectedRows.forEach(item => {
  2856. if (item && item._rowKey) {
  2857. selectedKeyMap[item._rowKey] = true
  2858. }
  2859. })
  2860. const nextRows = this.processCategoryRows.filter(item => !(item && selectedKeyMap[item._rowKey]))
  2861. const submitRows = this.normalizeProcessCategoryRowsForSave(nextRows)
  2862. if (!submitRows) {
  2863. return
  2864. }
  2865. const inData = {
  2866. site: this.$store.state.user.site,
  2867. buNo: this.processCategoryDialogBuNo,
  2868. updateBy: this.$store.state.user.name,
  2869. rows: submitRows
  2870. }
  2871. this.processCategorySaveLoading = true
  2872. saveProofTrackingProcessCategoriesManage(inData).then(({ data }) => {
  2873. if (!(data && data.code === 0)) {
  2874. this.$message.error((data && data.msg) || '删除工序分类失败')
  2875. return
  2876. }
  2877. this.handleProcessCategoryConfigSaved(data.msg || '删除成功')
  2878. }).catch(() => {
  2879. this.$message.error('删除工序分类异常')
  2880. }).finally(() => {
  2881. this.processCategorySaveLoading = false
  2882. })
  2883. }).catch(() => {
  2884. // 用户取消删除时无需额外提示。
  2885. })
  2886. },
  2887. normalizeProcessCategoryRowsForSave (sourceRowsInput) {
  2888. const sourceRows = Array.isArray(sourceRowsInput)
  2889. ? sourceRowsInput
  2890. : (Array.isArray(this.processCategoryRows) ? this.processCategoryRows : [])
  2891. const normalizedRows = []
  2892. const usedNameMap = {}
  2893. for (let i = 0; i < sourceRows.length; i++) {
  2894. const row = sourceRows[i] || {}
  2895. const rowNo = i + 1
  2896. const categoryCode = row.categoryCode == null ? '' : String(row.categoryCode).trim()
  2897. const categoryName = row.categoryName == null ? '' : String(row.categoryName).trim()
  2898. if (!categoryName) {
  2899. this.$message.warning(`${rowNo}行的分类名称不能为空`)
  2900. return null
  2901. }
  2902. const nameKey = categoryName.toLowerCase()
  2903. if (usedNameMap[nameKey]) {
  2904. this.$message.warning(`分类名称【${categoryName}】重复,请检查`)
  2905. return null
  2906. }
  2907. usedNameMap[nameKey] = true
  2908. normalizedRows.push({
  2909. // 旧分类携带隐藏编码回传,便于后端识别“保留/新增/删除”并做使用校验。
  2910. categoryCode: categoryCode || null,
  2911. categoryName: categoryName,
  2912. sortNo: rowNo * 10
  2913. })
  2914. }
  2915. return normalizedRows
  2916. },
  2917. handleProcessCategoryConfigSaved (successMsg) {
  2918. this.$message.success(successMsg || '工序分类已保存')
  2919. this.queryProcessCategoryConfig(this.processCategoryDialogBuNo, true).then(() => {
  2920. const buNo = this.processCategoryDialogBuNo
  2921. const validCategoryCodeMap = {}
  2922. this.getProcessCategoryOptionsByBuNo(buNo).forEach(item => {
  2923. if (item && item.categoryCode) {
  2924. validCategoryCodeMap[String(item.categoryCode).trim()] = true
  2925. }
  2926. })
  2927. this.buProcessConfigRows.forEach(configRow => {
  2928. const rowBuNo = this.normalizeBuNo(configRow && configRow.buNo ? configRow.buNo : '', this.$store.state.user.site)
  2929. if (!rowBuNo || rowBuNo !== buNo) {
  2930. return
  2931. }
  2932. const nextCategoryMap = this.normalizeProcessCategoryMapForRow(configRow)
  2933. Object.keys(nextCategoryMap).forEach(processCode => {
  2934. const categoryCode = nextCategoryMap[processCode]
  2935. if (!categoryCode || validCategoryCodeMap[categoryCode]) {
  2936. return
  2937. }
  2938. nextCategoryMap[processCode] = ''
  2939. })
  2940. this.$set(configRow, 'processCategoryMap', nextCategoryMap)
  2941. })
  2942. this.queryProcessColumns().then(() => {
  2943. this.refreshTrackingTableLayout()
  2944. })
  2945. })
  2946. },
  2947. saveProcessCategoryConfig () {
  2948. const submitRows = this.normalizeProcessCategoryRowsForSave()
  2949. if (!submitRows) {
  2950. return
  2951. }
  2952. const inData = {
  2953. site: this.$store.state.user.site,
  2954. buNo: this.processCategoryDialogBuNo,
  2955. updateBy: this.$store.state.user.name,
  2956. rows: submitRows
  2957. }
  2958. this.processCategorySaveLoading = true
  2959. saveProofTrackingProcessCategoriesManage(inData).then(({ data }) => {
  2960. if (!(data && data.code === 0)) {
  2961. this.$message.error((data && data.msg) || '工序分类保存失败')
  2962. return
  2963. }
  2964. this.handleProcessCategoryConfigSaved(data.msg || '工序分类已保存')
  2965. }).catch(() => {
  2966. this.$message.error('工序分类保存异常')
  2967. }).finally(() => {
  2968. this.processCategorySaveLoading = false
  2969. })
  2970. },
  2971. addDefaultProcessConfigRow () {
  2972. this.defaultProcessConfigRows.push(this.buildDefaultProcessConfigRow({
  2973. sortNo: this.getNextDefaultProcessSortNo(),
  2974. activeFlag: 'Y'
  2975. }))
  2976. this.resetDefaultProcessConfigSortNoByOrder()
  2977. this.initDefaultProcessConfigSortable()
  2978. },
  2979. deleteDefaultProcessConfigRow () {
  2980. if (!this.defaultProcessConfigSelectionRows || this.defaultProcessConfigSelectionRows.length === 0) {
  2981. this.$message.warning('请先勾选要删除的行')
  2982. return
  2983. }
  2984. const selectedRows = this.defaultProcessConfigSelectionRows.slice()
  2985. this.$confirm(`确认删除选中的 ${selectedRows.length} 条默认工序吗?`, '提示', {
  2986. confirmButtonText: '确定',
  2987. cancelButtonText: '取消',
  2988. type: 'warning'
  2989. }).then(() => {
  2990. const selectedKeyMap = {}
  2991. const persistedCodeMap = {}
  2992. selectedRows.forEach(item => {
  2993. if (item && item._rowKey) {
  2994. selectedKeyMap[item._rowKey] = true
  2995. }
  2996. const persistedCode = item && item._persistedCode ? String(item._persistedCode).trim() : ''
  2997. const persistedCodeKey = persistedCode.toLowerCase()
  2998. if (persistedCode && !persistedCodeMap[persistedCodeKey]) {
  2999. persistedCodeMap[persistedCodeKey] = persistedCode
  3000. }
  3001. })
  3002. const nextRows = this.defaultProcessConfigRows.filter(row => {
  3003. return !(row && row._rowKey && selectedKeyMap[row._rowKey])
  3004. })
  3005. const applyDeletedRowsToUi = () => {
  3006. this.defaultProcessConfigRows = nextRows
  3007. this.resetDefaultProcessConfigSortNoByOrder()
  3008. this.clearDefaultProcessConfigSelection()
  3009. this.initDefaultProcessConfigSortable()
  3010. }
  3011. const persistedCodes = Object.keys(persistedCodeMap).map(key => persistedCodeMap[key])
  3012. if (persistedCodes.length === 0) {
  3013. applyDeletedRowsToUi()
  3014. this.$message.success('删除成功')
  3015. return
  3016. }
  3017. const inData = {
  3018. site: '*',
  3019. buNo: '*',
  3020. updateBy: this.$store.state.user.name,
  3021. processCodes: persistedCodes
  3022. }
  3023. this.defaultProcessConfigDeleteLoading = true
  3024. deleteProofTrackingProcessColumnsManage(inData).then(({ data }) => {
  3025. if (!(data && data.code === 0)) {
  3026. this.$message.error((data && data.msg) || '删除默认工序失败')
  3027. return
  3028. }
  3029. applyDeletedRowsToUi()
  3030. this.$message.success(data.msg || '删除成功')
  3031. // 删除动作实时落库后立即刷新主列表列定义,避免页面继续展示已删工序。
  3032. this.queryProcessColumns().then(() => {
  3033. this.queryBuProcessConfig()
  3034. this.getDataList()
  3035. this.refreshTrackingTableLayout()
  3036. })
  3037. }).catch(() => {
  3038. this.$message.error('删除默认工序异常')
  3039. }).finally(() => {
  3040. this.defaultProcessConfigDeleteLoading = false
  3041. })
  3042. }).catch(() => {
  3043. // 用户取消删除时无需额外提示。
  3044. })
  3045. },
  3046. normalizeDefaultProcessConfigRowsForSave () {
  3047. const sourceRows = Array.isArray(this.defaultProcessConfigRows) ? this.defaultProcessConfigRows : []
  3048. const normalizedRows = []
  3049. const usedCodeMap = {}
  3050. for (let i = 0; i < sourceRows.length; i++) {
  3051. const row = sourceRows[i] || {}
  3052. const rowNo = i + 1
  3053. const code = row.code == null ? '' : String(row.code).trim()
  3054. const codeKey = code.toLowerCase()
  3055. const label = row.label == null ? '' : String(row.label).trim()
  3056. if (!code) {
  3057. this.$message.warning(`${rowNo}行的工序编码不能为空`)
  3058. return null
  3059. }
  3060. if (usedCodeMap[codeKey]) {
  3061. this.$message.warning(`工序编码【${code}】重复,请检查`)
  3062. return null
  3063. }
  3064. if (!label) {
  3065. this.$message.warning(`${rowNo}行的工序名称不能为空`)
  3066. return null
  3067. }
  3068. const fieldData = this.buildDefaultProcessFieldsByCode(code)
  3069. if (!fieldData.planField || !fieldData.actualField || !fieldData.statusField) {
  3070. this.$message.warning(`${rowNo}行的工序编码格式无法生成字段,请调整编码`)
  3071. return null
  3072. }
  3073. usedCodeMap[codeKey] = true
  3074. normalizedRows.push({
  3075. code: code,
  3076. label: label,
  3077. planField: fieldData.planField,
  3078. actualField: fieldData.actualField,
  3079. statusField: fieldData.statusField,
  3080. // 用户要求:默认进度设置不维护工序分类。
  3081. processCategoryCode: null,
  3082. sortNo: rowNo * 10,
  3083. activeFlag: String(row.activeFlag || 'Y').toUpperCase() === 'N' ? 'N' : 'Y'
  3084. })
  3085. }
  3086. return normalizedRows
  3087. },
  3088. saveDefaultProcessConfig () {
  3089. const submitRows = this.normalizeDefaultProcessConfigRowsForSave()
  3090. if (!submitRows) {
  3091. return
  3092. }
  3093. const executeSave = () => {
  3094. const inData = {
  3095. site: '*',
  3096. buNo: '*',
  3097. updateBy: this.$store.state.user.name,
  3098. rows: submitRows
  3099. }
  3100. saveProofTrackingProcessColumnsManage(inData).then(({ data }) => {
  3101. if (!(data && data.code === 0)) {
  3102. this.$message.error((data && data.msg) || '默认工序配置保存失败')
  3103. return
  3104. }
  3105. this.$message.success(data.msg || '默认工序配置已保存')
  3106. this.queryDefaultProcessConfig()
  3107. // 保存后刷新页面列定义,确保新增/删除工序立即反映到主表列。
  3108. this.queryProcessColumns().then(() => {
  3109. this.queryBuProcessConfig()
  3110. this.getDataList()
  3111. this.refreshTrackingTableLayout()
  3112. })
  3113. }).catch(() => {
  3114. this.$message.error('默认工序配置保存异常')
  3115. })
  3116. }
  3117. if (submitRows.length === 0) {
  3118. this.$confirm('当前将清空默认工序配置,保存后页面会回退到系统兜底配置,是否继续?', '提示', {
  3119. confirmButtonText: '确定',
  3120. cancelButtonText: '取消',
  3121. type: 'warning'
  3122. }).then(() => {
  3123. executeSave()
  3124. }).catch(() => {
  3125. // 用户取消清空时无需额外提示。
  3126. })
  3127. return
  3128. }
  3129. executeSave()
  3130. },
  3131. refreshTrackingTableLayout () {
  3132. this.$nextTick(() => {
  3133. const tableRef = this.$refs.trackingTable
  3134. if (tableRef && tableRef.doLayout) {
  3135. tableRef.doLayout()
  3136. }
  3137. })
  3138. },
  3139. handleBuProcessCodesChange (row) {
  3140. if (!row) {
  3141. return
  3142. }
  3143. const validOrderCodes = this.getValidProcessOrderCodes(row.processOrderCodes)
  3144. const validCodes = this.getValidProcessCodes(row.processCodes, validOrderCodes)
  3145. const selectedCodeMap = {}
  3146. validCodes.forEach(code => {
  3147. selectedCodeMap[code] = true
  3148. })
  3149. row.processOrderCodes = validOrderCodes
  3150. row.processCodes = validOrderCodes.filter(code => selectedCodeMap[code])
  3151. row.processCategoryMap = this.normalizeProcessCategoryMapForRow(row)
  3152. },
  3153. isProcessOrderDraggingItem (row, index) {
  3154. if (!row || index == null) {
  3155. return false
  3156. }
  3157. return this.processOrderDragState.buNo === row.buNo && this.processOrderDragState.fromIndex === index
  3158. },
  3159. handleProcessOrderDragStart (row, index, event) {
  3160. if (!row || index == null || index < 0) {
  3161. return
  3162. }
  3163. this.processOrderDragState = {
  3164. buNo: row.buNo || '',
  3165. fromIndex: index
  3166. }
  3167. if (event && event.dataTransfer) {
  3168. event.dataTransfer.effectAllowed = 'move'
  3169. }
  3170. },
  3171. handleProcessOrderDrop (row, targetIndex, event) {
  3172. if (event && event.dataTransfer) {
  3173. event.dataTransfer.dropEffect = 'move'
  3174. }
  3175. if (!row || targetIndex == null || targetIndex < 0) {
  3176. this.handleProcessOrderDragEnd()
  3177. return
  3178. }
  3179. const dragBuNo = this.processOrderDragState.buNo
  3180. const fromIndex = Number(this.processOrderDragState.fromIndex)
  3181. if (!dragBuNo || dragBuNo !== row.buNo || Number.isNaN(fromIndex) || fromIndex < 0) {
  3182. this.handleProcessOrderDragEnd()
  3183. return
  3184. }
  3185. const validOrderCodes = this.getValidProcessOrderCodes(row.processOrderCodes)
  3186. if (fromIndex >= validOrderCodes.length || targetIndex >= validOrderCodes.length) {
  3187. this.handleProcessOrderDragEnd()
  3188. return
  3189. }
  3190. if (fromIndex === targetIndex) {
  3191. this.handleProcessOrderDragEnd()
  3192. return
  3193. }
  3194. const movedCode = validOrderCodes.splice(fromIndex, 1)[0]
  3195. const insertIndex = targetIndex
  3196. validOrderCodes.splice(insertIndex, 0, movedCode)
  3197. row.processOrderCodes = validOrderCodes
  3198. this.handleBuProcessCodesChange(row)
  3199. this.handleProcessOrderDragEnd()
  3200. },
  3201. handleProcessOrderDragEnd () {
  3202. this.processOrderDragState = {
  3203. buNo: '',
  3204. fromIndex: -1
  3205. }
  3206. },
  3207. saveBuProcessConfig () {
  3208. if (!this.buProcessConfigRows || this.buProcessConfigRows.length === 0) {
  3209. this.$message.warning('暂无可配置的事业部')
  3210. return
  3211. }
  3212. const nextMap = {}
  3213. const submitRows = []
  3214. for (let i = 0; i < this.buProcessConfigRows.length; i++) {
  3215. const row = this.buProcessConfigRows[i]
  3216. const buNo = this.normalizeBuNo(row && row.buNo ? row.buNo : '', this.$store.state.user.site)
  3217. if (!buNo) {
  3218. continue
  3219. }
  3220. const validOrderCodes = this.getValidProcessOrderCodes(row.processOrderCodes)
  3221. const selectedCodeMap = {}
  3222. this.getValidProcessCodes(row.processCodes, validOrderCodes).forEach(code => {
  3223. selectedCodeMap[code] = true
  3224. })
  3225. const validCodes = validOrderCodes.filter(code => selectedCodeMap[code])
  3226. if (validCodes.length === 0) {
  3227. this.$message.warning(`事业部【${row.buDesc || buNo}】至少需要选择一个可见进度`)
  3228. return
  3229. }
  3230. const processCategoryMap = this.normalizeProcessCategoryMapForRow(Object.assign({}, row, {
  3231. processOrderCodes: validOrderCodes
  3232. }))
  3233. const processCategoryNameMap = {}
  3234. this.getProcessCategoryOptionsByBuNo(buNo).forEach(item => {
  3235. const categoryCode = item && item.categoryCode ? String(item.categoryCode).trim() : ''
  3236. const categoryName = item && item.categoryName ? String(item.categoryName).trim() : ''
  3237. if (!categoryCode || !categoryName) {
  3238. return
  3239. }
  3240. processCategoryNameMap[categoryCode] = categoryName
  3241. processCategoryNameMap[categoryCode.toLowerCase()] = categoryName
  3242. })
  3243. const showAll = validCodes.length >= validOrderCodes.length
  3244. nextMap[buNo] = {
  3245. showAll: showAll,
  3246. processOrderCodes: validOrderCodes,
  3247. processCodes: showAll ? validOrderCodes.slice() : validCodes,
  3248. processCategoryMap: processCategoryMap,
  3249. processCategoryNameMap: processCategoryNameMap
  3250. }
  3251. submitRows.push({
  3252. buNo: buNo,
  3253. showAll: showAll,
  3254. processCodes: validCodes,
  3255. processOrderCodes: validOrderCodes,
  3256. processCategoryMap: processCategoryMap
  3257. })
  3258. }
  3259. const inData = {
  3260. site: this.$store.state.user.site,
  3261. updateBy: this.$store.state.user.name,
  3262. rows: submitRows
  3263. }
  3264. saveProofTrackingBuProcessConfig(inData).then(({ data }) => {
  3265. if (!(data && data.code === 0)) {
  3266. this.$message.error((data && data.msg) || '事业部进度展示设置保存失败')
  3267. return
  3268. }
  3269. this.buProcessConfigMap = nextMap
  3270. this.ensureBuProcessConfigRows()
  3271. this.$message.success(data.msg || '事业部进度展示设置已保存')
  3272. // 保存后重新拉取工序列和列表,确保页面立即按最新可见性与顺序刷新。
  3273. this.queryProcessColumns().then(() => {
  3274. this.getDataList()
  3275. this.refreshTrackingTableLayout()
  3276. })
  3277. }).catch(() => {
  3278. this.$message.error('事业部进度展示设置保存异常')
  3279. })
  3280. },
  3281. getRoleApi (apiKey) {
  3282. const apiMap = {
  3283. 0: searchBusinessInfo,
  3284. 1: searchBusinessInfo1,
  3285. 2: searchBusinessInfo2,
  3286. 3: searchBusinessInfo3,
  3287. 4: searchBusinessInfo4,
  3288. 5: searchBusinessInfo5,
  3289. 6: searchBusinessInfo6,
  3290. 7: searchBusinessInfo7,
  3291. 8: searchBusinessInfo8
  3292. }
  3293. return apiMap[apiKey]
  3294. },
  3295. fetchRoleListByApiKey (apiKey, queryData) {
  3296. const apiFn = this.getRoleApi(apiKey)
  3297. if (!apiFn) {
  3298. return Promise.resolve([])
  3299. }
  3300. const params = {
  3301. site: this.$store.state.user.site,
  3302. username: queryData && queryData.username ? queryData.username : '',
  3303. userDisplay: queryData && queryData.userDisplay ? queryData.userDisplay : '',
  3304. roleDesc: '',
  3305. active: queryData && queryData.active ? queryData.active : '',
  3306. page: 1,
  3307. limit: 200
  3308. }
  3309. return apiFn(params).then(({ data }) => {
  3310. if (data && data.code === 0) {
  3311. return (data.rows || []).map(item => {
  3312. return {
  3313. username: item.username,
  3314. userDisplay: item.userDisplay || item.user_display || '',
  3315. active: item.active
  3316. }
  3317. })
  3318. }
  3319. return []
  3320. })
  3321. },
  3322. newCustomer () {
  3323. this.newCustomerData.customerDesc = ''
  3324. this.newCustomerFlag = true
  3325. },
  3326. getBaseList (val) {
  3327. this.tagNo = val
  3328. this.$nextTick(() => {
  3329. const strVal = val === 509 ? (this.oneKeyForm.customerNo || '') : ''
  3330. this.$refs.baseList.init(val, strVal, '')
  3331. })
  3332. },
  3333. getBaseData (val) {
  3334. if (this.tagNo === 509) {
  3335. this.oneKeyForm.customerNo = val.customer_no
  3336. this.oneKeyForm.customerDesc = val.customer_desc
  3337. }
  3338. },
  3339. customerNoBlur () {
  3340. if (!this.oneKeyForm.customerNo) {
  3341. this.oneKeyForm.customerDesc = ''
  3342. return
  3343. }
  3344. const params = {
  3345. customerNo: this.oneKeyForm.customerNo,
  3346. createBy: this.$store.state.user.name
  3347. }
  3348. queryCustomerList(params).then(({ data }) => {
  3349. if (data && data.code === 0 && data.rows && data.rows.length === 1) {
  3350. this.oneKeyForm.customerDesc = data.rows[0].customerDesc
  3351. } else {
  3352. this.oneKeyForm.customerDesc = ''
  3353. }
  3354. })
  3355. },
  3356. saveNewCustomer () {
  3357. if (!this.newCustomerData.customerDesc) {
  3358. this.$message.warning('请输入客户名称!')
  3359. return
  3360. }
  3361. this.generateNextCustomerNo().then(customerNo => {
  3362. if (!customerNo) {
  3363. this.$alert('该客户已存在,请重新输入客户名称!', '提示', {
  3364. confirmButtonText: '确定',
  3365. type: 'warning'
  3366. })
  3367. return
  3368. }
  3369. const params = {
  3370. site: this.$store.state.user.site,
  3371. customerNo: customerNo,
  3372. customerDesc: this.newCustomerData.customerDesc,
  3373. createBy: this.$store.state.user.name
  3374. }
  3375. saveNewCustomer(params).then(({ data }) => {
  3376. if (data && data.code === 0) {
  3377. this.oneKeyForm.customerNo = customerNo
  3378. this.oneKeyForm.customerDesc = this.newCustomerData.customerDesc
  3379. this.newCustomerFlag = false
  3380. this.$message.success(data.msg || '新增客户成功')
  3381. } else {
  3382. this.$alert(data && data.msg ? data.msg : '新增客户失败', '提示', {
  3383. confirmButtonText: '确定',
  3384. type: 'warning'
  3385. })
  3386. }
  3387. })
  3388. })
  3389. },
  3390. generateNextCustomerNo () {
  3391. const params = {
  3392. site: this.$store.state.user.site,
  3393. customerDesc: this.newCustomerData.customerDesc,
  3394. createBy: this.$store.state.user.name
  3395. }
  3396. return new Promise(resolve => {
  3397. getCustomerNo(params).then(({ data }) => {
  3398. if (!(data && data.code === 0)) {
  3399. resolve(false)
  3400. return
  3401. }
  3402. if (!data.data || !data.data.customerNo || data.data === '0') {
  3403. resolve('C0001')
  3404. return
  3405. }
  3406. const lastCustomerNo = parseInt(String(data.data.customerNo).substring(1), 10) || 0
  3407. const nextCustomerNo = lastCustomerNo + 1
  3408. if (nextCustomerNo < 10) {
  3409. resolve('C000' + nextCustomerNo)
  3410. } else if (nextCustomerNo < 100) {
  3411. resolve('C00' + nextCustomerNo)
  3412. } else if (nextCustomerNo < 1000) {
  3413. resolve('C0' + nextCustomerNo)
  3414. } else {
  3415. resolve('C' + nextCustomerNo)
  3416. }
  3417. }).catch(() => {
  3418. resolve(false)
  3419. })
  3420. })
  3421. },
  3422. closeNewCustomer () {
  3423. this.newCustomerData.customerDesc = ''
  3424. this.newCustomerFlag = false
  3425. },
  3426. openRoleDialog (field) {
  3427. const roleCfg = this.roleConfig[field]
  3428. if (!roleCfg) {
  3429. return
  3430. }
  3431. this.roleDialogField = field
  3432. this.roleDialogApiKey = roleCfg.apiKey
  3433. this.roleDialogTitle = roleCfg.title
  3434. this.roleSearchData = {
  3435. site: this.$store.state.user.site,
  3436. username: '',
  3437. userDisplay: '',
  3438. active: '',
  3439. page: 1,
  3440. limit: 50
  3441. }
  3442. this.roleDialogVisible = true
  3443. this.queryRoleDialogList()
  3444. },
  3445. queryRoleDialogList () {
  3446. this.fetchRoleListByApiKey(this.roleDialogApiKey, this.roleSearchData).then(rows => {
  3447. this.roleDialogList = rows
  3448. })
  3449. },
  3450. pickRole (row) {
  3451. const roleCfg = this.roleConfig[this.roleDialogField]
  3452. if (!roleCfg) {
  3453. return
  3454. }
  3455. const value = row.username + '-' + row.userDisplay
  3456. this.oneKeyForm[this.roleDialogField] = value
  3457. this.oneKeyForm[roleCfg.nameField] = row.userDisplay
  3458. this.roleDialogVisible = false
  3459. },
  3460. getEmptyOneKeyForm () {
  3461. return {
  3462. site: this.$store.state.user.site,
  3463. buNo: '',
  3464. projectNo: '',
  3465. projectName: '',
  3466. projectDesc: '',
  3467. projectStatus: '草稿',
  3468. projectSource: '',
  3469. testPartNo: '',
  3470. partDesc: '',
  3471. partName: '',
  3472. partSpec: '',
  3473. materialNumber: '',
  3474. finalPartDesc: '',
  3475. finalPartNo: '',
  3476. baseNo: '',
  3477. revNo: '',
  3478. customerNo: '',
  3479. customerDesc: '',
  3480. finalCustomerId: '',
  3481. customerRemark: '',
  3482. parentProjectNo: '',
  3483. oriProjectId: '',
  3484. projectCategory: '',
  3485. cProjectRegion: '',
  3486. projectManager: '',
  3487. projectManagerName: '',
  3488. projectOwner: '',
  3489. projectOwnerName: '',
  3490. cQualityEngineer1: '',
  3491. cQualityEngineer1Name: '',
  3492. cQualityEngineer2: '',
  3493. cQualityEngineer2Name: '',
  3494. cQualityEngineer3: '',
  3495. cQualityEngineer3Name: '',
  3496. cQualityEngineer4: '',
  3497. cQualityEngineer4Name: '',
  3498. cQualityEngineer5: '',
  3499. cQualityEngineer5Name: '',
  3500. cQualityEngineer6: '',
  3501. cQualityEngineer6Name: '',
  3502. cManufactureEngineer: '',
  3503. cManufactureEngineerName: '',
  3504. docEngineer: '',
  3505. docEngineerName: '',
  3506. docEngineer2: '',
  3507. docEngineer2Name: '',
  3508. ipqcHardTag: '',
  3509. ipqcHardTagName: '',
  3510. cQualityEngineer7: '',
  3511. cQualityEngineer7Name: '',
  3512. partType: '',
  3513. dry_wet_type: '',
  3514. partStatus: '草稿',
  3515. projectPhase: '',
  3516. tracker: '',
  3517. engineer: '',
  3518. engineerName: '',
  3519. priorityLevel: '',
  3520. proofingNo: '',
  3521. proofingStatus: '草稿',
  3522. proofingNumber: '',
  3523. projectCreationDate: '',
  3524. projectCloseDate: '',
  3525. buildDate: '',
  3526. closeDate: '',
  3527. comments: '',
  3528. planStartDate: '',
  3529. requiredDeliveryDate: '',
  3530. baseline: '',
  3531. needDate: '',
  3532. remark: ''
  3533. }
  3534. },
  3535. getDefaultProofDialogData () {
  3536. return {
  3537. trackingId: null,
  3538. site: this.$store.state.user.site,
  3539. projectId: null,
  3540. projectNo: '',
  3541. projectDesc: '',
  3542. buNo: '',
  3543. customerNo: '',
  3544. customerDesc: '',
  3545. projectPartId: null,
  3546. testPartNo: '',
  3547. partDesc: '',
  3548. projectCategory: '',
  3549. cProjectTypeDb: '',
  3550. projectPhase: '',
  3551. projectManager: '',
  3552. projectOwner: '',
  3553. engineer: '',
  3554. priorityLevel: '',
  3555. proofingNo: '',
  3556. proofingNumber: '',
  3557. planStartDate: '',
  3558. requiredDeliveryDate: '',
  3559. actualityDeliveryDate: '',
  3560. proofingStatus: '草稿',
  3561. remark: '',
  3562. createBy: this.$store.state.user.name,
  3563. updateBy: this.$store.state.user.name
  3564. }
  3565. },
  3566. getDataList (flag) {
  3567. if (flag === 'Y') {
  3568. this.pageIndex = 1
  3569. }
  3570. this.searchData.site = this.$store.state.user.site
  3571. this.searchData.userName = this.$store.state.user.name
  3572. const inData = Object.assign({}, this.searchData, {
  3573. page: this.pageIndex,
  3574. limit: this.pageSize
  3575. })
  3576. inData.buNo = this.normalizeBuNo(inData.buNo, inData.site)
  3577. this.dataListLoading = true
  3578. searchProofTracking(inData).then(({ data }) => {
  3579. this.dataListLoading = false
  3580. this.resetProcessTooltip()
  3581. if (data && data.code === 0) {
  3582. this.dataList = (data.page && data.page.list) || []
  3583. this.syncDataListProcessValues()
  3584. this.totalPage = (data.page && data.page.totalCount) || 0
  3585. if (this.commentsEditingTrackingId) {
  3586. const hasEditingRow = this.dataList.some(item => item.trackingId === this.commentsEditingTrackingId)
  3587. if (!hasEditingRow) {
  3588. this.cancelCommentsEdit()
  3589. }
  3590. }
  3591. const manualSelectedId = this.manualSelectedTrackingId
  3592. const previousCurrentId = this.currentRow && this.currentRow.trackingId
  3593. let nextCurrentRow = null
  3594. if (manualSelectedId != null) {
  3595. // 只要用户没有手动点新行,刷新后始终尝试回选上次手动选中的那一行
  3596. nextCurrentRow = this.dataList.find(item => item && item.trackingId === manualSelectedId) || null
  3597. } else if (previousCurrentId != null) {
  3598. nextCurrentRow = this.dataList.find(item => item && item.trackingId === previousCurrentId) || null
  3599. }
  3600. if (!nextCurrentRow && manualSelectedId == null && this.dataList.length > 0) {
  3601. nextCurrentRow = this.dataList[0]
  3602. }
  3603. this.currentRow = nextCurrentRow
  3604. this.$nextTick(() => {
  3605. if (this.$refs.trackingTable && typeof this.$refs.trackingTable.setCurrentRow === 'function') {
  3606. this.$refs.trackingTable.setCurrentRow(this.currentRow || null)
  3607. }
  3608. })
  3609. } else {
  3610. this.dataList = []
  3611. this.totalPage = 0
  3612. this.currentRow = null
  3613. this.$nextTick(() => {
  3614. if (this.$refs.trackingTable && typeof this.$refs.trackingTable.setCurrentRow === 'function') {
  3615. this.$refs.trackingTable.setCurrentRow(null)
  3616. }
  3617. })
  3618. this.$message.error(data.msg || '查询失败')
  3619. }
  3620. }).catch(() => {
  3621. this.dataListLoading = false
  3622. this.resetProcessTooltip()
  3623. this.$message.error('查询异常')
  3624. })
  3625. },
  3626. async exportTrackingData () {
  3627. if (this.exportLoading) {
  3628. return
  3629. }
  3630. this.searchData.site = this.$store.state.user.site
  3631. this.searchData.userName = this.$store.state.user.name
  3632. const exportTrackingColumns = this.visibleTrackingColumns.map(item => ({
  3633. columnProp: item.columnProp,
  3634. columnLabel: item.columnLabel
  3635. }))
  3636. const exportProcessColumns = this.visibleProcessColumns.map(item => ({
  3637. code: item.code,
  3638. label: item.label,
  3639. planField: item.planField,
  3640. actualField: item.actualField,
  3641. statusField: item.statusField,
  3642. processCategoryCode: item.processCategoryCode,
  3643. processCategoryName: item.processCategoryName
  3644. }))
  3645. if (exportTrackingColumns.length === 0 && exportProcessColumns.length === 0) {
  3646. this.$message.warning('当前没有可导出的列')
  3647. return
  3648. }
  3649. const inData = Object.assign({}, this.searchData, {
  3650. buNo: this.normalizeBuNo(this.searchData.buNo, this.searchData.site),
  3651. exportTrackingColumns: exportTrackingColumns,
  3652. exportProcessColumns: exportProcessColumns,
  3653. exportCommentsColumn: true
  3654. })
  3655. this.exportLoading = true
  3656. try {
  3657. const response = await exportProofTracking(inData)
  3658. const blob = response && response.data ? response.data : null
  3659. if (!blob) {
  3660. this.$message.error('导出失败')
  3661. return
  3662. }
  3663. if (blob.type && blob.type.indexOf('application/json') > -1) {
  3664. const errorText = await blob.text()
  3665. let errorMsg = '导出失败'
  3666. try {
  3667. const errorObj = JSON.parse(errorText)
  3668. if (errorObj && errorObj.msg) {
  3669. errorMsg = errorObj.msg
  3670. }
  3671. } catch (e) {
  3672. }
  3673. this.$message.error(errorMsg)
  3674. return
  3675. }
  3676. const fileName = this.resolveProofTrackingExportFileName(response)
  3677. const url = window.URL.createObjectURL(blob)
  3678. const link = document.createElement('a')
  3679. link.href = url
  3680. link.setAttribute('download', fileName)
  3681. document.body.appendChild(link)
  3682. link.click()
  3683. link.remove()
  3684. window.URL.revokeObjectURL(url)
  3685. } catch (e) {
  3686. this.$message.error('导出失败')
  3687. } finally {
  3688. this.exportLoading = false
  3689. }
  3690. },
  3691. resolveProofTrackingExportFileName (response) {
  3692. const defaultFileName = `项目打样跟踪_${this.getExportTimestamp()}.xlsx`
  3693. const disposition = response && response.headers
  3694. ? (response.headers['content-disposition'] || response.headers['Content-Disposition'] || '')
  3695. : ''
  3696. if (!disposition) {
  3697. return defaultFileName
  3698. }
  3699. const utf8Match = disposition.match(/filename\*=utf-8''([^;]+)/i)
  3700. if (utf8Match && utf8Match[1]) {
  3701. try {
  3702. return decodeURIComponent(utf8Match[1])
  3703. } catch (e) {
  3704. return utf8Match[1]
  3705. }
  3706. }
  3707. const normalMatch = disposition.match(/filename="?([^";]+)"?/i)
  3708. if (normalMatch && normalMatch[1]) {
  3709. return normalMatch[1]
  3710. }
  3711. return defaultFileName
  3712. },
  3713. getExportTimestamp () {
  3714. const now = new Date()
  3715. const pad = num => String(num).padStart(2, '0')
  3716. return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`
  3717. },
  3718. resetSearch () {
  3719. this.searchData.projectNo = ''
  3720. this.searchData.projectDesc = ''
  3721. this.searchData.testPartNo = ''
  3722. this.searchData.partDesc = ''
  3723. this.searchData.dryWetType = ''
  3724. this.searchData.customerNo = ''
  3725. this.searchData.proofingNo = ''
  3726. this.searchData.projectPartSyncFlag = ''
  3727. this.searchData.proofSyncFlag = ''
  3728. this.searchData.proofingStatus = ''
  3729. this.ensureSearchBuNoSelected()
  3730. this.queryProcessColumns().then(() => {
  3731. this.getDataList('Y')
  3732. })
  3733. },
  3734. selectionChange (rows) {
  3735. this.selectionRows = rows || []
  3736. },
  3737. isCommentsEditing (row) {
  3738. return !!row && row.trackingId === this.commentsEditingTrackingId
  3739. },
  3740. startCommentsEdit (row) {
  3741. if (this.batchEditMode || !row || !row.trackingId || this.commentsSaveLoading) {
  3742. return
  3743. }
  3744. this.commentsEditingTrackingId = row.trackingId
  3745. this.commentsDraft = row.comments || ''
  3746. },
  3747. cancelCommentsEdit () {
  3748. if (this.commentsSaveLoading) {
  3749. return
  3750. }
  3751. this.commentsEditingTrackingId = null
  3752. this.commentsDraft = ''
  3753. },
  3754. handleCommentsBlur (row) {
  3755. if (this.commentsSaveLoading || !this.isCommentsEditing(row)) {
  3756. return
  3757. }
  3758. const comments = this.commentsDraft == null ? '' : String(this.commentsDraft)
  3759. if (comments.trim()) {
  3760. this.saveCommentsEdit(row, {
  3761. fromBlur: true,
  3762. draftComments: comments
  3763. })
  3764. return
  3765. }
  3766. this.cancelCommentsEdit()
  3767. },
  3768. saveCommentsEdit (row, options) {
  3769. const saveOptions = options || {}
  3770. const fromBlur = !!saveOptions.fromBlur
  3771. if (this.commentsSaveLoading) {
  3772. return
  3773. }
  3774. if (!row || !row.trackingId) {
  3775. this.$message.warning('跟踪记录ID不能为空')
  3776. return
  3777. }
  3778. const comments = Object.prototype.hasOwnProperty.call(saveOptions, 'draftComments')
  3779. ? (saveOptions.draftComments == null ? '' : String(saveOptions.draftComments))
  3780. : (this.commentsDraft == null ? '' : String(this.commentsDraft))
  3781. if (comments === (row.comments || '')) {
  3782. this.cancelCommentsEdit()
  3783. return
  3784. }
  3785. if (fromBlur) {
  3786. this.commentsEditingTrackingId = null
  3787. this.commentsDraft = ''
  3788. }
  3789. this.commentsSaveLoading = true
  3790. updateProofTrackingComments({
  3791. trackingId: row.trackingId,
  3792. comments: comments,
  3793. updateBy: this.$store.state.user.name
  3794. }).then(({ data }) => {
  3795. this.commentsSaveLoading = false
  3796. if (data && data.code === 0) {
  3797. row.comments = comments
  3798. this.$message.success('Comments保存成功')
  3799. this.cancelCommentsEdit()
  3800. } else {
  3801. this.$message.error(data.msg || 'Comments保存失败')
  3802. }
  3803. }).catch(() => {
  3804. this.commentsSaveLoading = false
  3805. this.$message.error('Comments保存异常')
  3806. })
  3807. },
  3808. rowClick (row) {
  3809. this.currentRow = row
  3810. this.manualSelectedTrackingId = row && row.trackingId ? row.trackingId : null
  3811. },
  3812. sizeChangeHandle (val) {
  3813. this.pageSize = val
  3814. this.pageIndex = 1
  3815. this.getDataList()
  3816. },
  3817. currentChangeHandle (val) {
  3818. this.pageIndex = val
  3819. this.getDataList()
  3820. },
  3821. getActionRow (row) {
  3822. return row || this.currentRow || (this.selectionRows.length > 0 ? this.selectionRows[0] : null)
  3823. },
  3824. getTrackingProjectPartId (row) {
  3825. if (!row) {
  3826. return null
  3827. }
  3828. return row.projectPartId || row.project_part_id || null
  3829. },
  3830. isSyncedByIdRule (idValue) {
  3831. if (idValue == null || String(idValue).trim() === '') {
  3832. return false
  3833. }
  3834. // 约定:NPI 正式库主键 < 100000,tracking 临时主键 >= 100000。
  3835. const numericId = Number(idValue)
  3836. return Number.isFinite(numericId) && numericId > 0 && numericId < 100000
  3837. },
  3838. isProjectPartSynced (row) {
  3839. if (!row) {
  3840. return false
  3841. }
  3842. const projectId = row.projectId || row.project_id || null
  3843. const projectPartId = this.getTrackingProjectPartId(row)
  3844. return this.isSyncedByIdRule(projectId) && this.isSyncedByIdRule(projectPartId)
  3845. },
  3846. isProofSynced (row) {
  3847. if (!row) {
  3848. return false
  3849. }
  3850. const proofingId = row.proofingId || row.proofing_id || row.trackingId || null
  3851. return this.isSyncedByIdRule(proofingId)
  3852. },
  3853. getProjectPartSyncTextClass (row) {
  3854. return this.isProjectPartSynced(row) ? 'sync-text--ok' : 'sync-text--pending'
  3855. },
  3856. getProofSyncTextClass (row) {
  3857. return this.isProofSynced(row) ? 'sync-text--ok' : 'sync-text--pending'
  3858. },
  3859. getProjectCategoryValue (source) {
  3860. if (!source) {
  3861. return ''
  3862. }
  3863. const category = source.cProjectTypeDb || source.projectCategory || source.project_category || source.c_project_type_db || ''
  3864. return this.isBlankValue(category) ? '' : String(category).trim()
  3865. },
  3866. isOneKeyCreateFieldVisible (fieldKey) {
  3867. if (this.oneKeyDialogMode !== 'create') {
  3868. return true
  3869. }
  3870. return this.oneKeyCreateVisibleFieldKeys.indexOf(fieldKey) > -1
  3871. },
  3872. isBlankValue (value) {
  3873. return value == null || String(value).trim() === ''
  3874. },
  3875. getApplyQuantityDisplayText (applyQuantity) {
  3876. if (this.isBlankValue(applyQuantity)) {
  3877. return '未维护'
  3878. }
  3879. return String(applyQuantity).trim()
  3880. },
  3881. normalizeProjectNo (projectNo) {
  3882. if (this.isBlankValue(projectNo)) {
  3883. return ''
  3884. }
  3885. return String(projectNo).trim()
  3886. },
  3887. queryApplyOptionsByProjectNo (projectNo) {
  3888. const normalizedProjectNo = this.normalizeProjectNo(projectNo)
  3889. if (!normalizedProjectNo) {
  3890. return Promise.resolve([])
  3891. }
  3892. return searchExpApplyList({
  3893. projectNo: normalizedProjectNo,
  3894. page: 1,
  3895. limit: 500
  3896. }).then(({ data }) => {
  3897. if (!(data && data.code === 0)) {
  3898. throw new Error((data && data.msg) || '查询试验单失败')
  3899. }
  3900. const pageList = (data.page && data.page.list) || []
  3901. const matchedProjectNo = normalizedProjectNo.toUpperCase()
  3902. const optionMap = {}
  3903. const optionRows = []
  3904. pageList.forEach(item => {
  3905. if (!item || this.isBlankValue(item.applyNo)) {
  3906. return
  3907. }
  3908. const applyStatus = this.isBlankValue(item.status) ? '' : String(item.status).trim()
  3909. // 业务要求:已完成的试验单不允许继续作为 tracking 打样单号使用。
  3910. if (applyStatus === '草稿' || applyStatus === '已完成') {
  3911. return
  3912. }
  3913. const rowProjectNo = this.normalizeProjectNo(item.projectNo).toUpperCase()
  3914. // searchExpApplyList 按模糊 project_no 查询,这里再次做等值过滤,确保与 tracking 项目编码一一对应。
  3915. if (rowProjectNo !== matchedProjectNo) {
  3916. return
  3917. }
  3918. const applyNo = String(item.applyNo).trim()
  3919. if (optionMap[applyNo]) {
  3920. return
  3921. }
  3922. optionMap[applyNo] = true
  3923. optionRows.push({
  3924. applyNo: applyNo,
  3925. expectedFinishDate: this.formatDate(item.expectedFinishDate),
  3926. applyQuantity: this.isBlankValue(item.quantityReq) ? '' : String(item.quantityReq).trim()
  3927. })
  3928. })
  3929. return optionRows
  3930. })
  3931. },
  3932. handleOneKeyProjectNoBlur () {
  3933. const projectNo = this.normalizeProjectNo(this.oneKeyForm.projectNo)
  3934. const previousProjectNo = this.normalizeProjectNo(this.oneKeyProjectNoSnapshot)
  3935. this.oneKeyForm.projectNo = projectNo
  3936. if (!projectNo) {
  3937. this.oneKeyProofingApplyQueryToken += 1
  3938. this.oneKeyProofingApplyLoading = false
  3939. this.oneKeyProofingApplyOptions = []
  3940. this.oneKeyForm.proofingNo = ''
  3941. this.oneKeyForm.requiredDeliveryDate = ''
  3942. this.oneKeyProjectNoSnapshot = ''
  3943. this.oneKeyForm.testPartNo = ''
  3944. this.oneKeyForm.partDesc = ''
  3945. this.oneKeyForm.partType = ''
  3946. this.oneKeyForm.dry_wet_type = ''
  3947. return
  3948. }
  3949. if (previousProjectNo.toUpperCase() !== projectNo.toUpperCase()) {
  3950. // 项目编码变化后需清理旧料号,避免跨项目保留旧料号与角色信息。
  3951. this.oneKeyForm.testPartNo = ''
  3952. this.oneKeyForm.partDesc = ''
  3953. this.oneKeyForm.partType = ''
  3954. this.oneKeyForm.dry_wet_type = ''
  3955. }
  3956. this.oneKeyProjectNoSnapshot = projectNo
  3957. this.loadOneKeyProofingApplyOptions(projectNo, { keepCurrentIfMissing: false })
  3958. },
  3959. handleOneKeyProofingNoChange (proofingNo) {
  3960. const selectedNo = this.isBlankValue(proofingNo) ? '' : String(proofingNo).trim()
  3961. if (this.oneKeyDialogMode === 'edit') {
  3962. // 修改模式下预计完成日期以数据库已存值为准,不跟随打样单号自动回填/清空。
  3963. if (!selectedNo) {
  3964. this.oneKeyForm.proofingNo = ''
  3965. return
  3966. }
  3967. const matchedInEdit = this.oneKeyProofingApplyOptions.find(item => item && item.applyNo === selectedNo)
  3968. if (matchedInEdit) {
  3969. this.oneKeyForm.proofingNo = matchedInEdit.applyNo
  3970. }
  3971. return
  3972. }
  3973. if (!selectedNo) {
  3974. this.oneKeyForm.requiredDeliveryDate = ''
  3975. return
  3976. }
  3977. const matched = this.oneKeyProofingApplyOptions.find(item => item && item.applyNo === selectedNo)
  3978. if (!matched) {
  3979. return
  3980. }
  3981. this.oneKeyForm.proofingNo = matched.applyNo
  3982. this.oneKeyForm.requiredDeliveryDate = matched.expectedFinishDate || ''
  3983. this.syncOneKeyBaselineByRequiredDeliveryDate(this.oneKeyForm.requiredDeliveryDate, false)
  3984. },
  3985. handleOneKeyRequiredDeliveryDateChange (dateVal) {
  3986. this.syncOneKeyBaselineByRequiredDeliveryDate(dateVal, false)
  3987. },
  3988. syncOneKeyBaselineByRequiredDeliveryDate (requiredDeliveryDate, forceSync) {
  3989. const normalizedRequiredDate = this.formatDate(requiredDeliveryDate)
  3990. if (!normalizedRequiredDate) {
  3991. return
  3992. }
  3993. const normalizedBaseline = this.formatDate(this.oneKeyForm.baseline)
  3994. if (forceSync || !normalizedBaseline) {
  3995. // baseline 首次默认跟随 Delivery&Package 日期,后续可按业务独立调整。
  3996. this.oneKeyForm.baseline = normalizedRequiredDate
  3997. }
  3998. },
  3999. loadOneKeyProofingApplyOptions (projectNo, options) {
  4000. const queryOptions = options || {}
  4001. const normalizedProjectNo = this.normalizeProjectNo(projectNo)
  4002. const currentProofingNo = this.isBlankValue(this.oneKeyForm.proofingNo)
  4003. ? ''
  4004. : String(this.oneKeyForm.proofingNo).trim()
  4005. const queryToken = this.oneKeyProofingApplyQueryToken + 1
  4006. this.oneKeyProofingApplyQueryToken = queryToken
  4007. if (!normalizedProjectNo) {
  4008. this.oneKeyProofingApplyLoading = false
  4009. this.oneKeyProofingApplyOptions = []
  4010. return Promise.resolve([])
  4011. }
  4012. this.oneKeyProofingApplyLoading = true
  4013. return this.queryApplyOptionsByProjectNo(normalizedProjectNo).then(optionRows => {
  4014. if (queryToken !== this.oneKeyProofingApplyQueryToken) {
  4015. return optionRows
  4016. }
  4017. const nextOptions = optionRows.slice()
  4018. const keepCurrentIfMissing = !!queryOptions.keepCurrentIfMissing
  4019. if (currentProofingNo) {
  4020. const hasCurrent = nextOptions.some(item => item && item.applyNo === currentProofingNo)
  4021. if (!hasCurrent && keepCurrentIfMissing) {
  4022. // 历史记录可能早于 ERF 规范化,保留原打样单号避免编辑场景被强制清空。
  4023. nextOptions.unshift({
  4024. applyNo: currentProofingNo,
  4025. expectedFinishDate: this.formatDate(this.oneKeyForm.requiredDeliveryDate),
  4026. applyQuantity: ''
  4027. })
  4028. }
  4029. if (!hasCurrent && !keepCurrentIfMissing) {
  4030. this.oneKeyForm.proofingNo = ''
  4031. this.oneKeyForm.requiredDeliveryDate = ''
  4032. }
  4033. }
  4034. this.oneKeyProofingApplyOptions = nextOptions
  4035. if (currentProofingNo) {
  4036. this.handleOneKeyProofingNoChange(currentProofingNo)
  4037. }
  4038. return nextOptions
  4039. }).catch((e) => {
  4040. if (queryToken === this.oneKeyProofingApplyQueryToken) {
  4041. this.oneKeyProofingApplyOptions = []
  4042. this.$message.error((e && e.message) || '查询试验单异常')
  4043. }
  4044. return []
  4045. }).finally(() => {
  4046. if (queryToken === this.oneKeyProofingApplyQueryToken) {
  4047. this.oneKeyProofingApplyLoading = false
  4048. }
  4049. })
  4050. },
  4051. handleProofDialogProofingNoChange (proofingNo) {
  4052. const selectedNo = this.isBlankValue(proofingNo) ? '' : String(proofingNo).trim()
  4053. if (!selectedNo) {
  4054. this.proofDialogData.requiredDeliveryDate = ''
  4055. return
  4056. }
  4057. const matched = this.proofDialogApplyOptions.find(item => item && item.applyNo === selectedNo)
  4058. if (!matched) {
  4059. return
  4060. }
  4061. this.proofDialogData.proofingNo = matched.applyNo
  4062. this.proofDialogData.requiredDeliveryDate = matched.expectedFinishDate || ''
  4063. },
  4064. loadProofDialogApplyOptions (projectNo) {
  4065. const normalizedProjectNo = this.normalizeProjectNo(projectNo)
  4066. const queryToken = this.proofDialogApplyQueryToken + 1
  4067. this.proofDialogApplyQueryToken = queryToken
  4068. if (!normalizedProjectNo) {
  4069. this.proofDialogApplyLoading = false
  4070. this.proofDialogApplyOptions = []
  4071. this.proofDialogData.proofingNo = ''
  4072. this.proofDialogData.requiredDeliveryDate = ''
  4073. return Promise.resolve([])
  4074. }
  4075. this.proofDialogApplyLoading = true
  4076. return this.queryApplyOptionsByProjectNo(normalizedProjectNo).then(optionRows => {
  4077. if (queryToken !== this.proofDialogApplyQueryToken) {
  4078. return optionRows
  4079. }
  4080. this.proofDialogApplyOptions = optionRows
  4081. const currentProofingNo = this.isBlankValue(this.proofDialogData.proofingNo)
  4082. ? ''
  4083. : String(this.proofDialogData.proofingNo).trim()
  4084. if (currentProofingNo) {
  4085. this.handleProofDialogProofingNoChange(currentProofingNo)
  4086. }
  4087. return optionRows
  4088. }).catch((e) => {
  4089. if (queryToken === this.proofDialogApplyQueryToken) {
  4090. this.proofDialogApplyOptions = []
  4091. this.$message.error((e && e.message) || '查询试验单异常')
  4092. }
  4093. return []
  4094. }).finally(() => {
  4095. if (queryToken === this.proofDialogApplyQueryToken) {
  4096. this.proofDialogApplyLoading = false
  4097. }
  4098. })
  4099. },
  4100. getBuOptionValue (site, buNo) {
  4101. const rawBuNo = buNo == null ? '' : String(buNo).trim()
  4102. if (!rawBuNo) {
  4103. return ''
  4104. }
  4105. if (rawBuNo.indexOf('_') > -1) {
  4106. return rawBuNo
  4107. }
  4108. const targetSuffix = `_${rawBuNo}`
  4109. const matched = this.userBuList.find(item => item && item.buNo && (item.buNo === rawBuNo || item.buNo === `${site}_${rawBuNo}` || String(item.buNo).endsWith(targetSuffix)))
  4110. if (matched && matched.buNo) {
  4111. return matched.buNo
  4112. }
  4113. return site ? `${site}_${rawBuNo}` : rawBuNo
  4114. },
  4115. getRoleDisplayName (value) {
  4116. if (this.isBlankValue(value)) {
  4117. return ''
  4118. }
  4119. const strVal = String(value)
  4120. const splitIdx = strVal.indexOf('-')
  4121. return splitIdx > -1 ? strVal.substring(splitIdx + 1) : strVal
  4122. },
  4123. normalizeRoleValue (value) {
  4124. if (this.isBlankValue(value)) {
  4125. return ''
  4126. }
  4127. return String(value).trim()
  4128. },
  4129. getSiteByJoinedBuNo (buNo) {
  4130. const rawBuNo = buNo == null ? '' : String(buNo).trim()
  4131. if (!rawBuNo) {
  4132. return this.$store.state.user.site
  4133. }
  4134. const splitIdx = rawBuNo.indexOf('_')
  4135. if (splitIdx > 0) {
  4136. return rawBuNo.substring(0, splitIdx)
  4137. }
  4138. return this.$store.state.user.site
  4139. },
  4140. getOneKeyBuQueryContext () {
  4141. const currentBuNo = this.oneKeyForm && this.oneKeyForm.buNo
  4142. ? String(this.oneKeyForm.buNo).trim()
  4143. : ''
  4144. const normalizedBuNo = this.normalizeBuNo(currentBuNo, this.$store.state.user.site)
  4145. if (!normalizedBuNo) {
  4146. return null
  4147. }
  4148. const matchedBu = this.userBuList.find(item => {
  4149. if (!item || !item.buNo) {
  4150. return false
  4151. }
  4152. const itemBuNo = this.normalizeBuNo(item.buNo, this.$store.state.user.site)
  4153. return itemBuNo.toUpperCase() === normalizedBuNo.toUpperCase()
  4154. }) || null
  4155. const joinedBuNo = matchedBu && matchedBu.buNo ? matchedBu.buNo : currentBuNo
  4156. return {
  4157. normalizedBuNo: normalizedBuNo,
  4158. buDesc: matchedBu && matchedBu.buDesc ? String(matchedBu.buDesc).trim() : '',
  4159. site: this.getSiteByJoinedBuNo(joinedBuNo)
  4160. }
  4161. },
  4162. isOneKeyDryWetTypeVisible () {
  4163. return this.isRfidBuNo(this.oneKeyForm && this.oneKeyForm.buNo ? this.oneKeyForm.buNo : '')
  4164. },
  4165. handleOneKeyBuChange () {
  4166. // 干湿分类仅用于 RFID,切换到其他 BU 时主动清空,避免隐藏字段误提交。
  4167. if (!this.isOneKeyDryWetTypeVisible()) {
  4168. this.oneKeyForm.dry_wet_type = ''
  4169. }
  4170. },
  4171. getRowDateSortTime (row, fieldList) {
  4172. if (!row || !Array.isArray(fieldList)) {
  4173. return 0
  4174. }
  4175. for (let i = 0; i < fieldList.length; i += 1) {
  4176. const fieldKey = fieldList[i]
  4177. const rawVal = row[fieldKey]
  4178. if (this.isBlankValue(rawVal)) {
  4179. continue
  4180. }
  4181. const date = new Date(rawVal)
  4182. if (isNaN(date.getTime())) {
  4183. continue
  4184. }
  4185. return date.getTime()
  4186. }
  4187. return 0
  4188. },
  4189. sortRowsByDateDesc (rows, fieldList) {
  4190. const sourceRows = Array.isArray(rows) ? rows : []
  4191. return sourceRows.slice().sort((a, b) => {
  4192. const diff = this.getRowDateSortTime(b, fieldList) - this.getRowDateSortTime(a, fieldList)
  4193. if (diff !== 0) {
  4194. return diff
  4195. }
  4196. const leftKey = this.isBlankValue(a && a.projectNo) ? (a && a.testPartNo ? String(a.testPartNo) : '') : String(a.projectNo)
  4197. const rightKey = this.isBlankValue(b && b.projectNo) ? (b && b.testPartNo ? String(b.testPartNo) : '') : String(b.projectNo)
  4198. return leftKey.localeCompare(rightKey)
  4199. })
  4200. },
  4201. queryOneKeyProjectOptions (projectNoKeyword) {
  4202. const buContext = this.getOneKeyBuQueryContext()
  4203. if (!buContext) {
  4204. return Promise.resolve([])
  4205. }
  4206. const queryKeyword = this.isBlankValue(projectNoKeyword) ? '' : String(projectNoKeyword).trim()
  4207. const inData = {
  4208. site: buContext.site,
  4209. userName: this.$store.state.user.name,
  4210. buDesc: buContext.buDesc,
  4211. projectNo: queryKeyword,
  4212. projectDesc: '',
  4213. customerNo: '',
  4214. customerDesc: '',
  4215. projectCategory: '',
  4216. status: '',
  4217. projectManager: '',
  4218. projectOwner: '',
  4219. engineer: '',
  4220. cProjectRegion: '',
  4221. startDate: '',
  4222. endDate: '',
  4223. page: 1,
  4224. limit: 1000
  4225. }
  4226. return eamProjectInfoSearch(inData).then(({ data }) => {
  4227. if (!(data && data.code === 0)) {
  4228. return []
  4229. }
  4230. const sourceRows = (data.page && data.page.list) || []
  4231. const targetBuNo = buContext.normalizedBuNo.toUpperCase()
  4232. const targetBuDesc = this.isBlankValue(buContext.buDesc) ? '' : String(buContext.buDesc).trim().toUpperCase()
  4233. const normalizedKeyword = queryKeyword.toUpperCase()
  4234. const filteredRows = sourceRows.filter(item => {
  4235. if (!item || this.isBlankValue(item.projectNo)) {
  4236. return false
  4237. }
  4238. const rowBuNo = this.normalizeBuNo(item.buNo || item.bu || '', buContext.site).toUpperCase()
  4239. const rowBuDesc = this.isBlankValue(item.buDesc) ? '' : String(item.buDesc).trim().toUpperCase()
  4240. const matchedBu = (targetBuNo && rowBuNo && rowBuNo === targetBuNo) || (targetBuDesc && rowBuDesc && rowBuDesc === targetBuDesc)
  4241. const hasRowBuInfo = !this.isBlankValue(item.buNo || item.bu || item.buDesc)
  4242. if (hasRowBuInfo && !matchedBu) {
  4243. return false
  4244. }
  4245. if (!normalizedKeyword) {
  4246. return true
  4247. }
  4248. return String(item.projectNo).toUpperCase().indexOf(normalizedKeyword) > -1
  4249. })
  4250. return this.sortRowsByDateDesc(filteredRows, ['createDate', 'projectCreationDate', 'updateDate'])
  4251. }).catch(() => {
  4252. return []
  4253. })
  4254. },
  4255. queryOneKeyPartOptions (projectNo, partNoKeyword) {
  4256. const normalizedProjectNo = this.normalizeProjectNo(projectNo)
  4257. if (!normalizedProjectNo) {
  4258. return Promise.resolve([])
  4259. }
  4260. const buContext = this.getOneKeyBuQueryContext()
  4261. const queryKeyword = this.isBlankValue(partNoKeyword) ? '' : String(partNoKeyword).trim()
  4262. const inData = {
  4263. site: buContext ? buContext.site : this.$store.state.user.site,
  4264. userName: this.$store.state.user.name,
  4265. buDesc: buContext ? buContext.buDesc : '',
  4266. projectNo: normalizedProjectNo,
  4267. projectDesc: '',
  4268. testPartNo: queryKeyword,
  4269. partDesc: '',
  4270. customerNo: '',
  4271. customerDesc: '',
  4272. projectCategory: '',
  4273. status: '',
  4274. startDate: '',
  4275. endDate: '',
  4276. projectManager: '',
  4277. projectOwner: '',
  4278. engineer: '',
  4279. cProjectRegion: '',
  4280. finalPartNo: '',
  4281. massProductionStartDate: '',
  4282. massProductionEndDate: '',
  4283. feedingFlag: '0',
  4284. page: 1,
  4285. limit: 1000
  4286. }
  4287. return eamProjectPartSearch(inData).then(({ data }) => {
  4288. if (!(data && data.code === 0)) {
  4289. return []
  4290. }
  4291. const sourceRows = (data.page && data.page.list) || []
  4292. const targetProjectNo = normalizedProjectNo.toUpperCase()
  4293. const normalizedKeyword = queryKeyword.toUpperCase()
  4294. const filteredRows = sourceRows.filter(item => {
  4295. const rowPartNo = item && item.testPartNo ? String(item.testPartNo).trim() : ''
  4296. const rowProjectNo = this.normalizeProjectNo(item && item.projectNo ? item.projectNo : '').toUpperCase()
  4297. if (!rowPartNo || rowProjectNo !== targetProjectNo) {
  4298. return false
  4299. }
  4300. if (!normalizedKeyword) {
  4301. return true
  4302. }
  4303. return rowPartNo.toUpperCase().indexOf(normalizedKeyword) > -1
  4304. })
  4305. return this.sortRowsByDateDesc(filteredRows, ['createDate', 'updateDate', 'buildDate'])
  4306. }).catch(() => {
  4307. return []
  4308. })
  4309. },
  4310. queryOneKeyProjectSuggestions (queryString, callback) {
  4311. const queryToken = this.oneKeyProjectQueryToken + 1
  4312. this.oneKeyProjectQueryToken = queryToken
  4313. this.queryOneKeyProjectOptions(queryString).then(rows => {
  4314. if (queryToken !== this.oneKeyProjectQueryToken) {
  4315. return
  4316. }
  4317. const suggestionRows = rows.map(item => {
  4318. const projectNo = this.normalizeProjectNo(item.projectNo)
  4319. if (!projectNo) {
  4320. return null
  4321. }
  4322. return {
  4323. value: projectNo,
  4324. projectDesc: item.projectDesc || item.projectName || '',
  4325. customerDesc: item.customerDesc || '',
  4326. createDateText: this.formatDate(item.createDate || item.projectCreationDate || item.updateDate),
  4327. source: item
  4328. }
  4329. }).filter(item => !!item)
  4330. callback(suggestionRows)
  4331. }).catch(() => {
  4332. if (queryToken === this.oneKeyProjectQueryToken) {
  4333. callback([])
  4334. }
  4335. })
  4336. },
  4337. queryOneKeyPartSuggestions (queryString, callback) {
  4338. const projectNo = this.normalizeProjectNo(this.oneKeyForm.projectNo)
  4339. if (!projectNo) {
  4340. callback([])
  4341. return
  4342. }
  4343. const queryToken = this.oneKeyPartQueryToken + 1
  4344. this.oneKeyPartQueryToken = queryToken
  4345. this.queryOneKeyPartOptions(projectNo, queryString).then(rows => {
  4346. if (queryToken !== this.oneKeyPartQueryToken) {
  4347. return
  4348. }
  4349. const suggestionRows = rows.map(item => {
  4350. const partNo = item && item.testPartNo ? String(item.testPartNo).trim() : ''
  4351. if (!partNo) {
  4352. return null
  4353. }
  4354. return {
  4355. value: partNo,
  4356. partDesc: item.partDesc || '',
  4357. createDateText: this.formatDate(item.createDate || item.updateDate || item.buildDate),
  4358. source: item
  4359. }
  4360. }).filter(item => !!item)
  4361. callback(suggestionRows)
  4362. }).catch(() => {
  4363. if (queryToken === this.oneKeyPartQueryToken) {
  4364. callback([])
  4365. }
  4366. })
  4367. },
  4368. setOneKeyRoleFieldValue (fieldKey, roleValue) {
  4369. const normalizedRoleValue = this.normalizeRoleValue(roleValue)
  4370. this.oneKeyForm[fieldKey] = normalizedRoleValue
  4371. const roleCfg = this.roleConfig[fieldKey]
  4372. if (roleCfg && roleCfg.nameField) {
  4373. this.oneKeyForm[roleCfg.nameField] = this.getRoleDisplayName(normalizedRoleValue)
  4374. }
  4375. },
  4376. setOneKeyRoleFieldsFromSource (source) {
  4377. const roleSource = source || {}
  4378. ONE_KEY_ROLE_FIELDS.forEach(fieldKey => {
  4379. this.setOneKeyRoleFieldValue(fieldKey, roleSource[fieldKey])
  4380. })
  4381. },
  4382. snapshotOneKeyProjectRoles (source) {
  4383. const roleSource = source || {}
  4384. const roleBaseline = {}
  4385. ONE_KEY_ROLE_FIELDS.forEach(fieldKey => {
  4386. roleBaseline[fieldKey] = this.normalizeRoleValue(roleSource[fieldKey])
  4387. })
  4388. this.oneKeyProjectRoleBaseline = roleBaseline
  4389. },
  4390. syncOneKeyPartRoleFields (partRow) {
  4391. const changedRoleFields = []
  4392. ONE_KEY_ROLE_FIELDS.forEach(fieldKey => {
  4393. const partRoleValue = this.normalizeRoleValue(partRow && partRow[fieldKey])
  4394. if (!partRoleValue) {
  4395. return
  4396. }
  4397. const baselineRoleValue = this.normalizeRoleValue(this.oneKeyProjectRoleBaseline[fieldKey])
  4398. if (baselineRoleValue === partRoleValue) {
  4399. return
  4400. }
  4401. const currentRoleValue = this.normalizeRoleValue(this.oneKeyForm[fieldKey])
  4402. if (currentRoleValue === partRoleValue) {
  4403. return
  4404. }
  4405. this.setOneKeyRoleFieldValue(fieldKey, partRoleValue)
  4406. changedRoleFields.push(fieldKey)
  4407. })
  4408. if (changedRoleFields.length > 0) {
  4409. this.$message.info('项目料号角色人员有变动,已同步角色账号')
  4410. }
  4411. },
  4412. applyOneKeyProjectSelection (projectRow) {
  4413. const selectedProject = projectRow || {}
  4414. const projectNo = this.normalizeProjectNo(selectedProject.projectNo)
  4415. if (!projectNo) {
  4416. return
  4417. }
  4418. const previousProjectNo = this.normalizeProjectNo(this.oneKeyProjectNoSnapshot)
  4419. const projectChanged = previousProjectNo.toUpperCase() !== projectNo.toUpperCase()
  4420. this.oneKeyForm.projectNo = projectNo
  4421. this.oneKeyProjectNoSnapshot = projectNo
  4422. this.oneKeyForm.projectName = selectedProject.projectName || selectedProject.projectDesc || ''
  4423. this.oneKeyForm.projectDesc = selectedProject.projectDesc || selectedProject.projectName || ''
  4424. this.oneKeyForm.customerNo = this.isBlankValue(selectedProject.customerNo) ? '' : String(selectedProject.customerNo).trim()
  4425. this.oneKeyForm.customerDesc = selectedProject.customerDesc || ''
  4426. this.oneKeyForm.projectCreationDate = this.formatDate(selectedProject.projectCreationDate || selectedProject.createDate)
  4427. this.oneKeyForm.needDate = this.formatDate(selectedProject.needDate)
  4428. this.oneKeyForm.projectCategory = this.getProjectCategoryValue(selectedProject)
  4429. this.oneKeyForm.cProjectRegion = this.isBlankValue(selectedProject.cProjectRegion) ? '' : String(selectedProject.cProjectRegion).trim()
  4430. const rawPriority = this.isBlankValue(selectedProject.priorityLevel) ? selectedProject.priority : selectedProject.priorityLevel
  4431. this.oneKeyForm.priorityLevel = this.isBlankValue(rawPriority) ? '' : String(rawPriority).trim()
  4432. this.setOneKeyRoleFieldsFromSource(selectedProject)
  4433. this.snapshotOneKeyProjectRoles(selectedProject)
  4434. if (projectChanged) {
  4435. // 项目切换时清理料号相关字段,防止旧料号角色残留到新项目。
  4436. this.oneKeyForm.testPartNo = ''
  4437. this.oneKeyForm.partDesc = ''
  4438. this.oneKeyForm.partType = ''
  4439. this.oneKeyForm.dry_wet_type = ''
  4440. }
  4441. this.loadOneKeyProofingApplyOptions(projectNo, { keepCurrentIfMissing: false })
  4442. },
  4443. applyOneKeyPartSelection (partRow) {
  4444. const selectedPart = partRow || {}
  4445. const partNo = this.isBlankValue(selectedPart.testPartNo) ? '' : String(selectedPart.testPartNo).trim()
  4446. if (!partNo) {
  4447. return
  4448. }
  4449. this.oneKeyForm.testPartNo = partNo
  4450. if (!this.isBlankValue(selectedPart.partDesc)) {
  4451. this.oneKeyForm.partDesc = String(selectedPart.partDesc).trim()
  4452. }
  4453. if (!this.isBlankValue(selectedPart.partType)) {
  4454. this.oneKeyForm.partType = String(selectedPart.partType).trim()
  4455. }
  4456. // 接口可能返回 camelCase 或下划线字段名,这里统一兼容后回填到表单。
  4457. const selectedDryWetType = this.isBlankValue(selectedPart.dry_wet_type)
  4458. ? selectedPart.dryWetType
  4459. : selectedPart.dry_wet_type
  4460. if (this.isOneKeyDryWetTypeVisible() && !this.isBlankValue(selectedDryWetType)) {
  4461. this.oneKeyForm.dry_wet_type = String(selectedDryWetType).trim()
  4462. } else if (!this.isOneKeyDryWetTypeVisible()) {
  4463. this.oneKeyForm.dry_wet_type = ''
  4464. }
  4465. // 业务要求:料号角色与项目角色存在差异时,界面需优先回填料号角色账号。
  4466. this.syncOneKeyPartRoleFields(selectedPart)
  4467. },
  4468. handleOneKeyProjectSelect (item) {
  4469. const selectedRow = item && item.source ? item.source : null
  4470. if (!selectedRow) {
  4471. return
  4472. }
  4473. this.applyOneKeyProjectSelection(selectedRow)
  4474. },
  4475. handleOneKeyPartSelect (item) {
  4476. const selectedRow = item && item.source ? item.source : null
  4477. if (!selectedRow) {
  4478. return
  4479. }
  4480. this.applyOneKeyPartSelection(selectedRow)
  4481. },
  4482. handleOneKeyPartNoBlur () {
  4483. this.oneKeyForm.testPartNo = this.isBlankValue(this.oneKeyForm.testPartNo)
  4484. ? ''
  4485. : String(this.oneKeyForm.testPartNo).trim()
  4486. },
  4487. buildOneKeySubmitPayload () {
  4488. const inData = Object.assign({}, this.oneKeyForm, {
  4489. createBy: this.$store.state.user.name,
  4490. updateBy: this.$store.state.user.name
  4491. })
  4492. if (!inData.projectName && inData.projectDesc) {
  4493. inData.projectName = inData.projectDesc
  4494. }
  4495. if (!inData.projectDesc && inData.projectName) {
  4496. inData.projectDesc = inData.projectName
  4497. }
  4498. if (!inData.tracker) {
  4499. inData.tracker = inData.projectOwner
  4500. }
  4501. if (!inData.priorityLevel) {
  4502. inData.priorityLevel = ''
  4503. }
  4504. if (this.oneKeyDialogMode === 'create' && this.isBlankValue(inData.baseline) && !this.isBlankValue(inData.requiredDeliveryDate)) {
  4505. // 一键创建若已带出 Delivery&Package 日期,则 baseline 默认同步该日期(允许用户提交前改写)。
  4506. inData.baseline = this.formatDate(inData.requiredDeliveryDate)
  4507. }
  4508. // 干湿分类仅在 RFID 使用,非 RFID 提交时统一清空,避免旧值串到错误 BU。
  4509. if (!this.isRfidBuNo(inData.buNo)) {
  4510. inData.dry_wet_type = ''
  4511. }
  4512. // 弹窗仅展示一个立项日期输入,提交时同步到项目与物料两个日期字段,避免物料立项日期被误判为空
  4513. if (!this.isBlankValue(inData.projectCreationDate)) {
  4514. inData.buildDate = inData.projectCreationDate
  4515. } else if (!this.isBlankValue(inData.buildDate)) {
  4516. inData.projectCreationDate = inData.buildDate
  4517. }
  4518. return inData
  4519. },
  4520. validateOneKeySubmitPayload (inData) {
  4521. if (this.isBlankValue(inData.site) || this.isBlankValue(inData.buNo)) {
  4522. this.$message.error('工厂和BU不能为空')
  4523. return false
  4524. }
  4525. // create 模式在保存前要求补齐关键责任人与料号描述,避免创建后出现主责缺失数据。
  4526. if (this.oneKeyDialogMode === 'create') {
  4527. if (this.isOneKeyCreateFieldVisible('partDesc') && this.isBlankValue(inData.partDesc)) {
  4528. this.$message.error('料号描述不能为空')
  4529. return false
  4530. }
  4531. if (this.isOneKeyCreateFieldVisible('projectOwner') && this.isBlankValue(inData.projectOwner)) {
  4532. this.$message.error('PjM不能为空')
  4533. return false
  4534. }
  4535. if (this.isOneKeyCreateFieldVisible('engineer') && this.isBlankValue(inData.engineer)) {
  4536. this.$message.error('Engineer不能为空')
  4537. return false
  4538. }
  4539. }
  4540. if (this.oneKeyDialogMode === 'edit') {
  4541. if (!inData.projectId || !inData.projectPartId || !inData.trackingId) {
  4542. this.$message.error('项目/物料/打样记录ID缺失,无法修改')
  4543. return false
  4544. }
  4545. // 修改模式按业务要求仅保留 3 个必填项,其他业务字段允许为空。
  4546. if (this.isBlankValue(inData.partDesc)) {
  4547. this.$message.error('料号描述不能为空')
  4548. return false
  4549. }
  4550. if (this.isBlankValue(inData.projectOwner)) {
  4551. this.$message.error('PjM不能为空')
  4552. return false
  4553. }
  4554. if (this.isBlankValue(inData.engineer)) {
  4555. this.$message.error('Engineer不能为空')
  4556. return false
  4557. }
  4558. if (!this.isBlankValue(inData.proofingNumber) && !/^[1-9]\d*$/.test(String(inData.proofingNumber))) {
  4559. this.$message.error('打样数量必须是正整数')
  4560. return false
  4561. }
  4562. }
  4563. return true
  4564. },
  4565. buildOneKeyEditForm (current, projectData, partData) {
  4566. const project = projectData || {}
  4567. const part = partData || {}
  4568. const form = Object.assign(this.getEmptyOneKeyForm(), {
  4569. site: project.site || part.site || current.site || this.$store.state.user.site,
  4570. projectId: project.projectId || current.projectId || null,
  4571. projectPartId: part.projectPartId || this.getTrackingProjectPartId(current),
  4572. trackingId: current.trackingId || null,
  4573. buNo: this.getBuOptionValue(project.site || current.site || this.$store.state.user.site, project.buNo || current.buNo),
  4574. projectNo: project.projectNo || current.projectNo || '',
  4575. projectName: project.projectName || project.projectDesc || current.projectName || current.projectDesc || '',
  4576. projectDesc: project.projectDesc || project.projectName || current.projectDesc || current.projectName || '',
  4577. projectStatus: project.status || project.projectStatus || '草稿',
  4578. projectSource: project.projectSource || '',
  4579. testPartNo: part.testPartNo || current.testPartNo || '',
  4580. partDesc: part.partDesc || current.partDesc || '',
  4581. partName: part.partName || '',
  4582. partSpec: part.partSpec || '',
  4583. materialNumber: part.materialNumber || '',
  4584. finalPartDesc: part.finalPartDesc || '',
  4585. finalPartNo: part.finalPartNo || current.finalPartNo || '',
  4586. baseNo: part.baseNo || '',
  4587. revNo: part.revNo || '',
  4588. customerNo: project.customerNo || part.customerNo || current.customerNo || '',
  4589. customerDesc: project.customerDesc || current.customerDesc || '',
  4590. finalCustomerId: project.finalCustomerId || '',
  4591. customerRemark: project.customerRemark || '',
  4592. parentProjectNo: project.parentProjectNo || '',
  4593. oriProjectId: project.oriProjectId || '',
  4594. projectCategory: project.projectCategory || part.projectCategory || current.projectCategory || '',
  4595. cProjectRegion: project.cProjectRegion || current.cProjectRegion || '',
  4596. projectManager: project.projectManager || part.projectManager || current.projectManager || '',
  4597. projectOwner: project.projectOwner || part.projectOwner || current.projectOwner || '',
  4598. cQualityEngineer1: project.cQualityEngineer1 || part.cQualityEngineer1 || '',
  4599. cQualityEngineer2: project.cQualityEngineer2 || part.cQualityEngineer2 || '',
  4600. cQualityEngineer3: project.cQualityEngineer3 || part.cQualityEngineer3 || '',
  4601. cQualityEngineer4: project.cQualityEngineer4 || part.cQualityEngineer4 || '',
  4602. cQualityEngineer5: project.cQualityEngineer5 || part.cQualityEngineer5 || '',
  4603. cQualityEngineer6: project.cQualityEngineer6 || part.cQualityEngineer6 || '',
  4604. cManufactureEngineer: project.cManufactureEngineer || part.cManufactureEngineer || '',
  4605. docEngineer: project.docEngineer || part.docEngineer || '',
  4606. docEngineer2: project.docEngineer2 || part.docEngineer2 || '',
  4607. ipqcHardTag: project.ipqcHardTag || part.ipqcHardTag || '',
  4608. cQualityEngineer7: project.cQualityEngineer7 || part.cQualityEngineer7 || '',
  4609. partType: part.partType || '',
  4610. dry_wet_type: part.dry_wet_type || part.dryWetType || current.dry_wet_type || current.dryWetType || '',
  4611. partStatus: part.status || part.partStatus || '草稿',
  4612. projectPhase: current.projectPhase || '',
  4613. tracker: current.tracker || project.projectOwner || part.projectOwner || '',
  4614. engineer: current.engineer || project.engineer || part.engineer || '',
  4615. priorityLevel: current.priorityLevel || current.priority || project.priority || part.priority || '',
  4616. proofingNo: current.proofingNo || '',
  4617. proofingStatus: current.proofingStatus || '进行中',
  4618. proofingNumber: current.proofingNumber,
  4619. projectCreationDate: project.projectCreationDate || part.buildDate || '',
  4620. projectCloseDate: project.projectCloseDate || '',
  4621. buildDate: part.buildDate || project.projectCreationDate || '',
  4622. closeDate: part.closeDate || '',
  4623. comments: current.comments || '',
  4624. planStartDate: current.planStartDate || '',
  4625. requiredDeliveryDate: current.requiredDeliveryDate || '',
  4626. baseline: current.baseline || '',
  4627. needDate: project.needDate || part.needDate || current.needDate || '',
  4628. remark: part.remark || project.remark || current.remark || ''
  4629. })
  4630. form.projectManagerName = this.getRoleDisplayName(form.projectManager)
  4631. form.projectOwnerName = this.getRoleDisplayName(form.projectOwner)
  4632. form.engineerName = this.getRoleDisplayName(form.engineer)
  4633. form.cManufactureEngineerName = this.getRoleDisplayName(form.cManufactureEngineer)
  4634. form.cQualityEngineer1Name = this.getRoleDisplayName(form.cQualityEngineer1)
  4635. form.cQualityEngineer2Name = this.getRoleDisplayName(form.cQualityEngineer2)
  4636. form.cQualityEngineer3Name = this.getRoleDisplayName(form.cQualityEngineer3)
  4637. form.cQualityEngineer4Name = this.getRoleDisplayName(form.cQualityEngineer4)
  4638. form.cQualityEngineer5Name = this.getRoleDisplayName(form.cQualityEngineer5)
  4639. form.cQualityEngineer6Name = this.getRoleDisplayName(form.cQualityEngineer6)
  4640. form.docEngineerName = this.getRoleDisplayName(form.docEngineer)
  4641. form.docEngineer2Name = this.getRoleDisplayName(form.docEngineer2)
  4642. form.ipqcHardTagName = this.getRoleDisplayName(form.ipqcHardTag)
  4643. form.cQualityEngineer7Name = this.getRoleDisplayName(form.cQualityEngineer7)
  4644. if (!this.isRfidBuNo(form.buNo)) {
  4645. form.dry_wet_type = ''
  4646. }
  4647. return form
  4648. },
  4649. openOneKeyDialog (mode, row) {
  4650. const dialogMode = mode === 'edit' ? 'edit' : 'create'
  4651. this.oneKeyDialogMode = dialogMode
  4652. if (dialogMode !== 'edit') {
  4653. this.oneKeyProofingApplyQueryToken += 1
  4654. this.oneKeyProofingApplyLoading = false
  4655. this.oneKeyProofingApplyOptions = []
  4656. this.oneKeyProjectQueryToken += 1
  4657. this.oneKeyPartQueryToken += 1
  4658. this.oneKeyProjectNoSnapshot = ''
  4659. this.oneKeyProjectRoleBaseline = {}
  4660. this.oneKeyForm = this.getEmptyOneKeyForm()
  4661. const selectedSearchBuNo = this.getSelectedSearchBuNo()
  4662. if (selectedSearchBuNo) {
  4663. // 一键创建默认沿用查询区 BU,避免用户重复选择。
  4664. this.oneKeyForm.buNo = this.getBuOptionValue(this.searchData.site, selectedSearchBuNo)
  4665. } else if (this.userBuList.length > 0) {
  4666. this.oneKeyForm.buNo = this.userBuList[0].buNo || ''
  4667. }
  4668. this.handleOneKeyBuChange()
  4669. this.oneKeyDialogVisible = true
  4670. return
  4671. }
  4672. const current = this.getActionRow(row)
  4673. if (!current) {
  4674. this.$message.warning('请先选择一条记录后再修改项目/物料/打样')
  4675. this.oneKeyDialogMode = 'create'
  4676. return
  4677. }
  4678. const projectPartId = this.getTrackingProjectPartId(current)
  4679. if (!current.projectId || !projectPartId || !current.trackingId) {
  4680. this.$message.warning('当前记录缺少项目/物料/打样关键信息,无法修改')
  4681. this.oneKeyDialogMode = 'create'
  4682. return
  4683. }
  4684. const queryUser = this.$store.state.user.name
  4685. const querySite = current.site || this.$store.state.user.site
  4686. this.saveOneKeyLoading = true
  4687. Promise.all([
  4688. searchProjectInfoTracking({
  4689. site: querySite,
  4690. userName: queryUser,
  4691. projectId: current.projectId,
  4692. page: 1,
  4693. limit: 1
  4694. }),
  4695. searchProjectPartTracking({
  4696. site: querySite,
  4697. userName: queryUser,
  4698. projectPartId: projectPartId,
  4699. page: 1,
  4700. limit: 1
  4701. })
  4702. ]).then(([projectResp, partResp]) => {
  4703. const projectData = projectResp && projectResp.data
  4704. const partData = partResp && partResp.data
  4705. if (!projectData || projectData.code !== 0) {
  4706. throw new Error((projectData && projectData.msg) || '加载项目信息失败')
  4707. }
  4708. if (!partData || partData.code !== 0) {
  4709. throw new Error((partData && partData.msg) || '加载项目物料信息失败')
  4710. }
  4711. const projectRows = (projectData.page && projectData.page.list) || []
  4712. const partRows = (partData.page && partData.page.list) || []
  4713. if (projectRows.length === 0) {
  4714. throw new Error('未找到对应的项目信息,无法修改')
  4715. }
  4716. if (partRows.length === 0) {
  4717. throw new Error('未找到对应的项目物料信息,无法修改')
  4718. }
  4719. this.oneKeyForm = this.buildOneKeyEditForm(current, projectRows[0], partRows[0])
  4720. this.snapshotOneKeyProjectRoles(projectRows[0])
  4721. this.oneKeyProjectNoSnapshot = this.normalizeProjectNo(this.oneKeyForm.projectNo)
  4722. this.oneKeyProofingApplyQueryToken += 1
  4723. this.oneKeyProofingApplyLoading = false
  4724. this.oneKeyProofingApplyOptions = []
  4725. this.oneKeyProjectQueryToken += 1
  4726. this.oneKeyPartQueryToken += 1
  4727. this.oneKeyDialogVisible = true
  4728. this.loadOneKeyProofingApplyOptions(this.oneKeyForm.projectNo, { keepCurrentIfMissing: true })
  4729. }).catch((e) => {
  4730. this.$message.error((e && e.message) || '加载项目/物料/打样信息异常')
  4731. this.oneKeyDialogMode = 'create'
  4732. }).finally(() => {
  4733. this.saveOneKeyLoading = false
  4734. })
  4735. },
  4736. submitOneKey () {
  4737. const inData = this.buildOneKeySubmitPayload()
  4738. if (!this.validateOneKeySubmitPayload(inData)) {
  4739. return
  4740. }
  4741. this.saveOneKeyLoading = true
  4742. if (this.oneKeyDialogMode === 'edit') {
  4743. oneKeyUpdateProofTracking(inData).then(({ data }) => {
  4744. if (!data || data.code !== 0) {
  4745. throw new Error((data && data.msg) || '修改项目/物料/打样失败')
  4746. }
  4747. this.$message.success('项目/物料/打样修改成功')
  4748. this.oneKeyDialogVisible = false
  4749. this.getDataList()
  4750. }).catch((e) => {
  4751. this.$message.error((e && e.message) || '项目/物料/打样修改异常')
  4752. }).finally(() => {
  4753. this.saveOneKeyLoading = false
  4754. })
  4755. return
  4756. }
  4757. oneKeyCreateProofTracking(inData).then(({ data }) => {
  4758. this.saveOneKeyLoading = false
  4759. if (data && data.code === 0) {
  4760. this.$message.success('一键创建成功')
  4761. this.oneKeyDialogVisible = false
  4762. this.searchData.projectNo = ''
  4763. this.searchData.projectDesc = ''
  4764. this.searchData.testPartNo = ''
  4765. this.searchData.partDesc = ''
  4766. this.searchData.dryWetType = ''
  4767. this.searchData.customerNo = ''
  4768. this.searchData.proofingNo = ''
  4769. this.searchData.projectPartSyncFlag = ''
  4770. this.searchData.proofSyncFlag = ''
  4771. this.searchData.proofingStatus = ''
  4772. this.getDataList('Y')
  4773. } else {
  4774. this.$message.error(data.msg || '一键创建失败')
  4775. }
  4776. }).catch(() => {
  4777. this.saveOneKeyLoading = false
  4778. this.$message.error('一键创建异常')
  4779. })
  4780. },
  4781. openCreateProofDialog (row) {
  4782. const current = this.getActionRow(row)
  4783. if (!current) {
  4784. this.$message.warning('请先选择一条记录')
  4785. return
  4786. }
  4787. this.currentRow = current
  4788. const projectPartId = this.getTrackingProjectPartId(current) || current.id
  4789. const projectCategory = this.getProjectCategoryValue(current)
  4790. this.proofDialogApplyQueryToken += 1
  4791. this.proofDialogApplyLoading = false
  4792. this.proofDialogApplyOptions = []
  4793. this.proofDialogData = Object.assign(this.getDefaultProofDialogData(), {
  4794. trackingId: null,
  4795. site: current.site || this.$store.state.user.site,
  4796. projectId: current.projectId,
  4797. projectNo: current.projectNo,
  4798. projectDesc: current.projectDesc,
  4799. buNo: current.buNo,
  4800. customerNo: current.customerNo,
  4801. customerDesc: current.customerDesc,
  4802. projectPartId: projectPartId,
  4803. testPartNo: current.testPartNo,
  4804. partDesc: current.partDesc,
  4805. projectCategory: projectCategory,
  4806. cProjectTypeDb: projectCategory,
  4807. projectManager: current.projectManager,
  4808. projectOwner: current.projectOwner,
  4809. engineer: current.engineer,
  4810. priorityLevel: current.priority,
  4811. proofingNo: '',
  4812. proofingNumber: '',
  4813. planStartDate: '',
  4814. requiredDeliveryDate: '',
  4815. actualityDeliveryDate: '',
  4816. proofingStatus: '草稿',
  4817. remark: '',
  4818. createBy: this.$store.state.user.name,
  4819. updateBy: this.$store.state.user.name
  4820. })
  4821. if (this.isBlankValue(projectCategory)) {
  4822. this.$message.warning('未获取到项目分类,请手动选择')
  4823. }
  4824. this.proofDialogVisible = true
  4825. this.loadProofDialogApplyOptions(this.proofDialogData.projectNo)
  4826. },
  4827. collectSyncToNpiMissingLabels (row) {
  4828. const projectPartId = this.getTrackingProjectPartId(row)
  4829. const missingLabels = []
  4830. if (this.isBlankValue(row && row.site)) {
  4831. missingLabels.push('工厂')
  4832. }
  4833. if (this.isBlankValue(row && row.buNo)) {
  4834. missingLabels.push('BU')
  4835. }
  4836. if (!row || !row.projectId) {
  4837. missingLabels.push('项目ID')
  4838. }
  4839. if (this.isBlankValue(row && row.projectNo)) {
  4840. missingLabels.push('项目编码')
  4841. }
  4842. if (this.isBlankValue(row && row.projectName) && this.isBlankValue(row && row.projectDesc)) {
  4843. missingLabels.push('项目名称')
  4844. }
  4845. if (!projectPartId) {
  4846. missingLabels.push('项目物料ID')
  4847. }
  4848. if (this.isBlankValue(row && row.testPartNo)) {
  4849. missingLabels.push('项目料号')
  4850. }
  4851. if (this.isBlankValue(row && row.partDesc)) {
  4852. missingLabels.push('料号描述')
  4853. }
  4854. return missingLabels
  4855. },
  4856. getSelectedRowsForBatchSync (actionLabel) {
  4857. const rows = this.selectionRows && this.selectionRows.length > 0 ? this.selectionRows : []
  4858. if (rows.length === 0) {
  4859. this.$message.warning(`请先勾选需要${actionLabel}的记录`)
  4860. }
  4861. return rows
  4862. },
  4863. syncBatchProjectPartToNpi () {
  4864. const selectedRows = this.getSelectedRowsForBatchSync('同步项目物料到NPI')
  4865. if (selectedRows.length === 0) {
  4866. return
  4867. }
  4868. const idMap = {}
  4869. const projectPartIds = []
  4870. const invalidRows = []
  4871. selectedRows.forEach(row => {
  4872. const projectPartId = this.getTrackingProjectPartId(row)
  4873. const missingLabels = this.collectSyncToNpiMissingLabels(row)
  4874. if (!projectPartId || missingLabels.length > 0) {
  4875. invalidRows.push({
  4876. projectNo: row && row.projectNo ? row.projectNo : '-',
  4877. testPartNo: row && row.testPartNo ? row.testPartNo : '-',
  4878. missingLabels: missingLabels.length > 0 ? missingLabels : ['项目物料ID']
  4879. })
  4880. return
  4881. }
  4882. const idKey = String(projectPartId)
  4883. if (idMap[idKey]) {
  4884. return
  4885. }
  4886. idMap[idKey] = true
  4887. projectPartIds.push(projectPartId)
  4888. })
  4889. if (projectPartIds.length === 0) {
  4890. this.$message.warning('勾选记录未包含可同步的项目物料')
  4891. return
  4892. }
  4893. const invalidHint = invalidRows.length > 0
  4894. ? `<br/>其中${invalidRows.length}条记录不满足同步条件,已自动跳过`
  4895. : ''
  4896. const confirmMsg = `确认批量同步项目物料到NPI吗?<br/>已勾选${selectedRows.length}条,可同步${projectPartIds.length}${invalidHint}`
  4897. this.$confirm(confirmMsg, '提示', {
  4898. confirmButtonText: '确定',
  4899. cancelButtonText: '取消',
  4900. type: 'warning',
  4901. dangerouslyUseHTMLString: true
  4902. }).then(() => {
  4903. const operator = this.$store.state.user.name
  4904. this.syncBatchNpiLoading = true
  4905. batchSyncProjectPartTracking({
  4906. projectPartIds: projectPartIds,
  4907. updateBy: operator,
  4908. createBy: operator,
  4909. userName: operator
  4910. }).then(({ data }) => {
  4911. if (data && data.code === 0) {
  4912. const syncedCount = data.syncedCount || projectPartIds.length
  4913. this.$message.success(`批量同步成功,共${syncedCount}条项目物料`)
  4914. this.getDataList()
  4915. } else {
  4916. const failMsg = (data && data.msg) || '批量同步项目物料到NPI失败'
  4917. if (failMsg.indexOf('缺少以下必填项') > -1) {
  4918. this.$alert(failMsg, '同步校验未通过', {
  4919. confirmButtonText: '确定'
  4920. })
  4921. } else {
  4922. this.$message.error(failMsg)
  4923. }
  4924. }
  4925. }).catch(() => {
  4926. this.$message.error('批量同步项目物料到NPI异常')
  4927. }).finally(() => {
  4928. this.syncBatchNpiLoading = false
  4929. })
  4930. }).catch(() => {})
  4931. },
  4932. syncBatchProofToNpi () {
  4933. const selectedRows = this.getSelectedRowsForBatchSync('同步打样到NPI')
  4934. if (selectedRows.length === 0) {
  4935. return
  4936. }
  4937. const idMap = {}
  4938. const trackingIds = []
  4939. const invalidRows = []
  4940. selectedRows.forEach(row => {
  4941. const trackingId = row && row.trackingId
  4942. if (!trackingId) {
  4943. invalidRows.push({
  4944. projectNo: row && row.projectNo ? row.projectNo : '-',
  4945. testPartNo: row && row.testPartNo ? row.testPartNo : '-',
  4946. proofingNo: row && row.proofingNo ? row.proofingNo : '-'
  4947. })
  4948. return
  4949. }
  4950. const idKey = String(trackingId)
  4951. if (idMap[idKey]) {
  4952. return
  4953. }
  4954. idMap[idKey] = true
  4955. trackingIds.push(trackingId)
  4956. })
  4957. if (trackingIds.length === 0) {
  4958. this.$message.warning('勾选记录未包含可同步的打样单')
  4959. return
  4960. }
  4961. const invalidHint = invalidRows.length > 0
  4962. ? `<br/>其中${invalidRows.length}条记录缺少打样ID,已自动跳过`
  4963. : ''
  4964. const confirmMsg = `确认批量同步打样到NPI吗?<br/>已勾选${selectedRows.length}条,可同步${trackingIds.length}${invalidHint}`
  4965. this.$confirm(confirmMsg, '提示', {
  4966. confirmButtonText: '确定',
  4967. cancelButtonText: '取消',
  4968. type: 'warning',
  4969. dangerouslyUseHTMLString: true
  4970. }).then(() => {
  4971. const operator = this.$store.state.user.name
  4972. this.syncBatchProofLoading = true
  4973. batchSyncProofTracking({
  4974. trackingIds: trackingIds,
  4975. updateBy: operator,
  4976. createBy: operator,
  4977. userName: operator
  4978. }).then(({ data }) => {
  4979. if (data && data.code === 0) {
  4980. const syncedCount = data.syncedCount || trackingIds.length
  4981. this.$message.success(`批量同步成功,共${syncedCount}条打样记录`)
  4982. this.getDataList()
  4983. } else {
  4984. const failMsg = (data && data.msg) || '批量同步打样到NPI失败'
  4985. if (failMsg.indexOf('缺少以下必填项') > -1 || failMsg.indexOf('请先同步项目') > -1 || failMsg.indexOf('请先同步项目物料') > -1) {
  4986. this.$alert(failMsg, '同步校验未通过', {
  4987. confirmButtonText: '确定'
  4988. })
  4989. } else {
  4990. this.$message.error(failMsg)
  4991. }
  4992. }
  4993. }).catch(() => {
  4994. this.$message.error('批量同步打样到NPI异常')
  4995. }).finally(() => {
  4996. this.syncBatchProofLoading = false
  4997. })
  4998. }).catch(() => {})
  4999. },
  5000. validateProofDialogData () {
  5001. if (!this.proofDialogData.projectId || !this.proofDialogData.projectPartId) {
  5002. this.$message.warning('项目或项目料号信息不完整')
  5003. return false
  5004. }
  5005. if (!this.proofDialogData.cProjectTypeDb) {
  5006. this.$message.warning('项目分类不能为空')
  5007. return false
  5008. }
  5009. if (!this.proofDialogData.projectPhase) {
  5010. this.$message.warning('项目阶段不能为空')
  5011. return false
  5012. }
  5013. if (!this.proofDialogData.proofingNo) {
  5014. this.$message.warning('打样单号不能为空')
  5015. return false
  5016. }
  5017. if (!this.proofDialogData.proofingNumber) {
  5018. this.$message.warning('数量不能为空')
  5019. return false
  5020. }
  5021. if (!/^[1-9]\d*$/.test(String(this.proofDialogData.proofingNumber))) {
  5022. this.$alert('数量必需是大于等于0的正整数', '提示', {
  5023. confirmButtonText: '确定'
  5024. })
  5025. return false
  5026. }
  5027. if (!this.proofDialogData.planStartDate) {
  5028. this.$message.warning('打样开始日期不能为空')
  5029. return false
  5030. }
  5031. if (!this.proofDialogData.requiredDeliveryDate) {
  5032. this.$message.warning('预计完成日期不能为空')
  5033. return false
  5034. }
  5035. return true
  5036. },
  5037. buildProofPayload () {
  5038. return {
  5039. site: this.proofDialogData.site,
  5040. projectId: this.proofDialogData.projectId,
  5041. projectPartId: this.proofDialogData.projectPartId,
  5042. projectNo: this.proofDialogData.projectNo,
  5043. testPartNo: this.proofDialogData.testPartNo,
  5044. customerNo: this.proofDialogData.customerNo,
  5045. buNo: this.proofDialogData.buNo,
  5046. projectCategory: this.proofDialogData.cProjectTypeDb,
  5047. projectPhase: this.proofDialogData.projectPhase,
  5048. proofingNo: this.proofDialogData.proofingNo,
  5049. proofingNumber: Number(this.proofDialogData.proofingNumber),
  5050. planStartDate: this.proofDialogData.planStartDate,
  5051. requiredDeliveryDate: this.proofDialogData.requiredDeliveryDate,
  5052. actualityDeliveryDate: this.proofDialogData.actualityDeliveryDate,
  5053. proofingStatus: this.proofDialogData.proofingStatus || '草稿',
  5054. remark: this.proofDialogData.remark,
  5055. tracker: this.proofDialogData.projectOwner,
  5056. engineer: this.proofDialogData.engineer,
  5057. priorityLevel: this.proofDialogData.priorityLevel,
  5058. createBy: this.$store.state.user.name,
  5059. updateBy: this.$store.state.user.name
  5060. }
  5061. },
  5062. saveProofRecord () {
  5063. if (!this.validateProofDialogData()) {
  5064. return
  5065. }
  5066. const payload = this.buildProofPayload()
  5067. this.proofSaveLoading = true
  5068. createProofTrackingRecord(payload).then(({ data }) => {
  5069. this.proofSaveLoading = false
  5070. if (data && data.code === 0) {
  5071. this.$message.success(data.msg || '新增打样成功')
  5072. this.proofDialogVisible = false
  5073. this.getDataList()
  5074. } else {
  5075. this.$alert(data && data.msg ? data.msg : '新增打样失败', '错误', { confirmButtonText: '确定' })
  5076. }
  5077. }).catch(() => {
  5078. this.proofSaveLoading = false
  5079. this.$message.error('新增打样异常')
  5080. })
  5081. },
  5082. toggleBatchEdit () {
  5083. this.batchEditMode = !this.batchEditMode
  5084. if (this.batchEditMode) {
  5085. this.cancelCommentsEdit()
  5086. }
  5087. this.$nextTick(() => {
  5088. const tableRef = this.$refs.trackingTable
  5089. if (tableRef && tableRef.doLayout) {
  5090. tableRef.doLayout()
  5091. }
  5092. if (this.batchEditMode) {
  5093. setTimeout(() => {
  5094. this.scrollToRawMaterialColumn()
  5095. }, 0)
  5096. }
  5097. })
  5098. },
  5099. scrollToRawMaterialColumn () {
  5100. const tableRef = this.$refs.trackingTable
  5101. if (!tableRef || !tableRef.$el) {
  5102. return
  5103. }
  5104. const headerWrapper = tableRef.headerWrapper || tableRef.$el.querySelector('.el-table__header-wrapper')
  5105. const bodyWrapper = tableRef.bodyWrapper || tableRef.$el.querySelector('.el-table__body-wrapper')
  5106. if (!headerWrapper || !bodyWrapper) {
  5107. return
  5108. }
  5109. const headerCells = headerWrapper.querySelectorAll('th')
  5110. let targetCell = null
  5111. for (let i = 0; i < headerCells.length; i++) {
  5112. const text = (headerCells[i].innerText || '').replace(/\s+/g, ' ').trim()
  5113. if (text === 'Raw Material') {
  5114. targetCell = headerCells[i]
  5115. break
  5116. }
  5117. }
  5118. if (!targetCell) {
  5119. return
  5120. }
  5121. const fixedLeftWrapper = tableRef.$el.querySelector('.el-table__fixed')
  5122. const fixedLeftWidth = fixedLeftWrapper ? fixedLeftWrapper.offsetWidth : 0
  5123. const nextScrollLeft = Math.max(targetCell.offsetLeft - fixedLeftWidth - 8, 0)
  5124. bodyWrapper.scrollLeft = nextScrollLeft
  5125. headerWrapper.scrollLeft = nextScrollLeft
  5126. },
  5127. saveBatchPlan () {
  5128. const rows = this.selectionRows && this.selectionRows.length > 0 ? this.selectionRows : this.dataList
  5129. if (!rows || rows.length === 0) {
  5130. this.$message.warning('没有可保存的数据')
  5131. return
  5132. }
  5133. const payloadRows = rows.map(row => ({
  5134. trackingId: row.trackingId,
  5135. rawMaterialActualDate: row.rawMaterialActualDate,
  5136. cylinderCutterActualDate: row.cylinderCutterActualDate,
  5137. laminationActualDate: row.laminationActualDate,
  5138. printingActualDate: row.printingActualDate,
  5139. etchingActualDate: row.etchingActualDate,
  5140. slittingPackingActualDate: row.slittingPackingActualDate,
  5141. bondingActualDate: row.bondingActualDate,
  5142. cl60Cl822ActualDate: row.cl60Cl822ActualDate,
  5143. spottingActualDate: row.spottingActualDate,
  5144. encodingActualDate: row.encodingActualDate,
  5145. dieCutActualDate: row.dieCutActualDate,
  5146. inspectionActualDate: row.inspectionActualDate,
  5147. deliveryPackageActualDate: row.deliveryPackageActualDate,
  5148. comments: row.comments == null ? '' : String(row.comments),
  5149. updateBy: this.$store.state.user.name
  5150. }))
  5151. batchUpdateProofTrackingPlan({
  5152. updateBy: this.$store.state.user.name,
  5153. rows: payloadRows
  5154. }).then(({ data }) => {
  5155. if (data && data.code === 0) {
  5156. this.$message.success('批量修改成功')
  5157. this.batchEditMode = false
  5158. this.getDataList()
  5159. } else {
  5160. this.$message.error(data.msg || '批量修改失败')
  5161. }
  5162. }).catch(() => {
  5163. this.$message.error('批量修改异常')
  5164. })
  5165. },
  5166. handleProcessDateChange (row, processCol, dateVal) {
  5167. if (!row || !processCol) {
  5168. return
  5169. }
  5170. const actualDate = this.formatDate(dateVal)
  5171. // 业务要求:表格选择日期仅更新日期,不自动改变工序状态(状态由“√”动作控制)。
  5172. const currentStatus = row[processCol.statusField] ? String(row[processCol.statusField]).trim() : ''
  5173. const nextStatus = currentStatus || '未完成'
  5174. this.updateProcessFromTable(row, processCol, {
  5175. status: nextStatus,
  5176. actualDate: actualDate,
  5177. remark: actualDate ? '表格直接修改实际完成日期(状态不变)' : '表格清空实际完成日期(状态不变)'
  5178. }, true)
  5179. },
  5180. markProcessComplete (row, processCol) {
  5181. if (!row || !processCol || this.isProcessStatusComplete(row, processCol)) {
  5182. return
  5183. }
  5184. // 若用户已手工选择日期,打勾仅改状态,不覆盖已选日期;无日期时才补当天。
  5185. const currentActualDate = this.formatDate(row[processCol.actualField])
  5186. const nextActualDate = currentActualDate || this.formatDate(new Date())
  5187. this.updateProcessFromTable(row, processCol, {
  5188. status: '已完成',
  5189. actualDate: nextActualDate,
  5190. remark: '表格一键完成'
  5191. }, false, '工序已标记为完成')
  5192. },
  5193. handleProcessDateDoubleClick (row, processCol) {
  5194. if (!row || !processCol || !this.isProcessStatusComplete(row, processCol)) {
  5195. return
  5196. }
  5197. // 按页面交互约定:已完成工序允许双击日期输入框进行回退。
  5198. this.rollbackProcessComplete(row, processCol)
  5199. },
  5200. rollbackProcessComplete (row, processCol) {
  5201. if (!row || !processCol || !this.isProcessStatusComplete(row, processCol)) {
  5202. return
  5203. }
  5204. const currentActualDate = this.formatDate(row[processCol.actualField])
  5205. this.$confirm('确定回退该工序状态为“未完成”吗?', '提示', {
  5206. confirmButtonText: '确定',
  5207. cancelButtonText: '取消',
  5208. type: 'warning'
  5209. }).then(() => {
  5210. // 仅回退状态,保留当前实际完成日期,避免误回退时丢失用户已维护的日期信息。
  5211. this.updateProcessFromTable(row, processCol, {
  5212. status: '未完成',
  5213. actualDate: currentActualDate,
  5214. remark: '表格一键回退为未完成'
  5215. }, false, '工序状态已回退为未完成')
  5216. }).catch(() => {})
  5217. },
  5218. updateProcessFromTable (row, processCol, processData, silentSuccess, successMsg) {
  5219. if (!row || !processCol || !row.trackingId) {
  5220. this.$message.error('工序参数缺失')
  5221. return
  5222. }
  5223. const status = processData && processData.status ? String(processData.status).trim() : ''
  5224. if (!status) {
  5225. this.$message.error('工序状态不能为空')
  5226. return
  5227. }
  5228. const actualDate = processData && processData.actualDate ? this.formatDate(processData.actualDate) : ''
  5229. const inData = {
  5230. trackingId: row.trackingId,
  5231. processCode: processCol.code,
  5232. processName: processCol.label,
  5233. status: status,
  5234. actualDate: actualDate || null,
  5235. remark: processData && processData.remark ? processData.remark : '',
  5236. updateBy: this.$store.state.user.name
  5237. }
  5238. updateProofTrackingProcess(inData).then(({ data }) => {
  5239. if (data && data.code === 0) {
  5240. this.updateRowProcessValue(row, processCol, status, actualDate)
  5241. const cacheKey = `${row.trackingId}_${processCol.code}`
  5242. if (this.processHistoryMap[cacheKey]) {
  5243. this.$delete(this.processHistoryMap, cacheKey)
  5244. }
  5245. if (!silentSuccess) {
  5246. this.$message.success(successMsg || '工序更新成功')
  5247. }
  5248. // 按业务要求:工序日期或状态变更后回查列表,保证动态列/派生字段展示最新值。
  5249. this.getDataList()
  5250. } else {
  5251. this.$message.error(data.msg || '工序更新失败')
  5252. this.getDataList()
  5253. }
  5254. }).catch(() => {
  5255. this.$message.error('工序更新异常')
  5256. this.getDataList()
  5257. })
  5258. },
  5259. openProcessDialog (row, processCol) {
  5260. if (!row || !processCol) {
  5261. return
  5262. }
  5263. this.currentRow = row
  5264. this.processForm = {
  5265. trackingId: row.trackingId,
  5266. processCode: processCol.code,
  5267. processName: processCol.label,
  5268. status: row[processCol.statusField] || '未完成',
  5269. actualDate: row[processCol.actualField] || '',
  5270. remark: ''
  5271. }
  5272. this.processHistoryRows = []
  5273. this.processDialogVisible = true
  5274. this.loadProcessHistory(row, processCol, true)
  5275. },
  5276. submitProcessUpdate () {
  5277. if (!this.processForm.trackingId || !this.processForm.processCode) {
  5278. this.$message.error('参数缺失')
  5279. return
  5280. }
  5281. if (!this.processForm.status) {
  5282. this.$message.error('请选择状态')
  5283. return
  5284. }
  5285. const inData = Object.assign({}, this.processForm, {
  5286. updateBy: this.$store.state.user.name
  5287. })
  5288. this.saveProcessLoading = true
  5289. updateProofTrackingProcess(inData).then(({ data }) => {
  5290. this.saveProcessLoading = false
  5291. if (data && data.code === 0) {
  5292. this.$message.success('工序更新成功')
  5293. this.processDialogVisible = false
  5294. this.processHistoryMap = {}
  5295. this.getDataList()
  5296. } else {
  5297. this.$message.error(data.msg || '工序更新失败')
  5298. }
  5299. }).catch(() => {
  5300. this.saveProcessLoading = false
  5301. this.$message.error('工序更新异常')
  5302. })
  5303. },
  5304. finishProof (row) {
  5305. const current = row || this.currentRow
  5306. if (!current || !current.trackingId) {
  5307. this.$message.warning('请先选择记录')
  5308. return
  5309. }
  5310. this.finishForm = {
  5311. trackingId: current.trackingId,
  5312. actualityDeliveryDate: current.actualityDeliveryDate || this.formatDate(new Date())
  5313. }
  5314. this.finishDialogVisible = true
  5315. },
  5316. deleteProof (row) {
  5317. const current = row || this.currentRow
  5318. if (!current || !current.trackingId) {
  5319. this.$message.warning('请先选择记录')
  5320. return
  5321. }
  5322. this.$confirm('确定删除该打样记录吗?该操作仅删除打样记录,不会删除项目和项目物料。', '提示', {
  5323. confirmButtonText: '确定',
  5324. cancelButtonText: '取消',
  5325. type: 'warning'
  5326. }).then(() => {
  5327. deleteProofTracking({
  5328. trackingId: current.trackingId,
  5329. updateBy: this.$store.state.user.name
  5330. }).then(({ data }) => {
  5331. if (data && data.code === 0) {
  5332. this.$message.success('打样记录删除成功')
  5333. this.processHistoryMap = {}
  5334. this.resetProcessTooltip()
  5335. this.getDataList()
  5336. } else {
  5337. this.$message.error((data && data.msg) || '删除打样记录失败')
  5338. }
  5339. }).catch(() => {
  5340. this.$message.error('删除打样记录异常')
  5341. })
  5342. }).catch(() => {})
  5343. },
  5344. submitFinishProof () {
  5345. if (!this.finishForm.trackingId) {
  5346. this.$message.warning('跟踪记录ID不能为空')
  5347. return
  5348. }
  5349. if (!this.finishForm.actualityDeliveryDate) {
  5350. this.$message.warning('请选择实际完成日期')
  5351. return
  5352. }
  5353. this.finishSaveLoading = true
  5354. finishProofTracking({
  5355. trackingId: this.finishForm.trackingId,
  5356. actualityDeliveryDate: this.finishForm.actualityDeliveryDate,
  5357. updateBy: this.$store.state.user.name
  5358. }).then(({ data }) => {
  5359. this.finishSaveLoading = false
  5360. if (data && data.code === 0) {
  5361. this.$message.success('打样完成成功')
  5362. this.finishDialogVisible = false
  5363. this.getDataList()
  5364. } else {
  5365. this.$message.error(data.msg || '打样完成失败')
  5366. }
  5367. }).catch(() => {
  5368. this.finishSaveLoading = false
  5369. this.$message.error('打样完成异常')
  5370. })
  5371. },
  5372. getProcessCacheKey (row, processCol) {
  5373. if (!row || !processCol || !row.trackingId) {
  5374. return ''
  5375. }
  5376. return `${row.trackingId}_${processCol.code}`
  5377. },
  5378. resetProcessTooltip () {
  5379. this.processTooltipHoverKey = ''
  5380. this.processTooltip.visible = false
  5381. this.processTooltip.content = ''
  5382. },
  5383. hideProcessTooltip () {
  5384. this.processTooltip.visible = false
  5385. this.processTooltip.content = ''
  5386. },
  5387. updateProcessTooltipPosition (event) {
  5388. if (!event) {
  5389. return
  5390. }
  5391. const offsetX = 12
  5392. const offsetY = 16
  5393. let left = event.clientX + offsetX
  5394. let top = event.clientY + offsetY
  5395. const maxWidth = 220
  5396. const maxHeight = 120
  5397. if (left + maxWidth > window.innerWidth - 8) {
  5398. left = window.innerWidth - maxWidth - 8
  5399. }
  5400. if (top + maxHeight > window.innerHeight - 8) {
  5401. top = event.clientY - maxHeight - 8
  5402. }
  5403. this.processTooltip.left = Math.max(8, left)
  5404. this.processTooltip.top = Math.max(8, top)
  5405. },
  5406. handleProcessTooltipMouseMove (event) {
  5407. if (!this.processTooltip.visible) {
  5408. return
  5409. }
  5410. this.updateProcessTooltipPosition(event)
  5411. },
  5412. handleProcessTooltipMouseLeave () {
  5413. this.processTooltipHoverKey = ''
  5414. this.hideProcessTooltip()
  5415. },
  5416. handleProcessTooltipMouseEnter (row, processCol, event) {
  5417. const cacheKey = this.getProcessCacheKey(row, processCol)
  5418. if (!cacheKey) {
  5419. return
  5420. }
  5421. this.processTooltipHoverKey = cacheKey
  5422. this.updateProcessTooltipPosition(event)
  5423. const showTooltip = () => {
  5424. if (this.processTooltipHoverKey !== cacheKey) {
  5425. return
  5426. }
  5427. if (!this.hasProcessHistory(row, processCol)) {
  5428. this.hideProcessTooltip()
  5429. return
  5430. }
  5431. const content = this.getProcessTooltip(row, processCol)
  5432. if (!content) {
  5433. this.hideProcessTooltip()
  5434. return
  5435. }
  5436. this.processTooltip.content = content
  5437. this.processTooltip.visible = true
  5438. }
  5439. if (Object.prototype.hasOwnProperty.call(this.processHistoryMap, cacheKey)) {
  5440. showTooltip()
  5441. return
  5442. }
  5443. this.loadProcessHistory(row, processCol, false).then(() => {
  5444. showTooltip()
  5445. })
  5446. },
  5447. loadProcessHistory (row, processCol, refreshDialog) {
  5448. if (!row || !row.trackingId) {
  5449. return Promise.resolve([])
  5450. }
  5451. const cacheKey = this.getProcessCacheKey(row, processCol)
  5452. if (Object.prototype.hasOwnProperty.call(this.processHistoryMap, cacheKey)) {
  5453. const cachedRows = this.processHistoryMap[cacheKey] || []
  5454. if (refreshDialog) {
  5455. this.processHistoryRows = cachedRows
  5456. }
  5457. return Promise.resolve(cachedRows)
  5458. }
  5459. return queryProofTrackingProcessHistory({
  5460. trackingId: row.trackingId,
  5461. processCode: processCol.code
  5462. }).then(({ data }) => {
  5463. let rows = []
  5464. if (data && data.code === 0) {
  5465. rows = data.rows || []
  5466. // Vue2 对对象新增 key 需要使用 $set 才能触发视图更新(tooltip 才会刷新历史列表)
  5467. this.$set(this.processHistoryMap, cacheKey, rows)
  5468. if (refreshDialog) {
  5469. this.processHistoryRows = rows
  5470. }
  5471. }
  5472. return rows
  5473. }).catch(() => {
  5474. if (refreshDialog) {
  5475. this.processHistoryRows = []
  5476. }
  5477. this.$set(this.processHistoryMap, cacheKey, [])
  5478. return []
  5479. })
  5480. },
  5481. hasProcessHistory (row, processCol) {
  5482. if (!row || !processCol || !row.trackingId) {
  5483. return false
  5484. }
  5485. const cacheKey = `${row.trackingId}_${processCol.code}`
  5486. const rows = this.processHistoryMap[cacheKey] || []
  5487. return rows.length > 0
  5488. },
  5489. getProcessTooltip (row, processCol) {
  5490. if (!row || !processCol) {
  5491. return ''
  5492. }
  5493. const cacheKey = row.trackingId ? `${row.trackingId}_${processCol.code}` : ''
  5494. const rows = this.processHistoryMap[cacheKey] || []
  5495. const dateList = []
  5496. const pushDate = (dateVal) => {
  5497. const formatted = this.formatDate(dateVal)
  5498. if (!formatted) {
  5499. return
  5500. }
  5501. if (dateList.indexOf(formatted) === -1) {
  5502. dateList.push(formatted)
  5503. }
  5504. }
  5505. // 先放当前实际完成日期,再放历史变更日期(实际)
  5506. pushDate(row[processCol.actualField])
  5507. rows.forEach(item => {
  5508. pushDate(item.newActualDate)
  5509. pushDate(item.oldActualDate)
  5510. })
  5511. if (dateList.length <= 1) {
  5512. return ''
  5513. }
  5514. // tooltip 只展示历史日期,不展示最近一次日期(当前值)以避免重复。
  5515. const latestDate = dateList.reduce((max, currentDate) => {
  5516. if (!max || currentDate > max) {
  5517. return currentDate
  5518. }
  5519. return max
  5520. }, '')
  5521. const historyDateList = dateList.filter(item => item !== latestDate)
  5522. if (historyDateList.length === 0) {
  5523. return ''
  5524. }
  5525. return historyDateList.join('\n')
  5526. },
  5527. isProcessStatusComplete (row, processCol) {
  5528. if (!row || !processCol) {
  5529. return false
  5530. }
  5531. const statusVal = row[processCol.statusField]
  5532. const status = statusVal ? String(statusVal).trim() : ''
  5533. if (!status || status === '未完成' || status === '进行中') {
  5534. return false
  5535. }
  5536. if (status === '已完成' || status === '打样完成') {
  5537. return true
  5538. }
  5539. return status.indexOf('完成') > -1 && status.indexOf('未') === -1
  5540. },
  5541. isProcessComplete (row, processCol) {
  5542. if (!row || !processCol) {
  5543. return false
  5544. }
  5545. const statusVal = row[processCol.statusField]
  5546. const status = statusVal ? String(statusVal).trim() : ''
  5547. if (!status) {
  5548. // 兼容历史数据:若没有状态但有实际完成日期,也按完成处理
  5549. return !!this.formatDate(row[processCol.actualField])
  5550. }
  5551. if (status === '未完成' || status === '进行中') {
  5552. return false
  5553. }
  5554. if (status === '已完成' || status === '打样完成') {
  5555. return true
  5556. }
  5557. return status.indexOf('完成') > -1 && status.indexOf('未') === -1
  5558. },
  5559. getTrackingProcessColumnsByRow (row) {
  5560. if (!row) {
  5561. return []
  5562. }
  5563. const processColumnMap = {}
  5564. const appendProcessColumn = (processCol) => {
  5565. if (!processCol || !processCol.code || processColumnMap[processCol.code]) {
  5566. return
  5567. }
  5568. processColumnMap[processCol.code] = processCol
  5569. }
  5570. this.processColumns.forEach(item => {
  5571. appendProcessColumn(item)
  5572. })
  5573. // 兜底补齐默认工序元信息,避免配置变化导致状态字段找不到。
  5574. DEFAULT_PROCESS_COLUMNS.forEach(item => {
  5575. appendProcessColumn(item)
  5576. })
  5577. let orderedCodes = []
  5578. if (this.getSelectedSearchBuNo()) {
  5579. orderedCodes = this.visibleProcessColumns.map(item => item.code)
  5580. } else {
  5581. const configEntry = this.getBuProcessConfigEntry(row.buNo, row.site)
  5582. const config = configEntry ? configEntry.config : null
  5583. orderedCodes = config && Array.isArray(config.processOrderCodes) && config.processOrderCodes.length > 0
  5584. ? this.getValidProcessOrderCodes(config.processOrderCodes)
  5585. : this.getAllProcessCodes()
  5586. const visibleCodes = this.getProcessVisibleCodesByBuNo(row.buNo, row.site)
  5587. if (visibleCodes) {
  5588. const visibleCodeMap = {}
  5589. visibleCodes.forEach(code => {
  5590. visibleCodeMap[code] = true
  5591. })
  5592. orderedCodes = orderedCodes.filter(code => visibleCodeMap[code])
  5593. }
  5594. }
  5595. const result = []
  5596. const usedCodeMap = {}
  5597. orderedCodes.forEach(code => {
  5598. const codeVal = code == null ? '' : String(code).trim()
  5599. if (!codeVal || usedCodeMap[codeVal]) {
  5600. return
  5601. }
  5602. const processCol = processColumnMap[codeVal]
  5603. if (!processCol) {
  5604. return
  5605. }
  5606. usedCodeMap[codeVal] = true
  5607. result.push(processCol)
  5608. })
  5609. return result
  5610. },
  5611. getTrackingStatusProcessLabel (row, processCol) {
  5612. if (!row || !processCol) {
  5613. return ''
  5614. }
  5615. const processCategoryName = processCol.processCategoryName == null
  5616. ? ''
  5617. : String(processCol.processCategoryName).trim()
  5618. if (processCategoryName) {
  5619. return processCategoryName
  5620. }
  5621. const processCategoryCode = this.getBuProcessCategoryCodeByProcessCode(row.buNo, processCol.code)
  5622. const buCategoryName = this.getBuProcessCategoryNameByCode(row.buNo, processCategoryCode)
  5623. if (buCategoryName) {
  5624. return buCategoryName
  5625. }
  5626. return processCol.label || processCol.code
  5627. },
  5628. getPendingProcessColumnForTrackingStatus (row) {
  5629. const processColumns = this.getTrackingProcessColumnsByRow(row)
  5630. let firstPendingWithDate = null
  5631. let firstPendingDate = ''
  5632. let firstPendingWithoutDate = null
  5633. for (let i = 0; i < processColumns.length; i += 1) {
  5634. const processCol = processColumns[i]
  5635. if (this.isProcessComplete(row, processCol)) {
  5636. continue
  5637. }
  5638. const actualDate = this.formatDate(row[processCol.actualField])
  5639. if (!actualDate) {
  5640. // 无日期的未完成工序仅作为兜底:优先展示“有日期的未完成工序”。
  5641. if (!firstPendingWithoutDate) {
  5642. firstPendingWithoutDate = processCol
  5643. }
  5644. continue
  5645. }
  5646. // 同日期按工序顺序兜底:仅在日期更小时替换,日期相同保留先命中的工序。
  5647. if (!firstPendingWithDate || actualDate < firstPendingDate) {
  5648. firstPendingWithDate = processCol
  5649. firstPendingDate = actualDate
  5650. }
  5651. }
  5652. return firstPendingWithDate || firstPendingWithoutDate
  5653. },
  5654. getFirstPendingProcessLabel (row) {
  5655. const processCol = this.getPendingProcessColumnForTrackingStatus(row)
  5656. if (!processCol) {
  5657. return '已全部完成'
  5658. }
  5659. return this.getTrackingStatusProcessLabel(row, processCol)
  5660. },
  5661. getTrackingStatusType (row) {
  5662. if (!row) {
  5663. return ''
  5664. }
  5665. const requiredDeliveryDate = this.formatDate(row.requiredDeliveryDate)
  5666. if (!requiredDeliveryDate) {
  5667. return ''
  5668. }
  5669. const today = this.formatDate(new Date())
  5670. // 按业务口径:今日 <= 预计完成日期 判定为 On schedule,其余判定为 Delay。
  5671. if (today <= requiredDeliveryDate) {
  5672. return 'onSchedule'
  5673. }
  5674. return 'delay'
  5675. },
  5676. getTrackingStatusText (row) {
  5677. const statusType = this.getTrackingStatusType(row)
  5678. if (!statusType) {
  5679. return '-'
  5680. }
  5681. const statusPrefix = statusType === 'onSchedule' ? 'On schedule' : 'Delay'
  5682. return `${statusPrefix}-${this.getFirstPendingProcessLabel(row)}`
  5683. },
  5684. getTrackingStatusClass (row) {
  5685. const statusType = this.getTrackingStatusType(row)
  5686. if (statusType === 'onSchedule') {
  5687. return 'tracking-status tracking-status--on-schedule'
  5688. }
  5689. if (statusType === 'delay') {
  5690. return 'tracking-status tracking-status--delay'
  5691. }
  5692. return 'tracking-status tracking-status--unknown'
  5693. },
  5694. formatDate (val) {
  5695. if (!val) {
  5696. return ''
  5697. }
  5698. if (typeof val === 'string') {
  5699. return val.length > 10 ? val.substring(0, 10) : val
  5700. }
  5701. const date = new Date(val)
  5702. if (isNaN(date.getTime())) {
  5703. return ''
  5704. }
  5705. const y = date.getFullYear()
  5706. const m = `${date.getMonth() + 1}`.padStart(2, '0')
  5707. const d = `${date.getDate()}`.padStart(2, '0')
  5708. return `${y}-${m}-${d}`
  5709. },
  5710. formatDateTime (val) {
  5711. if (!val) {
  5712. return ''
  5713. }
  5714. if (typeof val === 'string') {
  5715. return val.length > 19 ? val.substring(0, 19) : val
  5716. }
  5717. const date = new Date(val)
  5718. if (isNaN(date.getTime())) {
  5719. return ''
  5720. }
  5721. const y = date.getFullYear()
  5722. const m = `${date.getMonth() + 1}`.padStart(2, '0')
  5723. const d = `${date.getDate()}`.padStart(2, '0')
  5724. const hh = `${date.getHours()}`.padStart(2, '0')
  5725. const mm = `${date.getMinutes()}`.padStart(2, '0')
  5726. const ss = `${date.getSeconds()}`.padStart(2, '0')
  5727. return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
  5728. }
  5729. }
  5730. }
  5731. </script>
  5732. <style >
  5733. .data-table .cell {
  5734. line-height: 26px;
  5735. font-size: 12px;
  5736. height: 28px;
  5737. }
  5738. .search-form {
  5739. margin-bottom: 0;
  5740. }
  5741. .button-group-col {
  5742. width: 100%;
  5743. margin-top: 16px;
  5744. }
  5745. .button-row {
  5746. width: 100%;
  5747. flex-wrap: wrap;
  5748. }
  5749. .bu-process-config-dialog .bu-process-order-list {
  5750. display: grid;
  5751. grid-template-columns: repeat(3, minmax(220px, 1fr));
  5752. column-gap: 12px;
  5753. row-gap: 8px;
  5754. align-items: center;
  5755. }
  5756. .bu-process-config-dialog .bu-process-list-column .cell {
  5757. white-space: normal !important;
  5758. height: auto !important;
  5759. line-height: 20px;
  5760. padding-top: 6px;
  5761. padding-bottom: 6px;
  5762. overflow: hidden;
  5763. }
  5764. .bu-process-config-dialog .bu-process-list-column {
  5765. vertical-align: top;
  5766. }
  5767. .bu-process-config-dialog .bu-process-order-item {
  5768. display: flex;
  5769. align-items: center;
  5770. gap: 6px;
  5771. min-height: 28px;
  5772. padding: 2px 6px;
  5773. border: 1px solid transparent;
  5774. border-radius: 4px;
  5775. cursor: move;
  5776. transition: border-color .2s ease, background-color .2s ease;
  5777. }
  5778. .bu-process-config-dialog .bu-process-order-item:hover {
  5779. border-color: #7cc4ff;
  5780. background: #f5fbff;
  5781. }
  5782. .bu-process-config-dialog .bu-process-order-item.is-dragging {
  5783. opacity: .55;
  5784. border-color: #409eff;
  5785. background: #ecf5ff;
  5786. }
  5787. .bu-process-config-dialog .process-order-index {
  5788. width: 22px;
  5789. color: #606266;
  5790. font-size: 12px;
  5791. text-align: right;
  5792. margin-right: 4px;
  5793. flex-shrink: 0;
  5794. }
  5795. .bu-process-config-dialog .process-order-handle {
  5796. margin-right: 6px;
  5797. color: #909399;
  5798. font-size: 14px;
  5799. }
  5800. .bu-process-config-dialog .bu-process-order-item .el-checkbox,
  5801. .bu-process-config-dialog .bu-process-order-item .el-checkbox + .el-checkbox {
  5802. width: 100%;
  5803. margin: 0 !important;
  5804. }
  5805. .bu-process-config-dialog .bu-process-order-item .el-checkbox__label {
  5806. white-space: nowrap;
  5807. }
  5808. .bu-process-config-dialog .bu-process-order-item .el-checkbox {
  5809. flex: 1;
  5810. min-width: 0;
  5811. }
  5812. .bu-process-config-dialog .bu-process-category-select {
  5813. width: 100px;
  5814. flex-shrink: 0;
  5815. }
  5816. .bu-process-config-dialog .bu-process-config-tip {
  5817. display: flex;
  5818. align-items: center;
  5819. gap: 6px;
  5820. margin-bottom: 8px;
  5821. padding: 6px 10px;
  5822. border-radius: 4px;
  5823. background: #f4f8ff;
  5824. color: #5f6b7a;
  5825. font-size: 12px;
  5826. }
  5827. .bu-process-config-dialog .bu-process-config-tip i {
  5828. color: #409eff;
  5829. }
  5830. .default-process-config-dialog .default-process-config-actions {
  5831. margin-bottom: 8px;
  5832. }
  5833. .default-process-config-dialog .bu-process-config-tip {
  5834. display: flex;
  5835. align-items: center;
  5836. gap: 6px;
  5837. margin-bottom: 8px;
  5838. padding: 6px 10px;
  5839. border-radius: 4px;
  5840. background: #f4f8ff;
  5841. color: #5f6b7a;
  5842. font-size: 12px;
  5843. }
  5844. .default-process-config-dialog .bu-process-config-tip i {
  5845. color: #409eff;
  5846. }
  5847. .default-process-config-dialog .default-process-config-empty {
  5848. margin: 12px 0;
  5849. padding: 12px;
  5850. border: 1px dashed #dcdfe6;
  5851. border-radius: 4px;
  5852. color: #909399;
  5853. text-align: center;
  5854. }
  5855. .default-process-config-dialog .default-process-drag-handle {
  5856. color: #909399;
  5857. cursor: move;
  5858. font-size: 15px;
  5859. margin-right: 6px;
  5860. }
  5861. .default-process-config-dialog .default-process-order-index {
  5862. color: #606266;
  5863. font-size: 12px;
  5864. }
  5865. .default-process-config-dialog .el-table__body-wrapper tbody tr {
  5866. cursor: move;
  5867. }
  5868. .default-process-config-dialog .el-table__body-wrapper tbody tr .el-input__inner {
  5869. cursor: text;
  5870. }
  5871. .default-process-config-dialog .el-table__body-wrapper tbody tr .el-checkbox__inner,
  5872. .default-process-config-dialog .el-table__body-wrapper tbody tr .el-select .el-input__inner {
  5873. cursor: pointer;
  5874. }
  5875. .process-category-config-dialog .default-process-config-actions {
  5876. margin-bottom: 8px;
  5877. }
  5878. .process-category-config-dialog .el-table__body-wrapper tbody tr .el-input__inner {
  5879. cursor: text;
  5880. }
  5881. .default-process-sortable-ghost > td {
  5882. background: #f0f9ff !important;
  5883. }
  5884. .default-process-sortable-chosen > td {
  5885. background: #ecf5ff !important;
  5886. }
  5887. .button-col {
  5888. margin-bottom: 8px;
  5889. }
  5890. .button-col .el-button {
  5891. width: 100%;
  5892. }
  5893. .one-key-grid-form {
  5894. padding: 0 8px;
  5895. }
  5896. .one-key-grid-form .el-form-item {
  5897. margin-bottom: 10px;
  5898. }
  5899. .one-key-suggest-main {
  5900. color: #303133;
  5901. font-size: 12px;
  5902. line-height: 18px;
  5903. }
  5904. .one-key-suggest-sub {
  5905. color: #909399;
  5906. font-size: 12px;
  5907. line-height: 16px;
  5908. }
  5909. .proof-grid-form {
  5910. padding: 0 8px;
  5911. }
  5912. .proof-grid-form .el-form-item {
  5913. margin-bottom: 10px;
  5914. }
  5915. .apply-qty-reference {
  5916. margin-top: 6px;
  5917. padding: 4px 8px;
  5918. border-radius: 4px;
  5919. background: #ecf5ff;
  5920. color: #3d77b1;
  5921. font-size: 12px;
  5922. line-height: 18px;
  5923. }
  5924. .big-label a {
  5925. color: #35b4b4;
  5926. }
  5927. .sync-text--ok {
  5928. color: #67c23a;
  5929. font-weight: 600;
  5930. }
  5931. .sync-text--pending {
  5932. color: #606266;
  5933. }
  5934. .tracking-status {
  5935. width: 100%;
  5936. min-height: 26px;
  5937. padding: 4px 6px;
  5938. box-sizing: border-box;
  5939. border: 1px solid #dcdfe6;
  5940. border-radius: 4px;
  5941. background: #fff;
  5942. color: #303133;
  5943. font-weight: 500;
  5944. line-height: 18px;
  5945. text-align: left;
  5946. overflow: hidden;
  5947. text-overflow: ellipsis;
  5948. white-space: nowrap;
  5949. }
  5950. .tracking-status--on-schedule {
  5951. background: #e8f5e9;
  5952. }
  5953. .tracking-status--delay {
  5954. background: #fde2e2;
  5955. }
  5956. .tracking-status--unknown {
  5957. background: #f4f4f5;
  5958. }
  5959. .process-cell {
  5960. background: #fff;
  5961. min-height: 44px;
  5962. padding: 4px 2px;
  5963. }
  5964. .process-cell.is-complete {
  5965. background: #ececec;
  5966. }
  5967. .process-cell.is-complete .el-input__inner {
  5968. background: #ececec;
  5969. border-color: #d5d7de;
  5970. color: #8c8c8c;
  5971. }
  5972. .process-cell.is-complete .el-input.is-disabled .el-input__inner {
  5973. background: #ececec;
  5974. color: #8c8c8c;
  5975. }
  5976. .process-cell.is-complete .link-date {
  5977. color: #8c8c8c;
  5978. }
  5979. .process-cell.is-complete .link-date:hover,
  5980. .process-cell.is-complete .link-date:focus {
  5981. color: #8c8c8c;
  5982. }
  5983. .process-cell--masked {
  5984. background: transparent !important;
  5985. }
  5986. .process-content {
  5987. display: flex;
  5988. align-items: center;
  5989. justify-content: center;
  5990. gap: 4px;
  5991. text-align: center;
  5992. line-height: 18px;
  5993. }
  5994. .process-complete-btn {
  5995. padding: 4px 8px;
  5996. min-width: 12px;
  5997. }
  5998. .comments-cell {
  5999. display: flex;
  6000. align-items: center;
  6001. gap: 4px;
  6002. min-height: 26px;
  6003. }
  6004. .comments-edit-input {
  6005. flex: 1;
  6006. }
  6007. .comments-save-btn {
  6008. padding: 4px 7px;
  6009. min-width: 28px;
  6010. }
  6011. .comments-text {
  6012. width: 100%;
  6013. overflow: hidden;
  6014. text-overflow: ellipsis;
  6015. white-space: nowrap;
  6016. }
  6017. .process-date-tooltip {
  6018. white-space: pre-line;
  6019. }
  6020. .process-history-tooltip {
  6021. position: fixed;
  6022. z-index: 3000;
  6023. max-width: 220px;
  6024. padding: 10px 12px;
  6025. border-radius: 4px;
  6026. background: rgba(48, 49, 51, 0.95);
  6027. color: #fff;
  6028. font-size: 12px;
  6029. line-height: 1.5;
  6030. white-space: pre-line;
  6031. pointer-events: none;
  6032. }
  6033. .link-date {
  6034. color: #0c4dbb;
  6035. text-decoration: none;
  6036. }
  6037. .link-date:hover,
  6038. .link-date:focus {
  6039. text-decoration: none;
  6040. }
  6041. .actual-date {
  6042. margin-top: 2px;
  6043. color: #666;
  6044. }
  6045. .finish-dialog-tip {
  6046. display: flex;
  6047. align-items: center;
  6048. gap: 8px;
  6049. margin-bottom: 10px;
  6050. padding: 8px 10px;
  6051. border-radius: 4px;
  6052. background: #f4f8ff;
  6053. color: #3a4b5f;
  6054. font-size: 12px;
  6055. }
  6056. .finish-dialog-tip i {
  6057. color: #409eff;
  6058. }
  6059. .finish-tip-highlight {
  6060. font-size: 13px;
  6061. font-weight: 700;
  6062. color: #1f2d3d;
  6063. }
  6064. .finish-proof-dialog .el-dialog__body {
  6065. padding-bottom: 8px;
  6066. }
  6067. .finish-form .el-form-item {
  6068. margin-bottom: 14px;
  6069. }
  6070. .action-link {
  6071. color: #0c4dbb;
  6072. margin: 0 4px;
  6073. cursor: pointer;
  6074. }
  6075. .edit-link {
  6076. color: #e6a23c;
  6077. }
  6078. .delete-link {
  6079. color: #f56c6c;
  6080. }
  6081. .end-link {
  6082. color: #a303ff;
  6083. }
  6084. .zxClass .cell {
  6085. line-height: 24px;
  6086. font-size: 12px;
  6087. height: 24px;
  6088. }
  6089. </style>