Jelajahi Sumber

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

hurx 23 jam lalu
induk
melakukan
09d048d2a5

+ 0 - 2
src/api/plan/index.ts

@@ -51,7 +51,6 @@ export function getProductProgramDetail(id: any) {
   });
 }
 
-
 // 场景采购详情的商品
 export function getProductProgramProductList(id: any) {
   return request({
@@ -169,4 +168,3 @@ export function getProductBrandDetail(id: any) {
     method: 'get'
   });
 }
-

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

@@ -55,3 +55,12 @@ export const getBrandByCategoryList = (query: any) => {
     params: query
   });
 };
+
+//品牌详情
+
+export const getBrandDetail = (id: any) => {
+  return request({
+    url: `/product/indexProduct/getBrandDetail/${id}`,
+    method: 'get'
+  });
+};

+ 1 - 5
src/layout/components/breadcrumb.vue

@@ -6,7 +6,7 @@
       <template v-if="meta.navList && meta.navList.length > 0">
         <div v-for="(item, index) in meta.navList" :key="index" class="nav-list">
           <el-icon style="margin: 0 4px"><ArrowRight /></el-icon>
-          <div @click="goPath(item)" class="like">{{ item.title || '' }}</div>
+          <div @click="onPath(item.url)" class="like">{{ item.title || '' }}</div>
         </div>
       </template>
       <el-icon style="margin: 0 4px"><ArrowRight /></el-icon>
@@ -26,10 +26,6 @@ meta.value = route.meta;
 watch(route, () => {
   meta.value = route.meta;
 });
-
-const goPath = (res: any) => {
-  router.push(res.url);
-};
 </script>
 
 <style lang="scss" scoped>

+ 1 - 0
src/layout/components/nav.vue

@@ -111,6 +111,7 @@ const leaveClassify = () => {
 .nav-pages {
   width: 100%;
   background-color: #ffffff;
+  border-bottom: 2px solid var(--el-color-primary);
 
   .nav-bos {
     margin: 0 auto;

+ 1 - 1
src/views/home/index.vue

@@ -177,7 +177,7 @@ getHotSchemeList({}).then((res) => {
       item.title = item.advertTitle;
       item.subtitle = item.advertBrief;
       item.imageUrl = item.coverImage;
-      item.url = '';
+      item.url = item.advertUrl ? item.advertUrl : '';
       if (index < 4) hotList.value.push(item);
     });
   }

+ 104 - 86
src/views/plan/guide.vue

@@ -30,25 +30,30 @@
       </div>
     </div>
     <!-- 数据 -->
-    <div class="procure-bos">
-      <div v-for="(item, index) in dataList" :key="index" class="procure-list" @click="onPath('/plan_info/guide?id=' + item.id)">
-        <img :src="item.coverImage" alt="" />
-        <div class="procure1">{{ item.title }}</div>
-        <div class="procure2">{{ item.releaseTime }}</div>
+    <div class="data-bos">
+      <div v-for="(item, index) in dataList" :key="index" class="data-list" @click="onPath('/plan_info/guide?id=' + item.id)">
+        <el-image class="data-img" :src="item.coverImage" fit="cover" />
+        <div class="data-box flex-column-between">
+          <div>
+            <div class="title ellipsis">{{ item.title }}</div>
+            <div class="info ellipsis">{{ item.releaseTime }}</div>
+          </div>
+          <div class="text flex-row-start">
+            <div>了解详情</div>
+            <el-icon color="#e7000b" size="14" style="margin: 0 0 0 10px">
+              <ArrowRight />
+            </el-icon>
+          </div>
+        </div>
+      </div>
+      <div class="flex-row-center w100%" v-if="dataList.length === 0">
+        <el-empty description="暂无数据" />
       </div>
     </div>
     <!-- 游标分页控制 -->
-    <div class="pagination-bos flex-row-between">
+    <div class="table-pagination flex-row-between">
       <div></div>
-      <pagination
-        v-show="dataList.length > 0"
-        v-model:page="httpObj.pageNum"
-        v-model:limit="httpObj.pageSize"
-        v-model:way="way"
-        :cursor-mode="true"
-        :has-more="hasMore"
-        @pagination="getList"
-      />
+      <TablePagination v-model:page="httpObj.pageNum" v-model:page-size="httpObj.pageSize" :total="total" @change="getList" />
     </div>
   </div>
 </template>
@@ -75,7 +80,7 @@ const httpObj = ref<any>({
 });
 const dataList = ref<any>([]);
 const hasMore = ref(true); // 是否还有更多数据
-const way = ref<any>(1);
+const total = ref(0);
 
 const navList = ref<any>([
   { title: '专题分类' },
@@ -131,8 +136,6 @@ const filterListy = ref<any>([
     ]
   }
 ]);
-const navIndex = ref(0);
-const router = useRouter();
 
 onMounted(() => {
   // 采购分类列表
@@ -193,8 +196,7 @@ const getList = () => {
   getPurchaseGuideList(httpObj.value).then((res) => {
     if (res.code == 200) {
       dataList.value = res.rows;
-      // 判断是否还有更多数据
-      hasMore.value = res.total > httpObj.value.pageSize * httpObj.value.pageNum;
+      total.value = res.total;
     }
   });
 };
@@ -218,6 +220,14 @@ const handleCurrentChange = (val: number) => {
 </script>
 
 <style lang="scss" scoped>
+// 定义响应式容器 Mixin
+@mixin responsive-container {
+  width: 100%;
+  min-width: 1200px;
+  max-width: 1500px;
+  margin: 0 auto;
+  box-sizing: border-box;
+}
 .solve {
   width: 100%;
 
@@ -239,7 +249,6 @@ const handleCurrentChange = (val: number) => {
     width: 100%;
     min-width: 1200px;
     max-width: 1500px;
-    padding-bottom: 20px;
 
     .nav-list {
       height: 32px;
@@ -249,6 +258,7 @@ const handleCurrentChange = (val: number) => {
       font-size: 14px;
       color: #4e5969;
       margin-right: 8px;
+      margin-bottom: 10px; // 换行时的间距
       line-height: 32px;
       cursor: pointer;
 
@@ -282,86 +292,94 @@ const handleCurrentChange = (val: number) => {
         &.hig {
           color: #e7000b;
         }
+
+        &:hover {
+          color: var(--el-color-primary);
+        }
+
+        &:last-child {
+          margin-right: 0;
+        }
       }
     }
   }
 
-  // 采购指南
-  .procure-bos {
+  // 数据列表
+  .data-bos {
+    @include responsive-container;
     display: flex;
-    width: 100%;
-    min-width: 1200px;
-    max-width: 1500px;
-    margin-top: 12px;
-    gap: 15px;
+    gap: 20px;
     flex-wrap: wrap;
-    margin: 0 auto;
     padding: 22px 0 40px 0;
-    .procure-list {
-      width: 390px;
-      height: 268px;
+    justify-content: space-between; // 均匀分布
+
+    .data-list {
+      // 计算宽度:每行3个。 (100% - 2个间隙 * 20px) / 3
+      width: calc((100% - 40px) / 3);
+      height: 302px;
       background: #ffffff;
-      cursor: pointer;
-      border-radius: 10px;
+      border-radius: 5px;
       overflow: hidden;
-      img {
-        width: 390px;
-        height: 200px;
-      }
-      .procure1 {
-        padding: 12px 0 4px 20px;
-        font-weight: 600;
-        font-size: 14px;
-        color: #101828;
+      cursor: pointer;
+      transition:
+        transform 0.2s ease,
+        box-shadow 0.2s ease;
+      display: flex;
+      flex-direction: column;
+
+      &:hover {
+        transform: translateY(-2px);
+        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+        .title {
+          color: #e7000b !important;
+        }
       }
-      .procure2 {
-        padding-left: 20px;
-        font-size: 12px;
-        color: #364153;
+
+      .data-img {
+        width: 100%;
+        height: 200px;
+        object-fit: cover; // 保持图片比例填充
       }
-    }
-  }
 
-  //分页
-  .pagination-bos {
-    width: 1200px;
-    margin: 0 auto;
-    padding-bottom: 60px;
-    :deep(.el-select__wrapper) {
-      background: #f4f4f4;
-      box-shadow: 0 0 0 1px #e5e6eb inset;
-      border-radius: 2px;
-    }
-    :deep(.el-select__placeholder) {
-      color: #1d2129;
-    }
-    :deep(.el-input__wrapper) {
-      background: #f4f4f4;
-      box-shadow: 0 0 0 1px #e5e6eb inset;
-      border-radius: 2px;
-    }
-    :deep(.el-input__inner) {
-      color: #1d2129;
-    }
+      .data-box {
+        flex: 1; // 占据剩余高度
+        width: 100%;
+        padding: 12px 20px;
+        box-sizing: border-box;
+        display: flex;
+        flex-direction: column;
+        justify-content: space-between;
 
-    :deep(.btn-prev) {
-      background: #f4f4f4;
-      border: 1px solid #e5e6eb;
-      margin-right: 8px;
-    }
-    :deep(.btn-next) {
-      background: #f4f4f4;
-      border: 1px solid #e5e6eb;
-      margin-left: 8px;
-    }
-    :deep(.el-pager) {
-      gap: 0 8px;
-      li {
-        background: #f4f4f4;
-        border: 1px solid #e5e6eb;
-        color: #1d2129;
+        .title {
+          font-weight: 600;
+          font-size: 14px;
+          color: #101828;
+          line-height: 1.4;
+        }
+
+        .info {
+          font-size: 12px;
+          color: #364153;
+          margin-top: 4px;
+          line-height: 1.4;
+          // 限制显示两行,超出省略
+          display: -webkit-box;
+          -webkit-line-clamp: 2;
+          -webkit-box-orient: vertical;
+          overflow: hidden;
+        }
+
+        .text {
+          font-size: 14px;
+          color: #e7000b;
+          margin-top: auto; // 推到底部
+        }
       }
     }
   }
+  .table-pagination {
+    @include responsive-container;
+    padding-bottom: 30px;
+  }
 }
 </style>

+ 18 - 58
src/views/plan/index.vue

@@ -32,7 +32,7 @@
     <!-- 数据 -->
     <div class="data-bos">
       <div v-for="(item, index) in dataList" :key="index" class="data-list" @click="onPath('/plan_info?id=' + item.id)">
-        <img class="data-img" :src="item.coverImage" alt="" />
+        <el-image class="data-img" :src="item.coverImage" fit="cover" />
         <div class="data-box flex-column-between">
           <div>
             <div class="title ellipsis">{{ item.tweetsTitle }}</div>
@@ -46,19 +46,14 @@
           </div>
         </div>
       </div>
+      <div class="flex-row-center w100%" v-if="dataList.length === 0">
+        <el-empty description="暂无数据" />
+      </div>
     </div>
     <!-- 游标分页控制 -->
-    <div class="pagination-bos flex-row-between">
+    <div class="table-pagination flex-row-between">
       <div></div>
-      <pagination
-        v-show="dataList.length > 0"
-        v-model:page="httpObj.pageNum"
-        v-model:limit="httpObj.pageSize"
-        v-model:way="way"
-        :cursor-mode="true"
-        :has-more="hasMore"
-        @pagination="getList"
-      />
+      <TablePagination v-model:page="httpObj.pageNum" v-model:page-size="httpObj.pageSize" :total="total" @change="getList" />
     </div>
   </div>
 </template>
@@ -85,9 +80,8 @@ const httpObj = ref<any>({
   pageNum: 1
 });
 const dataList = ref<any>([]);
-const hasMore = ref(true); // 是否还有更多数据
-const way = ref<any>(1);
 const navList = ref<any>([]);
+const total = ref(0);
 
 const filterListy = ref<any>([
   {
@@ -197,8 +191,7 @@ const getList = () => {
   getProcurementProgramList(httpObj.value).then((res) => {
     if (res.code == 200) {
       dataList.value = res.rows;
-      // 判断是否还有更多数据
-      hasMore.value = res.total > httpObj.value.pageSize * httpObj.value.pageNum;
+      total.value = res.total;
     }
   });
 };
@@ -241,7 +234,6 @@ const onFilter = (item1: any, item2: any) => {
   .nav-bos {
     border-bottom: 1px solid #e5e7eb;
     width: 100%; // 继承父容器宽度
-    padding-bottom: 20px;
     display: flex;
     flex-wrap: wrap; // 防止导航项过多时溢出
 
@@ -253,7 +245,7 @@ const onFilter = (item1: any, item2: any) => {
       font-size: 14px;
       color: #4e5969;
       margin-right: 8px;
-      margin-bottom: 8px; // 换行时的间距
+      margin-bottom: 10px; // 换行时的间距
       line-height: 32px;
       cursor: pointer;
       white-space: nowrap;
@@ -295,6 +287,9 @@ const onFilter = (item1: any, item2: any) => {
         &.hig {
           color: #e7000b;
         }
+        &:hover {
+          color: var(--el-color-primary);
+        }
 
         &:last-child {
           margin-right: 0;
@@ -317,7 +312,7 @@ const onFilter = (item1: any, item2: any) => {
       width: calc((100% - 40px) / 3);
       height: 302px;
       background: #ffffff;
-      border-radius: 10px;
+      border-radius: 5px;
       overflow: hidden;
       cursor: pointer;
       transition:
@@ -329,6 +324,9 @@ const onFilter = (item1: any, item2: any) => {
       &:hover {
         transform: translateY(-2px);
         box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+        .title {
+          color: #e7000b !important;
+        }
       }
 
       .data-img {
@@ -373,47 +371,9 @@ const onFilter = (item1: any, item2: any) => {
       }
     }
   }
-
-  // 分页
-  .pagination-bos {
+  .table-pagination {
     @include responsive-container;
-    padding-bottom: 60px;
-
-    :deep(.el-select__wrapper) {
-      background: #f4f4f4;
-      box-shadow: 0 0 0 1px #e5e6eb inset;
-      border-radius: 2px;
-    }
-    :deep(.el-select__placeholder) {
-      color: #1d2129;
-    }
-    :deep(.el-input__wrapper) {
-      background: #f4f4f4;
-      box-shadow: 0 0 0 1px #e5e6eb inset;
-      border-radius: 2px;
-    }
-    :deep(.el-input__inner) {
-      color: #1d2129;
-    }
-
-    :deep(.btn-prev) {
-      background: #f4f4f4;
-      border: 1px solid #e5e6eb;
-      margin-right: 8px;
-    }
-    :deep(.btn-next) {
-      background: #f4f4f4;
-      border: 1px solid #e5e6eb;
-      margin-left: 8px;
-    }
-    :deep(.el-pager) {
-      gap: 0 8px;
-      li {
-        background: #f4f4f4;
-        border: 1px solid #e5e6eb;
-        color: #1d2129;
-      }
-    }
+    padding-bottom: 30px;
   }
 }
 </style>

+ 28 - 22
src/views/plan/procure.vue

@@ -33,7 +33,7 @@
       <!-- 数据 -->
       <div class="data-bos">
         <div v-for="(item, index) in dataList" :key="index" class="data-list" @click="onPath('/plan_info/procure?id=' + item.id)">
-          <img class="data-img" :src="item.coverImage" alt="" />
+          <el-image class="data-img" :src="item.coverImage" fit="cover" />
           <div class="data-box">
             <div class="title ellipsis">{{ item.title }}</div>
             <div class="info ellipsis2">
@@ -42,6 +42,7 @@
             <div class="time">2026-03-12</div>
           </div>
         </div>
+        <TablePagination v-model:page="httpObj.pageNum" v-model:page-size="httpObj.pageSize" :total="total" @change="getList" />
       </div>
       <div class="related-bos">
         <div class="related-box">
@@ -55,8 +56,9 @@
               class="procure-list"
               @click="onPath('/item?id=' + item.id + '&productNo=' + item.productNo)"
             >
-              <img :src="item.productImage" alt="" />
-              <div>
+              <el-image class="img" :src="item.productImage" fit="cover" />
+              <!-- <img :src="item.productImage" alt="" /> -->
+              <div class="flex-1">
                 <div class="procure1">{{ item.itemName }}</div>
                 <div class="procure2">¥{{ item.memberPrice }}</div>
               </div>
@@ -66,12 +68,6 @@
         </div>
       </div>
     </div>
-
-    <!-- 游标分页控制 -->
-    <div class="pagination-bos flex-row-between">
-      <div></div>
-      <TablePagination v-model:page="httpObj.pageNum" v-model:page-size="httpObj.pageSize" :total="total" @change="getList" />
-    </div>
   </div>
 </template>
 
@@ -218,8 +214,6 @@ const getList = () => {
     if (res.code == 200) {
       dataList.value = res.rows;
       total.value = res.total;
-      // 判断是否还有更多数据
-      hasMore.value = res.total > httpObj.value.pageSize * httpObj.value.pageNum;
     }
   });
 };
@@ -290,8 +284,7 @@ function getLatest5Items(data: any): any[] {
 
   .nav-bos {
     border-bottom: 1px solid #e5e7eb;
-    width: 1200px;
-    padding-bottom: 20px;
+    width: 100%;
 
     .nav-list {
       height: 32px;
@@ -301,6 +294,7 @@ function getLatest5Items(data: any): any[] {
       font-size: 14px;
       color: #4e5969;
       margin-right: 8px;
+      margin-bottom: 10px; // 换行时的间距
       line-height: 32px;
       cursor: pointer;
 
@@ -334,6 +328,9 @@ function getLatest5Items(data: any): any[] {
         &.hig {
           color: #e7000b;
         }
+        &:hover {
+          color: var(--el-color-primary);
+        }
       }
     }
   }
@@ -371,12 +368,17 @@ function getLatest5Items(data: any): any[] {
             height: 130px;
             background: #ffffff;
             cursor: pointer;
-            border-radius: 10px;
+            border-radius: 5px;
             overflow: hidden;
             border: 1px solid #d0d5dd;
             padding: 15px;
             display: flex;
-            img {
+            &:hover {
+              .procure1 {
+                color: #e7000b !important;
+              }
+            }
+            .img {
               width: 100px;
               height: 100px;
               border-radius: 5px;
@@ -413,6 +415,17 @@ function getLatest5Items(data: any): any[] {
         display: flex;
         gap: 0 15px;
         margin-bottom: 15px;
+        transition:
+          transform 0.2s ease,
+          box-shadow 0.2s ease;
+
+        &:hover {
+          transform: translateY(-2px);
+          box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+          .title {
+            color: #e7000b !important;
+          }
+        }
 
         .data-img {
           height: 170px;
@@ -447,12 +460,5 @@ function getLatest5Items(data: any): any[] {
       }
     }
   }
-
-  //分页
-  .pagination-bos {
-    width: 1200px;
-    margin: 0 auto;
-    padding-bottom: 60px;
-  }
 }
 </style>

+ 105 - 214
src/views/plan/project.vue

@@ -1,7 +1,9 @@
 <template>
   <div class="project">
     <div class="project-pages">
-      <img class="solve-img" src="@/assets/images/login-background.jpg" alt="" />
+      <div class="img-bos">
+        <el-image class="solve-img" src="@/assets/images/login-background.jpg" fit="cover" />
+      </div>
       <div class="filter-bos">
         <div v-for="(item1, index1) in filterListy" :key="index1" class="filter-list flex-row-start">
           <div class="filter-title">{{ item1.title }}</div>
@@ -16,39 +18,30 @@
           </div>
         </div>
       </div>
-      <!-- 数据 -->
-      <div class="project-bos">
-        <div v-for="(item, index) in dataList" :key="index" class="project-list" @click="onPath('/plan_info/project?id=' + item.id)">
-          <img :src="item.uploadProgram" alt="" />
-          <div class="project-box">
-            <div class="project1">{{ item.caseTitle || '' }}</div>
-            <div class="project2">
-              {{ item.projectBrief || '' }}
-            </div>
-            <div class="project-more flex-row-between">
-              <div></div>
-              <div class="flex-row-start">
-                <div style="margin-right: 5px">了解详情</div>
-                <el-icon :size="14" color="#E7000B"><ArrowRight /></el-icon>
-              </div>
+    </div>
+    <!-- 数据 -->
+    <div class="project-bos">
+      <div v-for="(item, index) in dataList" :key="index" class="project-list" @click="onPath('/plan_info/project?id=' + item.id)">
+        <el-image class="data-img" :src="item.uploadProgram" fit="cover" />
+        <div class="project-box">
+          <div class="project1 ellipsis">{{ item.caseTitle || '' }}</div>
+          <div class="project2">
+            {{ item.projectBrief || '' }}
+          </div>
+          <div class="project-more flex-row-between">
+            <div></div>
+            <div class="flex-row-start">
+              <div style="margin-right: 5px">了解详情</div>
+              <el-icon :size="14" color="#E7000B"><ArrowRight /></el-icon>
             </div>
           </div>
         </div>
       </div>
     </div>
-
     <!-- 游标分页控制 -->
-    <div class="pagination-bos flex-row-between">
+    <div class="table-pagination flex-row-between">
       <div></div>
-      <pagination
-        v-show="dataList.length > 0"
-        v-model:page="httpObj.pageNum"
-        v-model:limit="httpObj.pageSize"
-        v-model:way="way"
-        :cursor-mode="true"
-        :has-more="hasMore"
-        @pagination="getList"
-      />
+      <TablePagination v-model:page="httpObj.pageNum" v-model:page-size="httpObj.pageSize" :total="total" @change="getList" />
     </div>
   </div>
 </template>
@@ -70,15 +63,8 @@ const httpObj = ref<any>({
 const dataList = ref<any>([]);
 const hasMore = ref(true); // 是否还有更多数据
 const way = ref<any>(1);
+const total = ref(0);
 
-const navList = ref<any>([
-  { title: '专题分类' },
-  { title: '大中型企业采购' },
-  { title: '政府&公共采购' },
-  { title: '营销福利' },
-  { title: '商用工程' },
-  { title: '中小型企业采购' }
-]);
 const filterListy = ref<any>([
   {
     title: '客户行业',
@@ -130,8 +116,7 @@ const getList = () => {
   getProjectCaseAllList(httpObj.value).then((res) => {
     if (res.code == 200) {
       dataList.value = res.rows;
-      // 判断是否还有更多数据
-      hasMore.value = dataList.value.length === httpObj.value.pageSize;
+      total.value = res.total;
     }
   });
 };
@@ -152,18 +137,28 @@ const handleCurrentChange = (val: number) => {
 <style lang="scss" scoped>
 .project {
   width: 100%;
-  background-color: #ffffff;
   .project-pages {
     width: 100%;
-    max-width: 1500px;
-    min-width: 1200px;
-    margin: 0 auto;
-    .solve-img {
+    background-color: #ffffff;
+    .img-bos {
       width: 100%;
-      height: 380px;
-      border-radius: 10px;
+      display: flex;
+      justify-content: center;
+      .solve-img {
+        width: 100%;
+        max-width: 1500px;
+        min-width: 1200px;
+        height: 380px;
+        border-radius: 5px;
+      }
     }
+
     .filter-bos {
+      width: 100%;
+      max-width: 1500px;
+      min-width: 1200px;
+      margin: 0 auto;
+      padding-bottom: 20px;
       .filter-list {
         margin-top: 20px;
 
@@ -182,193 +177,89 @@ const handleCurrentChange = (val: number) => {
           &.hig {
             color: #e7000b;
           }
-        }
-      }
-    }
-    //项目案例
-    .project-bos {
-      display: flex;
-      width: 100%;
-      margin-top: 25px;
-      gap: 15px;
-      flex-wrap: wrap;
-      .project-list {
-        flex: 0 0 calc((100% - 45px) / 3);
-        width: 0;
-        height: 350px;
-        cursor: pointer;
-        border-radius: 10px;
-        overflow: hidden;
-
-        .project-box {
-          width: 100%;
-          height: 156px;
-          background: #ffffff;
-          border-radius: 10px;
-          margin-top: -20px;
-          z-index: 2;
-          // position: absolute;
-          padding: 20px;
-          border: 1px solid #e5e7eb;
-        }
-        img {
-          width: 100%;
-          height: 180px;
-        }
-        .project1 {
-          font-weight: 600;
-          font-size: 16px;
-          color: #101828;
-          margin-bottom: 14px;
-        }
-        .project2 {
-          font-size: 14px;
-          color: #364153;
-          display: -webkit-box;
-          -webkit-line-clamp: 2;
-          line-clamp: 2; /* 添加标准属性 */
-          -webkit-box-orient: vertical;
-          overflow: hidden;
-          text-overflow: ellipsis;
-        }
-        .project-more {
-          text-align: right;
-          font-size: 14px;
-          color: var(--el-color-primary);
-          margin-top: 18px;
+          &:hover {
+            color: var(--el-color-primary);
+          }
         }
       }
     }
   }
-}
-.solve {
-  width: 100%;
-
-  .solve-head {
+  //项目案例
+  .project-bos {
     width: 100%;
-    background: #ffffff;
-
-    .head-bos {
-      width: 1200px;
-      margin: 0 auto;
-      padding-bottom: 20px;
-    }
-  }
-
-  .nav-bos {
-    border-bottom: 1px solid #e5e7eb;
-    width: 1200px;
-    padding-bottom: 20px;
-
-    .nav-list {
-      height: 32px;
-      padding: 0 12px;
-      background: #f7f8fa;
-      border-radius: 2px 2px 2px 2px;
-      font-size: 14px;
-      color: #4e5969;
-      margin-right: 8px;
-      line-height: 32px;
-      cursor: pointer;
-
-      &.hig {
-        background: #ffe8e8;
-        color: #e7000b;
-      }
-
-      &:hover {
-        color: #e7000b;
-      }
-    }
-  }
-
-  // 数据
-  .data-bos {
-    width: 1200px;
+    max-width: 1500px;
+    min-width: 1200px;
     margin: 0 auto;
     display: flex;
-    gap: 20px;
+    margin-top: 25px;
+    gap: 15px;
     flex-wrap: wrap;
-    padding: 22px 0 40px 0;
-
-    .data-list {
-      width: 386px;
-      height: 302px;
-      background: #ffffff;
+    .project-list {
+      flex: 0 0 calc((100% - 45px) / 3);
+      width: 0;
+      height: 320px;
+      cursor: pointer;
       border-radius: 10px;
       overflow: hidden;
-      cursor: pointer;
+      position: relative;
+      transition:
+        transform 0.2s ease,
+        box-shadow 0.2s ease;
 
-      .data-img {
-        height: 200px;
-        width: 386px;
-      }
-
-      .data-box {
-        height: 102px;
-        width: 386px;
-        padding: 12px 20px;
-
-        .title {
-          font-weight: 600;
-          font-size: 14px;
-          color: #101828;
-        }
-
-        .info {
-          font-size: 12px;
-          color: #364153;
-          margin-top: 4px;
+      &:hover {
+        transform: translateY(-2px);
+        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+        .project1 {
+          color: #e7000b !important;
         }
+      }
 
-        .text {
-          font-size: 14px;
-          color: #e7000b;
-        }
+      .project-box {
+        width: 100%;
+        height: 156px;
+        background: #ffffff;
+        border-radius: 10px;
+        margin-top: -20px;
+        z-index: 2;
+        position: absolute;
+        padding: 20px;
+        border: 1px solid #e5e7eb;
+      }
+      .data-img {
+        width: 100%;
+        height: 180px;
+      }
+      .project1 {
+        font-weight: 600;
+        font-size: 16px;
+        color: #101828;
+        margin-bottom: 14px;
+      }
+      .project2 {
+        font-size: 14px;
+        color: #364153;
+        display: -webkit-box;
+        -webkit-line-clamp: 2;
+        line-clamp: 2; /* 添加标准属性 */
+        -webkit-box-orient: vertical;
+        overflow: hidden;
+        text-overflow: ellipsis;
+      }
+      .project-more {
+        text-align: right;
+        font-size: 14px;
+        color: var(--el-color-primary);
+        margin-top: 18px;
       }
     }
   }
-
-  //分页
-  .pagination-bos {
-    width: 1200px;
+  .table-pagination {
+    width: 100%;
+    min-width: 1200px;
+    max-width: 1500px;
     margin: 0 auto;
-    padding-bottom: 60px;
-    :deep(.el-select__wrapper) {
-      background: #f4f4f4;
-      box-shadow: 0 0 0 1px #e5e6eb inset;
-      border-radius: 2px;
-    }
-    :deep(.el-select__placeholder) {
-      color: #1d2129;
-    }
-    :deep(.el-input__wrapper) {
-      background: #f4f4f4;
-      box-shadow: 0 0 0 1px #e5e6eb inset;
-      border-radius: 2px;
-    }
-    :deep(.el-input__inner) {
-      color: #1d2129;
-    }
-
-    :deep(.btn-prev) {
-      background: #f4f4f4;
-      border: 1px solid #e5e6eb;
-      margin-right: 8px;
-    }
-    :deep(.btn-next) {
-      background: #f4f4f4;
-      border: 1px solid #e5e6eb;
-      margin-left: 8px;
-    }
-    :deep(.el-pager) {
-      gap: 0 8px;
-      li {
-        background: #f4f4f4;
-        border: 1px solid #e5e6eb;
-        color: #1d2129;
-      }
-    }
+    box-sizing: border-box;
+    padding-bottom: 30px;
   }
 }
 </style>

+ 138 - 102
src/views/plan_info/guide.vue

@@ -1,37 +1,36 @@
 <template>
-  <div class="real-pages">
-    <div class="flex-1">
-      <div class="real-info">
+  <div class="guide-pages">
+    <div class="guide-bos">
+      <div class="guide-info">
         <div class="title">{{ dataInfo.title }}</div>
         <div class="time">{{ dataInfo.releaseTime }}</div>
-        <div v-if="dataInfo.content" class="real-html" v-html="dataInfo.content"></div>
+        <div v-if="dataInfo.content" class="guide-html" v-html="dataInfo.content"></div>
       </div>
-    </div>
-    <el-affix :offset="0">
-      <div class="related-bos">
-        <div class="related-box">
-          <div class="flex-row-between related-title">
-            <div>相关专题</div>
-            <div class="flex-row-start related-huan" @click="getList">
-              <el-icon><Sort /></el-icon>
-              <div style="margin-left: 4px">换一换</div>
-            </div>
+      <div class="guide-box">
+        <div class="flex-row-between related-title">
+          <div class="fw-[600]">相关专题</div>
+          <div class="flex-row-start related-huan" @click="getList">
+            <el-icon><Sort /></el-icon>
+            <div style="margin-left: 4px">换一换</div>
           </div>
-          <div class="procure-bos">
-            <div v-for="(item, index) in dataList" :key="index" class="procure-list" @click="onPath('/plan_info/guide?id=' + item.id)">
-              <img :src="item.coverImage" alt="" />
-              <div class="procure1">{{ item.title }}</div>
-              <div class="procure2">{{ item.releaseTime }}</div>
-            </div>
+        </div>
+        <div class="data-bos">
+          <div v-for="(item, index) in dataList" :key="index" class="data-list" @click="onPath('/plan_info/guide?id=' + item.id)">
+            <el-image class="data-img" :src="item.coverImage" fit="cover" />
+            <div class="procure1">{{ item.title }}</div>
+            <div class="procure2">{{ item.releaseTime }}</div>
           </div>
         </div>
+        <div class="flex-row-center w100% no-data" v-if="dataList.length === 0">
+          <el-empty description="暂无数据" />
+        </div>
       </div>
-    </el-affix>
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { onMounted, ref } from 'vue';
+import { onMounted, ref, watch } from 'vue'; // 引入 watch
 import { useRoute, onBeforeRouteUpdate } from 'vue-router';
 import { getPurchaseGuideDetail, getPurchaseGuideList } from '@/api/plan/index';
 import { onPath } from '@/utils/siteConfig';
@@ -42,19 +41,21 @@ const dataInfo = ref<any>({});
 const dataList = ref<any>([]);
 
 // 封装获取详情的逻辑
-const getInfo = () => {
-  // 防御性编程:如果 id 为空,可以不请求或清空数据
-  if (!id.value) return;
+const getInfo = (currentId: any) => {
+  if (!currentId) return;
 
-  // 可选:添加 loading 状态或清空旧数据以避免视觉残留
-  // dataInfo.value = {};
+  // 可选:添加 loading 状态
+  // isLoading.value = true;
 
-  getPurchaseGuideDetail(id.value).then((res) => {
-    if (res.code == 200) {
-      console.log('详情数据已更新,ID:', id.value);
-      dataInfo.value = res.data;
-    }
-  });
+  getPurchaseGuideDetail(currentId)
+    .then((res) => {
+      if (res.code == 200) {
+        dataInfo.value = res.data;
+      }
+    })
+    .finally(() => {
+      // isLoading.value = false;
+    });
 };
 
 // 封装获取列表的逻辑
@@ -65,6 +66,7 @@ const getList = () => {
   }).then((res) => {
     if (res.code == 200) {
       if (res.rows && res.rows.length > 0) {
+        // 保持原有的随机逻辑
         if (res.rows.length <= 2) {
           dataList.value = res.rows;
         } else {
@@ -75,14 +77,18 @@ const getList = () => {
   });
 };
 
-// 核心修复:统一初始化函数
+// 核心初始化函数
 const initData = () => {
-  // 1. 【关键步骤】先从路由获取最新的 ID
-  id.value = route.query.id;
-  console.log('当前路由 ID 已更新为:', id.value);
+  // 1. 获取最新 ID
+  const currentId = route.query.id;
+  id.value = currentId;
+
+  // 2. 请求详情 (传入当前 ID,避免依赖 ref 的异步更新问题)
+  getInfo(currentId);
 
-  // 2. 再请求数据
-  getInfo();
+  // 3. 请求列表 (通常列表不需要随 ID 变化,但如果需求是每次换一换,则保留;如果只需加载一次,可移出)
+  // 注意:原代码中点击“换一换”也会调用 getList,所以这里可以只加载一次,或者每次都刷新看需求
+  // 如果希望每次进入页面都刷新右侧推荐列表,保留此行;否则建议只在 onMounted 中调用一次
   getList();
 };
 
@@ -90,10 +96,25 @@ onMounted(() => {
   initData();
 });
 
-// 修复后的路由更新监听
+// 监听路由查询参数变化
+watch(
+  () => route.query.id,
+  (newId, oldId) => {
+    if (newId !== oldId) {
+      initData();
+    }
+  }
+);
+
+// 兼容 onBeforeRouteUpdate (作为双重保险)
 onBeforeRouteUpdate((to, from, next) => {
-  // 执行初始化逻辑(包含更新 id 和请求数据)
-  initData();
+  // 如果 watch 已经处理了,这里可以只做 next()
+  // 但为了确保万无一失,可以再次调用,或者仅在此处处理一些 watch 无法覆盖的边缘情况
+  if (to.query.id !== from.query.id) {
+    // 注意:如果在 watch 中已经调用了 initData,这里可能会重复请求。
+    // 建议二选一:推荐使用 watch 监听 query 变化,更符合 Vue 3 组合式 API 习惯。
+    // 如果保留此钩子,请确保不要与 watch 冲突,或者在此处直接调用逻辑并 next()
+  }
   next();
 });
 
@@ -112,87 +133,97 @@ function getRandomElements(arr: any, count: any) {
 </script>
 
 <style lang="scss" scoped>
-.real-pages {
+.guide-pages {
   width: 100%;
-  min-width: 1200px;
-  max-width: 1500px;
-  margin: 0 auto;
-  display: flex;
-  gap: 0 20px;
-  padding-bottom: 50px;
-  .real-info {
+  .guide-bos {
     width: 100%;
-    background: #ffffff;
-    border-radius: 10px;
-    padding: 20px;
-    .title {
-      font-size: 20px;
-      color: #666666;
-    }
-    .time {
-      font-size: 14px;
-      color: #999999;
-      margin: 10px 0 18px 0;
-    }
-    .real-html {
-      width: 100%;
-      :deep(img) {
-        max-width: 100%;
-        height: auto;
+    max-width: 1500px;
+    min-width: 1200px;
+    margin: 0 auto;
+    padding-bottom: 30px;
+    display: flex;
+    gap: 20px;
+    position: relative;
+    .guide-info {
+      min-height: calc(100vh - 280px);
+      background: #ffffff;
+      border-radius: 5px;
+      padding: 20px;
+      flex: 1;
+      .title {
+        font-size: 20px;
+        color: #666666;
+      }
+      .time {
+        font-size: 14px;
+        color: #999999;
+        margin: 10px 0 18px 0;
+      }
+      .guide-html {
+        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;
+        }
       }
     }
-    .img {
-      width: 679px;
-      height: 351px;
-      border-radius: 10px;
-    }
-    .head {
-      font-weight: 600;
-      font-size: 14px;
-      color: #101828;
-      margin: 10px 0 18px 0;
-    }
-    .info {
-      font-size: 14px;
-      color: #364153;
-      margin-bottom: 15px;
-    }
-  }
-
-  .related-bos {
-    flex: 1;
-    .related-box {
-      width: 100%;
-      background-color: #ffffff;
-      border-radius: 10px;
-      padding: 20px;
+    .guide-box {
+      width: 400px;
+      position: sticky;
+      top: 0;
       .related-title {
-        font-size: 16px;
-        color: #666666;
+        font-size: 15px;
+        background-color: #ffffff;
+        border-radius: 5px;
+        padding: 12px 20px;
         .related-huan {
           font-size: 14px;
           cursor: pointer;
+          &:hover {
+            color: #e7000b !important;
+          }
         }
       }
-      // 采购指南
-      .procure-bos {
+      .data-bos {
         width: 100%;
         display: flex;
         flex-direction: column;
         margin-top: 12px;
         gap: 12px 0px;
-        .procure-list {
+        .data-list {
           width: 100%;
           height: 268px;
-          background: #ffffff;
+          background-color: #ffffff;
           cursor: pointer;
-          border-radius: 10px;
+          border-radius: 5px;
           overflow: hidden;
-          border: 1px solid #d0d5dd;
-          img {
+          transition:
+            transform 0.2s ease,
+            box-shadow 0.2s ease;
+          display: flex;
+          flex-direction: column;
+
+          &:hover {
+            transform: translateY(-2px);
+            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+            .procure1 {
+              color: #e7000b !important;
+            }
+          }
+          .data-img {
             width: 100%;
             height: 200px;
-            border-bottom: 1px solid #d0d5dd;
           }
           .procure1 {
             padding: 12px 0 4px 20px;
@@ -207,6 +238,11 @@ function getRandomElements(arr: any, count: any) {
           }
         }
       }
+      .no-data {
+        background-color: #ffffff;
+        border-radius: 5px;
+        margin-top: 10px;
+      }
     }
   }
 }

+ 36 - 96
src/views/plan_info/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="solve-page">
     <div class="solve-bos">
-      <img class="solve-img" :src="dataInfo.coverImage" alt="" />
+      <el-image class="solve-img" :src="dataInfo.coverImage" fit="cover" />
       <div v-for="(item, index) in dataList" :key="index">
         <!-- 标题 -->
         <div class="solve-title">
@@ -15,9 +15,9 @@
               v-for="(item1, index1) in item.list"
               :key="index1"
               class="data-list"
-              @click="onPath('/item?id=' + item.id + '&productNo=' + item.productNo)"
+              @click="onPath('/item?id=' + item1.id + '&productNo=' + item1.productNo)"
             >
-              <img class="data-img" :src="item1.productImage" alt="" />
+              <el-image class="data-img" :src="item1.productImage" fit="cover" />
               <div class="data-title ellipsis">{{ item1.itemName }}</div>
               <div class="money">
                 <span class="money1">¥{{ item1.memberPrice }}</span>
@@ -26,17 +26,16 @@
               <div class="data-cat" @click.stop="onCart(item1)">加入购物车</div>
             </div>
           </div>
+          <div class="flex-row-center w100%" v-if="item.list.length === 0">
+            <el-empty description="暂无数据" />
+          </div>
           <!-- 游标分页控制 -->
-          <pagination
-            v-show="item.list.length > 0"
-            v-model:page="item.pageNum"
-            v-model:limit="item.pageSize"
-            :cursor-mode="true"
-            :has-more="item.hasMore"
-            @pagination="getList(item)"
-          />
+          <TablePagination v-model:page="item.pageNum" v-model:page-size="item.pageSize" :total="item.total" @change="getList(item)" />
         </div>
       </div>
+      <div class="flex-row-center w100%" v-if="dataList.length === 0">
+        <el-empty description="暂无数据" />
+      </div>
     </div>
   </div>
 </template>
@@ -73,7 +72,7 @@ const getInfo = async () => {
           item.pageNum = 1;
           item.hasMore = false;
           const productList: any = await onGoods(item); // 等待每个分组的商品列表加载完成
-          item.hasMore = productList.total > item.pageSize * item.pageNum;
+          item.total = productList.total;
           return {
             ...item,
             list: productList.rows // 将商品列表赋值给 item.List
@@ -81,7 +80,6 @@ const getInfo = async () => {
         })
       );
       dataList.value = updatedData; // 更新 dataList
-      console.log(dataList.value);
     }
   } catch (error) {
     console.error('获取数据失败:', error);
@@ -106,8 +104,7 @@ const getList = (row: any) => {
   getProcurementProgramGroupProductList({ groupId: row.id, pageSize: row.pageSize, pageNum: row.pageNum }).then((res) => {
     if (res.code == 200) {
       row.list = res.rows;
-      // 判断是否还有更多数据
-      row.hasMore = res.total > row.pageSize * row.pageNum;
+      row.total = res.total;
     }
   });
 };
@@ -131,6 +128,7 @@ const onCart = (row: any) => {
 <style lang="scss" scoped>
 .solve-page {
   width: 100%;
+  background-color: #ffffff;
 
   .solve-bos {
     width: 100%;
@@ -141,36 +139,28 @@ const onCart = (row: any) => {
     .solve-img {
       width: 100%;
       height: 380px;
-      border-radius: 10px;
-      margin-top: 20px;
+      border-radius: 5px;
     }
 
     .solve-title {
       width: 100%;
-      padding: 10px 20px;
-      background-color: #ffffff;
-      border-radius: 10px;
-      margin-top: 20px;
+      margin-top: 10px;
       .title1 {
-        font-size: 24px;
+        font-size: 20px;
         font-weight: bold;
-        margin-bottom: 20px;
+        margin-bottom: 5px;
       }
       .title2 {
-        background-color: #f2f2f2;
-        padding: 17px 20px;
         font-size: 14px;
         color: #666666;
-        border-radius: 10px;
+        border-bottom: 1px solid #e5e7eb;
+        padding-bottom: 10px;
       }
     }
 
     //数据
     .data-bos {
-      background-color: #ffffff;
-      padding: 20px 20px;
-      margin-top: 20px;
-      border-radius: 10px;
+      margin-top: 15px;
       .data-box {
         width: 100%;
         display: flex;
@@ -180,13 +170,26 @@ const onCart = (row: any) => {
           flex: 0 0 calc((100% - 80px) / 5);
           width: 0;
           background: #f4f4f4;
-          border-radius: 10px;
+          border-radius: 5px;
           padding: 20px 20px 22px 20px;
           cursor: pointer;
+          transition:
+            transform 0.2s ease,
+            box-shadow 0.2s ease;
+          display: flex;
+          flex-direction: column;
+
+          &:hover {
+            transform: translateY(-2px);
+            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+            .data-title {
+              color: #e7000b !important;
+            }
+          }
           .data-img {
-            width: 184px;
+            width: 100%;
             height: 184px;
-            border-radius: 10px;
+            border-radius: 5px;
           }
           .data-title {
             margin-top: 4px;
@@ -221,69 +224,6 @@ const onCart = (row: any) => {
         }
       }
     }
-
-    .title {
-      font-weight: 600;
-      font-size: 20px;
-      color: #101828;
-      margin-top: 20px;
-    }
-    .info {
-      font-size: 14px;
-      color: #364153;
-      margin-top: 8px;
-      border-bottom: 1px solid #e5e7eb;
-      padding-bottom: 20px;
-    }
-    .filter-bos {
-      padding-bottom: 20px;
-      border-bottom: 1px solid #e5e7eb;
-      .filter-list {
-        margin-top: 20px;
-
-        .filter-title {
-          font-size: 14px;
-          color: #101828;
-          margin-right: 40px;
-        }
-
-        .filter-item {
-          font-size: 14px;
-          color: #364153;
-          margin-right: 30px;
-          cursor: pointer;
-
-          &.hig {
-            color: #e7000b;
-          }
-        }
-      }
-    }
-    .nav-bos {
-      width: 100%;
-      padding: 20px 0;
-
-      .nav-list {
-        height: 32px;
-        padding: 0 12px;
-        background: #f7f8fa;
-        border-radius: 2px 2px 2px 2px;
-        font-size: 14px;
-        color: #4e5969;
-        margin-right: 8px;
-        line-height: 32px;
-        cursor: pointer;
-
-        &.hig {
-          background: #ffe8e8;
-          color: #e7000b;
-        }
-
-        &:hover {
-          color: #e7000b;
-        }
-      }
-    }
   }
 }
 </style>

+ 30 - 87
src/views/plan_info/procure.vue

@@ -1,15 +1,11 @@
 <template>
   <div class="solve-page">
     <div class="solve-bos">
-      <img class="solve-img" :src="dataInfo.coverImage" alt="" />
+      <el-image class="solve-img" :src="dataInfo.coverImage" fit="cover" />
       <div v-for="(item, index) in dataList" :key="index">
         <!-- 标题 -->
-        <div class="solve-title flex-row-between">
+        <div class="solve-title">
           <div class="title1">{{ item.info.brandName }}</div>
-          <!-- <div class="title2 flex-row-start">
-            更多
-            <el-icon><arrow-right /></el-icon>
-          </div> -->
         </div>
         <!-- 数据 -->
         <div class="data-bos">
@@ -20,8 +16,8 @@
               class="data-list"
               @click="onPath('/item?id=' + item1.id + '&productNo=' + item1.productNo)"
             >
-              <img class="data-img" :src="item1.productImage" alt="" />
-              <div class="data-title">{{ item1.itemName }}</div>
+              <el-image class="data-img" :src="item1.productImage" fit="cover" />
+              <div class="data-title ellipsis">{{ item1.itemName }}</div>
               <div class="money">
                 <span class="money1">¥{{ item1.memberPrice }}</span>
                 <span class="money2">¥{{ item1.marketPrice }}</span>
@@ -29,8 +25,14 @@
               <div class="data-cat" @click.stop="onCart(item1)">加入购物车</div>
             </div>
           </div>
+          <div class="flex-row-center w100%" v-if="item.list.length === 0">
+            <el-empty description="暂无数据" />
+          </div>
         </div>
       </div>
+      <div class="flex-row-center w100%" v-if="dataList.length === 0">
+        <el-empty description="暂无数据" />
+      </div>
     </div>
   </div>
 </template>
@@ -105,43 +107,36 @@ const onCart = (row: any) => {
 <style lang="scss" scoped>
 .solve-page {
   width: 100%;
+  background-color: #ffffff;
 
   .solve-bos {
     width: 100%;
-    max-width: 1500px;
     min-width: 1200px;
+    max-width: 1500px;
     margin: 0 auto;
     padding-bottom: 30px;
     .solve-img {
       width: 100%;
       height: 380px;
       border-radius: 5px;
-      margin-top: 20px;
     }
 
     .solve-title {
       width: 100%;
-      padding: 15px 20px;
-      background-color: #ffffff;
-      border-radius: 5px;
       margin-top: 10px;
       .title1 {
-        font-size: 16px;
+        font-size: 20px;
         font-weight: bold;
-      }
-      .title2 {
-        font-size: 14px;
-        color: #666666;
+        border-bottom: 1px solid #e5e7eb;
+        padding-bottom: 10px;
       }
     }
 
     //数据
     .data-bos {
-      background-color: #ffffff;
-      padding: 20px 20px;
-      margin-top: 10px;
-      border-radius: 5px;
+      margin-top: 15px;
       .data-box {
+        width: 100%;
         display: flex;
         flex-wrap: wrap;
         gap: 20px;
@@ -149,13 +144,24 @@ const onCart = (row: any) => {
           flex: 0 0 calc((100% - 80px) / 5);
           width: 0;
           background: #f4f4f4;
-          border-radius: 10px;
+          border-radius: 5px;
           padding: 20px 20px 22px 20px;
           cursor: pointer;
+          transition:
+            transform 0.2s ease,
+            box-shadow 0.2s ease;
+
+          &:hover {
+            transform: translateY(-2px);
+            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+            .data-title {
+              color: #e7000b !important;
+            }
+          }
           .data-img {
             width: 100%;
             height: 184px;
-            border-radius: 10px;
+            border-radius: 5px;
           }
           .data-title {
             margin-top: 4px;
@@ -190,69 +196,6 @@ const onCart = (row: any) => {
         }
       }
     }
-
-    .title {
-      font-weight: 600;
-      font-size: 20px;
-      color: #101828;
-      margin-top: 20px;
-    }
-    .info {
-      font-size: 14px;
-      color: #364153;
-      margin-top: 8px;
-      border-bottom: 1px solid #e5e7eb;
-      padding-bottom: 20px;
-    }
-    .filter-bos {
-      padding-bottom: 20px;
-      border-bottom: 1px solid #e5e7eb;
-      .filter-list {
-        margin-top: 20px;
-
-        .filter-title {
-          font-size: 14px;
-          color: #101828;
-          margin-right: 40px;
-        }
-
-        .filter-item {
-          font-size: 14px;
-          color: #364153;
-          margin-right: 30px;
-          cursor: pointer;
-
-          &.hig {
-            color: #e7000b;
-          }
-        }
-      }
-    }
-    .nav-bos {
-      width: 1200px;
-      padding: 20px 0;
-
-      .nav-list {
-        height: 32px;
-        padding: 0 12px;
-        background: #f7f8fa;
-        border-radius: 2px 2px 2px 2px;
-        font-size: 14px;
-        color: #4e5969;
-        margin-right: 8px;
-        line-height: 32px;
-        cursor: pointer;
-
-        &.hig {
-          background: #ffe8e8;
-          color: #e7000b;
-        }
-
-        &:hover {
-          color: #e7000b;
-        }
-      }
-    }
   }
 }
 </style>

+ 184 - 107
src/views/plan_info/project.vue

@@ -1,23 +1,28 @@
 <template>
-  <div class="real-pages">
-    <div>
-      <div class="real-info">
+  <div class="guide-pages">
+    <div class="guide-bos">
+      <div class="guide-info">
         <div class="title">{{ dataInfo.caseTitle }}</div>
-        <div class="time">{{ dataInfo.projectBrief }}</div>
-        <div class="real-html" v-html="dataInfo.projectDetails"></div>
+        <div class="time">{{ dataInfo.releaseprojectBriefTime }}</div>
+        <div v-if="dataInfo.projectDetails" class="guide-html" v-html="dataInfo.projectDetails"></div>
       </div>
-    </div>
-    <div class="related-bos">
-      <div class="flex-row-between related-title">
-        <div>其他客户案例</div>
-        <div class="flex-row-start related-huan" @click="onPath('/plan/project')">
-          <div>更多</div>
-          <el-icon style="margin-left: 4px"><ArrowRight /></el-icon>
+      <div class="guide-box">
+        <div class="flex-row-between related-title">
+          <div class="fw-[600]">其他客户案例</div>
+          <div class="flex-row-start related-huan" @click="onPath('/plan/project')">
+            <div>更多</div>
+            <el-icon style="margin-left: 4px"><ArrowRight /></el-icon>
+          </div>
         </div>
-      </div>
-      <div class="related-box">
-        <div v-for="(item, index) in dataList" :key="index" class="procure-list ellipsis" @click="onPath('/plan_info/project?id=' + item.id)">
-          {{ item.caseTitle }}
+        <div class="data-bos">
+          <div v-for="(item, index) in dataList" :key="index" class="data-list" @click="onPath('/plan_info/project?id=' + item.id)">
+            <el-image class="data-img" :src="item.uploadProgram" fit="cover" />
+            <div class="procure1">{{ item.caseTitle }}</div>
+            <div class="procure2">{{ item.releaseTime }}</div>
+          </div>
+        </div>
+        <div class="flex-row-center w100% no-data" v-if="dataList.length === 0">
+          <el-empty description="暂无数据" />
         </div>
       </div>
     </div>
@@ -25,25 +30,47 @@
 </template>
 
 <script setup lang="ts">
-import { onPath } from '@/utils/siteConfig';
+import { onMounted, ref, watch } from 'vue'; // 引入 watch
+import { useRoute, onBeforeRouteUpdate } from 'vue-router';
 import { getProjectCaseDetail, getProjectCaseAllList } from '@/api/plan/index';
+import { onPath } from '@/utils/siteConfig';
+
+const route = useRoute();
 const id = ref<any>(null);
 const dataInfo = ref<any>({});
 const dataList = ref<any>([]);
-const route = useRoute();
 
-onMounted(() => {
-  id.value = route.query.id;
-  getInfo();
-  getList();
-});
+// 封装获取详情的逻辑
+const getInfo = (currentId: any) => {
+  if (!currentId) return;
 
-const getInfo = () => {
-  getProjectCaseDetail(id.value).then((res) => {
-    if (res.code == 200) {
-      dataInfo.value = res.data;
-    }
-  });
+  // 可选:添加 loading 状态
+  // isLoading.value = true;
+
+  getProjectCaseDetail(currentId)
+    .then((res) => {
+      if (res.code == 200) {
+        dataInfo.value = res.data;
+      }
+    })
+    .finally(() => {
+      // isLoading.value = false;
+    });
+};
+
+// 核心初始化函数
+const initData = () => {
+  // 1. 获取最新 ID
+  const currentId = route.query.id;
+  id.value = currentId;
+
+  // 2. 请求详情 (传入当前 ID,避免依赖 ref 的异步更新问题)
+  getInfo(currentId);
+
+  // 3. 请求列表 (通常列表不需要随 ID 变化,但如果需求是每次换一换,则保留;如果只需加载一次,可移出)
+  // 注意:原代码中点击“换一换”也会调用 getList,所以这里可以只加载一次,或者每次都刷新看需求
+  // 如果希望每次进入页面都刷新右侧推荐列表,保留此行;否则建议只在 onMounted 中调用一次
+  getList();
 };
 
 const getList = () => {
@@ -52,99 +79,149 @@ const getList = () => {
     pageNum: 1
   }).then((res) => {
     if (res.code == 200) {
-      dataList.value = res.rows;
+      dataList.value = res.rows ? res.rows.slice(0, 5) : [];
     }
   });
 };
+
+onMounted(() => {
+  initData();
+});
+
+// 监听路由查询参数变化
+watch(
+  () => route.query.id,
+  (newId, oldId) => {
+    if (newId !== oldId) {
+      initData();
+    }
+  }
+);
+
+// 兼容 onBeforeRouteUpdate (作为双重保险)
+onBeforeRouteUpdate((to, from, next) => {
+  // 如果 watch 已经处理了,这里可以只做 next()
+  // 但为了确保万无一失,可以再次调用,或者仅在此处处理一些 watch 无法覆盖的边缘情况
+  if (to.query.id !== from.query.id) {
+    // 注意:如果在 watch 中已经调用了 initData,这里可能会重复请求。
+    // 建议二选一:推荐使用 watch 监听 query 变化,更符合 Vue 3 组合式 API 习惯。
+    // 如果保留此钩子,请确保不要与 watch 冲突,或者在此处直接调用逻辑并 next()
+  }
+  next();
+});
 </script>
 
 <style lang="scss" scoped>
-.real-pages {
+.guide-pages {
   width: 100%;
-  max-width: 1500px;
-  min-width: 1200px;
-  margin: 0 auto;
-  display: flex;
-  gap: 0 20px;
-  padding-bottom: 50px;
-  .real-info {
-    flex: 1;
-    background: #ffffff;
-    border-radius: 10px;
-    padding: 20px;
-    .title {
-      font-size: 20px;
-      color: #666666;
-    }
-    .time {
-      font-size: 14px;
-      color: #999999;
-      margin: 10px 0 18px 0;
-    }
-    .real-html {
-      width: 679px;
-      :deep(img) {
-        max-width: 100%;
-        height: auto;
+  .guide-bos {
+    width: 100%;
+    max-width: 1500px;
+    min-width: 1200px;
+    margin: 0 auto;
+    padding-bottom: 30px;
+    display: flex;
+    gap: 20px;
+    position: relative;
+    .guide-info {
+      min-height: calc(100vh - 280px);
+      background: #ffffff;
+      border-radius: 5px;
+      padding: 20px;
+      flex: 1;
+      .title {
+        font-size: 20px;
+        color: #666666;
       }
-    }
-    .img {
-      width: 679px;
-      height: 351px;
-      border-radius: 10px;
-    }
-    .head {
-      font-weight: 600;
-      font-size: 14px;
-      color: #101828;
-      margin: 10px 0 18px 0;
-    }
-    .info {
-      font-size: 14px;
-      color: #364153;
-      margin-bottom: 15px;
-    }
-  }
-
-  .related-bos {
-    width: 400px;
-    .related-title {
-      font-size: 16px;
-      color: #323232;
-      width: 100%;
-      background-color: #ffffff;
-      border-radius: 10px;
-      padding: 10px 20px;
-      .related-huan {
+      .time {
         font-size: 14px;
-        cursor: pointer;
-        color: #969696;
-        &:hover {
-          color: var(--el-color-primary);
+        color: #999999;
+        margin: 10px 0 18px 0;
+      }
+      .guide-html {
+        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;
         }
       }
     }
-    .related-box {
-      width: 100%;
-      background-color: #ffffff;
-      border-radius: 10px;
-      padding: 10px 20px;
-      margin-top: 20px;
-      display: flex;
-      flex-direction: column;
-      gap: 15px;
-
-      .procure-list {
+    .guide-box {
+      width: 400px;
+      position: sticky;
+      top: 0;
+      .related-title {
+        font-size: 15px;
+        background-color: #ffffff;
+        border-radius: 5px;
+        padding: 12px 20px;
+        .related-huan {
+          font-size: 14px;
+          cursor: pointer;
+          &:hover {
+            color: #e7000b !important;
+          }
+        }
+      }
+      .data-bos {
         width: 100%;
-        background: #ffffff;
-        cursor: pointer;
-        border-radius: 10px;
-        font-size: 14px;
-        color: #333333;
-        &:hover {
-          color: var(--el-color-primary);
+        display: flex;
+        flex-direction: column;
+        margin-top: 12px;
+        gap: 12px 0px;
+        .data-list {
+          width: 100%;
+          height: 268px;
+          background-color: #ffffff;
+          cursor: pointer;
+          border-radius: 5px;
+          overflow: hidden;
+          transition:
+            transform 0.2s ease,
+            box-shadow 0.2s ease;
+          display: flex;
+          flex-direction: column;
+
+          &:hover {
+            transform: translateY(-2px);
+            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+            .procure1 {
+              color: #e7000b !important;
+            }
+          }
+          .data-img {
+            width: 100%;
+            height: 200px;
+          }
+          .procure1 {
+            padding: 12px 0 4px 20px;
+            font-weight: 600;
+            font-size: 14px;
+            color: #101828;
+          }
+          .procure2 {
+            padding-left: 20px;
+            font-size: 12px;
+            color: #364153;
+          }
         }
       }
+      .no-data {
+        background-color: #ffffff;
+        border-radius: 5px;
+        margin-top: 10px;
+      }
     }
   }
 }

+ 38 - 23
src/views/search/brand.vue

@@ -17,8 +17,8 @@
         </div>
       </div>
       <div class="head-bos head-bos1">
-        <div class="head-title">字母:</div>
-        <div class="head-box">
+        <div class="head-title !pt-[0px]">字母:</div>
+        <div class="head-box" style="align-items: center">
           <div
             @click="onHead(item, 'initial')"
             class="classify-list"
@@ -31,19 +31,24 @@
         </div>
       </div>
       <div class="head-bos head-bos1">
-        <div class="head-title">品牌名称:</div>
-        <div class="head-box">
-          <el-input v-model="httpObj.name" style="width: 240px" placeholder="请输入品牌名称" />
+        <div class="head-title !pt-[0px]">品牌名称:</div>
+        <div class="head-box head-box1">
+          <el-input clearable v-model="httpObj.name" style="width: 240px" placeholder="请输入品牌名称">
+            <template #append>
+              <el-button @click="getBrand" :icon="Search" />
+            </template>
+          </el-input>
         </div>
       </div>
     </div>
     <!-- 商品 -->
     <div class="expert-bos">
-      <div v-for="(item, index) in dataList" :key="index" class="expert-list" @click="onPath('/item?id=' + item.id + '&productNo=' + item.productNo)">
-        <img :src="item.brandBigImage" alt="" />
+      <div v-for="(item, index) in dataList" :key="index" class="expert-list" @click="onPath('/search?type=1&brandId=' + item.id)">
+        <el-image class="data-img" :src="item.brandLogo" fit="cover" />
         <div class="itemName ellipsis">{{ item.brandName || '' }}</div>
       </div>
     </div>
+    <TablePagination v-model:page="httpObj.pageNum" v-model:page-size="httpObj.pageSize" :total="total" @change="getList" />
     <!-- 游标分页控制 -->
     <pagination
       v-show="dataList.length > 0"
@@ -62,6 +67,8 @@ import { getBrandByCategoryList, getBrandPage } from '@/api/search/index';
 import { getProductCategoryTree } from '@/api/home/index';
 import { onPath } from '@/utils/siteConfig';
 import Pagination from '@/components/Pagination/index.vue';
+import { Search } from '@element-plus/icons-vue';
+const total = ref(0);
 const route = useRoute();
 const type = ref<any>(1);
 const dataList = ref<any>([]);
@@ -115,8 +122,7 @@ const getList = () => {
   getBrandByCategoryList(httpObj.value).then((res) => {
     if (res.code == 200) {
       dataList.value = res.rows;
-      // 判断是否还有更多数据
-      hasMore.value = res.total > httpObj.value.pageSize * httpObj.value.pageNum;
+      total.value = res.total;
     }
   });
 };
@@ -124,20 +130,16 @@ const getList = () => {
 //头部分类
 
 const onHead = (item: any, type: string) => {
-  if (type == 'topCategoryId') {
-    if (item.id) {
-      getBrand();
-    } else {
-    }
-  }
   httpObj.value[type] = item.id;
+  getBrand();
 };
 
 //查询品牌
 const getBrand = () => {
   getBrandPage({
-    categoryName: categoryName.value,
-    categoryId: httpObj.value.topCategoryId,
+    initial: httpObj.value.initial,
+    categoryId: httpObj.value.categoryId,
+    name: httpObj.value.name,
     pageSize: 100,
     pageNum: 1
   }).then((res) => {
@@ -209,8 +211,8 @@ onMounted(() => {
   .search-head {
     width: 100%;
     background: #ffffff;
-    border-radius: 10px;
-    padding: 0 15px 15px 15px;
+    border-radius: 5px;
+    padding: 0 15px 0px 15px;
     font-size: 14px;
     color: #101828;
 
@@ -218,7 +220,7 @@ onMounted(() => {
       display: flex;
 
       &.head-bos1 {
-        // align-items: center;
+        align-items: center;
       }
 
       .head-title {
@@ -234,6 +236,9 @@ onMounted(() => {
         gap: 10px 15px;
         border-bottom: 1px solid #e5e7eb;
         padding: 15px 0;
+        &.head-box1 {
+          border-bottom: 0px solid #e5e7eb;
+        }
 
         .classify-list {
           cursor: pointer;
@@ -247,7 +252,6 @@ onMounted(() => {
   }
 
   // 商品
-  // 行家精选
   .expert-bos {
     width: 100%;
     display: flex;
@@ -259,11 +263,22 @@ onMounted(() => {
       flex: 0 0 calc((100% - 75px) / 6);
       height: 140px;
       background: #ffffff;
-      border-radius: 10px;
+      border-radius: 5px;
       padding: 20px;
       cursor: pointer;
+      transition:
+        transform 0.2s ease,
+        box-shadow 0.2s ease;
+
+      &:hover {
+        transform: translateY(-2px);
+        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+        .itemName {
+          color: #e7000b !important;
+        }
+      }
 
-      img {
+      .data-img {
         width: 100%;
         height: 70px;
       }

+ 30 - 3
src/views/search/index.vue

@@ -38,7 +38,15 @@
       <div class="head-bos" v-if="type == 1">
         <div class="head-title">品牌:</div>
         <div class="head-box">
-          <el-select v-model="httpObj.brandId" filterable remote :remote-method="remoteMethod" placeholder="请输入名牌名称" style="width: 240px">
+          <el-select
+            @change="getList"
+            v-model="httpObj.brandId"
+            filterable
+            remote
+            :remote-method="remoteMethod"
+            placeholder="请输入名牌名称"
+            style="width: 240px"
+          >
             <el-option v-for="item in brandList" :key="item.id" :label="item.brandName" :value="item.id" />
           </el-select>
         </div>
@@ -151,7 +159,7 @@
 </template>
 
 <script setup lang="ts">
-import { getPcProductPage, getBrandPage, getBrandByCategoryList } from '@/api/search/index';
+import { getPcProductPage, getBrandPage, getBrandByCategoryList, getBrandDetail } from '@/api/search/index';
 import { getProductCategoryTree } from '@/api/home/index';
 import { onPath } from '@/utils/siteConfig';
 import Pagination from '@/components/Pagination/index.vue';
@@ -303,6 +311,19 @@ const getBrand2 = () => {
   });
 };
 
+const getBrandInfo = () => {
+  getBrandDetail(httpObj.value.brandId).then((res) => {
+    if (res.code == 200) {
+      brandList.value = [
+        {
+          brandName: res.data.brandName,
+          id: res.data.id
+        }
+      ];
+    }
+  });
+};
+
 //筛选品牌
 const remoteMethod = (res: any) => {
   if (res) {
@@ -343,13 +364,19 @@ const onSort = (type: number) => {
 const initData = () => {
   httpObj.value.searchKeyword = '';
   httpObj.value.topCategoryId = '';
+  httpObj.value.brandId = '';
   httpObj.value.pageNum = 1;
   type.value = route.query.type;
   if (route.query.input) {
     httpObj.value.searchKeyword = route.query.input;
   }
   if (type.value == 1) {
-    getBrand1();
+    if (route.query.brandId) {
+      httpObj.value.brandId = Number(route.query.brandId);
+      getBrandInfo();
+    } else {
+      getBrand1();
+    }
     if (route.query.topCategoryId) {
       httpObj.value.topCategoryId = route.query.topCategoryId;
     }