Explorar o código

权重页面bug修改

haiyang hai 1 día
pai
achega
1029488707
Modificáronse 89 ficheiros con 1710 adicións e 1546 borrados
  1. 4 2
      env/.env
  2. 3 2
      env/.env.development
  3. 4 0
      env/.env.production
  4. 1 2
      pages.config.ts
  5. 1 3
      src/App.ku.vue
  6. 4 0
      src/api/home.ts
  7. 9 4
      src/api/income.ts
  8. 2 4
      src/api/login.ts
  9. 11 0
      src/api/receiveCoupon.ts
  10. 1 0
      src/api/types/coupon.ts
  11. 0 281
      src/components/ReceiveCoupon.vue
  12. 111 0
      src/components/ShortLinkModal.vue
  13. 41 4
      src/components/discountCoupon.vue
  14. 42 16
      src/components/spendAndSaveCoupon.vue
  15. 39 5
      src/components/spendAndSaveCoupon_large.vue
  16. 107 0
      src/hooks/useCouponShare.ts
  17. 11 9
      src/hooks/useShare.ts
  18. 5 5
      src/http/alova.ts
  19. 12 7
      src/pages-A/applyForm/index.vue
  20. 54 45
      src/pages-A/bank/index.vue
  21. 1 1
      src/pages-A/discountcouponList/index.vue
  22. 143 127
      src/pages-A/myBankList/index.vue
  23. 1 1
      src/pages-A/spendAndSaveCouponList/index.vue
  24. 130 133
      src/pages-A/unlockRewards/index.vue
  25. 119 108
      src/pages-A/withdraw/index.vue
  26. 156 148
      src/pages-A/withdrawalDetails/index.vue
  27. 1 2
      src/pages-fg/404/index.vue
  28. 2 1
      src/pages-fg/login/login.vue
  29. 106 87
      src/pages/home/home.vue
  30. 384 309
      src/pages/income/income.vue
  31. 14 13
      src/pages/my/my.vue
  32. 0 97
      src/pages/receiveCoupon/index.vue
  33. 78 1
      src/router/interceptor.ts
  34. BIN=BIN
      src/static/app/icons/1024x1024.png
  35. BIN=BIN
      src/static/app/icons/120x120.png
  36. BIN=BIN
      src/static/app/icons/144x144.png
  37. BIN=BIN
      src/static/app/icons/152x152.png
  38. BIN=BIN
      src/static/app/icons/167x167.png
  39. BIN=BIN
      src/static/app/icons/180x180.png
  40. BIN=BIN
      src/static/app/icons/192x192.png
  41. BIN=BIN
      src/static/app/icons/20x20.png
  42. BIN=BIN
      src/static/app/icons/29x29.png
  43. BIN=BIN
      src/static/app/icons/40x40.png
  44. BIN=BIN
      src/static/app/icons/58x58.png
  45. BIN=BIN
      src/static/app/icons/60x60.png
  46. BIN=BIN
      src/static/app/icons/72x72.png
  47. BIN=BIN
      src/static/app/icons/76x76.png
  48. BIN=BIN
      src/static/app/icons/80x80.png
  49. BIN=BIN
      src/static/app/icons/87x87.png
  50. BIN=BIN
      src/static/app/icons/96x96.png
  51. BIN=BIN
      src/static/images/avatar.jpg
  52. BIN=BIN
      src/static/images/default-avatar.png
  53. BIN=BIN
      src/static/images/income/djs.png
  54. BIN=BIN
      src/static/images/income/emptyBank.png
  55. BIN=BIN
      src/static/images/income/hb.png
  56. BIN=BIN
      src/static/images/income/income-bg.png
  57. BIN=BIN
      src/static/images/income/logo-gh.png
  58. BIN=BIN
      src/static/images/income/logo-jh.png
  59. BIN=BIN
      src/static/images/income/logo-nh.png
  60. BIN=BIN
      src/static/images/income/logo-zh.png
  61. BIN=BIN
      src/static/images/income/logogh.png
  62. BIN=BIN
      src/static/images/income/logojh.png
  63. BIN=BIN
      src/static/images/income/logonh.png
  64. BIN=BIN
      src/static/images/income/logozh.png
  65. BIN=BIN
      src/static/images/income/notice.png
  66. BIN=BIN
      src/static/images/income/tx-icon.png
  67. BIN=BIN
      src/static/images/income/y-1.png
  68. BIN=BIN
      src/static/images/index/coupon-bg.png
  69. BIN=BIN
      src/static/images/index/coupon1.png
  70. BIN=BIN
      src/static/images/index/coupon2.png
  71. BIN=BIN
      src/static/images/index/coupon3.png
  72. BIN=BIN
      src/static/images/index/icon1.png
  73. BIN=BIN
      src/static/images/index/icon2.png
  74. BIN=BIN
      src/static/images/index/index-bg.png
  75. BIN=BIN
      src/static/images/index/lock.png
  76. BIN=BIN
      src/static/images/index/logo.jpg
  77. BIN=BIN
      src/static/images/me/coupon-need.png
  78. BIN=BIN
      src/static/images/me/loginOut.png
  79. BIN=BIN
      src/static/images/me/me-bg.png
  80. BIN=BIN
      src/static/images/receiveCoupon/button.png
  81. BIN=BIN
      src/static/images/receiveCoupon/coupon-bg.png
  82. BIN=BIN
      src/static/images/share.png
  83. 0 1
      src/static/my-icons/copyright.svg
  84. 3 0
      src/store/coupon.ts
  85. 48 36
      src/store/token.ts
  86. 4 40
      src/tabbar/config.ts
  87. 1 1
      src/tabbar/index.vue
  88. 51 51
      src/tabbar/store.ts
  89. 6 0
      src/utils/imageUtil.ts

+ 4 - 2
env/.env

@@ -10,7 +10,8 @@ VITE_WX_APPID = 'wx23776edbfe90d642'
 VITE_APP_PUBLIC_BASE=/
 
 # 后台请求地址
-VITE_SERVER_BASEURL = 'http://192.168.1.28:8082/jeecg-boot'
+#VITE_SERVER_BASEURL = 'http://192.168.1.28:8082/jeecg-boot'
+VITE_SERVER_BASEURL = 'https://life.baoxianzhanggui.com/locallive-pro-java'
 # 后台静态资源请求地址
 VITE_SERVER_RESOURCE_BASEURL ='/sys/common/static'
 # 备注:如果后台带统一前缀,则也要加到后面,eg: https://ukw0y1.laf.run/api
@@ -23,10 +24,11 @@ VITE_APP_PROXY_ENABLE = false
 VITE_APP_PROXY_PREFIX = '/fg-api'
 
 # 第二个请求地址 (目前alova中可以使用)
-VITE_SERVER_BASEURL_SECONDARY = 'http://192.168.1.28:8082/jeecg-boot'
+VITE_SERVER_BASEURL_SECONDARY = 'https://life.baoxianzhanggui.com/locallive-pro-java'
 
 # 认证模式,'single' | 'double' ==> 单token | 双token
 VITE_AUTH_MODE = 'single'
 
 # 原生插件资源复制开关,控制是否启用 copy-native-resources 插件
 VITE_COPY_NATIVE_RES_ENABLE = false
+

+ 3 - 2
env/.env.development

@@ -9,5 +9,6 @@ VITE_SHOW_SOURCEMAP = false
 VITE_SERVER_BASEURL = 'http://192.168.1.28:8082/jeecg-boot'
 # VITE_SERVER_BASEURL = 'http://192.168.1.232:8082/jeecg-boot'
 
-# 后台静态资源请求地址
-VITE_SERVER_RESOURCE_BASEURL ='/sys/common/static'
+
+# 静态资源请求地址
+VITE_CDN_DOMAIN = 'https://life.baoxianzhanggui.com/pro-img/securities'

+ 4 - 0
env/.env.production

@@ -10,3 +10,7 @@ VITE_SHOW_SOURCEMAP = false
 
 # 后台静态资源请求地址
 #VITE_SERVER_RESOURCE_BASEURL ='/sys/common/static'
+
+
+# 静态资源请求地址
+VITE_CDN_DOMAIN = 'https://life.baoxianzhanggui.com/pro-img/securities'

+ 1 - 2
pages.config.ts

@@ -28,13 +28,12 @@ export default defineUniPages({
             enhance: true,
             postcss: true,
         },
-        lazyCodeLoading: 'requiredComponents',
+        'lazyCodeLoading': 'requiredComponents',
         // 微信小程序全局分享配置
         'window': {
             'enableShareAppMessage': true,
             'enableShareTimeline': true,
         },
-        'navigateToMiniProgramAppIdList': ['wxab9634bf98628a03'],
     },
     // tabbar 的配置统一在 “./src/tabbar/config.ts” 文件中
     'tabBar': tabBar as any,

+ 1 - 3
src/App.ku.vue

@@ -30,8 +30,6 @@ onShow(() => {
     loadingStore.hideLoading()
 })
 
-const helloKuRoot = ref('Hello AppKuVue')
-
 const exposeRef = ref('this is form app.Ku.vue')
 
 defineExpose({
@@ -47,7 +45,7 @@ defineExpose({
         </view> -->
 
         <KuRootView />
-        <up-loading-page :loading="loadingStore.isLoading" z-index="10000" size="50" />
+        <up-loading-page :loading="loadingStore.isLoading" z-index="1000" size="50" />
         <FgTabbar v-if="isCurrentPageTabbar" />
     </view>
 </template>

+ 4 - 0
src/api/home.ts

@@ -61,3 +61,7 @@ export function getCouponDetail(params) {
 export function getIssuerDetail() {
     return http.Get('/couponCenter/APP/couponIssuerApply/queryById')
 }
+
+export function getIssuerStatus() {
+    return http.Get('/couponCenter/APP/couponIssuerApply/queryStatusById')
+}

+ 9 - 4
src/api/income.ts

@@ -10,10 +10,10 @@ import { http } from '@/http/alova'
 // }
 
 export interface CouponIssuerAccountByPageMapForm {
-    pageNo: number
-    pageSize: number
-    startTime: string
-    endTime: string
+    pageNo?: number
+    pageSize?: number
+    year: string
+    month: string
     status: number // 0-待结算, 1-已结算/成功
     type: number // 明细类型: 1-佣金收入, 2-提现支出, 3-退款扣减, 4-系统调整
 }
@@ -31,6 +31,7 @@ export interface AccountDetailItem {
 // 定义分页响应数据接口
 export interface PageMapResponse {
     detailList: AccountDetailItem[]
+    total: number
     [key: string]: any
 }
 
@@ -46,3 +47,7 @@ export function getCouponIssuerAccountByPageMap(couponIssuerAccountByPageMapForm
 export function getUnlockAmountByUserId() {
     return http.Get<number>('/couponCenter/APP/couponIssuerAccount/queryUnlockAmountByUserId')
 }
+
+export function getAccountDetailTotalAmount(params: CouponIssuerAccountByPageMapForm) {
+    return http.Post('/couponCenter/APP/couponIssuerAccountDetail/getAccountDetailTotalAmount', params)
+}

+ 2 - 4
src/api/login.ts

@@ -45,7 +45,7 @@ export function getUserInfo() {
  * 退出登录
  */
 export function logout() {
-    return http.Get<void>('/couponCenter/APP/wx/logout')
+    return http.Post<void>('/couponCenter/APP/wx/logout')
 }
 
 /**
@@ -94,7 +94,5 @@ export function wxLogin(code: string) {
 }
 
 export function addInviteConversion(params) {
-    return http.Post('/couponCenter/APP/shareRecord/addInviteConversion', {
-        ...params,
-    })
+    return http.Get(`/couponCenter/APP/shareRecord/addInviteConversion?shareRecordId=${params.shareRecordId}`)
 }

+ 11 - 0
src/api/receiveCoupon.ts

@@ -1,4 +1,5 @@
 import { http } from '@/http/alova'
+import { ContentTypeEnum } from '@/http/tools/enum'
 
 export function getReceiveCouponDetail(couponId: string) {
     return http.Get('/couponCenter/APP/shareRecord/queryTemplateBySendUserId', {
@@ -7,3 +8,13 @@ export function getReceiveCouponDetail(couponId: string) {
         }
     })
 }
+
+// 新增:获取优惠券分享短链的接口
+export function getCouponShareLink(params) {
+    return http.Get('/couponCenter/APP/shareRecord/createShareWechatUrl', {
+        params: {
+            templateId: params.templateId,
+            pathUrl: params.pathUrl,
+        }
+    })
+}

+ 1 - 0
src/api/types/coupon.ts

@@ -14,4 +14,5 @@ export interface CouponSituation {
     usedQuantity: number
     quantityToBeUsed: number
     quantityForComplimentary: number
+    expired: number
 }

+ 0 - 281
src/components/ReceiveCoupon.vue

@@ -1,281 +0,0 @@
-<script lang="ts" setup>
-import CouponBtnBg from '@img/receiveCoupon/button.png'
-import CouponImg from '@img/receiveCoupon/coupon-bg.png'
-
-const props = defineProps({
-    coupon: {
-        type: Object,
-        default: () => ({
-            ruleReductionAmount: '0',
-            ruleMinSpendAmount: '0',
-        }),
-    },
-})
-const emit = defineEmits(['button-click'])
-const coupon = computed(() => props.coupon)
-
-const category = computed(() => {
-    const relatedType = props.coupon.relatedType
-    if (relatedType === '1') {
-        return `限${props.coupon.relatedName}分类商品使用`
-    }
-    else {
-        return `限${props.coupon.relatedName}商品使用`
-    }
-})
-
-function handleButtonClick() {
-    emit('button-click')
-}
-</script>
-
-<template>
-    <view class="discount-coupon">
-        <view class="coupon-container">
-            <view class="coupon-content">
-                <image class="coupon-bg" :src="CouponImg" mode="aspectFit" />
-                <view class="coupon-content-layout">
-                    <view class="coupon-content-text-content">
-                        <view v-if="coupon.type === '2'" class="coupon-content-text-title">
-                            <view class="coupon-amount">
-                                <view class="coupon-amount-count-number">
-                                    {{ coupon.ruleDiscountRate }}
-                                </view>
-                                <view class="coupon-amount-count-unit">
-                                    折
-                                </view>
-                            </view>
-                            <view class="coupon-type">
-                                <view style="font-size: 18rpx;">
-                                    COUPON
-                                </view>
-                                <view style="font-size: 34rpx;">
-                                    折扣券
-                                </view>
-                                <view style="font-size:18rpx;">
-                                    最高优惠{{ coupon.ruleDiscountCapAmount }}元
-                                </view>
-                            </view>
-                        </view>
-                        <view v-else class="coupon-content-text-title">
-                            <view class="coupon-amount">
-                                <view class="coupon-amount-count-icon">
-                                    ¥
-                                </view>
-                                <view class="coupon-amount-count-number">
-                                    {{ coupon?.ruleReductionAmount || '0' }}
-                                </view>
-                            </view>
-                            <view class="coupon-type">
-                                <view style="font-size: 18rpx;">
-                                    YUAN
-                                </view>
-                                <view style="font-size: 34rpx;">
-                                    满减券
-                                </view>
-                                <view style="font-size:18rpx;">
-                                    COUPON
-                                </view>
-                            </view>
-                        </view>
-                        <view class="coupon-content-text-cart">
-                            {{ category }}
-                        </view>
-                        <view class="coupon-content-text-amount">
-                            订单满{{ coupon?.ruleMinSpendAmount }}元可用此券
-                        </view>
-                    </view>
-                </view>
-            </view>
-            <button class="coupon-button" hover-class="none" @click="handleButtonClick">
-                <image class="coupon-btn-bg" :src="CouponBtnBg" mode="aspectFit" />
-                <view class="coupon-btn-text">
-                    立即领取
-                </view>
-            </button>
-        </view>
-    </view>
-</template>
-
-<style lang="scss" scoped>
-.discount-coupon {
-    // width: 670rpx;
-    width: 100%;
-    height: 100%;
-    position: absolute;
-    // position: relative;
-    overflow: hidden;
-    // top: 50%;
-    // left: 50%;
-    // transform: translate(-50%, -50%);
-    background-color: rgba(0, 0, 0, 0.5);
-    z-index: 99999;
-
-    .coupon-container {
-        position: relative;
-        height: inherit;
-        width: inherit;
-        display: flex;
-        flex-direction: column;
-        justify-content: center;
-
-        .coupon-content {
-            position: relative;
-            width: 100%;
-            height: 638rpx;
-
-            .coupon-bg {
-                position: absolute;
-                left: 50%;
-                transform: translate(-50%, -30%);
-                width: 149%;
-                height: 149%;
-                z-index: 10;
-                object-fit: cover;
-            }
-
-            .coupon-content-layout {
-                position: absolute;
-                left: 50%;
-                transform: translate(-46%, 45%);
-                z-index: 11;
-                width: 400rpx;
-                height: 370rpx;
-
-                .coupon-content-text-content {
-                    width: 347rpx;
-                    height: 206rpx;
-                    margin-top: 125rpx;
-                    margin-left: 31rpx;
-
-                    display: flex;
-                    flex-direction: column;
-                    align-items: center;
-                    justify-content: center;
-                    gap: 12rpx;
-
-                    color: #ffffff;
-
-                    .coupon-content-text-title {
-                        display: flex;
-                        flex-direction: row;
-                        flex-wrap: nowrap;
-                        gap: 29rpx;
-
-                        .coupon-amount {
-                            display: flex;
-                            flex-direction: row;
-                            flex-wrap: nowrap;
-                            align-items: baseline;
-                            justify-content: end;
-
-                            .coupon-amount-count-number {
-                                font-weight: 400;
-                                font-size: 89rpx;
-                                color: #ffffff;
-                            }
-
-                            .coupon-amount-count-unit {
-                                font-weight: 500;
-                                font-size: 31rpx;
-                                color: #f62a2a;
-                                background: #ffffff;
-                                border-radius: 50%;
-                                border: 3px solid #f62a2a;
-                                padding: 8rpx;
-                                margin-left: -19rpx;
-                            }
-
-                            .coupon-amount-count-icon {
-                                font-weight: 400;
-                                font-size: 43rpx;
-                                color: #ffffff;
-                            }
-                        }
-
-                        .coupon-type {
-                            display: flex;
-                            flex-direction: column;
-                            justify-content: space-evenly;
-                            gap: 6rpx;
-                            font-weight: 400;
-                            font-size: 18rpx;
-                            color: #ffffff;
-                        }
-                    }
-
-                    .coupon-content-text-cart {
-                        padding: 8rpx 38rpx;
-                        background-color: #ffffff;
-                        font-weight: 500;
-                        font-size: 24rpx;
-                        color: #f62a2a;
-                    }
-
-                    .coupon-content-text-amount {
-                        font-weight: 400;
-                        font-size: 24rpx;
-                        color: #ffffff;
-                    }
-                }
-            }
-        }
-
-        .coupon-button {
-            position: relative;
-            width: 254rpx;
-            height: 75rpx;
-            left: 50%;
-            transform: translate(-43%, -83%);
-            z-index: 99999;
-            display: flex;
-            justify-content: center;
-            align-items: center;
-
-            // 去除button默认样式
-            background-color: transparent;
-            border: none;
-            padding: 0;
-            margin: 0;
-            outline: none;
-            cursor: pointer;
-            -webkit-appearance: none; // 移除浏览器默认样式
-            -moz-appearance: none;
-            appearance: none;
-
-            // 去除uni-app button默认样式
-            &::after {
-                display: none; // 移除默认边框
-            }
-
-            .coupon-btn-bg {
-                position: absolute;
-                width: 100%;
-                height: 110%;
-                z-index: 10;
-                object-fit: cover;
-                pointer-events: none; // 确保文字不阻挡点击事件
-            }
-
-            .coupon-btn-text {
-                position: absolute;
-                z-index: 11;
-
-                height: 34rpx;
-                width: 138rpx;
-
-                text-align: center;
-                font-weight: bolder;
-                font-size: 34rpx;
-                color: #bd521e;
-                line-height: 30rpx;
-                padding-bottom: 14rpx;
-                background: linear-gradient(180deg, #fffadc 0%, #ffef8d 100%);
-                -webkit-background-clip: text;
-                -webkit-text-fill-color: transparent;
-                pointer-events: none; // 确保文字不阻挡点击事件
-            }
-        }
-    }
-}
-</style>

+ 111 - 0
src/components/ShortLinkModal.vue

@@ -0,0 +1,111 @@
+<script setup lang="ts">
+// 监听showModal变化,当弹窗显示时自动获取分享短链
+import { watch } from 'vue'
+
+import { useCouponShare } from '../hooks/useCouponShare'
+
+// 定义props
+const props = defineProps<{
+    couponId?: string // 优惠券ID,从父组件传入
+    showModal?: boolean // 控制弹窗显示/隐藏的prop
+}>()
+
+// 定义emit
+const emit = defineEmits<{
+    // 弹窗关闭时触发
+    (e: 'close'): void
+    // 用于v-model的update事件
+    (e: 'update:showModal', value: boolean): void
+}>()
+
+const { shareLink, copySuccess, shareLoading, shareCoupon, copyShareLink } = useCouponShare()
+watch(() => props.showModal, (newVal) => {
+    console.log('showModal', newVal)
+    if (newVal && props.couponId) {
+        shareCoupon(props.couponId)
+    }
+})
+
+// 弹窗关闭时触发事件
+function handleClose() {
+    emit('update:showModal', false)
+    emit('close')
+}
+</script>
+
+<template>
+    <!-- 分享弹窗 - 使用uview-plus的Modal组件 -->
+    <!-- <up-modal :show="props.showModal" title="分享优惠券" :show-cancel-button="false" confirm-text="关闭" :border-radius="16"
+        width="600rpx" @confirm="handleClose">
+        <view class="share-modal-content">
+            <view class="share-link-container">
+                <text class="share-link">{{ shareLink }}</text>
+                <button class="copy-btn" @click="copyShareLink">
+                    复制
+                </button>
+            </view>
+            <view v-if="copySuccess" class="copy-success">
+                复制成功!
+            </view>
+        </view>
+    </up-modal> -->
+    <up-modal title="分享优惠券" style="z-index: 1000;" :show="props.showModal" :show-confirm-button="false">
+        <view class="share-modal-content">
+            <view class="share-link-container">
+                <text class="share-link">{{ shareLink }}</text>
+                <button class="copy-btn" @click="copyShareLink">
+                    复制
+                </button>
+            </view>
+            <view v-if="copySuccess" class="copy-success">
+                复制成功!
+            </view>
+        </view>
+        <template #popupBottom>
+            <view class="rounded" style="margin-top: 20px;" @click="handleClose">
+                <up-icon name="close" color="#fff" />
+            </view>
+        </template>
+    </up-modal>
+</template>
+
+<style scoped>
+.share-modal-content {
+    margin: 20rpx 0;
+}
+
+.share-link-container {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 20rpx;
+    background-color: #f5f5f5;
+    border-radius: 8rpx;
+    margin-bottom: 20rpx;
+}
+
+.share-link {
+    flex: 1;
+    font-size: 28rpx;
+    color: #333;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    margin-right: 20rpx;
+}
+
+.copy-btn {
+    padding: 10rpx 20rpx;
+    font-size: 28rpx;
+    color: #007aff;
+    background-color: transparent;
+    border: 1px solid #007aff;
+    border-radius: 8rpx;
+}
+
+.copy-success {
+    text-align: center;
+    font-size: 28rpx;
+    color: #07c160;
+}
+</style>

+ 41 - 4
src/components/discountCoupon.vue

@@ -1,5 +1,8 @@
 <script lang="ts" setup>
-import CouponImg from '@img/index/coupon2.png'
+import { computed, ref } from 'vue'
+import ShortLinkModal from '@/components/ShortLinkModal.vue'
+import { getImageUrl } from '@/utils/imageUtil'
+import { useCouponShare } from '../hooks/useCouponShare'
 
 const props = defineProps({
     coupon: {
@@ -25,11 +28,43 @@ const deadline = computed(() => {
     }
     return '长期有效'
 })
+
+// 分享弹窗相关 - 使用v-model控制弹窗显示
+const showShareModal = ref(false)
+const couponShareLink = ref('')
+
+const { shareCoupon, shareLoading } = useCouponShare()
+
+// 调用分享弹窗
+async function handleShareCoupon() {
+    // 确保有优惠券数据
+    if (coupon.value.templateId) {
+        uni.showLoading({
+            title: '获取分享链接中...',
+            mask: true,
+        })
+        try {
+            const shareLink = await shareCoupon(couponTemplateId.value)
+            if (shareLink) {
+                couponShareLink.value = shareLink
+                showShareModal.value = true
+            }
+        }
+        finally {
+            uni.hideLoading()
+        }
+    }
+}
+
+// 关闭分享弹窗
+function handleCloseShareModal() {
+    console.log('分享弹窗已关闭')
+}
 </script>
 
 <template>
     <view class="discount-coupon">
-        <image class="coupon-bg" :src="CouponImg" mode="aspectFill" />
+        <image class="coupon-bg" :src="getImageUrl('@img/index/coupon2.png')" mode="aspectFill" />
         <view class="discount-content">
             <view class="discount-amount">
                 <view class="discount-amount-number">
@@ -51,16 +86,18 @@ const deadline = computed(() => {
         <view class="discount-desc-time">
             {{ deadline }},最高优惠{{ coupon.ruleDiscountCapAmount }}元
         </view>
-        <button class="discount-btn" open-type="share" :data-coupon-id="couponTemplateId" data-share-type="coupon">
+        <!-- <button class="discount-btn" open-type="share" :data-coupon-id="couponTemplateId" data-share-type="coupon"> -->
+        <button class="discount-btn" @click="handleShareCoupon">
             <view>立即</view>
             <view>发券</view>
         </button>
+        <ShortLinkModal v-model:show-modal="showShareModal" :coupon-id="couponTemplateId" :share-link="couponShareLink"
+            @close="handleCloseShareModal" />
     </view>
 </template>
 
 <style lang="scss" scoped>
 .discount-coupon {
-    // width: 670rpx;
     width: 100%;
     max-width: 800rpx;
     height: 209rpx;

+ 42 - 16
src/components/spendAndSaveCoupon.vue

@@ -1,4 +1,9 @@
 <script lang="ts" setup>
+import { computed, ref } from 'vue'
+import ShortLinkModal from '@/components/ShortLinkModal.vue'
+import { getImageUrl } from '@/utils/imageUtil'
+import { useCouponShare } from '../hooks/useCouponShare'
+
 const props = defineProps({
     coupon: {
         type: Object,
@@ -12,23 +17,41 @@ const props = defineProps({
 const coupon = computed(() => props.coupon)
 const couponTemplateId = computed(() => coupon.value.templateId || '')
 
-// async function handleShareClick() {
-//     if (!props.coupon?.templateId)
-//         return
-
-//     try {
-//         // 触发分享事件,传递给父组件处理
-//         emit('share', props.coupon)
-//     }
-//     finally {
-//         // 这里不需要立即设置为false,因为异步分享完成后由父组件控制
-//         // shareLoading.value = false
-//     }
-// }
+// 分享弹窗相关 - 使用v-model控制弹窗显示
+const showShareModal = ref(false)
+const couponShareLink = ref('')
+
+const { shareCoupon, shareLoading } = useCouponShare()
+
+// 调用分享弹窗
+async function handleShareCoupon() {
+    // 确保有优惠券数据
+    if (coupon.value.templateId) {
+        uni.showLoading({
+            title: '获取分享链接中...',
+            mask: true,
+        })
+        try {
+            const shareLink = await shareCoupon(couponTemplateId.value)
+            if (shareLink) {
+                couponShareLink.value = shareLink
+                showShareModal.value = true
+            }
+        }
+        finally {
+            uni.hideLoading()
+        }
+    }
+}
+
+// 关闭分享弹窗
+function handleCloseShareModal() {
+    console.log('分享弹窗已关闭')
+}
 </script>
 
 <template>
-    <view class="spend-and-save-coupon">
+    <view class="spend-and-save-coupon" :style="{ backgroundImage: `url(${getImageUrl('@img/index/coupon1.png')})` }">
         <view class="coupon-content">
             <view class="coupon-amount">
                 <view class="coupon-amount-text">
@@ -43,9 +66,11 @@ const couponTemplateId = computed(() => coupon.value.templateId || '')
             </view>
         </view>
 
-        <button class="coupon-btn" open-type="share" :data-coupon-id="couponTemplateId" data-share-type="coupon">
+        <button class="coupon-btn" @click="handleShareCoupon">
             去发券
         </button>
+        <ShortLinkModal v-model:show-modal="showShareModal" :coupon-id="couponTemplateId" :share-link="couponShareLink"
+            @close="handleCloseShareModal" />
     </view>
 </template>
 
@@ -53,7 +78,8 @@ const couponTemplateId = computed(() => coupon.value.templateId || '')
 .spend-and-save-coupon {
     width: 210rpx;
     height: 205rpx;
-    background: url('@img/index/coupon1.png') no-repeat right top;
+    background-position: right top;
+    background-repeat: no-repeat;
     background-size: cover;
     position: relative;
 

+ 39 - 5
src/components/spendAndSaveCoupon_large.vue

@@ -1,5 +1,8 @@
 <script lang="ts" setup>
-import CouponImg from '@img/index/coupon3.png'
+import { computed, ref } from 'vue'
+import ShortLinkModal from '@/components/ShortLinkModal.vue'
+import { getImageUrl } from '@/utils/imageUtil'
+import { useCouponShare } from '../hooks/useCouponShare'
 
 const props = defineProps({
     coupon: {
@@ -32,11 +35,42 @@ const category = computed(() => {
         return `限${props.coupon.relatedName}商品使用`
     }
 })
+// 分享弹窗相关 - 使用v-model控制弹窗显示
+const showShareModal = ref(false)
+const couponShareLink = ref('')
+
+const { shareCoupon, shareLoading } = useCouponShare()
+
+// 调用分享弹窗
+async function handleShareCoupon() {
+    // 确保有优惠券数据
+    if (coupon.value.templateId) {
+        uni.showLoading({
+            title: '获取分享链接中...',
+            mask: true,
+        })
+        try {
+            const shareLink = await shareCoupon(couponTemplateId.value)
+            if (shareLink) {
+                couponShareLink.value = shareLink
+                showShareModal.value = true
+            }
+        }
+        finally {
+            uni.hideLoading()
+        }
+    }
+}
+
+// 关闭分享弹窗
+function handleCloseShareModal() {
+    console.log('分享弹窗已关闭')
+}
 </script>
 
 <template>
     <view class="discount-coupon">
-        <image class="coupon-bg" :src="CouponImg" mode="aspectFit" />
+        <image class="coupon-bg" :src="getImageUrl('@img/index/coupon3.png')" mode="aspectFit" />
         <view class="coupon-content">
             <view class="coupon-amount">
                 <view class="coupon-amount-count">
@@ -70,11 +104,12 @@ const category = computed(() => {
             </view>
         </view>
         <view class="discount-btn">
-            <button class="coupon-btn-container" open-type="share" :data-coupon-id="couponTemplateId"
-                data-share-type="coupon">
+            <button class="coupon-btn-container" @click="handleShareCoupon">
                 <view>立即</view>
                 <view>发券</view>
             </button>
+            <ShortLinkModal v-model:show-modal="showShareModal" :coupon-id="couponTemplateId"
+                :share-link="couponShareLink" @close="handleCloseShareModal" />
         </view>
     </view>
 </template>
@@ -101,7 +136,6 @@ const category = computed(() => {
 
     .coupon-content {
         position: absolute;
-        z-index: 1;
         height: 161.5rpx;
         width: calc(100% - 27.68%);
         padding-top: 18rpx;

+ 107 - 0
src/hooks/useCouponShare.ts

@@ -0,0 +1,107 @@
+import { useRequest } from 'alova/client'
+import { ref } from 'vue'
+import { getIssuerStatus } from '@/api/home'
+import { getCouponShareLink } from '@/api/receiveCoupon'
+import { useTokenStore } from '@/store/token'
+import { toLoginPage } from '@/utils/toLoginPage'
+
+interface ICouponShareResult {
+    shareLink: string // 分享短链
+}
+
+export function useCouponShare() {
+    // 分享短链
+    const shareLink = ref('')
+    // 复制成功提示
+    const copySuccess = ref(false)
+
+    const redirectPath = ref('pages/home/home')
+
+    // 获取分享短链的请求
+    const {
+        send: getShareLink,
+        loading: shareLoading,
+    } = useRequest(getCouponShareLink, {
+        immediate: false,
+    })
+
+    // 分享优惠券
+    const shareCoupon = async (couponId?: string) => {
+        try {
+            // 检查用户是否已登录
+            const tokenStore = useTokenStore()
+            if (!tokenStore.hasLogin) {
+                uni.showToast({
+                    title: '请先登录',
+                    icon: 'none',
+                    duration: 2000,
+                })
+                // 跳转到登录页
+                toLoginPage()
+                return null
+            }
+            // 判断是否是发券人
+            const IssuerResult = await getIssuerStatus()
+            if (!IssuerResult) {
+                uni.showToast({
+                    title: '您不是发券人,不能分享优惠券',
+                    icon: 'fail',
+                    duration: 2000,
+                })
+                return null
+            }
+
+            // 请求短链
+            const result = await getShareLink({
+                pathUrl: redirectPath.value,
+                templateId: couponId,
+            })
+            if (result) {
+                // 保存短链
+                console.log('分享短链:', result)
+                shareLink.value = result as string
+                return shareLink.value
+            }
+            return null
+        }
+        catch (error) {
+            console.error('获取分享短链失败:', error)
+            uni.showToast({
+                title: '获取分享短链失败',
+                icon: 'none',
+                duration: 2000,
+            })
+            return null
+        }
+    }
+
+    // 复制短链
+    const copyShareLink = () => {
+        // 使用uni-app的复制API
+        uni.setClipboardData({
+            data: shareLink.value,
+            success: () => {
+                // 显示复制成功提示
+                copySuccess.value = true
+                setTimeout(() => {
+                    copySuccess.value = false
+                }, 2000)
+            },
+            fail: () => {
+                uni.showToast({
+                    title: '复制失败',
+                    icon: 'none',
+                    duration: 2000,
+                })
+            }
+        })
+    }
+
+    return {
+        shareLink,
+        copySuccess,
+        shareLoading,
+        shareCoupon,
+        copyShareLink
+    }
+}

+ 11 - 9
src/hooks/useShare.ts

@@ -1,5 +1,6 @@
 import { getIssuerDetail, getShareInfo } from '@/api/home'
 import { useTokenStore } from '@/store/token'
+import { getImageUrl } from '@/utils/imageUtil'
 
 /**
  * 分享配置选项
@@ -25,8 +26,8 @@ interface ShareInfo {
  */
 const defaultOptions: Required<ShareOptions> = {
     title: '领券省心,消费超值,优质生活轻松开启!',
-    path: '/pages/index/index',
-    imageUrl: '/static/images/share.png',
+    path: '/pages/home/home',
+    imageUrl: `${getImageUrl('@img/share.png')}`,
     shareType: 'INVITE',
     showLoading: true,
     imageSource: 'LOCAL',
@@ -40,9 +41,9 @@ const defaultOptions: Required<ShareOptions> = {
  */
 export function useShare(initOptions: ShareOptions = {}) {
     /**
-                                           * 获取分享配置 - 可以直接在onShareAppMessage中调用
-                                           * @returns 完整的分享配置
-                                           */
+                                                     * 获取分享配置 - 可以直接在onShareAppMessage中调用
+                                                     * @returns 完整的分享配置
+                                                     */
     const getShareConfig = async (customOption?: ShareOptions, extraParams?): Promise<WechatMiniprogram.Page.ICustomShareContent | {}> => {
         // 合并默认配置和自定义配置
         const options = { ...defaultOptions, ...initOptions, ...customOption }
@@ -64,7 +65,8 @@ export function useShare(initOptions: ShareOptions = {}) {
                 })
             }
             const IssuerResult = await getIssuerDetail()
-            if (IssuerResult?.status !== '1') {
+            console.log(IssuerResult, 'IssuerResult')
+            if (!IssuerResult || (IssuerResult?.status && IssuerResult?.status !== '1')) {
                 throw new Error('当前用户不是发券人,无法发放优惠券')
             }
             const params: ShareInfo = {
@@ -126,9 +128,9 @@ export function useShare(initOptions: ShareOptions = {}) {
     }
 
     /**
-                                           * 获取朋友圈分享配置 - 可以直接在onShareTimeline中调用
-                                           * @returns 完整的朋友圈分享配置
-                                           */
+                                                     * 获取朋友圈分享配置 - 可以直接在onShareTimeline中调用
+                                                     * @returns 完整的朋友圈分享配置
+                                                     */
     const getTimelineShareConfig = async (): Promise<WechatMiniprogram.Page.ICustomShareTimelineContent | {}> => {
         const config = await getShareConfig() as WechatMiniprogram.Page.ICustomShareContent
         return {

+ 5 - 5
src/http/alova.ts

@@ -54,14 +54,14 @@ const alovaInstance = createAlova({
     beforeRequest: (method) => {
         // 设置默认 Content-Type
         method.config.headers = {
-            ContentType: ContentTypeEnum.JSON,
-            // #ifndef MP-WEIXIN
-            responseType: 'json',
-            // #endif
+            ContentType: method.type === 'POST' ? ContentTypeEnum.FORM_URLENCODED : ContentTypeEnum.JSON,
+            // // #ifndef MP-WEIXIN
+            // responseType: 'json',
+            // // #endif
             Accept: 'application/json, text/plain, */*',
             ...method.config.headers,
         }
-
+        console.log('method===>', method)
         const { config } = method
         const ignoreAuth = !config.meta?.ignoreAuth
         console.log('ignoreAuth===>', ignoreAuth)

+ 12 - 7
src/pages-A/applyForm/index.vue

@@ -4,7 +4,8 @@ import {
 } from 'alova/client'
 import {
     reactive,
-    ref
+    ref,
+    computed
 } from 'vue'
 import {
     couponIssuerApplyByAdd,
@@ -53,8 +54,8 @@ const {
 const formResult = ref(null)
 onLoad(async (options) => {
     await getCouponIssuerApplyByIdRequest()
-    console.log(999999, couponIssuerApplyByIdData.value.status)
-    if (couponIssuerApplyByIdData.value && couponIssuerApplyByIdData.value.status === '1') {
+    console.log(999999, couponIssuerApplyByIdData.value?.status)
+    if (couponIssuerApplyByIdData.value && couponIssuerApplyByIdData.value?.status && (couponIssuerApplyByIdData.value.status === '1' || couponIssuerApplyByIdData.value.status === '2')) {
         formResult.value = couponIssuerApplyByIdData.value
         model = couponIssuerApplyByIdData.value
     }
@@ -107,6 +108,10 @@ function submitForm() {
         })
     })
 }
+
+const isShowBtn = computed(() => {
+    return !formResult.value || (!!formResult.value && (formResult.value.status !== '1' && formResult.value.status !== '2'))
+})
 </script>
 
 <template>
@@ -114,10 +119,10 @@ function submitForm() {
         <view class="form-wrapper">
             <u-form ref="uFormRef" label-position="left" :model="model" :rules="rules">
                 <u-form-item ref="item1" label="申请人姓名" prop="name" :border-bottom="true">
-                    <u-input v-model="model.name" border="none" disabled="formResult" placeholder="请输入申请人姓名" />
+                    <u-input v-model="model.name" border="none" :disabled="formResult" placeholder="请输入申请人姓名" />
                 </u-form-item>
                 <u-form-item ref="item2" label="电话" prop="phone" :border-bottom="true">
-                    <u-input v-model="model.phone" border="none" disabled="formResult" placeholder="请输入小程序登录的手机号" />
+                    <u-input v-model="model.phone" border="none" :disabled="formResult" placeholder="请输入小程序登录的手机号" />
                 </u-form-item>
                 <u-form-item v-if="formResult" ref="item3" label="申请结果" :border-bottom="true">
                     <view v-if="formResult.status === '2'" class="form-item-result u-text-danger">
@@ -134,7 +139,7 @@ function submitForm() {
                 </view>
             </u-form>
 
-            <view v-if="formResult && formResult.status !== '1'" class="form-btn" @click="submitForm">
+            <view v-if="isShowBtn" class="form-btn" @click="submitForm">
                 提交申请
             </view>
         </view>
@@ -217,7 +222,7 @@ function submitForm() {
     background: linear-gradient(90deg, #ee6b67 0%, #ff7d78 100%);
     border-radius: 10rpx;
     position: fixed;
-    bottom: 52rpx;
+    bottom: 62rpx;
     left: 50%;
     transform: translateX(-50%);
     font-weight: 400;

+ 54 - 45
src/pages-A/bank/index.vue

@@ -3,6 +3,7 @@ import { storeToRefs } from 'pinia'
 import { ref } from 'vue'
 import { useUserStore } from '@/store'
 import { useTokenStore } from '@/store/token'
+import { getImageUrl } from '@/utils/imageUtil'
 
 definePage({
     style: {
@@ -27,7 +28,7 @@ const list = ref([{
             <view v-for="item in list" :key="item.bankName" class="bank-list-item">
                 <view class="bank-list-item-left">
                     <view class="bank-list-item-left-hanke">
-                        <image src="/static/images/income/y-1.png" />
+                        <image :src="getImageUrl('@img/income/y-1.png')" />
                         <view class="bank-list-item-left-hanke-text">
                             {{ item.bankName }}
                         </view>
@@ -43,57 +44,65 @@ const list = ref([{
 
 <style lang="scss" scoped>
 .profile-container {
-  font-family: Alibaba PuHuiTi;
-  min-height: 100vh;
-  background-color: #f5f5f5;
-  line-height: 1;
-  padding: 20rpx;
+    font-family: Alibaba PuHuiTi;
+    min-height: 100vh;
+    background-color: #f5f5f5;
+    line-height: 1;
+    padding: 20rpx;
 }
+
 .bank-list {
-  padding: 0 20rpx;
-  background: #ffff;
-  border-radius: 10rpx;
-  .bank-list-item {
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    // padding: 0 24rpx;
-    background: #fff;
+    padding: 0 20rpx;
+    background: #ffff;
     border-radius: 10rpx;
-    margin-bottom: 20rpx;
-    border-bottom: 1rpx solid #eeeeee;
-    &:last-child {
-      border-bottom: none;
-    }
-    .bank-list-item-left {
-      display: flex;
-      align-items: center;
-      gap: 14rpx;
-      padding: 28rpx 0 28rpx 4rpx;
-      .bank-list-item-left-hanke {
+
+    .bank-list-item {
         display: flex;
         align-items: center;
-        gap: 14rpx;
-        font-weight: 400;
-        font-size: 26rpx;
-        color: #333333;
-        image {
-          width: 30rpx;
-          height: 30rpx;
+        justify-content: space-between;
+        // padding: 0 24rpx;
+        background: #fff;
+        border-radius: 10rpx;
+        margin-bottom: 20rpx;
+        border-bottom: 1rpx solid #eeeeee;
+
+        &:last-child {
+            border-bottom: none;
         }
-        .bank-list-item-left-hanke-text {
-          font-weight: 400;
-          font-size: 26rpx;
-          color: #333333;
+
+        .bank-list-item-left {
+            display: flex;
+            align-items: center;
+            gap: 14rpx;
+            padding: 28rpx 0 28rpx 4rpx;
+
+            .bank-list-item-left-hanke {
+                display: flex;
+                align-items: center;
+                gap: 14rpx;
+                font-weight: 400;
+                font-size: 26rpx;
+                color: #333333;
+
+                image {
+                    width: 30rpx;
+                    height: 30rpx;
+                }
+
+                .bank-list-item-left-hanke-text {
+                    font-weight: 400;
+                    font-size: 26rpx;
+                    color: #333333;
+                }
+            }
+        }
+
+        .bank-list-item-right {
+            display: flex;
+            align-items: center;
+            gap: 10rpx;
+            padding: 28rpx 4rpx 28rpx 0;
         }
-      }
-    }
-    .bank-list-item-right {
-      display: flex;
-      align-items: center;
-      gap: 10rpx;
-      padding: 28rpx 4rpx 28rpx 0;
     }
-  }
 }
 </style>

+ 1 - 1
src/pages-A/discountcouponList/index.vue

@@ -4,7 +4,7 @@ import { useScroll } from '@/hooks/useScroll'
 import { useShare } from '@/hooks/useShare'
 import { safeAreaInsets } from '@/utils'
 import { getCouponByType, getCouponDetail } from '../../api/home'
-import DiscountCoupon from '../../components/discountCoupon.vue'
+import DiscountCoupon from '../../components/DiscountCoupon.vue'
 
 definePage({
     style: {

+ 143 - 127
src/pages-A/myBankList/index.vue

@@ -3,33 +3,34 @@ import { storeToRefs } from 'pinia'
 import { ref } from 'vue'
 import { useUserStore } from '@/store'
 import { useTokenStore } from '@/store/token'
+import { getImageUrl } from '@/utils/imageUtil'
 
 definePage({
-  style: {
-    navigationBarTitleText: '发券人申请',
-  },
+    style: {
+        navigationBarTitleText: '发券人申请',
+    },
 })
 
 const list = ref([{
-  bankName: '建设银行',
-  bankLogo: '/static/images/income/y-1.png',
-  bankNumber: '6228 **** **** *** 000',
-  bankShortName: 'jh',
+    bankName: '建设银行',
+    bankLogo: getImageUrl('@img/income/y-1.png'),
+    bankNumber: '6228 **** **** *** 000',
+    bankShortName: 'jh',
 }, {
-  bankName: '中国银行',
-  bankLogo: '/static/images/income/y-1.png',
-  bankNumber: '6228 **** **** *** 000',
-  bankShortName: 'zh',
+    bankName: '中国银行',
+    bankLogo: getImageUrl('@img/income/y-1.png'),
+    bankNumber: '6228 **** **** *** 000',
+    bankShortName: 'zh',
 }, {
-  bankName: '农业银行',
-  bankLogo: '/static/images/income/y-1.png',
-  bankNumber: '6228 **** **** *** 000',
-  bankShortName: 'nh',
+    bankName: '农业银行',
+    bankLogo: getImageUrl('@img/income/y-1.png'),
+    bankNumber: '6228 **** **** *** 000',
+    bankShortName: 'nh',
 }, {
-  bankName: '工商银行',
-  bankLogo: '/static/images/income/y-1.png',
-  bankNumber: '6228 **** **** *** 000',
-  bankShortName: 'gh',
+    bankName: '工商银行',
+    bankLogo: getImageUrl('@img/income/y-1.png'),
+    bankNumber: '6228 **** **** *** 000',
+    bankShortName: 'gh',
 }])
 function bindBank() {
     uni.navigateTo({
@@ -42,16 +43,13 @@ function bindBank() {
     <view class="profile-container">
         <view class="bank-list">
             <template v-if="list.length">
-                <view
-                    v-for="item in list"
-                    :key="item.bankName"
-                    class="bank-list-item-container"
-                    :class="item.bankShortName"
-                >
+                <view v-for="item in list" :key="item.bankName" class="bank-list-item-container"
+                    :class="item.bankShortName">
                     <view class="bank-list-item">
                         <view class="bank-list-item-left">
                             <view class="bank-list-item-left-hanke">
-                                <image :src="`/static/images/income/logo-${item.bankShortName}.png`" class="bank-list-item-left-hanke-logo" />
+                                <image :src="getImageUrl(`@img/income/logo-${item.bankShortName}.png`)"
+                                    class="bank-list-item-left-hanke-logo" />
                                 <view class="bank-list-item-left-hanke-text">
                                     {{ item.bankName }}
                                 </view>
@@ -69,11 +67,12 @@ function bindBank() {
                             </text>
                         </view>
                     </view>
-                    <image :src="`/static/images/income/logo${item.bankShortName}.png`" class="bank-list-item-right-logo" />
+                    <image :src="getImageUrl(`@img/income/logo${item.bankShortName}.png`)"
+                        class="bank-list-item-right-logo" />
                 </view>
             </template>
             <view v-else class="bank-list-empty-container">
-                <image src="/static/images/income/emptyBank.png" class="bank-list-empty" />
+                <image :src="getImageUrl('@img/income/emptyBank.png')" class="bank-list-empty" />
                 <view class="bank-list-empty-text">
                     暂无银行卡~
                 </view>
@@ -87,115 +86,132 @@ function bindBank() {
 
 <style lang="scss" scoped>
 .profile-container {
-  font-family: Alibaba PuHuiTi;
-  min-height: 100vh;
-  background-color: #f5f5f5;
-  line-height: 1;
-  padding: 20rpx;
+    font-family: Alibaba PuHuiTi;
+    min-height: 100vh;
+    background-color: #f5f5f5;
+    line-height: 1;
+    padding: 20rpx;
 }
+
 .bank-list {
-  padding: 0 20rpx;
-  border-radius: 10rpx;
-  .bank-list-item-container {
-    position: relative;
-    &.jh {
-      background: linear-gradient(270deg, #3571fe 0%, #5386ff 100%);
-    }
-    &.gh {
-      background: linear-gradient(270deg, #ff3137 0%, #ff7278 100%);
-    }
-    &.zh {
-      background: linear-gradient(270deg, #c80c31 0%, #bd445c 100%);
-    }
-    &.nh {
-      background: linear-gradient(270deg, #009981 0%, #00b699 100%);
+    padding: 0 20rpx;
+    border-radius: 10rpx;
+
+    .bank-list-item-container {
+        position: relative;
+
+        &.jh {
+            background: linear-gradient(270deg, #3571fe 0%, #5386ff 100%);
+        }
+
+        &.gh {
+            background: linear-gradient(270deg, #ff3137 0%, #ff7278 100%);
+        }
+
+        &.zh {
+            background: linear-gradient(270deg, #c80c31 0%, #bd445c 100%);
+        }
+
+        &.nh {
+            background: linear-gradient(270deg, #009981 0%, #00b699 100%);
+        }
+
+        .bank-list-item {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            padding: 30rpx 27rpx 40rpx 30rpx;
+            border-radius: 10rpx;
+            margin-bottom: 20rpx;
+            color: #fff;
+            position: relative;
+            z-index: 1;
+
+            .bank-list-item-left {
+                display: flex;
+                flex-direction: column;
+                gap: 38rpx;
+
+                .bank-list-item-left-hanke {
+                    display: flex;
+                    align-items: center;
+                    gap: 14rpx;
+                    font-weight: 400;
+                    font-size: 30rpx;
+
+                    image.bank-list-item-left-hanke-logo {
+                        width: 40rpx;
+                        height: 40rpx;
+                    }
+
+                    .bank-list-item-left-hanke-text {
+                        font-weight: 400;
+                        font-size: 30rpx;
+                    }
+                }
+            }
+
+            .bank-list-item-right {
+                display: flex;
+                flex-direction: column;
+                gap: 41rpx;
+                font-size: 24rpx;
+                position: relative;
+
+                .bank-list-item-right-full-number {
+                    font-weight: 400;
+                    color: #ffffff;
+                    text-align: center;
+                    background: rgba(#001649, 0.21);
+                    border-radius: 10rpx;
+                    padding: 7rpx 15rpx;
+                }
+            }
+        }
+
+        .bank-list-item-right-logo {
+            width: 115rpx;
+            height: 122rpx;
+            position: absolute;
+            bottom: 11rpx;
+            right: 53rpx;
+        }
     }
-    .bank-list-item {
-      display: flex;
-      align-items: center;
-      justify-content: space-between;
-      padding: 30rpx 27rpx 40rpx 30rpx;
-      border-radius: 10rpx;
-      margin-bottom: 20rpx;
-      color: #fff;
-      position: relative;
-      z-index: 1;
-
-      .bank-list-item-left {
+
+    .bank-list-empty-container {
         display: flex;
         flex-direction: column;
-        gap: 38rpx;
-        .bank-list-item-left-hanke {
-          display: flex;
-          align-items: center;
-          gap: 14rpx;
-          font-weight: 400;
-          font-size: 30rpx;
-          image.bank-list-item-left-hanke-logo {
-            width: 40rpx;
-            height: 40rpx;
-          }
-          .bank-list-item-left-hanke-text {
+        align-items: center;
+        justify-content: center;
+        gap: 57rpx;
+
+        .bank-list-empty {
+            width: 358rpx;
+            height: 246rpx;
+            margin-top: 181rpx;
+        }
+
+        .bank-list-empty-text {
             font-weight: 400;
             font-size: 30rpx;
-          }
-        }
-      }
-      .bank-list-item-right {
-        display: flex;
-        flex-direction: column;
-        gap: 41rpx;
-        font-size: 24rpx;
-        position: relative;
-        .bank-list-item-right-full-number {
-          font-weight: 400;
-          color: #ffffff;
-          text-align: center;
-          background: rgba(#001649, 0.21);
-          border-radius: 10rpx;
-          padding: 7rpx 15rpx;
+            color: #888888;
         }
-      }
     }
-    .bank-list-item-right-logo {
-      width: 115rpx;
-      height: 122rpx;
-      position: absolute;
-      bottom: 11rpx;
-      right: 53rpx;
-    }
-  }
-  .bank-list-empty-container {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    gap: 57rpx;
-    .bank-list-empty {
-      width: 358rpx;
-      height: 246rpx;
-      margin-top: 181rpx;
-    }
-    .bank-list-empty-text {
-      font-weight: 400;
-      font-size: 30rpx;
-      color: #888888;
-    }
-  }
 }
+
 .submit-btn {
-  width: 600rpx;
-  height: 80rpx;
-  background: linear-gradient(90deg, #ee6b67 0%, #ff7d78 100%);
-  border-radius: 10rpx;
-  font-weight: 400;
-  font-size: 30rpx;
-  color: #ffffff;
-  text-align: center;
-  line-height: 80rpx;
-  position: fixed;
-  bottom: 52rpx;
-  left: 50%;
-  transform: translateX(-50%);
+    width: 600rpx;
+    height: 80rpx;
+    background: linear-gradient(90deg, #ee6b67 0%, #ff7d78 100%);
+    border-radius: 10rpx;
+    font-weight: 400;
+    font-size: 30rpx;
+    color: #ffffff;
+    text-align: center;
+    line-height: 80rpx;
+    position: fixed;
+    bottom: 52rpx;
+    left: 50%;
+    transform: translateX(-50%);
 }
 </style>

+ 1 - 1
src/pages-A/spendAndSaveCouponList/index.vue

@@ -4,7 +4,7 @@ import { useScroll } from '@/hooks/useScroll'
 import { useShare } from '@/hooks/useShare'
 import { safeAreaInsets } from '@/utils'
 import { getCouponByType, getCouponDetail } from '../../api/home'
-import SpendAndSaveCoupon from '../../components/spendAndSaveCoupon_large.vue'
+import SpendAndSaveCoupon from '../../components/SpendAndSaveCoupon_large.vue'
 
 definePage({
     style: {

+ 130 - 133
src/pages-A/unlockRewards/index.vue

@@ -9,6 +9,7 @@ import { LOGIN_PAGE } from '@/router/config'
 import { useUserStore } from '@/store'
 import { useTokenStore } from '@/store/token'
 import { changtime, menuButtonInfo, safeAreaInsets, systemInfo } from '@/utils'
+import { getImageUrl } from '@/utils/imageUtil'
 
 definePage({
     style: {
@@ -85,14 +86,10 @@ function submitPay() {
         <CustomNavigationBar title="解锁收益" background-color="transparent" />
 
         <!-- 顶部区域 -->
-        <view
-            class="income-header"
-            style="background: url('../../static/images/income/income-bg.png') no-repeat center center; background-size: cover;"
-        >
-            <view
-                class="income-header-avatar-info"
-                :style="{ paddingTop: `${safeAreaInsets.top + menuButtonInfo.height + 12}px` }"
-            >
+        <view class="income-header"
+            :style="{ background: `url(${getImageUrl('@img/income/income-bg.png')}) no-repeat center center`, backgroundSize: 'cover' }">
+            <view class="income-header-avatar-info"
+                :style="{ paddingTop: `${safeAreaInsets.top + menuButtonInfo.height + 12}px` }">
                 <view class="income-header-balance">
                     锁定余额(元)
                 </view>
@@ -144,149 +141,149 @@ function submitPay() {
 
 <style lang="scss" scoped>
 ::v-deep .u-input__content__field-wrapper__field {
-  height: 80rpx !important;
-  font-size: 80rpx !important;
+    height: 80rpx !important;
+    font-size: 80rpx !important;
 }
 
 .profile-container {
-  font-family: Alibaba PuHuiTi;
-  min-height: 100vh;
-  background-color: #f5f5f5;
-  line-height: 1;
-
-  //   padding-top: 44px; /* 为固定导航栏留出空间 */
-  .income-header {
-    height: 525rpx;
-
-    .income-header-avatar-info {
-      display: flex;
-      flex-direction: column;
-      gap: 30rpx;
-      padding: 0 24rpx;
-
-      .income-header-balance {
-        font-weight: 400;
-        font-size: 26rpx;
-        color: #ffffff;
-      }
+    font-family: Alibaba PuHuiTi;
+    min-height: 100vh;
+    background-color: #f5f5f5;
+    line-height: 1;
+
+    //   padding-top: 44px; /* 为固定导航栏留出空间 */
+    .income-header {
+        height: 525rpx;
+
+        .income-header-avatar-info {
+            display: flex;
+            flex-direction: column;
+            gap: 30rpx;
+            padding: 0 24rpx;
+
+            .income-header-balance {
+                font-weight: 400;
+                font-size: 26rpx;
+                color: #ffffff;
+            }
 
-      .income-header-balance-num {
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
+            .income-header-balance-num {
+                display: flex;
+                justify-content: space-between;
+                align-items: center;
+
+                .income-header-balance-num-amount {
+                    font-weight: 500;
+                    font-size: 65rpx;
+                    color: #ffffff;
+                }
+
+                .income-header-balance-num-btns {
+                    display: flex;
+                    gap: 15rpx;
+
+                    .income-header-balance-num-btn {
+                        padding: 20rpx 40rpx;
+                        border-radius: 33rpx;
+                        font-weight: 400;
+                        font-size: 26rpx;
+
+                        &.js {
+                            background: #da4c47;
+                            color: #ffffff;
+                        }
+
+                        &.tx {
+                            background: #bfbfbf;
+                            color: #747474;
+                        }
+                    }
+                }
+            }
+        }
 
-        .income-header-balance-num-amount {
-          font-weight: 500;
-          font-size: 65rpx;
-          color: #ffffff;
+        .income-header-tips {
+            height: 124rpx;
+            display: flex;
+            background: linear-gradient(114deg, #f67873, #fb847f, #f67873);
+            border-radius: 10rpx;
+            margin: 47rpx 24rpx 0 24rpx;
+
+            .income-header-tips-item {
+                flex: 1;
+                display: flex;
+                flex-direction: column;
+                justify-content: center;
+                align-items: center;
+                gap: 15rpx;
+                font-weight: 500;
+                font-size: 30rpx;
+                color: #ffffff;
+                line-height: 1;
+                position: relative;
+
+                &:first-child:after {
+                    content: '';
+                    position: absolute;
+                    top: 50%;
+                    right: 0;
+                    transform: translateY(-50%);
+                    width: 2px;
+                    height: 40rpx;
+                    background: #ffffff;
+                }
+
+                .income-header-tips-item-num {
+                    font-weight: bold;
+                }
+
+                .income-header-tips-item-des {
+                    font-weight: 400;
+                    font-size: 24rpx;
+                    color: #ffffff;
+                }
+            }
         }
+    }
 
-        .income-header-balance-num-btns {
-          display: flex;
-          gap: 15rpx;
+    .income-header-menu {
+        background: #ffffff;
+        border-radius: 10rpx;
+        margin: 24rpx 24rpx 0;
+        margin-top: -30rpx;
 
-          .income-header-balance-num-btn {
-            padding: 20rpx 40rpx;
-            border-radius: 33rpx;
+        .income-header-menu-title {
             font-weight: 400;
             font-size: 26rpx;
+            color: #333333;
+            padding: 34rpx 20rpx;
+            border-bottom: 2rpx solid #eeeeee;
+        }
 
-            &.js {
-              background: #da4c47;
-              color: #ffffff;
-            }
+        .income-header-menu-input {
+            display: flex;
+            align-items: flex-end;
+            gap: 17rpx;
+            padding: 66rpx 20rpx;
 
-            &.tx {
-              background: #bfbfbf;
-              color: #747474;
+            .income-header-menu-input-symbol {
+                font-weight: 400;
+                font-size: 60rpx;
+                color: #333333;
             }
-          }
         }
-      }
     }
 
-    .income-header-tips {
-      height: 124rpx;
-      display: flex;
-      background: linear-gradient(114deg, #f67873, #fb847f, #f67873);
-      border-radius: 10rpx;
-      margin: 47rpx 24rpx 0 24rpx;
-
-      .income-header-tips-item {
-        flex: 1;
-        display: flex;
-        flex-direction: column;
-        justify-content: center;
-        align-items: center;
-        gap: 15rpx;
-        font-weight: 500;
+    .income-header-menu-btn {
+        width: 600rpx;
+        line-height: 80rpx;
+        background: linear-gradient(90deg, #ee6b67 0%, #ff7d78 100%);
+        border-radius: 10rpx;
+        margin: 100rpx auto 0;
+        font-weight: 400;
         font-size: 30rpx;
         color: #ffffff;
-        line-height: 1;
-        position: relative;
-
-        &:first-child:after {
-          content: '';
-          position: absolute;
-          top: 50%;
-          right: 0;
-          transform: translateY(-50%);
-          width: 2px;
-          height: 40rpx;
-          background: #ffffff;
-        }
-
-        .income-header-tips-item-num {
-          font-weight: bold;
-        }
-
-        .income-header-tips-item-des {
-          font-weight: 400;
-          font-size: 24rpx;
-          color: #ffffff;
-        }
-      }
-    }
-  }
-
-  .income-header-menu {
-    background: #ffffff;
-    border-radius: 10rpx;
-    margin: 24rpx 24rpx 0;
-    margin-top: -30rpx;
-
-    .income-header-menu-title {
-      font-weight: 400;
-      font-size: 26rpx;
-      color: #333333;
-      padding: 34rpx 20rpx;
-      border-bottom: 2rpx solid #eeeeee;
-    }
-
-    .income-header-menu-input {
-      display: flex;
-      align-items: flex-end;
-      gap: 17rpx;
-      padding: 66rpx 20rpx;
-
-      .income-header-menu-input-symbol {
-        font-weight: 400;
-        font-size: 60rpx;
-        color: #333333;
-      }
+        text-align: center;
     }
-  }
-
-  .income-header-menu-btn {
-    width: 600rpx;
-    line-height: 80rpx;
-    background: linear-gradient(90deg, #ee6b67 0%, #ff7d78 100%);
-    border-radius: 10rpx;
-    margin: 100rpx auto 0;
-    font-weight: 400;
-    font-size: 30rpx;
-    color: #ffffff;
-    text-align: center;
-  }
 }
 </style>

+ 119 - 108
src/pages-A/withdraw/index.vue

@@ -4,11 +4,12 @@ import { ref } from 'vue'
 import { LOGIN_PAGE } from '@/router/config'
 import { useUserStore } from '@/store'
 import { useTokenStore } from '@/store/token'
+import { getImageUrl } from '@/utils/imageUtil'
 
 definePage({
-  style: {
-    navigationBarTitleText: '提现',
-  },
+    style: {
+        navigationBarTitleText: '提现',
+    },
 })
 
 const userStore = useUserStore()
@@ -19,9 +20,9 @@ const { userInfo } = storeToRefs(userStore)
 const unlockAmount = ref('')
 
 function goPage(page: string) {
-  uni.navigateTo({
-    url: `/pages-A/${page}/index`,
-  })
+    uni.navigateTo({
+        url: `/pages-A/${page}/index`,
+    })
 }
 </script>
 
@@ -33,7 +34,7 @@ function goPage(page: string) {
                     提现至
                 </view>
                 <view class="widthdraw-select-card-left-hanke">
-                    <image src="/static/images/income/y-1.png" />
+                    <image :src="getImageUrl('@img/income/y-1.png')" />
                     <view class="widthdraw-select-card-left-hanke-text">
                         建设银行(8463)
                     </view>
@@ -53,11 +54,7 @@ function goPage(page: string) {
             <view class="income-header-menu-input-box">
                 <view class="income-header-menu-input">
                     <text class="income-header-menu-input-symbol">¥</text>
-                    <u-input
-                        v-model="unlockAmount"
-                        type="number"
-                        border="none"
-                    />
+                    <u-input v-model="unlockAmount" type="number" border="none" />
                 </view>
                 <view class="income-header-menu-input-text">
                     全部提现
@@ -78,111 +75,125 @@ function goPage(page: string) {
 
 <style lang="scss" scoped>
 ::v-deep .u-input__content__field-wrapper__field {
-  height: 80rpx !important;
-  font-size: 80rpx !important;
+    height: 80rpx !important;
+    font-size: 80rpx !important;
 }
+
 .profile-container {
-  font-family: Alibaba PuHuiTi;
-  min-height: calc(100vh - 20rpx);
-  background-color: #f5f5f5;
-  line-height: 1;
-  padding: 20rpx 0 0 0;
-  //   padding-top: 44px; /* 为固定导航栏留出空间 */
-  .widthdraw-select-card {
-    height: 88rpx;
-    background: #ffffff;
-    border-radius: 10rpx;
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    font-weight: 400;
-    font-size: 26rpx;
-    color: #333333;
-    padding: 0 20rpx;
-    margin: 0 20rpx;
-    .widthdraw-select-card-left {
-      display: flex;
-      align-items: center;
-      gap: 38rpx;
-      .widthdraw-select-card-left-hanke {
+    font-family: Alibaba PuHuiTi;
+    min-height: calc(100vh - 20rpx);
+    background-color: #f5f5f5;
+    line-height: 1;
+    padding: 20rpx 0 0 0;
+
+    //   padding-top: 44px; /* 为固定导航栏留出空间 */
+    .widthdraw-select-card {
+        height: 88rpx;
+        background: #ffffff;
+        border-radius: 10rpx;
         display: flex;
+        justify-content: space-between;
         align-items: center;
-        gap: 15rpx;
-        image {
-          width: 30rpx;
-          height: 30rpx;
+        font-weight: 400;
+        font-size: 26rpx;
+        color: #333333;
+        padding: 0 20rpx;
+        margin: 0 20rpx;
+
+        .widthdraw-select-card-left {
+            display: flex;
+            align-items: center;
+            gap: 38rpx;
+
+            .widthdraw-select-card-left-hanke {
+                display: flex;
+                align-items: center;
+                gap: 15rpx;
+
+                image {
+                    width: 30rpx;
+                    height: 30rpx;
+                }
+            }
+        }
+
+        .widthdraw-select-card-right {
+            display: flex;
+            align-items: center;
+            gap: 20rpx;
         }
-      }
-    }
-    .widthdraw-select-card-right {
-      display: flex;
-      align-items: center;
-      gap: 20rpx;
-    }
-  }
-
-  .income-header-menu {
-    background: #ffffff;
-    border-radius: 10rpx;
-    margin: 24rpx 24rpx 0;
-    .income-header-menu-title {
-      font-weight: 400;
-      font-size: 26rpx;
-      color: #333333;
-      padding: 34rpx 20rpx;
-      //   border-bottom: 2rpx solid #eeeeee;
     }
-    .income-header-menu-input-box {
-      display: flex;
-      align-items: flex-end;
-      justify-content: space-between;
-      .income-header-menu-input {
-        display: flex;
-        align-items: flex-end;
-        gap: 17rpx;
-        padding: 66rpx 20rpx;
-        .income-header-menu-input-symbol {
-          font-weight: 400;
-          font-size: 60rpx;
-          color: #333333;
+
+    .income-header-menu {
+        background: #ffffff;
+        border-radius: 10rpx;
+        margin: 24rpx 24rpx 0;
+
+        .income-header-menu-title {
+            font-weight: 400;
+            font-size: 26rpx;
+            color: #333333;
+            padding: 34rpx 20rpx;
+            //   border-bottom: 2rpx solid #eeeeee;
         }
-      }
-      .income-header-menu-input-text {
-        width: 200rpx;
+
+        .income-header-menu-input-box {
+            display: flex;
+            align-items: flex-end;
+            justify-content: space-between;
+
+            .income-header-menu-input {
+                display: flex;
+                align-items: flex-end;
+                gap: 17rpx;
+                padding: 66rpx 20rpx;
+
+                .income-header-menu-input-symbol {
+                    font-weight: 400;
+                    font-size: 60rpx;
+                    color: #333333;
+                }
+            }
+
+            .income-header-menu-input-text {
+                width: 200rpx;
+                font-weight: 400;
+                font-size: 26rpx;
+                color: #1777ff;
+                padding: 66rpx 20rpx;
+            }
+        }
+
+        .income-header-menu-input-tips {
+            font-weight: 400;
+            font-size: 28rpx;
+            color: #888888;
+            padding: 30rpx 0;
+            margin: 0 20rpx;
+            border-top: 2rpx solid #eeeeee;
+        }
+    }
+
+    .income-header-menu-btn {
+        width: 600rpx;
+        line-height: 80rpx;
+        background: linear-gradient(90deg, #ee6b67 0%, #ff7d78 100%);
+        border-radius: 10rpx;
+        margin: 100rpx auto 0;
         font-weight: 400;
-        font-size: 26rpx;
-        color: #1777ff;
-        padding: 66rpx 20rpx;
-      }
+        font-size: 30rpx;
+        color: #ffffff;
+        text-align: center;
     }
-    .income-header-menu-input-tips {
-      font-weight: 400;
-      font-size: 28rpx;
-      color: #888888;
-      padding: 30rpx 0;
-      margin: 0 20rpx;
-      border-top: 2rpx solid #eeeeee;
+
+    .income-header-menu-btn-cancle {
+        width: 600rpx;
+        line-height: 80rpx;
+        margin: 10rpx auto 0;
+        font-weight: 400;
+        font-size: 30rpx;
+        color: #333;
+        text-align: center;
     }
-  }
-  .income-header-menu-btn {
-    width: 600rpx;
-    line-height: 80rpx;
-    background: linear-gradient(90deg, #ee6b67 0%, #ff7d78 100%);
-    border-radius: 10rpx;
-    margin: 100rpx auto 0;
-    font-weight: 400;
-    font-size: 30rpx;
-    color: #ffffff;
-    text-align: center;
-  }
-  .income-header-menu-btn-cancle {
-    width: 600rpx;
-    line-height: 80rpx;
-    margin: 10rpx auto 0;
-    font-weight: 400;
-    font-size: 30rpx;
-    color: #333;
-    text-align: center;
-  }
 }
 </style>

+ 156 - 148
src/pages-A/withdrawalDetails/index.vue

@@ -6,11 +6,12 @@ import { LOGIN_PAGE } from '@/router/config'
 import { useUserStore } from '@/store'
 import { useTokenStore } from '@/store/token'
 import { changtime, menuButtonInfo, safeAreaInsets, systemInfo } from '@/utils'
+import { getImageUrl } from '@/utils/imageUtil'
 
 definePage({
-  style: {
-    navigationBarTitleText: '提现明细',
-  },
+    style: {
+        navigationBarTitleText: '提现明细',
+    },
 })
 
 const userStore = useUserStore()
@@ -19,97 +20,97 @@ const tokenStore = useTokenStore()
 const { userInfo } = storeToRefs(userStore)
 
 function menuClick(page: string) {
-  uni.navigateTo({
-    url: `/pages-A/${page}/index`,
-  })
+    uni.navigateTo({
+        url: `/pages-A/${page}/index`,
+    })
 }
 
 const show = ref(false)
 // up-datetime-picker 返回字符串格式,FilterTime 返回数组格式
 const filterValue = ref<string[] | string>([])
 function confirm(value) {
-  // up-datetime-picker 组件确认时保持字符串格式 YYYY-MM
-  console.log(111, value)
-  filterValue.value = changtime(value.value, 'YYYY年MM月')
+    // up-datetime-picker 组件确认时保持字符串格式 YYYY-MM
+    console.log(111, value)
+    filterValue.value = changtime(value.value, 'YYYY年MM月')
     show.value = false
 }
 function cancel() {
-  show.value = false
+    show.value = false
 }
 function close() {
-  show.value = false
+    show.value = false
 }
 function showTimeFilter() {
-  show.value = true
+    show.value = true
 }
 
 // 处理时间选择变化
 function handleTimeChange(value: string | string[]) {
-  console.log('时间选择变化', value)
-  filterValue.value = value
+    console.log('时间选择变化', value)
+    filterValue.value = value
 }
 
 // 格式化时间显示
 function formatTimeDisplay() {
-  console.log('formatTimeDisplay1111', filterValue.value)
-  // 处理 filterValue 是字符串的情况(单个月份)
-  if (typeof filterValue.value === 'string') {
-    return filterValue.value
-  }
-  // 处理 filterValue 是数组的情况(时间范围)
-  if (!filterValue.value || !filterValue.value[0] || !filterValue.value[1]) {
-    // 当天的YYYY年MM月
-    return changtime(new Date(), 'YYYY年MM月')
-  }
-  return `${filterValue.value[0]} 至 ${filterValue.value[1]}`
+    console.log('formatTimeDisplay1111', filterValue.value)
+    // 处理 filterValue 是字符串的情况(单个月份)
+    if (typeof filterValue.value === 'string') {
+        return filterValue.value
+    }
+    // 处理 filterValue 是数组的情况(时间范围)
+    if (!filterValue.value || !filterValue.value[0] || !filterValue.value[1]) {
+        // 当天的YYYY年MM月
+        return changtime(new Date(), 'YYYY年MM月')
+    }
+    return `${filterValue.value[0]} 至 ${filterValue.value[1]}`
 }
 const wjsList = ref([
-  {
-    id: 1,
-    nickName: '昵称1',
-    name: '优惠券1',
-    price: 1000,
-    date: '2023-08-01 10:00:00',
-  },
-  {
-    id: 2,
-    nickName: '昵称2',
-    name: '优惠券2',
-    price: 1300,
-    date: '2023-10-01 10:00:00',
-  },
+    {
+        id: 1,
+        nickName: '昵称1',
+        name: '优惠券1',
+        price: 1000,
+        date: '2023-08-01 10:00:00',
+    },
+    {
+        id: 2,
+        nickName: '昵称2',
+        name: '优惠券2',
+        price: 1300,
+        date: '2023-10-01 10:00:00',
+    },
 ])
 const yjsList = ref([
-  {
-    id: 1,
-    nickName: '昵称3',
-    name: '优惠券3',
-    price: 2300,
-    date: '2023-10-01 10:00:00',
-  },
-  {
-    id: 2,
-    nickName: '昵称4',
-    name: '优惠券4',
-    price: 300,
-    date: '2023-10-01 10:00:00',
-  },
+    {
+        id: 1,
+        nickName: '昵称3',
+        name: '优惠券3',
+        price: 2300,
+        date: '2023-10-01 10:00:00',
+    },
+    {
+        id: 2,
+        nickName: '昵称4',
+        name: '优惠券4',
+        price: 300,
+        date: '2023-10-01 10:00:00',
+    },
 ])
 const list = ref([])
 onLoad(() => {
-  list.value = [...wjsList.value]
+    list.value = [...wjsList.value]
 })
 
 function goPage(page: string) {
-  uni.navigateTo({
-    url: `/pages-A/${page}/index`,
-  })
+    uni.navigateTo({
+        url: `/pages-A/${page}/index`,
+    })
 }
 
 const filterTimeRef = ref()
 function openDatePicker() {
-  // 调用子组件的方法
-  filterTimeRef.value.openPopup()
+    // 调用子组件的方法
+    filterTimeRef.value.openPopup()
 }
 </script>
 
@@ -133,12 +134,12 @@ function openDatePicker() {
             <view class="income-header-menu-list">
                 <view v-for="item in list" :key="item.id" class="income-header-menu-item">
                     <view class="income-header-menu-icon">
-                        <image src="@/static/images/income/tx-icon.png" mode="aspectFit" />
+                        <image :src="getImageUrl('@img/income/tx-icon.png')" mode="aspectFit" />
                         <view class="income-header-menu-text">
                             <view class="income-header-menu-text-nickname">
                                 提现金额
                             </view>
-                            <view>2025/10/29  12:44:26</view>
+                            <view>2025/10/29 12:44:26</view>
                         </view>
                     </view>
                     <view class="income-header-menu-left">
@@ -149,108 +150,115 @@ function openDatePicker() {
                 </view>
             </view>
         </view>
-        <up-datetime-picker
-            :value="Array.isArray(filterValue) ? filterValue[0] : filterValue"
-            mode="year-month"
-            :show="show"
-            close-on-click-overlay
-            @confirm="(value) => {
+        <up-datetime-picker :value="Array.isArray(filterValue) ? filterValue[0] : filterValue" mode="year-month"
+            :show="show" close-on-click-overlay @confirm="(value) => {
                 // filterValue.value = value;
                 confirm(value);
-            }"
-            @cancel="cancel"
-        />
+            }" @cancel="cancel" />
         <FilterTime ref="filterTimeRef" v-model="filterValue" @update:value="handleTimeChange" />
     </view>
 </template>
 
 <style lang="scss" scoped>
 .profile-container {
-  font-family: Alibaba PuHuiTi;
-  min-height: 100vh;
-  background-color: #f5f5f5;
-  line-height: 1;
-  padding: 20rpx;
-  .income-menu-time-filter {
-    height: 83rpx;
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    font-weight: 400;
-    font-size: 24rpx;
-    color: #333333;
-    .income-menu-time-filter-left {
-      display: flex;
-      align-items: center;
-      gap: 39rpx;
-      .income-menu-time-filter-text {
-        font-weight: 400;
-        font-size: 26rpx;
-        color: #000000;
-        display: flex;
-        align-items: center;
-        gap: 12rpx;
-      }
-    }
-    .income-menu-time-filter-right {
-      font-weight: 400;
-      font-size: 26rpx;
-      color: #1777ff;
-      display: flex;
-      align-items: center;
-    }
-  }
-  .income-header-menu {
-    .income-header-menu-list {
-      background: #ffffff;
-      border-radius: 10rpx;
-      .income-header-menu-item {
-        height: 88rpx;
+    font-family: Alibaba PuHuiTi;
+    min-height: 100vh;
+    background-color: #f5f5f5;
+    line-height: 1;
+    padding: 20rpx;
+
+    .income-menu-time-filter {
+        height: 83rpx;
         display: flex;
         justify-content: space-between;
         align-items: center;
-        border-bottom: 1px solid #eeeeee;
-        padding: 32rpx 0 29rpx;
-        margin: 0 26rpx;
-        &:last-child {
-          border-bottom: none;
+        font-weight: 400;
+        font-size: 24rpx;
+        color: #333333;
+
+        .income-menu-time-filter-left {
+            display: flex;
+            align-items: center;
+            gap: 39rpx;
+
+            .income-menu-time-filter-text {
+                font-weight: 400;
+                font-size: 26rpx;
+                color: #000000;
+                display: flex;
+                align-items: center;
+                gap: 12rpx;
+            }
         }
-        .income-header-menu-icon {
-          display: flex;
-          justify-content: center;
-          align-items: center;
-          gap: 19rpx;
-          image {
-            width: 60rpx;
-            height: 60rpx;
-          }
-          .income-header-menu-text {
+
+        .income-menu-time-filter-right {
             font-weight: 400;
-            font-size: 24rpx;
-            color: #888888;
-            .income-header-menu-text-nickname {
-              font-size: 28rpx;
-              color: #222222;
-              margin-bottom: 18rpx;
-            }
-          }
+            font-size: 26rpx;
+            color: #1777ff;
+            display: flex;
+            align-items: center;
         }
-        .income-header-menu-left {
-          height: 100%;
-          line-height: 100%;
-          font-weight: 500;
-          font-size: 24rpx;
-          color: #888888;
-          display: flex;
-          align-items: center;
-          .income-header-menu-left-amount {
-            font-size: 30rpx;
-            color: #222222;
-            margin-bottom: 18rpx;
-          }
+    }
+
+    .income-header-menu {
+        .income-header-menu-list {
+            background: #ffffff;
+            border-radius: 10rpx;
+
+            .income-header-menu-item {
+                height: 88rpx;
+                display: flex;
+                justify-content: space-between;
+                align-items: center;
+                border-bottom: 1px solid #eeeeee;
+                padding: 32rpx 0 29rpx;
+                margin: 0 26rpx;
+
+                &:last-child {
+                    border-bottom: none;
+                }
+
+                .income-header-menu-icon {
+                    display: flex;
+                    justify-content: center;
+                    align-items: center;
+                    gap: 19rpx;
+
+                    image {
+                        width: 60rpx;
+                        height: 60rpx;
+                    }
+
+                    .income-header-menu-text {
+                        font-weight: 400;
+                        font-size: 24rpx;
+                        color: #888888;
+
+                        .income-header-menu-text-nickname {
+                            font-size: 28rpx;
+                            color: #222222;
+                            margin-bottom: 18rpx;
+                        }
+                    }
+                }
+
+                .income-header-menu-left {
+                    height: 100%;
+                    line-height: 100%;
+                    font-weight: 500;
+                    font-size: 24rpx;
+                    color: #888888;
+                    display: flex;
+                    align-items: center;
+
+                    .income-header-menu-left-amount {
+                        font-size: 30rpx;
+                        color: #222222;
+                        margin-bottom: 18rpx;
+                    }
+                }
+            }
         }
-      }
     }
-  }
 }
 </style>

+ 1 - 2
src/pages-fg/404/index.vue

@@ -26,5 +26,4 @@ function goBack() {
 </template>
 
 <style lang="scss" scoped>
-//
-</style>
+//</style>

+ 2 - 1
src/pages-fg/login/login.vue

@@ -1,6 +1,7 @@
 <script lang="ts" setup>
 import { useTokenStore } from '@/store/token'
 import { isPageTabbar } from '@/tabbar/store'
+import { getImageUrl } from '@/utils/imageUtil'
 import { HOME_PAGE } from '@/utils/index'
 
 // 使用自定义导航栏样式
@@ -66,7 +67,7 @@ async function doLogin() {
     <view class="login-container">
         <!-- Logo区域 -->
         <view class="logo-section">
-            <image class="logo" src="~@/static/images/index/logo.jpg" mode="aspectFit" />
+            <image class="logo" :src="getImageUrl('@img/index/logo.jpg')" mode="aspectFit" />
         </view>
 
         <!-- 登录按钮区域 -->

+ 106 - 87
src/pages/index/index.vue → src/pages/home/home.vue

@@ -1,15 +1,14 @@
 <script lang="ts" setup>
-import couponBg from '@img/index/coupon-bg.png'
-import indexBg from '@img/index/index-bg.png'
 import { useRequest } from 'alova/client'
 import { storeToRefs } from 'pinia'
 import { ref, watch } from 'vue'
 import { getAccountCount, getCouponDetail, getCouponSituation, getShareInfo } from '@/api/home'
-import DiscountCoupon from '@/components/discountCoupon.vue'
-import SpendAndSaveCoupon from '@/components/spendAndSaveCoupon.vue'
+import DiscountCoupon from '@/components/DiscountCoupon.vue'
+import SpendAndSaveCoupon from '@/components/SpendAndSaveCoupon.vue'
 import { useShare } from '@/hooks/useShare'
 import { useCouponStore } from '@/store/coupon'
 import { useTokenStore } from '@/store/token'
+import { getImageUrl } from '@/utils/imageUtil'
 import { toLoginPage } from '@/utils/toLoginPage'
 
 defineOptions({
@@ -28,6 +27,8 @@ definePage({
 const tokenStore = useTokenStore()
 const { hasLogin } = storeToRefs(tokenStore)
 
+const refreshing = ref(false)
+
 // 分享配置
 const shareConfig = ref(null)
 const shareButtonRef = ref<HTMLElement | null>(null)
@@ -35,7 +36,7 @@ const currentCouponId = ref<string>('')
 
 // 获取优惠券
 const couponStore = useCouponStore()
-const { couponList, discountVoucherList } = storeToRefs(couponStore)
+const { couponList, discountVoucherList, loading } = storeToRefs(couponStore)
 
 // 获取首页收益
 const { send: getAccountCountRequest, data: accountCountData } = useRequest(getAccountCount, {
@@ -49,12 +50,13 @@ const { send: getCouponSituationRequest, data: couponSituationData } = useReques
     dependencies: [],
 })
 
-onLoad(async () => {
-    // 获取优惠券
-    couponStore.getCouponListByType()
-})
+// onLoad(async () => {
+//     // 获取优惠券
+//     couponStore.getCouponListByType()
+// })
 
 onShow((options) => {
+    couponStore.getCouponListByType()
     // 登录后查询收益数据
     if (hasLogin) {
         Promise.allSettled([getAccountCountRequest(), getCouponSituationRequest()])
@@ -132,104 +134,118 @@ onShareAppMessage(async (options) => {
     }
 })
 // #endif
+
+async function onRefresh() {
+    refreshing.value = true
+    couponStore.getCouponListByType()
+    setTimeout(() => {
+        refreshing.value = false
+    }, 1000)
+}
 </script>
 
 <template>
     <view class="home-container">
-        <!-- 顶部区域 -->
-        <view class="home-header">
-            <image class="home-header-bg" :src="indexBg" mode="aspectFill" />
-            <view class="absolute left-5 z-3 text-xl c-white" :style="{ top: `${navigationBarHeight - 39}px` }">
-                券中心
-            </view>
-            <view class="home-header-avatar-info" :style="{ paddingTop: `${navigationBarHeight + 10}px` }">
-                <view class="home-header-balance">
-                    我的收益(元)
-                </view>
-                <view class="home-header-balance-num">
-                    <view class="home-header-balance-num-amount">
-                        {{ accountCountData?.balance || 0 }}
-                    </view>
+        <up-pull-refresh :refreshing="refreshing" @refresh="onRefresh">
+            <!-- 顶部区域 -->
+            <view class="home-header">
+                <image class="home-header-bg" :src="getImageUrl('@img/index/index-bg.png')" mode="aspectFill" />
+                <view class="absolute left-5 z-3 text-xl c-white" :style="{ top: `${navigationBarHeight - 39}px` }">
+                    券中心
                 </view>
-            </view>
-            <view class="home-header-tips">
-                <view class="home-header-tips-item" @click="toCouponRedemptionList('2')">
-                    <view class="home-header-tips-item-num">
-                        {{ couponSituationData?.usedQuantity || 0 }}张
+                <view class="home-header-avatar-info" :style="{ paddingTop: `${navigationBarHeight + 10}px` }">
+                    <view class="home-header-balance">
+                        我的收益(元)
                     </view>
-                    <view class="home-header-tips-item-des">
-                        已核销
+                    <view class="home-header-balance-num">
+                        <view class="home-header-balance-num-amount">
+                            {{ accountCountData?.balance || 0 }}
+                        </view>
                     </view>
                 </view>
-                <view class="home-header-tips-item" @click="toCouponRedemptionList('1')">
-                    <view class="home-header-tips-item-num">
-                        {{ couponSituationData?.quantityToBeUsed || 0 }}张
+                <view class="home-header-tips">
+                    <view class="home-header-tips-item" @click="toCouponRedemptionList('2')">
+                        <view class="home-header-tips-item-num">
+                            {{ couponSituationData?.usedQuantity || 0 }}张
+                        </view>
+                        <view class="home-header-tips-item-des">
+                            已核销
+                        </view>
                     </view>
-                    <view class="home-header-tips-item-des">
-                        未核销
+                    <view class="home-header-tips-item" @click="toCouponRedemptionList('1')">
+                        <view class="home-header-tips-item-num">
+                            {{ couponSituationData?.quantityToBeUsed || 0 }}张
+                        </view>
+                        <view class="home-header-tips-item-des">
+                            未核销
+                        </view>
                     </view>
-                </view>
-                <view class="home-header-tips-item">
-                    <view class="home-header-tips-item-num">
-                        {{ couponSituationData?.quantityForComplimentary || 0 }}张
+                    <view class="home-header-tips-item">
+                        <view class="home-header-tips-item-num">
+                            {{ couponSituationData?.quantityForComplimentary || 0 }}张
+                        </view>
+                        <view class="home-header-tips-item-des">
+                            已发放
+                        </view>
                     </view>
-                    <view class="home-header-tips-item-des">
-                        已发放
+                </view>
+                <view v-if="!hasLogin" class="home-hidden" @click="login">
+                    <image class="home-hidden-img" :src="getImageUrl('@img/index/lock.png')" mode="scaleToFill" />
+                    <view class="home-hidden-text">
+                        请登录,查看更多内容~
                     </view>
                 </view>
             </view>
-            <view v-if="!hasLogin" class="home-hidden" @click="login">
-                <image class="home-hidden-img" src="@img/index/lock.png" mode="scaleToFill" />
-                <view class="home-hidden-text">
-                    请登录,查看更多内容~
+            <!-- 满减券 -->
+            <view class="home-header-coupon">
+                <image class="home-header-coupon-bg" :src="getImageUrl('@img/index/coupon-bg.png')" mode="aspectFill" />
+                <view class="home-header-coupon-title">
+                    <image class="home-header-coupon-title-icon" :src="getImageUrl('@img/index/icon1.png')"
+                        mode="scaleToFill" />
+                    <view class="home-header-coupon-title-text">
+                        满减券
+                    </view>
+                    <view class="home-header-coupon-title-des">
+                        平台满减&nbsp;&nbsp;乐享不停
+                    </view>
                 </view>
-            </view>
-        </view>
-        <!-- 满减券 -->
-        <view class="home-header-coupon">
-            <image class="home-header-coupon-bg" :src="couponBg" mode="aspectFill" />
-            <view class="home-header-coupon-title">
-                <image class="home-header-coupon-title-icon" src="@img/index/icon1.png" mode="scaleToFill" />
-                <view class="home-header-coupon-title-text">
-                    满减券
+                <view class="home-header-coupon-content">
+                    <template v-for="item in couponList" :key="item.id">
+                        <spend-and-save-coupon :coupon="item" />
+                    </template>
+                    <up-loading-icon v-if="loading" class="coupon-content-loading" text="加载中" text-size="18" />
                 </view>
-                <view class="home-header-coupon-title-des">
-                    平台满减&nbsp;&nbsp;乐享不停
+                <view class="home-header-coupon-btn">
+                    <up-button class="home-header-coupon-btn-text" text="查看更多优惠券"
+                        color="linear-gradient(0deg, #FFE8CE 0%, #FBB8A0 100%)" @click="toSpendAndSaveCouponList" />
                 </view>
             </view>
-            <view class="home-header-coupon-content">
-                <template v-for="item in couponList" :key="item.id">
-                    <spend-and-save-coupon :coupon="item" />
-                </template>
-            </view>
-            <view class="home-header-coupon-btn">
-                <up-button class="home-header-coupon-btn-text" text="查看更多优惠券"
-                    color="linear-gradient(0deg, #FFE8CE 0%, #FBB8A0 100%)" @click="toSpendAndSaveCouponList" />
-            </view>
-        </view>
-        <!-- 折扣券 -->
-        <view class="home-coupon">
-            <view class="home-coupon-title">
-                <image class="home-coupon-title-icon" src="@img/index/icon2.png" mode="scaleToFill" />
-                <view class="home-coupon-title-text">
-                    折扣券
-                </view>
-                <view class="home-coupon-title-des">
-                    分享折扣&nbsp;&nbsp;立享优惠
-                </view>
-                <view class="home-coupon-title-more" @click="toDiscountCouponList">
-                    <view class="home-coupon-title-more-text">
-                        更多
+            <!-- 折扣券 -->
+            <view class="home-coupon">
+                <view class="home-coupon-title">
+                    <image class="home-coupon-title-icon" :src="getImageUrl('@img/index/icon2.png')"
+                        mode="scaleToFill" />
+                    <view class="home-coupon-title-text">
+                        折扣券
+                    </view>
+                    <view class="home-coupon-title-des">
+                        分享折扣&nbsp;&nbsp;立享优惠
+                    </view>
+                    <view class="home-coupon-title-more" @click="toDiscountCouponList">
+                        <view class="home-coupon-title-more-text">
+                            更多
+                        </view>
+                        <up-icon size="14" name="arrow-right" />
                     </view>
-                    <up-icon size="14" name="arrow-right" />
+                </view>
+                <view class="home-coupon-content">
+                    <template v-for="item in discountVoucherList" :key="item.id">
+                        <discount-coupon :coupon="item" />
+                    </template>
+                    <up-loading-icon v-if="loading" text="加载中" text-size="18" />
                 </view>
             </view>
-            <view class="home-coupon-content">
-                <template v-for="item in discountVoucherList" :key="item.id">
-                    <discount-coupon :coupon="item" />
-                </template>
-            </view>
-        </view>
+        </up-pull-refresh>
     </view>
 </template>
 
@@ -448,12 +464,15 @@ onShareAppMessage(async (options) => {
                 opacity: 0;
                 pointer-events: none;
             }
+
+            .coupon-content-loading {
+                align-self: center;
+            }
         }
 
         .home-header-coupon-btn {
             padding: 21rpx 70rpx 22px 69rpx;
             position: relative;
-            z-index: 1;
 
             .home-header-coupon-btn-text {
                 box-shadow: 0rpx 4rpx 7rpx 0rpx rgba(230, 77, 13, 0.17);

+ 384 - 309
src/pages/income/income.vue

@@ -2,14 +2,16 @@
 import type { AccountDetailItem } from '@/api/income'
 import { useRequest } from 'alova/client'
 import { storeToRefs } from 'pinia'
-import { ref } from 'vue'
+import { ref, watch } from 'vue'
 import { getAccountCount } from '@/api/home'
-import { getCouponIssuerAccountByPageMap, PageMapResponse } from '@/api/income'
+import { getAccountDetailTotalAmount, getCouponIssuerAccountByPageMap } from '@/api/income'
+import { useScroll } from '@/hooks/useScroll'
 
 import { LOGIN_PAGE } from '@/router/config'
 import { useUserStore } from '@/store'
 import { useTokenStore } from '@/store/token'
 import { changtime, menuButtonInfo, safeAreaInsets, systemInfo } from '@/utils'
+import { getImageUrl } from '@/utils/imageUtil'
 
 definePage({
     style: {
@@ -25,7 +27,7 @@ const { userInfo } = storeToRefs(userStore)
 const { hasLogin } = storeToRefs(tokenStore)
 
 const show = ref(false)
-const filterValue = ref(Number(new Date()))
+const filterValue = ref(Date.now())
 function confirm() {
     // 函数实现
     show.value = false
@@ -41,36 +43,90 @@ const { send: getAccountCountRequest, data: accountCountData } = useRequest(getA
     immediate: false,
 })
 
-const queryForm = ref({
-    pageNo: 1,
-    pageSize: 10,
-    startTime: '',
-    endTime: '',
-    status: 0, // 0-待结算, 1-已结算/成功
-    type: 1, // 明细类型: 1-佣金收入, 2-提现支出, 3-退款扣减, 4-系统调整
+const refreshing = ref(false)
+const status = ref(0) // 0-待结算, 1-已结算/成功
+const type = ref(1) // 明细类型: 1-佣金收入, 2-提现支出, 3-退款扣减, 4-系统调整
+
+const { send: getAccountDetailTotalAmountRequest, data: accountDetailTotalAmountData } = useRequest(() => getAccountDetailTotalAmount({ status: status.value, year: changtime(filterValue.value, 'YYYY'), month: changtime(filterValue.value, 'MM'), type: type.value }), {
+    immediate: false,
+})
+
+// 获取账户明细分页数据(Map格式)---待结算
+const {
+    list: pendingSettlementData,
+    loading: pendingSettlementLoading,
+    finished: pendingSettlementFinished,
+    refresh: pendingSettlementRefresh,
+    loadMore: pendingSettlementLoadMore,
+} = useScroll<AccountDetailItem>({
+    fetchData: async (page, pageSize) => {
+        const response = await getCouponIssuerAccountByPageMap({
+            pageNo: page,
+            pageSize,
+            status: 0,
+            year: changtime(filterValue.value, 'YYYY'),
+            month: changtime(filterValue.value, 'MM'),
+            type: type.value,
+        })
+        return response.detailList || []
+    },
+    pageSize: 5,
 })
 
-// 获取账户明细分页数据(Map格式)
-const { send: getCouponIssuerAccountByPageMapRequest, data: couponIssuerAccountByPageMapData } = useRequest(
-    getCouponIssuerAccountByPageMap,
-    { immediate: false },
-)
+// 获取账户明细分页数据(Map格式)---已结算
+const {
+    list: settledData,
+    loading: settledLoading,
+    finished: settledFinished,
+    refresh: settledRefresh,
+    loadMore: settledLoadMore,
+} = useScroll<AccountDetailItem>({
+    fetchData: async (page, pageSize) => {
+        const response = await getCouponIssuerAccountByPageMap({
+            pageNo: page,
+            pageSize,
+            status: 1,
+            year: changtime(filterValue.value, 'YYYY'),
+            month: changtime(filterValue.value, 'MM'),
+            type: type.value,
+        })
+        return response.detailList || []
+    },
+    pageSize: 5,
+})
+
+// 下拉刷新
+async function onRefresh() {
+    refreshing.value = true
+    // 重置列表并重新加载数据
+    if (status.value === 0) {
+        await pendingSettlementRefresh()
+    }
+    else {
+        await settledRefresh()
+    }
+    refreshing.value = false
+}
+
+// 上拉加载更多
+function onLoadMore() {
+    console.log(status.value, '执行了')
+    if (status.value === 0) {
+        pendingSettlementLoadMore()
+    }
+    else {
+        settledLoadMore()
+    }
+}
+
 const list = ref<AccountDetailItem[]>([])
 
 onShow(async () => {
     // 登录后查询收益数据
     if (hasLogin) {
-        try {
-            await getAccountCountRequest()
-            await getCouponIssuerAccountByPageMapRequest(queryForm.value)
-            // 使用可选链和空值合并运算符避免undefined错误,直接赋值而非展开运算符
-            list.value = couponIssuerAccountByPageMapData.value?.detailList || []
-        }
-        catch (error) {
-            console.error('获取收益数据失败:', error)
-            uni.showToast({ title: '获取数据失败', icon: 'error' })
-            list.value = []
-        }
+        await getAccountCountRequest()
+        await getAccountDetailTotalAmountRequest()
+        // 数据会在useScroll的onMounted中自动加载,这里不需要额外调用
     }
 })
 
@@ -79,26 +135,19 @@ function showTimeFilter() {
 }
 const wjsList = ref<AccountDetailItem[]>([])
 const yjsList = ref<AccountDetailItem[]>([])
-onLoad(() => {
-    list.value = wjsList.value
-})
 const activeTab = ref('pending')
 async function changeTab(tab: string) {
-    console.log(999, activeTab.value, tab)
     if (activeTab.value === tab)
         return
     activeTab.value = tab
-    queryForm.value.status = tab === 'pending' ? 0 : 1
-
-    try {
-        await getCouponIssuerAccountByPageMapRequest(queryForm.value)
-        // 使用可选链和空值合并运算符避免undefined错误,直接赋值而非展开运算符
-        list.value = couponIssuerAccountByPageMapData.value?.detailList || []
+    status.value = tab === 'pending' ? 0 : 1
+    await getAccountDetailTotalAmountRequest()
+    // 切换标签时刷新对应列表数据
+    if (tab === 'pending') {
+        await pendingSettlementRefresh()
     }
-    catch (error) {
-        console.error('切换标签页失败:', error)
-        uni.showToast({ title: '获取数据失败', icon: 'error' })
-        list.value = []
+    else {
+        await settledRefresh()
     }
 }
 
@@ -110,41 +159,32 @@ function goPage(page: string) {
 </script>
 
 <template>
-    <view class="profile-container">
+    <view class="profile-container"
+        :style="{ height: `calc(100vh - ${safeAreaInsets.top}px - ${safeAreaInsets.bottom}px)` }">
         <!-- 顶部区域 -->
-        <view
-            class="income-header"
-            style="background: url('../../static/images/income/income-bg.png') no-repeat center center; background-size: cover;"
-        >
-            <view
-                class="income-header-avatar-info"
-                :style="{ paddingTop: `${safeAreaInsets.top + menuButtonInfo.height + 12}px` }"
-            >
+        <view class="income-header"
+            :style="{ background: `url(${getImageUrl('@img/income/income-bg.png')}) no-repeat center center`, backgroundSize: 'cover' }">
+            <view class="income-header-avatar-info"
+                :style="{ paddingTop: `${safeAreaInsets.top + menuButtonInfo.height + 12}px` }">
                 <view class="income-header-balance">
                     当前账户余额(元)
                 </view>
                 <view class="income-header-balance-num">
                     <view class="income-header-balance-num-amount">
-                        {{ accountCountData?.balance }}
+                        {{ accountCountData?.balance || 0 }}
                     </view>
                     <view class="income-header-balance-num-btns">
-                        <view
-                            v-if="accountCountData?.status === 0"
-                            class="income-header-balance-num-btn js"
-                            @click="goPage('unlockRewards')"
-                        >
+                        <view v-if="accountCountData?.status === 0" class="income-header-balance-num-btn js"
+                            @click="goPage('unlockRewards')">
                             解锁
                         </view>
-                        <!-- <view class="income-header-balance-num-btn tx" @click="goPage('withdraw')">
-                            提现
-                        </view> -->
                     </view>
                 </view>
             </view>
             <view class="income-header-tips">
                 <view class="income-header-tips-item">
                     <view class="income-header-tips-item-num">
-                        {{ accountCountData?.withdrawableAmount }}
+                        {{ accountCountData?.withdrawableAmount || 0 }}
                     </view>
                     <view class="income-header-tips-item-des">
                         可提现金额
@@ -152,7 +192,7 @@ function goPage(page: string) {
                 </view>
                 <view class="income-header-tips-item">
                     <view class="income-header-tips-item-num">
-                        {{ accountCountData?.unsettledAmount }}
+                        {{ accountCountData?.unsettledAmount || 0 }}
                     </view>
                     <view class="income-header-tips-item-des">
                         未结算金额
@@ -163,7 +203,7 @@ function goPage(page: string) {
         <!-- 公告 -->
         <view class="income-header-notice">
             <view class="income-header-notice-icon">
-                <image src="@/static/images/income/notice.png" mode="aspectFit" />
+                <image :src="getImageUrl('@img/income/notice.png')" mode="aspectFit" />
             </view>
             <view class="income-header-notice-content">
                 在考核周期内未达标已锁定收益,<text>前往查看~</text>
@@ -173,16 +213,12 @@ function goPage(page: string) {
         <view class="income-header-menu">
             <!-- 结算筛选 -->
             <view class="income-header-menu-filter">
-                <view
-                    class="income-header-menu-filter-item" :class="[activeTab === 'pending' ? 'active' : '']"
-                    @click="changeTab('pending')"
-                >
+                <view class="income-header-menu-filter-item" :class="[activeTab === 'pending' ? 'active' : '']"
+                    @click="changeTab('pending')">
                     待结算
                 </view>
-                <view
-                    class="income-header-menu-filter-item" :class="[activeTab === 'settled' ? 'active' : '']"
-                    @click="changeTab('settled')"
-                >
+                <view class="income-header-menu-filter-item" :class="[activeTab === 'settled' ? 'active' : '']"
+                    @click="changeTab('settled')">
                     已结算
                 </view>
             </view>
@@ -191,289 +227,328 @@ function goPage(page: string) {
                 <view class="income-menu-time-filter-text" @click="showTimeFilter">
                     <text>{{ changtime(filterValue) }}</text>
                     <up-icon name="arrow-down" color="#666666" size="14" />
+                    <up-datetime-picker v-model="filterValue" :show="show" mode="year-month" close-on-click-overlay
+                        @confirm="confirm" @cancel="cancel" />
+                </view>
+                <view>
+                    合计:¥{{ accountDetailTotalAmountData?.totalAmount || 0 }}
                 </view>
-                <view>合计:¥{{ couponIssuerAccountByPageMapData?.totalAmount || 0 }}</view>
             </view>
             <!-- 优惠券列表 -->
-            <view class="income-header-menu-list">
-                <view v-for="item in list" :key="item.id" class="income-header-menu-item">
-                    <view class="income-header-menu-icon">
-                        <image v-if="activeTab === 'settled'" src="@/static/images/income/hb.png" mode="aspectFit" />
-                        <image v-else src="@/static/images/income/djs.png" mode="aspectFit" />
-                        <view class="income-header-menu-text">
-                            <view class="income-header-menu-text-nickname">
-                                {{ item.receiveUserName }}
+            <scroll-view class="income-header-menu-list" :scroll-y="true" :refresher-enabled="true"
+                :refresher-triggered="refreshing" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore">
+                <template v-for="item in activeTab === 'pending' ? pendingSettlementData || [] : settledData || []"
+                    :key="item.id">
+                    <view class="income-header-menu-item">
+                        <view class="income-header-menu-icon">
+                            <image v-if="activeTab === 'settled'" :src="getImageUrl('@img/income/hb.png')"
+                                mode="aspectFit" />
+                            <image v-else :src="getImageUrl('@img/income/djs.png')" mode="aspectFit" />
+                            <view class="income-header-menu-text">
+                                <view class="income-header-menu-text-nickname">
+                                    {{ item.receiveUserName }}
+                                </view>
+                                <view>{{ item.couponName }}</view>
                             </view>
-                            <view>{{ item.couponName }}</view>
                         </view>
-                    </view>
-                    <view class="income-header-menu-left">
-                        <view class="income-header-menu-left-amount">
-                            ¥{{ item.changeAmount }}
-                        </view>
-                        <view class="income-header-menu-left-time">
-                            {{ item.settleTime }}
+                        <view class="income-header-menu-left">
+                            <view class="income-header-menu-left-amount">
+                                ¥{{ item.changeAmount }}
+                            </view>
+                            <view class="income-header-menu-left-time">
+                                {{ item.settleTime }}
+                            </view>
                         </view>
                     </view>
+                </template>
+
+                <!-- 加载状态提示 -->
+                <view v-if="(activeTab === 'pending' ? pendingSettlementLoading : settledLoading)" class="loading-tip">
+                    加载中...
                 </view>
-            </view>
+                <view
+                    v-else-if="(activeTab === 'pending' ? pendingSettlementFinished : settledFinished) && (activeTab === 'pending' ? pendingSettlementData.length : settledData.length) > 0"
+                    class="finished-tip">
+                    没有更多数据了
+                </view>
+
+                <!-- 空状态 -->
+                <u-empty
+                    v-if="(activeTab === 'pending' ? pendingSettlementData.length === 0 : settledData.length === 0)"
+                    class="p-b-12 pt-12" mode="list" />
+            </scroll-view>
         </view>
-        <up-datetime-picker
-            v-model="filterValue" :show="show" mode="year-month" close-on-click-overlay
-            @confirm="confirm" @cancel="cancel"
-        />
     </view>
 </template>
 
 <style lang="scss" scoped>
 .profile-container {
-  font-family: Alibaba PuHuiTi;
-  min-height: 100vh;
-  background-color: #f5f5f5;
-  line-height: 1;
-
-  .income-header {
-    height: 525rpx;
-
-    .income-header-avatar-info {
-      display: flex;
-      flex-direction: column;
-      gap: 30rpx;
-      padding: 0 24rpx;
-
-      .income-header-balance {
-        font-weight: 400;
-        font-size: 26rpx;
-        color: #ffffff;
-      }
-
-      .income-header-balance-num {
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
+    font-family: Alibaba PuHuiTi;
+    background-color: #f5f5f5;
+    line-height: 1;
+    display: flex;
+    flex-direction: column;
 
-        .income-header-balance-num-amount {
-          font-weight: 500;
-          font-size: 65rpx;
-          color: #ffffff;
-        }
+    /* 添加这行,设置容器高度为视口高度 */
 
-        .income-header-balance-num-btns {
-          display: flex;
-          gap: 15rpx;
+    .income-header {
+        height: 525rpx;
 
-          .income-header-balance-num-btn {
-            padding: 20rpx 40rpx;
-            border-radius: 33rpx;
-            font-weight: 400;
-            font-size: 26rpx;
+        .income-header-avatar-info {
+            display: flex;
+            flex-direction: column;
+            gap: 30rpx;
+            padding: 0 24rpx;
 
-            &.js {
-              background: #da4c47;
-              color: #ffffff;
+            .income-header-balance {
+                font-weight: 400;
+                font-size: 26rpx;
+                color: #ffffff;
             }
 
-            &.tx {
-              background: #bfbfbf;
-              color: #747474;
+            .income-header-balance-num {
+                display: flex;
+                justify-content: space-between;
+                align-items: center;
+
+                .income-header-balance-num-amount {
+                    font-weight: 500;
+                    font-size: 65rpx;
+                    color: #ffffff;
+                }
+
+                .income-header-balance-num-btns {
+                    display: flex;
+                    gap: 15rpx;
+
+                    .income-header-balance-num-btn {
+                        padding: 20rpx 40rpx;
+                        border-radius: 33rpx;
+                        font-weight: 400;
+                        font-size: 26rpx;
+
+                        &.js {
+                            background: #da4c47;
+                            color: #ffffff;
+                        }
+
+                        &.tx {
+                            background: #bfbfbf;
+                            color: #747474;
+                        }
+                    }
+                }
             }
-          }
         }
-      }
-    }
 
-    .income-header-tips {
-      height: 124rpx;
-      display: flex;
-      background: linear-gradient(114deg, #f67873, #fb847f, #f67873);
-      border-radius: 10rpx;
-      margin: 47rpx 24rpx 0 24rpx;
+        .income-header-tips {
+            height: 124rpx;
+            display: flex;
+            background: linear-gradient(114deg, #f67873, #fb847f, #f67873);
+            border-radius: 10rpx;
+            margin: 47rpx 24rpx 0 24rpx;
+
+            .income-header-tips-item {
+                flex: 1;
+                display: flex;
+                flex-direction: column;
+                justify-content: center;
+                align-items: center;
+                gap: 15rpx;
+                font-weight: 500;
+                font-size: 34rpx;
+                color: #ffffff;
+                line-height: 1;
+                position: relative;
+
+                &:first-child:after {
+                    content: '';
+                    position: absolute;
+                    top: 50%;
+                    right: 0;
+                    transform: translateY(-50%);
+                    width: 2px;
+                    height: 40rpx;
+                    background: #ffffff;
+                }
+
+                .income-header-tips-item-num {
+                    font-weight: bold;
+                }
+
+                .income-header-tips-item-des {
+                    font-weight: 400;
+                    font-size: 24rpx;
+                    color: #ffffff;
+                }
+            }
+        }
+    }
 
-      .income-header-tips-item {
-        flex: 1;
+    .income-header-notice {
+        margin: -50rpx 24rpx 24rpx 24rpx;
+        background: #ffffff;
+        border-radius: 10rpx;
         display: flex;
-        flex-direction: column;
-        justify-content: center;
         align-items: center;
-        gap: 15rpx;
-        font-weight: 500;
-        font-size: 34rpx;
-        color: #ffffff;
-        line-height: 1;
-        position: relative;
-
-        &:first-child:after {
-          content: '';
-          position: absolute;
-          top: 50%;
-          right: 0;
-          transform: translateY(-50%);
-          width: 2px;
-          height: 40rpx;
-          background: #ffffff;
-        }
+        gap: 20rpx;
+        padding: 28rpx 20rpx;
 
-        .income-header-tips-item-num {
-          font-weight: bold;
-        }
+        .income-header-notice-icon {
+            width: 52rpx;
+            height: 35rpx;
 
-        .income-header-tips-item-des {
-          font-weight: 400;
-          font-size: 24rpx;
-          color: #ffffff;
+            image {
+                width: 100%;
+                height: 100%;
+            }
         }
-      }
-    }
-  }
 
-  .income-header-notice {
-    margin: -50rpx 24rpx 24rpx 24rpx;
-    background: #ffffff;
-    border-radius: 10rpx;
-    display: flex;
-    align-items: center;
-    gap: 20rpx;
-    padding: 28rpx 20rpx;
-
-    .income-header-notice-icon {
-      width: 52rpx;
-      height: 35rpx;
-
-      image {
-        width: 100%;
-        height: 100%;
-      }
-    }
-
-    .income-header-notice-content {
-      font-weight: 400;
-      font-size: 24rpx;
-      color: #333333;
+        .income-header-notice-content {
+            font-weight: 400;
+            font-size: 24rpx;
+            color: #333333;
 
-      text {
-        color: #c52d27;
-      }
-    }
-  }
-
-  .income-header-menu {
-    // background: #ffffff;
-    border-radius: 10rpx 10rpx 0rpx 0rpx;
-    margin: 24rpx 24rpx 0;
-
-    .income-header-menu-filter {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      font-weight: 500;
-      font-size: 32rpx;
-      color: #333333;
-      height: 90rpx;
-      background: #ffffff;
-      box-shadow: 0rpx 3rpx 7rpx 0rpx rgba(213, 213, 213, 0.29);
-      border-radius: 10rpx 10rpx 0rpx 0rpx;
-
-      .income-header-menu-filter-item {
-        flex: 1;
-        display: flex;
-        justify-content: center;
-        align-items: center;
-        position: relative;
-        height: 100%;
-
-        &.active {
-          color: #ed6b66;
-
-          &:after {
-            content: '';
-            position: absolute;
-            bottom: 0;
-            left: 50%;
-            transform: translateX(-50%);
-            width: 67rpx;
-            height: 6rpx;
-            background: #ed6b66;
-            border-radius: 3rpx;
-          }
+            text {
+                color: #c52d27;
+            }
         }
-      }
     }
 
-    .income-menu-time-filter {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      font-weight: 400;
-      font-size: 24rpx;
-      color: #333333;
-      padding: 50rpx 20rpx 14rpx;
-      background: #ffffff;
-      margin-top: 4rpx;
-
-      .income-menu-time-filter-text {
-        font-weight: 400;
-        font-size: 26rpx;
-        color: #000000;
-        display: flex;
-        align-items: center;
-        gap: 12rpx;
-      }
-    }
-
-    .income-header-menu-list {
-      background: #ffffff;
-
-      .income-header-menu-item {
-        height: 88rpx;
+    .income-header-menu {
+        // background: #ffffff;
+        border-radius: 10rpx 10rpx 0rpx 0rpx;
+        margin: 24rpx 24rpx 23rpx;
+        flex: 1;
         display: flex;
-        justify-content: space-between;
-        align-items: center;
-        border-bottom: 1px solid #eeeeee;
-        padding: 32rpx 0 29rpx;
-        margin: 0 20rpx;
-
-        &:last-child {
-          border-bottom: none;
+        flex-direction: column;
+        min-height: 0;
+
+        .income-header-menu-filter {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            font-weight: 500;
+            font-size: 32rpx;
+            color: #333333;
+            height: 90rpx;
+            background: #ffffff;
+            box-shadow: 0rpx 3rpx 7rpx 0rpx rgba(213, 213, 213, 0.29);
+            border-radius: 10rpx 10rpx 0rpx 0rpx;
+
+            .income-header-menu-filter-item {
+                flex: 1;
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                position: relative;
+                height: 100%;
+
+                &.active {
+                    color: #ed6b66;
+
+                    &:after {
+                        content: '';
+                        position: absolute;
+                        bottom: 0;
+                        left: 50%;
+                        transform: translateX(-50%);
+                        width: 67rpx;
+                        height: 6rpx;
+                        background: #ed6b66;
+                        border-radius: 3rpx;
+                    }
+                }
+            }
         }
 
-        .income-header-menu-icon {
-          display: flex;
-          justify-content: center;
-          align-items: center;
-          gap: 19rpx;
-
-          image {
-            width: 60rpx;
-            height: 60rpx;
-          }
-
-          .income-header-menu-text {
+        .income-menu-time-filter {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
             font-weight: 400;
             font-size: 24rpx;
-            color: #888888;
-
-            .income-header-menu-text-nickname {
-              font-size: 28rpx;
-              color: #222222;
-              margin-bottom: 18rpx;
+            color: #333333;
+            padding: 30rpx 20rpx 14rpx;
+            background: #ffffff;
+            margin-top: 4rpx;
+
+            .income-menu-time-filter-text {
+                font-weight: 400;
+                font-size: 26rpx;
+                color: #000000;
+                display: flex;
+                align-items: center;
+                gap: 12rpx;
             }
-          }
         }
 
-        .income-header-menu-left {
-          font-weight: 400;
-          font-size: 24rpx;
-          color: #888888;
-
-          .income-header-menu-left-amount {
-            font-size: 28rpx;
-            color: #222222;
-            margin-bottom: 18rpx;
-            text-align: right;
-          }
+        .income-header-menu-list {
+            background: #ffffff;
+            flex: 1;
+            min-height: 200rpx;
+            overflow-y: auto;
+            /* 确保内容超出时可滚动 */
+
+            .income-header-menu-item {
+                height: 88rpx;
+                display: flex;
+                justify-content: space-between;
+                align-items: center;
+                border-bottom: 1px solid #eeeeee;
+                padding: 32rpx 0 29rpx;
+                margin: 0 20rpx;
+
+                &:last-child {
+                    border-bottom: none;
+                }
+
+                .income-header-menu-icon {
+                    display: flex;
+                    justify-content: center;
+                    align-items: center;
+                    gap: 19rpx;
+
+                    image {
+                        width: 60rpx;
+                        height: 60rpx;
+                    }
+
+                    .income-header-menu-text {
+                        font-weight: 400;
+                        font-size: 24rpx;
+                        color: #888888;
+
+                        .income-header-menu-text-nickname {
+                            font-size: 28rpx;
+                            color: #222222;
+                            margin-bottom: 18rpx;
+                        }
+                    }
+                }
+
+                .income-header-menu-left {
+                    font-weight: 400;
+                    font-size: 24rpx;
+                    color: #888888;
+
+                    .income-header-menu-left-amount {
+                        font-size: 28rpx;
+                        color: #222222;
+                        margin-bottom: 18rpx;
+                        text-align: right;
+                    }
+                }
+            }
+
+            .loading-tip,
+            .finished-tip {
+                text-align: center;
+                padding: 20rpx 0;
+                color: #999;
+                font-size: 24rpx;
+            }
         }
-      }
     }
-  }
 }
 </style>

+ 14 - 13
src/pages/me/me.vue → src/pages/my/my.vue

@@ -7,6 +7,7 @@ import { getCouponIssuerApplyById } from '@/api/me'
 import { LOGIN_PAGE } from '@/router/config'
 import { useUserStore } from '@/store'
 import { useTokenStore } from '@/store/token'
+import { getImageUrl } from '@/utils/imageUtil'
 
 definePage({
     style: {
@@ -33,8 +34,9 @@ const { send: getCouponIssuerApplyByIdRequest, data: couponIssuerApplyByIdData }
 // 昵称输入值
 const nicknameInput = ref('')
 onShow(() => {
+    console.log('登录判断:', hasLogin.value)
     // 登录后查询收益数据
-    if (hasLogin) {
+    if (hasLogin.value) {
         // 获取用户信息
         userStore.fetchUserInfo().then((data) => {
             // #ifdef MP-WEIXIN
@@ -47,7 +49,7 @@ onShow(() => {
         getCouponIssuerApplyByIdRequest()
         if (couponIssuerApplyByIdData.value?.status === '0') {
             console.log('优惠券统计数据:', couponSituationData.value)
-            open()
+            // open()
         }
     }
 })
@@ -81,7 +83,7 @@ function handleLogout() {
                 })
                 // #ifdef MP-WEIXIN
                 // 微信小程序,去首页
-                // uni.reLaunch({ url: '/pages/index/index' })
+                uni.reLaunch({ url: '/pages/home/home' })
                 // #endif
                 // #ifndef MP-WEIXIN
                 // 非微信小程序,去登录页
@@ -192,17 +194,16 @@ function menuClick(page) {
     <view class="profile-container">
         <!-- 顶部区域 -->
         <view class="me-header"
-            style="background: url('../../static/images/me/me-bg.png') no-repeat center center; background-size: cover;">
+            :style="{ background: `url(${getImageUrl('@img/me/me-bg.png')}) no-repeat center center`, backgroundSize: 'cover' }">
             <view class="me-header-avatar-info" :style="{ paddingTop: `${navigationBarHeight}px` }">
-                <button class="me-header-avatar" open-type="chooseAvatar" @click="handleGetAvatar">
+                <button class="me-header-avatar" @click="handleGetAvatar">
                     <image :src="avatarDisplay" mode="aspectFill" />
                 </button>
                 <view class="me-header-info">
                     <view class="me-header-name">
                         <!-- #ifdef MP-WEIXIN -->
-                        <input v-model="nicknameInput" type="nickname" placeholder="用户昵称"
-                            placeholder-style="color: #fff; opacity: 0.7;" class="nickname-input"
-                            @confirm="saveNickname" @blur="saveNickname">
+                        <input v-model="nicknameInput" type="nickname" :disabled="true" placeholder="用户昵称"
+                            placeholder-style="color: #fff; opacity: 0.7;" class="nickname-input">
                         <!-- #endif -->
                         <!-- #ifndef MP-WEIXIN -->
                         {{ userInfo.value?.nickname || '用户' }}
@@ -213,7 +214,7 @@ function menuClick(page) {
             <view class="me-header-tips">
                 <view class="me-header-tips-item">
                     <view class="me-header-tips-item-num">
-                        1,125
+                        {{ couponSituationData?.quantityForComplimentary || 0 }}
                     </view>
                     <view class="me-header-tips-item-des">
                         已领取
@@ -221,7 +222,7 @@ function menuClick(page) {
                 </view>
                 <view class="me-header-tips-item">
                     <view class="me-header-tips-item-num">
-                        1,125
+                        {{ couponSituationData?.usedQuantity || 0 }}
                     </view>
                     <view class="me-header-tips-item-des">
                         已使用
@@ -229,7 +230,7 @@ function menuClick(page) {
                 </view>
                 <view class="me-header-tips-item">
                     <view class="me-header-tips-item-num">
-                        1,125
+                        {{ couponSituationData?.expired || 0 }}
                     </view>
                     <view class="me-header-tips-item-des">
                         已过期
@@ -241,7 +242,7 @@ function menuClick(page) {
         <view class="me-header-menu">
             <view class="me-header-menu-item" @click="menuClick('applyForm')">
                 <view class="me-header-menu-icon">
-                    <image src="@/static/images/me/coupon-need.png" mode="aspectFill" />
+                    <image :src="getImageUrl('@img/me/coupon-need.png')" mode="aspectFill" />
                     <view class="me-header-menu-text">
                         发券人申请
                     </view>
@@ -252,7 +253,7 @@ function menuClick(page) {
             </view>
             <view class="me-header-menu-item" @click="handleLogout">
                 <view class="me-header-menu-icon">
-                    <image src="@/static/images/me/loginOut.png" mode="aspectFill" />
+                    <image :src="getImageUrl('@img/me/loginOut.png')" mode="aspectFill" />
                     <view class="me-header-menu-text">
                         退出登录
                     </view>

+ 0 - 97
src/pages/receiveCoupon/index.vue

@@ -1,97 +0,0 @@
-<script setup lang="ts">
-import { useRequest } from 'alova/client'
-import { ref } from 'vue'
-import { getReceiveCouponDetail } from '@/api/receiveCoupon'
-import ReceiveCoupon from '@/components/ReceiveCoupon.vue'
-
-// 页面配置
-definePage({
-    style: {
-        navigationBarTitleText: '领取优惠券',
-        navigationStyle: 'custom',
-    },
-})
-
-// 优惠券详情数据
-const couponDetail = ref(null)
-
-const { data, loading, error, send } = useRequest(arg => getReceiveCouponDetail(arg), {
-    immediate: false,
-})
-
-// 页面加载时获取优惠券详情
-onLoad((options) => {
-    console.log(options)
-    const templateId = options.couponShareRecordId as string
-    send(templateId)
-})
-
-// 格式化日期
-function formatDate(dateStr: string) {
-    const date = new Date(dateStr)
-    const year = date.getFullYear()
-    const month = String(date.getMonth() + 1).padStart(2, '0')
-    const day = String(date.getDate()).padStart(2, '0')
-    return `${year}-${month}-${day}`
-}
-
-function navigateToMiniProgram() {
-    uni.navigateToMiniProgram({
-        appId: 'wxab9634bf98628a03',
-        path: 'pages/index/index',
-        envVersion: 'release',
-        success(res) {
-            console.log('打开成功', res)
-        },
-        fail(res) {
-            console.log('打开失败', res)
-        }
-    })
-}
-</script>
-
-<template>
-    <view class="container">
-        <!-- 加载中 -->
-        <view v-if="loading" class="loading">
-            <uni-loading-spinner type="snake" size="40" />
-            <text class="loading-text">加载中...</text>
-        </view>
-
-        <!-- 错误信息 -->
-        <view v-else-if="error" class="error">
-            <text class="error-text">{{ error }}</text>
-        </view>
-
-        <view v-if="!!data">
-            <ReceiveCoupon :coupon="data" @button-click="navigateToMiniProgram" />
-        </view>
-    </view>
-</template>
-
-<style scoped>
-.container {}
-
-.loading {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    height: 60vh;
-}
-
-.loading-text {
-    margin-top: 20rpx;
-    color: #999;
-    font-size: 28rpx;
-}
-
-.error {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    height: 60vh;
-    color: #ff4444;
-    font-size: 32rpx;
-}
-</style>

+ 78 - 1
src/router/interceptor.ts

@@ -187,6 +187,83 @@ export const navigateToInterceptor = {
     },
 }
 
+// 为switchTab创建单独的拦截器
+export const switchTabInterceptor = {
+    invoke({ url }: { url: string }) {
+        if (url === undefined) {
+            return
+        }
+
+        const loadingStore = useLoadingStore()
+        loadingStore.showLoading('页面跳转中...')
+
+        let { path } = parseUrlToObj(url)
+
+        // 处理相对路径
+        if (!path.startsWith('/')) {
+            const currentPath = getLastPage()?.route || ''
+            const normalizedCurrentPath = currentPath.startsWith('/') ? currentPath : `/${currentPath}`
+            const baseDir = normalizedCurrentPath.substring(0, normalizedCurrentPath.lastIndexOf('/'))
+            path = `${baseDir}/${path}`
+        }
+
+        // 处理路由不存在的情况
+        if (!isRouteExists(path)) {
+            console.warn('路由不存在:', path)
+            uni.navigateTo({
+                url: NOT_FOUND_PAGE,
+                complete: () => {
+                    loadingStore.hideLoading()
+                }
+            })
+            return false
+        }
+
+        // 检查是否是登录页
+        const isLoginPage = path === LOGIN_PAGE
+
+        // 检查是否是首页
+        const isHomePage = path === HOME_PAGE
+
+        // 更新tabbarIndex
+        setTimeout(() => {
+            tabbarStore.setAutoCurIdx(path)
+        }, 0)
+
+        // 如果是登录页,直接放行
+        if (isLoginPage) {
+            return true
+        }
+
+        // 检查登录状态
+        const tokenStore = useTokenStore()
+        if (tokenStore.hasLogin) {
+            // 已登录,直接放行
+            return true
+        }
+
+        // 未登录状态下
+        if (isHomePage) {
+            // 首页可以直接访问
+            return true
+        }
+        else {
+            // 非首页tabbar页面,跳转到登录页
+            const redirectUrl = `${LOGIN_PAGE}?redirect=${encodeURIComponent(path)}`
+
+            setTimeout(() => {
+                uni.navigateTo({
+                    url: redirectUrl,
+                    complete: () => {
+                        loadingStore.hideLoading()
+                    }
+                })
+            }, 50)
+            return false
+        }
+    }
+}
+
 // 针对 chooseLocation 的特殊处理
 export const chooseLocationInterceptor = {
     invoke(options: any) {
@@ -201,7 +278,7 @@ export const routeInterceptor = {
         uni.addInterceptor('navigateTo', navigateToInterceptor)
         uni.addInterceptor('reLaunch', navigateToInterceptor)
         uni.addInterceptor('redirectTo', navigateToInterceptor)
-        uni.addInterceptor('switchTab', navigateToInterceptor)
+        uni.addInterceptor('switchTab', switchTabInterceptor)
 
         // 添加 chooseLocation 的拦截器,确保直接放行
         uni.addInterceptor('chooseLocation', chooseLocationInterceptor)

BIN=BIN
src/static/app/icons/1024x1024.png


BIN=BIN
src/static/app/icons/120x120.png


BIN=BIN
src/static/app/icons/144x144.png


BIN=BIN
src/static/app/icons/152x152.png


BIN=BIN
src/static/app/icons/167x167.png


BIN=BIN
src/static/app/icons/180x180.png


BIN=BIN
src/static/app/icons/192x192.png


BIN=BIN
src/static/app/icons/20x20.png


BIN=BIN
src/static/app/icons/29x29.png


BIN=BIN
src/static/app/icons/40x40.png


BIN=BIN
src/static/app/icons/58x58.png


BIN=BIN
src/static/app/icons/60x60.png


BIN=BIN
src/static/app/icons/72x72.png


BIN=BIN
src/static/app/icons/76x76.png


BIN=BIN
src/static/app/icons/80x80.png


BIN=BIN
src/static/app/icons/87x87.png


BIN=BIN
src/static/app/icons/96x96.png


BIN=BIN
src/static/images/avatar.jpg


BIN=BIN
src/static/images/default-avatar.png


BIN=BIN
src/static/images/income/djs.png


BIN=BIN
src/static/images/income/emptyBank.png


BIN=BIN
src/static/images/income/hb.png


BIN=BIN
src/static/images/income/income-bg.png


BIN=BIN
src/static/images/income/logo-gh.png


BIN=BIN
src/static/images/income/logo-jh.png


BIN=BIN
src/static/images/income/logo-nh.png


BIN=BIN
src/static/images/income/logo-zh.png


BIN=BIN
src/static/images/income/logogh.png


BIN=BIN
src/static/images/income/logojh.png


BIN=BIN
src/static/images/income/logonh.png


BIN=BIN
src/static/images/income/logozh.png


BIN=BIN
src/static/images/income/notice.png


BIN=BIN
src/static/images/income/tx-icon.png


BIN=BIN
src/static/images/income/y-1.png


BIN=BIN
src/static/images/index/coupon-bg.png


BIN=BIN
src/static/images/index/coupon1.png


BIN=BIN
src/static/images/index/coupon2.png


BIN=BIN
src/static/images/index/coupon3.png


BIN=BIN
src/static/images/index/icon1.png


BIN=BIN
src/static/images/index/icon2.png


BIN=BIN
src/static/images/index/index-bg.png


BIN=BIN
src/static/images/index/lock.png


BIN=BIN
src/static/images/index/logo.jpg


BIN=BIN
src/static/images/me/coupon-need.png


BIN=BIN
src/static/images/me/loginOut.png


BIN=BIN
src/static/images/me/me-bg.png


BIN=BIN
src/static/images/receiveCoupon/button.png


BIN=BIN
src/static/images/receiveCoupon/coupon-bg.png


BIN=BIN
src/static/images/share.png


+ 0 - 1
src/static/my-icons/copyright.svg

@@ -1 +0,0 @@
-<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1762219859937" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8816" id="mx_n_1762219859938" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64z m0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" p-id="8817"></path><path d="M517.6 351.3c53 0 89 33.8 93 83.4 0.3 4.2 3.8 7.4 8 7.4h56.7c2.6 0 4.7-2.1 4.7-4.7 0-86.7-68.4-147.4-162.7-147.4C407.4 290 344 364.2 344 486.8v52.3C344 660.8 407.4 734 517.3 734c94 0 162.7-58.8 162.7-141.4 0-2.6-2.1-4.7-4.7-4.7h-56.8c-4.2 0-7.6 3.2-8 7.3-4.2 46.1-40.1 77.8-93 77.8-65.3 0-102.1-47.9-102.1-133.6v-52.6c0.1-87 37-135.5 102.2-135.5z" p-id="8818"></path></svg>

+ 3 - 0
src/store/coupon.ts

@@ -5,10 +5,13 @@ export const useCouponStore = defineStore('coupon', {
     state: () => ({
         discountVoucherList: [], // 折扣券
         couponList: [], // 满减券
+        loading: false,
     }),
     actions: {
         async getCouponListByType() {
+            this.loading = true
             const res = await getCouponList()
+            this.loading = false
             this.discountVoucherList = (res.discountCoupon || []).slice(0, 3)
             this.couponList = (res.fullReduceCoupon || []).slice(0, 3)
         }

+ 48 - 36
src/store/token.ts

@@ -61,8 +61,8 @@ export const useTokenStore = defineStore(
         }
 
         /**
-                                                                                                     * 判断token是否过期
-                                                                                                     */
+                                                                                                                                             * 判断token是否过期
+                                                                                                                                             */
         const isTokenExpired = computed(() => {
             if (!tokenInfo.value) {
                 return true
@@ -77,8 +77,8 @@ export const useTokenStore = defineStore(
         })
 
         /**
-                                                                                                     * 判断refreshToken是否过期
-                                                                                                     */
+                                                                                                                                             * 判断refreshToken是否过期
+                                                                                                                                             */
         const isRefreshTokenExpired = computed(() => {
             if (!isDoubleTokenMode)
                 return true
@@ -92,9 +92,9 @@ export const useTokenStore = defineStore(
         })
 
         /**
-                                                                                                     * 登录成功后处理逻辑
-                                                                                                     * @param tokenInfo 登录返回的token信息
-                                                                                                     */
+                                                                                                                                             * 登录成功后处理逻辑
+                                                                                                                                             * @param tokenInfo 登录返回的token信息
+                                                                                                                                             */
         async function _postLogin(tokenInfo: IAuthLoginRes) {
             setTokenInfo(tokenInfo)
             // const userStore = useUserStore()
@@ -102,12 +102,12 @@ export const useTokenStore = defineStore(
         }
 
         /**
-                                                                                                     * 用户登录
-                                                                                                     * 有的时候后端会用一个接口返回token和用户信息,有的时候会分开2个接口,一个获取token,一个获取用户信息
-                                                                                                     * (各有利弊,看业务场景和系统复杂度),这里使用2个接口返回的来模拟
-                                                                                                     * @param loginForm 登录参数
-                                                                                                     * @returns 登录结果
-                                                                                                     */
+                                                                                                                                             * 用户登录
+                                                                                                                                             * 有的时候后端会用一个接口返回token和用户信息,有的时候会分开2个接口,一个获取token,一个获取用户信息
+                                                                                                                                             * (各有利弊,看业务场景和系统复杂度),这里使用2个接口返回的来模拟
+                                                                                                                                             * @param loginForm 登录参数
+                                                                                                                                             * @returns 登录结果
+                                                                                                                                             */
         const login = async (loginForm: ILoginForm) => {
             try {
                 const res = await _login(loginForm)
@@ -130,11 +130,11 @@ export const useTokenStore = defineStore(
         }
 
         /**
-                                                                                                     * 微信登录
-                                                                                                     * 有的时候后端会用一个接口返回token和用户信息,有的时候会分开2个接口,一个获取token,一个获取用户信息
-                                                                                                     * (各有利弊,看业务场景和系统复杂度),这里使用2个接口返回的来模拟
-                                                                                                     * @returns 登录结果
-                                                                                                     */
+                                                                                                                                             * 微信登录
+                                                                                                                                             * 有的时候后端会用一个接口返回token和用户信息,有的时候会分开2个接口,一个获取token,一个获取用户信息
+                                                                                                                                             * (各有利弊,看业务场景和系统复杂度),这里使用2个接口返回的来模拟
+                                                                                                                                             * @returns 登录结果
+                                                                                                                                             */
         const wxLogin = async () => {
             try {
                 // 获取用户信息
@@ -144,11 +144,23 @@ export const useTokenStore = defineStore(
                 const code = await getWxCode()
                 console.log('微信登录-code: ', code.code)
                 const res = await _wxLogin(code.code)
+                console.log('微信登录分享登录 ', isShareEnter.value)
+                console.log('微信登录-res: ', res)
+                await _postLogin(res)
+                // 将微信用户信息转换为系统要求的格式
+                const userStore = useUserStore()
+                const systemUserInfo = {
+                    userId: -1, // 初始值,后续可由后端更新
+                    username: '', // 微信登录可能没有用户名,使用空字符串
+                    nickname: profile.userInfo.nickName,
+                    avatar: profile.userInfo.avatarUrl,
+                }
+                // 存储系统格式的用户信息
+                userStore.setUserInfo(systemUserInfo)
                 if (isShareEnter.value) {
+                    console.log('微信登录分享登录-params: ', JSON.parse(uni.getStorageSync('shareParams')))
                     await addInviteConversion(JSON.parse(uni.getStorageSync('shareParams')))
                 }
-                console.log('微信登录-res: ', res)
-                await _postLogin(res)
                 uni.showToast({
                     title: '登录成功',
                     icon: 'success',
@@ -166,8 +178,8 @@ export const useTokenStore = defineStore(
         }
 
         /**
-                                                                                                     * 退出登录 并 删除用户信息
-                                                                                                     */
+                                                                                                                                             * 退出登录 并 删除用户信息
+                                                                                                                                             */
         const logout = async () => {
             try {
                 // TODO 实现自己的退出登录逻辑
@@ -198,9 +210,9 @@ export const useTokenStore = defineStore(
         }
 
         /**
-                                                                                                     * 刷新token
-                                                                                                     * @returns 刷新结果
-                                                                                                     */
+                                                                                                                                             * 刷新token
+                                                                                                                                             * @returns 刷新结果
+                                                                                                                                             */
         const refreshToken = async () => {
             if (!isDoubleTokenMode) {
                 console.error('单token模式不支持刷新token')
@@ -226,10 +238,10 @@ export const useTokenStore = defineStore(
         }
 
         /**
-                                                                                                     * 获取有效的token
-                                                                                                     * 注意:在computed中不直接调用异步函数,只做状态判断
-                                                                                                     * 实际的刷新操作应由调用方处理
-                                                                                                     */
+                                                                                                                                             * 获取有效的token
+                                                                                                                                             * 注意:在computed中不直接调用异步函数,只做状态判断
+                                                                                                                                             * 实际的刷新操作应由调用方处理
+                                                                                                                                             */
         const getValidToken = computed(() => {
             // token已过期,返回空
             if (isTokenExpired.value) {
@@ -245,8 +257,8 @@ export const useTokenStore = defineStore(
         })
 
         /**
-                                                                                                     * 检查是否有登录信息(不考虑token是否过期)
-                                                                                                     */
+                                                                                                                                             * 检查是否有登录信息(不考虑token是否过期)
+                                                                                                                                             */
         const hasLoginInfo = computed(() => {
             if (!tokenInfo.value) {
                 return false
@@ -260,17 +272,17 @@ export const useTokenStore = defineStore(
         })
 
         /**
-                                                     * 检查是否已登录且token有效
-                                                     */
+                                                                                             * 检查是否已登录且token有效
+                                                                                             */
         const hasValidLogin = computed(() => {
             console.log('hasValidLogin', hasLoginInfo.value && !isTokenExpired.value, hasLoginInfo.value, !isTokenExpired.value)
             return hasLoginInfo.value && !isTokenExpired.value
         })
 
         /**
-                                                 * 尝试获取有效的token,如果过期且可刷新,则刷新token
-                                                 * @returns 有效的token或空字符串
-                                                 */
+                                                                                         * 尝试获取有效的token,如果过期且可刷新,则刷新token
+                                                                                         * @returns 有效的token或空字符串
+                                                                                         */
         const tryGetValidToken = async (): Promise<string> => {
             if (!getValidToken.value && isDoubleTokenMode && !isRefreshTokenExpired.value) {
                 try {

+ 4 - 40
src/tabbar/config.ts

@@ -28,7 +28,7 @@ export const nativeTabbarList: NativeTabBarItem[] = [
     {
         iconPath: 'static/tabbar/home.png',
         selectedIconPath: 'static/tabbar/home_select.png',
-        pagePath: 'pages/index/index',
+        pagePath: 'pages/home/home',
         text: '首页',
     },
     {
@@ -40,7 +40,7 @@ export const nativeTabbarList: NativeTabBarItem[] = [
     {
         iconPath: 'static/tabbar/me.png',
         selectedIconPath: 'static/tabbar/me_selected.png',
-        pagePath: 'pages/me/me',
+        pagePath: 'pages/my/my',
         text: '个人',
     },
 ]
@@ -50,10 +50,7 @@ export const nativeTabbarList: NativeTabBarItem[] = [
 export const customTabbarList: CustomTabBarItem[] = [
     {
         text: '首页',
-        pagePath: 'pages/index/index',
-        // 注意 unocss 图标需要如下处理:(二选一)
-        // 1)在fg-tabbar.vue页面上引入一下并注释掉(见tabbar/index.vue代码第2行)
-        // 2)配置到 unocss.config.ts 的 safelist 中
+        pagePath: 'pages/home/home',
         iconType: 'image',
         icon: '/static/tabbar/home.png',
         iconActive: '/static/tabbar/home_select.png',
@@ -68,46 +65,13 @@ export const customTabbarList: CustomTabBarItem[] = [
         // badge: 10,
     },
     {
-        pagePath: 'pages/me/me',
+        pagePath: 'pages/my/my',
         text: '我的',
-        // 1)在fg-tabbar.vue页面上引入一下并注释掉(见tabbar/index.vue代码第2行)
-        // 2)配置到 unocss.config.ts 的 safelist 中
         iconType: 'image',
         icon: '/static/tabbar/me.png',
         iconActive: '/static/tabbar/me_selected.png',
         // badge: 10,
     },
-    // {
-    //   pagePath: 'pages/income/income',
-    //   text: '收益',
-    //   iconType: 'unocss',
-    //   icon: 'i-carbon-chart-line',
-    // },
-    // 其他类型演示
-    // 1、uiLib
-    // {
-    //   pagePath: 'pages/index/index',
-    //   text: '首页',
-    //   iconType: 'uiLib',
-    //   icon: 'home',
-    // },
-    // 2、iconfont
-    // {
-    //   pagePath: 'pages/index/index',
-    //   text: '首页',
-    //   // 注意 iconfont 图标需要额外加上 'iconfont',如下
-    //   iconType: 'iconfont',
-    //   icon: 'iconfont icon-my',
-    // },
-    // 3、image
-    // {
-    //   pagePath: 'pages/index/index',
-    //   text: '首页',
-    //   // 使用 ‘image’时,需要配置 icon + iconActive 2张图片
-    //   iconType: 'image',
-    //   icon: '/static/tabbar/home.png',
-    //   iconActive: '/static/tabbar/homeHL.png',
-    // },
 ]
 
 /**

+ 1 - 1
src/tabbar/index.vue

@@ -115,7 +115,7 @@ function getImageByIndex(index: number, item: CustomTabBarItem) {
                         <view class="bulge">
                             <!-- TODO 2/2: 中间鼓包tabbarItem配置:通常是一个图片,或者icon,点击触发业务逻辑 -->
                             <!-- 常见的是:扫描按钮、发布按钮、更多按钮等 -->
-                            <image class="mt-6rpx h-200rpx w-200rpx" src="/static/tabbar/scan.png" />
+                            <!-- <image class="mt-6rpx h-200rpx w-200rpx" src="/static/tabbar/scan.png" /> -->
                         </view>
                     </view>
                     <view v-else class="relative px-3 text-center">

+ 51 - 51
src/tabbar/store.ts

@@ -8,25 +8,25 @@ const BULGE_ENABLE = false
 
 /** tabbarList 里面的 path 从 pages.config.ts 得到 */
 const tabbarList = reactive<CustomTabBarItem[]>(_tabbarList.map(item => ({
-  ...item,
-  pagePath: item.pagePath.startsWith('/') ? item.pagePath : `/${item.pagePath}`,
+    ...item,
+    pagePath: item.pagePath.startsWith('/') ? item.pagePath : `/${item.pagePath}`,
 })))
 
 if (customTabbarEnable && BULGE_ENABLE) {
-  if (tabbarList.length % 2) {
-    console.error('有鼓包时 tabbar 数量必须是偶数,否则样式很奇怪!!')
-  }
-  tabbarList.splice(tabbarList.length / 2, 0, {
-    isBulge: true,
-  } as CustomTabBarItem)
+    if (tabbarList.length % 2) {
+        console.error('有鼓包时 tabbar 数量必须是偶数,否则样式很奇怪!!')
+    }
+    tabbarList.splice(tabbarList.length / 2, 0, {
+        isBulge: true,
+    } as CustomTabBarItem)
 }
 
 export function isPageTabbar(path: string) {
-  if (selectedTabbarStrategy === TABBAR_STRATEGY_MAP.NO_TABBAR) {
-    return false
-  }
-  const _path = path.split('?')[0]
-  return tabbarList.some(item => item.pagePath === _path)
+    if (selectedTabbarStrategy === TABBAR_STRATEGY_MAP.NO_TABBAR) {
+        return false
+    }
+    const _path = path.split('?')[0]
+    return tabbarList.some(item => item.pagePath === _path)
 }
 
 /**
@@ -35,44 +35,44 @@ export function isPageTabbar(path: string) {
  * 使用reactive简单状态,而不是 pinia 全局状态
  */
 const tabbarStore = reactive({
-  curIdx: uni.getStorageSync('app-tabbar-index') || 0,
-  prevIdx: uni.getStorageSync('app-tabbar-index') || 0,
-  setCurIdx(idx: number) {
-    this.curIdx = idx
-    uni.setStorageSync('app-tabbar-index', idx)
-  },
-  setTabbarItemBadge(idx: number, badge: CustomTabBarItemBadge) {
-    if (tabbarList[idx]) {
-      tabbarList[idx].badge = badge
-    }
-  },
-  setAutoCurIdx(path: string) {
-    // '/' 当做首页
-    if (path === '/') {
-      this.setCurIdx(0)
-      return
-    }
-    const index = tabbarList.findIndex(item => item.pagePath === path)
-    // console.log('tabbarList:', tabbarList)
-    if (index === -1) {
-      const pagesPathList = getCurrentPages().map(item => item.route.startsWith('/') ? item.route : `/${item.route}`)
-      // console.log(pagesPathList)
-      const flag = tabbarList.some(item => pagesPathList.includes(item.pagePath))
-      if (!flag) {
-        this.setCurIdx(0)
-        return
-      }
-    }
-    else {
-      this.setCurIdx(index)
-    }
-  },
-  restorePrevIdx() {
-    if (this.prevIdx === this.curIdx)
-      return
-    this.setCurIdx(this.prevIdx)
-    this.prevIdx = uni.getStorageSync('app-tabbar-index') || 0
-  },
+    curIdx: uni.getStorageSync('app-tabbar-index') || 0,
+    prevIdx: uni.getStorageSync('app-tabbar-index') || 0,
+    setCurIdx(idx: number) {
+        this.curIdx = idx
+        uni.setStorageSync('app-tabbar-index', idx)
+    },
+    setTabbarItemBadge(idx: number, badge: CustomTabBarItemBadge) {
+        if (tabbarList[idx]) {
+            tabbarList[idx].badge = badge
+        }
+    },
+    setAutoCurIdx(path: string) {
+        // '/' 当做首页
+        if (path === '/') {
+            this.setCurIdx(0)
+            return
+        }
+        const index = tabbarList.findIndex(item => item.pagePath === path)
+        // console.log('tabbarList:', tabbarList)
+        if (index === -1) {
+            const pagesPathList = getCurrentPages().map(item => item.route.startsWith('/') ? item.route : `/${item.route}`)
+            // console.log(pagesPathList)
+            const flag = tabbarList.some(item => pagesPathList.includes(item.pagePath))
+            if (!flag) {
+                this.setCurIdx(0)
+                return
+            }
+        }
+        else {
+            this.setCurIdx(index)
+        }
+    },
+    restorePrevIdx() {
+        if (this.prevIdx === this.curIdx)
+            return
+        this.setCurIdx(this.prevIdx)
+        this.prevIdx = uni.getStorageSync('app-tabbar-index') || 0
+    },
 })
 
 export { tabbarList, tabbarStore }

+ 6 - 0
src/utils/imageUtil.ts

@@ -0,0 +1,6 @@
+export function getImageUrl(localPath: string): string {
+    // 移除本地路径前缀(如@img/或@/static/)
+    const relativePath = localPath.replace(/^@img\/|^@\/static\//, '')
+    // 拼接CDN域名
+    return `${import.meta.env.VITE_CDN_DOMAIN}/${relativePath}`
+}