Parcourir la source

Merge branch 'uat'

liangan il y a 1 mois
Parent
commit
03cdf1bd08

+ 3 - 1
env/.env.development

@@ -5,10 +5,12 @@ VITE_DELETE_CONSOLE = false
 # 是否开启sourcemap
 VITE_SHOW_SOURCEMAP = true
 
-VITE_UNI_APPID = '__UNI__F0919F6'
+VITE_UNI_APPID = '__UNI__FDE416C'
 
 VITE_SERVER_BASEURL = 'http://124.222.152.234:8101' # mall
 VITE_API_SECONDARY_URL = 'http://124.222.152.234:8101' # cif
 VITE_API_THIRD_URL = 'http://124.222.152.234:8101' # operating
 VITE_UPLOAD_BASEURL = 'http://124.222.152.234:8101/operating/file/upload'
 
+# 极光key
+VITE_MTPUSH_APPKEY_ANDROID = 'c95a2cb077c243ebbfa896f5'

+ 3 - 1
env/.env.production

@@ -5,10 +5,12 @@ VITE_DELETE_CONSOLE = true
 # 是否开启sourcemap
 VITE_SHOW_SOURCEMAP = false
 
-VITE_UNI_APPID = '__UNI__D38110B'
+VITE_UNI_APPID = '__UNI__BA8433E'
 
 VITE_SERVER_BASEURL = 'https://api.bandhubuy.com' # mall
 VITE_API_SECONDARY_URL = 'https://api.bandhubuy.com' # cif
 VITE_API_THIRD_URL = 'https://api.bandhubuy.com' # operating
 VITE_UPLOAD_BASEURL = 'https://api.bandhubuy.com/operating/file/upload'
 
+# 极光key
+VITE_MTPUSH_APPKEY_ANDROID = '0655e889f95b4325a6712905'

+ 4 - 1
env/.env.test

@@ -5,8 +5,11 @@ VITE_DELETE_CONSOLE = false
 # 是否开启sourcemap
 VITE_SHOW_SOURCEMAP = true
 
-VITE_UNI_APPID = '__UNI__F0919F6'
+VITE_UNI_APPID = '__UNI__FDE416C'
 VITE_SERVER_BASEURL = 'http://124.222.152.234:8101' # mall
 VITE_API_SECONDARY_URL = 'http://124.222.152.234:8101' # cif
 VITE_API_THIRD_URL = 'http://124.222.152.234:8101' # operating
 VITE_UPLOAD_BASEURL = 'http://124.222.152.234:8101/operating/file/upload'
+
+# 极光key
+VITE_MTPUSH_APPKEY_ANDROID = 'c95a2cb077c243ebbfa896f5'

+ 82 - 84
manifest.config.ts

@@ -17,7 +17,8 @@ const {
   VITE_UNI_APPID,
   VITE_WX_APPID,
   VITE_APP_PUBLIC_BASE,
-  VITE_FALLBACK_LOCALE // 默认系统语言
+  VITE_FALLBACK_LOCALE, // 默认系统语言
+  VITE_MTPUSH_APPKEY_ANDROID,
 } = env
 
 export default defineManifestConfig({
@@ -31,11 +32,11 @@ export default defineManifestConfig({
   'fallbackLocale': 'bn',
   'h5': {
     router: {
-      base: VITE_APP_PUBLIC_BASE
+      base: VITE_APP_PUBLIC_BASE,
     },
     uniStatistics: {
-      enable: true
-    }
+      enable: true,
+    },
   },
   /* 5+App特有相关 */
   'app-plus': {
@@ -49,19 +50,19 @@ export default defineManifestConfig({
     nvueStyleCompiler: 'uni-app',
     compilerVersion: 3,
     compatible: {
-      ignoreVersion: true
+      ignoreVersion: true,
     },
     splashscreen: {
       alwaysShowBeforeRender: true,
       waiting: true,
       autoclose: true,
-      delay: 0
+      delay: 0,
     },
     /* 模块配置 */
     modules: {
       Share: {},
       Camera: {},
-      Push: {}
+      Push: {},
     },
     /* 应用发布信息 */
     distribute: {
@@ -85,12 +86,12 @@ export default defineManifestConfig({
           '<uses-permission android:name="android.permission.WAKE_LOCK"/>',
           '<uses-permission android:name="android.permission.FLASHLIGHT"/>',
           '<uses-feature android:name="android.hardware.camera"/>',
-          '<uses-permission android:name="android.permission.WRITE_SETTINGS"/>'
-        ]
+          '<uses-permission android:name="android.permission.WRITE_SETTINGS"/>',
+        ],
       },
       /* ios打包配置 */
       ios: {
-        idfa: false
+        idfa: false,
       },
       /* SDK配置 */
       sdkConfigs: {},
@@ -100,7 +101,7 @@ export default defineManifestConfig({
           hdpi: 'src/static/app/icons/72x72.png',
           xhdpi: 'src/static/app/icons/96x96.png',
           xxhdpi: 'src/static/app/icons/144x144.png',
-          xxxhdpi: 'src/static/app/icons/192x192.png'
+          xxxhdpi: 'src/static/app/icons/192x192.png',
         },
         ios: {
           appstore: 'src/static/app/icons/1024x1024.png',
@@ -113,7 +114,7 @@ export default defineManifestConfig({
             'settings': 'src/static/app/icons/29x29.png',
             'settings@2x': 'src/static/app/icons/58x58.png',
             'spotlight': 'src/static/app/icons/40x40.png',
-            'spotlight@2x': 'src/static/app/icons/80x80.png'
+            'spotlight@2x': 'src/static/app/icons/80x80.png',
           },
           iphone: {
             'app@2x': 'src/static/app/icons/120x120.png',
@@ -123,27 +124,27 @@ export default defineManifestConfig({
             'settings@2x': 'src/static/app/icons/58x58.png',
             'settings@3x': 'src/static/app/icons/87x87.png',
             'spotlight@2x': 'src/static/app/icons/80x80.png',
-            'spotlight@3x': 'src/static/app/icons/120x120.png'
-          }
-        }
+            'spotlight@3x': 'src/static/app/icons/120x120.png',
+          },
+        },
       },
       splashscreen: {
         androidStyle: 'common',
         android: {
           hdpi: 'src/static/app/start/start-480.png',
           xhdpi: 'src/static/app/start/start-720.png',
-          xxhdpi: 'src/static/app/start/start-1080.png'
-        }
-      }
+          xxhdpi: 'src/static/app/start/start-1080.png',
+        },
+      },
     },
     uniStatistics: {
-      enable: true
+      enable: true,
     },
     plugins: {
       'EL-MTPush': {
-        version: "1.1.6",
-        provider: "10093"
-      }
+        version: '1.1.6',
+        provider: '10093',
+      },
     },
     nativePlugins: {
       'EL-MTPush': {
@@ -152,10 +153,7 @@ export default defineManifestConfig({
         MTPUSH_ISPRODUCTION_IOS: '',
         MTPUSH_ADVERTISINGID_IOS: '',
         MTPUSH_DEFAULTINIT_IOS: '',
-        // 正式环境key
-        MTPUSH_APPKEY_ANDROID: '0655e889f95b4325a6712905',
-        // 测试环境key
-        // MTPUSH_APPKEY_ANDROID: '0655e889f95b4325a6712905',
+        MTPUSH_APPKEY_ANDROID: VITE_MTPUSH_APPKEY_ANDROID,
         MTPUSH_CHANNEL_ANDROID: '',
         MTPUSH_PROCESS_ANDROID: '',
         MTPUSH_XIAOMI_APPKEY: '',
@@ -188,133 +186,133 @@ export default defineManifestConfig({
             MTPUSH_APPKEY_IOS: {
               des: '[iOS]EngageLab portal配置应用信息时分配的AppKey',
               key: 'MTPush:APP_KEY',
-              value: ''
+              value: '',
             },
             MTPUSH_CHANNEL_IOS: {
               des: '[iOS]用于统计分发渠道,不需要可填默认值developer-default',
               key: 'MTPush:CHANNEL',
-              value: ''
+              value: '',
             },
             MTPUSH_ISPRODUCTION_IOS: {
               des: '[iOS]是否是生产环境,是填true,不是填false或者不填',
               key: 'MTPush:ISPRODUCTION',
-              value: ''
+              value: '',
             },
             MTPUSH_ADVERTISINGID_IOS: {
               des: '[iOS]广告标识符(IDFA)如果不需要使用IDFA,可不填',
               key: 'MTPush:ADVERTISINGID',
-              value: ''
+              value: '',
             },
             MTPUSH_DEFAULTINIT_IOS: {
               des: '[iOS]是否默认初始化,是填true,不是填false或者不填',
               key: 'MTPush:DEFAULTINIT',
-              value: ''
+              value: '',
             },
             MTPUSH_APPKEY_ANDROID: {
               des: '[Android]EngageLab portal配置应用信息时分配的AppKey',
               key: '',
-              value: ''
+              value: '',
             },
             MTPUSH_CHANNEL_ANDROID: {
               des: '[Android]用于统计分发渠道,不需要可填默认值developer-default',
               key: '',
-              value: ''
+              value: '',
             },
             MTPUSH_PROCESS_ANDROID: {
-              des: "[Android] Engagelab process,Engagelabsdk工作所在的进程,请填写 ':remote', 注意:开头",
+              des: '[Android] Engagelab process,Engagelabsdk工作所在的进程,请填写 \':remote\', 注意:开头',
               key: '',
-              value: ''
+              value: '',
             },
             MTPUSH_XIAOMI_APPKEY: {
               des: '厂商XIAOMI-appKey,示例:MI-12345678',
               key: '',
-              value: ''
+              value: '',
             },
             MTPUSH_XIAOMI_APPID: {
               des: '厂商XIAOMI-appId,示例:MI-12345678',
               key: '',
-              value: ''
+              value: '',
             },
             MTPUSH_MEIZU_APPKEY: {
               des: '厂商MEIZU-appKey,示例:MZ-12345678',
               key: '',
-              value: ''
+              value: '',
             },
             MTPUSH_MEIZU_APPID: {
               des: '厂商MEIZU-appId,示例:MZ-12345678',
               key: '',
-              value: ''
+              value: '',
             },
             MTPUSH_OPPO_APPKEY: {
               des: '厂商OPPO-appkey,示例:OP-12345678',
               key: '',
-              value: ''
+              value: '',
             },
             MTPUSH_OPPO_APPID: {
               des: '厂商OPPO-appId,示例:OP-12345678',
               key: '',
-              value: ''
+              value: '',
             },
             MTPUSH_OPPO_APPSECRET: {
               des: '厂商OPPO-appSecret,示例:OP-12345678',
               key: '',
-              value: ''
+              value: '',
             },
             MTPUSH_VIVO_APPKEY: {
               des: '厂商VIVO-appkey,示例:12345678',
               key: '',
-              value: ''
+              value: '',
             },
             MTPUSH_VIVO_APPID: {
               des: '厂商VIVO-appId,示例:12345678',
               key: '',
-              value: ''
+              value: '',
             },
             MTPUSH_HONOR_APPID: {
               des: '厂商HONOR-appId,示例:12345678',
               key: '',
-              value: ''
+              value: '',
             },
             MTPUSH_HUAWEI_APPID: {
               des: '厂商HUAWEI-appId,示例:appid=12346578',
               key: 'com.huawei.hms.client.appid',
-              value: ''
+              value: '',
             },
             MTPUSH_GOOGLE_API_KEY: {
               des: '厂商google api_key,示例:G-asxa1232',
               key: 'google_api_key',
-              value: ''
+              value: '',
             },
             MTPUSH_GOOGLE_APP_ID: {
               des: '厂商google mobilesdk_app_id,示例:G-12346578',
               key: 'google_app_id',
-              value: ''
+              value: '',
             },
             MTPUSH_GOOGLE_PROJECT_NUMBER: {
               des: '厂商google project_number,示例:G-12346578',
               key: 'gcm_defaultSenderId',
-              value: ''
+              value: '',
             },
             MTPUSH_GOOGLE_PROJECT_ID: {
               des: '厂商google project_id ,示例:G-12346578',
               key: 'project_id',
-              value: ''
+              value: '',
             },
             MTPUSH_GOOGLE_STORAGE_BUCKET: {
               des: '厂商google storage_bucket,示例:G-12346578',
               key: 'google_storage_bucket',
-              value: ''
-            }
-          }
-        }
-      }
-    }
+              value: '',
+            },
+          },
+        },
+      },
+    },
   },
   /* 快应用特有相关 */
   'quickapp': {
     uniStatistics: {
-      enable: false
-    }
+      enable: false,
+    },
   },
   /* 小程序特有相关 */
   'mp-weixin': {
@@ -323,74 +321,74 @@ export default defineManifestConfig({
       urlCheck: false,
       // 是否启用 ES6 转 ES5
       es6: true,
-      minified: true
+      minified: true,
     },
     optimization: {
-      subPackages: true
+      subPackages: true,
     },
     usingComponents: true,
     uniStatistics: {
-      enable: false
-    }
+      enable: false,
+    },
     // __usePrivacyCheck__: true,
   },
   'mp-alipay': {
     usingComponents: true,
     styleIsolation: 'shared',
     uniStatistics: {
-      enable: false
-    }
+      enable: false,
+    },
   },
   'mp-baidu': {
     usingComponents: true,
     uniStatistics: {
-      enable: false
-    }
+      enable: false,
+    },
   },
   'mp-toutiao': {
     usingComponents: true,
     uniStatistics: {
-      enable: false
-    }
+      enable: false,
+    },
   },
   'app-harmony': {
     uniStatistics: {
-      enable: false
-    }
+      enable: false,
+    },
   },
   'mp-harmony': {
     uniStatistics: {
-      enable: false
-    }
+      enable: false,
+    },
   },
   'mp-jd': {
     uniStatistics: {
-      enable: false
-    }
+      enable: false,
+    },
   },
   'mp-kuaishou': {
     uniStatistics: {
-      enable: false
-    }
+      enable: false,
+    },
   },
   'mp-lark': {
     uniStatistics: {
-      enable: false
-    }
+      enable: false,
+    },
   },
   'mp-qq': {
     uniStatistics: {
-      enable: false
-    }
+      enable: false,
+    },
   },
   'mp-xhs': {
     uniStatistics: {
-      enable: false
-    }
+      enable: false,
+    },
   },
   'uniStatistics': {
     enable: false, // 全局开启
-    version: '2' // 开启新版uni统计,值为字符串
+    version: '2', // 开启新版uni统计,值为字符串
   },
-  'vueVersion': '3'
+  'vueVersion': '3',
 })

+ 1 - 1
package.json

@@ -4,7 +4,7 @@
   "version": "3.2.0",
   "packageManager": "pnpm@10.10.0",
   "description": "BandhuBuy - APP",
-  "update-time": "2026-01-12",
+  "update-time": "2026-01-13",
   "engines": {
     "node": ">=18",
     "pnpm": ">=7.30"

+ 0 - 2
pages.config.ts

@@ -94,7 +94,6 @@ export default defineUniPages({
       path: 'pages/income/income',
       type: 'page',
       layout: 'tabbar',
-      needLogin: true,
       style: {
         navigationBarTitleText: '%income.title%',
         navigationBarBackgroundColor: '#fff',
@@ -130,7 +129,6 @@ export default defineUniPages({
       path: 'pages/mine/mine',
       type: 'page',
       layout: 'tabbar',
-      needLogin: true,
       style: {
         navigationStyle: 'custom',
       },

+ 17 - 0
src/api/order.ts

@@ -91,3 +91,20 @@ export function bindingAddress(data: any) {
 export function pendingRedDots() {
   return http.get<any>(`${pre}/app/order/pendingRedDots`)
 }
+
+/**
+ * 可回收订单数
+ * @returns
+ */
+export function userCanRecycleNum() {
+  return http.get<any>(`${pre}/app/order/app/userCanRecycleNum`)
+}
+
+/**
+ * 回收订单
+ * @param data { orderNos: string[] }
+ * @returns
+ */
+export function recycleOrder(data: { orderNos: string[] }) {
+  return http.post<any>(`${pre}/app/order/app/recycleOrder`, data)
+}

+ 168 - 0
src/components/CustomerServiceFab.vue

@@ -0,0 +1,168 @@
+<script lang="ts" setup>
+import { onShow } from '@dcloudio/uni-app'
+import { getConfigByCode } from '@/api/common'
+import { openH5WhatsApp } from '@/utils/social'
+import { toast } from '@/utils/toast'
+
+const props = defineProps<{
+  bottomOffset?: number
+}>()
+
+const STORAGE_KEY = 'customer_service_fab_position_v1'
+
+const x = ref<number>(0)
+const y = ref<number>(0)
+
+function rpxToPx(rpx: number) {
+  const sys = uni.getSystemInfoSync()
+  const windowWidth = sys.windowWidth || 375
+  return (rpx * windowWidth) / 750
+}
+
+function getBottomLimitPx() {
+  const sys = uni.getSystemInfoSync() as any
+  const bottomOffsetPx = rpxToPx(props.bottomOffset ?? 0)
+  const safeBottomPx = Number(sys?.safeAreaInsets?.bottom) || 0
+  return bottomOffsetPx + safeBottomPx
+}
+
+function initDefaultPosition() {
+  const sys = uni.getSystemInfoSync()
+  const windowWidth = sys.windowWidth || 375
+  const windowHeight = sys.windowHeight || 667
+  const sizePx = rpxToPx(80)
+  const rightInsetPx = rpxToPx(24)
+  const bottomInsetPx = rpxToPx(-86)
+  const bottomOffsetPx = getBottomLimitPx()
+  x.value = Math.max(0, windowWidth - sizePx - rightInsetPx)
+  y.value = Math.max(0, windowHeight - sizePx - bottomOffsetPx - bottomInsetPx)
+}
+
+function clampPosition() {
+  const sys = uni.getSystemInfoSync()
+  const windowWidth = sys.windowWidth || 375
+  const windowHeight = sys.windowHeight || 667
+  const sizePx = rpxToPx(80)
+  const bottomOffsetPx = getBottomLimitPx()
+  const maxX = Math.max(0, windowWidth - sizePx)
+  const maxY = Math.max(0, windowHeight - sizePx - bottomOffsetPx)
+  x.value = Math.min(Math.max(0, x.value), maxX)
+  y.value = Math.min(Math.max(0, y.value), maxY)
+}
+
+function restorePosition() {
+  try {
+    const raw = uni.getStorageSync(STORAGE_KEY)
+    if (!raw) {
+      initDefaultPosition()
+      return
+    }
+    const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw
+    const nx = Number(parsed?.x)
+    const ny = Number(parsed?.y)
+    if (Number.isFinite(nx) && Number.isFinite(ny)) {
+      x.value = nx
+      y.value = ny
+      clampPosition()
+      return
+    }
+    initDefaultPosition()
+  }
+  catch {
+    initDefaultPosition()
+  }
+}
+
+function persistPosition() {
+  try {
+    uni.setStorageSync(STORAGE_KEY, JSON.stringify({ x: x.value, y: y.value }))
+  }
+  catch {
+  }
+}
+
+function persistPositionClamped() {
+  clampPosition()
+  persistPosition()
+}
+
+onShow(() => {
+  restorePosition()
+})
+
+function handleMoveChange(e: any) {
+  const detail = e?.detail
+  const nx = Number(detail?.x)
+  const ny = Number(detail?.y)
+  if (Number.isFinite(nx))
+    x.value = nx
+  if (Number.isFinite(ny))
+    y.value = ny
+  clampPosition()
+}
+
+async function openCustomerService() {
+  try {
+    const res = await getConfigByCode({ code: 'live_chat' })
+    const value = res?.data?.valueInfo
+    if (!value) {
+      toast.info('客服暂不可用')
+      return
+    }
+    openH5WhatsApp('live_chat', value)
+  }
+  catch {
+    toast.info('客服暂不可用')
+  }
+}
+</script>
+
+<template>
+  <movable-area class="customer-fab-area">
+    <movable-view
+      :x="x"
+      :y="y"
+      direction="all"
+      :inertia="false"
+      :animation="false"
+      :out-of-bounds="false"
+      class="customer-fab-movable"
+      @change="handleMoveChange"
+      @touchend="persistPositionClamped"
+      @touchcancel="persistPositionClamped"
+    >
+      <image
+        src="/static/icons/whatsapp.png"
+        class="customer-fab-trigger__icon"
+        mode="heightFix"
+        @click="openCustomerService"
+      />
+    </movable-view>
+  </movable-area>
+</template>
+
+<style lang="scss" scoped>
+.customer-fab-area {
+  position: fixed;
+  left: 0;
+  top: 0;
+  width: 100vw;
+  height: 100vh;
+  pointer-events: none;
+  z-index: 999;
+}
+
+.customer-fab-movable {
+  pointer-events: auto;
+  transition: none;
+  width: 80rpx;
+  height: 80rpx;
+}
+
+.customer-fab-trigger__icon {
+  box-shadow: 0 12rpx 30rpx rgba(0, 0, 0, 0.16);
+  height: 80rpx;
+  width: 80rpx;
+  border-radius: 50%;
+}
+</style>

+ 1 - 1
src/components/DialogBox/DialogBox.vue

@@ -93,7 +93,7 @@ function handleCancel() {
 </script>
 
 <template>
-  <wd-overlay :show="props.show" @click="handleOverlayClick">
+  <wd-overlay :show="props.show" :z-index="999" @click="handleOverlayClick">
     <view class="wrapper">
       <view class="w-full rounded-24rpx bg-white p-40rpx text-center" @click.stop>
         <slot>

+ 5 - 0
src/layouts/default.vue

@@ -1,6 +1,8 @@
 <script lang="ts" setup>
 import type { ConfigProviderThemeVars } from 'wot-design-uni'
 
+import CustomerServiceFab from '@/components/CustomerServiceFab.vue'
+
 const themeVars: ConfigProviderThemeVars = {
   // colorTheme: 'red',
   // buttonPrimaryBgColor: '#07c160',
@@ -11,6 +13,9 @@ const themeVars: ConfigProviderThemeVars = {
 <template>
   <wd-config-provider :theme-vars="themeVars">
     <slot />
+    <!-- #ifdef H5 -->
+    <CustomerServiceFab :bottom-offset="120" />
+    <!-- #endif -->
     <wd-toast />
     <wd-message-box />
   </wd-config-provider>

+ 5 - 0
src/layouts/tabbar.vue

@@ -1,5 +1,7 @@
 <script lang="ts" setup>
 import type { ConfigProviderThemeVars } from 'wot-design-uni'
+import CustomerServiceFab from '@/components/CustomerServiceFab.vue'
+
 import FgTabbar from './fg-tabbar/fg-tabbar.vue'
 
 const themeVars: ConfigProviderThemeVars = {
@@ -12,6 +14,9 @@ const themeVars: ConfigProviderThemeVars = {
 <template>
   <wd-config-provider :theme-vars="themeVars">
     <slot />
+    <!-- #ifdef H5 -->
+    <CustomerServiceFab :bottom-offset="180" />
+    <!-- #endif -->
     <FgTabbar />
     <wd-toast />
     <wd-message-box />

+ 19 - 0
src/locale/bn.json

@@ -36,6 +36,18 @@
   "myOrders.order.id": "অর্ডার আইডি",
   "myOrders.order.color": "রং",
   "myOrders.order.quantity": "পরিমাণ",
+  "myOrders.selectTip": "আপনি পুনর্ব্যবহারের জন্য {0}টি অর্ডার বেছে নিতে পারেন",
+  "myOrders.total": "মোট",
+  "myOrders.recycleOrder": "অর্ডার পুনর্ব্যবহার করুন",
+  "myOrders.recycleSuccess": "অর্ডার সফলভাবে পুনর্ব্যবহার করা হয়েছে",
+  "myOrders.recycleFailed": "অর্ডার পুনর্ব্যবহার করতে ব্যর্থ",
+  "myOrders.dialog.title": "অর্ডার বিক্রয় করুন",
+  "myOrders.dialog.notesTitle": "নোট:",
+  "myOrders.dialog.note1": "① আপনি BandhuBuy কে গ্রুপ অর্ডারের আইটেমগুলি বিক্রয় করতে বেছে নিতে পারেন।",
+  "myOrders.dialog.note2": "② বিক্রয় মূল্য পণ্যের {0}%।",
+  "myOrders.dialog.note3": "③ পুনরুদ্ধার করা পরিমাণ আপনার আয় অ্যাকাউন্টে বিতরণ করা হবে।",
+  "myOrders.dialog.priceLabel": "বিক্রয় মূল্য",
+  "myOrders.dialog.confirm": "নিশ্চিত করুন",
   "orderDetail.title": "অর্ডারের বিস্তারিত",
   "orderDetail.loading": "লোড হচ্ছে...",
   "orderDetail.congrats": "অভিনন্দন, আপনি এই গ্রুপে পুরস্কার জিতেছেন!",
@@ -43,6 +55,9 @@
   "orderDetail.sorry": "দুঃখিত, আপনি এই গ্রুপে জিততে পারেননি",
   "orderDetail.waiting": "অনুগ্রহ করে এই গ্রুপের ড্র এর জন্য অপেক্ষা করুন",
   "orderDetail.paymentCountdown": "অনুগ্রহ করে এই সময়ের মধ্যে পেমেন্ট করুন:",
+  "orderDetail.status9.sellBackPrefix": "পণ্যটি সেল ব্যাক করা হয়েছে",
+  "orderDetail.status9.receivePrefix": "আপনি পেয়েছেন",
+  "orderDetail.status9.rewardSuffix": "গ্রুপ ওপেনিং রিওয়ার্ড",
   "orderDetail.address.add": "অনুগ্রহ করে আপনার শিপিং ঠিকানা প্রদান করুন",
   "orderDetail.address.name": "প্রাপক",
   "orderDetail.address.orderNo": "অর্ডার আইডি",
@@ -301,6 +316,8 @@
   "notifications.order.groupBuyFail.content": "আপনার অংশগ্রহণ করা গ্রুপ অর্ডার [{orderId}] ব্যর্থ হয়েছে",
   "notifications.order.shipped.title": "অর্ডার সফলভাবে শিপ হয়েছে",
   "notifications.order.shipped.content": "আপনার অংশগ্রহণ করা গ্রুপ অর্ডার [{orderId}] সফলভাবে শিপ হয়েছে",
+  "notifications.order.recycleReward.title": "অর্ডার রিসাইকেল পুরস্কার",
+  "notifications.order.recycleReward.content": "আপনি অর্ডার রিসাইকেলের জন্য পুরস্কার পেয়েছেন",
   "notifications.reward.referFriends.title": "বন্ধু রেফার পুরস্কার",
   "notifications.reward.referFriends.content": "আপনি বন্ধু রেফারের জন্য পুরস্কার পেয়েছেন",
   "notifications.reward.groupBuy.title": "গ্রুপ বাই যোগ দিন পুরস্কার",
@@ -365,6 +382,8 @@
   "checkout.selectPaymentMethod": "পেমেন্ট পদ্ধতি নির্বাচন করুন",
   "checkout.walletBalance": "ব্যালেন্স",
   "checkout.placeOrder": "অর্ডার করুন",
+  "checkout.trust.rewards": "সবার জন্য নিশ্চিত রিওয়ার্ড!",
+  "checkout.trust.refund": "না জিতলে সম্পূর্ণ রিফান্ড!",
   "checkout.dialog.insufficientBalance": "আপনার ওয়ালেট ব্যালেন্স অপর্যাপ্ত।\nঅনুগ্রহ করে রিচার্জ করুন!",
   "checkout.dialog.rechargeDiscount": "রিচার্জ সর্বোচ্চ ছাড় ৫%",
   "checkout.dialog.rechargeNow": "এখনই রিচার্জ করুন",

+ 19 - 0
src/locale/en.json

@@ -36,6 +36,18 @@
   "myOrders.order.id": "Order ID",
   "myOrders.order.color": "Color",
   "myOrders.order.quantity": "Quantity",
+  "myOrders.selectTip": "You can choose {0} orders for recycling",
+  "myOrders.total": "Total",
+  "myOrders.recycleOrder": "Recycle Order",
+  "myOrders.recycleSuccess": "Order recycled successfully",
+  "myOrders.recycleFailed": "Failed to recycle order",
+  "myOrders.dialog.title": "Sell Back Order",
+  "myOrders.dialog.notesTitle": "Notes:",
+  "myOrders.dialog.note1": "① You can choose to have BandhuBuy sell back the items in the group order.",
+  "myOrders.dialog.note2": "② The sell back price is {0}% of the product.",
+  "myOrders.dialog.note3": "③ The recovered amount will be distributed to your earnings account.",
+  "myOrders.dialog.priceLabel": "Sell Back Price",
+  "myOrders.dialog.confirm": "Confirm",
   "orderDetail.title": "Order Detail",
   "orderDetail.loading": "Loading...",
   "orderDetail.congrats": "Congrats, You won the prize in this group!",
@@ -43,6 +55,9 @@
   "orderDetail.sorry": "So sorry, You didn't win in this group",
   "orderDetail.waiting": "Please wait for the draw of this group",
   "orderDetail.paymentCountdown": "Please make payment within:",
+  "orderDetail.status9.sellBackPrefix": "Product has been sell back for",
+  "orderDetail.status9.receivePrefix": "You have received",
+  "orderDetail.status9.rewardSuffix": "group opening reward",
   "orderDetail.address.add": "Please provide your shipping address",
   "orderDetail.address.name": "Recipient",
   "orderDetail.address.orderNo": "Order ID",
@@ -301,6 +316,8 @@
   "notifications.order.groupBuyFail.content": "The group order you participated in [{orderId}] has failed",
   "notifications.order.shipped.title": "Order shipped successfully",
   "notifications.order.shipped.content": "The group order you participated in [{orderId}] has been successfully shipped",
+  "notifications.order.recycleReward.title": "Order Recycle Reward",
+  "notifications.order.recycleReward.content": "You have received the reward for order recycle",
   "notifications.reward.referFriends.title": "Refer Friends Reward",
   "notifications.reward.referFriends.content": "You have received the reward for refer friends",
   "notifications.reward.groupBuy.title": "Join Group Buy Reward",
@@ -365,6 +382,8 @@
   "checkout.selectPaymentMethod": "Select Payment Method",
   "checkout.walletBalance": "Balance",
   "checkout.placeOrder": "Place Order",
+  "checkout.trust.rewards": "Guaranteed rewards for ALL!",
+  "checkout.trust.refund": "Full refund if not won!",
   "checkout.dialog.insufficientBalance": "Your wallet balance is insufficient.\nPlease recharge!",
   "checkout.dialog.rechargeDiscount": "Recharge Highest Discount 5%",
   "checkout.dialog.rechargeNow": "Recharge Now",

+ 19 - 0
src/locale/zh-Hans.json

@@ -36,6 +36,18 @@
   "myOrders.order.id": "订单号",
   "myOrders.order.color": "颜色",
   "myOrders.order.quantity": "数量",
+  "myOrders.selectTip": "您可以选择 {0} 个订单进行回收",
+  "myOrders.total": "总计",
+  "myOrders.recycleOrder": "回收订单",
+  "myOrders.recycleSuccess": "订单回收成功",
+  "myOrders.recycleFailed": "订单回收失败",
+  "myOrders.dialog.title": "回售订单",
+  "myOrders.dialog.notesTitle": "注意事项:",
+  "myOrders.dialog.note1": "① 您可以选择让 BandhuBuy 回售团购订单中的商品。",
+  "myOrders.dialog.note2": "② 回收价格为商品价格的 {0}%。",
+  "myOrders.dialog.note3": "③ 回收金额将分配到您的收益账户。",
+  "myOrders.dialog.priceLabel": "回售价格",
+  "myOrders.dialog.confirm": "确认",
   "orderDetail.title": "订单详情",
   "orderDetail.loading": "加载中...",
   "orderDetail.congrats": "恭喜,您在这个团中中奖了!",
@@ -43,6 +55,9 @@
   "orderDetail.sorry": "抱歉,您在这个团中未中奖",
   "orderDetail.waiting": "请等待本团开奖",
   "orderDetail.paymentCountdown": "请在以下时间内完成支付:",
+  "orderDetail.status9.sellBackPrefix": "商品已回收,回收价",
+  "orderDetail.status9.receivePrefix": "您已获得",
+  "orderDetail.status9.rewardSuffix": "开团奖励",
   "orderDetail.address.add": "请填写收货地址",
   "orderDetail.address.name": "收件人",
   "orderDetail.address.orderNo": "订单号",
@@ -301,6 +316,8 @@
   "notifications.order.groupBuyFail.content": "您参与的团购订单[{orderId}]已失败",
   "notifications.order.shipped.title": "订单发货成功",
   "notifications.order.shipped.content": "您参与的团购订单[{orderId}]已成功发货",
+  "notifications.order.recycleReward.title": "订单回收奖励",
+  "notifications.order.recycleReward.content": "您已收到订单回收奖励",
   "notifications.reward.referFriends.title": "推荐好友奖励",
   "notifications.reward.referFriends.content": "您已获得推荐好友奖励",
   "notifications.reward.groupBuy.title": "参团奖励",
@@ -365,6 +382,8 @@
   "checkout.selectPaymentMethod": "选择支付方式",
   "checkout.walletBalance": "余额",
   "checkout.placeOrder": "下单",
+  "checkout.trust.rewards": "人人有奖,保证获得奖励!",
+  "checkout.trust.refund": "未中奖全额退款!",
   "checkout.dialog.insufficientBalance": "您的钱包余额不足。\n请充值!",
   "checkout.dialog.rechargeDiscount": "充值最高优惠5%",
   "checkout.dialog.rechargeNow": "立即充值",

+ 1 - 1
src/manifest.json

@@ -117,7 +117,7 @@
         "MTPUSH_ISPRODUCTION_IOS": "",
         "MTPUSH_ADVERTISINGID_IOS": "",
         "MTPUSH_DEFAULTINIT_IOS": "",
-        "MTPUSH_APPKEY_ANDROID": "0655e889f95b4325a6712905",
+        "MTPUSH_APPKEY_ANDROID": "c95a2cb077c243ebbfa896f5",
         "MTPUSH_CHANNEL_ANDROID": "",
         "MTPUSH_PROCESS_ANDROID": "",
         "MTPUSH_XIAOMI_APPKEY": "",

+ 12 - 2
src/pages.json

@@ -80,7 +80,6 @@
       "path": "pages/income/income",
       "type": "page",
       "layout": "tabbar",
-      "needLogin": true,
       "style": {
         "navigationBarTitleText": "%income.title%",
         "navigationBarBackgroundColor": "#fff"
@@ -98,6 +97,7 @@
       "path": "pages/mine/addressBook",
       "type": "page",
       "layout": "default",
+      "needLogin": true,
       "style": {
         "navigationBarTitleText": "%addressBook.title%",
         "navigationBarBackgroundColor": "#fff"
@@ -107,6 +107,7 @@
       "path": "pages/mine/addressBookOperate",
       "type": "page",
       "layout": "default",
+      "needLogin": true,
       "style": {
         "navigationBarTitleText": "%addressBook.title%",
         "navigationBarBackgroundColor": "#fff"
@@ -116,7 +117,6 @@
       "path": "pages/mine/mine",
       "type": "page",
       "layout": "tabbar",
-      "needLogin": true,
       "style": {
         "navigationStyle": "custom"
       }
@@ -125,6 +125,7 @@
       "path": "pages/mine/myFavorite",
       "type": "page",
       "layout": "default",
+      "needLogin": true,
       "style": {
         "navigationBarTitleText": "%mine.pages.myFavorite.title%",
         "navigationBarBackgroundColor": "#fff"
@@ -134,6 +135,7 @@
       "path": "pages/mine/myProfile",
       "type": "page",
       "layout": "default",
+      "needLogin": true,
       "style": {
         "navigationBarTitleText": "%myProfile.title%",
         "navigationBarBackgroundColor": "#fff"
@@ -152,6 +154,7 @@
       "path": "pages/mine/share",
       "type": "page",
       "layout": "default",
+      "needLogin": true,
       "style": {
         "navigationStyle": "custom",
         "navigationBarTitleText": "%mine.pages.share.title%"
@@ -171,6 +174,7 @@
       "path": "pages/myOrders/myOrders",
       "type": "page",
       "layout": "default",
+      "needLogin": true,
       "style": {
         "navigationBarTitleText": "%myOrders.title%",
         "navigationBarBackgroundColor": "#fff"
@@ -180,6 +184,7 @@
       "path": "pages/myOrders/orderDetail",
       "type": "page",
       "layout": "default",
+      "needLogin": true,
       "style": {
         "navigationBarTitleText": "%orderDetail.title%",
         "navigationBarBackgroundColor": "#fff"
@@ -259,6 +264,7 @@
       "path": "pages/wallet/frozenRecord",
       "type": "page",
       "layout": "default",
+      "needLogin": true,
       "style": {
         "navigationBarTitleText": "%wallet.frozenRecord.title%",
         "navigationBarBackgroundColor": "#fff"
@@ -278,6 +284,7 @@
       "path": "pages/wallet/recharge",
       "type": "page",
       "layout": "default",
+      "needLogin": true,
       "style": {
         "navigationBarTitleText": "%wallet.recharge.title%",
         "navigationBarBackgroundColor": "#fff",
@@ -298,6 +305,7 @@
       "path": "pages/wallet/rechargeRecord",
       "type": "page",
       "layout": "default",
+      "needLogin": true,
       "style": {
         "navigationBarTitleText": "%wallet.rechargeRecord.title%",
         "navigationBarBackgroundColor": "#fff"
@@ -307,6 +315,7 @@
       "path": "pages/wallet/withdraw",
       "type": "page",
       "layout": "default",
+      "needLogin": true,
       "style": {
         "navigationStyle": "custom",
         "navigationBarTitleText": "%wallet.withdraw.title%"
@@ -316,6 +325,7 @@
       "path": "pages/wallet/withdrawRecord",
       "type": "page",
       "layout": "default",
+      "needLogin": true,
       "style": {
         "navigationBarTitleText": "%wallet.withdrawRecord.title%",
         "navigationBarBackgroundColor": "#fff"

+ 17 - 2
src/pages/income/income.vue

@@ -1,7 +1,6 @@
 <route lang="json5">
   {
     layout: 'tabbar',
-    needLogin: true,
     style: {
       navigationBarTitleText: '%income.title%',
       navigationBarBackgroundColor: '#fff',
@@ -16,6 +15,7 @@ import useZPaging from 'z-paging/components/z-paging/js/hooks/useZPaging.js'
 
 import { getEnum as _getEnum } from '@/api/common'
 import { getAccountInfo as _getAccountInfo, envelopeList } from '@/api/wallet'
+import { useUserStore } from '@/store/user'
 import { formatNumber } from '@/utils'
 import { toPage } from '@/utils/page'
 
@@ -30,6 +30,9 @@ useZPaging(paging)
 
 const dayType = ref(1)
 
+const userStore = useUserStore()
+const isLoggedIn = computed(() => !!userStore.token)
+
 // 搜索结果
 const statusEnum = ref<any[]>([])
 const typeEnum = ref<any[]>([])
@@ -45,6 +48,8 @@ async function getEnum(id: number) {
   }
 }
 async function getAccountInfo() {
+  if (!isLoggedIn.value)
+    return
   const res = await _getAccountInfo()
   console.log(res)
   if (res.code === '200') {
@@ -52,6 +57,10 @@ async function getAccountInfo() {
   }
 }
 async function queryList(pageNo: number, pageSize: number) {
+  if (!isLoggedIn.value) {
+    paging.value.complete([])
+    return
+  }
   const data = {
     page: pageNo,
     size: pageSize,
@@ -68,7 +77,13 @@ async function queryList(pageNo: number, pageSize: number) {
 onShow(() => {
   getEnum(4)
   getEnum(5)
-  getAccountInfo()
+  if (isLoggedIn.value) {
+    getAccountInfo()
+  }
+  else {
+    walletInfo.value = {}
+    paging.value?.complete([])
+  }
 })
 </script>
 

+ 5 - 1
src/pages/index/index.vue

@@ -126,6 +126,10 @@ const priceTabList = ref([
 const dataList = ref<any>([])
 const isProductListLoading = ref(false) // 商品列表加载状态
 
+function handlePriceTabChange() {
+  paging.value?.reload()
+}
+
 async function queryList(pageNo: number, pageSize: number) {
   // 如果是第一页,显示骨架屏
   if (pageNo === 1) {
@@ -374,7 +378,7 @@ onShow(() => {
           </scroll-view>
         </view>
         <view class="productList">
-          <wd-tabs v-model="priceTab" slidable="always" :line-width="0" :line-height="0" @click="() => queryList(1, 20)">
+          <wd-tabs v-model="priceTab" slidable="always" :line-width="0" :line-height="0" @click="handlePriceTabChange">
             <template v-for="item in priceTabList" :key="item">
               <wd-tab :title="$t(item.title)" :name="item.value" />
             </template>

+ 1 - 0
src/pages/mine/addressBook.vue

@@ -1,6 +1,7 @@
 <route lang="json5" type="page">
 {
   layout: 'default',
+  needLogin: true,
   style: {
     navigationBarTitleText: '%addressBook.title%',
     navigationBarBackgroundColor: '#fff',

+ 1 - 0
src/pages/mine/addressBookOperate.vue

@@ -1,6 +1,7 @@
 <route lang="json5" type="page">
 {
   layout: 'default',
+  needLogin: true,
   style: {
     navigationBarTitleText: '%addressBook.title%',
     navigationBarBackgroundColor: '#fff',

+ 21 - 6
src/pages/mine/mine.vue

@@ -1,7 +1,6 @@
 <route lang="json5">
   {
     layout: 'tabbar',
-    needLogin: true,
     style: {
       navigationStyle: 'custom',
     },
@@ -104,7 +103,7 @@ const menuList = computed(() => {
     { name: t('mine.menu.share'), url: '/pages/mine/share', icon: '/static/icons/share.png' },
     { name: t('mine.menu.favorite'), url: '/pages/mine/myFavorite', icon: '/static/icons/my-favorite.png' },
     { name: t('mine.menu.chat'), config: 'live_chat', icon: '/static/icons/live-chat.png' },
-    { name: t('mine.menu.activity'), config: 'activity_group', icon: '/static/icons/activity-group.png' },
+    // { name: t('mine.menu.activity'), config: 'activity_group', icon: '/static/icons/activity-group.png' },
   ]
 })
 
@@ -146,6 +145,8 @@ async function menuClick(item: any) {
 }
 const walletInfo = ref<any>({})
 async function getWalletInfo() {
+  if (!isLoggedIn.value)
+    return
   // 获取钱包信息-查询余额
   const res = await getWalletAccountInfo()
   console.log(res)
@@ -154,6 +155,8 @@ async function getWalletInfo() {
 
 const pendingRedDotsData = ref<any>({})
 async function getPendingRedDots() {
+  if (!isLoggedIn.value)
+    return
   try {
     const res = await pendingRedDots()
     if (res.code === '200') {
@@ -166,6 +169,12 @@ async function getPendingRedDots() {
 
 // 下拉刷新时调用
 async function handleRefresh() {
+  if (!isLoggedIn.value) {
+    walletInfo.value = {}
+    pendingRedDotsData.value = {}
+    paging.value?.complete()
+    return
+  }
   await Promise.all([
     getWalletInfo(),
     getPendingRedDots(),
@@ -175,14 +184,20 @@ async function handleRefresh() {
 }
 
 onShow(() => {
-  getWalletInfo()
-  getPendingRedDots()
-  userStore.getUserInfo()
+  if (isLoggedIn.value) {
+    getWalletInfo()
+    getPendingRedDots()
+    userStore.getUserInfo()
+  }
+  else {
+    walletInfo.value = {}
+    pendingRedDotsData.value = {}
+  }
 })
 </script>
 
 <template>
-  <z-paging ref="paging" refresher-only use-page-scroll @refresh="handleRefresh">
+  <z-paging ref="paging" use-page-scroll refresher-only @refresh="handleRefresh">
     <view
       class="flex items-center justify-between bg-[rgba(var(--wot-color-theme-rgb),0.3)] pb-72rpx pl-24rpx pr-54rpx"
       :style="{ paddingTop: `${safeAreaInsets?.top + 24}px` }"

+ 1 - 0
src/pages/mine/myFavorite.vue

@@ -1,6 +1,7 @@
 <route lang="json5">
 {
   layout: 'default',
+  needLogin: true,
   style: {
     navigationBarTitleText: '%mine.pages.myFavorite.title%',
     navigationBarBackgroundColor: '#fff'

+ 1 - 0
src/pages/mine/myProfile.vue

@@ -1,6 +1,7 @@
 <route lang="json5" type="page">
 {
   layout: 'default',
+  needLogin: true,
   style: {
     navigationBarTitleText: '%myProfile.title%',
     navigationBarBackgroundColor: '#fff',

+ 1 - 0
src/pages/mine/share.vue

@@ -1,6 +1,7 @@
 <route lang="json5">
 {
   layout: 'default',
+  needLogin: true,
   style: {
     navigationStyle: 'custom',
     navigationBarTitleText: '%mine.pages.share.title%'

+ 202 - 6
src/pages/myOrders/myOrders.vue

@@ -1,6 +1,7 @@
 <route lang="json5" type="page">
 {
   layout: 'default',
+  needLogin: true,
   style: {
     navigationBarTitleText: '%myOrders.title%',
     navigationBarBackgroundColor: '#fff',
@@ -13,10 +14,13 @@
 // eslint-disable-next-line unused-imports/no-unused-imports
 import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
 import useZPaging from 'z-paging/components/z-paging/js/hooks/useZPaging.js'
-import { orderList, orderStatusEnum } from '@/api/order'
+import { getConfigByCode } from '@/api/common'
+import { orderList, orderStatusEnum, recycleOrder, userCanRecycleNum } from '@/api/order'
+import { DialogBox } from '@/components/DialogBox'
 import { t } from '@/locale'
 import { formatNumber } from '@/utils'
 import { toPage } from '@/utils/page'
+import { toast } from '@/utils/toast'
 
 defineOptions({
   name: 'MyOrders', // 我的订单
@@ -27,6 +31,7 @@ const paging = ref(null)
 // 类似mixins,如果是页面滚动务必要写这一行,并传入当前ref绑定的paging,注意此处是paging,而非paging.value
 useZPaging(paging)
 const isPageLoading = ref(true) // 页面加载状态
+const systemInfo = ref(uni.getSystemInfoSync()) // 获取系统信息
 const tab = ref<number>(0)
 const tabList = ref<any>([
   {
@@ -50,6 +55,41 @@ const tabList = ref<any>([
     value: 4,
   },
 ])
+
+// 搜索结果
+const dataList = ref<any[]>([])
+
+const maxSelectCount = ref<number>(0) // 最大可选订单数量
+const selectedOrders = ref<string[]>([]) // 已选中的订单ID列表
+
+const recycleProportion = ref<string>('')
+const recycleRate = ref<number>(0)
+
+async function getRecycleProportion() {
+  try {
+    const res = await getConfigByCode({ code: 'recycle_proportion' })
+    if (res.code === '200' && res.data?.valueInfo) {
+      let rate = Number(res.data.valueInfo) || 0
+      if (rate > 1)
+        rate = rate / 100
+      recycleRate.value = rate
+      recycleProportion.value = String(rate * 100)
+    }
+  }
+  catch {
+  }
+}
+
+async function getUserCanRecycleNum() {
+  try {
+    const res = await userCanRecycleNum()
+    if (res.code === '200') {
+      maxSelectCount.value = Number(res.data) || 0
+    }
+  }
+  catch {
+  }
+}
 const orderStatusEnumData = ref<any>([])
 async function getOrderStatus() {
   try {
@@ -63,8 +103,91 @@ async function getOrderStatus() {
   }
 }
 
-// 搜索结果
-const dataList = ref<any[]>([])
+// 判断订单是否已选中
+function isOrderSelected(orderId: string) {
+  return selectedOrders.value.includes(orderId)
+}
+
+// 判断订单是否可以勾选(未选中且未达到上限,或已选中)
+function canSelectOrder(orderId: string) {
+  return isOrderSelected(orderId) || selectedOrders.value.length < maxSelectCount.value
+}
+
+// 切换订单选中状态
+function toggleOrderSelection(orderId: string) {
+  const index = selectedOrders.value.indexOf(orderId)
+  if (index > -1) {
+    // 已选中,取消选中
+    selectedOrders.value.splice(index, 1)
+  }
+  else if (selectedOrders.value.length < maxSelectCount.value) {
+    // 未选中且未达到上限,添加选中
+    selectedOrders.value.push(orderId)
+  }
+}
+
+// 切换tab时清空选中状态
+function handleTabChange() {
+  selectedOrders.value = []
+  getUserCanRecycleNum()
+  queryList(1, 20)
+}
+
+// 计算选中订单的总金额
+const totalAmount = computed(() => {
+  if (selectedOrders.value.length === 0)
+    return 0
+  return dataList.value
+    .filter(item => selectedOrders.value.includes(item.orderId))
+    .reduce((sum, item) => {
+      const price = Number(item?.orderInfoVO?.[0]?.price) || 0
+      const quantity = Number(item?.orderInfoVO?.[0]?.payNum) || 0
+      return sum + (price * quantity)
+    }, 0)
+})
+
+const recycleAmount = computed(() => {
+  return totalAmount.value * recycleRate.value
+})
+
+// 弹框状态
+const showRecycleDialog = ref(false)
+
+// 点击 Recycle Order 按钮,显示二次确认弹框
+function handleRecycleOrder() {
+  showRecycleDialog.value = true
+}
+
+// 确认回收订单
+async function confirmRecycle() {
+  try {
+    if (selectedOrders.value.length === 0) {
+      toast.info(t('myOrders.selectTip', [maxSelectCount.value]))
+      return
+    }
+
+    uni.showLoading({
+      title: t('common.loading'),
+      mask: true,
+    })
+
+    const res = await recycleOrder({ orderNos: selectedOrders.value })
+    if (res.code === '200') {
+      toast.success(t('myOrders.recycleSuccess'))
+    }
+    selectedOrders.value = []
+    // 刷新列表
+    paging.value?.reload()
+    getUserCanRecycleNum()
+  }
+  catch (error) {
+    console.error('回收订单失败:', error)
+    toast.error(t('myOrders.recycleFailed'))
+  }
+  finally {
+    uni.hideLoading()
+  }
+}
 async function queryList(pageNo: number, pageSize: number) {
   // 此处请求仅为演示,请替换为自己项目中的请求
   try {
@@ -79,6 +202,9 @@ async function queryList(pageNo: number, pageSize: number) {
       type: tab.value, // 根据tab的值来查询不同状态的订单
     })
     paging.value.complete(res.data.list)
+    if (pageNo === 1) {
+      getUserCanRecycleNum()
+    }
   }
   catch {
     paging.value.complete(false)
@@ -92,6 +218,8 @@ async function queryList(pageNo: number, pageSize: number) {
 }
 onLoad((options) => {
   getOrderStatus()
+  getUserCanRecycleNum()
+  getRecycleProportion()
 
   // 处理页面参数,如果有type参数则切换到对应的tab
   if (options) {
@@ -109,7 +237,7 @@ onLoad((options) => {
 <template>
   <z-paging ref="paging" v-model="dataList" use-page-scroll @query="queryList">
     <template #top>
-      <wd-tabs v-model="tab" :auto-line-width="true" custom-class="bg-transparent!" slidable="always" @click="() => queryList(1, 20)">
+      <wd-tabs v-model="tab" :auto-line-width="true" custom-class="bg-transparent!" slidable="always" @click="handleTabChange">
         <wd-tab v-for="tabItem in tabList" :key="tabItem.value" :title="t(tabItem.label)" :name="tabItem.value" />
       </wd-tabs>
     </template>
@@ -149,12 +277,26 @@ onLoad((options) => {
 
     <!-- 实际内容 -->
     <template v-else>
+      <!-- Success tab 选择提示 -->
+      <view v-if="tab === 2" class="pt-24rpx text-center font-bold">
+        {{ t('myOrders.selectTip', [maxSelectCount]) }}
+      </view>
       <view class="pt-24rpx">
         <wd-card v-for="item in dataList" :key="item.orderId" type="rectangle" custom-class="px-24rpx! py-6rpx!" custom-content-class="py-18rpx!" custom-title-class="py-18rpx!" @click="toPage({ url: '/pages/myOrders/orderDetail', params: { orderNo: item?.orderId } })">
           <template #title>
             <view class="flex items-center justify-between">
-              <view class="text-28rpx text-#000">
-                {{ t('myOrders.order.id') }}:{{ item.orderId }}
+              <view class="flex items-center text-28rpx text-#000">
+                <!-- 仅在 success tab (tab=2) 时显示勾选框 -->
+                <wd-checkbox
+                  v-if="tab === 2"
+                  :model-value="isOrderSelected(item.orderId)"
+                  :disabled="!canSelectOrder(item.orderId)"
+                  custom-class="mr-16rpx"
+                  @click.stop="toggleOrderSelection(item.orderId)"
+                />
+                <view>
+                  {{ t('myOrders.order.id') }}:{{ item.orderId }}
+                </view>
               </view>
               <wd-text size="26rpx" type="primary" :text="orderStatusEnumData.find((i:any) => i.code === item.status)?.name" />
             </view>
@@ -189,7 +331,61 @@ onLoad((options) => {
         </wd-card>
       </view>
     </template>
+
+    <!-- 底部提交信息块 -->
+    <template #bottom>
+      <view
+        v-if="tab === 2 && selectedOrders.length > 0"
+        class="flex items-center justify-end bg-white px-24rpx pt-30rpx shadow-[0_-2rpx_8rpx_0_rgba(0,0,0,0.1)]"
+        :style="{ paddingBottom: `${systemInfo.safeAreaInsets?.bottom + 30 || 30}rpx` }"
+      >
+        <view>
+          <text>
+            {{ $t('myOrders.total') }}:
+          </text>
+          <text class="text-[var(--wot-color-theme)]">
+            ৳{{ formatNumber(recycleAmount) }}
+          </text>
+        </view>
+        <wd-button
+          custom-class="ml-16rpx! min-w-260rpx! bg-[var(--wot-color-theme)]"
+          @click="handleRecycleOrder"
+        >
+          {{ $t('myOrders.recycleOrder') }}
+        </wd-button>
+      </view>
+    </template>
   </z-paging>
+
+  <!-- 回收订单确认弹框 -->
+  <DialogBox
+    v-model:show="showRecycleDialog"
+    :show-cancel="false"
+    :confirm-text="$t('myOrders.dialog.confirm')"
+    @confirm="confirmRecycle"
+  >
+    <view class="text-left">
+      <view class="pb-24rpx text-center text-32rpx font-bold">
+        {{ $t('myOrders.dialog.title') }}
+      </view>
+      <view class="pb-24rpx text-24rpx font-bold">
+        {{ $t('myOrders.dialog.notesTitle') }}
+      </view>
+      <view class="pb-32rpx text-24rpx text-#666">
+        <view class="space-y-16rpx">
+          <view>{{ $t('myOrders.dialog.note1') }}</view>
+          <view>{{ $t('myOrders.dialog.note2', [recycleProportion]) }}</view>
+          <view>{{ $t('myOrders.dialog.note3') }}</view>
+        </view>
+      </view>
+      <view class="mb-44rpx text-center">
+        <text>{{ $t('myOrders.dialog.priceLabel') }}: </text>
+        <text class="text-[var(--wot-color-theme)]">
+          ৳{{ formatNumber(recycleAmount) }}
+        </text>
+      </view>
+    </view>
+  </DialogBox>
 </template>
 
 <style lang="scss" scoped>

+ 16 - 3
src/pages/myOrders/orderDetail.vue

@@ -1,6 +1,7 @@
 <route lang="json5" type="page">
 {
   layout: 'default',
+  needLogin: true,
   style: {
     navigationBarTitleText: '%orderDetail.title%',
     navigationBarBackgroundColor: '#fff',
@@ -436,10 +437,22 @@ onUnmounted(() => {
     <!-- 实际内容 -->
     <template v-else>
       <view class="pt-20rpx">
-        <!-- 状态显示 -->
+        <!-- 状态显示 lId=1是中奖0是未中奖。storePink?.status是拼团状态 detail?.status是订单状态 -->
         <template v-if="detail.status !== 4 && detail?.status !== 2">
+          <view v-if="detail?.status === 9" class="mb-20rpx bg-#17AA68/80 py-20rpx text-center text-28rpx text-white">
+            {{ t('orderDetail.status9.sellBackPrefix') }}
+            <text class="text-[var(--wot-color-theme)]">
+              ৳{{ formatNumber(detail?.recycleAmount) }}
+            </text>
+            <br>
+            {{ t('orderDetail.status9.receivePrefix') }}
+            <text class="text-[var(--wot-color-theme)]">
+              ৳{{ formatNumber(detail?.brokerage) }}
+            </text>
+            {{ t('orderDetail.status9.rewardSuffix') }}
+          </view>
           <!-- 已中奖 -->
-          <view v-if="detail?.storePink?.status === 2 && detail?.storePink?.lId === 1" class="mb-20rpx bg-#17AA68/80 py-20rpx text-center text-28rpx text-white">
+          <view v-else-if="detail?.storePink?.status === 2 && detail?.storePink?.lId === 1" class="mb-20rpx bg-#17AA68/80 py-20rpx text-center text-28rpx text-white">
             {{ t('orderDetail.congrats') }}
             <br>
             {{ t('orderDetail.receiveReward') }}
@@ -471,7 +484,7 @@ onUnmounted(() => {
           </view>
         </template>
         <!-- 地址 -->
-        <view v-if="detail?.storePink?.status === 2 && detail?.storePink?.lId === 1" class="mb-20rpx bg-white px-24rpx py-20rpx">
+        <view v-if="detail?.storePink?.status === 2 && detail?.storePink?.lId === 1 && detail?.status !== 9" class="mb-20rpx bg-white px-24rpx py-20rpx">
           <!-- 无地址 -->
           <template v-if="!detail.orderAddressVO">
             <view class="flex items-center justify-between" @click="selectAddress">

+ 7 - 2
src/pages/notifications/notifications.vue

@@ -48,7 +48,7 @@ const tabs = computed(() => {
 
 const typeMap = {
   order: ['ORDER_GROUP_BUY_PAYMENT_SUCCESS', 'ORDER_GROUP_BUY_SUCCESS_WIN', 'ORDER_GROUP_BUY_SUCCESS_LOSE', 'ORDER_PROVIDE_SHIPPING_ADDRESS', 'ORDER_GROUP_BUY_FAIL', 'ORDER_SHIPPED_SUCCESS'],
-  income: ['REWARD_REFER_FRIENDS', 'REWARD_GROUP_BUY', 'REWARD_OPEN_GROUP_BUY', 'REWARD_DIRECT_REFERRAL', 'REWARD_CHECKIN', 'REWARD_FIRST_COMMISSION', 'REWARD_SECONDARY_COMMISSION'],
+  income: ['REWARD_REFER_FRIENDS', 'REWARD_GROUP_BUY', 'REWARD_OPEN_GROUP_BUY', 'REWARD_DIRECT_REFERRAL', 'REWARD_CHECKIN', 'REWARD_FIRST_COMMISSION', 'REWARD_SECONDARY_COMMISSION', 'ORDER_RECYCLE_REWARD'],
   wallet: ['MONEY_RECHARGE_SUCCESS', 'MONEY_WITHDRAWAL_ACCOUNT_SUCCESS', 'MONEY_WITHDRAWAL_WALLET_SUCCESS', 'MONEY_WITHDRAWAL_FAIL'],
   other: ['OTHER'],
 }
@@ -84,6 +84,11 @@ const contentTypeMap = {
     contentKey: 'notifications.order.shipped.content',
     link: '/pages/myOrders/orderDetail',
   },
+  ORDER_RECYCLE_REWARD: {
+    titleKey: 'notifications.order.recycleReward.title',
+    contentKey: 'notifications.order.recycleReward.content',
+    link: '/pages/income/income',
+  },
   REWARD_REFER_FRIENDS: {
     titleKey: 'notifications.reward.referFriends.title',
     contentKey: 'notifications.reward.referFriends.content',
@@ -212,7 +217,7 @@ function handleNoticeClick(item: any) {
     const config = contentTypeMap[item.noticeType]
     const link = config?.link
     if (link) {
-      toPage({ url: link, params: typeMap.order.includes(item.noticeType) ? { orderNo: item.noticeMessage } : {} })
+      toPage({ url: link, params: link === '/pages/myOrders/orderDetail' ? { orderNo: item.noticeMessage } : {} })
     }
   }
 }

+ 73 - 0
src/pages/productDetail/checkOut.vue

@@ -25,6 +25,21 @@ onLoad((options) => {
   getPrice()
 })
 
+const trustBanners = ref([
+  {
+    key: 'rewards',
+    icon: '/static/icons/rewards.png',
+    textKey: 'checkout.trust.rewards',
+    variant: 'red',
+  },
+  {
+    key: 'refund',
+    icon: '/static/icons/refund.png',
+    textKey: 'checkout.trust.refund',
+    variant: 'green',
+  },
+])
+
 // 商品详情
 const orderDetail = ref<any>({})
 
@@ -79,6 +94,26 @@ async function handlePlaceOrder() {
 <template>
   <z-paging>
     <view class="pt-20rpx">
+      <view class="trust-banner">
+        <swiper
+          class="trust-banner__swiper"
+          :vertical="true"
+          :autoplay="true"
+          :interval="2500"
+          :duration="350"
+          :circular="true"
+          :disable-touch="true"
+        >
+          <swiper-item v-for="item in trustBanners" :key="item.key">
+            <view class="trust-banner__item" :class="`trust-banner__item--${item.variant}`">
+              <image :src="item.icon" class="trust-banner__icon" mode="heightFix" />
+              <text class="trust-banner__text">
+                {{ $t(item.textKey) }}
+              </text>
+            </view>
+          </swiper-item>
+        </swiper>
+      </view>
       <view class="mb-20rpx flex items-center gap-24rpx bg-white p-24rpx">
         <image
           :src="orderDetail.image"
@@ -164,4 +199,42 @@ async function handlePlaceOrder() {
 .space-y-24rpx > * + * {
   margin-top: 24rpx;
 }
+
+.trust-banner {
+  margin: 0 0 20rpx;
+}
+
+.trust-banner__swiper {
+  height: 72rpx;
+}
+
+.trust-banner__item {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 72rpx;
+  padding: 0 20rpx;
+  font-size: 28rpx;
+  font-weight: 600;
+  overflow: hidden;
+}
+
+.trust-banner__item--red {
+  background: #ffeae8;
+  color: var(--wot-color-theme);
+}
+
+.trust-banner__item--green {
+  background: #ebfbee;
+  color: #25a755;
+}
+
+.trust-banner__icon {
+  height: 32rpx;
+  margin-right: 8rpx;
+}
+
+.trust-banner__text {
+  line-height: 1;
+}
 </style>

+ 9 - 4
src/pages/search/search.vue

@@ -103,6 +103,11 @@ async function getCategoryList() {
 
 // 搜索结果
 const dataList = ref([])
+
+function reloadList() {
+  paging.value?.reload()
+}
+
 async function queryList(pageNo: number, pageSize: number) {
   try {
     // const currentTab = option1.value.find((i: any) => i.value === formData.value.price) || option1.value[0]
@@ -143,16 +148,16 @@ onLoad(() => {
               <view class="back">
                 <wd-icon name="thin-arrow-left" size="32rpx" @click="() => goBack()" />
               </view>
-              <input v-model.trim="formData.storeName" class="search-input" type="text" :placeholder="$t('search.placeholder')" @confirm="queryList(1, 20)">
+              <input v-model.trim="formData.storeName" class="search-input" type="text" :placeholder="$t('search.placeholder')" @confirm="reloadList">
               <wd-icon name="search" custom-class="search-icon" color="#999" size="32rpx" />
             </view>
           </template>
         </wd-navbar>
         <view class="bg-white text-center">
           <wd-drop-menu>
-            <wd-drop-menu-item v-model="formData.price" :options="option1" @change="queryList(1, 20)" />
-            <wd-drop-menu-item v-model="formData.cateId" :options="option2" @change="queryList(1, 20)" />
-            <wd-drop-menu-item v-model="formData.sort" :options="option3" @change="queryList(1, 20)" />
+            <wd-drop-menu-item v-model="formData.price" :options="option1" @change="reloadList" />
+            <wd-drop-menu-item v-model="formData.cateId" :options="option2" @change="reloadList" />
+            <wd-drop-menu-item v-model="formData.sort" :options="option3" @change="reloadList" />
           </wd-drop-menu>
         </view>
       </view>

+ 1 - 0
src/pages/wallet/frozenRecord.vue

@@ -1,6 +1,7 @@
 <route lang="json5" type="page">
 {
   layout: 'default',
+  needLogin: true,
   style: {
     navigationBarTitleText: '%wallet.frozenRecord.title%',
     navigationBarBackgroundColor: '#fff',

+ 1 - 0
src/pages/wallet/recharge.vue

@@ -1,6 +1,7 @@
 <route lang="json5" type="page">
 {
   layout: 'default',
+  needLogin: true,
   style: {
     navigationBarTitleText: '%wallet.recharge.title%',
     navigationBarBackgroundColor: '#fff',

+ 1 - 0
src/pages/wallet/rechargeRecord.vue

@@ -1,6 +1,7 @@
 <route lang="json5" type="page">
 {
   layout: 'default',
+  needLogin: true,
   style: {
     navigationBarTitleText: '%wallet.rechargeRecord.title%',
     navigationBarBackgroundColor: '#fff',

+ 2 - 1
src/pages/wallet/withdraw.vue

@@ -1,6 +1,7 @@
 <route lang="json5" type="page">
 {
   layout: 'default',
+  needLogin: true,
   style: {
     navigationStyle: 'custom',
     navigationBarTitleText: '%wallet.withdraw.title%',
@@ -151,7 +152,7 @@ onLoad((options) => {
     <wd-navbar
       custom-class="bg-#FEE750!"
       :bordered="false"
-      safe-area-inset-top placeholder fixed
+      placeholder safe-area-inset-top fixed
       :title="t('wallet.withdraw.title')"
     >
       <template #left>

+ 1 - 0
src/pages/wallet/withdrawRecord.vue

@@ -1,6 +1,7 @@
 <route lang="json5" type="page">
 {
   layout: 'default',
+  needLogin: true,
   style: {
     navigationBarTitleText: '%wallet.withdrawRecord.title%',
     navigationBarBackgroundColor: '#fff',

BIN
src/static/icons/refund.png


BIN
src/static/icons/rewards.png


+ 1 - 1
src/utils/social.ts

@@ -118,7 +118,7 @@ export function openH5WhatsApp(type: 'live_chat' | 'activity_group', value: stri
   const phone = (value || '').replace(/\s+/g, '')
   const url = type === 'activity_group'
     ? `https://chat.whatsapp.com/${encodeURIComponent(value || '')}`
-    : `https://wa.me/${encodeURIComponent(phone)}`
+    : `https://wa.me/message/${encodeURIComponent(phone)}`
 
   if (typeof window !== 'undefined' && typeof window.open === 'function')
     window.open(url, '_blank')