Bladeren bron

名片解析

lifanagju_citu 4 dagen geleden
bovenliggende
commit
4eb3ed3337

+ 26 - 0
src/api/menduner/system/talentMap/labeling.ts

@@ -51,4 +51,30 @@ export const talentLabelingApi = {
 			data 
 		})
 	},
+
+	// 非结构化数据源 更新名片
+	updateBusinessCard: async (data: any, id: any) => {
+		return await request.put({ 
+			url: `/api/parse/business-cards/${id}`,
+			baseURL: import.meta.env.VITE_BASE_URL,
+			data
+		})
+	},
+
+	// 非结构化数据源 更新名片状态
+	updateBusinessCardStatus: async (data: any, id: any) => {
+		return await request.put({ 
+			url: `/api/parse/update-business-cards/${id}/status`,
+			baseURL: import.meta.env.VITE_BASE_URL,
+			data
+		})
+	},
+
+	// 非结构化数据源 获取名片图片
+	getBusinessCardImage: async (path: any) => {
+		return await request.download({ 
+			url: `/api/parse/business-cards/image/${path}`,
+			baseURL: import.meta.env.VITE_BASE_URL
+		})
+	},
 }

+ 21 - 3
src/components/UploadFile/src/UploadImg.vue

@@ -5,7 +5,7 @@
       :accept="fileType.join(',')"
       :action="uploadUrl"
       :before-upload="beforeUpload"
-      :class="['upload', drag ? 'no-border' : '']"
+      :class="['upload', drag ? 'no-border' : '', buttonUpload ? 'notBorder' : '']"
       :disabled="disabled"
       :drag="drag"
       :http-request="httpRequest"
@@ -33,7 +33,11 @@
         </div>
       </template>
       <template v-else>
-        <div class="upload-empty">
+        <el-button v-if="props.buttonUpload" :type="props.butType" :plain="props.plain">
+          <Icon icon="ep:upload-filled" />
+          {{ props.txt }}
+        </el-button>
+        <div v-else class="upload-empty">
           <slot name="empty">
             <Icon icon="ep:plus" />
             <!-- <span>请上传图片</span> -->
@@ -82,8 +86,13 @@ const props = defineProps({
   showDelete: propTypes.bool.def(true), // 是否显示删除按钮
   showBtnText: propTypes.bool.def(true), // 是否显示按钮文字
   validSpecifications: propTypes.bool.def(false), // 是否开启图片规格校验
+  uploadSuccessTip: propTypes.bool.def(true), // 是否开启图片上传成功提示
   maxWidth: propTypes.number.def(0), // 图片最大宽度
   maxHeight: propTypes.number.def(0), // 图片最大高度
+  buttonUpload: propTypes.bool.def(false), // 是否开启按钮上传
+  butType: propTypes.any.def('primary'), // 按钮上传类型
+  plain: propTypes.bool.def(false), // 按钮上传样式
+  txt: propTypes.string.def('点击上传'), // 按钮上传文字
 })
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
@@ -148,7 +157,7 @@ const handleChange = (file: any, fileList: any) => {
 
 // 图片上传成功提示
 const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
-  message.success('上传成功')
+  if (props.uploadSuccessTip) message.success('上传成功')
   emit('update:modelValue', res.data)
 }
 
@@ -159,6 +168,15 @@ const uploadError = () => {
 </script>
 
 <style lang="scss" scoped>
+.notBorder {
+  :deep(.el-upload),
+  :deep(.el-upload-dragger) {
+    border: none !important;
+    &:hover {
+      border: none !important;
+    }
+  }
+}
 .is-error {
   .upload {
     :deep(.el-upload),

+ 0 - 9
src/views/menduner/system/talentMap/components/ImageImportEdit.vue

@@ -1,9 +0,0 @@
-<template>
-  <div>ImageImportEdit</div>
-</template>
-
-<script setup>
-defineOptions({ name: 'ImageImportEdit' })
-
-const activeName = ref('resume')
-</script>

+ 266 - 77
src/views/menduner/system/talentMap/components/card.vue

@@ -8,8 +8,8 @@
       :inline="true"
       label-width="68px"
     >
-      <el-form-item label="名称" prop="name">
-        <el-input v-model="queryParams.email" placeholder="请输入名称" clearable @keyup.enter="handleQuery" class="!w-180px" />
+      <el-form-item label="名称" prop="name_zh">
+        <el-input v-model="queryParams.name_zh" placeholder="请输入名称" clearable @keyup.enter="handleQuery" class="!w-180px" />
       </el-form-item>
       <el-form-item>
         <el-button @click="handleQuery"><Icon icon="ep:search" /> 搜索</el-button>
@@ -26,14 +26,21 @@
     <el-table v-loading="loading" :data="list" :stripe="true" height="550">
       <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_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="created_at" :formatter="dateFormatter2" />
-      <el-table-column label="状态" align="center" prop="status" />
+      <el-table-column label="创建日期" align="center" prop="created_at" :formatter="dateFormatter" />
+      <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" fixed="right" min-width="110">
         <template #default="scope">
-          <el-button link type="primary" @click="handleEdit(scope.row.id)">编辑</el-button>
-          <el-button link type="danger" @click="handleDisable(scope.row.id)">禁用</el-button>
+          <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
+          <el-button link :type="scope.row.status === 'active' ? 'danger': 'success'" @click="handleDisable(scope.row)">
+            {{ scope.row.status === 'active' ? '禁用' : '启用'}}
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -47,11 +54,17 @@
     />
     <!-- 上传 -->
     <Dialog title="名片解析" v-model="openUploadImg" width="500">
-      <UploadImg v-model="filePath" :limit="1" @handle-change="uploadChange" height="150px" width="150px" style="margin: 20px auto; width: 150px;">
+      <UploadImg
+        v-model="filePath"
+        :limit="1"
+        :uploadSuccessTip="false"
+        @handle-change="uploadChange"
+        height="150px" width="150px" style="margin: 20px auto; width: 150px;"
+      >
         <template #tip>{{ filePath ? '' : '请上传名片' }}</template>
       </UploadImg>
       <template #footer>
-        <el-button @click="handleAnalysis" type="success" :disabled="analysisLoading" :loading="analysisLoading">{{ buttonValue ? '更 换' : '解 析' }}</el-button>
+        <el-button @click="handleAnalysis" type="success" :disabled="analysisLoading" :loading="analysisLoading">解 析</el-button>
         <el-button @click="openUploadImg = false">取 消</el-button>
       </template>
     </Dialog>
@@ -59,23 +72,163 @@
     <Dialog title="名片解析" v-model="showAnalysisTable" width="80%">
       <div class="analysisInfoBox">
         <div class="image">
-          <el-image style="width: 100%;" :src="filePath" :fit="fit" />
+          <el-image v-if="filePath" style="width: 100%;" :src="filePath" />
+          <div v-else>
+            <UploadImg
+              v-model="filePath"
+              :limit="1"
+              :uploadSuccessTip="false"
+              drag
+              buttonUpload
+              @handle-change="uploadChange"
+              height="32px" width="104px"
+              style="margin: 0 auto; width: 104px;margin-top: 40%;"
+            >
+              <template #tip>{{ filePath ? '' : '请上传名片' }}</template>
+            </UploadImg>
+          </div>
         </div>
         <div class="formBox">
           <el-form
             ref="formRef"
-            :model="formData"
-            label-width="100px"
+            :model="formQuery"
+            label-width="128px"
             v-loading="formLoading"
           >
-            <el-form-item label="姓名" prop="name">
-              <el-input :disabled="formType === 'handle'" v-model="formData.name" placeholder="请输入姓名" />
+            <el-row>
+              <div class="m-title">基础信息</div>
+            </el-row>
+            <el-row :gutter="10">
+              <el-col :span="12">
+                <el-form-item label="姓名(中)" prop="name_zh">
+                  <el-input v-model="formQuery.name_zh" placeholder="请输入中文姓名" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="姓名(英)" prop="name_en">
+                  <el-input v-model="formQuery.name_en" placeholder="请输入英文姓名" />
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row :gutter="10">
+              <el-col :span="12">
+                <el-form-item label="职位/头衔(中)" prop="title_zh">
+                  <el-input v-model="formQuery.title_zh" placeholder="请输入中文职位/头衔" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="职位/头衔(英)" prop="title_en">
+                  <el-input v-model="formQuery.title_en" placeholder="请输入英文职位/头衔" />
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row>
+              <div class="m-title">联系方式</div>
+            </el-row>
+            <el-row :gutter="10">
+              <el-col :span="12">
+                <el-form-item label="手机号码" prop="mobile">
+                  <el-input v-model="formQuery.mobile" placeholder="请输入手机号码" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="固定电话" prop="phone">
+                  <el-input v-model="formQuery.phone" placeholder="请输入固定电话" />
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-form-item label="电子邮箱" prop="email">
+              <el-input v-model="formQuery.email" placeholder="请输入电子邮箱" />
+            </el-form-item>
+            <el-row>
+              <div class="m-title">酒店/公司信息</div>
+            </el-row>
+            <el-form-item label="酒店/公司名称(中)" prop="hotel_zh">
+              <el-input v-model="formQuery.hotel_zh" placeholder="请输入中文酒店/公司名称" />
+            </el-form-item>
+            <el-form-item label="酒店/公司名称(英)" prop="hotel_en">
+              <el-input v-model="formQuery.hotel_en" placeholder="请输入英文酒店/公司名称" />
+            </el-form-item>
+            <el-form-item label="品牌名称(中)" prop="brand_zh">
+              <el-input v-model="formQuery.brand_zh" placeholder="请输入中文品牌名称" />
+            </el-form-item>
+            <el-form-item label="品牌名称(英)" prop="brand_en">
+              <el-input v-model="formQuery.brand_en" placeholder="请输入英文品牌名称" />
+            </el-form-item>
+            <el-form-item label="隶属关系(中)" prop="affiliation_zh">
+              <el-input v-model="formQuery.affiliation_zh" placeholder="请输入中文隶属关系" />
+            </el-form-item>
+            <el-form-item label="隶属关系(英)" prop="affiliation_en">
+              <el-input v-model="formQuery.affiliation_en" placeholder="请输入英文隶属关系" />
+            </el-form-item>
+            <el-form-item label="品牌组合" prop="brand_group">
+              <el-input v-model="formQuery.brand_group" placeholder="请输入品牌组合" />
+            </el-form-item>
+            <el-row>
+              <div class="m-title">职业轨迹</div>
+            </el-row>
+            <el-row :gutter="10" class="trajectoryBox" v-for="(item, index) of careerTrajectory" :key="'trajectory' + index">
+              <el-col :span="18">
+                <el-form-item label="酒店名称" prop="company_name" label-width="128px">
+                  <el-input v-model="item.company_name" placeholder="请输入酒店名称" />
+                </el-form-item>
+                <el-form-item label="职位名称" prop="position" label-width="128px">
+                  <el-input v-model="item.position" placeholder="请输入职位名称" />
+                </el-form-item>
+                <el-form-item label="任职时间" prop="current_date">
+                  <el-date-picker
+                    v-model="item.current_date"
+                    value-format="YYYY-MM-DD"
+                    type="date"
+                    start-placeholder="开始日期"
+                    end-placeholder="结束日期"
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <div class="action">
+                  <el-button class="button" type="primary" @click="addCareer(index)">添加</el-button>
+                  <el-button v-if="careerTrajectory.length > 1" @click="removeCareer(index)" type="danger">删除</el-button>
+                </div>
+              </el-col>
+            </el-row>
+            <el-row>
+              <div class="m-title">地址信息</div>
+            </el-row>
+            <el-form-item label="中文地址" prop="address_zh">
+              <el-input v-model="formQuery.address_zh" placeholder="请输入中文地址" />
+            </el-form-item>
+            <el-form-item label="英文地址" prop="address_en">
+              <el-input v-model="formQuery.address_en" placeholder="请输入英文地址" />
+            </el-form-item>
+            <el-row :gutter="10">
+              <el-col :span="12">
+                <el-form-item label="邮政编码(中)" prop="postal_code_zh">
+                  <el-input v-model="formQuery.postal_code_zh" placeholder="请输入中文邮政编码" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="邮政编码(英)" prop="postal_code_en">
+                  <el-input v-model="formQuery.postal_code_en" placeholder="请输入英文邮政编码" />
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row>
+              <div class="m-title">系统信息</div>
+            </el-row>
+            <el-form-item label="状态">
+               <el-tag :type="itemData.status === 'active' ? 'success' : 'info'">{{ itemData.status === 'active' ? '已启用' : '已禁用' }}</el-tag>
+            </el-form-item>
+            <el-form-item label="创建时间">
+              <el-tag v-if="itemData.created_at" type="info" effect="light">{{ itemData.created_at }}</el-tag>
+            </el-form-item>
+            <el-form-item label="更新时间">
+              <el-tag v-if="itemData.updated_at" type="info" effect="light">{{ itemData.updated_at }}</el-tag>
             </el-form-item>
           </el-form>
         </div>
       </div>
       <template #footer>
-        <!-- <el-button @click="changeImg" type="primary">更换名片</el-button> -->
         <el-button @click="handleSave" type="success" :disabled="analysisLoading">更 新</el-button>
         <el-button @click="showAnalysisTable = false">取 消</el-button>
       </template>
@@ -84,7 +237,7 @@
 </template>
 
 <script setup>
-import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
+import { dateFormatter } from '@/utils/formatTime'
 import { talentLabelingApi } from '@/api/menduner/system/talentMap/labeling'
 
 /** 人才地图 列表 */
@@ -100,12 +253,6 @@ const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
   name: undefined,
-  phone: undefined,
-  sex: undefined,
-  email: undefined,
-  eduType: undefined,
-  expType: undefined,
-  areaId: undefined,
 })
 const queryFormRef = ref() // 搜索的表单
 
@@ -113,6 +260,7 @@ const queryFormRef = ref() // 搜索的表单
 const getList = async () => {
   loading.value = true
   try {
+    list.value = []
     const data = await talentLabelingApi.getCardList(queryParams)
     list.value = data || []
     // total.value = total || []
@@ -133,21 +281,51 @@ const resetQuery = () => {
   handleQuery()
 }
 
+const dealData = (item) => {
+  itemData.value = { ...item }
+  careerTrajectory.value = item?.career_path ? JSON.parse(JSON.stringify(item.career_path)) : []
+  Object.keys(formQuery.value).forEach(key => {
+    formQuery.value[key] = item[key] || null
+  })
+}
+
 /** 编辑 */
 const { push } = useRouter()
-const handleEdit = (id) => {
-  push({ name: 'ImageImportEdit', params: { id } })
+const handleEdit = async (item) => {
+  dealData(item)
+  file.value = null
+  filePath.value = null
+  try {
+    if (!item?.image_path) {
+      showAnalysisTable.value = true
+      return
+    }
+    const data = await talentLabelingApi.getBusinessCardImage(item.image_path)
+    if (!data?.type) return
+
+    file.value = new File([data], item.image_path, { type: data.type })
+    filePath.value = URL.createObjectURL(data)
+  } catch (error) {
+    console.log('打印->getBusinessCardImage', error)
+  } finally {
+    showAnalysisTable.value = true
+  }
 }
 
 /** 禁用按钮操作 */
-const handleDisable = async (id) => {
-  if (!id) return message.warning('禁用失败,请稍后再试')
+const handleDisable = async (item) => {
+  if (!item?.id) return message.warning('操作失败,请稍后再试')
   try {
     // 禁用的二次确认
-    await message.delConfirm()
+    const status = item.status === 'active' ? 'inactive' : 'active'
+    const text = status === 'inactive' ? '禁用' : '启用'
+    
+    await message.delConfirm(`是否${text}该名片?`)
     // 发起禁用
-    await talentLabelingApi.deleteTalentMap(id)
-    message.success(t('common.delSuccess'))
+    await talentLabelingApi.updateBusinessCardStatus({
+      status,
+    }, item.id)
+    message.success(`${text}成功`)
     // 刷新列表
     await getList()
   } catch {}
@@ -155,33 +333,59 @@ const handleDisable = async (id) => {
 
 const formQuery = ref({
   id: undefined,
-  name: undefined,
+  name_zh: undefined,
+  name_en: undefined,
+  title_zh: undefined,
+  title_en: undefined,
+  mobile: undefined,
+  phone: undefined,
+  email: undefined,
+  hotel_zh: undefined,
+  hotel_en: undefined,
+  brand_zh: undefined,
+  brand_en: undefined,
+  affiliation_zh: undefined,
+  affiliation_en: undefined,
+  brand_group: undefined,
+  address_zh: undefined,
+  address_en: undefined,
+  postal_code_zh: undefined,
+  postal_code_en: undefined,
 })
+const careerTrajectory = ref([])
 
 const file = ref(null)
 const uploadChange = (raw) => {
   file.value = raw
 }
 
-// 更换名片
-const changeImg = () => {
-  openUploadImg.value = true
+const addCareer = () => {
+  careerTrajectory.value = careerTrajectory.value || []
+  careerTrajectory.value.push({ company_name: null,  position: null, current_date: null })
+}
+const removeCareer = (index) => {
+  if (careerTrajectory.value.length <= 1) return
+  careerTrajectory.value.splice(index, 1)
 }
 
 // 更新
 const showAnalysisTable = ref(false)
 const formLoading = ref(false)
 const formRef = ref() // 表单 Ref
-const handleSave = () => {
-  if (!filePath.value) {
+const handleSave = async () => {
+  if (!itemData.value.id) {
     message.warning('ID获取异常')
     return
   }
   try {
     formLoading.value = true
-    Object.assign(this.itemData, this.formQuery)
-
+    formQuery.value.career_path = careerTrajectory
+    Object.assign(itemData.value, formQuery.value)
+    await talentLabelingApi.updateBusinessCard(itemData.value, itemData.value.id)
+    showAnalysisTable.value = false
     message.success('更新成功!')
+    // 刷新列表
+    getList()
   } catch (error) {
     console.log('更新失败', error)
   } finally {
@@ -202,44 +406,9 @@ const handleAnalysis = async () => {
     analysisLoading.value = true
     const query = new FormData()
     query.append('image', file.value)
-    // const data = await talentLabelingApi.businessCardParse(query)
-    const data = {
-      address_en: "", 
-      address_zh: "苏州市吴中区木渎镇花苑东路726号3幢", 
-      affiliation_en: "", 
-      affiliation_zh: "", 
-      brand_en: "", 
-      brand_group: "", 
-      brand_zh: "", 
-      career_path: [
-        {
-          company_name: "苏州木渎古镇ROSSO酒店", 
-          current_date: null, 
-          position: "销售总监"
-        }
-      ], 
-      created_at: "2025-05-16 18:11:46", 
-      email: "cw928383712@163.com", 
-      hotel_en: "ROSSO Hotel Suzhou Mudu Ancient Town", 
-      hotel_zh: "苏州木渎古镇 ROSSO酒店", 
-      id: 38, 
-      image_path: "e0a5a36e8be643e5a2953cd384836bdb.jpg", 
-      mobile: "13073381364", 
-      name_en: "", 
-      name_zh: "陈玮", 
-      phone: "0512-66563999", 
-      postal_code_en: "", 
-      postal_code_zh: "", 
-      status: "active", 
-      title_en: "Sales Director", 
-      title_zh: "销售总监", 
-      updated_at: null, 
-      updated_by: "system"
-    }
-    itemData.value = { ...data }
-    Object.keys(formQuery.value).forEach(key => {
-      formQuery.value[key] = data[key] || null
-    })
+    message.warning('正在解析...')
+    const res = await talentLabelingApi.businessCardParse(query)
+    dealData(res?.data || res)
     openUploadImg.value = false
     showAnalysisTable.value = true
     message.success('名片解析成功')
@@ -252,11 +421,10 @@ const handleAnalysis = async () => {
 
 // 新增
 const openUploadImg = ref(false)
-const buttonValue = ref(0)
 const handleAdd = () => {
   file.value = null
   filePath.value = null
-  buttonValue.value = 0
+  careerTrajectory.value = [{ company_name: null,  position: null, current_date: null }]
   openUploadImg.value = true
 }
 
@@ -278,8 +446,29 @@ onMounted(() => {
   .formBox {
     flex: 1;
     max-height: 70vh;
+    padding: 12px;
     overflow: auto;
     background-color: #f5f7f9;
+    .m-title {
+      margin: 12px 8px;
+      font-size: 13px;
+      // font-weight: bold;
+      color: #999;
+    }
+    .trajectoryBox {
+      margin: 10px 20px;
+      padding-top: 20px;
+      padding-bottom: 5px;
+      border-radius: 5px;
+      background-color: #fff;
+    }
+    .action {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      // flex-direction: column;
+      height: 131px;
+    }
   }
 }
 </style>