quotation.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. <template>
  2. <div class="quotation">
  3. <el-dialog v-model="dialogFormVisible" :close-on-click-modal="false" :show-close="false" width="1200" top="40px">
  4. <template #header="{ titleId, titleClass }">
  5. </template>
  6. <div class="max-h-[calc(100vh-140px)] overflow-y-auto">
  7. <div class="content font-size-16px color-[#000] line-height-[1] p-24px">
  8. <div class="flex justify-center flex-col items-center">
  9. <img class="w-[48px] h-[48px]" :src="quotationObj.logo" />
  10. <div class="font-size-[24px]">{{ quotationObj.nameSimple }}</div>
  11. </div>
  12. <div class="m-t-22px">尊敬的客户,您于{{ dayjs(new Date()).format("YYYY年MM月DD日") }}投保方案如下:</div>
  13. <div class="b-t-(1px solid #DDDDDD) b-l-(1px solid #DDDDDD) m-t-[14px]">
  14. <div class=" flex" v-for="(row, index) in descriptionsData" :key="index">
  15. <div v-for="item in row" :key="item.title" class="flex w-full">
  16. <div
  17. class="w-[180px] min-h-[38px] p-[8px_0] h-full flex justify-center items-center b-r-(1px solid #DDDDDD) b-b-(1px solid #DDDDDD)">
  18. {{ item.title }}</div>
  19. <div v-html="item.value"
  20. class="w-[calc(100%-180px)] h-full min-h-[38px] p-[8px] flex items-center b-r-(1px solid #DDDDDD) b-b-(1px solid #DDDDDD) line-height-[1.2]">
  21. </div>
  22. </div>
  23. </div>
  24. </div>
  25. <div class="m-t-[14px]">
  26. <div class="bg-[#EDEEF0] flex ">
  27. <div class="p-[8px] w-[calc(50%-0px)]">险别名称</div>
  28. <div class="p-[8px] w-[calc(50%-160px)]">保额(元)</div>
  29. <div class="p-[8px] w-[160px] text-center">保费(元)</div>
  30. </div>
  31. <div class="b-t-(1px solid #DDDDDD) b-l-(1px solid #DDDDDD)">
  32. <div class="w-full flex" v-for="(row, index) in tableData" :key="index">
  33. <template v-if="!row.total">
  34. <div
  35. class="w-[calc(50%-0px)] h-full min-h-[38px] p-[8px] flex items-center b-r-(1px solid #DDDDDD) b-b-(1px solid #DDDDDD)">
  36. {{ row.title }}</div>
  37. <div
  38. class="w-[calc(50%-160px)] h-full min-h-[38px] p-[8px] flex items-center b-r-(1px solid #DDDDDD) b-b-(1px solid #DDDDDD)">
  39. {{ row.amount }}</div>
  40. <div
  41. class="w-[160px] min-h-[38px] p-[8px_0] h-full flex justify-center items-center b-r-(1px solid #DDDDDD) b-b-(1px solid #DDDDDD)">
  42. {{ row.premium }}</div>
  43. </template>
  44. <template v-if="row.total">
  45. <div
  46. class="w-[calc(100%-160px)] color-[#FF4545] h-full min-h-[38px] p-[8px] flex items-center justify-center b-r-(1px solid #DDDDDD) b-b-(1px solid #DDDDDD)">
  47. {{ row.title }}</div>
  48. <div
  49. class="w-[160px] color-[#FF4545] min-h-[38px] p-[8px_0] h-full flex justify-center items-center b-r-(1px solid #DDDDDD) b-b-(1px solid #DDDDDD)">
  50. {{ row.premium }}</div>
  51. </template>
  52. </div>
  53. </div>
  54. </div>
  55. <div class="flex justify-between m-t-14px">
  56. <div class="w-[calc(100%-200px)]">注意:以上保费为试算保费,仅供参考,右下角扫码查看权益订单,并将您的要求通知我们,我们会尽快与您联系,谢谢!</div>
  57. <div class="flex-col items-center">
  58. <img :src="shareImageUrl" class="" />
  59. <div class="font-size-14px color-[#333] m-t-4px text-center">微信扫码看权益单</div>
  60. </div>
  61. </div>
  62. </div>
  63. </div>
  64. <template #footer>
  65. <div class="dialog-footer">
  66. <el-button type="primary" @click="savePoster()">下载报价单</el-button>
  67. <el-button @click="dialogFormVisible = false">
  68. 取消
  69. </el-button>
  70. </div>
  71. </template>
  72. </el-dialog>
  73. </div>
  74. </template>
  75. <script setup lang="ts">
  76. import html2canvas from 'html2canvas';
  77. import QRCode from 'qrcode'
  78. import dayjs from "dayjs";
  79. const props = defineProps<{
  80. quotationObj: object,
  81. insurancePolicyForm: object
  82. }>();
  83. const descriptionsData = computed(() => {
  84. return [
  85. [
  86. { title: '投保人姓名/名称', value: props.insurancePolicyForm.name2, },
  87. { title: '车主姓名/名称', value: props.insurancePolicyForm.name1, },
  88. ], [
  89. { title: '被保人姓名/名称', value: props.insurancePolicyForm.name3 },
  90. { title: '车牌号', value: props.insurancePolicyForm.licenseNo },
  91. ], [
  92. { title: '车型名称', value: props.insurancePolicyForm.brandName },
  93. ], [
  94. { title: '新车购置价(元)', value: props.insurancePolicyForm.purchasePrice },
  95. { title: '座位数', value: props.insurancePolicyForm.seatCount },
  96. ], [
  97. { title: '发动机号、VIN码', value: props.insurancePolicyForm.engineNo + '、' + props.insurancePolicyForm.vinNo },
  98. { title: '初登日期', value: props.insurancePolicyForm.registerDate },
  99. ], [
  100. { title: '使用性质', value: props.insurancePolicyForm.vehicleUseName },
  101. {
  102. title: '承保年数、出险次数', value: `商业险:期间内共出险${props.quotationObj.quoteResult?.scoreVo?.biClaims || 0}次<br>
  103. 交强险:期间内共出险${props.quotationObj.quoteResult?.scoreVo?.ciClaims || 0}次`
  104. },
  105. ], [
  106. { title: '商业险保险期限', value: props.insurancePolicyForm.SYStartDate + '至' + props.insurancePolicyForm.SYEndDate },
  107. { title: '交强险保险期限', value: props.insurancePolicyForm.JQStartDate + '至' + props.insurancePolicyForm.JQEndDate },
  108. ],
  109. ]
  110. })
  111. const tableData = computed(() => {
  112. let arr = []
  113. const quoteResult = props.quotationObj.quoteResult
  114. if (quoteResult.kindInfoVo) {
  115. const kindArr = quoteResult.kindInfoVo.filter(v => v.kindCode !== "J1")
  116. for (let i = 0; i < kindArr.length; i++) {
  117. const element = kindArr[i];
  118. const obj = {
  119. title: element.kindName,
  120. amount: element.kindCode === 'D4' ? element.amount + '*' + (props.insurancePolicyForm.seatCount - 1) : element.amount,
  121. premium: element.premium,
  122. }
  123. arr.push(obj)
  124. }
  125. }
  126. if (quoteResult?.costs?.biPremium) arr.push({ total: true, title: '商业险合计保费', premium: quoteResult.costs.biPremium })
  127. if (quoteResult?.costs?.ciPremium) arr.push({ total: true, title: '机动车交通事故责任强制保险', premium: quoteResult.costs.ciPremium })
  128. if (quoteResult?.costs?.vvTax) arr.push({ total: true, title: '车船税', premium: quoteResult.costs.vvTax })
  129. let product = props.quotationObj.projectList?.find((v, i) => props.quotationObj.productIndex === i)
  130. const drivings = product?.drivings ?? props.quotationObj.insurancePlanInfo?.drivings ?? []
  131. if (drivings.length > 0) {
  132. for (let i = 0; i < drivings.length; i++) {
  133. const element = drivings[i];
  134. const obj = {
  135. title: element.productName,
  136. amount: element.quantity + '份',
  137. premium: element.premium,
  138. }
  139. arr.push(obj)
  140. }
  141. }
  142. arr.push({ total: true, title: '保费合计(不含车船税)', premium: quoteResult.costs.totalAmount })
  143. arr.push({ total: true, title: '保费合计(含车船税)', premium: quoteResult.costs.sumPremium })
  144. return arr
  145. })
  146. const dialogFormVisible = ref(false)
  147. const shareImageUrl = ref('')
  148. const show = async (obj) => {
  149. dialogFormVisible.value = true
  150. shareImageUrl.value = await QRCode.toDataURL(`https://tennat.baoxianzhanggui.com/test_h5/#/pages/quote/quoteRecordDetail?ordersNo=${obj.quoteResult?.ordersNo}&from=share`, {
  151. width: 160,
  152. margin: 2
  153. });
  154. }
  155. const savePoster = () => {
  156. html2canvas(document.querySelector(".content"), { useCORS: true }).then((canvas) => {
  157. downloadBase64(canvas.toDataURL(), props.insurancePolicyForm.licenseNo + "报价单");
  158. });
  159. };
  160. const downloadBase64 = (dataUrl, fileName = 'download', fileExtension = "png") => {
  161. const byteCharacters = atob(dataUrl.split(',')[1]);
  162. const byteArrays = [];
  163. for (let offset = 0; offset < byteCharacters.length; offset += 10240) {
  164. const slice = byteCharacters.slice(offset, offset + 10240);
  165. const byteNumbers = new Array(slice.length);
  166. for (let i = 0; i < slice.length; i++) {
  167. byteNumbers[i] = slice.charCodeAt(i);
  168. }
  169. const byteArray = new Uint8Array(byteNumbers);
  170. byteArrays.push(byteArray);
  171. }
  172. const blob = new Blob(byteArrays, { type: 'application/octet-stream' });
  173. const blobUrl = URL.createObjectURL(blob);
  174. const creatDom = document.createElement('a');
  175. document.body.appendChild(creatDom);
  176. creatDom.href = blobUrl;
  177. creatDom.download = `${fileName}.${fileExtension}`;
  178. creatDom.click();
  179. document.body.removeChild(creatDom);
  180. URL.revokeObjectURL(blobUrl);
  181. };
  182. defineExpose({
  183. show,
  184. })
  185. </script>
  186. <style scoped lang="scss">
  187. :deep(.el-dialog__header) {
  188. padding: unset;
  189. border-bottom: unset;
  190. }
  191. :deep(.el-dialog__body) {
  192. padding: unset;
  193. max-height: unset !important;
  194. }
  195. </style>