浏览代码

人才地图更新

Xiao_123 7 月之前
父节点
当前提交
285ef882e5

+ 3 - 0
.env.demo

@@ -12,6 +12,9 @@ VITE_UPLOAD_TYPE=server
 # 上传路径
 VITE_UPLOAD_URL='http://menduner.citupro.com:6868/admin-api/infra/file/upload' # 测试环境
 
+# 预览路径
+VITE_PREVIEW_URL = 'http://192.168.3.91:8012'
+
 # 接口地址
 VITE_API_URL=/admin-api
 

+ 3 - 0
.env.dev

@@ -11,6 +11,9 @@ VITE_UPLOAD_TYPE=server
 # 上传路径
 VITE_UPLOAD_URL='/admin-api/infra/file/upload'
 
+# 预览路径
+VITE_PREVIEW_URL = 'http://192.168.3.91:8012'
+
 # 接口地址
 VITE_API_URL=/admin-api
 

+ 3 - 0
.env.local

@@ -12,6 +12,9 @@ VITE_UPLOAD_TYPE=server
 # 上传路径
 VITE_UPLOAD_URL='https://www.menduner.com:1443/admin-api/infra/file/upload' # 生产环境
 
+#文件预览
+VITE_PREVIEW_URL = 'https://kkfileview.menduner.com/'
+
 # 接口地址
 VITE_API_URL=/admin-api
 

+ 68 - 3
src/api/menduner/system/talentMap/index.ts

@@ -3,12 +3,77 @@ import request from '@/config/axios'
 // 人才地图 API
 export const TalentMap = {
   // 简历解析
-  resumeParse: async (params) => {
+  resumeParse: async (params: any) => {
     return await request.get({ url: `/menduner/system/online/resume/parser`, params })
   },
 
   // 获得人才地图-人才基本信息分页
-  getTalentMapInfoPage: async (params) => {
+  getTalentMapInfoPage: async (params: any) => {
     return await request.get({ url: `/menduner/system/talent-map-info/page`, params })
-  }
+  },
+
+  // 创建人才地图-新增人才的信息
+  createTalentMapInfo: async (data: any) => {
+    return await request.post({ url: `/menduner/system/talent-map-info/add`, data })
+  },
+
+  // 删除人才地图-人才基本信息
+  deleteTalentMap: async (id: number) => {
+    return await request.delete({ url: `/menduner/system/talent-map-info/delete?id=` + id })
+  },
+
+  // 获得人才地图-人才基本信息
+  getTalentMapDetails: async (id: number) => {
+    return await request.get({ url: `/menduner/system/talent-map-info/get?id=` + id })
+  },
+
+  // 更新人才地图-人才基本信息
+  updateTalentMapInfo: async (data: any) => {
+    return await request.put({ url: `/menduner/system/talent-map-info/update`, data })
+  },
+
+  // 创建人才地图-人才教育经历
+  createTalentMapEdu: async (data: any) => {
+    return await request.post({ url: `/menduner/system/talent-map-edu-exp/create`, data })
+  },
+
+  // 更新人才地图-人才教育经历
+  updateTalentMapEdu: async (data: any) => {
+    return await request.put({ url: `/menduner/system/talent-map-edu-exp/update`, data })
+  },
+
+  // 删除人才地图-人才教育经历
+  deleteTalentMapEdu: async (id: number) => {
+    return await request.delete({ url: `/menduner/system/talent-map-edu-exp/delete?id=` + id })
+  },
+
+  // 创建人才地图-人才工作经历
+  createTalentMapExp: async (data: any) => {
+    return await request.post({ url: `/menduner/system/talent-map-work-exp/create`, data })
+  },
+
+  // 更新人才地图-人才工作经历
+  updateTalentMapExp: async (data: any) => {
+    return await request.put({ url: `/menduner/system/talent-map-work-exp/update`, data })
+  },
+
+  // 删除人才地图-人才工作经历
+  deleteTalentMapExp: async (id: number) => {
+    return await request.delete({ url: `/menduner/system/talent-map-work-exp/delete?id=` + id })
+  },
+
+  // 创建人才地图-人才培训经历
+  createTalentMapTrain: async (data: any) => {
+    return await request.post({ url: `/menduner/system/talent-map-train-exp/create`, data })
+  },
+
+  // 更新人才地图-人才培训经历
+  updateTalentMapTrain: async (data: any) => {
+    return await request.put({ url: `/menduner/system/talent-map-train-exp/update`, data })
+  },
+
+  // 删除人才地图-人才培训经历
+  deleteTalentMapTrain: async (id: number) => {
+    return await request.delete({ url: `/menduner/system/talent-map-train-exp/delete?id=` + id })
+  },
 }

+ 1 - 1
src/router/modules/remaining.ts

@@ -510,7 +510,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
     meta: { hidden: true },
     children: [
       {
-        path: 'talentMap/detail',
+        path: 'talentMap/detail/:id',
         name: 'TalentMapDetail',
         meta: {
           title: '人才详情',

+ 29 - 11
src/views/menduner/system/talentMap/details/components/edu.vue

@@ -24,7 +24,7 @@
       <el-table-column label="操作" align="center" prop="actions">
         <template #default="{ row, $index }">
           <el-button link type="primary" @click="handleEdit(row, $index)">编辑</el-button>
-          <el-button link type="danger" @click="handleDelete($index)">删除</el-button>
+          <el-button link type="danger" @click="handleDelete($index, row.id)">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -44,9 +44,8 @@
         <el-input v-model="formData.major" />
       </el-form-item>
       <el-form-item label="学历" prop="educationType" required>
-        <el-select v-model="formData.educationType" placeholder="请选择学历">
-          <el-option
-            v-for="dict in getIntDictOptions(DICT_TYPE.MENDUNER_EDUCATION_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value.toString()" />
+        <el-select v-model="formData.educationType" placeholder="请选择学历" @change="val => handleChangeSelect(val, 'MENDUNER_EDUCATION_TYPE', 'educationTypeStr')">
+          <el-option v-for="dict in getIntDictOptions(DICT_TYPE.MENDUNER_EDUCATION_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value.toString()" />
         </el-select>
       </el-form-item>
       <el-form-item label="开始日期" prop="startTime" required>
@@ -68,13 +67,15 @@ defineOptions({ name: 'TalentMapEdu'})
 import { cloneDeep } from 'lodash-es'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { timesTampChange } from '@/utils/transform/date'
+import { TalentMap } from '@/api/menduner/system/talentMap';
 
 const props = defineProps({
-  data: Array
+  modelValue: Array,
+  isEdit: Boolean
 })
 const list = ref([])
-watch(() => props.data, (newVal) => {
-  list.value = cloneDeep(newVal)
+watch(() => props.modelValue, (newVal) => {
+  list.value = newVal
 }, { deep: true })
 
 const message = useMessage() // 消息弹窗
@@ -111,19 +112,36 @@ const handleEdit = (row, index) => {
 }
 
 // 删除
-const handleDelete = async (index) => {
+const handleDelete = async (index, id) => {
   try {
     await message.delConfirm()
-    delete list.value[index]
+    if (props.isEdit) {
+      await TalentMap.deleteTalentMapEdu(id)
+      list.value.splice(index, 1)
+      message.success('删除成功')
+    } else list.value.splice(index, 1)
   } catch {}
 }
 
+// 下拉框选择
+const handleChangeSelect = (val, dictType, labelKey) => {
+  const dict = getIntDictOptions(DICT_TYPE[dictType])
+  const obj = dict.find(e => e.value.toString() === val)
+  formData.value[labelKey] = obj?.label
+}
+
 const submitForm = async () => {
   await formRef.value.validate()
-  type.value === 'add' ? list.value.push(formData.value) : list.value[editIndex.value] = formData.value
+
+  if (props.isEdit) {
+    const data = type.value === 'add' ? await TalentMap.createTalentMapEdu(formData.value) : await TalentMap.updateTalentMapEdu(formData.value)
+    message.success(type.value === 'add' ? '添加成功' : '编辑成功')
+    if (type.value === 'add') formData.value.id = data
+    type.value === 'add' ? list.value.push(formData.value) : list.value[editIndex.value] = formData.value
+  } else type.value === 'add' ? list.value.push(formData.value) : list.value[editIndex.value] = formData.value
+
   formData.value = {}
   editIndex.value = 0
   dialogVisible.value = false
-  console.log(list.value, 'submit')
 }
 </script>

+ 20 - 8
src/views/menduner/system/talentMap/details/components/exp.vue

@@ -19,7 +19,7 @@
       <el-table-column label="操作" align="center" prop="actions">
         <template #default="{ row, $index }">
           <el-button link type="primary" @click="handleEdit(row, $index)">编辑</el-button>
-          <el-button link type="danger" @click="handleDelete($index)">删除</el-button>
+          <el-button link type="danger" @click="handleDelete($index, row.id)">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -60,13 +60,15 @@
 defineOptions({ name: 'TalentMapExp'})
 import { cloneDeep } from 'lodash-es'
 import { timesTampChange } from '@/utils/transform/date'
+import { TalentMap } from '@/api/menduner/system/talentMap';
 
 const props = defineProps({
-  data: Array
+  modelValue: Array,
+  isEdit: Boolean
 })
 const list = ref([])
-watch(() => props.data, (newVal) => {
-  list.value = cloneDeep(newVal)
+watch(() => props.modelValue, (newVal) => {
+  list.value = newVal
 }, { deep: true })
 
 const message = useMessage() // 消息弹窗
@@ -102,21 +104,31 @@ const handleEdit = (row, index) => {
 }
 
 // 删除
-const handleDelete = async (index) => {
+const handleDelete = async (index, id) => {
   try {
     await message.delConfirm()
-    delete list.value[index]
+    if (props.isEdit) {
+      await TalentMap.deleteTalentMapExp(id)
+      list.value.splice(index, 1)
+      message.success('删除成功')
+    } else list.value.splice(index, 1)
   } catch {}
 }
 
 const submitForm = async () => {
   await formRef.value.validate()
   if (!sofar.value && !formData.value.endTime) return message.warning('请选择结束时间')
-  type.value === 'add' ? list.value.push(formData.value) : list.value[editIndex.value] = formData.value
+
+  if (props.isEdit) {
+    const data = type.value === 'add' ? await TalentMap.createTalentMapExp(formData.value) : await TalentMap.updateTalentMapExp(formData.value)
+    message.success(type.value === 'add' ? '添加成功' : '编辑成功')
+    if (type.value === 'add') formData.value.id = data
+    type.value === 'add' ? list.value.push(formData.value) : list.value[editIndex.value] = formData.value
+  } else type.value === 'add' ? list.value.push(formData.value) : list.value[editIndex.value] = formData.value
+  
   formData.value = {}
   editIndex.value = 0
   dialogVisible.value = false
-  console.log(list.value, 'submit')
 }
 
 const handleSofar = (e) => {

+ 46 - 48
src/views/menduner/system/talentMap/details/components/info.vue

@@ -3,22 +3,16 @@
     <template #header>
       <CardTitle title="基本信息" />
     </template>
-    <el-form
-      ref="formRef"
-      :model="formData"
-      :rules="formRules"
-      label-width="100px"
-    >
+    <el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading">
       <el-form-item label="用户头像" prop="avatar">
         <UploadImg v-model="formData.avatar" height="140px" />
       </el-form-item>
       <el-form-item label="用户姓名" prop="name">
-        <el-input v-model="formData.name" />
+        <el-input v-model="formData.name"/>
       </el-form-item>
       <el-form-item label="性别" prop="sex">
-        <el-select v-model="formData.sex" placeholder="请选择性别">
-          <el-option
-            v-for="dict in getIntDictOptions(DICT_TYPE.MENDUNER_SEX)" :key="dict.value" :label="dict.label" :value="dict.value.toString()" />
+        <el-select v-model="formData.sex" placeholder="请选择性别" @change="val => handleChangeSelect(val, 'MENDUNER_SEX', 'sexStr')">
+          <el-option v-for="dict in getIntDictOptions(DICT_TYPE.MENDUNER_SEX)" :key="dict.value" :label="dict.label" :value="dict.value.toString()"/>
         </el-select>
       </el-form-item>
       <el-form-item label="联系电话" prop="phone">
@@ -31,21 +25,18 @@
         <el-date-picker v-model="formData.birthday" type="date" :disabledDate="disabledDates" value-format="x" placeholder="选择出生日期" />
       </el-form-item>
       <el-form-item label="学历" prop="eduType">
-        <el-select v-model="formData.eduType" placeholder="请选择学历">
-          <el-option
-            v-for="dict in getIntDictOptions(DICT_TYPE.MENDUNER_EDUCATION_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value.toString()" />
+        <el-select v-model="formData.eduType" placeholder="请选择学历" @change="val => handleChangeSelect(val, 'MENDUNER_EDUCATION_TYPE', 'eduTypeStr')">
+          <el-option v-for="dict in getIntDictOptions(DICT_TYPE.MENDUNER_EDUCATION_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value.toString()" />
         </el-select>
       </el-form-item>
       <el-form-item label="工作经验" prop="expType">
-        <el-select v-model="formData.expType" placeholder="请选择工作经验">
-          <el-option
-            v-for="dict in getIntDictOptions(DICT_TYPE.MENDUNER_EXP_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value.toString()" />
+        <el-select v-model="formData.expType" placeholder="请选择工作经验" @change="val => handleChangeSelect(val, 'MENDUNER_EXP_TYPE', 'expTypeStr')">
+          <el-option v-for="dict in getIntDictOptions(DICT_TYPE.MENDUNER_EXP_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value.toString()" />
         </el-select>
       </el-form-item>
       <el-form-item label="婚姻状况" prop="maritalStatus">
-        <el-select v-model="formData.maritalStatus" placeholder="请选择婚姻状况">
-          <el-option
-            v-for="dict in getIntDictOptions(DICT_TYPE.MENDUNER_MARITAL_STATUS)" :key="dict.value" :label="dict.label" :value="dict.value.toString()" />
+        <el-select v-model="formData.maritalStatus" placeholder="请选择婚姻状况" @change="val => handleChangeSelect(val, 'MENDUNER_MARITAL_STATUS', 'maritalStatusStr')">
+          <el-option v-for="dict in getIntDictOptions(DICT_TYPE.MENDUNER_MARITAL_STATUS)" :key="dict.value" :label="dict.label" :value="dict.value.toString()" />
         </el-select>
       </el-form-item>
       <el-form-item label="首次工作时间" prop="firstWorkTime">
@@ -54,13 +45,16 @@
       <el-form-item label="现居城市" prop="areaId">
         <el-cascader v-model="formData.areaId" :options="areaTreeData" :props="{ label: 'name', value: 'id', emitPath: false, checkStrictly: true }"  class="!w-240px" />
       </el-form-item>
+      <el-form-item label="意向城市" prop="interestedAreaIdList">
+        <el-cascader v-model="formData.interestedAreaIdList" :options="areaTreeData" :props="{ label: 'name', value: 'id', emitPath: false, checkStrictly: true, multiple: true }"  class="!w-240px" />
+      </el-form-item>
       <el-form-item label="个人优势" prop="advantage">
         <el-input v-model="formData.advantage" :rows="8" type="textarea" placeholder="请输入您的个人优势" />
       </el-form-item>
     </el-form>
-    <!-- <div class="text-right mt-2">
-      <el-button @click="submitForm" type="primary">保 存</el-button>
-    </div> -->
+    <div v-if="isEdit" class="text-right">
+      <el-button type="primary" @click="handleSave">保 存</el-button>
+    </div>
   </el-card>
 </template>
 
@@ -70,37 +64,24 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { cloneDeep } from 'lodash-es'
 import { timesTampChange } from '@/utils/transform/date'
 import { getDict } from '@/hooks/web/useDictionaries'
+import { TalentMap } from '@/api/menduner/system/talentMap';
 
 const props = defineProps({
-  data: Object
+  modelValue: Object,
+  isEdit: Boolean
 })
 
+const message = useMessage() // 消息弹窗
 const formRef = ref()
-const formData = ref({
-  avatar: undefined,
-  sex: undefined,
-  phone: undefined,
-  email: undefined,
-  birthday: undefined,
-  eduType: undefined,
-  expType: undefined,
-  maritalStatus: undefined,
-  major: undefined,
-  firstWorkTime: undefined,
-  areaId: undefined,
-  advantage: undefined
-})
+const formData = ref({})
+const formLoading = ref(false)
 const formRules = reactive({
-  // schoolName: [{ required: true, message: '学校名称不能为空', trigger: 'blur' }],
-  // major: [{ required: true, message: '专业名称不能为空', trigger: 'blur' }],
-  // educationType: [{ required: true, message: '学历不能为空', trigger: 'blur' }],
-  // startDate: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
-  // endDate: [{ required: true, message: '结束时间不能为空', trigger: 'blur' }]
+  // schoolName: [{ required: true, message: '学校名称不能为空', trigger: 'blur' }]
 })
 
-watch(() => props.data, (newVal) => {
-  formData.value = cloneDeep(newVal)
-}, { deep: true })
+watch(() => props.modelValue, (newVal) => {
+  formData.value = newVal
+}, { deep: true, immediate: true })
 
 // 未来的时间不可选
 const disabledDates = (date) => {
@@ -122,7 +103,24 @@ const getDictData = async () => {
 }
 getDictData()
 
-// const submitForm = async () => {
-//   await formRef.value.validate()
-// }
+// 下拉框选择
+const handleChangeSelect = (val, dictType, labelKey) => {
+  const dict = getIntDictOptions(DICT_TYPE[dictType])
+  const obj = dict.find(e => e.value.toString() === val)
+  formData.value[labelKey] = obj?.label
+}
+
+const handleSave = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  formLoading.value = true
+  try {
+    await TalentMap.updateTalentMapInfo(formData.value)
+    message.success('保存成功')
+  } catch (error) {
+    console.log(error)
+  } finally {
+    formLoading.value = false
+  }
+}
 </script>

+ 9 - 7
src/views/menduner/system/talentMap/details/components/jobIntention.vue

@@ -41,17 +41,17 @@ import { cloneDeep } from 'lodash-es'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { timesTampChange } from '@/utils/transform/date'
 
+const emit = defineEmits(['update:modelValue'])
 const props = defineProps({
-  data: Array
+  modelValue: Array
 })
 const list = ref([])
-watch(() => props.data, (newVal) => {
+watch(() => props.modelValue, (newVal) => {
   if (!newVal || !newVal.length) return list.value = []
-  const arr = newVal.map(e => {
+  list.value = newVal.map(e => {
     return { name: e }
   })
-  list.value = cloneDeep(arr)
-}, { deep: true, immediate: true })
+}, { deep: true })
 
 const message = useMessage() // 消息弹窗
 const editIndex = ref(0)
@@ -81,7 +81,8 @@ const handleEdit = (row, index) => {
 const handleDelete = async (index) => {
   try {
     await message.delConfirm()
-    delete list.value[index]
+    list.value.splice(index, 1)
+    emit('update:modelValue', list.value.length ? list.value.map(e => e.name) : [])
   } catch {}
 }
 
@@ -91,6 +92,7 @@ const submitForm = async () => {
   formData.value = {}
   editIndex.value = 0
   dialogVisible.value = false
-  console.log(list.value, 'jobIntention')
+
+  emit('update:modelValue', list.value.map(e => e.name))
 }
 </script>

+ 8 - 8
src/views/menduner/system/talentMap/details/components/search.vue

@@ -9,28 +9,28 @@
       label-width="68px"
     >
       <el-form-item label="姓名" prop="name">
-        <el-input v-model="queryParams.name" placeholder="请输入姓名" clearable @keyup.enter="handleQuery" />
+        <el-input v-model="queryParams.name" placeholder="请输入姓名" clearable @keyup.enter="handleQuery" class="!w-130px" />
       </el-form-item>
       <el-form-item label="联系电话" prop="phone">
-        <el-input v-model="queryParams.phone" placeholder="请输入联系电话" clearable @keyup.enter="handleQuery" />
+        <el-input v-model="queryParams.phone" placeholder="请输入联系电话" clearable @keyup.enter="handleQuery" class="!w-130px" />
       </el-form-item>
       <el-form-item label="性别" prop="sex">
-        <el-select v-model="queryParams.sex" placeholder="请选择性别" clearable class="!w-192px">
+        <el-select v-model="queryParams.sex" placeholder="请选择性别" clearable class="!w-130px">
           <el-option v-for="dict in getIntDictOptions(DICT_TYPE.MENDUNER_SEX)" :key="dict.value" :label="dict.label" :value="dict.value" />
         </el-select>
       </el-form-item>
       <el-form-item label="求职状态" prop="jobStatus">
-        <el-select v-model="queryParams.jobStatus" placeholder="请选择求职状态" clearable class="!w-192px">
+        <el-select v-model="queryParams.jobStatus" placeholder="请选择求职状态" clearable class="!w-130px">
           <el-option v-for="dict in getIntDictOptions(DICT_TYPE.MENDUNER_JOB_SEEK_STATUS)" :key="dict.value" :label="dict.label" :value="dict.value" />
         </el-select>
       </el-form-item>
       <el-form-item label="学历" prop="eduType">
-        <el-select v-model="queryParams.eduType" placeholder="请选择学历" clearable class="!w-192px">
+        <el-select v-model="queryParams.eduType" placeholder="请选择学历" clearable class="!w-130px">
           <el-option v-for="dict in getIntDictOptions(DICT_TYPE.MENDUNER_EDUCATION_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value" />
         </el-select>
       </el-form-item>
       <el-form-item label="工作经验" prop="expType">
-        <el-select v-model="queryParams.expType" placeholder="请选择工作经验" clearable class="!w-192px">
+        <el-select v-model="queryParams.expType" placeholder="请选择工作经验" clearable class="!w-130px">
           <el-option v-for="dict in getIntDictOptions(DICT_TYPE.MENDUNER_EXP_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value" />
         </el-select>
       </el-form-item>
@@ -66,7 +66,7 @@
           <dict-tag :type="DICT_TYPE.MENDUNER_EXP_TYPE" :value="scope.row.person?.expType" />
         </template>
       </el-table-column>
-      <el-table-column label="操作" align="center">
+      <el-table-column label="操作" align="center" fixed="right" min-width="60">
         <template #default="scope">
           <el-button link type="primary" @click="handleDetail(scope.row)">详情</el-button>
         </template>
@@ -99,7 +99,7 @@ const total = ref(0) // 列表的总页数
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  name: '樊登登',
+  name: '',
   phone: undefined,
   expType: undefined,
   eduType: undefined,

+ 20 - 8
src/views/menduner/system/talentMap/details/components/training.vue

@@ -19,7 +19,7 @@
       <el-table-column label="操作" align="center" prop="actions">
         <template #default="{ row, $index }">
           <el-button link type="primary" @click="handleEdit(row, $index)">编辑</el-button>
-          <el-button link type="danger" @click="handleDelete($index)">删除</el-button>
+          <el-button link type="danger" @click="handleDelete($index, row.id)">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -59,13 +59,15 @@
 defineOptions({ name: 'TalentMapTraining'})
 import { cloneDeep } from 'lodash-es'
 import { timesTampChange } from '@/utils/transform/date'
+import { TalentMap } from '@/api/menduner/system/talentMap';
 
 const props = defineProps({
-  data: Array
+  modelValue: Array,
+  isEdit: Boolean
 })
 const list = ref([])
-watch(() => props.data, (newVal) => {
-  list.value = cloneDeep(newVal)
+watch(() => props.modelValue, (newVal) => {
+  list.value = newVal
 }, { deep: true })
 
 const message = useMessage() // 消息弹窗
@@ -99,19 +101,29 @@ const handleEdit = (row, index) => {
 }
 
 // 删除
-const handleDelete = async (index) => {
+const handleDelete = async (index, id) => {
   try {
     await message.delConfirm()
-    delete list.value[index]
+    if (props.isEdit) {
+      await TalentMap.deleteTalentMapTrain(id)
+      list.value.splice(index, 1)
+      message.success('删除成功')
+    } else list.value.splice(index, 1)
   } catch {}
 }
 
 const submitForm = async () => {
   await formRef.value.validate()
-  type.value === 'add' ? list.value.push(formData.value) : list.value[editIndex.value] = formData.value
+
+  if (props.isEdit) {
+    const data = type.value === 'add' ? await TalentMap.createTalentMapTrain(formData.value) : await TalentMap.updateTalentMapTrain(formData.value)
+    message.success(type.value === 'add' ? '添加成功' : '编辑成功')
+    if (type.value === 'add') formData.value.id = data
+    type.value === 'add' ? list.value.push(formData.value) : list.value[editIndex.value] = formData.value
+  } else type.value === 'add' ? list.value.push(formData.value) : list.value[editIndex.value] = formData.value
+
   formData.value = {}
   editIndex.value = 0
   dialogVisible.value = false
-  console.log(list.value, 'submit')
 }
 </script>

+ 164 - 0
src/views/menduner/system/talentMap/details/defaultData.js

@@ -0,0 +1,164 @@
+export default {
+    "person": {
+        "id": null,
+        "userId": null,
+        "resumeUrl": "",
+        "name": "",
+        "foreignName": null,
+        "sex": "",
+        "sexStr": "",
+        "avatar": "",
+        "phone": "",
+        "email": "",
+        "qq": "",
+        "wxCode": "",
+        "birthday": null,
+        "partyMember": false,
+        "nationality": "",
+        "maritalStatus": "",
+        "maritalStatusStr": "",
+        "areaId": null,
+        "areaStr": "",
+        "regId": null,
+        "regStr": "",
+        "jobType": "",
+        "jobTypeStr": "",
+        "jobStatus": "",
+        "jobStatusStr": "",
+        "firstWorkTime": null,
+        "advantage": "",
+        "height": "",
+        "weight": "",
+        "expType": "",
+        "expTypeStr": "",
+        "eduType": "",
+        "eduTypeStr": "",
+        "prepareExp": null,
+        "interestedAreaIdList": null,
+        "tagList": null,
+        "jobInterestedList": [],
+        "spouseHometownId": null,
+        "spouseHometownStr": null,
+        "childAge": null,
+        "createTime": null
+    },
+    "eduList": [],
+    "workList": [],
+    "trainList": [],
+    "resume": {
+        "name": "",
+        "surname": "",
+        "gender": "",
+        "genderInf": "",
+        "age": null,
+        "age_inf": null,
+        "height": "",
+        "weight": "",
+        "maritalStatus": "",
+        "birthday": "",
+        "hukouAddress": "",
+        "hukouAddressNorm": "",
+        "hometownAddress": "",
+        "hometownAddressNorm": "",
+        "idCard": "",
+        "race": "",
+        "nationality": "",
+        "politStatus": "",
+        "bloodType": "",
+        "starSign": "",
+        "languages": "",
+        "englishLevel": "",
+        "computerLevel": "",
+        "blog": "",
+        "applyJob": null,
+        "applyCpy": null,
+        "workYear": "",
+        "workYearNorm": "",
+        "workYearInf": "",
+        "workStartTime": "",
+        "workStartTimeInf": "",
+        "workPosition": "",
+        "workPosTypeP": "",
+        "workCompany": "",
+        "workIndustry": "",
+        "workStatus": "",
+        "workSalary": "",
+        "workSalaryMin": "",
+        "workSalaryMax": "",
+        "workLocation": "",
+        "workLocationNorm": "",
+        "workJobNature": "",
+        "hasOverseaEdu": "",
+        "hasOverseaExp": "",
+        "gradTime": "",
+        "college": "",
+        "collegeType": "",
+        "collegeRank": "",
+        "collegeDept": "",
+        "major": "",
+        "degree": "",
+        "recruit": "",
+        "email": "",
+        "phone": "",
+        "virtualPhone": null,
+        "virtualPhoneTime": null,
+        "qq": "",
+        "weixin": "",
+        "postalCode": "",
+        "city": "",
+        "cityNorm": "",
+        "livingAddress": "",
+        "livingAddressNorm": "",
+        "expectJob": "",
+        "expectCpy": "",
+        "expectSalary": "",
+        "expectSalaryMin": "",
+        "expectSalaryMax": "",
+        "expectIndustry": "",
+        "expectTime": "",
+        "expectJNature": "",
+        "expectJStatus": "",
+        "expectJLocation": "",
+        "expectJLocationNorm": "",
+        "resumeType": "0",
+        "resumeSource": "",
+        "resumeId": "",
+        "resumeName": "",
+        "resumeParseTime": "",
+        "resumeUpdateTime": "",
+        "resumeIntegrity": "",
+        "avatarUrl": "",
+        "avatarData": "",
+        "contBasicInfo": "",
+        "contExpectJob": "",
+        "contEducation": "",
+        "contJobExp": "",
+        "contProjExp": "",
+        "contInternship": "",
+        "contSocialExp": "",
+        "contCampusExp": "",
+        "contJobSkill": "",
+        "contMyDesc": "",
+        "contHobby": "",
+        "contLanguage": "",
+        "contCertificate": "",
+        "contAward": "",
+        "contTraining": "",
+        "contCourse": "",
+        "contResearch": "",
+        "contPublications": null,
+        "contMyProject": null,
+        "contCoverLetter": "",
+        "contExtraInfo": "",
+        "rawText": "",
+        "educationObjs": [],
+        "jobExpObjs": [],
+        "socialExpObjs": [],
+        "projExpObjs": [],
+        "trainingObjs": [],
+        "skillsObjs": [],
+        "langObjs": [],
+        "certObjs": [],
+        "allCertObjs": []
+    }
+}

+ 74 - 22
src/views/menduner/system/talentMap/details/index.vue

@@ -1,7 +1,7 @@
 <template>
-  <div>
+  <div v-loading="saveLoading">
     <el-row :gutter="10">
-      <el-col :span="14">
+      <el-col :span="12">
         <el-tabs type="border-card">
           <el-tab-pane label="简历解析" v-loading="loading">
             <div v-if="fileUrl">
@@ -42,15 +42,19 @@
         </el-tabs>
       </el-col>
 
-      <el-col :span="10">
+      <el-col :span="12">
         <div :style="{'height': height, 'overflow-y': 'auto'}">
           <el-tabs type="border-card">
             <el-tab-pane label="简历解析内容">
-              <Info :data="result?.person"/>
-              <JobIntention :data="result?.person?.jobInterestedList" />
-              <Edu :data="result?.eduList"  />
-              <Exp :data="result?.workList" />
-              <Training :data="result?.trainList" />
+              <Info v-model="result.person" :isEdit="isEdit" />
+              <JobIntention v-model="result.person.jobInterestedList" />
+              <Edu v-model="result.eduList" :isEdit="isEdit"  />
+              <Exp v-model="result.workList" :isEdit="isEdit" />
+              <Training v-model="result.trainList" :isEdit="isEdit" />
+              <div class="text-center m-t-30px">
+                <el-button @click="push('/menduner/talentMap')">取 消</el-button>
+                <el-button v-if="id === 'add'" type="primary" @click="handleSave">保 存</el-button>
+              </div>
             </el-tab-pane>
             <el-tab-pane label="标签">
               <el-card shadow="never">
@@ -107,6 +111,9 @@ import Tags from './components/tags.vue'
 import TagsRecommend from './components/tagsRecommend.vue'
 import Search from './components/search.vue'
 
+import DefaultData from './defaultData'
+
+const { push } = useRouter()
 const addNewTag = ref(false)
 const newTagText = ref('')
 const saveTags = () => {
@@ -114,8 +121,9 @@ const saveTags = () => {
 }
 
 const loading = ref(false)
-const result = ref({})
-const fileUrl = ref('https://minio.menduner.com/dev/person/229988673960153088/attachment/ee3eb21f45e13ede3557a03d18585ed80c5b4212ac5634e3436e309afaa8fe6a.pdf')
+const saveLoading = ref(false)
+const result = ref(DefaultData)
+const fileUrl = ref('') // https://minio.menduner.com/dev/person/229988673960153088/attachment/ee3eb21f45e13ede3557a03d18585ed80c5b4212ac5634e3436e309afaa8fe6a.pdf
 const uploadRef = ref()
 const fileList = ref([])
 const data = ref({ path: '' })
@@ -127,33 +135,58 @@ const height = ref(0)
 const { currentRoute } = useRouter() // 路由
 const route = useRoute()
 const { id } = route.params
+const baseUrl = import.meta.env.VITE_PREVIEW_URL
+const isEdit = ref(false)
+
+// 获取人才详情
+const getTalentMap = async () => {
+  result.value = DefaultData
+  loading.value = true
+  try {
+    const data = await TalentMap.getTalentMapDetails(id)
+    if (!data || !Object.keys(data).length) return
+    if (data.person?.resumeUrl) {
+      fileUrl.value = !data.person?.resumeUrl.includes('.pdf') ?  `${baseUrl}/onlinePreview?url=${encodeURIComponent(Base64.encode(data.person?.resumeUrl))}` : data.person?.resumeUrl
+    }
+    result.value = data
+    result.value.person.interestedAreaIdList = result.value.person.interestedAreaIdList && result.value.person.interestedAreaIdList.length ? result.value.person.interestedAreaIdList.map(e => Number(e)) : []
+  } catch (error) {
+    console.log(error)
+  } finally {
+    loading.value = false
+  }
+}
+
 onMounted(async () => {
   height.value = document.documentElement.clientHeight + 'px'
+  if (id && id !== 'add') {
+    isEdit.value = true
+    await getTalentMap()
+  } else result.value = DefaultData
 })
 
 // 简历解析
 const getResumeParser = async (url) => {
+  result.value = DefaultData
   loading.value = true
   try {
     const data = await commonApi.resumeParser({ fileUrl: url })
-    console.log(data, 'resumeParserData')
     result.value = data
+    result.value.person.interestedAreaIdList = result.value.person.interestedAreaIdList && result.value.person.interestedAreaIdList.length ? result.value.person.interestedAreaIdList.map(e => Number(e)) : []
   } finally {
     loading.value = false
   }
 }
-getResumeParser(fileUrl.value)
 
 // 文件上传
-const baseUrl = import.meta.env.VITE_PREVIEW_URL
 const handleChange = async (file) => {
-  // data.value.path = file.name
-  // unref(uploadRef)?.submit()
-  // if (!fileList.value.length) return
+  data.value.path = file.name
+  unref(uploadRef)?.submit()
+  if (!fileList.value.length) return
 
-  // const url = fileList.value[0].response.data
-  // fileUrl.value = !url.includes('.pdf') ?  `${baseUrl}/onlinePreview?url=${encodeURIComponent(Base64.encode(url))}` : url
-  // await getResumeParser(url)
+  const url = fileList.value[0].response.data
+  fileUrl.value = !url.includes('.pdf') ?  `${baseUrl}/onlinePreview?url=${encodeURIComponent(Base64.encode(url))}` : url
+  await getResumeParser(url)
 }
 
 /** 上传错误提示 */
@@ -172,14 +205,15 @@ const submitFormSuccess = () => {
 }
 
 // 重新上传简历
-const handleResetUpload = () => {
+const handleResetUpload = async () => {
+  await message.confirm('是否确定重新上传简历?确定后将清空当前信息')
   fileUrl.value = ''
   data.value.path = ''
   fileList.value = []
-  result.value = {}
+  result.value = DefaultData
 }
 
-// 查看详情
+// 搜索-查看详情
 const apiArr = [
   { api: PersonInfoApi.getPersonDetails, key: 'person' },
   { api: PersonInfoApi.getPersonEduPage, key: 'eduList', type: 'array' },
@@ -190,7 +224,25 @@ const handleDetail = async (id, userId) => {
   if (!id) return message.warning('请先选择人才!')
   apiArr.forEach(async (val) => {
     const data = await val.api(val.type === 'array' ? { pageSize: 100, pageNo: 1, userId } : id)
+    if (!val.type) delete data.id
     result.value[val.key] = val.type === 'array' ? data.list : data || {}
   })
 }
+
+// 新增人才
+const handleSave = async () => {
+  if (result.value?.resume) delete result.value.resume
+  console.log(result.value, 'result-add')
+  saveLoading.value = true
+  try {
+    await TalentMap.createTalentMapInfo(result.value)
+    message.success('新增成功!')
+    result.value = DefaultData
+    push('/menduner/talentMap')
+  } catch (error) {
+    console.log(error)
+  } finally {
+    saveLoading.value = false
+  }
+}
 </script>

+ 82 - 11
src/views/menduner/system/talentMap/index.vue

@@ -9,7 +9,31 @@
       label-width="68px"
     >
       <el-form-item label="姓名" prop="name">
-        <el-input v-model="queryParams.name" placeholder="请输入姓名" clearable @keyup.enter="handleQuery" class="!w-240px" />
+        <el-input v-model="queryParams.name" placeholder="请输入姓名" clearable @keyup.enter="handleQuery" class="!w-180px" />
+      </el-form-item>
+      <el-form-item label="联系电话" prop="phone">
+        <el-input v-model="queryParams.phone" placeholder="请输入联系电话" clearable @keyup.enter="handleQuery" class="!w-180px" />
+      </el-form-item>
+      <el-form-item label="邮箱" prop="email">
+        <el-input v-model="queryParams.email" placeholder="请输入邮箱" clearable @keyup.enter="handleQuery" class="!w-180px" />
+      </el-form-item>
+      <el-form-item label="性别" prop="sex">
+        <el-select v-model="queryParams.sex" placeholder="请选择性别" class="!w-180px">
+          <el-option v-for="dict in getIntDictOptions(DICT_TYPE.MENDUNER_SEX)" :key="dict.value" :label="dict.label" :value="dict.value"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="学历" prop="eduType">
+        <el-select v-model="queryParams.eduType" placeholder="请选择学历" class="!w-180px">
+          <el-option v-for="dict in getIntDictOptions(DICT_TYPE.MENDUNER_EDUCATION_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="工作经验" prop="expType">
+        <el-select v-model="queryParams.expType" placeholder="请选择工作经验" class="!w-180px">
+          <el-option v-for="dict in getIntDictOptions(DICT_TYPE.MENDUNER_EXP_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="所在城市" prop="areaId">
+        <el-cascader v-model="queryParams.areaId" :options="areaTreeData" :props="{ label: 'name', value: 'id', emitPath: false, checkStrictly: true }"  class="!w-180px" />
       </el-form-item>
       <el-form-item>
         <el-button @click="handleQuery"><Icon icon="ep:search" /> 搜索</el-button>
@@ -24,12 +48,34 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table v-loading="loading" :data="list" :stripe="true">
-      <el-table-column label="姓名" align="center" prop="name" />
-      <el-table-column label="性别" align="center" prop="sexStr" />
-      <el-table-column label="操作" align="center">
+      <el-table-column label="头像" align="center" prop="avatar" width="70px" fixed="left">
+        <template #default="scope">
+          <el-image v-if="scope.row?.person.avatar" class="h-60px w-60px" :src="scope.row?.person.avatar" lazy preview-teleported :preview-src-list="[scope.row?.person.avatar]" fit="contain" />
+        </template>
+      </el-table-column>
+      <el-table-column label="姓名" align="center" prop="person.name" fixed="left" />
+      <el-table-column label="联系电话" align="center" prop="person.phone" />
+      <el-table-column label="邮箱" align="center" prop="person.email" />
+      <el-table-column label="性别" align="center" prop="person.sexStr" />
+      <el-table-column label="出生日期" align="center" prop="person.birthday" :formatter="dateFormatter2" width="180px" />
+      <el-table-column label="学历" align="center" prop="person.eduTypeStr" />
+      <el-table-column label="工作经验" align="center" prop="person.expTypeStr" />
+      <el-table-column label="所在城市" align="center" prop="person.areaStr" />
+      <el-table-column label="意向城市" align="center">
         <template #default="scope">
-          <el-button link type="primary" @click="openDetail(scope.row)">查看</el-button>
-          <el-button link type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
+          {{ scope.row?.person?.interestedAreaStrList && scope.row?.person?.interestedAreaStrList.length ? scope.row?.person?.interestedAreaStrList.join(',') : '' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="求职意向" align="center">
+        <template #default="scope">
+          {{ scope.row?.person?.jobInterestedList && scope.row?.person?.jobInterestedList.length ? scope.row?.person?.jobInterestedList.join(',') : '' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="首次工作时间" align="center" prop="person.firstWorkTime" :formatter="dateFormatter2" width="180px" />
+      <el-table-column label="操作" align="center" fixed="right" min-width="110">
+        <template #default="scope">
+          <el-button link type="primary" @click="openDetail(scope.row.person.id)">详情</el-button>
+          <el-button link type="danger" @click="handleDelete(scope.row.person.id)">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -45,6 +91,9 @@
 
 <script setup>
 import { TalentMap } from '@/api/menduner/system/talentMap'
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { getDict } from '@/hooks/web/useDictionaries'
+import { dateFormatter2 } from '@/utils/formatTime'
 
 /** 人才地图 列表 */
 defineOptions({ name: 'TalentMap' })
@@ -59,9 +108,30 @@ const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
   name: undefined,
+  phone: undefined,
+  sex: undefined,
+  email: undefined,
+  eduType: undefined,
+  expType: undefined,
+  areaId: undefined,
 })
 const queryFormRef = ref() // 搜索的表单
 
+// 地区列表
+const areaTreeData = ref([])
+const getDictData = async () => {
+  const { data } = await getDict('areaTreeData', {}, 'areaTreeData')
+  const obj = data.find(e => e.name === '中国')
+  const list = obj?.children ? obj.children.map(e =>{
+    // 市辖区直接显示区
+    const municipality = e.children && e.children.length && e.children[0].name === '市辖区'
+    if (municipality && e.children[0].children?.length) e.children = e.children[0].children
+    return e
+  }) : []
+  areaTreeData.value = list.length ? list : []
+}
+getDictData()
+
 /** 查询列表 */
 const getList = async () => {
   loading.value = true
@@ -88,25 +158,26 @@ const resetQuery = () => {
 
 /** 打开用户详情 */
 const { push } = useRouter()
-const openDetail = ({ id, userId }) => {
-  push({ name: 'TalentMapDetail', query: { id, userId } })
+const openDetail = (id) => {
+  push({ name: 'TalentMapDetail', params: { id } })
 }
 
 /** 删除按钮操作 */
 const handleDelete = async (id) => {
+  if (!id) return message.warning('删除失败,请稍后再试')
   try {
     // 删除的二次确认
     await message.delConfirm()
     // 发起删除
-    // await HuntApi.deleteHunt(id)
-    // message.success(t('common.delSuccess'))
+    await TalentMap.deleteTalentMap(id)
+    message.success(t('common.delSuccess'))
     // 刷新列表
     await getList()
   } catch {}
 }
 
 const handleAdd = () => {
-  push({ name: 'TalentMapDetail' })
+  push({ name: 'TalentMapDetail', params: { id: 'add' } })
 }
 
 /** 初始化 **/