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

2529 lines
78 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">
<div class="filter-row filter-row--primary">
<div class="filter-fields">
<div class="filter-field filter-field--name">
<el-input type="text" placeholder="课程名称" v-model="params.name" clearable maxlength="50"></el-input>
</div>
<div class="filter-field filter-field--category">
<el-cascader placeholder="课程分类" clearable v-model="sysTypeList" :props="defaultTypeProps"
:options="sysTypeListMap"></el-cascader>
</div>
<div class="filter-field filter-field--teacher teacher-filter">
<div class="teacher-select-wrapper">
<el-select
class="creator-select"
ref="teacherSelect"
v-model="teacherSelected"
multiple
filterable
remote
clearable
reserve-keyword
placeholder="授课教师"
:multiple-limit="5"
:remote-method="remoteSearchTeacher"
:loading="teacherLoading"
@input.native="limitTeacherInput"
@visible-change="handleTeacherVisibleChange"
@change="handleTeacherChange"
@clear="handleTeacherClear"
>
<el-option v-for="item in teacherOptions" :key="item.teacherId" :label="item.teacherName"
:value="item.teacherId">
<span>{{ item.teacherName }}</span>
<span v-if="item.teacherCode" class="option-code">{{ item.teacherCode }}</span>
</el-option>
</el-select>
</div>
</div>
<div class="filter-field filter-field--time learning-time-range">
<div :class="[
'grid-content',
'bg-purple',
'resetDatePicker',
!learningTimeRange || learningTimeRange.length === 0 ? 'noSplitDatePicker' : ''
]">
<el-date-picker v-model="learningTimeRange" type="daterange" align="right" unlink-panels clearable
value-format="yyyy-MM-dd 00:00:00" range-separator="" start-placeholder="培训时间"
:picker-options="pickerOptions" @change="handleLearningTimeRangeChange"></el-date-picker>
</div>
</div>
<div class="filter-field filter-field--status">
<el-select v-model="params.status" placeholder="审核状态" clearable>
<el-option label="-" value="1"></el-option>
<el-option label="审核中" value="2"></el-option>
<el-option label="审核通过" value="5"></el-option>
<el-option label="审核驳回" value="3"></el-option>
</el-select>
</div>
<div class="filter-field filter-field--publish">
<el-select v-model="params.publish" placeholder="发布状态" clearable>
<el-option label="已发布" :value="true"></el-option>
<el-option label="未发布" :value="false"></el-option>
</el-select>
</div>
</div>
<div class="filter-actions" v-show="!showAdvancedFilter">
<el-button type="text" class="toggle-link" @click="toggleAdvancedFilter">
{{ showAdvancedFilter ? '收起' : '展开' }}
<i :class="showAdvancedFilter ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i>
</el-button>
<el-tooltip content="查询" placement="top" effect="dark" popper-class="icon-btn-tooltip">
<div class="icon-btn icon-btn--search" @click="searchData(true)" aria-label="查询"></div>
</el-tooltip>
<el-tooltip content="重置" placement="top" effect="dark" popper-class="icon-btn-tooltip">
<div class="icon-btn icon-btn--reset" @click="reset" aria-label="重置"></div>
</el-tooltip>
</div>
</div>
<div v-if="showAdvancedFilter" class="filter-row filter-row--advanced advanced-filter">
<div class="filter-fields">
<div class="filter-field filter-field--enabled">
<el-select v-model="params.enabled" placeholder="启停用状态" clearable>
<el-option label="启用" :value="true"></el-option>
<el-option label="停用" :value="false"></el-option>
</el-select>
</div>
<div class="filter-field filter-field--open">
<el-select v-model="params.openCourse" placeholder="是否公开课" clearable>
<el-option label="是" :value="1"></el-option>
<el-option label="否" :value="0"></el-option>
</el-select>
</div>
<div class="filter-field filter-field--resowner">
<el-cascader
ref="resOwnerCascader"
v-model="resOwnerSelected"
placeholder="资源归属"
clearable
:props="resOwnerCascaderProps"
:show-all-levels="false"
@change="handleResOwnerChange"
@input.native="limitResOwnerInput"
filterable
:filter-method="resOwnerFilterMethod"
></el-cascader>
</div>
<div class="filter-field filter-field--creator creator-filter">
<div class="teacher-select-wrapper">
<el-select
class="creator-select"
ref="creatorSelect"
v-model="creatorSelected"
multiple
filterable
remote
clearable
reserve-keyword
placeholder="创建人"
:multiple-limit="5"
:remote-method="remoteSearchCreator"
:loading="creatorLoading"
@input.native="limitCreatorInput"
@visible-change="handleCreatorVisibleChange"
@change="handleCreatorChange"
@clear="handleCreatorClear"
>
<el-option v-for="item in creatorOptions" :key="item.userId" :label="item.name" :value="item.userId">
<span>{{ item.name }}</span>
<span v-if="item.code" class="option-code">{{ item.code }}</span>
</el-option>
</el-select>
</div>
</div>
<div class="filter-field filter-field--create-from">
<el-select v-model="params.createFrom" placeholder="创建来源" clearable>
<el-option label="教师端" value="teacher"></el-option>
<el-option label="管理端" value="admin"></el-option>
</el-select>
</div>
</div>
<div class="filter-actions filter-actions--inline">
<el-button type="text" class="toggle-link" @click="toggleAdvancedFilter">
{{ showAdvancedFilter ? '收起' : '展开' }}
<i :class="showAdvancedFilter ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i>
</el-button>
<el-tooltip content="查询" placement="top" effect="dark" popper-class="icon-btn-tooltip">
<div class="icon-btn icon-btn--search" @click="searchData(true)" aria-label="查询"></div>
</el-tooltip>
<el-tooltip content="重置" placement="top" effect="dark" popper-class="icon-btn-tooltip">
<div class="icon-btn icon-btn--reset" @click="reset" aria-label="重置"></div>
</el-tooltip>
</div>
</div>
</div>
<div class="table-wrapper">
<div class="filter-extra-actions">
<div class="create-course-btn" @click="addNewCourse()" aria-label="新建课程">新建课程</div>
<el-tooltip v-if="showSetTopFeature" content="置顶排序" placement="top" effect="dark" popper-class="icon-btn-tooltip">
<div class="icon-btn icon-btn--top" @click="handleTopSort" aria-label="置顶排序"></div>
</el-tooltip>
<el-tooltip content="导出" placement="top" effect="dark" popper-class="icon-btn-tooltip">
<div class="icon-btn icon-btn--export" :class="{ 'is-loading': exportLoading, 'is-disabled': exportLoading }"
@click="!exportLoading && handleExport()" aria-label="导出"></div>
</el-tooltip>
</div>
<el-table :data="pageData" @sort-change="handleSortChange">
<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="360"
show-overflow-tooltip fixed="left" sortable="custom">
<template slot-scope="scope">
<span class="course-name" @click="viewTopic(scope.row)">{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column label="课程分类" prop="sysType" min-width="180" align="center" sortable="custom" show-overflow-tooltip>
<template slot-scope="scope">
<span class="common-cell common-cell-right single-line-ellipsis">{{ formatSysTypeChain(scope.row) }}</span>
</template>
</el-table-column>
<el-table-column label="授课教师" prop="teacherName" :min-width="teacherColumnWidth" align="center" >
<template slot-scope="scope">
<span class="common-cell">{{ scope.row.teacherName }}</span>
</template>
</el-table-column>
<el-table-column label="课程时长" prop="courseDuration" min-width="110" align="center" sortable="custom">
<template slot-scope="scope">
<span class="common-cell common-cell-right">{{ formatCourseDuration(scope.row) }}</span>
</template>
</el-table-column>
<el-table-column label="学习人数" prop="studys" min-width="110" align="center" sortable="custom">
<template slot-scope="scope">
<span class="common-cell common-cell-right">{{ scope.row.studys || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="课程评分" prop="score" min-width="110" align="center" sortable="custom">
<template slot-scope="scope">
<span class="common-cell common-cell-right">{{ formatScore(scope.row) }}</span>
</template>
</el-table-column>
<el-table-column label="审核状态" prop="status" min-width="110" align="center" sortable="custom">
<template slot-scope="scope">
<span class="common-cell common-cell-right" v-if="scope.row.status == 1">-</span>
<span class="common-cell common-cell-right" v-if="scope.row.status == 2">审核中</span>
<span :class="['common-cell common-cell-right', 'status--pass']" v-if="scope.row.status == 5">审核通过</span>
<span :class="['common-cell common-cell-right', 'status--reject']" v-if="scope.row.status == 3">审核驳回</span>
</template>
</el-table-column>
<el-table-column label="发布状态" prop="published" min-width="110" align="center" sortable="custom">
<template slot-scope="scope">
<span class="common-cell common-cell-right">{{ scope.row.published == true ? '已发布' : '未发布' }}</span>
</template>
</el-table-column>
<el-table-column label="启停用状态" prop="enabled" min-width="130" align="center" sortable="custom">
<template slot-scope="scope">
<span class="common-cell common-cell-right">{{ scope.row.enabled == true ? '启用' : '停用' }}</span>
</template>
</el-table-column>
<el-table-column label="公开课" prop="openCourse" min-width="110" align="center" sortable="custom">
<template slot-scope="scope">
<span class="common-cell common-cell-right">{{ scope.row.openCourse == 1 ? '是' : '否' }}</span>
</template>
</el-table-column>
<el-table-column label="资源归属" prop="orgName" min-width="220" align="center" sortable="custom">
<template slot-scope="scope">
<el-tooltip :content="scope.row.orgFullName || scope.row.orgName" placement="top" effect="dark">
<span class="common-cell common-cell-right">{{ scope.row.orgName }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="创建人" prop="sysCreateBy" min-width="130" align="center" sortable="custom" show-overflow-tooltip>
<template slot-scope="scope">
<span class="common-cell common-cell-right">{{ scope.row.sysCreateBy }}</span>
</template>
</el-table-column>
<el-table-column label="创建来源" min-width="140" align="center">
<template slot-scope="scope">
<span class="common-cell" v-if="scope.row.createFrom === 'teacher'">教师端</span>
<span class="common-cell" v-else-if="scope.row.createFrom === 'admin'">管理端</span>
<span class="common-cell" v-else>-</span>
</template>
</el-table-column>
<el-table-column label="创建时间" prop="sysCreateTime" min-width="200" show-overflow-tooltip align="center"
sortable="custom">
<template slot-scope="scope">
<span class="common-cell">{{ scope.row.sysCreateTime }}</span>
</template>
</el-table-column>
<el-table-column label="操作" min-width="280px" fixed="right" header-align="center" align="left">
<template slot-scope="scope" class="btn-gl">
<template v-if="getInlineActions(scope.row).length">
<span v-for="(action, index) in getInlineActions(scope.row)" :key="action.key">
<el-button
type="text"
size="mini"
:class="action.className"
@click="handleAction(action.key, scope.row)"
>
{{ action.label }}
</el-button>
<el-divider
v-if="index < getInlineActions(scope.row).length - 1 || (index === getInlineActions(scope.row).length - 1 && getDropdownActions(scope.row).length)"
direction="vertical"
></el-divider>
</span>
</template>
<el-dropdown
v-if="getDropdownActions(scope.row).length"
type="text"
size="mini"
style="margin-left:0px"
trigger="click"
>
<el-button type="text" size="mini" class="action-link--more">
更多<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
v-for="action in getDropdownActions(scope.row)"
:key="action.key"
:class="action.className"
@click.native="handleAction(action.key, scope.row)"
>
{{ action.label }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<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>
<!-- <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="二维码" :visible.sync="qrCodedialogVisible" width="900px" @close="closeCode" custom-class="g-dialog">
<div>
<el-form size="medium" label-width="100px">
<el-form-item label="二维码">
<div id="qrcode" ref="qrcode" class="qrcode-img" @mouseenter="showDownloadButton = true"
@mouseleave="showDownloadButton = false">
<div v-show="showDownloadButton" @click="downloadQrcode" class="downloadn-container">
<i class="el-icon-download" style="color: #409EFF;font-size:18px;margin-bottom:5px"></i>
<span>下载</span>
</div>
</div>
</el-form-item>
<el-form-item label="链接">
<el-input v-model="copyUrl" readonly class="input-with-select" id="text">
<el-button slot="append" @click="handleCopyUrl">复制</el-button>
</el-input>
</el-form-item>
<el-form-item label="">上述内容兼容PC端与移动端您可按需分享</el-form-item>
</el-form>
</div>
<span slot="footer" class="dialog-footer"><el-button @click="closeCode"> </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 v-if="showSetTopFeature" 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', 'identity']),
resOwnerCascaderProps() {
return {
value: 'id',
label: 'name',
children: 'children',
lazy: true,
lazyLoad: this.loadResOwnerNode,
leaf: 'leaf',
checkStrictly: true // 允许选择任意一级选项
};
},
// 动态计算“授课教师”列的最小宽度,避免固定 260px
teacherColumnWidth() {
return this.calcColumnWidth('授课教师', 'teacherName');
}
},
data() {
return {
audiences: [],
forChoose: false,
pageManage: false,
showDetails: false,
examin: {
detailType: '',
examineId: '',
examineName: '',
},
paperJson: { items: [] },
courseType: courseType,
sysTypeListMap: [],
sysTypeList: [],
resOwnerListMap: [],
resOwnerSelected: [],
resOwnerCascaderOptions: [],
showSetTopFeature: false,
page: {
pageIndex: 1,//第几页
pageSize: 10, // 每页多少条
count: 0
},
exportLoading: false,
resOwner: [],
resOwnerFilterWarned: false,
orgId: '',
orgName: '',
defaultProps: {
value: 'code',
label: 'name',
},
defaultTypeProps: {
value: 'id',
label: 'name',
},
showAdvancedFilter: false,
learningTimeRange: [],
pickerOptions: { shortcuts: [] },
manageStudyData: {},
expandDetails: true,
qrCodedialogVisible: false,
copyUrl: '',
qrcodeImgUrl: '',
showDownloadButton: false,
isExamine: 1,
auditInfo: {
pass: true,
remark: ''
},
dialogVisible: false,
currentPage4: 4,
inviteTeacher: {
//邀请
dlgShow: false,
params: { name: '' },
list: []
},
manageStudy: {
dlgShow: false
},
params: {
name: '',
teacherId: '',
learningTimeStart: '',
learningTimeEnd: '',
status: '',
publish: '',
enabled: '',
openCourse: '',
createUserId: '',
createFrom: '',
},
orderField: '', // 排序字段
orderAsc: true, // 排序顺序true-升序false-降序
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: '',
scrollbarStyleApplied: false,
};
},
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.loadShowSetTopFlag();
// 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();
document.querySelector('#app').style.overflowX = 'hidden';
this.applyAppScrollbarStyle();
},
methods: {
toggleAdvancedFilter() {
this.showAdvancedFilter = !this.showAdvancedFilter;
},
// 计算文本宽度(通过隐藏 span 获取实际宽度)
getTextWidth(text = '') {
if (typeof document === 'undefined') return 0;
const span = document.createElement('span');
span.innerText = text;
span.style.cssText = 'position:absolute;visibility:hidden;font-size:14px;font-family:inherit;white-space:nowrap;';
document.body.appendChild(span);
const { width } = span.getBoundingClientRect();
document.body.removeChild(span);
return Math.ceil(width);
},
// 计算列宽(头+内容取最大值,加 padding并控制最小值
calcColumnWidth(label, prop, padding = 24, min = 260, max = Infinity) {
const contents = (this.pageData || []).map(row => this.getTextWidth((row && row[prop]) || ''));
const maxContentWidth = contents.length ? Math.max(...contents) : 0;
const labelWidth = this.getTextWidth(label || '');
const baseWidth = Math.max(maxContentWidth, labelWidth) + padding;
const clamped = Math.max(min, Math.min(baseWidth, max));
return `${clamped}px`;
},
applyAppScrollbarStyle() {
if (this.scrollbarStyleApplied || typeof document === 'undefined') return;
if (document.getElementById('app-scrollbar-style')) {
this.scrollbarStyleApplied = true;
return;
}
const style = document.createElement('style');
style.id = 'app-scrollbar-style';
style.innerHTML = `
#app::-webkit-scrollbar {
width: 6px;
height: 8px;
}
#app::-webkit-scrollbar-thumb {
border-radius: 4px;
background-color: #4284F7;
}
#app::-webkit-scrollbar {
width: 6px;
height: 6px;
background-color:rgba(0, 0, 0, .1);
border-radius: 4px;
}
`;
document.head.appendChild(style);
this.scrollbarStyleApplied = true;
},
async loadShowSetTopFlag() {
try {
const res = await apiCourse.showSetTop();
this.showSetTopFeature = res && res.status === 200 && res.result === true;
} catch (error) {
this.showSetTopFeature = false;
}
},
async remoteSearchTeacher(keyword) {
const limited = (keyword || '').slice(0, 50);
if (this.$refs.teacherSelect && this.$refs.teacherSelect.query !== limited) {
this.$refs.teacherSelect.query = limited;
this.$nextTick(() => {
if (this.$refs.teacherSelect && this.$refs.teacherSelect.$refs && this.$refs.teacherSelect.$refs.input) {
this.$refs.teacherSelect.$refs.input.value = limited;
}
});
}
const query = limited.trim();
if (!query || query.length <= 1) {
this.teacherOptions = [];
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 = '';
},
// 授课教师下拉展开时,如果当前没有关键字,则清空上一次的查询结果
handleTeacherVisibleChange(visible) {
if (!visible) return;
// 打开时才处理
const select = this.$refs.teacherSelect;
const query = (select && select.query) || '';
if (!query) {
this.teacherOptions = [];
}
},
async remoteSearchCreator(keyword) {
const limited = (keyword || '').slice(0, 50);
if (this.$refs.creatorSelect && this.$refs.creatorSelect.query !== limited) {
this.$refs.creatorSelect.query = limited;
this.$nextTick(() => {
if (this.$refs.creatorSelect && this.$refs.creatorSelect.$refs && this.$refs.creatorSelect.$refs.input) {
this.$refs.creatorSelect.$refs.input.value = limited;
}
});
}
const query = limited.trim();
if (!query || query.length <= 1) {
this.creatorOptions = [];
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 = '';
},
// 创建人下拉展开时,如果当前没有关键字,则清空上一次的查询结果
handleCreatorVisibleChange(visible) {
if (!visible) return;
const select = this.$refs.creatorSelect;
const query = (select && select.query) || '';
if (!query) {
this.creatorOptions = [];
}
},
limitTeacherInput(event) {
const limited = (event && event.target && event.target.value ? event.target.value : '').slice(0, 50);
if (event && event.target && event.target.value !== limited) {
event.target.value = limited;
}
if (this.$refs.teacherSelect) {
this.$refs.teacherSelect.query = limited;
}
},
limitCreatorInput(event) {
const limited = (event && event.target && event.target.value ? event.target.value : '').slice(0, 50);
if (event && event.target && event.target.value !== limited) {
event.target.value = limited;
}
if (this.$refs.creatorSelect) {
this.$refs.creatorSelect.query = limited;
}
},
limitResOwnerInput(event) {
const limited = (event && event.target && event.target.value ? event.target.value : '').slice(0, 200);
if (event && event.target && event.target.value !== limited) {
event.target.value = limited;
}
if (this.$refs.resOwnerCascader) {
this.$refs.resOwnerCascader.inputValue = limited;
}
},
resOwnerFilterMethod(node, keyword) {
if (!keyword) return true;
const text = (node.label || (node.data && node.data.name) || '').toString().toLowerCase();
const kw = keyword.toString().toLowerCase();
return text.includes(kw);
},
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.$showMessage('请选择完整的培训时间范围', 'warning');
return false;
}
return true;
},
formatCourseDuration(row) {
// if (row.durationDesc) {
// return row.durationDesc;
// }
const duration = row.courseDuration
if (duration === undefined || duration === null || duration === 0) {
return '0';
}
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) {
let score;
// 优先取score其次取avgScore
if (row.score !== undefined && (row.score || row.score === 0)) {
score = row.score;
} else if (row.avgScore !== undefined && (row.avgScore || row.avgScore === 0)) {
score = row.avgScore;
} else {
return '-';
}
// 处理数值四舍五入保留1位小数
// 先转换为数字类型,避免字符串格式的数值问题
const numScore = Number(score);
// 检查是否为有效数字
if (isNaN(numScore)) {
return '-';
}
// 四舍五入保留1位小数
return numScore.toFixed(1);
},
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);
},
formatSysTypeChain(row = {}) {
const codes = [row.sysType1, row.sysType2, row.sysType3];
const names = codes
.filter(code => code !== undefined && code !== null && code !== '')
.map(code => this.sysTypeName(code))
.filter(name => name && name !== '');
return names.length ? names.join('/') : '';
},
sysTypeName(code) {
// console.log('code', code);
// console.log('this.sysTypeMap', this.sysTypeMap);
if (code == '') { return ''; }
return this.sysTypeMap.get(code);
},
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 [sysOne = '', sysTwo = '', sysThree = ''] = this.sysTypeList || [];
query.sysType1 = sysOne;
query.sysType2 = sysTwo;
query.sysType3 = sysThree;
if (this.orgId) {
query.orgId = this.orgId;
}
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;
// 添加排序参数
if (this.orderField) {
query.orderField = this.orderField;
query.orderAsc = this.orderAsc;
}
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 = `在线课列表.xlsx`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (error) {
this.$showMessage(error.message || '导出失败', 'error');
} 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);
},
// 置顶
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')
apiCourse.setTop(params).then(res => {
if (res.status === 200 && res.result === true) {
this.$showMessage('置顶成功!', 'success')
this.searchData();
} else if (res.status === 500) {
const confirmText = `<i class="el-icon-warning-outline"></i>已置顶10条课程若需继续置顶请对部分课程执行取消置顶操作`;
this.$confirm(confirmText, '置顶确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
dangerouslyUseHTMLString: true,
type: 'warning',
customClass: 'custom-confirm-dialog'
}).then(() => {}).catch(() => { });
} else {
this.$showMessage(res.message, 'error');
}
})
} else if (row.isTop == true) {
apiCourse.setTop(params).then(res => {
if (res.status === 200 && res.result === true) {
this.$showMessage('取消成功!', 'success')
this.searchData();
} else {
this.$showMessage(res.message, 'error');
}
})
}
},
// 复制
copyCourse(item) {
const confirmText = `<i class="el-icon-warning-outline"></i>确认复制${item.name}吗?`;
this.$confirm(confirmText, '复制确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
dangerouslyUseHTMLString: true,
type: 'warning',
customClass: 'custom-confirm-dialog'
}).then(() => {
const reqdata = {
id: item.id,
refId: this.extendRefId,
refType: this.extendRefType,
};
apiCourse.copyCourse(reqdata).then(rs => {
if (rs.status === 200) {
this.$showMessage('复制成功', 'success');
this.searchData();
} else {
this.$showMessage(rs.message || '复制失败', 'error');
}
}).catch(() => {
this.$showMessage('复制失败', 'error');
});
}).catch(() => { });
},
// 撤回接口
withdraw(row) {
this.$confirm(`确定撤回${row.name}的审核申请吗?`, '撤回确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
customClass: 'custom-confirm-dialog'
}).then(() => {
apiCourse.revokeSubmit(row.id).then((res) => {
if (res.status === 200 && res.result) {
this.$showMessage('撤回成功', 'success');
this.searchData();
} else {
this.$showMessage(res.message || '撤回失败', 'error');
}
}).catch(() => {
this.$showMessage('撤回失败', 'error');
});
}).catch(() => { });
},
reset() {
this.resOwner = [];
this.sysTypeList = [];
this.learningTimeRange = [];
this.orgId = '';
this.orgName = '';
this.resOwnerSelected = [];
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.orderField = '';
this.orderAsc = true;
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);
},
// 直接审核
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.$showMessage('操作成功!', 'success');
this.dialogVisible = false;
this.searchData();
} else {
this.$showMessage(res.message, 'error');
}
})
} else {
this.$showMessage('暂未开放!', '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(`<i class="el-icon-warning-outline"></i>确定${actionText}${row.name}吗?`, `${actionText}确认`, {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
dangerouslyUseHTMLString: true,
customClass: 'custom-confirm-dialog'
});
} catch (error) {
return;
}
const params = {
ids: row.id,
title: row.name,
enabled: nextEnabled,
};
try {
const { status } = await apiCourse.setEnabled(params);
if (status === 200) {
this.$showMessage(`${actionText}成功`, 'success');
row.enabled = nextEnabled;
} else {
this.$showMessage(`${actionText}失败`, 'error');
}
} catch (error) {
this.$showMessage(`${actionText}失败`, 'error');
}
},
// 课程查询
searchData(pageReset) {
if (pageReset) {
this.page.pageIndex = 1;
}
if (!this.validateLearningTimeRange()) {
return;
}
console.log('apiCourse', apiCourse);
console.log('buildQueryParams', this.buildQueryParams());
const query = this.buildQueryParams();
apiCourse.managePage(query).then(rs => {
if (rs.status == 200) {
this.pageData = rs.result.list ? rs.result.list : [];
this.page.count = rs.result.count;
this.page.pageSize = rs.result.pageSize;
} else {
return this.$showMessage(rs.message, 'error');
}
}).catch(err => {
console.error('查询课程列表失败:', err);
this.$showMessage('查询失败,请稍后重试', 'error');
});
},
// 表格排序变化事件
handleSortChange({ column, prop, order }) {
// order: ascending(升序) | descending(降序) | null(取消排序)
console.log('排序变化:', { column, prop, order });
if (order) {
// 有排序:保存排序字段和顺序
this.orderField = prop;
this.orderAsc = order === 'ascending';
} else {
// 取消排序:清空排序字段
this.orderField = '';
this.orderAsc = true;
}
// 重新查询数据
this.searchData(true);
},
viewTopic(row) {
if (row.status == 1) {
return this.$showMessage('请提交课程再预览!', '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.qrCodedialogVisible = true;
// 与 TeacherList 保持一致的二维码生成逻辑
this.copyUrl = this.qrcodeImgUrl = `${process.env.VUE_APP_BOE_WEB_URL}/systemapi/xboe/m/course/manage/redirectDetail?courseId=${row.id}`;
this.$nextTick(() => {
this.crateQrcode();
});
},
handleCopyUrl() {
const ele = document.getElementById('text');
if (!ele) return;
ele.select();
document.execCommand('Copy');
this.$showMessage('复制成功', 'success');
},
downloadQrcode() {
const container = document.getElementById('qrcode');
if (!container) return;
const img = container.getElementsByTagName('img')[0];
if (!img) return;
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
const tempUrl = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.style.display = 'none';
link.href = tempUrl;
link.setAttribute('download', '二维码.jpg');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
},
crateQrcode() {
// eslint-disable-next-line no-new
new QRCode('qrcode', {
width: 150,
height: 150,
text: this.qrcodeImgUrl
});
},
// 关闭弹框,清除已经生成的二维码
closeCode() {
this.qrCodedialogVisible = false;
// 逐个节点移除,避免事件丢失
const images = document.querySelectorAll('.qrcode-img img');
images.forEach(img => img.remove());
const canvas = document.querySelectorAll('.qrcode-img canvas');
canvas.forEach(c => c.remove());
},
showManageStudy(row) {
// 带课程详情跳转到课程管理页
sessionStorage.setItem('courseDetail', JSON.stringify(row));
this.$router.push({ path: '/iframe/course/coursemanage-remote' });
},
async loadResOwnerNode(node, resolve) {
const { level } = node;
let parentId = '';
if (level === 0) {
// 第一层:获取根节点
try {
const res = await apiUserbasic.findOrgsByKeyword('');
if (res && res.result) {
const nodes = res.result.map(item => ({
id: item.id,
name: item.name,
hrbpId: item.hrbpId,
leaf: false, // 懒加载模式下,先假设所有节点都可能有子节点
children: [] // 空数组表示需要懒加载
}));
resolve(nodes);
} else {
resolve([]);
}
} catch (error) {
console.error('加载资源归属失败:', error);
resolve([]);
}
} else {
// 子节点根据父节点ID加载
parentId = node.value || (node.data && node.data.id);
if (!parentId) {
resolve([]);
return;
}
try {
const res = await apiUserbasic.getOrgInfo(parentId);
if (res && res.status === 200 && res.result) {
let treeList = [];
if (res.result.directChildList && res.result.directChildList.length > 0) {
treeList = res.result.directChildList.map(item => ({
id: item.id,
name: item.name,
hrbpId: item.hrbpId,
leaf: false, // 懒加载模式下,先假设所有节点都可能有子节点
children: [] // 空数组表示需要懒加载
}));
}
resolve(treeList);
} else {
// 如果没有子节点,返回空数组,级联选择器会自动将其视为叶子节点
resolve([]);
}
} catch (error) {
console.error('加载资源归属子节点失败:', error);
resolve([]);
}
}
},
handleResOwnerChange(value) {
if (!value || value.length === 0) {
this.orgId = '';
this.orgName = '';
return;
}
// value 是选中的路径数组最后一个元素是选中的节点ID
const selectedId = value[value.length - 1];
this.orgId = selectedId;
// 通过级联选择器的 getCheckedNodes 方法获取选中的节点信息
this.$nextTick(() => {
if (this.$refs.resOwnerCascader) {
const checkedNodes = this.$refs.resOwnerCascader.getCheckedNodes();
if (checkedNodes && checkedNodes.length > 0) {
const lastNode = checkedNodes[checkedNodes.length - 1];
this.orgName = lastNode.label || lastNode.name || '';
}
}
});
},
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.$showMessage('xxx', 'success');
// },
delItem(row) {
// this.$showMessage('删除成功', 'success');
// return false
this.$confirm(`<i class="el-icon-warning-outline"></i>确认删除${row.name}吗?`, '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
dangerouslyUseHTMLString: true,
type: 'warning',
customClass: 'custom-confirm-dialog'
}).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.$showMessage('删除成功', 'success'); //只是之前发布过的课程删除才可
// 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.$showMessage('删除失败', 'error');
}
})
.catch((err) => {
// this.$showMessage('已取消删除', 'info');
});
},
showAddCatalogZhang(bal) {
this.catalogs.addNewZhang = bal;
},
saveNewCatalogZhang() {
this.catalogs.addNewZhang = false;
},
handleAction(key, row) {
switch (key) {
case 'edit':
return this.editCurriculum(row);
case 'qrcode':
return this.showQrimage(row);
case 'manage':
return this.showManageStudy(row);
case 'withdraw':
return this.withdraw(row);
case 'delete':
return this.delItem(row);
case 'copy':
return this.copyCourse(row);
case 'toggleEnable':
return this.isDisable(row);
case 'toggleTop':
return this.setTop(row);
default:
return;
}
},
buildActions(row) {
const actions = [];
// 优先级按原有展示顺序
if (row.isPermission && row.status != 2) {
actions.push({ key: 'edit', label: '编辑', className: 'action-link--primary' });
}
if (row.published) {
actions.push({ key: 'qrcode', label: '二维码', className: 'action-link--primary' });
}
if (row.isPermission && !this.forChoose && row.published) {
actions.push({ key: 'manage', label: '管理', className: 'action-link--primary' });
}
if (row.isPermission && !this.forChoose && row.status == 2) {
actions.push({ key: 'withdraw', label: '撤回', className: 'action-link--primary' });
}
if ((row.isPermission && row.status != 2 && !row.published) || (row.isPermission && !row.enabled)) {
actions.push({ key: 'delete', label: '删除', className: 'action-link--danger' });
}
// 更多里的动作
if (row.isPermission && row.published) {
actions.push({ key: 'copy', label: '复制', className: 'action-link--primary' });
actions.push({ key: 'toggleEnable', label: row.enabled ? '停用' : '启用', className: 'action-link--primary' });
if (this.showSetTopFeature) {
actions.push({ key: 'toggleTop', label: row.isTop ? '取消置顶' : '置顶', className: 'action-link--primary' });
}
}
return actions;
},
getInlineActions(row) {
const actions = this.buildActions(row);
return actions.slice(0, 4);
},
getDropdownActions(row) {
const actions = this.buildActions(row);
return actions.length > 4 ? actions.slice(4) : [];
}
}
};
</script>
<style lang="scss" scoped>
#app::-webkit-scrollbar {
width: 6px;
height: 8px;
}
#app::-webkit-scrollbar-thumb {
border-radius: 6px;
background-color: rgb(78, 166, 255);
}
.noSplitDatePicker {
/* 初始隐藏范围选择器的分隔符与关闭图标 */
::v-deep .el-range-separator,
::v-deep .el-range__close-icon {
display: none !important;
}
}
// .resetDatePicker {
// ::v-deep .el-date-editor {
// width: 250px;
// }
// }
.sou {
padding: 0 0 0 0px !important;
}
.el-col {
padding: 0 0 0 10px !important;
}
.table-wrapper {
padding: 16px;
background-color: #ffffff;
margin-top: 10px;
border-radius: 6px;
}
.grid-content {
padding-right: 0px;
}
.org-name-cell {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.el-button--text {
font-size: 14px;
}
.filter-wrapper {
padding: 16px;
background-color: #ffffff;
border-radius: 6px;
}
.filter-row {
display: flex;
flex-wrap: nowrap;
align-items: center;
// margin-bottom: 10px;
}
.filter-fields {
display: flex;
flex-wrap: wrap;
}
.filter-field {
margin-right: 10px;
margin-bottom: 0;
}
.filter-row--advanced {
margin-top: 10px;
}
.toggle-link {
padding: 0;
font-size: 14px;
color: #4284F7;
}
.option-code {
margin-left: 4px;
color: #999;
}
.teacher-filter {
::v-deep .choice {
width: 100%;
}
}
.creator-filter {
::v-deep .choice {
width: 100%;
}
}
.filter-actions--inline {
padding-top: 0;
}
.filter-actions .el-button+.el-button {
margin-left: 10px;
}
.filter-actions {
display: flex;
align-items: center;
}
.filter-actions .icon-btn+.icon-btn {
margin-left: 8px;
}
.filter-actions .toggle-link {
margin-right: 10px;
}
.icon-btn {
width: 32px;
height: 32px;
cursor: pointer;
background-repeat: no-repeat;
background-position: center;
background-size: 32px 32px;
background-color: transparent;
// transition: all 0.2s ease;
}
.icon-btn:disabled {
cursor: not-allowed;
opacity: 0.6;
}
.icon-btn--search {
background-image: url('~@/assets/images/svg/search_active.svg');
}
.icon-btn--reset {
background-image: url('~@/assets/images/svg/reset.svg');
background-size: 15px 15px;
}
.icon-btn--reset:hover,
.icon-btn--reset:active {
background-image: url('~@/assets/images/svg/reset_active.svg');
background-size: 32px 32px;
}
.icon-btn--top {
background-image: url('~@/assets/images/svg/pintotop.svg');
background-size: 14px 14px;
}
.icon-btn--top:hover,
.icon-btn--top:active {
background-image: url('~@/assets/images/svg/pintotop_active.svg');
background-size: 32px 32px;
}
.icon-btn--export {
background-image: url('~@/assets/images/svg/export.svg');
background-size: 14px 14px;
}
.icon-btn--export:hover,
.icon-btn--export:active {
background-image: url('~@/assets/images/svg/export_active.svg');
background-size: 32px 32px;
}
.icon-btn.is-disabled {
cursor: not-allowed;
opacity: 0.6;
pointer-events: none;
}
.filter-extra-actions {
display: flex;
align-items: center;
justify-content: flex-start;
padding-bottom: 20px;
}
.filter-extra-actions .icon-btn,
.filter-extra-actions .create-course-btn {
margin-right: 10px;
}
.create-course-btn {
width: 120px;
height: 32px;
padding-left: 36px;
padding-right: 16px;
border-radius: 6px;
border: 1px solid #4284F7;
background-color: #ffffff;
background-image: url('~@/assets/images/svg/createCourse.svg');
background-repeat: no-repeat;
background-position: 12px center;
background-size: 14px 14px;
color: #4284F7;
font-weight: 400;
font-size: 14px;
line-height: 32px;
cursor: pointer;
transition: all 0.2s ease;
}
.create-course-btn:hover,
.create-course-btn:active {
background-color: #4284F7;
border-color: transparent;
color: #ffffff;
background-image: url('~@/assets/images/svg/createCourse_active.svg');
}
.toggle-link {
padding: 0;
}
/* 移除右对齐,保持左侧布局 */
::v-deep .el-table .el-table__body-wrapper::-webkit-scrollbar {
display: block;
}
.advanced-filter {
margin-top: 10px;
}
// .learning-time-range .el-date-editor {
// width: 250px;
// }
.pagination {
text-align: right;
padding-top: 20px;
::v-deep .el-pagination {
.el-pagination__total {
font-size: 14px;
color: #000000;
}
.el-pagination__sizes {
margin-right: 4px;
.el-input{
margin: 0;
width: 89px;
}
.el-input__inner {
width: 89px;
background: #F5F9FF;
border-radius: 4px;
border: 1px solid #DFDFDF;
height: 28px;
font-size: 14px;
color: #000000;
}
}
.btn-prev, .btn-next {
width: 28px;
height: 28px;
background: #F5F9FF;
border-radius: 4px;
border: 1px solid #DFDFDF;
// &:hover {
// background: #4284F7;
// color: #FFFFFF;
// }
}
.btn-quicknext{
background: transparent;
border: none;
line-height: 44px;
&:before {
content: '......';
}
}
.el-pager {
.number {
min-width: 28px;
height: 28px;
background: #F5F9FF;
border-radius: 4px;
border: 1px solid #DFDFDF;
font-weight: normal;
color: #000000;
margin: 0 4px;
&.active {
background: #4284F7;
color: #FFFFFF;
border: none;
}
}
}
.el-pagination__jump {
font-size: 14px;
color: #000000;
margin-left: 4px;
.el-input__inner {
width: 28px;
height: 28px;
background: #F5F9FF;
border-radius: 4px;
border: 1px solid #DFDFDF;
font-size: 14px;
color: #000000;
}
}
}
}
.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: 4px;
background-color: #4284F7;
}
::v-deep .el-table .el-table__body-wrapper::-webkit-scrollbar {
width: 6px;
height: 6px;
background-color:rgba(0, 0, 0, .1);
border-radius: 4px;
}
::v-deep .el-table .el-table__body-wrapper::-webkit-scrollbar-thumb {
border-radius: 4px;
background-color: #4284F7
}
::v-deep.el-table {
border-radius: 6px 6px 0 0;
td.el-table__cell {
border-bottom: 1px solid rgba(0, 0, 0, .1);
}
th.el-table__cell {
background: rgba(66, 132, 247, 0.1);
padding: 3px 0;
.cell {
font-weight: bold;
font-size: 14px;
color: #60769D;
}
.caret-wrapper {
.sort-caret {
border: 4px solid transparent;
&.ascending {
border-bottom-color: #C0C4CC;
top: 8px;
}
&.descending {
border-top-color: #C0C4CC;
bottom: 8px;
}
}
}
&.ascending .sort-caret.ascending {
border-bottom-color: #409EFF;
}
&.descending .sort-caret.descending{
border-top-color: #409EFF;
}
&.el-table--medium .el-table__cell {
padding: 5px 0;
}
}
.course-name {
font-weight: 400;
font-size: 14px;
color: #000000;
line-height: 20px;
}
.common-cell {
font-weight: 400;
font-size: 14px;
color: #000000;
}
.common-cell-right {
padding-right: 20px;
}
.single-line-ellipsis {
display: inline-block;
max-width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
vertical-align: middle;
}
.status--pass {
color: #2EAD4D;
}
.status--reject {
color: #F41228;
}
.action-link--primary {
color: #4284F7;
font-weight: bold;
}
.action-link--danger {
color: #E32E2E;
font-weight: bold;
}
.action-link--more {
color: #999999;
font-weight: bold;
.el-icon-arrow-down {
font-weight: bold;
}
}
.action-link--bold {
font-weight: bold;
}
::v-deep .el-table--medium .el-table__cell {
padding: 5px 0;
}
}
.qrcode-img {
width: 150px;
height: 150px;
display: block;
position: relative;
.downloadn-container {
position: absolute;
width: 40px;
height: 45px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 99;
background: white;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
transition: all 0.3s ease;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
cursor: pointer;
span {
color: #409eff;
display: block;
font-size: 12px;
line-height: 12px;
}
}
}
</style>
<style lang="scss">
.filter-field--name {
.el-input__inner {
width: 444px;
height: 32px;
background: #FFFFFF;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, .2);
line-height: 32px;
padding: 0 10px;
&:focus {
border-color: #4284F7;
}
}
}
.filter-field--category {
.el-cascader {
line-height: 32px;
.el-input__inner {
width: 180px;
height: 32px;
background: #FFFFFF;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, .2);
line-height: 32px;
padding: 0 10px;
color: #000000;
&:focus {
border: 1px solid #4284F7;
}
}
.el-input {
line-height: 32px;
&.is-focus .el-input__inner{
border-color: #4284F7;
}
}
.el-input__suffix {
line-height: 32px;
}
.el-input__icon {
line-height: 32px;
}
}
}
.filter-field--teacher {
.el-select {
.el-select__input {
margin-left: 10px;
}
.el-input__inner {
width: 180px;
height: 32px;
background: #FFFFFF;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, .2);
line-height: 32px;
padding: 0 10px;
}
.el-input {
&.is-focus .el-input__inner{
border-color: #4284F7;
}
}
}
}
.filter-field--status {
.el-select {
.el-select__input {
margin-left: 10px;
}
.el-input__inner {
width: 136px;
height: 32px;
background: #FFFFFF;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, .2);
line-height: 32px;
padding: 0 10px;
&.is-focus .el-input__inner{
border-color: #4284F7;
}
}
.el-input__icon {
line-height: 32px;
}
}
}
.filter-field--publish {
.el-select {
.el-select__input {
margin-left: 10px;
}
.el-input__inner {
width: 136px;
height: 32px;
background: #FFFFFF;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, .2);
line-height: 32px;
padding: 0 10px;
&.is-focus .el-input__inner{
border-color: #4284F7;
}
}
.el-input__icon {
line-height: 32px;
}
}
}
.filter-field--enabled {
.el-select {
.el-select__input {
margin-left: 10px;
}
.el-input__inner {
width: 136px;
height: 32px;
background: #FFFFFF;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, .2);
line-height: 32px;
padding: 0 10px;
&.is-focus .el-input__inner{
border-color: #4284F7;
}
}
.el-input__icon {
line-height: 32px;
}
}
}
.filter-field--open {
.el-select {
.el-select__input {
margin-left: 10px;
}
.el-input__inner {
width: 124px;
height: 32px;
background: #FFFFFF;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, .2);
line-height: 32px;
padding: 0 10px;
&.is-focus .el-input__inner{
border-color: #4284F7;
}
}
.el-input__icon {
line-height: 32px;
}
}
}
.filter-field--resowner {
.el-cascader {
width: 180px;
line-height: 32px;
.el-input__inner {
width: 180px;
height: 32px;
background: #FFFFFF;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, .2);
line-height: 32px;
padding: 0 10px;
&:focus {
border-color: #4284F7;
}
}
&.is-focus .el-input__inner {
border-color: #4284F7;
}
}
.el-input {
.el-input__inner {
width: 180px;
height: 32px;
background: #FFFFFF;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, .2);
line-height: 32px;
padding: 0 10px;
}
}
}
.filter-field--creator {
.el-select {
.el-select__input {
margin-left: 10px;
}
.el-input__inner {
width: 136px;
height: 32px;
background: #FFFFFF;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, .2);
line-height: 32px;
padding: 0 10px;
&.is-focus .el-input__inner{
border-color: #4284F7;
}
}
}
}
.filter-field--create-from {
.el-select {
.el-select__input {
margin-left: 10px;
}
.el-input__inner {
width: 180px;
height: 32px;
background: #FFFFFF;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, .2);
line-height: 32px;
padding: 0 10px;
&.is-focus .el-input__inner{
border-color: #4284F7;
}
}
.el-input__icon {
line-height: 32px;
}
}
}
.filter-field--time {
.el-input__inner {
width: 316px;
height: 32px;
background: #FFFFFF;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, .2);
line-height: 32px;
padding: 0 10px;
.el-input__icon {
line-height: 32px;
&.el-icon-date {
position: absolute;
right: 0;
}
&.el-icon-close {
margin-right: 8px;
}
}
.el-range-editor {
.is-active:hover{
border-color: #4284F7;
}
}
.el-range-separator {
line-height: 30px;
}
.el-range-input {
line-height: 30px;
}
}
.noSplitDatePicker {
.el-range-input {
text-align: left;
}
}
}
.icon-btn-tooltip {
height: 30px;
background: rgba(0, 0, 0, 1);
border-radius: 15px;
padding: 0 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
line-height: 30px;
.popper__arrow {
display: none !important;
}
.el-tooltip__popper__arrow {
display: none !important;
}
}
</style>