爱简收旧衣按件回收前端代码仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

450 lines
10 KiB

4 weeks ago
3 weeks ago
4 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
4 weeks ago
3 weeks ago
3 weeks ago
4 weeks ago
4 weeks ago
4 days ago
4 days ago
4 days ago
4 days ago
4 weeks ago
3 weeks ago
4 weeks ago
4 weeks ago
3 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
3 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
  1. <template>
  2. <view class="promo-modal-page">
  3. <!-- 顶部导航栏 -->
  4. <!-- <view class="nav-bar">
  5. <view class="back" @tap="navigateBack">
  6. <uni-icons type="left" size="20" />
  7. </view>
  8. <text class="title">推广链接</text>
  9. </view> -->
  10. <navbar title="推广链接" leftClick
  11. @leftClick="$utils.navigateBack" />
  12. <!-- 页面内容 -->
  13. <view class="content">
  14. <!-- 用户信息 -->
  15. <!-- <view class="user-info-modal">
  16. <view class="avatar-frame">
  17. <image class="avatar-img" :src="userInfo.headImage || '/static/avatar.png'" mode="aspectFill" />
  18. </view>
  19. <view class="nickname">{{ userInfo.nickName || '用户' }}</view>
  20. </view> -->
  21. <!-- 二维码区 -->
  22. <view class="qrcode-modal-section">
  23. <!-- 加载骨架屏 -->
  24. <view v-if="isLoading" class="qrcode-skeleton">
  25. <view class="skeleton-img"></view>
  26. <view class="skeleton-text"></view>
  27. </view>
  28. <!-- 二维码图片 -->
  29. <image
  30. v-else-if="qrcodeUrl"
  31. class="qrcode-img"
  32. :src="qrcodeUrl"
  33. mode="widthFix"
  34. :show-menu-by-longpress="true"
  35. @error="onImageError"
  36. @load="onImageLoad"
  37. />
  38. <view class="invite-code">邀请码{{inviteCode}}</view>
  39. <view class="invite-code">长按二维码可保存到相册</view>
  40. </view>
  41. <!-- 底部按钮 -->
  42. <view class="bottom-btns-modal">
  43. <button class="btn gray" open-type="share">分享给好友</button>
  44. <!-- <button class="btn green" @tap="saveToAlbum">保存到本地</button> -->
  45. </view>
  46. </view>
  47. </view>
  48. </template>
  49. <script>
  50. import pullRefreshMixin from '@/pages/mixins/pullRefreshMixin.js'
  51. import config from '@/config.js'
  52. import navbar from '@/compoent/base/navbar.vue'
  53. import authorize from '@/utils/authorize.js'
  54. export default {
  55. components : {
  56. navbar
  57. },
  58. mixins: [pullRefreshMixin],
  59. data() {
  60. return {
  61. userInfo: {},
  62. qrcodeUrl: '', // 二维码图片URL
  63. inviteCode: '888888',
  64. isLoading: true, // 加载状态,控制骨架屏显示
  65. retryCount: 0, // 重试次数
  66. maxRetries: 2, // 最大重试次数
  67. }
  68. },
  69. onLoad() {
  70. // 获取用户信息和二维码
  71. this.fetchUserInfo()
  72. this.getQrcode()
  73. },
  74. onUnload() {
  75. // 页面销毁时的清理逻辑
  76. },
  77. // 微信小程序分享配置
  78. // #ifdef MP-WEIXIN
  79. onShareAppMessage() {
  80. return {
  81. title: `${this.userInfo.nickName || '用户'}邀请您一起参与旧衣回收`,
  82. path: `/pages/component/home?shareId=${this.inviteCode}`,
  83. imageUrl: this.qrcodeUrl
  84. }
  85. },
  86. onShareTimeline() {
  87. return {
  88. title: `${this.userInfo.nickName || '用户'}邀请您一起参与旧衣回收,环保从我做起!`,
  89. query: `shareId=${this.inviteCode}`,
  90. imageUrl: this.qrcodeUrl
  91. }
  92. },
  93. // #endif
  94. methods: {
  95. async onRefresh() {
  96. // 重新获取用户信息和二维码
  97. this.retryCount = 0 // 重置重试计数
  98. this.isLoading = true // 重置加载状态,显示骨架屏
  99. this.fetchUserInfo()
  100. this.getQrcode()
  101. uni.stopPullRefresh()
  102. },
  103. navigateBack() {
  104. uni.navigateBack()
  105. },
  106. // 获取用户信息
  107. fetchUserInfo() {
  108. if (uni.getStorageSync('token')) {
  109. this.$api("getUserByToken", {}, (res) => {
  110. if (res.code == 200) {
  111. this.userInfo = res.result
  112. // 更新邀请码
  113. if (res.result.intentioCode) {
  114. this.inviteCode = res.result.intentioCode
  115. }
  116. }
  117. })
  118. }
  119. },
  120. getQrcode() {
  121. console.log('调用后端API生成二维码')
  122. uni.showLoading({
  123. title: '生成二维码中...'
  124. })
  125. this.generateQrcode()
  126. },
  127. // 生成二维码
  128. generateQrcode() {
  129. // 重置重试计数(仅在第一次调用时)
  130. if (this.retryCount === 0) {
  131. this.retryCount = 0
  132. }
  133. // 设置加载状态
  134. this.isLoading = true
  135. const token = uni.getStorageSync('token')
  136. const qrcodeUrl = `${config.baseUrl}/recycle-admin/applet/promotion/getInviteCode?token=${token}`
  137. // console.log('二维码URL:', qrcodeUrl)
  138. // 直接使用网络图片
  139. this.qrcodeUrl = qrcodeUrl
  140. if(this.qrcodeUrl){
  141. this.isLoading = false
  142. }
  143. uni.hideLoading()
  144. },
  145. // 保存到手机相册
  146. async saveToAlbum() {
  147. if (!this.qrcodeUrl) {
  148. uni.showToast({
  149. title: '二维码还未加载完成',
  150. icon: 'none'
  151. })
  152. return
  153. }
  154. try {
  155. // 请求相册权限
  156. await authorize('scope.writePhotosAlbum')
  157. // 获取图片信息并保存
  158. await this.imgApi(this.qrcodeUrl)
  159. } catch (error) {
  160. console.log('保存失败:', error)
  161. uni.showToast({
  162. title: '保存失败',
  163. icon: 'none'
  164. })
  165. }
  166. },
  167. // 获取图片信息并保存到相册
  168. imgApi(image) {
  169. uni.showLoading({
  170. title: '保存中...'
  171. })
  172. return new Promise((resolve, reject) => {
  173. // 先下载图片到本地临时路径
  174. wx.downloadFile({
  175. url: image,
  176. success: (res) => {
  177. if (res.statusCode === 200) {
  178. const tempFilePath = res.tempFilePath;
  179. // 保存到相册
  180. wx.saveImageToPhotosAlbum({
  181. filePath: tempFilePath,
  182. success: () => {
  183. uni.hideLoading()
  184. wx.showToast({ title: '保存成功', icon: 'success' });
  185. },
  186. fail: (err) => {
  187. uni.hideLoading()
  188. console.error('保存失败:', err);
  189. wx.showToast({ title: '保存失败', icon: 'none' });
  190. }
  191. });
  192. }
  193. },
  194. fail: (err) => {
  195. uni.hideLoading()
  196. console.error('下载失败:', err);
  197. wx.showToast({ title: '图片下载失败', icon: 'none' });
  198. }
  199. });
  200. // // 获取图片的信息
  201. // uni.getImageInfo({
  202. // src: image,
  203. // success: (imageInfo) => {
  204. // // 保存图片到手机相册
  205. // uni.saveImageToPhotosAlbum({
  206. // filePath: imageInfo.path,
  207. // success: () => {
  208. // uni.showModal({
  209. // title: '保存成功',
  210. // content: '图片已成功保存到相册',
  211. // showCancel: false
  212. // })
  213. // resolve()
  214. // },
  215. // fail: (err) => {
  216. // console.log('保存失败', err)
  217. // reject(new Error('保存失败'))
  218. // },
  219. // complete: (res) => {
  220. // console.log('保存结果:', res)
  221. // }
  222. // })
  223. // },
  224. // fail: (err) => {
  225. // console.log('获取图片信息失败:', err)
  226. // reject(new Error('获取图片信息失败'))
  227. // }
  228. // })
  229. })
  230. },
  231. // 图片加载成功
  232. onImageLoad() {
  233. console.log('二维码图片加载成功')
  234. this.isLoading = false // 隐藏骨架屏
  235. },
  236. // 图片加载失败
  237. onImageError() {
  238. console.log('二维码图片加载失败,尝试重试')
  239. // 如果还有重试次数,则重试
  240. if (this.retryCount < this.maxRetries) {
  241. this.retryCount++
  242. console.log(`${this.retryCount}次重试加载二维码`)
  243. // 延迟1秒后重试
  244. setTimeout(() => {
  245. this.generateQrcode()
  246. }, 1000)
  247. return
  248. }
  249. // 重试次数用完,显示错误提示并隐藏骨架屏
  250. console.log('重试次数用完,二维码加载失败')
  251. this.isLoading = false // 隐藏骨架屏
  252. uni.showToast({
  253. title: '二维码加载失败',
  254. icon: 'none'
  255. })
  256. }
  257. }
  258. }
  259. </script>
  260. <style lang="scss" scoped>
  261. .promo-modal-page {
  262. min-height: 100vh;
  263. background: #f8f8f8;
  264. // padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
  265. overflow: hidden;
  266. }
  267. .nav-bar {
  268. display: flex;
  269. align-items: center;
  270. height: calc(150rpx + var(--status-bar-height));
  271. padding: 0 32rpx;
  272. padding-top: var(--status-bar-height);
  273. background: #fff;
  274. position: fixed;
  275. top: 0;
  276. left: 0;
  277. right: 0;
  278. z-index: 999;
  279. box-sizing: border-box;
  280. .back {
  281. padding: 20rpx;
  282. margin-left: -20rpx;
  283. }
  284. .title {
  285. flex: 1;
  286. text-align: center;
  287. font-size: 34rpx;
  288. font-weight: 500;
  289. color: #222;
  290. }
  291. }
  292. .content {
  293. // padding: 30rpx 0 0 0;
  294. padding: 20rpx;
  295. // margin-top: calc(150rpx + var(--status-bar-height) + 80rpx);
  296. height: 100%;
  297. display: flex;
  298. flex-direction: column;
  299. align-items: center;
  300. overflow: hidden;
  301. box-sizing: border-box;
  302. .user-info-modal {
  303. display: flex;
  304. flex-direction: column;
  305. align-items: center;
  306. margin-bottom: 48rpx;
  307. .avatar-frame {
  308. width: 104rpx;
  309. height: 104rpx;
  310. border-radius: 10rpx;
  311. overflow: hidden;
  312. background: #f2f2f2;
  313. box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.10);
  314. .avatar-img {
  315. width: 104rpx;
  316. height: 104rpx;
  317. object-fit: cover;
  318. display: block;
  319. }
  320. }
  321. .nickname {
  322. margin-top: 24rpx;
  323. font-size: 32rpx;
  324. font-weight: bold;
  325. color: #222;
  326. text-align: center;
  327. }
  328. }
  329. .qrcode-modal-section {
  330. width: 100%;
  331. display: flex;
  332. flex-direction: column;
  333. align-items: center;
  334. margin-bottom: 48rpx;
  335. .qrcode-skeleton {
  336. width: 100%;
  337. display: flex;
  338. flex-direction: column;
  339. align-items: center;
  340. .skeleton-img {
  341. width: 300rpx;
  342. height: 300rpx;
  343. border-radius: 20rpx;
  344. background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  345. background-size: 200% 100%;
  346. animation: skeleton-loading 1.5s infinite;
  347. margin-bottom: 20rpx;
  348. }
  349. .skeleton-text {
  350. width: 200rpx;
  351. height: 32rpx;
  352. border-radius: 16rpx;
  353. background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  354. background-size: 200% 100%;
  355. animation: skeleton-loading 1.5s infinite;
  356. }
  357. @keyframes skeleton-loading {
  358. 0% {
  359. background-position: 200% 0;
  360. }
  361. 100% {
  362. background-position: -200% 0;
  363. }
  364. }
  365. }
  366. .qrcode-img {
  367. width: 100%;
  368. height: 100%;
  369. background: #fff;
  370. border-radius: 24rpx;
  371. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
  372. }
  373. .invite-code {
  374. margin-top: 32rpx;
  375. font-size: 30rpx;
  376. color: #222;
  377. font-weight: bold;
  378. text-align: center;
  379. }
  380. }
  381. .bottom-btns-modal {
  382. position: fixed;
  383. left: 0;
  384. right: 0;
  385. bottom: 0;
  386. display: flex;
  387. justify-content: space-between;
  388. padding: 24rpx 32rpx calc(env(safe-area-inset-bottom) + 24rpx) 32rpx;
  389. background: #fff;
  390. z-index: 100;
  391. .btn {
  392. flex: 1;
  393. height: 88rpx;
  394. border-radius: 44rpx;
  395. font-size: 32rpx;
  396. font-weight: bold;
  397. margin: 0 12rpx;
  398. border: none;
  399. display: flex;
  400. align-items: center;
  401. justify-content: center;
  402. &.gray {
  403. background: linear-gradient(90deg, #b2f08d, #39e9d2);
  404. color: #fff;
  405. }
  406. &.green {
  407. background: linear-gradient(90deg, #b2f08d, #39e9d2);
  408. color: #fff;
  409. }
  410. }
  411. }
  412. }
  413. </style>