fix:项目&学员管理改造支持社招新员工

This commit is contained in:
zxj
2025-11-13 14:50:16 +08:00
parent 09c4a6da80
commit ec03ab4427
9 changed files with 14520 additions and 671 deletions

View File

@@ -350,7 +350,7 @@
<div class="nubbox">
<div>
<span class="nub1" style="color: #a497ff">{{
((projectInfoOverview.completeRatio || 0) * 100).toFixed(2)
((projectInfoOverview.completeRatio || 0) * 100).toFixed(2)
}}</span
><span style="color: #a497ff; font-size: 14px">%</span>
</div>
@@ -390,7 +390,10 @@
type="dashboard"
gapDegree="0"
:percent="
fixDoublePer( stageOverviewList[choosedStageIndex]?.completeCourseRatio || 0)
fixDoublePer(
stageOverviewList[choosedStageIndex]
?.completeCourseRatio || 0
)
"
:width="140"
/>
@@ -401,7 +404,10 @@
type="dashboard"
gapDegree="0"
:percent="
fixDoublePer(stageOverviewList[choosedStageIndex]?.completeExamRatio || 0)
fixDoublePer(
stageOverviewList[choosedStageIndex]?.completeExamRatio ||
0
)
"
:width="140"
/>
@@ -411,39 +417,67 @@
<a-progress
type="dashboard"
gapDegree="0"
:percent="fixDoublePer(stageOverviewList[choosedStageIndex]?.completeRatio || 0)"
:percent="
fixDoublePer(
stageOverviewList[choosedStageIndex]?.completeRatio || 0
)
"
:width="140"
/>
<div class="protext">作业完成率</div>
</div>
<div class="proright">
<div class="pronub" style="margin-left: 142px">
{{ stageOverviewList[choosedStageIndex]?.completeTaskCnt || 0}}
{{
stageOverviewList[choosedStageIndex]?.completeTaskCnt || 0
}}
</div>
<div class="proright1">
<span class="textpro">阶段任务总数</span>
<a-progress
:percent="fixDoublePer(stageOverviewList[choosedStageIndex]?.completeTaskCnt/(stageOverviewList[choosedStageIndex]?.totalTaskCnt || 0))"
:percent="
fixDoublePer(
stageOverviewList[choosedStageIndex]?.completeTaskCnt /
(stageOverviewList[choosedStageIndex]?.totalTaskCnt ||
0)
)
"
style="width: 369px"
/>
</div>
<div class="pronub" style="margin-left: 142px">
{{ stageOverviewList[choosedStageIndex]?.completeReqCnt || 0}}
{{
stageOverviewList[choosedStageIndex]?.completeReqCnt || 0
}}
</div>
<div class="proright1">
<span class="textpro">必修任务</span>
<a-progress
:percent="fixDoublePer(stageOverviewList[choosedStageIndex]?.completeReqCnt/(stageOverviewList[choosedStageIndex]?.totalReqCnt || 0))"
:percent="
fixDoublePer(
stageOverviewList[choosedStageIndex]?.completeReqCnt /
(stageOverviewList[choosedStageIndex]?.totalReqCnt ||
0)
)
"
style="width: 369px"
/>
</div>
<div class="pronub" style="margin-left: 142px">
{{ stageOverviewList[choosedStageIndex]?.completeOptCnt || 0}}
{{
stageOverviewList[choosedStageIndex]?.completeOptCnt || 0
}}
</div>
<div class="proright1">
<span class="textpro">选修任务</span>
<a-progress
:percent="fixDoublePer(stageOverviewList[choosedStageIndex]?.completeOptCnt/(stageOverviewList[choosedStageIndex]?.totalOptCnt || 0))"
:percent="
fixDoublePer(
stageOverviewList[choosedStageIndex]?.completeOptCnt /
(stageOverviewList[choosedStageIndex]?.totalOptCnt ||
0)
)
"
style="width: 369px"
/>
</div>
@@ -585,10 +619,27 @@
<div class="time">
<div class="timetext">开始时间</div>
<div class="timetext">
{{ item.type==2 ? item.createTime !== null ? item.createTime : "-" : item.startTime !== null ? item.startTime : "-" }}
{{
item.type == 2
? item.createTime !== null
? item.createTime
: "-"
: item.startTime !== null
? item.startTime
: "-"
}}
</div>
</div>
<div class="progress" v-if="item.averageScore" style="display: flex;flex-grow: 0;margin: 0;min-width: 66px;">
<div
class="progress"
v-if="item.averageScore"
style="
display: flex;
flex-grow: 0;
margin: 0;
min-width: 66px;
"
>
<!-- <div class="progresstext">
{{ item.finishStuCnt || 0 }}/{{
item.totalStuCnt || 0
@@ -613,24 +664,45 @@
}}%</span
>
</div> -->
<div>
{{ item.averageScore}}分
</div>
<div>{{ item.averageScore }}分</div>
<div class="img_daoc" @click="exportScore(item)"></div>
</div>
<div class="progress" v-if="!item.averageScore" style="display: flex;flex-grow: 0;margin: 0;min-width: 60px;">
</div>
<div
class="progress"
v-if="!item.averageScore"
style="
display: flex;
flex-grow: 0;
margin: 0;
min-width: 60px;
"
></div>
<div class="operations">
<div class="operations_dropdown">
<a-dropdown :getPopupContainer='triggerNode=> triggerNode.parentNode' v-if="item.assessmentIds.filter(id => id !== null).length>1&&item.type == 2" :trigger="['click']" v-model:visible="visibleEwmpg[item.id]">
<a class="ant-dropdown-link" @click="qrcodeAssement(item)">
<a-dropdown
:getPopupContainer="
(triggerNode) => triggerNode.parentNode
"
v-if="
item.assessmentIds.filter((id) => id !== null)
.length > 1 && item.type == 2
"
:trigger="['click']"
v-model:visible="visibleEwmpg[item.id]"
>
<a
class="ant-dropdown-link"
@click="qrcodeAssement(item)"
>
评估二维码
<DownOutlined />
</a>
<template #overlay>
<a-menu @click="handleMenuClickpg">
<template v-for="(item, index) in qrCodeItemspg" :key="index">
<template
v-for="(item, index) in qrCodeItemspg"
:key="index"
>
<a-menu-item>
<div>
<p>{{ item.name }}</p>
@@ -640,23 +712,47 @@
</a-menu>
</template>
</a-dropdown>
<div v-if="item.assessmentIds.filter(id => id !== null).length==1&&item.type == 2">
<a class="ant-dropdown-link" @click="qrcodeAssement(item)">
<div
v-if="
item.assessmentIds.filter((id) => id !== null)
.length == 1 && item.type == 2
"
>
<a
class="ant-dropdown-link"
@click="qrcodeAssement(item)"
>
评估二维码
<DownOutlined />
</a>
</div>
</div>
<div class="operations_dropdown" v-if="item.assessmentIds.length!=1">
<a-dropdown :getPopupContainer='(triggerNode) => triggerNode.parentNode' v-if="item.type == 2" :trigger="['click']" v-model:visible="visibleEwm[item.id]">
<a class="ant-dropdown-link" @click="qrcodeVisible(item)">
<div
class="operations_dropdown"
v-if="item.assessmentIds.length != 1"
>
<a-dropdown
:getPopupContainer="
(triggerNode) => triggerNode.parentNode
"
v-if="item.type == 2"
:trigger="['click']"
v-model:visible="visibleEwm[item.id]"
>
<a
class="ant-dropdown-link"
@click="qrcodeVisible(item)"
>
签到二维码
<DownOutlined />
</a>
<template #overlay v-if="qrCodeItems.length>0">
<template #overlay v-if="qrCodeItems.length > 0">
<a-menu @click="handleMenuClick">
<template v-for="(item, index) in qrCodeItems" :key="index">
<template
v-for="(item, index) in qrCodeItems"
:key="index"
>
<a-menu-item>
<div>
<p>{{ item.name }}</p>
@@ -667,16 +763,24 @@
</template>
</a-dropdown>
</div>
<div class="operations_dropdown" v-if="item.type == 2&&item.assessmentIds.length==1">
<a class="ant-dropdown-link" @click="qrcodeVisible(item)">
签到二维码
<DownOutlined />
</a>
<div
class="operations_dropdown"
v-if="
item.type == 2 && item.assessmentIds.length == 1
"
>
<a
class="ant-dropdown-link"
@click="qrcodeVisible(item)"
>
签到二维码
<DownOutlined />
</a>
</div>
<div
class="operation"
style="cursor: pointer"
@click="openCourse(item,key,index)"
@click="openCourse(item, key, index)"
v-if="item.type == 2"
>
开课
@@ -828,11 +932,26 @@
:visable="tabFlag"
:groupList="groupList"
:remarksTrue="remarksTrue"
:isNewEmployee="isNewEmployee"
>
<template #extension="{ data: { record } }">
<a-button @click="showStudent(record)" type="link"
>查看
</a-button>
<!-- 查看报名记录 -->
<a-button
v-if="isNewEmployee"
@click="showSignUpRecord(record)"
type="link"
>查看报名记录</a-button
>
<!-- 成绩单下载 -->
<a-button
v-if="isNewEmployee"
@click="downloadReport(record)"
type="link"
>成绩单下载
</a-button>
<a-button
@click="settingTopFlag(record)"
v-if="checkPer(permissions, createId)"
@@ -846,9 +965,7 @@
@click="showChangeGroupModal(record)"
>换组
</a-button>
<a-button
type="link"
@click="modifyRemarks(record)"
<a-button type="link" @click="modifyRemarks(record)"
>修改备注
</a-button>
</template>
@@ -972,7 +1089,15 @@
</div>
</div>
<!-- 小组列表分页 -->
<div style="width: 100%;display:flex;justify-content:center;align-items:center;margin-top: 12px;">
<div
style="
width: 100%;
display: flex;
justify-content: center;
align-items: center;
margin-top: 12px;
"
>
<a-pagination
:showSizeChanger="false"
showQuickJumper="true"
@@ -1736,14 +1861,14 @@
:createId="createId"
/>
<!-- 面授学员抽屉 -->
<RouterFaceStu
:permissions="permissions"
:createId="createId"
v-model:FSvisible="FSvisible"
:datasource="facestudent"
:type="1"
:courseName="name"
/>
<RouterFaceStu
:permissions="permissions"
:createId="createId"
v-model:FSvisible="FSvisible"
:datasource="facestudent"
:type="1"
:courseName="name"
/>
<!-- 活动直播考勤抽屉 -->
<active-attendance
:permissions="permissions"
@@ -2352,24 +2477,31 @@
/>
<!-- 换组弹窗 -->
<!-- 面授课开课弹框 -->
<AddOpenCourse @call-parent-method="getTaskListAll" ref="coursePlanRef" :type="1"/>
<AddOpenCourse
@call-parent-method="getTaskListAll"
ref="coursePlanRef"
:type="1"
/>
<!-- 修改备注弹窗 -->
<a-modal
<a-modal
v-model:visible="showRemarks"
:footer="null"
closable="false"
style="margin-top: 400px"
@cancel="of_remarks"
>
<div class="selectonlineface" :style="{ display: showRemarks ? 'block' : 'none' }">
<div class="bg_headers"></div>
<div
class="selectonlineface"
:style="{ display: showRemarks ? 'block' : 'none' }"
>
<div class="bg_headers"></div>
<div class="bg_main">
<div class="bg_main_header">
<div>修改备注</div>
<div class="bg_main_header_close" @click="of_remarks"></div>
</div>
<div class="bg_body">
<div class="bg_body_bt" style="align-items: flex-start;">
<div class="bg_body_bt" style="align-items: flex-start">
<!-- <div class="bg_body_bttext" style="margin-top: 5px;">修改备注</div> -->
<div class="bg_body_input">
<a-input v-model:value="remarks" placeholder="请输入" />
@@ -2379,10 +2511,7 @@
<div class="btn btn6" @click="of_remarks">
<div class="btnText">取消</div>
</div>
<a-button
class="btn btn6"
@click="RemarksUpdata"
>
<a-button class="btn btn6" @click="RemarksUpdata">
确定
</a-button>
</div>
@@ -2390,6 +2519,38 @@
</div>
</div>
</a-modal>
<!-- 查看报名记录 -->
<a-modal
v-model:visible="showSignUpRecordModal"
title="报名记录"
:footer="null"
width="700px"
>
<div style="padding: 20px">
<a-table
:columns="signupRecordColumns"
:data-source="signupRecordList"
:pagination="true"
:loading="signupRecordLoading"
row-key="id"
bordered
>
<template #status="{ text, record }">
<span
:style="{
color:
record.enrollStatusCode === 3
? '#52c41a'
: record.enrollStatusCode === 2
? '#1890ff'
: '#ff4d4f',
}"
>{{ text }}</span
>
</template>
</a-table>
</div>
</a-modal>
</div>
</template>
<script lang="jsx">
@@ -2436,7 +2597,7 @@ import { getTask } from "../../api/indexTaskadd";
import { editProjDoc } from "../../api/indexTaskadd";
import {getCookieForName, toDate} from "../../api/method";
import projSet from "../../components/Modals/projSet";
import { overview } from "../../api/indexProjStu";
import { overview,downloadTranscript } from "../../api/indexProjStu";
import TwoDimensionalCode from "../../components/TwoDimensionalCode.vue";
import ProjectScore from "../../components/drawers/ProjectScore";
import { useStore } from "vuex";
@@ -2450,7 +2611,7 @@ import ChangeGroupModal from "@/components/student/ChangeGroupModal.vue";
import { checkPer,fixDoublePer } from "@/utils/utils";
import ImpoterGroupLeader from "@/components/drawers/project/ImpoterGroupLeader.vue";
import qrCode from "@/utils/qrCode";
import {request} from "@/api/request";
import {request, boeRequest} from "@/api/request";
import { PROJECT_RELEASE} from "@/api/apis";
export default {
name: "taskPage",
@@ -2498,6 +2659,70 @@ export default {
coursePlanRef.value.openDrawer(res.data.data.stageList[index].taskDraftDtoList.find(fin=>fin.projectTaskId === item.id));
})
}
// 报名记录弹窗与列配置
const showSignUpRecordModal = ref(false);
const signupRecordLoading = ref(false);
const signupRecordColumns = [
{ title: "姓名", dataIndex: "name", align: "center" },
{ title: "工号", dataIndex: "workNo", align: "center" },
{
title: "状态",
dataIndex: "status",
align: "center",
slots: { customRender: "status" },
},
{ title: "报名时间", dataIndex: "applierAtStr", align: "center" },
{ title: "描述", dataIndex: "desc", align: "left" },
];
const signupRecordList = ref([]);
// 状态码映射
const getStatusText = (code) => {
const statusMap = {
1: "报名失败",
2: "审核中",
3: "审核通过",
4: "审核不通过"
};
return statusMap[code] || "未知";
};
const showSignUpRecord = async (record) => {
if (!state.projectId || !record.studentId) {
message.warning("缺少必要参数");
return;
}
showSignUpRecordModal.value = true;
signupRecordLoading.value = true;
signupRecordList.value = [];
try {
const params = {
pid: state.projectId,
stuId: record.studentId
};
const res = await api.getElnBoeEnrollListByStudent(params);
if (res.data.code === 200 && res.data.success && res.data.data) {
// 将接口返回的数据映射到表格列
signupRecordList.value = res.data.data.map((item, index) => ({
id: index + 1,
name: record.studentName ||"",
workNo: record.studentUserNo || "",
status: getStatusText(item.enrollStatusCode),
enrollStatusCode: item.enrollStatusCode,
applierAtStr: item.applierAtStr || "",
desc: item.enrollMark || ""
}));
} else {
message.error(res.msg || "获取报名记录失败");
signupRecordList.value = [];
}
} catch (error) {
console.error("获取报名记录失败:", error);
message.error("获取报名记录失败,请稍后重试");
signupRecordList.value = [];
} finally {
signupRecordLoading.value = false;
}
};
const qrCodeItems = ref([]);
const visibleEwm = ref({});
const qrcodeVisible = async (item)=>{
@@ -2593,6 +2818,7 @@ export default {
const headers = { token: getCookieForName("token") };
const state = reactive({
isNewEmployee: false,
createId: "",
permissions: "",
screenHeight: document.body.clientHeight,
@@ -4243,6 +4469,9 @@ export default {
getTask({
projectId: state.projectId,
}).then((res) => {
if(res.data.data.projectInfo.szxygProjectFlag == "1"){
state.isNewEmployee = true;
}
if (
res.data.data.projectAuditLogDtoList &&
res.data.data.projectAuditLogDtoList.length
@@ -5113,6 +5342,25 @@ export default {
state.Seevisible = true;
state.checkStuId = record.studentId;
}
function downloadReport(record) {
// window.open(
// window.location.protocol +
// process.env.VUE_APP_BASE_API +'/stu/project/socialRecruit/report/download?projectId='+state.projectId+'&studentId='+record.studentId
// );
api.downloadTranscript({projectId:state.projectId,studentId:record.studentId}).then(async (res) => {
console.log(res,"@@@");
// const binaryData = [];
// binaryData.push(res);
// console.log(binaryData);
// var pdfUrl = window.URL.createObjectURL(new Blob(binaryData, { type: 'application/pdf' }));
const pdfBlob = new Blob([res.data], { type: 'application/pdf' });
// 2. 生成前端临时预览 URL
const pdfUrl = URL.createObjectURL(pdfBlob);
window.open(pdfUrl);
})
}
// 共享文档文件下载
const downloadFile = (url) => {
console.log(url);
@@ -5203,6 +5451,7 @@ export default {
headers,
stageChange,
showStudent,
downloadReport,
settingTopFlag,
totask,
tostudent,
@@ -5339,6 +5588,13 @@ export default {
coursePlanRef,
openCourse,
exportScore,
// 报名记录相关
showSignUpRecordModal,
signupRecordColumns,
signupRecordList,
showSignUpRecord,
signupRecordLoading,
};
},
};
@@ -6177,113 +6433,113 @@ export default {
}
}
}
.selectonlineface{
z-index: 999;
width: 679px;
background: #ffffff;
box-shadow: 0px 1px 35px 0px rgba(118, 136, 166, 0.21);
.selectonlineface {
z-index: 999;
width: 679px;
background: #ffffff;
box-shadow: 0px 1px 35px 0px rgba(118, 136, 166, 0.21);
position: absolute;
left: 50%;
top: -100%;
transform: translate(-50%, -50%);
.bg_headers {
position: absolute;
left: 50%;
top: -100%;
transform: translate(-50%, -50%);
.bg_headers {
position: absolute;
width: 100%;
height: 40px;
background: linear-gradient(
rgba(78, 166, 255, 0.2) 0%,
rgba(78, 166, 255, 0) 100%
);
width: 100%;
height: 40px;
background: linear-gradient(
rgba(78, 166, 255, 0.2) 0%,
rgba(78, 166, 255, 0) 100%
);
}
.bg_main {
width: 100%;
position: relative;
.bg_main_header {
display: flex;
align-items: center;
padding-top: 20px;
padding-left: 26px;
font-size: 16px;
.bg_main_header_close {
position: absolute;
right: 42px;
cursor: pointer;
width: 20px;
height: 20px;
background-image: url(@/assets/images/coursewareManage/close.png);
background-size: 100% 100%;
}
}
.bg_main {
width: 100%;
position: relative;
.bg_main_header {
.bg_body {
width: 80%;
margin: 3px auto;
.bg_body_bt {
display: flex;
align-items: center;
padding-top: 20px;
padding-left: 26px;
font-size: 16px;
.bg_main_header_close {
position: absolute;
right: 42px;
cursor: pointer;
width: 20px;
height: 20px;
background-image: url(@/assets/images/coursewareManage/close.png);
background-size: 100% 100%;
justify-content: end;
margin: 14px auto;
.bg_body_bttext {
width: 110px;
display: flex;
justify-content: end;
margin-right: 20px;
}
}
.bg_body {
width: 80%;
margin: 3px auto;
.bg_body_input {
flex: 1;
position: relative;
.ant-upload-picture-card-wrapper {
width: 200px;
margin-right: 18px;
}
}
.bg_footer {
width: 100%;
margin-left: 174px;
margin-top: 25px;
margin-bottom: 20px;
display: flex;
.bg_body_bt {
.btn {
width: 100px;
height: 40px;
background: rgba(64, 158, 255, 0);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: end;
margin: 14px auto;
justify-content: center;
margin-right: 14px;
flex-shrink: 0;
cursor: pointer;
.bg_body_bttext {
width: 110px;
display: flex;
justify-content: end;
margin-right: 20px;
.btnText {
font-size: 14px;
font-weight: 400;
line-height: 40px;
}
}
.bg_body_input {
flex: 1;
position: relative;
.ant-upload-picture-card-wrapper{
width: 200px;
margin-right: 18px;
}
}
.bg_footer {
width: 100%;
margin-left: 174px;
margin-top: 25px;
margin-bottom: 20px;
display: flex;
.btn {
width: 100px;
height: 40px;
background: rgba(64, 158, 255, 0);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 14px;
flex-shrink: 0;
cursor: pointer;
.btnText {
font-size: 14px;
font-weight: 400;
line-height: 40px;
}
}
.btn6 {
background-color: #4ea6ff;
color: #ffffff;
}
.btn6 {
background-color: #4ea6ff;
color: #ffffff;
}
}
.headers{
margin-left: 38px;
margin-right: 38px;
margin-top: 30px;
}
.headers {
margin-left: 38px;
margin-right: 38px;
margin-top: 30px;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
.btn {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
.btn{
display: flex;
}
}
}
}
}
.taskpage {
width: 100%;
display: flex;
@@ -6954,14 +7210,14 @@ export default {
margin-right: 50px;
flex-grow: 1;
.img_daoc {
cursor: pointer;
width: 16px;
height: 18px;
background-image: url(@/assets/images/coursewareManage/export.png);
background-size: 100% 100%;
// background-color: #4ea6ff;
margin-left: 7px;
}
cursor: pointer;
width: 16px;
height: 18px;
background-image: url(@/assets/images/coursewareManage/export.png);
background-size: 100% 100%;
// background-color: #4ea6ff;
margin-left: 7px;
}
.progresstext {
color: #ffc067;
font-size: 14px;
@@ -6972,7 +7228,7 @@ export default {
display: flex;
width: 420px;
flex-grow: 1;
.operations_dropdown{
.operations_dropdown {
padding: 10px;
text-align: center;
position: relative;