PrintTemplate.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. <template>
  2. <div
  3. class="print-wrap"
  4. v-show="true"
  5. >
  6. <div
  7. id="printMe"
  8. ref="printMe"
  9. v-if="elementStyle"
  10. :style="[elementStyle.canvas,defaultTemplate==='horizen'?{width:elementStyle.canvas.height,height:elementStyle.canvas.width}:{}]"
  11. >
  12. <div
  13. v-if="currentTicket"
  14. :style="[{ position: 'relative',width: elementStyle.canvas.width,height:elementStyle.canvas.height},defaultTemplate==='horizen'?{transform:elementStyle.canvas.reverse?'translateX(50%) rotate(90deg)':'translateX(-100%) rotate(-90deg)', transformOrigin:elementStyle.canvas.reverse?'0 0':'100% 0'}:{ transform:elementStyle.canvas.reverse?'rotate(180deg)':''}]">
  15. <div
  16. class="el sign"
  17. :style="elementStyle.sign">
  18. </div>
  19. <span
  20. v-if="projectName!=='mazu'"
  21. class="el"
  22. :style="elementStyle.num">
  23. - NO.{{ isNaN(currentTicket.index)? 1 : currentTicket.index + 1 }} -
  24. </span>
  25. <span
  26. v-else
  27. class="el"
  28. :style="elementStyle.num">
  29. - NO.{{ isNaN(currentTicket.id)? '00000000' : currentTicket.id.toString().padStart(8,'0') }} -
  30. </span>
  31. <!-- 是否使用打印价格 -->
  32. <!--
  33. <span
  34. class="el"
  35. v-if="!currentTicket.is_otaorder"
  36. :style="elementStyle.price">
  37. <i style="font-size:0.8em; font-style:normal">{{ elementStyle.price&&elementStyle.price.format }}</i> ¥ {{ currentTicket.isPrint_price_active ? currentTicket.print_price*currentTicket.checkNum : currentTicket.price }} <span v-if="showUnit">元</span>
  38. </span>
  39. <span
  40. class="el"
  41. v-if="!currentTicket.is_otaorder"
  42. :style="elementStyle.singlePrice">
  43. <i style="font-size:0.8em; font-style:normal">{{ elementStyle.singlePrice&&elementStyle.singlePrice.format }}</i> ¥ {{ currentTicket.isPrint_price_active ? currentTicket.print_price : $NP.divide(currentTicket.price, currentTicket.checkNum) }} <span v-if="showUnit">元</span>
  44. </span>
  45. -->
  46. <!-- 三河古镇定制:线上订单也打印价格 -->
  47. <template v-if="'三河古镇' === scenicName">
  48. <span
  49. class="el"
  50. :style="elementStyle.price">
  51. <i style="font-size:0.8em; font-style:normal">{{ elementStyle.price&&elementStyle.price.format }}</i> ¥ {{ currentTicket.isPrint_price_active ? currentTicket.print_price*currentTicket.checkNum : currentTicket.price }} <span v-if="showUnit">元</span>
  52. </span>
  53. <span
  54. class="el"
  55. :style="elementStyle.singlePrice">
  56. <i style="font-size:0.8em; font-style:normal">{{ elementStyle.singlePrice&&elementStyle.singlePrice.format }}</i> ¥ {{ currentTicket.isPrint_price_active ? currentTicket.print_price : $NP.divide(currentTicket.price, currentTicket.checkNum) }} <span v-if="showUnit">元</span>
  57. </span>
  58. </template>
  59. <!-- 其余景区线上订单不打印价格 -->
  60. <template v-else>
  61. <span
  62. class="el"
  63. v-if="!currentTicket.is_otaorder"
  64. :style="elementStyle.price">
  65. <i style="font-size:0.8em; font-style:normal">{{ elementStyle.price&&elementStyle.price.format }}</i> ¥ {{ currentTicket.isPrint_price_active ? currentTicket.print_price*currentTicket.checkNum : currentTicket.price }} <span v-if="showUnit">元</span>
  66. </span>
  67. <span
  68. class="el"
  69. v-if="!currentTicket.is_otaorder"
  70. :style="elementStyle.singlePrice">
  71. <i style="font-size:0.8em; font-style:normal">{{ elementStyle.singlePrice&&elementStyle.singlePrice.format }}</i> ¥ {{ currentTicket.isPrint_price_active ? currentTicket.print_price : $NP.divide(currentTicket.price, currentTicket.checkNum) }} <span v-if="showUnit">元</span>
  72. </span>
  73. </template>
  74. <div :style="elementStyle.orderNo">
  75. {{ elementStyle.orderNo&&elementStyle.orderNo.format || '订单号:' }}{{ currentTicket.orderNo }}
  76. </div>
  77. <div
  78. :style="elementStyle.saleAdminName"
  79. v-if="currentTicket.saleAdminName">
  80. 售票员:{{ currentTicket.saleAdminName }}
  81. </div>
  82. <div
  83. :style="elementStyle.guestName"
  84. v-if="(currentTicket.buyerName||currentTicket.guestName)">
  85. {{ elementStyle.guestName.format&&elementStyle.guestName.format||'游客:' }} {{ currentTicket.guestName || currentTicket.buyerName }}
  86. <span v-if="currentTicket.guestIdentify && elementStyle.guestName.showId">({{ currentTicket.guestIdentify.replace(/^(.{6})(?:\d+)(.{4})$/,"$1******$2") }})</span>
  87. </div>
  88. <div
  89. :style="elementStyle.teamName"
  90. v-if="currentTicket.travelAgencyName">
  91. {{ elementStyle.teamName&&elementStyle.teamName.format||'旅行社:' }} {{ currentTicket.travelAgencyName }}
  92. </div>
  93. <div :style="elementStyle.ticketId">
  94. {{ elementStyle.ticketId&&elementStyle.ticketId.format || '票号:' }}{{ currentTicket.ticketNo }}
  95. </div>
  96. <div
  97. :style="elementStyle.createTime"
  98. v-if="currentTicket.createTime">
  99. {{ elementStyle.createTime&&elementStyle.createTime.format || '出票时间:' }}{{ currentTicket.buyTime }}
  100. </div>
  101. <div :style="elementStyle.scenics">
  102. {{ scenics }}
  103. </div>
  104. <div :style="elementStyle.scenic">
  105. <div :style="((elementStyle.scenic && (elementStyle.scenic.direction==='inline'|| !elementStyle.scenic.direction))?{}:{display: 'flex','flex-direction': 'column'})">
  106. <template v-if="elementStyle.scenic.number==='0'">
  107. <span
  108. style="margin-right:5px"
  109. v-for="(item,index) in scenicList"
  110. :key="index">
  111. {{ item.scenicName }} {{ item.checkLimitTimes }}次<span v-if="index!==(scenicList.length-1)">,</span>
  112. </span>
  113. </template>
  114. <template v-else>
  115. <span
  116. style="margin-right:5px"
  117. v-for="(item,index) in scenicList.slice(0,elementStyle.scenic.number||2)"
  118. :key="index">
  119. {{ item.scenicName }} {{ item.checkLimitTimes }}次<span v-if="elementStyle.scenic.number>scenicList.length?index!==(scenicList.length-1):index!==(elementStyle.scenic.number||2)-1">,</span>
  120. </span>
  121. <span v-if="elementStyle.scenic.number<scenicList.length">...(多景点门票)</span>
  122. </template>
  123. </div>
  124. </div>
  125. <!-- <table :style="elementStyle.info">
  126. <tr>
  127. <td width="40%">
  128. 订单号:
  129. </td>
  130. <td></td>
  131. </tr>
  132. <tr>
  133. <td>票号:</td>
  134. <td>{{ currentTicket.qrcode }}</td>
  135. </tr>
  136. <tr v-if="scenicList && scenicList.length>0">
  137. <td>{{ scenicList[0].scenicName }}</td>
  138. <td>
  139. 可检 {{ scenicList[0].checkLimitTimes }} 次
  140. 单次可过人数: {{ scenicList[0].single_pass_times }} 人 <br>
  141. <span v-if="scenicList.length>1">
  142. (多景点门票)
  143. </span>
  144. </td>
  145. </tr>
  146. </table> -->
  147. <div :style="elementStyle.ticketTypeName">
  148. {{ elementStyle.ticketTypeName&&elementStyle.ticketTypeName.format || '' }}{{ currentTicket.ticketTypeName }}
  149. </div>
  150. <div
  151. class="img"
  152. :style="[elementStyle.qrcode,{backgroundImage:`url(${qrImg})`}]">
  153. </div>
  154. <!--
  155. <div
  156. v-if="currentTicket.invoiceQrcode"
  157. class="img-wrap"
  158. :style="[elementStyle.invoiceQrcode,{display:currentTicket.invoiceQrcode?'block':'none'}]">
  159. <div
  160. class="img"
  161. :style="{backgroundImage:`url(${fpImg})`,height:'100%'}">
  162. </div>
  163. <div v-if="elementStyle.invoiceQrcode">
  164. {{ elementStyle.invoiceQrcode&&elementStyle.invoiceQrcode.format }}
  165. </div>
  166. </div>
  167. -->
  168. <!-- 不需要再设置display,否则它会覆盖elementStyle里设置的display -->
  169. <div
  170. v-if="currentTicket.invoiceQrcode"
  171. class="img-wrap"
  172. :style="[elementStyle.invoiceQrcode]">
  173. <div
  174. class="img"
  175. :style="{backgroundImage:`url(${fpImg})`,height:'100%'}">
  176. </div>
  177. <div v-if="elementStyle.invoiceQrcode">
  178. {{ elementStyle.invoiceQrcode&&elementStyle.invoiceQrcode.format }}
  179. </div>
  180. </div>
  181. <!--
  182. <div
  183. v-if="currentTicket.allInvoiceQrcode"
  184. class="img-wrap"
  185. :style="[elementStyle.allInvoiceQrcode,{display:currentTicket.allInvoiceQrcode?'block':'none'}]">
  186. <div
  187. class="img"
  188. :style="{backgroundImage:`url(${afpImg})`,height:'100%'}">
  189. </div>
  190. <div v-if="elementStyle.allInvoiceQrcode">
  191. {{ elementStyle.allInvoiceQrcode.format }}
  192. </div>
  193. </div>
  194. -->
  195. <!-- 不需要再设置display,否则它会覆盖elementStyle里设置的display -->
  196. <div
  197. v-if="currentTicket.allInvoiceQrcode"
  198. class="img-wrap"
  199. :style="[elementStyle.allInvoiceQrcode]">
  200. <div
  201. class="img"
  202. :style="{backgroundImage:`url(${afpImg})`,height:'100%'}">
  203. </div>
  204. <div v-if="elementStyle.allInvoiceQrcode">
  205. {{ elementStyle.allInvoiceQrcode.format }}
  206. </div>
  207. </div>
  208. <div :style="elementStyle.des">
  209. <span v-if="currentTicket.isNeedPlaytime">
  210. {{ elementStyle.des&&elementStyle.des.format||'游玩日期:' }}{{ currentTicket.payDateBegin | formatDate }} <br>
  211. </span>
  212. <span v-else>
  213. 有效日期: {{ currentTicket.payDateBegin | formatDate }} 至 {{ currentTicket.playDateEnd | formatDate }}<br>
  214. </span>
  215. </div>
  216. <div
  217. :style="elementStyle.orderRemark"
  218. v-if="currentTicket.remark">
  219. {{ elementStyle.orderRemark&&elementStyle.orderRemark.format||'订单备注:' }}{{ currentTicket.remark }}
  220. </div>
  221. <div
  222. :style="elementStyle.performName"
  223. v-if="currentTicket.performName">
  224. <!-- {{ elementStyle.performName&&elementStyle.performName.format||'节目:' }}{{ currentTicket.performName }} -->
  225. <!-- 草房子这个景点不显示后面的"草房子剧场" -->
  226. {{ elementStyle.performName&&elementStyle.performName.format||'节目:' }}{{ '草房子' === scenicName ? '' : currentTicket.performName }}
  227. </div>
  228. <div
  229. :style="elementStyle.batchConfigName"
  230. v-if="currentTicket.batchConfigName">
  231. {{ elementStyle.batchConfigName&&elementStyle.batchConfigName.format||'场次:' }}{{ currentTicket.batchConfigName }}
  232. </div>
  233. <div
  234. :style="elementStyle.serialNumber"
  235. v-if="currentTicket.serialNumber">
  236. {{ elementStyle.serialNumber&&elementStyle.serialNumber.format||'场次内序号:' }}{{ currentTicket.serialNumber }}
  237. </div>
  238. <div
  239. :style="elementStyle.queueSerialNumber"
  240. v-if="currentTicket.queue_info">
  241. <span>{{ elementStyle.queueSerialNumber&&elementStyle.queueSerialNumber.format||'排队序列号::' }}</span>
  242. <span> {{ currentTicket.queue_info.queue_serialNumber }}</span>
  243. </div>
  244. <div
  245. :style="elementStyle.schedule"
  246. v-if="currentTicket.scheduleConfigName">
  247. {{ elementStyle.schedule&&elementStyle.schedule.format||'班次:' }}{{ currentTicket.scheduleConfigName }}
  248. </div>
  249. <div
  250. :style="elementStyle.scheduleSerialNumber"
  251. v-if="currentTicket.scheduleSerialNumber">
  252. {{ elementStyle.scheduleSerialNumber&&elementStyle.scheduleSerialNumber.format||'班次内序号:' }}{{ currentTicket.scheduleSerialNumber }}
  253. </div>
  254. <div
  255. :style="elementStyle.areaName"
  256. v-if="currentTicket.seatAreaName">
  257. {{ elementStyle.areaName&&elementStyle.areaName.format||'区域:' }}{{ currentTicket.seatAreaName }}
  258. </div>
  259. <div
  260. :style="elementStyle.pricePlan"
  261. v-if="currentTicket.ticket_price_plan_name">
  262. {{ elementStyle.pricePlan&&elementStyle.pricePlan.format||'价格方案:' }}{{ currentTicket.ticket_price_plan_name }}
  263. </div>
  264. <div
  265. :style="elementStyle.seatNameNew"
  266. v-if="currentTicket.seatNameNew">
  267. {{ elementStyle.seatNameNew&&elementStyle.seatNameNew.format||'A区' }}{{ currentTicket.seatNameNew }}
  268. </div>
  269. <div
  270. :style="elementStyle.rowTag"
  271. v-if="currentTicket.seatRowTag">
  272. {{ currentTicket.seatRowTag }}{{ elementStyle.rowTag&&elementStyle.rowTag.format||'排' }}
  273. </div>
  274. <div
  275. :style="elementStyle.seatName"
  276. v-if="currentTicket.seatName">
  277. {{ currentTicket.seatName }}{{ elementStyle.seatName&&elementStyle.seatName.format||'座' }}
  278. </div>
  279. <div
  280. :style="elementStyle.batchConfigName"
  281. v-if="currentTicket.batchConfigName">
  282. {{ elementStyle.batchConfigName&&elementStyle.batchConfigName.format||'场次:' }}{{ currentTicket.batchConfigName }}
  283. </div>
  284. <div
  285. :style="elementStyle.schedule"
  286. v-if="currentTicket.scheduleConfigName">
  287. {{ elementStyle.schedule&&elementStyle.schedule.format||'班次:' }}{{ currentTicket.scheduleConfigName }}
  288. </div>
  289. <div
  290. :style="elementStyle.areaName"
  291. v-if="currentTicket.seatAreaName">
  292. {{ elementStyle.areaName&&elementStyle.areaName.format||'区域:' }}{{ currentTicket.seatAreaName }}
  293. </div>
  294. <div :style="elementStyle.checkNum">
  295. {{ elementStyle.checkNum&&elementStyle.checkNum.format||'检票人数:' }}{{ currentTicket.checkNum }} 人
  296. </div>
  297. <div :style="elementStyle.remark">
  298. {{ elementStyle.remark&&elementStyle.remark.format||'' }}<span
  299. style="white-space: pre-wrap;"
  300. v-html="elementStyle.canvas.remark.replace(/\n/g,'<br>')">
  301. </span>
  302. </div>
  303. <div :style="elementStyle.fixedText">
  304. {{ elementStyle.fixedText&&elementStyle.fixedText.format||'备注2:' }}
  305. </div>
  306. </div>
  307. </div>
  308. </div>
  309. </template>
  310. <script>
  311. import QRCode from 'qrcode'
  312. export default {
  313. computed: {
  314. elementStyle () {
  315. let style = {}
  316. if (this.customStyle) {
  317. console.log(this.customStyle)
  318. this.customStyle.elements.forEach(i => {
  319. style[i.type] = i.style
  320. })
  321. style.canvas = this.customStyle.canvas
  322. if (!style.scenic) {
  323. style.scenic = { display: 'none' }
  324. }
  325. }
  326. return this.customStyle ? style : null
  327. },
  328. scenicList () {
  329. return this.currentTicket?.ticketScenic || this.currentTicket?.ticketCheckScenicList || []
  330. },
  331. scenics () {
  332. if (this.currentTicket) {
  333. const scenics = this.scenicList.map(i => i.scenicName)
  334. return scenics.join(' - ')
  335. } else {
  336. return ''
  337. }
  338. },
  339. printTemplate () {
  340. return this.$store.state.app.printTemplate
  341. },
  342. showUnit () {
  343. return this.scenicName !== '狼山景区' && this.scenicName !== '军山景区'
  344. },
  345. scenicName () {
  346. return this.$localStore.get('scenicName') || this.$store.state.user.scenicName
  347. }
  348. },
  349. data () {
  350. return {
  351. projectName: process.env.VUE_APP_PROJECT,
  352. qrImg: '',
  353. fpImg: '',
  354. afpImg: '',
  355. customStyle: null,
  356. defaultTemplate: ''
  357. }
  358. },
  359. props: {
  360. preview: { // 仅预览时,不打印
  361. type: Boolean,
  362. default: false
  363. },
  364. currentTicket: {
  365. type: Object,
  366. default: () => ({})
  367. }
  368. },
  369. methods: {
  370. startPrint () {
  371. console.log('currentTicket', this.currentTicket)
  372. const webview = document.querySelector('#printWebview')
  373. let printDoc = this.$refs.printMe.outerHTML
  374. // if (this.preview) {
  375. console.log(printDoc)
  376. // return
  377. // }
  378. // webview.openDevTools()
  379. webview.send('webview-print-render', printDoc)
  380. },
  381. sendPrinter (style) {
  382. if (style) {
  383. this.customStyle = JSON.parse(JSON.stringify(style))
  384. this.defaultTemplate = this.customStyle.theme
  385. this.$nextTick(() => {
  386. this.startPrint()
  387. })
  388. } else {
  389. this.startPrint()
  390. }
  391. }
  392. },
  393. watch: {
  394. printTemplate: {
  395. handler (val) {
  396. if (val) {
  397. this.defaultTemplate = val.canvas && val.canvas.direction
  398. // console.log(this.defaultTemplate)
  399. this.customStyle = val
  400. }
  401. },
  402. deep: true,
  403. immediate: true
  404. },
  405. currentTicket: {
  406. handler (val) {
  407. if (!val) return
  408. let target
  409. if (val.print_model_id !== 0) {
  410. target = this.$store.state.app.printTemplateList.find(i => i.id === val.print_model_id)
  411. } else {
  412. let id = this.$localStore.get('defaultTemplate')
  413. target = this.$store.state.app.printTemplateList.find(i => i.id === id)
  414. }
  415. this.$store.commit('SET_PRINT_TEMPLATE', target ? JSON.parse(target.content) : this.printTemplate)
  416. this.$nextTick(async () => {
  417. var opts = {
  418. errorCorrectionLevel: 'H',
  419. type: 'image/png',
  420. quality: 1,
  421. color: {
  422. dark: '#000000ff', // Blue dots
  423. light: '#0000' // Transparent background
  424. }
  425. }
  426. if (this.currentTicket.ticketNo) {
  427. this.qrImg = await QRCode.toDataURL(this.currentTicket.ticketNo, opts)
  428. }
  429. if (this.currentTicket.invoiceQrcode) {
  430. this.fpImg = await QRCode.toDataURL(this.currentTicket.invoiceQrcode, opts)
  431. }
  432. if (this.currentTicket.allInvoiceQrcode) {
  433. this.afpImg = await QRCode.toDataURL(this.currentTicket.allInvoiceQrcode, opts)
  434. }
  435. console.warn(this.currentTicket)
  436. if (this.preview) return
  437. setTimeout(() => {
  438. this.sendPrinter()
  439. }, 100)
  440. })
  441. },
  442. immediate: true
  443. }
  444. }
  445. }
  446. </script>
  447. <style scoped>
  448. .img{
  449. height: 100%; background: no-repeat center center /contain;
  450. }
  451. </style>