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.

1924 lines
70 KiB

  1. <template>
  2. <div class="customer-css">
  3. <el-dialog :title="titleCon" :close-on-click-modal="false" :visible.sync="visible"
  4. :width="showPreview ? '1300px' : '600px'" class="customer-dialog" >
  5. <el-form label-position="top">
  6. <el-row :gutter="16">
  7. <el-col :span="12">
  8. <el-form-item label="BU">
  9. <el-select v-model="pageData.buNo" placeholder="请选择" style="width: 100%">
  10. <el-option v-for="i in buList" :key="i.buNo" :label="i.buDesc" :value="i.buNo"></el-option>
  11. </el-select>
  12. </el-form-item>
  13. </el-col>
  14. <el-col :span="4">
  15. <el-form-item label=" ">
  16. </el-form-item>
  17. </el-col>
  18. <el-col :span="8">
  19. <el-form-item label=" " v-if="showPreview">
  20. <div style="margin-top:40px;padding: 10px; background-color: #FFF3E0; border: 1px solid #FFB74D; border-radius: 4px; font-size: 13px; line-height: 1.6;">
  21. <div style="color: #E65100; font-weight: bold; margin-bottom: 5px;">
  22. <i class="el-icon-warning" style="margin-right: 5px;"></i>导入规则
  23. </div>
  24. <div style="color: #666;">
  25. 以下3种情况不读取Excel内容<br/>
  26. 1发票在系统已存在<br/>
  27. 2内销<br/>
  28. 3CargoReady Date为空
  29. </div>
  30. <div style="color: #E65100; font-weight: bold; margin-bottom: 5px;">
  31. <i class="el-icon-warning" style="margin-right: 5px;"></i>保存模板规则
  32. </div>
  33. <div style="color: #666;">
  34. 客户模板名称无修改"保存模板"会修改原模板<br/>
  35. 客户模板名称有修改"保存模板"会另存为新模板
  36. </div>
  37. </div>
  38. </el-form-item>
  39. </el-col>
  40. <el-col :span="20" style="margin-top: 10px">
  41. <el-upload class="customer-upload" drag action="javascript:void(0);" ref="uploadFile" :limit="1" accept=".xlsx,.xls"
  42. :before-upload="beforeUploadHandle" :on-change="onChange" :auto-upload="false" style="text-align: left;">
  43. <i class="el-icon-upload"></i>
  44. <div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
  45. </el-upload>
  46. </el-col>
  47. </el-row>
  48. </el-form>
  49. <!-- 预览数据表格 -->
  50. <!-- 加载状态 -->
  51. <div v-if="previewLoading" style="margin-top: 20px; text-align: center; padding: 40px;">
  52. <i class="el-icon-loading" style="font-size: 24px; color: #409EFF;"></i>
  53. <p style="margin-top: 10px; color: #666;">正在解析文件请稍候...</p>
  54. </div>
  55. <div v-if="showPreview" style="margin-top: 10px; margin-bottom: 10px;">
  56. <!-- 可导入的发票表格 -->
  57. <div v-if="validInvoices.length > 0">
  58. <div style="display: flex; align-items: center; margin-bottom: 8px;">
  59. <span style="color: #67C23A;">
  60. <i class="el-icon-success" style="margin-right: 8px;"></i>
  61. 可导入的发票 ({{ validInvoices.length }}个发票)
  62. </span>
  63. <el-button
  64. type="danger"
  65. size="mini"
  66. style="margin-left: 15px;"
  67. :disabled="selectedRows.length === 0"
  68. @click="batchDeleteRows">
  69. <i class="el-icon-delete"></i>
  70. 批量删除 <span v-if="selectedRows.length > 0">({{ selectedRows.length }})</span>
  71. </el-button>
  72. </div>
  73. <el-table
  74. :data="validInvoices"
  75. border
  76. style="width: 100%"
  77. max-height="300"
  78. :show-overflow-tooltip="true"
  79. class="delClass valid-table"
  80. ref="validInvoicesTable"
  81. @selection-change="handleSelectionChange">
  82. <el-table-column type="selection" width="40" fixed="left" align="center"></el-table-column>
  83. <!-- <el-table-column label="操作" width="50" fixed="left">
  84. <template slot-scope="scope">
  85. <a type="text" size="small" style="color: red" @click="deleteRow(scope.$index, scope.row)">删除</a>
  86. </template>
  87. </el-table-column>-->
  88. <el-table-column prop="cmcInvoice" label="发票号" width="90" fixed="left">
  89. <template slot-scope="scope">
  90. <div>
  91. <span :style="scope.row.exists ? 'color: red; font-weight: bold;' : ''">
  92. {{ scope.row.cmcInvoice }}
  93. </span>
  94. <div style="margin-top: 2px;">
  95. <el-tag v-if="scope.row.exists" type="warning" size="mini">已存在</el-tag>
  96. </div>
  97. </div>
  98. </template>
  99. </el-table-column>
  100. <el-table-column prop="destination" label="目的地" width="100" :show-overflow-tooltip="true"></el-table-column>
  101. <el-table-column label="客户模板名称" width="150">
  102. <template slot-scope="scope">
  103. <el-input
  104. v-model="scope.row.selectedTemplate"
  105. placeholder="请选择客户模板名称"
  106. @dblclick.native="selectTemplateForRow(scope.row)"
  107. size="small"
  108. style="cursor: pointer;">
  109. </el-input>
  110. </template>
  111. </el-table-column>
  112. <el-table-column label="客户" width="150">
  113. <template slot-scope="scope">
  114. <el-input
  115. v-model="scope.row.selectedCustomer"
  116. placeholder="请选择客户"
  117. @dblclick.native="selectCustomerForRow(scope.row)"
  118. size="small"
  119. style="cursor: pointer;"
  120. readonly>
  121. <i slot="suffix" class="el-icon-edit" style="cursor: pointer;" @click.stop="openEditDialog(scope.row, 'selectedCustomer', '客户', '编辑客户信息')"></i>
  122. </el-input>
  123. </template>
  124. </el-table-column>
  125. <el-table-column label="客户地址" min-width="160">
  126. <template slot-scope="scope">
  127. <el-input
  128. v-model="scope.row.selectedLocalAddress"
  129. placeholder="请选择客户地址"
  130. @dblclick.native="selectLocalAddressForRow(scope.row)"
  131. size="small"
  132. style="cursor: pointer;"
  133. readonly>
  134. <i slot="suffix" class="el-icon-edit" style="cursor: pointer;" @click.stop="openEditDialog(scope.row, 'selectedLocalAddress', '客户地址', '编辑客户地址')"></i>
  135. </el-input>
  136. </template>
  137. </el-table-column>
  138. <el-table-column label="收货单位" width="150">
  139. <template slot-scope="scope">
  140. <el-input
  141. v-model="scope.row.selectedOverseasShipper"
  142. placeholder="请选择收货单位"
  143. @dblclick.native="selectOverseasShipperForRow(scope.row)"
  144. size="small"
  145. style="cursor: pointer;"
  146. readonly>
  147. <i slot="suffix" class="el-icon-edit" style="cursor: pointer;" @click.stop="openEditDialog(scope.row, 'selectedOverseasShipper', '收货单位', '编辑收货单位')"></i>
  148. </el-input>
  149. </template>
  150. </el-table-column>
  151. <el-table-column label="收货单位地址" width="160">
  152. <template slot-scope="scope">
  153. <el-input
  154. v-model="scope.row.selectedOverseasAddress"
  155. placeholder="请选择收货单位地址"
  156. @dblclick.native="selectOverseasAddressForRow(scope.row)"
  157. size="small"
  158. style="cursor: pointer;"
  159. readonly>
  160. <i slot="suffix" class="el-icon-edit" style="cursor: pointer;" @click.stop="openEditDialog(scope.row, 'selectedOverseasAddress', '收货单位地址', '编辑收货单位地址')"></i>
  161. </el-input>
  162. </template>
  163. </el-table-column>
  164. <el-table-column label="运抵国" width="80">
  165. <template slot-scope="scope">
  166. <el-input
  167. v-model="scope.row.selectedCnative"
  168. placeholder="运抵国"
  169. size="small"
  170. style="cursor: pointer;"
  171. readonly>
  172. <i slot="suffix" class="el-icon-edit" style="cursor: pointer;" @click.stop="openEditDialog(scope.row, 'selectedCnative', '运抵国', '编辑运抵国')"></i>
  173. </el-input>
  174. </template>
  175. </el-table-column>
  176. <el-table-column label="贸易国" width="80">
  177. <template slot-scope="scope">
  178. <el-input
  179. v-model="scope.row.selectedSalesArea"
  180. placeholder="贸易国"
  181. size="small"
  182. style="cursor: pointer;"
  183. readonly>
  184. <i slot="suffix" class="el-icon-edit" style="cursor: pointer;" @click.stop="openEditDialog(scope.row, 'selectedSalesArea', '贸易国', '编辑贸易国')"></i>
  185. </el-input>
  186. </template>
  187. </el-table-column>
  188. <el-table-column label="操作" width="60" fixed="right">
  189. <template slot-scope="scope">
  190. <a v-if="scope.row.selectedTemplate"
  191. type="text"
  192. size="small"
  193. @click="saveTemplateChanges(scope.row)">
  194. 保存模板
  195. </a>
  196. <span v-else style="color: #C0C4CC; font-size: 12px;">未选择</span>
  197. </template>
  198. </el-table-column>
  199. </el-table>
  200. </div>
  201. <!-- 物料不存在的发票表格 -->
  202. <div v-if="invalidInvoices.length > 0" style="margin-top: 10px;margin-bottom: 10px">
  203. <span style="color: #F56C6C;">
  204. <i class="el-icon-warning" style="margin-right: 8px;"></i>
  205. 物料不存在的发票 ({{ invalidInvoices.length }}个发票将不会导入)
  206. </span>
  207. <el-table :data="invalidInvoices" border style="width: 100%" max-height="300" :show-overflow-tooltip="true" class="delClass invalid-table">
  208. <el-table-column prop="cmcInvoice" label="发票号" width="120" fixed="left">
  209. <template slot-scope="scope">
  210. <div>
  211. <span
  212. class="clickable-invoice"
  213. style="color: #F56C6C; font-weight: bold;"
  214. @click="showInvalidMaterials(scope.row)">
  215. {{ scope.row.cmcInvoice }}
  216. <i class="el-icon-view" style="margin-left: 4px; font-size: 12px;"></i>
  217. </span>
  218. <div style="margin-top: 2px;">
  219. <el-tag type="danger" size="mini">物料不存在</el-tag>
  220. </div>
  221. </div>
  222. </template>
  223. </el-table-column>
  224. <el-table-column prop="destination" label="目的地" width="100"></el-table-column>
  225. <el-table-column label="状态" width="100">
  226. <template slot-scope="scope">
  227. <div style="color: #F56C6C;">
  228. <el-tag type="danger" size="small">物料不存在</el-tag>
  229. <div style="font-size: 12px; margin-top: 2px;">将不会导入</div>
  230. </div>
  231. </template>
  232. </el-table-column>
  233. <el-table-column label="不存在物料" width="150">
  234. <template slot-scope="scope">
  235. <div style="color: #F56C6C;">
  236. <el-tag type="danger" size="small">{{ scope.row.invalidMaterials ? scope.row.invalidMaterials.length : 0 }}个物料</el-tag>
  237. <div style="font-size: 12px; margin-top: 2px;">
  238. 点击发票号查看详情
  239. </div>
  240. </div>
  241. </template>
  242. </el-table-column>
  243. <el-table-column label="客户信息" width="150">
  244. <template slot-scope="scope">
  245. <div style="color: #999; font-size: 12px;">
  246. 无需填写
  247. </div>
  248. </template>
  249. </el-table-column>
  250. <el-table-column prop="readyDate" label="Ready Date" width="160"></el-table-column>
  251. <el-table-column prop="totalQty" label="总数量" width="150" align="right">
  252. <template slot-scope="scope">
  253. {{ scope.row.totalQty || 0 }}
  254. </template>
  255. </el-table-column>
  256. <el-table-column prop="totalItems" label="明细数" width="160" align="center">
  257. <template slot-scope="scope">
  258. {{ scope.row.totalItems || 0 }}
  259. </template>
  260. </el-table-column>
  261. <el-table-column prop="shippingMode" label="运输方式" min-width="80"></el-table-column>
  262. </el-table>
  263. </div>
  264. <!-- Sheet错误信息展示 -->
  265. <div v-if="sheetErrors.length > 0" style="margin-bottom: 5px;">
  266. <span style="color: #E6A23C; ">
  267. <i class="el-icon-warning" style="margin-right: 8px;"></i>
  268. Sheet处理警告 ({{ sheetErrors.length }}个Sheet存在问题)
  269. </span>
  270. <el-table :data="sheetErrors" border style="width: 100%" max-height="400" class="delClass warning-table">
  271. <el-table-column prop="sheetName" label="Sheet名称" width="150" fixed="left" >
  272. <template slot-scope="scope">
  273. <span style="font-weight: bold;">{{ scope.row.sheetName }}</span>
  274. </template>
  275. </el-table-column>
  276. <el-table-column prop="errorMessage" label="原因" min-width="300">
  277. <template slot-scope="scope">
  278. <div>
  279. <el-tooltip
  280. v-if="scope.row.errorDetails && scope.row.errorDetails.length > 0"
  281. placement="top"
  282. effect="light">
  283. <div slot="content" style="max-width: 500px; line-height: 1.6;">
  284. <div style="font-weight: bold; margin-bottom: 8px; color: #E6A23C;">详细错误信息 ({{ scope.row.errorDetails.length }})</div>
  285. <div v-for="(detail, index) in scope.row.errorDetails" :key="index" style="margin-bottom: 6px;">
  286. <span style="color: #E6A23C; font-weight: bold;">{{ index + 1 }}.</span> {{ detail }}
  287. </div>
  288. </div>
  289. <div style="font-size: 12px; color: #909399; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
  290. <span v-for="(detail, index) in scope.row.errorDetails" :key="index">
  291. <span v-if="index > 0">; </span>
  292. <span style="color: #E6A23C; font-weight: bold;">{{ index + 1 }}.</span> {{ detail }}
  293. </span>
  294. </div>
  295. </el-tooltip>
  296. </div>
  297. </template>
  298. </el-table-column>
  299. </el-table>
  300. </div>
  301. </div>
  302. <span slot="footer" class="dialog-footer" style="margin-top: 10px">
  303. <el-button v-if="!showPreview" type="primary" @click="saveUploadFile" :loading="saveButtonLoading" :disabled="saveButtonLoading">保存</el-button>
  304. <el-button v-if="showPreview" type="primary" @click="saveFromPreview" :loading="confirmSaveButtonLoading" :disabled="confirmSaveButtonLoading">确认保存</el-button>
  305. <el-button v-if="showPreview" @click="cancelPreview" :disabled="confirmSaveButtonLoading">取消预览</el-button>
  306. <el-button v-if="!showPreview" type="primary" @click="closeDialog" :disabled="saveButtonLoading">关闭</el-button>
  307. </span>
  308. </el-dialog>
  309. <el-dialog :title="currentRow ? `为发票号 ${currentRow.cmcInvoice} 选择客户模板` : '客户模板'"
  310. @close="templateFlag = false; currentRow = null" :visible.sync="templateFlag" width="1200px" v-drag>
  311. <!-- 搜索表单 -->
  312. <el-form :inline="true" label-position="top" :model="templateSearchData" label-width="100px" >
  313. <el-form-item label="模板名称">
  314. <el-input
  315. v-model="templateSearchData.templateName"
  316. placeholder="请输入模板名称"
  317. style="width: 200px;"
  318. clearable>
  319. </el-input>
  320. </el-form-item>
  321. <el-form-item label="客户名称">
  322. <el-input
  323. v-model="templateSearchData.customerName"
  324. placeholder="请输入客户名称"
  325. style="width: 200px;"
  326. clearable>
  327. </el-input>
  328. </el-form-item>
  329. <el-form-item label=" ">
  330. <el-button type="primary" @click="searchTemplateList">查询</el-button>
  331. </el-form-item>
  332. <el-form-item label=" ">
  333. <el-button @click="resetTemplateSearch">重置</el-button>
  334. </el-form-item>
  335. </el-form>
  336. <el-table
  337. :height="400"
  338. :data="customerTemplateList"
  339. stripe
  340. highlight-current-row
  341. border class="delClass"
  342. @row-dblclick="rowDblclickTemplate"
  343. style="width: 100%;">
  344. <el-table-column
  345. prop="template_name"
  346. header-align="center"
  347. align="left"
  348. width="150"
  349. label="模板名称">
  350. </el-table-column>
  351. <el-table-column
  352. prop="ccusname"
  353. header-align="center"
  354. align="left"
  355. width="200"
  356. label="客户名称">
  357. </el-table-column>
  358. <el-table-column
  359. prop="localShipAddress"
  360. header-align="center"
  361. align="left"
  362. width="300"
  363. label="客户地址">
  364. </el-table-column>
  365. <el-table-column
  366. prop="overseasShipper"
  367. header-align="center"
  368. align="left"
  369. width="200"
  370. label="收货单位">
  371. </el-table-column>
  372. <el-table-column
  373. prop="overseasAddress"
  374. header-align="center"
  375. align="left"
  376. width="300"
  377. label="收货地址">
  378. </el-table-column>
  379. <el-table-column
  380. prop="salesArea"
  381. header-align="center"
  382. align="left"
  383. width="80"
  384. label="贸易国">
  385. </el-table-column>
  386. <el-table-column
  387. prop="cnative"
  388. header-align="center"
  389. align="left"
  390. width="80"
  391. label="运抵国">
  392. </el-table-column>
  393. </el-table>
  394. <el-footer style="height:40px;margin-top: 10px;text-align:center">
  395. <el-button @click="templateFlag = false; currentRow = null">关闭</el-button>
  396. </el-footer>
  397. </el-dialog>
  398. <el-dialog :title="currentRow ? `为发票号 ${currentRow.cmcInvoice} 选择客户` : '客户'" @close="closeCustomDialog" :visible.sync="customFlag" width="500px" v-drag>
  399. <el-form inline="inline" label-position="top" :model="customSearchData" style="margin-left: 7px;margin-top: -5px;">
  400. <el-form-item label="客户名称">
  401. <el-input v-model="customSearchData.ccusname" clearable style="width: 150px"></el-input>
  402. </el-form-item>
  403. <el-form-item label=" ">
  404. <el-button type="primary" style="padding: 3px 12px" @click="getCustomerList()">查询</el-button>
  405. </el-form-item>
  406. </el-form>
  407. <el-table
  408. :height="400"
  409. :data="customerList"
  410. stripe
  411. highlight-current-row
  412. border
  413. @row-dblclick="rowDblclick"
  414. style="width: 100%;">
  415. <el-table-column
  416. prop="ccusname"
  417. header-align="center"
  418. align="left"
  419. width="350"
  420. label="客户名称">
  421. </el-table-column>
  422. <el-table-column
  423. prop="country"
  424. header-align="center"
  425. align="left"
  426. label="贸易国">
  427. </el-table-column>
  428. </el-table>
  429. <el-footer style="height:40px;margin-top: 10px;text-align:center">
  430. <el-button @click="customFlag = false">关闭</el-button>
  431. </el-footer>
  432. </el-dialog>
  433. <el-dialog :title="currentRow ? `为发票号 ${currentRow.cmcInvoice} 选择客户地址` : '客户地址'" @close="localShipAddressFlag = false; currentRow = null" :visible.sync="localShipAddressFlag" width="500px" v-drag>
  434. <el-table
  435. :height="400"
  436. :data="customerAddrs"
  437. stripe
  438. highlight-current-row
  439. border
  440. @row-dblclick="rowDblclick2"
  441. style="width: 100%;">
  442. <el-table-column
  443. prop="cDeliverAdd"
  444. header-align="center"
  445. align="left"
  446. label="客户地址">
  447. </el-table-column>
  448. </el-table>
  449. <el-footer style="height:40px;margin-top: 10px;text-align:center">
  450. <el-button @click="localShipAddressFlag = false; currentRow = null">关闭</el-button>
  451. </el-footer>
  452. </el-dialog>
  453. <el-dialog :title="currentRow ? `为发票号 ${currentRow.cmcInvoice} 选择收货单位地址` : '收货单位地址'" @close="overseasAddressFlag = false; currentRow = null" :visible.sync="overseasAddressFlag" width="500px" v-drag>
  454. <el-table
  455. :height="400"
  456. :data="customerAddrs"
  457. stripe
  458. highlight-current-row
  459. border
  460. @row-dblclick="rowDblclick3"
  461. style="width: 100%;">
  462. <el-table-column
  463. prop="cDeliverAdd"
  464. header-align="center"
  465. align="left"
  466. label="收货单位地址">
  467. </el-table-column>
  468. </el-table>
  469. <el-footer style="height:40px;margin-top: 10px;text-align:center">
  470. <el-button @click="overseasAddressFlag = false; currentRow = null">关闭</el-button>
  471. </el-footer>
  472. </el-dialog>
  473. <el-dialog :title="currentRow ? `为发票号 ${currentRow.cmcInvoice} 选择收货单位` : '收货单位'" @close="overseasShipperFlag = false; currentRow = null" :visible.sync="overseasShipperFlag" width="500px" v-drag>
  474. <el-table
  475. :height="400"
  476. :data="customerAddrs"
  477. stripe
  478. highlight-current-row
  479. border
  480. @row-dblclick="rowDblclick4"
  481. style="width: 100%;">
  482. <el-table-column
  483. prop="cDeliverUnit"
  484. header-align="left"
  485. align="left"
  486. width="350"
  487. label="收货单位">
  488. </el-table-column>
  489. <el-table-column
  490. prop="deliverycountry"
  491. header-align="center"
  492. align="left"
  493. label="运抵国">
  494. </el-table-column>
  495. </el-table>
  496. <el-footer style="height:40px;margin-top: 10px;text-align:center">
  497. <el-button @click="overseasShipperFlag = false; currentRow = null">关闭</el-button>
  498. </el-footer>
  499. </el-dialog>
  500. <!-- 大输入框编辑弹窗 -->
  501. <el-dialog
  502. :title="editDialog.title"
  503. :visible.sync="editDialog.visible"
  504. width="600px"
  505. class="edit-dialog"
  506. @close="closeEditDialog">
  507. <div class="edit-dialog-tip">
  508. <i class="el-icon-info"></i> 提示您可以在此编辑较长的文本内容支持多行输入
  509. </div>
  510. <el-form label-position="top" style="margin-bottom: 120px">
  511. <el-form-item :label="editDialog.label">
  512. <el-input ref="editTextarea"
  513. v-model="editDialog.value"
  514. type="textarea"
  515. :rows="6"
  516. placeholder="请输入内容,支持多行文本..."
  517. maxlength="500"
  518. show-word-limit
  519. class="edit-textarea">
  520. </el-input>
  521. </el-form-item>
  522. </el-form>
  523. <span slot="footer" class="dialog-footer">
  524. <el-button @click="closeEditDialog">取消</el-button>
  525. <el-button type="primary" @click="confirmEdit">保存</el-button>
  526. </span>
  527. </el-dialog>
  528. <!-- 物料不存在详情对话框 -->
  529. <el-dialog
  530. :title="`发票号 ${invalidMaterialsDialog.invoice} - 不存在的物料详情`"
  531. :visible.sync="invalidMaterialsDialog.visible"
  532. width="320px"
  533. class="invalid-materials-dialog"
  534. @close="closeInvalidMaterialsDialog">
  535. <div class="invalid-materials-content">
  536. <el-table
  537. :data="invalidMaterialsDialog.materials"
  538. border
  539. style="width: 100%; margin-top: 15px;"
  540. max-height="300">
  541. <el-table-column
  542. type="index"
  543. label="序号"
  544. width="60"
  545. align="center">
  546. </el-table-column>
  547. <el-table-column
  548. prop="material"
  549. label="物料号"
  550. align="left">
  551. <template slot-scope="scope">
  552. <span style="color: #F56C6C; font-weight: bold;">{{ scope.row }}</span>
  553. </template>
  554. </el-table-column>
  555. </el-table>
  556. <div class="materials-summary" style="margin-top: 15px; padding: 10px; background-color: #FEF0F0; border-radius: 4px;">
  557. <span style="color: #F56C6C;">
  558. <i class="el-icon-info"></i>
  559. 共发现 <strong>{{ invalidMaterialsDialog.materials.length }}</strong> 个不存在的物料
  560. </span>
  561. </div>
  562. </div>
  563. <span slot="footer" class="dialog-footer">
  564. <el-button type="primary" @click="closeInvalidMaterialsDialog">确定</el-button>
  565. </span>
  566. </el-dialog>
  567. </div>
  568. </template>
  569. <script>
  570. import {queryFileId} from "@/api/qc/qc.js"
  571. import {previewExcelTX,saveEcssCoDelNotifyByExcelTX,getCustomerList,getCustomers,getCustomerAdd,getCustomerTemplateList,saveExcelImportTemplate} from '@/api/ecss/ecss.js'
  572. import {downLoadObjectFile} from '@/api/eam/eam_object_list.js'
  573. import {getBuList}from '@/api/factory/site.js'
  574. export default {
  575. name: 'delUploadExcelTx',
  576. data() {
  577. return {
  578. buList: [],
  579. titleCon: '天线/硬标导入',
  580. visible: false,
  581. fileList: [],
  582. pageData: {
  583. site: '',
  584. buNo: '',
  585. createBy: this.$store.state.user.name,
  586. customerName:'',
  587. localShipAddress:'',
  588. overseasShipper:'',
  589. overseasAddress:'',
  590. cnative:'',
  591. salesArea:'',
  592. },
  593. previewData: [], // 预览数据
  594. showPreview: false, // 是否显示预览
  595. selectedFile: null, // 选择的文件
  596. previewLoading: false, // 预览加载状态
  597. saveButtonLoading: false, // 保存按钮加载状态
  598. confirmSaveButtonLoading: false, // 确认保存按钮加载状态
  599. deletedInvoices: [], // 被删除的发票号列表
  600. sheetErrors: [], // Sheet错误信息列表
  601. customSearchData: {},
  602. customerList : [],//所有客户
  603. customerAddrs : [],//地址
  604. customerPersons : [],//联系人
  605. customerMap:new Map,
  606. customFlag:false,
  607. localShipAddressFlag:false,
  608. overseasShipperFlag:false,
  609. overseasAddressFlag:false,
  610. templateFlag:false,
  611. customerTemplateList: [],//客户模板列表
  612. templateSearchData: {
  613. templateName: '',
  614. customerName: ''
  615. },//模板搜索条件
  616. cacheKey: '', // 缓存键
  617. cacheTimer: null, // 缓存定时器
  618. currentRow: null, // 当前编辑的行
  619. cachedFileInfo: null, // 缓存的文件信息
  620. cachedFileList: [], // 缓存的文件列表
  621. // 大输入框编辑弹窗相关数据
  622. editDialog: {
  623. visible: false,
  624. title: '',
  625. label: '',
  626. value: '',
  627. fieldName: '', // 要编辑的字段名
  628. row: null // 当前编辑的行数据
  629. },
  630. // 物料不存在详情对话框相关数据
  631. invalidMaterialsDialog: {
  632. visible: false,
  633. invoice: '',
  634. materials: []
  635. },
  636. // 批量删除相关数据
  637. selectedRows: [] // 选中的行
  638. }
  639. },
  640. computed: {
  641. // 可导入的发票(不包含物料不存在的发票)
  642. validInvoices() {
  643. return this.previewData.filter(item => !item.hasInvalidMaterials)
  644. },
  645. // 物料不存在的发票
  646. invalidInvoices() {
  647. return this.previewData.filter(item => item.hasInvalidMaterials)
  648. }
  649. },
  650. watch: {
  651. // 深度监听pageData的变化,自动保存到缓存
  652. pageData: {
  653. handler: function(newVal, oldVal) {
  654. // 只有在组件已初始化且对话框可见时才保存缓存
  655. if (this.visible && this.cacheKey) {
  656. this.saveToCache();
  657. }
  658. },
  659. deep: true
  660. },
  661. // 深度监听previewData的变化,保存用户的编辑
  662. previewData: {
  663. handler: function(newVal, oldVal) {
  664. // 只有在组件已初始化且对话框可见时才保存缓存
  665. if (this.visible && this.cacheKey && newVal && newVal.length > 0) {
  666. this.saveToCache();
  667. }
  668. },
  669. deep: true
  670. }
  671. },
  672. beforeDestroy() {
  673. // 组件销毁前清理定时器
  674. if (this.cacheTimer) {
  675. clearTimeout(this.cacheTimer);
  676. }
  677. },
  678. methods: {
  679. // 生成缓存键
  680. generateCacheKey() {
  681. const userId = this.$store.state.user.id || this.$store.state.user.name;
  682. return `ecss_del_upload_cache_${userId}`;
  683. },
  684. // 保存数据到缓存(防抖处理)
  685. saveToCache() {
  686. // 清除之前的定时器
  687. if (this.cacheTimer) {
  688. clearTimeout(this.cacheTimer);
  689. }
  690. // 设置新的定时器,500ms后执行保存
  691. this.cacheTimer = setTimeout(() => {
  692. try {
  693. const cacheData = {
  694. pageData: {
  695. buNo: this.pageData.buNo,
  696. customerName: this.pageData.customerName,
  697. localShipAddress: this.pageData.localShipAddress,
  698. overseasShipper: this.pageData.overseasShipper,
  699. overseasAddress: this.pageData.overseasAddress,
  700. cnative: this.pageData.cnative,
  701. salesArea: this.pageData.salesArea,
  702. },
  703. // 保存文件和预览相关数据
  704. fileInfo: this.selectedFile ? {
  705. name: this.selectedFile.name,
  706. size: this.selectedFile.size,
  707. lastModified: this.selectedFile.lastModified
  708. } : null,
  709. showPreview: this.showPreview,
  710. previewData: this.previewData,
  711. deletedInvoices: this.deletedInvoices,
  712. fileList: this.fileList.map(file => ({
  713. name: file.name,
  714. size: file.size,
  715. lastModified: file.lastModified,
  716. uid: file.uid
  717. }))
  718. };
  719. localStorage.setItem(this.cacheKey, JSON.stringify(cacheData));
  720. } catch (error) {
  721. console.warn('保存缓存失败:', error);
  722. }
  723. }, 500);
  724. },
  725. // 从缓存加载数据
  726. loadFromCache() {
  727. try {
  728. const cachedData = localStorage.getItem(this.cacheKey);
  729. if (cachedData) {
  730. const parsedData = JSON.parse(cachedData);
  731. if (parsedData.pageData) {
  732. // 恢复基础表单数据
  733. Object.assign(this.pageData, parsedData.pageData);
  734. // 恢复预览数据和文件状态
  735. if (parsedData.previewData && parsedData.previewData.length > 0) {
  736. this.previewData = parsedData.previewData;
  737. this.showPreview = parsedData.showPreview || false;
  738. }
  739. // 恢复删除的发票号列表
  740. if (parsedData.deletedInvoices) {
  741. this.deletedInvoices = parsedData.deletedInvoices;
  742. }
  743. // 恢复文件列表信息(用于界面显示)
  744. if (parsedData.fileList && parsedData.fileList.length > 0) {
  745. // 记录缓存的文件信息,用于后续比对
  746. this.cachedFileInfo = parsedData.fileInfo;
  747. this.cachedFileList = parsedData.fileList;
  748. }
  749. return true;
  750. }
  751. }
  752. } catch (error) {
  753. console.warn('加载缓存失败:', error);
  754. }
  755. return false;
  756. },
  757. // 清除缓存
  758. clearCache() {
  759. try {
  760. localStorage.removeItem(this.cacheKey);
  761. } catch (error) {
  762. console.warn('清除缓存失败:', error);
  763. }
  764. },
  765. // 检查并恢复文件状态
  766. checkAndRestoreFileState() {
  767. // 检查上传组件中是否有文件
  768. const uploadFiles = this.$refs.uploadFile.uploadFiles;
  769. if (this.cachedFileInfo && this.cachedFileList.length > 0) {
  770. // 如果有缓存的文件信息,检查当前上传组件中的文件
  771. if (uploadFiles && uploadFiles.length > 0) {
  772. const currentFile = uploadFiles[0];
  773. const isSameFile = currentFile.name === this.cachedFileInfo.name &&
  774. currentFile.size === this.cachedFileInfo.size &&
  775. currentFile.lastModified === this.cachedFileInfo.lastModified;
  776. if (isSameFile) {
  777. // 是同一个文件,恢复文件相关状态
  778. this.selectedFile = currentFile;
  779. this.fileList = [currentFile];
  780. console.log('恢复文件状态:', currentFile.name);
  781. } else {
  782. // 文件不匹配,清除预览状态但保留表单数据
  783. this.showPreview = false;
  784. this.previewData = [];
  785. this.selectedFile = null;
  786. this.fileList = [];
  787. console.log('文件不匹配,清除预览状态');
  788. }
  789. } else {
  790. // 没有文件但有缓存,说明文件可能被清除了
  791. this.showPreview = false;
  792. this.previewData = [];
  793. this.selectedFile = null;
  794. this.fileList = [];
  795. console.log('没有找到文件,清除预览状态');
  796. }
  797. }
  798. // 保存当前状态
  799. this.saveToCache();
  800. },
  801. // 初始化组件的参数
  802. init () {
  803. // 初始化缓存键,但不清除任何现有数据
  804. this.cacheKey = this.generateCacheKey();
  805. this.previewLoading = false
  806. let tempData = {
  807. username: this.$store.state.user.name,
  808. }
  809. getBuList(tempData).then(({data}) => {
  810. if (data.code === 0) {
  811. this.buList = data.row2
  812. if(data.row2.length===1){
  813. this.pageData.buNo=data.row2[0].buNo
  814. }
  815. }
  816. // 在获取BU列表后尝试加载缓存
  817. this.loadFromCache();
  818. // 检查并恢复文件状态
  819. this.$nextTick(() => {
  820. this.checkAndRestoreFileState();
  821. });
  822. })
  823. getCustomerList({}).then(({data}) => {
  824. //区分请求成功和失败的状况
  825. if (data && data.code === 0) {
  826. this.customerList=data.rows
  827. }
  828. });
  829. // 只有在完全没有缓存数据时才初始化为空
  830. const hasCache = this.loadFromCache();
  831. if (!hasCache) {
  832. this.fileList = []
  833. this.previewData = []
  834. this.showPreview = false
  835. this.selectedFile = null
  836. this.deletedInvoices = []
  837. this.pageData.customerName=''
  838. this.pageData.cnative=''
  839. this.pageData.localShipAddress='',
  840. this.pageData.overseasShipper='',
  841. this.pageData.overseasAddress='',
  842. this.pageData.salesArea=''
  843. }
  844. this.customerPersons=[]
  845. this.customerAddrs=[]
  846. // 打开页面
  847. this.visible = true
  848. },
  849. getCustomerList(){
  850. getCustomerList(this.customSearchData).then(({data}) => {
  851. //区分请求成功和失败的状况
  852. if (data && data.code === 0) {
  853. this.customerList=data.rows
  854. }
  855. });
  856. },
  857. getCusPersons(){
  858. let cusData = {ccusname: this.pageData.customerName}
  859. getCustomers(cusData).then(({data}) => {
  860. //区分请求成功和失败的状况
  861. if (data && data.code === 0) {
  862. this.customerPersons=data.rows
  863. this.customerPersons.forEach(o => {
  864. if (!this.customerMap.has(o.ccontactname)) {
  865. this.customerMap.set(o.ccontactname, o.cnative);
  866. }
  867. });
  868. }
  869. });
  870. getCustomerAdd(cusData).then(({data}) => {
  871. //区分请求成功和失败的状况
  872. if (data && data.code === 0) {
  873. this.customerAddrs=data.rows
  874. }
  875. });
  876. },
  877. setCnative(){
  878. this.pageData.cnative=this.customerMap.get(this.pageData.overseasShipper)
  879. },
  880. closeCustomDialog () {
  881. this.customFlag = false
  882. this.currentRow = null
  883. },
  884. rowDblclickTemplate(row) {
  885. console.log(row)
  886. if (this.currentRow) {
  887. // 使用模板设置当前行的所有信息
  888. this.currentRow.templateNo = row.template_no
  889. this.currentRow.ccuscode = row.ccuscode
  890. this.currentRow.selectedTemplate = row.template_name
  891. this.currentRow.originalTemplateName = row.template_name // 保存原始模板名称,用于判断是否修改
  892. this.currentRow.selectedCustomer = row.ccusname || ''
  893. this.currentRow.selectedLocalAddress = row.localShipAddress || ''
  894. this.currentRow.selectedOverseasShipper = row.overseasShipper || ''
  895. this.currentRow.selectedOverseasAddress = row.overseasAddress || ''
  896. this.currentRow.selectedCnative = row.cnative || ''
  897. this.currentRow.selectedSalesArea = row.salesArea || ''
  898. // 如果模板中有客户信息,自动获取该客户的地址和收货单位信息
  899. if (row.ccusname) {
  900. this.getCusPersonsForRow(row.ccusname)
  901. }
  902. }
  903. this.templateFlag = false
  904. this.currentRow = null
  905. },
  906. rowDblclick (row) {
  907. if (this.currentRow) {
  908. // 预览模式下,为当前行设置客户信息
  909. this.currentRow.selectedCustomer = row.ccusname
  910. this.currentRow.selectedSalesArea = row.country
  911. this.currentRow.selectedLocalAddress = ''
  912. this.currentRow.selectedOverseasShipper = ''
  913. this.currentRow.selectedOverseasAddress = ''
  914. this.currentRow.selectedCnative = ''
  915. this.getCusPersonsForRow(row.ccusname)
  916. } else {
  917. // 普通模式下,设置全局信息
  918. this.pageData.customerName=row.ccusname
  919. this.pageData.cnative='',
  920. this.pageData.localShipAddress='',
  921. this.pageData.overseasShipper='',
  922. this.pageData.overseasAddress='',
  923. this.pageData.salesArea = row.country
  924. this.getCusPersons()
  925. }
  926. this.customFlag = false
  927. },
  928. rowDblclick2 (row) {
  929. if (this.currentRow) {
  930. this.currentRow.selectedLocalAddress = row.cDeliverAdd
  931. } else {
  932. this.pageData.localShipAddress = row.cDeliverAdd
  933. }
  934. this.localShipAddressFlag = false
  935. this.currentRow = null
  936. },
  937. rowDblclick3 (row) {
  938. if (this.currentRow) {
  939. this.currentRow.selectedOverseasAddress = row.cDeliverAdd
  940. } else {
  941. this.pageData.overseasAddress = row.cDeliverAdd
  942. }
  943. this.overseasAddressFlag = false
  944. this.currentRow = null
  945. },
  946. rowDblclick4 (row) {
  947. if (this.currentRow) {
  948. this.currentRow.selectedOverseasShipper = row.cDeliverUnit
  949. this.currentRow.selectedCnative = row.deliverycountry
  950. } else {
  951. this.pageData.overseasShipper = row.cDeliverUnit
  952. this.pageData.cnative = row.deliverycountry
  953. }
  954. this.overseasShipperFlag = false
  955. this.currentRow = null
  956. },
  957. // 上传之前
  958. beforeUploadHandle (file) {
  959. let extName = file[0].name.substring(file[0].name.lastIndexOf('.')).toLowerCase()
  960. if (!(extName === '.xlsx' || extName === '.xls')) {
  961. this.$message.error('数据导入失败,请选择正确的xlsx模板文件')
  962. return false
  963. }
  964. },
  965. // 选择上传文件时
  966. onChange (file) {
  967. this.fileList.push(file)
  968. this.selectedFile = file
  969. this.previewFile(file)
  970. // 保存文件信息到缓存
  971. this.saveToCache()
  972. },
  973. // 关闭modal
  974. closeDialog () {
  975. // 保存当前状态到缓存
  976. this.saveToCache()
  977. // 关闭当前的页面
  978. this.visible = false
  979. this.$emit('refreshTable')
  980. },
  981. deleteFile(){
  982. // 只有在明确需要清除数据时才调用此方法
  983. this.fileList = []
  984. this.previewData = []
  985. this.showPreview = false
  986. this.selectedFile = null
  987. this.deletedInvoices = []
  988. this.cachedFileInfo = null
  989. this.cachedFileList = []
  990. // 清空文件上传记录
  991. this.$refs.uploadFile.clearFiles()
  992. // 清除缓存
  993. this.clearCache()
  994. // 刷新报工的页面
  995. this.$emit('refreshTable')
  996. },
  997. // 保修当前的数据
  998. saveUploadFile () {
  999. if (null == this.pageData.buNo || this.pageData.buNo=='') {
  1000. this.$message.error("请先选择BU!")
  1001. return false
  1002. }
  1003. if (null == this.pageData.customerName || this.pageData.customerName=='') {
  1004. this.$message.error("请先选择客户!")
  1005. return false
  1006. }
  1007. if (null == this.pageData.localShipAddress || this.pageData.localShipAddress=='') {
  1008. this.$message.error("请先填写客户发货地址!")
  1009. return false
  1010. }
  1011. if (null == this.pageData.overseasShipper || this.pageData.overseasShipper=='') {
  1012. this.$message.error("请先填写收货单位!")
  1013. return false
  1014. }
  1015. if (null == this.pageData.overseasAddress || this.pageData.overseasAddress=='') {
  1016. this.$message.error("请先填写收货单位地址!")
  1017. return false
  1018. }
  1019. if (null == this.pageData.cnative || this.pageData.cnative=='') {
  1020. this.$message.error("请先填写收货单位运抵国!")
  1021. return false
  1022. }
  1023. // 判断文件是否上传
  1024. if (null == this.fileList || 0 === this.fileList.length) {
  1025. this.$message.error("请先上传文件!")
  1026. return false
  1027. }
  1028. this.saveButtonLoading = true // 开始加载
  1029. const formData = new FormData()
  1030. formData.append("buNo",this.pageData.buNo)
  1031. formData.append("username",this.$store.state.user.name)
  1032. formData.append("file", this.fileList[0].raw)
  1033. formData.append("customerName", this.pageData.customerName)
  1034. formData.append("localShipAddress", this.pageData.localShipAddress)
  1035. formData.append("overseasShipper", this.pageData.overseasShipper)
  1036. formData.append("overseasAddress", this.pageData.overseasAddress)
  1037. formData.append("cnative", this.pageData.cnative)
  1038. formData.append("salesArea", this.pageData.salesArea)
  1039. saveEcssCoDelNotifyByExcelTX(formData).then(({ data }) => {
  1040. if (data.code === 0) {
  1041. const { resultMap } = data;
  1042. const successList = resultMap.success || [];
  1043. const failList = resultMap.fail || [];
  1044. // 紧凑样式
  1045. let html = `
  1046. <div style="max-height:380px;overflow:auto;font-size:12px;line-height:1.4;">
  1047. `;
  1048. if (successList.length > 0) {
  1049. html += `<div style="margin-bottom:6px;">
  1050. <div style="color:green;font-weight:bold;margin-bottom:3px;">
  1051. 成功${successList.length}
  1052. </div>
  1053. <table border="1" cellspacing="0" cellpadding="2"
  1054. style="border-collapse:collapse;width:100%;">
  1055. `;
  1056. successList.forEach(item => {
  1057. html += `<tr><td style="color:green;padding:2px 4px;">${item}</td></tr>`;
  1058. });
  1059. html += `</table></div>`;
  1060. }
  1061. if (failList.length > 0) {
  1062. html += `<div>
  1063. <div style="color:red;font-weight:bold;margin-bottom:3px;">
  1064. 失败${failList.length}
  1065. </div>
  1066. <table border="1" cellspacing="0" cellpadding="2"
  1067. style="border-collapse:collapse;width:100%;">
  1068. `;
  1069. failList.forEach(item => {
  1070. html += `<tr><td style="color:red;padding:2px 4px;">${item}</td></tr>`;
  1071. });
  1072. html += `</table></div>`;
  1073. }
  1074. html += `</div>`;
  1075. this.$alert(html, '导入结果', {
  1076. confirmButtonText: '确定',
  1077. dangerouslyUseHTMLString: true,
  1078. callback: () => {
  1079. if (successList.length > 0){
  1080. this.clearCache();
  1081. this.clearPreviewData();
  1082. this.closeDialog();
  1083. }
  1084. }
  1085. });
  1086. } else {
  1087. this.$alert(data.msg, '错误', {
  1088. confirmButtonText: '确定'
  1089. });
  1090. }
  1091. }).catch(error => {
  1092. this.$message.error('保存失败:' + (error.message || '网络异常'))
  1093. }).finally(() => {
  1094. this.saveButtonLoading = false // 结束加载
  1095. });
  1096. },
  1097. // 预览文件
  1098. async previewFile(file) {
  1099. if (!file || !file.raw) {
  1100. return
  1101. }
  1102. if (!this.pageData.buNo) {
  1103. this.$message.error('请先选择BU!')
  1104. this.fileList = []
  1105. this.$refs.uploadFile.clearFiles()
  1106. return
  1107. }
  1108. this.previewLoading = true // 开始加载
  1109. const formData = new FormData()
  1110. formData.append("file", file.raw)
  1111. formData.append("buNo", this.pageData.buNo)
  1112. try {
  1113. const { data } = await previewExcelTX(formData)
  1114. if (data.code === 0) {
  1115. this.previewData = data.data || []
  1116. this.sheetErrors = data.sheetErrors || []
  1117. this.showPreview = true
  1118. // 清空删除列表,因为这是新的文件预览
  1119. this.deletedInvoices = []
  1120. // 如果有Sheet错误,显示警告信息
  1121. if (this.sheetErrors.length > 0) {
  1122. const skippedCount = this.sheetErrors.filter(e => e.skipped).length
  1123. const errorCount = this.sheetErrors.filter(e => !e.skipped).length
  1124. let message = `文件解析完成,但有 ${this.sheetErrors.length} 个Sheet存在问题`
  1125. if (skippedCount > 0) {
  1126. message += `${skippedCount}个已跳过`
  1127. }
  1128. if (errorCount > 0) {
  1129. message += `${skippedCount > 0 ? ',' : '('}${errorCount}个处理失败`
  1130. }
  1131. message += '),请查看详细信息'
  1132. this.$message.warning(message)
  1133. }
  1134. // 为每个发票号初始化客户信息
  1135. this.previewData.forEach(item => {
  1136. this.$set(item, 'templateNo', '')
  1137. this.$set(item, 'ccuscode', '')
  1138. this.$set(item, 'selectedTemplate', '')
  1139. this.$set(item, 'originalTemplateName', '')
  1140. this.$set(item, 'selectedCustomer', '')
  1141. this.$set(item, 'selectedLocalAddress', '')
  1142. this.$set(item, 'selectedOverseasShipper', '')
  1143. this.$set(item, 'selectedOverseasAddress', '')
  1144. this.$set(item, 'selectedCnative', '')
  1145. this.$set(item, 'selectedSalesArea','')
  1146. })
  1147. // 预览成功后立即保存缓存
  1148. this.saveToCache()
  1149. } else {
  1150. this.$message.error(data.msg || '文件预览失败')
  1151. }
  1152. } catch (error) {
  1153. this.$message.error('文件预览失败:' + error.message)
  1154. } finally {
  1155. this.previewLoading = false // 结束加载
  1156. }
  1157. },
  1158. // 从预览数据保存
  1159. saveFromPreview() {
  1160. // 检查是否有可导入的发票
  1161. if (this.validInvoices.length === 0) {
  1162. this.$message.warning('没有可导入的发票!')
  1163. return false
  1164. }
  1165. // 只验证可导入发票的必填信息
  1166. for (let item of this.validInvoices) {
  1167. if (!item.selectedCustomer) {
  1168. this.$message.error(`发票号 ${item.cmcInvoice} 请选择客户`)
  1169. return false
  1170. }
  1171. if (!item.selectedLocalAddress) {
  1172. this.$message.error(`发票号 ${item.cmcInvoice} 请填写客户地址`)
  1173. return false
  1174. }
  1175. if (!item.selectedOverseasShipper) {
  1176. this.$message.error(`发票号 ${item.cmcInvoice} 请填写收货单位`)
  1177. return false
  1178. }
  1179. if (!item.selectedOverseasAddress) {
  1180. this.$message.error(`发票号 ${item.cmcInvoice} 请填写收货单位地址`)
  1181. return false
  1182. }
  1183. if (!item.selectedCnative) {
  1184. this.$message.error(`发票号 ${item.cmcInvoice} 请填写运抵国`)
  1185. return false
  1186. }
  1187. }
  1188. this.confirmSaveButtonLoading = true // 开始加载
  1189. // 发送保存请求
  1190. const formData = new FormData()
  1191. formData.append("buNo", this.pageData.buNo)
  1192. formData.append("username", this.$store.state.user.name)
  1193. formData.append("file", this.fileList[0].raw)
  1194. // 添加被删除的发票号列表(包括用户手动删除的和物料不存在的)
  1195. const allDeletedInvoices = [...this.deletedInvoices]
  1196. // 将物料不存在的发票也添加到删除列表中
  1197. this.invalidInvoices.forEach(item => {
  1198. if (!allDeletedInvoices.includes(item.cmcInvoice)) {
  1199. allDeletedInvoices.push(item.cmcInvoice)
  1200. }
  1201. })
  1202. formData.append("deletedInvoices", JSON.stringify(allDeletedInvoices))
  1203. // 只为可导入的发票设置客户信息
  1204. this.validInvoices.forEach((item, index) => {
  1205. formData.append(`templateNo_${item.cmcInvoice}`, item.templateNo)
  1206. formData.append(`customerName_${item.cmcInvoice}`, item.selectedCustomer)
  1207. formData.append(`localShipAddress_${item.cmcInvoice}`, item.selectedLocalAddress)
  1208. formData.append(`overseasShipper_${item.cmcInvoice}`, item.selectedOverseasShipper)
  1209. formData.append(`overseasAddress_${item.cmcInvoice}`, item.selectedOverseasAddress)
  1210. formData.append(`cnative_${item.cmcInvoice}`, item.selectedCnative)
  1211. formData.append(`salesArea_${item.cmcInvoice}`, item.selectedSalesArea)
  1212. })
  1213. saveEcssCoDelNotifyByExcelTX(formData).then(({ data }) => {
  1214. if (data.code === 0) {
  1215. const { resultMap } = data;
  1216. const successList = resultMap.success || [];
  1217. const failList = resultMap.fail || [];
  1218. // 紧凑样式
  1219. let html = `
  1220. <div style="max-height:380px;overflow:auto;font-size:12px;line-height:1.4;">
  1221. `;
  1222. if (successList.length > 0) {
  1223. html += `<div style="margin-bottom:6px;">
  1224. <div style="color:green;font-weight:bold;margin-bottom:3px;">
  1225. 成功${successList.length}
  1226. </div>
  1227. <table border="1" cellspacing="0" cellpadding="2"
  1228. style="border-collapse:collapse;width:100%;">
  1229. `;
  1230. successList.forEach(item => {
  1231. html += `<tr><td style="color:green;padding:2px 4px;">${item}</td></tr>`;
  1232. });
  1233. html += `</table></div>`;
  1234. }
  1235. if (failList.length > 0) {
  1236. html += `<div>
  1237. <div style="color:red;font-weight:bold;margin-bottom:3px;">
  1238. 失败${failList.length}
  1239. </div>
  1240. <table border="1" cellspacing="0" cellpadding="2"
  1241. style="border-collapse:collapse;width:100%;">
  1242. `;
  1243. failList.forEach(item => {
  1244. html += `<tr><td style="color:red;padding:2px 4px;">${item}</td></tr>`;
  1245. });
  1246. html += `</table></div>`;
  1247. }
  1248. html += `</div>`;
  1249. this.$alert(html, '导入结果', {
  1250. confirmButtonText: '确定',
  1251. dangerouslyUseHTMLString: true,
  1252. callback: () => {
  1253. if (successList.length > 0){
  1254. this.clearCache();
  1255. this.clearPreviewData();
  1256. this.closeDialog();
  1257. }
  1258. }
  1259. });
  1260. } else {
  1261. this.$alert(data.msg, '错误', {
  1262. confirmButtonText: '确定'
  1263. });
  1264. }
  1265. }).catch(error => {
  1266. this.$message.error('保存失败:' + (error.message || '网络异常'))
  1267. }).finally(() => {
  1268. this.confirmSaveButtonLoading = false // 结束加载
  1269. });
  1270. },
  1271. // 取消预览
  1272. cancelPreview() {
  1273. this.showPreview = false
  1274. this.previewData = []
  1275. this.sheetErrors = []
  1276. this.fileList = []
  1277. this.selectedFile = null
  1278. this.deletedInvoices = []
  1279. this.cachedFileInfo = null
  1280. this.cachedFileList = []
  1281. this.$refs.uploadFile.clearFiles()
  1282. // 更新缓存
  1283. this.saveToCache()
  1284. },
  1285. // 清除预览数据
  1286. clearPreviewData() {
  1287. this.previewData = []
  1288. this.sheetErrors = []
  1289. this.showPreview = false
  1290. this.selectedFile = null
  1291. this.fileList = []
  1292. this.deletedInvoices = []
  1293. this.previewLoading = false
  1294. this.cachedFileInfo = null
  1295. this.cachedFileList = []
  1296. this.$refs.uploadFile.clearFiles()
  1297. // 更新缓存
  1298. this.saveToCache()
  1299. },
  1300. // 为行选择客户模板
  1301. selectTemplateForRow(row) {
  1302. this.currentRow = row
  1303. this.getCustomerTemplateList()
  1304. this.templateFlag = true
  1305. },
  1306. // 为行选择客户
  1307. selectCustomerForRow(row) {
  1308. this.currentRow = row
  1309. this.customFlag = true
  1310. },
  1311. // 为行选择客户地址
  1312. selectLocalAddressForRow(row) {
  1313. if (!row.selectedCustomer) {
  1314. this.$message.warning('请先选择客户')
  1315. return
  1316. }
  1317. this.currentRow = row
  1318. this.getCusPersonsForRow(row.selectedCustomer)
  1319. this.localShipAddressFlag = true
  1320. },
  1321. // 为行选择收货单位
  1322. selectOverseasShipperForRow(row) {
  1323. if (!row.selectedCustomer) {
  1324. this.$message.warning('请先选择客户')
  1325. return
  1326. }
  1327. this.currentRow = row
  1328. this.getCusPersonsForRow(row.selectedCustomer)
  1329. this.overseasShipperFlag = true
  1330. },
  1331. // 为行选择收货单位地址
  1332. selectOverseasAddressForRow(row) {
  1333. if (!row.selectedCustomer) {
  1334. this.$message.warning('请先选择客户')
  1335. return
  1336. }
  1337. this.currentRow = row
  1338. this.getCusPersonsForRow(row.selectedCustomer)
  1339. this.overseasAddressFlag = true
  1340. },
  1341. // 获取客户模板列表
  1342. async getCustomerTemplateList(searchParams = {}) {
  1343. try {
  1344. const { data } = await getCustomerTemplateList(searchParams)
  1345. if (data && data.code === 0) {
  1346. this.customerTemplateList = data.rows || []
  1347. }
  1348. } catch (error) {
  1349. console.error('获取客户模板列表失败:', error)
  1350. this.customerTemplateList = []
  1351. }
  1352. },
  1353. // 搜索客户模板
  1354. searchTemplateList() {
  1355. const searchParams = {
  1356. templateName: this.templateSearchData.templateName,
  1357. customerName: this.templateSearchData.customerName
  1358. }
  1359. this.getCustomerTemplateList(searchParams)
  1360. },
  1361. // 重置模板搜索
  1362. resetTemplateSearch() {
  1363. this.templateSearchData = {
  1364. templateName: '',
  1365. customerName: ''
  1366. }
  1367. this.getCustomerTemplateList()
  1368. },
  1369. // 获取特定客户的地址信息
  1370. getCusPersonsForRow(customerName) {
  1371. let cusData = {ccusname: customerName}
  1372. getCustomers(cusData).then(({data}) => {
  1373. if (data && data.code === 0) {
  1374. this.customerPersons = data.rows
  1375. this.customerPersons.forEach(o => {
  1376. if (!this.customerMap.has(o.ccontactname)) {
  1377. this.customerMap.set(o.ccontactname, o.cnative);
  1378. }
  1379. });
  1380. }
  1381. });
  1382. getCustomerAdd(cusData).then(({data}) => {
  1383. if (data && data.code === 0) {
  1384. this.customerAddrs = data.rows
  1385. }
  1386. });
  1387. },
  1388. // 打开大输入框编辑弹窗
  1389. openEditDialog(row, fieldName, label, title) {
  1390. this.editDialog.visible = true
  1391. this.editDialog.title = title
  1392. this.editDialog.label = label
  1393. this.editDialog.fieldName = fieldName
  1394. this.editDialog.row = row
  1395. this.editDialog.value = row[fieldName] || ''
  1396. this.$nextTick(() => this.$refs.editTextarea.focus());
  1397. },
  1398. // 关闭大输入框编辑弹窗
  1399. closeEditDialog() {
  1400. this.editDialog.visible = false
  1401. this.editDialog.title = ''
  1402. this.editDialog.label = ''
  1403. this.editDialog.fieldName = ''
  1404. this.editDialog.row = null
  1405. this.editDialog.value = ''
  1406. },
  1407. // 确认编辑
  1408. confirmEdit() {
  1409. if (this.editDialog.row && this.editDialog.fieldName) {
  1410. this.editDialog.row[this.editDialog.fieldName] = this.editDialog.value
  1411. }
  1412. this.closeEditDialog()
  1413. },
  1414. // 处理表格选择变化
  1415. handleSelectionChange(selection) {
  1416. this.selectedRows = selection
  1417. },
  1418. // 批量删除行
  1419. batchDeleteRows() {
  1420. if (this.selectedRows.length === 0) {
  1421. this.$message.warning('请先选择要删除的发票')
  1422. return
  1423. }
  1424. const invoiceNumbers = this.selectedRows.map(row => row.cmcInvoice).join('、')
  1425. this.$confirm(`确定要删除以下 ${this.selectedRows.length} 个发票吗?\n${invoiceNumbers}\n\n删除后将不会提交到后台。`, '批量删除确认', {
  1426. confirmButtonText: '确定删除',
  1427. cancelButtonText: '取消',
  1428. type: 'warning'
  1429. }).then(() => {
  1430. // 记录被删除的发票号并从预览数据中移除
  1431. this.selectedRows.forEach(row => {
  1432. // 记录被删除的发票号
  1433. if (!this.deletedInvoices.includes(row.cmcInvoice)) {
  1434. this.deletedInvoices.push(row.cmcInvoice)
  1435. }
  1436. // 从预览数据中移除该行
  1437. const previewIndex = this.previewData.findIndex(item => item.cmcInvoice === row.cmcInvoice)
  1438. if (previewIndex !== -1) {
  1439. this.previewData.splice(previewIndex, 1)
  1440. }
  1441. })
  1442. this.$message.success(`已成功删除 ${this.selectedRows.length} 个发票`)
  1443. // 清空选中状态
  1444. this.selectedRows = []
  1445. // 保存到缓存
  1446. this.saveToCache()
  1447. }).catch(() => {
  1448. // 用户取消删除
  1449. })
  1450. },
  1451. // 删除行
  1452. deleteRow(index, row) {
  1453. this.$confirm(`确定要删除发票号 "${row.cmcInvoice}" 吗?删除后将不会提交到后台。`, '删除确认', {
  1454. confirmButtonText: '确定删除',
  1455. cancelButtonText: '取消',
  1456. type: 'warning'
  1457. }).then(() => {
  1458. // 记录被删除的发票号
  1459. if (!this.deletedInvoices.includes(row.cmcInvoice)) {
  1460. this.deletedInvoices.push(row.cmcInvoice)
  1461. }
  1462. // 从预览数据中移除该行(根据发票号查找)
  1463. const previewIndex = this.previewData.findIndex(item => item.cmcInvoice === row.cmcInvoice)
  1464. if (previewIndex !== -1) {
  1465. this.previewData.splice(previewIndex, 1)
  1466. }
  1467. this.$message.success(`发票号 "${row.cmcInvoice}" 已删除`)
  1468. // 清空选中状态(如果删除的是选中的行)
  1469. this.selectedRows = this.selectedRows.filter(r => r.cmcInvoice !== row.cmcInvoice)
  1470. // 保存到缓存
  1471. this.saveToCache()
  1472. }).catch(() => {
  1473. // 用户取消删除
  1474. })
  1475. },
  1476. // 下载
  1477. async downloadFile () {
  1478. let file = {
  1479. id: 0,
  1480. fileName: ''
  1481. }
  1482. let tempData = {
  1483. orderRef1: 'ecss',
  1484. orderRef2: 'upLoadDel'
  1485. }
  1486. await queryFileId(tempData).then(({data}) => {
  1487. if (data && data.code === 0) {
  1488. file.id = data.data.id
  1489. file.fileName = data.data.fileName
  1490. } else {
  1491. this.$alert(data.msg, '错误', {
  1492. confirmButtonText: '确定'
  1493. })
  1494. }
  1495. })
  1496. await downLoadObjectFile(file).then(({data}) => {
  1497. // 不限制文件下载类型
  1498. const blob = new Blob([data], {type: "application/octet-stream"})
  1499. // 下载文件名称
  1500. const fileName = file.fileName
  1501. // a标签下载
  1502. const linkNode = document.createElement('a')
  1503. // a标签的download属性规定下载文件的名称
  1504. linkNode.download = fileName
  1505. linkNode.style.display = 'none'
  1506. // 生成一个Blob URL
  1507. linkNode.href = URL.createObjectURL(blob)
  1508. document.body.appendChild(linkNode)
  1509. // 模拟在按钮上的一次鼠标单击
  1510. linkNode.click()
  1511. // 释放URL 对象
  1512. URL.revokeObjectURL(linkNode.href)
  1513. document.body.removeChild(linkNode)
  1514. })
  1515. },
  1516. // 显示物料不存在详情
  1517. showInvalidMaterials(row) {
  1518. this.invalidMaterialsDialog.invoice = row.cmcInvoice
  1519. this.invalidMaterialsDialog.materials = row.invalidMaterials || []
  1520. this.invalidMaterialsDialog.visible = true
  1521. },
  1522. // 关闭物料不存在详情对话框
  1523. closeInvalidMaterialsDialog() {
  1524. this.invalidMaterialsDialog.visible = false
  1525. this.invalidMaterialsDialog.invoice = ''
  1526. this.invalidMaterialsDialog.materials = []
  1527. },
  1528. /**
  1529. * 检查行数据是否已准备好保存模板
  1530. * @param {Object} row - 行数据
  1531. * @return {boolean} 是否可以保存
  1532. */
  1533. isRowReadyForSave(row) {
  1534. return !!(row.selectedTemplate &&
  1535. row.selectedTemplate.trim() &&
  1536. row.selectedCustomer &&
  1537. row.selectedCustomer.trim())
  1538. },
  1539. /**
  1540. * 保存模板修改
  1541. * @param {Object} row - 行数据
  1542. */
  1543. async saveTemplateChanges(row) {
  1544. // 验证必填字段
  1545. if (!row.selectedTemplate || !row.selectedTemplate.trim()) {
  1546. this.$message.warning('请输入模板名称')
  1547. return
  1548. }
  1549. if (!row.selectedCustomer || !row.selectedCustomer.trim()) {
  1550. this.$message.warning('请填写客户信息')
  1551. return
  1552. }
  1553. // 判断操作类型,给用户明确提示
  1554. const isNewTemplate = !row.originalTemplateName || row.originalTemplateName === ''
  1555. const isNameChanged = row.originalTemplateName && row.selectedTemplate !== row.originalTemplateName
  1556. let confirmMessage = ''
  1557. if (isNewTemplate) {
  1558. // 手动输入的新模板名
  1559. confirmMessage = `确定要保存模板"${row.selectedTemplate}"吗?\n\n如果此模板名已存在,将会覆盖原有模板内容!`
  1560. } else if (isNameChanged) {
  1561. // 基于已选模板另存为新模板
  1562. confirmMessage = `确定要将模板"${row.originalTemplateName}"另存为"${row.selectedTemplate}"吗?\n\n如果"${row.selectedTemplate}"已存在,将会覆盖原有模板内容!`
  1563. } else {
  1564. // 更新原有模板
  1565. confirmMessage = `确定要更新模板"${row.selectedTemplate}"吗?\n\n此操作将覆盖原有模板内容!`
  1566. }
  1567. // 弹出确认对话框
  1568. try {
  1569. await this.$confirm(confirmMessage, '操作确认', {
  1570. confirmButtonText: '确定',
  1571. cancelButtonText: '取消',
  1572. type: 'warning',
  1573. distinguishCancelAndClose: true
  1574. })
  1575. } catch (error) {
  1576. // 用户取消操作
  1577. return
  1578. }
  1579. // 准备保存数据
  1580. const templateData = {
  1581. templateName: row.selectedTemplate.trim(),
  1582. // ✅ 修复Bug:保持空字符串不变,不要用 || 运算符替换
  1583. // 空字符串表示"新增或更新同名模板",非空表示"基于原模板修改"
  1584. originalTemplateName: row.originalTemplateName || '',
  1585. ccuscode: '', // 后端会根据实际情况自动处理
  1586. ccusname: row.selectedCustomer.trim(),
  1587. localShipAddress: row.selectedLocalAddress || '',
  1588. overseasShipper: row.selectedOverseasShipper || '',
  1589. overseasAddress: row.selectedOverseasAddress || '',
  1590. cnative: row.selectedCnative || '',
  1591. salesArea: row.selectedSalesArea || ''
  1592. }
  1593. try {
  1594. const { data } = await saveExcelImportTemplate(templateData)
  1595. if (data && data.code === 0) {
  1596. // 判断是新增还是修改,给出不同的成功提示
  1597. if (isNewTemplate) {
  1598. this.$message.success(`模板"${row.selectedTemplate}"保存成功!`)
  1599. // 保存成功后更新 originalTemplateName,下次保存时就是修改模式
  1600. row.originalTemplateName = row.selectedTemplate
  1601. } else if (isNameChanged) {
  1602. this.$message.success(`新模板"${row.selectedTemplate}"另存成功!`)
  1603. // 另存为成功后更新 originalTemplateName
  1604. row.originalTemplateName = row.selectedTemplate
  1605. } else {
  1606. this.$message.success(`模板"${row.selectedTemplate}"更新成功!`)
  1607. }
  1608. // 保存成功后更新缓存
  1609. this.saveToCache()
  1610. } else {
  1611. this.$message.error(data.msg || '保存模板失败')
  1612. }
  1613. } catch (error) {
  1614. console.error('保存模板失败:', error)
  1615. this.$message.error('保存模板失败:' + (error.message || '网络异常'))
  1616. }
  1617. },
  1618. }
  1619. }
  1620. </script>
  1621. <style scoped>
  1622. /deep/ .delClass .cell {
  1623. line-height: 24px !important;
  1624. font-size: 12px !important;
  1625. height: 24px !important;
  1626. }
  1627. /deep/ .customer-upload .el-upload .el-upload-dragger {
  1628. width: 580px;
  1629. }
  1630. /* 输入框样式优化 */
  1631. .el-input.is-readonly {
  1632. cursor: pointer !important;
  1633. }
  1634. .el-input.is-readonly .el-input__inner {
  1635. cursor: pointer !important;
  1636. background-color: #f8f9fa;
  1637. }
  1638. .el-input.is-readonly .el-input__inner:hover {
  1639. background-color: #e9ecef;
  1640. border-color: #409EFF;
  1641. }
  1642. /* 编辑图标样式 */
  1643. .el-input__suffix .el-icon-edit {
  1644. color: #409EFF;
  1645. transition: all 0.3s;
  1646. font-size: 14px;
  1647. padding: 2px;
  1648. border-radius: 3px;
  1649. }
  1650. .el-input__suffix .el-icon-edit:hover {
  1651. color: #ffffff;
  1652. background-color: #409EFF;
  1653. transform: scale(1.1);
  1654. }
  1655. /* 编辑弹窗整体样式 */
  1656. .edit-dialog .el-dialog__body {
  1657. padding: 20px 20px 0;
  1658. }
  1659. .edit-dialog-tip {
  1660. margin-bottom: 15px;
  1661. color: #909399;
  1662. font-size: 12px;
  1663. background-color: #f4f4f5;
  1664. padding: 8px 12px;
  1665. border-radius: 4px;
  1666. border-left: 3px solid #409EFF;
  1667. }
  1668. .edit-dialog-tip .el-icon-info {
  1669. margin-right: 5px;
  1670. color: #409EFF;
  1671. }
  1672. /* 大输入框样式 */
  1673. .edit-textarea .el-textarea__inner {
  1674. resize: vertical !important;
  1675. min-height: 120px !important;
  1676. border: 1px solid #dcdfe6 !important;
  1677. border-radius: 4px !important;
  1678. padding: 8px 12px !important;
  1679. font-size: 14px !important;
  1680. line-height: 1.5 !important;
  1681. color: #606266 !important;
  1682. background-color: #fff !important;
  1683. transition: border-color 0.2s cubic-bezier(.645,.045,.355,1) !important;
  1684. box-sizing: border-box !important;
  1685. }
  1686. .edit-textarea .el-textarea__inner:focus {
  1687. outline: none !important;
  1688. border-color: #409EFF !important;
  1689. box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2) !important;
  1690. }
  1691. .edit-textarea .el-input__count {
  1692. color: #909399 !important;
  1693. background: #fff !important;
  1694. position: absolute !important;
  1695. font-size: 12px !important;
  1696. bottom: 5px !important;
  1697. right: 10px !important;
  1698. }
  1699. /* 修复弹窗中的表单项样式 */
  1700. .edit-dialog .el-form-item {
  1701. margin-bottom: 18px !important;
  1702. }
  1703. .edit-dialog .el-form-item__label {
  1704. color: #606266 !important;
  1705. font-weight: 500 !important;
  1706. line-height: 1.5 !important;
  1707. padding: 0 0 8px 0 !important;
  1708. box-sizing: border-box !important;
  1709. font-size: 14px !important;
  1710. }
  1711. /* 编辑弹窗按钮样式 */
  1712. .edit-dialog .dialog-footer {
  1713. text-align: right !important;
  1714. padding: 15px 20px 20px !important;
  1715. }
  1716. .edit-dialog .dialog-footer .el-button {
  1717. margin-left: 10px !important;
  1718. }
  1719. /* 确保弹窗内容不会溢出 */
  1720. .edit-dialog .el-dialog__wrapper {
  1721. overflow: hidden;
  1722. }
  1723. .edit-dialog .el-dialog {
  1724. margin-top: 5vh !important;
  1725. margin-bottom: 50px !important;
  1726. }
  1727. /* 表格输入框提示文字 */
  1728. .el-table .el-input__inner::placeholder {
  1729. font-size: 12px;
  1730. color: #c0c4cc;
  1731. }
  1732. /* 可点击发票号样式 */
  1733. .clickable-invoice {
  1734. cursor: pointer !important;
  1735. text-decoration: underline;
  1736. transition: all 0.3s;
  1737. }
  1738. .clickable-invoice:hover {
  1739. color: #409EFF !important;
  1740. text-shadow: 0 0 3px rgba(64, 158, 255, 0.3);
  1741. }
  1742. /* 表格样式区分 */
  1743. .valid-table .el-table__header {
  1744. background-color: #F0F9FF !important;
  1745. }
  1746. .valid-table .el-table th {
  1747. background-color: #F0F9FF !important;
  1748. color: #67C23A !important;
  1749. }
  1750. .invalid-table .el-table__header {
  1751. background-color: #FEF0F0 !important;
  1752. }
  1753. .invalid-table .el-table th {
  1754. background-color: #FEF0F0 !important;
  1755. color: #F56C6C !important;
  1756. }
  1757. .invalid-table .el-table .el-table__row:hover > td {
  1758. background-color: #FDF2F2 !important;
  1759. }
  1760. /* 物料不存在详情对话框样式 */
  1761. .invalid-materials-dialog .el-dialog__body {
  1762. padding: 20px;
  1763. }
  1764. .invalid-materials-content .warning-tip {
  1765. padding: 12px 16px;
  1766. background-color: #FEF0F0;
  1767. border: 1px solid #FBC4C4;
  1768. border-radius: 4px;
  1769. margin-bottom: 15px;
  1770. }
  1771. .invalid-materials-content .materials-summary {
  1772. text-align: center;
  1773. font-size: 14px;
  1774. }
  1775. .invalid-materials-dialog .el-table th {
  1776. background-color: #FEF0F0 !important;
  1777. }
  1778. .invalid-materials-dialog .el-table .el-table__row:hover > td {
  1779. background-color: #FDF2F2 !important;
  1780. }
  1781. /* Sheet错误表格样式 */
  1782. .warning-table .el-table__header {
  1783. background-color: #FDF6EC !important;
  1784. }
  1785. .warning-table .el-table th {
  1786. background-color: #FDF6EC !important;
  1787. color: #E6A23C !important;
  1788. }
  1789. .warning-table .el-table .el-table__row:hover > td {
  1790. background-color: #FEF5E7 !important;
  1791. }
  1792. </style>