Region.vue 6.3 KB

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