Files
learning-system-portal/src/views/course/ManageListRemote.vue

1516 lines
52 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="manage-list-remote">
<div class="filter-wrapper">
<el-form :inline="true" label-width="0" class="filter-form filter-form--primary">
<div class="filter-fields">
<el-form-item>
<el-input
placeholder="课程名称"
v-model="params.name"
clearable
show-word-limit
></el-input>
</el-form-item>
<el-form-item>
<el-cascader placeholder="全部课程分类" clearable v-model="sysTypeList" :props="defaultTypeProps"
:options="sysTypeListMap"></el-cascader>
</el-form-item>
<el-form-item class="teacher-filter">
<div class="teacher-select-wrapper">
<el-select
class="creator-select"
v-model="teacherSelected"
multiple
filterable
remote
clearable
reserve-keyword
placeholder="授课教师"
:multiple-limit="5"
collapse-tags
:remote-method="remoteSearchTeacher"
:loading="teacherLoading"
@change="handleTeacherChange"
@clear="handleTeacherClear"
>
<el-option
v-for="item in teacherOptions"
:key="item.teacherId"
:label="formatTeacherLabel(item)"
:value="item.teacherId"
></el-option>
</el-select>
</div>
</el-form-item>
<el-form-item class="learning-time-range">
<el-date-picker v-model="learningTimeRange" type="daterange" align="right" unlink-panels clearable
start-placeholder="开始时间" end-placeholder="结束时间" value-format="yyyy-MM-dd" range-separator=""
:picker-options="pickerOptions" @change="handleLearningTimeRangeChange"></el-date-picker>
</el-form-item>
<el-form-item>
<el-select v-model="params.status" placeholder="全部审核状态" clearable>
<el-option label="全部" :value="''"></el-option>
<el-option label="审核中" value="2"></el-option>
<el-option label="审核驳回" value="3"></el-option>
<el-option label="审核通过" value="5"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="params.publish" placeholder="全部发布状态" clearable>
<el-option label="全部" :value="''"></el-option>
<el-option label="已发布" :value="true"></el-option>
<el-option label="未发布" :value="false"></el-option>
</el-select>
</el-form-item>
</div>
<div class="filter-actions" v-show="!showAdvancedFilter">
<el-button type="primary" @click="searchData(true)" icon="el-icon-search">搜索</el-button>
<el-button icon="el-icon-refresh-right" type="primary" @click="reset">重置</el-button>
<el-button type="text" class="toggle-link" @click="toggleAdvancedFilter">
{{ showAdvancedFilter ? '收起' : '展开' }}
<i :class="showAdvancedFilter ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i>
</el-button>
</div>
</el-form>
<el-form
v-show="showAdvancedFilter"
:inline="true"
label-width="0"
class="filter-form filter-form--advanced advanced-filter"
>
<div class="filter-fields">
<el-form-item>
<el-select v-model="params.enabled" placeholder="全部启用停用状态" clearable>
<el-option label="全部" :value="''"></el-option>
<el-option label="启用" :value="true"></el-option>
<el-option label="停用" :value="false"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="params.openCourse" placeholder="是否公开课" clearable>
<el-option label="全部" :value="''"></el-option>
<el-option label="公开课" :value="1"></el-option>
<el-option label="非公开课" :value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-cascader
placeholder="全部资源归属"
clearable
filterable
v-model="resOwner"
:props="defaultProps"
:options="resOwnerListMap"
></el-cascader>
</el-form-item>
<el-form-item class="creator-filter">
<el-select
class="creator-select"
v-model="creatorSelected"
multiple
filterable
remote
clearable
reserve-keyword
:multiple-limit="5"
placeholder="创建人"
collapse-tags
:remote-method="remoteSearchCreator"
:loading="creatorLoading"
@change="handleCreatorChange"
@clear="handleCreatorClear"
>
<el-option
v-for="item in creatorOptions"
:key="item.userId"
:label="formatCreatorLabel(item)"
:value="item.userId"
></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="params.createFrom" placeholder="全部创建来源" clearable>
<el-option label="全部" :value="''"></el-option>
<el-option label="教师端" value="teacher"></el-option>
<el-option label="管理员端" value="admin"></el-option>
</el-select>
</el-form-item>
</div>
<div class="filter-actions filter-actions--inline">
<el-button type="primary" @click="searchData(true)" icon="el-icon-search">搜索</el-button>
<el-button icon="el-icon-refresh-right" type="primary" @click="reset">重置</el-button>
<el-button type="text" class="toggle-link" @click="toggleAdvancedFilter">
{{ showAdvancedFilter ? '收起' : '展开' }}
<i :class="showAdvancedFilter ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i>
</el-button>
</div>
</el-form>
<!-- <el-row :gutter="20" type="flex" justify="end" style="margin-top:16px">
<el-col :span="6" class="filter-extra-actions">
<el-button icon="el-icon-top" type="primary" @click="handleTopSort">置顶排序</el-button>
<el-button icon="el-icon-upload2" type="primary" :loading="exportLoading" @click="handleExport">导出</el-button>
<el-button class="Create-coures" type="primary" @click="addNewCourse()" icon="el-icon-plus">新建课程</el-button>
</el-col>
</el-row> -->
<div class="filter-extra-actions">
<el-button icon="el-icon-top" type="primary" @click="handleTopSort">置顶排序</el-button>
<el-button icon="el-icon-upload2" type="primary" :loading="exportLoading" @click="handleExport">导出</el-button>
<el-button class="Create-coures" type="primary" @click="addNewCourse()" icon="el-icon-plus">新建课程</el-button>
</div>
</div>
<div class="table-wrapper">
<el-table :data="pageData" border stripe>
<el-table-column v-if="forChoose" label="选择" width="80" align="center">
<template slot-scope="scope" v-if="scope.row.published">
<el-button type="default" size="mini" @click="handleChoose(scope.row)">选择</el-button>
</template>
</el-table-column>
<el-table-column label="课程名称" prop="name" header-align="center" align="left" min-width="220" show-overflow-tooltip fixed="left" sortable>
<template slot-scope="scope">
<span class="previewStyle" @click="viewTopic(scope.row)">{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column label="课程分类" prop="sysType" min-width="240" align="center" sortable>
<template slot-scope="scope">
<span>{{ sysTypeName(scope.row.sysType1) }}</span>
<span v-if="scope.row.sysType2 != ''">/{{ sysTypeName(scope.row.sysType2) }}</span>
<span v-if="scope.row.sysType3 != ''">/{{ sysTypeName(scope.row.sysType3) }}</span>
</template>
</el-table-column>
<el-table-column label="授课教师" prop="teacherName" min-width="160" show-overflow-tooltip align="center" sortable></el-table-column>
<el-table-column label="课程时长" min-width="140" align="center" :sort-method="sortByDuration" sortable>
<template slot-scope="scope">
{{ formatCourseDuration(scope.row) }}
</template>
</el-table-column>
<el-table-column label="学习人数" min-width="120" align="center" :sort-method="sortByStudyCount" sortable>
<template slot-scope="scope">
{{ formatStudyCount(scope.row) }}
</template>
</el-table-column>
<el-table-column label="课程评分" min-width="120" align="center" :sort-method="sortByScore" sortable>
<template slot-scope="scope">
{{ formatScore(scope.row) }}
</template>
</el-table-column>
<el-table-column label="审核状态" prop="status" min-width="120" align="center" sortable>
<template slot-scope="scope">
<span v-if="scope.row.status == 1">无审核状态</span>
<span v-if="scope.row.status == 2">审核中</span>
<span v-if="scope.row.status == 5">审核通过</span>
<span v-if="scope.row.status == 3">审核驳回</span>
</template>
</el-table-column>
<el-table-column label="发布状态" min-width="130" align="center" sortable>
<template slot-scope="scope">
{{ scope.row.published == true ? '已发布' : '未发布' }}
</template>
</el-table-column>
<el-table-column label="启用停用状态" min-width="130" align="center" sortable>
<template slot-scope="scope">
{{ scope.row.enabled == true ? '启用' : '停用' }}
</template>
</el-table-column>
<el-table-column label="排序" min-width="110" align="center" :sort-method="sortByOrderValue" sortable>
<template slot-scope="scope">
{{ formatOrderValue(scope.row) }}
</template>
</el-table-column>
<el-table-column label="公开课" min-width="110" align="center" sortable>
<template slot-scope="scope">
{{ scope.row.openCourse == 1 ? '公开课' : '非公开课' }}
</template>
</el-table-column>
<el-table-column label="资源归属" min-width="220" align="center" sortable>
<template slot-scope="scope">
<span>{{ resOwnerName(scope.row.resOwner1) }}</span>
<span v-if="scope.row.resOwner2 != ''">/{{ resOwnerName(scope.row.resOwner2) }}</span>
<span v-if="scope.row.resOwner3 != ''">/{{ resOwnerName(scope.row.resOwner3) }}</span>
</template>
</el-table-column>
<el-table-column label="创建人" prop="sysCreateBy" min-width="130" align="center" sortable></el-table-column>
<el-table-column label="创建来源" min-width="140" align="center" sortable>
<template slot-scope="scope">
<span v-if="scope.row.createFrom === 'teacher'">教师端</span>
<span v-else-if="scope.row.createFrom === 'admin'">管理员端</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="创建时间" prop="sysCreateTime" min-width="200" show-overflow-tooltip align="center" sortable></el-table-column>
<el-table-column label="操作" min-width="280px" fixed="right" header-align="center" align="left">
<template slot-scope="scope" class="btn-gl">
<!-- 20240621 修改scope.row.isPermission = fasle 时不展示操作按钮-->
<el-button
v-if="scope.row.isPermission && !forChoose && scope.row.status == 2"
type="text"
size="mini"
@click="withdraw(scope.row)"
>撤回</el-button>
<el-button
v-if="scope.row.isPermission && !forChoose && scope.row.published"
@click="showManageStudy(scope.row)"
type="text"
size="mini"
>管理</el-button>
<el-button v-if="scope.row.isPermission && scope.row.status != 2" type="text" size="mini"
@click="editCurriculum(scope.row)">编辑</el-button>
<el-button
v-if="scope.row.isPermission && (scope.row.status != 2 && !scope.row.published) || scope.row.isPermission && !scope.row.enabled"
type="text" style="color: #F56C6C;" size="mini" @click="delItem(scope.row)">删除</el-button>
<el-dropdown v-if="scope.row.isPermission && scope.row.published" type="text" size="mini"
style="margin-left:10px">
<el-button type="text" size="mini">更多<i class="el-icon-arrow-down el-icon--right"></i></el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="copyCourse(scope.row)">复制</el-dropdown-item>
<el-dropdown-item v-if="scope.row.published" @click.native="isDisable(scope.row)">{{ scope.row.enabled ?
'停用':'启用'}}</el-dropdown-item>
<el-dropdown-item v-if="scope.row.published"
@click.native="showQrimage(scope.row)">二维码</el-dropdown-item><!--发布之后才可以查看二维码-->
<el-dropdown-item v-if="scope.row.published" @click.native="setTop(scope.row)">{{ scope.row.isTop ?
'取消置顶':'置顶'}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
</div>
<div class="pagination">
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-page="page.pageIndex" :page-sizes="[10, 20, 30, 40]" :page-size="page.pageSize"
layout="total, sizes, prev, pager, next, jumper" :total="page.count"></el-pagination>
</div>
<!-- <div style="height: 100px;"></div> -->
<!--邀请审核-->
<el-dialog custom-class="g-dialog" title="邀请教师审核课程" :visible.sync="inviteTeacher.dlgShow">
<div style="display: flex;justify-content:flex-start;padding-bottom: 10px;">
<div style="padding: 0px 5px;"><el-input placeholder="姓名" v-model="inviteTeacher.params.name"></el-input></div>
<div style="padding: 0px 5px;"><el-button @click="findTeachers()" icon="el-icon-search"
type="primary">搜索</el-button></div>
</div>
<div>
<el-table max-height="500" border :data="inviteTeacher.list" style="width: 100%">
<el-table-column prop="name" label="姓名" width="180"></el-table-column>
<el-table-column prop="sex" label="性别"></el-table-column>
<el-table-column prop="code" label="工号"></el-table-column>
<el-table-column prop="orgInfo" label="组织"></el-table-column>
<el-table-column prop="orgInfo" label="选择">
<template slot-scope="scope">
<el-radio v-model="scope.row.checked">选择</el-radio>
</template>
</el-table-column>
</el-table>
</div>
<template #footer>
<el-button @click="inviteTeacher.dlgShow = false"> </el-button>
<el-button type="primary" @click="enSure">确认</el-button>
</template>
</el-dialog>
<!--课程管理-->
<el-dialog custom-class="g-dialog" title="课程学习管理" width="900px" height="900px" :visible.sync="manageStudy.dlgShow"
:close-on-click-modal="false">
<manager :manageStudyData="manageStudyData" :isShowDialog="manageStudy.dlgShow"></manager>
<template #footer>
<el-button @click="manageStudy.dlgShow = false">关闭</el-button>
</template>
</el-dialog>
<el-dialog title="二维码" center :visible.sync="qrcodeShow" @close="closeCode" width="500px">
<div style="height:250px;display: flex;flex-direction:column;justify-content: space-evenly;align-items: center;">
<div id="qrcode" ref="qrcode"></div>
<el-input v-model="pcDetailUrl" id="text"></el-input>
<el-button type="primary" size="mini" @click="myCopy()">复制链接</el-button>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="qrcodeShow = false"> </el-button>
<el-button type="primary" @click="qrcodeShow = false"> </el-button>
</span>
</el-dialog>
<!-- 审核 -->
<el-dialog title="审核" :visible.sync="dialogVisible" width="900px" custom-class="g-dialog">
<div v-show="expandDetails">
<div v-if="examin.detailType == 10">
<auditCourse1 :id="examin.examineId"></auditCourse1>
</div>
<div v-if="examin.detailType == 20">
<auditCourse2 :id="examin.examineId"></auditCourse2>
</div>
</div>
<div style="border-top: 1px solid #eee; background-color: #eee; padding: 5px;">
<div style=" text-align: center;margin-bottom: 10px;">
<el-button @click="expandDetails = !expandDetails">{{ expandDetails ? '详情折叠' : '详情展开' }}</el-button>
<el-button @click="isExamine = 1">直接审核</el-button>
<el-button @click="isExamine = 2">邀请审核</el-button>
</div>
<el-form label-width="80px" v-if="isExamine === 1">
<el-form-item label="审核">
<el-radio-group v-model="auditInfo.pass">
<el-radio :label="true">通过</el-radio>
<el-radio :label="false">不通过</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="审核意见"><el-input v-model="auditInfo.remark" type="textarea"
rows="3"></el-input></el-form-item>
</el-form>
<div v-if="isExamine === 2">
<div style="display: flex;justify-content:flex-start;padding-bottom: 10px;">
<div style="padding: 0px 5px;"><el-input placeholder="姓名" v-model="inviteTeacher.params.name"></el-input>
</div>
<div style="padding: 0px 5px;"><el-button @click="findTeachers()" icon="el-icon-search"
type="primary">搜索</el-button></div>
</div>
<div>
<el-table v-if="inviteTeacher.list.length !== 0" max-height="500" border :data="inviteTeacher.list"
style="width: 100%;margin-bottom: 10px;">
<el-table-column prop="name" label="姓名" width="180"></el-table-column>
<el-table-column prop="code" label="工号"></el-table-column>
<el-table-column prop="orgInfo" label="组织"></el-table-column>
<el-table-column prop="orgInfo" label="选择">
<template slot-scope="scope">
<el-radio v-model="scope.row.checked">选择</el-radio>
</template>
</el-table-column>
</el-table>
<div>审核记录</div>
<el-table max-height="500" border :data="inviteTeacher.list" style="width: 100%;">
<el-table-column prop="name" label="姓名" width="180"></el-table-column>
<el-table-column prop="code" label="工号"></el-table-column>
<el-table-column prop="orgInfo" label="组织"></el-table-column>
<el-table-column prop="type" label="审核状态"></el-table-column>
<el-table-column prop="text" label="备注"></el-table-column>
</el-table>
</div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="examineData()">提交</el-button>
</span>
</el-dialog>
<el-dialog v-if="showDetails" title="课程详情" :visible.sync="showDetails" @close="examin = {};" width="900px"
custom-class="g-dialog">
<div v-show="expandDetails">
<div v-if="examin.detailType == 10">
<auditCourse1 :showTest="true" :isDetails="false" :isShow="false" :id="examin.examineId"></auditCourse1>
</div>
<div v-if="examin.detailType == 20">
<auditCourse2 :showTest="true" :isDetails="false" :isShow="false" :id="examin.examineId"></auditCourse2>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="showDetails = false; examin = {};"> </el-button>
</span>
</el-dialog>
<div>
<course-form ref="courseForm" @submitSuccess="searchData" @close="searchData"></course-form>
</div>
<top-course-sorter ref="topSorter" @sorted="onTopSorted"></top-course-sorter>
</div>
</template>
<script>
import courseForm from '@/components/Course/courseForm.vue';
import manager from '@/components/Study/manager.vue';
import QRCode from 'qrcodejs2';
import auditCourse1 from '@/components/Course/auditCourse1.vue';
import auditCourse2 from '@/components/Course/auditCourse2.vue';
import adminPage from '@/components/Administration/adminPage.vue';
import TopCourseSorter from '@/components/Course/TopCourseSorter.vue';
import apiResowner from '../../api/modules/resowner.js';
import apiType from '../../api/modules/type.js'
import { courseType } from '../../utils/tools.js';
import apiCourse from '../../api/modules/course.js';
// import {resOwnerIndexName,sysTypeIndexName} from '@/utils/type.js';
import { mapGetters, mapActions } from 'vuex';
import apiUserbasic from "@/api/boe/userbasic.js"
import apiTeacher from '../../api/modules/teacher.js';
export default {
name: 'manageCourse',
components: { courseForm, manager, auditCourse1, auditCourse2, adminPage, TopCourseSorter },
computed: {
...mapGetters(['resOwnerMap', 'sysTypeMap', 'userInfo']),
},
data() {
return {
audiences: [],
forChoose: false,
pageManage: false,
showDetails: false,
examin: {
detailType: '',
examineId: '',
examineName: '',
},
paperJson: { items: [] },
courseType: courseType,
sysTypeListMap: [],
sysTypeList: [],
resOwnerListMap: [],
page: {
pageIndex: 1,//第几页
pageSize: 10, // 每页多少条
count: 0
},
exportLoading: false,
resOwner: [],
resOwnerFilterWarned: false,
defaultProps: {
value: 'code',
label: 'name',
},
defaultTypeProps: {
value: 'id',
label: 'name',
},
showAdvancedFilter: false,
learningTimeRange: [],
pickerOptions: { shortcuts: [] },
manageStudyData: {},
expandDetails: true,
qrcode: '',
pcDetailUrl: '',
isExamine: 1,
auditInfo: {
pass: true,
remark: ''
},
dialogVisible: false,
qrcodeShow: false,
currentPage4: 4,
inviteTeacher: {
//邀请
dlgShow: false,
params: { name: '' },
list: []
},
manageStudy: {
dlgShow: false
},
params: {
name: '',
teacherId: '',
learningTimeStart: '',
learningTimeEnd: '',
status: '',
publish: '',
enabled: '',
openCourse: '',
createUserId: '',
createFrom: '',
},
pageData: [],
courseChooseShow: false,
courseChooseId: '',
teacherFilterList: [],
teacherSelected: [],
teacherOptions: [],
teacherLoading: false,
creatorSelected: [],
creatorOptions: [],
creatorLoading: false,
courseTypes: [
{ id: '1', img: this.webBaseUrl + '/images/ctype1.png', name: '微课', info: '一种单一课件的课程' },
{ id: '2', img: this.webBaseUrl + '/images/ctype2.png', name: '在线课', info: '有章节多课件的课程' },
{ id: '3', img: this.webBaseUrl + '/images/ctype3.png', name: '课程包', info: '微课和在线课组成' },
{ id: '4', img: this.webBaseUrl + '/images/ctype4.png', name: '线下课', info: 'XXXX' },
{ id: '5', img: this.webBaseUrl + '/images/ctype5.png', name: '直播课', info: 'XXXX' }
],
weike: {
onlyRequired: false,
dlgShow: false,
fileType: '',
info: {
shebei: ''
}
},
biaoke: {
dlgShow: false
},
recommend: {
dlgShow: false,
},
catalogs: {
addNewZhang: false,
addNewCell: false,
},
extendRefId: '',
extendRefType: '',
};
},
created() {
this.pickerOptions = this.buildPickerOptions();
},
mounted() {
this.getAudiences()
let chooseFlag = this.$route.query.f;
this.extendRefId = this.$route.query.refId;
this.extendRefType = this.$route.query.refType;
if (chooseFlag && chooseFlag == 'choose') {
this.forChoose = true;
}
if (this.$route.query && this.$route.query.page && this.$route.query.page == 'manage') {
this.pageManage = true;
}
if (this.$route.query && this.$route.query.open && this.$route.query.open == 'new') {
this.addNewCourse();
}
// this.getTree();
// this.getTypeData();
// this.searchData();
this.getResOwnerTree().then(rs => {
this.resOwnerListMap = rs;
});
// 取消全局课程分类
this.getSysTypeTree().then(rs => {
this.sysTypeListMap = rs;
})
//已经加载tree的情况下不需要再单独的加载一次
this.loadResOwners();
this.loadSysTypes();
},
methods: {
toggleAdvancedFilter() {
this.showAdvancedFilter = !this.showAdvancedFilter;
},
async remoteSearchTeacher(keyword) {
const query = (keyword || '').trim();
if (!query || query.length <= 1) {
this.teacherOptions = [];
return;
}
if (query.length > 50) {
this.$message.warning('授课教师搜索最多50个字符');
return;
}
this.teacherLoading = true;
try {
const res = await apiTeacher.findByNameNew(query);
if (res && res.code === 200) {
const teacherList = res.data || [];
this.teacherOptions = teacherList
.map(item => this.formatTeacherItem(item))
.filter(item => item.teacherId);
} else {
this.teacherOptions = [];
}
} catch (error) {
this.teacherOptions = [];
} finally {
this.teacherLoading = false;
}
},
formatTeacherItem(item = {}) {
return {
teacherId: item.id,
teacherName: item.name,
teacherCode: item.mobile || '',
};
},
formatTeacherLabel(item = {}) {
if (!item.teacherName) {
return '';
}
return item.teacherCode ? `${item.teacherName}(${item.teacherCode})` : item.teacherName;
},
handleTeacherChange(value = []) {
this.params.teacherId = (value || []).join(',');
},
handleTeacherClear() {
this.teacherSelected = [];
this.teacherOptions = [];
this.params.teacherId = '';
},
async remoteSearchCreator(keyword) {
const query = (keyword || '').trim();
if (!query || query.length <= 1) {
this.creatorOptions = [];
return;
}
if (query.length > 50) {
this.$message.warning('创建人搜索最多50个字符');
return;
}
this.creatorLoading = true;
try {
const res = await apiUserbasic.selectUser(query);
if (res && res.status === 200) {
const resultList = res.result || [];
this.creatorOptions = resultList
.map(item => this.formatCreatorItem(item))
.filter(item => item.userId);
} else {
this.creatorOptions = [];
}
} catch (error) {
this.creatorOptions = [];
} finally {
this.creatorLoading = false;
}
},
formatCreatorItem(item = {}) {
return {
userId: item.id,
name: item.realName,
code: item.userNo,
};
},
formatCreatorLabel(item = {}) {
if (!item.name) {
return '';
}
return item.code ? `${item.name}(${item.code})` : item.name;
},
handleCreatorChange(value = []) {
this.params.createUserId = (value || []).slice(0, 5).join(',');
},
handleCreatorClear() {
this.creatorSelected = [];
this.params.createUserId = '';
},
handleTopSort() {
if (this.$refs.topSorter) {
this.$refs.topSorter.open();
}
},
onTopSorted() {
this.searchData(true);
},
handleLearningTimeRangeChange(val) {
if (val && val.length === 2) {
this.params.learningTimeStart = val[0];
this.params.learningTimeEnd = val[1];
} else {
this.params.learningTimeStart = '';
this.params.learningTimeEnd = '';
}
},
buildPickerOptions() {
const shortcutConfigs = [
{
text: '今年以来',
range: () => {
const end = new Date();
const start = new Date(end.getFullYear(), 0, 1);
return [start, end];
}
},
{
text: '最近一年',
range: () => {
const end = new Date();
const start = new Date();
start.setTime(end.getTime() - 3600 * 1000 * 24 * 365);
return [start, end];
}
},
{
text: '最近三个月',
range: () => {
const end = new Date();
const start = new Date();
start.setTime(end.getTime() - 3600 * 1000 * 24 * 90);
return [start, end];
}
},
{
text: '最近一个月',
range: () => {
const end = new Date();
const start = new Date();
start.setTime(end.getTime() - 3600 * 1000 * 24 * 30);
return [start, end];
}
},
{
text: '最近一周',
range: () => {
const end = new Date();
const start = new Date();
start.setTime(end.getTime() - 3600 * 1000 * 24 * 7);
return [start, end];
}
}
];
return {
shortcuts: shortcutConfigs.map(item => ({
text: item.text,
onClick: picker => {
picker.$emit('pick', item.range());
}
}))
};
},
validateLearningTimeRange() {
const hasStart = !!this.params.learningTimeStart;
const hasEnd = !!this.params.learningTimeEnd;
if ((hasStart && !hasEnd) || (!hasStart && hasEnd)) {
this.$message.warning('请选择完整的培训时间范围');
return false;
}
return true;
},
formatCourseDuration(row) {
if (row.durationDesc) {
return row.durationDesc;
}
const duration = row.duration !== undefined
? row.duration
: (row.studyDuration !== undefined ? row.studyDuration : row.totalDuration);
if (duration === undefined || duration === null) {
return '-';
}
const minutes = Math.round(duration / 60);
if (minutes >= 60) {
const hours = (minutes / 60).toFixed(1);
return `${hours}小时`;
}
return `${minutes || 0}分钟`;
},
formatStudyCount(row) {
const count = row.studyCount !== undefined
? row.studyCount
: (row.studyNum !== undefined ? row.studyNum : (row.learners !== undefined ? row.learners : row.studyUsers));
if (count === undefined || count === null) {
return '-';
}
return count;
},
formatScore(row) {
if (row.score || row.score === 0) {
return row.score;
}
if (row.avgScore || row.avgScore === 0) {
return row.avgScore;
}
return '-';
},
getDurationNumber(row) {
const duration = row.duration !== undefined
? row.duration
: (row.studyDuration !== undefined ? row.studyDuration : row.totalDuration);
if (duration === undefined || duration === null) {
return 0;
}
return Number(duration) || 0;
},
sortByDuration(a, b) {
return this.getDurationNumber(a) - this.getDurationNumber(b);
},
getStudyCountNumber(row) {
const count = row.studyCount !== undefined
? row.studyCount
: (row.studyNum !== undefined ? row.studyNum : (row.learners !== undefined ? row.learners : row.studyUsers));
if (count === undefined || count === null) {
return 0;
}
return Number(count) || 0;
},
sortByStudyCount(a, b) {
return this.getStudyCountNumber(a) - this.getStudyCountNumber(b);
},
getScoreNumber(row) {
if (row.score || row.score === 0) {
return Number(row.score) || 0;
}
if (row.avgScore || row.avgScore === 0) {
return Number(row.avgScore) || 0;
}
return 0;
},
sortByScore(a, b) {
return this.getScoreNumber(a) - this.getScoreNumber(b);
},
getOrderNumber(row) {
if (row.orderIndex || row.orderIndex === 0) {
return Number(row.orderIndex) || 0;
}
if (row.orderValue || row.orderValue === 0) {
return Number(row.orderValue) || 0;
}
if (row.sortIndex || row.sortIndex === 0) {
return Number(row.sortIndex) || 0;
}
return 0;
},
sortByOrderValue(a, b) {
return this.getOrderNumber(a) - this.getOrderNumber(b);
},
formatOrderValue(row) {
if (row.orderIndex || row.orderIndex === 0) {
return row.orderIndex;
}
if (row.orderValue || row.orderValue === 0) {
return row.orderValue;
}
if (row.sortIndex || row.sortIndex === 0) {
return row.sortIndex;
}
return '-';
},
buildQueryParams() {
const query = {
...this.params,
pageIndex: this.page.pageIndex,
pageSize: this.page.pageSize,
};
Object.keys(query).forEach(key => {
if (query[key] === '' || query[key] === null) {
delete query[key];
}
});
const [resOne = '', resTwo = '', resThree = ''] = this.resOwner || [];
const [sysOne = '', sysTwo = '', sysThree = ''] = this.sysTypeList || [];
query.orgld = resOne;
query.orgName = resTwo;
query.orgFullName = resThree;
query.sysType1 = sysOne;
query.sysType2 = sysTwo;
query.sysType3 = sysThree;
if (this.params.name) {
query.keyword = this.params.name;
}
if (this.$route.query.courseIds) query.courseIds = this.$route.query.courseIds.split(',');
if (this.$route.query.projectId) query.projectId = this.$route.query.projectId;
if (this.audiences && this.audiences.length > 0) {
query.audiences = this.audiences.join(',');
}
query.isCreateCourse = !this.pageManage;
return query;
},
async handleExport() {
if (!this.validateLearningTimeRange()) {
return;
}
const query = this.buildQueryParams();
delete query.pageIndex;
delete query.pageSize;
this.exportLoading = true;
try {
const blob = await apiCourse.exportCourse(query);
if (!(blob instanceof Blob)) {
throw new Error('导出失败');
}
const link = document.createElement('a');
const url = window.URL.createObjectURL(blob);
const timestamp = new Date().toISOString().replace(/[-:T]/g, '').split('.')[0];
link.href = url;
link.download = `课程管理_${timestamp}.xlsx`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (error) {
this.$message.error(error.message || '导出失败');
} finally {
this.exportLoading = false;
}
},
getAudiences() {
apiUserbasic.getInAudienceIds().then(res => {
if (res.status == 200) {
this.audiences = res.result;
}
this.searchData();
})
},
handleChoose(row) { //选择课程
window.parent.selectCourse(row);
},
myCopy() {
var ele = document.getElementById("text");
ele.select();
document.execCommand("Copy");
},
// 置顶
setTop(row) {
let params = {
ids: row.id,//课程id,多个使用逗号分隔,
title: row.name,//课程的名称,
top: !row.isTop,// top 是否置顶}
}
console.log(row.isTop);
if (row.isTop == false) {
// console.log('fa')
this.$confirm('此操作将置顶此课程, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
apiCourse.setTop(params).then(res => {
if (res.status === 200 && res.result === true) {
this.$message.success('置顶成功!')
this.searchData();
} else {
this.$message({
type: 'error',
message: res.message
});
}
})
}).catch(() => {
this.$message({
type: 'info',
message: '置顶失败'
});
});
} else if (row.isTop == true) {
this.$confirm('此操作将取消置顶此课程, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
apiCourse.setTop(params).then(res => {
if (res.status === 200 && res.result === true) {
this.$message.success('取消成功!')
this.searchData();
} else {
this.$message({
type: 'error',
message: res.message
});
}
})
}).catch(() => {
this.$message({
type: 'info',
message: '取消置顶失败'
});
});
}
},
// 复制
copyCourse(item) {
const confirmText = `确认复制${item.name}吗?`;
this.$confirm(confirmText, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const reqdata = {
id: item.id,
refId: this.extendRefId,
refType: this.extendRefType,
};
apiCourse.copyCourse(reqdata).then(rs => {
if (rs.status === 200) {
this.$message({ type: 'success', message: '复制成功', duration: 5000 });
this.searchData();
} else {
this.$message({ type: 'error', message: rs.message || '复制失败', duration: 5000 });
}
}).catch(() => {
this.$message({ type: 'error', message: '复制失败', duration: 5000 });
});
}).catch(() => {});
},
// 撤回接口
withdraw(row) {
this.$confirm(`确定撤回${row.name}的审核申请吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
apiCourse.revokeSubmit(row.id).then((res) => {
if (res.status === 200 && res.result) {
this.$message({ type: 'success', message: '撤回成功', duration: 5000 });
this.searchData();
} else {
this.$message({ type: 'error', message: res.message || '撤回失败', duration: 5000 });
}
}).catch(() => {
this.$message({ type: 'error', message: '撤回失败', duration: 5000 });
});
}).catch(() => {});
},
reset() {
this.resOwner = [];
this.sysTypeList = [];
this.learningTimeRange = [];
this.teacherFilterList = [];
this.teacherSelected = [];
this.teacherOptions = [];
this.teacherLoading = false;
this.creatorSelected = [];
this.creatorOptions = [];
Object.assign(this.params, {
name: '',
teacherId: '',
learningTimeStart: '',
learningTimeEnd: '',
status: '',
publish: '',
enabled: '',
openCourse: '',
createUserId: '',
createFrom: '',
});
this.searchData(true);
},
...mapActions({
getResOwnerTree: 'resOwner/getResOwnerTree',
loadResOwners: 'resOwner/loadResOwners',
getSysTypeTree: 'sysType/getSysTypeTree',
loadSysTypes: 'sysType/loadSysTypes'
}),
resOwnerName(code) {
if (code == '') { return ''; }
return this.resOwnerMap.get(code);
},
sysTypeName(code) {
if (code == '') { return ''; }
return this.sysTypeMap.get(code);
},
// 直接审核
examineData() {
if (this.isExamine == 1) {
let params = {
id: this.examin.examineId,//课程id,
title: this.examin.examineName,//课程的名称,
pass: this.auditInfo.pass,//Boolean 是否通过,
remark: this.auditInfo.remark,// 备注
}
apiCourse.audit(params).then(res => {
if (res.status === 200) {
this.$message.success('操作成功!');
this.dialogVisible = false;
this.searchData();
} else {
this.$message.error(res.message);
}
})
} else {
this.$message.warning('暂未开放!');
}
},
addNewCourse() {
this.$refs.courseForm.initShow();
},
editCurriculum(row) {
this.$refs.courseForm.initShow(row);
},
// 课程启停
async isDisable(row) {
const nextEnabled = !row.enabled;
const actionText = nextEnabled ? '启用' : '停用';
try {
await this.$confirm(`确定${actionText}${row.name}吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
} catch (error) {
return;
}
const params = {
ids: row.id,
title: row.name,
enabled: nextEnabled,
};
try {
const { status } = await apiCourse.setEnabled(params);
if (status === 200) {
this.$message({ type: 'success', message: `${actionText}成功`, duration: 5000 });
row.enabled = nextEnabled;
} else {
this.$message({ type: 'error', message: `${actionText}失败`, duration: 5000 });
}
} catch (error) {
this.$message({ type: 'error', message: `${actionText}失败`, duration: 5000 });
}
},
// 课程查询
searchData(pageReset) {
if (this.params.name && this.params.name.length > 50) {
this.$message.warning('课程名称最多50个字符');
return;
}
if (pageReset) {
this.page.pageIndex = 1;
}
if (!this.validateLearningTimeRange()) {
return;
}
const query = this.buildQueryParams();
apiCourse.managePage(query).then(rs => {
if (rs.status == 200) {
this.pageData = rs.result.list ? rs.result.list : [];
// this.pageData = rs.result.list;
this.page.count = rs.result.count;
this.page.pageSize = rs.result.pageSize;
} else {
return this.$message.error(rs.message);
}
})
},
viewTopic(row) {
if (row.status == 1) {
return this.$message.warning('请提交课程再预览!')
}
this.examin = {};
this.examin.detailType = row.type;
this.examin.examineId = row.id;
this.examin.examineName = row.name;
this.showDetails = true;
},
jumpDetails() {
// this.$router.push('/course/micro');
// 跳转打开新页面
let routeData = this.$router.resolve({ path: '/course/detail' }); // , query: { id: 1 }
window.open(this.webBaseUrl + routeData.href, '_blank');
},
toExamine(row) {
this.auditInfo = { pass: true };
this.examin.detailType = row.type;
this.examin.examineId = row.id;
this.examin.examineName = row.name;
this.dialogVisible = true;
},
enSure() {
// 确认事件
},
handleSizeChange(val) {
this.page.pageSize = val;
this.page.pageIndex = 1;
this.searchData();
},
handleCurrentChange(val) {
this.page.pageIndex = val;
this.searchData();
},
chooseInvite(row) {
//邀请老师审核
this.inviteTeacher.dlgShow = true;
},
findTeachers() {
this.inviteTeacher.list = [
{ id: '1', name: '李玉冰', type: '通过', text: '实用', sex: '男', code: '1023123', orgInfo: '教育技术中心', checked: false },
{ id: '2', name: '李玉冰', type: '未通过', text: '内容在调整', sex: '男', code: '1023123', orgInfo: '教育技术中心', checked: false },
{ id: '3', name: '李玉冰', type: '驳回', text: '内容重复', sex: '男', code: '1023123', orgInfo: '教育技术中心', checked: false }
];
},
showQrimage(row) {
this.qrcodeShow = true;
let urlPre = window.location.protocol + '//' + window.location.host;
//动态的地址
//urlPre=urlPre+'/m?returnUrl='+urlPre+'/mobile/pages/login/loading?returnUrl=';
//固定的地址
let returnUrl = urlPre + '/mobile/pages/login/loading?returnUrl=/pages/study/courseStudy?id=' + row.id;
let mobilePre = urlPre + '/m?returnUrl=';
this.qrcode = mobilePre + encodeURIComponent(returnUrl);
this.pcDetailUrl = urlPre + this.webBaseUrl + '/course/studyindex?id=' + row.id;
if (row.type == 20) {
this.pcDetailUrl = urlPre + this.webBaseUrl + '/course/detail?id=' + row.id;
}
// 使用$nextTick确保数据渲染
this.$nextTick(() => {
this.crateQrcode();
});
},
// 生成二维码
crateQrcode() {
this.qr = new QRCode('qrcode', {
width: 150,
height: 150, // 高度
text: this.qrcode // 二维码内容
// render: 'canvas' // 设置渲染方式(有两种方式 table和canvas默认是canvas
// background: '#f0f'
// foreground: '#ff0'
});
},
// 关闭弹框,清除已经生成的二维码
closeCode() {
this.$refs.qrcode.innerHTML = '';
},
showManageStudy(row) {
this.manageStudyData = row;
this.manageStudy.dlgShow = true;
},
showChooseCourse() {
this.courseChooseShow = true;
},
chooseCourseType(item, idx) {
this.courseChooseId = item.id;
},
toInputCourse() {
if (this.courseChooseId == '1') {
this.showWeike();
} else if (this.courseChooseId == '2') {
this.showBiaoke();
}
this.courseChooseShow = false;
},
newHandleClick() { },
showRecords(item) {
this.recommend.dlgShow = true;
},
showWeike() {
this.weike.dlgShow = true;
},
showBiaoke() {
this.biaoke.dlgShow = true;
},
// setTop(item, idx) {
// let msg = '已设置置顶';
// if (item.isTop) {
// item.isTop = false;
// msg = '已取消置顶';
// } else {
// item.isTop = true;
// }
// this.$message({ message: msg, type: 'success', offset: 50 });
// },
delItem(row) {
this.$confirm(`确认删除${row.name}吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
let params = {
id: row.id,
title: row.name,
};
try {
// {id:课程id,多个使用逗号分隔,Boolean erasable 是否物理删除,title:课程的名称, remark 备注}
const { status } = await apiCourse.del(params);
if (status === 200) {
this.$message({ type: 'success', message: '删除成功', duration: 5000 }); //只是之前发布过的课程删除才可
// if(!row.erasable){
// let event = {
// key: "CourseDelete",//被管理员删除
// title: '被管理员删除课程',//事件的标题
// parameters:"author:"+row.sysCreateAid,//作者,一般这种情况不是管理员自己添加的
// content: '被管理员删除课程',//事件的内容
// objId: row.id,//关联的id
// objType: "1",//关联的类型
// objInfo:row.name,
// aid: this.userInfo.aid, //当前登录人的id
// aname: this.userInfo.name,//当前人的姓名
// status: 1 //状态直接写1
// }
// this.$store.dispatch("userTrigger", event);
// }
this.searchData();
}
} catch (error) {
this.$message({ type: 'error', message: '删除失败', duration: 5000 });
}
})
.catch((err) => {
this.$message({ type: 'info', message: '已取消删除', duration: 5000 });
});
},
showAddCatalogZhang(bal) {
this.catalogs.addNewZhang = bal;
},
saveNewCatalogZhang() {
this.catalogs.addNewZhang = false;
},
}
};
</script>
<style lang="scss" scoped>
.manage-list-remote {
background-color: #fff;
overflow: hidden;
}
.sou {
padding: 0 0 0 0px !important;
}
.el-col {
padding: 0 0 0 10px !important;
}
.table-wrapper {
padding: 0 20px;
}
.grid-content {
padding-right: 0px;
}
.el-button--text {
font-size: 14px;
}
.filter-wrapper {
padding: 12px 12px 10px 22px;
}
.filter-form {
display: flex;
flex-wrap: wrap;
align-items: center;
margin-bottom: 10px;
}
.filter-form .el-form-item {
margin-right: 10px;
margin-bottom: 0;
}
.teacher-filter {
::v-deep .choice {
width: 100%;
}
}
.filter-form--primary {
flex-wrap: nowrap;
}
.filter-form--advanced {
flex-wrap: nowrap;
}
.filter-form--primary .filter-fields,
.filter-form--advanced .filter-fields {
display: flex;
flex-wrap: wrap;
align-items: center;
}
.filter-form--primary .filter-actions {
margin-left: 20px;
}
.filter-actions {
display: flex;
align-items: center;
flex-shrink: 0;
}
.filter-actions--inline {
padding-top: 0;
}
.filter-actions .el-button+.el-button {
margin-left: 10px;
}
.toggle-link {
padding: 0;
}
.filter-extra-actions {
text-align: right;
padding-right: 10px;
}
::v-deep .el-table .el-table__body-wrapper::-webkit-scrollbar{
display: block;
}
.advanced-filter {
margin-top: 10px;
}
.learning-time-range .el-date-editor {
width: 300px;
}
::v-deep .el-pagination {
// margin-top: 16px;
// padding: 0;
// width: 50%;
}
.pagination {
text-align: center;
padding: 40px 0 80px 0;
}
.course-types {
display: flex;
justify-content: center;
padding: 15px;
.course-type {
margin: 10px;
text-align: center;
padding: 5px;
cursor: pointer;
img {
width: 110px;
height: 110px;
}
.info {
padding-top: 10px;
}
}
.choose {
border: 2px solid #008000;
}
}
.el-aside {
padding: 5px 10px;
}
.cctree {
.cctree-chapter {
.cctree-chapter-name {
border-bottom: 1px solid #dddddd;
}
.cctree-chapter-cells {
margin: 0px;
list-style-type: circle;
padding: 0px;
.cctree-chapter-cell {
line-height: 30px;
list-style-type: circle;
padding-left: 25px;
}
}
}
}
.el-dialog__body {
overflow: hidden;
}
::v-deep .el-table .el-table__body-wrapper::-webkit-scrollbar-thumb {
border-radius: 3px;
background-color: rgb(78, 166, 255);
}
:global(#app) {
overflow: hidden;
}
:global(::-webkit-scrollbar) {
width: 8px;
height: 10px;
}
:global(::-webkit-scrollbar-thumb) {
border-radius: 3px;
background-color: rgb(78, 166, 255);
}
::v-deep .el-table .el-table__body-wrapper::-webkit-scrollbar {
width: 8px;
height: 10px;
}
::v-deep .el-table .el-table__body-wrapper::-webkit-scrollbar-thumb {
border-radius: 3px;
background-color: rgb(78, 166, 255);
}
</style>