quotation.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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)] min-h-500px overflow-y-auto" v-loading="dialogFormLoading">
  7. <div class="content font-size-16px color-[#000] line-height-[1] p-24px" v-if="!dialogFormLoading">
  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 }}
  37. <div class="color-[#666] m-l-20px" v-if="row.jyxBlames">
  38. <div v-for="blame in row.jyxBlames" :key="blame.id">{{ blame.blame }}*{{ blame.blamePremium }}</div>
  39. </div>
  40. </div>
  41. <div
  42. class="w-[calc(50%-160px)] min-h-[38px] p-[8px] flex items-center b-r-(1px solid #DDDDDD) b-b-(1px solid #DDDDDD)">
  43. {{ row.amount }}</div>
  44. <div
  45. class="w-[160px] min-h-[38px] p-[8px_0] flex justify-center items-center b-r-(1px solid #DDDDDD) b-b-(1px solid #DDDDDD)">
  46. {{ row.premium }}</div>
  47. </template>
  48. <template v-if="row.total">
  49. <div
  50. 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)">
  51. {{ row.title }}</div>
  52. <div
  53. 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)">
  54. {{ row.premium }}</div>
  55. </template>
  56. </div>
  57. </div>
  58. </div>
  59. <div class="flex justify-between m-t-14px">
  60. <div class="w-[calc(100%-200px)]">注意:以上保费为试算保费,仅供参考,右下角扫码查看权益订单,并将您的要求通知我们,我们会尽快与您联系,谢谢!</div>
  61. <div class="flex-col items-center">
  62. <img :src="shareImageUrl" class="" />
  63. <div class="font-size-14px color-[#333] m-t-4px text-center">微信扫码看权益单</div>
  64. </div>
  65. </div>
  66. </div>
  67. </div>
  68. <template #footer>
  69. <div class="dialog-footer">
  70. <el-button type="primary" @click="savePoster()">下载报价单</el-button>
  71. <el-button @click="dialogFormVisible = false">
  72. 取消
  73. </el-button>
  74. </div>
  75. </template>
  76. </el-dialog>
  77. </div>
  78. </template>
  79. <script setup lang="ts">
  80. import api from '@/api';
  81. import html2canvas from 'html2canvas';
  82. import QRCode from 'qrcode'
  83. import dayjs from "dayjs";
  84. const props = defineProps<{
  85. quotationObj: object,
  86. insurancePolicyForm: object
  87. }>();
  88. const quotationObj=ref({})
  89. const insurancePolicyForm=ref({})
  90. const descriptionsData = computed(() => {
  91. let arr= [
  92. [
  93. { title: '投保人姓名/名称', value: insurancePolicyForm.value.name2, },
  94. { title: '车主姓名/名称', value: insurancePolicyForm.value.name1, },
  95. ], [
  96. { title: '被保人姓名/名称', value: insurancePolicyForm.value.name3 },
  97. { title: '车牌号', value: insurancePolicyForm.value.licenseNo },
  98. ], [
  99. { title: '车型名称', value: insurancePolicyForm.value.brandName },
  100. ], [
  101. { title: '新车购置价(元)', value: insurancePolicyForm.value.purchasePrice },
  102. { title: '座位数', value: insurancePolicyForm.value.seatCount },
  103. ], [
  104. { title: '发动机号、VIN码', value: insurancePolicyForm.value.engineNo + '、' + insurancePolicyForm.value.vinNo },
  105. { title: '初登日期', value: insurancePolicyForm.value.registerDate },
  106. ], [
  107. { title: '使用性质', value: insurancePolicyForm.value.vehicleUseName },
  108. {
  109. title: '承保年数、出险次数', value: `商业险:期间内共出险${quotationObj.value.quoteResult?.scoreVo?.biClaims || 0}次<br>
  110. 交强险:期间内共出险${quotationObj.value.quoteResult?.scoreVo?.ciClaims || 0}次`
  111. },
  112. ],
  113. ]
  114. let list=[]
  115. if(insurancePolicyForm.value.SYStartDate){
  116. list.push({ title: '商业险保险期限', value: insurancePolicyForm.value.SYStartDate + '至' + insurancePolicyForm.value.SYEndDate })
  117. }
  118. if(insurancePolicyForm.value.JQStartDate){
  119. list.push({ title: '交强险保险期限', value: insurancePolicyForm.value.JQStartDate + '至' + insurancePolicyForm.value.JQEndDate })
  120. }
  121. arr.push(list)
  122. return arr
  123. })
  124. const tableData = computed(() => {
  125. let arr = []
  126. const quoteResult = quotationObj.value.quoteResult
  127. if (quoteResult.kindInfoVo) {
  128. const kindArr = quoteResult.kindInfoVo.filter(v => v.kindCode !== "J1")
  129. for (let i = 0; i < kindArr.length; i++) {
  130. const element = kindArr[i];
  131. const obj = {
  132. title: element.kindName,
  133. amount: element.kindCode === 'D4' ? element.amount + '*' + (insurancePolicyForm.value.seatCount - 1) : element.amount,
  134. premium: element.premium,
  135. }
  136. arr.push(obj)
  137. }
  138. }
  139. if (quoteResult?.costs?.biPremium) arr.push({ total: true, title: '商业险合计保费', premium: quoteResult.costs.biPremium })
  140. if (quoteResult?.costs?.ciPremium) arr.push({ total: true, title: '机动车交通事故责任强制保险', premium: quoteResult.costs.ciPremium })
  141. if (quoteResult?.costs?.vvTax) arr.push({ total: true, title: '车船税', premium: quoteResult.costs.vvTax })
  142. const drivings = quotationObj.value.quoteResult?.accidentalDrivings ?? []
  143. if (drivings.length > 0) {
  144. for (let i = 0; i < drivings.length; i++) {
  145. const element = drivings[i];
  146. const obj = {
  147. title: element.productName,
  148. amount: element.quantity + '份',
  149. premium: element.premium,
  150. jyxBlames: element.jyxBlames,
  151. }
  152. arr.push(obj)
  153. }
  154. }
  155. arr.push({ total: true, title: '保费合计(不含车船税)', premium: quoteResult.costs.totalAmount })
  156. arr.push({ total: true, title: '保费合计(含车船税)', premium: quoteResult.costs.sumPremium })
  157. return arr
  158. })
  159. const dialogFormVisible = ref(false)
  160. const dialogFormLoading = ref(false)
  161. let optionObj = ref({
  162. vehicle_use_code: [], // 车辆用途
  163. })
  164. const initOption = () => {
  165. api.additionalApi.dictDetails('vehicle_use_code').then((res: any) => {
  166. if (res.code == 200) {
  167. if (res.data) {
  168. optionObj.value['vehicle_use_code'] = res.data
  169. }
  170. }
  171. })
  172. }
  173. initOption()
  174. const shareImageUrl = ref('')
  175. const show = async (orderNo) => {
  176. dialogFormLoading.value = true
  177. dialogFormVisible.value = true
  178. const { data } = await api.insurancePolicyApi.getOrderDetail(orderNo)
  179. dialogFormLoading.value = false
  180. quotationObj.value = {
  181. ...data,
  182. logo: data.insCompanyVo[0]?.logo,
  183. nameSimple: data.insCompanyVo[0]?.nameSimple,
  184. insurancePlanInfo: { drivings: data.accidentalDrivings },
  185. quoteResult: data
  186. }
  187. const { carInfoVo, ownersInfo, policyHolderInfo, insuredPersonInfo, biRiskInfoVo, ciRiskInfoVo } = data
  188. insurancePolicyForm.value = {
  189. ...data,
  190. name1: ownersInfo?.name,
  191. name2: policyHolderInfo?.name,
  192. name3: insuredPersonInfo?.name,
  193. ...carInfoVo,
  194. SYStartDate: biRiskInfoVo?.startDate,
  195. SYEndDate: biRiskInfoVo?.endDate,
  196. JQStartDate: ciRiskInfoVo?.startDate,
  197. JQEndDate: ciRiskInfoVo?.endDate,
  198. }
  199. insurancePolicyForm.value.vehicleUseName = optionObj.value.vehicle_use_code.find(a => a.value === insurancePolicyForm.value.vehicleUseCode)?.label
  200. shareImageUrl.value = await QRCode.toDataURL(`https://tennat.baoxianzhanggui.com/test_h5/#/pages/quote/quoteRecordDetail?ordersNo=${orderNo}&from=share`, {
  201. width: 160,
  202. margin: 2
  203. });
  204. }
  205. const savePoster = () => {
  206. html2canvas(document.querySelector(".content"), { useCORS: true }).then((canvas) => {
  207. downloadBase64(canvas.toDataURL(), insurancePolicyForm.value.licenseNo + "报价单");
  208. });
  209. };
  210. const downloadBase64 = (dataUrl, fileName = 'download', fileExtension = "png") => {
  211. const byteCharacters = atob(dataUrl.split(',')[1]);
  212. const byteArrays = [];
  213. for (let offset = 0; offset < byteCharacters.length; offset += 10240) {
  214. const slice = byteCharacters.slice(offset, offset + 10240);
  215. const byteNumbers = new Array(slice.length);
  216. for (let i = 0; i < slice.length; i++) {
  217. byteNumbers[i] = slice.charCodeAt(i);
  218. }
  219. const byteArray = new Uint8Array(byteNumbers);
  220. byteArrays.push(byteArray);
  221. }
  222. const blob = new Blob(byteArrays, { type: 'application/octet-stream' });
  223. const blobUrl = URL.createObjectURL(blob);
  224. const creatDom = document.createElement('a');
  225. document.body.appendChild(creatDom);
  226. creatDom.href = blobUrl;
  227. creatDom.download = `${fileName}.${fileExtension}`;
  228. creatDom.click();
  229. document.body.removeChild(creatDom);
  230. URL.revokeObjectURL(blobUrl);
  231. };
  232. defineExpose({
  233. show,
  234. })
  235. </script>
  236. <style scoped lang="scss">
  237. :deep(.el-dialog__header) {
  238. padding: unset;
  239. border-bottom: unset;
  240. }
  241. :deep(.el-dialog__body) {
  242. padding: unset;
  243. max-height: unset !important;
  244. }
  245. </style>