Sfoglia il codice sorgente

门墩儿招聘个人信息展示优化

lifanagju_citu 2 settimane fa
parent
commit
a07367cb97

+ 7 - 0
src/components/DictTag/src/DictTag.vue

@@ -7,6 +7,10 @@ import { DictDataType, getDictOptions } from '@/utils/dict'
 export default defineComponent({
   name: 'DictTag',
   props: {
+    showText: {
+      type: Boolean as PropType<boolean>,
+      required: false
+    },
     type: {
       type: String as PropType<string>,
       required: true
@@ -39,6 +43,9 @@ export default defineComponent({
       }
       getDictObj(props.type, props.value.toString())
       // 添加标签的文字颜色为白色,解决自定义背景颜色时标签文字看不清的问题
+      if (props.showText) return (
+        <div>{dictData.value?.label}</div>
+      )
       return (
         <ElTag
           style={dictData.value?.cssClass ? 'color: #fff' : ''}

+ 57 - 0
src/utils/transform/date.js

@@ -92,4 +92,61 @@ export const  convertTimestampsToDayRange = (timestamps) => {
   const endOfDay = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate() + 1, 0, 0, 0, -1)
 
   return [formatDate(startOfDay), formatDate(endOfDay)]
+}
+
+
+// 返回时间差,最小计量单位为一个月。示例用法:
+// const startTime = 1633072800000; // 2021-10-01 00:00:00 UTC(秒级时间戳)
+// const endTime = 1667244000000;  // 2022-11-01 00:00:00 UTC(秒级时间戳)
+export const getTimeDifferenceInChinese = (startTime, endTime) => {
+  // 将时间戳转换为 Date 对象(假设时间戳单位为毫秒)
+  const startDate = startTime ? new Date(startTime) : new Date();
+  const endDate = endTime ? new Date(endTime) : new Date();
+
+  // 计算年份差
+  let yearsDiff = endDate.getFullYear() - startDate.getFullYear();
+  // 计算月份差(考虑年份差后调整)
+  let monthsDiff = endDate.getMonth() - startDate.getMonth();
+  // 如果月份差为负,则从上一年借月
+  if (monthsDiff < 0) {
+    yearsDiff--;
+    monthsDiff += 12;
+  }
+  // 计算日期差(考虑月份差后调整,如果日期差为负,则从上一月借天)
+  let daysDiff = endDate.getDate() - startDate.getDate();
+  if (daysDiff < 0) {
+    monthsDiff--;
+    // 获取 startDate 所在月的最后一天
+    const lastDayOfMonth = new Date(startDate.getFullYear(), startDate.getMonth() + 1, 0).getDate();
+    daysDiff += lastDayOfMonth; // 加上最后一天以补全月份差
+  }
+
+  // 构建结果字符串
+  let result = "";
+  if (yearsDiff > 0) {
+    result += `${yearsDiff}年`;
+  }
+  if (monthsDiff > 0) {
+    if (result) {
+      // 如果已经有年份差异,则直接添加月份数(不带单位),后面正则替换会处理
+      result += monthsDiff;
+    } else {
+      // 如果没有年份差异,则正常添加月份和单位
+      result += `${monthsDiff}个月`;
+      // 特别处理只有1个月的情况
+      if (monthsDiff === 1) {
+        result = "不到1个月"; // 直接替换为“不到1个月”,避免后续复杂的正则替换
+      }
+    }
+  } else if (!result && daysDiff >= 0) {
+    // 如果没有年份和月份差异,但天数差异存在(这里其实没处理天数,只是为完整性添加)
+    // 理论上应该处理天数,但题目要求只到月份,所以这里直接返回“不到1个月”
+    result = "不到1个月";
+  }
+ 
+  // 如果之前添加了月份数但没有年份(且不是直接处理的1个月情况),则需要去除末尾多余的数字并添加“个月”
+  if (result && !/\d年$/.test(result) && /\d$/.test(result)) {
+    result += "个月";
+  }
+  return result
 }

+ 100 - 0
src/views/menduner/system/person/details/components/expExtend.vue

@@ -0,0 +1,100 @@
+<template>
+  <div class="exp" v-if="list?.length">
+    <div class="ml">工作经验</div>
+    <el-timeline>
+      <template v-for="(val, index) in list" :key="index">
+        <template v-if="retract || index < 2">
+          <el-timeline-item center placement="top" color="#0bbd87">
+            <div class="timeline-item">
+              <div style="width: 20%;">
+                <span>{{ val?.startTime ? timesTampChange(val.startTime, 'Y-M') : '未填写工作时间' }}</span>
+                <span v-if="val?.startTime"> - {{ val.startTime ? val.endTime ? timesTampChange(val.endTime, 'Y-M') : '至今' : '' }}</span>
+                <span v-if="val?.startTime"> ({{ val.startTime ? getTimeDifferenceInChinese(val.startTime, val.endTime) : '' }})</span>
+              </div>
+              <div class="timeline-item-name mx-3">{{ formatName(val.enterpriseName) || '' }}</div>
+              <div class="timeline-item-name">{{ formatName(val.positionName) || '' }}</div>
+            </div>
+          </el-timeline-item>
+        </template>
+      </template>
+      允许折叠情况下数据大于
+      <div v-if="list.length > 2 && !displayAll" class="showBtn color-primary" @click.stop="retract = Boolean(!retract)">
+        <span>{{ retract ? '折叠' : '展开全部' }}</span>
+        <!-- <div class="icons">
+          <i :class="retract ? 'up' : 'down'"></i>
+          <i class="el-icon-down"></i>
+        </div> -->
+      </div>
+    </el-timeline>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'PersonExpList'})
+import { PersonInfoApi } from '@/api/menduner/system/person'
+import { DICT_TYPE } from '@/utils/dict'
+import { dateFormatter2 } from '@/utils/formatTime'
+import { timesTampChange, getTimeDifferenceInChinese } from '@/utils/transform/date'
+import { formatName } from '@/utils'
+
+const props = defineProps({
+  userId: [String, Number],
+  displayAll: Boolean
+})
+
+const retract = ref(!props.displayAll) // 是否默认折叠
+const display = ref(!props.displayAll) // 是否默认折叠
+const loading = ref(false)
+const list = ref([])
+const total = ref(0)
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 100,
+  userId: props.userId
+})
+
+const getList = async () => {
+  if (!queryParams.userId) return
+  loading.value = true
+  try {
+    const data = await PersonInfoApi.getPersonExpPage(queryParams)
+    // list.value = data.list ? data.list.map(e => {
+    //   return { ...e, enterpriseName: formatName(e.enterpriseName), positionName: formatName(e.positionName), content: formatName(e.content) }
+    // }) : []
+    list.value = data.list ? data.list.map(exp => {
+      exp.startTimeStr = exp.startTime ? timesTampChange(exp.startTime, 'Y-M') : '未填写工作时间'
+      exp.endTimeStr = exp.startTime ? exp.endTime ? timesTampChange(exp.endTime, 'Y-M') : '至今' : ''
+      exp.year = exp.endTimeStr ? getTimeDifferenceInChinese(exp.startTime, exp.endTime) : ''
+      // 未填写工作经验内容,不展示
+      exp.show = Boolean(exp.year) || Boolean(exp.enterpriseName) || Boolean(exp.positionName)
+    }) : []
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+getList()
+</script>
+<style lang="scss" scoped>
+.exp {
+  margin-top: 20px;
+  color: #666;
+  .timeline-item {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    width: 100%;
+    color: var(--color-666);
+    font-size: 13px;
+    .timeline-item-name {
+      width: 40%;
+    }
+    .mx-3 {
+      margin: 0 12px;
+    }
+  }
+  .el-timeline-item {
+    padding-bottom: 0;
+  }
+}
+</style>

+ 1 - 0
src/views/menduner/system/talentMap/maintenance/gather/components/index_split.vue

@@ -1,3 +1,4 @@
+<!-- 备份文件 -->
 <template>
   <ContentWrap>
     <!-- 搜索工作栏 -->

+ 159 - 0
src/views/menduner/system/talentMap/maintenance/gather/components/personCard.vue

@@ -0,0 +1,159 @@
+<!--  -->
+<template>
+  <el-card class="box" v-if="info">
+    <div class="infoBox">
+      <el-image v-if="info?.avatar" class="h-60px w-60px avatar" :src="info?.avatar" fit="contain" hide-on-click-modal :preview-src-list="[info?.avatar]"/>
+      <div class="infos">
+        <div
+          class="name"
+          :class="{'male': info?.sex === '1', 'female': info?.sex === '2'}"
+        >
+         <span>{{ info?.name + (info.type === '1' ? ' (在校学生)' : ' (职场人士)') }}</span>
+        </div>
+        <div class="other">
+          <div v-if="info?.sex">{{ info?.sex === '1' ? '男' : info?.sex === '2' ? '女' : '' }}<span class="ml">|</span></div>
+          <template v-if="info?.jobStatus">
+            <dict-tag showText :type="DICT_TYPE.MENDUNER_JOB_SEEK_STATUS" :value="info?.jobStatus" />
+            <span class="mr">|</span>
+          </template>
+          <template v-if="info?.expType">
+            <dict-tag showText :type="DICT_TYPE.MENDUNER_EXP_TYPE" :value="info?.expType" />
+            <span class="mr">|</span>
+          </template>
+          <template v-if="info?.eduType">
+            <dict-tag showText :type="DICT_TYPE.MENDUNER_EDUCATION_TYPE" :value="info?.eduType" />
+            <!-- <span>|</span> -->
+          </template>
+          <div class="firstWorkTime">首次工作时间:{{ info.firstWorkTime ? timesTampChange(info.firstWorkTime, 'Y-M-D') : '未填写' }}</div>
+        </div>
+      </div>
+    </div>
+    <div class="exp" v-if="info?.workExpList?.length">
+      <div class="ml">工作经验</div>
+      <el-timeline>
+        <template v-for="(val, index) in info.workExpList" :key="index">
+          <template v-if="info.showAll || index < 2">
+            <el-timeline-item center placement="top" color="#0bbd87">
+              <div class="timeline-item">
+                <div style="width: 20%;">
+                  <span>{{ val?.startTime ? timesTampChange(val.startTime, 'Y-M') : '未填写工作时间' }}</span>
+                  <span v-if="val?.startTime"> - {{ val.startTime ? val.endTime ? timesTampChange(val.endTime, 'Y-M') : '至今' : '' }}</span>
+                  <span v-if="val?.startTime"> ({{ val.startTime ? getTimeDifferenceInChinese(val.startTime, val.endTime) : '' }})</span>
+                </div>
+                <div class="timeline-item-name mx-3">{{ formatName(val.enterpriseName) || '' }}</div>
+                <div class="timeline-item-name">{{ formatName(val.positionName) || '' }}</div>
+              </div>
+            </el-timeline-item>
+          </template>
+        </template>
+				<div v-if="info.workExpList.length > 2" class="showBtn color-primary" @click.stop="info.showAll = Boolean(!info.showAll)">
+					{{ info.showAll ? '折叠' : '展开全部' }}
+					<!-- <div class="icons">
+            <i :class="info.showAll ? 'up' : 'down'"></i>
+            <i class="el-icon-down"></i>
+					</div> -->
+				</div>
+      </el-timeline>
+    </div>
+    <div class="button">
+      <el-button type="success" plain @click="personClick">{{ props.detailButTxt || '加入人才地图' }}</el-button>
+    </div>
+  </el-card>
+</template>
+<script setup>
+defineOptions({ name: 'PersonCard'})
+import { DICT_TYPE } from '@/utils/dict'
+import { timesTampChange, getTimeDifferenceInChinese } from '@/utils/transform/date'
+import { formatName } from '@/utils'
+const emit = defineEmits(['detail'])
+
+const props = defineProps({
+  data: Object,
+  detailButTxt: String,
+})
+
+const info = ref(props?.data)
+
+const personClick = () => {
+ emit('detail', info.value)
+}
+
+</script>
+<style lang="scss" scoped>
+.box {
+  position: relative;
+}
+.infoBox {
+  display: flex;
+  color: #666;
+  .infos {
+    margin-left: 10pxs;
+  }
+  .avatar {
+    margin-right: 20px;
+    border-radius: 50%;
+  }
+  .name {
+    font-size: 16px;
+    margin-bottom: 5px;
+    // color: #00b760;
+  }
+  .male {
+    color: #1867c0;
+  }
+  .female {
+    color: #ff5252;
+  }
+  .other {
+    display: flex;
+    .ml { margin-left: 10px; }
+    .mr { margin-right: 10px; }
+    .mx { margin: 0 10px; }
+    div {
+      margin-right: 10px;
+    }
+    .firstWorkTime { margin-left: 30px; }
+  }
+}
+.exp {
+  margin-top: 20px;
+  color: #666;
+  .timeline-item {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    width: 100%;
+    color: var(--color-666);
+    font-size: 13px;
+    .timeline-item-name {
+      width: 40%;
+    }
+    .mx-3 {
+      margin: 0 12px;
+    }
+  }
+  .el-timeline-item {
+    padding-bottom: 0;
+  }
+}
+.showBtn {
+	display: flex;
+	justify-content: center;
+	align-items: center;
+  color: #00b760;
+  cursor: pointer;
+	.icons {
+		display: flex;
+		flex-direction: column;
+		margin-left: 5px;
+		.icon {
+			line-height: 4px;
+		}
+	}
+}
+.button {
+  position: absolute;
+  top: 10px;
+  right: 10px;
+}
+</style>

+ 34 - 11
src/views/menduner/system/talentMap/maintenance/gather/components/search.vue

@@ -26,12 +26,19 @@
   </div>
   <!-- 列表 -->
   <ContentWrap>
-    <el-table v-loading="loading" :data="list" :stripe="true">
-      <!-- <el-table-column label="用户头像" align="center" prop="avatar" width="90px" fixed="left">
-        <template #default="scope">
-          <el-image v-if="scope.row.person?.avatar" class="h-80px w-80px" :src="scope.row.person?.avatar" lazy preview-teleported :preview-src-list="[scope.row.person?.avatar]" fit="contain" />
-        </template>
-      </el-table-column> -->
+    <div class="listBox" v-loading="loading">
+      <personCard
+        v-for="item of list"
+        :key="item.id"
+        :data="item"
+        class="item"
+        @detail="detail"
+      />
+    </div>
+  </ContentWrap>
+
+  <ContentWrap>
+    <!-- <el-table v-loading="loading" :data="list" :stripe="true">
       <el-table-column label="姓名" align="center" prop="person.name" fixed="left" />
       <el-table-column label="联系电话" align="center" prop="user.phone" width="120px" />
       <el-table-column label="求职状态" align="center" prop="person.jobStatus" width="130px">
@@ -54,7 +61,7 @@
           <el-button link type="primary" @click="handleDetail(scope.row)">{{ detailButTxt || '详情'}}</el-button>
         </template>
       </el-table-column>
-    </el-table>
+    </el-table> -->
     <!-- 分页 -->
     <Pagination
       :total="total"
@@ -71,6 +78,7 @@ defineOptions({ name: 'TalentMapSearch' })
 import { TalentMap } from '@/api/menduner/system/talentMap'
 import { PersonInfoApi } from '@/api/menduner/system/person'
 import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
+import personCard from './personCard.vue'
 
 const emit = defineEmits(['detail'])
 const props = defineProps({
@@ -86,7 +94,7 @@ const list = ref([]) // 列表的数据
 const total = ref(0) // 列表的总页数
 const queryParams = reactive({
   pageNo: 1,
-  pageSize: 10,
+  pageSize: 5,
   name: '',
   phone: undefined
 })
@@ -97,7 +105,13 @@ const getList = async () => {
   loading.value = true
   try {
     const data = await PersonInfoApi.getPersonInfoPage(queryParams)
-    list.value = data?.list || []
+    list.value = data?.list.map(e => {
+      return {
+        ...e.person,
+        // Boolean(exp.year) || Boolean(exp.enterpriseName) || Boolean(exp.positionName)
+        workExpList: e.work ? [e.work] : []
+      }
+    }) || []
     total.value = data?.total || 0
   } finally {
     loading.value = false
@@ -121,8 +135,8 @@ const resetQuery = () => {
   handleQuery()
 }
 
-const handleDetail = (row) => {
-  emit('detail', { id: row.person.id, userId: row.person.userId })
+const detail = (row) => {
+  emit('detail', { id: row.id, userId: row.userId })
 }
 
 /** 初始化 **/
@@ -145,4 +159,13 @@ if (!props.paramsVerify) getList()
   margin-bottom: 10px;
   color: #e6a23c;
 }
+.listBox {
+  min-height: 200px;
+  max-height: calc(100vh - 400px);
+  overflow-y: auto;
+  padding: 10px;
+  .item {
+    margin-bottom: 10px;
+  }
+}
 </style>

+ 5 - 12
src/views/menduner/system/talentMap/maintenance/gather/index2.vue

@@ -67,7 +67,7 @@
 
     <!-- 人员搜索 -->
     <Dialog :title="radioObject.menduner" v-model="openSearch" width="1200" @close="openSearch = false">
-      <Search @detail="handleDetail" :detailButTxt="detailButTxt" />
+      <Search @detail="handleDetail" />
     </Dialog>
 
     <!-- 解析文件上传 -->
@@ -134,17 +134,11 @@
             <el-tabs v-model="activeName" type="border-card">
               <el-tab-pane label="基本信息" name="info">
                 <el-card shadow="never" class="m-b-20px">
-                  <template #header>
+                  <!-- <template #header>
                     <CardTitle title="人才详情" />
-                  </template>
+                  </template> -->
                   <Info :id="id" :user-id="userId" />
-                </el-card>
-                
-                <el-card shadow="never" class="m-b-20px">
-                  <template #header>
-                    <CardTitle title="工作经历" />
-                  </template>
-                  <Exp :user-id="userId" />
+                  <expExtend :user-id="userId" />
                 </el-card>
               </el-tab-pane>
 
@@ -250,7 +244,7 @@ import { useUpload } from '@/components/UploadFile/src/useUpload'
 import { commonApi } from '@/api/menduner/common'
 import { Base64 } from 'js-base64'
 import Info from '@/views/menduner/system/person/details/components/info.vue'
-import Exp from '@/views/menduner/system/person/details/components/exp.vue'
+import expExtend from '@/views/menduner/system/person/details/components/expExtend.vue'
 import Attachment from '@/views/menduner/system/person/details/components/attachment.vue'
 
 const baseUrl = import.meta.env.VITE_PREVIEW_URL
@@ -536,7 +530,6 @@ const cardUploadChange = (raw) => {
 }
 
 // 人员搜索
-const detailButTxt = '加入人才地图'
 const openSearch = ref(false)
 const id = ref(null)
 const userId = ref(null)