Region.vue 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. <template>
  2. <div class="video-page-container">
  3. <el-row :gutter="20" class="main-layout">
  4. <el-col :md="5" :lg="4" class="sidebar-col">
  5. <el-card class="category-card shadow-sm" :body-style="{ padding: '10px 0' }">
  6. <div slot="header" class="category-header">
  7. <i class="el-icon-menu"></i>
  8. <span>视频分区</span>
  9. </div>
  10. <div class="category-menu-wrapper">
  11. <el-tree
  12. :accordion="true"
  13. :data="treeNode"
  14. :props="defaultProps"
  15. highlight-current
  16. node-key="value"
  17. ref="categoryTree"
  18. @node-click="handleNodeClick"
  19. class="custom-tree"
  20. >
  21. <span slot-scope="{ node, data }" class="tree-node-item">
  22. <i :class="data.icon || 'el-icon-folder'"></i>
  23. <span class="node-label">{{ node.label }}</span>
  24. </span>
  25. </el-tree>
  26. </div>
  27. </el-card>
  28. </el-col>
  29. <el-col :md="19" :lg="20" class="content-col">
  30. <div v-loading="loading" class="video-grid-wrapper">
  31. <el-row :gutter="16" v-if="dataList && dataList.length > 0">
  32. <el-col
  33. v-for="(video, index) in dataList"
  34. :key="index"
  35. :xs="12" :sm="8" :md="8" :lg="6"
  36. class="video-item"
  37. >
  38. <video-card :video="video" class="hover-up" />
  39. </el-col>
  40. </el-row>
  41. <el-empty
  42. v-else-if="!loading"
  43. description="该分区下暂无视频内容"
  44. :image-size="200"
  45. ></el-empty>
  46. <div class="pagination-container" v-if="totalSize > pageSize">
  47. <el-pagination
  48. background
  49. :small="screenWidth <= 768"
  50. layout="prev, pager, next, total"
  51. :page-size="pageSize"
  52. :current-page="currentPage"
  53. :total="totalSize"
  54. @current-change="handleCurrentChange"
  55. />
  56. </div>
  57. </div>
  58. </el-col>
  59. </el-row>
  60. </div>
  61. </template>
  62. <script>
  63. import VideoCard from 'components/card/VideoCard'
  64. import { categoryVideos, videoCategories } from '@/api/video'
  65. export default {
  66. name: 'Region',
  67. components: { VideoCard },
  68. data() {
  69. return {
  70. screenWidth: document.body.clientWidth,
  71. currentPage: 1,
  72. pageSize: 12,
  73. totalSize: 0,
  74. dataList: [],
  75. categoryId: 1, // 默认分区 ID
  76. treeNode: [],
  77. defaultProps: {
  78. children: 'children',
  79. label: 'label'
  80. },
  81. loading: false
  82. }
  83. },
  84. watch: {
  85. // 核心:监听路由变化,当 URL 参数改变时重新加载数据
  86. '$route': {
  87. handler: 'loadDataFromRoute',
  88. immediate: false
  89. }
  90. },
  91. created() {
  92. document.title = '视频分区 - 探索精彩'
  93. this.fetchCategories()
  94. // 初始化:从 URL 中解析参数并加载数据
  95. this.loadDataFromRoute()
  96. },
  97. mounted() {
  98. window.addEventListener('resize', this.handleResize)
  99. },
  100. beforeDestroy() {
  101. window.removeEventListener('resize', this.handleResize)
  102. },
  103. methods: {
  104. handleResize() {
  105. this.screenWidth = document.body.clientWidth
  106. },
  107. // 1. 从路由 Query 参数中提取状态
  108. loadDataFromRoute() {
  109. const query = this.$route.query
  110. // 如果 url 有参数则取参数,否则用 data 里的默认值
  111. this.categoryId = query.category ? parseInt(query.category) : 1
  112. this.currentPage = query.page ? parseInt(query.page) : 1
  113. // 同步 Tree 组件的高亮状态
  114. this.$nextTick(() => {
  115. if (this.$refs.categoryTree) {
  116. this.$refs.categoryTree.setCurrentKey(this.categoryId)
  117. }
  118. })
  119. this.videoPageWrapper(this.categoryId, this.currentPage)
  120. },
  121. // 2. 更新 URL 的方法(不直接请求接口,交由 watch $route 处理)
  122. updateRoute() {
  123. this.$router.push({
  124. path: this.$route.path,
  125. query: {
  126. category: this.categoryId,
  127. page: this.currentPage
  128. }
  129. }).catch(err => {
  130. // 规避掉重复点击同一路由产生的 NavigationDuplicated 报错
  131. if (err.name !== 'NavigationDuplicated') throw err
  132. })
  133. },
  134. fetchCategories() {
  135. videoCategories().then(resp => {
  136. if (resp.code === 0) {
  137. this.treeNode = resp.data
  138. // 获取分类后再次确认 Tree 的选中状态
  139. this.$nextTick(() => {
  140. this.$refs.categoryTree.setCurrentKey(this.categoryId)
  141. })
  142. }
  143. })
  144. },
  145. handleNodeClick(data) {
  146. if (this.categoryId === data.value) return
  147. this.categoryId = data.value
  148. this.currentPage = 1 // 切换分类时重置为第一页
  149. this.updateRoute()
  150. },
  151. handleCurrentChange(page) {
  152. this.currentPage = page
  153. this.updateRoute()
  154. window.scrollTo({ top: 0, behavior: 'smooth' })
  155. },
  156. videoPageWrapper(categoryId, currentPage) {
  157. this.loading = true
  158. categoryVideos(categoryId, currentPage).then(resp => {
  159. if (resp.code === 0) {
  160. this.dataList = resp.data.list
  161. this.totalSize = resp.data.totalSize
  162. }
  163. }).finally(() => {
  164. this.loading = false
  165. })
  166. }
  167. }
  168. }
  169. </script>
  170. <style scoped>
  171. /* 样式部分保持之前的优化版本不变 */
  172. .video-page-container {
  173. padding: 25px 5%;
  174. background-color: #f9fafb;
  175. min-height: calc(100vh - 60px);
  176. }
  177. .shadow-sm {
  178. box-shadow: 0 1px 3px rgba(0,0,0,0.1) !important;
  179. border: none !important;
  180. border-radius: 12px;
  181. }
  182. .category-header {
  183. font-weight: bold;
  184. font-size: 16px;
  185. color: #303133;
  186. display: flex;
  187. align-items: center;
  188. }
  189. .category-header i { margin-right: 8px; color: #409EFF; }
  190. .tree-node-item { display: flex; align-items: center; font-size: 14px; }
  191. .tree-node-item i { margin-right: 10px; font-size: 16px; color: #909399; }
  192. ::v-deep .el-tree-node__content { height: 40px !important; margin: 4px 12px; border-radius: 8px; }
  193. ::v-deep .el-tree-node.is-current > .el-tree-node__content { background-color: #ecf5ff !important; color: #409EFF; font-weight: 600; }
  194. .video-item { margin-bottom: 20px; }
  195. .hover-up { transition: transform 0.3s; }
  196. .hover-up:hover { transform: translateY(-5px); }
  197. .pagination-container { margin-top: 40px; display: flex; justify-content: center; }
  198. </style>