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

443 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 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. return new Promise((resolve, reject) => {
  169. // 先下载图片到本地临时路径
  170. wx.downloadFile({
  171. url: image,
  172. success: (res) => {
  173. if (res.statusCode === 200) {
  174. const tempFilePath = res.tempFilePath;
  175. // 保存到相册
  176. wx.saveImageToPhotosAlbum({
  177. filePath: tempFilePath,
  178. success: () => {
  179. wx.showToast({ title: '保存成功', icon: 'success' });
  180. },
  181. fail: (err) => {
  182. console.error('保存失败:', err);
  183. wx.showToast({ title: '保存失败', icon: 'none' });
  184. }
  185. });
  186. }
  187. },
  188. fail: (err) => {
  189. console.error('下载失败:', err);
  190. wx.showToast({ title: '图片下载失败', icon: 'none' });
  191. }
  192. });
  193. // // 获取图片的信息
  194. // uni.getImageInfo({
  195. // src: image,
  196. // success: (imageInfo) => {
  197. // // 保存图片到手机相册
  198. // uni.saveImageToPhotosAlbum({
  199. // filePath: imageInfo.path,
  200. // success: () => {
  201. // uni.showModal({
  202. // title: '保存成功',
  203. // content: '图片已成功保存到相册',
  204. // showCancel: false
  205. // })
  206. // resolve()
  207. // },
  208. // fail: (err) => {
  209. // console.log('保存失败', err)
  210. // reject(new Error('保存失败'))
  211. // },
  212. // complete: (res) => {
  213. // console.log('保存结果:', res)
  214. // }
  215. // })
  216. // },
  217. // fail: (err) => {
  218. // console.log('获取图片信息失败:', err)
  219. // reject(new Error('获取图片信息失败'))
  220. // }
  221. // })
  222. })
  223. },
  224. // 图片加载成功
  225. onImageLoad() {
  226. console.log('二维码图片加载成功')
  227. this.isLoading = false // 隐藏骨架屏
  228. },
  229. // 图片加载失败
  230. onImageError() {
  231. console.log('二维码图片加载失败,尝试重试')
  232. // 如果还有重试次数,则重试
  233. if (this.retryCount < this.maxRetries) {
  234. this.retryCount++
  235. console.log(`${this.retryCount}次重试加载二维码`)
  236. // 延迟1秒后重试
  237. setTimeout(() => {
  238. this.generateQrcode()
  239. }, 1000)
  240. return
  241. }
  242. // 重试次数用完,显示错误提示并隐藏骨架屏
  243. console.log('重试次数用完,二维码加载失败')
  244. this.isLoading = false // 隐藏骨架屏
  245. uni.showToast({
  246. title: '二维码加载失败',
  247. icon: 'none'
  248. })
  249. }
  250. }
  251. }
  252. </script>
  253. <style lang="scss" scoped>
  254. .promo-modal-page {
  255. min-height: 100vh;
  256. background: #f8f8f8;
  257. // padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
  258. overflow: hidden;
  259. }
  260. .nav-bar {
  261. display: flex;
  262. align-items: center;
  263. height: calc(150rpx + var(--status-bar-height));
  264. padding: 0 32rpx;
  265. padding-top: var(--status-bar-height);
  266. background: #fff;
  267. position: fixed;
  268. top: 0;
  269. left: 0;
  270. right: 0;
  271. z-index: 999;
  272. box-sizing: border-box;
  273. .back {
  274. padding: 20rpx;
  275. margin-left: -20rpx;
  276. }
  277. .title {
  278. flex: 1;
  279. text-align: center;
  280. font-size: 34rpx;
  281. font-weight: 500;
  282. color: #222;
  283. }
  284. }
  285. .content {
  286. // padding: 30rpx 0 0 0;
  287. padding: 20rpx;
  288. // margin-top: calc(150rpx + var(--status-bar-height) + 80rpx);
  289. height: 100%;
  290. display: flex;
  291. flex-direction: column;
  292. align-items: center;
  293. overflow: hidden;
  294. box-sizing: border-box;
  295. .user-info-modal {
  296. display: flex;
  297. flex-direction: column;
  298. align-items: center;
  299. margin-bottom: 48rpx;
  300. .avatar-frame {
  301. width: 104rpx;
  302. height: 104rpx;
  303. border-radius: 10rpx;
  304. overflow: hidden;
  305. background: #f2f2f2;
  306. box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.10);
  307. .avatar-img {
  308. width: 104rpx;
  309. height: 104rpx;
  310. object-fit: cover;
  311. display: block;
  312. }
  313. }
  314. .nickname {
  315. margin-top: 24rpx;
  316. font-size: 32rpx;
  317. font-weight: bold;
  318. color: #222;
  319. text-align: center;
  320. }
  321. }
  322. .qrcode-modal-section {
  323. width: 100%;
  324. display: flex;
  325. flex-direction: column;
  326. align-items: center;
  327. margin-bottom: 48rpx;
  328. .qrcode-skeleton {
  329. width: 100%;
  330. display: flex;
  331. flex-direction: column;
  332. align-items: center;
  333. .skeleton-img {
  334. width: 300rpx;
  335. height: 300rpx;
  336. border-radius: 20rpx;
  337. background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  338. background-size: 200% 100%;
  339. animation: skeleton-loading 1.5s infinite;
  340. margin-bottom: 20rpx;
  341. }
  342. .skeleton-text {
  343. width: 200rpx;
  344. height: 32rpx;
  345. border-radius: 16rpx;
  346. background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  347. background-size: 200% 100%;
  348. animation: skeleton-loading 1.5s infinite;
  349. }
  350. @keyframes skeleton-loading {
  351. 0% {
  352. background-position: 200% 0;
  353. }
  354. 100% {
  355. background-position: -200% 0;
  356. }
  357. }
  358. }
  359. .qrcode-img {
  360. width: 100%;
  361. height: 100%;
  362. background: #fff;
  363. border-radius: 24rpx;
  364. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
  365. }
  366. .invite-code {
  367. margin-top: 32rpx;
  368. font-size: 30rpx;
  369. color: #222;
  370. font-weight: bold;
  371. text-align: center;
  372. }
  373. }
  374. .bottom-btns-modal {
  375. position: fixed;
  376. left: 0;
  377. right: 0;
  378. bottom: 0;
  379. display: flex;
  380. justify-content: space-between;
  381. padding: 24rpx 32rpx calc(env(safe-area-inset-bottom) + 24rpx) 32rpx;
  382. background: #fff;
  383. z-index: 100;
  384. .btn {
  385. flex: 1;
  386. height: 88rpx;
  387. border-radius: 44rpx;
  388. font-size: 32rpx;
  389. font-weight: bold;
  390. margin: 0 12rpx;
  391. border: none;
  392. display: flex;
  393. align-items: center;
  394. justify-content: center;
  395. &.gray {
  396. background: linear-gradient(90deg, #b2f08d, #39e9d2);
  397. color: #fff;
  398. }
  399. &.green {
  400. background: linear-gradient(90deg, #b2f08d, #39e9d2);
  401. color: #fff;
  402. }
  403. }
  404. }
  405. }
  406. </style>