lifanagju_citu пре 1 недеља
родитељ
комит
58db3efd82

+ 51 - 52
src/views/menduner/system/talentMap/maintenance/gather/index.vue

@@ -71,7 +71,7 @@
     </Dialog>
 
     <!-- 解析文件上传 -->
-    <Dialog :title="radioObject[radioValue]" v-model="dialog_upload" :width="DialogWidth" @close="handleCancel">
+    <Dialog :title="radioObject[radioValue]" v-model="dialog_upload" :modalClose="false" :width="DialogWidth" @close="handleCancel">
       <div>
         <!-- 简历解析 -->
         <template v-if="radioValue === 'file'">
@@ -81,7 +81,6 @@
             :action="uploadUrl"
             :auto-upload="false"
             :data="fileData"
-            :limit="1"
             :on-change="handleChange"
             :on-error="submitFormError"
             :on-exceed="handleExceed"
@@ -89,6 +88,7 @@
             :http-request="httpRequest"
             accept=".pdf, doc, .docx"
             drag
+            multiple
             class="flex-1"
           >
             <i class="el-icon-upload"></i>
@@ -102,26 +102,26 @@
         </template>
         <!-- 名片解析 -->
         <template v-if="radioValue === 'card'">
-          <UploadImg
+          <UploadImgs
             v-model="cardImgUrl"
-            :limit="1"
             :uploadSuccessTip="false"
+            :limit="100"
             @handle-change="cardUploadChange"
-            height="150px" width="150px" style="margin: 20px auto; width: 150px;"
+            height="150px" width="150px" style="margin: 20px auto; text-align: center;"
           >
             <template #tip>{{ cardImgUrl ? '' : '请上传名片' }}</template>
-          </UploadImg>
+          </UploadImgs>
         </template>
         <template v-if="radioValue === 'picture'">
-          <UploadImg
+          <UploadImgs
             v-model="cardImgUrl"
-            :limit="1"
             :uploadSuccessTip="false"
+            :limit="100"
             @handle-change="cardUploadChange"
-            height="150px" width="150px" style="margin: 20px auto; width: 150px;"
+            height="150px" width="150px" style="margin: 20px auto; text-align: center;"
           >
             <template #tip>{{ cardImgUrl ? '' : '请上传图片' }}</template>
-          </UploadImg>
+          </UploadImgs>
         </template>
       </div>
       <template #footer>
@@ -173,8 +173,7 @@
               v-model:file-list="fileList"
               :action="uploadUrl"
               :auto-upload="false"
-              :data="data"
-              :limit="1"
+              :data="fileData"
               :on-change="handleChange"
               :on-error="submitFormError"
               :on-exceed="handleExceed"
@@ -182,6 +181,7 @@
               :http-request="httpRequest"
               accept=".pdf, doc, .docx"
               drag
+              multiple
               class="flex-1"
             >
               <i class="el-icon-upload"></i>
@@ -198,18 +198,17 @@
             <div class="image">
               <el-image v-if="cardImgUrl" class="!w-100%" :src="cardImgUrl" />
               <div v-else>
-                <UploadImg
+                <UploadImgs
                   v-model="cardImgUrl"
-                  :limit="1"
                   :uploadSuccessTip="false"
-                  drag
-                  buttonUpload
+                  :limit="100"
+                  drag buttonUpload
                   @handle-change="cardUploadChange"
                   height="32px" width="104px"
                   style="margin: 0 auto; width: 104px;margin-top: 40%;"
                 >
                   <template #tip>{{ cardImgUrl ? '' : '请上传名片' }}</template>
-                </UploadImg>
+                </UploadImgs>
               </div>
             </div>
           </template>
@@ -536,31 +535,29 @@ const handleAnalysis = async () => {
   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('正在解析...')
+    // 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') {}
+    //   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('名片解析成功')
+    // }
 
     dialog_upload.value = false
-    dialog_analysisInfo.value = true
+    message.success('上传成功,请等待解析任务完成,解析完成后点击入库即可完成人才入库!')
+    // dialog_analysisInfo.value = true
   } catch (error) {
     console.log('解析失败', error)
     cardFileQuery.value = null
@@ -585,19 +582,19 @@ 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)
-  }
+  // 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('上传失败,请您重新上传!')
@@ -684,9 +681,11 @@ const handleSelect = () => {
   openSelect.value = false
   formData.value = null
   showWebAnalysis.value = false
+  DialogWidth.value = '500'
 	const type = radioValue.value
   if (type === 'card') {
     createAnalysisNum.value++
+  DialogWidth.value = '800'
   }
   if (type === 'menduner') {
     openSearch.value = true

+ 765 - 0
src/views/menduner/system/talentMap/maintenance/gather/index3.vue

@@ -0,0 +1,765 @@
+<template>
+  <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 @detail="handleDetail" />
+    </Dialog>
+
+    <!-- 解析文件上传 -->
+    <Dialog :title="radioObject[radioValue]" v-model="dialog_upload" :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 { 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 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
+  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
+  }
+}
+
+// 监听表单变化,同步更新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)
+    // 去除id
+    resumeAnalysisToForm(data) // 简历解析
+    dialog_analysisInfo.value = true
+    // message.success(`操作成功`)
+  } catch {}
+}
+
+const DialogWidth = ref('500')
+const showWebAnalysis = ref(false)
+// 选择解析方式
+const handleSelect = () => {
+  openSelect.value = false
+  formData.value = null
+  showWebAnalysis.value = false
+	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
+  analysisLoading.value = false
+  analysisType.value = 'create'
+  radioValue.value = defaultValue // 重置解析类型
+  // 
+  openSelect.value = true
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+
+</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>