index.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. // invitationCode/index.js
  2. const app = getApp();
  3. import util from '../utils/util.js'
  4. Page({
  5. /**
  6. * 页面的初始数据
  7. */
  8. data: {
  9. appAssetsUrl: app.appAssetsUrl,
  10. appAssetsUrl3: app.appAssetsUrl3,
  11. bottomLeft: app.bottomLeft,
  12. user: {},
  13. name: '',
  14. memberphoto: '',
  15. inviteCode: '',
  16. invitationCodeImg: ''
  17. },
  18. /**
  19. * 生命周期函数--监听页面加载
  20. */
  21. onLoad: function (options) {
  22. let that = this;
  23. const user = wx.getStorageSync("USER");
  24. this.setData({
  25. name: options.name ? options.name : (user.name ? user.name : user.vipname),
  26. inviteCode: options.inviteCode ? options.inviteCode : user.inviteCode,
  27. memberphoto: options.memberphoto ? options.memberphoto : user.memberphoto,
  28. user
  29. })
  30. if (wx.getStorageSync('invitationCodeImg')) {
  31. that.setData({
  32. invitationCodeImg: wx.getStorageSync('invitationCodeImg'),
  33. });
  34. } else {
  35. this.getCode();
  36. }
  37. wx.setNavigationBarTitle({
  38. title: options.inviteCode ? '他/她的会员码' : '会员码'
  39. });
  40. },
  41. downloadQR() {
  42. console.log('下载海报');
  43. const that = this;
  44. wx.getSetting({ //获取权限
  45. success(res) {
  46. console.log(res);
  47. if (res.authSetting["scope.writePhotosAlbum"]) {
  48. that.createPoster();
  49. } else if (res.authSetting["scope.writePhotosAlbum"] == false) {
  50. wx.showToast({
  51. title: '请先授权相册',
  52. icon: 'none'
  53. })
  54. wx.openSetting({
  55. scope: "scope.writePhotosAlbum",
  56. success() {
  57. that.createPoster();
  58. }
  59. });
  60. } else {
  61. wx.authorize({
  62. scope: "scope.writePhotosAlbum",
  63. success() {
  64. that.createPoster();
  65. },
  66. fail(e) {
  67. console.log('未授权相册', e);
  68. wx.showToast({
  69. title: '请先授权相册',
  70. icon: 'none'
  71. })
  72. }
  73. });
  74. }
  75. }
  76. });
  77. },
  78. // 生成海报
  79. createPoster() {
  80. wx.showLoading({
  81. title: '生成海报中...',
  82. });
  83. const that = this;
  84. // 获取背景图片的实际尺寸
  85. const bgUrl = that.data.appAssetsUrl3 + 'invitation_code_bg.png';
  86. // 使用背景图片的实际尺寸作为画布尺寸
  87. const canvasWidth = 750;
  88. const canvasHeight = 1334;
  89. // 计算缩放比例
  90. const scale = Math.min(canvasWidth, canvasHeight) / Math.min(canvasWidth, canvasHeight);
  91. // 创建canvas绘图上下文
  92. const ctx = wx.createCanvasContext('myCanvas', that);
  93. // 绘制背景图片,沾满整个画布
  94. that.drawImageWithRetry(ctx, bgUrl, 0, 0, canvasWidth, canvasHeight, false, () => {
  95. // 背景图片绘制完成后继续绘制其他元素
  96. // 绘制完成,导出图片
  97. ctx.draw(false, () => {
  98. // 绘制用户头像
  99. const avatarUrl = that.data.memberphoto ? that.data.memberphoto : (that.data.appAssetsUrl + '/images/bz1_nor.png');
  100. // 按比例调整头像尺寸和位置
  101. const avatarSize = 99 * scale; // 99rpx
  102. const avatarX = 40 * scale;
  103. const avatarY = 40 * scale;
  104. that.drawImageWithRetry(ctx, avatarUrl, avatarX, avatarY, avatarSize, avatarSize, true, () => {
  105. // 绘制用户名 (与头像对齐并排展示)
  106. ctx.setTextAlign('left');
  107. ctx.setFillStyle('#ffffff');
  108. ctx.setFontSize(30 * scale); // 字体大小
  109. // 文字垂直居中对齐头像
  110. const textY = avatarY + avatarSize / 2 - 10 * scale;
  111. ctx.fillText(that.data.name || '--', avatarX + avatarSize + 20 * scale, textY);
  112. // 绘制邀请码 (与头像对齐并排展示)
  113. ctx.setFontSize(28 * scale); // 字体大小
  114. ctx.fillText('邀请码: ' + (that.data.inviteCode || ''), avatarX + avatarSize + 20 * scale, textY + 35 * scale);
  115. // 头像和文字绘制完成后绘制二维码
  116. if (that.data.invitationCodeImg) {
  117. // 按比例调整二维码尺寸和位置,保持居中
  118. const qrSize = 148 * scale; // 148rpx
  119. const qrX = (canvasWidth - qrSize) / 2; // 居中显示
  120. const qrY = canvasHeight - 200 * scale - 20 * scale; // 向上移动20px
  121. that.drawImageWithRetry(ctx, that.data.invitationCodeImg, qrX, qrY, qrSize, qrSize, false, () => {
  122. // 二维码绘制完成后绘制底部文字,保持居中
  123. ctx.setTextAlign('center');
  124. ctx.setFillStyle('#ffffff');
  125. ctx.setFontSize(30 * scale);
  126. ctx.fillText('青雲慧青年服务平台', canvasWidth / 2, canvasHeight - 30 * scale);
  127. // 全部绘制完成,导出图片
  128. ctx.draw(true, () => {
  129. that.exportCanvasToImage();
  130. });
  131. });
  132. } else {
  133. // 没有二维码时绘制底部文字,保持居中
  134. ctx.setTextAlign('center');
  135. ctx.setFillStyle('#ffffff');
  136. ctx.setFontSize(30 * scale);
  137. ctx.fillText('青雲慧青年服务平台', canvasWidth / 2, canvasHeight - 30 * scale);
  138. // 全部绘制完成,导出图片
  139. ctx.draw(true, () => {
  140. that.exportCanvasToImage();
  141. });
  142. }
  143. });
  144. });
  145. });
  146. },
  147. // 带重试机制的图片绘制方法
  148. drawImageWithRetry(ctx, imageUrl, x, y, width, height, isAvatar, callback) {
  149. const that = this;
  150. // 检查是否为base64格式的图片数据
  151. if (imageUrl && (imageUrl.startsWith('data:image') || imageUrl.startsWith('/9j/'))) {
  152. // 处理base64格式的图片
  153. that.drawBase64Image(ctx, imageUrl, x, y, width, height, isAvatar, callback);
  154. return;
  155. }
  156. // 如果imageUrl为空或无效,直接回调
  157. if (!imageUrl) {
  158. callback && callback();
  159. return;
  160. }
  161. wx.getImageInfo({
  162. src: imageUrl,
  163. success: function (res) {
  164. try {
  165. // 检查是否为二维码图片,如果是则添加圆角
  166. if (!isAvatar && that.data.invitationCodeImg && imageUrl === that.data.invitationCodeImg) {
  167. // 为二维码添加5px圆角
  168. ctx.save();
  169. ctx.beginPath();
  170. ctx.moveTo(x + 5, y);
  171. ctx.lineTo(x + width - 5, y);
  172. ctx.quadraticCurveTo(x + width, y, x + width, y + 5);
  173. ctx.lineTo(x + width, y + height - 5);
  174. ctx.quadraticCurveTo(x + width, y + height, x + width - 5, y + height);
  175. ctx.lineTo(x + 5, y + height);
  176. ctx.quadraticCurveTo(x, y + height, x, y + height - 5);
  177. ctx.lineTo(x, y + 5);
  178. ctx.quadraticCurveTo(x, y, x + 5, y);
  179. ctx.closePath();
  180. ctx.clip();
  181. ctx.drawImage(res.path, x, y, width, height);
  182. ctx.restore();
  183. } else if (isAvatar) {
  184. // 如果是头像,需要圆形裁剪
  185. ctx.save();
  186. ctx.beginPath();
  187. ctx.arc(x + width / 2, y + height / 2, width / 2, 0, 2 * Math.PI);
  188. ctx.clip();
  189. ctx.drawImage(res.path, x, y, width, height);
  190. ctx.restore();
  191. } else {
  192. // 普通图片直接绘制
  193. ctx.drawImage(res.path, x, y, width, height);
  194. }
  195. callback && callback();
  196. } catch (e) {
  197. console.log('绘制图片失败', e);
  198. callback && callback();
  199. }
  200. },
  201. fail: function (err) {
  202. console.log('获取图片失败', err, imageUrl);
  203. // 如果是头像且获取失败,使用默认头像
  204. if (isAvatar) {
  205. const defaultAvatar = that.data.appAssetsUrl + '/images/bz1_nor.png';
  206. if (defaultAvatar !== imageUrl) {
  207. // 重试默认头像
  208. that.drawImageWithRetry(ctx, defaultAvatar, x, y, width, height, true, callback);
  209. } else {
  210. // 默认头像也失败了,直接回调
  211. callback && callback();
  212. }
  213. } else {
  214. // 非头像图片失败,直接回调
  215. callback && callback();
  216. }
  217. }
  218. });
  219. },
  220. // 绘制base64格式的图片
  221. drawBase64Image(ctx, base64Data, x, y, width, height, isAvatar, callback) {
  222. try {
  223. const that = this;
  224. // 创建文件管理器
  225. const fsm = wx.getFileSystemManager();
  226. // 生成临时文件路径
  227. const filePath = `${wx.env.USER_DATA_PATH}/temp_image_${Date.now()}_${Math.floor(Math.random() * 1000)}.png`;
  228. // 清理base64数据
  229. let imageData = base64Data;
  230. if (imageData.includes('data:image')) {
  231. imageData = imageData.split(',')[1];
  232. }
  233. // 将base64数据写入临时文件
  234. fsm.writeFile({
  235. filePath: filePath,
  236. data: imageData,
  237. encoding: 'base64',
  238. success: function () {
  239. try {
  240. // 绘制图片
  241. if (isAvatar) {
  242. // 如果是头像,需要圆形裁剪
  243. ctx.save();
  244. ctx.beginPath();
  245. ctx.arc(x + width / 2, y + height / 2, width / 2, 0, 2 * Math.PI);
  246. ctx.clip();
  247. ctx.drawImage(filePath, x, y, width, height);
  248. ctx.restore();
  249. } else {
  250. // 普通图片直接绘制
  251. ctx.drawImage(filePath, x, y, width, height);
  252. }
  253. // 延迟删除临时文件,确保绘制完成
  254. setTimeout(() => {
  255. try {
  256. fsm.unlinkSync(filePath);
  257. } catch (e) {
  258. console.log('删除临时文件失败', e);
  259. }
  260. }, 1000);
  261. callback && callback();
  262. } catch (e) {
  263. console.log('绘制base64图片失败', e);
  264. // 清理临时文件
  265. try {
  266. fsm.unlinkSync(filePath);
  267. } catch (e) {
  268. console.log('删除临时文件失败', e);
  269. }
  270. callback && callback();
  271. }
  272. },
  273. fail: function (err) {
  274. console.log('写入base64图片失败', err);
  275. callback && callback();
  276. }
  277. });
  278. } catch (e) {
  279. console.log('处理base64图片异常', e);
  280. callback && callback();
  281. }
  282. },
  283. // 导出canvas到图片
  284. exportCanvasToImage() {
  285. const that = this;
  286. wx.canvasToTempFilePath({
  287. canvasId: 'myCanvas',
  288. success: function (res) {
  289. wx.hideLoading();
  290. // 保存图片到相册
  291. that.saveImageToPhotosAlbum(res.tempFilePath);
  292. },
  293. fail: function (err) {
  294. wx.hideLoading();
  295. console.log('生成海报失败', err);
  296. wx.showToast({
  297. title: '生成海报失败',
  298. icon: 'none'
  299. });
  300. }
  301. }, that);
  302. },
  303. // 保存图片到相册
  304. saveImageToPhotosAlbum(filePath) {
  305. wx.saveImageToPhotosAlbum({
  306. filePath: filePath,
  307. success: function () {
  308. wx.showToast({
  309. title: '已保存到相册',
  310. icon: 'success'
  311. });
  312. },
  313. fail: function (err) {
  314. console.log('保存失败', err);
  315. wx.showToast({
  316. title: '保存失败',
  317. icon: 'none'
  318. });
  319. }
  320. });
  321. },
  322. //将base64图片转网络图片
  323. sendCode() {
  324. let code = this.data.invitationCodeImg;
  325. let qrcode = code.replace(/\. +/g, '').replace(/[\r\n]/g, '')
  326. /*code是指图片base64格式数据*/
  327. //声明文件系统
  328. const fs = wx.getFileSystemManager();
  329. //随机定义路径名称
  330. var times = new Date().getTime();
  331. var filePath = wx.env.USER_DATA_PATH + '/' + times + '.png';
  332. //将base64图片写入
  333. fs.writeFile({
  334. filePath,
  335. data: qrcode.slice(22),
  336. encoding: 'base64',
  337. success: () => {
  338. wx.saveImageToPhotosAlbum({
  339. filePath,
  340. success: function (res) {
  341. wx.showToast({
  342. title: '已保存图片',
  343. icon: 'none'
  344. })
  345. }
  346. });
  347. }
  348. });
  349. },
  350. handleInvite() {
  351. wx.navigateTo({
  352. url: "/pages/my/myInvite/myInvite",
  353. });
  354. },
  355. /**
  356. * 生命周期函数--监听页面初次渲染完成
  357. */
  358. onReady: function () {
  359. },
  360. /**
  361. * 生命周期函数--监听页面显示
  362. */
  363. onShow: function () {
  364. },
  365. /**
  366. * 生命周期函数--监听页面隐藏
  367. */
  368. onHide: function () {
  369. },
  370. /**
  371. * 生命周期函数--监听页面卸载
  372. */
  373. onUnload: function () {
  374. },
  375. /**
  376. * 页面相关事件处理函数--监听用户下拉动作
  377. */
  378. onPullDownRefresh: function () {
  379. },
  380. /**
  381. * 页面上拉触底事件的处理函数
  382. */
  383. onReachBottom: function () {
  384. },
  385. /**
  386. * 用户点击右上角分享
  387. */
  388. onShareAppMessage: function () {
  389. this.addScore();
  390. return {
  391. title: '青雲慧小程序-邀请码分享',
  392. path: `/pages/login?inviteCode=${this.data.inviteCode}`
  393. }
  394. },
  395. //统计积分(每日小程序分享)
  396. addScore: function () {
  397. if (!util.getUserId()) {
  398. return;
  399. }
  400. wx.showLoading({
  401. title: '努力加载中...',
  402. })
  403. app._post_form('scoreStu/share', "", {
  404. stuId: util.getUserId()
  405. }, function (res) {
  406. if (res.code === 0) { }
  407. })
  408. },
  409. getCode() {
  410. let that = this;
  411. wx.showLoading({
  412. title: '努力加载中...',
  413. })
  414. app._post_form('create/wxaqrcode', '', {
  415. inviteCode: that.data.inviteCode
  416. },
  417. function (res) {
  418. if (res.code == 0) {
  419. that.setData({
  420. invitationCodeImg: res.data
  421. })
  422. wx.setStorageSync('invitationCodeImg', res.data);
  423. }
  424. })
  425. },
  426. copyText() {
  427. wx.setClipboardData({
  428. data: this.data.inviteCode || '',
  429. success: function (res) {
  430. wx.showToast({
  431. title: '已复制邀请码',
  432. icon: 'none'
  433. })
  434. },
  435. fail: function (err) {
  436. console.log(err)
  437. wx.showToast({
  438. title: '复制失败',
  439. icon: 'none'
  440. })
  441. }
  442. })
  443. },
  444. /**
  445. * 将网络图片转换为base64格式
  446. * @param {string} url - 网络图片地址
  447. * @returns {Promise<string>} base64格式字符串
  448. */
  449. convertImageToBase64(url) {
  450. return new Promise((resolve, reject) => {
  451. // 下载网络图片
  452. wx.downloadFile({
  453. url: url,
  454. success: (res) => {
  455. if (res.statusCode === 200) {
  456. const tempFilePath = res.tempFilePath;
  457. // 读取本地文件内容并转换为base64
  458. const fs = wx.getFileSystemManager();
  459. fs.readFile({
  460. filePath: tempFilePath,
  461. encoding: 'base64',
  462. success: (readRes) => {
  463. // 构造完整的base64数据URI
  464. const base64Data = 'data:image/png;base64,' + readRes.data;
  465. resolve(base64Data);
  466. },
  467. fail: (error) => {
  468. console.error('读取图片文件失败:', error);
  469. reject(error);
  470. }
  471. });
  472. } else {
  473. reject(new Error('下载图片失败'));
  474. }
  475. },
  476. fail: (error) => {
  477. console.error('下载图片失败:', error);
  478. reject(error);
  479. }
  480. });
  481. });
  482. }
  483. })