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.

6873 lines
258 KiB

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