feat:新增社招新员工项目欢迎页与学习页面

This commit is contained in:
zxj
2025-11-14 10:26:24 +08:00
parent d07f732959
commit e9f18191fb
14 changed files with 4032 additions and 2368 deletions

View File

@@ -0,0 +1,798 @@
<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="52px"
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"
:class="it.completed ? 'done' : 'link'"
>
<el-button
v-if="!it.completed"
type="text"
style="color: #ffb01e"
>进行中</el-button
>
<el-button
v-else
type="text"
style="color: #27ce9a"
disabled
>已完成</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;`"
></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>
<el-alert
v-if="approvalResults == 2"
title="报名申请正在审核中"
type="primary"
center
show-icon
:closable="false"
/>
<el-alert
v-if="approvalResults == 3"
title="报名申请审核已通过"
type="success"
center
show-icon
:closable="false"
/>
<div style="cursor: pointer" @click="returnEnroll">
<el-alert
v-if="approvalResults == 4"
title="报名申请审核不通过,请重新报名"
type="error"
center
show-icon
:closable="false"
/>
</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" type="success" @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(#FFB01E 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"
/>
<!-- 转正流程图弹窗 -->
<el-dialog
:visible.sync="chartVisible"
: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>
</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 { mapGetters } from "vuex";
import {
getWelcomeData,
getStuProjectTaskList,
getStuProjectProcess,
downloadSocialRecruitReport,
getSzxygProjectInfo,
} from "@/api/new-employee/newEmployee";
import { start } from "nprogress";
export default {
name: "StudyPage",
components: {
portalHeader,
portalFooter,
portalFloatTools,
NewEmployeeGuideDialog,
},
data() {
return {
flag: "all",
status: "all",
page: 1,
pageSize: 10,
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"]),
},
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.projectId) return;
try {
const res = await getStuProjectProcess(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.$message.warning(
"您的报名正在审核中,无法下载成绩单,请审核通过后下载"
);
return;
}
if (this.approvalResults != 4) {
this.$message.warning(
"请您重新报名,重新报名不影响当前学习进度,请在审核通过后下载成绩单"
);
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("下载失败");
}
},
returnEnroll() {
this.$router.push("/new-employee/welcome");
},
},
async mounted() {
const res = await getWelcomeData();
// 社招新员工项目标识0否1是
if (res.data.approvalResults == 1) {
this.$message.error("未报名或报名失败,请前往报名页面进行报名");
this.$router.push("/new-employee/welcome");
}
// 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: 8px 12px;
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.link {
color: #0f80ff;
}
.item-right.done {
color: #8a8f99;
}
.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: 8px;
}
.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: #ffffff;
color: rgba(0, 120, 252, 1);
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);
}
.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>