Răsfoiți Sursa

feat(api): 添加用户信息更新和文件上传API接口

- 新增 sys.updateUserInfo 接口用于更新用户信息
- 新增 sys.uploadFile 接口用于文件上传
- 完善用户信息类型定义,将id字段改为必填
- 修复API响应处理中的字符串解析问题

fix(auth): 登录过期时清除token并优化重定向逻辑

- 登录过期时清除用户token信息
- 优化登录页面重定向逻辑,区分tab页面跳转
- 修改硬编码的授权头为动态token获取

refactor(config): 更新开发环境和静态资源服务器配置

- 将开发环境服务器地址更新为公网地址
- 静态资源配置改为阿里云OSS地址
- 优化环境配置管理

feat(pages): 新增订单确认和支付成功页面

- 新增确认订单页面 common-confirmOrder
- 新增支付成功页面 common-paySuccess
- 创建确认订单状态管理store

feat(user): 完善用户信息管理和头像更新功能

- 添加用户头像getter方法
- 实现用户昵称修改功能
- 实现用户头像上传更新功能
- 优化用户信息展示逻辑

refactor(store): 调整状态持久化配置

- 将临时状态排除列表改为confirmOrder
- 完善用户状态初始值设置

feat(tabbar): 优化首页tabbar功能和商品列表加载

- 实现首页回到顶部功能
- 添加商品列表滚动触底加载
- 优化tabbar页面跳转逻辑
- 更新商品列表展示数据
zhangtao 3 zile în urmă
părinte
comite
8de420d9b2

+ 1 - 1
src/api/api.type.d.ts

@@ -11,7 +11,7 @@ namespace Api {
     /**
      * 会员ID
      */
-    id?: number
+    id: number
     /**
      * 会员手机号
      */

+ 2 - 0
src/api/apiDefinitions.ts

@@ -26,6 +26,8 @@ Some useful links:
 export default {
   'sys.auth': ['POST', '/smqjh-auth/oauth2/token'],
   'sys.userInfo': ['GET', '/smqjh-system/app-api/v1/members/me'],
+  'sys.uploadFile': ['POST', '/smqjh-system/api/v1/files'],
+  'sys.updateUserInfo': ['PUT', '/smqjh-system/app-api/v1/members/{memberId}'],
   'xsb.categories':['GET', '/smqjh-pms/app-api/v1/categories'],
   'xsb.getCategoryProductList':['GET', '/smqjh-pms/app-api/v1/spu/getCategoryProductList'],
   'xsb.getProductDetail':['GET', '/smqjh-pms/app-api/v1/spu/getProductDetails'],

+ 7 - 3
src/api/core/handlers.ts

@@ -33,8 +33,9 @@ export async function handleAlovaResponse(
   const { statusCode, data } = response as UniNamespace.RequestSuccessCallbackResult
   // 处理401/403错误(如果不是在handleAlovaResponse中处理的)
   if ((statusCode === 401 || statusCode === 403)) {
-    const { redirectName } = storeToRefs(useUserStore())
+    const { redirectName, token } = storeToRefs(useUserStore())
     // 如果是未授权错误,清除用户信息并跳转到登录页
+    token.value = ''
     globalToast.error({ msg: '登录已过期,请重新登录!', duration: 2000 })
     redirectName.value = getCurrentPath()
     const timer = setTimeout(() => {
@@ -57,8 +58,10 @@ export async function handleAlovaResponse(
   if (import.meta.env.MODE === 'development') {
     console.log('[Alova Response]', json)
   }
-
   // Return data for successful responses
+  if (typeof json == 'string') {
+    return JSON.parse(json).data
+  }
   return json.data
 }
 
@@ -73,8 +76,9 @@ export function handleAlovaError(error: any, method: Method) {
   // 处理401/403错误(如果不是在handleAlovaResponse中处理的)
   if (error instanceof ApiError && (error.code === 401 || error.code === 403)) {
     // 如果是未授权错误,清除用户信息并跳转到登录页
-    const { redirectName } = storeToRefs(useUserStore())
+    const { redirectName, token } = storeToRefs(useUserStore())
     // 如果是未授权错误,清除用户信息并跳转到登录页
+    token.value = ''
     globalToast.error({ msg: '登录已过期,请重新登录!', duration: 2000 })
     redirectName.value = getCurrentPath()
     const timer = setTimeout(() => {

+ 3 - 1
src/api/createApis.ts

@@ -14,7 +14,7 @@ Some useful links:
  *
  * OpenAPI version: 3.0.4
  *
- * Contact: 
+ * Contact:
  *
  * NOTE: This file is auto generated by the alova's vscode plugin.
  *
@@ -49,6 +49,7 @@ const createFunctionalProxy = (array: (string | symbol)[], alovaInstance: Alova<
         ...configMap[apiPathKey],
         ...config
       };
+
       const [method, url] = apiItem;
       const pathParams = mergedConfig.pathParams;
       const urlReplaced = url!.replace(/\{([^}]+)\}/g, (_, key) => {
@@ -57,6 +58,7 @@ const createFunctionalProxy = (array: (string | symbol)[], alovaInstance: Alova<
       });
       delete mergedConfig.pathParams;
       let data = mergedConfig.data;
+
       if (Object.prototype.toString.call(data) === '[object Object]' && typeof FormData !== 'undefined') {
         let hasBlobData = false;
         const formData = new FormData();

+ 18 - 0
src/api/globals.d.ts

@@ -120,6 +120,24 @@ declare global {
       >(
         config: Config
       ): Alova2Method<userInfo, 'sys.userInfo', Config>;
+      updateUserInfo<
+        Config extends Alova2MethodConfig<any> & {
+          pathParams: { memberId: number };
+          data:Api.userInfo;
+        }
+        >(
+        config: Config
+      ): Alova2Method<any, 'sys.updateUserInfo', Config>;
+      uploadFile<
+        Config extends Alova2MethodConfig<{url:string}>&{
+          data: {
+            name: string;
+            filePath: string;
+          };
+        }
+        >(
+        config: Config
+      ): Alova2Method<{url:string}, 'sys.uploadFile', Config>;
     }
     xsb: {
       categories<

+ 2 - 0
src/auto-imports.d.ts

@@ -338,6 +338,7 @@ declare global {
   const useXsbCartStore: typeof import('./subPack-xsb/store-xsb/cart')['useXsbCartStore']
   const useXsbTabbar: typeof import('./composables/useXsbTabbar')['useXsbTabbar']
   const useXsbTabbarStore: typeof import('./subPack-xsb/store-xsb/tabbar')['useXsbTabbarStore']
+  const useconfirmOrderStore: typeof import('./store/confirmOrder')['useconfirmOrderStore']
   const watch: typeof import('vue')['watch']
   const watchArray: typeof import('@vueuse/core')['watchArray']
   const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
@@ -693,6 +694,7 @@ declare module 'vue' {
     readonly useWindowScroll: UnwrapRef<typeof import('@vueuse/core')['useWindowScroll']>
     readonly useWindowSize: UnwrapRef<typeof import('@vueuse/core')['useWindowSize']>
     readonly useXsbCartStore: UnwrapRef<typeof import('./subPack-xsb/store-xsb/cart')['useXsbCartStore']>
+    readonly useconfirmOrderStore: UnwrapRef<typeof import('./store/confirmOrder')['useconfirmOrderStore']>
     readonly watch: UnwrapRef<typeof import('vue')['watch']>
     readonly watchArray: UnwrapRef<typeof import('@vueuse/core')['watchArray']>
     readonly watchAtMost: UnwrapRef<typeof import('@vueuse/core')['watchAtMost']>

+ 2 - 1
src/components/Zupload.vue

@@ -4,6 +4,7 @@ import { BASE_URL, StaticUrl } from '@/config'
 
 const props = defineProps(uploadProps)
 const emit = defineEmits(['update:value'])
+const { token } = storeToRefs(useUserStore())
 const fileSubmitList = computed({
   get() {
     console.log(props.fileList, ' props.fileList')
@@ -25,7 +26,7 @@ const fileSubmitList = computed({
   <wd-upload
     v-bind="props" v-model:file-list="fileSubmitList" image-mode="aspectFill"
     :action="`${BASE_URL}/smqjh-system/api/v1/files`" :header="{
-      authorization: 'Bearer eyJraWQiOiJkYjRiYjA3MS1iYmJlLTQ4NDctODI0NS1mZGJiYTE3MGY4YWIiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImlzcyI6Imh0dHA6Ly8xOTIuMTY4LjEuMTAxOjkwMDAiLCJkZXB0SWQiOjIsImRhdGFTY29wZSI6MCwidXNlcklkIjoyLCJhdXRob3JpdGllcyI6WyJBRE1JTiJdLCJhdWQiOiJtYWxsLWFkbWluIiwibmJmIjoxNzY1OTQzNDk4LCJzY29wZSI6WyJvcGVuaWQiLCJwcm9maWxlIl0sImV4cCI6MTc2ODUzNTQ5OCwiaWF0IjoxNzY1OTQzNDk4LCJqdGkiOiIxYTliMzkwNy1jNDkwLTQ2MzEtYTUyZi05ZTg3NjUyZTk4ZjkiLCJ1c2VybmFtZSI6ImFkbWluIn0.Dc-uxgCS-9-wuE7n20WfHV8dMZbIGtrWZnPluDwlhE32GRWaMVFL2UO8s-NR8lljDV4kRv5obQuGaJwtj3IXBJ_lZBG0MKmdTaZ9o3F68TCrdqxOpSMlzN2k95_m-6xOM_abNd1QXUy0W_H7RtOcWSr8xdMC8JWKuHiIPDPx8hur7M7rEiP5WrSDFlKa9tXnuvxyzeqITzKr7VE-vk71JbMCzOz_EUxo4HSGrR6DQCXpbWlZq-yrOcp8WwMlkCfzCgSs0PJ7PlWuqWHJFrTSHj-EWAbMcwWwVVNE8n3rdDbAsigJSmtKN-2rScd1B8FqD2jv56CVArk6cH9Rtq0YSQ',
+      authorization: token,
     }"
   >
     <view class="h120rpx w120rpx flex items-center justify-center rounded-32rpx bg-white">

+ 5 - 5
src/config/index.ts

@@ -14,13 +14,13 @@ const mapEnvVersion = {
   // develop: 'http://192.168.1.166:8080', // 张
   // develop: 'http://192.168.1.101:8080',
   // develop: 'http://192.168.0.157:8080',
-  develop: 'http://192.168.1.242:8080',
+  develop: 'http://47.109.84.152:8081',
   /**
    * 体验版
    */
   // trial: "http://192.168.1.166:8080/jeecg-boot",
   // trial: "http://192.168.0.11:8080/jeecg-boot",
-  trial: 'http://192.168.1.242:8080',
+  trial: 'http://47.109.84.152:8081',
   /**
    * 正式版
    */
@@ -31,15 +31,15 @@ const mapEnvStaticVersion = {
   /**
    * 测试环境
    */
-  develop: 'http://192.168.1.242/static',
+  develop: 'https://zswl-smqjh.oss-cn-chengdu.aliyuncs.com/static/static',
   /**
    * 体验环境
    */
-  trial: 'http://192.168.1.242/static',
+  trial: 'https://zswl-smqjh.oss-cn-chengdu.aliyuncs.com/static/static',
   /**
    * 正式环境
    */
-  release: 'http://192.168.1.242/static',
+  release: 'https://zswl-smqjh.oss-cn-chengdu.aliyuncs.com/static/static',
 }
 /**
  * Base URL请求基本url

+ 20 - 0
src/pages.json

@@ -180,6 +180,15 @@
             "navigationBarTitleText": "售后列表"
           }
         },
+        {
+          "path": "confirmOrder/index",
+          "type": "page",
+          "name": "common-confirmOrder",
+          "islogin": true,
+          "style": {
+            "navigationBarTitleText": "提交订单"
+          }
+        },
         {
           "path": "editAddress/index",
           "type": "page",
@@ -218,6 +227,17 @@
             "navigationBarTitleText": "订单详情"
           }
         },
+        {
+          "path": "paySuccess/index",
+          "type": "page",
+          "name": "common-paySuccess",
+          "islogin": true,
+          "style": {
+            "navigationBarTitleText": "支付成功",
+            "navigationStyle": "custom",
+            "disableScroll": true
+          }
+        },
         {
           "path": "revalue/index",
           "type": "page",

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

@@ -149,7 +149,7 @@ onShareAppMessage(() => {
           </view>
         </view>
       </view>
-      <view class="mt20rpx" @click="router.push({ name: 'smqjh-login' })">
+      <view class="mt20rpx">
         <view class="flex items-center">
           <scroll-view scroll-y type="custom">
             <grid-view type="masonry" cross-axis-count="2" main-axis-gap="10" cross-axis-gap="10">

+ 4 - 3
src/pages/login/index.vue

@@ -15,6 +15,7 @@ const { data, send } = useRequest((code, phoneCode) => Apis.sys.auth({ params: {
   code,
   phoneCode,
 } }), { middleware: createGlobalLoadingMiddleware({ loadingText: '微信授权登录中' }), immediate: false })
+const tabList = ['/pages/my/index', '/pages/index/index', '/pages/cart/index', '/pages/classfiy/index']
 async function handleGetPhone(e: UniHelper.ButtonOnGetphonenumberDetail) {
   console.log(e, '手机号')
   uni.showLoading({ mask: true })
@@ -26,11 +27,11 @@ async function handleGetPhone(e: UniHelper.ButtonOnGetphonenumberDetail) {
       token.value = `Bearer ${data.value.access_token}`
       useGlobalToast().show({ msg: '登录成功' })
       setTimeout(() => {
-        if (redirectName.value) {
-          router.replace({ path: redirectName.value })
+        if (tabList.includes(redirectName.value)) {
+          router.pushTab({ path: redirectName.value })
         }
         else {
-          router.back()
+          router.replace({ path: redirectName.value })
         }
         useUserStore().getUserInfo()
       }, 2000)

+ 11 - 2
src/pages/my/index.vue

@@ -20,7 +20,7 @@ const tabList = ref([
 onMounted(() => {
   useUserStore().getUserInfo()
 })
-const { token, userInfo } = storeToRefs(useUserStore())
+const { token, userInfo, getUserAvatar } = storeToRefs(useUserStore())
 </script>
 
 <template>
@@ -42,7 +42,7 @@ const { token, userInfo } = storeToRefs(useUserStore())
         </template>
         <template v-else>
           <view class="flex items-center">
-            <image :src="`${StaticUrl}/9.png`" alt="" class="h100rpx w100rpx flex-shrink-0" />
+            <image :src="getUserAvatar" alt="" class="h100rpx w100rpx flex-shrink-0 rounded-full" />
             <view class="ml20rpx flex-1">
               <view class="text-32rpx font-semibold">
                 {{ userInfo.nickName }}
@@ -52,6 +52,15 @@ const { token, userInfo } = storeToRefs(useUserStore())
               </view>
             </view>
           </view>
+          <view class="flex flex-col items-center" @click="router.push({ name: 'common-user-center' })">
+            <image
+              :src="`${StaticUrl}/user-setting.png`"
+              class="h48rpx w48rpx"
+            />
+            <view class="mt12rpx text-24rpx text-[var(--them-color)]">
+              账户设置
+            </view>
+          </view>
         </template>
       </view>
     </view>

+ 3 - 0
src/router/index.ts

@@ -32,6 +32,9 @@ function whitePathName() {
 router.beforeEach((to, from, next) => {
   console.log('🚀 beforeEach 守卫触发:', { to, from }, '')
   const { token, redirectName } = storeToRefs(useUserStore())
+  if (to.name === 'smqjh-login') {
+    redirectName.value = String(from.path)
+  }
   if (!whitePathName().includes(to.name) && !token.value) {
     const { confirm: showConfirm } = useGlobalMessage()
 

+ 16 - 0
src/store/confirmOrder.ts

@@ -0,0 +1,16 @@
+import { defineStore } from 'pinia'
+
+interface confirmState {
+  /**
+   * 下单信息统一字段
+   */
+  orderList: Api.xsbCategoryProductList[]
+}
+export const useconfirmOrderStore = defineStore('confirmOrder', {
+  state: (): confirmState => ({
+    orderList: [],
+  }),
+  actions: {
+
+  },
+})

+ 1 - 1
src/store/persist.ts

@@ -25,5 +25,5 @@ function persist({ store }: PiniaPluginContext, excludedIds: string[]) {
 
 export function persistPlugin(context: PiniaPluginContext) {
   // 调用persist函数,并传入排除列表
-  persist(context, ['temp'])
+  persist(context, ['confirmOrder'])
 }

+ 19 - 1
src/store/user.ts

@@ -1,4 +1,5 @@
 import { defineStore } from 'pinia'
+import { StaticUrl } from '@/config'
 
 interface userStroe {
   token: string
@@ -16,8 +17,18 @@ export const useUserStore = defineStore('user', {
   state: (): userStroe => ({
     token: '',
     redirectName: '',
-    userInfo: {},
+    userInfo: {
+      id: 0,
+    },
   }),
+  getters: {
+    getUserAvatar(): string {
+      if (this.userInfo.avatarUrl) {
+        return this.userInfo.avatarUrl
+      }
+      return `${StaticUrl}/avator.png`
+    },
+  },
   actions: {
     async getUserInfo() {
       if (this.token) {
@@ -25,5 +36,12 @@ export const useUserStore = defineStore('user', {
         this.userInfo = res
       }
     },
+    async updataUserInfo(data: Api.userInfo) {
+      uni.showLoading({ mask: true })
+      await Apis.sys.updateUserInfo({ pathParams: { memberId: data.id }, data })
+      uni.hideLoading()
+      useGlobalToast().show({ msg: '修改成功' })
+      this.getUserInfo()
+    },
   },
 })

+ 133 - 0
src/subPack-common/confirmOrder/index.vue

@@ -0,0 +1,133 @@
+<script setup lang="ts">
+import { StaticUrl } from '@/config'
+import router from '@/router'
+
+definePage({
+  name: 'common-confirmOrder',
+  islogin: true,
+  style: {
+    navigationBarTitleText: '提交订单',
+  },
+})
+function handlePay() {
+  router.replace({ name: 'common-paySuccess' })
+}
+</script>
+
+<template>
+  <view class="page px20rpx py20rpx">
+    <view class="mb20rpx rounded-16rpx bg-white p24rpx">
+      <view class="flex items-center justify-between">
+        <view class="flex items-center">
+          <wd-icon name="location" size="18px" />
+          <view class="ml10rpx text-28rpx">
+            请添加收货地址
+          </view>
+        </view>
+        <wd-icon name="arrow-right" size="18px" color="#aaa" />
+      </view>
+    </view>
+
+    <view class="rounded-16rpx bg-white p24rpx">
+      <view class="flex items-center text-28rpx font-semibold">
+        市民请集合官方旗舰店
+      </view>
+      <view class="my24rpx h2rpx w-full bg-#F0F0F0" />
+      <CollapsePanel :line-height="150">
+        <view v-for="item in 5" :key="item" class="mb20rpx w-full flex items-center">
+          <view class="mr20rpx w120rpx flex-shrink-0">
+            <image
+              :src="`${StaticUrl}/shu.png`"
+              class="h120rpx w120rpx"
+            />
+          </view>
+          <view class="flex-1">
+            <view class="w-full flex items-center justify-between font-semibold">
+              <view class="text-28rpx">
+                赶海季生鲜大闸蟹
+              </view>
+              <view class="text-32rpx text-#FF4D3A">
+                ¥103.95
+              </view>
+            </view>
+            <view class="text-24rpx text-#AAAAAA">
+              规格:5kg,盒
+            </view>
+            <view class="text-24rpx text-#AAAAAA">
+              ×1
+            </view>
+          </view>
+        </view>
+      </CollapsePanel>
+    </view>
+    <view class="mt20rpx rounded-16rpx bg-white p24rpx">
+      <view class="mb28rpx flex items-center justify-between text-28rpx">
+        <view>商品金额</view>
+        <view class="text-#FF4D3A font-semibold">
+          ¥54.00
+        </view>
+      </view>
+      <view class="mb28rpx flex items-center justify-between text-28rpx">
+        <view>配送费(即时配送)</view>
+        <view class="text-#FF4D3A font-semibold">
+          ¥54.00
+        </view>
+      </view>
+      <view class="flex items-center justify-between text-28rpx">
+        <view>积分(1400)</view>
+        <view class="text-#FF4D3A font-semibold">
+          ¥54.00
+        </view>
+      </view>
+      <view class="my24rpx h2rpx w-full bg-#F0F0F0" />
+      <view class="flex items-center justify-between text-28rpx">
+        <view class="font-semibold">
+          总计:
+        </view>
+        <view class="text-#FF4D3A font-semibold">
+          ¥54.00
+        </view>
+      </view>
+    </view>
+    <view class="mt20rpx flex items-center rounded-16rpx bg-white p24rpx">
+      <view class="w80rpx">
+        备注
+      </view>
+      <view class="flex-1">
+        <wd-input placeholder="选填,请先和商家协商一致,付款后商家可见" no-border clearable />
+      </view>
+    </view>
+    <view class="ios footer fixed bottom-0 left-0 box-border w-full rounded-t-16rpx bg-white px24rpx">
+      <view class="box-border w-full flex items-center justify-between py20rpx">
+        <view class="flex items-center text-#FF4D3A">
+          <view class="font-semibold10 flex items-baseline text-36rpx">
+            <text class="text-24rpx">
+              ¥
+            </text> 230.95
+          </view>
+          <view class="ml20rpx text-22rpx">
+            共减¥15
+          </view>
+        </view>
+        <view class="w180rpx">
+          <wd-button size="large" @click="handlePay">
+            立即支付
+          </wd-button>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<style scoped lang="scss">
+  .footer{
+    box-shadow: 0rpx -6rpx 12rpx 2rpx rgba(0,0,0,0.05);
+  }
+  .page{
+    :deep(){
+      .wd-button{
+        width: 180rpx !important;
+      }
+    }
+  }
+</style>

+ 11 - 4
src/subPack-common/nickName/index.vue

@@ -6,9 +6,16 @@ definePage({
     navigationBarTitleText: '修改昵称',
   },
 })
-const nickName = ref('')
-function handleSubmit() {
-  console.log(nickName.value, '昵称')
+const nickName = ref<string | undefined>('')
+const { userInfo } = storeToRefs(useUserStore())
+const loading = ref(false)
+onLoad(() => {
+  nickName.value = userInfo.value.nickName
+})
+async function handleSubmit() {
+  loading.value = true
+  await useUserStore().updataUserInfo({ ...userInfo.value, nickName: nickName.value })
+  loading.value = false
 }
 </script>
 
@@ -20,7 +27,7 @@ function handleSubmit() {
     <view class="mb28rpx mt20rpx text-28rpx text-#AAAAAA">
       限2-10个汉字
     </view>
-    <wd-button block size="large" @click="handleSubmit">
+    <wd-button block size="large" :loading="loading" @click="handleSubmit">
       确定
     </wd-button>
   </view>

+ 70 - 0
src/subPack-common/paySuccess/index.vue

@@ -0,0 +1,70 @@
+<script setup lang="ts">
+import router from '@/router'
+import { StaticUrl } from '@/config'
+
+definePage({
+  name: 'common-paySuccess',
+  islogin: true,
+  style: {
+    navigationBarTitleText: '支付成功',
+    navigationStyle: 'custom',
+    disableScroll: true,
+  },
+})
+function handleBackIndex() {
+  const xsbIndex = 'subPack-xsb/commonTab/index'
+  const pages = getCurrentPages()
+  const targetPageIndex = pages.findIndex(page => page.route === xsbIndex)
+
+  if (targetPageIndex !== -1) {
+    const delta = pages.length - targetPageIndex - 1
+    router.back({ delta, animationType: 'fade-out' })
+  }
+  else {
+    // 如果未找到目标页面,则跳转到首页
+    router.back({ delta: pages.length, animationType: 'fade-out' })
+  }
+}
+</script>
+
+<template>
+  <view class="pages">
+    <wd-navbar
+      title="支付成功" custom-style="background-color:transparent !important" :bordered="false" :z-index="99"
+
+      safe-area-inset-top left-arrow fixed
+      @click-left="router.back()"
+    />
+    <view class="header-line h406rpx" />
+    <view class="-mt200rpx">
+      <view class="flex flex-col items-center">
+        <image
+          :src="`${StaticUrl}/revalue-success.png`"
+          class="h200rpx w200rpx"
+        />
+        <view class="mt20rpx text-32rpx font-semibold">
+          支付成功
+        </view>
+        <view class="mt60rpx flex items-center">
+          <wd-button type="info" block size="large">
+            <text class="text-32rpx font-semibold">
+              查看订单
+            </text>
+          </wd-button>
+          <view class="mx20rpx" />
+          <wd-button block size="large" @click="handleBackIndex">
+            <text class="text-32rpx font-semibold">
+              返回首页
+            </text>
+          </wd-button>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<style scoped>
+.header-line{
+  background: linear-gradient( 179deg, rgba(190,255,13,0.4) 0%, rgba(220,255,125,0.2) 49%, rgba(158,214,5,0) 100%);
+}
+</style>

+ 1 - 1
src/subPack-common/revalueSuccess/index.vue

@@ -12,7 +12,7 @@ definePage({
   },
 })
 function handleBackIndex() {
-  const xsbIndex = 'subPack-xsb/home/index'
+  const xsbIndex = 'subPack-xsb/commonTab/index'
   const pages = getCurrentPages()
   const targetPageIndex = pages.findIndex(page => page.route === xsbIndex)
 

+ 8 - 5
src/subPack-common/user-center/index.vue

@@ -1,5 +1,4 @@
 <script setup lang="ts">
-import { StaticUrl } from '@/config'
 import router from '@/router'
 
 definePage({
@@ -9,8 +8,12 @@ definePage({
     navigationBarTitleText: '账户设置',
   },
 })
-function handleChooseAvatar(e: UniHelper.ButtonOnChooseavatarEvent) {
-  console.log(e.avatarUrl)
+const { userInfo, getUserAvatar } = storeToRefs(useUserStore())
+async function handleChooseAvatar(e: UniHelper.ButtonOnChooseavatarEvent) {
+  const res = await Apis.sys.uploadFile({ data: { filePath: e.avatarUrl, name: 'file' }, fileType: 'image', requestType: 'upload', headers: {
+    'Content-Type': 'multipart/form-data',
+  } })
+  useUserStore().updataUserInfo({ ...userInfo.value, avatarUrl: res.url })
 }
 </script>
 
@@ -20,12 +23,12 @@ function handleChooseAvatar(e: UniHelper.ButtonOnChooseavatarEvent) {
       <view class="flex items-center justify-between">
         <view>头像</view>
         <view class="h60rpx w60rpx">
-          <wd-button :icon="`${StaticUrl}/avator.png`" type="icon" open-type="chooseAvatar" @chooseavatar="handleChooseAvatar" />
+          <wd-button :icon="getUserAvatar" type="icon" open-type="chooseAvatar" @chooseavatar="handleChooseAvatar" />
         </view>
       </view>
       <view class="mt26rpx flex items-center justify-between" @click="router.push({ name: 'common-nickName' })">
         <view>昵称</view>
-        <view>赵四</view>
+        <view>{{ userInfo.nickName }}</view>
       </view>
     </view>
   </view>

+ 35 - 14
src/subPack-xsb/commonTab/components/index.vue

@@ -2,19 +2,20 @@
 import { StaticUrl, VITE_OSS_BASE_URL } from '@/config'
 import router from '@/router'
 
-const props = defineProps<{ categoryList: Api.xsbCategories[], swiper: Api.xsbAdvertInfo[], hotText: Api.xsbSearchTerm[], recommendText: Api.xsbSearchTerm[], loading: boolean }>()
+const props = defineProps<{ error: any, lastPage: boolean, categoryList: Api.xsbCategories[], swiper: Api.xsbAdvertInfo[], hotText: Api.xsbSearchTerm[], recommendText: Api.xsbSearchTerm[], loading: boolean, goodsList: Api.xsbCategoryProductList[] }>()
 
-const emit = defineEmits(['changeNav'])
+const emit = defineEmits(['changeNav', 'scrollBottom'])
 
 const { ScrollDown } = storeToRefs(useSysXsbStore())
 
 const { statusBarHeight, MenuButtonHeight } = storeToRefs(useSysStore())
 const { name } = storeToRefs(useAddressStore())
-const { topNavActive, leftActive } = storeToRefs(useSysXsbStore())
+const { topNavActive, leftActive, backTop } = storeToRefs(useSysXsbStore())
 const swiperList = computed(() => props.swiper)
 const navActive = ref(0)
 const classfiylist = computed(() => props.categoryList.slice(0, 10))
 const swiperClassList = computed(() => props.categoryList.slice(10, props.categoryList.length))
+const scrollTop = ref()
 
 const navList = ref([
   { title: '为你推荐' },
@@ -55,6 +56,20 @@ function handleSwiperClick(e: { index: number, item: Api.xsbCategories }) {
 function handleGo(e: { index: number, item: Api.xsbAdvertInfo }) {
   console.log(e, '跳转消息')
 }
+
+const state = computed(() => {
+  return props.error ? 'error' : !props.lastPage ? 'loading' : 'finished'
+})
+
+watch(() => backTop.value, () => {
+  if (backTop.value) {
+    scrollTop.value = null
+    nextTick(() => {
+      scrollTop.value = 0
+    })
+    backTop.value = false
+  }
+})
 </script>
 
 <template>
@@ -76,7 +91,11 @@ function handleGo(e: { index: number, item: Api.xsbAdvertInfo }) {
         </view>
       </template>
     </wd-navbar>
-    <scroll-view scroll-y class="content" @scroll="handleScroll">
+    <scroll-view
+
+      :lower-threshold="80"
+      scroll-y enable-passive scroll-anchoring :scroll-top="scrollTop" class="content ios" @scroll="handleScroll" @scrolltolower="emit('scrollBottom')"
+    >
       <view
         class="header-linear h320rpx px24rpx"
         :style="{ paddingTop: `${(Number(statusBarHeight) || 44) + MenuButtonHeight + 12}px` }"
@@ -212,25 +231,26 @@ function handleGo(e: { index: number, item: Api.xsbAdvertInfo }) {
                   ]" :height="231"
                 />
                 <view
-                  v-for="it in 20" :key="it" class="overflow-hidden rounded-16rpx bg-white pb16rpx"
+                  v-for="it in goodsList" :key="it.id" class="overflow-hidden rounded-16rpx bg-white pb16rpx"
+                  @click="router.push({ name: 'xsb-goods', params: { id: it.id } })"
                 >
                   <view class="relative h344rpx">
                     <image
-                      :src="`${VITE_OSS_BASE_URL}2025/11/9d42892888304abf85487deea0271f62.png`"
+                      :src="it.pic"
                       class="h344rpx w344rpx"
                     />
                   </view>
                   <view class="mt20rpx px20rpx">
-                    <view class="text-left text-28rpx font-semibold">
-                      <view v-for="i in 2" :key="i" class="mr5px inline-block">
+                    <view class="line-clamp-2 text-left text-28rpx font-semibold">
+                      <!-- <view v-for="i in 2" :key="i" class="mr5px inline-block">
                         <wd-tag type="primary">
                           新品{{ i }}
                         </wd-tag>
-                      </view>
-                      海湾高盐特大白虾200g
+                      </view> -->
+                      {{ it.prodName }}
                     </view>
                     <view class="mt12rpx text-22rpx text-#AAAAAA">
-                      已售5999
+                      已售{{ it.soldNum }}
                     </view>
 
                     <view class="mt10rpx flex items-center justify-between">
@@ -240,7 +260,7 @@ function handleGo(e: { index: number, item: Api.xsbAdvertInfo }) {
                           </view>
                           <view class="text-36rpx line-height-[36rpx]">
-                            1.395
+                            {{ it.channelProdPrice }}
                           </view>
                         </view>
                       </view>
@@ -249,18 +269,19 @@ function handleGo(e: { index: number, item: Api.xsbAdvertInfo }) {
                   </view>
                 </view>
               </grid-view>
+              <wd-loadmore :state="state" :loading-props="{ color: '#9ED605', size: 20 }" @reload="emit('scrollBottom')" />
             </scroll-view>
           </view>
         </view>
       </view>
-      <view class="h100rpx" />
+      <view class="h60rpx" />
     </scroll-view>
   </view>
 </template>
 
 <style scoped lang="scss">
 .content {
-  height: calc(100vh - var(--window-top) - 80rpx);
+  height: calc(100vh - var(--window-top) - 100rpx);
 }
 .nav-line{
   background: linear-gradient( 90deg, #9ED605 0%, rgba(158,214,5,0.7) 43%, rgba(158,214,5,0.2) 79%, rgba(158,214,5,0) 100%);

+ 11 - 2
src/subPack-xsb/commonTab/components/my.vue

@@ -18,7 +18,7 @@ const tabList = ref([
   { title: '退款售后', icon: `${StaticUrl}/3.png`, name: 'common-afterSalesList' },
 ])
 
-const { token, userInfo } = storeToRefs(useUserStore())
+const { token, userInfo, getUserAvatar } = storeToRefs(useUserStore())
 </script>
 
 <template>
@@ -40,7 +40,7 @@ const { token, userInfo } = storeToRefs(useUserStore())
         </template>
         <template v-else>
           <view class="flex items-center">
-            <image :src="`${StaticUrl}/9.png`" alt="" class="h100rpx w100rpx flex-shrink-0" />
+            <image :src="getUserAvatar" alt="" class="h100rpx w100rpx flex-shrink-0 rounded-full" />
             <view class="ml20rpx flex-1">
               <view class="text-32rpx font-semibold">
                 {{ userInfo.nickName }}
@@ -50,6 +50,15 @@ const { token, userInfo } = storeToRefs(useUserStore())
               </view>
             </view>
           </view>
+          <view class="flex flex-col items-center" @click="router.push({ name: 'common-user-center' })">
+            <image
+              :src="`${StaticUrl}/user-setting.png`"
+              class="h48rpx w48rpx"
+            />
+            <view class="mt12rpx text-24rpx text-[var(--them-color)]">
+              账户设置
+            </view>
+          </view>
         </template>
       </view>
     </view>

+ 21 - 6
src/subPack-xsb/commonTab/index.vue

@@ -22,8 +22,9 @@ definePage({
     disableScroll: true,
   },
 })
-const { ScrollDown, topNavActive, leftActive } = storeToRefs(useSysXsbStore())
+const { ScrollDown, topNavActive, leftActive, backTop } = storeToRefs(useSysXsbStore())
 const commonCategoryData = ref<Api.xsbCategories[]>([])
+const { data: goodsList, isLastPage, page, error, refresh } = usePagination((pageNum, pageSize) => Apis.xsb.getSearchProductList({ data: { pageNum, pageSize, salesNum: 'DESC' } }), { data: resp => resp.list, initialData: [], initialPage: 1, initialPageSize: 10, append: true })
 const tabbarItems = ref([
   { name: 'xsb-home', value: null, active: true, title: '首页', icon1: '', icon2: '' },
   { name: 'xsb-classfiy', value: null, active: false, title: '分类', icon1: class1, icon2: class2 },
@@ -49,13 +50,13 @@ function setTabbarItemActive(name: string) {
   })
 }
 function handleClick() {
-  uni.pageScrollTo({
-    duration: 50,
-    scrollTop: 0,
-  })
+  backTop.value = true
 }
-onLoad(() => {
+onLoad((options: any) => {
   ScrollDown.value = false
+  if (options.name) {
+    handleTabbarChange({ value: options.name })
+  }
 })
 const loading = ref(true)
 async function getCategories() {
@@ -84,6 +85,16 @@ onShareAppMessage(() => {
     title: '市民请集合',
   }
 })
+function handleScrollBottom() {
+  console.log('触底', error.value)
+  if (error.value) {
+    refresh()
+    return
+  }
+  if (!isLastPage.value) {
+    page.value++
+  }
+}
 </script>
 
 <template>
@@ -94,7 +105,11 @@ onShareAppMessage(() => {
       :hot-text="hotText"
       :recommend-text="recommendText"
       :loading="loading"
+      :goods-list="goodsList"
+      :last-page="isLastPage"
+      :error="error"
       @change-nav="tabbarName = 'xsb-classfiy'"
+      @scroll-bottom="handleScrollBottom"
     />
     <cart v-if="tabbarName == 'xsb-cart'" />
     <classfiy v-if="tabbarName == 'xsb-classfiy'" :category-list="commonCategoryData" :hot-text="hotText" />

+ 3 - 3
src/subPack-xsb/goods/index.vue

@@ -265,7 +265,7 @@ async function getGoodsDetaile() {
     <view class="h180rpx" />
     <view class="ios shadow-fixed fixed bottom-0 left-0 w-full rounded-t-32rpx bg-white">
       <view class="flex items-center justify-between px24rpx py20rpx">
-        <view class="mr36rpx" @click="router.replace({ name: 'xsb-home' })">
+        <view class="mr36rpx" @click="router.replace({ name: 'xsb-homeTabbar' })">
           <image
             :src="`${StaticUrl}/goods-home.png`"
             class="h44rpx w44rpx"
@@ -283,7 +283,7 @@ async function getGoodsDetaile() {
             客服
           </view>
         </view>
-        <view>
+        <view @click="router.replace({ name: 'xsb-homeTabbar', animationType: 'fade-out', params: { name: 'xsb-cart' } })">
           <image
             :src="`${StaticUrl}/goods-cart.png`"
             class="h44rpx w44rpx"
@@ -298,7 +298,7 @@ async function getGoodsDetaile() {
               加入购物车
             </wd-button>
           </view>
-          <view class="ml20rpx w220rpx">
+          <view class="ml20rpx w220rpx" @click="router.push({ name: 'common-confirmOrder' })">
             <wd-button block class="w-full">
               立即购买
             </wd-button>

+ 5 - 1
src/subPack-xsb/search/index.vue

@@ -18,7 +18,7 @@ const tabbarlist = ref([
 const activeTab = ref(0)
 const { confirm: showConfirm } = useGlobalMessage()
 
-const { data, send, isLastPage, page } = usePagination((pageNum, pageSize) => Apis.xsb.getSearchProductList({ data: {
+const { data, send, isLastPage, page, error } = usePagination((pageNum, pageSize) => Apis.xsb.getSearchProductList({ data: {
   keywords: searchText.value,
   pageNum,
   pageSize,
@@ -82,6 +82,9 @@ async function getSearchData() {
   hotText.value = await Apis.xsb.SearchTerm({ data: { type: 2 } })
 }
 getSearchData()
+const state = computed(() => {
+  return error.value ? 'error' : !isLastPage.value ? 'loading' : 'finished'
+})
 function handleSearchText(text: string) {
   searchText.value = text
   handleSearch()
@@ -153,6 +156,7 @@ function handleSearchText(text: string) {
           </view>
         </view>
       </view>
+      <wd-loadmore v-if="data.length" :state="state" :loading-props="{ color: '#9ED605', size: 20 }" />
       <wd-status-tip v-if="!data.length" image="search" tip="当前搜索无结果" />
     </view>
     <view class="h30rpx" />

+ 5 - 0
src/subPack-xsb/store-xsb/sys.ts

@@ -11,6 +11,10 @@ interface SysState {
    */
   leftActive: string
   searchList: string[]
+  /**
+   * 回到顶部
+   */
+  backTop: boolean
 }
 export const useSysXsbStore = defineStore('system-xsb', {
   state: (): SysState => ({
@@ -18,6 +22,7 @@ export const useSysXsbStore = defineStore('system-xsb', {
     topNavActive: '',
     leftActive: '',
     searchList: [],
+    backTop: false,
   }),
   actions: {
     getTabbarItemValue(name: string) {

+ 2 - 0
src/uni-pages.d.ts

@@ -17,10 +17,12 @@ type _LocationUrl =
   "/subPack-common/afterSales/index" |
   "/subPack-common/afterSalesDetail/index" |
   "/subPack-common/afterSalesList/index" |
+  "/subPack-common/confirmOrder/index" |
   "/subPack-common/editAddress/index" |
   "/subPack-common/integral/index" |
   "/subPack-common/nickName/index" |
   "/subPack-common/orderDetaile/index" |
+  "/subPack-common/paySuccess/index" |
   "/subPack-common/revalue/index" |
   "/subPack-common/revalueSuccess/index" |
   "/subPack-common/selectAddress/index" |