新增教师节

This commit is contained in:
joshen@zcwytd.com
2023-09-06 17:21:30 +08:00
parent 10b9e2cfa5
commit f543d1ee5b
27 changed files with 5798 additions and 1360 deletions

63
src/api/configPublic.js Normal file
View File

@@ -0,0 +1,63 @@
import { message } from "ant-design-vue";
import axios from "axios";
import router from "@/router";
import Cookies from 'vue-cookies'
axios.defaults.withCredentials = true;
const http = axios.create({
timeout: 1000 * 15,
headers: { "Content-Type": "application/json", },
});
http.interceptors.request.use(
(config) => {
const token = Cookies.get("token")
if (token) {
config.headers.token = token; //测试1111
} else {
message.error('未获取到登录信息,请先登录')
}
return config;
},
(err) => {
console.log("登陆前拦截", err);
return Promise.reject(err);
}
);
http.interceptors.response.use(
(response) => {
// console.log('response', response)
const {
data: { code },
} = response;
if (code === 0 || code === 200) {
return response.data ? response.data : response;
}
if (code == 500) {
return message.error('请求失败');
}
if (code == 601) {
message.error('token过期请重新登陆');
}
if (code === 1000) {
(process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'alpine') ? router.push({ path: 'login' }) : (window.location.href = process.env.VUE_APP_LOGIN_URL)
return Promise.reject(response);
}
// show && message.error(msg);
// console.log("api %o", msg);
// return Promise.reject(response);
return response
},
function (error) {
if (error.message == "timeout of 1ms exceeded") {
message.destroy();
message.error("请求超时");
}
console.log("api error %o", error);
return message.error(error.message);
}
);
export default http;

113
src/api/grateful.js Normal file
View File

@@ -0,0 +1,113 @@
import http from './configPublic'
const ACTIVITYAPI = '/activityApi'
/**
* 通知
*/
//通知列表
const noticeList = (data = {}) => http.post(`${ACTIVITYAPI}/xboe/m/boe/notice/list`, data);
//通知新增或修改
const noticeAddAndUpdate = (data = {}) => http.post(`${ACTIVITYAPI}/xboe/m/boe/notice/save`, data);
//通知删除
const noticeDelete = (params) => http.delete(`${ACTIVITYAPI}/xboe/m/boe/notice/delete`, { params });
//通知根据id查询
const noticeDataById = (params) => http.get(`${ACTIVITYAPI}/xboe/m/boe/notice/getDataById`, { params });
/**
* 轮播图
*/
//轮播图列表
const carouselList = `${ACTIVITYAPI}/xboe/m/boe/rotation/list`
//轮播图新增或修改
const carouseAddAndUpdate = (data = {}) => http.post(`${ACTIVITYAPI}/xboe/m/boe/rotation/save`, data);
//轮播图删除
const carouseDelete = (params) => http.delete(`${ACTIVITYAPI}/xboe/m/boe/rotation/delete`, { params });
//轮播图删除
const getCarouseById = (params) => http.get(`${ACTIVITYAPI}/xboe/m/boe/rotation/getDataById`, { params });
/**
* 教师赋能
*/
//教师赋能列表
const courselList = `${ACTIVITYAPI}/xboe/m/boe/course/list`
//添加赋能选择课程接口
const addCourselList = (data = {}) => http.post(`${process.env.VUE_APP_SYS_API}/xboe/m/course/fulltext/search`, data, {
headers: { "Content-Type": "application/x-www-form-urlencoded" },
});
//取消赋能
const courseDelete = (params) => http.delete(`${ACTIVITYAPI}/xboe/m/boe/course/disEnabled`, { params });
//获取已有课程id集合
const courseIds = (params) => http.get(`${ACTIVITYAPI}/xboe/m/boe/course/ids`, { params });
//批量增加课程
const saveCourseList = (data = {}) => http.post(`${ACTIVITYAPI}/xboe/m/boe/course/saveList`, data);
/**
* 工具
*/
//工具列表
const toolList = (data = {}) => http.post(`${ACTIVITYAPI}/xboe/m/boe/tools/list`, data);
//新增工具
const saveTool = (data = {}) => http.post(`${ACTIVITYAPI}/xboe/m/boe/tools/save`, data);
//工具删除
const toolDelete = (params) => http.delete(`${ACTIVITYAPI}/xboe/m/boe/tools/delete`, { params });
//工具下载
const toolDown = (params) => http.get(`/systemapi/xboe/sys/xuploader/url/download`, { params });
/**
* 师资大全
*/
//师资大全列表
const teachersList = `${ACTIVITYAPI}/xboe/m/boe/teachers/list`
//师资大全列表添加和编辑接口
const teachersAddAndUpdate = (data = {}) => http.post(`${ACTIVITYAPI}/xboe/m/boe/teachers/save`, data);
//教师删除
const teachersDelete = (params) => http.delete(`${ACTIVITYAPI}/xboe/m/boe/teachers/delete`, { params });
/**
* 意见
*/
//意见列表
const opinionList = `${ACTIVITYAPI}/xboe/m/boe/opinion/list`
//意见删除
const opinionDelete = (params) => http.delete(`${ACTIVITYAPI}/xboe/m/boe/opinion/delete`, { params });
//查看当前协议
//type 查看的类型
const query = (type) => http.get('/systemapi/xboe/m/assistance/protocol/query?type=' + type);
const RECOMMEND_PAGE = "/systemapi/xboe/m/boe/cases/recommend/page";
export {
RECOMMEND_PAGE,
noticeList,
noticeDelete,
noticeAddAndUpdate,
noticeDataById,
carouselList,
carouseAddAndUpdate,
carouseDelete,
getCarouseById,
courselList,
courseIds,
saveCourseList,
addCourselList,
courseDelete,
toolList,
saveTool,
toolDelete,
toolDown,
teachersList,
teachersAddAndUpdate,
teachersDelete,
opinionList,
opinionDelete,
query
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,396 @@
<template>
<a-drawer class="recommend" v-model:visible="visible" width="80%" title="添加教师" @close="closeDrawer"
:maskClosable="false">
<div class="cstm_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="b_input">
<a-input v-model:value="formData.teacherName" maxlength="50"
style="width: 88%; height: 40px; border-radius: 8px" placeholder="请输入教师姓名" />
</div>
</div>
<div class="cstm_items">
<div class="signbox">
<span style="margin-right: 3px">工号</span>
</div>
<div class="b_input">
<a-input v-model:value="formData.teacherNo" maxlength="50"
style="width: 88%; height: 40px; border-radius: 8px" placeholder="请输入教师工号" />
</div>
</div>
<div class="cstm_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="b_input">
<a-input v-model:value="formData.courseName" placeholder="请输入课程名称" show-count
style="width: 88%; height: 40px; border-radius: 8px" :maxlength="30" />
</div>
</div>
<div class="cstm_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="b_input">
<a-input v-model:value="formData.teacherTime" maxlength="50"
style="width: 88%; height: 40px; border-radius: 8px" placeholder="请输入授课时长">
<template #suffix>
<div class="inp_num">
<span style="color: #c7cbd2">/h</span>
</div>
</template>
</a-input>
</div>
</div>
<div class="cstm_items">
<div class="signbox">
<span style="margin-right: 3px">教师等级</span>
</div>
<div class="b_input">
<a-input-number style="width: 88%; height: 40px; line-height: 40px; border-radius: 8px" :min="0"
:max="999999" placeholder="请输入教师等级" :precision="0" v-model:value="formData.teacherLevel">
</a-input-number>
</div>
</div>
<div class="cstm_items">
<div class="signbox">
<span style="margin-right: 3px">组织全路径</span>
</div>
<div class="b_input">
<a-input v-model:value="formData.orgPath" maxlength="50"
style="width: 88%; height: 40px; border-radius: 8px" placeholder="请输入组织全路径" />
</div>
</div>
<div class="cstm_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="b_input">
<a-input-number style="width: 88%; height: 40px; line-height: 40px; border-radius: 8px" :min="0"
:max="999999" :precision="0" v-model:value="formData.sort">
</a-input-number>
</div>
</div>
<div class="cstm_items" style="align-items: start;">
<div class="signbox">
<div class="sign">
<img src="@/assets/images/coursewareManage/asterisk.png" alt="" />
</div>
<span style="margin-right: 3px">课程价值</span>
</div>
<div class="b_input">
<a-textarea v-model:value="formData.meaning" showCount :maxlength="30"
:auto-size="{ minRows: 5, maxRows: 5 }" style="width: 88%; border-radius: 8px" placeholder="请输入课程价值" />
</div>
</div>
<div class="items_btn">
<div class="cstm_btn btn6" @click="closeDrawer">
<div class="btnText">取消</div>
</div>
<a-button class="cstm_btn btn6" @click="submit">
确定
</a-button>
</div>
</a-drawer>
<a-button type="link" @click="openDrawer">
<slot></slot>
</a-button>
</template>
<script setup>
import { nextTick, ref, toRaw, watch, reactive } from 'vue';
import { Form, message } from "ant-design-vue";
import { useResetRef } from "@/utils/useCommon";
import { teachersAddAndUpdate } from '@/api/grateful';
const formData = useResetRef({
courseName: "",
enabled: false,
id: '',
meaning: "",
orgPath: "",
sort: 1000,
teacherLevel: "",
teacherName: "",
teacherNo: "",
teacherTime: ""
});
const emit = defineEmits(['getList'])
const visible = ref(false);
const formDataRule = {
teacherName: [
{
required: true,
message: "请输入姓名",
},
],
courseName: [
{
required: true,
message: "请输入课程名称",
},
],
teacherTime: [
{
required: true,
message: "请输入授课时长",
},
],
sort: [
{
required: true,
message: "请选择排序",
},
],
meaning: [
{
required: true,
message: "请输入课程价值",
},
]
};
const { validate } = Form.useForm(formData, formDataRule);
// 取消抽屉
const closeDrawer = async () => {
visible.value = false
}
const openDrawer = async () => {
await formData.reset()
console.log('哈哈哈');
visible.value = true;
};
const submit = async () => {
await validate().catch(({ errorFields }) => {
message.error(errorFields[0].errors.join());
throw Error("数据校验不通过");
});
const result = await teachersAddAndUpdate(formData.value)
if (result.code == 200) {
visible.value = false;
message.success("添加成功!");
emit('getList')
} else {
message.error("添加失败!");
}
}
</script>
<style lang="scss" scoped>
.items_btn {
display: flex;
justify-content: center;
margin-top: 20px;
margin-bottom: 20px;
.cstm_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;
}
}
.recommend {
:global(.ant-drawer-header-title) {
flex-direction: row-reverse !important;
}
:global(.ant-drawer-close) {
margin-right: 0;
}
.btnn {
height: 72px;
width: 100%;
position: absolute;
background-color: #fff;
bottom: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0px 1px 35px 0px rgba(118, 136, 166, 0.16);
.btn1 {
width: 100px;
height: 40px;
border: 1px solid #4ea6ff;
border-radius: 8px;
color: #4ea6ff;
background-color: #fff;
cursor: pointer;
}
.btn2 {
cursor: pointer;
width: 100px;
height: 40px;
background: #4ea6ff;
border-radius: 8px;
border: 0;
margin-left: 15px;
color: #fff;
}
}
// 头部
.filter {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
.filterItems {
display: flex;
flex-wrap: wrap;
.select {
margin-right: 20px;
margin-bottom: 20px;
}
.btn {
padding: 0 26px 0 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: 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;
}
}
.btn1 {
.search {
width: 15px;
height: 17px;
background-image: url("../../assets/images/courseManage/search0.png");
}
}
.btn1:active {
background: #0982ff;
}
}
}
}
.cstm_items {
display: flex;
width: 80%;
margin: auto;
align-items: center;
margin-bottom: 23px;
.signbox {
display: flex;
justify-content: end;
width: 100px;
margin-right: 6px;
.sign {
margin-top: -5px;
margin-right: 4px;
}
}
.b_input {
flex: 1;
position: relative;
.upload_box {
display: flex;
cursor: pointer;
.upload_icon {
width: 16px;
height: 16px;
margin-right: 5px;
}
}
.inp_num {
position: absolute;
top: 9px;
right: 10px;
}
}
}
</style>

View File

@@ -0,0 +1,35 @@
<template>
<div class="item-agr">
<div style="padding: 0 10px 0 10px;color:#626262;;">
<div style="text-align: center;font-size: 26px;padding-bottom:20px ;">京东方大学堂内容发布须知</div>
<div class="agr-content" style="min-height: 320px;max-height: 320px;overflow-y: auto;">
<div style="">
<p style="margin-bottom:10px;line-height: 24px; white-space:pre-line" v-for="(item, index) in context"
:key="index">{{ item }}</p>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { query } from '@/api/grateful.js';
import { ref } from 'vue';
const context = ref([])
const getInfo = () => {
query(1).then(res => {
if (res.status == 200) {
let list = res.data.result.content.split('\n');
context.value = list;
}
})
}
getInfo()
</script>
<style scoped lang="scss">
.agr-content {
background: #f7f7f7;
padding: 20px;
color: #626262;
}
</style>

View File

@@ -0,0 +1,160 @@
<template>
<a-modal v-model:visible="visiable" width="813px" @cancel="cancel" :title="title" :footer="null" :maskClosable="false">
<div class="content">
<a-form :rules="rules" :label-col="{ span: 4 }">
<a-form-item label="展示页面">
<a-input style="width: 60%" v-model:value="params.displayPage" />
</a-form-item>
<a-form-item label="轮播图片" name="picture">
<UploadDragger ref="uploadRef" v-model:value="files" @change="change" :accept="accept"
:uploadUrl="uploadUrl" :params="folderId" />
</a-form-item>
<a-form-item v-if="params.picPath" :wrapper-col="{ offset: 4 }">
<a-image :src="path" />
</a-form-item>
<a-form-item :wrapper-col="{ offset: 4 }">
<div class="footer">
<a-button class="btn" @click="cancel" type="primary">取消</a-button>
<a-button class="btn" @click="submit" type="primary" style="margin:0 20px;">提交</a-button>
<!-- <a-checkbox v-model:checked="checked">我已阅读并遵守<span
style="color: rgb(88, 138, 252);">平台内容发布要求</span></a-checkbox> -->
</div>
</a-form-item>
</a-form>
</div>
</a-modal>
<a-modal :visible="open" width="800px" :closable="false">
<Agreement></Agreement>
<template #footer>
<a-button type="submit" @click="submitOpen">确定</a-button>
</template>
</a-modal>
<a-button @click="showModal" type="link">
<slot></slot>
</a-button>
</template>
<script setup>
import { computed, defineProps, ref, toRefs, watch, nextTick } from "vue";
import UploadDragger from './UploadDragger'
import { carouseAddAndUpdate, getCarouseById, } from "@/api/grateful";
import { message } from "ant-design-vue";
const props = defineProps({
title: {
type: String,
},
id: {
type: String
},
handleRest: {
type: Function,
default: () => { }
}
});
const open = ref(false)
const publishInfo = () => {
open.value = !open.value
}
const submitOpen = () => {
open.value = !open.value
}
const uploadRef = ref()
//上传文件夹的id
const folderId = ref({ folderId: process.env.VUE_APP_PIC_FOLDERID })
const files = ref([]);
const visiable = ref(false);
const params = ref({
id: '',
pid: '',
picFolderId: '',
picPath: '',
displayPage: '感恩教师节'
})
const path = computed(() => `https:${process.env.VUE_APP_BOE_API_URL}/upload${params.value.picPath}`)
const rules = {
picture: [{
required: true,
message: ''
}]
}
const uploadUrl = ref("/systemapi/api/m/xfile/base/file/upload");
const checked = ref(false)
const accept = ".png,.jpg,.jpeg,.gif,.svg,.bmp"
const showModal = async () => {
files.value = []
params.value.id = props.id
console.log(files.value);
if (!props.id) {
params.value.displayPage = '感恩教师节'
params.value.picFolderId = ''
params.value.pid = ''
params.value.picPath = ''
checked.value = false
visiable.value = true;
return
}
getCarouseById({ id: props.id }).then((result) => {
console.log(result);
params.value.displayPage = result.data.displayPage
params.value.picPath = result.data.picPath
params.value.pid = result.data.picId
params.value.picFolderId = result.data.picFolderId
visiable.value = true;
checked.value = true
}).catch((err) => {
message.error(err)
visiable.value = true;
});
};
const change = (newValue) => {
if (newValue[0].status == 'done' && newValue[0]?.response?.status == 200) {
const { id, folderId, path } = files.value[0].response.result
console.log(newValue[0].response.result.path, 9999999999999);
params.value.picPath = path
params.value.folderId = folderId
params.value.pid = id
}
}
const emit = defineEmits(['change'])
const submit = () => {
console.log('提交', files);
if (!params.value.picPath) return message.error('请上传轮播图图片')
if (!checked.value) return message.error('请先阅读并遵守平台内容发布要求')
carouseAddAndUpdate(params.value).then((result) => {
console.log(result);
emit('change')
message.success('提交成功')
props.handleRest()
}).catch(() => {
message.error('提交失败')
})
visiable.value = false;
uploadRef.value.removeUpload()
}
const cancel = () => {
console.log('取消');
visiable.value = false;
uploadRef.value.removeUpload()
}
</script>
<style scoped lang="scss">
.content {
padding: 24px;
.footer {
display: flex;
align-items: center;
.btn {
border-radius: 4px;
}
}
}
</style>

View File

@@ -0,0 +1,313 @@
<template>
<a-modal v-model:visible="visiable" width="1000px" @ok="submit" @cancel="getIdList()" :title="title"
:maskClosable="false">
<div class="content">
<div class="topContent">
<div class="select">
<a-input v-model:value="searchDataInfo.keyword" style="width: 270px; height: 40px; border-radius: 8px"
placeholder="请输入标题" />
</div>
<div style="display: flex; margin-bottom: 20px;margin-left: 20px;">
<div class="btn btn1" @click="getTopList(false)">
<div class="search"></div>
<div class="btnText">搜索</div>
</div>
<div class="btn btn2" style="width: 105px" @click="handleRest">
<div class="search"></div>
<div class="btnText">重置</div>
</div>
</div>
</div>
<div class="table">
<a-table style="border: 1px solid #f2f6fe;" :columns="column" :data-source="state.dataSource"
:loading="!loading" :row-selection="rowSelection" :pagination="pagination">
<template #bodyCell="{ column, record }">
<div v-if="column.dataIndex === 'title'" style="display:flex;align-items:center;">
<div>
<a-image :width="150" :height="75" :src="record.coverImg"></a-image>
</div>
<div style="height:75px;margin-left: 10px;">
<div style="height:25px">{{ record.name }}</div>
<div style="height:25px;">
<span v-if="record.type == 30"
style="background-color: #efbf82;padding: 0 5px;border-radius: 3px;color: white;">
面授
</span>
<span v-if="record.type == 10 || record.type == 20"
style="background-color: #4f9bcb;padding: 0 5px;border-radius: 3px;color: white;">
录播
</span>
<span v-if="record.type == 40"
style="background-color: #efbf82;padding: 0 5px;border-radius: 3px;color: white;">
学习项目
</span>
</div>
<div style="height:25px;display:flex;">
<div style="margin-right: 20px;">{{ formatNum(record.studies) }}人已学习</div>
<div v-if="record.score">
<span class="course-score-value">{{ toScore(record.score) }}</span>
</div>
<div v-else class="course-score-no">未评分</div>
</div>
</div>
</div>
</template>
</a-table>
</div>
</div>
</a-modal>
<a-button @click="showModal" type="link">
<slot></slot>
</a-button>
</template>
<script setup>
import { defineProps, ref, computed, reactive, nextTick, watch } from "vue";
import { Form, message } from "ant-design-vue";
import { addCourselList, courseIds, saveCourseList } from "@/api/grateful";
const formatNum = (num) => {
let rsNum = 0;
if (num < 5) { return num; }
if (num >= 5 && num <= 10) { return 10 + "+"; }
if (num <= 94) {
rsNum = Math.round((num) / 10) * 10;
return rsNum + '+';
}
if (num > 94 && num <= 1000) {
rsNum = Math.round((num) / 100) * 100;
return rsNum + '+';
}
if (num > 1000 && num <= 10000) {
rsNum = Math.round((num) / 1000) * 1000;
return rsNum + '+';
}
if (num > 10000) {
rsNum = Math.round((num) / 10000);
return rsNum + 'W+';
}
return num;
}
const toScore = (score) => {
if (!score) {
return '0';
}
if (('' + score).indexOf('.') > -1) {
return score.toFixed(1);
} else {
return score + '.0';
}
}
const emit = defineEmits(['change'])
const props = defineProps({
title: {
type: String,
},
pageSizeOptions: {
type: Array,
default: ['5', '10', '30', '50']
}
});
const column = ref([{
title: "选择课程",
dataIndex: "title",
key: "title",
align: "left",
width: '75%',
ellipsis: true,
},
{
title: "授课讲师",
dataIndex: "teacher",
key: "teacher",
width: '20%',
align: "center",
ellipsis: true,
}]);
const state = reactive({
selectedRowKeys: [],//案例标题的id
selectedRow: [],//选择的每一行数据
dataSource: [],//表格的数据
})
const visiable = ref(false);
// 查询数据
const searchDataInfo = reactive({
pageIndex: 1,
pageSize: 5,
keyword: '',
orderField: 'publishTime'
});
const { resetFields } = Form.useForm(searchDataInfo, {});
const loading = ref(false)
const total = ref(0)
const idList = ref([])
// 计算全选
const rowSelection = computed(() => {
return {
selectedRowKeys: state.selectedRowKeys,
onChange: onSelectChange,
preserveSelectedRowKeys: true,
getCheckboxProps: record => ({
disabled: idList.value.some((item) => record.id == item)
})
}
});
//计算出来已选中状态
const onSelectChange = (selectedRowKeys, selectedRow) => {
console.log(selectedRowKeys, selectedRow);
state.selectedRowKeys = selectedRowKeys
state.selectedRow = selectedRow
};
// 分页
const pagination = computed(() => ({
total: total.value,
showQuickJumper: true,
showSizeChanger: false,
current: searchDataInfo.pageIndex,
pageSize: searchDataInfo.pageSize,
pageSizeOptions: props.pageSizeOptions,
onChange: changePagination,
}));
const changePagination = (e, pageSize) => {
loading.value = false
searchDataInfo.pageIndex = e
searchDataInfo.pageSize = pageSize
nextTick(getTopList);
};
const getIdList = () => {
resetFields()
courseIds().then((res) => {
console.log(res);
idList.value = res.data
getTopList()
})
}
// 请求列表数据是formdata类型的
const getTopList = (type = true) => {
if (!type) searchDataInfo.pageIndex = 1
addCourselList(searchDataInfo).then((res) => {
loading.value = true;
console.log(res);
res?.data?.result?.list?.forEach(element => {
element.key = element.id
});
state.dataSource = res?.data?.result?.list || []
total.value = res?.data?.result?.count
}).catch((err) => {
message.error(err);
loading.value = false;
})
}
getIdList()
const showModal = () => {
visiable.value = true;
resetFields()
state.selectedRowKeys = []
state.selectedRow = []
};
const paramList = computed(() => state.selectedRow.map((item) => ({
id: item.id,
name: item.name,
publishTime: item.publishTime,
score: item.score,
studies: item.studies,
teacher: item.teacher,
type: item.type,
coverImg: item.coverImg
})))
const submit = () => {
console.log('确定');
saveCourseList(paramList.value).then((res) => {
console.log(res);
visiable.value = false;
emit('change')
getIdList()
})
}
const handleRest = () => {
resetFields()
getTopList()
}
defineExpose({
getIdList
})
</script>
<style scoped lang="scss">
.content {
padding: 24px 24px 0 24px;
.topContent {
display: flex;
.btn {
padding: 0 26px 0 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;
}
}
.btn1 {
.search {
width: 15px;
height: 17px;
background-image: url("@/assets/images/courseManage/search0.png");
}
}
.btn2 {
.search {
width: 16px;
height: 18px;
background-image: url("@/assets/images/courseManage/reset0.png");
}
}
.btn1:active {
background: #0982ff;
}
.btn2:active {
background: rgba(64, 158, 255, 0.2);
}
}
.table {
margin: 20px 38px 30px;
display: flex;
flex: 1;
flex-direction: column;
th.ant-table-cell {
background-color: #eff4fc !important;
text-align: center;
color: #999ba3;
}
td.ant-table-cell {
text-align: center;
}
}
}
</style>

View File

@@ -0,0 +1,443 @@
<template>
<div @click="openDrawer">
<slot></slot>
</div>
<a-drawer :visible="visible" class="drawerStyle impotergroupleader" placement="right" width="800px">
<div class="drawerMain">
<div class="header">
<div class="headerTitle">{{ title }}</div>
<img style="width: 29px; height: 29px; cursor: pointer" src="../../assets/images/basicinfo/close.png"
@click="closeDrawer" />
</div>
<div class="main">
<div class="minatitl" v-if="templateUrl">
<div class="up1">请下载</div>
<div class="up2" @click="downLoad" style="cursor: pointer">导入模版</div>
<div class="up1">按要求填写数据并导入</div>
</div>
<div class="upload">
<div class="text">上传</div>
<div class="right">
<div style="height: 176px; margin-bottom: 20px">
<a-upload-dragger :data="data" :multiple="true" :name="name" :headers="headers" :accept="accept"
:action="uploadUrl" @change="handleChange" v-model:file-list="fileList">
<p class="ant-upload-drag-icon">
</p>
<p class="ant-upload-text">点击或将文件拖拽到此处上传</p>
<p class="ant-upload-hint">支持扩展名.xls/.xlsx</p>
<template #itemRender="{ file }">
<div class="loadstate">
<div class="loadborder">
<div class="content">
<div class="img"></div>
<div class="timebox">
<div class="timetop">
<div class="tit">{{ file.name }}</div>
<div class="stateloading">{{
{
done: "上传成功",
uploading: "正在上传",
error: "上传失败",
removed: "正在上传",
}[file.status]
}}
</div>
</div>
<a-progress :percent="file.percent" />
</div>
<div class="curloading">
<div style="color: #387df7; margin-left: 20px; cursor: pointer" @click="removeUpload">删除
</div>
</div>
</div>
<div style="margin-top: 20px;" v-if="file?.status === 'done'">
<div class="tacl">
导入{{ file?.response?.data?.total || 0 }}成功{{ file?.response?.data?.success || 0 }}失败<span
style="color: red;">{{ file?.response?.data?.fail || 0 }}</span>
</div>
</div>
</div>
</div>
</template>
</a-upload-dragger>
</div>
</div>
</div>
</div>
<div class="btnn">
<button class="btn2" @click="closeDrawer">取消</button>
<button class="btn2" @click="closeDrawer">确定</button>
</div>
</div>
</a-drawer>
</template>
<script setup>
import { defineEmits, defineProps, ref } from "vue";
import { useTimeout } from "@/utils/useCommon";
import { getCookieForName } from "@/api/method";
import { message } from "ant-design-vue";
const props = defineProps({
url: String,
name: {
type: String,
defalut: "file"
},
title: String,
accept: String,
fileType: Object,
data: Object,
templateUrl: {
type:String,
defalut:'/upload/教师导入模版.xlsx'
},
template: {
type: String,
defalut: '导入模版'
}
});
const emit = defineEmits({});
const visible = ref(false);
const fileList = ref([]);
const headers = { token: getCookieForName("token") };
const uploadUrl = ref(props.url);
const downLoad = () => {
console.log(props.templateUrl);
window.open(props.templateUrl)
}
const { start } = useTimeout(async ({ file }) => {
if (file.status == 'done' && file.response.code == 200) {
fileList.value = [...fileList.value];
emit("change", "done");
message.success("导入成功");
throw Error("查询任务结束");
} else {
console.log(fileList.value);
message.error(file.response.message);
throw Error("任务结束");
}
}, 1000);
const closeDrawer = () => {
visible.value = false;
fileList.value = [];
};
function openDrawer() {
visible.value = true;
}
function handleChange({ file }) {
console.log(file);
emit("change", "start");
file && file.response && start({ file: file });
}
const removeUpload = () => {
fileList.value = [];
};
</script>
<style scoped lang="scss">
.impotergroupleader>.ant-drawer-content-wrapper {
min-width: 800px !important;
width: 800px !important;
}
.impotergroupleader {
.drawerMain {
min-width: 600px;
margin: 0px 32px 0px 32px;
overflow-x: auto;
display: flex;
flex-direction: column;
.header {
height: 73px;
border-bottom: 1px solid #e8e8e8;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-shrink: 0;
.headerTitle {
font-size: 18px;
font-weight: 600;
color: #333333;
line-height: 25px;
}
}
.main {
.minatitl {
display: flex;
.up1 {
font-size: 16px;
font-weight: 400;
color: #333333;
}
.up2 {
font-size: 16px;
font-weight: 400;
color: #4ea6ff;
margin-left: 4px;
}
}
.upload {
margin-top: 32px;
display: flex;
.text {
font-size: 14px;
font-weight: 400;
color: #333333;
}
.right {
margin-left: 6px;
width: 500px;
.load {
width: 500px;
height: 176px;
background: #f5f9fd;
border-radius: 4px;
border: 1px dashed #caddfd;
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20px;
.cloud {
margin-top: 52px;
width: 28px;
height: 28px;
background-image: url(../../assets/images/basicinfo/cloud.png);
}
.tip {
font-size: 14px;
font-weight: 400;
color: #4ea6ff;
margin-top: 15px;
cursor: pointer;
}
.tipz {
font-size: 14px;
font-weight: 400;
color: #999999;
margin-top: 10px;
}
}
.loadstate {
width: 500px;
margin-bottom: 50px;
.loadborder {
width: 500px;
height: 173px;
border-radius: 4px;
border: 1px dashed #eaeaea;
margin-bottom: 30px;
display: flex;
flex-direction: column;
justify-content: center;
position: relative;
.content {
display: flex;
margin-left: 20px;
position: relative;
.defeat {
width: 262px;
padding: 10px 20px;
position: absolute;
left: 46px;
top: 42px;
font-size: 14px;
font-weight: 500;
height: 32px;
border-radius: 2px;
border: 1px solid #387df7;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.detext {
font-size: 14px;
font-weight: 400;
color: #387df7;
}
}
.img {
width: 30px;
height: 34px;
background-image: url(../../assets/images/basicinfo/exl.png);
}
.timebox {
margin-left: 15px;
margin-top: -5px;
.timetop {
display: flex;
width: 262px;
justify-content: space-between;
// margin-bottom: 8px;
.tit {
font-size: 14px;
font-weight: 400;
color: #333333;
}
.stateloading {
font-size: 14px;
font-weight: 400;
color: #4ea6ff;
}
.statedefeat {
font-size: 14px;
font-weight: 400;
color: #ff7474;
}
.statesucce {
font-size: 14px;
font-weight: 400;
color: #35ae69;
}
}
.prog {
width: 262px;
height: 5px;
background: #eaf1fe;
border-radius: 4px;
.inprogloading {
width: 55%;
height: 5px;
border-radius: 4px;
background: #4ea6ff;
}
//下载失败条
.inprogdefeat {
width: 55%;
height: 5px;
border-radius: 4px;
background: #ff7474;
}
//下载成功条
.inprogsucce {
width: 100%;
height: 5px;
border-radius: 4px;
background: #57c887;
}
}
}
.curloading {
margin-left: 15px;
margin-top: 15px;
display: flex;
.cur {
font-size: 14px;
font-weight: 400;
color: #333333;
}
.cancel {
font-size: 14px;
font-weight: 400;
color: #387df7;
}
}
}
.downloadErr {
width: 120px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 2px;
border: 1px solid #387df7;
font-size: 14px;
font-weight: 400;
color: #387df7;
line-height: 20px;
cursor: pointer;
margin-left: 66px;
margin-top: 16px;
position: absolute;
bottom: 28px;
}
.tacl {
padding-left: 20px;
}
}
}
}
}
}
.btnn {
height: 72px;
width: 100%;
position: absolute;
background-color: #fff;
bottom: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0px 1px 35px 0px rgba(118, 136, 166, 0.16);
.btn1 {
width: 100px;
height: 40px;
border: 1px solid #4ea6ff;
border-radius: 8px;
color: #4ea6ff;
background-color: #fff;
cursor: pointer;
}
.btn2 {
cursor: pointer;
width: 100px;
height: 40px;
background: #4ea6ff;
border-radius: 8px;
border: 0;
margin-left: 15px;
color: #fff;
}
}
}
}
</style>

View File

@@ -0,0 +1,180 @@
<template>
<a-modal :visible="visiable" width="813px" @cancel="cancel" title="发布通知" :footer="null" :maskClosable="false">
<div class="content">
<div class="cstm_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="b_input">
<a-input v-model:value="params.name" placeholder="请输入标题" show-count
style="width: 88%; height: 40px; border-radius: 8px" :maxlength="20" />
</div>
</div>
<div class="cstm_items">
<div class="signbox">
<span style="margin-right: 3px">活动时间</span>
</div>
<div class="select fitems">
<a-range-picker style="width: 88%; height: 40px; border-radius: 8px" format="YYYY-MM-DD"
valueFormat="YYYY-MM-DD" v-model:value="timeList" separator="至"
:placeholder="[' 开始时间', ' 结束时间']" @change="timeChange" />
</div>
</div>
<div class="cstm_items" style="align-items: start;">
<div class="signbox">
<div class="sign">
<img src="@/assets/images/coursewareManage/asterisk.png" alt="" />
</div>
<span style="margin-right: 3px">正文</span>
</div>
<div class="b_input">
<a-textarea v-model:value="params.content" :auto-size="{ minRows: 5 }"
style="width: 88%; border-radius: 8px" placeholder="请输入具体内容......" />
</div>
</div>
<div class="cstm_items" style="align-items: start;">
<div class="signbox">
</div>
<div class="b_input">
<div class="footer">
<a-button class="btn" @click="cancel()" type="primary">取消</a-button>
<a-button class="btn" @click="submit()" type="primary" style="margin:0 20px;">提交</a-button>
<!-- <a-checkbox v-model:checked="checked">我已阅读并遵守<span style="color: rgb(88, 138, 252);"
@click.stop="publishInfo">平台内容发布要求</span></a-checkbox> -->
</div>
</div>
</div>
</div>
</a-modal>
</template>
<script setup>
import { ref, watchEffect, defineProps, defineEmits, computed, watch, onMounted } from 'vue'
import { message } from "ant-design-vue";
import { noticeAddAndUpdate, noticeDataById } from '@/api/grateful'
import Agreement from '@/components/Grateful/Agreement'
const props = defineProps({
visiable: Boolean,
id: String
})
const emit = defineEmits(["update:visiable", 'change']);
const checked = ref(false)
const timeList = ref([])
const params = ref({
name: '',
content: '',
id: '',
startTime: '',
endTime: ''
})
const id = computed(() => props.id)
params.value.id = id.value
onMounted(() => {
checked.value = true
noticeDataById({ id: id.value }).then((res) => {
const { name, startTime, endTime, content } = res.data
params.value.name = name;
timeList.value = [startTime, endTime];
params.value.content = content;
})
})
const timeChange = (time, timeStr) => {
params.value.startTime = timeStr[0];
params.value.endTime = timeStr[1];
}
const submit = async () => {
if (!checked.value) {
message.error("请勾选平台内容发布要求");
return
}
if (!params.value.name) {
message.error("请输入标题内容");
return
}
if (!params.value.content) {
message.error("请输入正文具体内容");
return
}
const result = await noticeAddAndUpdate(params.value)
if (result.code == 200) {
if (props.id) {
message.success("编辑成功");
} else {
message.success("发布成功");
}
emit('change')
cancel()
}
}
const cancel = () => {
console.log('取消');
params.value.timeList = []
params.value.name = ''
params.value.content = ''
checked.value = false
emit('update:visiable', false)
}
</script>
<style scoped lang="scss">
.content {
padding: 24px;
.footer {
display: flex;
align-items: center;
.btn {
border-radius: 4px;
}
}
}
.cstm_items {
display: flex;
width: 80%;
margin: auto;
align-items: center;
margin-bottom: 23px;
.signbox {
display: flex;
justify-content: end;
width: 100px;
margin-right: 6px;
.sign {
margin-top: -5px;
margin-right: 4px;
}
}
.b_input {
flex: 1;
position: relative;
.upload_box {
display: flex;
cursor: pointer;
.upload_icon {
width: 16px;
height: 16px;
margin-right: 5px;
}
}
.inp_num {
position: absolute;
top: 9px;
right: 10px;
}
}
}
</style>

View File

@@ -0,0 +1,87 @@
<template>
<a-upload :file-list="files" :action="url" :show-upload-list="showUploadList" :multiple="multiple"
:before-upload="beforeUpload" :headers="headers" @change="handleChange" ref="imageRef" :data="{ ...params }">
<template v-for="(_, key, index) in $slots" :key="index" v-slot:[key]>
<slot :name="key"></slot>
</template>
</a-upload>
</template>
<script setup>
import { defineProps, defineEmits, defineExpose, ref, watch } from "vue";
import { message } from "ant-design-vue";
import { getCookieForName } from "@/api/method";
const props = defineProps({
value: {
type: Array,
default: () => []
},
multiple: {
type: Boolean,
default: false
},
showUploadList: {
type: Boolean,
default: false
},
fileType: {
type: Array,
default: () => []
},
url: {
type: String,
default: '/systemapi/api/m/xfile/base/file/upload'
},
params: {
type: Object,
default: {
folderId: process.env.VUE_APP_TOOL_FOLDERID
}
}
})
const emit = defineEmits({})
const files = ref([])
const imageRef = ref()
const headers = { token: getCookieForName("token") };
watch(props, () => {
props.value.length !== files.value.length && (files.value = props.value)
})
function handleChange({ file, fileList }) {
file.response && file.response.code === 200 && (file.url = file.response.data)
files.value = fileList
emit('update:value', fileList)
}
function beforeUpload(file) {
if (!props.fileType.includes(file.name.split(".").slice(-1).join(''))) {
message.error("目前只支持zip格式");
return false;
}
}
function remove(i) {
files.value.splice(i, 1)
emit('update:value', files.value)
}
function reUpload(i) {
if (files.value[i].status === 'ready' || files.value[i].status === 'uploading') {
imageRef.value.abort(files.value[i].raw)
files.value[i].status = 'abort';
} else if (files.value[i].status === 'fail' || files.value[i].status === 'abort') {
imageRef.value.handleStart(files.value[i].raw)
imageRef.value.submit()
}
}
function abort(i) {
imageRef.value.abort(files.value[i].raw)
}
defineExpose({ reUpload, remove, abort })
</script>

View File

@@ -0,0 +1,227 @@
<template>
<a-upload-dragger :data="{ ...params }" :multiple="false" :accept="accept" :action="uploadUrl" :maxCount="maxCount"
@change="handleUploadChange" v-model:file-list="fileList" style="width:50%;">
<p class="ant-upload-drag-icon">
<UploadOutlined />
</p>
<p class="ant-upload-text">将文件拖到此处或点击上传</p>
<template #itemRender="{ file }">
<div class="loadstate">
<div class="loadborder">
<div class="content">
<div class="timebox">
<div class="timetop">
<div class="tit">{{ file.name }}</div>
<div class="stateloading">{{
{
done: "上传成功",
uploading: "正在上传",
error: "上传失败",
removed: "正在上传",
}[file.status]
}}
</div>
</div>
<a-progress :percent="file.percent" />
</div>
<!-- <div class="curloading">
<div class="cur">100%</div>
<div class="cancel" style="margin-left: 20px; cursor: pointer" @click="removeUpload">
删除
</div>
</div> -->
</div>
</div>
</div>
</template>
</a-upload-dragger>
</template>
<script setup>
import { UploadOutlined } from '@ant-design/icons-vue'
import { defineProps, ref, defineExpose } from "vue";
import { message } from "ant-design-vue";
const props = defineProps({
uploadUrl: {
type: String,
default: ''
},
params: {
type: Object,
default: {}
},
maxCount: {
type: Number,
default: 1
},
accept: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:value', 'change'])
const fileList = ref([]);
const handleUploadChange = ({ file, fileList }) => {
console.log(file, fileList, 'file');
var FileExt = file.name.replace(/.+\./, "");
if (props.accept && props.accept.split(',').indexOf('.' + FileExt.toLowerCase()) === -1) {
fileList.value = [];
return message.error("请上传正确的文件格式");
}
emit('update:value', fileList)
emit('change', fileList)
}
const removeUpload = () => {
fileList.value = [];
}
defineExpose({
fileList,
removeUpload
})
</script>
<style scoped lang="scss">
.loadstate {
width: 500px;
margin-bottom: 80px;
.loadborder {
width: 500px;
height: 70px;
border-radius: 4px;
border: 1px dashed #eaeaea;
margin-top: 10px;
margin-bottom: 10px;
display: flex;
flex-direction: column;
justify-content: center;
position: relative;
.content {
display: flex;
margin-left: 20px;
position: relative;
.defeat {
width: 262px;
padding: 10px 20px;
position: absolute;
left: 46px;
top: 42px;
font-size: 14px;
font-weight: 500;
height: 32px;
border-radius: 2px;
border: 1px solid #387df7;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.detext {
font-size: 14px;
font-weight: 400;
color: #387df7;
}
}
.img {
width: 30px;
height: 34px;
// background-image: url(@/assets/images/basicinfo/exl.png);
}
.timebox {
margin-left: 15px;
margin-top: -5px;
.timetop {
display: flex;
width: 262px;
justify-content: space-between;
.tit {
font-size: 14px;
font-weight: 400;
color: #333333;
width: 200px;
height: 21px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.stateloading {
font-size: 14px;
font-weight: 400;
color: #4ea6ff;
}
.statedefeat {
font-size: 14px;
font-weight: 400;
color: #ff7474;
}
.statesucce {
font-size: 14px;
font-weight: 400;
color: #35ae69;
}
}
.prog {
width: 262px;
height: 5px;
background: #eaf1fe;
border-radius: 4px;
.inprogloading {
width: 55%;
height: 5px;
border-radius: 4px;
background: #4ea6ff;
}
//下载失败条
.inprogdefeat {
width: 55%;
height: 5px;
border-radius: 4px;
background: #ff7474;
}
//下载成功条
.inprogsucce {
width: 100%;
height: 5px;
border-radius: 4px;
background: #57c887;
}
}
}
.curloading {
margin-left: 15px;
margin-top: 15px;
display: flex;
.cur {
font-size: 14px;
font-weight: 400;
color: #333333;
}
.cancel {
font-size: 14px;
font-weight: 400;
color: #387df7;
}
}
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,50 @@
<template>
<a-pagination
:showSizeChanger="showSizeChanger"
:showQuickJumper="showQuickJumper"
:hideOnSinglePage="hideOnSinglePage"
:pageSizeOptions="pageSizeOptions"
:pageSize="pageSize"
:current="page"
:total="total"
@change="changePagination"
/>
</template>
<script setup>
import { defineProps, defineEmits } from "vue";
const props = defineProps({
total: {
type: Number,
default: 0,
},
pageSize: {
type: Number,
default: 10,
},
page: {
type: Number,
default: 1,
},
showSizeChanger: {
type: Boolean,
default: true,
},
showQuickJumper: {
type: Boolean,
default: true,
},
hideOnSinglePage: {
type: Boolean,
default: false,
},
pageSizeOptions:{
type:Array,
default:["10","20","50","100"]
}
});
const emit = defineEmits(["changePagination"]);
const changePagination = (page, pageSize) =>{
emit("changePagination", page, pageSize);
}
</script>
<style scoped></style>

View File

@@ -1,140 +1,165 @@
<template>
<a-table
:customRow="customRow"
class="ant-table-striped"
:row-class-name="(_, index) => (index % 2 === 1 ? 'table-striped' : null)"
row-key="id"
:columns="columns"
:data-source="data"
:loading="loading"
:pagination="pagination"
:row-selection="rowSelection"
:customRow="customRow"
class="ant-table-striped"
:row-class-name="(_, index) => (index % 2 === 1 ? 'table-striped' : null)"
row-key="id"
:columns="columns"
:data-source="data"
:loading="loading"
:pagination="pagination"
:row-selection="rowSelection"
:scroll="scroll"
/>
</template>
<script setup>
import {defineProps, defineExpose, ref, computed, onMounted, defineEmits, nextTick} from "vue";
import {usePage, useRequest, useRowsPageNoInit} from "@/api/request";
import {useResetRef} from "@/utils/useCommon";
import {defineProps, defineExpose, ref, computed, onMounted, defineEmits, nextTick} from "vue";
import {usePage, useRequest, useRowsPageNoInit} from "@/api/request";
import {useResetRef} from "@/utils/useCommon";
const props = defineProps({
type: {
type: String,
default: ""
},
columns: {
type: Array,
default: () => []
},
url: {
type: String,
default: ""
},
pageKey: {
type: String,
default: "pageNo"
},
params: {
type: Object,
default: () => ({})
},
init: {
type: Boolean,
default: true
},
request: {
type: Function,
default: usePage
}
});
const emit = defineEmits(["update:params", "update:selectedRowKeys", "update:selectedRows"]);
const rowSelectKeys = ref([]);
const selectsData = ref([]);
const params = useResetRef({ [props.pageKey]: 1, pageSize: 10 });
const postParam = computed(() => ({ ...params.value, ...props.params }));
const { data, loading, total, fetch: onFetch } = props.request(props.url, postParam);
const rowSelection = computed(() => (props.type ? {
type: props.type,
columnWidth: 20,
selectedRowKeys: rowSelectKeys.value,
onChange: onSelectChange,
preserveSelectedRowKeys: true,
} : null));
const customRow = (record) => ({
onClick: () => {
if (props.type === "checkbox") {
if (rowSelectKeys.value.some(t => t === record.id)) {
rowSelectKeys.value = rowSelectKeys.value.filter(t => t !== record.id);
selectsData.value = selectsData.value.filter(t => t.id !== record.id);
} else {
rowSelectKeys.value.push(record.id);
selectsData.value.push(record);
}
} else {
rowSelectKeys.value = [record.id];
selectsData.value = [record];
const props = defineProps({
type: {
type: String,
default: ""
},
columns: {
type: Array,
default: () => []
},
url: {
type: String,
default: ""
},
pageKey: {
type: String,
default: "pageNo"
},
params: {
type: Object,
default: () => ({})
},
init: {
type: Boolean,
default: true
},
request: {
type: Function,
default: usePage
},
showQuickJumper:{
type: Boolean,
default:false
},
scroll:{
type:Object,
default:{}
},
pageSize:{
type:Number,
default:10
},
getCheckboxProps:{
type: Function,
default: () => {}
},
showSizeChanger:{
type:Boolean,
default:false
}
emit("update:selectedRowKeys", [...rowSelectKeys.value]);
emit("update:selectedRows", [...selectsData.value]);
});
const emit = defineEmits(["update:params", "update:selectedRowKeys", "update:selectedRows"]);
const rowSelectKeys = ref([]);
const selectsData = ref([]);
const params = useResetRef({ [props.pageKey]: 1, pageSize: props.pageSize });
const postParam = computed(() => ({ ...params.value, ...props.params }));
const { data, loading, total, fetch: onFetch } = props.request(props.url, postParam);
const rowSelection = computed(() => (props.type ? {
type: props.type,
columnWidth: 20,
selectedRowKeys: rowSelectKeys.value,
onChange: onSelectChange,
preserveSelectedRowKeys: true,
getCheckboxProps: record => props.getCheckboxProps(record)
} : null));
const customRow = (record) => ({
onClick: () => {
if (props.type === "checkbox") {
if(props.getCheckboxProps(record)?.disabled) return
if (rowSelectKeys.value.some(t => t === record.id)) {
rowSelectKeys.value = rowSelectKeys.value.filter(t => t !== record.id);
selectsData.value = selectsData.value.filter(t => t.id !== record.id);
} else {
rowSelectKeys.value.push(record.id);
selectsData.value.push(record);
}
} else {
rowSelectKeys.value = [record.id];
selectsData.value = [record];
}
emit("update:selectedRowKeys", [...rowSelectKeys.value]);
emit("update:selectedRows", [...selectsData.value]);
}
});
onMounted(() => props.init && nextTick(onFetch));
function onSelectChange(e, l) {
rowSelectKeys.value = e;
selectsData.value = l;
emit("update:selectedRowKeys", e);
emit("update:selectedRows", l);
}
});
onMounted(() => props.init && nextTick(onFetch));
const pagination = computed(() => ({
total: total.value,
showSizeChanger: props.showSizeChanger,
showQuickJumper:props.showQuickJumper,
current: params.value[props.pageKey],
pageSize: params.value.pageSize,
onChange: changePagination,
}));
const changePagination = (e,pageSize) => {
params.value[props.pageKey] = e;
params.value.pageSize = pageSize
nextTick(onFetch);
};
function onSelectChange(e, l) {
rowSelectKeys.value = e;
selectsData.value = l;
emit("update:selectedRowKeys", e);
emit("update:selectedRows", l);
}
function reset(v) {
params.reset();
v && emit("update:params", { ...v });
nextTick(onFetch);
}
const pagination = computed(() => ({
total: total.value,
showSizeChanger: false,
current: params.value[props.pageKey],
pageSize: params.value.pageSize,
onChange: changePagination,
}));
const changePagination = (e) => {
params.value[props.pageKey] = e;
nextTick(onFetch);
};
function resetSelected() {
rowSelectKeys.value = [];
selectsData.value = [];
emit("update:selectedRowKeys", []);
emit("update:selectedRows", []);
}
function reset(v) {
params.reset();
v && emit("update:params", { ...v });
nextTick(onFetch);
}
function clear(v) {
rowSelectKeys.value = [];
selectsData.value = [];
params.reset();
v && emit("update:params", { ...v });
emit("update:selectedRowKeys", []);
emit("update:selectedRows", []);
}
function resetSelected() {
rowSelectKeys.value = [];
selectsData.value = [];
emit("update:selectedRowKeys", []);
emit("update:selectedRows", []);
}
const toLoading = () => loading.value = true;
function clear(v) {
rowSelectKeys.value = [];
selectsData.value = [];
params.reset();
v && emit("update:params", { ...v });
emit("update:selectedRowKeys", []);
emit("update:selectedRows", []);
}
function remove(i) {
rowSelectKeys.value.splice(i, 1);
selectsData.value.splice(i, 1);
emit("update:selectedRowKeys", rowSelectKeys.value);
emit("update:selectedRows", selectsData.value);
}
const toLoading = () => loading.value = true;
const fetch = () => nextTick(onFetch);
function remove(i) {
rowSelectKeys.value.splice(i, 1);
selectsData.value.splice(i, 1);
emit("update:selectedRowKeys", rowSelectKeys.value);
emit("update:selectedRows", selectsData.value);
}
const fetch = () => nextTick(onFetch);
defineExpose({ fetch, reset, resetSelected, clear, toLoading, remove });
defineExpose({ fetch, reset, resetSelected, clear, toLoading, remove , params });
</script>

14
src/components/index.js Normal file
View File

@@ -0,0 +1,14 @@
import Pagination from './Pagination.vue'
import Table from './common/BaseTable.vue'
const globalComponents = {
Pagination,
Table
}
export default {
install(app) {
Object.keys(globalComponents).forEach(key => {
app.component(key, globalComponents[key])
})
}
}

46
src/hooks/useRequest.js Normal file
View File

@@ -0,0 +1,46 @@
import { isRef, reactive, ref, toRefs, unref, watch, watchEffect } from "vue";
import http from '@/api/configPublic'
const useTotalPage = (_url, params, config = {}) => {
const s = _url.split(" ");
const url = s[0];
let methods = 'post'
if (s[1]) methods = 'get'
const state = reactive({
data: [],
total: 1,
current: 1,
pages: 1,
pageNo: 1,
pageSize: 10,
loading: false
});
if (isRef(_url)) {
watchEffect(fetch);
}
function reset() {
state.data = [];
state.loading = false;
}
function fetch() {
state.loading = true;
return http[methods](unref(url), !s[1] ? unref(params) : { params: unref(params) }, { ...config }).then(r => {
state.data = r.data?.records || 0;
state.total = r.data?.total || 0;
state.loading = false;
})
}
return {
...toRefs(state),
fetch,
reset,
};
}
export {
useTotalPage
}

View File

@@ -20,10 +20,13 @@ import {request} from "@/api/request";
import {USER_INFO, USER_PERMISSION, VALIDATE_TOKEN} from "@/api/apis";
import * as api1 from "@/api/index1";
import {getCookieForName} from "@/api/method";
import components from './components'
// import axios from 'axios'
// axios.defaults.withCredentials = true;
// import zhCN from 'ant-design-vue/es/locale/zh_CN';
const app = createApp(App)
//全局注册
app.use(components)
// 清理控制台warn信息
app.config.warnHandler = () => null;
// app.use(ElementPlus, {

View File

@@ -0,0 +1,340 @@
<!-- 感恩教师通知 -->
<template>
<div class="researchmanage">
<!-- 搜索框及按钮 -->
<div class="filter">
<div class="filterItems">
<div class="select">
<div class="select addTimeBox">
<div class="addTime">创建时间</div>
<a-date-picker style="width: 300px" v-model:value="params.createTime" valueFormat="YYYY-MM-DD" />
</div>
</div>
<div style="display: flex; margin-bottom: 20px">
<div class="btn btn1" @click="searchInfo">
<div class="search"></div>
<div class="btnText">搜索</div>
</div>
<div class="btn btn2" style="width: 105px" @click="handleRest">
<div class="search"></div>
<div class="btnText">重置</div>
</div>
</div>
</div>
<CarouselDialog title="新建轮播图" @change="handleRest">
<div class="btns">
<div class="btn btn3">
<div class="search"></div>
<div class="btnText">新建</div>
</div>
</div>
</CarouselDialog>
</div>
<!-- 搜索框及按钮 -->
<!-- 表格 -->
<div class="table">
<Table ref="carouselTableRef" :columns="column" :url="carouselList" page-key="pageNo" v-model:params="params"
:request="useTotalPage" :showQuickJumper="true" :scroll="{ x: 1000 }" :showSizeChanger="true">
</Table>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, reactive, computed, watchEffect } from "vue";
import dialog from "@/utils/dialog";
import { carouselList, carouseDelete } from "@/api/grateful";
import { useTotalPage } from "@/hooks/useRequest";
import CarouselDialog from "@/components/Grateful/CarouselDialog.vue";
import { message } from "ant-design-vue";
const column = ref([
{
title: "序号",
key: "index",
width: 10,
align: "center",
ellipsis: true,
customRender: ({ index }) => {
return index + 1;
},
},
{
title: "图片",
dataIndex: "picPath",
key: "picPath",
width: 35,
align: "center",
className: "h",
customRender: ({ text }) => {
return (
<div style="height:100px;">
<img style="width:80%;height:100%;object-fit:contain;" src={`https:${process.env.VUE_APP_BOE_API_URL}/upload${text}`} />
</div>
);
},
},
{
title: "展示页面",
dataIndex: "displayPage",
key: "displayPage",
width: 15,
align: "center",
className: "h",
customRender: ({ text }) => (<span>{text ? text : '-'}</span>)
},
{
title: "创建时间",
dataIndex: "createTime",
key: "createTime",
width: 20,
align: "center",
className: "h",
},
{
title: "操作",
width: 10,
className: "h",
dataIndex: "operation",
key: "id",
fixed: "right",
align: "center",
customRender: ({ record }) => {
return (
<div class="opa">
<CarouselDialog id={record.id} handleRest={handleRest} title="编辑轮播图">
<a style="margin-right:10px;">
编辑
</a>
</CarouselDialog>
<a onClick={() => handleOper(record, "del")}>删除</a>
</div>
);
},
},
]);
const params = ref({
createTime: ''
});
const carouselTableRef = ref();
const searchInfo = () => {
console.log("搜索");
carouselTableRef.value.params.pageNo = 1;
carouselTableRef.value.fetch();
};
const handleRest = () => {
console.log("重置");
params.value.createTime = '';
carouselTableRef.value.reset();
};
const handle = ({ id }) => ({
del: async () => {
console.log("删除接口", id);
carouseDelete({ id }).then((result) => {
message.success('删除成功')
carouselTableRef.value.fetch();
}).catch((err) => {
message.error('删除失败')
});
},
});
const handleMsg = {
del: "你确定要删除这条意见吗?",
};
function handleOper(record, type, status = "") {
dialog({
content: handleMsg[status ? type + status : type],
ok: handle(record)[type],
});
}
</script>
<style lang="scss" scoped>
.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;
}
}
.researchmanage {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
position: relative;
.filter {
margin-left: 38px;
margin-right: 38px;
margin-top: 30px;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
.filterItems {
display: flex;
flex-wrap: wrap;
.select {
margin-right: 20px;
margin-bottom: 20px;
}
.btn {
padding: 0 26px 0 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: 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;
}
}
.btn1 {
.search {
width: 15px;
height: 17px;
background-image: url("@/assets/images/courseManage/search0.png");
}
}
.btn2 {
.search {
width: 16px;
height: 18px;
background-image: url("@/assets/images/courseManage/reset0.png");
}
}
.btn1:active {
background: #0982ff;
}
.btn2:active {
background: rgba(64, 158, 255, 0.2);
}
}
.btns {
display: flex;
.btn {
padding: 0 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;
}
}
.btn3 {
margin-right: 0;
.search {
width: 17px;
height: 18px;
background-image: url("@/assets/images/courseManage/add0.png");
}
}
.btn3:active {
background: #0982ff;
}
}
}
.table {
margin: 20px 38px 30px;
display: flex;
flex: 1;
flex-direction: column;
th.ant-table-cell {
background-color: #eff4fc !important;
text-align: center;
color: #999ba3;
}
td.ant-table-cell {
text-align: center;
}
}
}
</style>

View File

@@ -0,0 +1,371 @@
<!-- 感恩教师通知 -->
<template>
<div class="researchmanage">
<!-- 搜索框及按钮 -->
<div class="filter">
<div class="filterItems">
<div class="select">
<a-input v-model:value="searchData.name" style="width: 270px; height: 40px; border-radius: 8px"
placeholder="请输入标题" />
</div>
<div class="select">
<a-select v-model:value="searchData.isTop" style="width: 270px" placeholder="全部" :options="state.pinnedList"
@change="selectPinned" allowClear></a-select>
</div>
<div style="display: flex; margin-bottom: 20px">
<div class="btn btn1" @click="getList(false)">
<div class="search"></div>
<div class="btnText">搜索</div>
</div>
<div class="btn btn2" style="width: 105px" @click="handleRest">
<div class="search"></div>
<div class="btnText">重置</div>
</div>
</div>
</div>
<div class="btns" @click="publish('')">
<div class="btn btn3">
<div class="search"></div>
<div class="btnText">发布</div>
</div>
</div>
</div>
<!-- 搜索框及按钮 -->
<!-- 表格 -->
<div class="table">
<a-table style="border: 1px solid #f2f6fe" :columns="column" :data-source="state.data" :loading="!loading"
:pagination="pagination">
<template #bodyCell="{ column, record }">
<a-space v-if="column.dataIndex === 'operation'" style="padding-right: 10px">
<a-button type="link" @click="publish(record.id)">编辑 </a-button>
<a-button v-if="record.isTop === '1'" type="link" @click="handleOper(record, 'pinned', '0')">
置顶
</a-button>
<a-button v-else type="link" @click="handleOper(record, 'pinned', '1')">
取消置顶
</a-button>
<a-button type="link" @click="handleOper(record, 'del')">删除
</a-button>
</a-space>
</template>
</a-table>
</div>
</div>
<PublishNotice v-if="visiable" v-model:visiable="visiable" :id="state.id" @change="handleRest"></PublishNotice>
</template>
<script setup lang="jsx">
import { ref, onMounted, reactive, computed } from "vue";
import { message, Form } from "ant-design-vue";
import dialog from "@/utils/dialog";
import PublishNotice from "@/components/Grateful/PublishNotice.vue";
import { noticeList, noticeAddAndUpdate, noticeDelete } from '@/api/grateful'
const column = [
{
title: "标题",
dataIndex: "name",
key: "name",
width: "15%",
align: "center",
ellipsis: true,
className: "h",
customRender: ({ text }) => {
return text ? text : "-";
},
},
{
title: "创建人",
dataIndex: "createName",
key: "createName",
width: "5%",
align: "center",
className: "h",
customRender: ({ text }) => {
return text ? text : "-";
},
},
{
title: "开始时间",
dataIndex: "startTime",
key: "startTime",
width: "10%",
align: "center",
className: "h",
customRender: ({ text }) => {
return text ? text : "-";
},
},
{
title: "结束时间",
dataIndex: "endTime",
key: "endTime",
width: "10%",
align: "center",
className: "h",
customRender: ({ text }) => {
return text ? text : "-";
},
},
{
title: "操作",
width: "15%",
className: "h",
dataIndex: "operation",
key: "id",
fixed: "right",
align: "center",
// slots: { customRender: "operation" },
},
];
const visiable = ref(false)
const publish = (id) => {
state.id = id
visiable.value = !visiable.value
}
const loading = ref(false);
const state = reactive({
data: [],
total: 0,
id: '',
pinnedList: [
{
id: 1,
value: '0',
label: "已置顶",
},
{
id: 2,
value: '1',
label: "未置顶",
},
],
});
const pagination = computed(() => ({
total: state.total,
showQuickJumper: true,
showSizeChanger: true,
current: searchData.value.pageNo,
pageSize: searchData.value.pageSize,
onChange: changePagination,
}));
// 查询数据
const searchData = ref({
pageNo: 1,
pageSize: 10,
name: "",
isTop: undefined
});
const { resetFields } = Form.useForm(searchData, {});
const changePagination = (e, pageSize) => {
searchData.value.pageNo = e;
searchData.value.pageSize = pageSize;
getList()
};
const selectPinned = (value) => {
searchData.isTop = value;
};
const handleRest = () => {
resetFields();
getList();
};
const getList = async (type = true) => {
loading.value = false;
if (!type) searchData.value.pageNo = 1;
const result = await noticeList(searchData.value)
if (result.code === 200) {
loading.value = true;
state.data = result?.data?.records ?? [];
state.total = result?.data?.total ?? 0;
} else {
loading.value = false;
}
};
// 获取列表数据
getList();
const handle = (record, status) => ({
pinned: async () => {
try {
console.log(record.id, status);
noticeAddAndUpdate({
id: record.id,
isTop: status
}).then(() => {
getList();
})
} catch (error) {
message.error("置顶失败!");
}
},
del: async () => {
noticeDelete({ id: record.id }).then(() => {
message.success("删除成功!");
getList();
}).catch(() => {
message.error("删除失败!");
})
},
});
const handleMsg = {
del: "你确定要删除这条通知吗?",
pinned0: "您确定要置顶此通知吗?",
pinned1: "您确定要取消置顶此通知吗?",
};
function handleOper(record, type, status = "") {
console.log(record);
dialog({
content: handleMsg[status ? type + status : type],
ok: handle(record, status)[type],
});
}
defineExpose({
handleRest
})
</script>
<style lang="scss" scoped>
.researchmanage {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
position: relative;
.filter {
margin-left: 38px;
margin-right: 38px;
margin-top: 30px;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
.filterItems {
display: flex;
flex-wrap: wrap;
.select {
margin-right: 20px;
margin-bottom: 20px;
}
.btn {
padding: 0 26px 0 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;
}
}
.btn1 {
.search {
width: 15px;
height: 17px;
background-image: url("@/assets/images/courseManage/search0.png");
}
}
.btn2 {
.search {
width: 16px;
height: 18px;
background-image: url("@/assets/images/courseManage/reset0.png");
}
}
.btn1:active {
background: #0982ff;
}
.btn2:active {
background: rgba(64, 158, 255, 0.2);
}
}
.btns {
display: flex;
.btn {
padding: 0 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;
}
}
.btn3 {
margin-right: 0;
.search {
width: 17px;
height: 18px;
background-image: url("@/assets/images/courseManage/add0.png");
}
}
.btn3:active {
background: #0982ff;
}
}
}
.table {
margin: 20px 38px 30px;
display: flex;
flex: 1;
flex-direction: column;
th.ant-table-cell {
background-color: #eff4fc !important;
text-align: center;
color: #999ba3;
}
td.ant-table-cell {
text-align: center;
}
}
}
</style>

View File

@@ -0,0 +1,361 @@
<!-- 感恩教师通知 -->
<template>
<div class="researchmanage">
<!-- 搜索框及按钮 -->
<div class="filter">
<div class="filterItems">
<div class="select">
<a-input v-model:value="params.createName" style="width: 270px; height: 40px; border-radius: 8px"
placeholder="请输入创建人" />
</div>
<div class="select">
<a-input v-model:value="params.name" style="width: 270px; height: 40px; border-radius: 8px"
placeholder="请输入名称" />
</div>
<div style="display: flex; margin-bottom: 20px">
<div class="btn btn1" @click="searchInfo">
<div class="search"></div>
<div class="btnText">搜索</div>
</div>
<div class="btn btn2" style="width: 105px" @click="handleRest">
<div class="search"></div>
<div class="btnText">重置</div>
</div>
</div>
</div>
<EmpowerDialog ref="EmpowerDialogRef" title="选择课程" @change="handleRest">
<div class="btns">
<div class="btn btn3">
<div class="search"></div>
<div class="btnText">添加赋能</div>
</div>
</div>
</EmpowerDialog>
</div>
<!-- 搜索框及按钮 -->
<!-- 表格 -->
<div class="table">
<Table ref="carouselTableRef" :columns="column" :url="courselList" page-key="pageNo" v-model:params="params"
:request="useTotalPage" :showQuickJumper="true" :scroll="{ x: 1000 }" :showSizeChanger="true">
</Table>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, reactive, computed } from "vue";
import dialog from "@/utils/dialog";
import { courselList, courseDelete } from "@/api/grateful";
import { useTotalPage } from "@/hooks/useRequest";
import EmpowerDialog from '@/components/Grateful/EmpowerDialog'
import { Form, message } from "ant-design-vue";
const column = ref([
{
title: "序号",
key: "index",
width: 10,
align: "center",
ellipsis: true,
customRender: ({ index }) => {
return index + 1;
},
},
{
title: "课程名称",
dataIndex: "name",
key: "name",
width: 20,
align: "center",
className: "h",
ellipsis: true,
},
{
title: "课程形式",
dataIndex: "type",
key: "type",
width: 15,
align: "center",
className: "h",
customRender: ({ text }) => {
return (
<div class="racona">
<span>{text == 10 || text == 20 ? '录播课' : text == 30 ? '面授课' : '学习项目'}</span>
</div>
)
}
},
{
title: "学习人数",
dataIndex: "studies",
key: "studies",
width: 15,
align: "center",
className: "h",
customRender: ({ text }) => (<span> {text ? text : 0}</span>)
},
{
title: "评分",
dataIndex: "score",
key: "score",
width: 10,
align: "center",
className: "h",
customRender: ({ text }) => (<span> {text ? text : 0}</span>)
},
{
title: "授课讲师",
dataIndex: "teacher",
key: "teacher",
width: 15,
align: "center",
className: "h",
},
{
title: "设置赋能时间",
dataIndex: "createTime",
key: "createTime",
width: 20,
align: "center",
className: "h",
customRender: ({ text }) => (<span> {text ? text.replace('T', ' ') : 0}</span>)
},
{
title: "发布时间",
dataIndex: "publishTime",
key: "publishTime",
width: 20,
align: "center",
className: "h",
customRender: ({ text }) => (<span> {text ? text : '-'}</span>)
},
{
title: "创建人",
dataIndex: "createName",
key: "createName",
width: 15,
align: "center",
className: "h",
},
{
title: "操作",
width: 20,
className: "h",
dataIndex: "operation",
key: "id",
fixed: "right",
align: "center",
customRender: ({ record }) => {
return (
<div class="opa">
<a onClick={() => handleOper(record, 'empower')}>取消赋能</a>
</div>
);
},
},
]);
const params = ref({
createName: '',
name: '',
});
const carouselTableRef = ref();
const searchInfo = () => {
console.log("搜索");
console.log(carouselTableRef.value.params);
carouselTableRef.value.params.pageNo = 1
carouselTableRef.value.fetch();
};
const handleRest = () => {
console.log("重置");
params.value.createName = ''
params.value.name = ''
carouselTableRef.value.reset();
};
const EmpowerDialogRef = ref()
const handle = ({ id }) => ({
empower: async () => {
console.log("删除接口", id);
courseDelete({ id }).then(() => {
console.log(EmpowerDialogRef.value);
EmpowerDialogRef.value.getIdList()
message.success('取消成功')
carouselTableRef.value.fetch();
}).catch(() => {
message.error(); ('取消失败')
})
},
});
const handleMsg = {
empower: "你确定取消该课程为赋能课程?",
};
function handleOper(record, type, status = "") {
dialog({
content: handleMsg[status ? type + status : type],
ok: handle(record)[type],
});
}
</script>
<style lang="scss" scoped>
.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;
}
}
.researchmanage {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
position: relative;
.filter {
margin-left: 38px;
margin-right: 38px;
margin-top: 30px;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
.filterItems {
display: flex;
flex-wrap: wrap;
.select {
margin-right: 20px;
margin-bottom: 20px;
}
.btn {
padding: 0 26px 0 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;
}
}
.btn1 {
.search {
width: 15px;
height: 17px;
background-image: url("@/assets/images/courseManage/search0.png");
}
}
.btn2 {
.search {
width: 16px;
height: 18px;
background-image: url("@/assets/images/courseManage/reset0.png");
}
}
.btn1:active {
background: #0982ff;
}
.btn2:active {
background: rgba(64, 158, 255, 0.2);
}
}
.btns {
display: flex;
.btn {
padding: 0 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;
}
}
.btn3 {
margin-right: 0;
.search {
width: 17px;
height: 18px;
background-image: url("@/assets/images/courseManage/add0.png");
}
}
.btn3:active {
background: #0982ff;
}
}
}
.table {
margin: 20px 38px 30px;
display: flex;
flex: 1;
flex-direction: column;
th.ant-table-cell {
background-color: #eff4fc !important;
text-align: center;
color: #999ba3;
}
td.ant-table-cell {
text-align: center;
}
}
}
</style>

View File

@@ -0,0 +1,236 @@
<!-- 感恩教师通知 -->
<template>
<div class="researchmanage">
<!-- 搜索框及按钮 -->
<div class="filter">
<div class="filterItems">
<div class="select">
<div class="select addTimeBox">
<a-date-picker style="width: 300px" v-model:value="params.createTime" valueFormat="YYYY-MM-DD" />
</div>
</div>
<div style="display: flex; margin-bottom: 20px">
<div class="btn btn1" @click="searchInfo()">
<div class="search"></div>
<div class="btnText">搜索</div>
</div>
<div class="btn btn2" style="width: 105px" @click="handleRest">
<div class="search"></div>
<div class="btnText">重置</div>
</div>
</div>
</div>
</div>
<!-- 搜索框及按钮 -->
<!-- 表格 -->
<div class="table">
<Table ref="opinionTableRef" :columns="column" :url="opinionList" page-key="pageNo" v-model:params="params"
:request="useTotalPage" :showQuickJumper="true" :scroll="{ x: 1000 }" :showSizeChanger="true">
</Table>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import dialog from "@/utils/dialog";
import { opinionList, opinionDelete } from "@/api/grateful";
import { useTotalPage } from "@/hooks/useRequest";
import { message } from "ant-design-vue";
const column = ref([
{
title: "序号",
key: "index",
width: 10,
align: "center",
ellipsis: true,
customRender: ({ index }) => {
return index + 1;
},
},
{
title: "意见内容",
dataIndex: "content",
key: "content",
width: 40,
align: "center",
className: "h",
ellipsis: true,
customRender: ({ text }) => {
return text ? text : "-";
},
},
{
title: "用户",
dataIndex: "createName",
key: "createName",
width: 10,
align: "center",
className: "h",
},
{
title: "提交时间",
dataIndex: "createTime",
key: "createTime",
width: 20,
align: "center",
className: "h",
customRender: ({ text }) => {
return (
<span>{text.replace('T', ' ')}</span>
);
},
},
{
title: "操作",
width: 10,
className: "h",
dataIndex: "operation",
key: "id",
fixed: "right",
align: "center",
customRender: ({ record }) => {
return (
<div class="opa">
<a onClick={() => handleOper(record, "del")}>删除</a>
</div>
);
},
},
]);
const params = ref({
createTime: ''
});
const opinionTableRef = ref();
const searchInfo = () => {
console.log("搜索");
opinionTableRef.value.params.pageNo = 1
opinionTableRef.value.fetch();
};
const handleRest = () => {
console.log("重置");
params.value.createTime = '';
opinionTableRef.value.reset();
};
const handle = ({ id }) => ({
del: async () => {
opinionDelete({ id }).then(() => {
message.success('删除成功')
opinionTableRef.value.fetch();
}).catch(() => {
message.error('删除失败')
})
},
});
const handleMsg = {
del: "你确定要删除这条意见吗?",
};
function handleOper(record, type, status = "") {
dialog({
content: handleMsg[status ? type + status : type],
ok: handle(record)[type],
});
}
</script>
<style lang="scss" scoped>
.researchmanage {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
position: relative;
.filter {
margin-left: 38px;
margin-right: 38px;
margin-top: 30px;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
.filterItems {
display: flex;
flex-wrap: wrap;
.select {
margin-right: 20px;
margin-bottom: 20px;
}
.btn {
padding: 0 26px 0 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;
}
}
.btn1 {
.search {
width: 15px;
height: 17px;
background-image: url("@/assets/images/courseManage/search0.png");
}
}
.btn2 {
.search {
width: 16px;
height: 18px;
background-image: url("@/assets/images/courseManage/reset0.png");
}
}
.btn1:active {
background: hsl(210, 100%, 52%);
}
.btn2:active {
background: rgba(64, 158, 255, 0.2);
}
}
}
.table {
margin: 20px 38px 30px;
display: flex;
flex: 1;
flex-direction: column;
th.ant-table-cell {
background-color: #eff4fc !important;
text-align: center;
color: #999ba3;
}
td.ant-table-cell {
text-align: center;
}
}
}
</style>

View File

@@ -0,0 +1,383 @@
<!-- 感恩教师师资大全 -->
<template>
<div class="researchmanage">
<!-- 搜索框及按钮 -->
<div class="filter">
<div class="filterItems">
<div class="select">
<a-input v-model:value="params.teacherName" style="width: 270px; height: 40px; border-radius: 8px"
placeholder="请输入教师姓名" />
</div>
<div class="select">
<a-select v-model:value="params.teacherLevel" style="width: 270px" placeholder="全部"
:options="teacherLevelList" allowClear></a-select>
</div>
<div style="display: flex; margin-bottom: 20px">
<div class="btn btn1" @click="searchInfo">
<div class="search"></div>
<div class="btnText">搜索</div>
</div>
<div class="btn btn2" style="width: 105px" @click="handleRest">
<div class="search"></div>
<div class="btnText">重置</div>
</div>
</div>
</div>
<div style="display: flex;">
<AddTeacherInfo @getList="searchInfo">
<div class="btns" style="margin-right: 10px;">
<div class="btn btn3">
<div class="search"></div>
<div class="btnText">添加教师</div>
</div>
</div>
</AddTeacherInfo>
<ImportTeacher @change="change" title="导入教师" :template-url="teacherTemplateUrl" :data="{ type: 3 }"
:url="`/activityApi/xboe/m/boe/teachers/import`" name="file">
<div class="btns">
<div class="btn btn3">
<div class="search"></div>
<div class="btnText">导入教师</div>
</div>
</div>
</ImportTeacher>
</div>
</div>
<!-- 搜索框及按钮 -->
<!-- 表格 -->
<div class="table">
<Table ref="carouselTableRef" :columns="column" :url="teachersList" page-key="pageNo" v-model:params="params"
:request="useTotalPage" :showQuickJumper="true" :scroll="{ x: 1000 }" :showSizeChanger="true">
</Table>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, reactive, computed } from "vue";
import dialog from "@/utils/dialog";
import { message } from "ant-design-vue";
import { teachersList, teachersDelete } from "@/api/grateful";
import { useTotalPage } from "@/hooks/useRequest";
import AddTeacherInfo from '@/components/Grateful/AddTeacherInfo.vue'
import ImportTeacher from '@/components/Grateful/ImportTeacher'
//下载模版
const teacherTemplateUrl = ref(process.env.VUE_APP_FILE_PATH + '教师导入模版.xlsx');
const column = ref([
{
title: "课程名称",
dataIndex: "courseName",
key: "courseName",
width: 30,
align: "center",
ellipsis: true,
},
{
title: "教师姓名",
dataIndex: "teacherName",
key: "teacherName",
width: 15,
align: "center"
},
{
title: "排序",
dataIndex: "sort",
key: "sort",
width: 15,
align: "center"
},
{
title: "教师工号",
dataIndex: "teacherNo",
key: "teacherNo",
width: 15,
align: "center",
customRender: ({ record }) => {
return (
<span>{record.teacherNo ? record.teacherNo : '-'}</span>
);
},
},
{
title: "授课时长(H)",
dataIndex: "teacherTime",
key: "teacherTime",
width: 20,
align: "center",
customRender: ({ record }) => {
return (
<span>{record.teacherTime ? record.teacherTime : '-'}</span>
);
},
},
{
title: "教师等级",
dataIndex: "teacherLevel",
key: "teacherLevel",
width: 15,
align: "center",
customRender: ({ record }) => {
return (
<span>{record.teacherLevel ? record.teacherLevel + '级' : '-'}</span>
);
},
},
{
title: "组织全路径",
dataIndex: "orgPath",
key: "orgPath",
width: 40,
align: "center",
ellipsis: true,
customRender: ({ record }) => {
return (
<span>{record.orgPath ? record.orgPath : '-'}</span>
);
},
},
{
title: "操作",
width: 10,
dataIndex: "operation",
key: "id",
fixed: "right",
align: "center",
customRender: ({ record }) => {
return (
<div class="opa">
<a onClick={() => handleOper(record, 'del')}>删除</a>
</div>
);
},
},
]);
const params = ref({
teacherName: '',
teacherLevel: undefined,
enabled: ''
});
const teacherLevelList = ref([{
id: 1,
value: '1',
label: "1级",
},
{
id: 2,
value: '2',
label: "2级",
},
{
id: 3,
value: '3',
label: "3级",
},
{
id: 4,
value: '4',
label: "4级",
},
{
id: 5,
value: '5',
label: "5级",
},
])
const carouselTableRef = ref();
const searchInfo = () => {
console.log("搜索");
carouselTableRef.value.params.pageNo = 1
carouselTableRef.value.fetch();
};
const handleRest = () => {
console.log("重置");
params.value.teacherName = ''
params.value.enabled = ''
params.value.teacherLevel = undefined
carouselTableRef.value.reset();
};
const handle = ({ id }) => ({
del: async () => {
console.log("删除接口", id);
teachersDelete({ id }).then(() => {
message.success("删除成功");
carouselTableRef.value.fetch();
}).catch((err) => {
message.error("删除失败");
})
},
});
const handleMsg = {
del: "你确定删除该教师信息吗?",
};
function handleOper(record, type, status = "") {
dialog({
content: handleMsg[status ? type + status : type],
ok: handle(record)[type],
});
}
</script>
<style lang="scss" scoped>
.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;
}
}
.researchmanage {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
position: relative;
.filter {
margin-left: 38px;
margin-right: 38px;
margin-top: 30px;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
.filterItems {
display: flex;
flex-wrap: wrap;
.select {
margin-right: 20px;
margin-bottom: 20px;
}
.btn {
padding: 0 26px 0 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;
}
}
.btn1 {
.search {
width: 15px;
height: 17px;
background-image: url("@/assets/images/courseManage/search0.png");
}
}
.btn2 {
.search {
width: 16px;
height: 18px;
background-image: url("@/assets/images/courseManage/reset0.png");
}
}
.btn1:active {
background: #0982ff;
}
.btn2:active {
background: rgba(64, 158, 255, 0.2);
}
}
.btns {
display: flex;
.btn {
padding: 0 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;
}
}
.btn3 {
margin-right: 0;
.search {
width: 17px;
height: 18px;
background-image: url("@/assets/images/courseManage/add0.png");
}
}
.btn3:active {
background: #0982ff;
}
}
}
.table {
margin: 20px 38px 30px;
display: flex;
flex: 1;
flex-direction: column;
th.ant-table-cell {
background-color: #eff4fc !important;
text-align: center;
color: #999ba3;
}
td.ant-table-cell {
text-align: center;
}
}
}
</style>

View File

@@ -0,0 +1,403 @@
<template>
<div class="DownLoad">
<div class="downTop">
<div class="tab1">
<div class="nameinp">
<a-input v-model:value="params.name" style="width: 330px; height: 40px; border-radius: 8px"
placeholder="关键词搜索" />
</div>
<div class="btns">
<div class="btn1" @click="searchDownloadList">
<div class="img1">
<img src="@/assets/images/courseManage/search0.png" />
</div>
<div class="wz">搜索</div>
</div>
<div class="btn2" @click="reseatDownloadList">
<div class="img2">
<img src="@/assets/images/courseManage/reset0.png" />
</div>
<div class="wz">重置</div>
</div>
</div>
</div>
<ToolUpload v-model:value="files" ref="uploadRef" :file-type="fileType">
<a-button type="primary" style=" border-radius: 8px;width: 100px;height: 40px;background: #4ea6ff"
:loading="!againUpload">上传</a-button>
</ToolUpload>
</div>
<div style="display: flex; flex-wrap: wrap">
<div v-if="teacherToolList.length == 0" class="downContent">
<img src="@/assets/images/courseManage/downloadnodata.png" />
</div>
<div v-else v-for="(toolInfo, index) in teacherToolList" class="zipcontainer" :key="index">
<div v-if="toolInfo" class="item">
<div class="itemup">
<div class="lefttop"></div>
<div class="cent">
<div class="zip"></div>
<div class="ziprit">
<div class="textop">{{ toolInfo.name }}</div>
<div class="texdown">
<div class="timemanag" style="margin-top: 12px">
{{ toolInfo.createTime ? toolInfo.createTime.replace('T', ' ') : "-" }}
{{ toolInfo.createName ? toolInfo.createName : "-" }}
</div>
</div>
</div>
</div>
</div>
<div class="itemdown">
<div class="download" @click="downLoadFile(toolInfo)">下载</div>
<div class="delete" @click="handleOper(toolInfo, 'del')">删除</div>
</div>
</div>
</div>
</div>
<div style="margin-top: 90px;display: flex;align-items: center;justify-content: flex-end;">
<Pagination :total="params.total" :pageSize="params.pageSize" :page="params.pageNo"
@changePagination="changePagination" :pageSizeOptions="['12', '24', '36', '48']">
</Pagination>
</div>
</div>
</template>
<script setup>
import { reactive, watch, ref } from "vue";
import ToolUpload from "@/components/Grateful/ToolUpload";
import dialog from "@/utils/dialog";
import { toolDelete, toolList, saveTool, toolDown } from "@/api/grateful";
import { message } from "ant-design-vue";
const params = reactive({
name: "",
pageSize: 12,
pageNo: 1,
total: 0
});
const teacherToolList = ref([])
const uploadRef = ref();
const fileType = ref(["zip"]);
const files = ref([]);
const againUpload = ref(true);
watch(files, () => {
console.log("lalalalala", files, files.value.length, files.value);
if (files.value.length !== 0) {
const file = files.value[files.value.length - 1]
if (file.status) {
if (file.status == "done") {
againUpload.value = true;
const { id, folderId, name, path } = file.response.result
saveTool({ fileId: id, fileFolderId: folderId, name, filePath: path }).then((result) => {
message.success("上传成功");
reseatDownloadList()
}).catch((err) => {
message.error("上传失败");
});
} else {
againUpload.value = false;
}
} else {
againUpload.value = true;
}
} else {
againUpload.value = true;
}
});
const changePagination = (page, pageSize) => {
params.pageNo = page;
params.pageSize = pageSize;
getList()
};
const getList = async () => {
const result = await toolList(params)
if (result.code == 200) {
const { total, records } = result.data
teacherToolList.value = records
params.total = total
}
}
getList()
const searchDownloadList = () => {
params.pageNo = 1
getList()
};
const reseatDownloadList = () => {
params.pageNo = 1;
params.pageSize = 12;
params.name = '';
getList()
};
const downLoadFile = (toolInfo) => {
if (!toolInfo.filePath) return;
console.log(toolInfo);
toolDown({
urlStr: `https:${process.env.VUE_APP_BOE_API_URL}/upload${toolInfo.filePath}`,
fileName: toolInfo.name
}).then((result) => {
if (!result.data) {
return
}
const link = document.createElement('a');// 创建a标签
let blob = new Blob([result.data], { type: '' }); // 设置文件类型
link.style.display = "none";
link.href = URL.createObjectURL(blob); // 创建URL
link.setAttribute("download", `${toolInfo.name}`);
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(link.href);
document.body.removeChild(link);
})
};
const handle = ({ id }) => ({
del: async () => {
console.log("删除接口");
toolDelete({ id }).then(() => {
message.success("删除成功");
getList()
}).catch(() => {
message.error("删除失败");
})
},
});
const handleMsg = {
del: "你确定要删除这条工具吗?",
};
function handleOper(record, type, status = "") {
dialog({
content: handleMsg[status ? type + status : type],
ok: handle(record)[type],
});
}
</script>
<style lang="scss" scoped>
.DownLoad {
width: 100%;
margin: 30px 38px;
.downTop {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
.tab1 {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
.t1 {
display: flex;
align-items: center;
margin-bottom: 5px;
}
.nameinp {
display: flex;
align-items: center;
margin-right: 10px;
.namee {
color: rgba(0, 0, 0, 0.85);
font-size: 14px;
}
}
.btns {
display: flex;
.btn1 {
width: 100px;
height: 40px;
margin-right: 16px;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-size: 14px;
background: #4ea6ff;
border-radius: 8px;
border: 1px solid #4ea6ff;
cursor: pointer;
.wz {
margin-left: 10px;
}
}
.btn1:active {
background: #0982ff;
}
.btn2:active {
background: rgba(64, 158, 255, 0.2);
}
.btn2 {
width: 100px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 14px;
background: #4ea6ff;
border-radius: 8px;
cursor: pointer;
.wz {
margin-left: 10px;
}
}
}
}
.btnImport {
width: 100px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 14px;
background: #4ea6ff;
border-radius: 8px;
cursor: pointer;
}
}
.downContent {
width: 100%;
height: 500px;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid #d9d9d9;
border-radius: 8px;
margin-top: 60px;
}
.zipcontainer {
display: flex;
width: 352px;
height: 160px;
flex-wrap: wrap;
margin-right: 56px;
margin-top: 90px;
.item {
margin-right: 50px;
margin-bottom: 50px;
.itemup {
width: 352px;
height: 160px;
background: #ffffff;
border-radius: 2px 2px 0px 0px;
border: 1px solid #4ea6ff;
position: relative;
.lefttop {
width: 8px;
height: 21px;
background: #4ea6ff;
border-radius: 0px 4px 4px 0px;
top: 18px;
position: absolute;
}
.cent {
display: flex;
margin-top: 40px;
margin-left: 40px;
.zip {
width: 62px;
height: 72px;
background-image: url(@/assets/images/leveladd/zip.png);
background-size: 100%;
}
.ziprit {
margin-left: 20px;
margin-top: -5px;
.textop {
font-size: 18px;
font-weight: 600;
color: #4ea6ff;
}
.texdown {
.timemanag {
font-size: 14px;
font-weight: 400;
color: #878b92;
}
}
}
}
}
.itemdown {
width: 352px;
height: 48px;
border-radius: 0px 0px 2px 2px;
border: 1px solid #4ea6ff;
border-top: 0;
display: flex;
font-size: 16px;
font-weight: 600;
line-height: 22px;
.download {
width: 176px;
height: 48px;
color: #ffffff;
cursor: pointer;
background: #409eff;
display: flex;
justify-content: center;
align-items: center;
}
.download:hover {
background-color: rgba(64, 158, 255, 0.76);
}
.download:active {
background-color: #0982ff;
}
.delete {
width: 176px;
height: 48px;
color: #4ea6ff;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
}
.delete:hover {
background-color: rgba(64, 158, 255, 0.1);
}
.delete:active {
background-color: rgba(64, 158, 255, 0.2);
}
.outime {
font-size: 16px;
font-weight: 400;
color: #878b92;
margin-left: 10px;
}
}
}
}
}
</style>