| | |
| | | <template> |
| | | <div class="myBook"> |
| | | <div class="myBook_header"> |
| | | <div class="myBook_header_title">我的课程</div> |
| | | <div class="coursePage"> |
| | | <div class="personalPage-title">我的课程</div> |
| | | <div class="headerBox"> |
| | | <div class="searchBox"> |
| | | <el-input |
| | | v-model="searchKey" |
| | | clearable |
| | | @clear="getData" |
| | | placeholder="请输入关键字" |
| | | > |
| | | <template #append> |
| | | <el-button type="primary" class="searchBtn" :icon="Search" @click="getData" /> |
| | | </template> |
| | | </el-input> |
| | | </div> |
| | | <el-button type="primary" class="applyStartClasses" @click="applyCourse" |
| | | >申请开课</el-button |
| | | > |
| | | </div> |
| | | <div class="courseListBox" v-loading="pages.loading"> |
| | | <div |
| | | class="courseItem" |
| | | v-for="(item, index) in courseList" |
| | | :key="index" |
| | | @click="gotoDetail(item)" |
| | | > |
| | | <div class="itemHeader"> |
| | | <div class="title" :title="item.name">{{ item.name }}</div> |
| | | </div> |
| | | <div class="reasonBox"> |
| | | <el-tooltip placement="right-end" effect="light" class="box-item"> |
| | | <template #content> |
| | | <div style="width: 300px" v-html="item.reason"></div> |
| | | </template> |
| | | <span v-if="item.applyState == 'Reject'" style="color: red"> |
| | | 拒绝原因:{{ item.reason != "" ? item.reason : "-" }} |
| | | </span> |
| | | </el-tooltip> |
| | | <el-button |
| | | v-if="item.applyState == 'Reject'" |
| | | type="primary" |
| | | size="small" |
| | | style="margin-left: 20px" |
| | | @click="reapplyCourse(item)" |
| | | >重新申请</el-button |
| | | > |
| | | </div> |
| | | |
| | | <div class="itemInfo"> |
| | | <div class="imgBox autoImgBox"> |
| | | <div |
| | | class="stateIcon" |
| | | v-if="item.applyState == 'Normal'" |
| | | style="background-color: #1dbd11" |
| | | > |
| | | 使用中 |
| | | </div> |
| | | <div |
| | | class="stateIcon" |
| | | v-if="item.applyState == 'WaitAudit'" |
| | | style="background-color: #ef9f29" |
| | | > |
| | | 审核中 |
| | | </div> |
| | | <div |
| | | class="stateIcon" |
| | | v-if="item.applyState == 'Reject'" |
| | | style="background-color: red" |
| | | > |
| | | 未通过 |
| | | </div> |
| | | <img v-if="item.icon" :src="item.icon" /> |
| | | <img v-else :src="defaultImg" alt="" /> |
| | | </div> |
| | | <div class="infoBox"> |
| | | <p class="id">ID:{{ item.id }}</p> |
| | | <div class="introduction" :title="item.introduction"> |
| | | {{ item.introduction }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="pagination-box" v-if="courseList.length > 0"> |
| | | <el-pagination |
| | | v-model:current-page="pages.page" |
| | | v-model:page-size="pages.pageSize" |
| | | :background="false" |
| | | layout="total, prev, pager, next" |
| | | :total="pages.count" |
| | | @current-change="pageChange" |
| | | /> |
| | | </div> |
| | | <div class="nullBox" v-if="!pages.loading && courseList.length == 0"> |
| | | <el-empty /> |
| | | </div> |
| | | <!-- 申请开课弹框 --> |
| | | <el-dialog v-model="applyCourseDialog" width="750" align-center> |
| | | <template #title>{{ editData ? "重新申请" : "申请开课" }}</template> |
| | | <el-form |
| | | :model="formData" |
| | | label-position="left" |
| | | ref="dialogFormRef" |
| | | label-width="80px" |
| | | > |
| | | <el-form-item |
| | | label="课程名称" |
| | | prop="name" |
| | | :rules="[{ required: true, message: '请输入课程名称' }]" |
| | | > |
| | | <el-input v-model="formData.name" placeholder="请输入课程名称" /> |
| | | </el-form-item> |
| | | <el-form-item |
| | | v-if="!editData" |
| | | label="关联教材" |
| | | prop="bookName" |
| | | :rules="[{ required: true, message: '请选择教材' }]" |
| | | > |
| | | <div style="display: flex"> |
| | | <el-input v-model="formData.bookName" disabled="true" style="width: 300px" /> |
| | | <el-button style="margin-left: 10px" @click="selectBook">选择教材</el-button> |
| | | </div> |
| | | </el-form-item> |
| | | <el-form-item label="课程介绍"> |
| | | <el-input |
| | | v-model="formData.desc" |
| | | type="textarea" |
| | | autocomplete="off" |
| | | maxlength="300" |
| | | show-word-limit |
| | | rows="7" |
| | | placeholder="请输入课程介绍" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="applyCourseDialog = false">取消</el-button> |
| | | <el-button type="primary" @click="submit" :loading="submitLoading" |
| | | >提交</el-button |
| | | > |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- 选择教材弹框 --> |
| | | <el-dialog v-model="selectTextBookDialog" width="1000" align-center> |
| | | <template #title>选择教材</template> |
| | | <div class="textBookList" v-loading="textBookPages.loading"> |
| | | <div class="headerBox" style="padding: 10px"> |
| | | <div class="searchBox" style="float: right"> |
| | | <el-input |
| | | v-model="selectName" |
| | | @clear="getTextBook" |
| | | clearable |
| | | placeholder="请输入关键字" |
| | | > |
| | | <template #append> |
| | | <el-button |
| | | type="primary" |
| | | class="searchBtn" |
| | | :icon="Search" |
| | | @click="getTextBook" |
| | | /> |
| | | </template> |
| | | </el-input> |
| | | </div> |
| | | </div> |
| | | <div style="min-height: 370px" v-if="textBookListData.length > 0"> |
| | | <div |
| | | v-for="(item, index) in textBookListData" |
| | | :key="index" |
| | | class="textBookItem" |
| | | > |
| | | <el-checkbox |
| | | class="checkBox" |
| | | v-model="item.check" |
| | | @change="selectChange(item)" |
| | | /> |
| | | <div class="imgBox autoImgBox"> |
| | | <img :src="item.img" /> |
| | | </div> |
| | | <p>{{ item.product.name }}</p> |
| | | </div> |
| | | </div> |
| | | <div |
| | | class="nullBox" |
| | | v-if="!textBookPages.loading && textBookListData.length == 0" |
| | | > |
| | | <el-empty /> |
| | | </div> |
| | | </div> |
| | | <div class="pagination-box" v-if="textBookListData.length > 0"> |
| | | <el-pagination |
| | | v-model:current-page="textBookPages.page" |
| | | v-model:page-size="textBookPages.pageSize" |
| | | :background="false" |
| | | layout="total, prev, pager, next" |
| | | :total="textBookPages.count" |
| | | @current-change="handleCurrentChange" |
| | | /> |
| | | </div> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="selectTextBookDialog = false">取消</el-button> |
| | | <el-button type="primary" @click="selectTextBookSubmit">确定</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { reactive, ref, onMounted, inject, watch } from "vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { useRouter, useRoute } from "vue-router"; |
| | | import { ElMessage } from "element-plus"; |
| | | // eslint-disable-next-line |
| | | import { getPublicImage } from "@/assets/js/middleGround/tool.js"; |
| | | // eslint-disable-next-line |
| | | import defaultImg from "@/assets/images/default-book-img.png"; |
| | | |
| | | const router: any = useRouter(); |
| | | const route: any = useRoute(); |
| | | const MG: any = inject("MG"); |
| | | const selectName = ref(""); |
| | | |
| | | // 申请开课防抖loading |
| | | const submitLoading = ref(false); |
| | | |
| | | const courseList: any = ref([]); |
| | | |
| | | onMounted(() => { |
| | | getData(); |
| | | }); |
| | | |
| | | const searchKey = ref(""); |
| | | const pages = reactive({ |
| | | page: 1, |
| | | pageSize: 6, |
| | | count: 0, |
| | | loading: true, |
| | | }); |
| | | |
| | | // 获取课程 |
| | | const getData = () => { |
| | | pages.loading = true; |
| | | MG.edu |
| | | .getAppCourseList({ |
| | | size: pages.pageSize, |
| | | start: pages.pageSize * pages.page - pages.pageSize, |
| | | sort: { |
| | | type: "Desc", |
| | | field: "CreateDate", |
| | | }, |
| | | filterList: [], |
| | | searchList: searchKey.value |
| | | ? [ |
| | | { |
| | | keywords: searchKey.value, |
| | | field: "Name", |
| | | compareType: "Contains", |
| | | }, |
| | | ] |
| | | : [], |
| | | }) |
| | | .then((res: any) => { |
| | | pages.loading = false; |
| | | pages.count = res.totalSize; |
| | | courseList.value = res.datas.map((item: any) => { |
| | | return { |
| | | ...item, |
| | | name: item.name, |
| | | id: item.id, |
| | | icon: item.icon != "default" ? getPublicImage(item.icon, 80) : defaultImg, |
| | | introduction: item.description, |
| | | reason: item.applyReturnMsg ? JSON.parse(item.applyReturnMsg).reason : "", |
| | | }; |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | // 申请开课 |
| | | const applyCourse = () => { |
| | | editData.value = null; |
| | | formData.value = { |
| | | name: "", |
| | | bookName: "", |
| | | bookId: "", |
| | | selectData: "", |
| | | desc: "", |
| | | }; |
| | | applyCourseDialog.value = true; |
| | | }; |
| | | |
| | | const editData: any = ref(null); |
| | | const reapplyCourse = (data: any) => { |
| | | editData.value = data; |
| | | formData.value = { |
| | | name: data.name, |
| | | bookName: data.linkProduct.name, |
| | | bookId: data.linkProduct.id, |
| | | selectData: "", |
| | | desc: data.description, |
| | | }; |
| | | applyCourseDialog.value = true; |
| | | }; |
| | | |
| | | // 申请教材弹框 |
| | | const applyCourseDialog = ref(false); |
| | | const selectTextBookDialog = ref(false); |
| | | const formData = ref({ |
| | | name: "", |
| | | bookName: "", |
| | | bookId: "", |
| | | selectData: "", |
| | | desc: "", |
| | | }); |
| | | |
| | | const selectChange = (select: any) => { |
| | | for (let i = 0; i < textBookListData.value.length; i++) { |
| | | const item: any = textBookListData.value[i]; |
| | | if (item.id == select.id) { |
| | | item.check = true; |
| | | } else { |
| | | item.check = false; |
| | | } |
| | | } |
| | | }; |
| | | |
| | | const selectTextBookSubmit = () => { |
| | | const selectData: any = textBookListData.value.filter((item: any) => item.check)[0]; |
| | | if (!selectData?.product?.id) { |
| | | ElMessage.warning("请选择开课教材!"); |
| | | return false; |
| | | } |
| | | formData.value.bookId = selectData.product.id; |
| | | formData.value.bookName = selectData.product.name; |
| | | formData.value.selectData = selectData; |
| | | selectTextBookDialog.value = false; |
| | | }; |
| | | |
| | | const submit = () => { |
| | | submitLoading.value = true; |
| | | if (editData.value) { |
| | | MG.edu |
| | | .updateCourse({ |
| | | courseId: editData.value.id, |
| | | name: formData.value.name, |
| | | description: formData.value.desc, |
| | | }) |
| | | .then((res: any) => { |
| | | MG.edu |
| | | .updateCourseApply({ |
| | | courseId: editData.value.id, |
| | | applyData: JSON.stringify({ |
| | | textBookId: formData.value.bookId, |
| | | textBookName: formData.value.bookName, |
| | | }), |
| | | }) |
| | | .then((ares: any) => { |
| | | ElMessage.success("课程已重新申请,等待管理员审核。"); |
| | | applyCourseDialog.value = false; |
| | | getData(); |
| | | }); |
| | | }); |
| | | } else { |
| | | if (formData.value.name == "") { |
| | | ElMessage({ |
| | | type: "warning", |
| | | message: "请填写课程名称", |
| | | }); |
| | | submitLoading.value = false; |
| | | return false; |
| | | } |
| | | if (!formData.value.bookId) { |
| | | ElMessage({ |
| | | type: "warning", |
| | | message: "请选择关联教材", |
| | | }); |
| | | submitLoading.value = false; |
| | | return false; |
| | | } |
| | | MG.edu |
| | | .applyNewCourse({ |
| | | name: formData.value.name, |
| | | description: formData.value.desc, |
| | | content: "", |
| | | icon: formData.value.selectData.product.icon ?? "default", |
| | | type: "course", |
| | | config: "", |
| | | applyData: JSON.stringify({ |
| | | textBookId: formData.value.bookId, |
| | | textBookName: formData.value.bookName, |
| | | }), |
| | | linkProductId: formData.value.bookId, |
| | | maxClassCount: 999, |
| | | payPrice: 0, |
| | | }) |
| | | .then((res: any) => { |
| | | if (res) { |
| | | ElMessage.success("课程已申请,等待管理员审核。"); |
| | | applyCourseDialog.value = false; |
| | | getData(); |
| | | } |
| | | }); |
| | | } |
| | | formData.value.selectData = ""; |
| | | setTimeout(() => { |
| | | submitLoading.value = false; |
| | | }, 1000); |
| | | }; |
| | | |
| | | // 获取已购买的教材列表 |
| | | const textBookPages = reactive({ |
| | | page: 1, |
| | | pageSize: 12, |
| | | count: 0, |
| | | loading: false, |
| | | }); |
| | | const textBookListData = ref([]); |
| | | |
| | | const getTextBook = () => { |
| | | textBookPages.loading = true; |
| | | const searchData = [ |
| | | { |
| | | keywords: "digitalTextbooks", |
| | | field: "ProductType", |
| | | }, |
| | | { |
| | | keywords: "mediaBook", |
| | | field: "ProductType", |
| | | }, |
| | | { |
| | | keywords: selectName.value, |
| | | field: "ProductName", |
| | | }, |
| | | ]; |
| | | const data = { |
| | | Size: textBookPages.pageSize, |
| | | Start: textBookPages.pageSize * textBookPages.page - textBookPages.pageSize, |
| | | sort: { |
| | | type: "Desc", |
| | | field: "CreateDate", |
| | | }, |
| | | searchList: searchData, |
| | | }; |
| | | MG.store |
| | | .getPurchasedProductList(data) |
| | | .then((res: any) => { |
| | | textBookPages.count = res.totalSize; |
| | | textBookListData.value = res.datas.map((item: any) => { |
| | | return { |
| | | ...item, |
| | | img: item.product.icon ? getPublicImage(item.product.icon, 80) : defaultImg, |
| | | }; |
| | | }); |
| | | textBookPages.loading = false; |
| | | }) |
| | | .catch(() => { |
| | | textBookPages.loading = false; |
| | | }); |
| | | }; |
| | | |
| | | // 选择教材 |
| | | const selectBook = () => { |
| | | selectTextBookDialog.value = true; |
| | | textBookListData.value = []; |
| | | getTextBook(); |
| | | }; |
| | | |
| | | const pageChange = (val: any) => { |
| | | pages.page = val; |
| | | getData(); |
| | | }; |
| | | const handleCurrentChange = (val: any) => { |
| | | textBookPages.page = val; |
| | | getTextBook(); |
| | | }; |
| | | |
| | | const gotoDetail = (data: any) => { |
| | | if (data.applyState == "Normal") { |
| | | router.push({ |
| | | name: "courseDetail", |
| | | query: { |
| | | courseId: data.id, |
| | | }, |
| | | }); |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="less" scoped> |
| | | .coursePage { |
| | | .headerBox { |
| | | padding: 20px; |
| | | margin-bottom: 10px; |
| | | overflow: hidden; |
| | | |
| | | .searchBox { |
| | | width: 300px; |
| | | float: left; |
| | | |
| | | .searchBtn { |
| | | background-color: var(--el-color-primary); |
| | | color: #fff; |
| | | border-top-left-radius: 0; |
| | | border-bottom-left-radius: 0; |
| | | } |
| | | } |
| | | |
| | | .applyStartClasses { |
| | | float: right; |
| | | } |
| | | } |
| | | |
| | | .courseListBox { |
| | | overflow: hidden; |
| | | min-height: 200px; |
| | | |
| | | .courseItem { |
| | | float: left; |
| | | width: 46%; |
| | | height: 210px; |
| | | margin: 0 2% 20px; |
| | | border-radius: 8px; |
| | | border: 1px solid #efefef; |
| | | overflow: hidden; |
| | | cursor: pointer; |
| | | |
| | | .itemHeader { |
| | | height: 40px; |
| | | line-height: 40px; |
| | | padding: 0 20px; |
| | | background-color: #f8f8f8; |
| | | .title { |
| | | font-weight: 600; |
| | | } |
| | | |
| | | div { |
| | | width: 100%; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | word-break: break-all; |
| | | } |
| | | } |
| | | |
| | | .reasonBox { |
| | | display: flex; |
| | | padding: 5px 20px; |
| | | cursor: default; |
| | | span { |
| | | font-size: 12px; |
| | | line-height: 24px; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | } |
| | | |
| | | .itemInfo { |
| | | padding: 10px 20px; |
| | | flex: 1; |
| | | display: flex; |
| | | .imgBox { |
| | | width: 90px; |
| | | height: 120px; |
| | | margin-right: 20px; |
| | | } |
| | | .infoBox { |
| | | flex: 1; |
| | | font-size: 14px; |
| | | p { |
| | | margin-bottom: 10px; |
| | | } |
| | | .introduction { |
| | | height: 88px; |
| | | line-height: 22px; |
| | | display: -webkit-box; |
| | | -webkit-box-orient: vertical; |
| | | -webkit-line-clamp: 4; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | word-break: break-all; |
| | | } |
| | | } |
| | | .stateIcon { |
| | | position: absolute; |
| | | right: -6px; |
| | | top: -4px; |
| | | padding: 5px 8px; |
| | | color: #fff; |
| | | border-top-left-radius: 30px; |
| | | border-bottom-right-radius: 30px; |
| | | border-top-right-radius: 30px; |
| | | z-index: 99; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .textBookList { |
| | | overflow: hidden; |
| | | margin-bottom: 20px; |
| | | .textBookItem { |
| | | float: left; |
| | | width: 160px; |
| | | height: 220px; |
| | | margin: 10px; |
| | | position: relative; |
| | | border: 1px solid #eee; |
| | | padding: 20px; |
| | | box-sizing: border-box; |
| | | .checkBox { |
| | | position: absolute; |
| | | right: 0; |
| | | top: 0; |
| | | height: 14px; |
| | | ::v-deep { |
| | | .el-checkbox__inner { |
| | | border-color: #888; |
| | | } |
| | | } |
| | | } |
| | | |
| | | p { |
| | | line-height: 1.2; |
| | | display: -webkit-box; |
| | | -webkit-line-clamp: 2; |
| | | -webkit-box-orient: vertical; |
| | | overflow: hidden; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .autoImgBox { |
| | | width: 120px; |
| | | height: 130px; |
| | | margin-bottom: 10px; |
| | | position: relative; |
| | | img { |
| | | width: auto; |
| | | height: auto; |
| | | max-width: 100%; |
| | | max-height: 100%; |
| | | position: absolute; |
| | | top: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | left: 0; |
| | | margin: auto; |
| | | } |
| | | } |
| | | |
| | | .pagination-box { |
| | | display: flex; |
| | | justify-content: center; |
| | | padding: 10px 0; |
| | | } |
| | | |
| | | .nullBox { |
| | | text-align: center; |
| | | margin-top: 30px; |
| | | font-size: 20px; |
| | | color: #ccc; |
| | | } |
| | | </style> |