index.vue 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177
  1. <template>
  2. <div class="w-[100vw] bg-white page-cargo-consolidation">
  3. <div
  4. v-loading="loading"
  5. class="pt-2 w-[100%] min-h-[100vh]"
  6. >
  7. <el-form
  8. style="width: 100%"
  9. inline
  10. :loading="loading"
  11. @submit.prevent="getList"
  12. >
  13. <el-form-item label="ETD Range">
  14. <el-date-picker
  15. v-model="dateRange"
  16. type="daterange"
  17. unlink-panels
  18. clearable
  19. start-placeholder="Start date"
  20. end-placeholder="End date"
  21. :shortcuts="dateShortcuts"
  22. />
  23. </el-form-item>
  24. <el-form-item>
  25. <el-tooltip content="时间范围最好不要选太大, 不然数据太多会卡">
  26. <el-button
  27. class="custom-button"
  28. @click="getList"
  29. >
  30. Search
  31. </el-button>
  32. </el-tooltip>
  33. </el-form-item>
  34. </el-form>
  35. <el-tabs v-model="currentTab">
  36. <el-tab-pane
  37. v-for="tab in tabs"
  38. :key="tab.value"
  39. :label="tab.label"
  40. :name="tab.value"
  41. ></el-tab-pane>
  42. </el-tabs>
  43. <el-table
  44. border
  45. :header-cell-style="{ backgroundColor: 'rgb(227, 241, 253)' }"
  46. :data="computedList"
  47. highlight-current-row
  48. style="width: 100%"
  49. max-height="330"
  50. @row-click="($e) => getSubList($e)"
  51. >
  52. <el-table-column
  53. prop="Name"
  54. label="船名&航次"
  55. min-width="500"
  56. />
  57. <el-table-column
  58. prop="Forwarder"
  59. label="货代"
  60. width="110"
  61. />
  62. <el-table-column
  63. prop="ETD"
  64. label="ETD"
  65. width="110"
  66. />
  67. <el-table-column
  68. prop="ATD"
  69. label="ATD"
  70. width="110"
  71. />
  72. <el-table-column
  73. prop="ETA"
  74. label="ETA"
  75. width="110"
  76. />
  77. <el-table-column
  78. prop="ATA"
  79. label="ATA"
  80. width="110"
  81. />
  82. <el-table-column
  83. prop="Cut_Off_Date"
  84. label="入仓时间"
  85. width="120"
  86. />
  87. <el-table-column
  88. prop="Status"
  89. label="状态"
  90. width="100"
  91. />
  92. <!-- <el-table-column
  93. label="修改时间"
  94. min-width="190"
  95. >
  96. <template #default="scope">
  97. {{ dayjs(scope.row.Modified_Time).format('YYYY-MM-DD HH:mm:ss') }}
  98. </template>
  99. </el-table-column> -->
  100. <el-table-column
  101. prop="GRN"
  102. label="入仓单号"
  103. min-width="200"
  104. />
  105. <el-table-column
  106. prop="Booking_Number"
  107. label="订舱号"
  108. min-width="120"
  109. />
  110. <el-table-column
  111. prop="Carrier"
  112. label="船东"
  113. min-width="120"
  114. />
  115. <el-table-column label="操作">
  116. <template #default="scope">
  117. <el-button
  118. size="small"
  119. type="warning"
  120. link
  121. :disabled="!scope.row.SubscriptionId"
  122. @click="openMapDrawer(scope.row)"
  123. >
  124. 查看轨迹
  125. </el-button>
  126. </template>
  127. </el-table-column>
  128. </el-table>
  129. <div
  130. v-show="currentRow.Name?.length"
  131. class="flex justify-between mt-8"
  132. >
  133. <div class="flex">
  134. <div class="flex items-center mr-2">
  135. <span class="min-w-[150px]">选中的船名&航次:&nbsp;</span>
  136. <div class="text-red-700 font-bold">
  137. {{ currentRow.Name }}
  138. </div>
  139. </div>
  140. <div class="flex flex-wrap min-w-[800px]">
  141. <el-button
  142. v-if="
  143. ['可用', '已截仓'].includes(currentRow.Status) &&
  144. [
  145. '4791186000259693001',
  146. '4791186000022965001',
  147. '4791186000052269001',
  148. ].includes(currentUser)
  149. "
  150. size="small"
  151. type="danger"
  152. @click="ensure"
  153. >
  154. 确认集货
  155. </el-button>
  156. <el-button
  157. v-if="currentTab === 'my_request'"
  158. size="small"
  159. @click="addBulkProduct"
  160. >
  161. Submit Bulk Production Request
  162. </el-button>
  163. <el-button
  164. v-if="currentTab === 'my_request'"
  165. size="small"
  166. @click="addSample"
  167. >
  168. Submit Sample Request
  169. </el-button>
  170. <el-button
  171. v-if="currentTab === 'my_request'"
  172. :disabled="subList.length < 1"
  173. size="small"
  174. type="danger"
  175. @click="generateSubList"
  176. >
  177. 放弃下方表格改动
  178. </el-button>
  179. <el-button
  180. size="small"
  181. :disabled="subList.length < 1"
  182. @click="exportSubTable"
  183. >
  184. Export Excel File
  185. </el-button>
  186. <el-button
  187. v-if="currentTab === 'my_request'"
  188. :disabled="subList.length < 1"
  189. size="small"
  190. type="primary"
  191. class="custom-button small"
  192. @click="commit"
  193. >
  194. Commit
  195. </el-button>
  196. </div>
  197. </div>
  198. <div class="flex justify-between min-w-[350px]">
  199. <div class="flex">总重量: {{ computedWeight }}</div>
  200. <div class="flex">总体积: {{ computedCube }}</div>
  201. <div class="flex">总离岸价: {{ computedTotalFOB }}</div>
  202. </div>
  203. </div>
  204. <el-table
  205. class="mt-4"
  206. size="small"
  207. :data="subList"
  208. style="width: 100%"
  209. :row-style="calcRowStyle"
  210. :header-cell-style="{ backgroundColor: 'rgb(227, 241, 253)' }"
  211. :empty-text="
  212. currentRow.Name?.length
  213. ? '暂无数据'
  214. : '请点击voyage表格其中一行以查询相应记录'
  215. "
  216. border
  217. >
  218. <el-table-column
  219. fixed
  220. type="index"
  221. width="50"
  222. ></el-table-column>
  223. <el-table-column
  224. fixed
  225. label="CRM批次记录"
  226. width="220"
  227. >
  228. <template #default="scope">
  229. <el-select
  230. v-model="scope.row.batchRecord"
  231. size="small"
  232. :remote-method="debounce(search, 1500)"
  233. remote
  234. style="width: 100%"
  235. :loading="loading2"
  236. filterable
  237. clearable
  238. :placeholder="scope.row.Sample ? 'Sample' : ''"
  239. :disabled="scope.row.Sample"
  240. @change="($e) => onBatchRecordChange($e, scope.$index)"
  241. >
  242. <el-option
  243. v-for="option in batchListOption.concat(qcList) as any[]"
  244. :key="option.id"
  245. :value="option.id"
  246. :label="
  247. option.Name +
  248. (option.Reference ? ` - ${option.Reference}` : '')
  249. "
  250. ></el-option>
  251. </el-select>
  252. </template>
  253. </el-table-column>
  254. <el-table-column
  255. label="备注"
  256. width="110"
  257. fixed
  258. >
  259. <template #default="scope">
  260. <el-input
  261. v-model="scope.row.User_Notes"
  262. size="small"
  263. :rows="2"
  264. type="textarea"
  265. @change="scope.row.editFlag = true"
  266. ></el-input>
  267. </template>
  268. </el-table-column>
  269. <el-table-column
  270. label="箱数"
  271. width="80"
  272. >
  273. <template #default="scope">
  274. <el-input
  275. v-model="scope.row.Carton"
  276. size="small"
  277. @change="scope.row.editFlag = true"
  278. ></el-input>
  279. </template>
  280. </el-table-column>
  281. <el-table-column
  282. label="唛头"
  283. width="220"
  284. >
  285. <template #default="scope">
  286. <el-input
  287. v-model="scope.row.Marks_Nos"
  288. size="small"
  289. @change="scope.row.editFlag = true"
  290. ></el-input>
  291. </template>
  292. </el-table-column>
  293. <el-table-column
  294. label="货物名称"
  295. width="180"
  296. >
  297. <template #default="scope">
  298. <el-input
  299. v-model="scope.row.Description_of_Goods"
  300. size="small"
  301. @change="scope.row.editFlag = true"
  302. ></el-input>
  303. </template>
  304. </el-table-column>
  305. <el-table-column
  306. label="货物材质"
  307. width="220"
  308. >
  309. <template #default="scope">
  310. <el-select
  311. v-model="scope.row.Material_of_Goods"
  312. size="small"
  313. multiple
  314. @change="scope.row.editFlag = true"
  315. >
  316. <el-option
  317. v-for="i in goodMaterialOption"
  318. :key="i"
  319. :value="i"
  320. :label="i"
  321. ></el-option>
  322. </el-select>
  323. </template>
  324. </el-table-column>
  325. <el-table-column
  326. label="数量"
  327. width="100"
  328. >
  329. <template #default="scope">
  330. <el-input
  331. v-model="scope.row.Quantity"
  332. size="small"
  333. @change="changeTotal(scope.row)"
  334. ></el-input>
  335. </template>
  336. </el-table-column>
  337. <el-table-column
  338. label="单价(澳币/AUD)"
  339. width="120"
  340. >
  341. <template #default="scope">
  342. <el-input
  343. v-model="scope.row.Unit_Price"
  344. size="small"
  345. @change="changeTotal(scope.row)"
  346. ></el-input>
  347. </template>
  348. </el-table-column>
  349. <el-table-column
  350. label="负责人"
  351. fixed="right"
  352. width="120"
  353. >
  354. <template #default="scope">
  355. <el-input
  356. v-model="scope.row.Requester.name"
  357. size="small"
  358. disabled
  359. ></el-input>
  360. </template>
  361. </el-table-column>
  362. <el-table-column
  363. label="申请人"
  364. fixed="right"
  365. width="120"
  366. prop="Sales_Person"
  367. >
  368. <template #default="scope">
  369. <el-input
  370. v-model="scope.row.Sales_Person"
  371. size="small"
  372. disabled
  373. ></el-input>
  374. </template>
  375. </el-table-column>
  376. <el-table-column
  377. v-show="currentTab === 'my_request'"
  378. fixed="right"
  379. label="更新时间"
  380. width="90"
  381. >
  382. <template #default="scope">
  383. <div v-if="scope.row.id">
  384. {{
  385. dayjs(scope.row.update_time || new Date()).format(
  386. 'YYYY-MM-DD HH:mm:ss',
  387. )
  388. }}
  389. </div>
  390. </template>
  391. </el-table-column>
  392. <el-table-column
  393. v-show="
  394. [
  395. '4791186000259693001',
  396. '4791186000022965001',
  397. '4791186000052269001',
  398. ].includes(currentUser)
  399. "
  400. fixed="right"
  401. label="操作"
  402. width="85"
  403. >
  404. <template #default="scope">
  405. <el-tooltip
  406. content="新增未commit的行会被直接移除; 已commit的行只会标记, 正式commit后才会删除"
  407. >
  408. <el-button
  409. v-if="currentTab === 'my_request'"
  410. type="danger"
  411. size="small"
  412. link
  413. @click="() => onDeleteRow(scope.row, scope.$index)"
  414. >
  415. 删除
  416. </el-button>
  417. </el-tooltip>
  418. <el-button
  419. size="small"
  420. link
  421. type="primary"
  422. @click="print(scope.row)"
  423. >
  424. 打印
  425. </el-button>
  426. </template>
  427. </el-table-column>
  428. <el-table-column
  429. fixed="right"
  430. label="重量 (KG)"
  431. width="70"
  432. >
  433. <template #default="scope">
  434. <el-input
  435. v-model="scope.row.Weight"
  436. size="small"
  437. @change="scope.row.editFlag = true"
  438. ></el-input>
  439. </template>
  440. </el-table-column>
  441. <el-table-column
  442. fixed="right"
  443. label="体积 m&sup3;"
  444. width="70"
  445. >
  446. <template #default="scope">
  447. <el-input
  448. v-model="scope.row.Cube"
  449. size="small"
  450. @change="scope.row.editFlag = true"
  451. ></el-input>
  452. </template>
  453. </el-table-column>
  454. <el-table-column
  455. fixed="right"
  456. label="总离岸价 (澳币/AUD)"
  457. width="140"
  458. >
  459. <template #default="scope">
  460. <el-input
  461. v-model="scope.row.Total_FOB"
  462. size="small"
  463. disabled
  464. ></el-input>
  465. </template>
  466. </el-table-column>
  467. </el-table>
  468. <el-drawer
  469. v-model:model-value="printDrawerVisible"
  470. :size="'1050px'"
  471. :title="'打印唛 (注意先写完唛内容和宽高最后再调页数, 不然可能会很卡)'"
  472. :close-on-click-modal="false"
  473. :close-on-press-escape="false"
  474. >
  475. <comp-print
  476. v-if="printDrawerVisible"
  477. :autoOpenQRCode="false"
  478. :content="[currentPrintRow.Marks_Nos || '']"
  479. :scene="'QC'"
  480. />
  481. </el-drawer>
  482. <el-drawer
  483. v-model:model-value="mapDrawerVisible"
  484. :size="'1050px'"
  485. :close-on-click-modal="false"
  486. :close-on-press-escape="false"
  487. :destroy-on-close="true"
  488. :with-header="false"
  489. header-class="mb-1"
  490. >
  491. <div class="relative h-[20px]">
  492. <span
  493. @click="closeMapDrawer"
  494. class="absolute top-[-16px] right-0 text-gray-600 text-sm cursor-pointer"
  495. >
  496. 关闭地图窗口
  497. </span>
  498. </div>
  499. <div
  500. id="mapDrawer"
  501. class="h-[100%]"
  502. ></div>
  503. </el-drawer>
  504. </div>
  505. </div>
  506. </template>
  507. <script lang="ts" setup>
  508. import { defineComponent, ref, watch, computed, nextTick } from 'vue'
  509. import {
  510. ElButton,
  511. ElForm,
  512. ElFormItem,
  513. ElInput,
  514. ElTabs,
  515. ElTabPane,
  516. ElTable,
  517. ElTableColumn,
  518. ElDatePicker,
  519. ElTooltip,
  520. ElSelect,
  521. ElOption,
  522. ElMessage,
  523. ElMessageBox,
  524. ElNotification,
  525. ElDrawer,
  526. } from 'element-plus'
  527. import cloneDeep from 'lodash.clonedeep'
  528. import dayjs from 'dayjs'
  529. import * as XLSX from 'xlsx'
  530. import debounce from 'lodash.debounce'
  531. import compPrint from '@/components/print.vue'
  532. import request from '@/utils/axios'
  533. defineComponent({
  534. name: 'ComponentCargoConsolidationRequest',
  535. })
  536. const currentUser = ref('')
  537. const currentUserName = ref('')
  538. let loading = ref(false)
  539. let list = ref([] as any[])
  540. let currentTab = ref('all')
  541. let tabs = [
  542. {
  543. label: 'All',
  544. value: 'all',
  545. },
  546. {
  547. label: 'Avaliable',
  548. value: 'avaliable',
  549. },
  550. {
  551. label: 'Closed Voyage',
  552. value: 'voyage_closed',
  553. },
  554. {
  555. label: 'My Request',
  556. value: 'my_request',
  557. },
  558. ]
  559. let computedList = computed(() => {
  560. return list.value.filter((i: any) => {
  561. let condition = true
  562. switch (currentTab.value) {
  563. case 'avaliable':
  564. condition = i.Status && i.Status === '可用'
  565. break
  566. case 'voyage_closed':
  567. condition = ['已截仓', '已确认', '已发出', '已到达'].includes(i.Status)
  568. break
  569. }
  570. return condition
  571. })
  572. })
  573. let dateRange = ref([] as any[])
  574. const generateDateRange = (days = 7) => {
  575. const today = new Date()
  576. const endDate = new Date()
  577. endDate.setDate(today.getDate() + days)
  578. return [today, endDate]
  579. }
  580. const dateShortcuts = ref([
  581. {
  582. text: 'Next 3 days',
  583. value: generateDateRange(3),
  584. },
  585. {
  586. text: 'Next week',
  587. value: generateDateRange(7),
  588. },
  589. {
  590. text: 'Next 14days',
  591. value: generateDateRange(14),
  592. },
  593. ] as any[])
  594. function getDefaultRange() {
  595. const today = dayjs()
  596. const lastMonthFirstDay = today
  597. .subtract(1, 'month')
  598. .startOf('month')
  599. .format('YYYY-MM-DD')
  600. const nextMonthLastDay = today
  601. .add(1, 'month')
  602. .endOf('month')
  603. .format('YYYY-MM-DD')
  604. return [lastMonthFirstDay, nextMonthLastDay]
  605. }
  606. dateRange.value = getDefaultRange()
  607. const clearSubList = () => {
  608. subList.value = []
  609. subListBackup = []
  610. currentRow.value = {}
  611. }
  612. let getList = () => {
  613. loading.value = true
  614. zoho.CRM.API.coql({
  615. select_query:
  616. 'select Name,Forwarder,ETD,ATD,ETA,ATA,Cut_Off_Date,Owner,Status,Modified_Time,SubscriptionId,Carrier,Booking_Number,GRN from Sea_Freight_Table' +
  617. " where ETD between '" +
  618. `${dateRange.value.map((i) => dayjs(i).format('YYYY-MM-DD')).join("' and '")}` +
  619. "'",
  620. })
  621. .then((res: any) => {
  622. if (Array.isArray(res.data) && res.data.length) {
  623. list.value = res.data.sort(
  624. (a: any, b: any) =>
  625. new Date(b.Cut_Off_Date).getTime() -
  626. new Date(a.Cut_Off_Date).getTime(),
  627. )
  628. } else if (res.status === 204) {
  629. ElNotification({
  630. type: 'warning',
  631. title: 'No Data Found',
  632. message: res.statusText || `zoho api return: ${res.status}`,
  633. duration: 3000,
  634. })
  635. list.value = []
  636. clearSubList()
  637. }
  638. })
  639. .finally(() => (loading.value = false))
  640. }
  641. let currentRow = ref({} as any)
  642. let subList = ref([] as any[])
  643. // 未经过滤的crm数据
  644. let subListBackup: any[] = []
  645. watch(currentTab, (value: string) => {
  646. // 切到可用tab, 但是当前行是不是 可用 状态
  647. let notAvaliable = value === 'avaliable' && currentRow.value.Status !== '可用'
  648. // 切到关闭tab, 但当前行不是‘关闭’状态
  649. let notClose =
  650. value === 'voyage_closed' &&
  651. !['已截仓', '已确认', '已发出'].includes(currentRow.value.Status)
  652. if (notAvaliable || notClose) {
  653. clearSubList()
  654. } else {
  655. generateSubList()
  656. }
  657. })
  658. const generateSubList = () => {
  659. // clonedeep 是因为数据里面有 object array, 担心浅复制到表单变量会影响到原始数据
  660. subList.value = cloneDeep(
  661. subListBackup
  662. .filter(
  663. (i: any) =>
  664. i.Parent_Id.id === currentRow.value.id &&
  665. (currentTab.value === 'my_request'
  666. ? i.Sales_Person === currentUserName.value
  667. : true),
  668. )
  669. .map((i: any) => ({
  670. ...i,
  671. Requester: i.Requester?.id
  672. ? {
  673. name: i.Requester.name || '',
  674. id: i.Requester.id,
  675. }
  676. : { name: '', id: '' },
  677. Sample: i.Sample || false,
  678. batchRecord: i.Batch_Record?.id || '',
  679. addFlag: false,
  680. editFlag: false,
  681. deleteFlag: false,
  682. })),
  683. )
  684. }
  685. const changeTotal = (row: any) => {
  686. row.editFlag = true
  687. row.Total_FOB = (Number(row.Unit_Price) * Number(row.Quantity)).toFixed(2)
  688. }
  689. /**
  690. * 用来增加新行的数据模版
  691. */
  692. const newLineTemplate = {
  693. addFlag: true,
  694. editFlag: true,
  695. deleteFlag: false,
  696. batchRecord: '',
  697. // 提交表单前删掉以上字段
  698. // id: '',
  699. Sample: false,
  700. Batch_Record: {
  701. name: '',
  702. id: '',
  703. } as any,
  704. Parent_Id: {
  705. name: '',
  706. id: '',
  707. } as any,
  708. Carton: '',
  709. Marks_Nos: '',
  710. Description_of_Goods: '',
  711. Material_of_Goods: [],
  712. Quantity: '',
  713. Unit_Price: '',
  714. Weight: '',
  715. Cube: '',
  716. Total_FOB: '',
  717. Requester: { name: '', id: '' } as any,
  718. Sales_Person: '', // 申请人
  719. User_Notes: '',
  720. }
  721. // 用在弹窗里面给批次记录做候选项
  722. let batchListOption = computed(() =>
  723. subList.value
  724. .filter(
  725. (i) =>
  726. i.Batch_Record && i.Batch_Record.name && i.Batch_Record.name.length,
  727. )
  728. .map((i) => {
  729. return {
  730. Name: i.Batch_Record.name || '',
  731. id: i.Batch_Record.id || '',
  732. }
  733. })
  734. // ai生成的去重逻辑.
  735. .filter(
  736. (item, index, self) =>
  737. index ===
  738. self.findIndex((t) => t.Name === item.Name && t.id === item.id),
  739. ),
  740. )
  741. let getSubList = (e: any = {}) => {
  742. if (e.id) currentRow.value = e
  743. loading.value = true
  744. zoho.CRM.API.searchRecord({
  745. Entity: 'Sea_Freight_Details',
  746. Type: 'criteria',
  747. Query: `(Parent_Id:equals:${currentRow.value.id})`,
  748. delay: false,
  749. })
  750. .then((res: any) => {
  751. if (Array.isArray(res.data) && res.data.length) {
  752. subListBackup = cloneDeep(res.data)
  753. } else {
  754. subListBackup = []
  755. }
  756. generateSubList()
  757. newLineTemplate.Parent_Id = { id: currentRow.value.id }
  758. newLineTemplate.Sales_Person = currentUserName.value
  759. newLineTemplate.Requester = {
  760. id: currentUser.value,
  761. name: currentUserName.value,
  762. }
  763. })
  764. .finally(() => (loading.value = false))
  765. }
  766. const goodMaterialOption = ref([
  767. 'Cotton 棉',
  768. 'Iron 铁',
  769. 'Neoprene 潜水料',
  770. 'Paper 纸质',
  771. 'Plastic 塑料',
  772. 'Polyester Fibre 聚酯纤维',
  773. 'PU 聚氨酯',
  774. 'PVC 聚氯乙烯',
  775. 'Velvet 天鹅绒',
  776. 'Zinc alloy 锌合金',
  777. ])
  778. const addBulkProduct = () => {
  779. let temp = cloneDeep(newLineTemplate)
  780. temp.Sample = false
  781. subList.value.unshift(temp)
  782. }
  783. const addSample = () => {
  784. let temp = cloneDeep(newLineTemplate)
  785. temp.Sample = true
  786. subList.value.unshift(temp)
  787. }
  788. const onDeleteRow = (row: any, index: number = -1) => {
  789. if (row.addFlag) {
  790. subList.value.splice(index, 1)
  791. } else {
  792. row.deleteFlag = true
  793. ElNotification({
  794. type: 'warning',
  795. title: '已标记删除',
  796. duration: 3000,
  797. message: '点 提交 按钮后会正式删除. 误操作请点击 放弃改动.',
  798. })
  799. }
  800. }
  801. const onBatchRecordChange = ($e: string, line = -1) => {
  802. const temp = qcList.value.filter((i) => i.id === $e)
  803. let result: any = {}
  804. if (temp.length) {
  805. result = cloneDeep(temp[0])
  806. }
  807. if (line > -1) {
  808. // 主页面数据编辑
  809. subList.value[line].editFlag = true
  810. subList.value[line].Batch_Record = { id: result.id, name: result.Name }
  811. if (result.Owner) {
  812. subList.value[line].Requester = {
  813. name: result.Owner.name,
  814. id: result.Owner.id,
  815. }
  816. }
  817. }
  818. }
  819. // 给子表格加红绿背景. 颜色用的tailwind的 red green
  820. const calcRowStyle = ($e: any) => {
  821. const result = {} as any
  822. if ($e.row.addFlag) result['background-color'] = 'rgb(187, 247, 208)'
  823. if ($e.row.editFlag) result['background-color'] = 'rgb(187, 247, 208)'
  824. if ($e.row.deleteFlag) result['background-color'] = 'rgb(254, 202, 202)'
  825. return result
  826. }
  827. const commit = () => {
  828. let temp = cloneDeep(subList.value)
  829. let result = temp
  830. .filter((i) => !i.deleteFlag)
  831. .map((i) => {
  832. return {
  833. id: i.id,
  834. Sample: i.Sample || false,
  835. Batch_Record: i.batchRecord
  836. ? {
  837. name: i.Batch_Record.name,
  838. id: i.Batch_Record.id,
  839. }
  840. : '',
  841. Parent_Id: {
  842. name: i.Parent_Id.name,
  843. id: i.Parent_Id.id || '',
  844. },
  845. Material_of_Goods: i.Material_of_Goods,
  846. Requester: i.Requester.name
  847. ? {
  848. name: i.Requester.name,
  849. id: i.Requester.id || '',
  850. }
  851. : '',
  852. Sales_Person: i.Sales_Person || '',
  853. Carton: i.Carton || '',
  854. Marks_Nos: i.Marks_Nos || '',
  855. Description_of_Goods: i.Description_of_Goods || '',
  856. Quantity: i.Quantity || '',
  857. Unit_Price: i.Unit_Price || '',
  858. Weight: i.Weight || '',
  859. Cube: i.Cube || '',
  860. Total_FOB: i.Total_FOB || '',
  861. User_Notes: i.User_Notes || '',
  862. }
  863. }) as any[]
  864. result = result.concat(
  865. temp
  866. .filter((i) => i.deleteFlag)
  867. .map((i) => ({ id: i.id, _delete: null, Material_of_Goods: null })),
  868. )
  869. const emptyBatchRecordList: number[] = []
  870. const emptyUserNotesList: number[] = []
  871. result.forEach((i, index) => {
  872. if (!i.Sample) {
  873. if (!i.Batch_Record) {
  874. emptyBatchRecordList.push(index)
  875. }
  876. }
  877. if (!i.User_Notes) {
  878. emptyUserNotesList.push(index)
  879. }
  880. })
  881. if (emptyBatchRecordList.length) {
  882. ElNotification({
  883. title: '请检查表单',
  884. message: `第 ${emptyBatchRecordList.map((i) => i + 1).join(', ')} 行的Batch Record数据`,
  885. duration: 5000,
  886. })
  887. return
  888. }
  889. if (emptyUserNotesList.length) {
  890. ElNotification({
  891. title: '请检查表单, 备注是必填的',
  892. message: `第 ${emptyUserNotesList
  893. .map((i) => i + 1)
  894. .join(', ')} 行的备注数据, 不能为空`,
  895. duration: 5000,
  896. })
  897. return
  898. }
  899. console.log(result, 'submit result')
  900. loading.value = true
  901. request
  902. .post('/sea_freight/updateSeaFreightData', {
  903. id: newLineTemplate.Parent_Id.id,
  904. Sea_Freight_array: result,
  905. Sales_Person: currentUserName.value,
  906. Sales_Person_Obj: {
  907. id: currentUser.value,
  908. name: currentUserName.value,
  909. },
  910. })
  911. .then((res) => {
  912. if (res.data.code === 1) {
  913. ElNotification({
  914. title: '保存成功',
  915. message: '正在刷新数据',
  916. duration: 3000,
  917. })
  918. getSubList(currentRow.value)
  919. } else {
  920. ElMessage.error('保存出错')
  921. loading.value = false
  922. }
  923. })
  924. .catch((e) => {
  925. console.log(e, 'commit error')
  926. loading.value = false
  927. })
  928. }
  929. let printDrawerVisible = ref(false)
  930. let currentPrintRow = ref({} as any)
  931. const print = (row: any) => {
  932. currentPrintRow.value = row
  933. printDrawerVisible.value = true
  934. }
  935. const exportSubTable = () => {
  936. const headers = [
  937. '箱数 Carton',
  938. '唛头 Marks & Nos',
  939. '货物名称 Description of Goods',
  940. '货物材质 Material goods',
  941. '数量 QTY',
  942. '单价 澳币/AUD Unit Price',
  943. '重量KG Weight',
  944. '体积m³ Cube',
  945. '总离岸价 澳币/AUD Total FOB',
  946. '负责人 Owner',
  947. ]
  948. const data: any[] = subList.value.map((i) => {
  949. return {
  950. '箱数 Carton': i.Carton || '',
  951. '唛头 Marks & Nos': i.Marks_Nos || '',
  952. '货物名称 Description of Goods': i.Description_of_Goods || '',
  953. '货物材质 Material goods': i.Material_of_Goods,
  954. '数量 QTY': i.Quantity || '',
  955. '单价 澳币/AUD Unit Price': i.Unit_Price || '',
  956. '重量KG Weight': i.Weight || '',
  957. '体积m³ Cube': i.Cube || '',
  958. '总离岸价 澳币/AUD Total FOB': i.Total_FOB || '',
  959. '负责人 Owner': i.Requester.name || '',
  960. }
  961. })
  962. if (data.length === 0) {
  963. throw new Error('Invalid data: Data array is empty.')
  964. }
  965. const worksheetData = []
  966. worksheetData.push(['发票/装箱单/INVOICE/PACKING LIST'])
  967. if (headers && headers.length > 0) {
  968. worksheetData.push(headers)
  969. } else {
  970. worksheetData.push(Object.keys(data[0]))
  971. }
  972. data.forEach((row) => {
  973. worksheetData.push(Object.values(row))
  974. })
  975. const wb = XLSX.utils.book_new()
  976. let ws = XLSX.utils.aoa_to_sheet(worksheetData)
  977. ws['!merges'] = [
  978. { s: { r: 0, c: 0 }, e: { r: 0, c: 9 } }, // 合并列作为标题
  979. ]
  980. ws['!cols'] = [
  981. { wpx: 80 },
  982. { wpx: 200 },
  983. { wpx: 180 },
  984. { wpx: 120 },
  985. { wpx: 90 },
  986. { wpx: 120 },
  987. { wpx: 100 },
  988. { wpx: 100 },
  989. { wpx: 120 },
  990. { wpx: 120 },
  991. ]
  992. XLSX.utils.book_append_sheet(wb, ws, 'Sheet1')
  993. XLSX.writeFile(wb, 'test.xlsx')
  994. }
  995. let qcList = ref([] as any[])
  996. let loading2 = ref(false)
  997. const search = (keyword: string) => {
  998. if (keyword.length < 2) return
  999. loading2.value = true
  1000. zoho.CRM.API.searchRecord({
  1001. Entity: 'QC_Record',
  1002. Type: 'criteria',
  1003. Query: `(Reference:in:${keyword})or(Reference:starts_with:${keyword})or(Purchase_Order.name:in:${keyword})or(Purchase_Order.name:starts_with:${keyword})`,
  1004. delay: false,
  1005. })
  1006. .then((res: any) => {
  1007. if (Array.isArray(res.data) && res.data.length) {
  1008. qcList.value = res.data
  1009. } else {
  1010. qcList.value = []
  1011. ElMessage.warning('No Data Found')
  1012. }
  1013. })
  1014. .finally(() => (loading2.value = false))
  1015. }
  1016. const ensure = () => {
  1017. ElMessageBox.confirm('确定要把该记录状态更新为"已确认"吗?', {
  1018. confirmButtonText: '确定',
  1019. cancelButtonText: '取消',
  1020. type: 'warning',
  1021. }).then(() => {
  1022. loading.value = true
  1023. zoho.CRM.API.updateRecord({
  1024. Entity: 'Sea_Freight_Table',
  1025. Trigger: ['workflow'],
  1026. APIData: {
  1027. id: currentRow.value.id,
  1028. Status: '已确认',
  1029. },
  1030. }).then((res: any) => {
  1031. if (
  1032. Array.isArray(res.data) &&
  1033. res.data.length &&
  1034. res.data[0].code === 'SUCCESS'
  1035. ) {
  1036. ElMessage.success('操作成功, 正在刷新数据')
  1037. loading.value = false
  1038. getList()
  1039. clearSubList()
  1040. } else {
  1041. loading.value = false
  1042. ElMessage.error('操作失败, 请稍后再试或者联系管理员')
  1043. }
  1044. })
  1045. })
  1046. }
  1047. // @ts-ignore
  1048. const zoho = window.ZOHO
  1049. zoho.embeddedApp.on('PageLoad', function () {
  1050. zoho.CRM.CONFIG.getCurrentUser().then(function (data: any) {
  1051. if (Array.isArray(data.users) && data.users.length) {
  1052. const user = data.users[0]
  1053. // console.log(user, 'user')
  1054. currentUser.value = user.id
  1055. currentUserName.value = user.full_name || ''
  1056. getList()
  1057. }
  1058. })
  1059. })
  1060. zoho.embeddedApp.init()
  1061. let computedWeight = computed(() => {
  1062. return subList.value.reduce((t, c) => {
  1063. t = t + Number(c.Weight)
  1064. return t
  1065. }, 0)
  1066. })
  1067. let computedCube = computed(() => {
  1068. return subList.value.reduce((t, c) => {
  1069. t = t + Number(c.Cube)
  1070. return t
  1071. }, 0)
  1072. })
  1073. let computedTotalFOB = computed(() => {
  1074. return subList.value.reduce((t, c) => {
  1075. t = t + Number(c.Total_FOB)
  1076. return t
  1077. }, 0)
  1078. })
  1079. // 订单轨迹地图插件
  1080. let mapDrawerVisible = ref(false)
  1081. const openMapDrawer = (row: any) => {
  1082. mapDrawerVisible.value = true
  1083. // console.log(row.Carrier, 'row.value.Carrier')
  1084. // console.log(row.Booking_Number, 'row.value.Booking_Number')
  1085. if ((window as any).Sgld) {
  1086. nextTick(() => {
  1087. ;(window as any).Sgld.createContainerTrack({
  1088. id: 'mapDrawer',
  1089. carrierCode: row.Carrier, // 船公司代码
  1090. billNo: row.Booking_Number, // 提单号
  1091. })
  1092. })
  1093. }
  1094. }
  1095. const closeMapDrawer = () => {
  1096. mapDrawerVisible.value = false
  1097. }
  1098. </script>
  1099. <style lang="scss">
  1100. .page-cargo-consolidation {
  1101. .el-table__body tr.current-row > td.el-table__cell {
  1102. background-color: rgb(56, 163, 238);
  1103. color: #fff;
  1104. }
  1105. }
  1106. </style>
  1107. <style lang="scss" scoped>
  1108. .el-button.custom-button {
  1109. height: 32px;
  1110. line-height: 32px;
  1111. background-image: linear-gradient(
  1112. 171deg,
  1113. rgb(28, 74, 136) 49%,
  1114. rgb(0, 130, 193) 100%
  1115. );
  1116. background-color: rgb(97, 165, 245);
  1117. color: #fff;
  1118. font-size: 13px;
  1119. font-family: Zoho_Puvi_Bold sans-serif;
  1120. border-radius: 6px;
  1121. cursor: pointer;
  1122. &:hover,
  1123. &:active {
  1124. background-image: linear-gradient(
  1125. 171deg,
  1126. rgb(28, 74, 136) 49%,
  1127. rgb(22, 208, 239) 100%
  1128. );
  1129. }
  1130. &.fb {
  1131. font-weight: 900;
  1132. }
  1133. &.small {
  1134. height: 24px;
  1135. line-height: 24px;
  1136. }
  1137. }
  1138. </style>