Files
learning-system-portal/src/views/new-employee/StudyPage.vue
2025-11-20 18:17:21 +08:00

893 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="ne-study-page">
<div class="banner-header">
<portalHeader textColor="#fff" />
</div>
<!-- 顶部横幅与标题 -->
<div class="banner">
<div class="banner-inner">
<div class="title">社招新员工培训及考试</div>
<!-- 筛选条 -->
<div class="filters">
<div class="filter-group">
<span class="label">类型</span>
<el-radio-group
v-model="flag"
@change="onTypeChange"
class="pill-radios"
>
<el-radio-button label="all">全部</el-radio-button>
<el-radio-button label="1">必修</el-radio-button>
<el-radio-button label="0">选修</el-radio-button>
</el-radio-group>
</div>
<div class="filter-group">
<span class="label">状态</span>
<el-radio-group
v-model="status"
@change="onStatusChange"
class="pill-radios"
>
<el-radio-button label="all">全部</el-radio-button>
<el-radio-button label="0">未开始</el-radio-button>
<el-radio-button label="2">进行中</el-radio-button>
<el-radio-button label="1">已完成</el-radio-button>
</el-radio-group>
</div>
</div>
</div>
</div>
<div class="content">
<!-- 左侧主区域 -->
<div class="main">
<!-- 列表课程 -->
<div class="list" v-loading="loading">
<div
class="list-item card"
v-for="it in items"
:key="it.id"
@click="toLearn(it.courseId)"
>
<div style="margin-right: 16px">
<img
width="160px"
src="../../assets/images/course_icon.png"
alt=""
/>
</div>
<div class="item-left">
<div>
<div class="title-box">
<div class="item-title">{{ it.name }}</div>
<!-- 右侧动作/成绩 -->
<div class="item-right">
<el-button
v-if="it.status == 0"
type="text"
style="color: #999999"
>未开始</el-button
>
<el-button
v-if="it.status == 2"
type="text"
style="color: #ffb01e"
>进行中</el-button
>
<el-button
v-if="it.status == 1"
type="text"
style="color: #27ce9a"
>已完成</el-button
>
</div>
</div>
<div class="tags">
<span class="tag" :class="'tag-blue'"> 在线课 </span>
<span v-if="it.flag" class="tag tag-green">必修</span>
<span v-else class="tag tag-yellow">选修</span>
</div>
<div class="progress-wrap">
<div class="progress">
<div
class="bar"
:style="{ width: (it.currentRatio || 0) + '%' }"
></div>
</div>
<div class="percent">{{ it.currentRatio || 0 }}%</div>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<el-pagination
background
layout="prev, pager, next"
:total="total"
:page-size="pageSize"
:current-page.sync="page"
@current-change="onPageChange"
/>
</div>
</div>
</div>
<!-- 右侧侧栏 -->
<div class="sidebar">
<div class="profile card">
<div
class="avatar"
:style="`background: url(${userInfo.avatar}) no-repeat center/100% 100%;`"
></div>
<div class="name">{{ userInfo.name }}</div>
<div class="desc">{{ userInfo.departName }}</div>
<div class="btns">
<el-button class="btn btn-outline" @click="onGuide"
>转正攻略</el-button
>
</div>
<div>
<div v-if="approvalResults == 2">
<el-alert type="warning" center show-icon :closable="false">
<template slot="title">
<span style="color: rgba(0, 0, 0, 0.88)"
>报名申请正在审核中</span
>
</template>
</el-alert>
</div>
<div v-if="approvalResults == 3">
<el-alert type="success" center show-icon :closable="false">
<template slot="title">
<span style="color: rgba(0, 0, 0, 0.88)"
>报名申请审核已通过</span
>
</template>
</el-alert>
</div>
<div
v-if="approvalResults == 4"
style="cursor: pointer"
@click="returnEnroll"
>
<el-alert type="error" center show-icon :closable="false">
<template slot="title">
<span style="color: rgba(0, 0, 0, 0.88)">审核已驳回</span>
<span style="color: #0078fc">重新报名</span>
</template>
</el-alert>
</div>
</div>
<div style="text-align: center">
<el-button type="text" @click="chartVisible = true"
>转正流程示意图 ></el-button
>
</div>
<div class="label">成绩单</div>
<div class="transcript">
<el-button
class="btn btn-long"
:class="{ 'btn-disabled': isDownloadDisabled }"
type="success"
icon="el-icon-download"
:disabled="isDownloadDisabled"
@click="onDownload"
>成绩下载</el-button
>
</div>
<div class="label">学习进度</div>
<div class="progress-circles">
<div class="circle">
<div
class="ring"
:style="`background: conic-gradient(#0078FC 0 ${progressRing.total}%, #eef2f6 ${progressRing.total}%);`"
>
<span>{{ progressRing.total }}</span>
</div>
<div class="text">总进度</div>
</div>
<div class="circle">
<div
class="ring"
:style="`background: conic-gradient(#27ce9a 0 ${progressRing.required}%, #eef2f6 ${progressRing.required}%);`"
>
<span>{{ progressRing.required }}</span>
</div>
<div class="text">必修课进度</div>
</div>
<div class="circle">
<div
class="ring"
:style="`background: conic-gradient(#FFB01E 0 ${progressRing.elective}%, #eef2f6 ${progressRing.elective}%);`"
>
<span>{{ progressRing.elective }}</span>
</div>
<div class="text">选修课进度</div>
</div>
</div>
</div>
</div>
</div>
<portalFooter />
<portalFloatTools />
<!-- 学习引导弹窗 -->
<NewEmployeeGuideDialog
:visible.sync="guideDialogVisible"
@close="guideDialogVisible = false"
:showBtn="true"
/>
<!-- 转正流程图弹窗 -->
<el-dialog
:visible.sync="chartVisible"
title="转正流程示意图"
:close-on-click-modal="false"
@close="chartVisible = false"
>
<div>
<!-- <el-image src="../../assets/images/flowchart.png" fit="fill" /> -->
<img width="100%" src="../../assets/images/flowchart.png" alt="" />
</div>
</el-dialog>
<!-- 自定义信息提示 -->
<CustomInfoMessage ref="customMessage" />
<CustomErrorMessage ref="errorMessage" />
</div>
</template>
<script>
import portalHeader from "@/components/PortalHeader.vue";
import portalFooter from "@/components/PortalFooter.vue";
import portalFloatTools from "@/components/PortalFloatTools.vue";
import NewEmployeeGuideDialog from "@/components/NewEmployeeGuideDialog.vue";
import CustomInfoMessage from "@/components/CustomInfoMessage.vue";
import { mapGetters } from "vuex";
import {
getWelcomeData,
getStuProjectTaskList,
getStuProjectProcess,
downloadSocialRecruitReport,
getSzxygProjectInfo,
enrollRequest,
} from "@/api/new-employee/newEmployee";
import CustomErrorMessage from "@/components/CustomErrorMessage.vue";
import { start } from "nprogress";
export default {
name: "StudyPage",
components: {
portalHeader,
portalFooter,
portalFloatTools,
NewEmployeeGuideDialog,
CustomInfoMessage,
CustomErrorMessage,
},
data() {
return {
flag: "all",
status: "all",
page: 1,
pageSize: 20,
total: 0,
loading: false,
projectId: "",
studentId: "",
// 列表数据(课程/考试混合)
items: [],
// 进度环数据
progressRing: {
total: 0,
required: 0,
elective: 0,
},
// 学习引导弹窗
guideDialogVisible: false,
// 转正流程图弹窗
chartVisible: false,
approvalList: {
1: "报名失败",
2: "审核中",
3: "审核通过",
4: "审核失败",
},
approvalResults: 1,
};
},
computed: {
...mapGetters(["userInfo"]),
// 判断下载按钮是否应该禁用
isDownloadDisabled() {
// 只有在审核通过(3)且学习进度为100时才可下载
// 其他情况报名失败1、审核中2、审核失败4、进度未到100都禁用
return this.approvalResults != 3 || this.progressRing.total != 100;
},
},
methods: {
async initIds() {
// studentId 来自用户信息
this.studentId = this.userInfo?.aid || this.userInfo?.id || "";
const res = await getSzxygProjectInfo();
this.projectId = res.data.id || "";
},
async loadList() {
if (!this.studentId || !this.projectId) return;
this.loading = true;
try {
const res = await getStuProjectTaskList({
pageNo: this.page,
pageSize: this.pageSize,
studentId: this.studentId,
projectId: this.projectId,
status: this.status === "all" ? undefined : this.status,
flag: this.flag === "all" ? undefined : this.flag,
});
const payload = res?.data || {};
const records = payload.rows || [];
this.total = Number(payload.total || 0);
this.items = records;
} catch (e) {
this.$message.error("加载学习列表失败");
} finally {
this.loading = false;
}
},
async loadProcess() {
if (!this.studentId || !this.projectId) return;
try {
const res = await getStuProjectProcess({
studentId: this.studentId,
projectId: this.projectId,
});
const data = res?.data || {};
const total = Number(data.totalProgress ?? 0) | 0;
const required = Number(data.compulsoryProgress ?? 0) | 0;
const elective = Number(data.electiveProgress ?? 0) | 0;
this.progressRing = {
total: total,
required: required,
elective: elective,
};
} catch (_) {
// 默认0
this.progressRing = { total: 0, required: 0, elective: 0 };
}
},
async onTypeChange() {
await this.loadList();
},
async onStatusChange() {
await this.loadList();
},
async onPageChange(p) {
this.page = p;
await this.loadList();
},
onGuide() {
this.guideDialogVisible = true;
},
toLearn(courseId) {
this.$router.push(
`/course/studyindex?id=${courseId}&newEmployee=${true}`
);
},
async onDownload() {
// if (!this.projectId || !this.studentId) {
// this.$message.warning("缺少项目信息或学员信息");
// return;
// }
if (this.approvalResults != 2) {
// this.$refs.customMessage.show(
// "您的报名正在审核中,无法下载成绩单,请审核通过后下载"
// );
return;
}
if (this.approvalResults != 4) {
// this.$refs.customMessage.show(
// "请您重新报名,重新报名不影响当前学习进度,请在审核通过后下载成绩单"
// );
return;
}
if (this.progressRing.total != 100) {
// this.$message.warning("请您完成学习课程后下载成绩单");
return;
}
try {
const res = await downloadSocialRecruitReport(
this.projectId,
this.studentId
);
const blob = new Blob([res], { type: "application/pdf" });
const pdfUrl = URL.createObjectURL(blob);
window.open(pdfUrl);
} catch (e) {
this.$message.error("下载失败");
}
},
async returnEnroll() {
this.$confirm("是否重新报名社招新员工项目?", {
title: "报名确认",
type: "warning",
customClass: "returnEnroll",
}).then(async () => {
const res = await enrollRequest();
if (res.data.sendToOaSuccess != 0) {
this.$refs.errorMessage.show(
"报名失败请稍后重试如果再次失败请联系XXXX联系方式XXXX。"
);
} else {
this.$refs.customMessage.show("您已重新报名成功");
this.approvalResults = 2;
}
});
},
},
async mounted() {
const res = await getWelcomeData();
// 社招新员工项目标识0否1是
if (res.data.approvalResults == 1 || !res.data.approvalResults) {
this.$refs.customMessage.show("未报名或报名失败,请前往报名页面进行报名");
this.$router.push("/new-employee/welcome");
}
this.guideDialogVisible = this.$route.query?.fromWelcome == 1;
// 1报名失败、2审核中、3审核通过、4审核失败
this.approvalResults = res.data.approvalResults;
await this.initIds();
if (!this.projectId || !this.studentId) {
this.$message.warning("未获得项目或学员信息,列表可能无法加载");
}
await Promise.all([this.loadList(), this.loadProcess()]);
},
};
</script>
<style scoped>
/* 颜色变量近似还原截图 */
.ne-study-page {
font-family: "PingFang SC", "Helvetica Neue", Arial, "Microsoft YaHei",
sans-serif;
color: #2b2f36;
background: #f4f6f9;
}
.banner-header {
background: #0f80ff;
}
.banner {
background: #0f80ff;
padding-top: 36px;
}
.banner-inner {
max-width: 1200px;
margin: 0 auto;
padding: 24px 16px 0px;
}
.title {
color: #ffffff;
font-weight: 600;
font-size: 20px;
margin-bottom: 10px;
}
.content {
max-width: 1200px;
margin: 16px auto 40px;
padding: 0 16px;
display: flex;
gap: 16px;
}
.main {
flex: 1;
}
.sidebar {
width: 360px;
margin-top: -70px;
}
.card {
background: #ffffff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04);
}
/* 筛选条 */
.filters {
padding: 0px 12px 8px;
display: flex;
gap: 24px;
align-items: center;
background: #ffffff;
border-radius: 8px 8px 0 0;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04);
width: 68%;
}
.filter-group {
display: flex;
align-items: center;
gap: 8px;
}
.label {
color: #333333;
font-size: 14px;
font-weight: bold;
margin: 24px 0px 12px;
}
.pills {
display: flex;
gap: 8px;
}
.pill {
padding: 6px 12px;
border-radius: 16px;
background: #f3f5f8;
color: #4b5563;
font-size: 12px;
cursor: default;
}
.pill.active {
background: #e6f1ff;
color: #0f80ff;
border: 1px solid #b3d4ff;
}
/* 列表 */
.list {
margin-top: 12px;
}
.list-item {
display: flex;
justify-content: space-between;
padding: 16px;
margin-bottom: 12px;
transition: transform 0.08s ease, box-shadow 0.12s ease,
background-color 0.12s ease;
cursor: pointer;
}
.list-item:hover,
.list-item:active {
transform: translateY(-1px);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.06);
background-color: #ffffff;
}
/* 触摸设备:点按反馈 */
@media (hover: none) {
.list-item:active {
transform: scale(0.995);
background-color: #f8fbff;
}
}
.item-left {
flex: 1;
}
.title-box {
display: flex;
justify-content: space-between;
}
.item-title {
font-size: 15px;
font-weight: 600;
margin-bottom: 8px;
color: #2b2f36;
}
.tags {
display: flex;
gap: 8px;
margin-bottom: 10px;
}
.tag {
display: inline-flex;
align-items: center;
height: 22px;
padding: 0 8px;
border-radius: 12px;
font-size: 12px;
color: #0f80ff;
background: #eaf3ff;
}
.tag-green {
color: #27ce9a;
background: #e9fbf3;
}
.tag-yellow {
color: #ffb01e;
background: #ffecec;
}
.tag-blue {
color: #0f80ff;
background: #eaf3ff;
}
.progress-wrap {
display: flex;
align-items: center;
gap: 10px;
}
.progress {
position: relative;
flex: 1;
height: 8px;
background: #eff2f6;
border-radius: 4px;
overflow: hidden;
}
.bar {
position: absolute;
left: 0;
top: 0;
bottom: 0;
background: #19c37d;
border-radius: 4px;
}
.w-8 {
width: 8%;
background: #ffb74d;
}
.w-40 {
width: 40%;
background: #2fa3ff;
}
.w-100 {
width: 100%;
background: #19c37d;
}
.percent {
font-size: 12px;
color: #8a8f99;
width: 42px;
text-align: right;
}
.item-right {
min-width: 72px;
text-align: right;
font-size: 14px;
}
.item-right.score {
font-weight: 700;
}
.item-right.score.good {
color: #19c37d;
}
.item-right.score.bad {
color: #ff5a5f;
}
/* 分页 */
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
padding: 12px 0 4px;
}
.pg {
min-width: 30px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 6px;
background: #ffffff;
color: #4b5563;
font-size: 12px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
}
.pg.btn {
padding: 0 8px;
}
.pg.active {
background: #0f80ff;
color: #ffffff;
}
/* 细化 text 按钮与原页面链接的一致性 */
.item-right >>> .el-button--text {
padding: 0;
color: inherit;
font-weight: inherit;
}
.item-right.link >>> .el-button--text {
color: #0f80ff;
}
.item-right.done >>> .el-button--text {
color: #8a8f99;
}
.item-right.score.good >>> .el-button--text {
color: #19c37d;
}
.item-right.score.bad >>> .el-button--text {
color: #ff5a5f;
}
/* 单选按钮组胶囊皮肤(替代 Tabs保持外观一致 */
.pill-radios {
padding-top: 20px;
}
.pill-radios >>> .el-radio-button__inner {
background: #f3f5f8;
color: #4b5563;
font-size: 12px;
padding: 6px 12px;
border-radius: 16px !important;
border: 1px solid transparent;
box-shadow: none;
margin-right: 8px;
}
.pill-radios
>>> .el-radio-button__orig-radio:checked
+ .el-radio-button__inner {
background: #e6f1ff;
color: #0f80ff;
border-color: #b3d4ff;
box-shadow: none;
}
.pill-radios >>> .el-radio-button__inner:hover {
filter: brightness(0.98);
}
.pill-radios >>> .el-radio-button__inner::before,
.pill-radios >>> .el-radio-button__inner::after {
display: none;
}
/* 侧栏 */
.profile {
padding: 20px 16px;
}
.avatar {
width: 80px;
height: 80px;
background: #d9e8ff;
border-radius: 50%;
margin: -48px auto 12px;
border: 6px solid #ffffff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.name {
text-align: center;
font-weight: 700;
color: #2b2f36;
}
.desc {
text-align: center;
color: #8a8f99;
font-size: 12px;
margin-top: 4px;
}
.btns {
display: flex;
gap: 12px;
justify-content: center;
margin-top: 14px;
margin-bottom: 16px;
}
.btn {
width: 100% !important;
height: 36px;
padding: 0 14px;
border-radius: 8px;
border: none;
font-weight: 600;
}
.btn-outline {
background: rgba(0, 120, 252, 1);
color: #fff;
border: 1px solid rgba(0, 120, 252, 1);
width: 120px;
}
.transcript {
text-align: center;
}
.transcript .label {
color: #8a8f99;
font-size: 12px;
margin-bottom: 8px;
}
.btn-long {
width: 100%;
height: 40px;
border-radius: 8px;
background: #1ac07f;
color: #ffffff;
box-shadow: 0 2px 8px rgba(26, 192, 127, 0.35);
}
.btn-long.btn-disabled,
.btn-long:disabled {
background: #cccccc !important;
color: #ffffff !important;
cursor: not-allowed !important;
box-shadow: none !important;
}
.btn-long.btn-disabled:hover,
.btn-long:disabled:hover {
background: #cccccc !important;
transform: none !important;
}
.progress-circles {
display: flex;
justify-content: space-between;
margin-top: 8px;
}
.circle {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
width: 32%;
}
.ring {
width: 84px;
height: 84px;
border-radius: 50%;
background: conic-gradient(#d9dfe7 0 65%, #eef2f6 65%);
display: flex;
align-items: center;
justify-content: center;
color: #4b5563;
font-weight: 700;
}
.ring span {
background: #ffffff;
width: 66px;
height: 66px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: inset 0 0 0 6px #ffffff;
}
.circle .text {
font-size: 12px;
color: #8a8f99;
text-align: center;
}
</style>
<style lang="scss">
.returnEnroll {
width: 380px;
height: auto;
.el-message-box__header {
display: block;
position: relative;
padding: 15px 15px 10px;
.el-message-box__title {
font-size: 16px;
}
}
.el-message-box__content {
padding: 10px 15px;
color: #606266;
font-size: 14px;
text-align: left;
font-weight: 500;
margin-top: 0px;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
Microsoft YaHei, SimSun, sans-serif;
}
.el-message-box__status {
display: block;
}
.el-message-box__btns {
padding: 5px 15px 0;
text-align: right;
margin-top: 0px;
.el-button {
padding: 9px 15px;
font-size: 12px;
border-radius: 3px;
width: auto;
height: auto;
}
}
.el-message-box__btns button:nth-child(2) {
margin-left: 10px;
}
}
</style>