Jelajahi Sumber

人才采集-网页解析

Xiao_123 1 Minggu lalu
induk
melakukan
c827aa59f3

+ 10 - 1
src/api/menduner/system/talentMap/webParsing.ts

@@ -9,5 +9,14 @@ export const talentWebParsingApi = {
 			timeout: 300000,
 			baseURL: import.meta.env.VITE_BASE_URL
 		})
-	}
+	},
+
+	// 网页解析-新增人才(多人)
+	webParsingTalentCreate: async (data: any) => {
+		return await request.post({ 
+			url: `/api/parse/add-webpage-talent`,
+			data,
+			baseURL: import.meta.env.VITE_BASE_URL
+		})
+	},
 }

+ 9 - 2
src/views/menduner/system/talentMap/components/FormPage.vue

@@ -192,6 +192,12 @@
       <el-form-item label="邮政编码" prop="postal_code_zh">
         <el-input v-model="formQuery.postal_code_zh" placeholder="请输入邮政编码" />
       </el-form-item>
+      <el-row>
+        <div class="m-title">信息补充</div>
+      </el-row>
+      <el-form-item label="人才信息" prop="talent_profile">
+        <el-input v-model="formQuery.talent_profile" type="textarea" :rows="2" placeholder="个人简介、背景、职业发展、专业技能、资质认证、教育背景、获奖情况及荣誉等信息" />
+      </el-form-item>
       <template v-if="formType === 'edit'">
         <el-row>
           <div class="m-title">系统信息</div>
@@ -202,10 +208,10 @@
           </el-tag>
         </el-form-item>
         <el-form-item label="创建时间">
-          <el-tag type="primary" effect="light">{{ itemData?.created_at || '--' }}</el-tag>
+          <el-tag v-if="itemData?.created_at" type="primary" effect="light">{{ itemData?.created_at }}</el-tag>
         </el-form-item>
         <el-form-item label="更新时间">
-          <el-tag type="primary" effect="light">{{ itemData?.updated_at || '--' }}</el-tag>
+          <el-tag v-if="itemData?.updated_at" type="primary" effect="light">{{ itemData?.updated_at }}</el-tag>
         </el-form-item>
       </template>
     </el-form>
@@ -252,6 +258,7 @@ const defaultQuery = {
   residence: undefined,
   age: undefined,
   native_place: undefined,
+  talent_profile: undefined,
   career_path: []
 }
 const formQuery = ref(cloneDeep(defaultQuery))

+ 41 - 35
src/views/menduner/system/talentMap/maintenance/gather/components/webAnalysis.vue

@@ -1,5 +1,5 @@
 <template>
-  <div v-if="!markdown_data || !Object.keys(markdown_data)?.length">
+  <!-- <div v-if="!markdown_data || !Object.keys(markdown_data)?.length"> -->
     <ContentWrap>
       <el-form
         class="-mb-15px"
@@ -20,7 +20,6 @@
 						type="primary"
 						class="ml-10px"
 						plain
-						:loading="loading"
 						@click="handleAnalysis"
 					>解析</el-button>
         </el-form-item>
@@ -37,7 +36,6 @@
                 type="primary"
                 plain
                 class="mt-10px"
-                :loading="content.itemLoading"
                 @click="handleSubmit(content, index)"
               >信息提取</el-button>
             </div>
@@ -51,8 +49,8 @@
         </el-card>
       </el-col>
     </el-row>
-  </div>
-  <div v-else class="!h-100%">
+  <!-- </div> -->
+  <!-- <div v-else class="!h-100%">
     <div class="text-right">
       <el-button type="primary" plain @click="handleReturn">返回查看解析内容</el-button>
     </div>
@@ -66,7 +64,7 @@
 			<pre>{{ markdown_data.title_zh }}</pre>
 			<pre>{{ markdown_data.detailIntroduction }}</pre>
     </el-card>
-  </div>
+  </div> -->
 </template>
 
 <script setup>
@@ -76,23 +74,24 @@ import axios from 'axios'
 import TurndownService from 'turndown'
 import { talentWebParsingApi } from '@/api/menduner/system/talentMap/webParsing.ts'
 import { generateUUID } from '@/utils'
+import { ElLoading } from 'element-plus'
 
 const emit = defineEmits(['analysis', 'reset'])
-const props = defineProps({
-	markDownData: Object
-})
+// const props = defineProps({
+// 	markDownData: Object
+// })
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 
-const markdown_data = ref({})
-watch(() => props.markDownData, val => {
-	markdown_data.value = val
-}, { deep: true })
+// const markdown_data = ref({})
+// watch(() => props.markDownData, val => {
+// 	markdown_data.value = val
+// }, { deep: true })
 
-const loading = ref(false)
 const queryParams = reactive({
 	// urls: 'https://mp.weixin.qq.com/s/JZ5qxaj9vXsEsswxxD1djA'
-	urls: 'https://mp.weixin.qq.com/s/vQLWlSB6DzqSewtBLkk_kQ'
+	// urls: 'https://mp.weixin.qq.com/s/vQLWlSB6DzqSewtBLkk_kQ'
+  urls: ''
 })
 const queryFormRef = ref()
 const contents = ref([])
@@ -230,16 +229,19 @@ const showPage = (res) => {
 const handleAnalysis = async () => {
 	if (!queryParams.urls) return
 
-  // 重置右侧标签及表单
-  emit('reset')
+  if (contents.value?.length > 0) {
+    await message.confirm('是否确认重新解析?确认后当前内容将会清空!')
+    // 重置右侧标签及表单
+    emit('reset')
+  }
 
-	if (contents.value && contents.value.length) {
-		const isAnalysis = contents.value.some(e => e.itemLoading)
-		if (isAnalysis) return message.warning('正在提取信息中,请稍后再试')
-	}
+  const loading = ElLoading.service({
+    lock: true,
+    text: '正在解析中...',
+    background: 'rgba(0, 0, 0, 0.7)',
+  })
 
 	contents.value = []
-	loading.value = true
 
 	const urlArr = queryParams.urls.split(',').map(url => url.trim()).filter(Boolean)
 
@@ -259,7 +261,6 @@ const handleAnalysis = async () => {
 			contents.value.push({
 				...e,
 				publish_time: null,
-				itemLoading: false,
 				id: generateUUID(),
 				markdown_text: handleConvert(e)
 			})
@@ -272,38 +273,43 @@ const handleAnalysis = async () => {
 		console.log(err, 'error');
 		message.error(err.message)
 	}).finally(_ => {
-		loading.value = false
+    loading.close()
 	})
 }
 
 // 信息提取
-const extractIndex = ref(0)
+// const extractIndex = ref(0)
 const handleSubmit = async (content, index) => {
-	extractIndex.value = index
-	if (loading.value) return message.warning('正在解析中,请稍后再试')
+	// extractIndex.value = index
+	// if (loading.value) return message.warning('正在解析中,请稍后再试')
 	if (!content.markdown_text) return
 
-	content.itemLoading = true
+  const loading = ElLoading.service({
+    lock: true,
+    text: '信息正在提取中...',
+    background: 'rgba(0, 0, 0, 0.7)',
+  })
+
   const { markdown_text, publish_time } = content
 	if (!publish_time) {
 		message.warning('发布时间不能为空')
-		content.itemLoading = false
+    loading.close()
 		return
 	}
 	try {
 		const data = await talentWebParsingApi.saveMarkdownContent({ markdown_text, publish_time })
-		emit('analysis', data ?? [])
+		emit('analysis', data ?? [], markdown_text)
 		message.success('信息提取成功')
 	} finally {
-		content.itemLoading = false
+    loading.close()
 	}
 }
 
 // 返回查看解析内容
-const handleReturn = () => {
-	markdown_data.value =  {}
-	showPage(contents.value[extractIndex.value])
-}
+// const handleReturn = () => {
+// 	markdown_data.value =  {}
+// 	showPage(contents.value[extractIndex.value])
+// }
 </script>
 
 <style scoped>

+ 112 - 26
src/views/menduner/system/talentMap/maintenance/gather/index.vue

@@ -39,7 +39,7 @@
       <el-table-column label="创建日期" align="center" prop="created_at" :formatter="dateFormatter" />
       <el-table-column label="操作" align="center" fixed="right" min-width="110">
         <template #default="scope">
-          <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
+          <!-- <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button> -->
           <el-button link type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
           <el-button link :type="scope.row.status === 'active' ? 'warning': 'success'" @click="handleDisable(scope.row)">
             {{ scope.row.status === 'active' ? '禁用' : '启用'}}
@@ -215,10 +215,10 @@
           </template>
           <!-- 网页解析 -->
           <template v-if="radioValue === 'web'">
+            <!-- :markDownData="markDownData" -->
             <webAnalysis
               v-if="showWebAnalysis"
-              :markDownData="markDownData"
-              @analysis="val => tagList = val"
+              @analysis="handleWebAnalysis"
               @reset="handleWebClear(), showFormPage = false, formData = {}"
             />
           </template>
@@ -230,11 +230,11 @@
               round
               size="large"
               v-for="(val, index) in tagList"
-              :key="index + val.name_zh"
+              :key="index + val"
               class="mr-10px cursor-pointer mb-10px"
-              @click="handleTagClick(val, index)"
+              @click="handleTagClick(index)"
             >
-              {{ val.name_zh }}
+              {{ val }}
               <Icon v-if="index === tagCurrentIndex" icon="ep:check" class="ml-5px" /> 
             </el-tag>
           </div>
@@ -243,12 +243,12 @@
             ref="FormPageRef"
             :formType="analysisType"
             :itemData="formData"
-          />
+            />
           <div v-if="tagList?.length && !tagCurrentIndex" class="!h-100% flex items-center justify-center">请点击上方人才姓名进行查看编辑</div>
         </div>
       </div>
       <template #footer>
-        <el-button @click="handleSave" type="success" :loading="formLoading">保 存</el-button>
+        <el-button @click="handleSave" type="success">保 存</el-button>
         <el-button @click="dialog_analysisInfo = false, handleWebClear">取 消</el-button>
       </template>
     </Dialog>
@@ -275,6 +275,8 @@ import { Base64 } from 'js-base64'
 import Info from '@/views/menduner/system/person/details/components/info.vue'
 import expExtend from '@/views/menduner/system/person/details/components/expExtend.vue'
 import Attachment from '@/views/menduner/system/person/details/components/attachment.vue'
+import { talentWebParsingApi } from '@/api/menduner/system/talentMap/webParsing'
+import { ElLoading } from 'element-plus'
 
 const baseUrl = import.meta.env.VITE_PREVIEW_URL
 const { uploadUrl, httpRequest } = useUpload()
@@ -332,28 +334,45 @@ const handleDelete = async (id) => {
   } catch {}
 }
 
-// 网页解析
+// 网页解析-人才标签点击
+// const markDownData = ref({})
+const handleTagClick = (index) => {
+  if (!webOriginList.value?.[index]) return
+  tagCurrentIndex.value = index
+  const obj = webOriginList.value[index]
+  formData.value = obj ?? {}
+  // markDownData.value = {
+  //   name_zh: obj.name_zh,
+  //   name_en: obj.name_en,
+  //   hotel_zh: obj.hotel_zh,
+  //   title_zh: obj.title_zh,
+  //   pic_url: obj.pic_url,
+  //   detailIntroduction: ''
+  // }
+  if (!showFormPage.value) showFormPage.value = true
+}
+
+// 网页解析-信息提取
 const tagCurrentIndex = ref(null)
 const tagList = ref([])
-const markDownData = ref({})
-const handleTagClick = (val, index) => {
-  tagCurrentIndex.value = index
-  formData.value = val
-  markDownData.value = {
-    name_zh: val.name_zh,
-    name_en: val.name_en,
-    hotel_zh: val.hotel_zh,
-    title_zh: val.title_zh,
-    pic_url: val.pic_url,
-    detailIntroduction: ''
+const originMarkdown = ref(null)
+const webOriginList = ref([])
+const handleWebAnalysis = (list, md) => {
+  webOriginList.value = list
+  tagList.value = list.map(e => e.name_zh)
+  originMarkdown.value = md
+  // 只有单个人才时默认选中
+  if (list?.length === 1) {
+    handleTagClick(0)
   }
-  if (!showFormPage.value) showFormPage.value = true
 }
 
 const handleWebClear = () => {
   tagCurrentIndex.value = null
-  markDownData.value = {}
+  originMarkdown.value = null
+  // markDownData.value = {}
   tagList.value = []
+  webOriginList.value = []
 }
 
 /** 编辑 */
@@ -403,12 +422,67 @@ const handleDisable = async (item) => {
 // 更新
 const activeName = ref('info')
 const dialog_analysisInfo = ref(false)
-const formLoading = ref(false)
 const analysisType = ref('')
 const showFormPage = ref(true)
 const FormPageRef = ref(null)
 const mergeFormRef = ref() // 合并表单 Ref
+
+const handleSaveWebData = async () => {
+  if (!webOriginList.value || !webOriginList.value?.length) {
+    message.warning('请输入微信公众号链接解析并提取信息核对后再提交保存')
+    return
+  }
+  if (!FormPageRef.value) return message.warning('请点击右侧顶部的人才标签查看核对信息后保存')
+  
+  // 验证webOriginList数组中每个人才的name_zh字段,并处理mobile字段
+  if (webOriginList.value && Array.isArray(webOriginList.value) && webOriginList.value.length > 0) {
+    for (let i = 0; i < webOriginList.value.length; i++) {
+      const talent = webOriginList.value[i]
+      if (!talent.name_zh || talent.name_zh.trim() === '') {
+        return message.warning(`第${i + 1}个人才中的中文姓名未填写,请完善后再进行保存`)
+      }
+      
+      // 处理mobile字段,如果是数组则转换为字符串
+      if (Array.isArray(talent.mobile)) {
+        talent.mobile = talent.mobile.filter(i => Boolean(i)).map(j => String(j).replace(/,|,/g, '')).join(',');
+      }
+    }
+  }
+  
+  console.log(webOriginList.value, '网页解析-新增')
+
+  const loading = ElLoading.service({
+    lock: true,
+    text: '正在保存中...',
+    background: 'rgba(0, 0, 0, 0.7)',
+  })
+  try {
+    const result = await talentWebParsingApi.webParsingTalentCreate({
+      talent_list: webOriginList.value,
+      web_md: originMarkdown.value
+    })
+    console.log(result, 'create-result')
+    message.success('新增成功')
+    dialog_analysisInfo.value = false
+    // 刷新列表
+    getList()
+    if (result.code === 202 || result.message.includes('疑似重复')) {
+      if (!result.data?.main_card?.id) return
+      
+      await message.confirm('发现与当前名片的疑似重复数据,去处理')
+      mergeFormRef.value.open(result.data?.main_card?.id)
+    }
+  } finally {
+    loading.close()
+    handleWebClear()
+  }
+  
+}
+
 const handleSave = async () => {
+  // 网页解析新增调用单独的接口保存
+  if (analysisType.value === 'create' && radioValue.value === 'web') return handleSaveWebData()
+
   if (!FormPageRef.value) return message.warning('请将表单信息完善后再提交')
 
   const params = { ...FormPageRef.value.formQuery, type: radioValue.value }
@@ -419,9 +493,12 @@ const handleSave = async () => {
     params.mobile = params.mobile.filter(i => Boolean(i)).map(j => String(j).replace(/,|,/g, '')).join(',');
   }
 
-  console.log(params, 'handleSubmit')
+  const loading = ElLoading.service({
+    lock: true,
+    text: '正在保存中...',
+    background: 'rgba(0, 0, 0, 0.7)',
+  })
   try {
-    formLoading.value = true
     let result = {}
 
     if (analysisType.value === 'create') {
@@ -454,7 +531,7 @@ const handleSave = async () => {
     console.log('更新失败', error)
   } finally {
     cardFileQuery.value = null
-    formLoading.value = false
+    loading.close()
     openSearch.value = false
     handleWebClear()
   }
@@ -503,6 +580,15 @@ const handleAnalysis = async () => {
   }
 }
 
+// 监听表单变化,同步更新tagList中对应的数据
+watch(() => FormPageRef.value?.formQuery, (newVal) => {
+  if (tagCurrentIndex.value !== null && webOriginList.value && webOriginList.value.length > tagCurrentIndex.value) {
+    // 保留原始pic_url
+    const { pic_url } = webOriginList.value[tagCurrentIndex.value]
+    webOriginList.value[tagCurrentIndex.value] = { ...newVal, pic_url }
+  }
+}, { deep: true })
+
 // 简历解析
 const fileUrl = ref('') // https://minio.menduner.com/dev/person/229988673960153088/attachment/ee3eb21f45e13ede3557a03d18585ed80c5b4212ac5634e3436e309afaa8fe6a.pdf
 const uploadRef = ref()

+ 2 - 48
src/views/menduner/system/talentMap/maintenance/labeling/LabelingForm.vue

@@ -130,17 +130,6 @@ const open = async (data) => {
   dialogVisible.value = true
 	talentItem.value = data
 
-	// 获取人才详情
-	// await getTalentMap()
-
-	// if (data) {
-	// 	Object.assign(result.value.person, {
-	// 		name: data.name_en ? `${data.name_zh}(${ data.name_en })` : data.name_zh,
-	// 		phone: data.mobile,
-	// 		email: data.email
-	// 	})
-	// }
-
 	// 获取所有人才标签
 	await getTagList()
 
@@ -166,7 +155,7 @@ const handleAdd = (item) => {
 	talentSelectedTags.value.push(item)
 }
 
-/** 提交表单 */
+/** 人才标签保存 */
 const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
 const submitForm = async () => {
 	if (!talentSelectedTags.value || !talentSelectedTags.value.length) return message.warning('请选择要更新的人才标签')
@@ -188,41 +177,6 @@ const submitForm = async () => {
   }
 }
 
-// 获取人才详情
-const getTalentMap = async () => {
-	result.value = cloneDeep(DefaultData)
-	const id = talentItem.value?.id
-	if (!id) {
-		return
-	}
-  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
-    } else activeName.value = 'personnelSearch'
-    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
-  }
-}
-
-// 查看详情-编辑求职意向
-const handleUpdateJobIntention = async (val, type) => {
-  try {
-    await TalentMap.updateTalentMapInfo(result.value.person)
-    message.success((type === 'add' ? '新增' : type === 'edit' ? '编辑' : '删除') + '成功')
-  } catch (error) {
-    console.log(error)
-  } finally {
-    formLoading.value = false
-  }
-}
-
 // 保存人才信息
 const baseInfoRef = ref(null)
 const saveLoading = ref(false)
@@ -231,7 +185,7 @@ const handleSave = async () => {
 	const params = baseInfoRef.value.formQuery
 	if (!params || !Object.keys(params).length) return saveLoading.value = true
 	try {
-		await talentLabelingApi.updateBusinessCard(params, params.id)
+		await talentLabelingApi.updateBusinessCard({...params, origin_source: talentItem.value?.origin_source}, talentItem.value.id)
 		dialogVisible.value = false
 		message.success('保存成功')
 		emit('success')

+ 1 - 1
src/views/menduner/system/talentMap/maintenance/labeling/index.vue

@@ -91,7 +91,7 @@ const getList = async () => {
   loading.value = true
   try {
     const data = await talentLabelingApi.getCardList()
-    list.value = data
+    list.value = data ? data.reverse() : []
   } finally {
     loading.value = false
   }