guide.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. <template>
  2. <div class="guide-pages">
  3. <div class="guide-bos">
  4. <div class="guide-info">
  5. <div class="title">{{ dataInfo.title }}</div>
  6. <div class="time">{{ dataInfo.releaseTime }}</div>
  7. <div v-if="dataInfo.content" class="guide-html" v-html="dataInfo.content"></div>
  8. </div>
  9. <div class="guide-box">
  10. <div class="flex-row-between related-title">
  11. <div class="fw-[600]">相关专题</div>
  12. <div class="flex-row-start related-huan" @click="getList">
  13. <el-icon><Sort /></el-icon>
  14. <div style="margin-left: 4px">换一换</div>
  15. </div>
  16. </div>
  17. <div class="data-bos">
  18. <div v-for="(item, index) in dataList" :key="index" class="data-list" @click="onPath('/plan_info/guide?id=' + item.id)">
  19. <el-image class="data-img" :src="item.coverImage" fit="cover" />
  20. <div class="procure1">{{ item.title }}</div>
  21. <div class="procure2">{{ item.releaseTime }}</div>
  22. </div>
  23. </div>
  24. <div class="flex-row-center w100% no-data" v-if="dataList.length === 0">
  25. <el-empty description="暂无数据" />
  26. </div>
  27. </div>
  28. </div>
  29. </div>
  30. </template>
  31. <script setup lang="ts">
  32. import { onMounted, ref, watch } from 'vue'; // 引入 watch
  33. import { useRoute, onBeforeRouteUpdate } from 'vue-router';
  34. import { getPurchaseGuideDetail, getPurchaseGuideList } from '@/api/plan/index';
  35. import { onPath } from '@/utils/siteConfig';
  36. const route = useRoute();
  37. const id = ref<any>(null);
  38. const dataInfo = ref<any>({});
  39. const dataList = ref<any>([]);
  40. // 封装获取详情的逻辑
  41. const getInfo = (currentId: any) => {
  42. if (!currentId) return;
  43. // 可选:添加 loading 状态
  44. // isLoading.value = true;
  45. getPurchaseGuideDetail(currentId)
  46. .then((res) => {
  47. if (res.code == 200) {
  48. dataInfo.value = res.data;
  49. }
  50. })
  51. .finally(() => {
  52. // isLoading.value = false;
  53. });
  54. };
  55. // 封装获取列表的逻辑
  56. const getList = () => {
  57. getPurchaseGuideList({
  58. pageSize: 10,
  59. pageNum: 1
  60. }).then((res) => {
  61. if (res.code == 200) {
  62. if (res.rows && res.rows.length > 0) {
  63. // 保持原有的随机逻辑
  64. if (res.rows.length <= 2) {
  65. dataList.value = res.rows;
  66. } else {
  67. dataList.value = getRandomElements(res.rows, 3);
  68. }
  69. }
  70. }
  71. });
  72. };
  73. // 核心初始化函数
  74. const initData = () => {
  75. // 1. 获取最新 ID
  76. const currentId = route.query.id;
  77. id.value = currentId;
  78. // 2. 请求详情 (传入当前 ID,避免依赖 ref 的异步更新问题)
  79. getInfo(currentId);
  80. // 3. 请求列表 (通常列表不需要随 ID 变化,但如果需求是每次换一换,则保留;如果只需加载一次,可移出)
  81. // 注意:原代码中点击“换一换”也会调用 getList,所以这里可以只加载一次,或者每次都刷新看需求
  82. // 如果希望每次进入页面都刷新右侧推荐列表,保留此行;否则建议只在 onMounted 中调用一次
  83. getList();
  84. };
  85. onMounted(() => {
  86. initData();
  87. });
  88. // 监听路由查询参数变化
  89. watch(
  90. () => route.query.id,
  91. (newId, oldId) => {
  92. if (newId !== oldId) {
  93. initData();
  94. }
  95. }
  96. );
  97. // 兼容 onBeforeRouteUpdate (作为双重保险)
  98. onBeforeRouteUpdate((to, from, next) => {
  99. // 如果 watch 已经处理了,这里可以只做 next()
  100. // 但为了确保万无一失,可以再次调用,或者仅在此处处理一些 watch 无法覆盖的边缘情况
  101. if (to.query.id !== from.query.id) {
  102. // 注意:如果在 watch 中已经调用了 initData,这里可能会重复请求。
  103. // 建议二选一:推荐使用 watch 监听 query 变化,更符合 Vue 3 组合式 API 习惯。
  104. // 如果保留此钩子,请确保不要与 watch 冲突,或者在此处直接调用逻辑并 next()
  105. }
  106. next();
  107. });
  108. function getRandomElements(arr: any, count: any) {
  109. if (count > arr.length) {
  110. throw new Error('要取的数量不能超过数组长度');
  111. }
  112. const copy = [...arr];
  113. const result = [];
  114. for (let i = 0; i < count; i++) {
  115. const randomIndex = Math.floor(Math.random() * copy.length);
  116. result.push(copy.splice(randomIndex, 1)[0]);
  117. }
  118. return result;
  119. }
  120. </script>
  121. <style lang="scss" scoped>
  122. .guide-pages {
  123. width: 100%;
  124. .guide-bos {
  125. width: 100%;
  126. max-width: 1500px;
  127. min-width: 1200px;
  128. margin: 0 auto;
  129. padding-bottom: 30px;
  130. display: flex;
  131. gap: 20px;
  132. position: relative;
  133. .guide-info {
  134. min-height: calc(100vh - 280px);
  135. background: #ffffff;
  136. border-radius: 5px;
  137. padding: 20px;
  138. flex: 1;
  139. .title {
  140. font-size: 20px;
  141. color: #666666;
  142. }
  143. .time {
  144. font-size: 14px;
  145. color: #999999;
  146. margin: 10px 0 18px 0;
  147. }
  148. .guide-html {
  149. width: 100%;
  150. :deep(img) {
  151. width: 100%;
  152. display: block;
  153. margin: 0;
  154. padding: 0;
  155. }
  156. :deep(p) {
  157. margin: 0;
  158. padding: 0;
  159. }
  160. :deep(*) {
  161. margin: 0;
  162. padding: 0;
  163. box-sizing: border-box;
  164. }
  165. }
  166. }
  167. .guide-box {
  168. width: 400px;
  169. position: sticky;
  170. top: 0;
  171. .related-title {
  172. font-size: 15px;
  173. background-color: #ffffff;
  174. border-radius: 5px;
  175. padding: 12px 20px;
  176. .related-huan {
  177. font-size: 14px;
  178. cursor: pointer;
  179. &:hover {
  180. color: #e7000b !important;
  181. }
  182. }
  183. }
  184. .data-bos {
  185. width: 100%;
  186. display: flex;
  187. flex-direction: column;
  188. margin-top: 12px;
  189. gap: 12px 0px;
  190. .data-list {
  191. width: 100%;
  192. // height: 268px;
  193. background-color: #ffffff;
  194. cursor: pointer;
  195. border-radius: 5px;
  196. overflow: hidden;
  197. transition:
  198. transform 0.2s ease,
  199. box-shadow 0.2s ease;
  200. display: flex;
  201. flex-direction: column;
  202. padding-bottom: 15px;
  203. &:hover {
  204. transform: translateY(-2px);
  205. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  206. .procure1 {
  207. color: #e7000b !important;
  208. }
  209. }
  210. .data-img {
  211. width: 100%;
  212. aspect-ratio: 393 / 220;
  213. // height: 200px;
  214. }
  215. .procure1 {
  216. padding: 12px 20px 4px 20px;
  217. font-weight: 600;
  218. font-size: 14px;
  219. color: #101828;
  220. }
  221. .procure2 {
  222. padding-left: 20px;
  223. font-size: 12px;
  224. color: #364153;
  225. }
  226. }
  227. }
  228. .no-data {
  229. background-color: #ffffff;
  230. border-radius: 5px;
  231. margin-top: 10px;
  232. }
  233. }
  234. }
  235. }
  236. </style>