Browse Source

Merge branch 'groupJobFair' of https://git.citupro.com/zhengnaiwen_citu/menduner into groupJobFair

Xiao_123 2 months ago
parent
commit
7b1012fb1a

+ 24 - 0
src/api/school.js

@@ -63,6 +63,30 @@ export const studentList = async (data) => {
 	})
 }
 
+// 学生详情
+export const stuDetail = async (data) => {
+	return await request.post({
+		url: '/app-api/flames/student/detail',
+		data
+	})
+}
+
+// 实习证书
+export const certificateList = async (data) => {
+	return await request.post({
+		url: '/app-api/flames/student/internship/certificate/list',
+		data
+	})
+}
+
+// 推荐信
+export const recommendationList = async (data) => {
+	return await request.post({
+		url: '/app-api/flames/student/recommendation/list',
+		data
+	})
+}
+
 // 学生实习情况
 export const studentPracticeStatistics = async (data) => {
 	return await request.post({

+ 1 - 0
src/layout/teacher.vue

@@ -44,6 +44,7 @@ const key = computed(() => {
 })
 
 const whiteList = [
+  '/recruit/teacher/studentList/detail',
 ]
 // 查询是否在白名单内,在则不展示面包屑
 const isInWhiteList = (url)=> {

+ 87 - 0
src/views/recruit/teacher/studentList/components/baseInfo.vue

@@ -0,0 +1,87 @@
+<!-- 基本信息 -->
+<template>
+  <div class="d-flex">
+    <!-- 头像 -->
+    <div class="avatarsBox">
+      <v-badge
+        v-if="info?.sex === '1' || info?.sex === '2'"
+        bordered 
+        offset-x="-25" 
+        offset-y="33" 
+        :color="info?.sex ? (info?.sex === '1' ? '#1867c0' : 'error') : 'error'" 
+        :icon="info?.sex ? (info?.sex === '1' ? 'mdi-gender-male' : 'mdi-gender-female') : 'mdi-gender-female'">
+        <v-avatar size=80 :image="getUserAvatar(info?.avatar, info?.sex)"></v-avatar>
+      </v-badge>
+      <v-avatar v-else size=80 :image="getUserAvatar(info?.student?.studentHeadImg, info?.sex)"></v-avatar>
+    </div>
+    <!-- 信息 -->
+    <div style="flex: 1;">
+      <span style="font-size: 20px; font-weight: 600;color: var(--color-666);">{{ info?.student?.studentName }}</span>
+      <div class="d-flex mt-2 listBox">
+        <span>{{ info?.student?.schoolDepartmentName }}</span>
+        <span v-if="info?.student?.schoolDepartmentName && info?.student?.majorName" class="mx-3">|</span>
+        <span>{{ info?.student?.majorName }}</span>
+      </div>
+      <view class="mt-2 listBox">{{ info?.student?.schoolClassName }}</view>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({name: 'studentList-student-details-baseInfo'})
+import { ref } from 'vue'
+import { getUserAvatar } from '@/utils/avatar'
+
+const props = defineProps({
+  data: Object
+})
+const info = ref({})
+if (props.data && Object.keys(props.data).length) {
+  info.value = props.data
+  info.value.sex = info.value?.student?.studentSex === '男' ? '1' : info.value?.student?.studentSex === '女' ? '2' : info.value?.student?.sex || null
+}
+</script>
+
+<style lang="scss" scoped>
+.avatarsBox {
+  height: 80px;
+  width: 80px;
+  position: relative;
+  // margin: 32px;
+  margin-right: 40px;
+  .img {
+    width: 100%;
+    height: 100%;
+  }
+  .mdi {
+    font-size: 42px;
+    color: #fff;
+  }
+  div {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    border-radius: 50%;
+  }
+}
+.listBox {
+  display: flex;
+  flex-wrap: wrap; /* 允许换行 */
+  width: 100%; /* 设置容器宽度 */
+  // height: 68px;
+  overflow: hidden;
+  color: var(--color-777);
+  div {
+    margin-right: 50px;
+    span {
+      height: 32px;
+      line-height: 32px;
+    }
+    .mdi {
+      font-size: 22px;
+      margin-right: 8px;
+    }
+  }
+}
+</style>

+ 114 - 0
src/views/recruit/teacher/studentList/components/other.vue

@@ -0,0 +1,114 @@
+<!-- 基本信息 -->
+<template>
+  <div>
+    <div class="boxMy">
+      <div class="title-text">基本信息</div>
+      <div class="my">
+        <span>出生年月:</span>
+        <span class="ml">{{ info?.student?.studentBirthday ? info?.student?.studentBirthday.slice(0, 10) : '' }}</span>
+      </div>
+      <div class="my">
+        <span>联系电话:</span>
+        <span class="ml">{{ info?.student?.phone }}</span>
+      </div>
+      <div class="my">
+        <span>就读院系:</span>
+        <span class="ml">{{ info?.schoolDepartment?.departmentTitle }}</span>
+      </div>
+      <div class="my">
+        <span>就读专业:</span>
+        <span class="ml">{{ info?.major?.majorName }}</span>
+      </div>
+      <div class="my">
+        <span>所在班级:</span>
+        <span class="ml">{{ info?.schoolClass?.title }}</span>
+      </div>
+      <div class="my">
+        <span>学号:</span>
+        <span class="ml">{{ info?.student?.studentNo }}</span>
+      </div>
+      <div class="my">
+        <span>紧急联系人:</span>
+        <span class="ml">{{ info?.student?.emergencyContactName }}</span>
+      </div>
+      <div class="my">
+        <span>紧急联系人电话:</span>
+        <span class="ml">{{ info?.student?.emergencyContactPhone }}</span>
+      </div>
+      <div class="my" style="display: flex;align-items: center;">
+        <span>学生简历:</span>
+        <img v-if="checkIsImage(info?.studentBiographicalNotes?.fileUrl)" :src="info?.studentBiographicalNotes?.fileUrl" style="height: 100px;" />
+      </div>
+      <div class="my">
+        <span>录用企业:</span>
+        <span class="ml">{{ info?.student?.enterpeiseName }}</span>
+      </div>
+      <div class="my">
+        <span>录用部门:</span>
+        <span class="ml">{{ info?.student?.jobDept }}</span>
+      </div>
+      <div class="my">
+        <span>录用岗位:</span>
+        <span class="ml">{{ info?.student?.enterpriseRecruitJobName }}</span>
+      </div>
+    </div>
+
+    <div class="boxMy">
+      <div class="title-text">实习证书</div>
+      <div class="my" style="display: flex;">
+        <span>点评:</span>
+        <span class="ml">{{ info?.certificate?.comment }}</span>
+      </div>
+      <div class="my">
+        <span>证书:</span>
+        <span v-if="info?.certificate?.comment" class="ml link-text" @click="viewCertificate">点击查看</span>
+      </div>
+      <div class="my">
+        <span>附件:</span>
+        <span v-if="info?.certificate?.fileUrl" class="ml link-text" @click="handlePreview(info?.certificate?.fileUrl)">在线预览</span>
+      </div>
+    </div>
+    <div class="boxMy">
+      <div class="title-text">企业推荐信</div>
+      <div class="my">
+        <span>附件:</span>
+        <span v-if="info?.commendation?.fileUrl" class="ml link-text" @click="handlePreview(info?.commendation?.fileUrl)">在线预览</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({name: 'studentList-student-details-baseInfoOther'})
+import { ref } from 'vue'
+import { checkIsImage } from '@/utils'
+
+const props = defineProps({
+  data: Object
+})
+const info = ref({})
+if (props.data && Object.keys(props.data).length) {
+  info.value = props.data
+}
+</script>
+
+<style lang="scss" scoped>
+.boxMy{
+  margin-top: 32px;
+  .title-text{
+    font-size: 16px;
+    font-weight: 600;
+    color: var(--color-333);
+  }
+  .my{
+    margin-top: 20px;
+    span {
+      margin-right: 20px;
+      color: var(--color-777);
+    }
+  }
+  .ml{
+    color: var(--color-666);
+  }
+}
+</style>

+ 31 - 20
src/views/recruit/teacher/studentList/index.vue

@@ -5,7 +5,8 @@
     <div class="d-flex justify-space-between mt-8 mb-10">
       <div class="d-flex align-center">
         <!-- <span class="mx-3 color-666 font-size-14">院系</span> -->
-        <Autocomplete class="mr-3" v-model="query.schoolDepartmentName" :item="yuanXi"></Autocomplete>
+        <Autocomplete class="mr-3" v-model="query.schoolDepartmentName" :item="schoolDepartmentItem"></Autocomplete>
+        <TextInput class="mr-3" v-model="query.studentName" :item="studentNameItem" @enter="handleSearch()"></TextInput>
         <v-btn color="primary" class="half-button ml-3" @click="handleSearch()">查 询</v-btn>
         <v-btn class="half-button ml-3" prepend-icon="mdi-refresh" variant="outlined" color="primary" @click="handleSearch(true)">刷 新</v-btn>
       </div>
@@ -27,14 +28,14 @@
         @pageHandleChange="handleChangePage"
       >
         <template #studentName="{ item }">
-          <div class="d-flex align-center cursor-pointer" @click="studentDetails(item.id)">
+          <div class="d-flex align-center defaultLink" @click="studentDetails(item.id)">
             <v-avatar size="40" :image="getUserAvatar(item?.person?.avatar, item?.person?.sex)"></v-avatar>
             <span class="ml-3">{{ item?.person?.name }}</span>
           </div>
         </template>
         <template #actions="{ item }">
-          <v-btn v-if="!item?.recommendationLetter" color="primary" variant="text" @click="handleUploadLetter(item.id)">上传推荐信</v-btn>
-          <v-btn v-if="!item?.evaluate" color="#00897B" variant="text" @click="handleIssueCertificate(item.id)">颁发实习证书</v-btn>
+          <v-btn v-if="!item?.recommendationLetter" color="primary" variant="text" @click="previewFile(item.recommendationLetter)">推荐信</v-btn>
+          <v-btn v-if="!item?.evaluate" color="#00897B" variant="text" @click="previewFile(item.evaluate)">实习证书</v-btn>
         </template>
       </CtTable>
       <!-- <Loading :visible="loading"></Loading> -->
@@ -49,13 +50,24 @@ import Snackbar from '@/plugins/snackbar'
 import { formatName } from '@/utils/getText'
 import { getUserAvatar } from '@/utils/avatar'
 import { schoolOrganization, studentList } from '@/api/school'
-import { useRouter } from 'vue-router'; const router = useRouter()
+// import { useRouter } from 'vue-router'; const router = useRouter()
+import { previewFile } from '@/utils'
 
 const loading = ref(false)
 const query = ref({
   pageSize: 20,
   pageNo: 1,
   schoolDepartmentName: null,
+  studentName: null,
+})
+
+const studentNameItem = ref({
+  type: 'text',
+  width: 300,
+  label: '请输入学生姓名搜索',
+  clearable: true,
+  hideDetails: true,
+  // appendInnerIcon: 'mdi-magnify'
 })
 
 const headers = [
@@ -67,37 +79,34 @@ const headers = [
   { title: '录用岗位', key: 'test', sortable: false, value: item => formatName(item.test) },
   { title: '操作', key: 'actions', sortable: false }
 ]
-
-const tableData = ref([])
-tableData.value = [{ test: 'ces', person: { name: '123' }, id: '1'}]
-const total = ref(0)
-// 列表
+// 数据列表
+const tableData = ref([]); const total = ref(0)
 const getData = async (isRefresh = false) => {
-  query.value.schoolDepartmentName = '中文系'
-  const { data, total: number } = await studentList(query.value)
+  if (!query.value?.schoolDepartmentName) return
 
+  const { data, total: number } = await studentList(query.value)
   tableData.value = data?.records?.length && data.records.map(item=>{
     const { enterpeiseName, enterpriseRecruitJobName, jobDept } = item
     return { ...item.student, enterpeiseName, enterpriseRecruitJobName, jobDept }
   })
-
   total.value = number
   if (isRefresh) Snackbar.success('刷新成功')
 }
 
+// 分页
 const handleChangePage = (val) => {
 	query.value.pageNo = val
 	getData()
 }
 
+// 查询
 const handleSearch = (refresh = false) => {
   query.value.pageNo = 1
   getData(refresh)
 }
 
 const schoolInfo = ref(localStorage.getItem('schoolInfo') ? JSON.parse(localStorage.getItem('schoolInfo')) : {})
-
-const yuanXi = ref({ width: 300, items: [], clearable: false, hideDetails: true, label: '请选择院系' })
+const schoolDepartmentItem = ref({ width: 300, items: [], clearable: false, hideDetails: true, label: '请选择院系' })
 
 // 列表
 const getYuanXiItem = async () => {
@@ -105,22 +114,24 @@ const getYuanXiItem = async () => {
   if (!schoolId) return Snackbar.warning('获取学校信息失败!')
   
   const { data } = await schoolOrganization({ schoolId })
-  yuanXi.value.items = data?.length && data.map(item=> {
+  schoolDepartmentItem.value.items = data?.length && data.map(item=> {
     return item?.title ? { label: item.title, value: item.title } : null
   }).filter(Boolean)
 
-  if (yuanXi.value.items?.length) {
-    query.value.schoolDepartmentName = yuanXi.value.items[0].value
+  if (schoolDepartmentItem.value.items?.length) {
+    query.value.schoolDepartmentName = schoolDepartmentItem.value.items[0].value
     getData()
   }
 }
-getYuanXiItem()
+getSchoolDepartmentItemItem()
 
 const studentDetails = (id) => {
-  if (id) router.push(`/recruit/teacher/studentList/detail/${id}`)
+  if (id) window.open(`/recruit/teacher/studentList/detail/${id}`)
 }
 
+// 导出
 const exportLoading = ref(false)
+
 </script>
 <style lang="scss" scoped>
 .title {

+ 106 - 2
src/views/recruit/teacher/studentList/studentDetails.vue

@@ -1,10 +1,114 @@
-<!--  -->
+<!-- 学生详情 -->
 <template>
-  <div>学生详情</div>
+  <div v-if="Object.keys(info).length" class="d-flex justify-center mb-8">
+    <div style="width: 940px;background: #fff;" class="px-8 pb-12 pt-3 my-n3 mr-3">
+      <!-- 基本信息 -->
+      <baseInfo class="mt-5" :data="info"></baseInfo>
+      <!-- 基本信息 -->
+      <other :data="info"></other>
+    </div>
+    <div class="operate pa-3">
+      <v-list>
+        <v-list-subheader class="title">操作</v-list-subheader>
+        <v-list-item
+          v-for="(item, i) in operateItems" :key="'操作' + i"
+          color="primary"
+          :prepend-icon="item.icon"
+          :title="item.text"
+          @click="handleClick(item)"
+        >
+        </v-list-item>
+      </v-list>
+    </div>
+  </div>
+  <Loading :visible="loading"></Loading>
 </template>
 
 <script setup>
 defineOptions({name: 'studentList-student-details'})
+import baseInfo from './components/baseInfo.vue'
+import other from './components/other.vue'
+import { ref } from 'vue'
+import { stuDetail, certificateList, recommendationList } from '@/api/school'
+import Snackbar from '@/plugins/snackbar'
+import { useRoute } from 'vue-router'; const route = useRoute()
+
+const operateItems = [
+  { text: '上传推荐信', key:'letter', icon: 'mdi-circle-medium' },
+  { text: '颁发实习证书', key:'certificate', icon: 'mdi-circle-medium' },
+]
+
+// 获取人才详情
+const info = ref({
+  student: {
+    studentName: '莫秋妮',
+    studentSex: '男',
+    studentBirthday: '2000-01-01 00:00:00',
+    schoolDepartmentName: '上海交通大学',
+    majorName: '野生动物与自然保护区管理',
+    schoolClassName: '1班',
+  },
+  studentBiographicalNotes: {
+    fileUrl: 'https://minio.huomiaoer.com/dev/data/a613722d-f0cc-404b-88b7-4f92dbed5264/ba9e680f-6bba-4fb9-8df6-1fd723b286ed.jpg',
+  }
+})
+const loading = ref(false)
+const { id: studentId } = route.params
+
+const getCvDetail = async () => {
+  if (!studentId) {
+    Snackbar.warning('缺少学生id')
+    setTimeout(() => {
+      window.close()
+    }, 2000)
+    return
+  }
+  loading.value = true
+  const data = await stuDetail({ studentId })
+  info.value = data
+  await getInternshipCertificate()
+  await getRecommendation()
+  loading.value = false
+}
+getCvDetail()
+
+// 实习证书
+const getInternshipCertificate = async () => {
+  const query = {
+    size: 999,
+    current: 1,
+    studentId
+  }
+  const data = await certificateList(query)
+  info.value.certificate = data.records.length > 0 ? data.records.reverse()[0].studentInternshipCertificate : {}
+  loading.value = false
+}
+
+// 学生推荐信
+const getRecommendation = async () => {
+  const query = {
+    page: {
+      size: 9999,
+      current: 1
+    },
+    entity: {
+      studentId
+    }
+  }
+  const data = await recommendationList(query)
+  info.value.commendation = data.records.length > 0 ? data.records.reverse()[0].entity : {}
+}
+
+const handleClick = (item) => {
+  console.log('handleClick->item:', item)
+}
+
 </script>
 <style lang="scss" scoped>
+.operate {
+  width: 240px;
+  height: 500px; // 272px
+  position: sticky;
+  top: 60px;
+}
 </style>