Explorar o código

fix: 问题处理

7746 hai 2 semanas
pai
achega
f25792f2c1

+ 1 - 1
.env.development

@@ -1,5 +1,5 @@
 ENV = 'development'
 
-VITE_APP_BASE_API = 'https://jiasm.zfire.top'
+VITE_APP_BASE_API = 'https://jiasm.zfire.top/overseas-miniapp'
 
 VITE_APP_BASE_OSS = 'https://jiasm.zfire.top/overseas-api/img/get?key='

+ 1 - 1
.env.production

@@ -1,5 +1,5 @@
 ENV = 'development'
 
-VITE_APP_BASE_API = 'https://jiasm.zfire.top'
+VITE_APP_BASE_API = 'https://jiasm.zfire.top/overseas-miniapp'
 
 VITE_APP_BASE_OSS = 'https://jiasm.zfire.top/overseas-api/img/get?key='

+ 86 - 0
package-lock.json

@@ -602,6 +602,19 @@
         "node": ">=6.0.0"
       }
     },
+    "node_modules/@jridgewell/source-map": {
+      "version": "0.3.11",
+      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
+      "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "peer": true,
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.25"
+      }
+    },
     "node_modules/@jridgewell/sourcemap-codec": {
       "version": "1.5.5",
       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
@@ -951,6 +964,18 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@types/node": {
+      "version": "24.10.1",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
+      "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "peer": true,
+      "dependencies": {
+        "undici-types": "~7.16.0"
+      }
+    },
     "node_modules/@vitejs/plugin-vue": {
       "version": "6.0.2",
       "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.2.tgz",
@@ -1189,6 +1214,15 @@
         "url": "https://github.com/sponsors/antfu"
       }
     },
+    "node_modules/buffer-from": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "peer": true
+    },
     "node_modules/call-bind-apply-helpers": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
@@ -1230,6 +1264,15 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "peer": true
+    },
     "node_modules/compute-scroll-into-view": {
       "version": "1.0.20",
       "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz",
@@ -2202,6 +2245,19 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/source-map-support": {
+      "version": "0.5.21",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+      "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "peer": true,
+      "dependencies": {
+        "buffer-from": "^1.0.0",
+        "source-map": "^0.6.0"
+      }
+    },
     "node_modules/speakingurl": {
       "version": "14.0.1",
       "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz",
@@ -2256,6 +2312,27 @@
         "url": "https://github.com/sponsors/mesqueeb"
       }
     },
+    "node_modules/terser": {
+      "version": "5.44.1",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz",
+      "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
+      "dev": true,
+      "license": "BSD-2-Clause",
+      "optional": true,
+      "peer": true,
+      "dependencies": {
+        "@jridgewell/source-map": "^0.3.3",
+        "acorn": "^8.15.0",
+        "commander": "^2.20.0",
+        "source-map-support": "~0.5.20"
+      },
+      "bin": {
+        "terser": "bin/terser"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/throttle-debounce": {
       "version": "5.0.2",
       "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
@@ -2296,6 +2373,15 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/undici-types": {
+      "version": "7.16.0",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+      "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "peer": true
+    },
     "node_modules/unplugin": {
       "version": "2.3.10",
       "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.10.tgz",

+ 3 - 4
src/api/category.js

@@ -1,9 +1,8 @@
 import request from '@/utils/request';
 
 // 获取商品分类
-export const getList = () => request({
-  // url: '/overseas-miniapp/goods/category/list?type=1',
-  url: '/overseas-api/goods/category/list?type=1',
+export const getList = params => request({
+  url: '/goods/category/list',
   method: 'get',
-  params: {}
+  params: params
 })

+ 10 - 2
src/api/common.js

@@ -2,14 +2,22 @@ import request from '@/utils/request';
 
 // 顶部公告
 export const getNotice = () => request({
-  url: '/overseas-miniapp/shpping/cart/notice',
+  url: '/shpping/cart/notice',
   method: 'get',
   params: {}
 })
 
+// 商户列表
+export const getCompanyList = () => request({
+  url: '/common/company/list',
+  method: 'post',
+  params: {}
+})
+
+
 // 获取仓库列表
 export const getStoreList = (params = {}) => request({
-  url: '/overseas-miniapp/storage/locate/list',
+  url: '/storage/locate/list',
   method: 'get',
   params
 })

+ 27 - 6
src/api/goods.js

@@ -2,29 +2,50 @@ import request from '@/utils/request'
 
 // 获取商品列表
 export const getList = params => request({
-  url: '/overseas-miniapp/goods/list/sort/page',
+  url: '/goods/list/sort/page',
   method: 'get',
   params: params
 })
 
 // 获取商品详情
-export const getDetail = goodsId => request({
-  url: `/overseas-api/goods/detail?goodsId=${goodsId}`,
+export const getDetail = params => request({
+  url: '/goods/detail',
   method: 'get',
-  params: {}
+  params: params
 })
 
 // 获取商品文档列表
 export const getFileList = goodsId => request({
-  url: `/overseas-api/goods/documents?goodsId=${goodsId}`,
+  url: `/goods/documents?goodsId=${goodsId}`,
   method: 'post',
   data: {}
 })
 
 // 加入购物车
 export const addToCard = params => request({
-  url: '/overseas-miniapp/shpping/cart/add/one',
+  url: '/shpping/cart/add/one',
   method: 'post',
   data: params,
   json: true
 })
+
+// 添加收藏
+export const addFavorite = params => request({
+  url: '/goods/favorite/add',
+  method: 'post',
+  data: params
+})
+
+// 删除收藏
+export const delFavorite = params => request({
+  url: '/goods/favorite/del',
+  method: 'post',
+  data: params
+})
+
+//  获取收藏列表
+export const getFavoriteList = params => request({
+  url: '/goods/favorite/query',
+  method: 'get',
+  params: params
+})

+ 25 - 3
src/api/user.js

@@ -1,13 +1,35 @@
 import request from '@/utils/request'
 
-export const getCode = () => request({
-  url: '/overseas-api/admin/user/imageVerification',
+// 获取拖拽式验证码
+export const getVerifiImage = () => request({
+  url: '/common/not/auth/getVerifi',
   method: 'get',
   params: {}
 })
 
+export const getCode = (params) => request({
+  url: '/user/send/message/code',
+  method: 'get',
+  params: params
+})
+
 export const userLogin = params => request({
-  url: '/overseas-api/admin/user/login',
+  url: '/user/worker/mobile/login',
   method: 'post',
+  params: params,
+  json: true
+})
+
+// 用户注册
+export const userRegister = params => request({
+  url: '/user/register/user',
+  method: 'post',
+  params: params
+})
+
+//  获取用户信息
+export const userLoginData = (params) => request({
+  url: '/user/user/detail',
+  method: 'get',
   params: params
 })

+ 20 - 23
src/main.js

@@ -3,8 +3,8 @@ import './reset.css'
 import App from './App.vue'
 import router from './router'
 import store from './store'
-import Antd from 'ant-design-vue'
-import 'ant-design-vue/dist/reset.css'
+// import Antd from 'ant-design-vue'
+// import 'ant-design-vue/dist/reset.css'
 import { ConfigProvider } from 'ant-design-vue'
 import {
   GlobalTextProcessor,
@@ -12,40 +12,37 @@ import {
   setupAntdLocale 
 } from './utils/init-processor'
 
-const app = createApp(App)
-
 // 使用 Ant Design
-app.use(Antd)
+// app.use(Antd)
 
 // 初始化翻译
 translaBeforeRegistration(() => {
   const language = window.Vue_Translation_Of_Text_Type || 'zh'
   
   // 设置 Ant Design 国际化
-  // setupAntdLocale(app, language)
-  const locale = setupAntdLocale(app, language)
-  
-  // 使用 ConfigProvider 包装应用
-  const AppWithConfig = {
-    components: { App },
-    setup() {
-      provide('antdLocale', locale)
-      return () => h(ConfigProvider, { locale }, () => h(App))
-    }
-  }
+  // const locale = setupAntdLocale(app, language)
   
+  // // 使用 ConfigProvider 包装应用
+  // const AppWithConfig = {
+  //   components: { App },
+  //   setup() {
+  //     provide('antdLocale', locale)
+  //     return () => h(ConfigProvider, { locale }, () => h(App))
+  //   }
+  // }
+  const app = createApp(App)
   // 注册全局文本处理器
   app.use(GlobalTextProcessor)
-
-  app.use(router)
   app.use(store)
+  app.use(router)
+  
   
   app.mount('#app')
   
   // 初始处理一次
-  setTimeout(() => {
-    if (app.config.globalProperties.$processAntdComponents) {
-      app.config.globalProperties.$processAntdComponents()
-    }
-  }, 100)
+  // setTimeout(() => {
+  //   if (app.config.globalProperties.$processAntdComponents) {
+  //     app.config.globalProperties.$processAntdComponents()
+  //   }
+  // }, 100)
 })

+ 11 - 3
src/pages/category/components/GridItem.vue

@@ -12,8 +12,8 @@
       <div v-else class="link-box">价格: ¥ {{ item.goodsPrice }}</div>
       <div class="item-handle__box">
         <a-flex justify="space-between">
-          <a-button :disabled="!isLogin" type="primary" @click="handleAddToCart">添加到购物车</a-button>
-          <a-button :disabled="!isLogin" type="primary">添加到列表</a-button>
+          <a-button :disabled="!isLogin || disabled" type="primary" @click="handleAddToCart">添加到购物车</a-button>
+          <a-button :disabled="!isLogin || disabled" type="primary" @click="handleAddToLike">添加到列表</a-button>
         </a-flex>
       </div>
     </div>
@@ -29,15 +29,23 @@ const props = defineProps({
   item: {
     type: Object,
     default: () => ({})
+  },
+  disabled: {
+    type: Boolean,
+    default: false
   }
 })
 
-const emits = defineEmits(['add-to-cart', 'goods-detail', 'to-login']);
+const emits = defineEmits(['add-to-cart', 'add-to-like', 'goods-detail', 'to-login']);
 
 const handleAddToCart = () => {
   emits('add-to-cart', props.item)
 }
 
+const handleAddToLike = () => {
+  emits('add-to-like', props.item)
+}
+
 const viewDetail = () => {
   emits('goods-detail', props.item)
 }

+ 21 - 4
src/pages/category/components/Right.vue

@@ -41,8 +41,8 @@
         </template>
         <template v-if="column.key === 'opetation'">
           <a-flex align="center" justify="space-between">
-            <a-button :disabled="!isLogin" type="primary" @click="handleAddToCart(record)">添加到购物车</a-button>
-            <a-button :disabled="!isLogin" type="primary">添加到列表</a-button>
+            <a-button :disabled="!isLogin || disabled" type="primary" @click="handleAddToCart(record)">添加到购物车</a-button>
+            <a-button :disabled="!isLogin || disabled" type="primary" @click="handleAddToLike(record)">添加到列表</a-button>
           </a-flex>
         </template>
       </template>
@@ -53,8 +53,11 @@
         :key="item.goodsId"
         :isLogin="isLogin"
         :item="item"
-        @goods-detail="viewDetail(item)"
+        :disabled="disabled"
+        @goods-detail="viewDetail"
         @to-login="toLogin"
+        @add-to-cart="handleAddToCart"
+        @add-to-like="handleAddToLike"
       />
     </div>
     <div style="margin-top: 10px;">
@@ -91,10 +94,20 @@ const props = defineProps({
   isList: {
     type: Boolean,
     default: true
+  },
+  disabled: {
+    type: Boolean,
+    default: false
   }
 })
 
-const emits = defineEmits(['change-pagination', 'goods-detail', 'add-to-cart', 'to-login']);
+const emits = defineEmits([
+  'change-pagination',
+  'goods-detail',
+  'add-to-cart',
+  'add-to-like',
+  'to-login'
+]);
 
 const userStore = useUserStore();
 
@@ -140,6 +153,10 @@ const handleAddToCart = record => {
   emits('add-to-cart', record)
 }
 
+const handleAddToLike = record => {
+  emits('add-to-like', record)
+}
+
 const toLogin = () => {
   emits('to-login')
 }

+ 35 - 6
src/pages/category/index.vue

@@ -7,7 +7,7 @@
           :checked="mainData.isList"
           :data="leftData"
           :active-id="queryData.categoryId"
-          @change-switch="value => mainData.isList = value"
+          @change-switch="changeSwitchFn"
           @click-category="changeChildCategoryFn"
         />
       </div>
@@ -20,9 +20,11 @@
             total: queryData.total
           }"
           :is-list="mainData.isList"
+          :disabled="mainData.disabled"
           @change-pagination="changePaginationFn"
           @goods-detail="viewDetailFn"
           @add-to-cart="addToCartFn"
+          @add-to-like="addToLikeFn"
           @to-login="toLoginFn"
         />
       </div>
@@ -32,26 +34,29 @@
 <script setup lang="js">
 import Left from './components/Left.vue';
 import Right from './components/Right.vue';
-import { reactive, computed } from 'vue';
+import { reactive, computed, watch } from 'vue';
 import { useRouter } from 'vue-router';
 import { useHomeStore } from '@/store/home';
 import { useGoodsStore } from '@/store/goods';
 import { useCategoryStore } from '@/store/category';
+import { useUserStore } from '@/store/user';
 
 const router = useRouter();
 const homeStore = useHomeStore();
 const goodsStore = useGoodsStore();
 const categoryStore = useCategoryStore();
+const userStore = useUserStore();
 
 const mainData = reactive({
-  isList: true
+  isList: window.localStorage.getItem('LIST_VIEW') == '1',
+  disabled: false
 })
 
 const tabIndex = computed(() => homeStore.tabIndex);
 const storageId = computed(() => homeStore.storageId);
 const categoryList = computed(() => categoryStore.list);
 const activedName = computed(() => categoryList.value[tabIndex.value]?.name || '');
-const leftData = computed(() => categoryList.value[tabIndex.value]?.children || []);
+const leftData = computed(() => categoryStore.childList);
 const rightData = computed(() => goodsStore.list);
 const queryData = computed(() => goodsStore.params);
 
@@ -85,19 +90,38 @@ const viewDetailFn = record => {
 
 // 添加到购物车
 const addToCartFn = row => {
+  mainData.disabled = true
   const buyGoods = [
     {
       goodsId: row.goodsId,
       goodsSpecId: row.goodsSpecs ? row.goodsSpecs[0]?.goodsSpecId : '',
       secKillId: row.secKillId || '',
-      promotionGroupId: row.promotionGroupId || ''
+      promotionGroupId: row.promotionGroupId || '',
+      num: 1
     }
   ]
   const params = {
+    userId: userStore.userInfo?.userId || '',
     storageId: storageId.value,
     buyGoods: buyGoods
   }
-  goodsStore.addToCard(params)
+  goodsStore.addToCard(params).then(() =>{
+    userStore.fetchUserDetail()
+  }).finally(() => {
+    mainData.disabled = false
+  })
+}
+
+const addToLikeFn = row => {
+  mainData.disabled = true
+  const params = {
+    goodsId: row.goodsId,
+    userId: userStore.userInfo?.userId || '',
+    goodsSpecId: row.goodsSpecs ? row.goodsSpecs[0]?.goodsSpecId : '',
+  }
+  goodsStore.addFavorite(params).finally(() => {
+    mainData.disabled = false
+  })
 }
 
 const toLoginFn = () => {
@@ -106,6 +130,11 @@ const toLoginFn = () => {
   })
 }
 
+const changeSwitchFn = value => {
+  mainData.isList = value
+  window.localStorage.setItem('LIST_VIEW', value ? '1' : '2')
+}
+
 </script>
 
 <style lang="less" scoped>

+ 37 - 11
src/pages/goods/index.vue

@@ -52,10 +52,10 @@
                 </a-input-number>
               </a-form-item>
               <a-form-item label="" name="addToCard">
-                <a-button :disabled="!isLogin" type="primary" block @click="handleAddToCart">添加到购物车</a-button>
+                <a-button :disabled="!isLogin || mainForm.disabled" type="primary" block @click="handleAddToCart">添加到购物车</a-button>
               </a-form-item>
               <a-form-item label="" name="saveToList">
-                <a-button :disabled="!isLogin" type="primary" block>保存到列表</a-button>
+                <a-button :disabled="!isLogin || mainForm.disabled" type="primary" block @click="handleAddToLike">添加到列表</a-button>
               </a-form-item>
             </a-form>
           </div>
@@ -135,7 +135,8 @@ const mainForm = reactive({
   quantity: 1,
   tabIndex: 0,
   detail: {},
-  searchFileName: ''
+  searchFileName: '',
+  disabled: false
 })
 
 const isLogin = computed(() => userStore.isLogin);
@@ -166,32 +167,56 @@ const onFinish = () => {}
 const onFailed = () => {}
 
 const fetchDetail = async () => {
-  // const res = await getDetail(route.query.id);
-  // mainForm.detail = res.data || {};
-  mainForm.detail = goodsDetailData;
+  const res = await getDetail({
+    goodsId: route.query.id,
+    userId: userStore.userInfo?.userId || '',
+    storageId: homeStore.storageId,
+  });
+  mainForm.detail = res.data || {};
+  // mainForm.detail = goodsDetailData;
 }
 
 const fetchFileData = async () => {
-  // const res = await getFileList(route.query.id);
-  // mainForm.detail.goodsDocumentsRelaList = res.data || [];
-  mainForm.detail.goodsDocumentsRelaList = fileList;
+  const res = await getFileList(route.query.id);
+  mainForm.detail.goodsDocumentsRelaList = res.data || [];
+  // mainForm.detail.goodsDocumentsRelaList = fileList;
 }
 
 const handleAddToCart = () => {
   if (!mainForm.detail.goodsId) return;
+  mainForm.disabled = true;
   const buyGoods = [
     {
       goodsId: route.query.id,
       goodsSpecId: mainForm.detail.goodsSpecs ? mainForm.detail.goodsSpecs[0]?.goodsSpecId : '',
       secKillId: mainForm.detail.secKillId || '',
-      promotionGroupId: mainForm.detail.promotionGroupId || ''
+      promotionGroupId: mainForm.detail.promotionGroupId || '',
+      num: 1
     }
   ]
   const params = {
+    userId: userStore.userInfo?.userId || '',
     storageId: homeStore.storageId,
     buyGoods: buyGoods
   }
-  goodsStore.addToCard(params)
+  goodsStore.addToCard(params).then(() => {
+    userStore.fetchUserDetail()
+  }).finally(() => {
+    mainForm.disabled = false
+  })
+}
+
+const handleAddToLike = () => {
+  if (!mainForm.detail.goodsId) return;
+  mainForm.disabled = true;
+  const params = {
+    goodsId: route.query.id,
+    userId: userStore.userInfo?.userId || '',
+    goodsSpecId: mainForm.detail.goodsSpecs ? mainForm.detail.goodsSpecs[0]?.goodsSpecId : '',
+  }
+  goodsStore.addFavorite(params).finally(() => {
+    mainForm.disabled = false
+  })
 }
 
 const toLogin = () => {
@@ -252,6 +277,7 @@ watch(() => route.query.id, newVal => {
           .stock-info__box {
             padding: 0 40px;
             .stock-content {
+              display: inline-block;
               padding: 20px 24px;
               border: 1px solid @border-color;
               border-radius: 2px;

+ 41 - 11
src/pages/home/components/HandleBar.vue

@@ -8,6 +8,7 @@
           placeholder="点击输入商品名称搜索"
           :filter-option="false"
           :show-search="true"
+          :field-names="{label: 'goodsName', value: 'goodsId'}"
           :not-found-content="mainData.fetching ? undefined : null"
           :options="mainData.goods"
           @search="fetchGoodsData"
@@ -45,23 +46,26 @@
             <UserOutlined />
             <span class="text">账户</span>
           </div>
-          <div class="handle-content__item">
+          <div class="handle-content__item" @click="handleLikeList">
             <CheckSquareOutlined />
             <span class="text">列表</span>
           </div>
           <div class="handle-content__item">
             <ShoppingCartOutlined :style="{fontSize: '16px'}" />
             <span class="text">购物车</span>
+            <span v-if="isLogin && shoppingCartNums > 0" style="margin-left: 3px;">({{ shoppingCartNums }})</span>
           </div>
         </a-flex>
       </div>
     </a-flex>
     <AccountModal :open="mainData.open" @close="mainData.open = false" />
+    <LikeModal :open="mainData.likeOpen" :data="mainData.likeList" @delete="deleteLikeFn" @close="mainData.likeOpen = false" />
   </div>
 </template>
 
 <script setup lang="js">
 import AccountModal from './AccountModal.vue';
+import LikeModal from './LikeModal.vue';
 import {
   EnvironmentOutlined,
   UserOutlined,
@@ -69,10 +73,11 @@ import {
   ShoppingCartOutlined,
   CheckOutlined
 } from '@ant-design/icons-vue';
-import { reactive, watch } from 'vue';
+import { reactive, computed, watch } from 'vue';
 import { useRouter } from 'vue-router';
 import { debounce } from 'lodash-es';
 import { useGoodsStore } from '@/store/goods';
+import { useUserStore } from '@/store/user';
 
 const props = defineProps({
   // 仓库列表
@@ -95,30 +100,32 @@ const emits = defineEmits(['change-store']);
 
 const router = useRouter();
 const goodsStore = useGoodsStore();
+const userStore = useUserStore();
 
 const mainData = reactive({
   goods: [],
   fetching: false,
   showStore: false,
-  open: false
+  open: false,
+  likeOpen: false,
+  likeList: []
 })
 
+const shoppingCartNums = computed(() => userStore.userDetail?.shoppingCartNums || 0)
+
 const fetchGoodsData = debounce(async (value) => {
   if (value == '') return;
   mainData.fetching = true;
   const goodsData  = await goodsStore.fetchAllData({
     pageNum: 1,
-    pageSize: -1,
+    pageSize: 100,
     keyword: value,
     storageId: props.storageId
+  }).finally(() => {
+    mainData.fetching = false;
   })
-  mainData.fetching = false;
-  mainData.goods = goodsData.map(item => ({
-    ...item,
-    label: item.goodsName,
-    value: item.goodsId
-  }))
-})
+  mainData.goods = goodsData;
+}, 500)
 
 const selectGoodsFn = goodsId => {
   router.push({
@@ -140,6 +147,29 @@ const handleAccount = () => {
   mainData.open = true;
 }
 
+const handleLikeList = () => {
+  if (props.isLogin) {
+    goodsStore.getFavoriteList({
+      userId: userStore.userInfo?.userId,
+      pageNum: 1,
+      pageSize: 20
+    }).then(res => {
+      mainData.likeOpen = true;
+      mainData.likeList = res.data?.list || [];
+    })
+  } else {
+    mainData.open = true;
+  }
+}
+
+const deleteLikeFn = row => {
+  goodsStore.delFavorite({
+    goodFavoriteId: row.goodsFavoriteId
+  }).then(() => {
+    handleLikeList()
+  })
+}
+
 watch(() => mainData.value, (newVal) => {
   if (!newVal) {
     mainData.goods = [];

+ 67 - 0
src/pages/home/components/LikeModal.vue

@@ -0,0 +1,67 @@
+<template>
+  <a-drawer
+    :open="open"
+    title="收藏列表"
+    width="520px"
+    placement="right"
+    @close="emits('close')"
+  >
+    <div class="drawer-box">
+      <div v-for="(item, index) in data" :key="index" class="list-item">
+        <div class="goods-name">{{ item.goodsName }}</div>
+        <div class="handle-area">
+          <a-popconfirm
+            title="是否确定删除 ?"
+            ok-text="确定"
+            cancel-text="取消"
+            @confirm="() => handleDelete(item)"
+          >
+            <a-button type="primary" size="small" danger ghost>删除</a-button>
+          </a-popconfirm>
+        </div>
+      </div>
+    </div>
+  </a-drawer>
+</template>
+
+<script setup lang="js">
+defineProps({
+  open: {
+    type: Boolean,
+    default: false
+  },
+  data: {
+    type: Array,
+    default: () => []
+  }
+})
+
+const emits = defineEmits(['close', 'delete']);
+
+const handleDelete = row => {
+  emits('delete', row)
+}
+
+</script>
+
+<style lang="less" scoped>
+.drawer-box {
+  .list-item {
+    width: 100%;
+    display: flex;
+    align-items: center;
+    padding: 10px 0;
+    border-bottom: 1px solid @border-color;
+    .goods-name {
+      flex: 1;
+      min-width: 1px;
+      margin-right: 10px;
+      color: @theme-color;
+      word-break: break-all;
+    }
+    .handle-area {
+      flex-shrink: 0;
+    }
+  }
+}
+</style>

+ 10 - 1
src/pages/home/components/Notice.vue

@@ -1,9 +1,18 @@
 <template>
   <div class="notice-box">
-    <div class="notice-content">测试顶部通知栏</div>
+    <div class="notice-content">{{ content }}</div>
   </div>
 </template>
 
+<script setup lang="js">
+defineProps({
+  content: {
+    type: String,
+    default: ''
+  }
+})
+</script>
+
 <style lang="less" scoped>
 .notice-box {
   padding: 14px 20px;

+ 0 - 2
src/pages/home/components/Toolbar.vue

@@ -28,8 +28,6 @@
 const handleLanguage = language => {
   window.Vue_Translation_Of_Text_Type = language
   window.localStorage.setItem('Vue_Translation_Of_Text_Type', language)
-  // 重新加载页面
-  window.location.reload()
   setTimeout(() => {
     window.location.reload();
   }, 250)

+ 40 - 10
src/pages/home/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="home-page">
     <header>
-      <Notice />
+      <Notice v-if="mainData.noticeContent" :content="mainData.noticeContent" />
       <Toolbar />
       <HandleBar
         :isLogin="isLogin"
@@ -31,7 +31,8 @@ import { useUserStore } from '@/store/user';
 import { useHomeStore } from '@/store/home';
 import { useGoodsStore } from '@/store/goods';
 import { useCategoryStore } from '@/store/category';
-import { getStoreList, getNotice, transformLanguageText } from '@/api/common';
+import { getStoreList, getNotice } from '@/api/common';
+import * as storeUtil from '@/utils/storeUtil';
 import { storeList } from '@/utils/mock';
 
 const route = useRoute();
@@ -48,7 +49,8 @@ const isLogin = computed(() => userStore.isLogin);
 const showBackIcon = computed(() => route.path !== '/category');
 
 const mainData = reactive({
-  storeList: []
+  storeList: [],
+  noticeContent: ''
 })
 
 const resetGoodsDataAndFetch = () => {
@@ -60,6 +62,13 @@ const resetGoodsDataAndFetch = () => {
 const changeTbaFn = newTabIndex => {
   if (newTabIndex == tabIndex.value) return;
   homeStore.changeTab(newTabIndex);
+  const parentId = categoryList.value[newTabIndex]?.categoryId;
+  if (parentId) {
+    categoryStore.fetchChildListData({
+      type: '1',
+      parentId: parentId
+    })
+  }
   if (route.path !== '/category') {
     router.push({
       path: '/category'
@@ -70,31 +79,52 @@ const changeTbaFn = newTabIndex => {
 }
 
 const fetchStoreListData = async () => {
-  // const res = await getStoreList();
-  // mainData.storeList = res.data || [];
+  const res = await getStoreList();
+  mainData.storeList = res.data || [];
   mainData.storeList = storeList;
-  if (mainData.storeList.length > 0) {
+  if (!storeUtil.getId() && mainData.storeList.length > 0) {
     homeStore.updateStorageId(mainData.storeList[0].storageId);
   }
 }
 
 const changeStoreFn = row => {
+  storeUtil.setId(row.storageId);
   homeStore.updateStorageId(row.storageId);
   homeStore.resetTab();
-  categoryStore.fetchListData();
+  categoryStore.fetchListData().then(() => {
+    const parentId = categoryList.value[tabIndex.value]?.categoryId;
+    if (parentId) {
+      categoryStore.fetchChildListData({
+        type: '1',
+        parentId: parentId
+      })
+    }
+  })
   resetGoodsDataAndFetch();
 }
 
 const fetchNoticeData = async () => {
   const res = await getNotice();
-  console.log(res)
+  mainData.noticeContent = res.data || '';
 }
 
 onMounted(() => {
-  categoryStore.fetchListData();
+  homeStore.updateStorageId(storeUtil.getId());
+  categoryStore.fetchListData().then(() => {
+    const parentId = categoryList.value[tabIndex.value]?.categoryId;
+    if (parentId) {
+      categoryStore.fetchChildListData({
+        type: '1',
+        parentId: parentId
+      })
+    }
+  });
   resetGoodsDataAndFetch();
   fetchStoreListData();
-  // fetchNoticeData();
+  fetchNoticeData();
+  if (isLogin.value) {
+    userStore.fetchUserDetail()
+  }
 })
 
 </script>

+ 422 - 0
src/pages/login/components/verify.vue

@@ -0,0 +1,422 @@
+<template>
+
+  <div class="modal-overlay" @click.self="closeDialog">
+    <div class="modal-content">
+      <button class="modal-close" @click="closeDialog">×</button>
+
+      <div class="code-dialog">
+        <div class="title">请完成安全验证</div>
+        <div class="pt">
+          <div class="pt-verification-box">
+            <div class="pt-verification-images">
+              <div class="iconfont refresh" @click="refresh">&#xe64c;</div>
+              <img :src="'data:image/jpeg;base64,' + bgImg" class="bg-img" alt="验证背景图">
+              <img :src="'data:image/jpeg;base64,' + maskImg" class="drag-img" alt="拼图块"
+                :style="{ left: dragWidth + 'px', top: top + 'px' }">
+              <div class="mask"></div>
+            </div>
+            <div class="pt-dragbar">
+              <div :class="['pt-drag-area', { fail: isFail, success: isSuccess }]" :style="{ width: dragWidth + 'px' }"
+                v-if="dragWidth"></div>
+              <div class="pt-dragbar-area" @mousemove="dragMove" @mouseup="dragEnd" @touchmove="dragMove"
+                @touchend="dragEnd">
+                <div :class="['pt-dragbar-view', {
+                  active: dragWidth > 2,
+                  fail: isFail,
+                  success: isSuccess
+                }]" @mousedown="dragStart" @touchstart="dragStart"
+                  :style="{ left: dragWidth + 'px' }">
+                  <span class="iconfont">
+                    <span v-if="isSuccess">&#xe687;</span>
+                    <span v-else-if="isFail">&#xe65c;</span>
+                    <span v-else>&#xe62a;</span>
+                  </span>
+                </div>
+                <span v-if="dragWidth === 0" class="tips">{{ tips }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { toRefs, ref, watch } from 'vue'
+const SliderVerifySetup = {
+  name: 'SliderVerifySetup',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    },
+    bgImg: {
+      type: [String, Number],
+      default: ''
+    },
+    maskImg: {
+      type: [String, Number],
+      default: ''
+    },
+    top: {
+      type: [String, Number],
+      default: 0
+    },
+    direction: {
+      type: [String, Number],
+      default: 'horizontal'
+    },
+    isSuccess: {
+      type: Boolean,
+      default: false,
+    },
+    isFail: {
+      type: Boolean,
+      default: false,
+    }
+  },
+  emits: ['update:show', 'close', 'finish', 'refresh'],
+  setup(props, { emit }) {
+    // 使用 toRefs 解构 props
+    const { show, isSuccess: propIsSuccess, isFail: propIsFail } = toRefs(props);
+
+    // 响应式数据
+    const tips = ref('向右拖动滑块填充拼图');
+    const disabled = ref(false);
+    const dragWidth = ref(0);
+    const x = ref(0);
+    const isDragging = ref(false);
+    const startX = ref(0);
+    const isSuccess = ref(propIsSuccess.value);
+    const isFail = ref(propIsFail.value);
+
+    // 计算属性
+    const maxDragWidth = ref(300 - 40);
+
+    // 方法
+    const closeDialog = () => {
+      emit('update:show', false);
+      emit('close');
+    };
+
+    const dragStart = (e) => {
+      isDragging.value = true;
+      const clientX = e.type.includes('touch') ? e.touches[0].clientX : e.clientX;
+      startX.value = clientX;
+
+      document.addEventListener('mousemove', dragMove);
+      document.addEventListener('touchmove', dragMove);
+      document.addEventListener('mouseup', dragEnd);
+      document.addEventListener('touchend', dragEnd);
+    };
+
+    const dragMove = (e) => {
+      if (!isDragging.value) return;
+
+      e.preventDefault();
+      const clientX = e.type.includes('touch') ? e.touches[0].clientX : e.clientX;
+      const deltaX = clientX - startX.value;
+
+      dragWidth.value = Math.max(0, Math.min(deltaX, maxDragWidth.value));
+    };
+
+    const dragEnd = (e) => {
+      if (!isDragging.value) return;
+
+      isDragging.value = false;
+      x.value = dragWidth.value;
+      emit('finish', dragWidth.value);
+
+      document.removeEventListener('mousemove', dragMove);
+      document.removeEventListener('touchmove', dragMove);
+      document.removeEventListener('mouseup', dragEnd);
+      document.removeEventListener('touchend', dragEnd);
+    };
+
+    const refresh = () => {
+      dragWidth.value = 0;
+      isFail.value = false;
+      isSuccess.value = false;
+      x.value = 0;
+      disabled.value = false;
+      emit('refresh');
+    };
+
+    // 监听器
+    watch(() => propIsSuccess, (newVal) => {
+      isSuccess.value = newVal;
+    });
+
+    watch(() => propIsFail, (newVal) => {
+      isFail.value = newVal;
+    });
+
+    watch(() => show, (newVal) => {
+      if (!newVal) {
+        dragWidth.value = 0;
+        isFail.value = false;
+        isSuccess.value = false;
+        x.value = 0;
+        disabled.value = false;
+      }
+    });
+
+    return {
+      tips,
+      disabled,
+      dragWidth,
+      x,
+      isDragging,
+      isSuccess,
+      isFail,
+      closeDialog,
+      dragStart,
+      dragMove,
+      dragEnd,
+      refresh
+    };
+  }
+};
+
+export default SliderVerifySetup
+
+</script>
+
+<style lang="less" scoped>
+/* 滑块验证组件样式 */
+@font-face {
+  font-family: 'iconfont';
+  src: url('https://at.alicdn.com/t/font_2047533_o8axbabfs3.ttf') format('truetype');
+}
+
+.iconfont {
+  font-family: iconfont !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 1000;
+}
+
+.modal-content {
+  background: white;
+  border-radius: 10px;
+  position: relative;
+  animation: modalAppear 0.3s ease;
+  max-width: 90vw;
+}
+
+@keyframes modalAppear {
+  from {
+    opacity: 0;
+    transform: scale(0.8);
+  }
+
+  to {
+    opacity: 1;
+    transform: scale(1);
+  }
+}
+
+.modal-close {
+  position: absolute;
+  top: 10px;
+  right: 10px;
+  background: none;
+  border: none;
+  font-size: 20px;
+  cursor: pointer;
+  color: #999;
+  z-index: 10;
+  width: 30px;
+  height: 30px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.modal-close:hover {
+  background: #f5f5f5;
+  color: #333;
+}
+
+.code-dialog {
+  padding: 20px;
+  border-radius: 10px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.title {
+  font-size: 18px;
+  color: #333333;
+  width: 100%;
+  text-align: left;
+  margin-bottom: 10px;
+  font-weight: 600;
+}
+
+.pt {
+  width: 300px;
+  max-width: 100%;
+  margin: 0 auto;
+}
+
+.pt-verification-images {
+  position: relative;
+  border-radius: 8px;
+  overflow: hidden;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.refresh {
+  position: absolute;
+  right: 10px;
+  top: 10px;
+  z-index: 10;
+  color: #FFF;
+  font-weight: bold;
+  cursor: pointer;
+  background: rgba(0, 0, 0, 0.5);
+  padding: 8px;
+  border-radius: 4px;
+  width: 30px;
+  height: 30px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.bg-img {
+  width: 100%;
+  height: auto;
+  display: block;
+}
+
+.drag-img {
+  position: absolute;
+  width: 56px;
+  height: 45px;
+  top: 0;
+  left: 0;
+  z-index: 1;
+  border: 2px dashed #1890ff;
+  border-radius: 4px;
+  pointer-events: none;
+}
+
+.mask {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(0, 0, 0, 0.2);
+  pointer-events: none;
+}
+
+.pt-dragbar {
+  position: relative;
+  height: 40px;
+  background-color: #F7F7F7;
+  border: solid 1px #EEE;
+  margin-top: 10px;
+  border-radius: 20px;
+  overflow: hidden;
+}
+
+.pt-drag-area {
+  position: absolute;
+  height: 40px;
+  border: solid 1px #1890ff;
+  background-color: #D1E9F1;
+  top: -1px;
+  border-radius: 20px;
+  transition: all 0.3s ease;
+}
+
+.pt-drag-area.fail {
+  border-color: #ff4d4f;
+  background-color: #ffdbdb;
+}
+
+.pt-drag-area.success {
+  border-color: #52c41a;
+  background-color: #d7ffe1;
+}
+
+.pt-dragbar-area {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  left: 0;
+  top: 0;
+  border-radius: 20px;
+  cursor: pointer;
+}
+
+.tips {
+  font-size: 12px;
+  color: #999;
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%);
+  pointer-events: none;
+  user-select: none;
+}
+
+.pt-dragbar-view {
+  position: absolute;
+  width: 40px;
+  height: 40px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: solid 1px #EEE;
+  background-color: #FFF;
+  top: -1px;
+  left: 0;
+  border-radius: 50%;
+  cursor: grab;
+  transition: all 0.3s ease;
+  user-select: none;
+  z-index: 2;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.pt-dragbar-view.active {
+  background-color: #1890ff;
+  border-color: #1890ff;
+  color: #FFF;
+}
+
+.pt-dragbar-view.fail {
+  background-color: #ff4d4f;
+  border-color: #ff4d4f;
+}
+
+.pt-dragbar-view.success {
+  border-color: #52c41a;
+  background-color: #00a029;
+}
+
+.pt-dragbar-view:active {
+  cursor: grabbing;
+}
+</style>

+ 118 - 47
src/pages/login/index.vue

@@ -14,42 +14,36 @@
         @finish="onFinish"
       >
         <a-form-item
-          label="号"
-          name="account"
-          :rules="[{ required: true, message: '账号不能为空' }]"
+          label="手机号"
+          name="mobile"
+          :rules="[{ required: true, validator: checkAccount }]"
         >
           <a-input
-            v-model:value="mainForm.account"
-            placeholder="请输入账号"
-            allowClear
-          />
-        </a-form-item>
-        <a-form-item
-          label="密码"
-          name="password"
-          :rules="[{ required: true, message: '密码不能为空' }]"
-        >
-          <a-input-password
-            v-model:value="mainForm.password"
-            placeholder="请输入密码"
+            v-model:value="mainForm.mobile"
+            placeholder="请输入手机号"
             allowClear
           />
         </a-form-item>
         <a-form-item
           label="验证码"
-          name="codeValue"
+          name="vrifyCode"
           :rules="[
             { required: true, message: '验证码不能为空' }
           ]"
         >
           <a-input-group compact>
             <a-input
-              v-model:value="mainForm.codeValue"
+              v-model:value="mainForm.vrifyCode"
               placeholder="请输入验证码"
               allowClear
-              style="width: calc(100% - 128px)"
+              style="width: calc(100% - 102px)"
             />
-            <img :src="mainForm.pic" @click="reFetchCode" :style="{width: '128px', height: '32px', cursor: 'pointer'}" />
+            <a-button
+              type="default"
+              :disabled="mainForm.disabled"
+              style="width: 102px;"
+              @click="reFetchCode"
+            >{{ mainForm.countDown > 0 ? `${mainForm.countDown}S` : '获取验证码' }}</a-button>
           </a-input-group>
         </a-form-item>
         <a-form-item label=" " :colon="false">
@@ -58,78 +52,155 @@
             html-type="submit"
             size="large"
             block
-            :disabled="mainForm.disabled"
+            :disabled="mainForm.submitBtnDisabled"
           >登 录</a-button>
         </a-form-item>
       </a-form>
     </div>
+    <!-- <a-modal
+      v-model:open="mainForm.open"
+      title="验证"
+      centered
+      :footer="null"
+    >
+      <div style="margin: 0 auto;">
+      </div>
+    </a-modal> -->
+    <div v-if="mainForm.open">
+      <SliderVerify
+        ref="verification"
+        :isShow="mainForm.open"
+        :top="codeObj.yHeight"
+        :bgImg="codeObj.bigImage"
+        :maskImg="codeObj.smallImage"
+        :isSuccess="codeObj.isSuccess"
+        :isFail="codeObj.isFail"
+        @close="mainForm.open = false"
+        @refresh="refresh"
+        @finish="finish"
+      />
+    </div>
   </div>
 </template>
 
 <script setup lang="js">
 import Logo from '@/components/logo/index.vue';
+import SliderVerify from './components/verify.vue'
 import { ref, reactive, onMounted } from 'vue';
 import { useRouter } from 'vue-router';
 import { message } from 'ant-design-vue';
-import { getCode, userLogin } from '@/api/user';
+import { getCode, getVerifiImage, userLogin } from '@/api/user';
 import { useUserStore } from '@/store/user';
 import { setToken } from '@/utils/token';
+import * as storeUtil from '@/utils/storeUtil';
+import { validPhone } from '@/utils/validate';
 
 const router = useRouter();
 const userStore = useUserStore();
 const formRef = ref(null)
 const mainForm = reactive({
-  account: '',
-  password: '',
+  mobile: '',
   code: '',
-  pic: '',
-  codeValue: '',
-  disabled: false
+  vrifyCode: '',
+  vrifyImage: '',
+  disabled: false,
+  open: false,
+  submitBtnDisabled: false,
+  countDown: 0,
+  timer: null
+})
+
+const codeObj = ref({
+  bigImage: '',
+  smallImage: '',
+  key: '',
+  yHeight: '',
+  isSuccess: false,
+  isFail: false
 })
 
-const fetchCode = async () => {
+const toSendCode = () => {
+  mainForm.open = false
   mainForm.disabled = true
-  getCode().then(res => {
-    mainForm.code = res.data?.code || '';
-    mainForm.pic = 'data:image/jpeg;base64,' + res.data?.pic;
-  }).finally(() => {
+  fetchCode()
+}
+
+const fetchCode = async (val) => {
+  mainForm.disabled = true
+  getCode({
+    mobile: mainForm.mobile,
+    code: mainForm.code,
+    vrifyCode: parseInt(val)
+  }).then(() => {
+    mainForm.open = false
+    mainForm.countDown = 60
+    handleCountDown()
+  }).catch(() => {
     mainForm.disabled = false
   })
 }
 
+const fetchVerifiImage = async () => {
+  const res = await getVerifiImage()
+  mainForm.code = res.data?.key || ''
+  mainForm.vrifyImage = 'data:image/jpeg;base64,' + res.data?.bigImage || ''
+  codeObj.value = res.data
+  mainForm.open = true
+}
+
 const fetchLogin = async () => {
-  mainForm.disabled = true
+  mainForm.submitBtnDisabled = true
   const params = {
-    userName: mainForm.account,
-    password: mainForm.password,
-    code: mainForm.code,
-    codeValue: mainForm.codeValue,
-    clientType: '1'
+    mobile: mainForm.mobile,
+    messageCode: mainForm.vrifyCode,
+    locate: '0,0',
   }
   userLogin(params).then(res => {
     setToken(res.data.token);
-    message.success('登录成功');
+    storeUtil.setId(res.data?.storage?.storageId);
     userStore.updateUserInfo(res.data);
+    message.success('登录成功');
     router.back();
-  }).catch(() => {
-    fetchCode();
   }).finally(() => {
-    mainForm.disabled = false
+    mainForm.submitBtnDisabled = false
   })
 }
 
+const finish = val => {
+  fetchCode(val)
+}
+
+const refresh = () => {
+  console.log('refresh')
+  fetchVerifiImage()
+}
+
+const handleCountDown = () => {
+  mainForm.timer = setInterval(() => {
+    mainForm.countDown -= 1;
+    if (mainForm.countDown <= 0) {
+      mainForm.disabled = false
+      clearInterval(mainForm.timer)
+    }
+  }, 1000)
+}
+
 const onFinish = () => {
   fetchLogin();
 }
 
 const reFetchCode = () => {
-  if (mainForm.disabled) return;
-  fetchCode();
+  formRef.value?.validateFields(['mobile']).then(async() => {
+    await fetchVerifiImage();
+    mainForm.open = true
+  })
 }
 
-onMounted(() => {
-  fetchCode()
-})
+const checkAccount = (rule, value) => {
+  if (value === '') return Promise.reject('手机号不能为空')
+  if (!validPhone(value)) return Promise.reject('手机号格式有误')
+  return Promise.resolve()
+}
 
 </script>
 

+ 61 - 32
src/pages/register/index.vue

@@ -5,7 +5,6 @@
         <Logo />
       </div>
       <a-form
-        ref="formRef"
         name="form"
         :model="mainForm"
         :label-col="{ style: {width: '100px'}}"
@@ -14,24 +13,24 @@
         @finish="onFinish"
       >
         <a-form-item
-          label="名字"
+          label=""
           name="firstName"
-          :rules="[{ required: true, message: '名字不能为空' }]"
+          :rules="[{ required: true, message: '不能为空' }]"
         >
           <a-input
             v-model:value="mainForm.firstName"
-            placeholder="请输入名字"
+            placeholder="请输入"
             allowClear
           />
         </a-form-item>
         <a-form-item
-          label=""
+          label="名字"
           name="lastName"
-          :rules="[{ required: true, message: '不能为空' }]"
+          :rules="[{ required: true, message: '名字不能为空' }]"
         >
           <a-input
             v-model:value="mainForm.lastName"
-            placeholder="请输入"
+            placeholder="请输入名字"
             allowClear
           />
         </a-form-item>
@@ -50,14 +49,19 @@
           <div>您的凭证将发送到此邮箱。</div>
         </a-form-item>
         <a-form-item
-          label="公司名称"
-          name="companyName"
-          :rules="[{ required: true, message: '公司名称不能为空' }]"
+          label="商家"
+          name="companyWechatId"
+          :rules="[{ required: true, message: '商家不能为空' }]"
         >
-          <a-input
-            v-model:value="mainForm.companyName"
-            placeholder="请输入公司名称"
-            allowClear
+          <a-select
+            v-model:value="mainForm.companyWechatId"
+            style="width: 100%"
+            placeholder="请选择商家"
+            :fieldNames="{
+              label: 'companyName',
+              value: 'companyWechatId'
+            }"
+            :options="mainForm.companyList"
           />
         </a-form-item>
         <a-form-item
@@ -73,18 +77,14 @@
             allowClear
           />
         </a-form-item>
-        <a-form-item
-          label="备注"
-          name="message"
-        >
-          <a-input
-            v-model:value="mainForm.message"
-            placeholder="请输入备注"
-            allowClear
-          />
-        </a-form-item>
         <a-form-item label=" " :colon="false">
-          <a-button type="primary" html-type="submit" size="large" block>提 交</a-button>
+          <a-button
+            type="primary"
+            html-type="submit"
+            size="large"
+            block
+            :disabled="mainForm.disabled"
+          >提 交</a-button>
         </a-form-item>
       </a-form>
     </div>
@@ -92,23 +92,53 @@
 </template>
 
 <script setup lang="js">
-import { ref, reactive } from 'vue';
 import Logo from '@/components/logo/index.vue'
+import { message } from 'ant-design-vue'
+import { reactive, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import { getCompanyList } from '@/api/common'
+import { userRegister } from '@/api/user'
+import { validPhone } from '@/utils/validate'
 
-const formRef = ref(null)
+const router = useRouter()
 const mainForm = reactive({
   firstName: '',
   lastName: '',
   email: '',
-  companyName: '',
+  companyWechatId: undefined,
   mobile: '',
-  message: ''
+  companyList: [],
+  disabled: false
 })
 
+const fetchCompanyData = async () => {
+  const res = await getCompanyList()
+  mainForm.companyList = res.data || []
+}
+
 const onFinish = () => {
-  console.log('--- finish ---')
+  mainForm.disabled = true
+  const params = {
+    companyWechatId: mainForm.companyWechatId,
+    email: mainForm.email,
+    firstName: mainForm.firstName,
+    lastName: mainForm.lastName,
+    mobile: mainForm.mobile
+  }
+  userRegister(params).then(() => {
+    message.success('提交成功, 信息审核通过后将会通过邮件通知您')
+    setTimeout(() => {
+      router.back()
+    }, 500)
+  }).catch(() => {
+    mainForm.disabled = false
+  })
 }
 
+onMounted(() => {
+  fetchCompanyData()
+})
+
 const checkEmail = (rule, value) => {
   if (value === '') return Promise.reject('邮箱不能为空');
   const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
@@ -118,8 +148,7 @@ const checkEmail = (rule, value) => {
 
 const checkMobile = (rule, value) => {
   if (value === '') return Promise.reject('手机号码不能为空');
-  const internationalPhoneRegex = /^(\+?0?86\-?)?1[3-9]\d{9}$|^\+[1-9]\d{0,3}[1-9]\d{5,14}$/
-  if (!internationalPhoneRegex.test(value)) return Promise.reject('手机号码格式有误');
+  if (!validPhone(value)) return Promise.reject('手机号码格式有误');
   return Promise.resolve()
 }
 </script>

+ 12 - 4
src/store/category.js

@@ -4,13 +4,21 @@ import { categoryList } from '@/utils/mock';
 
 export const useCategoryStore = defineStore('category', {
   state: () => ({
-    list: []
+    list: [],
+    childList: []
   }),
   actions: {
     async fetchListData() {
-      // const res = await getList();
-      // this.list = res.data || [];
-      this.list = categoryList;
+      const res = await getList({
+        type: '1'
+      });
+      this.list = res.data || [];
+      // this.list = categoryList;
+    },
+    async fetchChildListData(params) {
+      this.childList = [];
+      const res = await getList(params)
+      this.childList = res.data || [];
     }
   }
 })

+ 27 - 15
src/store/goods.js

@@ -1,6 +1,6 @@
 import { defineStore } from 'pinia';
 import { message } from 'ant-design-vue';
-import { getList, addToCard } from '@/api/goods';
+import { getList, addToCard, addFavorite, delFavorite, getFavoriteList } from '@/api/goods';
 import { goodsList } from '@/utils/mock';
 
 export const useGoodsStore = defineStore('goods', {
@@ -18,16 +18,20 @@ export const useGoodsStore = defineStore('goods', {
       this.list = [];
     },
     async fetchListData() {
-      // const res = await getList(this.params);
-      // this.list = res.data?.record || [];
-      // this.params.total = res.data?.total || 0;
-      this.list = goodsList;
-      this.params.total = 120;
+      const res = await getList({
+        pageNum: this.params.pageNum,
+        pageSize: this.params.pageSize,
+        categoryId: this.params.categoryId
+      });
+      this.list = res.data?.records || [];
+      this.params.total = res.data?.total || 0;
+      // this.list = goodsList;
+      // this.params.total = 120;
     },
     async fetchAllData(params) {
-      // const res = await getList(params);
-      // return Promise.resolve(res.data || []);
-      return new Promise(resolve => resolve(goodsList))
+      const res = await getList(params);
+      return Promise.resolve(res.data?.records || []);
+      // return new Promise(resolve => resolve(goodsList))
     },
     resetParams() {
       this.params.pageNum = 1;
@@ -41,12 +45,20 @@ export const useGoodsStore = defineStore('goods', {
         }
       })
     },
-    addToCard(params, success, fail) {
-      addToCard(params).then(() => {
-        message.success('添加成功')
-        success && success()
-      }).catch(() => {
-        fail && fail()
+    async addToCard(params) {
+      await addToCard(params)
+      message.success('添加成功')
+    },
+    async addFavorite(params) {
+      await addFavorite(params)
+      message.success('添加成功')
+    },
+    async getFavoriteList(params) {
+      return getFavoriteList(params)
+    },
+    async delFavorite(params) {
+      return delFavorite(params).then(() => {
+        message.success('删除成功')
       })
     }
   }

+ 20 - 2
src/store/user.js

@@ -1,18 +1,36 @@
 import { defineStore } from 'pinia';
+import { userLoginData } from '@/api/user';
 
 export const useUserStore = defineStore('user', {
   state: () => ({
-    userInfo: null
+    userInfo: window.sessionStorage.getItem('USER_INFO') ? JSON.parse(window.sessionStorage.getItem('USER_INFO')) : null,
+    userDetail: {}
   }),
   actions: {
     updateUserInfo(userInfo) {
+      window.sessionStorage.setItem('USER_INFO', JSON.stringify(userInfo))
       this.userInfo = userInfo
     },
     clearUserInfo() {
       this.userInfo = null
+      window.sessionStorage.removeItem('USER_INFO')
+    },
+    async fetchUserDetail() {
+      const res = await userLoginData({
+        userId: this.userInfo?.userId || ''
+      })
+      this.userDetail = res.data || {}
     }
   },
   getters: {
-    isLogin: state => state.userInfo !== null 
+    isLogin: state => {
+      try {
+        const stored = window.sessionStorage.getItem('USER_INFO')
+        return stored !== null && stored !== 'null'
+      } catch (error) {
+        console.error('读取 sessionStorage 失败:', error)
+        return state.userInfo !== null
+      }
+    }
   }
 })

+ 8 - 11
src/utils/request.js

@@ -1,10 +1,9 @@
 import axios from 'axios';
-import { getToken, removeToken } from '@/utils/token';
 import { createVNode } from 'vue';
 import { message, Modal } from 'ant-design-vue';
 import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
-import { useUserStore } from '@/store/user';
-import { useRouter } from 'vue-router';
+import { getToken, removeToken } from '@/utils/token';
+import * as storeUtil from '@/utils/storeUtil';
 
 const service = axios.create({
   baseURL: import.meta.env.VITE_APP_BASE_API,
@@ -31,13 +30,11 @@ const showConfirm = (options = {}) => {
 
 service.interceptors.request.use(
   config => {
-    if (config.json) {
-      config.headers['Content-Type'] = 'application/json'
-    } else {
-      config.headers['Content-Type'] = 'application/x-www-form-urlencoded'
-    }
+    config.headers.source = 'PC'
+    config.headers['storage_id'] = storeUtil.getId()
+    config.headers['Content-Type'] = config.json ? 'application/json' : 'application/x-www-form-urlencoded'
     if (getToken()) {
-      config.headers['x-token'] = getToken();
+      config.headers['x-token'] = getToken()
     }
     return config
   },
@@ -62,8 +59,8 @@ service.interceptors.response.use(response => {
           onOk: () => {
             // 重置token, 重置存在cookie里面的userId并且跳转到登录页
             removeToken();
-            useUserStore().clearUserInfo();
-            window.location.href = '/login'
+            window.sessionStorage.removeItem('USER_INFO');
+            window.location.href = '/login';
           }        
         })
       } else if (res.code === 4004) {

+ 12 - 0
src/utils/storeUtil.js

@@ -0,0 +1,12 @@
+const LOCAL_USER_STORE_ID_KEY = 'LOCAL_USER_STORE_ID_KEY'
+export const setId = (id) => {
+  window.sessionStorage.setItem(LOCAL_USER_STORE_ID_KEY, id);
+}
+
+export const getId = () => {
+  return window.sessionStorage.getItem(LOCAL_USER_STORE_ID_KEY);
+}
+
+export const removeId = () => {
+  window.sessionStorage.removeItem(LOCAL_USER_STORE_ID_KEY);
+}

+ 5 - 3
src/utils/token.js

@@ -1,12 +1,14 @@
 // 处理token
+const USER_TOKEN_KEY = 'TOKEN'
+
 export const setToken = (token) => {
-  window.sessionStorage.setItem('TOKEN', token)
+  window.sessionStorage.setItem(USER_TOKEN_KEY, token)
 }
 
 export const getToken = () => {
-  return window.sessionStorage.getItem('TOKEN');
+  return window.sessionStorage.getItem(USER_TOKEN_KEY);
 }
 
 export const removeToken = () => {
-  window.sessionStorage.removeItem('TOKEN')
+  window.sessionStorage.removeItem(USER_TOKEN_KEY)
 }

+ 1 - 0
src/utils/validate.js

@@ -0,0 +1 @@
+export const validPhone = phone => /^1[3-9]\d{9}$/.test(phone)

+ 1 - 1
vite.config.js

@@ -32,7 +32,7 @@ export default defineConfig({
     }
   },
   server: {
-    port: 8088,
+    port: 3000,
     open: true,
     proxy: {
       // '/api': {