精品课程页面ui

This commit is contained in:
dong.ai
2025-09-11 15:46:49 +08:00
parent 37863aee7f
commit 0eb80bc487
3 changed files with 1247 additions and 512 deletions

View File

@@ -32,8 +32,8 @@ export const detailPlan = (obj) =>
export const edit = (obj) => http.post("/admin/offcourse/edit", obj);
//7新建或编辑面授课开课
export const editPlan = (obj) => http.post("/admin/offcourse/editPlan", obj);
export const copyCoursePlan = (params) => http.get("/admin/offcourse/copyCoursePlan", {params});
export const copyCourse = (params) => http.get("/admin/offcourse/copyCourse", {params});
export const copyCoursePlan = (params) => http.get("/admin/offcourse/copyCoursePlan", { params });
export const copyCourse = (params) => http.get("/admin/offcourse/copyCourse", { params });
//8课程导出
export const exportP = (obj) => http.post("/admin/offcourse/export", obj);
//9操作面授课(发布,撤回,删除,审核,停用)
@@ -63,7 +63,24 @@ export const getMemberInfoApi = (obj) =>
http.post("/admin/orgStruct/getMemberInfo", obj);
//课程推荐列表
export const page = (obj) => http.post("/recommend/page",obj)
export const page = (obj) => http.post("/recommend/page", obj)
//课程推荐或撤回推荐
export const recommend = (obj) =>
Http.post("/manageApi/recommend/recommend",obj)
Http.post("/manageApi/recommend/recommend", obj)
// 精品课程列表
export const getExquisiteCoursePage = (params) =>
Http.post("/quality/manage/pages", params)
// 年份展示
export const getExquisiteYearList = (params) =>
Http.post("/quality/manage/createYearList", params)
export const getExport = (params) =>
Http.post("/quality/manage/export", params)
// 标记/导入标记
export const getMark = (params) =>
Http.post("/quality/manage/mark", params)
// / 置顶
export const getToTop = (params) =>
Http.post("/quality/manage/toTop", params)
export const getToTopSort = (params) =>
Http.post("/quality/manage/resetTopSort", params)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,959 @@
<template>
<div class="courseManage">
<div class="headerf">
<!-- 搜索框及按钮 -->
<div class="filter">
<div class="filterItems">
<div style="display: flex; width: 100%;">
<div class="pathnameInp">
<a-input v-model:value="courseNameOrTeacherName" style="width: 276px; height: 40px; border-radius: 8px;"
placeholder="请输入课程名称或授课教师" />
</div>
<div class="select">
<a-select v-model:value="courseSource" style="width: 200px" placeholder="请选择课程类型" allowClear>
<a-select-option value="1">内部课</a-select-option>
<a-select-option value="2">外部课</a-select-option>
</a-select>
</div>
<div class="select">
<a-tree-select v-model:value="sysType" show-search style="width: 200px"
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" placeholder="请选择内容分类" allow-clear
tree-default-expand-all :fieldNames="{
children: 'children',
label: 'name',
value: 'code',
}" :tree-data="sysTypeOptions">
</a-tree-select>
</div>
<!-- 添加年份选择 -->
<div class="select yearSelect">
<a-select v-model:value="years" mode="multiple" style="width: 200px" placeholder="请选择年份"
:options="yearOptions" allow-clear :max-tag-count="1">
</a-select>
</div>
<!-- 导入标记弹窗 -->
<!-- <a-modal v-model:visible="importMarkVisible" title="导入标记" @ok="handleImportMarkOk"
@cancel="handleImportMarkCancel">
<a-form :model="importMarkForm" layout="vertical">
<a-form-item label="课程名称" name="courseName">
<a-input v-model:value="importMarkForm.courseName" placeholder="请输入课程名称" />
</a-form-item>
</a-form>
</a-modal> -->
<a-modal v-model:visible="importMarkVisible" :closable="closableQR" wrapClassName="sofModal" :footer="null"
style="margin-top: 400px" @cancel="of_exit">
<div class="selectonlineface" :style="{ display: importMarkVisible ? 'block' : 'none' }">
<div class="of_header"></div>
<div class="of_main">
<div class="ofm_header">
<span>批量标记</span>
<div class="close_exit" @click="of_exit"></div>
</div>
<div class="ofm_body">
<div class="ofmb_items">
<div class="signbox">
<div class="sign">
<img src="@/assets/images/coursewareManage/asterisk.png" alt="" />
</div>
<span style="margin-right: 3px">课程名称</span>
</div>
<div class="in b_input">
<a-input v-model:value="xzinputV1" maxlength="20"
style="width: 440px; height: 40px; border-radius: 8px" placeholder="请输入课程名称" />
</div>
</div>
<div class="b_sub">
<div class="bs_header">
<div class="b_left">
<span style="color: #999ba3">*请按照excel表格中按列粘贴课程名称</span>
</div>
</div>
</div>
<div class="b_footer">
<div class="btn btn6" @click="handleImportMarkCancel">
<div class="btnText">取消</div>
</div>
<a-button class="btn btn6" @click="handleImportMarkOk" :loading="validated === 1">
确定
</a-button>
</div>
</div>
</div>
</div>
</a-modal>
<div style="display: flex; margin-bottom: 20px; margin-left: auto;" class="courseBtn">
<div class="btn btn1" @click="exportTaskStu">
<div class="btnText">导入标记</div>
</div>
<div class="btn btn1" @click="exportAll">
<div class="btnText">导出表格</div>
</div>
<div class="btn btn1" @click="handleSearch1">
<div class="search"></div>
<div class="btnText">搜索</div>
</div>
<div class="btnn btn2" @click="handleRest1">
<div class="search"></div>
<div class="btnText">重置</div>
</div>
</div>
</div>
</div>
</div>
<!-- 搜索框及按钮 -->
</div>
<!-- 表格 -->
<div style="padding: 10px 35px">
<a-table :header-cell-style="{ 'text-align': 'center' }" style="border: 1px solid #f2f6fe" :columns="columns1"
:data-source="tableData1" :loading="tableLoading" :pagination="false" :scroll="{ x: 1300 }" row-key="id">
<template #actions="{ text, record, index }">
<a-space :key="record.id">
<a-button @click="() => handleMark(record)" type="link" v-if="record.qualityStatus == false">
标记课程
</a-button>
<a-button @click="() => handleNo(record)" type="link" v-if="record.qualityStatus == true">
取消标记
</a-button>
<a-button @click="() => handleTop(record)" type="link" v-if="record.qualityTop == false"
:disabled="record.qualityStatus == false">
置顶展示
</a-button>
<a-button @click="() => handleNoTop(record)" type="link" v-if="record.qualityTop == true">
取消置顶
</a-button>
</a-space>
</template>
</a-table>
</div>
<div class="pa">
<a-pagination v-if="tableDataTotal1 > 10" :showSizeChanger="true" :showQuickJumper="true" :hideOnSinglePage="true"
:pageSize="pageSize1" :current="currentPage1" :total="tableDataTotal1" class="pagination"
@change="handelChangePage1" />
</div>
<!-- 表格 -->
</div>
</template>
<script lang="jsx">
import {
reactive,
toRefs,
defineComponent,
computed,
ref,
onMounted
} from "vue";
import { useStore } from "vuex";
import { message } from "ant-design-vue";
import dialog from "@/utils/dialog";
import "@wangeditor/editor/dist/css/style.css";
import {
getExquisiteCoursePage,
getExquisiteYearList,
getMark,
getToTop,
getToTopSort
} from "@/api/indexCourse";
import * as moment from "moment";
import useDownload from "@/hooks/useDownload";
export default defineComponent({
components: {},
setup() {
const store = useStore();
// 内容分类
const sysTypeOptions = computed(() => { return store.state.content_type; });
const handleMsg = {
mark: "是否确认标记当前课程为精品课?",
top: "是否确认置顶当前课程?",
noMark: "是否确认取消标记当前课程为精品课?",
noTop: "是否确认取消置顶当前课程?"
};
const state = reactive({
tableLoading: false,
// 添加年份相关数据
selectedYears: [],
yearOptions: [],
// 添加导入标记弹窗相关数据
importMarkVisible: false,
importMarkForm: {
courseName: ''
},
columns1: [
{
title: "序号",
width: 40,
dataIndex: "xuhaoid",
key: "xuhaoid",
align: "center",
customRender: ({ text, record, index }) => {
return index + 1
},
},
{
title: "名称",
width: 120,
dataIndex: "courseNameOrTeacherName",
key: "courseNameOrTeacherName",
ellipsis: true,
align: "center",
customRender: ({ text }) => {
return text ? text : "";
},
},
{
title: "课程类型",
width: 100,
dataIndex: "courseSource",
ellipsis: true,
key: "courseSource",
align: "center",
customRender: ({ text }) => {
return text == 1 ? '内部课' : text == 2 ? "外部课" : "-";
},
},
{
title: "内容分类",
width: 90,
dataIndex: "sysType",
key: "sysType",
align: "left",
ellipsis: true,
customRender: ({ text }) => {
return text ? text : "";
},
},
{
title: "关键字",
width: 52,
dataIndex: "keywords",
key: "8",
ellipsis: true,
align: "center",
customRender: ({ text }) => {
return text ? text : "-";
},
},
{
title: "授课教师",
width: 52,
dataIndex: "teacherName",
key: "8",
ellipsis: true,
align: "center",
customRender: ({ text }) => {
return text ? text : "-";
},
},
{
title: "创建时间",
width: 52,
dataIndex: "sysCreateTime",
key: "9",
ellipsis: true,
align: "center",
customRender: ({ text }) => {
return text ? text : "-";
},
},
{
title: "操作时间",
width: 84,
ellipsis: true,
dataIndex: "qualityTime",
key: "10",
align: "center",
customRender: ({ text }) => {
return text || "-";
},
},
{
title: "操作人",
width: 52,
dataIndex: "qualityPeople",
key: "qualityPeople",
align: "center",
customRender: ({ text }) => {
return text || "-";
},
},
{
title: "置顶顺序",
width: 52,
dataIndex: "qualityTopSort",
key: "qualityTopSort",
align: "center",
customRender: ({ text, record, index }) => {
// 如果处于编辑状态,显示输入框
if (record.editingTopOrder) {
return (
<a-input
value={text || ''}
style={{ width: '60px' }}
onBlur={() => {
// 失去焦点时取消编辑状态
record.editingTopOrder = false;
// 这里可以添加保存到服务器的逻辑
}}
onChange={(e) => {
// 更新数据
record.qualityTopSort = e.target.value;
handleSort(record)
}}
onKeydown={(e) => {
if (e.key === 'Enter') {
// 按回车时保存并取消编辑状态
record.editingTopOrder = false;
// 这里可以添加保存到服务器的逻辑
}
}}
/>
);
}
// 否则显示文本,点击后进入编辑状态
return (
<div onClick={() => { record.editingTopOrder = true; }}>
<span style={{ cursor: 'pointer' }}>
{text || '-'}
</span>
</div>
);
},
},
{
title: "操作",
width: 84,
dataIndex: "id",
key: "id",
fixed: "right",
align: "center",
slots: { customRender: "actions" },
},
],
//列表表格
tableData1: [
{
id: 2,
name: "React核心概念详解",
sysType1: "A01",
sysType2: "B02",
sysTypeId: "A01",
keywords: "前端,React,JavaScript",
teacherName: "王五",
sysCreateTime: "2023-01-20",
exquisiteTime: "2023-02-05",
exquisiteRecommender: "赵六",
qualityTop: true,
courseSource: 1,
qualityStatus: true,
qualityTopSort: 2
},
{
id: 2,
name: "React核心概念详解",
sysType1: "A01",
sysType2: "B02",
sysTypeId: "A01",
keywords: "前端,React,JavaScript",
teacherName: "王五",
sysCreateTime: "2023-01-20",
exquisiteTime: "2023-02-05",
exquisiteRecommender: "赵六",
courseSource: 2,
qualityTop: false,
qualityStatus: true,
qualityTopSort: 2
},
{
id: 1,
name: "Vue从入门到精通",
sysType1: "A01",
sysType2: "B01",
sysType3: "C01",
keywords: "前端,Vue,JavaScript",
teacherName: "",
sysCreateTime: "2023-01-15",
exquisiteTime: "2023-02-01",
exquisiteRecommender: "李四",
qualityTop: false,
qualityStatus: false,
qualityTopSort: 1
},
],
currentPage1: 1,
tableDataTotal1: 3,
pageSize1: 10,
pageSize: 10,
courseNameOrTeacherName: "",
courseSource: undefined,
years: [],
sysType: null,
});
// 调整顺序
const handleSort = (record) => async () => {
try {
// 这里应该调用实际的API接口
await getToTopSort({ courseId: record.courseId, qualityTopSort: record.qualityTopSort });
message.success("标记课程成功!");
// 重新加载数据
// getTableDate();
} catch (error) {
message.error("标记课程失败!");
}
};
// 标记课程处理
function handleMark(record) {
dialog({ content: handleMsg.mark, ok: handleMarkOk(record) });
}
// 取消标记课程处理
function handleNo(record) {
dialog({ content: handleMsg.noMark, ok: handleMarkNo(record) });
}
// 置顶处理
function handleTop(record) {
dialog({ content: handleMsg.top, ok: handleTopOk(record) });
}
// 取消置顶处理
function handleNoTop(record) {
dialog({ content: handleMsg.noTop, ok: handleTopNo(record) });
}
// 标记课程确认操作
const handleMarkOk = (record) => async () => {
try {
// 这里应该调用实际的API接口
await getMark({ courseId: record.courseId });
message.success("标记课程成功!");
// 重新加载数据
// getTableDate();
} catch (error) {
message.error("标记课程失败!");
}
};
// 取消标记课程确认操作
const handleMarkNo = (record) => async () => {
try {
// 这里应该调用实际的API接口
await getMark({ courseId: record.courseId });
message.success("取消标记课程成功!");
// 重新加载数据
// getTableDate();
} catch (error) {
message.error("取消标记课程失败!");
}
};
// 置顶确认操作
const handleTopOk = (record) => async () => {
try {
// 这里应该调用实际的API接口
await getToTop({ courseId: record.courseId });
message.success("置顶课程成功!");
// 重新加载数据
// getTableDate();
} catch (error) {
message.error("置顶课程失败!");
}
};
// 取消置顶确认操作
const handleTopNo = (record) => async () => {
try {
// 这里应该调用实际的API接口
await getToTop({ courseId: record.courseId });
message.success("取消置顶课程成功!");
// 重新加载数据
// getTableDate();
} catch (error) {
message.error("取消置顶课程失败!");
}
};
// 渲染列表1操作
const getTableDate = async () => {
state.tableLoading = true;
console.log("我是点击搜索的数据", state.sysTypeId);
let res = await getExquisiteCoursePage({
courseNameOrTeacherName: state.courseNameOrTeacherName,
pageNum: state.currentPage1,
pageSize: state.pageSize1,
courseSource: state.courseSource,
years: state.years,
sysType: state.sysTypeId,
// sysType1: state.sysType1,
// sysType2: state.sysType2,
// sysType3: state.sysType3
});
const { records, total } = res.data.data.result;
const { count } = res.data.data
state.count = count
state.tableData1 = records;
state.tableDataTotal1 = total;
state.tableLoading = false;
console.log("state.tableData1", state.tableData1);
};
getTableDate();
// 初始化年份选项
const initializeYearOptions = async () => {
// const currentYear = new Date().getFullYear();
const years = [];
// for (let i = currentYear; i >= 2000; i--) {
// years.push({ label: `${i}`, value: i.toString() });
// }
let res = await getExquisiteYearList()
const { data } = res.data
data.result.map((value) => {
years.push({ label: `${value.name}`, value: value.id.toString() });
})
state.yearOptions = years;
};
//内容分类
const getSysTypeMap = (code) => {
if (code == "") return
return state.sysTypeMap.get(code)
}
//分页
const handelChangePage1 = (page, pageSize) => {
state.currentPage1 = page;
state.pageSize1 = pageSize;
getTableDate();
};
//搜索
const handleSearch1 = () => {
state.currentPage1 = 1
// 在这里可以添加实际的搜索逻辑
console.log('搜索参数:', {
courseNameOrTeacherName: state.courseNameOrTeacherName,
courseSource: state.courseSource,
sysType: state.sysType,
years: state.years // 添加年份参数
})
getTableDate();
};
// 全部导出
const exportAll = async () => {
try {
await useDownload(
"/quality/manage/export",
{
courseNameOrTeacherName: state.courseNameOrTeacherName,
courseSource: state.courseSource,
years: state.years,
sysType: state.sysType,
},
"精美课程导出",
"xlsx"
);
message.success("导出成功");
} catch (error) {
message.error("导出失败");
console.error("导出失败:", error);
}
};
// 导入标记
const exportTaskStu = () => {
state.importMarkVisible = true;
};
// 导入标记弹窗确认
const handleImportMarkOk = () => {
console.log('导入标记表单数据:', state.importMarkForm);
// 这里应该调用实际的导入标记API
state.importMarkVisible = false;
state.importMarkForm.courseName = '';
};
// 导入标记弹窗取消
const handleImportMarkCancel = () => {
state.importMarkVisible = false;
state.importMarkForm.courseName = '';
};
// const handleSearch = () => {
// state.currentPage1 = 1
// // 在这里可以添加实际的搜索逻辑
// console.log('搜索参数:', {
// courseNameOrTeacherName: state.courseNameOrTeacherName,
// courseSource: state.courseSource,
// years: state.years,
// sysType: state.sysTypeId,// 添加年份参数
// })
// }
//重置
const handleRest1 = () => {
state.courseNameOrTeacherName = ''
state.courseSource = undefined
state.sysType = null;
state.years = []; // 重置年份选择
getTableDate();
};
onMounted(() => {
initializeYearOptions(); // 初始化年份选项
});
return {
...toRefs(state),
sysTypeOptions,
moment,
handelChangePage1,
handleSearch1,
// handleSearch,
handleRest1,
getSysTypeMap,
handleMark,
handleTop,
handleNo,
handleNoTop,
handleMarkOk,
handleTopOk,
handleMarkNo,
handleTopNo,
exportAll,
exportTaskStu,
handleImportMarkOk,
handleImportMarkCancel
};
},
});
</script>
<style lang="scss" scoped>
:deep(.ant-table-thead > tr > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before) {
background-color: rgba(0, 0, 0, 0.1);
}
.courseManage {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
position: relative;
.filter {
margin-left: 35px;
margin-right: 35px;
margin-top: 30px;
display: flex;
flex-wrap: wrap;
.filterItems {
width: 100%;
justify-content: space-between;
display: flex;
flex-wrap: wrap;
.pathnameInp {
margin-right: 20px;
margin-bottom: 20px;
}
.select {
margin-right: 20px;
margin-bottom: 20px;
}
.btn {
padding: 0px 26px 0px 26px;
height: 38px;
background: #4ea6ff;
border-radius: 8px;
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;
}
}
.btnn {
padding: 0px 26px 0px 26px;
height: 38px;
background: #ffffff;
color: #388BE1;
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: #388BE1;
line-height: 36px;
margin-left: 5px;
}
}
.btn1 {
.search {
width: 15px;
height: 17px;
background-image: url("../../assets/images/courseManage/search0.png");
}
}
.btn1:active {
background: #0982ff;
}
.btn2:active {
background: #0982ff;
}
.btn2 {
.search {
width: 16px;
height: 18px;
background-image: url("../../assets/images/courseManage/reset1.png");
}
.daochu {
width: 16px;
height: 18px;
background-image: url("../../assets/images/coursewareManage/export1.png");
}
}
.btn1 {
.search {
background-image: url("../../assets/images/courseManage/search0.png");
}
.btnText {
color: #ffffff;
}
}
}
}
}
.pa {
width: 100%;
display: flex;
justify-content: center;
margin-bottom: 20px;
.pagination {
margin-bottom: 20px;
}
.ant-pagination-item-link,
.ant-pagination-item,
.ant-select-selector,
.ant-pagination-options-quick-jumper input {
border-radius: 8px;
}
}
.ant-select-selector {
height: 100%;
border-radius: 8px;
}
.ant-upload.ant-upload-select-picture-card {
border: none;
}
:deep(.yearSelect .ant-select-selection-item) {
line-height: 20px !important;
}
.selectonlineface {
z-index: 999;
width: 679px;
background: #ffffff;
box-shadow: 0px 1px 35px 0px rgba(118, 136, 166, 0.21);
position: absolute;
left: 50%;
top: -100%;
transform: translate(-50%, -50%);
.of_header {
position: absolute;
width: 100%;
height: 40px;
background: linear-gradient(rgba(78, 166, 255, 0.2) 0%,
rgba(78, 166, 255, 0) 100%);
}
.of_main {
width: 100%;
position: relative;
.ofm_header {
display: flex;
align-items: center;
padding-top: 20px;
padding-left: 26px;
font-size: 16px;
.add_icon {
width: 16px;
height: 16px;
margin-right: 10px;
background-image: url(@/assets/images/coursewareManage/add1.png);
background-size: 100% 100%;
}
.close_exit {
position: absolute;
right: 42px;
cursor: pointer;
width: 20px;
height: 20px;
background-image: url(@/assets/images/coursewareManage/close.png);
background-size: 100% 100%;
}
}
.ofm_body {
width: 80%;
margin: 3px auto;
.ofmb_items {
display: flex;
align-items: center;
justify-content: end;
margin: 14px auto;
.signbox {
width: 110px;
display: flex;
justify-content: end;
.sign {
width: 10px;
height: 10px;
margin-top: -3px;
margin-right: 6px;
background-size: 100% 100%;
}
}
.b_input {
flex: 1;
position: relative;
.inp_num {
position: absolute;
right: 10px;
top: 10px;
}
}
.bc_box {
display: flex;
width: 440px;
flex: 1;
.bc_boxl,
.bc_boxr {
display: flex;
align-items: center;
}
}
}
.ol_checkbox {
display: flex;
align-items: center;
justify-content: center;
margin-left: 6px;
}
.b_sub {
width: 440px;
margin-left: 110px;
font-size: 12px;
.bs_header {
display: flex;
justify-content: space-between;
.b_right {
display: flex;
}
}
}
.bl_detail {
top: 24px;
}
.b_footer {
width: 100%;
margin-left: 212px;
margin-top: 25px;
margin-bottom: 20px;
display: flex;
.btn {
width: 100px;
height: 40px;
background: rgba(64, 158, 255, 0);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 14px;
flex-shrink: 0;
cursor: pointer;
.btnText {
font-size: 14px;
font-weight: 400;
line-height: 40px;
}
}
.btn5 {
border: 1px solid rgba(64, 158, 255, 1);
color: #4ea6ff;
}
.btn6 {
background-color: #4ea6ff;
color: #ffffff;
}
}
}
}
}
</style>