partner-home.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. <template>
  2. <view class="partner-home">
  3. <!-- 头部信息 -->
  4. <view class="header">
  5. <image class="avatar" src="/static/img/logo.png" mode="aspectFit"></image>
  6. <view class="header-text">
  7. <text class="title">您好,合伙人</text>
  8. <text class="subtitle">这里是合伙人数据中心</text>
  9. </view>
  10. <navigator url="/pages-mine/pages/partner/partner-rule" class="rule-btn">合伙人规则</navigator>
  11. </view>
  12. <view class="content" style="margin-top: -180rpx">
  13. <!-- 收入统计 -->
  14. <view class="income-section">
  15. <view class="section-header">
  16. <text class="section-title">收入统计</text>
  17. <text class="tip">每月20号到账上月结算收益</text>
  18. </view>
  19. <view class="income-grid">
  20. <view class="income-item">
  21. <text class="amount">¥{{ detail.totalIncome || "0.00" }}</text>
  22. <text class="label">累计收入</text>
  23. </view>
  24. <view class="income-item">
  25. <text class="amount">¥{{ detail.todayEstimateIncome || "0.00" }}</text>
  26. <text class="label">今日预估</text>
  27. </view>
  28. <view class="income-item">
  29. <text class="amount">¥{{ detail.lastMontyEstimateIncome || "0.00" }}</text>
  30. <text class="label">上月预估</text>
  31. </view>
  32. <view class="income-item">
  33. <text class="amount">¥{{ detail.thisMontyEstimateIncome || "0.00" }}</text>
  34. <text class="label">本月预估</text>
  35. </view>
  36. <view class="income-item">
  37. <text class="amount">¥{{ detail.lastMontySettlement || "0.00" }}</text>
  38. <text class="label">上月结算</text>
  39. </view>
  40. <view class="income-item">
  41. <text class="amount">¥{{ detail.waitAccount || "0.00" }}</text>
  42. <text class="label">待到账</text>
  43. </view>
  44. </view>
  45. </view>
  46. <!-- 实用工具 -->
  47. <view class="tools-section">
  48. <text class="section-title">实用工具</text>
  49. <view class="tools-grid">
  50. <navigator url="/pages-mine/pages/partner/income-detail" class="tool-item">
  51. <image src="/pages-mine/static/partner1.png" mode="aspectFit"></image>
  52. <text>收入明细</text>
  53. </navigator>
  54. <navigator url="/pages-mine/pages/partner/order-detail" class="tool-item">
  55. <image src="/pages-mine/static/partner2.png" mode="aspectFit"></image>
  56. <text>订单明细</text>
  57. </navigator>
  58. </view>
  59. </view>
  60. <!-- 生成海报按钮 -->
  61. <button class="generate-poster" @click="generatePoster" :loading="loading">生成专属二维码海报</button>
  62. <!-- 添加canvas元素 -->
  63. <canvas
  64. canvas-id="posterCanvas"
  65. style="width: 750px; height: 1334px; position: fixed; left: -9999px"
  66. ></canvas>
  67. <!-- 添加海报弹窗 -->
  68. <u-popup v-model="showPoster" mode="center" border-radius="16" :custom-style="posterStyle">
  69. <view class="poster-container">
  70. <image
  71. class="poster-image"
  72. :src="posterInfo.tempFilePath || posterInfo.background"
  73. mode="aspectFit"
  74. @longpress="saveImage"
  75. ></image>
  76. <view class="poster-tip">长按图片保存至相册分享</view>
  77. </view>
  78. </u-popup>
  79. </view>
  80. </view>
  81. </template>
  82. <script>
  83. export default {
  84. data() {
  85. return {
  86. detail: {
  87. totalIncome: 0,
  88. todayEstimateIncome: 0,
  89. lastMontyEstimateIncome: 0,
  90. thisMontyEstimateIncome: 0,
  91. lastMontySettlement: 0,
  92. waitAccount: 0,
  93. },
  94. showPoster: false,
  95. posterStyle: {
  96. backgroundColor: "transparent",
  97. },
  98. posterInfo: {},
  99. loading: false,
  100. };
  101. },
  102. onLoad() {
  103. this.getPartnerInfo();
  104. },
  105. methods: {
  106. getPartnerInfo() {
  107. uni.$u.get("/token/getUserPartnerInfo").then((res) => {
  108. if (res.code == 200) {
  109. this.detail = res.data;
  110. }
  111. });
  112. },
  113. //生成海报
  114. generatePoster() {
  115. this.loading = true;
  116. this.getPosterInfo().then((data) => {
  117. console.log("海报数据:", data);
  118. let userInfo = uni.getStorageSync("userInfo");
  119. // 下载背景图片
  120. uni.downloadFile({
  121. url: data.background,
  122. success: (downloadRes) => {
  123. const ctx = uni.createCanvasContext("posterCanvas");
  124. const dpr = uni.getSystemInfoSync().pixelRatio || 1;
  125. // 设置canvas尺寸为原图3倍,提高清晰度
  126. const canvasWidth = 750;
  127. const canvasHeight = 1334;
  128. // 绘制背景图
  129. ctx.drawImage(downloadRes.tempFilePath, 0, 0, canvasWidth, canvasHeight);
  130. // 下载并绘制二维码
  131. uni.downloadFile({
  132. url: data.inviteUrl,
  133. success: (qrRes) => {
  134. // 根据比例调整坐标
  135. const scale = canvasWidth / 250;
  136. const adjustX = (x) => x * scale;
  137. const adjustY = (y) => y * scale;
  138. const adjustSize = (size) => size * scale;
  139. // 绘制微信头像
  140. ctx.save();
  141. ctx.beginPath();
  142. ctx.arc(
  143. adjustX(data.headImgPosX),
  144. adjustY(data.headImgPosY),
  145. adjustSize(20),
  146. 0,
  147. 2 * Math.PI
  148. );
  149. ctx.clip();
  150. let avatar = userInfo.imgPath || "https://shuhi.oss-cn-qingdao.aliyuncs.com/mini/t11.png";
  151. ctx.drawImage(
  152. avatar,
  153. adjustX(data.headImgPosX),
  154. adjustY(data.headImgPosY),
  155. adjustSize(30),
  156. adjustSize(30)
  157. );
  158. ctx.restore();
  159. // 绘制微信昵称
  160. ctx.setFontSize(adjustSize(14));
  161. ctx.setFillStyle("#333333");
  162. let nickName = userInfo.nickName || "书嗨";
  163. let canvasStr = `${nickName}邀请你加入书嗨合伙人`;
  164. ctx.fillText(canvasStr, adjustX(data.nickNamePosX), adjustY(data.nickNamePosY));
  165. // 绘制二维码
  166. ctx.save(); // Save the current canvas state
  167. ctx.beginPath();
  168. // Create a circular clipping path for QR code
  169. ctx.arc(
  170. adjustX(data.qrCodePosX + data.qrCodeWidth / 2),
  171. adjustY(data.qrCodePosY + data.qrCodeHeight / 2),
  172. adjustSize(data.qrCodeWidth / 2),
  173. 0,
  174. 2 * Math.PI
  175. );
  176. ctx.clip(); // Apply the clipping path
  177. ctx.drawImage(
  178. qrRes.tempFilePath,
  179. adjustX(data.qrCodePosX),
  180. adjustY(data.qrCodePosY),
  181. adjustSize(data.qrCodeWidth),
  182. adjustSize(data.qrCodeHeight)
  183. );
  184. ctx.restore(); // Restore the canvas state
  185. // 执行绘制
  186. ctx.draw(false, () => {
  187. setTimeout(() => {
  188. // 将画布内容保存为图片
  189. uni.canvasToTempFilePath({
  190. canvasId: "posterCanvas",
  191. width: canvasWidth,
  192. height: canvasHeight,
  193. destWidth: canvasWidth * dpr,
  194. destHeight: canvasHeight * dpr,
  195. quality: 1,
  196. success: (res) => {
  197. this.loading = false;
  198. this.showPoster = true;
  199. this.posterInfo.tempFilePath = res.tempFilePath;
  200. },
  201. fail: (err) => {
  202. console.error("生成图片失败:", err);
  203. uni.showToast({
  204. title: "海报生成失败",
  205. icon: "none",
  206. });
  207. this.loading = false;
  208. },
  209. });
  210. }, 100);
  211. });
  212. },
  213. fail: (err) => {
  214. this.loading = false;
  215. console.error("二维码下载失败:", err);
  216. uni.showToast({
  217. title: "二维码加载失败",
  218. icon: "none",
  219. });
  220. },
  221. });
  222. },
  223. fail: (err) => {
  224. console.error("背景图下载失败:", err);
  225. uni.showToast({
  226. title: "背景图加载失败",
  227. icon: "none",
  228. });
  229. },
  230. });
  231. });
  232. },
  233. //获取海报信息
  234. getPosterInfo() {
  235. return new Promise((resolve, reject) => {
  236. uni.$u.get("/token/getUserPartnerPic").then((res) => {
  237. if (res.code == 200) {
  238. this.posterInfo = res.data;
  239. resolve(res.data);
  240. } else {
  241. reject(res.msg);
  242. }
  243. });
  244. });
  245. },
  246. saveImage() {
  247. uni.getSetting({
  248. success: (res) => {
  249. if (!res.authSetting["scope.writePhotosAlbum"]) {
  250. uni.authorize({
  251. scope: "scope.writePhotosAlbum",
  252. success: () => {
  253. this.saveImageToAlbum();
  254. },
  255. fail: () => {
  256. uni.showToast({
  257. title: "请授权保存图片到相册",
  258. icon: "none",
  259. });
  260. },
  261. });
  262. } else {
  263. this.saveImageToAlbum();
  264. }
  265. },
  266. });
  267. },
  268. saveImageToAlbum() {
  269. uni.saveImageToPhotosAlbum({
  270. filePath: this.posterInfo.tempFilePath,
  271. success: () => {
  272. uni.showToast({
  273. title: "保存成功",
  274. icon: "success",
  275. });
  276. },
  277. fail: () => {
  278. uni.showToast({
  279. title: "保存失败",
  280. icon: "none",
  281. });
  282. },
  283. });
  284. },
  285. },
  286. };
  287. </script>
  288. <style></style>
  289. <style lang="scss" scoped>
  290. .partner-home {
  291. min-height: 100vh;
  292. background-color: #f5f5f5;
  293. .section-title {
  294. font-size: 32rpx;
  295. font-weight: bold;
  296. margin-bottom: 20rpx;
  297. display: block;
  298. }
  299. .header {
  300. display: flex;
  301. align-items: flex-start;
  302. background-color: #4caf50;
  303. padding: 30rpx;
  304. padding-top: 45rpx;
  305. border-radius: 16rpx;
  306. margin-bottom: 20rpx;
  307. height: 322rpx;
  308. border-radius: 0rpx 0rpx 161rpx 161rpx;
  309. .avatar {
  310. width: 80rpx;
  311. height: 80rpx;
  312. border-radius: 50%;
  313. margin-right: 20rpx;
  314. }
  315. .header-text {
  316. flex: 1;
  317. color: #fff;
  318. .title {
  319. font-size: 32rpx;
  320. font-weight: bold;
  321. display: block;
  322. }
  323. .subtitle {
  324. font-size: 24rpx;
  325. opacity: 0.8;
  326. }
  327. }
  328. .rule-btn {
  329. padding: 10rpx 20rpx;
  330. background: rgba(255, 255, 255, 0.2);
  331. border-radius: 30rpx;
  332. color: #fff;
  333. font-size: 24rpx;
  334. }
  335. }
  336. .income-section {
  337. background-color: #fff;
  338. border-radius: 16rpx;
  339. padding: 30rpx;
  340. margin: 20rpx auto;
  341. width: calc(100% - 40rpx);
  342. .section-header {
  343. display: flex;
  344. justify-content: space-between;
  345. align-items: center;
  346. margin-bottom: 20rpx;
  347. .tip {
  348. font-size: 24rpx;
  349. color: #999;
  350. }
  351. }
  352. .income-grid {
  353. display: grid;
  354. grid-template-columns: repeat(2, 1fr);
  355. gap: 20rpx;
  356. .income-item {
  357. background-color: #f8f8f8;
  358. padding: 20rpx;
  359. border-radius: 12rpx;
  360. text-align: center;
  361. .amount {
  362. color: #ff4d4f;
  363. font-size: 32rpx;
  364. font-weight: bold;
  365. display: block;
  366. }
  367. .label {
  368. font-size: 24rpx;
  369. color: #666;
  370. margin-top: 8rpx;
  371. }
  372. }
  373. }
  374. }
  375. .tools-section {
  376. background-color: #fff;
  377. border-radius: 16rpx;
  378. padding: 30rpx;
  379. margin: 20rpx auto;
  380. width: calc(100% - 40rpx);
  381. .tools-grid {
  382. display: flex;
  383. gap: 30rpx;
  384. .tool-item {
  385. flex: 1;
  386. display: flex;
  387. align-items: center;
  388. padding: 20rpx;
  389. border-radius: 12rpx;
  390. image {
  391. width: 90rpx;
  392. height: 90rpx;
  393. margin-right: 10rpx;
  394. }
  395. text {
  396. font-size: 26rpx;
  397. color: #333;
  398. }
  399. }
  400. }
  401. }
  402. .generate-poster {
  403. width: calc(100% - 60rpx);
  404. height: 88rpx;
  405. line-height: 88rpx;
  406. background-color: #4caf50;
  407. color: #fff;
  408. border-radius: 10rpx;
  409. font-size: 28rpx;
  410. margin: 40rpx auto;
  411. }
  412. .poster-container {
  413. display: flex;
  414. flex-direction: column;
  415. align-items: center;
  416. .poster-image {
  417. width: 250px;
  418. height: 445px;
  419. }
  420. .poster-tip {
  421. font-size: 32rpx;
  422. color: #fff;
  423. text-align: center;
  424. background-color: #333;
  425. text-justify: space-between;
  426. width: 250px;
  427. line-height: 54rpx;
  428. position: relative;
  429. top: -2rpx;
  430. }
  431. }
  432. ::v-deep .u-mode-center-box {
  433. background-color: transparent !important;
  434. }
  435. }
  436. </style>