爱简收旧衣按件回收前端代码仓库
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.

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