feat(course):重构在线课程管理页面

- 移除旧的 iframe 嵌套方式,采用原生 Vue 组件实现
- 新增多条件筛选功能,支持课程名称、分类、教师等字段搜索
- 添加高级筛选面板,可展开收起第二排搜索项
- 实现课程表格展示,包含课程信息、状态及操作列
- 增加分页功能,支持数据分页加载
- 添加新建课程、导出数据等操作入口- 完善各类下拉选项,如审核状态、发布状态等枚举值
- 更新页面样式布局,优化用户体验
This commit is contained in:
陈昱达
2025-11-17 15:01:36 +08:00
parent 24ca49ce78
commit fe0372259b

View File

@@ -1,75 +1,758 @@
<!--
* @Author: lixg lixg@dongwu-inc.com
* @Date: 2022-11-09 09:26:26
* @LastEditors: lixg lixg@dongwu-inc.com
* @LastEditTime: 2022-11-25 17:37:05
* @FilePath: /fe-manage/src/views/courselibrary/CourseManage.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<!-- 课件管理页面 -->
<!-- sandbox="allow-forms allow-scripts allow-same-origin allow-popups" -->
<!-- 课程页面 -->
<template>
<div class="courseManage">
<iframe
id="iframe"
style="width: 100%; height: 100%"
:src="iframeUrl + '/course/manages?page=manage'"
frameborder="0"
name="myframe"
security="restricted"
sandbox="allow-forms allow-downloads allow-scripts allow-same-origin allow-popups"
></iframe>
<OnlineClassModelStudent ref="stuRef" :type="13"></OnlineClassModelStudent>
<div class="projectManage">
<!-- 搜索框及按钮 -->
<div class="filter">
<!-- 第一排搜索项 -->
<div class="filterItems">
<div class="select">
<a-input
v-model:value="searchParam.name"
style="width: 200px; height: 40px; border-radius: 8px"
placeholder="请输入课程名称"
allowClear
showSearch
>
</a-input>
</div>
<div class="select">
<a-select
v-model:value="searchParam.category"
style="width: 200px"
placeholder="全部课程分类"
:options="categoryList"
allowClear
></a-select>
</div>
<div class="select">
<a-input
v-model:value="searchParam.teacher"
style="width: 200px; height: 40px; border-radius: 8px"
placeholder="授课教师"
allowClear
showSearch
>
</a-input>
</div>
<div class="select addTimeBox">
<div class="addTime">培训时间</div>
<a-range-picker
v-model:value="searchParam.valueDate"
style="width:330px"
format="YYYY-MM-DD"
separator="至"
:placeholder="[' 开始时间', ' 结束时间']"
/>
</div>
<div class="select">
<a-select
v-model:value="searchParam.reviewStatus"
style="width: 200px"
placeholder="全部审核状态"
:options="reviewStatusList"
allowClear
></a-select>
</div>
<div class="select">
<a-select
v-model:value="searchParam.publishStatus"
style="width: 200px"
placeholder="全部发布状态"
:options="publishStatusList"
allowClear
></a-select>
</div>
<div class="select" style="display: flex; margin-bottom: 20px">
<div class="btn btn4" @click="toggleFilter">
<div class="search"></div>
<div class="btnText">{{ showSecondFilter ? '收起' : '展开' }} </div>
</div>
</div>
</div>
<!-- 第二排搜索项 (通过 showSecondFilter 控制显隐) -->
<div class="filterItems" style="justify-content: space-between;width: 100%">
<div class="filterItems">
<div class="select" v-show="showSecondFilter">
<a-select
v-model:value="searchParam.enableStatus"
style="width: 200px"
placeholder="全部启用状态"
:options="enableStatusList"
allowClear
></a-select>
</div>
<div class="select" v-show="showSecondFilter" >
<a-select
v-model:value="searchParam.isPublic"
style="width: 200px"
placeholder="是否公开课"
:options="isPublicList"
allowClear
></a-select>
</div>
<div class="select" v-show="showSecondFilter" >
<a-select
v-model:value="searchParam.resourceOwner"
style="width: 200px"
placeholder="全部资源归属"
:options="resourceOwnerList"
allowClear
></a-select>
</div>
<div class="select" v-show="showSecondFilter">
<a-input
v-model:value="searchParam.teacher"
style="width: 200px; height: 40px; border-radius: 8px"
placeholder="创建人"
allowClear
showSearch
>
</a-input>
</div>
<div class="select" v-show="showSecondFilter">
<a-select
v-model:value="searchParam.createSource"
style="width: 200px"
placeholder="全部创建来源"
:options="createSourceList"
allowClear
></a-select>
</div>
</div>
<!-- 查询重置导出新建课程按钮 -->
<div class="btns" style="">
<div class="btn btn1" @click="searchSubmit">
<div class="search"></div>
<div class="btnText">查询</div>
</div>
<div class="btn btn2" @click="searchReset">
<div class="search"></div>
<div class="btnText">重置</div>
</div>
<div class="btn btn5" @click="exportData">
<div class="search"></div>
<div class="btnText">导出</div>
</div>
<div class="btn btn3" @click="showModal1">
<div class="search"></div>
<div class="btnText">+ 新建课程</div>
</div>
</div>
</div>
<!-- 原有的第一排按钮组 (可以保留也可以删除看你是否需要两个按钮组) -->
<!-- 如果你希望只保留第二排的按钮组请注释掉下面这整个 .btns -->
<!--
<div class="btns">
<div class="btn btn3" @click="showModal1">
<div class="search"></div>
<div class="btnText">+ 新建课程</div>
</div>
<div class="btn btn5" @click="exportData">
<div class="search"></div>
<div class="btnText">导出</div>
</div>
</div>
-->
</div>
<!-- 搜索框及按钮 -->
<!-- 表格 -->
<div style="padding: 10px 35px">
<a-table
:header-cell-style="{ 'text-align': 'center' }"
style="border: 1px solid #f2f6fe"
:columns="columns"
:data-source="tableData"
:loading="tableLoading"
:scroll="{ x: 'max-content' }"
:pagination="false"
:expandable="false"
>
<template #bodyCell="{ record, column }">
<template v-if="column.key === 'operation'">
<a-space>
<a-button @click="handleEdit(record)" type="link">编辑</a-button>
<a-button @click="editMaterial(record)" type="link">编辑课件</a-button>
<a-button @click="grantPermission(record)" type="link">授权</a-button>
<a-button @click="deleteRecord(record)" type="link" danger>删除</a-button>
</a-space>
</template>
</template>
</a-table>
<div class="tableBox">
<div class="pa">
<a-pagination
v-if="tableDataTotal > 10"
:showSizeChanger="false"
:showQuickJumper="true"
:hideOnSinglePage="true"
:pageSize="pageSize"
v-model:current="searchParam.pageNo"
:total="tableDataTotal"
class="pagination"
@change="changePagination"
/>
</div>
</div>
</div>
<!-- 表格 -->
</div>
</template>
<script setup>
import {onMounted, ref} from "vue";
import {iframeUrl} from "@/api/method";
import OnlineClassModelStudent from "@/components/student/OnlineClassModelStudent";
const stuRef = ref()
<script>
export default {
name: "projectManage",
data() {
return {
// 控制第二排搜索项的显隐
showSecondFilter: false,
searchParam: {
name: '',
category: undefined,
teacher: '',
valueDate: [],
reviewStatus: undefined,
publishStatus: undefined,
enableStatus: undefined, // 新增:启用状态
isPublic: undefined, // 新增:是否公开课
resourceOwner: undefined, // 新增:资源归属
createSource: undefined, // 新增:创建来源
pageNo: 1,
pageSize: 10
},
tableData: [
{
key: '1',
name: '《少走弯路的职场法则》-2021公开课',
teacher: '张三',
courseDuration: 10,
studyDuration: 10,
studentCount: 1,
rating: 5.0,
reviewStatus: '-',
publishStatus: '未发布'
},
{
key: '2',
name: 'PDCA循环工作法',
teacher: '张三',
courseDuration: 20,
studyDuration: 20,
studentCount: 2,
rating: 4.0,
reviewStatus: '审核中',
publishStatus: '未发布'
},
{
key: '3',
name: 'BOE端到端的流程体系',
teacher: '张三',
courseDuration: 30,
studyDuration: 30,
studentCount: 3,
rating: 3.0,
reviewStatus: '审核驳回',
publishStatus: '未发布'
},
{
key: '4',
name: '结构性思维与表达-2023年公开课',
teacher: '张三',
courseDuration: 40,
studyDuration: 40,
studentCount: 4,
rating: 5.0,
reviewStatus: '审核通过',
publishStatus: '已发布'
},
{
key: '5',
name: '标准化异常处理流程',
teacher: '张三',
courseDuration: 10,
studyDuration: 10,
studentCount: 6,
rating: 4.0,
reviewStatus: '审核通过',
publishStatus: '已发布'
},
{
key: '6',
name: '企业经营法则',
teacher: '张三',
courseDuration: 20,
studyDuration: 20,
studentCount: 6,
rating: 3.0,
reviewStatus: '审核通过',
publishStatus: '已发布'
},
{
key: '7',
name: '京东方战略实践学习',
teacher: '张三',
courseDuration: 30,
studyDuration: 30,
studentCount: 7,
rating: 5.0,
reviewStatus: '审核通过',
publishStatus: '已发布'
},
{
key: '8',
name: '市场营销精要之《如何在新环境下做好新产品整合营销上市》',
teacher: '张三',
courseDuration: 40,
studyDuration: 40,
studentCount: 89,
rating: 4.0,
reviewStatus: '审核中',
publishStatus: '已发布'
},
{
key: '9',
name: '2024热点论坛第三期:国际格局的基本结构及其相关问题',
teacher: '张三',
courseDuration: 10,
studyDuration: 10,
studentCount: 1,
rating: 3.0,
reviewStatus: '审核中',
publishStatus: '已发布'
},
{
key: '10',
name: '2024热点论坛2-《新“人机”时代的生存与发展》',
teacher: '张三',
courseDuration: 20,
studyDuration: 20,
studentCount: 2,
rating: 5.0,
reviewStatus: '审核驳回',
publishStatus: '已发布'
}
],
tableDataTotal: 100,
tableLoading: false,
categoryList: [
{ value: 'all', label: '全部课程分类' },
{ value: 'tech', label: '技术类' },
{ value: 'manage', label: '管理类' },
{ value: 'sale', label: '销售类' }
],
reviewStatusList: [
{ value: 'all', label: '全部审核状态' },
{ value: 'pending', label: '审核中' },
{ value: 'approved', label: '审核通过' },
{ value: 'rejected', label: '审核驳回' }
],
publishStatusList: [
{ value: 'all', label: '全部发布状态' },
{ value: 'draft', label: '未发布' },
{ value: 'published', label: '已发布' }
],
// 新增的下拉选项列表
enableStatusList: [
{ value: 'all', label: '全部启用状态' },
{ value: 'enabled', label: '已启用' },
{ value: 'disabled', label: '已禁用' }
],
isPublicList: [
{ value: 'all', label: '是否公开课' },
{ value: 'yes', label: '是' },
{ value: 'no', label: '否' }
],
resourceOwnerList: [
{ value: 'all', label: '全部资源归属' },
{ value: 'self', label: '自有' },
{ value: 'third', label: '第三方' }
],
createSourceList: [
{ value: 'all', label: '全部创建来源' },
{ value: 'manual', label: '手动创建' },
{ value: 'import', label: '批量导入' },
{ value: 'api', label: 'API创建' }
],
columns: [
{
title: '课程名称',
dataIndex: 'name',
key: 'name',
className: 'h',
ellipsis: true,
width: 200,
sorter: true,
fixed: "left"
},
{
title: '课程分类',
dataIndex: 'teacher',
className: 'h',
key: 'teacher',
align: 'center',
onMounted(() => {
window.openSelectStu = stuRef.value.openDrawer
})
sorter: true
},
{
title: '授课教师',
dataIndex: 'teacher',
className: 'h',
key: 'teacher',
align: 'center',
sorter: true
},
{
title: '课程时长',
dataIndex: 'courseDuration',
className: 'h',
key: 'courseDuration',
align: 'center',
sorter: true
},
{
title: '学习时长',
dataIndex: 'studyDuration',
className: 'h',
key: 'studyDuration',
align: 'center',
sorter: true
},
{
title: '学习人数',
dataIndex: 'studentCount',
className: 'h',
key: 'studentCount',
align: 'center',
sorter: true
},
{
title: '课程评分',
dataIndex: 'rating',
className: 'h',
key: 'rating',
align: 'center',
sorter: true
},
{
title: '审核状态',
dataIndex: 'reviewStatus',
className: 'h',
key: 'reviewStatus',
align: 'center',
sorter: true
},
{
title: '发布状态',
dataIndex: 'publishStatus',
className: 'h',
key: 'publishStatus',
align: 'center',
sorter: true
},
{
title: '启停用状态',
dataIndex: 'publishStatus',
className: 'h',
key: 'publishStatus',
align: 'center',
sorter: true
},
{
title: '排序值',
dataIndex: 'publishStatus',
className: 'h',
key: 'publishStatus',
align: 'center',
sorter: true
},
{
title: '公开课',
dataIndex: 'publishStatus',
className: 'h',
key: 'publishStatus',
align: 'center',
sorter: true
},
{
title: '资源归属',
dataIndex: 'publishStatus',
className: 'h',
key: 'publishStatus',
align: 'center',
sorter: true
},
{
title: '创建人',
dataIndex: 'publishStatus',
className: 'h',
key: 'publishStatus',
align: 'center',
sorter: true
},
{
title: '创建来源',
dataIndex: 'publishStatus',
className: 'h',
key: 'publishStatus',
align: 'center',
sorter: true
},
{
title: '创建时间',
dataIndex: 'publishStatus',
className: 'h',
key: 'publishStatus',
align: 'center',
sorter: true
},
{
title: '操作',
dataIndex: 'operation',
key: 'operation',
className: 'h',
align: 'right',
fixed: 'right',
width: 200,
scopedSlots: { customRender: 'action' },
sorter: false
},
]
};
},
methods: {
// 切换第二排搜索项的显示状态
toggleFilter() {
this.showSecondFilter = !this.showSecondFilter;
},
// 保留必要的空方法以防止点击报错
searchSubmit() {},
searchReset() {},
showModal1() {},
handleEdit() {},
editMaterial() {},
grantPermission() {},
deleteRecord() {},
exportData() {},
changePagination() {}
}
};
</script>
<style lang="scss">
.courseManage {
// 保留原有的样式代码,并根据图片微调
.projectManage {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.btnn {
height: 72px;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0px 1px 35px 0px rgba(118, 136, 166, 0.16);
.filter {
margin-left: 38px;
margin-right: 38px;
margin-top: 30px;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
.btn1 {
width: 100px;
height: 40px;
border: 1px solid #4ea6ff;
border-radius: 8px;
color: #4ea6ff;
background-color: #fff;
cursor: pointer;
.filterItems {
display: flex;
flex-wrap: wrap;
.select {
margin-right: 20px;
margin-bottom: 20px;
}
.addTimeBox {
position: relative;
display: flex;
align-items: center;
.addTime {
position: absolute;
z-index: 10;
margin-left: 10px;
color: rgba(0, 0, 0, 0.4);
}
.ant-picker {
padding-left: 85px;
}
.ant-picker-range .ant-picker-active-bar {
margin-left: 85px;
}
}
.btn {
padding: 0 26px 0 26px;
height: 38px;
background: #4ea6ff;
border-radius: 8px;
border: 1px solid rgba(64, 158, 255, 1);
display: flex;
align-items: center;
justify-content: center;
margin-right: 14px;
flex-shrink: 0;
cursor: pointer;
.search {
background-size: 100%;
}
.btnText {
font-size: 14px;
font-weight: 400;
color: #fff;
line-height: 36px;
margin-left: 5px;
}
}
.btnn {
padding: 0 26px 0 26px;
height: 38px;
background: #4ea6ff;
border-radius: 8px;
border: 1px solid rgba(64, 158, 255, 1);
display: flex;
align-items: center;
justify-content: center;
margin-right: 14px;
flex-shrink: 0;
cursor: pointer;
.search {
background-size: 100%;
}
.btnText {
font-size: 14px;
font-weight: 400;
color: #ffffff;
line-height: 36px;
margin-left: 5px;
}
}
.btn1:active {
background: #0982ff;
}
.btn2:active {
background: #0982ff;
}
.btn4 {
padding: 0 12px 0 12px;
height: 38px;
background: #4ea6ff;
border-radius: 8px;
border: 1px solid rgba(64, 158, 255, 1);
display: flex;
align-items: center;
justify-content: center;
margin-right: 14px;
flex-shrink: 0;
cursor: pointer;
.search {
background-size: 100%;
}
.btnText {
font-size: 14px;
font-weight: 400;
color: #ffffff;
line-height: 36px;
margin-left: 5px;
}
}
.btn4:active {
background: #0982ff;
}
}
.btns {
display: flex;
.btn {
padding: 0 26px 0 26px;
height: 38px;
background: #4ea6ff;
border-radius: 8px;
border: 1px solid rgba(64, 158, 255, 1);
display: flex;
align-items: center;
cursor: pointer;
justify-content: center;
margin-right: 14px;
flex-shrink: 0;
.search {
background-size: 100%;
}
.btnText {
font-size: 14px;
font-weight: 400;
color: #ffffff;
line-height: 36px;
margin-left: 5px;
}
}
.btn3:active {
background: #0982ff;
}
.btn5:active {
background: #0982ff;
}
}
}
.btn2 {
cursor: pointer;
width: 100px;
height: 40px;
background: #4ea6ff;
border-radius: 8px;
border: 0;
margin-left: 15px;
color: #fff;
.tableBox {
margin: 20px 38px 30px;
.ant-table-thead > tr > th {
background-color: #eff4fc;
}
th {
background-color: #eff4fc !important;
text-align: center !important;
}
}
.tableBox {
padding-bottom: 20px;
.pa {
width: 100%;
display: flex;
justify-content: center;
}
}
}
</style>