index.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. <template>
  2. <div class="course-detail-page">
  3. <!-- 顶部课程信息卡片 -->
  4. <a-card class="course-info-card" bordered>
  5. <div class="course-info-main">
  6. <div class="cover-box">
  7. <a-avatar shape="square" :size="120" icon="play-circle" />
  8. </div>
  9. <div class="info-box">
  10. <div class="title">{{ course.name }}</div>
  11. <div class="meta">
  12. <p><EyeOutlined class="mr-0" /> {{ course.views }}</p>
  13. <p><ClockCircleOutlined class="mr-0" /> {{ course.updateTime }}</p>
  14. </div>
  15. </div>
  16. <div class="action-box">
  17. <div class="btn-group">
  18. <a-button @click="editVisible = true"> <EditOutlined /> 编辑课程</a-button>
  19. <a-button> <DownOutlined /> 下架课程</a-button>
  20. <a-button> <DeleteOutlined /> 删除课程</a-button>
  21. </div>
  22. <div class="extra-box">
  23. <div class="row"><span>当前状态</span><span class="status-normal">● 正常</span></div>
  24. <div class="row">
  25. <span>授课教师</span><span>{{ course.teacher }}</span>
  26. </div>
  27. <div class="row">
  28. <span>课程分类</span><span>{{ course.category }}</span>
  29. </div>
  30. <div class="row">
  31. <span>课时数量</span><span>{{ course.duration }}</span>
  32. </div>
  33. </div>
  34. </div>
  35. </div>
  36. </a-card>
  37. <!-- tab 导航 -->
  38. <a-tabs v-model:activeKey="activeTab" class="course-tabs">
  39. <a-tab-pane key="detail" tab="课程详情" />
  40. <a-tab-pane key="homework" tab="批改作业" />
  41. <a-tab-pane key="test" tab="批改测试" />
  42. <a-tab-pane key="student" tab="学员详情" />
  43. <a-tab-pane key="stat" tab="学习统计" />
  44. </a-tabs>
  45. <LessonDetails
  46. v-if="activeTab === 'detail'"
  47. :pagedSections="pagedSections"
  48. :currentPage="currentPage"
  49. :pageSize="pageSize"
  50. :sectionsLength="course.sections?.length || 0"
  51. @edit-lesson="onEditLesson"
  52. @delete-lesson="onDeleteLesson"
  53. @page-change="onPageChange"
  54. @page-size-change="onPageSizeChange"
  55. />
  56. <StudentDetails v-if="activeTab === 'student'" />
  57. <LearningStatistics v-if="activeTab === 'stat'" />
  58. <!-- 编辑课程弹窗 -->
  59. <a-modal v-model:visible="editVisible" title="新建工单" :footer="null" width="700px" @cancel="editVisible = false">
  60. <EditCourse :course="course" @close="editVisible = false" />
  61. </a-modal>
  62. <!-- 编辑课时弹窗 -->
  63. <AddClassHours v-model:visible="addClassHoursVisible" @ok="onAddClassHoursOk" />
  64. </div>
  65. </template>
  66. <script setup>
  67. import { ref, computed, onMounted } from 'vue'
  68. import { EyeOutlined, ClockCircleOutlined, EditOutlined, DeleteOutlined, DownOutlined } from '@ant-design/icons-vue'
  69. import EditCourse from './components/EditCourse.vue'
  70. import AddClassHours from './components/AddClassHours.vue'
  71. import LessonDetails from './components/tab/LessonDetails.vue'
  72. import StudentDetails from './components/tab/StudentDetails.vue'
  73. import LearningStatistics from './components/tab/LearningStatistics.vue'
  74. import { getCourseDetail } from '@/api/course/courseDetail.js'
  75. const course = ref({})
  76. const activeTab = ref('detail')
  77. const currentPage = ref(1)
  78. const pageSize = ref(10)
  79. const editVisible = ref(false)
  80. const addClassHoursVisible = ref(false)
  81. const editingLesson = ref(null)
  82. const pagedSections = computed(() => {
  83. if (!course.value.sections) return []
  84. const start = (currentPage.value - 1) * pageSize.value
  85. return course.value.sections.slice(start, start + pageSize.value)
  86. })
  87. onMounted(() => {
  88. getCourseDetail().then((data) => {
  89. course.value = data
  90. })
  91. })
  92. function onPageChange(page) {
  93. currentPage.value = page
  94. }
  95. function onPageSizeChange(size) {
  96. pageSize.value = size
  97. currentPage.value = 1
  98. }
  99. function onEditLesson(lesson) {
  100. editingLesson.value = lesson
  101. addClassHoursVisible.value = true
  102. }
  103. function onDeleteLesson(lesson) {
  104. // 这里只做mock删除
  105. course.value.sections.forEach((section) => {
  106. section.lessons = section.lessons.filter((l) => l.id !== lesson.id)
  107. })
  108. }
  109. function onAddClassHoursOk(data) {
  110. // 这里只做mock保存
  111. if (editingLesson.value) {
  112. Object.assign(editingLesson.value, data)
  113. }
  114. addClassHoursVisible.value = false
  115. }
  116. </script>
  117. <style lang="less" scoped>
  118. .course-detail-page {
  119. background: #f7f8fa;
  120. min-height: 100vh;
  121. padding: 32px 0 0 0;
  122. .course-info-card {
  123. width: 1200px;
  124. margin: 0 auto 24px auto;
  125. border-radius: 12px;
  126. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.04);
  127. border: none;
  128. .course-info-main {
  129. display: flex;
  130. align-items: flex-start;
  131. padding: 32px 32px 24px 32px;
  132. .cover-box {
  133. width: 160px;
  134. height: 120px;
  135. background: #f2f3f5;
  136. border-radius: 8px;
  137. display: flex;
  138. align-items: center;
  139. justify-content: center;
  140. margin-right: 32px;
  141. .ant-avatar {
  142. background: #e6e9ef;
  143. font-size: 48px;
  144. }
  145. }
  146. .info-box {
  147. flex: 1;
  148. .title {
  149. font-size: 22px;
  150. font-weight: 600;
  151. color: #222;
  152. margin-bottom: 12px;
  153. }
  154. .meta {
  155. color: #999;
  156. font-size: 14px;
  157. span {
  158. margin-right: 24px;
  159. display: inline-flex;
  160. align-items: center;
  161. .anticon {
  162. margin-right: 4px;
  163. }
  164. }
  165. }
  166. }
  167. .action-box {
  168. flex: 1;
  169. color: #999;
  170. .extra-box {
  171. min-width: 180px;
  172. margin-left: 32px;
  173. display: flex;
  174. justify-content: space-evenly;
  175. .row {
  176. display: flex;
  177. flex-direction: column;
  178. justify-content: space-between;
  179. font-size: 14px;
  180. margin-bottom: 8px;
  181. span:last-child {
  182. font-weight: 500;
  183. }
  184. .status-normal {
  185. margin-top: 10px;
  186. color: #52c41a;
  187. font-weight: 600;
  188. margin-left: 8px;
  189. }
  190. }
  191. }
  192. .btn-group {
  193. display: flex;
  194. flex-direction: row;
  195. margin-left: 40px;
  196. gap: 10px;
  197. justify-content: flex-end;
  198. .ant-btn {
  199. margin-bottom: 12px;
  200. width: 120px;
  201. font-size: 14px;
  202. border-radius: 6px;
  203. }
  204. .ant-btn-primary {
  205. background: #347aff;
  206. border-color: #347aff;
  207. }
  208. }
  209. }
  210. }
  211. }
  212. .course-tabs {
  213. width: 1200px;
  214. margin: 0 auto 0 auto;
  215. background: #fff;
  216. border-radius: 12px 12px 0 0;
  217. padding-left: 20px;
  218. .ant-tabs-bar {
  219. border-bottom: 1px solid #f0f0f0;
  220. margin-bottom: 0;
  221. }
  222. .ant-tabs-nav {
  223. font-size: 16px;
  224. .ant-tabs-tab {
  225. padding: 18px 32px 14px 32px;
  226. font-weight: 500;
  227. color: #666;
  228. &.ant-tabs-tab-active {
  229. color: #347aff;
  230. font-weight: 600;
  231. }
  232. }
  233. .ant-tabs-ink-bar {
  234. background: #347aff;
  235. height: 3px;
  236. border-radius: 2px 2px 0 0;
  237. }
  238. }
  239. }
  240. .mr-0 {
  241. margin-right: 5px !important;
  242. }
  243. }
  244. </style>