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.

3040 lines
113 KiB

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