card.vue 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. <template>
  2. <view class="content">
  3. <view class="inner">
  4. <view class="bg">
  5. <image class="w622 h622" src="@/static/image/bg_invitecard.png" mode=""></image>
  6. <view class="infor">
  7. <view class="title one-t">康复医学概论</view>
  8. <view class="name-title">
  9. <image class="w32 h32 " src="@/static/image/icon_doctor_fill.png" mode=""></image>
  10. <view class="one-t">王小明 | 消化内科 | 北京人民医院</view>
  11. </view>
  12. <view class="time-title">
  13. <image class="w32 h32 " src="@/static/image/icon_doctor_fill.png" mode=""></image>
  14. <view class="one-t">2025年12月13日 18:00</view>
  15. </view>
  16. </view>
  17. </view>
  18. <view class="code">
  19. <image class="w176 h176 " src="@/static/image/icon_doctor_fill.png" mode=""></image>
  20. <view class="tips">长按识别二维码,观看直播</view>
  21. </view>
  22. </view>
  23. <view class="share-box">
  24. <view class="weixin" style="margin-right: 176rpx;">
  25. <image class="w100 h100 " src="@/static/image/icon_share_wechat.png" mode=""></image>
  26. <text>微信好友</text>
  27. <button class="share" data-name="shareBtn" open-type="share"></button>
  28. </view>
  29. <view class="setimg" @tap="saveInviteCard">
  30. <image class="w100 h100 " src="@/static/image/icon_share_save.png" mode=""></image>
  31. <text>保存图片</text>
  32. </view>
  33. </view>
  34. <canvas type="2d" id="inviteCardCanvas" style="width: 622rpx; height: 920rpx; position: fixed; top: -9999rpx; left: -9999rpx;"></canvas>
  35. </view>
  36. </template>
  37. <script>
  38. import {
  39. getCompetitorById,saveCompetitor,updateCompetitor
  40. } from '@/api/companyUser.js'
  41. export default {
  42. data() {
  43. return {
  44. bgImg: "/static/image/bg_invitecard.png",
  45. doctorIcon: "/static/image/icon_doctor_fill.png",
  46. qrCodeImg: "/static/image/icon_doctor_fill.png", // 替换为真实二维码
  47. wechatIcon: "/static/image/icon_share_wechat.png",
  48. saveIcon: "/static/image/icon_share_save.png"
  49. };
  50. },
  51. onLoad(options) {
  52. // this.type = option.type;
  53. // this.form.userId=options.userId
  54. // this.form.companyUserId=options.companyUserId;
  55. // console.log(this.type)
  56. // if (this.type == 'edit') {
  57. // this.id=option.id;
  58. // this.getCompetitorInfo();
  59. // }
  60. uni.showShareMenu({
  61. withShareTicket:true,
  62. //小程序的原生菜单中显示分享按钮,才能够让发送给朋友与分享到朋友圈两个按钮可以点击
  63. menus:["shareAppMessage"] //不设置默认发送给朋友
  64. })
  65. },
  66. //发送给朋友
  67. onShareAppMessage(res) {
  68. // if(this.utils.isLogin()){
  69. // var user=JSON.parse( uni.getStorageSync('userInfo'))
  70. // return {
  71. // title: this.product.productName,
  72. // path: '/pages/shopping/productDetails?productId='+this.product.productId+"&userId="+user.userId,
  73. // imageUrl: 'https://user.test.ylrztop.com/images/logo.png' //分享图标,路径可以是本地文件路径、代码包文件路径或者网络图片路径.支持PNG及JPG。显示图片长宽比是 5:4
  74. // }
  75. // }
  76. },
  77. methods: {
  78. // 保存邀请卡图片
  79. async saveInviteCard() {
  80. try {
  81. uni.showLoading({ title: "生成图片中..." });
  82. // 1. 获取canvas上下文(Uniapp兼容写法)
  83. const query = uni.createSelectorQuery().in(this);
  84. const canvasRes = await new Promise((resolve) => {
  85. query.select("#inviteCardCanvas").node().exec((res) => {
  86. resolve(res[0]);
  87. });
  88. });
  89. if (!canvasRes.node) {
  90. throw new Error("获取Canvas失败");
  91. }
  92. const canvas = canvasRes.node;
  93. const ctx = canvas.getContext("2d");
  94. // 2. 适配设备像素比,保证图片高清
  95. const systemInfo = uni.getSystemInfoSync();
  96. const dpr = systemInfo.pixelRatio || 1;
  97. canvas.width = 622 * dpr;
  98. canvas.height = 920 * dpr; // 与canvas样式高度统一
  99. ctx.scale(dpr, dpr);
  100. // 3. 加载并绘制背景图
  101. const bgImg = await this.loadImage(this.bgImg,canvas);
  102. ctx.drawImage(bgImg, 0, 0, 622, 622);
  103. // 4. 绘制文字(标题/医生/时间)
  104. ctx.font = "bold 32rpx PingFang SC"; // 匹配样式中的字体和大小
  105. ctx.fillStyle = "#FFFFFF";
  106. ctx.fillText("康复医学概论", 46, 580); // 调整坐标匹配UI布局
  107. ctx.font = "24rpx PingFang SC";
  108. ctx.fillText("王小明 | 消化内科 | 北京人民医院", 46, 616);
  109. ctx.fillText("2025年12月13日 18:00", 46, 652);
  110. // 5. 绘制二维码
  111. const qrImg = await this.loadImage(this.qrCodeImg,canvas);
  112. const qrX = (622 - 176) / 2; // 二维码居中
  113. ctx.drawImage(qrImg, qrX, 622 + 32, 176, 176); // 匹配code区域的padding
  114. // 6. 绘制二维码提示文字
  115. ctx.font = "24rpx PingFang SC";
  116. ctx.fillStyle = "#999999";
  117. const tipsText = "长按识别二维码,观看直播";
  118. const textWidth = ctx.measureText(tipsText).width;
  119. const tipsX = (622 - textWidth) / 2;
  120. ctx.fillText(tipsText, tipsX, 622 + 32 + 176 + 16); // 匹配tips的margin-top
  121. // 7. 导出canvas为临时图片
  122. const tempFilePath = await new Promise((resolve, reject) => {
  123. uni.canvasToTempFilePath({
  124. canvas: canvas,
  125. x: 0,
  126. y: 0,
  127. width: 622,
  128. height: 920,
  129. destWidth: 622 * dpr,
  130. destHeight: 920 * dpr,
  131. success: (res) => resolve(res.tempFilePath),
  132. fail: (err) => reject(err)
  133. }, this);
  134. });
  135. uni.hideLoading();
  136. // 8. 申请相册权限并保存图片
  137. try {
  138. await uni.authorize({ scope: "scope.writePhotosAlbum" });
  139. } catch (authErr) {
  140. // 授权失败时引导用户手动开启
  141. uni.showModal({
  142. title: "提示",
  143. content: "需要相册权限才能保存图片,请前往设置开启",
  144. confirmText: "去设置",
  145. success: (res) => {
  146. if (res.confirm) uni.openSetting();
  147. }
  148. });
  149. return;
  150. }
  151. await uni.saveImageToPhotosAlbum({ filePath: tempFilePath });
  152. uni.showToast({ title: "图片保存成功", icon: "success" });
  153. } catch (err) {
  154. uni.hideLoading();
  155. uni.showToast({ title: "保存失败", icon: "none" });
  156. console.error("保存图片失败:", err);
  157. }
  158. },
  159. // 优化图片加载方法,适配Uniapp静态资源
  160. loadImage(src,canvas) {
  161. return new Promise((resolve, reject) => {
  162. // 处理@/static路径为绝对路径
  163. // const realSrc = src.replace("@", "");
  164. const img = canvas.createImage();
  165. // 小程序环境下添加跨域标识
  166. img.crossOrigin = "anonymous";
  167. img.src = src;
  168. img.onload = () => resolve(img);
  169. img.onerror = (err) => reject(err);
  170. });
  171. }
  172. }
  173. }
  174. </script>
  175. <style lang="scss">
  176. page {
  177. height: 100%;
  178. }
  179. .content {
  180. height: 100%;
  181. display: flex;
  182. flex-direction: column;
  183. .inner{
  184. width: 622rpx;
  185. height: 920rpx;
  186. margin: 64rpx;
  187. background: #FFFFFF;
  188. box-shadow: 0rpx 8rpx 32rpx 0rpx rgba(170,168,168,0.12);
  189. border-radius: 24rpx 24rpx 24rpx 24rpx;
  190. .bg{
  191. position: relative;
  192. // padding: 32rpx 46rpx;
  193. .infor{
  194. width: 100%;
  195. position: absolute;
  196. display: flex;
  197. flex-direction: column;
  198. align-items: flex-start;
  199. padding-left:46rpx;
  200. bottom: 24rpx;
  201. .title{
  202. font-family: PingFang SC, PingFang SC;
  203. font-weight: 600;
  204. font-size: 32rpx;
  205. color: #FFFFFF;
  206. margin-bottom: 26rpx;
  207. width: 80%;
  208. }
  209. .name-title,.time-title{
  210. width: 100%;
  211. display: flex;
  212. align-items: center;
  213. font-family: PingFang SC, PingFang SC;
  214. font-weight: 600;
  215. font-size: 24rpx;
  216. color: #FFFFFF;
  217. line-height: 36rpx;
  218. margin-bottom: 8rpx;
  219. image{
  220. margin-right: 14rpx;
  221. }
  222. .one-t{
  223. width: 80%;
  224. }
  225. }
  226. }
  227. }
  228. }
  229. .code{
  230. padding: 32rpx;
  231. display: flex;
  232. flex-direction: column;
  233. align-items: center;
  234. background: #FFFFFF;
  235. .tips{
  236. margin-top: 16rpx;
  237. font-family: PingFang SC, PingFang SC;
  238. font-weight: 400;
  239. font-size: 24rpx;
  240. color: #999999;
  241. line-height: 36rpx;
  242. }
  243. }
  244. .share-box{
  245. margin-top: 108rpx;
  246. display: flex;
  247. align-items: center;
  248. justify-content: center;
  249. .weixin,.setimg{
  250. display: flex;
  251. align-items: center;
  252. flex-direction: column;
  253. position: relative;
  254. text{
  255. margin-top: 18rpx;
  256. font-family: PingFang SC, PingFang SC;
  257. font-weight: 400;
  258. font-size: 28rpx;
  259. color: #666666;
  260. line-height: 44rpx;
  261. }
  262. }
  263. .share{
  264. display: inline-block;
  265. position: absolute;
  266. top: 0;
  267. left: 0;
  268. width: 100%;
  269. height: 100%;
  270. opacity: 0;
  271. }
  272. }
  273. }
  274. </style>