Xiao_123 3 dienas atpakaļ
vecāks
revīzija
fa78e44581

+ 30 - 0
src/api/menduner/system/talentMap/gather.ts

@@ -0,0 +1,30 @@
+import request from '@/config/axios'
+
+// 人才采集-创建任务
+export const talentGatherApi = {
+	// 获取人才采集任务列表
+	getTaskList: async (params: any) => {
+		return await request.get({ 
+			url: `/api/parse/get-parse-tasks`,
+			params,
+			baseURL: import.meta.env.VITE_BASE_URL
+		})
+	},
+
+	// 创建解析任务
+	createTask: async (data: any) => {
+		return await request.upload({ 
+			url: `/api/parse/add-parse-task`,
+			data,
+			baseURL: import.meta.env.VITE_BASE_URL
+		})
+	},
+
+	// 获取任务详情
+	getTaskDetail: async (task_name: string) => {
+		return await request.get({
+			url: `/api/parse/get-parse-task-detail?task_name=${task_name}`,
+			baseURL: import.meta.env.VITE_BASE_URL
+		})
+	}
+}

+ 18 - 5
src/components/UploadFile/src/UploadImgs.vue

@@ -14,6 +14,7 @@
       :on-error="uploadError"
       :on-exceed="handleExceed"
       :on-success="uploadSuccess"
+      :on-change="handleChange"
       list-type="picture-card"
     >
       <div class="upload-empty">
@@ -81,6 +82,8 @@ const props = defineProps({
   borderradius: propTypes.string.def('8px') // 组件边框圆角 ==> 非必传(默认为 8px)
 })
 
+const emit = defineEmits(['handleChange', 'update:modelValue'])
+
 const { uploadUrl, httpRequest } = useUpload()
 
 const fileList = ref<UploadUserFile[]>([])
@@ -110,11 +113,6 @@ const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
 }
 
 // 图片上传成功
-interface UploadEmits {
-  (e: 'update:modelValue', value: string[]): void
-}
-
-const emit = defineEmits<UploadEmits>()
 const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
   message.success('上传成功')
   // 删除自身
@@ -129,6 +127,16 @@ const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
   }
 }
 
+const originFileList = ref<any[]>([])
+const handleChange = (file: any) => {
+  // 判断是否已存在相同name的文件
+  const exists = originFileList.value.some((item) => item.name === file.name)
+  if (!exists) {
+    originFileList.value.push(file)
+  }
+  emit('handleChange', originFileList.value)
+}
+
 // 监听模型绑定值变动
 watch(
   () => props.modelValue,
@@ -155,10 +163,15 @@ const handleRemove = (uploadFile: UploadFile) => {
   fileList.value = fileList.value.filter(
     (item) => item.url !== uploadFile.url || item.name !== uploadFile.name
   )
+  // 同步删除originFileList中对应的数据
+  originFileList.value = originFileList.value.filter(
+    (item) => item.response?.data !== uploadFile.url
+  )
   emit(
     'update:modelValue',
     fileList.value.map((file) => file.url!)
   )
+  emit('handleChange', originFileList.value)
 }
 
 // 图片上传错误提示

+ 184 - 0
src/views/menduner/system/talentMap/maintenance/gather/components/store.vue

@@ -0,0 +1,184 @@
+<template>
+  <Dialog title="人才入库" v-model="dialogVisible" :modalClose="false" width="90%" @close="dialogVisible = false">
+    <div class="analysisInfoBox">
+      <div class="analysisFile !w-50%">
+          <!-- 门墩儿人才库 -->
+          <template v-if="source === 'menduner'">
+            <el-card shadow="never">
+              <!-- <Info :id="id" :user-id="userId" />
+              <expExtend :user-id="userId" defaultShowAll class="m-t-20px" /> -->
+            </el-card>
+          </template>
+          <!-- 简历解析 -->
+          <template v-if="source === 'file'">
+            <IFrame :src="''" />
+          </template>
+          <!-- 名片解析 -->
+          <template v-if="source === 'card'">
+            <div class="image">
+              <el-image class="!w-100%" :src="''" />
+            </div>
+          </template>
+          <!-- 网页解析 -->
+          <template v-if="source === 'web'">
+            <div></div>
+          </template>
+      </div>
+      <div class="flex-1">
+				<el-tabs type="border-card">
+					<el-tab-pane label="已解析人才列表">
+						<div class="tagBox mb-10px" v-if="tagList?.length">
+						<!-- <el-tag 
+							type="primary" 
+							round
+							size="large"
+							v-for="(val, index) in tagList"
+							:key="val.id"
+							class="mr-10px cursor-pointer mb-10px"
+							@click="handleTagClick(index)"
+						>
+							<div class="flex items-center">
+								<el-checkbox v-model="val.cehcked" />
+								<span class="ml-5px">{{ val.name }}</span>
+							</div>
+						</el-tag> -->
+						<el-checkbox-group v-model="selectTags">
+							<el-checkbox
+								v-for="(val, index) in tagList"
+								:key="val.id"
+								:label="val.name"
+								:value="val.value"
+								border
+								@click="handleTagClick(index)"
+							/>
+						</el-checkbox-group>
+					</div>
+					<FormPage
+						ref="FormPageRef"
+						formType="create"
+						:itemData="itemData"
+          />
+					</el-tab-pane>
+				</el-tabs>
+      </div>
+    </div>
+    <template #footer>
+      <el-button @click="handleStore" type="success">入 库</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script setup>
+/** 人才采集 入库 */
+defineOptions({ name: 'Store' })
+import { ElLoading } from 'element-plus'
+// import { generateUUID } from '@/utils'
+import FormPage from '@/views/menduner/system/talentMap/components/FormPage.vue'
+
+const dialogVisible = ref(false)
+const showFormPage = ref(false)
+const FormPageRef = ref(null)
+const itemData = ref({})
+const tagList = ref([])
+const tagCurrentIndex = ref(0)
+const source = ref('')
+const selectTags = ref([])
+
+const originData = ref([
+  {
+    "brand_group": "万豪",
+    "career_path": [
+      {
+        "date": "2025-06-24",
+        "hotel_en": "Shenzhen Marriott Hotel Nanshan",
+        "hotel_zh": "深圳中洲万豪酒店",
+        "image_path": "",
+        "source": "webpage_extraction",
+        "title_en": "Director of Human Resources",
+        "title_zh": "人力资源总监"
+      }
+    ],
+    "hotel_en": "Shenzhen Marriott Hotel Nanshan",
+    "hotel_zh": "深圳中洲万豪酒店",
+    "name_en": "Sandra Zhou",
+		id: 1,
+    "name_zh": "周银萍",
+    "pic_url": "https://mmbiz.qpic.cn/sz_mmbiz_jpg/UOfXdu2ZbTdomIosTR7sMXCbtjMXRHTSkwudn71WJOrkOzA7IYQyYvjRpPQ80cvyawKNSibxiafjCuamsVJlUo1A/640?wx_fmt=jpeg",
+    "title_en": "Director of Human Resources",
+    "title_zh": "人力资源总监"
+  },
+  {
+    "brand_group": "万豪",
+    "career_path": [
+      {
+        "date": "2025-06-24",
+        "hotel_en": "Shenzhen Marriott Hotel Nanshan",
+        "hotel_zh": "深圳中洲万豪酒店",
+        "image_path": "",
+        "source": "webpage_extraction",
+        "title_en": "Director of Human Resources",
+        "title_zh": "人力资源总监"
+      }
+    ],
+    "hotel_en": "Shenzhen Marriott Hotel Nanshan",
+    "hotel_zh": "广州希尔顿酒店",
+    "name_en": "Sandra Zhou",
+    "name_zh": "盘海龙",
+		id: 2,
+    "pic_url": "https://mmbiz.qpic.cn/sz_mmbiz_jpg/UOfXdu2ZbTdomIosTR7sMXCbtjMXRHTSkwudn71WJOrkOzA7IYQyYvjRpPQ80cvyawKNSibxiafjCuamsVJlUo1A/640?wx_fmt=jpeg",
+    "title_en": "Director of Human Resources",
+    "title_zh": "工程总监"
+  }
+])
+
+// 打开弹窗
+const open = async (list) => {
+	// if (!list || !list.length) return
+	tagList.value = originData.value.map(e => ({ name: e.name_zh, value: e.id }))
+	itemData.value = originData.value[0]
+  dialogVisible.value = true
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+const handleTagClick = (index) => {
+	tagCurrentIndex.value = index
+	itemData.value = originData.value[index]
+	showFormPage.value = true
+}
+
+// 监听表单变化,同步更新originData中对应的数据
+// watch(() => FormPageRef.value?.formQuery, (newVal) => {
+//   if (tagCurrentIndex.value !== null && originData.value && originData.value.length > tagCurrentIndex.value) {
+//     // 保留原始pic_url
+//     const { pic_url } = originData.value[tagCurrentIndex.value]
+//     originData.value[tagCurrentIndex.value] = { ...newVal, pic_url }
+//   }
+// }, { deep: true })
+
+const handleStore = async () => {
+	console.log(originData.value, 'store')
+}
+</script>
+
+<style lang="scss" scpoed>
+.analysisInfoBox {
+  display: flex;
+	min-height: 50vh;
+  .analysisFile {
+    padding-right: 12px;
+    overflow: auto;
+  }
+}
+.tagBox {
+  padding: 12px;
+  border: 1px dashed #409EFF;
+  border-radius: 4px;
+}
+:deep {
+  .el-tag__content {
+    display: flex;
+    align-items: center;
+  }
+}
+</style>

+ 127 - 117
src/views/menduner/system/talentMap/maintenance/gather/components/webAnalysis.vue

@@ -5,9 +5,9 @@
       :model="queryParams"
       ref="queryFormRef"
       :inline="true"
-      label-width="110px"
+      label-width="95px"
     >
-      <el-form-item label="新任命链接" prop="urls" class="!w-100%">
+      <el-form-item label="新任命链接" prop="urls" class="!w-100%" :required="true">
         <el-input
           v-model="queryParams.urls"
           class="!w-75%"
@@ -20,7 +20,7 @@
 					class="ml-10px"
 					plain
 					@click="handleAnalysis"
-				>解析</el-button>
+				>查看</el-button>
       </el-form-item>
     </el-form>
   </ContentWrap>
@@ -31,12 +31,12 @@
         <template #header>
           <div class="flex items-center justify-between">
             <el-text class="flex-1" truncated>{{ content.url }}</el-text>
-            <el-button
+            <!-- <el-button
               type="primary"
               plain
               class="mt-10px"
               @click="handleSubmit(content, index)"
-            >信息提取</el-button>
+            >信息提取</el-button> -->
           </div>
         </template>
         <iframe
@@ -65,8 +65,8 @@ const { t } = useI18n() // 国际化
 
 const queryParams = reactive({
 	// urls: 'https://mp.weixin.qq.com/s/JZ5qxaj9vXsEsswxxD1djA' // https://mp.weixin.qq.com/s/R1aJpn9z-Jf0dk9ttoYYeg
-	// urls: 'https://mp.weixin.qq.com/s/vQLWlSB6DzqSewtBLkk_kQ'
-  urls: ''
+	urls: 'https://mp.weixin.qq.com/s/vQLWlSB6DzqSewtBLkk_kQ'
+  // urls: ''
 })
 const queryFormRef = ref()
 const contents = ref([])
@@ -89,8 +89,8 @@ turndownService.addRule('wechatImages', {
   }
 })
 
-// 提取主要内容并转换为Markdown
-const wechatHtmlToMarkdown = (html) => {
+// 提取主要内容并转换为Markdown文件
+const wechatHtmlToMarkdown = (html, filename = '新任命.md') => {
   // 创建一个临时DOM解析器
   const parser = new DOMParser()
   const doc = parser.parseFromString(html, 'text/html')
@@ -109,95 +109,103 @@ const wechatHtmlToMarkdown = (html) => {
   })
 
   // 转换为Markdown
-  return turndownService.turndown(content.innerHTML)
-}
-
-// 转换为markdown格式
-const handleConvert = (res) => {
-	if (!res.data) return
-	const result = wechatHtmlToMarkdown(res.data)
-	if (!result) return message.warning('转换失败')
-	return result
-}
-
-function extractPublishTime(doc, html) {
-  // 1. 通过 id
-  let timeEl = doc.getElementById('publish_time')
-  if (timeEl && timeEl.innerText) return timeEl.innerText.trim()
-
-  // 2. 通过 class
-  let metaEls = doc.querySelectorAll('.rich_media_meta.rich_media_meta_text')
-  for (let el of metaEls) {
-    if (el.innerText && /\d{4}年\d{1,2}月\d{1,2}日/.test(el.innerText)) {
-      return el.innerText.trim()
-    }
-  }
+  const result = turndownService.turndown(content.innerHTML)
 
-  // 3. 通过 meta 标签
-  let meta = doc.querySelector('meta[property="article:published_time"]')
-  if (meta && meta.content) return meta.content.trim()
-
-  // 4. 通过正则从 html 里提取
-  let match = html.match(/(\d{4}年\d{1,2}月\d{1,2}日)/)
-  if (match) return match[1]
-
-  return ''
+  const blob = new Blob([result], { type: 'text/markdown' })
+  return new File([blob], filename, { type: 'text/markdown' })
 }
 
-function tryExtractPublishTime(doc, html, cb, maxTry = 10, interval = 200) {
-  let tryCount = 0
-  const timer = setInterval(() => {
-    const publishTime = extractPublishTime(doc, html)
-    if (publishTime || tryCount >= maxTry) {
-      clearInterval(timer)
-      cb(publishTime)
-    }
-    tryCount++
-  }, interval)
-}
+// 转换为markdown格式
+// const handleConvert = (res) => {
+// 	if (!res.data) return
+// 	const result = wechatHtmlToMarkdown(res.data)
+// 	if (!result) return message.warning('转换失败')
+// 	return result
+// }
+
+// function extractPublishTime(doc, html) {
+//   // 1. 通过 id
+//   let timeEl = doc.getElementById('publish_time')
+//   if (timeEl && timeEl.innerText) return timeEl.innerText.trim()
+
+//   // 2. 通过 class
+//   let metaEls = doc.querySelectorAll('.rich_media_meta.rich_media_meta_text')
+//   for (let el of metaEls) {
+//     if (el.innerText && /\d{4}年\d{1,2}月\d{1,2}日/.test(el.innerText)) {
+//       return el.innerText.trim()
+//     }
+//   }
+
+//   // 3. 通过 meta 标签
+//   let meta = doc.querySelector('meta[property="article:published_time"]')
+//   if (meta && meta.content) return meta.content.trim()
+
+//   // 4. 通过正则从 html 里提取
+//   let match = html.match(/(\d{4}年\d{1,2}月\d{1,2}日)/)
+//   if (match) return match[1]
+
+//   return ''
+// }
+
+// function tryExtractPublishTime(doc, html, cb, maxTry = 10, interval = 200) {
+//   let tryCount = 0
+//   const timer = setInterval(() => {
+//     const publishTime = extractPublishTime(doc, html)
+//     if (publishTime || tryCount >= maxTry) {
+//       clearInterval(timer)
+//       cb(publishTime)
+//     }
+//     tryCount++
+//   }, interval)
+// }
 
 // 查看原网页
 const showPage = (res) => {
-  if (res.data) {
-    let html = res.data
-    html = html.replace(/data-src/g, 'src') // 将 data-src 转化为 src
-      // .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/g, '') // 移除HTML内容中所有的<script>标签,这样可以避免在iframe中执行潜在的不受信任的脚本。
-      .replace(/https/g, 'http') // 将HTML内容中所有的https替换为http,可能是为了避免在HTTPS环境下加载非HTTPS资源导致浏览器警告
-    nextTick(() => {
-      const iframe = document.getElementById(res.id)
-      if (!iframe) return
-      const doc = iframe.contentDocument || iframe.document
-      // 设置 iframe 中请求不发送 referrer,以绕过图片防盗链
-      const htmlArr = html.split('</head>')
-      const html_src_add = htmlArr[0] + '<meta name="referrer" content="never"></head>' + htmlArr[1]
-      doc.open()
-      doc.write(html_src_add)
-      doc.close()
-
-      // 通过延时获取文档高度赋值Iframe去除滚动条,根据实际情况增加延时时间
-      setTimeout(() => {
-        const jsContent = doc.getElementById('js_content')
-				if (jsContent) {
-          jsContent.style.visibility = 'visible'
-          jsContent.style.opacity = 1
-        }
-      }, 100)
-
-      // 获取发布时间
-      setTimeout(() => {
-        tryExtractPublishTime(doc, html, (publishTime) => {
-          if (publishTime) {
-            res.publish_time = publishTime
-              .replace("年", "-")
-              .replace("月", "-")
-              .replace("日", "")
-              .split(" ")[0];
-            console.log(publishTime, '发布时间', res.publish_time)
-          }
-        });
-      }, 100); // 先等100ms让iframe初步渲染,再开始轮询
-    })
-  }
+  let html = res.data
+  if (!html) return
+  
+   // 将 data-src 转化为 src
+  html = html.replace(/data-src/g, 'src')
+    // 需要获取文章发布时间的话需注释下一行代码
+    // 移除HTML内容中所有的<script>标签,这样可以避免在iframe中执行潜在的不受信任的脚本。
+    .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/g, '')
+    // 将HTML内容中所有的https替换为http,可能是为了避免在HTTPS环境下加载非HTTPS资源导致浏览器警告
+    .replace(/https/g, 'http')
+  
+  nextTick(() => {
+    const iframe = document.getElementById(res.id)
+    if (!iframe) return
+    const doc = iframe.contentDocument || iframe.document
+    // 设置 iframe 中请求不发送 referrer,以绕过图片防盗链
+    const htmlArr = html.split('</head>')
+    const html_src_add = htmlArr[0] + '<meta name="referrer" content="never"></head>' + htmlArr[1]
+    doc.open()
+    doc.write(html_src_add)
+    doc.close()
+
+    // 通过延时获取文档高度赋值Iframe去除滚动条,根据实际情况增加延时时间
+    setTimeout(() => {
+      const jsContent = doc.getElementById('js_content')
+			if (jsContent) {
+        jsContent.style.visibility = 'visible'
+        jsContent.style.opacity = 1
+      }
+    }, 100)
+
+    // 获取发布时间
+    // setTimeout(() => {
+    //   tryExtractPublishTime(doc, html, (publishTime) => {
+    //     if (publishTime) {
+    //       res.publish_time = publishTime
+    //         .replace("年", "-")
+    //         .replace("月", "-")
+    //         .replace("日", "")
+    //         .split(" ")[0];
+    //       console.log(publishTime, '发布时间', res.publish_time)
+    //     }
+    //   });
+    // }, 100); // 先等100ms让iframe初步渲染,再开始轮询
+  })
 }
 
 // 解析
@@ -235,15 +243,17 @@ const handleAnalysis = async () => {
 		list.forEach(e => {
 			contents.value.push({
 				...e,
-				publish_time: null,
+				// publish_time: null,
 				id: generateUUID(),
-				markdown_text: handleConvert(e)
+        file: wechatHtmlToMarkdown(e.data)
+				// markdown_text: handleConvert(e)
 			})
 		})
 		contents.value.forEach(e => {
 			showPage(e)
 		})
 
+    emit('analysis', contents.value)
 	}).catch(err => {
 		console.log(err, 'error');
 		message.error(err.message)
@@ -253,29 +263,29 @@ const handleAnalysis = async () => {
 }
 
 // 信息提取
-const handleSubmit = async (content, index) => {
-	if (!content.markdown_text) return
-
-  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('发布时间不能为空')
-    loading.close()
-		return
-	}
-	try {
-		const data = await talentWebParsingApi.saveMarkdownContent({ markdown_text, publish_time })
-		emit('analysis', data ?? [], markdown_text)
-		message.success('信息提取成功')
-	} finally {
-    loading.close()
-	}
-}
+// const handleSubmit = async (content, index) => {
+// 	if (!content.markdown_text) return
+
+//   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('发布时间不能为空')
+//     loading.close()
+// 		return
+// 	}
+// 	try {
+// 		const data = await talentWebParsingApi.saveMarkdownContent({ markdown_text, publish_time })
+// 		emit('analysis', data ?? [], markdown_text)
+// 		message.success('信息提取成功')
+// 	} finally {
+//     loading.close()
+// 	}
+// }
 </script>
 
 <style scoped>

+ 209 - 587
src/views/menduner/system/talentMap/maintenance/gather/index.vue

@@ -1,6 +1,6 @@
 <template>
+  <!-- 搜索工作栏 -->
   <ContentWrap>
-    <!-- 搜索工作栏 -->
     <el-form
       class="-mb-15px"
       :model="queryParams"
@@ -8,8 +8,25 @@
       :inline="true"
       label-width="68px"
     >
-      <el-form-item label="名称" prop="name">
-        <el-input v-model="queryParams.name" placeholder="请输入名称" clearable @keyup.enter="handleQuery" class="!w-180px" />
+      <el-form-item label="任务类型" prop="task_type">
+        <el-select
+          v-model="queryParams.task_type"
+          placeholder="请选择类型"
+          clearable
+          class="!w-200px"
+        >
+          <el-option v-for="(val, index) in taskType" :label="val.label" :value="val.value" :key="index" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="任务状态" prop="task_status">
+        <el-select
+          v-model="queryParams.task_status"
+          placeholder="请选择状态"
+          clearable
+          class="!w-200px"
+        >
+          <el-option v-for="(val, index) in taskStatus" :label="val" :value="val" :key="index" />
+        </el-select>
       </el-form-item>
       <el-form-item>
         <el-button @click="handleQuery('search')"><Icon icon="ep:search" /> 搜索</el-button>
@@ -24,240 +41,127 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table v-loading="loading" :data="list" :stripe="true">
-      <el-table-column label="中文名称" align="center" prop="name_zh" fixed="left" />
-      <el-table-column label="英文名称" align="center" prop="name_en" />
-      <el-table-column label="职位" align="center" prop="title_zh" />
-      <el-table-column label="酒店/公司" align="center" prop="hotel_zh" />
-      <el-table-column label="手机号码" align="center" prop="mobile" />
-      <el-table-column label="固定电话" align="center" prop="phone" />
-      <el-table-column label="状态" align="center" prop="status" width="80">
-        <template #default="scope">
-          <el-tag type="success" v-if="scope.row.status === 'active'">已启用</el-tag>
-          <el-tag type="danger" v-else>已禁用</el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column label="创建日期" align="center" prop="created_at" :formatter="dateFormatter" />
+      <el-table-column label="任务名称" align="center" prop="task_name" />
+      <el-table-column label="任务类型" align="center" prop="task_type" />
+      <el-table-column label="任务状态" align="center" prop="task_status" />
+      <el-table-column label="创建时间" align="center" prop="created_at" :formatter="dateFormatter" />
+      <el-table-column label="更新时间" align="center" prop="updated_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="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' ? '禁用' : '启用'}}
-          </el-button>
+          <el-button
+            v-if="scope.row.task_status === '待解析'"
+            link
+            type="success"
+          >解析</el-button>
+          <el-button
+            v-if="scope.row.parse_result"
+            link
+            type="primary"
+            @click="handleStore(scope.row)"
+          >入库</el-button>
         </template>
       </el-table-column>
     </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.page"
+      v-model:limit="queryParams.per_page"
+      @pagination="getList"
+    />
+  </ContentWrap>
 
-    <!-- 选择来源 -->
-    <Dialog title="新增" v-model="openSelect" width="550" @close="openSelect = false">
-			<el-radio-group v-model="radioValue" size="large" class="radioBox">
-				<el-radio
-					v-for="item in radioList"
-					:key="item.value"
-					:value="item.value"
-				>
-					{{ item.label }}
-				</el-radio>
-			</el-radio-group>
-      <template #footer>
-        <el-button type="primary" @click="handleSelect">确 认</el-button>
-        <el-button @click="openSelect = false">取 消</el-button>
-      </template>
-    </Dialog>
-
-    <!-- 人员搜索 -->
-    <Dialog :title="radioObject.menduner" v-model="openSearch" :modalClose="false" width="1200" @close="openSearch = false">
-      <Search ref="SearchRef" />
-      <template #footer>
-        <el-button type="primary" @click="handleSubmit">确 认</el-button>
-        <el-button @click="openSearch = false">取 消</el-button>
-      </template>
-    </Dialog>
+  <!-- 选择来源 -->
+  <Dialog title="新增" v-model="openSelect" width="550" @close="openSelect = false">
+		<el-radio-group v-model="radioValue" size="large" class="radioBox">
+			<el-radio
+				v-for="item in radioList"
+				:key="item.value"
+				:value="item.value"
+			>
+				{{ item.label }}
+			</el-radio>
+		</el-radio-group>
+    <template #footer>
+      <el-button type="primary" @click="handleSelect">确 认</el-button>
+      <el-button @click="openSelect = false">取 消</el-button>
+    </template>
+  </Dialog>
+
+  <!-- 人员搜索 -->
+  <Dialog :title="radioObject.menduner" v-model="openSearch" :modalClose="false" width="1200" @close="openSearch = false">
+    <Search @detail="handleDetail" />
+  </Dialog>
 
     <!-- 解析文件上传 -->
-    <Dialog :title="radioObject[radioValue]" v-model="dialog_upload" :modalClose="false" :width="DialogWidth" @close="handleCancel">
-      <div>
-        <!-- 简历解析 -->
-        <template v-if="radioValue === 'file'">
-          <el-upload
-            ref="uploadRef"
-            v-model:file-list="fileList"
-            :action="uploadUrl"
-            :auto-upload="false"
-            :data="fileData"
-            :limit="1"
-            :on-change="handleChange"
-            :on-error="submitFormError"
-            :on-exceed="handleExceed"
-            :on-success="submitFormSuccess"
-            :http-request="httpRequest"
-            accept=".pdf, doc, .docx"
-            drag
-            class="flex-1"
-          >
-            <i class="el-icon-upload"></i>
-            <div class="el-upload__text">上传附件, 将文件拖到此处,或 <em>点击上传</em></div>
-            <template #tip>
-              <div class="el-upload__tip color-red">
-                提示:仅允许导入 pdf、doc、docx 格式文件!
-              </div>
-            </template>
-          </el-upload>
-        </template>
-        <!-- 名片解析 -->
-        <template v-if="radioValue === 'card'">
-          <UploadImg
-            v-model="cardImgUrl"
-            :limit="1"
-            :uploadSuccessTip="false"
-            @handle-change="cardUploadChange"
-            height="150px" width="150px" style="margin: 20px auto; width: 150px;"
-          >
-            <template #tip>{{ cardImgUrl ? '' : '请上传名片' }}</template>
-          </UploadImg>
-        </template>
-        <template v-if="radioValue === 'picture'">
+  <Dialog :title="radioObject[radioValue]" v-model="dialog_upload" :modalClose="false" :width="DialogWidth" @close="handleCancel">
+    <div>
+      <!-- 简历解析 -->
+      <template v-if="radioValue === 'file'">
+        <el-upload
+          ref="uploadRef"
+          v-model:file-list="fileList"
+          :action="uploadUrl"
+          :auto-upload="false"
+          :data="fileData"
+          :on-change="handleChange"
+          :on-error="submitFormError"
+          :on-exceed="handleExceed"
+          :http-request="httpRequest"
+          accept=".pdf"
+          drag
+          multiple
+          class="flex-1"
+        >
+          <i class="el-icon-upload"></i>
+          <div class="el-upload__text">上传附件, 将文件拖到此处,或 <em>点击上传</em></div>
+          <template #tip>
+            <div class="el-upload__tip color-red">
+              提示:仅允许导入 PDF格式文件!
+            </div>
+          </template>
+        </el-upload>
+      </template>
+      <!-- 名片解析 -->
+      <template v-if="radioValue === 'card'">
+        <UploadImgs
+          v-model="cardImgUrl"
+          :uploadSuccessTip="false"
+          :limit="30"
+          @handle-change="cardUploadChange"
+          height="150px" width="150px" style="margin: 20px auto; text-align: center;"
+        >
+          <template #tip>{{ cardImgUrl ? '' : '请上传名片' }}</template>
+        </UploadImgs>
+      </template>
+      <!-- 杂项 -->
+      <template v-if="radioValue === 'picture'">
+        <div class="flex align-center">
           <UploadImg
             v-model="cardImgUrl"
-            :limit="1"
             :uploadSuccessTip="false"
-            @handle-change="cardUploadChange"
-            height="150px" width="150px" style="margin: 20px auto; width: 150px;"
+            @handle-change="val => cardUploadChange(val, '杂项')"
+            height="150px" width="150px" style="margin: 20px auto; text-align: center;"
           >
             <template #tip>{{ cardImgUrl ? '' : '请上传图片' }}</template>
           </UploadImg>
-        </template>
-      </div>
-      <template #footer>
-        <el-button @click="handleAnalysis" type="success" :disabled="analysisLoading" :loading="analysisLoading">解 析</el-button>
-        <el-button @click="handleCancel">取 消</el-button>
-      </template>
-    </Dialog>
-
-    <!-- 解析回显 -->
-    <Dialog :title="radioObject[radioValue]" v-model="dialog_analysisInfo" :modalClose="false" width="90%" @close="dialog_analysisInfo = false">
-      <div class="analysisInfoBox">
-        <div class="analysisFile" :style="{'width': showFormPage || tagList?.length > 0 ? '50%' :'100%'}">
-          <!-- 门墩儿人才库 -->
-          <template v-if="radioValue === 'menduner'">
-            <el-tabs v-model="activeName" type="border-card">
-              <el-tab-pane label="基本信息" name="info">
-                <el-card shadow="never">
-                  <Info :id="id" :user-id="userId" />
-                  <expExtend :user-id="userId" defaultShowAll class="m-t-20px" />
-                </el-card>
-              </el-tab-pane>
-
-              <el-tab-pane label="附件简历" name="Attachment">
-                <Attachment showPreview :user-id="userId" />
-              </el-tab-pane>
-            </el-tabs>
-          </template>
-          <!-- 简历解析 -->
-          <template v-if="radioValue === 'file'">
-            <div v-if="fileUrl" style="position: relative;">
-              <div class="text-right m-b-10px">
-                <el-button v-if="!isEdit" @click="handleText">查看文本信息</el-button>
-                <el-button type="primary" @click="handleResetUpload">重新上传简历</el-button>
-              </div>
-              <IFrame :src="fileUrl" />
-              <el-drawer
-                v-model="drawer"
-                modal-class="drawer"
-                size="75%"
-                direction="ltr"
-                title="简历解析(可复制文本使用)"
-              >
-                <p v-for="(text, index) in resumeTxt" :key="text + index">{{ text }}</p>
-              </el-drawer>
-            </div>
-            <el-upload
-              v-else
-              ref="uploadRef"
-              v-model:file-list="fileList"
-              :action="uploadUrl"
-              :auto-upload="false"
-              :data="data"
-              :limit="1"
-              :on-change="handleChange"
-              :on-error="submitFormError"
-              :on-exceed="handleExceed"
-              :on-success="submitFormSuccess"
-              :http-request="httpRequest"
-              accept=".pdf, doc, .docx"
-              drag
-              class="flex-1"
-            >
-              <i class="el-icon-upload"></i>
-              <div class="el-upload__text">上传附件, 将文件拖到此处,或 <em>点击上传</em></div>
-              <template #tip>
-                <div class="el-upload__tip color-red">
-                  提示:仅允许导入 pdf、doc、docx 格式文件!
-                </div>
-              </template>
-            </el-upload>
-          </template>
-          <!-- 名片解析 -->
-          <template v-if="radioValue === 'card'">
-            <div class="image">
-              <el-image v-if="cardImgUrl" class="!w-100%" :src="cardImgUrl" />
-              <div v-else>
-                <UploadImg
-                  v-model="cardImgUrl"
-                  :limit="1"
-                  :uploadSuccessTip="false"
-                  drag
-                  buttonUpload
-                  @handle-change="cardUploadChange"
-                  height="32px" width="104px"
-                  style="margin: 0 auto; width: 104px;margin-top: 40%;"
-                >
-                  <template #tip>{{ cardImgUrl ? '' : '请上传名片' }}</template>
-                </UploadImg>
-              </div>
-            </div>
-          </template>
-          <!-- 网页解析 -->
-          <template v-if="radioValue === 'web'">
-            <webAnalysis
-              v-if="showWebAnalysis"
-              @analysis="handleWebAnalysis"
-              @reset="handleWebClear(), showFormPage = false, formData = {}"
-            />
-          </template>
-        </div>
-        <div class="flex-1">
-          <div class="tagBox mb-10px" v-if="tagList?.length">
-            <el-tag 
-              type="primary" 
-              round
-              size="large"
-              v-for="(val, index) in tagList"
-              :key="index + val"
-              class="mr-10px cursor-pointer mb-10px"
-              @click="handleTagClick(index)"
-            >
-              {{ val }}
-              <Icon v-if="index === tagCurrentIndex" icon="ep:check" class="ml-5px" /> 
-            </el-tag>
-          </div>
-          <FormPage
-            v-if="showFormPage"
-            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">保 存</el-button>
-        <el-button @click="dialog_analysisInfo = false, handleWebClear">取 消</el-button>
       </template>
-    </Dialog>
-  </ContentWrap>
-
-  <MergeForm ref="mergeFormRef" @refresh="getList" />
+      <!-- 网页解析 -->
+      <template v-if="radioValue === 'web'">
+        <webAnalysis
+          @analysis="handleWebAnalysis"
+          @reset="webOriginList = []"
+        />
+      </template>
+    </div>
+    <template #footer>
+      <el-button @click="handleSubmit" type="success" :disabled="analysisLoading" :loading="analysisLoading">提 交</el-button>
+      <el-button @click="handleCancel">取 消</el-button>
+    </template>
+  </Dialog>
+
+  <StorePage ref="StorePageRef" @refresh="getList" />
 </template>
 
 <script setup>
@@ -265,10 +169,7 @@ defineOptions({ name: 'TalentMapStoreIndex' })
 import { dateFormatter } from '@/utils/formatTime'
 import { talentLabelingApi } from '@/api/menduner/system/talentMap/labeling'
 import { TalentMap } from '@/api/menduner/system/talentMap'
-import { Delete, Plus } from '@element-plus/icons-vue'
-import MergeForm from '@/views/menduner/system/talentMap/components/merge.vue'
-import FormPage from '@/views/menduner/system/talentMap/components/FormPage.vue'
-// import uploadDialog from './components/uploadDialog.vue'
+import { Plus } from '@element-plus/icons-vue'
 import { timesTampChange, timestampToAge } from '@/utils/transform/date'
 import Search from './components/search.vue'
 import webAnalysis from './components/webAnalysis.vue'
@@ -280,8 +181,9 @@ import expExtend from '@/views/menduner/system/person/details/components/expExte
 import Attachment from '@/views/menduner/system/person/details/components/attachment.vue'
 import { talentWebParsingApi } from '@/api/menduner/system/talentMap/webParsing'
 import { ElLoading } from 'element-plus'
+import StorePage from '@/views/menduner/system/talentMap/maintenance/gather/components/store.vue'
+import { talentGatherApi } from '@/api/menduner/system/talentMap/gather'
 
-const baseUrl = import.meta.env.VITE_PREVIEW_URL
 const { uploadUrl, httpRequest } = useUpload()
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
@@ -290,32 +192,31 @@ const loading = ref(false) // 列表的加载中
 const list = ref([]) // 列表的数据
 const total = ref(0) // 列表的总页数
 const queryParams = reactive({
-  name: undefined
+  page: 1,
+  per_page: 10,
+  task_type: undefined, // 任务类型
+  task_status: undefined // 任务状态
 })
 const queryFormRef = ref() // 搜索的表单
 const dialog_upload = ref(false)
-
-const loadingMark = ref(null)
-const loadingMarkSetting = (text) => {
-  if (text) {
-    loadingMark.value = ElLoading.service({
-      lock: true,
-      text,
-      background: 'rgba(0, 0, 0, 0.7)',
-    })
-  } else {
-    if (loadingMark.value) loadingMark.value.close()
-    loadingMark.value = null
-  }
-}
+const taskType = [
+  { label: '名片', value: '名片' },
+  { label: '简历', value: '简历' },
+  { label: '门墩儿新任命', value: '新任命' },
+  { label: '门墩儿招聘', value: '招聘' },
+  { label: '杂项', value: '杂项' }
+]
+const taskStatus = ['待解析', '解析中', '解析完成']
+const itemData = ref({})
 
 /** 查询列表 */
 const getList = async () => {
   loading.value = true
   try {
     list.value = []
-    const data = await talentLabelingApi.getCardList()
-    list.value = data ?? []
+    const data = await talentGatherApi.getTaskList(queryParams)
+    list.value = data.tasks ?? []
+    total.value = data.pagination.total ?? 0
   } finally {
     loading.value = false
   }
@@ -323,10 +224,7 @@ const getList = async () => {
 
 /** 搜索按钮操作 */
 const handleQuery = (type) => {
-  if (type !== 'reset') {
-    message.warning('搜索正在建设中...')
-    return
-  }
+  queryParams.page = 1
   getList()
 }
 
@@ -336,270 +234,79 @@ const resetQuery = () => {
   handleQuery('reset')
 }
 
-/** 删除按钮操作 */
-const handleDelete = async (id) => {
-  try {
-    // 删除的二次确认
-    await message.delConfirm()
-    // 发起删除
-    await talentLabelingApi.deleteBusinessCard(id)
-    message.success(t('common.delSuccess'))
-    // 刷新列表
-    setTimeout(async () => {
-      await getList()
-    }, 0)
-  } catch {}
-}
-
-// 网页解析-人才标签点击
-const handleTagClick = (index) => {
-  if (!webOriginList.value?.[index]) return
-  tagCurrentIndex.value = index
-  const obj = webOriginList.value[index]
-  formData.value = obj ?? {}
-  if (!showFormPage.value) showFormPage.value = true
-}
-
 // 网页解析-信息提取
-const tagCurrentIndex = ref(null)
-const tagList = ref([])
-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)
-  }
+const handleWebAnalysis = (list) => {
+  webOriginList.value = list ?? []
 }
 
-const handleWebClear = () => {
-  tagCurrentIndex.value = null
-  originMarkdown.value = null
-  tagList.value = []
-  webOriginList.value = []
+// 入库
+const StorePageRef = ref(null)
+const handleStore = (row) => {
+  StorePageRef.value.open(row)
 }
 
-/** 编辑 */
-const { push } = useRouter()
-const handleEdit = async (item) => {
-  analysisType.value = 'edit'
-  formData.value = item
-  radioValue.value = item.type || 'card' // menduner
-  try {
-    if (radioValue.value === 'card') {
-      if (!item?.image_path) {
-        cardUploadRow.value = null
-        cardImgUrl.value = null
-        dialog_analysisInfo.value = true
-        return
-      }
-      const data = await talentLabelingApi.getBusinessCardImage(item.image_path)
-      cardUploadRow.value = data?.type ? new File([data], item.image_path, { type: data.type }) : null
-      cardImgUrl.value = data?.type ? URL.createObjectURL(data) : null
-    }
-  } catch (error) {
-    console.log('打印->getBusinessCardImage', error)
-  } finally {
-    dialog_analysisInfo.value = true
-  }
-}
-
-/** 禁用按钮操作 */
-const handleDisable = async (item) => {
-  if (!item?.id) return message.warning('操作失败,请稍后再试')
-  try {
-    // 禁用的二次确认
-    const status = item.status === 'active' ? 'inactive' : 'active'
-    const text = status === 'inactive' ? '禁用' : '启用'
-    
-    await message.delConfirm(`是否${text}该名片?`)
-    // 发起禁用
-    await talentLabelingApi.updateBusinessCardStatus({
-      status,
-    }, item.id)
-    message.success(`${text}成功`)
-    // 刷新列表
-    await getList()
-  } catch {}
+// 关闭上传弹窗
+const handleCancel = () => {
+  dialog_upload.value = false
+  analysisLoading.value = false
+  cardUploadRow.value = []
+  fileList.value = []
 }
 
-// 更新
-const activeName = ref('info')
-const dialog_analysisInfo = 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)',
-  })
+// 创建任务
+const handleCreateTask = async (query) => {
   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
-    // 刷新列表
+    await talentGatherApi.createTask(query)
+    message.success('任务创建成功')
     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()
+    analysisLoading.value = false
+    webOriginList.value = []
+    handleCancel()
   }
-  
 }
 
-const handleSave = async () => {
-  // 网页解析新增调用单独的接口保存
-  if (analysisType.value === 'create' && radioValue.value === 'web') return handleSaveWebData()
+// 新任命
+const handleCreateWebTask = async () => {
+  if (!webOriginList.value || !webOriginList.value?.length) return message.warning('请输入新任命链接查看后再进行提交')
 
-  if (!FormPageRef.value) return message.warning('请将表单信息完善后再提交')
-
-  const params = { ...FormPageRef.value.formQuery, type: radioValue.value }
-  if (!params.name_zh) return message.warning('请填写姓名!')
-  
-  // 数组转为字符串保存
-  if (Array.isArray(params?.mobile)) {
-    params.mobile = params.mobile.filter(i => Boolean(i)).map(j => String(j).replace(/,|,/g, '')).join(',');
-  }
+  analysisLoading.value = true
 
-  const loading = ElLoading.service({
-    lock: true,
-    text: '正在保存中...',
-    background: 'rgba(0, 0, 0, 0.7)',
+  const formData = new FormData()
+  formData.append('task_type', '新任命')
+  webOriginList.value.forEach(e => {
+    formData.append('files', e.file)
   })
-  try {
-    let result = {}
 
-    if (analysisType.value === 'create') {
-      if (cardFileQuery.value) {
-        cardFileQuery.value.append('card_data', JSON.stringify(params)) // 名片
-        result = await talentLabelingApi.createBusinessCard(cardFileQuery.value)
-      } else {
-        // 结构化数据源 不传递文件
-        result = await talentLabelingApi.createBusinessCardPost(params)
-      }
-      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)
-      }
-    } else {
-      await talentLabelingApi.updateBusinessCard(params, formData.value.id)
-      message.success('更新成功')
-      dialog_analysisInfo.value = false
-      // 刷新列表
-      getList()
-    }
-  } catch (error) {
-    console.log('更新失败', error)
-  } finally {
-    cardFileQuery.value = null
-    loading.close()
-    openSearch.value = false
-    handleWebClear()
-  }
+  handleCreateTask(formData)
 }
 
-// 解析中
+// 名片、简历、杂项
 const analysisLoading = ref(false)
-const formData = ref({})
-const handleAnalysis = async () => {
-  // 开始解析
-  analysisLoading.value = true
-  loadingMarkSetting('解析中...')
-  cardFileQuery.value = null
-  formData.value = null
-	const type = radioValue.value
-  try {
-    // if (type === 'menduner') { // 门墩儿人才库
-    // } else 
-    if (type === 'file') { // 简历解析
-      if (!fileUrl.value) return message.warning('获取文件失败,请重新上传!')
-      const data = await commonApi.resumeParser({ fileUrl: fileUrl.value })
-      resumeAnalysisToForm(data) // 简历解析
-    } else if (type === 'card') { // 名片解析
-      if (!cardImgUrl.value) {
-        message.warning('请先上传名片!')
-        return
-      }
-      cardFileQuery.value = new FormData()
-      cardFileQuery.value.append('image', cardUploadRow.value)
-      message.warning('正在解析...')
-  
-      const index = createAnalysisNum.value
-      const res = await talentLabelingApi.businessCardParse(cardFileQuery.value)
-      if (index !== createAnalysisNum.value || !dialog_upload.value) return // 不是最新的名片解析数据(用户在解析完成前已重新上传)或用户已取消解析
-      formData.value = res?.data || res
-      message.success('名片解析成功')
+const handleSubmit = async () => {
+  const type = radioValue.value
+  // 新任命任务创建
+  if (type === 'web') return handleCreateWebTask()
+
+  if (['card', 'file', 'picture'].includes(type)) {
+    const currentList = type === 'file' ? fileList.value : cardUploadRow.value
+    if (!currentList || !currentList?.length) {
+      message.warning(`请上传${type === 'file' ? '简历' : type === 'card' ? '名片' : '杂项'}`)
+      return
     }
-    // else if (type === 'web') {}
+    analysisLoading.value = true
 
-    dialog_upload.value = false
-    dialog_analysisInfo.value = true
-  } catch (error) {
-    console.log('解析失败', error)
-    cardFileQuery.value = null
-  } finally {
-    analysisLoading.value = false
-    loadingMarkSetting()
+    const formData = new FormData()
+    formData.append('task_type', type === 'file' ? '简历' : type === 'card' ? '名片' : '杂项')
+    currentList.forEach(e => {
+      formData.append('files', e.raw)
+    })
+    handleCreateTask(formData)
   }
 }
 
-// 监听表单变化,同步更新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()
 const fileList = ref([])
 const fileData = ref({ path: '' })
@@ -607,17 +314,6 @@ const fileData = ref({ path: '' })
 const handleChange = async (file) => {
   fileData.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
-  if (dialog_analysisInfo.value) {
-    if (FormPageRef.value?.changeLoading) FormPageRef.value.changeLoading(true)
-    message.warning('正在解析...')
-    const data = await commonApi.resumeParser({ fileUrl: fileUrl.value })
-    resumeAnalysisToForm(data) // 简历解析
-    if (FormPageRef.value?.changeLoading) FormPageRef.value.changeLoading(false)
-  }
 }
 const submitFormError = () => {
   message.error('上传失败,请您重新上传!')
@@ -625,34 +321,10 @@ const submitFormError = () => {
 const handleExceed = () => {
   message.error('最多只能上传一个文件!')
 }
-const submitFormSuccess = (e) => {
-  // 清理
-  // unref(uploadRef)?.clearFiles()
-}
-
-const drawer = ref(false)
-const resumeTxt = ref([])
-// 查看文本信息
-const handleText = () => {
-  drawer.value = true
-}
-// 重新上传简历
-const handleResetUpload = async () => {
-  await message.confirm('是否确定重新上传简历?确定后将清空当前信息')
-  fileUrl.value = ''
-  fileData.value.path = ''
-  fileList.value = []
-  resumeAnalysisToForm('reset') // 简历解析
-}
 
 // 简历解析数据解构赋值
 const resumeAnalysisToForm = (data) => {
-  if (data === 'reset') {
-    // 重置表单
-    resumeTxt.value = ''
-    if (FormPageRef.value?.resetFormData) FormPageRef.value.resetFormData()
-  }
-  formData.value = {
+  itemData.value = {
     name_zh: data?.person?.name || '',
     email: data?.person?.email || '',
     mobile: data?.person?.phone || '',
@@ -668,71 +340,47 @@ const resumeAnalysisToForm = (data) => {
     created_at: data?.person?.createTime ? timesTampChange(data.person.createTime, 'Y-M-D') : null,
     updated_at: data?.person?.updateTime ? timesTampChange(data.person.updateTime, 'Y-M-D') : null,
   }
-  resumeTxt.value = data?.resume?.rawText?.split('\n') || ''
-  if (FormPageRef.value?.setFormData) FormPageRef.value.setFormData(formData.value)
 }
 
 // 名片解析 
-const createAnalysisNum = ref(0)
-const cardFileQuery = ref(null)
 const cardUploadRow = ref(null)
 const cardImgUrl = ref(null)
-const cardUploadChange = (raw) => {
-  cardUploadRow.value = raw
+const cardUploadChange = (file, type) => {
+  cardUploadRow.value = type ? [{ raw: file }] : file
 }
 
 // 人员搜索
 const openSearch = ref(false)
 const id = ref(null)
 const userId = ref(null)
-// const handleDetail = async ({id: use_id, userId: use_userId}) => {
-//   if (!use_userId || !use_userId)  return message.warning('请先选择人才!')
-//   id.value = use_id; userId.value = use_userId
-//   try {
-//     const data = await TalentMap.getTalentMapDetail(use_userId)
-//     resumeAnalysisToForm(data) // 简历解析
-//     dialog_analysisInfo.value = true
-//   } catch {}
-// }
-
-const SearchRef = ref(null)
-const handleSubmit = async () => {
-  console.log('打印->addList', SearchRef.value.addList)
-  message.success('创建任务成功!')
-  openSearch.value = false
+const handleDetail = async ({id: use_id, userId: use_userId}) => {
+  if (!use_userId || !use_userId)  return message.warning('请先选择人才!')
+  id.value = use_id; userId.value = use_userId
+  try {
+    const data = await TalentMap.getTalentMapDetail(use_userId)
+    // 去除id
+    resumeAnalysisToForm(data) // 简历解析
+  } catch {}
 }
 
-const DialogWidth = ref('500')
-const showWebAnalysis = ref(false)
 // 选择解析方式
+const DialogWidth = ref('500')
 const handleSelect = () => {
   openSelect.value = false
-  formData.value = null
-  showWebAnalysis.value = false
-  showFormPage.value = true // 默认展示表单
+  itemData.value = {}
+  DialogWidth.value = '500'
+  
 	const type = radioValue.value
-  if (type === 'card') {
-    createAnalysisNum.value++
+  if (['card', 'web'].includes(type)) {
+    DialogWidth.value = '800'
   }
   if (type === 'menduner') {
     openSearch.value = true
     return
   }
-  if (type === 'web') {
-    showFormPage.value = false // 新任命没有表单
-    showWebAnalysis.value = true
-    dialog_analysisInfo.value = true
-    return
-  }
   dialog_upload.value = true
 }
 
-// 关闭上传弹窗
-const handleCancel = () => {
-  dialog_upload.value = false
-  analysisLoading.value = false
-}
-
 const openSelect = ref(false)
 const radioObject = { card: '名片', file: '简历', web: '门墩儿新任命', menduner: '门墩儿招聘', picture: '杂项' }
 const radioList = ref(Object.keys(radioObject).map(key => ({ value: key, label: radioObject[key]}) ))
@@ -740,16 +388,11 @@ const defaultValue = radioList.value[0].value // 默认选中
 const radioValue = ref(defaultValue)
 // 新增解析
 const handleAdd = () => {
-  handleWebClear()
+  webOriginList.value = []
   cardUploadRow.value = null
   cardImgUrl.value = null
-  fileUrl.value = ''
-  fileData.value.path = ''
-  fileList.value = []
   analysisLoading.value = false
-  analysisType.value = 'create'
   radioValue.value = defaultValue // 重置解析类型
-  // 
   openSelect.value = true
 }
 
@@ -761,30 +404,9 @@ onMounted(() => {
 </script>
 
 <style lang="scss" scoped>
-.analysisInfoBox {
-  display: flex;
-  .analysisFile {
-    // width: 50%;
-    // max-height: 70vh;
-    padding-right: 12px;
-    overflow: auto;
-  }
-}
 .radioBox {
 	margin: 40px 0;
 }
-
-:deep(.drawer) {
-  position: absolute;
-  .el-drawer {
-    background-color: aliceblue;
-  }
-}
-.tagBox {
-  padding: 12px;
-  border: 1px dashed #409EFF;
-  border-radius: 4px;
-}
 :deep {
   .el-tag__content {
     display: flex;

+ 784 - 17
src/views/menduner/system/talentMap/maintenance/gather/index1.vue

@@ -1,27 +1,794 @@
 <template>
-  <div>
-    <el-tabs v-model="activeName" type="border-card">
-      <el-tab-pane label="简历解析" name="resume">
-        <resumePage/>
-      </el-tab-pane>
-      <el-tab-pane label="名片解析" name="card">
-        <cardPage/>
-      </el-tab-pane>
-      <el-tab-pane label="网页解析" name="webpage">
-        <webPageParsing/>
-      </el-tab-pane>
-    </el-tabs>
-  </div>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="名称" prop="name">
+        <el-input v-model="queryParams.name" placeholder="请输入名称" clearable @keyup.enter="handleQuery" class="!w-180px" />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery('search')"><Icon icon="ep:search" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button type="primary" plain @click="handleAdd">
+          <Icon icon="ep:plus" class="mr-5px" /> 新增人才
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true">
+      <el-table-column label="中文名称" align="center" prop="name_zh" fixed="left" />
+      <el-table-column label="英文名称" align="center" prop="name_en" />
+      <el-table-column label="职位" align="center" prop="title_zh" />
+      <el-table-column label="酒店/公司" align="center" prop="hotel_zh" />
+      <el-table-column label="手机号码" align="center" prop="mobile" />
+      <el-table-column label="固定电话" align="center" prop="phone" />
+      <el-table-column label="状态" align="center" prop="status" width="80">
+        <template #default="scope">
+          <el-tag type="success" v-if="scope.row.status === 'active'">已启用</el-tag>
+          <el-tag type="danger" v-else>已禁用</el-tag>
+        </template>
+      </el-table-column>
+      <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="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' ? '禁用' : '启用'}}
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 选择来源 -->
+    <Dialog title="新增" v-model="openSelect" width="550" @close="openSelect = false">
+			<el-radio-group v-model="radioValue" size="large" class="radioBox">
+				<el-radio
+					v-for="item in radioList"
+					:key="item.value"
+					:value="item.value"
+				>
+					{{ item.label }}
+				</el-radio>
+			</el-radio-group>
+      <template #footer>
+        <el-button type="primary" @click="handleSelect">确 认</el-button>
+        <el-button @click="openSelect = false">取 消</el-button>
+      </template>
+    </Dialog>
+
+    <!-- 人员搜索 -->
+    <Dialog :title="radioObject.menduner" v-model="openSearch" :modalClose="false" width="1200" @close="openSearch = false">
+      <Search ref="SearchRef" />
+      <template #footer>
+        <el-button type="primary" @click="handleSubmit">确 认</el-button>
+        <el-button @click="openSearch = false">取 消</el-button>
+      </template>
+    </Dialog>
+
+    <!-- 解析文件上传 -->
+    <Dialog :title="radioObject[radioValue]" v-model="dialog_upload" :modalClose="false" :width="DialogWidth" @close="handleCancel">
+      <div>
+        <!-- 简历解析 -->
+        <template v-if="radioValue === 'file'">
+          <el-upload
+            ref="uploadRef"
+            v-model:file-list="fileList"
+            :action="uploadUrl"
+            :auto-upload="false"
+            :data="fileData"
+            :limit="1"
+            :on-change="handleChange"
+            :on-error="submitFormError"
+            :on-exceed="handleExceed"
+            :on-success="submitFormSuccess"
+            :http-request="httpRequest"
+            accept=".pdf, doc, .docx"
+            drag
+            class="flex-1"
+          >
+            <i class="el-icon-upload"></i>
+            <div class="el-upload__text">上传附件, 将文件拖到此处,或 <em>点击上传</em></div>
+            <template #tip>
+              <div class="el-upload__tip color-red">
+                提示:仅允许导入 pdf、doc、docx 格式文件!
+              </div>
+            </template>
+          </el-upload>
+        </template>
+        <!-- 名片解析 -->
+        <template v-if="radioValue === 'card'">
+          <UploadImg
+            v-model="cardImgUrl"
+            :limit="1"
+            :uploadSuccessTip="false"
+            @handle-change="cardUploadChange"
+            height="150px" width="150px" style="margin: 20px auto; width: 150px;"
+          >
+            <template #tip>{{ cardImgUrl ? '' : '请上传名片' }}</template>
+          </UploadImg>
+        </template>
+        <template v-if="radioValue === 'picture'">
+          <UploadImg
+            v-model="cardImgUrl"
+            :limit="1"
+            :uploadSuccessTip="false"
+            @handle-change="cardUploadChange"
+            height="150px" width="150px" style="margin: 20px auto; width: 150px;"
+          >
+            <template #tip>{{ cardImgUrl ? '' : '请上传图片' }}</template>
+          </UploadImg>
+        </template>
+      </div>
+      <template #footer>
+        <el-button @click="handleAnalysis" type="success" :disabled="analysisLoading" :loading="analysisLoading">解 析</el-button>
+        <el-button @click="handleCancel">取 消</el-button>
+      </template>
+    </Dialog>
+
+    <!-- 解析回显 -->
+    <Dialog :title="radioObject[radioValue]" v-model="dialog_analysisInfo" :modalClose="false" width="90%" @close="dialog_analysisInfo = false">
+      <div class="analysisInfoBox">
+        <div class="analysisFile" :style="{'width': showFormPage || tagList?.length > 0 ? '50%' :'100%'}">
+          <!-- 门墩儿人才库 -->
+          <template v-if="radioValue === 'menduner'">
+            <el-tabs v-model="activeName" type="border-card">
+              <el-tab-pane label="基本信息" name="info">
+                <el-card shadow="never">
+                  <Info :id="id" :user-id="userId" />
+                  <expExtend :user-id="userId" defaultShowAll class="m-t-20px" />
+                </el-card>
+              </el-tab-pane>
+
+              <el-tab-pane label="附件简历" name="Attachment">
+                <Attachment showPreview :user-id="userId" />
+              </el-tab-pane>
+            </el-tabs>
+          </template>
+          <!-- 简历解析 -->
+          <template v-if="radioValue === 'file'">
+            <div v-if="fileUrl" style="position: relative;">
+              <div class="text-right m-b-10px">
+                <el-button v-if="!isEdit" @click="handleText">查看文本信息</el-button>
+                <el-button type="primary" @click="handleResetUpload">重新上传简历</el-button>
+              </div>
+              <IFrame :src="fileUrl" />
+              <el-drawer
+                v-model="drawer"
+                modal-class="drawer"
+                size="75%"
+                direction="ltr"
+                title="简历解析(可复制文本使用)"
+              >
+                <p v-for="(text, index) in resumeTxt" :key="text + index">{{ text }}</p>
+              </el-drawer>
+            </div>
+            <el-upload
+              v-else
+              ref="uploadRef"
+              v-model:file-list="fileList"
+              :action="uploadUrl"
+              :auto-upload="false"
+              :data="data"
+              :limit="1"
+              :on-change="handleChange"
+              :on-error="submitFormError"
+              :on-exceed="handleExceed"
+              :on-success="submitFormSuccess"
+              :http-request="httpRequest"
+              accept=".pdf, doc, .docx"
+              drag
+              class="flex-1"
+            >
+              <i class="el-icon-upload"></i>
+              <div class="el-upload__text">上传附件, 将文件拖到此处,或 <em>点击上传</em></div>
+              <template #tip>
+                <div class="el-upload__tip color-red">
+                  提示:仅允许导入 pdf、doc、docx 格式文件!
+                </div>
+              </template>
+            </el-upload>
+          </template>
+          <!-- 名片解析 -->
+          <template v-if="radioValue === 'card'">
+            <div class="image">
+              <el-image v-if="cardImgUrl" class="!w-100%" :src="cardImgUrl" />
+              <div v-else>
+                <UploadImg
+                  v-model="cardImgUrl"
+                  :limit="1"
+                  :uploadSuccessTip="false"
+                  drag
+                  buttonUpload
+                  @handle-change="cardUploadChange"
+                  height="32px" width="104px"
+                  style="margin: 0 auto; width: 104px;margin-top: 40%;"
+                >
+                  <template #tip>{{ cardImgUrl ? '' : '请上传名片' }}</template>
+                </UploadImg>
+              </div>
+            </div>
+          </template>
+          <!-- 网页解析 -->
+          <template v-if="radioValue === 'web'">
+            <webAnalysis
+              v-if="showWebAnalysis"
+              @analysis="handleWebAnalysis"
+              @reset="handleWebClear(), showFormPage = false, formData = {}"
+            />
+          </template>
+        </div>
+        <div class="flex-1">
+          <div class="tagBox mb-10px" v-if="tagList?.length">
+            <el-tag 
+              type="primary" 
+              round
+              size="large"
+              v-for="(val, index) in tagList"
+              :key="index + val"
+              class="mr-10px cursor-pointer mb-10px"
+              @click="handleTagClick(index)"
+            >
+              {{ val }}
+              <Icon v-if="index === tagCurrentIndex" icon="ep:check" class="ml-5px" /> 
+            </el-tag>
+          </div>
+          <FormPage
+            v-if="showFormPage"
+            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">保 存</el-button>
+        <el-button @click="dialog_analysisInfo = false, handleWebClear">取 消</el-button>
+      </template>
+    </Dialog>
+  </ContentWrap>
+
+  <MergeForm ref="mergeFormRef" @refresh="getList" />
 </template>
 
 <script setup>
 defineOptions({ name: 'TalentMapStoreIndex' })
-import resumePage from './resume/index.vue'
-import cardPage from './businessCard/index.vue'
-import webPageParsing from './webPageParsing/index.vue'
+import { dateFormatter } from '@/utils/formatTime'
+import { talentLabelingApi } from '@/api/menduner/system/talentMap/labeling'
+import { TalentMap } from '@/api/menduner/system/talentMap'
+import { Delete, Plus } from '@element-plus/icons-vue'
+import MergeForm from '@/views/menduner/system/talentMap/components/merge.vue'
+import FormPage from '@/views/menduner/system/talentMap/components/FormPage.vue'
+// import uploadDialog from './components/uploadDialog.vue'
+import { timesTampChange, timestampToAge } from '@/utils/transform/date'
+import Search from './components/search.vue'
+import webAnalysis from './components/webAnalysis.vue'
+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 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()
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(false) // 列表的加载中
+const list = ref([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  name: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+const dialog_upload = ref(false)
+
+const loadingMark = ref(null)
+const loadingMarkSetting = (text) => {
+  if (text) {
+    loadingMark.value = ElLoading.service({
+      lock: true,
+      text,
+      background: 'rgba(0, 0, 0, 0.7)',
+    })
+  } else {
+    if (loadingMark.value) loadingMark.value.close()
+    loadingMark.value = null
+  }
+}
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    list.value = []
+    const data = await talentLabelingApi.getCardList()
+    list.value = data ?? []
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = (type) => {
+  if (type !== 'reset') {
+    message.warning('搜索正在建设中...')
+    return
+  }
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery('reset')
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await talentLabelingApi.deleteBusinessCard(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    setTimeout(async () => {
+      await getList()
+    }, 0)
+  } catch {}
+}
+
+// 网页解析-人才标签点击
+const handleTagClick = (index) => {
+  if (!webOriginList.value?.[index]) return
+  tagCurrentIndex.value = index
+  const obj = webOriginList.value[index]
+  formData.value = obj ?? {}
+  if (!showFormPage.value) showFormPage.value = true
+}
+
+// 网页解析-信息提取
+const tagCurrentIndex = ref(null)
+const tagList = ref([])
+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)
+  }
+}
+
+const handleWebClear = () => {
+  tagCurrentIndex.value = null
+  originMarkdown.value = null
+  tagList.value = []
+  webOriginList.value = []
+}
+
+/** 编辑 */
+const { push } = useRouter()
+const handleEdit = async (item) => {
+  analysisType.value = 'edit'
+  formData.value = item
+  radioValue.value = item.type || 'card' // menduner
+  try {
+    if (radioValue.value === 'card') {
+      if (!item?.image_path) {
+        cardUploadRow.value = null
+        cardImgUrl.value = null
+        dialog_analysisInfo.value = true
+        return
+      }
+      const data = await talentLabelingApi.getBusinessCardImage(item.image_path)
+      cardUploadRow.value = data?.type ? new File([data], item.image_path, { type: data.type }) : null
+      cardImgUrl.value = data?.type ? URL.createObjectURL(data) : null
+    }
+  } catch (error) {
+    console.log('打印->getBusinessCardImage', error)
+  } finally {
+    dialog_analysisInfo.value = true
+  }
+}
+
+/** 禁用按钮操作 */
+const handleDisable = async (item) => {
+  if (!item?.id) return message.warning('操作失败,请稍后再试')
+  try {
+    // 禁用的二次确认
+    const status = item.status === 'active' ? 'inactive' : 'active'
+    const text = status === 'inactive' ? '禁用' : '启用'
+    
+    await message.delConfirm(`是否${text}该名片?`)
+    // 发起禁用
+    await talentLabelingApi.updateBusinessCardStatus({
+      status,
+    }, item.id)
+    message.success(`${text}成功`)
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+// 更新
+const activeName = ref('info')
+const dialog_analysisInfo = 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 }
+  if (!params.name_zh) return message.warning('请填写姓名!')
+  
+  // 数组转为字符串保存
+  if (Array.isArray(params?.mobile)) {
+    params.mobile = params.mobile.filter(i => Boolean(i)).map(j => String(j).replace(/,|,/g, '')).join(',');
+  }
+
+  const loading = ElLoading.service({
+    lock: true,
+    text: '正在保存中...',
+    background: 'rgba(0, 0, 0, 0.7)',
+  })
+  try {
+    let result = {}
+
+    if (analysisType.value === 'create') {
+      if (cardFileQuery.value) {
+        cardFileQuery.value.append('card_data', JSON.stringify(params)) // 名片
+        result = await talentLabelingApi.createBusinessCard(cardFileQuery.value)
+      } else {
+        // 结构化数据源 不传递文件
+        result = await talentLabelingApi.createBusinessCardPost(params)
+      }
+      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)
+      }
+    } else {
+      await talentLabelingApi.updateBusinessCard(params, formData.value.id)
+      message.success('更新成功')
+      dialog_analysisInfo.value = false
+      // 刷新列表
+      getList()
+    }
+  } catch (error) {
+    console.log('更新失败', error)
+  } finally {
+    cardFileQuery.value = null
+    loading.close()
+    openSearch.value = false
+    handleWebClear()
+  }
+}
+
+// 解析中
+const analysisLoading = ref(false)
+const formData = ref({})
+const handleAnalysis = async () => {
+  // 开始解析
+  analysisLoading.value = true
+  loadingMarkSetting('解析中...')
+  cardFileQuery.value = null
+  formData.value = null
+	const type = radioValue.value
+  try {
+    // if (type === 'menduner') { // 门墩儿人才库
+    // } else 
+    if (type === 'file') { // 简历解析
+      if (!fileUrl.value) return message.warning('获取文件失败,请重新上传!')
+      const data = await commonApi.resumeParser({ fileUrl: fileUrl.value })
+      resumeAnalysisToForm(data) // 简历解析
+    } else if (type === 'card') { // 名片解析
+      if (!cardImgUrl.value) {
+        message.warning('请先上传名片!')
+        return
+      }
+      cardFileQuery.value = new FormData()
+      cardFileQuery.value.append('image', cardUploadRow.value)
+      message.warning('正在解析...')
+  
+      const index = createAnalysisNum.value
+      const res = await talentLabelingApi.businessCardParse(cardFileQuery.value)
+      if (index !== createAnalysisNum.value || !dialog_upload.value) return // 不是最新的名片解析数据(用户在解析完成前已重新上传)或用户已取消解析
+      formData.value = res?.data || res
+      message.success('名片解析成功')
+    }
+    // else if (type === 'web') {}
+
+    dialog_upload.value = false
+    dialog_analysisInfo.value = true
+  } catch (error) {
+    console.log('解析失败', error)
+    cardFileQuery.value = null
+  } finally {
+    analysisLoading.value = false
+    loadingMarkSetting()
+  }
+}
+
+// 监听表单变化,同步更新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()
+const fileList = ref([])
+const fileData = ref({ path: '' })
+// 文件上传
+const handleChange = async (file) => {
+  fileData.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
+  if (dialog_analysisInfo.value) {
+    if (FormPageRef.value?.changeLoading) FormPageRef.value.changeLoading(true)
+    message.warning('正在解析...')
+    const data = await commonApi.resumeParser({ fileUrl: fileUrl.value })
+    resumeAnalysisToForm(data) // 简历解析
+    if (FormPageRef.value?.changeLoading) FormPageRef.value.changeLoading(false)
+  }
+}
+const submitFormError = () => {
+  message.error('上传失败,请您重新上传!')
+}
+const handleExceed = () => {
+  message.error('最多只能上传一个文件!')
+}
+const submitFormSuccess = (e) => {
+  // 清理
+  // unref(uploadRef)?.clearFiles()
+}
+
+const drawer = ref(false)
+const resumeTxt = ref([])
+// 查看文本信息
+const handleText = () => {
+  drawer.value = true
+}
+// 重新上传简历
+const handleResetUpload = async () => {
+  await message.confirm('是否确定重新上传简历?确定后将清空当前信息')
+  fileUrl.value = ''
+  fileData.value.path = ''
+  fileList.value = []
+  resumeAnalysisToForm('reset') // 简历解析
+}
+
+// 简历解析数据解构赋值
+const resumeAnalysisToForm = (data) => {
+  if (data === 'reset') {
+    // 重置表单
+    resumeTxt.value = ''
+    if (FormPageRef.value?.resetFormData) FormPageRef.value.resetFormData()
+  }
+  formData.value = {
+    name_zh: data?.person?.name || '',
+    email: data?.person?.email || '',
+    mobile: data?.person?.phone || '',
+    birthday: data?.person?.birthday ? timesTampChange(data.person.birthday, 'Y-M-D') : '',
+    age: data?.person?.birthday ? timestampToAge(data.person.birthday) : null,
+    career_path: data?.workList ? data.workList.map(e => {
+      return {
+        hotel_zh: e?.enterpriseName || null,
+        title_zh: e?.positionName || null,
+        date: e?.startTime ? timesTampChange(e.startTime, 'Y-M-D') : null
+      }
+    }) : null,
+    created_at: data?.person?.createTime ? timesTampChange(data.person.createTime, 'Y-M-D') : null,
+    updated_at: data?.person?.updateTime ? timesTampChange(data.person.updateTime, 'Y-M-D') : null,
+  }
+  resumeTxt.value = data?.resume?.rawText?.split('\n') || ''
+  if (FormPageRef.value?.setFormData) FormPageRef.value.setFormData(formData.value)
+}
+
+// 名片解析 
+const createAnalysisNum = ref(0)
+const cardFileQuery = ref(null)
+const cardUploadRow = ref(null)
+const cardImgUrl = ref(null)
+const cardUploadChange = (raw) => {
+  cardUploadRow.value = raw
+}
+
+// 人员搜索
+const openSearch = ref(false)
+const id = ref(null)
+const userId = ref(null)
+// const handleDetail = async ({id: use_id, userId: use_userId}) => {
+//   if (!use_userId || !use_userId)  return message.warning('请先选择人才!')
+//   id.value = use_id; userId.value = use_userId
+//   try {
+//     const data = await TalentMap.getTalentMapDetail(use_userId)
+//     resumeAnalysisToForm(data) // 简历解析
+//     dialog_analysisInfo.value = true
+//   } catch {}
+// }
+
+const SearchRef = ref(null)
+const handleSubmit = async () => {
+  console.log('打印->addList', SearchRef.value.addList)
+  message.success('创建任务成功!')
+  openSearch.value = false
+}
+
+const DialogWidth = ref('500')
+const showWebAnalysis = ref(false)
+// 选择解析方式
+const handleSelect = () => {
+  openSelect.value = false
+  formData.value = null
+  showWebAnalysis.value = false
+  showFormPage.value = true // 默认展示表单
+	const type = radioValue.value
+  if (type === 'card') {
+    createAnalysisNum.value++
+  }
+  if (type === 'menduner') {
+    openSearch.value = true
+    return
+  }
+  if (type === 'web') {
+    showFormPage.value = false // 新任命没有表单
+    showWebAnalysis.value = true
+    dialog_analysisInfo.value = true
+    return
+  }
+  dialog_upload.value = true
+}
+
+// 关闭上传弹窗
+const handleCancel = () => {
+  dialog_upload.value = false
+  analysisLoading.value = false
+}
+
+const openSelect = ref(false)
+const radioObject = { card: '名片', file: '简历', web: '门墩儿新任命', menduner: '门墩儿招聘', picture: '杂项' }
+const radioList = ref(Object.keys(radioObject).map(key => ({ value: key, label: radioObject[key]}) ))
+const defaultValue = radioList.value[0].value // 默认选中
+const radioValue = ref(defaultValue)
+// 新增解析
+const handleAdd = () => {
+  handleWebClear()
+  cardUploadRow.value = null
+  cardImgUrl.value = null
+  fileUrl.value = ''
+  fileData.value.path = ''
+  fileList.value = []
+  analysisLoading.value = false
+  analysisType.value = 'create'
+  radioValue.value = defaultValue // 重置解析类型
+  // 
+  openSelect.value = true
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
 
-const activeName = ref('resume')
 </script>
 
 <style lang="scss" scoped>
+.analysisInfoBox {
+  display: flex;
+  .analysisFile {
+    // width: 50%;
+    // max-height: 70vh;
+    padding-right: 12px;
+    overflow: auto;
+  }
+}
+.radioBox {
+	margin: 40px 0;
+}
+
+:deep(.drawer) {
+  position: absolute;
+  .el-drawer {
+    background-color: aliceblue;
+  }
+}
+.tagBox {
+  padding: 12px;
+  border: 1px dashed #409EFF;
+  border-radius: 4px;
+}
+:deep {
+  .el-tag__content {
+    display: flex;
+    align-items: center;
+  }
+}
 </style>