index.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. <template>
  2. <div class="tenant">
  3. <div class="content">
  4. <byTable
  5. :source="sourceList.data"
  6. :pagination="sourceList.pagination"
  7. :config="config"
  8. :loading="loading"
  9. :selectConfig="selectConfig"
  10. highlight-current-row
  11. :action-list="[
  12. {
  13. text: '新建报价单',
  14. action: () => newPriceSheet(),
  15. },
  16. ]"
  17. @get-list="getList"
  18. >
  19. <template #code="{ item }">
  20. <div style="width: 100%">
  21. <span
  22. style="color: #409eff; cursor: pointer; word-break: break-all"
  23. @click="clickCode(item)"
  24. >{{ item.code }}</span
  25. >
  26. </div>
  27. </template>
  28. <template #amount="{ item }">
  29. <div>
  30. <span style="padding-right: 4px">{{ item.currency }}</span>
  31. <span>{{ moneyFormat(item.amount, 2) }}</span>
  32. </div>
  33. </template>
  34. <template #advanceRatio="{ item }">
  35. <div>
  36. <span>{{ item.advanceRatio }}%</span>
  37. </div>
  38. </template>
  39. </byTable>
  40. </div>
  41. <el-dialog title="打印" v-if="openPrint" v-model="openPrint" width="860">
  42. <div
  43. id="pdfDom"
  44. style="width: 800px; padding: 16px; font-size: 12px !important"
  45. >
  46. <div style="font-size: 18px; text-align: center">
  47. {{
  48. getLabel(printDetails.sellCorporationId, corporationList, "nameEn")
  49. }}
  50. </div>
  51. <div style="text-align: center">
  52. {{ printDetails.sellCountryName }},{{
  53. printDetails.sellProvinceName
  54. }},{{ printDetails.sellCityName }},{{ printDetails.sellAddress }}
  55. </div>
  56. <div
  57. style="
  58. font-size: 14px;
  59. color: #409eff;
  60. text-align: center;
  61. padding-top: 16px;
  62. "
  63. >
  64. QUOTATION
  65. </div>
  66. <div style="padding-top: 8px">
  67. <div>Reference NO. : {{ printDetails.code }}</div>
  68. <div style="display: flex">
  69. <div style="width: 50%">
  70. DATE: {{ moment(printDetails.createTime).format("DD/MMM/yyyy") }}
  71. </div>
  72. <div style="width: 50%">Valid Date:</div>
  73. </div>
  74. </div>
  75. <div style="border: 1px solid black; display: flex">
  76. <div style="width: 50%; border-right: 1px solid black">
  77. <div style="color: #409eff">VENDOR:</div>
  78. <div>
  79. {{
  80. getLabel(
  81. printDetails.sellCorporationId,
  82. corporationList,
  83. "nameEn"
  84. )
  85. }}
  86. </div>
  87. <div style="padding: 16px 0">
  88. {{ printDetails.sellCountryName }},{{
  89. printDetails.sellProvinceName
  90. }},{{ printDetails.sellCityName }},{{ printDetails.sellAddress }}
  91. </div>
  92. <div>
  93. {{ printDetails.sellContactName }},{{
  94. printDetails.sellContactNumber
  95. }}
  96. </div>
  97. </div>
  98. <div style="width: 50%">
  99. <div style="color: #409eff">BUYER:</div>
  100. <div>
  101. {{
  102. getLabel(printDetails.buyCorporationId, customerList, "name")
  103. }}
  104. </div>
  105. <div style="padding: 16px 0">
  106. {{ printDetails.buyCountryName }},{{
  107. printDetails.buyProvinceName
  108. }},{{ printDetails.buyCityName }},{{ printDetails.buyAddress }}
  109. </div>
  110. <div>
  111. {{ printDetails.buyContactName }},{{
  112. printDetails.buyContactNumber
  113. }}
  114. </div>
  115. </div>
  116. </div>
  117. <div style="height: 16px"></div>
  118. <div style="border: 1px solid black">
  119. <div style="display: flex; width: 100%">
  120. <div
  121. style="
  122. width: 33%;
  123. border-bottom: 1px solid black;
  124. border-right: 1px solid black;
  125. "
  126. >
  127. <div style="color: #409eff">COUNTRY OF ORIGIN:</div>
  128. <div>{{ printDetails.sellCountryName }}</div>
  129. </div>
  130. <div
  131. style="
  132. width: 34%;
  133. border-bottom: 1px solid black;
  134. border-right: 1px solid black;
  135. "
  136. >
  137. <div style="color: #409eff">COUNTRY OF DESTINATION:</div>
  138. <div>{{ printDetails.buyCountryName }}</div>
  139. </div>
  140. <div style="width: 33%; border-bottom: 1px solid black">
  141. <div style="color: #409eff">PLACE OF DISCHARGE:</div>
  142. <div>{{ printDetails.transportRemark }}</div>
  143. </div>
  144. </div>
  145. <div style="display: flex; width: 100%">
  146. <div
  147. style="
  148. width: 33%;
  149. border-bottom: 1px solid black;
  150. border-right: 1px solid black;
  151. "
  152. >
  153. <div style="color: #409eff">TERMS OF DELIVERY:</div>
  154. <div>
  155. {{ dictValueLabel(printDetails.tradeMethods, tradeMethods) }}
  156. </div>
  157. </div>
  158. <div
  159. style="
  160. width: 34%;
  161. border-bottom: 1px solid black;
  162. border-right: 1px solid black;
  163. "
  164. >
  165. <div style="color: #409eff">CURRENCY:</div>
  166. <div>
  167. {{ dictValueLabel(printDetails.currency, accountCurrency) }}
  168. </div>
  169. </div>
  170. <div style="width: 33%; border-bottom: 1px solid black">
  171. <div style="color: #409eff">EXPORT BY/VIA:</div>
  172. <div>
  173. {{
  174. dictValueLabel(printDetails.transportMethod, shippingMethod)
  175. }}
  176. </div>
  177. </div>
  178. </div>
  179. <div style="display: flex; width: 100%">
  180. <div style="width: 33%; border-right: 1px solid black">
  181. <div style="color: #409eff">DELIVERY TIME:</div>
  182. </div>
  183. <div style="width: 67%">
  184. <div style="color: #409eff">TERMS OF PAYMENT:</div>
  185. <div>{{ printDetails.remark }}</div>
  186. </div>
  187. </div>
  188. </div>
  189. <div style="height: 16px"></div>
  190. <div class="baseRow" style="display: flex; color: #409eff">
  191. <div class="contentRow" style="width: 50px; text-align: center">
  192. NO.
  193. </div>
  194. <div
  195. class="contentRow"
  196. style="width: calc(100% - 450px); text-align: center"
  197. >
  198. COMMODITY, SPECIFICATION
  199. </div>
  200. <div class="contentRow" style="width: 100px; text-align: center">
  201. UNIT
  202. </div>
  203. <div class="contentRow" style="width: 100px; text-align: center">
  204. QUANTITY
  205. </div>
  206. <div class="contentRow" style="width: 100px; text-align: center">
  207. UNIT PRICE
  208. </div>
  209. <div class="contentRow" style="width: 100px; text-align: center">
  210. TOTAL PRICE
  211. </div>
  212. </div>
  213. <div
  214. v-if="
  215. printDetails.quotationProductList &&
  216. printDetails.quotationProductList.length > 0
  217. "
  218. >
  219. <div
  220. class="baseRow"
  221. style="display: flex"
  222. v-for="(item, index) in printDetails.quotationProductList"
  223. :key="item.productId"
  224. >
  225. <div class="contentRow" style="width: 50px; text-align: center">
  226. {{ index + 1 }}
  227. </div>
  228. <div
  229. class="contentRow"
  230. style="width: calc(100% - 450px); text-align: center"
  231. >
  232. {{ item.productName }}
  233. </div>
  234. <div class="contentRow" style="width: 100px; text-align: center">
  235. {{ item.productUnit }}
  236. </div>
  237. <div class="contentRow" style="width: 100px; text-align: center">
  238. {{ item.quantity }}
  239. </div>
  240. <div class="contentRow" style="width: 100px; text-align: center">
  241. {{ item.price }}
  242. </div>
  243. <div class="contentRow" style="width: 100px; text-align: center">
  244. {{ item.amount }}
  245. </div>
  246. </div>
  247. </div>
  248. <div class="baseRow" style="display: flex; color: #409eff">
  249. <div
  250. class="contentRow"
  251. style="width: calc(100% - 400px); text-align: center"
  252. >
  253. SUBTOTAL:
  254. </div>
  255. <div
  256. class="contentRow"
  257. style="width: 100px; text-align: center"
  258. ></div>
  259. <div class="contentRow" style="width: 100px; text-align: center">
  260. {{ statistics("quantity", 0) }}
  261. </div>
  262. <div
  263. class="contentRow"
  264. style="width: 100px; text-align: center"
  265. ></div>
  266. <div class="contentRow" style="width: 100px; text-align: center">
  267. {{ statistics("amount", 2) }}
  268. </div>
  269. </div>
  270. <div
  271. v-if="
  272. printDetails.quotationPayList &&
  273. printDetails.quotationPayList.length > 0
  274. "
  275. >
  276. <div
  277. class="baseRow"
  278. style="display: flex"
  279. v-for="(item, index) in printDetails.quotationPayList"
  280. :key="index"
  281. >
  282. <div
  283. class="contentRow"
  284. style="
  285. width: calc(100% - 100px);
  286. text-align: right;
  287. color: #409eff;
  288. "
  289. >
  290. {{ item.payName }}:
  291. </div>
  292. <div class="contentRow" style="width: 100px; text-align: center">
  293. {{ item.amount }}
  294. </div>
  295. </div>
  296. </div>
  297. <div class="baseRow" style="display: flex">
  298. <div
  299. class="contentRow"
  300. style="width: calc(100% - 100px); text-align: right; color: #409eff"
  301. >
  302. TOTAL PRICE:
  303. </div>
  304. <div class="contentRow" style="width: 100px; text-align: center">
  305. {{ getAllMoney(statistics("amount", 2)) }}
  306. </div>
  307. </div>
  308. <div
  309. class="baseRow"
  310. style="display: flex; border-bottom: 1px solid black"
  311. >
  312. <div class="contentRow" style="width: 100%">
  313. {{
  314. translateIntoEnglish(printDetails.amount, printDetails.currency)
  315. }}
  316. </div>
  317. </div>
  318. <!-- <div style="height: 16px"></div>
  319. <div class="baseRow" style="color: #409eff">
  320. <div class="contentRow" style="width: 100%">ACCOUNT INFORMATION:</div>
  321. </div>
  322. <div class="baseRow" style="border-bottom: 1px solid black">
  323. <div class="contentRow" style="width: 100%">
  324. <div style="line-height: 24px; padding-left: 4px; word-break: break-all; word-wrap: break-word">
  325. Beneficiary Name: {{ printDetails.beneficiaryName }}
  326. </div>
  327. <div style="line-height: 24px; padding-left: 4px; word-break: break-all; word-wrap: break-word">
  328. Beneficiary Bank: {{ printDetails.beneficiaryBank }}
  329. </div>
  330. <div style="line-height: 24px; padding-left: 4px; word-break: break-all; word-wrap: break-word">
  331. Beneficiary Bank Address: {{ printDetails.beneficiaryBankAddress }}
  332. </div>
  333. <div style="line-height: 24px; padding-left: 4px; word-break: break-all; word-wrap: break-word">
  334. Beneficiary Account Number: {{ printDetails.beneficiaryAccountNumber }}
  335. </div>
  336. <div style="line-height: 24px; padding-left: 4px; word-break: break-all; word-wrap: break-word">Swift Code: {{ printDetails.swiftCode }}</div>
  337. <div style="line-height: 24px; padding-left: 4px; word-break: break-all; word-wrap: break-word">
  338. Beneficiary Address: {{ printDetails.beneficiaryAddress }}
  339. </div>
  340. </div>
  341. </div> -->
  342. <div style="height: 32px"></div>
  343. <div style="display: flex">
  344. <div style="width: 50%">
  345. <div style="color: #409eff">CONFIRMED BY VENDOR:</div>
  346. <div>
  347. {{
  348. getLabel(
  349. printDetails.sellCorporationId,
  350. corporationList,
  351. "nameEn"
  352. )
  353. }}
  354. </div>
  355. </div>
  356. <div style="width: 50%">
  357. <div style="color: #409eff">CONFIRMED BY BUYER:</div>
  358. <div>
  359. {{
  360. getLabel(printDetails.buyCorporationId, customerList, "name")
  361. }}
  362. </div>
  363. </div>
  364. </div>
  365. </div>
  366. <template #footer>
  367. <el-button @click="openPrint = false" size="large">取消</el-button>
  368. <el-button type="primary" @click="clickDownload()" size="large"
  369. >下载PDF</el-button
  370. >
  371. </template>
  372. </el-dialog>
  373. </div>
  374. </template>
  375. <script setup>
  376. import { computed, ref } from "vue";
  377. import byTable from "@/components/byTable/index";
  378. import moment from "moment";
  379. const { proxy } = getCurrentInstance();
  380. const accountList = ref([]);
  381. const corporationList = ref([]);
  382. const customerList = ref([]);
  383. const tradeMethods = ref([]);
  384. const accountCurrency = ref([]);
  385. const shippingMethod = ref([]);
  386. const status = ref([
  387. {
  388. label: "草稿",
  389. value: 0,
  390. },
  391. {
  392. label: "审批中",
  393. value: 10,
  394. },
  395. {
  396. label: "驳回",
  397. value: 20,
  398. },
  399. {
  400. label: "审批通过",
  401. value: 30,
  402. },
  403. {
  404. label: "终止",
  405. value: 99,
  406. },
  407. ]);
  408. const sourceList = ref({
  409. data: [],
  410. pagination: {
  411. total: 0,
  412. pageNum: 1,
  413. pageSize: 10,
  414. keyword: "",
  415. status: "",
  416. sellCorporationId: "",
  417. },
  418. });
  419. const loading = ref(false);
  420. const selectConfig = computed(() => {
  421. return [
  422. {
  423. label: "审批状态",
  424. prop: "status",
  425. data: status.value,
  426. },
  427. {
  428. label: "归属公司",
  429. prop: "sellCorporationId",
  430. data: corporationList.value,
  431. },
  432. ];
  433. });
  434. const config = computed(() => {
  435. return [
  436. {
  437. attrs: {
  438. label: "报价单号",
  439. slot: "code",
  440. width: 200,
  441. },
  442. },
  443. {
  444. attrs: {
  445. label: "归属公司",
  446. prop: "sellCorporationId",
  447. "min-width": 220,
  448. },
  449. render(type) {
  450. return proxy.dictValueLabel(type, corporationList.value);
  451. },
  452. },
  453. {
  454. attrs: {
  455. label: "报价人",
  456. prop: "userName",
  457. width: 160,
  458. },
  459. },
  460. {
  461. attrs: {
  462. label: "报价时间",
  463. prop: "createTime",
  464. width: 160,
  465. },
  466. },
  467. {
  468. attrs: {
  469. label: "客户名称",
  470. prop: "buyCorporationId",
  471. "min-width": 220,
  472. },
  473. render(type) {
  474. return proxy.dictValueLabel(type, customerList.value);
  475. },
  476. },
  477. {
  478. attrs: {
  479. label: "报价金额",
  480. slot: "amount",
  481. width: 140,
  482. },
  483. },
  484. {
  485. attrs: {
  486. label: "预付比例",
  487. slot: "advanceRatio",
  488. width: 120,
  489. },
  490. },
  491. {
  492. attrs: {
  493. label: "审批状态",
  494. prop: "status",
  495. width: 140,
  496. },
  497. render(type) {
  498. return proxy.dictValueLabel(type, status.value);
  499. },
  500. },
  501. {
  502. attrs: {
  503. label: "操作",
  504. width: 120,
  505. align: "center",
  506. fixed: "right",
  507. },
  508. renderHTML(row) {
  509. return [
  510. {
  511. attrs: {
  512. label: "复制",
  513. type: "primary",
  514. text: true,
  515. },
  516. el: "button",
  517. click() {
  518. clickCopy(row);
  519. },
  520. },
  521. {
  522. attrs: {
  523. label: "打印",
  524. type: "primary",
  525. text: true,
  526. },
  527. el: "button",
  528. click() {
  529. clickPrint(row);
  530. },
  531. },
  532. ];
  533. },
  534. },
  535. ];
  536. });
  537. const getDict = () => {
  538. proxy
  539. .post("/saleQuotation/page", { pageNum: 1, pageSize: 999 })
  540. .then((res) => {
  541. accountList.value = res.rows.map((item) => {
  542. return {
  543. label: item.alias,
  544. value: item.id,
  545. };
  546. });
  547. });
  548. proxy.post("/corporation/page", { pageNum: 1, pageSize: 999 }).then((res) => {
  549. corporationList.value = res.rows.map((item) => {
  550. return {
  551. ...item,
  552. label: item.name,
  553. value: item.id,
  554. };
  555. });
  556. });
  557. proxy.post("/customer/page", { pageNum: 1, pageSize: 999 }).then((res) => {
  558. customerList.value = res.rows.map((item) => {
  559. return {
  560. ...item,
  561. label: item.name,
  562. value: item.id,
  563. };
  564. });
  565. });
  566. proxy
  567. .getDictOne(["trade_mode", "account_currency", "shipping_method"])
  568. .then((res) => {
  569. tradeMethods.value = res["trade_mode"].map((x) => ({
  570. label: x.dictValue,
  571. value: x.dictKey,
  572. }));
  573. accountCurrency.value = res["account_currency"].map((x) => ({
  574. label: x.dictValue,
  575. value: x.dictKey,
  576. }));
  577. shippingMethod.value = res["shipping_method"].map((x) => ({
  578. label: x.dictValue,
  579. value: x.dictKey,
  580. }));
  581. });
  582. };
  583. const getList = async (req) => {
  584. sourceList.value.pagination = { ...sourceList.value.pagination, ...req };
  585. loading.value = true;
  586. proxy.post("/saleQuotation/page", sourceList.value.pagination).then((res) => {
  587. sourceList.value.data = res.rows;
  588. sourceList.value.pagination.total = res.total;
  589. setTimeout(() => {
  590. loading.value = false;
  591. }, 200);
  592. });
  593. };
  594. getDict();
  595. getList();
  596. const newPriceSheet = () => {
  597. proxy.$router.replace({
  598. path: "/platform_manage/process/processApproval",
  599. query: {
  600. flowKey: "sale_quotation_flow",
  601. flowName: "报价审批流程",
  602. random: proxy.random(),
  603. tenantType: "EHSD",
  604. },
  605. });
  606. };
  607. const clickCode = (row) => {
  608. proxy.$router.push({
  609. path: "/platform_manage/process/processApproval",
  610. query: {
  611. priceSheetId: row.id,
  612. flowKey: "sale_quotation_flow",
  613. flowName: "报价单详情",
  614. random: proxy.random(),
  615. tenantType: "EHSD",
  616. processType: 20,
  617. },
  618. });
  619. };
  620. const clickCopy = (item) => {
  621. proxy.$router.replace({
  622. path: "/platform_manage/process/processApproval",
  623. query: {
  624. flowKey: "sale_quotation_flow",
  625. flowName: "报价审批流程",
  626. priceSheetId: item.id,
  627. random: proxy.random(),
  628. tenantType: "EHSD",
  629. },
  630. });
  631. };
  632. const openPrint = ref(false);
  633. const printDetails = ref({});
  634. const clickPrint = (row) => {
  635. printDetails.value = {};
  636. openPrint.value = true;
  637. proxy.post("/saleQuotation/detail", { id: row.id }).then((res) => {
  638. printDetails.value = res;
  639. });
  640. };
  641. const clickDownload = () => {
  642. proxy.getPdf("报价单PDF文件");
  643. };
  644. const statistics = (label, index) => {
  645. let num = 0;
  646. if (
  647. printDetails.value.quotationProductList &&
  648. printDetails.value.quotationProductList.length > 0
  649. ) {
  650. printDetails.value.quotationProductList.map((item) => {
  651. if (item[label]) {
  652. num = parseFloat(Number(num) + Number(item[label])).toFixed(index);
  653. }
  654. });
  655. }
  656. return num;
  657. };
  658. const getLabel = (key, list, label) => {
  659. let text = "";
  660. if (list && list.length > 0) {
  661. let data = list.filter((item) => item.id === key);
  662. if (data && data.length > 0) {
  663. text = data[0][label];
  664. }
  665. }
  666. return text;
  667. };
  668. const getAllMoney = (num) => {
  669. let money = num;
  670. if (
  671. printDetails.value.quotationPayList &&
  672. printDetails.value.quotationPayList.length > 0
  673. ) {
  674. printDetails.value.quotationPayList.map((item) => {
  675. if (item.amount) {
  676. money = parseFloat(Number(money) + Number(item.amount)).toFixed(2);
  677. }
  678. });
  679. }
  680. return money;
  681. };
  682. </script>
  683. <style lang="scss" scoped>
  684. .tenant {
  685. padding: 20px;
  686. }
  687. ::v-deep(.el-input-number .el-input__inner) {
  688. text-align: left;
  689. }
  690. .baseRow {
  691. min-height: 24px;
  692. border-top: 1px solid black;
  693. border-left: 1px solid black;
  694. }
  695. .contentRow {
  696. border-right: 1px solid black;
  697. line-height: 24px;
  698. padding-left: 4px;
  699. }
  700. </style>