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.

3745 lines
140 KiB

2 months ago
2 months ago
1 month ago
2 months ago
1 month ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
1 month ago
1 month ago
2 months ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
1 month ago
2 months ago
1 month ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
1 month ago
2 months ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
1 month ago
1 month ago
1 month ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
1 month 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 month ago
2 months ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
1 month ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
1 month 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 month ago
2 months ago
2 months ago
2 months ago
1 month ago
1 month ago
1 month ago
2 months ago
2 months ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
2 months ago
1 month ago
1 month ago
1 month ago
1 month ago
2 months ago
1 month ago
2 months ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
2 months ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
1 month ago
1 month 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 month ago
2 months ago
2 months ago
1 month ago
2 months ago
2 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" clearable filterable placeholder="全部" style="width: 130px" @change="handleSearchBuChange">
  6. <el-option
  7. v-for="item in userBuList"
  8. :key="`searchBu_${item.buNo}`"
  9. :label="item.buDesc ? `${normalizeBuNo(item.buNo, searchData.site)}-${item.buDesc}` : normalizeBuNo(item.buNo, searchData.site)"
  10. :value="normalizeBuNo(item.buNo, searchData.site)"
  11. />
  12. </el-select>
  13. </el-form-item>
  14. <el-form-item label="项目编码">
  15. <el-input v-model="searchData.projectNo" clearable placeholder="请输入项目编码" style="width: 140px" />
  16. </el-form-item>
  17. <el-form-item label="项目描述">
  18. <el-input v-model="searchData.projectDesc" clearable placeholder="请输入项目描述" style="width: 160px" />
  19. </el-form-item>
  20. <el-form-item label="项目物料">
  21. <el-input v-model="searchData.testPartNo" clearable placeholder="请输入项目物料" style="width: 140px" />
  22. </el-form-item>
  23. <el-form-item label="料号描述">
  24. <el-input v-model="searchData.partDesc" clearable placeholder="请输入料号描述" style="width: 160px" />
  25. </el-form-item>
  26. <el-form-item label="客户编码">
  27. <el-input v-model="searchData.customerNo" clearable placeholder="请输入客户编码" style="width: 120px" />
  28. </el-form-item>
  29. <el-form-item label="打样单号">
  30. <el-input v-model="searchData.proofingNo" clearable placeholder="请输入打样单号" style="width: 150px" />
  31. </el-form-item>
  32. <el-form-item label="项目/物料同步">
  33. <el-select v-model="searchData.projectPartSyncFlag" clearable placeholder="全部" style="width: 120px">
  34. <el-option label="已同步" value="Y" />
  35. <el-option label="未同步" value="N" />
  36. </el-select>
  37. </el-form-item>
  38. <el-form-item label="打样单同步">
  39. <el-select v-model="searchData.proofSyncFlag" clearable placeholder="全部" style="width: 120px">
  40. <el-option label="已同步" value="Y" />
  41. <el-option label="未同步" value="N" />
  42. </el-select>
  43. </el-form-item>
  44. <el-form-item label="打样状态">
  45. <el-select v-model="searchData.proofingStatus" clearable placeholder="全部" style="width: 120px">
  46. <el-option label="进行中" value="进行中" />
  47. <el-option label="打样完成" value="打样完成" />
  48. <el-option label="草稿" value="草稿" />
  49. </el-select>
  50. <el-button type="primary" @click="getDataList('Y')">查询</el-button>
  51. <el-button @click="resetSearch">重置</el-button>
  52. </el-form-item>
  53. <el-form-item class="button-group-col">
  54. <el-row :gutter="8" type="flex" class="button-row" >
  55. <el-button type="primary" @click="openOneKeyDialog">一键创建</el-button>
  56. <!-- <el-button type="warning" @click="toggleBatchEdit">{{ batchEditMode ? '取消修改' : '批量修改' }}</el-button>
  57. <el-button v-if="batchEditMode" type="success" @click="saveBatchPlan">保存修改</el-button>-->
  58. <el-button type="warning" @click="openCreateProofDialog()">新增打样</el-button>
  59. <el-button type="success" plain :loading="syncNpiLoading" @click="syncToNpi()">同步项目物料到NPI</el-button>
  60. <el-button type="success" plain :loading="syncProofLoading" @click="syncProofToNpi()">同步打样到NPI</el-button>
  61. <el-button type="info" v-if="isAuth('833001005:set')" plain @click="openBuProcessConfigDialog">事业部进度设置</el-button>
  62. </el-row>
  63. </el-form-item>
  64. </el-form>
  65. <el-table
  66. ref="trackingTable"
  67. :data="dataList"
  68. border class="data-table"
  69. stripe highlight-current-row
  70. height="700"
  71. v-loading="dataListLoading"
  72. @selection-change="selectionChange"
  73. @row-click="rowClick"
  74. style="width: 100%; margin-top: 8px"
  75. >
  76. <!-- <el-table-column type="selection" width="45" />-->
  77. <el-table-column prop="projectNo" label="项目编码" width="90" show-overflow-tooltip>
  78. <template slot-scope="scope">
  79. <span :class="getProjectPartSyncTextClass(scope.row)">{{ scope.row.projectNo || '-' }}</span>
  80. </template>
  81. </el-table-column>
  82. <el-table-column prop="projectDesc" label="项目描述" width="120" show-overflow-tooltip>
  83. <template slot-scope="scope">
  84. <span :class="getProjectPartSyncTextClass(scope.row)">{{ scope.row.projectDesc || '-' }}</span>
  85. </template>
  86. </el-table-column>
  87. <el-table-column prop="testPartNo" label="项目物料" width="90" show-overflow-tooltip>
  88. <template slot-scope="scope">
  89. <span :class="getProjectPartSyncTextClass(scope.row)">{{ scope.row.testPartNo || '-' }}</span>
  90. </template>
  91. </el-table-column>
  92. <el-table-column prop="partDesc" label="物料描述" :fixed="'left'" width="150" show-overflow-tooltip>
  93. <template slot-scope="scope">
  94. <span :class="getProjectPartSyncTextClass(scope.row)">{{ scope.row.partDesc || '-' }}</span>
  95. </template>
  96. </el-table-column>
  97. <!-- <el-table-column prop="customerNo" label="客户编码" width="110" show-overflow-tooltip />
  98. <el-table-column prop="customerDesc" label="客户名称" width="130" show-overflow-tooltip />-->
  99. <el-table-column prop="proofingNo" label="打样单号" width="90" show-overflow-tooltip>
  100. <template slot-scope="scope">
  101. <span :class="getProofSyncTextClass(scope.row)">{{ scope.row.proofingNo || '-' }}</span>
  102. </template>
  103. </el-table-column>
  104. <el-table-column prop="proofingStatus" label="打样状态" width="60">
  105. <template slot-scope="scope">
  106. <a :style="{color: (scope.row.proofingStatus === '打样完成' || scope.row.proofingStatus === '打样结束') ? '#67c23a' : '#046e97'}">
  107. {{ scope.row.proofingStatus || '进行中' }}</a>
  108. </template>
  109. </el-table-column>
  110. <el-table-column label="Tracking Status" align="center" width="190" show-overflow-tooltip>
  111. <template slot-scope="scope">
  112. <div :class="getTrackingStatusClass(scope.row)">
  113. {{ getTrackingStatusText(scope.row) }}
  114. </div>
  115. </template>
  116. </el-table-column>
  117. <el-table-column
  118. v-for="(item, processColumnIndex) in visibleProcessColumns"
  119. :key="`${item.code}_${item.sortNo || ''}_${processColumnIndex}`"
  120. :label="item.label"
  121. width="120"
  122. align="center"
  123. header-align="center"
  124. >
  125. <template slot-scope="scope">
  126. <div
  127. v-if="isProcessColumnVisibleForRow(scope.row, item)"
  128. :class="['process-cell', isProcessComplete(scope.row, item) ? 'is-complete' : '']"
  129. >
  130. <el-date-picker
  131. v-if="batchEditMode"
  132. v-model="scope.row[item.actualField]"
  133. type="date"
  134. value-format="yyyy-MM-dd"
  135. format="yyyy-MM-dd"
  136. placeholder="实际完成日期"
  137. style="width: 120px"
  138. />
  139. <div
  140. v-else
  141. class="process-content"
  142. @mouseenter="handleProcessTooltipMouseEnter(scope.row, item, $event)"
  143. @mousemove="handleProcessTooltipMouseMove($event)"
  144. @mouseleave="handleProcessTooltipMouseLeave"
  145. >
  146. <el-date-picker
  147. v-model="scope.row[item.actualField]"
  148. type="date"
  149. value-format="yyyy-MM-dd"
  150. format="yyyy-MM-dd"
  151. placeholder=""
  152. style="width: 122px"
  153. @change="handleProcessDateChange(scope.row, item, $event)"
  154. />
  155. <el-button
  156. class="process-complete-btn"
  157. type="success"
  158. size="mini"
  159. plain v-show="!isProcessComplete(scope.row, item)"
  160. :disabled="isProcessComplete(scope.row, item)"
  161. @click.stop="markProcessComplete(scope.row, item)"
  162. ></el-button>
  163. </div>
  164. </div>
  165. <div v-else class="process-cell process-cell--masked"></div>
  166. </template>
  167. </el-table-column>
  168. <el-table-column label="Comments" width="220">
  169. <template slot-scope="scope">
  170. <div class="comments-cell" @dblclick.stop="startCommentsEdit(scope.row)">
  171. <template v-if="batchEditMode">
  172. <el-input
  173. v-model="scope.row.comments"
  174. class="comments-edit-input"
  175. size="mini"
  176. clearable
  177. placeholder="请输入Comments"
  178. />
  179. </template>
  180. <template v-else-if="isCommentsEditing(scope.row)">
  181. <el-input
  182. v-model="commentsDraft"
  183. v-focus-comments-input="isCommentsEditing(scope.row)"
  184. :ref="'commentsInput_' + scope.row.trackingId"
  185. :id="'commentsInput_' + scope.row.trackingId"
  186. class="comments-edit-input"
  187. size="mini"
  188. clearable
  189. placeholder="请输入,回车也可保存"
  190. @keydown.enter.native.prevent="saveCommentsEdit(scope.row)"
  191. @keyup.esc.native="cancelCommentsEdit"
  192. @blur="handleCommentsBlur(scope.row)"
  193. />
  194. <el-button
  195. class="comments-save-btn"
  196. type="success"
  197. size="mini"
  198. plain
  199. :loading="commentsSaveLoading"
  200. @mousedown.native.prevent
  201. @click.stop="saveCommentsEdit(scope.row)"
  202. ></el-button>
  203. </template>
  204. <span v-else class="comments-text" :title="scope.row.comments || ''">{{ scope.row.comments || '-' }}</span>
  205. </div>
  206. </template>
  207. </el-table-column>
  208. <el-table-column label="操作" fixed="right" width="150" align="center">
  209. <template slot-scope="scope">
  210. <!-- <a class="action-link" @click="syncToNpi(scope.row)">同步NPI</a>-->
  211. <!-- <a class="action-link" @click="openCreateProofDialog(scope.row)">新增打样</a>-->
  212. <a @click="openOneKeyDialog('edit', scope.row)">修改</a>
  213. <a class="action-link delete-link" @click="deleteProof(scope.row)">删除</a>
  214. <a class="action-link end-link" v-if="scope.row.proofingNo && scope.row.proofingStatus !== '打样完成'" @click="finishProof(scope.row)">打样完成</a>
  215. </template>
  216. </el-table-column>
  217. </el-table>
  218. <el-pagination
  219. @size-change="sizeChangeHandle"
  220. @current-change="currentChangeHandle"
  221. :current-page="pageIndex"
  222. :page-sizes="[10, 20, 50, 100]"
  223. :page-size="pageSize"
  224. :total="totalPage"
  225. layout="total, sizes, prev, pager, next, jumper"
  226. style="margin-top: 12px; text-align: right"
  227. />
  228. <div
  229. v-show="processTooltip.visible"
  230. class="process-history-tooltip"
  231. :style="{
  232. left: `${processTooltip.left}px`,
  233. top: `${processTooltip.top}px`
  234. }"
  235. >{{ processTooltip.content }}</div>
  236. <el-dialog
  237. title="进度展示设置"
  238. :visible.sync="buProcessConfigDialogVisible"
  239. width="1050px"
  240. :close-on-click-modal="false"
  241. custom-class="bu-process-config-dialog"
  242. >
  243. <div class="bu-process-config-tip">
  244. <i class="el-icon-info"></i>
  245. <span>拖动可调整顺序按序号从左到右勾选控制可见列</span>
  246. </div>
  247. <el-table :data="buProcessConfigRows" border stripe max-height="560">
  248. <el-table-column prop="buDesc" label="BU" width="50" show-overflow-tooltip />
  249. <el-table-column label="可见进度列表" min-width="450" class-name="bu-process-list-column">
  250. <template slot-scope="scope">
  251. <el-checkbox-group
  252. v-model="scope.row.processCodes"
  253. class="bu-process-order-list"
  254. @change="handleBuProcessCodesChange(scope.row)"
  255. >
  256. <div
  257. v-for="(processCode, processCodeIndex) in scope.row.processOrderCodes"
  258. :key="`${scope.row.buNo}_${processCode}_${processCodeIndex}`"
  259. :class="['bu-process-order-item', isProcessOrderDraggingItem(scope.row, processCodeIndex) ? 'is-dragging' : '']"
  260. draggable="true"
  261. @dragstart="handleProcessOrderDragStart(scope.row, processCodeIndex, $event)"
  262. @dragover.prevent
  263. @dragenter.prevent
  264. @drop.prevent="handleProcessOrderDrop(scope.row, processCodeIndex, $event)"
  265. @dragend="handleProcessOrderDragEnd"
  266. >
  267. <span class="process-order-index">{{ processCodeIndex + 1 }}.</span>
  268. <i class="el-icon-rank process-order-handle"></i>
  269. <el-checkbox :label="processCode">{{ getProcessLabelByCode(processCode) }}</el-checkbox>
  270. </div>
  271. </el-checkbox-group>
  272. </template>
  273. </el-table-column>
  274. </el-table>
  275. <div slot="footer">
  276. <el-button @click="buProcessConfigDialogVisible = false">取消</el-button>
  277. <el-button type="primary" @click="saveBuProcessConfig">保存设置</el-button>
  278. </div>
  279. </el-dialog>
  280. <el-dialog :title="oneKeyDialogMode === 'edit' ? '修改项目/物料/打样' : '一键创建项目/物料/打样'" :visible.sync="oneKeyDialogVisible" :close-on-click-modal="false" width="1000px">
  281. <el-form :model="oneKeyForm" label-position="top" class="one-key-grid-form">
  282. <el-row :gutter="16">
  283. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('buNo')">
  284. <el-form-item label="BU" required>
  285. <el-select v-model="oneKeyForm.buNo" placeholder="请选择" style="width: 100%">
  286. <el-option v-for="item in userBuList" :key="item.buNo" :label="item.buDesc" :value="item.buNo"></el-option>
  287. </el-select>
  288. </el-form-item>
  289. </el-col>
  290. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectNo')">
  291. <el-form-item label="项目编码">
  292. <el-input v-model="oneKeyForm.projectNo" style="width: 100%" @blur="handleOneKeyProjectNoBlur"></el-input>
  293. </el-form-item>
  294. </el-col>
  295. <el-col :span="12" v-show="isOneKeyCreateFieldVisible('projectDesc')">
  296. <el-form-item label="项目名称">
  297. <el-input v-model="oneKeyForm.projectDesc" style="width: 100%"></el-input>
  298. </el-form-item>
  299. </el-col>
  300. </el-row>
  301. <el-row :gutter="16">
  302. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('customerNo')">
  303. <el-form-item>
  304. <span slot="label" class="big-label">
  305. <a href="javascript:void(0)" @click="getBaseList(509)">客户编码</a>
  306. <a href="javascript:void(0)" @click="newCustomer">新客户</a>
  307. </span>
  308. <el-input v-model="oneKeyForm.customerNo" @blur="customerNoBlur" style="width: 100%"></el-input>
  309. </el-form-item>
  310. </el-col>
  311. <el-col :span="12" v-show="isOneKeyCreateFieldVisible('customerDesc')">
  312. <el-form-item label="客户名称">
  313. <el-input v-model="oneKeyForm.customerDesc" disabled style="width: 100%"></el-input>
  314. </el-form-item>
  315. </el-col>
  316. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cProjectRegion')">
  317. <el-form-item label="区域">
  318. <el-select v-model="oneKeyForm.cProjectRegion" placeholder="请选择" clearable style="width: 100%">
  319. <el-option
  320. v-for="i in cProjectRegionList"
  321. :key="i.cProjectRegion"
  322. :label="i.cProjectRegion"
  323. :value="i.cProjectRegion">
  324. </el-option>
  325. </el-select>
  326. </el-form-item>
  327. </el-col>
  328. </el-row>
  329. <el-row :gutter="16">
  330. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectCreationDate')">
  331. <el-form-item label="立项日期">
  332. <el-date-picker
  333. v-model="oneKeyForm.projectCreationDate"
  334. type="date"
  335. value-format="yyyy-MM-dd"
  336. placeholder="选择日期"
  337. style="width: 100%">
  338. </el-date-picker>
  339. </el-form-item>
  340. </el-col>
  341. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('needDate')">
  342. <el-form-item label="预计完成日期">
  343. <el-date-picker
  344. v-model="oneKeyForm.needDate"
  345. type="date"
  346. value-format="yyyy-MM-dd"
  347. placeholder="选择日期"
  348. style="width: 100%">
  349. </el-date-picker>
  350. </el-form-item>
  351. </el-col>
  352. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectCategory')">
  353. <el-form-item label="项目分类">
  354. <el-select v-model="oneKeyForm.projectCategory" placeholder="请选择" clearable style="width: 100%">
  355. <el-option v-for="item in projectCategoryList" :key="item" :label="item" :value="item"></el-option>
  356. </el-select>
  357. </el-form-item>
  358. </el-col>
  359. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('priorityLevel')">
  360. <el-form-item label="优先级">
  361. <el-select v-model="oneKeyForm.priorityLevel" placeholder="请选择" clearable style="width: 100%">
  362. <el-option v-for="item in priorityList" :key="item" :label="item" :value="item"></el-option>
  363. </el-select>
  364. </el-form-item>
  365. </el-col>
  366. </el-row>
  367. <el-row :gutter="16">
  368. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('testPartNo')">
  369. <el-form-item label="项目料号">
  370. <el-input v-model="oneKeyForm.testPartNo" style="width: 100%"></el-input>
  371. </el-form-item>
  372. </el-col>
  373. <el-col :span="12" v-show="isOneKeyCreateFieldVisible('partDesc')">
  374. <el-form-item label="料号描述" required>
  375. <el-input v-model="oneKeyForm.partDesc" style="width: 100%"></el-input>
  376. </el-form-item>
  377. </el-col>
  378. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('partType')">
  379. <el-form-item label="料号状态">
  380. <el-select v-model="oneKeyForm.partType" placeholder="请选择" clearable style="width: 100%">
  381. <el-option v-for="item in partTypeList" :key="item" :label="item" :value="item"></el-option>
  382. </el-select>
  383. </el-form-item>
  384. </el-col>
  385. </el-row>
  386. <el-row :gutter="16">
  387. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectManager')">
  388. <el-form-item>
  389. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('projectManager')">PM/Sales</a></span>
  390. <el-input v-model="oneKeyForm.projectManagerName" disabled style="width: 100%"></el-input>
  391. </el-form-item>
  392. </el-col>
  393. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectOwner')">
  394. <el-form-item required>
  395. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('projectOwner')">PjM</a></span>
  396. <el-input v-model="oneKeyForm.projectOwnerName" disabled style="width: 100%"></el-input>
  397. </el-form-item>
  398. </el-col>
  399. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('engineer')">
  400. <el-form-item required>
  401. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('engineer')">Engineer</a></span>
  402. <el-input v-model="oneKeyForm.engineerName" disabled style="width: 100%"></el-input>
  403. </el-form-item>
  404. </el-col>
  405. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cManufactureEngineer')">
  406. <el-form-item>
  407. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cManufactureEngineer')">MFG</a></span>
  408. <el-input v-model="oneKeyForm.cManufactureEngineerName" disabled style="width: 100%"></el-input>
  409. </el-form-item>
  410. </el-col>
  411. </el-row>
  412. <el-row :gutter="16">
  413. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer6')">
  414. <el-form-item>
  415. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer6')">IQC</a></span>
  416. <el-input v-model="oneKeyForm.cQualityEngineer6Name" disabled style="width: 100%"></el-input>
  417. </el-form-item>
  418. </el-col>
  419. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer1')">
  420. <el-form-item>
  421. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer1')">IPQC-Lam/Pri/Etch/Slit</a></span>
  422. <el-input v-model="oneKeyForm.cQualityEngineer1Name" disabled style="width: 100%"></el-input>
  423. </el-form-item>
  424. </el-col>
  425. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer2')">
  426. <el-form-item>
  427. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer2')">IPQC-Converting</a></span>
  428. <el-input v-model="oneKeyForm.cQualityEngineer2Name" disabled style="width: 100%"></el-input>
  429. </el-form-item>
  430. </el-col>
  431. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer4')">
  432. <el-form-item>
  433. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer4')">SQE</a></span>
  434. <el-input v-model="oneKeyForm.cQualityEngineer4Name" disabled style="width: 100%"></el-input>
  435. </el-form-item>
  436. </el-col>
  437. </el-row>
  438. <el-row :gutter="16">
  439. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer3')">
  440. <el-form-item>
  441. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer3')">FQC1</a></span>
  442. <el-input v-model="oneKeyForm.cQualityEngineer3Name" disabled style="width: 100%"></el-input>
  443. </el-form-item>
  444. </el-col>
  445. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer5')">
  446. <el-form-item>
  447. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer5')">FQC2</a></span>
  448. <el-input v-model="oneKeyForm.cQualityEngineer5Name" disabled style="width: 100%"></el-input>
  449. </el-form-item>
  450. </el-col>
  451. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('ipqcHardTag')">
  452. <el-form-item>
  453. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('ipqcHardTag')">IPQC-Hardtag</a></span>
  454. <el-input v-model="oneKeyForm.ipqcHardTagName" disabled style="width: 100%"></el-input>
  455. </el-form-item>
  456. </el-col>
  457. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('cQualityEngineer7')">
  458. <el-form-item>
  459. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('cQualityEngineer7')">前道工程师</a></span>
  460. <el-input v-model="oneKeyForm.cQualityEngineer7Name" disabled style="width: 100%"></el-input>
  461. </el-form-item>
  462. </el-col>
  463. </el-row>
  464. <el-row :gutter="16">
  465. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('docEngineer')">
  466. <el-form-item>
  467. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('docEngineer')">文档工程师</a></span>
  468. <el-input v-model="oneKeyForm.docEngineerName" disabled style="width: 100%"></el-input>
  469. </el-form-item>
  470. </el-col>
  471. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('docEngineer2')">
  472. <el-form-item>
  473. <span slot="label" class="big-label"><a href="javascript:void(0)" @click="openRoleDialog('docEngineer2')">文档工程师2</a></span>
  474. <el-input v-model="oneKeyForm.docEngineer2Name" disabled style="width: 100%"></el-input>
  475. </el-form-item>
  476. </el-col>
  477. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectStatus')">
  478. <el-form-item label="项目状态">
  479. <el-select v-model="oneKeyForm.projectStatus" clearable style="width: 100%">
  480. <el-option v-for="item in projectStatusList" :key="item" :label="item" :value="item" />
  481. </el-select>
  482. </el-form-item>
  483. </el-col>
  484. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('projectPhase')">
  485. <el-form-item label="项目阶段">
  486. <el-select v-model="oneKeyForm.projectPhase" clearable style="width: 100%">
  487. <el-option
  488. v-for="item in proofProjectPhaseList"
  489. :key="item.projectPhase"
  490. :label="item.projectPhase"
  491. :value="item.projectPhase"
  492. />
  493. </el-select>
  494. </el-form-item>
  495. </el-col>
  496. </el-row>
  497. <el-row :gutter="16">
  498. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('proofingNo')">
  499. <el-form-item label="打样单号">
  500. <el-select
  501. v-model="oneKeyForm.proofingNo"
  502. placeholder="请选择打样单号"
  503. clearable
  504. filterable
  505. :loading="oneKeyProofingApplyLoading"
  506. :disabled="isBlankValue(oneKeyForm.projectNo)"
  507. style="width: 100%"
  508. @change="handleOneKeyProofingNoChange">
  509. <el-option
  510. v-for="item in oneKeyProofingApplyOptions"
  511. :key="item.applyNo"
  512. :label="item.applyNo"
  513. :value="item.applyNo"
  514. />
  515. </el-select>
  516. </el-form-item>
  517. </el-col>
  518. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('proofingStatus')">
  519. <el-form-item label="打样状态">
  520. <el-select v-model="oneKeyForm.proofingStatus" clearable style="width: 100%">
  521. <el-option label="草稿" value="草稿" />
  522. <el-option label="进行中" value="进行中" />
  523. <el-option label="打样完成" value="打样完成" />
  524. <el-option label="打样结束" value="打样结束" />
  525. </el-select>
  526. </el-form-item>
  527. </el-col>
  528. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('proofingNumber')">
  529. <el-form-item label="数量">
  530. <el-input v-model.number="oneKeyForm.proofingNumber" style="width: 100%"></el-input>
  531. </el-form-item>
  532. </el-col>
  533. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('planStartDate')">
  534. <el-form-item label="打样开始日期">
  535. <el-date-picker
  536. v-model="oneKeyForm.planStartDate"
  537. type="date"
  538. value-format="yyyy-MM-dd"
  539. placeholder="选择日期"
  540. style="width: 100%">
  541. </el-date-picker>
  542. </el-form-item>
  543. </el-col>
  544. </el-row>
  545. <el-row :gutter="16">
  546. <el-col :span="6" v-show="isOneKeyCreateFieldVisible('requiredDeliveryDate')">
  547. <el-form-item label="预计完成日期">
  548. <el-date-picker
  549. v-model="oneKeyForm.requiredDeliveryDate"
  550. type="date"
  551. value-format="yyyy-MM-dd"
  552. placeholder="选择日期"
  553. style="width: 100%">
  554. </el-date-picker>
  555. </el-form-item>
  556. </el-col>
  557. <el-col :span="isOneKeyCreateFieldVisible('requiredDeliveryDate') ? 18 : 24" v-show="isOneKeyCreateFieldVisible('remark')">
  558. <el-form-item label="备注">
  559. <el-input type="textarea" :rows="2" v-model="oneKeyForm.remark" style="width: 100%"></el-input>
  560. </el-form-item>
  561. </el-col>
  562. </el-row>
  563. </el-form>
  564. <el-footer style="height: 40px; margin-top: 40px; text-align: center">
  565. <el-button @click="oneKeyDialogVisible = false">取消</el-button>
  566. <el-button type="primary" :loading="saveOneKeyLoading" @click="submitOneKey">{{ oneKeyDialogMode === 'edit' ? '保存修改' : '保存' }}</el-button>
  567. </el-footer>
  568. </el-dialog>
  569. <el-dialog title="新增打样" :visible.sync="proofDialogVisible" width="768px" :close-on-click-modal="false">
  570. <el-form :model="proofDialogData" label-position="top" class="proof-grid-form">
  571. <el-row :gutter="16">
  572. <el-col :span="6">
  573. <el-form-item label="项目编码">
  574. <el-input v-model="proofDialogData.projectNo" disabled style="width: 100%" />
  575. </el-form-item>
  576. </el-col>
  577. <el-col :span="12">
  578. <el-form-item label="项目名称">
  579. <el-input v-model="proofDialogData.projectDesc" disabled style="width: 100%" />
  580. </el-form-item>
  581. </el-col>
  582. <el-col :span="6">
  583. <el-form-item label="BU">
  584. <el-input v-model="proofDialogData.buNo" disabled style="width: 100%" />
  585. </el-form-item>
  586. </el-col>
  587. </el-row>
  588. <el-row :gutter="16">
  589. <el-col :span="6">
  590. <el-form-item label="客户编码">
  591. <el-input v-model="proofDialogData.customerNo" disabled style="width: 100%" />
  592. </el-form-item>
  593. </el-col>
  594. <el-col :span="12">
  595. <el-form-item label="客户名称">
  596. <el-input v-model="proofDialogData.customerDesc" disabled style="width: 100%" />
  597. </el-form-item>
  598. </el-col>
  599. <el-col :span="6">
  600. <el-form-item label="项目料号">
  601. <el-input v-model="proofDialogData.testPartNo" disabled style="width: 100%" />
  602. </el-form-item>
  603. </el-col>
  604. </el-row>
  605. <el-row :gutter="16">
  606. <el-col :span="12">
  607. <el-form-item label="料号描述">
  608. <el-input v-model="proofDialogData.partDesc" disabled style="width: 100%" />
  609. </el-form-item>
  610. </el-col>
  611. <el-col :span="6">
  612. <el-form-item label="项目分类" required>
  613. <el-select v-model="proofDialogData.cProjectTypeDb" placeholder="请选择" clearable style="width: 100%">
  614. <el-option
  615. v-for="item in cProjectTypeDbList"
  616. :key="item.cProjectTypeDb"
  617. :label="item.cProjectTypeDb"
  618. :value="item.cProjectTypeDb">
  619. </el-option>
  620. </el-select>
  621. </el-form-item>
  622. </el-col>
  623. <el-col :span="6">
  624. <el-form-item label="项目阶段" required>
  625. <el-select v-model="proofDialogData.projectPhase" placeholder="请选择" clearable style="width: 100%">
  626. <el-option
  627. v-for="item in proofProjectPhaseList"
  628. :key="item.projectPhase"
  629. :label="item.projectPhase"
  630. :value="item.projectPhase">
  631. </el-option>
  632. </el-select>
  633. </el-form-item>
  634. </el-col>
  635. </el-row>
  636. <el-row :gutter="16">
  637. <el-col :span="6">
  638. <el-form-item label="打样单号" required>
  639. <el-select
  640. v-model="proofDialogData.proofingNo"
  641. placeholder="请选择试验单号"
  642. clearable
  643. filterable
  644. :loading="proofDialogApplyLoading"
  645. :disabled="isBlankValue(proofDialogData.projectNo)"
  646. style="width: 100%"
  647. @change="handleProofDialogProofingNoChange">
  648. <el-option
  649. v-for="item in proofDialogApplyOptions"
  650. :key="item.applyNo"
  651. :label="item.applyNo"
  652. :value="item.applyNo"
  653. />
  654. </el-select>
  655. </el-form-item>
  656. </el-col>
  657. <el-col :span="6">
  658. <el-form-item label="数量" required>
  659. <el-input v-model="proofDialogData.proofingNumber" style="width: 100%" />
  660. </el-form-item>
  661. </el-col>
  662. <el-col :span="6">
  663. <el-form-item label="打样状态">
  664. <el-select v-model="proofDialogData.proofingStatus" placeholder="请选择" clearable style="width: 100%">
  665. <el-option label="草稿" value="草稿"></el-option>
  666. <el-option label="进行中" value="进行中"></el-option>
  667. </el-select>
  668. </el-form-item>
  669. </el-col>
  670. <el-col :span="6">
  671. <el-form-item label="实际完成日期">
  672. <el-date-picker
  673. v-model="proofDialogData.actualityDeliveryDate"
  674. type="date"
  675. value-format="yyyy-MM-dd"
  676. placeholder="选择日期"
  677. style="width: 100%">
  678. </el-date-picker>
  679. </el-form-item>
  680. </el-col>
  681. </el-row>
  682. <el-row :gutter="16">
  683. <el-col :span="6">
  684. <el-form-item label="打样开始日期" required>
  685. <el-date-picker
  686. v-model="proofDialogData.planStartDate"
  687. type="date"
  688. value-format="yyyy-MM-dd"
  689. placeholder="选择日期"
  690. style="width: 100%">
  691. </el-date-picker>
  692. </el-form-item>
  693. </el-col>
  694. <el-col :span="6">
  695. <el-form-item label="预计完成日期" required>
  696. <el-date-picker
  697. v-model="proofDialogData.requiredDeliveryDate"
  698. type="date"
  699. value-format="yyyy-MM-dd"
  700. placeholder="选择日期"
  701. style="width: 100%">
  702. </el-date-picker>
  703. </el-form-item>
  704. </el-col>
  705. <el-col :span="12">
  706. <el-form-item label="备注">
  707. <el-input v-model="proofDialogData.remark" style="width: 100%" type="textarea" :rows="2"></el-input>
  708. </el-form-item>
  709. </el-col>
  710. </el-row>
  711. </el-form>
  712. <el-footer style="height: 40px; margin-top: 40px; text-align: center">
  713. <el-button @click="proofDialogVisible = false"> </el-button>
  714. <el-button type="primary" :loading="proofSaveLoading" @click="saveProofRecord"> </el-button>
  715. </el-footer>
  716. </el-dialog>
  717. <el-dialog title="更新工序状态" :visible.sync="processDialogVisible" width="620px" :close-on-click-modal="false">
  718. <el-form :model="processForm" label-position="top" label-width="120px" :inline="true">
  719. <el-form-item label="工序">
  720. <el-input v-model="processForm.processName" disabled style="width: 180px" />
  721. </el-form-item>
  722. <el-form-item label="状态" required>
  723. <el-select v-model="processForm.status" style="width: 180px">
  724. <el-option label="未完成" value="未完成" />
  725. <el-option label="进行中" value="进行中" />
  726. <el-option label="已完成" value="已完成" />
  727. </el-select>
  728. </el-form-item>
  729. <el-form-item label="实际完成日期">
  730. <el-date-picker v-model="processForm.actualDate" type="date" value-format="yyyy-MM-dd" format="yyyy-MM-dd" style="width: 180px" />
  731. </el-form-item>
  732. <el-form-item label="备注">
  733. <el-input type="textarea" :rows="2" v-model="processForm.remark" style="width: 570px" />
  734. </el-form-item>
  735. </el-form>
  736. <el-table :data="processHistoryRows" border stripe height="220" style="margin-top: 32px">
  737. <el-table-column prop="changedDate" label="修改时间" width="120" />
  738. <el-table-column prop="changedBy" label="修改人" width="70" />
  739. <el-table-column prop="oldStatus" label="原状态" width="60" />
  740. <el-table-column prop="newStatus" label="新状态" width="60" />
  741. <el-table-column prop="oldActualDate" label="原实际" width="90" />
  742. <el-table-column prop="newActualDate" label="新实际" width="90" />
  743. <el-table-column prop="remark" label="备注" show-overflow-tooltip />
  744. </el-table>
  745. <div slot="footer">
  746. <el-button @click="processDialogVisible = false">取消</el-button>
  747. <el-button type="primary" :loading="saveProcessLoading" @click="submitProcessUpdate">保存</el-button>
  748. </div>
  749. </el-dialog>
  750. <el-dialog
  751. title="打样完成"
  752. :visible.sync="finishDialogVisible"
  753. width="260px"
  754. :close-on-click-modal="false"
  755. custom-class="finish-proof-dialog">
  756. <div class="finish-dialog-tip">
  757. <i class="el-icon-info"></i>
  758. <span>确认后将直接标记为<strong class="finish-tip-highlight">打样完成</strong></span>
  759. </div>
  760. <el-form :model="finishForm" label-width="100px" class="finish-form">
  761. <el-form-item label="实际完成日期" required>
  762. <el-date-picker
  763. v-model="finishForm.actualityDeliveryDate"
  764. type="date"
  765. value-format="yyyy-MM-dd"
  766. format="yyyy-MM-dd"
  767. placeholder="选择日期"
  768. style="width: 100%"
  769. />
  770. </el-form-item>
  771. </el-form>
  772. <div slot="footer">
  773. <el-button @click="finishDialogVisible = false">取消</el-button>
  774. <el-button type="primary" :loading="finishSaveLoading" @click="submitFinishProof">确认完成</el-button>
  775. </div>
  776. </el-dialog>
  777. <el-dialog :title="roleDialogTitle" :visible.sync="roleDialogVisible" width="559px" :close-on-click-modal="false">
  778. <el-form :inline="true" label-position="top" :model="roleSearchData" style="margin-left: 7px; margin-top: -5px;">
  779. <el-form-item label="用户账号">
  780. <el-input v-model="roleSearchData.username" clearable style="width: 110px"></el-input>
  781. </el-form-item>
  782. <el-form-item label="用户名">
  783. <el-input v-model="roleSearchData.userDisplay" clearable style="width: 110px"></el-input>
  784. </el-form-item>
  785. <el-form-item label="是否在用">
  786. <el-select filterable v-model="roleSearchData.active" clearable style="width: 140px">
  787. <el-option label="是" value="Y"></el-option>
  788. <el-option label="否" value="N"></el-option>
  789. </el-select>
  790. </el-form-item>
  791. <el-form-item label=" ">
  792. <el-button type="primary" style="padding: 3px 12px" @click="queryRoleDialogList">查询</el-button>
  793. </el-form-item>
  794. </el-form>
  795. <el-table
  796. :data="roleDialogList"
  797. stripe
  798. border
  799. highlight-current-row
  800. @row-dblclick="pickRole"
  801. style="width: 100%">
  802. <el-table-column prop="username" header-align="center" align="center" label="用户账号"></el-table-column>
  803. <el-table-column prop="userDisplay" header-align="center" align="center" label="用户名"></el-table-column>
  804. <el-table-column prop="active" header-align="center" align="center" label="是否在用"></el-table-column>
  805. </el-table>
  806. <el-footer style="height: 40px; margin-top: 10px; text-align: center">
  807. <el-button @click="roleDialogVisible = false">关闭</el-button>
  808. </el-footer>
  809. </el-dialog>
  810. <el-dialog title="新客户" :visible.sync="newCustomerFlag" width="348px" :close-on-click-modal="false" @close="closeNewCustomer">
  811. <el-form :inline="true" label-position="top" :model="newCustomerData" style="margin-left: 7px; margin-top: -5px;">
  812. <el-form-item label="客户名称">
  813. <el-input v-model="newCustomerData.customerDesc" style="width: 313px"></el-input>
  814. </el-form-item>
  815. </el-form>
  816. <el-footer style="height: 40px; margin-top: 10px; text-align: center">
  817. <el-button type="primary" @click="saveNewCustomer">保存</el-button>
  818. <el-button @click="newCustomerFlag = false">关闭</el-button>
  819. </el-footer>
  820. </el-dialog>
  821. <Chooselist ref="baseList" @getBaseData="getBaseData"></Chooselist>
  822. </div>
  823. </template>
  824. <script>
  825. import {
  826. batchUpdateProofTrackingPlan,
  827. createProofTrackingRecord,
  828. deleteProofTracking,
  829. finishProofTracking,
  830. oneKeyCreateProofTracking,
  831. oneKeyUpdateProofTracking,
  832. queryProofTrackingBuProcessConfig,
  833. queryProofTrackingProcessColumns,
  834. queryProofTrackingProcessHistory,
  835. saveProofTrackingBuProcessConfig,
  836. searchProjectInfoTracking,
  837. searchProjectPartTracking,
  838. searchProofTracking,
  839. syncProofTracking,
  840. syncProjectPartTracking,
  841. updateProofTrackingComments,
  842. updateProofTrackingProcess
  843. } from '@/api/sampleTracking/sampleTracking'
  844. import { searchExpApplyList } from '@/api/erf/erf'
  845. import { getCustomerNo, saveNewCustomer } from '@/api/eam/eamProject.js'
  846. import { getSiteAndBuByUserName } from '@/api/eam/eam.js'
  847. import {
  848. searchBusinessInfo,
  849. searchBusinessInfo1,
  850. searchBusinessInfo2,
  851. searchBusinessInfo3,
  852. searchBusinessInfo4,
  853. searchBusinessInfo5,
  854. searchBusinessInfo6,
  855. searchBusinessInfo7,
  856. searchBusinessInfo8
  857. } from '@/api/factory/site.js'
  858. import { queryCustomerList } from '@/api/customer/customer'
  859. import Chooselist from '@/views/modules/common/Chooselist_eam'
  860. function focusCommentsNativeInput (el, vnode) {
  861. const doFocus = () => {
  862. const componentInput = vnode && vnode.componentInstance && vnode.componentInstance.$refs
  863. ? vnode.componentInstance.$refs.input
  864. : null
  865. const inputEl = componentInput || (el && el.querySelector ? el.querySelector('input') : null)
  866. if (!inputEl || typeof inputEl.focus !== 'function') {
  867. return false
  868. }
  869. inputEl.focus()
  870. const textLength = inputEl.value ? String(inputEl.value).length : 0
  871. if (typeof inputEl.setSelectionRange === 'function') {
  872. inputEl.setSelectionRange(textLength, textLength)
  873. }
  874. return true
  875. }
  876. if (doFocus()) {
  877. return
  878. }
  879. setTimeout(doFocus, 0)
  880. setTimeout(doFocus, 50)
  881. setTimeout(doFocus, 100)
  882. }
  883. const DEFAULT_PROCESS_COLUMNS = [
  884. { label: 'Raw Material', code: 'rawMaterial', planField: 'rawMaterialPlanDate', actualField: 'rawMaterialActualDate', statusField: 'rawMaterialStatus' },
  885. { label: 'Cylinder/Cutter', code: 'cylinderCutter', planField: 'cylinderCutterPlanDate', actualField: 'cylinderCutterActualDate', statusField: 'cylinderCutterStatus' },
  886. { label: 'Lamination', code: 'lamination', planField: 'laminationPlanDate', actualField: 'laminationActualDate', statusField: 'laminationStatus' },
  887. { label: 'Printing', code: 'printing', planField: 'printingPlanDate', actualField: 'printingActualDate', statusField: 'printingStatus' },
  888. { label: 'Etching', code: 'etching', planField: 'etchingPlanDate', actualField: 'etchingActualDate', statusField: 'etchingStatus' },
  889. { label: 'Slitting&Packing', code: 'slittingPacking', planField: 'slittingPackingPlanDate', actualField: 'slittingPackingActualDate', statusField: 'slittingPackingStatus' },
  890. { label: 'Bonding', code: 'bonding', planField: 'bondingPlanDate', actualField: 'bondingActualDate', statusField: 'bondingStatus' },
  891. { label: 'CL60/CL822', code: 'cl60Cl822', planField: 'cl60Cl822PlanDate', actualField: 'cl60Cl822ActualDate', statusField: 'cl60Cl822Status' },
  892. { label: 'Spotting', code: 'spotting', planField: 'spottingPlanDate', actualField: 'spottingActualDate', statusField: 'spottingStatus' },
  893. { label: 'Encoding', code: 'encoding', planField: 'encodingPlanDate', actualField: 'encodingActualDate', statusField: 'encodingStatus' },
  894. { label: 'Die cut', code: 'dieCut', planField: 'dieCutPlanDate', actualField: 'dieCutActualDate', statusField: 'dieCutStatus' },
  895. { label: 'Inspection', code: 'inspection', planField: 'inspectionPlanDate', actualField: 'inspectionActualDate', statusField: 'inspectionStatus' },
  896. { label: 'Delivery&Package', code: 'deliveryPackage', planField: 'deliveryPackagePlanDate', actualField: 'deliveryPackageActualDate', statusField: 'deliveryPackageStatus' }
  897. ]
  898. function cloneDefaultProcessColumns () {
  899. return DEFAULT_PROCESS_COLUMNS.map(item => Object.assign({}, item))
  900. }
  901. export default {
  902. name: 'ProjectProofTracking',
  903. directives: {
  904. focusCommentsInput: {
  905. inserted (el, binding, vnode) {
  906. if (binding.value) {
  907. focusCommentsNativeInput(el, vnode)
  908. }
  909. },
  910. componentUpdated (el, binding, vnode) {
  911. if (binding.value && !binding.oldValue) {
  912. focusCommentsNativeInput(el, vnode)
  913. }
  914. }
  915. }
  916. },
  917. components: {
  918. Chooselist
  919. },
  920. data () {
  921. return {
  922. searchData: {
  923. site: this.$store.state.user.site,
  924. userName: this.$store.state.user.name,
  925. projectNo: '',
  926. projectDesc: '',
  927. buNo: '',
  928. testPartNo: '',
  929. partDesc: '',
  930. customerNo: '',
  931. proofingNo: '',
  932. projectPartSyncFlag: '',
  933. proofSyncFlag: '',
  934. proofingStatus: ''
  935. },
  936. processColumns: cloneDefaultProcessColumns(),
  937. buProcessConfigDialogVisible: false,
  938. buProcessConfigRows: [],
  939. buProcessConfigMap: {},
  940. processOrderDragState: {
  941. buNo: '',
  942. fromIndex: -1
  943. },
  944. projectCategoryList: ['Low Risk', 'High Risk', 'Sustaining'],
  945. cProjectRegionList: [
  946. { cProjectRegion: 'Global' },
  947. { cProjectRegion: 'APAC' },
  948. { cProjectRegion: 'CHINA' },
  949. { cProjectRegion: 'EU' },
  950. { cProjectRegion: 'US' },
  951. { cProjectRegion: 'Mexico' },
  952. { cProjectRegion: 'Other' }
  953. ],
  954. priorityList: ['Low', 'Middle', 'High'],
  955. projectStatusList: ['草稿', '进行中', '已关闭'],
  956. partTypeList: ['Active', 'On hold', 'Cancel', 'EOL'],
  957. partStatusList: ['草稿', '进行中', '已量产', '正式量产'],
  958. cProjectTypeDbList: [
  959. { cProjectTypeDb: 'Sustaining' },
  960. { cProjectTypeDb: 'Low Risk' },
  961. { cProjectTypeDb: 'High Risk' }
  962. ],
  963. proofProjectPhaseList: [
  964. { projectPhase: 'Prototype' },
  965. { projectPhase: 'Alpha' },
  966. { projectPhase: 'Beta' },
  967. { projectPhase: 'Pre-launch' }
  968. ],
  969. // 一键创建字段白名单(仅 create 模式生效):后续确认要隐藏哪些字段时,只改这里即可。
  970. oneKeyCreateVisibleFieldKeys: [
  971. 'buNo',
  972. 'projectNo',
  973. 'projectDesc',
  974. 'customerNo',
  975. 'customerDesc',
  976. 'cProjectRegion',
  977. 'projectCreationDate',
  978. 'needDate',
  979. 'projectCategory',
  980. 'priorityLevel',
  981. 'testPartNo',
  982. 'partDesc',
  983. 'partType',
  984. 'projectManager',
  985. 'projectOwner',
  986. 'engineer',
  987. 'cManufactureEngineer',
  988. 'cQualityEngineer6',
  989. 'cQualityEngineer1',
  990. 'cQualityEngineer2',
  991. 'cQualityEngineer4',
  992. 'cQualityEngineer3',
  993. 'cQualityEngineer5',
  994. 'ipqcHardTag',
  995. 'cQualityEngineer7',
  996. 'docEngineer',
  997. 'docEngineer2',
  998. 'projectStatus',
  999. 'projectPhase',
  1000. 'proofingNo',
  1001. 'proofingStatus',
  1002. 'proofingNumber',
  1003. 'planStartDate',
  1004. 'requiredDeliveryDate',
  1005. 'remark'
  1006. ],
  1007. userBuList: [],
  1008. tagNo: '',
  1009. newCustomerFlag: false,
  1010. newCustomerData: {
  1011. customerDesc: ''
  1012. },
  1013. roleDialogVisible: false,
  1014. roleDialogTitle: '',
  1015. roleDialogField: '',
  1016. roleDialogApiKey: '0',
  1017. roleDialogList: [],
  1018. roleSearchData: {
  1019. site: this.$store.state.user.site,
  1020. username: '',
  1021. userDisplay: '',
  1022. active: '',
  1023. page: 1,
  1024. limit: 50
  1025. },
  1026. roleConfig: {
  1027. projectManager: { apiKey: '0', title: 'PM/Sales', nameField: 'projectManagerName' },
  1028. projectOwner: { apiKey: '1', title: 'PjM', nameField: 'projectOwnerName' },
  1029. cQualityEngineer1: { apiKey: '2', title: 'IPQC-Lam/Pri/Etch/Slit', nameField: 'cQualityEngineer1Name' },
  1030. cQualityEngineer2: { apiKey: '3', title: 'IPQC-Converting', nameField: 'cQualityEngineer2Name' },
  1031. cQualityEngineer3: { apiKey: '4', title: 'FQC1', nameField: 'cQualityEngineer3Name' },
  1032. cManufactureEngineer: { apiKey: '5', title: 'MFG', nameField: 'cManufactureEngineerName' },
  1033. engineer: { apiKey: '6', title: 'Engineer', nameField: 'engineerName' },
  1034. cQualityEngineer4: { apiKey: '7', title: 'SQE', nameField: 'cQualityEngineer4Name' },
  1035. cQualityEngineer5: { apiKey: '7', title: 'FQC2', nameField: 'cQualityEngineer5Name' },
  1036. cQualityEngineer6: { apiKey: '7', title: 'IQC', nameField: 'cQualityEngineer6Name' },
  1037. docEngineer: { apiKey: '7', title: '文档工程师', nameField: 'docEngineerName' },
  1038. ipqcHardTag: { apiKey: '7', title: 'IPQC-Hardtag', nameField: 'ipqcHardTagName' },
  1039. cQualityEngineer7: { apiKey: '7', title: '前道工程师', nameField: 'cQualityEngineer7Name' },
  1040. docEngineer2: { apiKey: '8', title: '文档工程师2', nameField: 'docEngineer2Name' }
  1041. },
  1042. dataList: [],
  1043. dataListLoading: false,
  1044. syncNpiLoading: false,
  1045. syncProofLoading: false,
  1046. pageIndex: 1,
  1047. pageSize: 20,
  1048. totalPage: 0,
  1049. currentRow: null,
  1050. manualSelectedTrackingId: null,
  1051. selectionRows: [],
  1052. batchEditMode: false,
  1053. processHistoryMap: {},
  1054. processTooltipHoverKey: '',
  1055. processTooltip: {
  1056. visible: false,
  1057. content: '',
  1058. left: 0,
  1059. top: 0
  1060. },
  1061. processHistoryRows: [],
  1062. commentsEditingTrackingId: null,
  1063. commentsDraft: '',
  1064. commentsSaveLoading: false,
  1065. oneKeyDialogVisible: false,
  1066. oneKeyDialogMode: 'create',
  1067. saveOneKeyLoading: false,
  1068. oneKeyForm: {},
  1069. oneKeyProofingApplyOptions: [],
  1070. oneKeyProofingApplyLoading: false,
  1071. oneKeyProofingApplyQueryToken: 0,
  1072. proofDialogVisible: false,
  1073. proofSaveLoading: false,
  1074. proofDialogData: this.getDefaultProofDialogData(),
  1075. proofDialogApplyOptions: [],
  1076. proofDialogApplyLoading: false,
  1077. proofDialogApplyQueryToken: 0,
  1078. processDialogVisible: false,
  1079. saveProcessLoading: false,
  1080. processForm: {},
  1081. finishDialogVisible: false,
  1082. finishSaveLoading: false,
  1083. finishForm: {
  1084. trackingId: null,
  1085. actualityDeliveryDate: ''
  1086. }
  1087. }
  1088. },
  1089. activated () {
  1090. Promise.all([this.loadUserBuList(), this.queryProcessColumns()]).then(() => {
  1091. this.queryBuProcessConfig()
  1092. })
  1093. this.getDataList()
  1094. },
  1095. computed: {
  1096. visibleProcessColumns () {
  1097. const searchBuNo = this.getSelectedSearchBuNo()
  1098. // 指定了查询BU时,整表进度列按该BU配置展示。
  1099. if (!searchBuNo) {
  1100. return this.processColumns
  1101. }
  1102. const visibleCodes = this.getProcessVisibleCodesByBuNo(searchBuNo)
  1103. if (!visibleCodes) {
  1104. return this.processColumns
  1105. }
  1106. const visibleColumns = this.processColumns.filter(item => visibleCodes.indexOf(item.code) > -1)
  1107. return visibleColumns.length > 0 ? visibleColumns : this.processColumns
  1108. }
  1109. },
  1110. methods: {
  1111. loadUserBuList () {
  1112. if (this.userBuList.length > 0) {
  1113. this.ensureBuProcessConfigRows()
  1114. return Promise.resolve(this.userBuList)
  1115. }
  1116. const params = { username: this.$store.state.user.name }
  1117. return getSiteAndBuByUserName(params).then(({ data }) => {
  1118. if (data && data.code === 0) {
  1119. const excludedBuNoList = ['3_02-Hardtag', '4_04-MHM', '5_05-Alpha']
  1120. this.userBuList = (data.rows || []).filter(item => {
  1121. const buNo = item && item.buNo ? String(item.buNo).trim() : ''
  1122. return excludedBuNoList.indexOf(buNo) === -1
  1123. })
  1124. this.ensureBuProcessConfigRows()
  1125. }
  1126. return this.userBuList
  1127. }).catch(() => {
  1128. this.userBuList = []
  1129. this.ensureBuProcessConfigRows()
  1130. return []
  1131. })
  1132. },
  1133. queryProcessColumns () {
  1134. const selectedBuNo = this.getSelectedSearchBuNo()
  1135. const queryBuNo = selectedBuNo || '*'
  1136. // 列顺序来源规则:
  1137. // 1) 未选择查询 BU:固定按 bu='*' 读取默认工序顺序;
  1138. // 2) 已选择查询 BU:按该 BU 读取;若无配置由后端回退到 bu='*'。
  1139. const inData = {
  1140. site: this.$store.state.user.site,
  1141. userName: this.$store.state.user.name,
  1142. buNo: queryBuNo
  1143. }
  1144. return queryProofTrackingProcessColumns(inData).then(({ data }) => {
  1145. if (!(data && data.code === 0)) {
  1146. this.$message.error((data && data.msg) || '加载工序列配置失败,已使用默认配置')
  1147. this.processColumns = cloneDefaultProcessColumns()
  1148. this.ensureBuProcessConfigRows()
  1149. this.refreshTrackingTableLayout()
  1150. return this.processColumns
  1151. }
  1152. const rows = Array.isArray(data.rows) ? data.rows : []
  1153. // 防御性排序:即使后端 SQL 已 ORDER BY sort_no,前端仍按 sortNo 二次排序,确保展示稳定。
  1154. const sortedRows = rows
  1155. .map((item, index) => {
  1156. const parsedSortNo = parseInt(item && item.sortNo, 10)
  1157. return {
  1158. item: item,
  1159. sourceIndex: index,
  1160. sortNo: Number.isNaN(parsedSortNo) ? Number.MAX_SAFE_INTEGER : parsedSortNo
  1161. }
  1162. })
  1163. .sort((a, b) => {
  1164. if (a.sortNo !== b.sortNo) {
  1165. return a.sortNo - b.sortNo
  1166. }
  1167. return a.sourceIndex - b.sourceIndex
  1168. })
  1169. const nextColumns = []
  1170. const usedCodeMap = {}
  1171. sortedRows.forEach(({ item, sortNo }) => {
  1172. if (!item) {
  1173. return
  1174. }
  1175. const code = item.code == null ? '' : String(item.code).trim()
  1176. const label = item.label == null ? '' : String(item.label).trim()
  1177. const planField = item.planField == null ? '' : String(item.planField).trim()
  1178. const actualField = item.actualField == null ? '' : String(item.actualField).trim()
  1179. const statusField = item.statusField == null ? '' : String(item.statusField).trim()
  1180. if (!code || !label || !planField || !actualField || !statusField || usedCodeMap[code]) {
  1181. return
  1182. }
  1183. usedCodeMap[code] = true
  1184. nextColumns.push({
  1185. label: label,
  1186. code: code,
  1187. planField: planField,
  1188. actualField: actualField,
  1189. statusField: statusField,
  1190. sortNo: sortNo === Number.MAX_SAFE_INTEGER ? null : sortNo
  1191. })
  1192. })
  1193. // 保障切换期可用性:当表里无有效配置时继续使用前端兜底工序定义。
  1194. this.processColumns = nextColumns.length > 0 ? nextColumns : cloneDefaultProcessColumns()
  1195. this.syncDataListProcessValues()
  1196. this.ensureBuProcessConfigRows()
  1197. this.refreshTrackingTableLayout()
  1198. return this.processColumns
  1199. }).catch(() => {
  1200. this.$message.error('加载工序列配置异常,已使用默认配置')
  1201. this.processColumns = cloneDefaultProcessColumns()
  1202. this.syncDataListProcessValues()
  1203. this.ensureBuProcessConfigRows()
  1204. this.refreshTrackingTableLayout()
  1205. return this.processColumns
  1206. })
  1207. },
  1208. getRowProcessValue (row, processCode) {
  1209. if (!row || !processCode) {
  1210. return null
  1211. }
  1212. const processMap = row.processValueMap && typeof row.processValueMap === 'object'
  1213. ? row.processValueMap
  1214. : null
  1215. if (!processMap) {
  1216. return null
  1217. }
  1218. return processMap[processCode] || null
  1219. },
  1220. applyProcessValuesToRow (row) {
  1221. if (!row || !this.processColumns || this.processColumns.length === 0) {
  1222. return
  1223. }
  1224. this.processColumns.forEach(item => {
  1225. if (!item) {
  1226. return
  1227. }
  1228. const processValue = this.getRowProcessValue(row, item.code)
  1229. const planDate = processValue ? this.formatDate(processValue.planDate) : ''
  1230. const actualDate = processValue ? this.formatDate(processValue.actualDate) : ''
  1231. const status = processValue && processValue.status ? String(processValue.status).trim() : ''
  1232. this.$set(row, item.planField, planDate)
  1233. this.$set(row, item.actualField, actualDate)
  1234. this.$set(row, item.statusField, status)
  1235. })
  1236. },
  1237. syncDataListProcessValues () {
  1238. if (!Array.isArray(this.dataList) || this.dataList.length === 0) {
  1239. return
  1240. }
  1241. this.dataList.forEach(row => {
  1242. this.applyProcessValuesToRow(row)
  1243. })
  1244. },
  1245. updateRowProcessValue (row, processCol, status, actualDate) {
  1246. if (!row || !processCol) {
  1247. return
  1248. }
  1249. if (!row.processValueMap || typeof row.processValueMap !== 'object') {
  1250. this.$set(row, 'processValueMap', {})
  1251. }
  1252. const prevValue = this.getRowProcessValue(row, processCol.code) || {}
  1253. this.$set(row.processValueMap, processCol.code, Object.assign({}, prevValue, {
  1254. processCode: processCol.code,
  1255. status: status,
  1256. actualDate: actualDate || null
  1257. }))
  1258. this.$set(row, processCol.statusField, status)
  1259. this.$set(row, processCol.actualField, actualDate)
  1260. },
  1261. getAllProcessCodes () {
  1262. return this.processColumns.map(item => item.code)
  1263. },
  1264. getProcessLabelByCode (processCode) {
  1265. if (!processCode) {
  1266. return ''
  1267. }
  1268. const matched = this.processColumns.find(item => item && item.code === processCode)
  1269. if (matched && matched.label) {
  1270. return matched.label
  1271. }
  1272. const defaultMatched = DEFAULT_PROCESS_COLUMNS.find(item => item && item.code === processCode)
  1273. return defaultMatched && defaultMatched.label ? defaultMatched.label : processCode
  1274. },
  1275. getValidProcessOrderCodes (codes) {
  1276. // 顺序列表不能因为“当前 BU 不可见”而丢项:先保留后端返回顺序,再补齐当前已知工序。
  1277. const orderedCodes = []
  1278. if (Array.isArray(codes)) {
  1279. codes.forEach(code => {
  1280. const codeVal = code == null ? '' : String(code).trim()
  1281. if (!codeVal || orderedCodes.indexOf(codeVal) > -1) {
  1282. return
  1283. }
  1284. orderedCodes.push(codeVal)
  1285. })
  1286. }
  1287. const allCodes = this.getAllProcessCodes()
  1288. allCodes.forEach(code => {
  1289. if (orderedCodes.indexOf(code) === -1) {
  1290. orderedCodes.push(code)
  1291. }
  1292. })
  1293. return orderedCodes
  1294. },
  1295. normalizeBuNo (buNo, site) {
  1296. const rawBuNo = buNo == null ? '' : String(buNo).trim()
  1297. if (!rawBuNo) {
  1298. return ''
  1299. }
  1300. const splitIdx = rawBuNo.indexOf('_')
  1301. if (splitIdx < 1 || splitIdx >= rawBuNo.length - 1) {
  1302. return rawBuNo
  1303. }
  1304. // BU统一按“去掉site前缀”展示与保存,例如 2_01-Label -> 01-Label。
  1305. return rawBuNo.substring(splitIdx + 1)
  1306. },
  1307. getValidProcessCodes (codes, allowedCodes) {
  1308. if (!Array.isArray(codes) || codes.length === 0) {
  1309. return []
  1310. }
  1311. const allCodes = Array.isArray(allowedCodes) && allowedCodes.length > 0
  1312. ? allowedCodes
  1313. .map(code => (code == null ? '' : String(code).trim()))
  1314. .filter(code => !!code)
  1315. : this.getAllProcessCodes()
  1316. const normalizedCodes = []
  1317. codes.forEach(code => {
  1318. const codeVal = code == null ? '' : String(code).trim()
  1319. if (!codeVal || allCodes.indexOf(codeVal) === -1) {
  1320. return
  1321. }
  1322. if (normalizedCodes.indexOf(codeVal) === -1) {
  1323. normalizedCodes.push(codeVal)
  1324. }
  1325. })
  1326. return normalizedCodes
  1327. },
  1328. getBuProcessConfigEntry (buNo, site) {
  1329. const normalizedBuNo = this.normalizeBuNo(buNo, site)
  1330. if (!normalizedBuNo) {
  1331. return null
  1332. }
  1333. const config = this.buProcessConfigMap[normalizedBuNo]
  1334. if (config) {
  1335. return { key: normalizedBuNo, config: config }
  1336. }
  1337. return null
  1338. },
  1339. getSelectedSearchBuNo () {
  1340. return this.normalizeBuNo(this.searchData.buNo, this.searchData.site)
  1341. },
  1342. handleSearchBuChange () {
  1343. // BU筛选变化(含清空)后立即重新查询,并回到第一页。
  1344. this.queryProcessColumns().then(() => {
  1345. this.getDataList('Y')
  1346. })
  1347. },
  1348. getProcessVisibleCodesByBuNo (buNo, site) {
  1349. const configEntry = this.getBuProcessConfigEntry(buNo, site)
  1350. const config = configEntry ? configEntry.config : null
  1351. // 没有配置或配置为全部显示时,返回 null 表示该 BU 工序列不过滤。
  1352. if (!config || config.showAll !== false) {
  1353. return null
  1354. }
  1355. const validCodes = this.getValidProcessCodes(config.processCodes)
  1356. if (validCodes.length === 0 || validCodes.length >= this.processColumns.length) {
  1357. return null
  1358. }
  1359. return validCodes
  1360. },
  1361. isProcessColumnVisibleForRow (row, processCol) {
  1362. if (!row || !processCol) {
  1363. return true
  1364. }
  1365. // 已指定查询BU时,列集合已经按查询BU过滤,不再按行二次隐藏。
  1366. if (this.getSelectedSearchBuNo()) {
  1367. return true
  1368. }
  1369. const visibleCodes = this.getProcessVisibleCodesByBuNo(row.buNo, row.site)
  1370. if (!visibleCodes) {
  1371. return true
  1372. }
  1373. return visibleCodes.indexOf(processCol.code) > -1
  1374. },
  1375. queryBuProcessConfig () {
  1376. const inData = {
  1377. site: this.$store.state.user.site,
  1378. userName: this.$store.state.user.name
  1379. }
  1380. queryProofTrackingBuProcessConfig(inData).then(({ data }) => {
  1381. if (!(data && data.code === 0)) {
  1382. this.$message.error((data && data.msg) || '加载事业部进度展示配置失败')
  1383. return
  1384. }
  1385. const rows = data.rows || []
  1386. const nextMap = {}
  1387. rows.forEach(item => {
  1388. const buNo = this.normalizeBuNo(item && item.buNo ? item.buNo : '', this.$store.state.user.site)
  1389. if (!buNo) {
  1390. return
  1391. }
  1392. const processOrderCodes = this.getValidProcessOrderCodes(item.processOrderCodes)
  1393. const processCodes = this.getValidProcessCodes(item.processCodes, processOrderCodes)
  1394. const showAll = item && item.showAll === true
  1395. ? true
  1396. : processCodes.length >= processOrderCodes.length
  1397. const selectedCodeMap = {}
  1398. processCodes.forEach(code => {
  1399. selectedCodeMap[code] = true
  1400. })
  1401. nextMap[buNo] = {
  1402. showAll: showAll,
  1403. processOrderCodes: processOrderCodes,
  1404. processCodes: showAll ? processOrderCodes.slice() : processOrderCodes.filter(code => selectedCodeMap[code])
  1405. }
  1406. })
  1407. this.buProcessConfigMap = nextMap
  1408. this.ensureBuProcessConfigRows()
  1409. this.refreshTrackingTableLayout()
  1410. }).catch(() => {
  1411. this.$message.error('加载事业部进度展示配置异常')
  1412. })
  1413. },
  1414. ensureBuProcessConfigRows () {
  1415. const allCodes = this.getAllProcessCodes()
  1416. const rows = []
  1417. const usedBuMap = {}
  1418. const appendRow = (buNo, buDesc, site) => {
  1419. const key = this.normalizeBuNo(buNo, site || this.$store.state.user.site)
  1420. if (!key || usedBuMap[key]) {
  1421. return
  1422. }
  1423. usedBuMap[key] = true
  1424. const cfg = this.buProcessConfigMap[key]
  1425. const row = {
  1426. buNo: key,
  1427. buDesc: buDesc || key,
  1428. processOrderCodes: allCodes.slice(),
  1429. processCodes: allCodes.slice()
  1430. }
  1431. if (cfg) {
  1432. const validOrderCodes = this.getValidProcessOrderCodes(cfg.processOrderCodes)
  1433. row.processOrderCodes = validOrderCodes
  1434. if (cfg.showAll === false) {
  1435. const validCodes = this.getValidProcessCodes(cfg.processCodes, validOrderCodes)
  1436. if (validCodes.length > 0 && validCodes.length < validOrderCodes.length) {
  1437. const selectedCodeMap = {}
  1438. validCodes.forEach(code => {
  1439. selectedCodeMap[code] = true
  1440. })
  1441. row.processCodes = validOrderCodes.filter(code => selectedCodeMap[code])
  1442. }
  1443. } else {
  1444. row.processCodes = validOrderCodes.slice()
  1445. }
  1446. }
  1447. rows.push(row)
  1448. }
  1449. this.userBuList.forEach(item => {
  1450. if (!item || !item.buNo) {
  1451. return
  1452. }
  1453. appendRow(item.buNo, item.buDesc || this.normalizeBuNo(item.buNo, this.$store.state.user.site), this.$store.state.user.site)
  1454. })
  1455. Object.keys(this.buProcessConfigMap).forEach(buNo => {
  1456. appendRow(buNo, buNo, this.$store.state.user.site)
  1457. })
  1458. this.buProcessConfigRows = rows
  1459. },
  1460. openBuProcessConfigDialog () {
  1461. this.ensureBuProcessConfigRows()
  1462. this.processOrderDragState = {
  1463. buNo: '',
  1464. fromIndex: -1
  1465. }
  1466. this.buProcessConfigDialogVisible = true
  1467. },
  1468. refreshTrackingTableLayout () {
  1469. this.$nextTick(() => {
  1470. const tableRef = this.$refs.trackingTable
  1471. if (tableRef && tableRef.doLayout) {
  1472. tableRef.doLayout()
  1473. }
  1474. })
  1475. },
  1476. handleBuProcessCodesChange (row) {
  1477. if (!row) {
  1478. return
  1479. }
  1480. const validOrderCodes = this.getValidProcessOrderCodes(row.processOrderCodes)
  1481. const validCodes = this.getValidProcessCodes(row.processCodes, validOrderCodes)
  1482. const selectedCodeMap = {}
  1483. validCodes.forEach(code => {
  1484. selectedCodeMap[code] = true
  1485. })
  1486. row.processOrderCodes = validOrderCodes
  1487. row.processCodes = validOrderCodes.filter(code => selectedCodeMap[code])
  1488. },
  1489. isProcessOrderDraggingItem (row, index) {
  1490. if (!row || index == null) {
  1491. return false
  1492. }
  1493. return this.processOrderDragState.buNo === row.buNo && this.processOrderDragState.fromIndex === index
  1494. },
  1495. handleProcessOrderDragStart (row, index, event) {
  1496. if (!row || index == null || index < 0) {
  1497. return
  1498. }
  1499. this.processOrderDragState = {
  1500. buNo: row.buNo || '',
  1501. fromIndex: index
  1502. }
  1503. if (event && event.dataTransfer) {
  1504. event.dataTransfer.effectAllowed = 'move'
  1505. }
  1506. },
  1507. handleProcessOrderDrop (row, targetIndex, event) {
  1508. if (event && event.dataTransfer) {
  1509. event.dataTransfer.dropEffect = 'move'
  1510. }
  1511. if (!row || targetIndex == null || targetIndex < 0) {
  1512. this.handleProcessOrderDragEnd()
  1513. return
  1514. }
  1515. const dragBuNo = this.processOrderDragState.buNo
  1516. const fromIndex = Number(this.processOrderDragState.fromIndex)
  1517. if (!dragBuNo || dragBuNo !== row.buNo || Number.isNaN(fromIndex) || fromIndex < 0) {
  1518. this.handleProcessOrderDragEnd()
  1519. return
  1520. }
  1521. const validOrderCodes = this.getValidProcessOrderCodes(row.processOrderCodes)
  1522. if (fromIndex >= validOrderCodes.length || targetIndex >= validOrderCodes.length) {
  1523. this.handleProcessOrderDragEnd()
  1524. return
  1525. }
  1526. if (fromIndex === targetIndex) {
  1527. this.handleProcessOrderDragEnd()
  1528. return
  1529. }
  1530. const movedCode = validOrderCodes.splice(fromIndex, 1)[0]
  1531. const insertIndex = targetIndex
  1532. validOrderCodes.splice(insertIndex, 0, movedCode)
  1533. row.processOrderCodes = validOrderCodes
  1534. this.handleBuProcessCodesChange(row)
  1535. this.handleProcessOrderDragEnd()
  1536. },
  1537. handleProcessOrderDragEnd () {
  1538. this.processOrderDragState = {
  1539. buNo: '',
  1540. fromIndex: -1
  1541. }
  1542. },
  1543. saveBuProcessConfig () {
  1544. if (!this.buProcessConfigRows || this.buProcessConfigRows.length === 0) {
  1545. this.$message.warning('暂无可配置的事业部')
  1546. return
  1547. }
  1548. const nextMap = {}
  1549. const submitRows = []
  1550. for (let i = 0; i < this.buProcessConfigRows.length; i++) {
  1551. const row = this.buProcessConfigRows[i]
  1552. const buNo = this.normalizeBuNo(row && row.buNo ? row.buNo : '', this.$store.state.user.site)
  1553. if (!buNo) {
  1554. continue
  1555. }
  1556. const validOrderCodes = this.getValidProcessOrderCodes(row.processOrderCodes)
  1557. const selectedCodeMap = {}
  1558. this.getValidProcessCodes(row.processCodes, validOrderCodes).forEach(code => {
  1559. selectedCodeMap[code] = true
  1560. })
  1561. const validCodes = validOrderCodes.filter(code => selectedCodeMap[code])
  1562. if (validCodes.length === 0) {
  1563. this.$message.warning(`事业部【${row.buDesc || buNo}】至少需要选择一个可见进度`)
  1564. return
  1565. }
  1566. const showAll = validCodes.length >= validOrderCodes.length
  1567. nextMap[buNo] = {
  1568. showAll: showAll,
  1569. processOrderCodes: validOrderCodes,
  1570. processCodes: showAll ? validOrderCodes.slice() : validCodes
  1571. }
  1572. submitRows.push({
  1573. buNo: buNo,
  1574. showAll: showAll,
  1575. processCodes: validCodes,
  1576. processOrderCodes: validOrderCodes
  1577. })
  1578. }
  1579. const inData = {
  1580. site: this.$store.state.user.site,
  1581. updateBy: this.$store.state.user.name,
  1582. rows: submitRows
  1583. }
  1584. saveProofTrackingBuProcessConfig(inData).then(({ data }) => {
  1585. if (!(data && data.code === 0)) {
  1586. this.$message.error((data && data.msg) || '事业部进度展示设置保存失败')
  1587. return
  1588. }
  1589. this.buProcessConfigMap = nextMap
  1590. this.ensureBuProcessConfigRows()
  1591. this.buProcessConfigDialogVisible = false
  1592. this.$message.success(data.msg || '事业部进度展示设置已保存')
  1593. // 保存后重新拉取工序列和列表,确保页面立即按最新可见性与顺序刷新。
  1594. this.queryProcessColumns().then(() => {
  1595. this.getDataList()
  1596. this.refreshTrackingTableLayout()
  1597. })
  1598. }).catch(() => {
  1599. this.$message.error('事业部进度展示设置保存异常')
  1600. })
  1601. },
  1602. getRoleApi (apiKey) {
  1603. const apiMap = {
  1604. 0: searchBusinessInfo,
  1605. 1: searchBusinessInfo1,
  1606. 2: searchBusinessInfo2,
  1607. 3: searchBusinessInfo3,
  1608. 4: searchBusinessInfo4,
  1609. 5: searchBusinessInfo5,
  1610. 6: searchBusinessInfo6,
  1611. 7: searchBusinessInfo7,
  1612. 8: searchBusinessInfo8
  1613. }
  1614. return apiMap[apiKey]
  1615. },
  1616. fetchRoleListByApiKey (apiKey, queryData) {
  1617. const apiFn = this.getRoleApi(apiKey)
  1618. if (!apiFn) {
  1619. return Promise.resolve([])
  1620. }
  1621. const params = {
  1622. site: this.$store.state.user.site,
  1623. username: queryData && queryData.username ? queryData.username : '',
  1624. userDisplay: queryData && queryData.userDisplay ? queryData.userDisplay : '',
  1625. roleDesc: '',
  1626. active: queryData && queryData.active ? queryData.active : '',
  1627. page: 1,
  1628. limit: 200
  1629. }
  1630. return apiFn(params).then(({ data }) => {
  1631. if (data && data.code === 0) {
  1632. return (data.rows || []).map(item => {
  1633. return {
  1634. username: item.username,
  1635. userDisplay: item.userDisplay || item.user_display || '',
  1636. active: item.active
  1637. }
  1638. })
  1639. }
  1640. return []
  1641. })
  1642. },
  1643. newCustomer () {
  1644. this.newCustomerData.customerDesc = ''
  1645. this.newCustomerFlag = true
  1646. },
  1647. getBaseList (val) {
  1648. this.tagNo = val
  1649. this.$nextTick(() => {
  1650. const strVal = val === 509 ? (this.oneKeyForm.customerNo || '') : ''
  1651. this.$refs.baseList.init(val, strVal, '')
  1652. })
  1653. },
  1654. getBaseData (val) {
  1655. if (this.tagNo === 509) {
  1656. this.oneKeyForm.customerNo = val.customer_no
  1657. this.oneKeyForm.customerDesc = val.customer_desc
  1658. }
  1659. },
  1660. customerNoBlur () {
  1661. if (!this.oneKeyForm.customerNo) {
  1662. this.oneKeyForm.customerDesc = ''
  1663. return
  1664. }
  1665. const params = {
  1666. customerNo: this.oneKeyForm.customerNo,
  1667. createBy: this.$store.state.user.name
  1668. }
  1669. queryCustomerList(params).then(({ data }) => {
  1670. if (data && data.code === 0 && data.rows && data.rows.length === 1) {
  1671. this.oneKeyForm.customerDesc = data.rows[0].customerDesc
  1672. } else {
  1673. this.oneKeyForm.customerDesc = ''
  1674. }
  1675. })
  1676. },
  1677. saveNewCustomer () {
  1678. if (!this.newCustomerData.customerDesc) {
  1679. this.$message.warning('请输入客户名称!')
  1680. return
  1681. }
  1682. this.generateNextCustomerNo().then(customerNo => {
  1683. if (!customerNo) {
  1684. this.$alert('该客户已存在,请重新输入客户名称!', '提示', {
  1685. confirmButtonText: '确定',
  1686. type: 'warning'
  1687. })
  1688. return
  1689. }
  1690. const params = {
  1691. site: this.$store.state.user.site,
  1692. customerNo: customerNo,
  1693. customerDesc: this.newCustomerData.customerDesc,
  1694. createBy: this.$store.state.user.name
  1695. }
  1696. saveNewCustomer(params).then(({ data }) => {
  1697. if (data && data.code === 0) {
  1698. this.oneKeyForm.customerNo = customerNo
  1699. this.oneKeyForm.customerDesc = this.newCustomerData.customerDesc
  1700. this.newCustomerFlag = false
  1701. this.$message.success(data.msg || '新增客户成功')
  1702. } else {
  1703. this.$alert(data && data.msg ? data.msg : '新增客户失败', '提示', {
  1704. confirmButtonText: '确定',
  1705. type: 'warning'
  1706. })
  1707. }
  1708. })
  1709. })
  1710. },
  1711. generateNextCustomerNo () {
  1712. const params = {
  1713. site: this.$store.state.user.site,
  1714. customerDesc: this.newCustomerData.customerDesc,
  1715. createBy: this.$store.state.user.name
  1716. }
  1717. return new Promise(resolve => {
  1718. getCustomerNo(params).then(({ data }) => {
  1719. if (!(data && data.code === 0)) {
  1720. resolve(false)
  1721. return
  1722. }
  1723. if (!data.data || !data.data.customerNo || data.data === '0') {
  1724. resolve('C0001')
  1725. return
  1726. }
  1727. const lastCustomerNo = parseInt(String(data.data.customerNo).substring(1), 10) || 0
  1728. const nextCustomerNo = lastCustomerNo + 1
  1729. if (nextCustomerNo < 10) {
  1730. resolve('C000' + nextCustomerNo)
  1731. } else if (nextCustomerNo < 100) {
  1732. resolve('C00' + nextCustomerNo)
  1733. } else if (nextCustomerNo < 1000) {
  1734. resolve('C0' + nextCustomerNo)
  1735. } else {
  1736. resolve('C' + nextCustomerNo)
  1737. }
  1738. }).catch(() => {
  1739. resolve(false)
  1740. })
  1741. })
  1742. },
  1743. closeNewCustomer () {
  1744. this.newCustomerData.customerDesc = ''
  1745. this.newCustomerFlag = false
  1746. },
  1747. openRoleDialog (field) {
  1748. const roleCfg = this.roleConfig[field]
  1749. if (!roleCfg) {
  1750. return
  1751. }
  1752. this.roleDialogField = field
  1753. this.roleDialogApiKey = roleCfg.apiKey
  1754. this.roleDialogTitle = roleCfg.title
  1755. this.roleSearchData = {
  1756. site: this.$store.state.user.site,
  1757. username: '',
  1758. userDisplay: '',
  1759. active: '',
  1760. page: 1,
  1761. limit: 50
  1762. }
  1763. this.roleDialogVisible = true
  1764. this.queryRoleDialogList()
  1765. },
  1766. queryRoleDialogList () {
  1767. this.fetchRoleListByApiKey(this.roleDialogApiKey, this.roleSearchData).then(rows => {
  1768. this.roleDialogList = rows
  1769. })
  1770. },
  1771. pickRole (row) {
  1772. const roleCfg = this.roleConfig[this.roleDialogField]
  1773. if (!roleCfg) {
  1774. return
  1775. }
  1776. const value = row.username + '-' + row.userDisplay
  1777. this.oneKeyForm[this.roleDialogField] = value
  1778. this.oneKeyForm[roleCfg.nameField] = row.userDisplay
  1779. this.roleDialogVisible = false
  1780. },
  1781. getEmptyOneKeyForm () {
  1782. return {
  1783. site: this.$store.state.user.site,
  1784. buNo: '',
  1785. projectNo: '',
  1786. projectName: '',
  1787. projectDesc: '',
  1788. projectStatus: '草稿',
  1789. projectSource: '',
  1790. testPartNo: '',
  1791. partDesc: '',
  1792. partName: '',
  1793. partSpec: '',
  1794. materialNumber: '',
  1795. finalPartDesc: '',
  1796. finalPartNo: '',
  1797. baseNo: '',
  1798. revNo: '',
  1799. customerNo: '',
  1800. customerDesc: '',
  1801. finalCustomerId: '',
  1802. customerRemark: '',
  1803. parentProjectNo: '',
  1804. oriProjectId: '',
  1805. projectCategory: '',
  1806. cProjectRegion: '',
  1807. projectManager: '',
  1808. projectManagerName: '',
  1809. projectOwner: '',
  1810. projectOwnerName: '',
  1811. cQualityEngineer1: '',
  1812. cQualityEngineer1Name: '',
  1813. cQualityEngineer2: '',
  1814. cQualityEngineer2Name: '',
  1815. cQualityEngineer3: '',
  1816. cQualityEngineer3Name: '',
  1817. cQualityEngineer4: '',
  1818. cQualityEngineer4Name: '',
  1819. cQualityEngineer5: '',
  1820. cQualityEngineer5Name: '',
  1821. cQualityEngineer6: '',
  1822. cQualityEngineer6Name: '',
  1823. cManufactureEngineer: '',
  1824. cManufactureEngineerName: '',
  1825. docEngineer: '',
  1826. docEngineerName: '',
  1827. docEngineer2: '',
  1828. docEngineer2Name: '',
  1829. ipqcHardTag: '',
  1830. ipqcHardTagName: '',
  1831. cQualityEngineer7: '',
  1832. cQualityEngineer7Name: '',
  1833. partType: '',
  1834. partStatus: '草稿',
  1835. projectPhase: '',
  1836. tracker: '',
  1837. engineer: '',
  1838. engineerName: '',
  1839. priorityLevel: '',
  1840. proofingNo: '',
  1841. proofingStatus: '草稿',
  1842. proofingNumber: '',
  1843. projectCreationDate: '',
  1844. projectCloseDate: '',
  1845. buildDate: '',
  1846. closeDate: '',
  1847. comments: '',
  1848. planStartDate: '',
  1849. requiredDeliveryDate: '',
  1850. needDate: '',
  1851. remark: ''
  1852. }
  1853. },
  1854. getDefaultProofDialogData () {
  1855. return {
  1856. trackingId: null,
  1857. site: this.$store.state.user.site,
  1858. projectId: null,
  1859. projectNo: '',
  1860. projectDesc: '',
  1861. buNo: '',
  1862. customerNo: '',
  1863. customerDesc: '',
  1864. projectPartId: null,
  1865. testPartNo: '',
  1866. partDesc: '',
  1867. projectCategory: '',
  1868. cProjectTypeDb: '',
  1869. projectPhase: '',
  1870. projectManager: '',
  1871. projectOwner: '',
  1872. engineer: '',
  1873. priorityLevel: '',
  1874. proofingNo: '',
  1875. proofingNumber: '',
  1876. planStartDate: '',
  1877. requiredDeliveryDate: '',
  1878. actualityDeliveryDate: '',
  1879. proofingStatus: '草稿',
  1880. remark: '',
  1881. createBy: this.$store.state.user.name,
  1882. updateBy: this.$store.state.user.name
  1883. }
  1884. },
  1885. getDataList (flag) {
  1886. if (flag === 'Y') {
  1887. this.pageIndex = 1
  1888. }
  1889. this.searchData.site = this.$store.state.user.site
  1890. this.searchData.userName = this.$store.state.user.name
  1891. const inData = Object.assign({}, this.searchData, {
  1892. page: this.pageIndex,
  1893. limit: this.pageSize
  1894. })
  1895. inData.buNo = this.normalizeBuNo(inData.buNo, inData.site)
  1896. this.dataListLoading = true
  1897. searchProofTracking(inData).then(({ data }) => {
  1898. this.dataListLoading = false
  1899. this.resetProcessTooltip()
  1900. if (data && data.code === 0) {
  1901. this.dataList = (data.page && data.page.list) || []
  1902. this.syncDataListProcessValues()
  1903. this.totalPage = (data.page && data.page.totalCount) || 0
  1904. if (this.commentsEditingTrackingId) {
  1905. const hasEditingRow = this.dataList.some(item => item.trackingId === this.commentsEditingTrackingId)
  1906. if (!hasEditingRow) {
  1907. this.cancelCommentsEdit()
  1908. }
  1909. }
  1910. const manualSelectedId = this.manualSelectedTrackingId
  1911. const previousCurrentId = this.currentRow && this.currentRow.trackingId
  1912. let nextCurrentRow = null
  1913. if (manualSelectedId != null) {
  1914. // 只要用户没有手动点新行,刷新后始终尝试回选上次手动选中的那一行
  1915. nextCurrentRow = this.dataList.find(item => item && item.trackingId === manualSelectedId) || null
  1916. } else if (previousCurrentId != null) {
  1917. nextCurrentRow = this.dataList.find(item => item && item.trackingId === previousCurrentId) || null
  1918. }
  1919. if (!nextCurrentRow && manualSelectedId == null && this.dataList.length > 0) {
  1920. nextCurrentRow = this.dataList[0]
  1921. }
  1922. this.currentRow = nextCurrentRow
  1923. this.$nextTick(() => {
  1924. if (this.$refs.trackingTable && typeof this.$refs.trackingTable.setCurrentRow === 'function') {
  1925. this.$refs.trackingTable.setCurrentRow(this.currentRow || null)
  1926. }
  1927. })
  1928. } else {
  1929. this.dataList = []
  1930. this.totalPage = 0
  1931. this.currentRow = null
  1932. this.$nextTick(() => {
  1933. if (this.$refs.trackingTable && typeof this.$refs.trackingTable.setCurrentRow === 'function') {
  1934. this.$refs.trackingTable.setCurrentRow(null)
  1935. }
  1936. })
  1937. this.$message.error(data.msg || '查询失败')
  1938. }
  1939. }).catch(() => {
  1940. this.dataListLoading = false
  1941. this.resetProcessTooltip()
  1942. this.$message.error('查询异常')
  1943. })
  1944. },
  1945. resetSearch () {
  1946. this.searchData.projectNo = ''
  1947. this.searchData.projectDesc = ''
  1948. this.searchData.buNo = ''
  1949. this.searchData.testPartNo = ''
  1950. this.searchData.partDesc = ''
  1951. this.searchData.customerNo = ''
  1952. this.searchData.proofingNo = ''
  1953. this.searchData.projectPartSyncFlag = ''
  1954. this.searchData.proofSyncFlag = ''
  1955. this.searchData.proofingStatus = ''
  1956. this.queryProcessColumns().then(() => {
  1957. this.getDataList('Y')
  1958. })
  1959. },
  1960. selectionChange (rows) {
  1961. this.selectionRows = rows || []
  1962. },
  1963. isCommentsEditing (row) {
  1964. return !!row && row.trackingId === this.commentsEditingTrackingId
  1965. },
  1966. startCommentsEdit (row) {
  1967. if (this.batchEditMode || !row || !row.trackingId || this.commentsSaveLoading) {
  1968. return
  1969. }
  1970. this.commentsEditingTrackingId = row.trackingId
  1971. this.commentsDraft = row.comments || ''
  1972. },
  1973. cancelCommentsEdit () {
  1974. if (this.commentsSaveLoading) {
  1975. return
  1976. }
  1977. this.commentsEditingTrackingId = null
  1978. this.commentsDraft = ''
  1979. },
  1980. handleCommentsBlur (row) {
  1981. if (this.commentsSaveLoading || !this.isCommentsEditing(row)) {
  1982. return
  1983. }
  1984. const comments = this.commentsDraft == null ? '' : String(this.commentsDraft)
  1985. if (comments.trim()) {
  1986. this.saveCommentsEdit(row, {
  1987. fromBlur: true,
  1988. draftComments: comments
  1989. })
  1990. return
  1991. }
  1992. this.cancelCommentsEdit()
  1993. },
  1994. saveCommentsEdit (row, options) {
  1995. const saveOptions = options || {}
  1996. const fromBlur = !!saveOptions.fromBlur
  1997. if (this.commentsSaveLoading) {
  1998. return
  1999. }
  2000. if (!row || !row.trackingId) {
  2001. this.$message.warning('跟踪记录ID不能为空')
  2002. return
  2003. }
  2004. const comments = Object.prototype.hasOwnProperty.call(saveOptions, 'draftComments')
  2005. ? (saveOptions.draftComments == null ? '' : String(saveOptions.draftComments))
  2006. : (this.commentsDraft == null ? '' : String(this.commentsDraft))
  2007. if (comments === (row.comments || '')) {
  2008. this.cancelCommentsEdit()
  2009. return
  2010. }
  2011. if (fromBlur) {
  2012. this.commentsEditingTrackingId = null
  2013. this.commentsDraft = ''
  2014. }
  2015. this.commentsSaveLoading = true
  2016. updateProofTrackingComments({
  2017. trackingId: row.trackingId,
  2018. comments: comments,
  2019. updateBy: this.$store.state.user.name
  2020. }).then(({ data }) => {
  2021. this.commentsSaveLoading = false
  2022. if (data && data.code === 0) {
  2023. row.comments = comments
  2024. this.$message.success('Comments保存成功')
  2025. this.cancelCommentsEdit()
  2026. } else {
  2027. this.$message.error(data.msg || 'Comments保存失败')
  2028. }
  2029. }).catch(() => {
  2030. this.commentsSaveLoading = false
  2031. this.$message.error('Comments保存异常')
  2032. })
  2033. },
  2034. rowClick (row) {
  2035. this.currentRow = row
  2036. this.manualSelectedTrackingId = row && row.trackingId ? row.trackingId : null
  2037. },
  2038. sizeChangeHandle (val) {
  2039. this.pageSize = val
  2040. this.pageIndex = 1
  2041. this.getDataList()
  2042. },
  2043. currentChangeHandle (val) {
  2044. this.pageIndex = val
  2045. this.getDataList()
  2046. },
  2047. getActionRow (row) {
  2048. return row || this.currentRow || (this.selectionRows.length > 0 ? this.selectionRows[0] : null)
  2049. },
  2050. getTrackingProjectPartId (row) {
  2051. if (!row) {
  2052. return null
  2053. }
  2054. return row.projectPartId || row.project_part_id || null
  2055. },
  2056. isSyncedByIdRule (idValue) {
  2057. if (idValue == null || String(idValue).trim() === '') {
  2058. return false
  2059. }
  2060. // 约定:NPI 正式库主键 < 100000,tracking 临时主键 >= 100000。
  2061. const numericId = Number(idValue)
  2062. return Number.isFinite(numericId) && numericId > 0 && numericId < 100000
  2063. },
  2064. isProjectPartSynced (row) {
  2065. if (!row) {
  2066. return false
  2067. }
  2068. const projectId = row.projectId || row.project_id || null
  2069. const projectPartId = this.getTrackingProjectPartId(row)
  2070. return this.isSyncedByIdRule(projectId) && this.isSyncedByIdRule(projectPartId)
  2071. },
  2072. isProofSynced (row) {
  2073. if (!row) {
  2074. return false
  2075. }
  2076. const proofingId = row.proofingId || row.proofing_id || row.trackingId || null
  2077. return this.isSyncedByIdRule(proofingId)
  2078. },
  2079. getProjectPartSyncTextClass (row) {
  2080. return this.isProjectPartSynced(row) ? 'sync-text--ok' : 'sync-text--pending'
  2081. },
  2082. getProofSyncTextClass (row) {
  2083. return this.isProofSynced(row) ? 'sync-text--ok' : 'sync-text--pending'
  2084. },
  2085. getProjectCategoryValue (source) {
  2086. if (!source) {
  2087. return ''
  2088. }
  2089. const category = source.cProjectTypeDb || source.projectCategory || source.project_category || source.c_project_type_db || ''
  2090. return this.isBlankValue(category) ? '' : String(category).trim()
  2091. },
  2092. isOneKeyCreateFieldVisible (fieldKey) {
  2093. if (this.oneKeyDialogMode !== 'create') {
  2094. return true
  2095. }
  2096. return this.oneKeyCreateVisibleFieldKeys.indexOf(fieldKey) > -1
  2097. },
  2098. isBlankValue (value) {
  2099. return value == null || String(value).trim() === ''
  2100. },
  2101. normalizeProjectNo (projectNo) {
  2102. if (this.isBlankValue(projectNo)) {
  2103. return ''
  2104. }
  2105. return String(projectNo).trim()
  2106. },
  2107. queryApplyOptionsByProjectNo (projectNo) {
  2108. const normalizedProjectNo = this.normalizeProjectNo(projectNo)
  2109. if (!normalizedProjectNo) {
  2110. return Promise.resolve([])
  2111. }
  2112. return searchExpApplyList({
  2113. projectNo: normalizedProjectNo,
  2114. page: 1,
  2115. limit: 500
  2116. }).then(({ data }) => {
  2117. if (!(data && data.code === 0)) {
  2118. throw new Error((data && data.msg) || '查询试验单失败')
  2119. }
  2120. const pageList = (data.page && data.page.list) || []
  2121. const matchedProjectNo = normalizedProjectNo.toUpperCase()
  2122. const optionMap = {}
  2123. const optionRows = []
  2124. pageList.forEach(item => {
  2125. if (!item || this.isBlankValue(item.applyNo)) {
  2126. return
  2127. }
  2128. const applyStatus = this.isBlankValue(item.status) ? '' : String(item.status).trim()
  2129. // 业务要求:已完成的试验单不允许继续作为 tracking 打样单号使用。
  2130. if (applyStatus === '草稿' || applyStatus === '已完成') {
  2131. return
  2132. }
  2133. const rowProjectNo = this.normalizeProjectNo(item.projectNo).toUpperCase()
  2134. // searchExpApplyList 按模糊 project_no 查询,这里再次做等值过滤,确保与 tracking 项目编码一一对应。
  2135. if (rowProjectNo !== matchedProjectNo) {
  2136. return
  2137. }
  2138. const applyNo = String(item.applyNo).trim()
  2139. if (optionMap[applyNo]) {
  2140. return
  2141. }
  2142. optionMap[applyNo] = true
  2143. optionRows.push({
  2144. applyNo: applyNo,
  2145. expectedFinishDate: this.formatDate(item.expectedFinishDate)
  2146. })
  2147. })
  2148. return optionRows
  2149. })
  2150. },
  2151. handleOneKeyProjectNoBlur () {
  2152. const projectNo = this.normalizeProjectNo(this.oneKeyForm.projectNo)
  2153. if (!projectNo) {
  2154. this.oneKeyProofingApplyQueryToken += 1
  2155. this.oneKeyProofingApplyLoading = false
  2156. this.oneKeyProofingApplyOptions = []
  2157. this.oneKeyForm.proofingNo = ''
  2158. this.oneKeyForm.requiredDeliveryDate = ''
  2159. return
  2160. }
  2161. this.loadOneKeyProofingApplyOptions(projectNo, { keepCurrentIfMissing: false })
  2162. },
  2163. handleOneKeyProofingNoChange (proofingNo) {
  2164. const selectedNo = this.isBlankValue(proofingNo) ? '' : String(proofingNo).trim()
  2165. if (!selectedNo) {
  2166. this.oneKeyForm.requiredDeliveryDate = ''
  2167. return
  2168. }
  2169. const matched = this.oneKeyProofingApplyOptions.find(item => item && item.applyNo === selectedNo)
  2170. if (!matched) {
  2171. return
  2172. }
  2173. this.oneKeyForm.proofingNo = matched.applyNo
  2174. this.oneKeyForm.requiredDeliveryDate = matched.expectedFinishDate || ''
  2175. },
  2176. loadOneKeyProofingApplyOptions (projectNo, options) {
  2177. const queryOptions = options || {}
  2178. const normalizedProjectNo = this.normalizeProjectNo(projectNo)
  2179. const currentProofingNo = this.isBlankValue(this.oneKeyForm.proofingNo)
  2180. ? ''
  2181. : String(this.oneKeyForm.proofingNo).trim()
  2182. const queryToken = this.oneKeyProofingApplyQueryToken + 1
  2183. this.oneKeyProofingApplyQueryToken = queryToken
  2184. if (!normalizedProjectNo) {
  2185. this.oneKeyProofingApplyLoading = false
  2186. this.oneKeyProofingApplyOptions = []
  2187. return Promise.resolve([])
  2188. }
  2189. this.oneKeyProofingApplyLoading = true
  2190. return this.queryApplyOptionsByProjectNo(normalizedProjectNo).then(optionRows => {
  2191. if (queryToken !== this.oneKeyProofingApplyQueryToken) {
  2192. return optionRows
  2193. }
  2194. const nextOptions = optionRows.slice()
  2195. const keepCurrentIfMissing = !!queryOptions.keepCurrentIfMissing
  2196. if (currentProofingNo) {
  2197. const hasCurrent = nextOptions.some(item => item && item.applyNo === currentProofingNo)
  2198. if (!hasCurrent && keepCurrentIfMissing) {
  2199. // 历史记录可能早于 ERF 规范化,保留原打样单号避免编辑场景被强制清空。
  2200. nextOptions.unshift({
  2201. applyNo: currentProofingNo,
  2202. expectedFinishDate: this.formatDate(this.oneKeyForm.requiredDeliveryDate)
  2203. })
  2204. }
  2205. if (!hasCurrent && !keepCurrentIfMissing) {
  2206. this.oneKeyForm.proofingNo = ''
  2207. this.oneKeyForm.requiredDeliveryDate = ''
  2208. }
  2209. }
  2210. this.oneKeyProofingApplyOptions = nextOptions
  2211. if (currentProofingNo) {
  2212. this.handleOneKeyProofingNoChange(currentProofingNo)
  2213. }
  2214. return nextOptions
  2215. }).catch((e) => {
  2216. if (queryToken === this.oneKeyProofingApplyQueryToken) {
  2217. this.oneKeyProofingApplyOptions = []
  2218. this.$message.error((e && e.message) || '查询试验单异常')
  2219. }
  2220. return []
  2221. }).finally(() => {
  2222. if (queryToken === this.oneKeyProofingApplyQueryToken) {
  2223. this.oneKeyProofingApplyLoading = false
  2224. }
  2225. })
  2226. },
  2227. handleProofDialogProofingNoChange (proofingNo) {
  2228. const selectedNo = this.isBlankValue(proofingNo) ? '' : String(proofingNo).trim()
  2229. if (!selectedNo) {
  2230. this.proofDialogData.requiredDeliveryDate = ''
  2231. return
  2232. }
  2233. const matched = this.proofDialogApplyOptions.find(item => item && item.applyNo === selectedNo)
  2234. if (!matched) {
  2235. return
  2236. }
  2237. this.proofDialogData.proofingNo = matched.applyNo
  2238. this.proofDialogData.requiredDeliveryDate = matched.expectedFinishDate || ''
  2239. },
  2240. loadProofDialogApplyOptions (projectNo) {
  2241. const normalizedProjectNo = this.normalizeProjectNo(projectNo)
  2242. const queryToken = this.proofDialogApplyQueryToken + 1
  2243. this.proofDialogApplyQueryToken = queryToken
  2244. if (!normalizedProjectNo) {
  2245. this.proofDialogApplyLoading = false
  2246. this.proofDialogApplyOptions = []
  2247. this.proofDialogData.proofingNo = ''
  2248. this.proofDialogData.requiredDeliveryDate = ''
  2249. return Promise.resolve([])
  2250. }
  2251. this.proofDialogApplyLoading = true
  2252. return this.queryApplyOptionsByProjectNo(normalizedProjectNo).then(optionRows => {
  2253. if (queryToken !== this.proofDialogApplyQueryToken) {
  2254. return optionRows
  2255. }
  2256. this.proofDialogApplyOptions = optionRows
  2257. const currentProofingNo = this.isBlankValue(this.proofDialogData.proofingNo)
  2258. ? ''
  2259. : String(this.proofDialogData.proofingNo).trim()
  2260. if (currentProofingNo) {
  2261. this.handleProofDialogProofingNoChange(currentProofingNo)
  2262. }
  2263. return optionRows
  2264. }).catch((e) => {
  2265. if (queryToken === this.proofDialogApplyQueryToken) {
  2266. this.proofDialogApplyOptions = []
  2267. this.$message.error((e && e.message) || '查询试验单异常')
  2268. }
  2269. return []
  2270. }).finally(() => {
  2271. if (queryToken === this.proofDialogApplyQueryToken) {
  2272. this.proofDialogApplyLoading = false
  2273. }
  2274. })
  2275. },
  2276. getBuOptionValue (site, buNo) {
  2277. const rawBuNo = buNo == null ? '' : String(buNo).trim()
  2278. if (!rawBuNo) {
  2279. return ''
  2280. }
  2281. if (rawBuNo.indexOf('_') > -1) {
  2282. return rawBuNo
  2283. }
  2284. const targetSuffix = `_${rawBuNo}`
  2285. const matched = this.userBuList.find(item => item && item.buNo && (item.buNo === rawBuNo || item.buNo === `${site}_${rawBuNo}` || String(item.buNo).endsWith(targetSuffix)))
  2286. if (matched && matched.buNo) {
  2287. return matched.buNo
  2288. }
  2289. return site ? `${site}_${rawBuNo}` : rawBuNo
  2290. },
  2291. getRoleDisplayName (value) {
  2292. if (this.isBlankValue(value)) {
  2293. return ''
  2294. }
  2295. const strVal = String(value)
  2296. const splitIdx = strVal.indexOf('-')
  2297. return splitIdx > -1 ? strVal.substring(splitIdx + 1) : strVal
  2298. },
  2299. buildOneKeySubmitPayload () {
  2300. const inData = Object.assign({}, this.oneKeyForm, {
  2301. createBy: this.$store.state.user.name,
  2302. updateBy: this.$store.state.user.name
  2303. })
  2304. if (!inData.projectName && inData.projectDesc) {
  2305. inData.projectName = inData.projectDesc
  2306. }
  2307. if (!inData.projectDesc && inData.projectName) {
  2308. inData.projectDesc = inData.projectName
  2309. }
  2310. if (!inData.tracker) {
  2311. inData.tracker = inData.projectOwner
  2312. }
  2313. if (!inData.priorityLevel) {
  2314. inData.priorityLevel = ''
  2315. }
  2316. // 弹窗仅展示一个立项日期输入,提交时同步到项目与物料两个日期字段,避免物料立项日期被误判为空
  2317. if (!this.isBlankValue(inData.projectCreationDate)) {
  2318. inData.buildDate = inData.projectCreationDate
  2319. } else if (!this.isBlankValue(inData.buildDate)) {
  2320. inData.projectCreationDate = inData.buildDate
  2321. }
  2322. return inData
  2323. },
  2324. validateOneKeySubmitPayload (inData) {
  2325. if (this.isBlankValue(inData.site) || this.isBlankValue(inData.buNo)) {
  2326. this.$message.error('工厂和BU不能为空')
  2327. return false
  2328. }
  2329. // create 模式在保存前要求补齐关键责任人与料号描述,避免创建后出现主责缺失数据。
  2330. if (this.oneKeyDialogMode === 'create') {
  2331. if (this.isOneKeyCreateFieldVisible('partDesc') && this.isBlankValue(inData.partDesc)) {
  2332. this.$message.error('料号描述不能为空')
  2333. return false
  2334. }
  2335. if (this.isOneKeyCreateFieldVisible('projectOwner') && this.isBlankValue(inData.projectOwner)) {
  2336. this.$message.error('PjM不能为空')
  2337. return false
  2338. }
  2339. if (this.isOneKeyCreateFieldVisible('engineer') && this.isBlankValue(inData.engineer)) {
  2340. this.$message.error('Engineer不能为空')
  2341. return false
  2342. }
  2343. }
  2344. if (this.oneKeyDialogMode === 'edit') {
  2345. if (!inData.projectId || !inData.projectPartId || !inData.trackingId) {
  2346. this.$message.error('项目/物料/打样记录ID缺失,无法修改')
  2347. return false
  2348. }
  2349. // 修改模式按业务要求仅保留 3 个必填项,其他业务字段允许为空。
  2350. if (this.isBlankValue(inData.partDesc)) {
  2351. this.$message.error('料号描述不能为空')
  2352. return false
  2353. }
  2354. if (this.isBlankValue(inData.projectOwner)) {
  2355. this.$message.error('PjM不能为空')
  2356. return false
  2357. }
  2358. if (this.isBlankValue(inData.engineer)) {
  2359. this.$message.error('Engineer不能为空')
  2360. return false
  2361. }
  2362. if (!this.isBlankValue(inData.proofingNumber) && !/^[1-9]\d*$/.test(String(inData.proofingNumber))) {
  2363. this.$message.error('打样数量必须是正整数')
  2364. return false
  2365. }
  2366. }
  2367. return true
  2368. },
  2369. buildOneKeyEditForm (current, projectData, partData) {
  2370. const project = projectData || {}
  2371. const part = partData || {}
  2372. const form = Object.assign(this.getEmptyOneKeyForm(), {
  2373. site: project.site || part.site || current.site || this.$store.state.user.site,
  2374. projectId: project.projectId || current.projectId || null,
  2375. projectPartId: part.projectPartId || this.getTrackingProjectPartId(current),
  2376. trackingId: current.trackingId || null,
  2377. buNo: this.getBuOptionValue(project.site || current.site || this.$store.state.user.site, project.buNo || current.buNo),
  2378. projectNo: project.projectNo || current.projectNo || '',
  2379. projectName: project.projectName || project.projectDesc || current.projectName || current.projectDesc || '',
  2380. projectDesc: project.projectDesc || project.projectName || current.projectDesc || current.projectName || '',
  2381. projectStatus: project.status || project.projectStatus || '草稿',
  2382. projectSource: project.projectSource || '',
  2383. testPartNo: part.testPartNo || current.testPartNo || '',
  2384. partDesc: part.partDesc || current.partDesc || '',
  2385. partName: part.partName || '',
  2386. partSpec: part.partSpec || '',
  2387. materialNumber: part.materialNumber || '',
  2388. finalPartDesc: part.finalPartDesc || '',
  2389. finalPartNo: part.finalPartNo || current.finalPartNo || '',
  2390. baseNo: part.baseNo || '',
  2391. revNo: part.revNo || '',
  2392. customerNo: project.customerNo || part.customerNo || current.customerNo || '',
  2393. customerDesc: project.customerDesc || current.customerDesc || '',
  2394. finalCustomerId: project.finalCustomerId || '',
  2395. customerRemark: project.customerRemark || '',
  2396. parentProjectNo: project.parentProjectNo || '',
  2397. oriProjectId: project.oriProjectId || '',
  2398. projectCategory: project.projectCategory || part.projectCategory || current.projectCategory || '',
  2399. cProjectRegion: project.cProjectRegion || current.cProjectRegion || '',
  2400. projectManager: project.projectManager || part.projectManager || current.projectManager || '',
  2401. projectOwner: project.projectOwner || part.projectOwner || current.projectOwner || '',
  2402. cQualityEngineer1: project.cQualityEngineer1 || part.cQualityEngineer1 || '',
  2403. cQualityEngineer2: project.cQualityEngineer2 || part.cQualityEngineer2 || '',
  2404. cQualityEngineer3: project.cQualityEngineer3 || part.cQualityEngineer3 || '',
  2405. cQualityEngineer4: project.cQualityEngineer4 || part.cQualityEngineer4 || '',
  2406. cQualityEngineer5: project.cQualityEngineer5 || part.cQualityEngineer5 || '',
  2407. cQualityEngineer6: project.cQualityEngineer6 || part.cQualityEngineer6 || '',
  2408. cManufactureEngineer: project.cManufactureEngineer || part.cManufactureEngineer || '',
  2409. docEngineer: project.docEngineer || part.docEngineer || '',
  2410. docEngineer2: project.docEngineer2 || part.docEngineer2 || '',
  2411. ipqcHardTag: project.ipqcHardTag || part.ipqcHardTag || '',
  2412. cQualityEngineer7: project.cQualityEngineer7 || part.cQualityEngineer7 || '',
  2413. partType: part.partType || '',
  2414. partStatus: part.status || part.partStatus || '草稿',
  2415. projectPhase: current.projectPhase || '',
  2416. tracker: current.tracker || project.projectOwner || part.projectOwner || '',
  2417. engineer: current.engineer || project.engineer || part.engineer || '',
  2418. priorityLevel: current.priorityLevel || current.priority || project.priority || part.priority || '',
  2419. proofingNo: current.proofingNo || '',
  2420. proofingStatus: current.proofingStatus || '进行中',
  2421. proofingNumber: current.proofingNumber,
  2422. projectCreationDate: project.projectCreationDate || part.buildDate || '',
  2423. projectCloseDate: project.projectCloseDate || '',
  2424. buildDate: part.buildDate || project.projectCreationDate || '',
  2425. closeDate: part.closeDate || '',
  2426. comments: current.comments || '',
  2427. planStartDate: current.planStartDate || '',
  2428. requiredDeliveryDate: current.requiredDeliveryDate || '',
  2429. needDate: project.needDate || part.needDate || current.needDate || '',
  2430. remark: part.remark || project.remark || current.remark || ''
  2431. })
  2432. form.projectManagerName = this.getRoleDisplayName(form.projectManager)
  2433. form.projectOwnerName = this.getRoleDisplayName(form.projectOwner)
  2434. form.engineerName = this.getRoleDisplayName(form.engineer)
  2435. form.cManufactureEngineerName = this.getRoleDisplayName(form.cManufactureEngineer)
  2436. form.cQualityEngineer1Name = this.getRoleDisplayName(form.cQualityEngineer1)
  2437. form.cQualityEngineer2Name = this.getRoleDisplayName(form.cQualityEngineer2)
  2438. form.cQualityEngineer3Name = this.getRoleDisplayName(form.cQualityEngineer3)
  2439. form.cQualityEngineer4Name = this.getRoleDisplayName(form.cQualityEngineer4)
  2440. form.cQualityEngineer5Name = this.getRoleDisplayName(form.cQualityEngineer5)
  2441. form.cQualityEngineer6Name = this.getRoleDisplayName(form.cQualityEngineer6)
  2442. form.docEngineerName = this.getRoleDisplayName(form.docEngineer)
  2443. form.docEngineer2Name = this.getRoleDisplayName(form.docEngineer2)
  2444. form.ipqcHardTagName = this.getRoleDisplayName(form.ipqcHardTag)
  2445. form.cQualityEngineer7Name = this.getRoleDisplayName(form.cQualityEngineer7)
  2446. return form
  2447. },
  2448. openOneKeyDialog (mode, row) {
  2449. const dialogMode = mode === 'edit' ? 'edit' : 'create'
  2450. this.oneKeyDialogMode = dialogMode
  2451. if (dialogMode !== 'edit') {
  2452. this.oneKeyProofingApplyQueryToken += 1
  2453. this.oneKeyProofingApplyLoading = false
  2454. this.oneKeyProofingApplyOptions = []
  2455. this.oneKeyForm = this.getEmptyOneKeyForm()
  2456. if (this.userBuList.length > 0) {
  2457. this.oneKeyForm.buNo = this.userBuList[0].buNo || ''
  2458. }
  2459. this.oneKeyDialogVisible = true
  2460. return
  2461. }
  2462. const current = this.getActionRow(row)
  2463. if (!current) {
  2464. this.$message.warning('请先选择一条记录后再修改项目/物料/打样')
  2465. this.oneKeyDialogMode = 'create'
  2466. return
  2467. }
  2468. const projectPartId = this.getTrackingProjectPartId(current)
  2469. if (!current.projectId || !projectPartId || !current.trackingId) {
  2470. this.$message.warning('当前记录缺少项目/物料/打样关键信息,无法修改')
  2471. this.oneKeyDialogMode = 'create'
  2472. return
  2473. }
  2474. const queryUser = this.$store.state.user.name
  2475. const querySite = current.site || this.$store.state.user.site
  2476. this.saveOneKeyLoading = true
  2477. Promise.all([
  2478. searchProjectInfoTracking({
  2479. site: querySite,
  2480. userName: queryUser,
  2481. projectId: current.projectId,
  2482. page: 1,
  2483. limit: 1
  2484. }),
  2485. searchProjectPartTracking({
  2486. site: querySite,
  2487. userName: queryUser,
  2488. projectPartId: projectPartId,
  2489. page: 1,
  2490. limit: 1
  2491. })
  2492. ]).then(([projectResp, partResp]) => {
  2493. const projectData = projectResp && projectResp.data
  2494. const partData = partResp && partResp.data
  2495. if (!projectData || projectData.code !== 0) {
  2496. throw new Error((projectData && projectData.msg) || '加载项目信息失败')
  2497. }
  2498. if (!partData || partData.code !== 0) {
  2499. throw new Error((partData && partData.msg) || '加载项目物料信息失败')
  2500. }
  2501. const projectRows = (projectData.page && projectData.page.list) || []
  2502. const partRows = (partData.page && partData.page.list) || []
  2503. if (projectRows.length === 0) {
  2504. throw new Error('未找到对应的项目信息,无法修改')
  2505. }
  2506. if (partRows.length === 0) {
  2507. throw new Error('未找到对应的项目物料信息,无法修改')
  2508. }
  2509. this.oneKeyForm = this.buildOneKeyEditForm(current, projectRows[0], partRows[0])
  2510. this.oneKeyProofingApplyQueryToken += 1
  2511. this.oneKeyProofingApplyLoading = false
  2512. this.oneKeyProofingApplyOptions = []
  2513. this.oneKeyDialogVisible = true
  2514. this.loadOneKeyProofingApplyOptions(this.oneKeyForm.projectNo, { keepCurrentIfMissing: true })
  2515. }).catch((e) => {
  2516. this.$message.error((e && e.message) || '加载项目/物料/打样信息异常')
  2517. this.oneKeyDialogMode = 'create'
  2518. }).finally(() => {
  2519. this.saveOneKeyLoading = false
  2520. })
  2521. },
  2522. submitOneKey () {
  2523. const inData = this.buildOneKeySubmitPayload()
  2524. if (!this.validateOneKeySubmitPayload(inData)) {
  2525. return
  2526. }
  2527. this.saveOneKeyLoading = true
  2528. if (this.oneKeyDialogMode === 'edit') {
  2529. oneKeyUpdateProofTracking(inData).then(({ data }) => {
  2530. if (!data || data.code !== 0) {
  2531. throw new Error((data && data.msg) || '修改项目/物料/打样失败')
  2532. }
  2533. this.$message.success('项目/物料/打样修改成功')
  2534. this.oneKeyDialogVisible = false
  2535. this.getDataList()
  2536. }).catch((e) => {
  2537. this.$message.error((e && e.message) || '项目/物料/打样修改异常')
  2538. }).finally(() => {
  2539. this.saveOneKeyLoading = false
  2540. })
  2541. return
  2542. }
  2543. oneKeyCreateProofTracking(inData).then(({ data }) => {
  2544. this.saveOneKeyLoading = false
  2545. if (data && data.code === 0) {
  2546. this.$message.success('一键创建成功')
  2547. this.oneKeyDialogVisible = false
  2548. this.searchData.projectNo = ''
  2549. this.searchData.projectDesc = ''
  2550. this.searchData.testPartNo = ''
  2551. this.searchData.partDesc = ''
  2552. this.searchData.customerNo = ''
  2553. this.searchData.proofingNo = ''
  2554. this.searchData.projectPartSyncFlag = ''
  2555. this.searchData.proofSyncFlag = ''
  2556. this.searchData.proofingStatus = ''
  2557. this.getDataList('Y')
  2558. } else {
  2559. this.$message.error(data.msg || '一键创建失败')
  2560. }
  2561. }).catch(() => {
  2562. this.saveOneKeyLoading = false
  2563. this.$message.error('一键创建异常')
  2564. })
  2565. },
  2566. openCreateProofDialog (row) {
  2567. const current = this.getActionRow(row)
  2568. if (!current) {
  2569. this.$message.warning('请先选择一条记录')
  2570. return
  2571. }
  2572. this.currentRow = current
  2573. const projectPartId = this.getTrackingProjectPartId(current) || current.id
  2574. const projectCategory = this.getProjectCategoryValue(current)
  2575. this.proofDialogApplyQueryToken += 1
  2576. this.proofDialogApplyLoading = false
  2577. this.proofDialogApplyOptions = []
  2578. this.proofDialogData = Object.assign(this.getDefaultProofDialogData(), {
  2579. trackingId: null,
  2580. site: current.site || this.$store.state.user.site,
  2581. projectId: current.projectId,
  2582. projectNo: current.projectNo,
  2583. projectDesc: current.projectDesc,
  2584. buNo: current.buNo,
  2585. customerNo: current.customerNo,
  2586. customerDesc: current.customerDesc,
  2587. projectPartId: projectPartId,
  2588. testPartNo: current.testPartNo,
  2589. partDesc: current.partDesc,
  2590. projectCategory: projectCategory,
  2591. cProjectTypeDb: projectCategory,
  2592. projectManager: current.projectManager,
  2593. projectOwner: current.projectOwner,
  2594. engineer: current.engineer,
  2595. priorityLevel: current.priority,
  2596. proofingNo: '',
  2597. proofingNumber: '',
  2598. planStartDate: '',
  2599. requiredDeliveryDate: '',
  2600. actualityDeliveryDate: '',
  2601. proofingStatus: '草稿',
  2602. remark: '',
  2603. createBy: this.$store.state.user.name,
  2604. updateBy: this.$store.state.user.name
  2605. })
  2606. if (this.isBlankValue(projectCategory)) {
  2607. this.$message.warning('未获取到项目分类,请手动选择')
  2608. }
  2609. this.proofDialogVisible = true
  2610. this.loadProofDialogApplyOptions(this.proofDialogData.projectNo)
  2611. },
  2612. validateSyncToNpiRow (row) {
  2613. const projectPartId = this.getTrackingProjectPartId(row)
  2614. const missingLabels = []
  2615. if (this.isBlankValue(row && row.site)) {
  2616. missingLabels.push('工厂')
  2617. }
  2618. if (this.isBlankValue(row && row.buNo)) {
  2619. missingLabels.push('BU')
  2620. }
  2621. if (!row || !row.projectId) {
  2622. missingLabels.push('项目ID')
  2623. }
  2624. if (this.isBlankValue(row && row.projectNo)) {
  2625. missingLabels.push('项目编码')
  2626. }
  2627. if (this.isBlankValue(row && row.projectName) && this.isBlankValue(row && row.projectDesc)) {
  2628. missingLabels.push('项目名称')
  2629. }
  2630. if (!projectPartId) {
  2631. missingLabels.push('项目物料ID')
  2632. }
  2633. if (this.isBlankValue(row && row.testPartNo)) {
  2634. missingLabels.push('项目料号')
  2635. }
  2636. if (this.isBlankValue(row && row.partDesc)) {
  2637. missingLabels.push('料号描述')
  2638. }
  2639. if (missingLabels.length > 0) {
  2640. const missingHtml = missingLabels.map(item => `- ${item}`).join('<br/>')
  2641. this.$alert(`同步到NPI前,请先完善以下字段:<br/>${missingHtml}`, '提示', {
  2642. confirmButtonText: '确定',
  2643. dangerouslyUseHTMLString: true
  2644. })
  2645. return {
  2646. passed: false,
  2647. projectPartId: projectPartId
  2648. }
  2649. }
  2650. return {
  2651. passed: true,
  2652. projectPartId: projectPartId
  2653. }
  2654. },
  2655. syncToNpi (row) {
  2656. const current = this.getActionRow(row)
  2657. if (!current) {
  2658. this.$message.warning('请先选择一条记录')
  2659. return
  2660. }
  2661. const syncCheck = this.validateSyncToNpiRow(current)
  2662. if (!syncCheck.passed) {
  2663. return
  2664. }
  2665. const projectPartId = syncCheck.projectPartId
  2666. this.currentRow = current
  2667. const projectNo = current.projectNo || '-'
  2668. const testPartNo = current.testPartNo || '-'
  2669. const confirmMsg = `确认将项目编码[${projectNo}]物料编码[${testPartNo}]同步到NPI吗?`
  2670. this.$confirm(confirmMsg, '提示', {
  2671. confirmButtonText: '确定',
  2672. cancelButtonText: '取消',
  2673. type: 'warning',
  2674. dangerouslyUseHTMLString: true
  2675. }).then(() => {
  2676. const operator = this.$store.state.user.name
  2677. this.syncNpiLoading = true
  2678. syncProjectPartTracking({
  2679. projectPartId: projectPartId,
  2680. updateBy: operator,
  2681. createBy: operator,
  2682. userName: operator
  2683. }).then(({ data }) => {
  2684. if (data && data.code === 0) {
  2685. this.$message.success(`同步成功`)
  2686. this.getDataList()
  2687. } else {
  2688. const failMsg = (data && data.msg) || '同步到NPI失败'
  2689. if (failMsg.indexOf('缺少以下必填项') > -1) {
  2690. this.$alert(failMsg, '同步校验未通过', {
  2691. confirmButtonText: '确定'
  2692. })
  2693. } else {
  2694. this.$message.error(failMsg)
  2695. }
  2696. }
  2697. }).catch(() => {
  2698. this.$message.error('同步到NPI异常')
  2699. }).finally(() => {
  2700. this.syncNpiLoading = false
  2701. })
  2702. })
  2703. .catch(() => {})
  2704. },
  2705. syncProofToNpi (row) {
  2706. const current = this.getActionRow(row)
  2707. if (!current || !current.trackingId) {
  2708. this.$message.warning('请先选择一条打样记录')
  2709. return
  2710. }
  2711. this.currentRow = current
  2712. const projectNo = current.projectNo || '-'
  2713. const testPartNo = current.testPartNo || '-'
  2714. const proofingNo = current.proofingNo || '-'
  2715. const confirmMsg = `确认将当前打样记录同步到NPI吗?<br/>项目编码:${projectNo}<br/>物料编码:${testPartNo}<br/>打样单号:${proofingNo}`
  2716. this.$confirm(confirmMsg, '提示', {
  2717. confirmButtonText: '确定',
  2718. cancelButtonText: '取消',
  2719. type: 'warning',
  2720. dangerouslyUseHTMLString: true
  2721. }).then(() => {
  2722. const operator = this.$store.state.user.name
  2723. this.syncProofLoading = true
  2724. syncProofTracking({
  2725. trackingId: current.trackingId,
  2726. updateBy: operator,
  2727. createBy: operator,
  2728. userName: operator
  2729. }).then(({ data }) => {
  2730. if (data && data.code === 0) {
  2731. this.$message.success('打样同步成功')
  2732. this.getDataList()
  2733. } else {
  2734. const failMsg = (data && data.msg) || '同步打样到NPI失败'
  2735. if (failMsg.indexOf('缺少以下必填项') > -1 || failMsg.indexOf('请先同步项目') > -1 || failMsg.indexOf('请先同步项目物料') > -1) {
  2736. this.$alert(failMsg, '同步校验未通过', {
  2737. confirmButtonText: '确定'
  2738. })
  2739. } else {
  2740. this.$message.error(failMsg)
  2741. }
  2742. }
  2743. }).catch(() => {
  2744. this.$message.error('同步打样到NPI异常')
  2745. }).finally(() => {
  2746. this.syncProofLoading = false
  2747. })
  2748. }).catch(() => {})
  2749. },
  2750. validateProofDialogData () {
  2751. if (!this.proofDialogData.projectId || !this.proofDialogData.projectPartId) {
  2752. this.$message.warning('项目或项目料号信息不完整')
  2753. return false
  2754. }
  2755. if (!this.proofDialogData.cProjectTypeDb) {
  2756. this.$message.warning('项目分类不能为空')
  2757. return false
  2758. }
  2759. if (!this.proofDialogData.projectPhase) {
  2760. this.$message.warning('项目阶段不能为空')
  2761. return false
  2762. }
  2763. if (!this.proofDialogData.proofingNo) {
  2764. this.$message.warning('打样单号不能为空')
  2765. return false
  2766. }
  2767. if (!this.proofDialogData.proofingNumber) {
  2768. this.$message.warning('数量不能为空')
  2769. return false
  2770. }
  2771. if (!/^[1-9]\d*$/.test(String(this.proofDialogData.proofingNumber))) {
  2772. this.$alert('数量必需是大于等于0的正整数', '提示', {
  2773. confirmButtonText: '确定'
  2774. })
  2775. return false
  2776. }
  2777. if (!this.proofDialogData.planStartDate) {
  2778. this.$message.warning('打样开始日期不能为空')
  2779. return false
  2780. }
  2781. if (!this.proofDialogData.requiredDeliveryDate) {
  2782. this.$message.warning('预计完成日期不能为空')
  2783. return false
  2784. }
  2785. return true
  2786. },
  2787. buildProofPayload () {
  2788. return {
  2789. site: this.proofDialogData.site,
  2790. projectId: this.proofDialogData.projectId,
  2791. projectPartId: this.proofDialogData.projectPartId,
  2792. projectNo: this.proofDialogData.projectNo,
  2793. testPartNo: this.proofDialogData.testPartNo,
  2794. customerNo: this.proofDialogData.customerNo,
  2795. buNo: this.proofDialogData.buNo,
  2796. projectCategory: this.proofDialogData.cProjectTypeDb,
  2797. projectPhase: this.proofDialogData.projectPhase,
  2798. proofingNo: this.proofDialogData.proofingNo,
  2799. proofingNumber: Number(this.proofDialogData.proofingNumber),
  2800. planStartDate: this.proofDialogData.planStartDate,
  2801. requiredDeliveryDate: this.proofDialogData.requiredDeliveryDate,
  2802. actualityDeliveryDate: this.proofDialogData.actualityDeliveryDate,
  2803. proofingStatus: this.proofDialogData.proofingStatus || '草稿',
  2804. remark: this.proofDialogData.remark,
  2805. tracker: this.proofDialogData.projectOwner,
  2806. engineer: this.proofDialogData.engineer,
  2807. priorityLevel: this.proofDialogData.priorityLevel,
  2808. createBy: this.$store.state.user.name,
  2809. updateBy: this.$store.state.user.name
  2810. }
  2811. },
  2812. saveProofRecord () {
  2813. if (!this.validateProofDialogData()) {
  2814. return
  2815. }
  2816. const payload = this.buildProofPayload()
  2817. this.proofSaveLoading = true
  2818. createProofTrackingRecord(payload).then(({ data }) => {
  2819. this.proofSaveLoading = false
  2820. if (data && data.code === 0) {
  2821. this.$message.success(data.msg || '新增打样成功')
  2822. this.proofDialogVisible = false
  2823. this.getDataList()
  2824. } else {
  2825. this.$alert(data && data.msg ? data.msg : '新增打样失败', '错误', { confirmButtonText: '确定' })
  2826. }
  2827. }).catch(() => {
  2828. this.proofSaveLoading = false
  2829. this.$message.error('新增打样异常')
  2830. })
  2831. },
  2832. toggleBatchEdit () {
  2833. this.batchEditMode = !this.batchEditMode
  2834. if (this.batchEditMode) {
  2835. this.cancelCommentsEdit()
  2836. }
  2837. this.$nextTick(() => {
  2838. const tableRef = this.$refs.trackingTable
  2839. if (tableRef && tableRef.doLayout) {
  2840. tableRef.doLayout()
  2841. }
  2842. if (this.batchEditMode) {
  2843. setTimeout(() => {
  2844. this.scrollToRawMaterialColumn()
  2845. }, 0)
  2846. }
  2847. })
  2848. },
  2849. scrollToRawMaterialColumn () {
  2850. const tableRef = this.$refs.trackingTable
  2851. if (!tableRef || !tableRef.$el) {
  2852. return
  2853. }
  2854. const headerWrapper = tableRef.headerWrapper || tableRef.$el.querySelector('.el-table__header-wrapper')
  2855. const bodyWrapper = tableRef.bodyWrapper || tableRef.$el.querySelector('.el-table__body-wrapper')
  2856. if (!headerWrapper || !bodyWrapper) {
  2857. return
  2858. }
  2859. const headerCells = headerWrapper.querySelectorAll('th')
  2860. let targetCell = null
  2861. for (let i = 0; i < headerCells.length; i++) {
  2862. const text = (headerCells[i].innerText || '').replace(/\s+/g, ' ').trim()
  2863. if (text === 'Raw Material') {
  2864. targetCell = headerCells[i]
  2865. break
  2866. }
  2867. }
  2868. if (!targetCell) {
  2869. return
  2870. }
  2871. const fixedLeftWrapper = tableRef.$el.querySelector('.el-table__fixed')
  2872. const fixedLeftWidth = fixedLeftWrapper ? fixedLeftWrapper.offsetWidth : 0
  2873. const nextScrollLeft = Math.max(targetCell.offsetLeft - fixedLeftWidth - 8, 0)
  2874. bodyWrapper.scrollLeft = nextScrollLeft
  2875. headerWrapper.scrollLeft = nextScrollLeft
  2876. },
  2877. saveBatchPlan () {
  2878. const rows = this.selectionRows && this.selectionRows.length > 0 ? this.selectionRows : this.dataList
  2879. if (!rows || rows.length === 0) {
  2880. this.$message.warning('没有可保存的数据')
  2881. return
  2882. }
  2883. const payloadRows = rows.map(row => ({
  2884. trackingId: row.trackingId,
  2885. rawMaterialActualDate: row.rawMaterialActualDate,
  2886. cylinderCutterActualDate: row.cylinderCutterActualDate,
  2887. laminationActualDate: row.laminationActualDate,
  2888. printingActualDate: row.printingActualDate,
  2889. etchingActualDate: row.etchingActualDate,
  2890. slittingPackingActualDate: row.slittingPackingActualDate,
  2891. bondingActualDate: row.bondingActualDate,
  2892. cl60Cl822ActualDate: row.cl60Cl822ActualDate,
  2893. spottingActualDate: row.spottingActualDate,
  2894. encodingActualDate: row.encodingActualDate,
  2895. dieCutActualDate: row.dieCutActualDate,
  2896. inspectionActualDate: row.inspectionActualDate,
  2897. deliveryPackageActualDate: row.deliveryPackageActualDate,
  2898. comments: row.comments == null ? '' : String(row.comments),
  2899. updateBy: this.$store.state.user.name
  2900. }))
  2901. batchUpdateProofTrackingPlan({
  2902. updateBy: this.$store.state.user.name,
  2903. rows: payloadRows
  2904. }).then(({ data }) => {
  2905. if (data && data.code === 0) {
  2906. this.$message.success('批量修改成功')
  2907. this.batchEditMode = false
  2908. this.getDataList()
  2909. } else {
  2910. this.$message.error(data.msg || '批量修改失败')
  2911. }
  2912. }).catch(() => {
  2913. this.$message.error('批量修改异常')
  2914. })
  2915. },
  2916. handleProcessDateChange (row, processCol, dateVal) {
  2917. if (!row || !processCol) {
  2918. return
  2919. }
  2920. const actualDate = this.formatDate(dateVal)
  2921. // 工序日期被清空时,状态必须回退为“未完成”,避免出现“无日期但已完成”的脏状态。
  2922. const nextStatus = actualDate ? '已完成' : '未完成'
  2923. this.updateProcessFromTable(row, processCol, {
  2924. status: nextStatus,
  2925. actualDate: actualDate,
  2926. remark: actualDate ? '表格直接选择日期' : '表格清空实际完成日期'
  2927. }, true)
  2928. },
  2929. markProcessComplete (row, processCol) {
  2930. if (!row || !processCol || this.isProcessComplete(row, processCol)) {
  2931. return
  2932. }
  2933. const today = this.formatDate(new Date())
  2934. this.updateProcessFromTable(row, processCol, {
  2935. status: '已完成',
  2936. actualDate: today,
  2937. remark: '表格一键完成'
  2938. }, false, '工序已标记为完成')
  2939. },
  2940. updateProcessFromTable (row, processCol, processData, silentSuccess, successMsg) {
  2941. if (!row || !processCol || !row.trackingId) {
  2942. this.$message.error('工序参数缺失')
  2943. return
  2944. }
  2945. const status = processData && processData.status ? String(processData.status).trim() : ''
  2946. if (!status) {
  2947. this.$message.error('工序状态不能为空')
  2948. return
  2949. }
  2950. const actualDate = processData && processData.actualDate ? this.formatDate(processData.actualDate) : ''
  2951. const inData = {
  2952. trackingId: row.trackingId,
  2953. processCode: processCol.code,
  2954. processName: processCol.label,
  2955. status: status,
  2956. actualDate: actualDate || null,
  2957. remark: processData && processData.remark ? processData.remark : '',
  2958. updateBy: this.$store.state.user.name
  2959. }
  2960. updateProofTrackingProcess(inData).then(({ data }) => {
  2961. if (data && data.code === 0) {
  2962. this.updateRowProcessValue(row, processCol, status, actualDate)
  2963. const cacheKey = `${row.trackingId}_${processCol.code}`
  2964. if (this.processHistoryMap[cacheKey]) {
  2965. this.$delete(this.processHistoryMap, cacheKey)
  2966. }
  2967. if (!silentSuccess) {
  2968. this.$message.success(successMsg || '工序更新成功')
  2969. }
  2970. } else {
  2971. this.$message.error(data.msg || '工序更新失败')
  2972. this.getDataList()
  2973. }
  2974. }).catch(() => {
  2975. this.$message.error('工序更新异常')
  2976. this.getDataList()
  2977. })
  2978. },
  2979. openProcessDialog (row, processCol) {
  2980. if (!row || !processCol) {
  2981. return
  2982. }
  2983. this.currentRow = row
  2984. this.processForm = {
  2985. trackingId: row.trackingId,
  2986. processCode: processCol.code,
  2987. processName: processCol.label,
  2988. status: row[processCol.statusField] || '未完成',
  2989. actualDate: row[processCol.actualField] || '',
  2990. remark: ''
  2991. }
  2992. this.processHistoryRows = []
  2993. this.processDialogVisible = true
  2994. this.loadProcessHistory(row, processCol, true)
  2995. },
  2996. submitProcessUpdate () {
  2997. if (!this.processForm.trackingId || !this.processForm.processCode) {
  2998. this.$message.error('参数缺失')
  2999. return
  3000. }
  3001. if (!this.processForm.status) {
  3002. this.$message.error('请选择状态')
  3003. return
  3004. }
  3005. const inData = Object.assign({}, this.processForm, {
  3006. updateBy: this.$store.state.user.name
  3007. })
  3008. this.saveProcessLoading = true
  3009. updateProofTrackingProcess(inData).then(({ data }) => {
  3010. this.saveProcessLoading = false
  3011. if (data && data.code === 0) {
  3012. this.$message.success('工序更新成功')
  3013. this.processDialogVisible = false
  3014. this.processHistoryMap = {}
  3015. this.getDataList()
  3016. } else {
  3017. this.$message.error(data.msg || '工序更新失败')
  3018. }
  3019. }).catch(() => {
  3020. this.saveProcessLoading = false
  3021. this.$message.error('工序更新异常')
  3022. })
  3023. },
  3024. finishProof (row) {
  3025. const current = row || this.currentRow
  3026. if (!current || !current.trackingId) {
  3027. this.$message.warning('请先选择记录')
  3028. return
  3029. }
  3030. this.finishForm = {
  3031. trackingId: current.trackingId,
  3032. actualityDeliveryDate: current.actualityDeliveryDate || this.formatDate(new Date())
  3033. }
  3034. this.finishDialogVisible = true
  3035. },
  3036. deleteProof (row) {
  3037. const current = row || this.currentRow
  3038. if (!current || !current.trackingId) {
  3039. this.$message.warning('请先选择记录')
  3040. return
  3041. }
  3042. this.$confirm('确定删除该打样记录吗?该操作仅删除打样记录,不会删除项目和项目物料。', '提示', {
  3043. confirmButtonText: '确定',
  3044. cancelButtonText: '取消',
  3045. type: 'warning'
  3046. }).then(() => {
  3047. deleteProofTracking({
  3048. trackingId: current.trackingId,
  3049. updateBy: this.$store.state.user.name
  3050. }).then(({ data }) => {
  3051. if (data && data.code === 0) {
  3052. this.$message.success('打样记录删除成功')
  3053. this.processHistoryMap = {}
  3054. this.resetProcessTooltip()
  3055. this.getDataList()
  3056. } else {
  3057. this.$message.error((data && data.msg) || '删除打样记录失败')
  3058. }
  3059. }).catch(() => {
  3060. this.$message.error('删除打样记录异常')
  3061. })
  3062. }).catch(() => {})
  3063. },
  3064. submitFinishProof () {
  3065. if (!this.finishForm.trackingId) {
  3066. this.$message.warning('跟踪记录ID不能为空')
  3067. return
  3068. }
  3069. if (!this.finishForm.actualityDeliveryDate) {
  3070. this.$message.warning('请选择实际完成日期')
  3071. return
  3072. }
  3073. this.finishSaveLoading = true
  3074. finishProofTracking({
  3075. trackingId: this.finishForm.trackingId,
  3076. actualityDeliveryDate: this.finishForm.actualityDeliveryDate,
  3077. updateBy: this.$store.state.user.name
  3078. }).then(({ data }) => {
  3079. this.finishSaveLoading = false
  3080. if (data && data.code === 0) {
  3081. this.$message.success('打样完成成功')
  3082. this.finishDialogVisible = false
  3083. this.getDataList()
  3084. } else {
  3085. this.$message.error(data.msg || '打样完成失败')
  3086. }
  3087. }).catch(() => {
  3088. this.finishSaveLoading = false
  3089. this.$message.error('打样完成异常')
  3090. })
  3091. },
  3092. getProcessCacheKey (row, processCol) {
  3093. if (!row || !processCol || !row.trackingId) {
  3094. return ''
  3095. }
  3096. return `${row.trackingId}_${processCol.code}`
  3097. },
  3098. resetProcessTooltip () {
  3099. this.processTooltipHoverKey = ''
  3100. this.processTooltip.visible = false
  3101. this.processTooltip.content = ''
  3102. },
  3103. hideProcessTooltip () {
  3104. this.processTooltip.visible = false
  3105. this.processTooltip.content = ''
  3106. },
  3107. updateProcessTooltipPosition (event) {
  3108. if (!event) {
  3109. return
  3110. }
  3111. const offsetX = 12
  3112. const offsetY = 16
  3113. let left = event.clientX + offsetX
  3114. let top = event.clientY + offsetY
  3115. const maxWidth = 220
  3116. const maxHeight = 120
  3117. if (left + maxWidth > window.innerWidth - 8) {
  3118. left = window.innerWidth - maxWidth - 8
  3119. }
  3120. if (top + maxHeight > window.innerHeight - 8) {
  3121. top = event.clientY - maxHeight - 8
  3122. }
  3123. this.processTooltip.left = Math.max(8, left)
  3124. this.processTooltip.top = Math.max(8, top)
  3125. },
  3126. handleProcessTooltipMouseMove (event) {
  3127. if (!this.processTooltip.visible) {
  3128. return
  3129. }
  3130. this.updateProcessTooltipPosition(event)
  3131. },
  3132. handleProcessTooltipMouseLeave () {
  3133. this.processTooltipHoverKey = ''
  3134. this.hideProcessTooltip()
  3135. },
  3136. handleProcessTooltipMouseEnter (row, processCol, event) {
  3137. const cacheKey = this.getProcessCacheKey(row, processCol)
  3138. if (!cacheKey) {
  3139. return
  3140. }
  3141. this.processTooltipHoverKey = cacheKey
  3142. this.updateProcessTooltipPosition(event)
  3143. const showTooltip = () => {
  3144. if (this.processTooltipHoverKey !== cacheKey) {
  3145. return
  3146. }
  3147. if (!this.hasProcessHistory(row, processCol)) {
  3148. this.hideProcessTooltip()
  3149. return
  3150. }
  3151. const content = this.getProcessTooltip(row, processCol)
  3152. if (!content) {
  3153. this.hideProcessTooltip()
  3154. return
  3155. }
  3156. this.processTooltip.content = content
  3157. this.processTooltip.visible = true
  3158. }
  3159. if (Object.prototype.hasOwnProperty.call(this.processHistoryMap, cacheKey)) {
  3160. showTooltip()
  3161. return
  3162. }
  3163. this.loadProcessHistory(row, processCol, false).then(() => {
  3164. showTooltip()
  3165. })
  3166. },
  3167. loadProcessHistory (row, processCol, refreshDialog) {
  3168. if (!row || !row.trackingId) {
  3169. return Promise.resolve([])
  3170. }
  3171. const cacheKey = this.getProcessCacheKey(row, processCol)
  3172. if (Object.prototype.hasOwnProperty.call(this.processHistoryMap, cacheKey)) {
  3173. const cachedRows = this.processHistoryMap[cacheKey] || []
  3174. if (refreshDialog) {
  3175. this.processHistoryRows = cachedRows
  3176. }
  3177. return Promise.resolve(cachedRows)
  3178. }
  3179. return queryProofTrackingProcessHistory({
  3180. trackingId: row.trackingId,
  3181. processCode: processCol.code
  3182. }).then(({ data }) => {
  3183. let rows = []
  3184. if (data && data.code === 0) {
  3185. rows = data.rows || []
  3186. // Vue2 对对象新增 key 需要使用 $set 才能触发视图更新(tooltip 才会刷新历史列表)
  3187. this.$set(this.processHistoryMap, cacheKey, rows)
  3188. if (refreshDialog) {
  3189. this.processHistoryRows = rows
  3190. }
  3191. }
  3192. return rows
  3193. }).catch(() => {
  3194. if (refreshDialog) {
  3195. this.processHistoryRows = []
  3196. }
  3197. this.$set(this.processHistoryMap, cacheKey, [])
  3198. return []
  3199. })
  3200. },
  3201. hasProcessHistory (row, processCol) {
  3202. if (!row || !processCol || !row.trackingId) {
  3203. return false
  3204. }
  3205. const cacheKey = `${row.trackingId}_${processCol.code}`
  3206. const rows = this.processHistoryMap[cacheKey] || []
  3207. return rows.length > 0
  3208. },
  3209. getProcessTooltip (row, processCol) {
  3210. if (!row || !processCol) {
  3211. return ''
  3212. }
  3213. const cacheKey = row.trackingId ? `${row.trackingId}_${processCol.code}` : ''
  3214. const rows = this.processHistoryMap[cacheKey] || []
  3215. const dateList = []
  3216. const pushDate = (dateVal) => {
  3217. const formatted = this.formatDate(dateVal)
  3218. if (!formatted) {
  3219. return
  3220. }
  3221. if (dateList.indexOf(formatted) === -1) {
  3222. dateList.push(formatted)
  3223. }
  3224. }
  3225. // 先放当前实际完成日期,再放历史变更日期(实际)
  3226. pushDate(row[processCol.actualField])
  3227. rows.forEach(item => {
  3228. pushDate(item.newActualDate)
  3229. pushDate(item.oldActualDate)
  3230. })
  3231. if (dateList.length === 0) {
  3232. return ''
  3233. }
  3234. return dateList.join('\n')
  3235. },
  3236. isProcessComplete (row, processCol) {
  3237. if (!row || !processCol) {
  3238. return false
  3239. }
  3240. const statusVal = row[processCol.statusField]
  3241. const status = statusVal ? String(statusVal).trim() : ''
  3242. if (!status) {
  3243. // 兼容历史数据:若没有状态但有实际完成日期,也按完成处理
  3244. return !!this.formatDate(row[processCol.actualField])
  3245. }
  3246. if (status === '未完成' || status === '进行中') {
  3247. return false
  3248. }
  3249. if (status === '已完成' || status === '打样完成') {
  3250. return true
  3251. }
  3252. return status.indexOf('完成') > -1 && status.indexOf('未') === -1
  3253. },
  3254. getTrackingProcessColumnsByRow (row) {
  3255. if (!row) {
  3256. return []
  3257. }
  3258. const processColumnMap = {}
  3259. const appendProcessColumn = (processCol) => {
  3260. if (!processCol || !processCol.code || processColumnMap[processCol.code]) {
  3261. return
  3262. }
  3263. processColumnMap[processCol.code] = processCol
  3264. }
  3265. this.processColumns.forEach(item => {
  3266. appendProcessColumn(item)
  3267. })
  3268. // 兜底补齐默认工序元信息,避免配置变化导致状态字段找不到。
  3269. DEFAULT_PROCESS_COLUMNS.forEach(item => {
  3270. appendProcessColumn(item)
  3271. })
  3272. let orderedCodes = []
  3273. if (this.getSelectedSearchBuNo()) {
  3274. orderedCodes = this.visibleProcessColumns.map(item => item.code)
  3275. } else {
  3276. const configEntry = this.getBuProcessConfigEntry(row.buNo, row.site)
  3277. const config = configEntry ? configEntry.config : null
  3278. orderedCodes = config && Array.isArray(config.processOrderCodes) && config.processOrderCodes.length > 0
  3279. ? this.getValidProcessOrderCodes(config.processOrderCodes)
  3280. : this.getAllProcessCodes()
  3281. const visibleCodes = this.getProcessVisibleCodesByBuNo(row.buNo, row.site)
  3282. if (visibleCodes) {
  3283. const visibleCodeMap = {}
  3284. visibleCodes.forEach(code => {
  3285. visibleCodeMap[code] = true
  3286. })
  3287. orderedCodes = orderedCodes.filter(code => visibleCodeMap[code])
  3288. }
  3289. }
  3290. const result = []
  3291. const usedCodeMap = {}
  3292. orderedCodes.forEach(code => {
  3293. const codeVal = code == null ? '' : String(code).trim()
  3294. if (!codeVal || usedCodeMap[codeVal]) {
  3295. return
  3296. }
  3297. const processCol = processColumnMap[codeVal]
  3298. if (!processCol) {
  3299. return
  3300. }
  3301. usedCodeMap[codeVal] = true
  3302. result.push(processCol)
  3303. })
  3304. return result
  3305. },
  3306. getFirstPendingProcessLabel (row) {
  3307. const processColumns = this.getTrackingProcessColumnsByRow(row)
  3308. for (let i = 0; i < processColumns.length; i += 1) {
  3309. const processCol = processColumns[i]
  3310. if (!this.isProcessComplete(row, processCol)) {
  3311. return processCol.label || processCol.code
  3312. }
  3313. }
  3314. return '已全部完成'
  3315. },
  3316. getTrackingStatusType (row) {
  3317. if (!row) {
  3318. return ''
  3319. }
  3320. const requiredDeliveryDate = this.formatDate(row.requiredDeliveryDate)
  3321. if (!requiredDeliveryDate) {
  3322. return ''
  3323. }
  3324. const today = this.formatDate(new Date())
  3325. // 按业务口径:今日 <= 预计完成日期 判定为 On schedule,其余判定为 Delay。
  3326. if (today <= requiredDeliveryDate) {
  3327. return 'onSchedule'
  3328. }
  3329. return 'delay'
  3330. },
  3331. getTrackingStatusText (row) {
  3332. const statusType = this.getTrackingStatusType(row)
  3333. if (!statusType) {
  3334. return '-'
  3335. }
  3336. const statusPrefix = statusType === 'onSchedule' ? 'On schedule' : 'Delay'
  3337. return `${statusPrefix}-${this.getFirstPendingProcessLabel(row)}`
  3338. },
  3339. getTrackingStatusClass (row) {
  3340. const statusType = this.getTrackingStatusType(row)
  3341. if (statusType === 'onSchedule') {
  3342. return 'tracking-status tracking-status--on-schedule'
  3343. }
  3344. if (statusType === 'delay') {
  3345. return 'tracking-status tracking-status--delay'
  3346. }
  3347. return 'tracking-status tracking-status--unknown'
  3348. },
  3349. formatDate (val) {
  3350. if (!val) {
  3351. return ''
  3352. }
  3353. if (typeof val === 'string') {
  3354. return val.length > 10 ? val.substring(0, 10) : val
  3355. }
  3356. const date = new Date(val)
  3357. if (isNaN(date.getTime())) {
  3358. return ''
  3359. }
  3360. const y = date.getFullYear()
  3361. const m = `${date.getMonth() + 1}`.padStart(2, '0')
  3362. const d = `${date.getDate()}`.padStart(2, '0')
  3363. return `${y}-${m}-${d}`
  3364. },
  3365. formatDateTime (val) {
  3366. if (!val) {
  3367. return ''
  3368. }
  3369. if (typeof val === 'string') {
  3370. return val.length > 19 ? val.substring(0, 19) : val
  3371. }
  3372. const date = new Date(val)
  3373. if (isNaN(date.getTime())) {
  3374. return ''
  3375. }
  3376. const y = date.getFullYear()
  3377. const m = `${date.getMonth() + 1}`.padStart(2, '0')
  3378. const d = `${date.getDate()}`.padStart(2, '0')
  3379. const hh = `${date.getHours()}`.padStart(2, '0')
  3380. const mm = `${date.getMinutes()}`.padStart(2, '0')
  3381. const ss = `${date.getSeconds()}`.padStart(2, '0')
  3382. return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
  3383. }
  3384. }
  3385. }
  3386. </script>
  3387. <style >
  3388. .data-table .cell {
  3389. line-height: 26px;
  3390. font-size: 12px;
  3391. height: 26px;
  3392. }
  3393. .search-form {
  3394. margin-bottom: 0;
  3395. }
  3396. .button-group-col {
  3397. width: 100%;
  3398. margin-top: 16px;
  3399. }
  3400. .button-row {
  3401. width: 100%;
  3402. flex-wrap: wrap;
  3403. }
  3404. .bu-process-config-dialog .bu-process-order-list {
  3405. display: grid;
  3406. grid-template-columns: repeat(5, minmax(110px, 1fr));
  3407. column-gap: 12px;
  3408. row-gap: 8px;
  3409. align-items: center;
  3410. }
  3411. .bu-process-config-dialog .bu-process-list-column .cell {
  3412. white-space: normal !important;
  3413. height: auto !important;
  3414. line-height: 20px;
  3415. padding-top: 6px;
  3416. padding-bottom: 6px;
  3417. overflow: hidden;
  3418. }
  3419. .bu-process-config-dialog .bu-process-list-column {
  3420. vertical-align: top;
  3421. }
  3422. .bu-process-config-dialog .bu-process-order-item {
  3423. display: flex;
  3424. align-items: center;
  3425. min-height: 28px;
  3426. padding: 2px 6px;
  3427. border: 1px solid transparent;
  3428. border-radius: 4px;
  3429. cursor: move;
  3430. transition: border-color .2s ease, background-color .2s ease;
  3431. }
  3432. .bu-process-config-dialog .bu-process-order-item:hover {
  3433. border-color: #7cc4ff;
  3434. background: #f5fbff;
  3435. }
  3436. .bu-process-config-dialog .bu-process-order-item.is-dragging {
  3437. opacity: .55;
  3438. border-color: #409eff;
  3439. background: #ecf5ff;
  3440. }
  3441. .bu-process-config-dialog .process-order-index {
  3442. width: 22px;
  3443. color: #606266;
  3444. font-size: 12px;
  3445. text-align: right;
  3446. margin-right: 4px;
  3447. flex-shrink: 0;
  3448. }
  3449. .bu-process-config-dialog .process-order-handle {
  3450. margin-right: 6px;
  3451. color: #909399;
  3452. font-size: 14px;
  3453. }
  3454. .bu-process-config-dialog .bu-process-order-item .el-checkbox,
  3455. .bu-process-config-dialog .bu-process-order-item .el-checkbox + .el-checkbox {
  3456. width: 100%;
  3457. margin: 0 !important;
  3458. }
  3459. .bu-process-config-dialog .bu-process-order-item .el-checkbox__label {
  3460. white-space: nowrap;
  3461. }
  3462. .bu-process-config-dialog .bu-process-config-tip {
  3463. display: flex;
  3464. align-items: center;
  3465. gap: 6px;
  3466. margin-bottom: 8px;
  3467. padding: 6px 10px;
  3468. border-radius: 4px;
  3469. background: #f4f8ff;
  3470. color: #5f6b7a;
  3471. font-size: 12px;
  3472. }
  3473. .bu-process-config-dialog .bu-process-config-tip i {
  3474. color: #409eff;
  3475. }
  3476. .button-col {
  3477. margin-bottom: 8px;
  3478. }
  3479. .button-col .el-button {
  3480. width: 100%;
  3481. }
  3482. .one-key-grid-form {
  3483. padding: 0 8px;
  3484. }
  3485. .one-key-grid-form .el-form-item {
  3486. margin-bottom: 10px;
  3487. }
  3488. .proof-grid-form {
  3489. padding: 0 8px;
  3490. }
  3491. .proof-grid-form .el-form-item {
  3492. margin-bottom: 10px;
  3493. }
  3494. .big-label a {
  3495. color: #35b4b4;
  3496. }
  3497. .sync-text--ok {
  3498. color: #67c23a;
  3499. font-weight: 600;
  3500. }
  3501. .sync-text--pending {
  3502. color: #e6a23c;
  3503. }
  3504. .tracking-status {
  3505. width: 100%;
  3506. min-height: 26px;
  3507. padding: 4px 6px;
  3508. box-sizing: border-box;
  3509. border: 1px solid #dcdfe6;
  3510. border-radius: 4px;
  3511. background: #fff;
  3512. color: #303133;
  3513. font-weight: 500;
  3514. line-height: 18px;
  3515. text-align: left;
  3516. overflow: hidden;
  3517. text-overflow: ellipsis;
  3518. white-space: nowrap;
  3519. }
  3520. .tracking-status--on-schedule {
  3521. background: #e8f5e9;
  3522. }
  3523. .tracking-status--delay {
  3524. background: #fde2e2;
  3525. }
  3526. .tracking-status--unknown {
  3527. background: #f4f4f5;
  3528. }
  3529. .process-cell {
  3530. background: #fff;
  3531. min-height: 44px;
  3532. padding: 4px 2px;
  3533. }
  3534. .process-cell.is-complete {
  3535. background: #ececec;
  3536. }
  3537. .process-cell.is-complete .link-date {
  3538. color: #8c8c8c;
  3539. }
  3540. .process-cell.is-complete .link-date:hover,
  3541. .process-cell.is-complete .link-date:focus {
  3542. color: #8c8c8c;
  3543. }
  3544. .process-cell--masked {
  3545. background: transparent !important;
  3546. }
  3547. .process-content {
  3548. display: flex;
  3549. align-items: center;
  3550. justify-content: center;
  3551. gap: 4px;
  3552. text-align: center;
  3553. line-height: 18px;
  3554. }
  3555. .process-complete-btn {
  3556. padding: 4px 8px;
  3557. min-width: 12px;
  3558. }
  3559. .comments-cell {
  3560. display: flex;
  3561. align-items: center;
  3562. gap: 4px;
  3563. min-height: 26px;
  3564. }
  3565. .comments-edit-input {
  3566. flex: 1;
  3567. }
  3568. .comments-save-btn {
  3569. padding: 4px 7px;
  3570. min-width: 28px;
  3571. }
  3572. .comments-text {
  3573. width: 100%;
  3574. overflow: hidden;
  3575. text-overflow: ellipsis;
  3576. white-space: nowrap;
  3577. }
  3578. .process-date-tooltip {
  3579. white-space: pre-line;
  3580. }
  3581. .process-history-tooltip {
  3582. position: fixed;
  3583. z-index: 3000;
  3584. max-width: 220px;
  3585. padding: 10px 12px;
  3586. border-radius: 4px;
  3587. background: rgba(48, 49, 51, 0.95);
  3588. color: #fff;
  3589. font-size: 12px;
  3590. line-height: 1.5;
  3591. white-space: pre-line;
  3592. pointer-events: none;
  3593. }
  3594. .link-date {
  3595. color: #0c4dbb;
  3596. text-decoration: none;
  3597. }
  3598. .link-date:hover,
  3599. .link-date:focus {
  3600. text-decoration: none;
  3601. }
  3602. .actual-date {
  3603. margin-top: 2px;
  3604. color: #666;
  3605. }
  3606. .finish-dialog-tip {
  3607. display: flex;
  3608. align-items: center;
  3609. gap: 8px;
  3610. margin-bottom: 10px;
  3611. padding: 8px 10px;
  3612. border-radius: 4px;
  3613. background: #f4f8ff;
  3614. color: #3a4b5f;
  3615. font-size: 12px;
  3616. }
  3617. .finish-dialog-tip i {
  3618. color: #409eff;
  3619. }
  3620. .finish-tip-highlight {
  3621. font-size: 13px;
  3622. font-weight: 700;
  3623. color: #1f2d3d;
  3624. }
  3625. .finish-proof-dialog .el-dialog__body {
  3626. padding-bottom: 8px;
  3627. }
  3628. .finish-form .el-form-item {
  3629. margin-bottom: 14px;
  3630. }
  3631. .action-link {
  3632. color: #0c4dbb;
  3633. margin: 0 4px;
  3634. cursor: pointer;
  3635. }
  3636. .edit-link {
  3637. color: #e6a23c;
  3638. }
  3639. .delete-link {
  3640. color: #f56c6c;
  3641. }
  3642. .end-link {
  3643. color: #a303ff;
  3644. }
  3645. </style>