Browse Source

fix: 功能优化

7746 2 weeks ago
parent
commit
44c1bb95a0

+ 1 - 1
index.html

@@ -4,7 +4,7 @@
     <meta charset="UTF-8" />
     <link rel="icon" href="/logo.png" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>my-shop-app</title>
+    <title>商城</title>
   </head>
   <body>
     <div id="app"></div>

+ 2 - 2
package-lock.json

@@ -1,11 +1,11 @@
 {
-  "name": "my-shop-app",
+  "name": "商城",
   "version": "0.0.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
-      "name": "my-shop-app",
+      "name": "商城",
       "version": "0.0.0",
       "dependencies": {
         "@ant-design/icons-vue": "^7.0.1",

+ 1 - 1
package.json

@@ -1,5 +1,5 @@
 {
-  "name": "my-shop-app",
+  "name": "商城",
   "private": true,
   "version": "0.0.0",
   "type": "module",

+ 7 - 0
src/api/goods.js

@@ -48,4 +48,11 @@ export const getFavoriteList = params => request({
   url: '/goods/favorite/query',
   method: 'get',
   params: params
+})
+
+// 购物车商品列表
+export const getCartList = params => request({
+  url: '/shpping/cart/list',
+  method: 'get',
+  params: params
 })

+ 74 - 67
src/pages/category/components/Right.vue

@@ -1,74 +1,79 @@
 <template>
   <div class="right-box">
-    <a-table
-      v-if="isList"
-      bordered
-      :dataSource="data"
-      :columns="mainData.columns"
-      :showHeader="false"
-      :pagination="false"
-    >
-      <template #emptyText>暂无商品信息</template>
-      <template #bodyCell="{ column, record }">
-        <template v-if="column.key === 'goodsName'">
-          <div class="goods-name" @click="viewDetail(record)">
-            <a-flex align="center">
-              <img :src="record.imgUrl" width="60" height="60" style="margin-right: 10px;" />
-              <span>{{ record.goodsName }}</span>
+    <div v-if="data.length > 0">
+      <a-table
+        v-if="isList"
+        bordered
+        :dataSource="data"
+        :columns="mainData.columns"
+        :showHeader="false"
+        :pagination="false"
+      >
+        <template #emptyText>暂无商品信息</template>
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'goodsName'">
+            <div class="goods-name" @click="viewDetail(record)">
+              <a-flex align="center">
+                <img :src="record.imgUrl" width="60" height="60" style="margin-right: 10px;" />
+                <span>{{ record.goodsName }}</span>
+              </a-flex>
+              <a-flex v-if="foramtTagData(record).length > 0" gutter="small" style="margin-top: 10px; margin-left: 70px;">
+                <a-tag
+                  v-for="(tag, tIndex) in foramtTagData(record)"
+                  :key="tIndex"
+                  color="red"
+                >{{ tag }}</a-tag>
+              </a-flex>
+            </div>
+          </template>
+          <template v-if="column.key === 'soldNum'">
+            <div v-if="isLogin">销量: {{ record.soldNum }}</div>
+            <div v-else>
+              <a-button type="link" @click="toLogin">登录</a-button>
+              <span>查看销量</span>
+            </div>
+          </template>
+          <template v-if="column.key === 'goodsPrice'">
+            <div v-if="isLogin">价格: ¥ {{ record.goodsPrice }}</div>
+            <div v-else>
+              <a-button type="link" @click="toLogin">登录</a-button>
+              <span>查看价格</span>
+            </div>
+          </template>
+          <template v-if="column.key === 'opetation'">
+            <a-flex align="center" justify="space-between">
+              <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>
-            <a-flex v-if="foramtTagData(record).length > 0" gutter="small" style="margin-top: 10px; margin-left: 70px;">
-              <a-tag
-                v-for="(tag, tIndex) in foramtTagData(record)"
-                :key="tIndex"
-                color="red"
-              >{{ tag }}</a-tag>
-            </a-flex>
-          </div>
-        </template>
-        <template v-if="column.key === 'soldNum'">
-          <div v-if="isLogin">销量: {{ record.soldNum }}</div>
-          <div v-else>
-            <a-button type="link" @click="toLogin">登录</a-button>
-            <span>查看销量</span>
-          </div>
-        </template>
-        <template v-if="column.key === 'goodsPrice'">
-          <div v-if="isLogin">价格: ¥ {{ record.goodsPrice }}</div>
-          <div v-else>
-            <a-button type="link" @click="toLogin">登录</a-button>
-            <span>查看价格</span>
-          </div>
-        </template>
-        <template v-if="column.key === 'opetation'">
-          <a-flex align="center" justify="space-between">
-            <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>
-      </template>
-    </a-table>
-    <div v-else class="grid-list__box">
-      <GridItem
-        v-for="item in data"
-        :key="item.goodsId"
-        :isLogin="isLogin"
-        :item="item"
-        :disabled="disabled"
-        @goods-detail="viewDetail"
-        @to-login="toLogin"
-        @add-to-cart="handleAddToCart"
-        @add-to-like="handleAddToLike"
-      />
+      </a-table>
+      <div v-else class="grid-list__box">
+        <GridItem
+          v-for="item in data"
+          :key="item.goodsId"
+          :isLogin="isLogin"
+          :item="item"
+          :disabled="disabled"
+          @goods-detail="viewDetail"
+          @to-login="toLogin"
+          @add-to-cart="handleAddToCart"
+          @add-to-like="handleAddToLike"
+        />
+      </div>
+      <div style="margin-top: 10px;">
+        <a-pagination
+          show-size-changer
+          :current="pagination.pageNum"
+          :pageSize="pagination.pageSize"
+          :total="pagination.total"
+          :page-size-options="['6', '12', '24', '26', '48']"
+          @change="changePagination"
+        />
+      </div>
     </div>
-    <div style="margin-top: 10px;">
-      <a-pagination
-        show-size-changer
-        :current="pagination.pageNum"
-        :pageSize="pagination.pageSize"
-        :total="pagination.total"
-        :page-size-options="['6', '12', '24', '26', '48']"
-        @change="changePagination"
-      />
+    <div v-else>
+      <a-empty />
     </div>
   </div>
 </template>
@@ -111,7 +116,9 @@ const emits = defineEmits([
 
 const userStore = useUserStore();
 
-const isLogin = computed(() => userStore.isLogin);
+const isLogin = computed(() => {
+  return userStore.userInfo != null
+});
 
 const mainData = reactive({
   columns: [

+ 25 - 16
src/pages/category/index.vue

@@ -40,6 +40,7 @@ import { useHomeStore } from '@/store/home';
 import { useGoodsStore } from '@/store/goods';
 import { useCategoryStore } from '@/store/category';
 import { useUserStore } from '@/store/user';
+import { getDetail } from '@/api/goods';
 
 const router = useRouter();
 const homeStore = useHomeStore();
@@ -89,24 +90,32 @@ const viewDetailFn = record => {
 }
 
 // 添加到购物车
-const addToCartFn = row => {
+const addToCartFn = (row) => {
   mainData.disabled = true
-  const buyGoods = [
-    {
-      goodsId: row.goodsId,
-      goodsSpecId: row.goodsSpecs ? row.goodsSpecs[0]?.goodsSpecId : '',
-      secKillId: row.secKillId || '',
-      promotionGroupId: row.promotionGroupId || '',
-      num: 1
-    }
-  ]
-  const params = {
+  getDetail({
+    goodsId: row.goodsId,
     userId: userStore.userInfo?.userId || '',
-    storageId: storageId.value,
-    buyGoods: buyGoods
-  }
-  goodsStore.addToCard(params).then(() =>{
-    userStore.fetchUserDetail()
+    storageId: homeStore.storageId
+  }).then(res => {
+    const buyGoods = [
+      {
+        goodsId: row.goodsId,
+        goodsSpecId: res.data.goodsSpecs ? res.data.goodsSpecs[0]?.goodsSpecId : '',
+        secKillId: res.data.secKillId || '',
+        promotionGroupId: res.data.promotionGroupId || '',
+        num: 1
+      }
+    ]
+    const params = {
+      userId: userStore.userInfo?.userId || '',
+      storageId: storageId.value,
+      buyGoods: buyGoods
+    }
+    goodsStore.addToCard(params).then(() =>{
+      userStore.fetchUserDetail()
+    }).finally(() => {
+      mainData.disabled = false
+    })
   }).finally(() => {
     mainData.disabled = false
   })

+ 7 - 3
src/pages/goods/index.vue

@@ -139,7 +139,9 @@ const mainForm = reactive({
   disabled: false
 })
 
-const isLogin = computed(() => userStore.isLogin);
+const isLogin = computed(() => {
+  return userStore.userInfo != null
+});
 const goodsDocumentsRelaList = computed(() => {
   if (!mainForm.detail.goodsDocumentsRelaList || mainForm.detail.goodsDocumentsRelaList.length === 0) return [];
   const validList = mainForm.detail.goodsDocumentsRelaList.filter(item => item.goodsDocumentsId.indexOf(mainForm.searchFileName) > -1 || item.fileName.indexOf(mainForm.searchFileName) > -1);
@@ -231,8 +233,10 @@ onMounted(() => {
 })
 
 watch(() => route.query.id, newVal => {
-  fetchDetail();
-  fetchFileData();
+  if (newVal && route.path === '/goods') {
+    fetchDetail();
+    fetchFileData();
+  }
 })
 
 </script>

+ 84 - 0
src/pages/home/components/CartModal.vue

@@ -0,0 +1,84 @@
+<template>
+  <a-drawer
+    :open="open"
+    title="购物车列表"
+    width="520px"
+    placement="right"
+    @close="emits('close')"
+  >
+    <div class="drawer-box">
+      <div v-if="cartData.shoppingCartLists && cartData.shoppingCartLists.length > 0">
+        <div v-for="(item, index) in cartData.shoppingCartLists" :key="index" class="list-item">
+          <div class="goods-image">
+            <img :src="item.goodsImg" />
+          </div>
+          <div class="goods-content">
+            <div class="goods-name">{{ item.goodsName }}</div>
+            <a-flex align="center" justify="space-between">
+              <div class="goods-price">¥ {{ item.price }}</div>
+              <div class="goods-price">X {{ item.num }}</div>
+            </a-flex>
+          </div>
+        </div>
+      </div>
+      <a-empty v-else />
+    </div>
+  </a-drawer>
+</template>
+
+<script setup lang="js">
+defineProps({
+  open: {
+    type: Boolean,
+    default: false
+  },
+  cartData: {
+    type: Object,
+    default: () => ({
+      shoppingCartLists: [],
+      totalAmount: 0,
+      totalNum: 0
+    })
+  }
+})
+
+const emits = defineEmits(['close']);
+
+</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-image {
+      flex-shrink: 0;
+      width: 60px;
+      height: 60px;
+      margin-right: 8px;
+      img {
+        display: block;
+        width: 100%;
+        height: 100%;
+      }
+    }
+    .goods-content {
+      flex: 1;
+      min-width: 1px;
+      word-break: break-all;
+      .goods-name {
+        font-size: 14px;
+        line-height: 1.5;
+        margin-bottom: 10px;
+        color: @theme-color;
+      }
+      .goods-price {
+        font-size: 13px;
+      }
+    }
+  }
+}
+</style>

+ 31 - 6
src/pages/home/components/HandleBar.vue

@@ -50,7 +50,7 @@
             <CheckSquareOutlined />
             <span class="text">列表</span>
           </div>
-          <div class="handle-content__item">
+          <div class="handle-content__item" @click="handleCardList">
             <ShoppingCartOutlined :style="{fontSize: '16px'}" />
             <span class="text">购物车</span>
             <span v-if="isLogin && shoppingCartNums > 0" style="margin-left: 3px;">({{ shoppingCartNums }})</span>
@@ -60,12 +60,16 @@
     </a-flex>
     <AccountModal :open="mainData.open" @close="mainData.open = false" />
     <LikeModal :open="mainData.likeOpen" :data="mainData.likeList" @delete="deleteLikeFn" @close="mainData.likeOpen = false" />
+    <CartModal :open="mainData.cartOpen" :cartData="mainData.cartData" @close="mainData.cartOpen = false"  />
+    <UserModal :open="mainData.userOpen" @close="mainData.userOpen = false" />
   </div>
 </template>
 
 <script setup lang="js">
 import AccountModal from './AccountModal.vue';
 import LikeModal from './LikeModal.vue';
+import CartModal from './CartModal.vue';
+import UserModal from './UserModal.vue';
 import {
   EnvironmentOutlined,
   UserOutlined,
@@ -75,9 +79,10 @@ import {
 } from '@ant-design/icons-vue';
 import { reactive, computed, watch } from 'vue';
 import { useRouter } from 'vue-router';
-import { debounce } from 'lodash-es';
+import { debounce } from 'lodash';
 import { useGoodsStore } from '@/store/goods';
 import { useUserStore } from '@/store/user';
+import { getCartList } from '@/api/goods'
 
 const props = defineProps({
   // 仓库列表
@@ -108,7 +113,10 @@ const mainData = reactive({
   showStore: false,
   open: false,
   likeOpen: false,
-  likeList: []
+  likeList: [],
+  cartOpen: false,
+  cartData: {},
+  userOpen: false
 })
 
 const shoppingCartNums = computed(() => userStore.userDetail?.shoppingCartNums || 0)
@@ -143,14 +151,17 @@ const selectStore = row => {
 }
 
 const handleAccount = () => {
-  if (props.isLogin) return;
-  mainData.open = true;
+  if (props.isLogin) {
+    mainData.userOpen = true;
+  } else {
+    mainData.open = true;
+  }
 }
 
 const handleLikeList = () => {
   if (props.isLogin) {
     goodsStore.getFavoriteList({
-      userId: userStore.userInfo?.userId,
+      userId: userStore.userInfo?.userId || '',
       pageNum: 1,
       pageSize: 20
     }).then(res => {
@@ -170,6 +181,20 @@ const deleteLikeFn = row => {
   })
 }
 
+const handleCardList = () => {
+  if (props.isLogin) {
+    getCartList({
+      userId: userStore.userInfo?.userId || '',
+      storageId: props.storageId
+    }).then(res => {
+      mainData.cartOpen = true
+      mainData.cartData = res.data || {}
+    })
+  } else {
+    mainData.open = true;
+  }
+}
+
 watch(() => mainData.value, (newVal) => {
   if (!newVal) {
     mainData.goods = [];

+ 14 - 11
src/pages/home/components/LikeModal.vue

@@ -7,19 +7,22 @@
     @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 v-if="data.length > 0">
+        <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-empty v-else />
     </div>
   </a-drawer>
 </template>

+ 61 - 0
src/pages/home/components/UserModal.vue

@@ -0,0 +1,61 @@
+<template>
+  <a-drawer
+    :open="open"
+    title="用户信息"
+    width="520px"
+    placement="right"
+    @close="emits('close')"
+  >
+    <div class="drawer-box">
+      <div class="row-item__content">
+        <div class="row-item__label">昵称: </div>
+        <div class="row-item__value">{{ userStore.userDetail.nickName }}</div>
+      </div>
+      <div class="row-item__content">
+        <div class="row-item__label">手机号: </div>
+        <div class="row-item__value">{{ userStore.userDetail.mobile }}</div>
+      </div>
+      <div class="row-item__content">
+        <div class="row-item__label">邮箱: </div>
+        <div class="row-item__value">{{ userStore.userDetail.email }}</div>
+      </div>
+    </div>
+  </a-drawer>
+</template>
+
+<script setup lang="js">
+import { useUserStore } from '@/store/user'
+
+const userStore = useUserStore()
+defineProps({
+  open: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const emits = defineEmits(['close'])
+
+</script>
+
+<style lang="less" scoped>
+.drawer-box {
+  .row-item__content {
+    width: 100%;
+    display: flex;
+    align-items: center;
+    font-size: 16px;
+    margin-bottom: 15px;
+    .row-item__label {
+      width: 80px;
+      flex-shrink: 0;
+    }
+    .row-item__value {
+      color: #222;
+      flex: 1;
+      min-width: 1px;
+      word-break: break-all;
+    }
+  }
+}
+</style>

+ 18 - 6
src/pages/home/index.vue

@@ -45,7 +45,9 @@ const categoryStore = useCategoryStore();
 const tabIndex = computed(() => homeStore.tabIndex);
 const storageId = computed(() => homeStore.storageId);
 const categoryList = computed(() => categoryStore.list);
-const isLogin = computed(() => userStore.isLogin);
+const isLogin = computed(() => {
+  return userStore.userInfo != null
+});
 const showBackIcon = computed(() => route.path !== '/category');
 
 const mainData = reactive({
@@ -79,10 +81,14 @@ const changeTbaFn = newTabIndex => {
 }
 
 const fetchStoreListData = async () => {
-  const res = await getStoreList();
+  const res = await getStoreList({
+    lat: '23.13',
+    lng: '113.26'
+  });
   mainData.storeList = res.data || [];
   mainData.storeList = storeList;
   if (!storeUtil.getId() && mainData.storeList.length > 0) {
+    storeUtil.setId(mainData.storeList[0].storageId);
     homeStore.updateStorageId(mainData.storeList[0].storageId);
   }
 }
@@ -100,7 +106,13 @@ const changeStoreFn = row => {
       })
     }
   })
-  resetGoodsDataAndFetch();
+  if (route.path !== '/category') {
+    router.push({
+      path: '/category'
+    })
+  } else {
+    resetGoodsDataAndFetch();
+  }
 }
 
 const fetchNoticeData = async () => {
@@ -108,8 +120,9 @@ const fetchNoticeData = async () => {
   mainData.noticeContent = res.data || '';
 }
 
-onMounted(() => {
+onMounted(async () => {
   homeStore.updateStorageId(storeUtil.getId());
+  await fetchStoreListData();
   categoryStore.fetchListData().then(() => {
     const parentId = categoryList.value[tabIndex.value]?.categoryId;
     if (parentId) {
@@ -120,9 +133,8 @@ onMounted(() => {
     }
   });
   resetGoodsDataAndFetch();
-  fetchStoreListData();
-  fetchNoticeData();
   if (isLogin.value) {
+    fetchNoticeData();
     userStore.fetchUserDetail()
   }
 })

+ 2 - 7
src/store/user.js

@@ -1,5 +1,6 @@
 import { defineStore } from 'pinia';
 import { userLoginData } from '@/api/user';
+import { getToken } from '@/utils/token'
 
 export const useUserStore = defineStore('user', {
   state: () => ({
@@ -24,13 +25,7 @@ export const useUserStore = defineStore('user', {
   },
   getters: {
     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
-      }
+      return state.userInfo !== null 
     }
   }
 })

+ 1 - 1
src/utils/request.js

@@ -77,7 +77,7 @@ service.interceptors.response.use(response => {
     return Promise.resolve(res)
   },
   error => {
-    message.error(res.message || 'Error');
+    message.error(error.message || 'Error');
     return Promise.reject(error)
   }
 )

+ 52 - 36
vite.config.js

@@ -4,44 +4,60 @@ import path from 'path'
 import Components from 'unplugin-vue-components/vite'
 import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
 
-export default defineConfig({
-  plugins: [
-    vue(),
-    Components({
-      resolvers: [
-        AntDesignVueResolver({
-          importStyle: false, // css in js
-        })
-      ],
-    })
-  ],
-  resolve: {
-    alias: {
-      '@': path.resolve(__dirname, 'src')
-    }
-  },
-  css: {
-    preprocessorOptions: {
-      less: {
-        math: 'always',
-        globalVars: {},
-        modifyVars: {
-          'hack': 'true; @import "@/styles/variables.less";'
+export default defineConfig(({ mode }) => {
+  const isProduction = mode === 'production'
+  return {
+    base: isProduction ? './' : '/',
+    plugins: [
+      vue(),
+      Components({
+        resolvers: [
+          AntDesignVueResolver({
+            importStyle: false, // css in js
+          })
+        ],
+      })
+    ],
+    resolve: {
+      alias: {
+        '@': path.resolve(__dirname, 'src')
+      }
+    },
+    css: {
+      preprocessorOptions: {
+        less: {
+          math: 'always',
+          globalVars: {},
+          modifyVars: {
+            'hack': 'true; @import "@/styles/variables.less";'
+          }
+        }
+      }
+    },
+    server: {
+      port: 3000,
+      open: true,
+      proxy: {
+        // '/api': {
+        //   // target: 'https://jiasm.zfire.top/zfdapi/',
+        //   target: 'https://jiasm.zfire.top',
+        //   ws: true,
+        //   changeOrigin: true,
+        //   rewrite: (path) => path.replace(/^\/api/, '')
+        // }
+      }
+    },
+    build: {
+      outDir: 'dist',
+      assetsDir: 'assets',
+      rollupOptions: {
+        output: {
+          // 确保资源路径正确
+          chunkFileNames: 'assets/[name]-[hash].js',
+          entryFileNames: 'assets/[name]-[hash].js',
+          assetFileNames: 'assets/[name]-[hash].[ext]'
         }
       }
-    }
-  },
-  server: {
-    port: 3000,
-    open: true,
-    proxy: {
-      // '/api': {
-      //   // target: 'https://jiasm.zfire.top/zfdapi/',
-      //   target: 'https://jiasm.zfire.top',
-      //   ws: true,
-      //   changeOrigin: true,
-      //   rewrite: (path) => path.replace(/^\/api/, '')
-      // }
     }
   }
 })