|
|
@@ -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;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|