hch-poster.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. <!--
  2. * @Description: 生成图片组件
  3. * @Version: 1.0.0
  4. * @Autor:
  5. * @Date: 2020-08-07 14:48:41
  6. * @LastEditors: Please set LastEditors
  7. * @LastEditTime: 2021-07-31 18:11:35
  8. * 保存图片按钮和关闭按钮 在html代码中写出来 绑定点击方法然后透明 再用canvas 覆盖
  9. -->
  10. <template>
  11. <view class="canvas-content" v-show="canvasShow" :style="'width:' + system.w + 'px; height:' + system.h + 'px;'">
  12. <!-- 遮罩层 -->
  13. <view class="canvas-mask"></view>
  14. <!-- 图片 -->
  15. <!-- :width="system.w" :height="system.h" 支付宝必须要这样设置宽高才有效果 -->
  16. <canvas class="canvas" canvas-id="myCanvas" id="myCanvas"
  17. :style="'width:' + system.w + 'px; height:' + system.h + 'px;'" :width="system.w"
  18. :height="system.h"></canvas>
  19. <view class="button-wrapper">
  20. <!-- 保存图片按钮 -->
  21. <!-- #ifndef MP-QQ -->
  22. <!-- cover-view 标签qq小程序有问题 -->
  23. <cover-view class="save-btn" @tap="handleSaveCanvasImage">保存</cover-view>
  24. <cover-view class="save-btn cancel-btn" @tap="handleCanvasCancel">取消</cover-view>
  25. <!-- #endif -->
  26. <!-- #ifdef MP-QQ -->
  27. <view class="save-btn" @tap="handleSaveCanvasImage">保存</view>
  28. <view class="save-btn cancel-btn" @tap="handleCanvasCancel">取消</view>
  29. <!-- #endif -->
  30. </view>
  31. </view>
  32. </template>
  33. <script>
  34. import {
  35. drawSquarePic,
  36. drawTextReturnH,
  37. getSystem
  38. } from '@/common/poster'
  39. export default {
  40. data() {
  41. return {
  42. system: {},
  43. canvasShow: false
  44. }
  45. },
  46. props: {
  47. posterData: {
  48. type: Object,
  49. default: () => {
  50. return {}
  51. }
  52. }
  53. },
  54. computed: {
  55. /**
  56. * @description: 计算图片背景数据
  57. * @param {*}
  58. * @return {*}
  59. * @author: hch
  60. */
  61. poster() {
  62. let data = this.posterData
  63. let system = this.system
  64. let posterBg = {
  65. url: data.poster.url,
  66. r: data.poster.r * system.scale,
  67. w: data.poster.w * system.scale,
  68. h: data.poster.h * system.scale,
  69. x: (system.w - data.poster.w * system.scale) / 2,
  70. y: (system.h - data.poster.h * system.scale) / 2,
  71. p: data.poster.p * system.scale
  72. }
  73. return posterBg
  74. },
  75. // #ifdef H5
  76. /**
  77. * @description: 计算图片标题
  78. * @param {*}
  79. * @return {*}
  80. * @author: hch
  81. */
  82. title() {
  83. let data = this.posterData
  84. let system = this.system
  85. let posterTitle = data.title
  86. posterTitle.x = (system.w - data.title.w * system.scale) / 2
  87. posterTitle.y = this.poster.y + data.poster.p + data.title.mt * system.scale
  88. return posterTitle
  89. },
  90. /**
  91. * @description: 计算图片头部主图
  92. * @param {*}
  93. * @return {*}
  94. * @author: hch
  95. */
  96. mainImg() {
  97. let data = this.posterData
  98. let system = this.system
  99. let posterMain = {
  100. url: data.mainImg.url,
  101. r: data.mainImg.r * system.scale,
  102. w: data.mainImg.w * system.scale,
  103. h: data.mainImg.h * system.scale,
  104. x: (system.w - data.mainImg.w * system.scale) / 1.7,
  105. y: this.poster.y + data.poster.p + data.mainImg.mt * system.scale
  106. }
  107. return posterMain
  108. },
  109. /**
  110. * @description: 计算小程序码
  111. * @param {*}
  112. * @return {*}
  113. * @author: hch
  114. */
  115. codeImg() {
  116. let data = this.posterData
  117. let system = this.system
  118. let posterCode = {
  119. url: data.codeImg.url,
  120. r: data.codeImg.r * system.scale,
  121. w: data.codeImg.w * system.scale,
  122. h: data.codeImg.h * system.scale,
  123. x: (system.w - data.codeImg.w * system.scale) / 2.8,
  124. y: data.codeImg.mt * system.scale //y需要加上绘图后文本的y
  125. }
  126. return posterCode
  127. }
  128. // #endif
  129. // #ifndef H5
  130. /**
  131. * @description: 计算图片标题
  132. * @param {*}
  133. * @return {*}
  134. * @author: hch
  135. */
  136. title() {
  137. let data = this.posterData
  138. let system = this.system
  139. let posterTitle = data.title
  140. posterTitle.x = (system.w - data.title.w * system.scale) / 2
  141. posterTitle.y = this.poster.y + data.poster.p + data.title.mt * system.scale
  142. return posterTitle
  143. },
  144. /**
  145. * @description: 计算图片头部主图
  146. * @param {*}
  147. * @return {*}
  148. * @author: hch
  149. */
  150. mainImg() {
  151. let data = this.posterData
  152. let system = this.system
  153. let posterMain = {
  154. url: data.mainImg.url,
  155. r: data.mainImg.r * system.scale,
  156. w: data.mainImg.w * system.scale,
  157. h: data.mainImg.h * system.scale,
  158. x: (system.w - data.mainImg.w * system.scale) / 1.7,
  159. y: this.poster.y + data.poster.p + data.mainImg.mt * system.scale
  160. }
  161. return posterMain
  162. },
  163. /**
  164. * @description: 计算小程序码
  165. * @param {*}
  166. * @return {*}
  167. * @author: hch
  168. */
  169. codeImg() {
  170. let data = this.posterData
  171. let system = this.system
  172. let posterCode = {
  173. url: data.codeImg.url,
  174. r: data.codeImg.r * system.scale,
  175. w: data.codeImg.w * system.scale,
  176. h: data.codeImg.h * system.scale,
  177. x: (system.w - data.codeImg.w * system.scale) / 2.8,
  178. y: this.poster.y + data.poster.p + data.codeImg.mt * system.scale //y需要加上绘图后文本的y
  179. }
  180. return posterCode
  181. }
  182. // #endif
  183. },
  184. created() {
  185. // 获取设备信息
  186. this.system = getSystem()
  187. },
  188. methods: {
  189. /**
  190. * @description: 展示图片
  191. * @param {type}
  192. * @return {type}
  193. * @author: hch
  194. */
  195. posterShow() {
  196. this.canvasShow = true
  197. this.creatPoster()
  198. },
  199. /**
  200. * @description: 生成图片
  201. * @author: hch
  202. */
  203. async creatPoster() {
  204. uni.showLoading({
  205. title: '生成海报中...'
  206. })
  207. const ctx = uni.createCanvasContext('myCanvas', this)
  208. this.ctx = ctx
  209. ctx.clearRect(0, 0, this.system.w, this.system.h) //清空之前的图片
  210. ctx.draw() //清空之前的图片
  211. // 根据设备屏幕大小和距离屏幕上下左右距离,及圆角绘制背景
  212. let poster = this.poster
  213. let mainImg = this.mainImg
  214. let codeImg = this.codeImg
  215. let title = this.title
  216. await drawSquarePic(ctx, poster.x, poster.y, poster.w, poster.h, poster.r, poster.url)
  217. await drawSquarePic(ctx, mainImg.x, mainImg.y, mainImg.w, mainImg.h, mainImg.r, mainImg.url)
  218. // await drawSquarePic(ctx, codeImg.x, codeImg.y, codeImg.w, codeImg.h, codeImg.r, codeImg.url)
  219. // 绘制标题 textY 绘制文本的y位置
  220. console.log('creatPoster -> mainImg.x', mainImg.x)
  221. let textY = drawTextReturnH(
  222. ctx,
  223. title.text,
  224. title.x,
  225. title.y,
  226. title.w,
  227. title.fontSize,
  228. title.color,
  229. title.background,
  230. title.lineHeight,
  231. title.align
  232. )
  233. // 绘制小程序码
  234. await drawSquarePic(
  235. ctx,
  236. codeImg.x,
  237. codeImg.y,
  238. codeImg.w,
  239. codeImg.h,
  240. codeImg.r,
  241. codeImg.url
  242. )
  243. // 小程序的名称
  244. // 长按/扫描识别查看商品
  245. let y = 0
  246. this.posterData.tips.forEach((element, i) => {
  247. if (i == 0) {
  248. y = codeImg.y + textY + element.mt + codeImg.h
  249. } else {
  250. y += element.mt
  251. }
  252. y = drawTextReturnH(
  253. ctx,
  254. element.text,
  255. title.x,
  256. y,
  257. mainImg.w,
  258. element.fontSize,
  259. element.color,
  260. element.background,
  261. element.lineHeight,
  262. element.align
  263. )
  264. })
  265. uni.hideLoading()
  266. },
  267. /**
  268. * @description: 保存到系统相册
  269. * @param {type}
  270. * @return {type}
  271. * @author: hch
  272. */
  273. handleSaveCanvasImage() {
  274. uni.showLoading({
  275. title: '保存中...'
  276. })
  277. let _this = this
  278. // 把画布转化成临时文件
  279. // #ifndef MP-ALIPAY
  280. // 支付宝小程序外,其他都是用这个方法 canvasToTempFilePath
  281. uni.canvasToTempFilePath({
  282. x: this.poster.x,
  283. y: this.poster.y,
  284. width: this.poster.w, // 画布的宽
  285. height: this.poster.h, // 画布的高
  286. destWidth: this.poster.w * 5,
  287. destHeight: this.poster.h * 5,
  288. canvasId: 'myCanvas',
  289. success(res) {
  290. //保存图片至相册
  291. // #ifndef H5
  292. // 除了h5以外的其他端
  293. uni.saveImageToPhotosAlbum({
  294. filePath: res.tempFilePath,
  295. success(res) {
  296. uni.hideLoading()
  297. uni.showToast({
  298. title: '海报保存成功,可以去分享啦~',
  299. duration: 2000,
  300. icon: 'none'
  301. })
  302. _this.handleCanvasCancel()
  303. },
  304. fail(res1) {
  305. uni.showToast({
  306. title: '保存失败,稍后再试',
  307. duration: 2000,
  308. icon: 'none'
  309. })
  310. uni.hideLoading()
  311. }
  312. })
  313. // #endif
  314. // #ifdef H5
  315. // h5的时候
  316. uni.showToast({
  317. title: '请长按保存',
  318. duration: 3000,
  319. icon: 'none'
  320. })
  321. _this.handleCanvasCancel()
  322. _this.$emit('previewImage', res.tempFilePath)
  323. // #endif
  324. },
  325. fail(res) {
  326. console.log('fail -> res', res)
  327. uni.showToast({
  328. title: '保存失败,稍后再试',
  329. duration: 2000,
  330. icon: 'none'
  331. })
  332. uni.hideLoading()
  333. }
  334. },
  335. this
  336. )
  337. // #endif
  338. // #ifdef MP-ALIPAY
  339. // 支付宝小程序条件下 toTempFilePath
  340. this.ctx.toTempFilePath({
  341. x: this.poster.x,
  342. y: this.poster.y,
  343. width: this.poster.w, // 画布的宽
  344. height: this.poster.h, // 画布的高
  345. destWidth: this.poster.w * 5,
  346. destHeight: this.poster.h * 5,
  347. success(res) {
  348. //保存图片至相册
  349. my.saveImage({
  350. url: res.apFilePath,
  351. showActionSheet: true,
  352. success(res) {
  353. uni.hideLoading()
  354. uni.showToast({
  355. title: '图片保存成功,可以去分享啦~',
  356. duration: 2000,
  357. icon: 'none'
  358. })
  359. _this.handleCanvasCancel()
  360. },
  361. fail() {
  362. uni.showToast({
  363. title: '保存失败,稍后再试',
  364. duration: 2000,
  365. icon: 'none'
  366. })
  367. uni.hideLoading()
  368. }
  369. })
  370. },
  371. fail(res) {
  372. // console.log('fail -> res', res)
  373. uni.showToast({
  374. title: '保存失败,稍后再试',
  375. duration: 2000,
  376. icon: 'none'
  377. })
  378. uni.hideLoading()
  379. }
  380. },
  381. this
  382. )
  383. // #endif
  384. },
  385. /**
  386. * @description: 取消图片
  387. * @param {type}
  388. * @return {type}
  389. * @author: hch
  390. */
  391. handleCanvasCancel() {
  392. this.canvasShow = false
  393. this.$emit('cancel', true)
  394. },
  395. }
  396. }
  397. </script>
  398. <style lang="scss">
  399. .content {
  400. height: 100%;
  401. text-align: center;
  402. }
  403. .canvas-content {
  404. position: absolute;
  405. top: 0;
  406. .canvas-mask {
  407. position: fixed;
  408. top: 0;
  409. right: 0;
  410. bottom: 0;
  411. left: 0;
  412. z-index: 9;
  413. width: 100%;
  414. height: 100%;
  415. background: rgba(0, 0, 0, 0.5);
  416. }
  417. .canvas {
  418. z-index: 10;
  419. }
  420. .button-wrapper {
  421. position: fixed;
  422. bottom: 80rpx;
  423. z-index: 16;
  424. display: flex;
  425. width: 100%;
  426. height: 72rpx;
  427. justify-content: space-around;
  428. }
  429. .save-btn {
  430. z-index: 16;
  431. width: 40%;
  432. height: 100%;
  433. font-size: 30rpx;
  434. line-height: 72rpx;
  435. color: #fff;
  436. text-align: center;
  437. background: $uni-btn-color;
  438. border-radius: 45rpx;
  439. border-radius: 36rpx;
  440. }
  441. .cancel-btn {
  442. color: #fff;
  443. background: #4D402F;
  444. }
  445. .canvas-close-btn {
  446. position: fixed;
  447. top: 30rpx;
  448. right: 0;
  449. z-index: 12;
  450. width: 60rpx;
  451. height: 60rpx;
  452. padding: 20rpx;
  453. }
  454. }
  455. </style>