10c6d284 by simon

默认提交

1 parent 302e3d6b
...@@ -28,6 +28,7 @@ module.exports = { ...@@ -28,6 +28,7 @@ module.exports = {
28 */ 28 */
29 areaQuery: 'https://api.k.wxpai.cn/bizproxy/kdapi/area', // post 区域查询 29 areaQuery: 'https://api.k.wxpai.cn/bizproxy/kdapi/area', // post 区域查询
30 30
31 uploadFile: '/kdapi/file/upload' //上传图片通用接口 31 wxacodeGet: "/mzcfsapi/qrcode/createV2", // 通用上传 ?path=/pages/index/index?pa=1
32 uploadFile: '/kdapi/file/upload', //上传图片通用接口
32 33
33 } 34 }
......
1 const main = {
2 /**
3 * 渲染块
4 * @param {Object} params
5 */
6 drawBlock({ text, width = 0, height, x, y, paddingLeft = 0, paddingRight = 0, borderWidth, backgroundColor, borderColor, borderRadius = 0, opacity = 1 }) {
7 // 判断是否块内有文字
8 let blockWidth = 0; // 块的宽度
9 let textX = 0;
10 let textY = 0;
11 if (typeof text !== 'undefined') {
12 // 如果有文字并且块的宽度小于文字宽度,块的宽度为 文字的宽度 + 内边距
13 const textWidth = this._getTextWidth(typeof text.text === 'string' ? text : text.text);
14 blockWidth = textWidth > width ? textWidth : width;
15 blockWidth += paddingLeft + paddingLeft;
16
17 const { textAlign = 'left', text: textCon } = text;
18 textY = height / 2 + y; // 文字的y轴坐标在块中线
19 if (textAlign === 'left') {
20 // 如果是右对齐,那x轴在块的最左边
21 textX = x + paddingLeft;
22 } else if (textAlign === 'center') {
23 textX = blockWidth / 2 + x;
24 } else {
25 textX = x + blockWidth - paddingRight;
26 }
27 } else {
28 blockWidth = width;
29 }
30
31 if (backgroundColor) {
32 // 画面
33 this.ctx.save();
34 this.ctx.setGlobalAlpha(opacity);
35 this.ctx.setFillStyle(backgroundColor);
36 if (borderRadius > 0) {
37 // 画圆角矩形
38 this._drawRadiusRect(x, y, blockWidth, height, borderRadius);
39 this.ctx.fill();
40 } else {
41 this.ctx.fillRect(this.toPx(x), this.toPx(y), this.toPx(blockWidth), this.toPx(height));
42 }
43 this.ctx.restore();
44 }
45 if (borderWidth) {
46 // 画线
47 this.ctx.save();
48 this.ctx.setGlobalAlpha(opacity);
49 this.ctx.setStrokeStyle(borderColor);
50 this.ctx.setLineWidth(this.toPx(borderWidth));
51 if (borderRadius > 0) {
52 // 画圆角矩形边框
53 this._drawRadiusRect(x, y, blockWidth, height, borderRadius);
54 this.ctx.stroke();
55 } else {
56 this.ctx.strokeRect(this.toPx(x), this.toPx(y), this.toPx(blockWidth), this.toPx(height));
57 }
58 this.ctx.restore();
59 }
60
61 if (text) {
62 this.drawText(Object.assign(text, { x: textX, y: textY }))
63 }
64 },
65
66 /**
67 * 渲染文字
68 * @param {Object} params
69 */
70 drawText(params) {
71 const { x, y, fontSize, color, baseLine, textAlign, text, opacity = 1, width, lineNum, lineHeight } = params;
72 if (Object.prototype.toString.call(text) === '[object Array]') {
73 let preText = { x, y, baseLine };
74 text.forEach(item => {
75 preText.x += item.marginLeft || 0;
76 const textWidth = this._drawSingleText(Object.assign(item, {
77 ...preText,
78 }));
79 preText.x += textWidth + (item.marginRight || 0); // 下一段字的x轴为上一段字x + 上一段字宽度
80 })
81 } else {
82 this._drawSingleText(params);
83 }
84 },
85
86 /**
87 * 渲染图片
88 */
89 drawImage(data) {
90 const { imgPath, x, y, w, h, sx, sy, sw, sh, borderRadius = 0, borderWidth = 0, borderColor } = data;
91 this.ctx.save();
92 if (borderRadius > 0) {
93 this._drawRadiusRect(x, y, w, h, borderRadius);
94 this.ctx.strokeStyle = 'rgba(255,255,255,0)';
95 this.ctx.stroke();
96 this.ctx.clip();
97 this.ctx.drawImage(imgPath, this.toPx(sx), this.toPx(sy), this.toPx(sw), this.toPx(sh), this.toPx(x), this.toPx(y), this.toPx(w), this.toPx(h));
98 if (borderWidth > 0) {
99 this.ctx.setStrokeStyle(borderColor);
100 this.ctx.setLineWidth(this.toPx(borderWidth));
101 this.ctx.stroke();
102 }
103 } else {
104 this.ctx.drawImage(imgPath, this.toPx(sx), this.toPx(sy), this.toPx(sw), this.toPx(sh), this.toPx(x), this.toPx(y), this.toPx(w), this.toPx(h));
105 }
106 this.ctx.restore();
107 },
108 /**
109 * 渲染线
110 * @param {*} param0
111 */
112 drawLine({ startX, startY, endX, endY, color, width }) {
113 this.ctx.save();
114 this.ctx.beginPath();
115 this.ctx.setStrokeStyle(color);
116 this.ctx.setLineWidth(this.toPx(width));
117 this.ctx.moveTo(this.toPx(startX), this.toPx(startY));
118 this.ctx.lineTo(this.toPx(endX), this.toPx(endY));
119 this.ctx.stroke();
120 this.ctx.closePath();
121 this.ctx.restore();
122 },
123 downloadResource(images = []) {
124 const drawList = [];
125 this.drawArr = [];
126 images.forEach((image, index) => drawList.push(this._downloadImageAndInfo(image, index)));
127 return Promise.all(drawList);
128 },
129 initCanvas(w, h, debug) {
130 return new Promise((resolve) => {
131 this.setData({
132 pxWidth: this.toPx(w),
133 pxHeight: this.toPx(h),
134 debug,
135 }, resolve);
136 });
137 }
138 }
139 const handle = {
140 /**
141 * 画圆角矩形
142 */
143 _drawRadiusRect(x, y, w, h, r) {
144 const br = r / 2;
145 this.ctx.beginPath();
146 this.ctx.moveTo(this.toPx(x + br), this.toPx(y)); // 移动到左上角的点
147 this.ctx.lineTo(this.toPx(x + w - br), this.toPx(y));
148 this.ctx.arc(this.toPx(x + w - br), this.toPx(y + br), this.toPx(br), 2 * Math.PI * (3 / 4), 2 * Math.PI * (4 / 4))
149 this.ctx.lineTo(this.toPx(x + w), this.toPx(y + h - br));
150 this.ctx.arc(this.toPx(x + w - br), this.toPx(y + h - br), this.toPx(br), 0, 2 * Math.PI * (1 / 4))
151 this.ctx.lineTo(this.toPx(x + br), this.toPx(y + h));
152 this.ctx.arc(this.toPx(x + br), this.toPx(y + h - br), this.toPx(br), 2 * Math.PI * (1 / 4), 2 * Math.PI * (2 / 4))
153 this.ctx.lineTo(this.toPx(x), this.toPx(y + br));
154 this.ctx.arc(this.toPx(x + br), this.toPx(y + br), this.toPx(br), 2 * Math.PI * (2 / 4), 2 * Math.PI * (3 / 4))
155 },
156 /**
157 * 计算文本长度
158 * @param {Array|Object}} text 数组 或者 对象
159 */
160 _getTextWidth(text) {
161 let texts = [];
162 if (Object.prototype.toString.call(text) === '[object Object]') {
163 texts.push(text);
164 } else {
165 texts = text;
166 }
167 let width = 0;
168 texts.forEach(({ fontSize, text, marginLeft = 0, marginRight = 0 }) => {
169 this.ctx.setFontSize(this.toPx(fontSize));
170 width += this.ctx.measureText(text).width + marginLeft + marginRight;
171 })
172
173 return this.toRpx(width);
174 },
175 /**
176 * 渲染一段文字
177 */
178 _drawSingleText({ x, y, fontSize, color, baseLine, textAlign = 'left', text, opacity = 1, textDecoration = 'none',
179 width, lineNum = 1, lineHeight = 0, fontWeight = 'normal', fontStyle = 'normal', fontFamily = "sans-serif"}) {
180 this.ctx.save();
181 this.ctx.beginPath();
182 this.ctx.font = fontStyle + " " + fontWeight + " " + this.toPx(fontSize, true) + "px " + fontFamily
183 this.ctx.setGlobalAlpha(opacity);
184 // this.ctx.setFontSize(this.toPx(fontSize));
185 this.ctx.setFillStyle(color);
186 this.ctx.setTextBaseline(baseLine);
187 this.ctx.setTextAlign(textAlign);
188 let textWidth = this.toRpx(this.ctx.measureText(text).width);
189 const textArr = [];
190 if (textWidth > width) {
191 // 文本宽度 大于 渲染宽度
192 let fillText = '';
193 let line = 1;
194 for (let i = 0; i <= text.length - 1 ; i++) { // 将文字转为数组,一行文字一个元素
195 fillText = fillText + text[i];
196 if (this.toRpx(this.ctx.measureText(fillText).width) >= width) {
197 if (line === lineNum) {
198 if (i !== text.length - 1) {
199 fillText = fillText.substring(0, fillText.length - 1) + '...';
200 }
201 }
202 if(line <= lineNum) {
203 textArr.push(fillText);
204 }
205 fillText = '';
206 line++;
207 } else {
208 if(line <= lineNum) {
209 if(i === text.length -1){
210 textArr.push(fillText);
211 }
212 }
213 }
214 }
215 textWidth = width;
216 } else {
217 textArr.push(text);
218 }
219
220 textArr.forEach((item, index) => {
221 this.ctx.fillText(item, this.toPx(x), this.toPx(y + (lineHeight || fontSize) * index));
222 })
223
224 this.ctx.restore();
225
226 // textDecoration
227 if (textDecoration !== 'none') {
228 let lineY = y;
229 if (textDecoration === 'line-through') {
230 // 目前只支持贯穿线
231 lineY = y;
232 }
233 this.ctx.save();
234 this.ctx.moveTo(this.toPx(x), this.toPx(lineY));
235 this.ctx.lineTo(this.toPx(x) + this.toPx(textWidth), this.toPx(lineY));
236 this.ctx.setStrokeStyle(color);
237 this.ctx.stroke();
238 this.ctx.restore();
239 }
240
241 return textWidth;
242 },
243 }
244 const helper = {
245 /**
246 * 下载图片并获取图片信息
247 */
248 _downloadImageAndInfo(image, index) {
249 return new Promise((resolve, reject) => {
250 const { x, y, url, zIndex } = image;
251 const imageUrl = url;
252 // 下载图片
253 this._downImage(imageUrl, index)
254 // 获取图片信息
255 .then(imgPath => this._getImageInfo(imgPath, index))
256 .then(({ imgPath, imgInfo }) => {
257 // 根据画布的宽高计算出图片绘制的大小,这里会保证图片绘制不变形
258 let sx;
259 let sy;
260 const borderRadius = image.borderRadius || 0;
261 const setWidth = image.width;
262 const setHeight = image.height;
263 const width = this.toRpx(imgInfo.width);
264 const height = this.toRpx(imgInfo.height);
265
266 if (width / height <= setWidth / setHeight) {
267 sx = 0;
268 sy = (height - ((width / setWidth) * setHeight)) / 2;
269 } else {
270 sy = 0;
271 sx = (width - ((height / setHeight) * setWidth)) / 2;
272 }
273 this.drawArr.push({
274 type: 'image',
275 borderRadius,
276 borderWidth: image.borderWidth,
277 borderColor: image.borderColor,
278 zIndex: typeof zIndex !== 'undefined' ? zIndex : index,
279 imgPath,
280 sx,
281 sy,
282 sw: (width - (sx * 2)),
283 sh: (height - (sy * 2)),
284 x,
285 y,
286 w: setWidth,
287 h: setHeight,
288 });
289 resolve();
290 })
291 .catch(err => reject(err));
292 });
293 },
294 /**
295 * 下载图片资源
296 * @param {*} imageUrl
297 */
298 _downImage(imageUrl) {
299 return new Promise((resolve, reject) => {
300 if (/^http/.test(imageUrl) && !new RegExp(wx.env.USER_DATA_PATH).test(imageUrl)) {
301 wx.downloadFile({
302 url: this._mapHttpToHttps(imageUrl),
303 success: (res) => {
304 if (res.statusCode === 200) {
305 resolve(res.tempFilePath);
306 } else {
307 reject(res.errMsg);
308 }
309 },
310 fail(err) {
311 reject(err);
312 },
313 });
314 } else {
315 // 支持本地地址
316 resolve(imageUrl);
317 }
318 });
319 },
320 /**
321 * 获取图片信息
322 * @param {*} imgPath
323 * @param {*} index
324 */
325 _getImageInfo(imgPath, index) {
326 return new Promise((resolve, reject) => {
327 wx.getImageInfo({
328 src: imgPath,
329 success(res) {
330 resolve({ imgPath, imgInfo: res, index });
331 },
332 fail(err) {
333 reject(err);
334 },
335 });
336 });
337 },
338 toPx(rpx, int) {
339 if (int) {
340 return parseInt(rpx * this.factor);
341 }
342 return rpx * this.factor;
343 },
344 toRpx(px, int) {
345 if (int) {
346 return parseInt(px / this.factor);
347 }
348 return px / this.factor;
349 },
350 /**
351 * 将http转为https
352 * @param {String}} rawUrl 图片资源url
353 */
354 _mapHttpToHttps(rawUrl) {
355 if (rawUrl.indexOf(':') < 0) {
356 return rawUrl;
357 }
358 const urlComponent = rawUrl.split(':');
359 if (urlComponent.length === 2) {
360 if (urlComponent[0] === 'http') {
361 urlComponent[0] = 'https';
362 return `${urlComponent[0]}:${urlComponent[1]}`;
363 }
364 }
365 return rawUrl;
366 },
367 }
368 Component({
369 properties: {
370 },
371 created() {
372 const sysInfo = wx.getSystemInfoSync();
373 const screenWidth = sysInfo.screenWidth;
374 this.factor = screenWidth / 750;
375 },
376 methods: Object.assign({
377 /**
378 * 计算画布的高度
379 * @param {*} config
380 */
381 getHeight(config) {
382 const getTextHeight = (text) => {
383 let fontHeight = text.lineHeight || text.fontSize;
384 let height = 0;
385 if (text.baseLine === 'top') {
386 height = fontHeight;
387 } else if (text.baseLine === 'middle') {
388 height = fontHeight / 2;
389 } else {
390 height = 0;
391 }
392 return height;
393 }
394 const heightArr = [];
395 (config.blocks || []).forEach((item) => {
396 heightArr.push(item.y + item.height);
397 });
398 (config.texts || []).forEach((item) => {
399 let height;
400 if (Object.prototype.toString.call(item.text) === '[object Array]') {
401 item.text.forEach((i) => {
402 height = getTextHeight({...i, baseLine: item.baseLine});
403 heightArr.push(item.y + height);
404 });
405 } else {
406 height = getTextHeight(item);
407 heightArr.push(item.y + height);
408 }
409 });
410 (config.images || []).forEach((item) => {
411 heightArr.push(item.y + item.height);
412 });
413 (config.lines || []).forEach((item) => {
414 heightArr.push(item.startY);
415 heightArr.push(item.endY);
416 });
417 const sortRes = heightArr.sort((a, b) => b - a);
418 let canvasHeight = 0;
419 if (sortRes.length > 0) {
420 canvasHeight = sortRes[0];
421 }
422 if (config.height < canvasHeight || !config.height) {
423 return canvasHeight;
424 } else {
425 return config.height;
426 }
427 },
428 create(config) {
429 this.ctx = wx.createCanvasContext('canvasid', this);
430
431 const height = this.getHeight(config);
432 this.initCanvas(config.width, height, config.debug)
433 .then(() => {
434 // 设置画布底色
435 if (config.backgroundColor) {
436 this.ctx.save();
437 this.ctx.setFillStyle(config.backgroundColor);
438 this.ctx.fillRect(0, 0, this.toPx(config.width), this.toPx(height));
439 this.ctx.restore();
440 }
441 const { texts = [], images = [], blocks = [], lines = [] } = config;
442 const queue = this.drawArr
443 .concat(texts.map((item) => {
444 item.type = 'text';
445 item.zIndex = item.zIndex || 0;
446 return item;
447 }))
448 .concat(blocks.map((item) => {
449 item.type = 'block';
450 item.zIndex = item.zIndex || 0;
451 return item;
452 }))
453 .concat(lines.map((item) => {
454 item.type = 'line';
455 item.zIndex = item.zIndex || 0;
456 return item;
457 }));
458 // 按照顺序排序
459 queue.sort((a, b) => a.zIndex - b.zIndex);
460
461 queue.forEach((item) => {
462 if (item.type === 'image') {
463 this.drawImage(item)
464 } else if (item.type === 'text') {
465 this.drawText(item)
466 } else if (item.type === 'block') {
467 this.drawBlock(item)
468 } else if (item.type === 'line') {
469 this.drawLine(item)
470 }
471 });
472
473 const res = wx.getSystemInfoSync();
474 const platform = res.platform;
475 let time = 0;
476 if (platform === 'android') {
477 // 在安卓平台,经测试发现如果海报过于复杂在转换时需要做延时,要不然样式会错乱
478 time = 300;
479 }
480 this.ctx.draw(false, () => {
481 setTimeout(() => {
482 wx.canvasToTempFilePath({
483 canvasId: 'canvasid',
484 success: (res) => {
485 this.triggerEvent('success', res.tempFilePath);
486 },
487 fail: (err) => {
488 this.triggerEvent('fail', err);
489 },
490 }, this);
491 }, time);
492 });
493 })
494 .catch((err) => {
495 wx.showToast({ icon: 'none', title: err.errMsg || '生成失败' });
496 console.error(err);
497 });
498 },
499 }, main, handle, helper),
500 });
501
1 {
2 "component": true
3 }
...\ No newline at end of file ...\ No newline at end of file
1 <!--index.wxml-->
2 <view class="container">
3 <canvas canvas-id='canvasid' class="canvas {{debug ? 'debug' : 'pro'}}" style='width: {{pxWidth}}px; height: {{pxHeight}}px;'></canvas>
4 </view>
1 .canvas {
2 width: 750rpx;
3 height: 750rpx;
4 }
5 .canvas.pro {
6 position: absolute;
7 bottom: 0;
8 left: 0;
9 transform: translate3d(-9999rpx, 0, 0);
10 }
11 .canvas.debug {
12 position: absolute;
13 bottom: 0;
14 left: 0;
15 border: 1rpx solid #ccc;
16 }
...\ No newline at end of file ...\ No newline at end of file
1 Component({
2 properties: {
3 config: {
4 type: Object,
5 value: {},
6 },
7 preload: { // 是否预下载图片资源
8 type: Boolean,
9 value: false,
10 },
11 hideLoading: { // 是否隐藏loading
12 type: Boolean,
13 value: false,
14 }
15 },
16 ready() {
17 if (this.data.preload) {
18 const poster = this.selectComponent('#poster');
19 this.downloadStatus = 'doing';
20 poster.downloadResource(this.data.config.images).then(() => {
21 this.downloadStatus = 'success';
22 this.trigger('downloadSuccess');
23 }).catch((e) => {
24 this.downloadStatus = 'fail';
25 this.trigger('downloadFail', e);
26 });
27 }
28 },
29 methods: {
30 trigger(event, data) {
31 if (this.listener && typeof this.listener[event] === 'function') {
32 this.listener[event](data);
33 }
34 },
35 once(event, fun) {
36 if (typeof this.listener === 'undefined') {
37 this.listener = {};
38 }
39 this.listener[event] = fun;
40 },
41 downloadResource(reset) {
42 return new Promise((resolve, reject) => {
43 if (reset) {
44 this.downloadStatus = null;
45 }
46 const poster = this.selectComponent('#poster');
47 if (this.downloadStatus && this.downloadStatus !== 'fail') {
48 if (this.downloadStatus === 'success') {
49 resolve();
50 } else {
51 this.once('downloadSuccess', () => resolve());
52 this.once('downloadFail', (e) => reject(e));
53 }
54 } else {
55 poster.downloadResource(this.data.config.images)
56 .then(() => {
57 this.downloadStatus = 'success';
58 resolve();
59 })
60 .catch((e) => reject(e));
61 }
62 })
63 },
64 onCreate(reset = false) {
65 !this.data.hideLoading && wx.showLoading({ mask: true, title: '生成中' });
66 return this.downloadResource(typeof reset === 'boolean' && reset).then(() => {
67 !this.data.hideLoading && wx.hideLoading();
68 const poster = this.selectComponent('#poster');
69 poster.create(this.data.config);
70 })
71 .catch((err) => {
72 !this.data.hideLoading && wx.hideLoading();
73 wx.showToast({ icon: 'none', title: err.errMsg || '生成失败' });
74 console.error(err);
75 this.triggerEvent('fail', err);
76 })
77 },
78 onCreateSuccess(e) {
79 const { detail } = e;
80 this.triggerEvent('success', detail);
81 },
82 onCreateFail(err) {
83 console.error(err);
84 this.triggerEvent('fail', err);
85 }
86 }
87 })
...\ No newline at end of file ...\ No newline at end of file
1 {
2 "component": true,
3 "usingComponents": {
4 "we-canvas": "../index/index"
5 }
6 }
...\ No newline at end of file ...\ No newline at end of file
1 <view bindtap='onCreate'>
2 <slot/>
3 </view>
4 <we-canvas id="poster" bind:success="onCreateSuccess" bind:fail="onCreateFail"/>
...\ No newline at end of file ...\ No newline at end of file
1 const defaultOptions = {
2 selector: '#poster'
3 };
4
5 function Poster(options = {}) {
6 options = {
7 ...defaultOptions,
8 ...options,
9 };
10
11 const pages = getCurrentPages();
12 const ctx = pages[pages.length - 1];
13
14 const poster = ctx.selectComponent(options.selector);
15 delete options.selector;
16
17 return poster;
18 };
19
20 Poster.create = (reset = false) => {
21 const poster = Poster();
22 if (!poster) {
23 console.error('请设置组件的id="poster"!!!');
24 } else {
25 return Poster().onCreate(reset);
26 }
27 }
28
29 export default Poster;
...\ No newline at end of file ...\ No newline at end of file
...@@ -4,6 +4,7 @@ import { ...@@ -4,6 +4,7 @@ import {
4 } from '../../utils/util'; 4 } from '../../utils/util';
5 5
6 import Dialog from '../../ui/vant-weapp/dialog/dialog'; 6 import Dialog from '../../ui/vant-weapp/dialog/dialog';
7 import Poster from '../../miniprogram_dist/poster/poster';
7 8
8 const back = wx.getBackgroundAudioManager(); 9 const back = wx.getBackgroundAudioManager();
9 10
...@@ -41,7 +42,9 @@ Page({ ...@@ -41,7 +42,9 @@ Page({
41 isPlayingBgm: false, 42 isPlayingBgm: false,
42 isMore: false, 43 isMore: false,
43 44
44 // shortcutPics:["red-package2","more-bless"] 45 imageUrl: "", // 海报图
46 posterVisible: false,
47 wxCodeUrl: "", // 微信小程序码
45 }, 48 },
46 onShareAppMessage(res) { 49 onShareAppMessage(res) {
47 let shareType = "" 50 let shareType = ""
...@@ -128,7 +131,6 @@ Page({ ...@@ -128,7 +131,6 @@ Page({
128 */ 131 */
129 initData() {}, 132 initData() {},
130 playBgm() { 133 playBgm() {
131 return;
132 let _this = this; 134 let _this = this;
133 let { 135 let {
134 detailData 136 detailData
...@@ -192,11 +194,273 @@ Page({ ...@@ -192,11 +194,273 @@ Page({
192 } 194 }
193 }, 195 },
194 196
197 onHidePosterHandler() {
198 this.setData({
199 posterVisible: false
200 })
201 },
202
203 /**
204 * 图片查看
205 */
206 onPreviewImageHandler(evt) {
207 let current = this.data.imageUrl;
208 let urls = [current];
209 wx.previewImage({
210 current: current,
211 urls: urls
212 })
213 },
214
215 /**
216 * 保存图片到本地
217 */
218 saveImageToPhotosAlbum() {
219 let _this = this;
220 wx.saveImageToPhotosAlbum({
221 filePath: _this.data.imageUrl,
222 success(res) {
223 wx.showToast({
224 title: '保存成功',
225 icon: 'success'
226 });
227 },
228 fail(err) {
229 wx.getSetting({
230 success: (res) => {
231 if (!res.authSetting['scope.writePhotosAlbum']) {
232 // 未授权
233 wx.showModal({
234 title: '提示',
235 content: '小程序请求访问相册权限',
236 confirmText: '前往授权',
237 success(res) {
238 if (res.confirm) {
239 wx.openSetting({
240 success(res) {}
241 })
242 } else if (res.cancel) {}
243 }
244 })
245 }
246 }
247 })
248 }
249 })
250 },
251
252 // 配置小程序码
253 getWxCode() {
254 let {
255 detailData
256 } = this.data;
257 let memberCode = app.store.getItem("memberCode");
258 let wxCodePath = `pages/blessing/blessing?c=${detailData.blessCode}&m=${memberCode}`;
259
260 return new Promise((resolve, reject) => {
261 // 先获取小程序码
262 app.get({
263 mode: "common",
264 url: app.api.wxacodeGet,
265 data: {
266 path: encodeURIComponent(wxCodePath)
267 }
268 }).then((result) => {
269 this.setData({
270 wxCodeUrl: result
271 })
272 resolve(result);
273 })
274 });
275
276 },
277
195 /** 278 /**
196 * 分享图片祝福 279 * 分享图片祝福
197 * 生成海报 280 * 生成海报
198 */ 281 */
199 onPosterHandler(){ 282 onPosterHandler() {
283 console.log("onPosterHandler");
284
285 // 先获取小程序码
286 this.getWxCode().then((result) => {
287 let posterData = this.getPosterConfig();
288 this.onCreatePoster(posterData);
289 });
290 },
291
292 onPosterSuccess(e) {
293 wx.hideLoading();
294 const {
295 detail
296 } = e;
297 console.log("detail:", detail)
298 this.setData({
299 imageUrl: detail,
300 posterVisible: true,
301 })
302 },
303
304 onPosterFail(err) {
305 wx.hideLoading();
306 console.error(err);
307 },
308
309 /**
310 * 异步生成海报
311 */
312 onCreatePoster(posterConfig) {
313 console.log("posterConfig:", posterConfig);
314 this.setData({
315 posterConfig: posterConfig
316 }, () => {
317 Poster.create(true); // 入参:true为抹掉重新生成
318 });
319 },
320
321 // 获取海报数据
322 getPosterConfig() {
323 let {
324 detailData,
325 ownerMember,
326 memberList,
327 wxCodeUrl
328 } = this.data;
329 let avatarWid = 120;
330 let nickname = ownerMember.memberName;
331 let avatar = ownerMember.memberHead;
332 let desc = "";
333 if (detailData.type == 1) {
334 // 组队
335 if (detailData.count > 0) {
336 desc = `携${detailData.familyName}${detailData.count}人`
337 } else {
338 desc = `携${detailData.familyName}`
339 }
340 } else {
341 // 单人
342 desc = `向各位亲朋好友`
343 }
344 let blocks = []
345 let images = [
346 // 背景图
347 {
348 x: 0,
349 y: 0,
350 width: 750,
351 height: 1334,
352 url: "../../image/poster/poster_c1.png",
353 },
354 // 头像
355 {
356 x: 184,
357 y: 186,
358 width: avatarWid,
359 height: avatarWid,
360 borderRadius: avatarWid,
361 zIndex: 22,
362 url: avatar,
363 },
364 // 小程序码
365 {
366 width: 100,
367 height: 100,
368 x: 586,
369 y: 1198,
370 url: wxCodeUrl,
371 zIndex: 31
372 }
373 ];
374 let lines = [];
375 let texts = [
376 // 房主昵称
377 {
378 x: 53,
379 y: 460,
380 fontSize: 60,
381 color: "#fee085",
382 textAlign: "left",
383 zIndex: 11,
384 text: nickname,
385 },
386 // 文案
387 {
388 x: 53,
389 y: 540,
390 fontSize: 60,
391 color: "#fee085",
392 textAlign: "left",
393 zIndex: 11,
394 text: desc,
395 }
396 ];
397
398 // 组队补充
399 if (detailData.type == 1) {
400 // 头像昵称取出3人
401 let member1 = memberList[0] || null;
402 let member2 = memberList[1] || null;
403 let member3 = memberList[2] || null;
404
405 let memberDrawList = [];
406 if (member1) memberDrawList.push(member1);
407 if (member2) memberDrawList.push(member2);
408 if (member3) memberDrawList.push(member3);
409 let endX = 630;
410 let wid = 160;
411 let startY = 800;
412 memberDrawList.forEach((element, idx) => {
413 // blocks.push({
414 // x: endX - (idx * wid) - ((wid - 92)),
415 // y: startY,
416 // width: 92,
417 // height: 92,
418 // borderRadius: 92,
419 // zIndex: 90,
420 // borderColor: "#fee085",
421 // })
422 images.push({
423 x: endX - (idx * wid) - ((wid - 92) / 2) - 4,
424 y: startY + 2,
425 width: 92,
426 height: 92,
427 borderRadius: 92,
428 zIndex: 101,
429 url: avatar,
430 })
431 texts.push({
432 x: endX - (idx * wid),
433 y: startY + 136,
434 width: wid,
435 fontSize: 24,
436 color: "#fee085",
437 textAlign: "center",
438 zIndex: 101,
439 text: element.memberName,
440 })
441 });
442
443 texts.push({
444 x: 686,
445 y: startY + 200,
446 fontSize: 48,
447 color: "#fee085",
448 textAlign: "right",
449 zIndex: 11,
450 text: `长按查看全部${detailData.count}人 >>`,
451 })
452 }
453
454 let posterData = {
455 width: 750,
456 height: 1334,
457 debug: false,
458 blocks: blocks,
459 images: images,
460 lines: lines,
461 texts: texts,
462 }
463 return posterData;
200 }, 464 },
201 465
202 // 显示更新用户信息 466 // 显示更新用户信息
......
1 {} 1 {
2 "usingComponents": {
3 "poster": "/miniprogram_dist/poster/index"
4 }
5 }
......
...@@ -5,6 +5,10 @@ ...@@ -5,6 +5,10 @@
5 font-size: 36rpx; 5 font-size: 36rpx;
6 } 6 }
7 7
8 .van-popup {
9 background-color: transparent !important;
10 }
11
8 // 用户头像 12 // 用户头像
9 .portrait { 13 .portrait {
10 position: relative; 14 position: relative;
...@@ -65,6 +69,24 @@ ...@@ -65,6 +69,24 @@
65 text-align: center; 69 text-align: center;
66 } 70 }
67 71
72 .poster {
73 width: 540px;
74 }
75
76 .save-btn {
77 margin: 24px auto;
78 @include btc(290px, 76px);
79 color: #fee085;
80 font-size: 36px;
81 border-radius: 8px;
82 box-shadow: 5.9px 5.5px 18px 0 rgba(26, 36, 91, 0.73);
83 background-image: linear-gradient(to top, #b41d36, #bb2634);
84
85 color: #940023;
86 box-shadow: 5.9px 5.5px 18px 0 rgba(26, 36, 91, 0.73);
87 background-image: linear-gradient(to top, #f4b44d, #e8b976, #ffebb5);
88 }
89
68 .page { 90 .page {
69 padding-bottom: 200px; 91 padding-bottom: 200px;
70 92
...@@ -321,7 +343,7 @@ ...@@ -321,7 +343,7 @@
321 position: fixed; 343 position: fixed;
322 bottom: 0; 344 bottom: 0;
323 width: 100%; 345 width: 100%;
324 z-index: 101; 346 z-index: 21;
325 347
326 .bottom-bg { 348 .bottom-bg {
327 width: 750px; 349 width: 750px;
...@@ -362,7 +384,7 @@ ...@@ -362,7 +384,7 @@
362 position: fixed; 384 position: fixed;
363 right: 24px; 385 right: 24px;
364 top: 24px; 386 top: 24px;
365 z-index: 201; 387 z-index: 31;
366 } 388 }
367 389
368 390
...@@ -403,3 +425,7 @@ ...@@ -403,3 +425,7 @@
403 -webkit-transform: rotate(360deg); 425 -webkit-transform: rotate(360deg);
404 } 426 }
405 } 427 }
428
429 #poster {
430 position: absolute;
431 }
......
1 <poster id="poster" hide-loading="{{true}}" preload="{{false}}" config="{{posterConfig}}" bind:success="onPosterSuccess" bind:fail="onPosterFail"></poster>
1 <view class="page"> 2 <view class="page">
2 <view class="app__bgc bgc" style="background-color: {{detailData.background}};"></view> 3 <view class="app__bgc bgc" style="background-color: {{detailData.background}};"></view>
3 <!-- <view class="app__bg bg " style="background: url('{{detailData.backgroundImage}}')"></view> --> 4 <!-- <view class="app__bg bg " style="background: url('{{detailData.backgroundImage}}')"></view> -->
...@@ -67,7 +68,7 @@ ...@@ -67,7 +68,7 @@
67 <!-- 尾部头像 --> 68 <!-- 尾部头像 -->
68 <view class="portrait"> 69 <view class="portrait">
69 <image class="portrait-inner" mode="scaleToFill" src="{{ownerMember.memberHead}}" /> 70 <image class="portrait-inner" mode="scaleToFill" src="{{ownerMember.memberHead}}" />
70 <image class="portrait-border" mode="scaleToFill" src="{{detailData.headFrame}}" /> 71 <image class="portrait-border ani-rotation" mode="scaleToFill" src="{{detailData.headFrame}}" />
71 </view> 72 </view>
72 <view class="name"> 73 <view class="name">
73 <view class="tt t1">{{ownerMember.memberName}}</view> 74 <view class="tt t1">{{ownerMember.memberName}}</view>
...@@ -185,4 +186,11 @@ ...@@ -185,4 +186,11 @@
185 <view class="t1">邀请你一起加入组队送祝福!</view> 186 <view class="t1">邀请你一起加入组队送祝福!</view>
186 </view> 187 </view>
187 </van-dialog> 188 </van-dialog>
189 <van-popup show="{{ authorizeVisible }}">
190 <authorize-comp bind:evtcomp="evtcomp"></authorize-comp>
191 </van-popup>
192 <van-popup bind:click-overlay="onHidePosterHandler" show="{{ posterVisible }}">
193 <image bindtap="onPreviewImageHandler" class="poster" mode="widthFix" src="{{imageUrl}}" />
194 <view bindtap="saveImageToPhotosAlbum" class="save-btn">一键保存</view>
195 </van-popup>
188 <shortcut2 types="{{[]}}" pics="{{['red-package2','more-bless']}}"></shortcut2> 196 <shortcut2 types="{{[]}}" pics="{{['red-package2','more-bless']}}"></shortcut2>
......