index.vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. <template>
  2. <div class="page-content">
  3. <h2 class="title">课程列表</h2>
  4. <course-search v-show="showSearchBar" @search="getCourseData"></course-search>
  5. <el-card class="art-table-card" shadow="never">
  6. <ArtTableHeader v-model:showSearchBar="showSearchBar" :loading="loading" @refresh="() => getCourseData({})" layout="search,refresh">
  7. <template #left>
  8. <ElSpace wrap>
  9. <el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" @change="handleCheckAllChange">
  10. <div style="width: 110px">全选({{ selectCourses.length }} / {{ courseData.length }})</div>
  11. </el-checkbox>
  12. <el-button v-ripple type="primary" :icon="Plus" @click="() => router.push({ path: '/course/detail/0' })"> 新增课程 </el-button>
  13. <el-button v-ripple type="default" :icon="Upload" @click="courseImportHandle"> 导入课程 </el-button>
  14. <el-button v-ripple type="default" :icon="Download" :disabled="selectCourses.length === 0" @click="exportCourseHandle"> 导出课程 </el-button>
  15. <el-button v-ripple type="danger" :icon="Delete" :disabled="selectCourses.length === 0" @click="batchDeleteHandle"> 删除课程 </el-button>
  16. <el-button v-ripple type="default" :icon="CopyDocument" :disabled="selectCourses.length === 0" @click="copyCourseHandle"> 复制课程 </el-button>
  17. </ElSpace>
  18. </template>
  19. </ArtTableHeader>
  20. <course-list :data="courseData" :loading="loading" @handle="(type, item) => courseHandle(type, item)" :checkedCourse="selectCourses" @update:checkedList="handleCheckedChange"></course-list>
  21. </el-card>
  22. <course-import ref="courseImportRef"></course-import>
  23. </div>
  24. </template>
  25. <script lang="ts" setup>
  26. import { ref } from 'vue'
  27. import { CourseServer, type Course } from '@/api/course.api'
  28. import courseSearch from './course-search.vue'
  29. import courseList from './course-list.vue'
  30. import courseImport from './course-import.vue'
  31. import { Plus, Upload, Download, Delete, CopyDocument } from '@element-plus/icons-vue'
  32. import { router } from '@/router'
  33. import { CheckboxValueType, ElMessageBox } from 'element-plus'
  34. // 搜索项
  35. const showSearchBar = ref<boolean>(false)
  36. // 导入课程
  37. const courseImportRef = ref()
  38. const courseImportHandle = () => courseImportRef?.value.show()
  39. // 选择的课程Id
  40. const selectCourses = ref<string[]>([])
  41. // 是否全选
  42. const checkAll = computed({
  43. get: () => selectCourses.value.length === courseData.value.length && courseData.value.length > 0,
  44. set: (val: boolean) => (selectCourses.value = val ? [...courseData.value.map((item) => item.courseID)] : [])
  45. })
  46. // 是否半选
  47. const isIndeterminate = computed(() => {
  48. const checkedCount = selectCourses.value.length
  49. return checkedCount > 0 && checkedCount < courseData.value.length
  50. })
  51. // 处理子组件更新
  52. const handleCheckedChange = (newList: string[]) => {
  53. selectCourses.value = newList
  54. }
  55. // 全选复选框变更(其实 computed setter 已处理,此函数可省略,但保留以兼容 @change)
  56. const handleCheckAllChange = (val: CheckboxValueType) => {
  57. const checked = val as boolean
  58. selectCourses.value = checked ? [...courseData.value.map((item) => String(item.courseID))] : []
  59. }
  60. const courseData = ref<Course[]>([])
  61. const loading = ref(false)
  62. const getCourseData = async (params: Record<string, unknown>) => {
  63. loading.value = true
  64. const { data } = await CourseServer.list(params)
  65. courseData.value = data!
  66. loading.value = false
  67. }
  68. const courseHandle = (type: string, item: Course) => {
  69. switch (type) {
  70. case 'delete':
  71. CourseServer.delete(item.courseID).then(() => {
  72. ElMessage.success('删除成功')
  73. getCourseData({})
  74. })
  75. break
  76. case 'copy':
  77. CourseServer.copy(item.courseID).then(() => {
  78. ElMessage.success('复制成功')
  79. getCourseData({})
  80. })
  81. break
  82. case 'detail':
  83. router.push({ path: '/course/detail/' + item.courseID, query: { edit: `${false}` } })
  84. break
  85. case 'edit':
  86. router.push({ path: '/course/detail/' + item.courseID, query: { edit: `${true}` } })
  87. break
  88. default:
  89. ElMessage.error('未知操作')
  90. break
  91. }
  92. }
  93. const copyCourseHandle = async () => {
  94. if (selectCourses.value.length === 0) {
  95. ElMessage.warning('请选择要复制的课程')
  96. return
  97. }
  98. CourseServer.copyBatch(selectCourses.value).then(() => {
  99. ElMessage.success('复制成功')
  100. })
  101. }
  102. const exportCourseHandle = async () => {
  103. if (selectCourses.value.length === 0) {
  104. ElMessage.warning('请选择要导出的课程')
  105. return
  106. }
  107. CourseServer.export(selectCourses.value).then(() => {
  108. ElMessage.success('导出成功')
  109. })
  110. }
  111. const batchDeleteHandle = async () => {
  112. if (selectCourses.value.length === 0) {
  113. ElMessage.warning('请选择要删除的课程')
  114. return
  115. }
  116. ElMessageBox.confirm(`确定删除选中的 ${selectCourses.value.length} 条课程吗?`, '删除确认', {
  117. confirmButtonText: '确定',
  118. cancelButtonText: '取消',
  119. type: 'warning'
  120. }).then(() => {
  121. CourseServer.deleteBatch(selectCourses.value).then(() => {
  122. ElMessage.success('删除成功')
  123. getCourseData({})
  124. })
  125. })
  126. }
  127. onMounted(async () => {
  128. getCourseData({})
  129. })
  130. </script>
  131. <style lang="scss" scoped>
  132. .title {
  133. margin-bottom: 5px;
  134. font-size: 18px;
  135. font-weight: 500;
  136. &.m-15 {
  137. margin-top: 15px;
  138. }
  139. }
  140. </style>