PrintTemplate.vue 17 KB

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