Ver código fonte

Merge branch 'master' of http://8.152.4.3:3000/yp_web/yoe-shop-web

hurx 1 semana atrás
pai
commit
0be717f87d

+ 9 - 0
src/api/breg/index.ts

@@ -17,6 +17,15 @@ export const selectBusinessByCustomerName = (name: any) => {
   });
 };
 
+//个人注册
+export const selfRegister = (params: any) => {
+  return request({
+    url: '/customer/pcCustomer/selfRegister',
+    method: 'post',
+    data: params
+  });
+};
+
 //企业注册
 export const registerCustomer = (params: any) => {
   return request({

+ 8 - 0
src/api/goods/index.ts

@@ -18,6 +18,14 @@ export const getProductDetailByNo = (id: any) => {
   });
 };
 
+//商品预览详情
+export const getProductPreview = (id: any) => {
+  return request({
+    url: '/product/indexProduct/getProductPreview/' + id,
+    method: 'get'
+  });
+};
+
 //将商品添加到购物车
 export const addProductShoppingCart = (params: any) => {
   return request({

+ 26 - 0
src/api/pc/system/index.ts

@@ -0,0 +1,26 @@
+import { UserVO } from '@/api/system/user/types';
+import { UserQuery } from '@/api/system/user/types';
+import { AxiosPromise } from 'axios';
+import { RoleQuery, RoleVO, RoleDeptTree } from '@/api/system/role/types';
+import { MenuQuery, MenuVO, MenuForm, MenuTreeOption, RoleMenuTree } from '@/api/system/menu/types';
+import request from '@/utils/request';
+
+/**
+ * 获取工作台菜单列表
+ */
+export function getWorkbenchMenuList(): AxiosPromise<MenuVO[]> {
+  return request({
+    url: '/system/pc/system/getWorkbenchMenuList',
+    method: 'get'
+  });
+}
+
+/**
+ * 获取工作台角色列表
+ * */
+export function getWorkbenchRoleList(): AxiosPromise<RoleVO[]> {
+  return request({
+    url: '/system/pc/system/getWorkbenchRoleList',
+    method: 'get'
+  });
+}

+ 34 - 4
src/layout/components/header.vue

@@ -23,21 +23,22 @@
           @mouseleave="handleMouseLeave"
         >
           <div class="userInfo-customer">
-            <div class="mr-[5px]">{{ userInfo.customerName || '' }}</div>
+            <div class="mr-[5px]" v-if="userInfo.user.userSonType == 4">{{ userInfo.user.nickName || '' }}</div>
+            <div class="mr-[5px]" v-else>{{ userInfo.customerName || '' }}</div>
             <el-icon>
               <ArrowDown />
             </el-icon>
           </div>
-          <!-- <div class="userInfo-box" @click="onPath('/indexEnterprise')">切换到企业</div> -->
           <div class="userInfo-box">姓名:{{ userInfo.user.nickName }}</div>
           <div class="userInfo-box" @click="onlogout">退出登录</div>
         </div>
         <div v-if="!userInfo.user" class="header-text end" @click="onPath('/login')" style="cursor: pointer">请登录</div>
         <div v-if="!userInfo.user" class="header-text hig" @click="onPath('/breg')">免费注册</div>
         <div v-if="route.path == '/indexEnterprise'" class="header-text" @click="onPath('/')">切换到个人</div>
-        <div v-else class="header-text" @click="onPath('/indexEnterprise')">切换到企业</div>
+        <!-- onPath('/indexEnterprise') -->
+        <div v-else class="header-text" @click="goEnterprise">切换到企业</div>
         <div class="header-text" @click="onPath('/order/orderManage')">我的订单</div>
-        <div class="header-text" @click="onPath('/enterprise/companyInfo')">会员中心</div>
+        <div v-if="userInfo.user && userInfo.user.userSonType != 4" class="header-text" @click="onPath('/enterprise/companyInfo')">会员中心</div>
         <div class="header-text" @click="onPath('/theme?id=1')">人才招聘</div>
         <div class="header-text">帮助中心</div>
         <div class="header-text">在线客服</div>
@@ -75,6 +76,25 @@ const handleMouseLeave = () => {
   userInfoOpen.value = false;
 };
 
+const goEnterprise = () => {
+  if (userInfo.value && userInfo.value.user) {
+    if (userInfo.value.user.userSonType == 4) {
+      ElMessageBox.confirm('您还不是企业用户,是否需要注册', '提示', {
+        confirmButtonText: '注册',
+        type: 'warning'
+      })
+        .then(() => {
+          onPath('/breg');
+        })
+        .catch(() => {});
+    } else {
+      onPath('/indexEnterprise');
+    }
+  } else {
+    onPath('/indexEnterprise');
+  }
+};
+
 /**
  * 获取浏览器定位并转换
  */
@@ -144,11 +164,13 @@ const onlogout = () => {
 
     .header-left {
       height: 40px;
+
       .positioning {
         font-weight: 400;
         font-size: 12px;
         color: #322b2b;
         cursor: pointer;
+
         img {
           height: 18px;
           width: 18px;
@@ -156,8 +178,10 @@ const onlogout = () => {
         }
       }
     }
+
     .header-right {
       display: flex;
+
       .userInfo-bos {
         min-width: 200px;
         padding: 0 14px;
@@ -167,32 +191,38 @@ const onlogout = () => {
         margin-right: 5px;
         overflow: hidden;
         cursor: pointer;
+
         &.open {
           background-color: #ffffff;
           box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.12);
           height: 110px;
           overflow: visible;
+
           .userInfo-customer {
             color: #e7000b;
           }
         }
+
         .userInfo-customer {
           height: 40px;
           display: flex;
           align-items: center;
           justify-content: flex-end;
         }
+
         .userInfo-box {
           color: #999999;
           height: 30px;
           line-height: 30px;
           text-align: right;
           cursor: pointer;
+
           &:hover {
             color: #e7000b;
           }
         }
       }
+
       .header-text {
         padding: 0 14px;
         position: relative;

+ 191 - 71
src/layout/components/workbench.vue

@@ -1,32 +1,38 @@
 <template>
   <div class="workbench-bos" :class="isOpen ? 'workbench-bos1' : 'workbench-bos2'">
+    <!-- 展开状态 -->
     <div class="workbench-box1" v-if="isOpen">
       <div class="workbench-expand1 flex-row-start" @click="onOpen">
         <img src="@/assets/images/layout/workbench.png" alt="" />
         <div>收起菜单</div>
       </div>
-      <div v-for="(item1, index1) in menuList" :key="index1" class="menu-list1" @click="toggleMenu(item1.path)">
-        <div class="menu-head1 flex-row-between">
-          <div class="menu-title1 flex-row-start">
-            <img :src="item1.icon" alt="" />
-            <div>{{ item1.title }}</div>
+      <!-- 修改点1: 增加 v-if="item1.show" 过滤无权限菜单 -->
+      <template v-for="(item1, index1) in menuList" :key="index1">
+        <div class="menu-list1" v-if="item1.show" @click="toggleMenu(item1.path)">
+          <div class="menu-head1 flex-row-between">
+            <div class="menu-title1 flex-row-start">
+              <img :src="item1.icon" alt="" />
+              <div>{{ item1.title }}</div>
+            </div>
+            <el-icon v-if="openedMenus.includes(item1.path)" size="14">
+              <ArrowDown />
+            </el-icon>
+            <el-icon v-else size="14">
+              <ArrowRight />
+            </el-icon>
           </div>
-          <el-icon v-if="openedMenus.includes(item1.path)" size="14"><ArrowDown /></el-icon>
-          <el-icon v-else size="14"><ArrowRight /></el-icon>
-        </div>
-        <div class="menu-item" v-show="openedMenus.includes(item1.path)">
-          <div
-            @click.stop="onPath(item2.path)"
-            v-for="(item2, index2) in item1.children"
-            :key="index2"
-            class="menu-box"
-            :class="{ 'menu-hig': activeMenu == item2.path }"
-          >
-            {{ item2.title }}
+          <div class="menu-item" v-show="openedMenus.includes(item1.path)">
+            <template v-for="(item2, index2) in item1.children" :key="index2">
+              <div @click.stop="onPath(item2.path)" class="menu-box" :class="{ 'menu-hig': activeMenu == item2.path }">
+                {{ item2.title }}
+              </div>
+            </template>
           </div>
         </div>
-      </div>
+      </template>
     </div>
+
+    <!-- 收起状态 -->
     <div v-else class="workbench-box2">
       <div class="workbench-expand2 flex-row-center" @click="onOpen">
         <div class="menu-icon flex-row-center">
@@ -34,30 +40,30 @@
         </div>
       </div>
 
-      <div v-for="(item1, index1) in menuList" :key="index1" class="workbench-expand2 flex-row-center">
-        <el-popover placement="right">
-          <template #reference>
-            <div class="menu-icon flex-row-center" :class="{ 'hig': openedMenus.includes(item1.path) }">
-              <img :src="item1.icon" alt="" />
-            </div>
-          </template>
-          <div class="popover-bos">
-            <div class="popover-title">{{ item1.title }}</div>
-            <div
-              class="popover-list"
-              :class="{ 'popover-hig': activeMenu == item2.path }"
-              v-for="(item2, index2) in item1.children"
-              :key="index2"
-              @click="onPath(item2.path)"
-            >
-              {{ item2.title }}
+      <!-- 修改点2: 增加 v-if="item1.show" 过滤无权限菜单 -->
+      <template v-for="(item1, index1) in menuList" :key="index1">
+        <div class="workbench-expand2 flex-row-center" v-if="item1.show">
+          <el-popover placement="right">
+            <template #reference>
+              <div class="menu-icon flex-row-center" :class="{ 'hig': openedMenus.includes(item1.path) }">
+                <img :src="item1.icon" alt="" />
+              </div>
+            </template>
+            <div class="popover-bos">
+              <div class="popover-title">{{ item1.title }}</div>
+              <template v-for="(item2, index2) in item1.children" :key="index2">
+                <div class="popover-list" :class="{ 'popover-hig': activeMenu == item2.path }" @click="onPath(item2.path)">
+                  {{ item2.title }}
+                </div>
+              </template>
             </div>
-          </div>
-        </el-popover>
-      </div>
+          </el-popover>
+        </div>
+      </template>
     </div>
   </div>
 </template>
+
 <script setup lang="ts">
 import workbench1 from '@/assets/images/layout/workbench1.png';
 import workbench2 from '@/assets/images/layout/workbench2.png';
@@ -66,19 +72,41 @@ import workbench4 from '@/assets/images/layout/workbench4.png';
 import workbench5 from '@/assets/images/layout/workbench5.png';
 import workbench6 from '@/assets/images/layout/workbench6.png';
 import workbench7 from '@/assets/images/layout/workbench7.png';
+import { getWorkbenchMenuList } from '@/api/pc/system/index';
 import { onPath } from '@/utils/siteConfig';
+import { useRouter } from 'vue-router';
+import { getInfo } from '@/api/login';
+import { el } from 'element-plus/es/locale/index.mjs';
+
+const router = useRouter();
 const isOpen = ref(false);
 const openedMenus = ref<string[]>([]);
 const route = useRoute();
 const menuIndex = ref<any>(0);
-const menuList = [
+
+interface MenuItemChild {
+  path: string;
+  title: string;
+}
+
+interface MenuItem {
+  path: string;
+  title: string;
+  icon: string;
+  children?: MenuItemChild[];
+  show?: boolean;
+}
+
+// 注意:这里定义的是完整的全量菜单结构,后续会根据权限动态修改 children 和 show
+const menuList = ref<MenuItem[]>([
   {
     path: '/enterprise',
     title: '企业账户',
     icon: workbench1,
+    show: false,
     children: [
       { path: '/enterprise/companyInfo', title: '企业信息' },
-      { path: '/enterprise/messageNotice', title: '消息通知' },
+      { path: '/enterprise/messageNotice', title: '消息通知' }, // 接口没返这个,应该被过滤
       { path: '/easybuv', title: '地址管理' },
       { path: '/enterprise/invoiceManage', title: '发票抬头管理' },
       { path: '/enterprise/purchasePlan', title: '专属采购方案' },
@@ -92,6 +120,7 @@ const menuList = [
     path: '/order',
     title: '交易管理',
     icon: workbench2,
+    show: false,
     children: [
       { path: '/order/orderManage', title: '订单管理' },
       { path: '/order/orderAudit', title: '审核订单' },
@@ -104,6 +133,7 @@ const menuList = [
     path: '/organization',
     title: '组织管理',
     icon: workbench3,
+    show: false,
     children: [
       { path: '/i', title: '个人信息' },
       { path: '/organization/deptManage', title: '部门管理' },
@@ -117,6 +147,7 @@ const menuList = [
     path: '/cost',
     title: '成本管理',
     icon: workbench4,
+    show: false,
     children: [
       { path: '/cost/itemExpense', title: '分项费用' },
       { path: '/cost/quotaControl', title: '额度控制' }
@@ -126,6 +157,7 @@ const menuList = [
     path: '/reconciliation',
     title: '对账管理',
     icon: workbench5,
+    show: false,
     children: [
       { path: '/reconciliation/billManage', title: '对账单管理' },
       { path: '/reconciliation/invoiceManage', title: '开票管理' }
@@ -135,6 +167,7 @@ const menuList = [
     path: '/valueAdded',
     title: '增值服务',
     icon: workbench6,
+    show: false,
     children: [
       { path: '/valueAdded/maintenance', title: '维保服务' },
       { path: '/valueAdded/complaint', title: '投诉与建议' }
@@ -144,6 +177,7 @@ const menuList = [
     path: '/analysis',
     title: '采购分析',
     icon: workbench7,
+    show: false,
     children: [
       { path: '/analysis/orderAnalysis', title: '订单交易分析' },
       { path: '/analysis/purchaseDetail', title: '商品采购明细' },
@@ -152,22 +186,101 @@ const menuList = [
       { path: '/analysis/deptPurchase', title: '部门采购金额' }
     ]
   }
-];
+]);
 
 const activeMenu = computed(() => route.path);
+const userInfo = ref<any>({});
+
 const onOpen = () => {
   isOpen.value = !isOpen.value;
 };
 
-onMounted(() => {
+const allowedPaths = ref<Set<string>>(new Set());
+
+const processMenuPermissions = (apiMenuList: any[]) => {
+  // 1. 收集接口返回的所有合法 path
+  const paths = new Set<string>();
+  const traverse = (items: any[]) => {
+    items.forEach((item) => {
+      // 统一格式:确保以 / 开头
+      const p = item.path.startsWith('/') ? item.path : `/${item.path}`;
+      paths.add(p);
+      if (item.children && item.children.length > 0) {
+        traverse(item.children);
+      }
+    });
+  };
+  traverse(apiMenuList);
+  allowedPaths.value = paths;
+
+  // 2. 更新本地 menuList:过滤子菜单并设置父级显示状态
+  menuList.value.forEach((menu) => {
+    if (!menu.children) {
+      menu.show = false;
+      return;
+    }
+
+    // 【核心修改】过滤出有权限的子菜单
+    const permittedChildren = menu.children.filter((child) => {
+      const childPath = child.path.startsWith('/') ? child.path : `/${child.path}`;
+      return paths.has(childPath);
+    });
+
+    // 将过滤后的子菜单重新赋值给 menu.children
+    // 这样模板里 v-for 循环时就只有有权限的子项了
+    menu.children = permittedChildren;
+
+    // 如果过滤后还有子菜单,则显示父级;否则隐藏父级
+    menu.show = permittedChildren.length > 0;
+  });
+
+  checkCurrentRoutePermission();
+
+  // 权限处理完后,重新计算展开状态
   initOpenedMenus();
+};
 
+const checkCurrentRoutePermission = () => {
+  const currentPath = route.path;
+  const publicPaths = ['/', '/login', '/404'];
+  if (!publicPaths.includes(currentPath) && !allowedPaths.value.has(currentPath)) {
+    router.push('/');
+  }
+};
+
+onMounted(() => {
   if (window.innerWidth > 1420) {
     isOpen.value = true;
   } else {
     isOpen.value = false;
   }
   window.addEventListener('resize', handleResize);
+
+  getInfo().then((res1) => {
+    if (res1.code == 200) {
+      userInfo.value = res1.data;
+      if (res1.data && res1.data.user?.userSonType == 4) {
+        menuList.value.forEach((item1: any) => {
+          if (item1.path == '/enterprise') {
+            item1.show = false;
+          } else {
+            item1.show = true;
+          }
+          item1.children.forEach((item2: any) => {
+            item2.show = true;
+          });
+        });
+        initOpenedMenus();
+      } else {
+        getWorkbenchMenuList().then((res: any) => {
+          const apiData = res.data || res;
+          if (Array.isArray(apiData)) {
+            processMenuPermissions(apiData);
+          }
+        });
+      }
+    }
+  });
 });
 
 const handleResize = () => {
@@ -178,13 +291,20 @@ const handleResize = () => {
   }
 };
 
-// 根据当前路由自动展开对应的父级菜单
 const initOpenedMenus = () => {
   const currentPath = route.path;
-  for (const menu of menuList) {
-    const hasActiveChild = menu.children?.some((child) => currentPath === child.path || currentPath.startsWith(child.path + '/'));
+  openedMenus.value = [];
+
+  for (const menu of menuList.value) {
+    if (!menu.show) continue;
+
+    // 此时 menu.children 已经是过滤后的干净数据
+    const hasActiveChild = menu.children?.some((child) => {
+      const childPath = child.path.startsWith('/') ? child.path : `/${child.path}`;
+      return currentPath === childPath || currentPath.startsWith(childPath + '/');
+    });
+
     if (hasActiveChild) {
-      // 只展开当前路由对应的菜单
       openedMenus.value = [menu.path];
       return;
     }
@@ -194,10 +314,8 @@ const initOpenedMenus = () => {
 const toggleMenu = (path: string) => {
   const index = openedMenus.value.indexOf(path);
   if (index > -1) {
-    // 如果已展开,则收起
     openedMenus.value.splice(index, 1);
   } else {
-    // 手风琴效果:先收起所有,再展开当前
     openedMenus.value = [path];
   }
 };
@@ -206,11 +324,15 @@ watch(
   () => route.path,
   () => {
     initOpenedMenus();
+    if (userInfo.value && userInfo.value.user.userSonType != 4) {
+      checkCurrentRoutePermission();
+    }
   }
 );
 </script>
 
 <style lang="scss" scoped>
+/* 样式保持不变 */
 .workbench-bos {
   // position: absolute;
   // top: 0;
@@ -221,6 +343,7 @@ watch(
     border-radius: 10px;
     padding: 10px 8px;
     width: 180px;
+
     .workbench-expand1 {
       height: 42px;
       width: 100%;
@@ -228,30 +351,37 @@ watch(
       color: #1d2129;
       padding-left: 12px;
       cursor: pointer;
+
       &:hover {
         color: #e60012;
       }
+
       img {
         height: 16px;
         width: 16px;
         margin-right: 8px;
       }
     }
+
     .menu-list1 {
       width: calc(100% - 24px);
       border-bottom: 1px #e5e6eb solid;
       margin: 0 12px;
+
       &:nth-last-child(1) {
         border-bottom: none;
       }
+
       .menu-head1 {
         height: 54px;
         width: 100%;
         color: #1d2129;
         cursor: pointer;
+
         &:hover {
           color: #e60012;
         }
+
         .menu-title1 {
           font-size: 14px;
 
@@ -262,8 +392,10 @@ watch(
           }
         }
       }
+
       .menu-item {
         padding-bottom: 5px;
+
         .menu-box {
           width: 100%;
           height: 36px;
@@ -273,10 +405,12 @@ watch(
           padding-left: 12px;
           line-height: 36px;
           cursor: pointer;
+
           &.menu-hig {
             background: #f7f8fa;
             color: #e60012;
           }
+
           &:hover {
             color: #e60012;
           }
@@ -291,17 +425,21 @@ watch(
     border-radius: 10px;
     padding: 10px 8px;
     width: 50px;
+
     .workbench-expand2 {
       width: 34px;
       height: 36px;
       cursor: pointer;
       position: relative;
+
       .menu-icon {
         width: 24px;
         height: 24px;
+
         &.hig {
           background-color: #ffe8e8;
         }
+
         img {
           height: 16px;
           width: 16px;
@@ -319,33 +457,12 @@ watch(
         border-radius: 10px;
       }
     }
-    // .workbench-expand2 {
-    //   width: 24px;
-    //   height: 24px;
-    //   cursor: pointer;
-    //   margin: 16px auto;
-    //   position: relative;
-    //   &:hover {
-    //     background-color: #ffe8e8;
-    //   }
-    //   &::after {
-    //     content: '';
-    //     width: 10px;
-    //     height: 1px;
-    //     background-color: #e5e6eb;
-    //     position: absolute;
-    //     left: 7px;
-    //     bottom: -10px;
-    //   }
-    //   img {
-    //     height: 16px;
-    //     width: 16px;
-    //   }
-    // }
   }
+
   &.workbench-bos1 {
     left: -200px;
   }
+
   &.workbench-bos2 {
     left: -70px;
   }
@@ -358,6 +475,7 @@ watch(
     margin-bottom: 7px;
     padding-left: 4px;
   }
+
   .popover-list {
     width: 120px;
     height: 32px;
@@ -366,9 +484,11 @@ watch(
     line-height: 32px;
     padding-left: 4px;
     cursor: pointer;
+
     &:hover {
       color: var(--el-color-primary);
     }
+
     &.popover-hig {
       background: #ffe8e8;
     }

+ 7 - 1
src/router/index.ts

@@ -132,7 +132,7 @@ export const constantRoutes: RouteRecordRaw[] = [
         path: '/reg',
         component: () => import('@/views/reg/index.vue'),
         name: 'Reg',
-        meta: { title: '个人注册', icon: 'dashboard', affix: true, nav: true }
+        meta: { title: '个人注册', header: 'hide', search: 'hide' }
       },
       {
         path: '/breg',
@@ -170,6 +170,12 @@ export const constantRoutes: RouteRecordRaw[] = [
         name: 'Item',
         meta: { title: '商品详情', nav: true, breadcrumbColor: '#F4F4F4' }
       },
+      {
+        path: '/itemPreview',
+        component: () => import('@/views/item/preview.vue'),
+        name: 'preview',
+        meta: { title: '商品预览详情', nav: true, breadcrumbColor: '#F4F4F4' }
+      },
       {
         path: '/cart',
         component: () => import('@/views/cart/index.vue'),

+ 1 - 1
src/utils/siteConfig.ts

@@ -40,7 +40,7 @@ export const SITE_ROUTES: Record<any, string[]> = {
   greg: ['/greg'], //供应商注册
   passport: ['/login'], //登录页
   search: ['/search', '/search/special', '/search/brand'], //搜索
-  item: ['/item'], //商品详情,
+  item: ['/item', '/itemPreview'], //商品详情,
   cart: ['/cart'], //商品详情
   trad: ['/trad'], //确认订单信息
   payc: ['/payc'], //支付订单

+ 16 - 7
src/views/enterprise/myCollection/index.vue

@@ -41,6 +41,12 @@
     <el-dialog v-model="categoryDialogVisible" title="添加分类" width="400px">
       <el-form ref="categoryFormRef" :model="categoryForm" :rules="categoryRules">
         <el-form-item prop="name"><el-input v-model="categoryForm.name" placeholder="请输入分类名称" /></el-form-item>
+        <el-form-item label="是否默认">
+          <el-radio-group v-model="categoryForm.isDefault">
+            <el-radio :value="0">是</el-radio>
+            <el-radio :value="1">否</el-radio>
+          </el-radio-group>
+        </el-form-item>
       </el-form>
       <template #footer>
         <el-button @click="categoryDialogVisible = false">取消</el-button>
@@ -55,7 +61,7 @@ import { ref, reactive, watch, onMounted } from 'vue';
 import { Plus } from '@element-plus/icons-vue';
 import { ElMessage, ElMessageBox, type CheckboxValueType } from 'element-plus';
 import { PageTitle, StatusTabs, ProductCard, TablePagination } from '@/components';
-import { favoritesList, favoritesProductList, cancelProductCollect, addProductShoppingCart } from '@/api/goods/index';
+import { favoritesList, favoritesProductList, cancelProductCollect, addProductShoppingCart, addProductCollect } from '@/api/goods/index';
 
 const activeCategory = ref('all');
 const selectAll = ref(false);
@@ -64,7 +70,7 @@ const categoryFormRef = ref();
 const loading = ref(false);
 
 const categoryTabs = ref<any[]>([{ key: 'all', label: '全部' }]);
-const categoryForm = reactive({ name: '' });
+const categoryForm = reactive({ name: '', isDefault: 0 });
 const categoryRules = { name: [{ required: true, message: '请输入分类名称', trigger: 'blur' }] };
 const queryParams = reactive({ pageNum: 1, pageSize: 15, favoritesId: '' as any });
 const total = ref(0);
@@ -134,16 +140,19 @@ const handleSelectAll = (val: CheckboxValueType) => {
 };
 const handleAddCategory = () => {
   categoryForm.name = '';
+  categoryForm.isDefault = 0;
   categoryDialogVisible.value = true;
 };
 const handleSaveCategory = async () => {
   const valid = await categoryFormRef.value?.validate();
   if (!valid) return;
-  // TODO: 调用后端新增收藏夹接口
-  categoryTabs.value.push({ key: categoryForm.name, label: categoryForm.name });
-  ElMessage.success('添加成功');
-  categoryDialogVisible.value = false;
-  getFavoritesTabs();
+  addProductCollect({ title: categoryForm.name, isDefault: categoryForm.isDefault }).then((res: any) => {
+    if (res.code == 200) {
+      ElMessage.success('添加成功');
+      categoryDialogVisible.value = false;
+      getFavoritesTabs();
+    }
+  });
 };
 
 /** 单个加入购物车 */

+ 7 - 8
src/views/item/index.vue

@@ -296,13 +296,6 @@ const initData = () => {
   } else {
     getInfoId();
   }
-
-  if (token) {
-    console.log('收藏');
-    getCollection();
-    // 浏览记录
-    addProductBrowsingHistory(id.value).then((res) => {});
-  }
 };
 
 // 商品详情-id
@@ -318,12 +311,18 @@ const getInfoId = () => {
 const getInfoProductNo = () => {
   getProductDetailByNo(productNo.value).then((res) => {
     if (res.code == 200 && res.data) {
+      id.value = res.data.id;
       getInfo(res);
     }
   });
 };
 
 const getInfo = (res: any) => {
+  if (token) {
+    getCollection();
+    // 浏览记录
+    addProductBrowsingHistory(id.value).then((res) => {});
+  }
   carousel.value = [figure];
   if (res.data && res.data.imageUrl) {
     carousel.value = res.data.imageUrl.split(',');
@@ -334,7 +333,7 @@ const getInfo = (res: any) => {
   num.value = Number(res.data.minOrderQuantity || 1);
   dataInfo.value = res.data;
   dataInfo.value.minOrderQuantity = num.value;
-  dataInfo.value.allStock = Number(res.data.totalInventory || 0) + Number(res.data.nowInventory || 0) + Number(res.data.virtualInventory || 0);
+  dataInfo.value.allStock = Number(res.data.totalInventory || 0);
 };
 
 // 收藏

+ 900 - 0
src/views/item/preview.vue

@@ -0,0 +1,900 @@
+<template>
+  <div class="shop-pages">
+    <!-- 面包屑 -->
+    <div class="breadcrumb-bos">
+      <div class="home" @click="onPath('/index')">首页</div>
+    </div>
+    <div class="shop-bos">
+      <div class="shop-left">
+        <!-- 图片展示 -->
+        <div class="images-bos">
+          <!-- 轮播 -->
+          <div class="carousel-bos">
+            <div class="left-carousel" v-if="carousel && carousel.length > 0">
+              <div v-for="(item, index) in carousel" :key="index" class="carousel-item" :class="carouselIndex == index ? 'hig' : ''">
+                <img :src="item" @click="onCarousel(1, index)" @mouseenter="onCarousel(1, index)" />
+              </div>
+            </div>
+            <div class="left-next flex-row-center" @click="onCarousel(2)">
+              <el-icon color="#6A7282" size="15">
+                <ArrowDown />
+              </el-icon>
+            </div>
+          </div>
+          <!-- 中间大图 -->
+          <div class="images-box" @mouseenter="showZoom = true" @mouseleave="showZoom = false" @mousemove="handleMouseMove">
+            <img v-if="carousel && carousel.length > 0" :src="carousel[carouselIndex]" alt="" class="images-img" ref="mainImageRef" />
+            <!-- 新增:放大镜遮罩层 -->
+            <div
+              v-show="showZoom"
+              class="zoom-mask"
+              :style="{
+                left: maskPos.x + 'px',
+                top: maskPos.y + 'px'
+              }"
+            ></div>
+          </div>
+        </div>
+        <!-- tab切换 -->
+        <div class="nav-bos flex-row-start">
+          <div @click="onNav(index)" v-for="(item, index) in navList" :key="index" :class="navIndex == index ? 'hig' : ''">{{ item.title }}</div>
+        </div>
+        <!-- 内容 -->
+        <div class="content-bos">
+          <!-- 商品详情 -->
+          <div class="pc-detail" v-if="navIndex == 0" v-html="dataInfo.pcDetail"></div>
+          <!-- 售后服务说明 -->
+          <div class="service" v-if="navIndex == 1">
+            <div class="service1">原厂正品保证承诺:</div>
+            <div>(1)所有商品均为符合国家有关商品质量的技术标准、服务标准、环保标准的原厂正品。</div>
+            <div>(2)所有商品保证是全新的货物,且来源于中华人民共和国或与中华人民共和国有正常贸易往来的国家或地区。</div>
+            <div>(3)所有商品均为自营,不涉及任何第三方店铺,充分保证产品来源,保证质量。</div>
+            <div>(4)所有商品均为原厂正品,如有假货,假一罚十。</div>
+            <div>(5)所有产品均严格按照国家三包标准执行。</div>
+            <div>(6)保证所售商品开具机打发票或电子发票。</div>
+            <div class="service1">厂家服务:</div>
+            <div>
+              本商品质保周期均为厂商对外公布的质保期或优易365对客户承诺的质保期为准。在此时间范围内可提交维修申请,具体请以厂家服务为准。
+              如因质量问题或故障,凭厂商维修中心或特约维修点的质量检测证明,享受7日内退货,15日内换货,15日以上在质保期内享受免费保修等三包服务!
+            </div>
+            <div>全国统一售后及服务电话:400-111-0027</div>
+            <div>(注:如优易365在商品介绍中有售后保障的说明,则此商品按照说明执行售后保障服务。)</div>
+            <div class="service1">服务承诺:</div>
+            <div>平台销售并发货的商品,均由平台提供发票和相应的售后服务。请您放心购买!</div>
+            <div>优易365确保客户收到的货物与商城图片、产地、附件说明完全一致。均为原厂正货!并且保证与当时市场上同样主流新品一致。</div>
+            <div class="service1">无忧退货:</div>
+            <div>
+              客户购买商品7日内(含7日,自客户收到商品之日起计算),在保证商品完好的前提下,可无理由退货。(部分商品除外,详情请见各商品细则)
+            </div>
+            <div>• 购买运费如何收取?</div>
+            <div class="service2">
+              单笔订单金额(不含运费)满88元免邮费;不满88元,每单收取10元运费。(港澳台地区需满500元免邮费;不满500元,每单收取30元运费)
+            </div>
+            <div>• 使用什么快递发货?</div>
+            <div class="service2">默认使用顺丰快递发货(个别商品使用其他快递)</div>
+            <div class="service2">配送范围覆盖全国大部分地区(港澳台地区除外)。</div>
+            <div>• 如何申请退货?</div>
+            <div class="service2">1.自收到商品之日起30日内,顾客可申请无忧退货,退款将原路返还,不同的银行处理时间不同,预计1-5个工作日到账;</div>
+            <div class="service2">2.内裤和食品等特殊商品无质量问题不支持退货;</div>
+            <div>3.退货流程:</div>
+            <div class="service2">确认收货-申请退货-客服审核通过-用户寄回商品-仓库签收验货-退款审核-退款完成;</div>
+            <div>
+              4.因优品汇产生的退货,如质量问题,退货邮费由优品汇承担,退款完成后会以现金券的形式报销。因客户个人原因产生的退货,购买和寄回运费由客户个人承担。
+            </div>
+          </div>
+          <el-empty v-if="navIndex == 2" description="暂无品牌信息" />
+          <el-empty v-if="navIndex == 3" description="暂无热门评价" />
+        </div>
+      </div>
+      <!-- 图片放大展示区域 -->
+      <div
+        class="image-zoom"
+        v-show="showZoom"
+        :style="{
+          backgroundImage: `url(${carousel[carouselIndex]})`,
+          backgroundPosition: `${bgPos.x} ${bgPos.y}`
+        }"
+      ></div>
+      <!-- 右边 -->
+      <div class="shop-right">
+        <div class="shop-info">
+          <div class="right-title">{{ dataInfo.itemName || '' }}</div>
+          <div class="right-price flex-row-between">
+            <div class="flex-row-start">
+              <div class="price1">
+                <span>¥</span>
+                <span style="font-size: 24px">{{ dataInfo.memberPrice || 0 }}</span>
+              </div>
+              <div class="price2">平台价</div>
+              <div class="price3">
+                <span>¥</span>
+                <span style="font-size: 16px">{{ dataInfo.marketPrice || 0 }}</span>
+              </div>
+            </div>
+          </div>
+          <div class="address flex-row-start">
+            <img class="address-img" src="@/assets/images/item1.png" alt="" />
+            <div style="margin-right: 10px">配送至</div>
+            <el-cascader v-model="regionCodes" :options="regionData as any" :placeholder="'请选择省/市/区'" style="width: 260px" />
+          </div>
+          <div class="specs-bos">
+            <div class="specs-list">
+              <div>商品编号</div>
+              <div class="specs-item hig">
+                {{ dataInfo.productNo }}
+              </div>
+              <div class="specs-box"></div>
+            </div>
+            <div class="specs-list">
+              <div>规格/型号</div>
+              <div class="specs-item hig">
+                {{ dataInfo.specificationsCode }}
+              </div>
+              <div class="specs-box"></div>
+            </div>
+            <div class="specs-list">
+              <div>UPC(69)条码</div>
+              <div class="specs-item hig">
+                {{ dataInfo.upcBarcode }}
+              </div>
+              <div class="specs-box"></div>
+            </div>
+            <div class="specs-list">
+              <div>定制</div>
+              <div class="specs-item hig">
+                {{ dataInfo.isCustomize == 1 ? '可定制' : '不可定制' }}
+              </div>
+              <div class="specs-box"></div>
+            </div>
+            <div class="specs-list">
+              <div>供货时间</div>
+              <div class="specs-item hig">
+                {{ dataInfo.deliveryTime || '' }}
+              </div>
+              <div class="specs-box"></div>
+            </div>
+            <div class="specs-list">
+              <div>商品库存</div>
+              <div class="specs-item hig">{{ dataInfo.allStock || 0 }} {{ dataInfo.unitName }}</div>
+              <div class="specs-box"></div>
+            </div>
+          </div>
+          <div class="number-bos">
+            <div>数量</div>
+            <div class="flex-row-start number-box">
+              <el-input-number v-model="num" :min="dataInfo.minOrderQuantity" :max="dataInfo.allStock" />
+              <div style="margin-left: 10px">提示:本产品起订为:{{ dataInfo.minOrderQuantity }}{{ dataInfo.unitName }}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <!-- 更多推荐 -->
+    <div class="goods-bos">
+      <div class="shop-more flex-row-between">
+        <div class="flex-row-start">
+          <div class="more1">更多推荐</div>
+          <div class="more2">甄选大牌,优质好品</div>
+        </div>
+      </div>
+      <div class="data-bos">
+        <div
+          v-for="(item, index) in recommendList"
+          :key="index"
+          class="data-list"
+          @click="onPath('/item?id=' + item.id + '&productNo=' + item.productNo)"
+        >
+          <img class="data-img" :src="item.productImage ? item.productImage.split(',')[0] : ''" alt="" />
+          <div class="data-title">{{ item.itemName || '' }}</div>
+          <div class="money">
+            <span class="money1">¥{{ item.memberPrice || '' }}</span>
+            <span class="money2">¥{{ item.marketPrice || '' }}</span>
+          </div>
+        </div>
+      </div>
+    </div>
+    <!-- 取消收藏 -->
+    <el-dialog
+      v-model="dialogVisible"
+      title="选择取消的收藏夹"
+      width="500"
+      :before-close="
+        () => {
+          dialogVisible = false;
+        }
+      "
+    >
+      <div>
+        <el-radio-group v-model="radio">
+          <el-radio v-for="(item, index) in favorites" :key="index" :value="item.id">{{ item.title }}</el-radio>
+        </el-radio-group>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="onCancel"> 确认 </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import Cookies from 'js-cookie';
+import { regionData } from 'element-china-area-data';
+import { onPath } from '@/utils/siteConfig';
+import figure from '@/assets/images/figure.png';
+import {
+  getProductDetail,
+  addProductShoppingCart,
+  isProductInDefaultCollect,
+  addProductBrowsingHistory,
+  addProductCollect,
+  favoritesList,
+  cancelProductCollect,
+  getProductDetailByNo,
+  getProductPreview
+} from '@/api/goods/index';
+
+
+const route = useRoute();
+const id = ref<any>(null);
+const dataInfo = ref<any>({});
+const carousel = ref<any>([figure]);
+const regionCodes = ref<any>([]);
+const radio = ref<any>(null);
+const favorites = ref<any>([]);
+const collection = ref<any>(null);
+const dialogVisible = ref<any>(false);
+const navList = ref<any>([{ title: '商品详情' }, { title: '售后服务说明' }, { title: '品牌信息' }, { title: '商品评价' }]);
+const navIndex = ref<any>(0);
+const recommendList = ref<any>([]);
+const num = ref<any>(1);
+const carouselIndex = ref<any>(0);
+const productNo = ref<any>(null);
+const token = Cookies.get('Authorization');
+
+watch(route, () => {
+  initData();
+});
+
+onMounted(() => {
+  initData();
+  nextTick(() => {
+    setTimeout(() => {
+      // 兼容不同浏览器的滚动元素
+      window.scrollTo({ top: 0, left: 0, behavior: 'auto' });
+      document.documentElement.scrollTop = 0;
+      document.body.scrollTop = 0;
+    }, 0);
+  });
+});
+
+// 核心修复:统一初始化函数
+const initData = () => {
+  id.value = route.query.id ? route.query.id : null;
+  getInfoId();
+
+  if (token) {
+    console.log('收藏');
+    getCollection();
+    // 浏览记录
+    addProductBrowsingHistory(id.value).then((res) => {});
+  }
+};
+
+// 商品详情-id
+const getInfoId = () => {
+  getProductPreview(id.value).then((res) => {
+    if (res.code == 200) {
+      getInfo(res);
+    }
+  });
+};
+
+//商品详情-productNo
+const getInfoProductNo = () => {
+  getProductDetailByNo(productNo.value).then((res) => {
+    if (res.code == 200 && res.data) {
+      getInfo(res);
+    }
+  });
+};
+
+const getInfo = (res: any) => {
+  carousel.value = [figure];
+  if (res.data && res.data.imageUrl) {
+    carousel.value = res.data.imageUrl.split(',');
+    if (carousel.value.length > 5) {
+      carousel.value = carousel.value.slice(0, 5);
+    }
+  }
+  num.value = Number(res.data.minOrderQuantity || 1);
+  dataInfo.value = res.data;
+  dataInfo.value.minOrderQuantity = num.value;
+  dataInfo.value.allStock = Number(res.data.totalInventory || 0);
+};
+
+// 收藏
+const getCollection = () => {
+  isProductInDefaultCollect(id.value).then((res) => {
+    if (res.code == 200) {
+      collection.value = res.data;
+    }
+  });
+};
+
+//修改收藏
+const editCollection = () => {
+  if (collection.value) {
+    dialogVisible.value = true;
+    favoritesList(id.value).then((res) => {
+      if (res.code == 200) {
+        if (res.rows.length > 0) {
+          radio.value = res.rows[0].id;
+        }
+        favorites.value = res.rows;
+      }
+    });
+  } else {
+    // 添加
+    addProductCollect({ productId: id.value }).then((res) => {
+      if (res.code == 200) {
+        getCollection();
+      }
+    });
+  }
+};
+
+// 取消收藏
+const onCancel = () => {
+  cancelProductCollect({ productId: id.value, favoritesId: radio.value }).then((res) => {
+    if (res.code == 200) {
+      getCollection();
+      dialogVisible.value = false;
+    }
+  });
+};
+
+// 切换
+const onNav = (index: any) => {
+  navIndex.value = index;
+};
+
+const onCarousel = (type: number, index?: any) => {
+  if (type == 1) {
+    carouselIndex.value = index;
+  } else {
+    carouselIndex.value++;
+    if (carouselIndex.value > carousel.value.length - 1) {
+      carouselIndex.value = 0;
+    }
+  }
+};
+
+import { cartStore } from '@/store/modules/cart';
+const cart = cartStore();
+const onCart = () => {
+  addProductShoppingCart({
+    productId: id.value,
+    productNum: num.value
+  }).then((res) => {
+    if (res.code == 200) {
+      ElMessage.success('加入购物车成功');
+      cart.onCartCount();
+    }
+  });
+};
+
+
+// 新增:放大功能相关
+const showZoom = ref(false);
+const mainImageRef = ref<HTMLImageElement | null>(null);
+const bgPos = ref({ x: '0%', y: '0%' });
+// 新增:遮罩层位置
+const maskPos = ref({ x: 0, y: 0 });
+
+const handleMouseMove = (e: MouseEvent) => {
+  if (!mainImageRef.value) return;
+
+  const image = mainImageRef.value;
+  const rect = image.getBoundingClientRect();
+
+  // 获取容器尺寸 (images-box)
+  const width = rect.width;
+  const height = rect.height;
+
+  // 计算鼠标在图片内的相对坐标
+  const x = e.clientX - rect.left;
+  const y = e.clientY - rect.top;
+
+  // 遮罩层尺寸 (假设放大倍数为2,遮罩层大小为容器的一半,即正方形)
+  // 如果希望遮罩层固定大小,可以写死,例如 200px
+  const maskSize = width / 2;
+
+  // 计算遮罩层左上角的位置 (让鼠标位于遮罩层中心)
+  let maskX = x - maskSize / 2;
+  let maskY = y - maskSize / 2;
+
+  // 边界限制:确保遮罩层不超出图片范围
+  maskX = Math.max(0, Math.min(maskX, width - maskSize));
+  maskY = Math.max(0, Math.min(maskY, height - maskSize + 60));
+
+  // 更新遮罩层位置
+  maskPos.value = {
+    x: maskX,
+    y: maskY
+  };
+
+  // 计算背景图偏移百分比 (用于右侧放大区域)
+  // 背景图移动的比例 = 遮罩层移动的比例
+  // 注意:这里分母是 (总宽度 - 遮罩宽度),因为背景图移动范围也是这么多
+  const percentX = (maskX / (width - maskSize)) * 100;
+  const percentY = (maskY / (height - maskSize)) * 100;
+
+  bgPos.value = {
+    x: `${percentX}%`,
+    y: `${percentY}%`
+  };
+};
+</script>
+<style lang="scss" scoped>
+.shop-pages {
+  margin: 0 auto;
+
+  //面包屑
+  .breadcrumb-bos {
+    width: 1200px;
+    height: 44px;
+    margin: 0 auto;
+    display: flex;
+    align-items: center;
+    @media (min-width: 1600px) {
+      width: 1600px;
+    }
+    .home {
+      cursor: pointer;
+      font-size: 14px;
+      &:hover {
+        color: var(--el-color-primary);
+      }
+    }
+  }
+
+  .shop-bos {
+    display: flex;
+    gap: 0 15px;
+    position: relative;
+    padding-bottom: 15px;
+    //左边
+    .shop-left {
+      // 图片展示
+      .images-bos {
+        display: flex;
+        gap: 15px;
+        // 轮播
+        .carousel-bos {
+          width: 80px;
+
+          @media (min-width: 1600px) {
+            width: 112px;
+          }
+          // 左边的轮播
+          .left-carousel {
+            height: 494px;
+            overflow-y: auto;
+            @media (min-width: 1600px) {
+              height: 674px;
+            }
+
+            .carousel-item {
+              width: 80px;
+              height: 80px;
+              @media (min-width: 1600px) {
+                width: 112px;
+                height: 112px;
+              }
+              border-radius: 4px;
+              overflow: hidden;
+              margin-top: 10px;
+              cursor: pointer;
+
+              &:nth-of-type(1) {
+                margin-top: 0px;
+              }
+
+              &.hig {
+                border: 1px solid #e7000b;
+              }
+
+              img {
+                width: 100%;
+                height: 100%;
+              }
+            }
+          }
+          .left-next {
+            width: 80px;
+            @media (min-width: 1600px) {
+              width: 112px;
+            }
+            height: 36px;
+            background: #ffffff;
+            border-radius: 6px 6px 6px 6px;
+            margin-top: 10px;
+            cursor: pointer;
+          }
+        }
+        // 中间大图
+        .images-box {
+          width: 645px;
+          height: 540px;
+          border-radius: 5px;
+          overflow: hidden;
+          position: relative; /* 关键:作为遮罩层的定位父级 */
+          cursor: crosshair; /* 关键:鼠标变成十字准星 */
+
+          @media (min-width: 1600px) {
+            width: 860px;
+            height: 720px;
+          }
+
+          .images-img {
+            width: 100%;
+            height: 100%;
+            display: block;
+          }
+
+          // 新增:遮罩层样式
+          .zoom-mask {
+            position: absolute;
+            width: 50%; /* 对应2倍放大,宽高各占50%形成正方形 */
+            height: 50%; /* 如果图片不是正方形,这里可能需要调整以保持正方形,但通常跟随容器比例即可 */
+            background-color: rgba(255, 255, 255, 0.3); /* 半透明白色 */
+            border: 1px solid #ccc; /* 边框 */
+            pointer-events: none; /* 关键:让鼠标事件穿透遮罩层,避免闪烁 */
+            z-index: 5;
+          }
+        }
+      }
+      //tab切换
+      .nav-bos {
+        width: 740px;
+        height: 48px;
+        background: #f9fafb;
+        border: 1px solid #e5e7eb;
+        border-bottom: 0px solid #e5e7eb;
+        margin-top: 15px;
+        gap: 0 32px;
+        padding-left: 32px;
+        font-size: 16px;
+        color: #364153;
+        border-radius: 5px 5px 0 0;
+        position: sticky;
+        top: 0;
+        @media (min-width: 1600px) {
+          width: 987px;
+        }
+
+        div {
+          cursor: pointer;
+        }
+
+        .hig {
+          color: #101828;
+          font-weight: 600;
+        }
+      }
+      //内容
+      .content-bos {
+        width: 740px;
+        background-color: #ffffff;
+        padding: 20px 15px;
+        border: 1px solid #e5e7eb;
+        border-top: 0px solid #e5e7eb;
+        border-radius: 0 0 5px 5px;
+        min-height: calc(100vh - 853px);
+        @media (min-width: 1600px) {
+          width: 987px;
+          min-height: calc(100vh - 1033px);
+        }
+
+        //商品详情
+        .pc-detail {
+          width: 100%;
+          :deep(img) {
+            width: 100%;
+            display: block;
+            margin: 0;
+            padding: 0;
+          }
+          :deep(p) {
+            margin: 0;
+            padding: 0;
+          }
+          :deep(*) {
+            margin: 0;
+            padding: 0;
+            box-sizing: border-box;
+          }
+        }
+        // 售后服务说明
+        .service {
+          width: 100%;
+          background: #ffffff;
+          div {
+            margin-bottom: 20px;
+            font-size: 14px;
+          }
+          .service1 {
+            font-weight: 600;
+          }
+          .service2 {
+            color: #999999;
+          }
+        }
+      }
+    }
+    //图片放大展示区域
+    .image-zoom {
+      width: 445px;
+      height: 540px;
+      background-color: #ffffff;
+      position: absolute;
+      z-index: 10;
+      right: 0;
+      border: 1px solid #e5e7eb;
+
+      // 新增/修改以下属性以实现放大
+      background-repeat: no-repeat;
+      background-size: 200%; // 放大2倍,可根据需求调整为 250% 等
+      background-position: 0 0; // 默认位置
+
+      @media (min-width: 1600px) {
+        width: 598px;
+        height: 720px;
+      }
+    }
+
+    .shop-right {
+      width: 445px;
+      background-color: #ffffff;
+      border-radius: 5px;
+      height: calc(100vh - 250px);
+      min-height: 580px;
+      position: sticky;
+      top: 0px;
+      display: flex;
+      flex-direction: column;
+      justify-content: space-between;
+      @media (min-width: 1600px) {
+        width: 598px;
+      }
+
+      .shop-info {
+        padding: 20px 20px 30px 20px;
+        .right-title {
+          min-height: 56px;
+          font-weight: 600;
+          font-size: 20px;
+          color: #101828;
+        }
+
+        .right-num {
+          font-size: 14px;
+          color: #6a7282;
+        }
+
+        .right-price {
+          margin-top: 10px;
+
+          .price1 {
+            font-size: 12px;
+            color: #e7000b;
+          }
+
+          .price2 {
+            width: 52px;
+            height: 21px;
+            background: #e7000b;
+            border-radius: 2px;
+            font-size: 12px;
+            color: #ffffff;
+            line-height: 21px;
+            text-align: center;
+            margin: 0 16px 0 8px;
+          }
+
+          .price3 {
+            font-size: 12px;
+            color: #6a7282;
+            text-decoration: line-through;
+          }
+
+          .right-collect {
+            font-size: 14px;
+            color: #6a7282;
+            cursor: pointer;
+            .icon-star {
+              margin-right: 4px;
+            }
+
+            img {
+              width: 12px;
+              height: 12px;
+              margin-right: 6px;
+            }
+          }
+        }
+
+        .address {
+          height: 44px;
+          width: 100%;
+          border-top: 1px solid #e5e7eb;
+          border-bottom: 1px solid #e5e7eb;
+          margin-top: 20px;
+          font-size: 14px;
+          color: #000000;
+
+          .address-img {
+            width: 16px;
+            height: 16px;
+            margin-right: 10px;
+            margin-top: 2px;
+          }
+
+          .address-text {
+            margin-left: 10px;
+            width: 138px;
+          }
+        }
+
+        .specs-bos {
+          .specs-list {
+            margin-top: 15px;
+            font-size: 14px;
+            color: #364153;
+            display: flex;
+            align-items: center;
+            .specs-item {
+              color: #6a7282;
+              margin-left: 10px;
+            }
+          }
+        }
+
+        .number-bos {
+          margin-top: 20px;
+          font-size: 14px;
+          color: #364153;
+
+          .number-box {
+            font-size: 14px;
+            color: #6a7282;
+            margin-top: 8px;
+          }
+        }
+      }
+      .bnt-bos {
+        width: 100%;
+        overflow: hidden;
+        padding: 0 20px 20px 20px;
+        .btn-text {
+          font-size: 12px;
+          color: #666666;
+          margin-left: 10px;
+        }
+
+        .btn {
+          flex: 1;
+          height: 44px;
+          line-height: 44px;
+          width: 100%;
+
+          &:nth-of-type(1) {
+            // background: #fcecf1;
+            // color: #e7000b;
+          }
+
+          &:nth-of-type(2) {
+            background: #e7000b;
+            color: #ffffff;
+          }
+        }
+      }
+    }
+  }
+  // 更多推荐
+  .goods-bos {
+    width: 1200px;
+    @media (min-width: 1600px) {
+      width: 1600px;
+    }
+    .shop-more {
+      width: 100%;
+      margin-top: 15px;
+
+      .more1 {
+        font-weight: 600;
+        font-size: 20px;
+        color: #101828;
+      }
+
+      .more2 {
+        font-size: 14px;
+        color: #364153;
+        margin-left: 10px;
+      }
+
+      .more3 {
+        font-size: 14px;
+        color: #364153;
+        margin-right: 6px;
+        cursor: pointer;
+      }
+    }
+    //数据
+    .data-bos {
+      display: flex;
+      gap: 20px;
+      flex-wrap: wrap;
+      margin-bottom: 40px;
+      margin-top: 24px;
+
+      .data-list {
+        width: 224px;
+        @media (min-width: 1600px) {
+          width: 250px;
+        }
+        background: #ffffff;
+        border-radius: 10px;
+        padding: 20px 20px 22px 20px;
+        cursor: pointer;
+
+        .data-img {
+          width: 184px;
+          height: 184px;
+          border-radius: 10px;
+        }
+
+        .data-title {
+          margin-top: 4px;
+          font-size: 14px;
+          color: #101828;
+          height: 40px;
+        }
+
+        .money {
+          margin-top: 4px;
+
+          .money1 {
+            font-size: 16px;
+            color: #e7000b;
+          }
+
+          .money2 {
+            font-size: 12px;
+            color: #99a1af;
+            text-decoration: line-through;
+            padding-left: 6px;
+          }
+        }
+
+        .data-cat {
+          width: 86px;
+          height: 26px;
+          background: #e7000b;
+          border-radius: 2px;
+          font-size: 14px;
+          color: #ffffff;
+          line-height: 26px;
+          text-align: center;
+          margin-top: 16px;
+        }
+      }
+    }
+  }
+}
+</style>
+
+<!-- 到这里就可以了 -->

+ 3 - 2
src/views/login.vue

@@ -56,7 +56,8 @@
           <div class="login-text flex-row-between">
             <div @click="handleForgetPassword">忘记密码?</div>
             <div class="border"></div>
-            <div @click="onPath('/breg')">新用户注册</div>
+            <div @click="onPath('/breg')">企业注册</div>
+            <div @click="onPath('/reg')">个人注册</div>
             <!-- <router-link to="/register" class="register-link">新用户注册</router-link> -->
           </div>
         </el-form>
@@ -349,7 +350,7 @@ onMounted(() => {
       .login-text {
         font-size: 14px;
         color: #6a7282;
-        padding: 0 107px;
+        padding: 0 80px;
         margin-top: 14px;
         div {
           cursor: pointer;

Diferenças do arquivo suprimidas por serem muito extensas
+ 121 - 3
src/views/reg/index.vue


+ 1 - 0
src/views/trad/index.vue

@@ -420,6 +420,7 @@ const disabledDate = (date: Date) => {
     .address-bos {
       display: flex;
       gap: 10px 12px;
+      flex-wrap: wrap;
       .address-list {
         width: 291px;
         height: 92px;

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff