Преглед изворни кода

人才去重对接,名片解析由解析后生成新记录改为查看详情后点击按钮保存后生成记录,名片删除

Xiao_123 пре 1 недеља
родитељ
комит
2aec979c2b

+ 9 - 18
src/api/menduner/system/talentMap/duplicate.ts

@@ -10,29 +10,20 @@ export const talentDuplicateApi = {
 		})
 	},
 
-	// 创建职位
-	createHotelPosition: async (data: any) => {
-		return await request.post({ 
-			url: `/api/parse/add-hotel-positions`, 
-			data, 
+	// 获取重复记录详情
+	getDuplicateDetail: async (duplicate_id: number) => {
+		return await request.get({ 
+			url: `/api/parse/get-duplicate-record-detail/${duplicate_id}`,
 			baseURL: import.meta.env.VITE_BASE_URL
 		})
 	},
 
-	// 更新职位
-	updateHotelPosition: async (position_id: number, data: any) => {
-		return await request.put({ 
-			url: `/api/parse/update-hotel-positions/${position_id}`, 
+	// 处理重复记录
+	processDuplicateRecord: async (duplicate_id: number, data: any) => {
+		return await request.post({ 
+			url: `/api/parse/process-duplicate-record/${duplicate_id}`, 
 			data, 
-			baseURL: import.meta.env.VITE_BASE_URL 
-		})
-	},
-
-	// 删除职位
-	deleteHotelPosition: async (position_id: number) => {
-		return await request.delete({ 
-			url: `/api/parse/delete-hotel-positions/${position_id}`, 
-			baseURL: import.meta.env.VITE_BASE_URL 
+			baseURL: import.meta.env.VITE_BASE_URL
 		})
 	}
 }

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

@@ -52,6 +52,15 @@ export const talentLabelingApi = {
 		})
 	},
 
+	// 非结构化数据源 创建名片
+	createBusinessCard: async (data: any) => {
+		return await request.upload({ 
+			url: `/api/parse/add-business-card`,
+			baseURL: import.meta.env.VITE_BASE_URL,
+			data
+		})
+	},
+
 	// 非结构化数据源 更新名片
 	updateBusinessCard: async (data: any, id: any) => {
 		return await request.put({ 
@@ -77,4 +86,12 @@ export const talentLabelingApi = {
 			baseURL: import.meta.env.VITE_BASE_URL
 		})
 	},
+
+	// 删除名片记录
+	deleteBusinessCard: async (id: any) => {
+		return await request.delete({ 
+			url: `/api/parse/delete-business-card/${id}`,
+			baseURL: import.meta.env.VITE_BASE_URL
+		})
+	}
 }

+ 1 - 0
src/config/axios/service.ts

@@ -172,6 +172,7 @@ service.interceptors.response.use(
       })
       return Promise.reject(new Error(msg))
     } else if (code !== 200) {
+      if (code === 202) return data
       if (msg === '无效的刷新令牌') {
         // hard coding:忽略这个提示,直接登出
         console.log(msg)

+ 36 - 36
src/views/menduner/system/talentMap/store/components/info.vue → src/views/menduner/system/talentMap/components/info.vue

@@ -2,45 +2,45 @@
   <div class="!h-65vh overflow-y-auto">
     <slot name="header"></slot>
     <el-descriptions title="基础信息" :column="2" border>
-      <el-descriptions-item min-width="120" label="姓名(中)">{{ data?.title || '--' }}</el-descriptions-item>
-      <el-descriptions-item min-width="120" label="姓名(英)">{{ data?.title || '--' }}</el-descriptions-item>
-      <el-descriptions-item min-width="120" label="职位/头衔(中)">{{ data?.title || '--' }}</el-descriptions-item>
-      <el-descriptions-item min-width="120" label="职位/头衔(英)">{{ data?.title || '--' }}</el-descriptions-item>
-      <el-descriptions-item min-width="120" label="生日">{{ data?.title || '--' }}</el-descriptions-item>
-      <el-descriptions-item min-width="120" label="居住地">{{ data?.title || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="姓名(中)">{{ data?.name_zh || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="姓名(英)">{{ data?.name_en || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="职位/头衔(中)">{{ data?.title_zh || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="职位/头衔(英)">{{ data?.title_en || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="生日">{{ data?.birthday || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="居住地">{{ data?.residence || '--' }}</el-descriptions-item>
     </el-descriptions>
     <el-descriptions title="联系方式" class="mt-20px" :column="2" border>
-      <el-descriptions-item min-width="120" label="手机号码">{{ data?.title || '--' }}</el-descriptions-item>
-      <el-descriptions-item min-width="120" label="固定电话">{{ data?.title || '--' }}</el-descriptions-item>
-      <el-descriptions-item min-width="120" label="电子邮箱">{{ data?.title || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="手机号码">{{ data?.phone || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="固定电话">{{ data?.mobile || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="电子邮箱">{{ data?.email || '--' }}</el-descriptions-item>
     </el-descriptions>
     <el-descriptions title="酒店/公司信息" class="mt-20px" :column="2" border>
-      <el-descriptions-item min-width="120" label="酒店/公司名称(中)">{{ data?.title || '--' }}</el-descriptions-item>
-      <el-descriptions-item min-width="120" label="酒店/公司名称(英)">{{ data?.title || '--' }}</el-descriptions-item>
-      <el-descriptions-item min-width="120" label="隶属关系(中)">{{ data?.title || '--' }}</el-descriptions-item>
-      <el-descriptions-item min-width="120" label="隶属关系(英)">{{ data?.title || '--' }}</el-descriptions-item>
-      <el-descriptions-item min-width="120" label="品牌名称(中)">{{ data?.title || '--' }}</el-descriptions-item>
-      <el-descriptions-item min-width="120" label="品牌名称(英)">{{ data?.title || '--' }}</el-descriptions-item>
-      <el-descriptions-item min-width="120" label="品牌组合">{{ data?.title || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="酒店/公司名称(中)">{{ data?.hotel_zh || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="酒店/公司名称(英)">{{ data?.hotel_en || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="隶属关系(中)">{{ data?.affiliation_zh || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="隶属关系(英)">{{ data?.affiliation_en || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="品牌名称(中)">{{ data?.brand_zh || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="品牌名称(英)">{{ data?.brand_en || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="品牌组合">{{ data?.brand_group || '--' }}</el-descriptions-item>
     </el-descriptions>
-    <el-descriptions title="职业轨迹" class="mt-20px" border />
-    <el-timeline class="ml-12px">
-      <el-timeline-item color="#0bbd87" center>
+    <el-descriptions v-if="data?.career_path && data.career_path.length > 0" title="职业轨迹" class="mt-20px" border />
+    <el-timeline class="ml-12px" v-if="data?.career_path && data.career_path.length > 0">
+      <el-timeline-item color="#0bbd87" center v-for="(val, index) in data.career_path" :key="index">
         <el-descriptions title="" border>
-          <el-descriptions-item min-width="120" label="酒店名称">{{ data?.title || '--' }}</el-descriptions-item>
-          <el-descriptions-item min-width="120" label="职位名称">{{ data?.title || '--' }}</el-descriptions-item>
-          <el-descriptions-item min-width="120" label="任职时间">{{ data?.title || '--' }}</el-descriptions-item>
+          <el-descriptions-item min-width="120" label="酒店名称">{{ val.company_name || '--' }}</el-descriptions-item>
+          <el-descriptions-item min-width="120" label="职位名称">{{ val.position || '--' }}</el-descriptions-item>
+          <el-descriptions-item min-width="120" label="任职时间">{{ val.current_date || '--' }}</el-descriptions-item>
         </el-descriptions>
       </el-timeline-item>
     </el-timeline>
     <el-descriptions title="地址信息" class="mt-20px" :column="2" border>
-      <el-descriptions-item min-width="120" label="中文地址">{{ data?.title || '--' }}</el-descriptions-item>
-      <el-descriptions-item min-width="120" label="英文地址">{{ data?.title || '--' }}</el-descriptions-item>
-      <el-descriptions-item min-width="120" label="邮政编码(中)">{{ data?.title || '--' }}</el-descriptions-item>
-      <el-descriptions-item min-width="120" label="邮政编码(英)">{{ data?.title || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="中文地址">{{ data?.address_zh || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="英文地址">{{ data?.address_en || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="邮政编码(中)">{{ data?.postal_code_zh || '--' }}</el-descriptions-item>
+      <el-descriptions-item min-width="120" label="邮政编码(英)">{{ data?.postal_code_en || '--' }}</el-descriptions-item>
     </el-descriptions>
-    <el-descriptions title="人才标签" class="mt-20px" :column="2" border />
-    <el-tag v-for="k in talentTags" :key="k.talent" type="success" class="mr-10px my-10px">{{ k.tag }}</el-tag>
+    <!-- <el-descriptions title="人才标签" class="mt-20px" :column="2" border />
+    <el-tag v-for="k in talentTags" :key="k.talent" type="success" class="mr-10px my-10px">{{ k.tag }}</el-tag> -->
     <slot name="thumbnail"></slot>
   </div>
 </template>
@@ -54,13 +54,13 @@ const props = defineProps({
   }
 })
 
-const talentTags = ref([]) 
+// const talentTags = ref([]) 
 
-watch(
-  () => props.data,
-  () => {
-    talentTags.value = props.data?.talentTags || []
-  },
-  { immediate: true }
-)
+// watch(
+//   () => props.data,
+//   () => {
+//     talentTags.value = props.data?.talentTags || []
+//   },
+//   { immediate: true }
+// )
 </script>

+ 240 - 0
src/views/menduner/system/talentMap/components/merge.vue

@@ -0,0 +1,240 @@
+<template>
+	<Dialog title="人才对比" v-model="dialogVisible" class="!wh-full">
+		<el-row :gutter="10">
+			<el-col :span="12">
+				<el-card>
+					<template #header>
+						<CardTitle title="当前记录" />
+					</template>
+					<infoForm :data="currentData" />
+				</el-card>
+			</el-col>
+			<el-col :span="12">
+				<el-card class="position-relative">
+					<template #header>
+						<CardTitle title="重复记录" />
+					</template>
+					<infoForm :data="activeThumbnail">
+						<template #header>
+							<el-button type="primary" size="small" class="mb-10px" @click="submitForm('merge_to_suspected')">点击合并到此记录</el-button>
+						</template>
+						<template #thumbnail>
+							<div v-if="thumbnails && thumbnails.length && thumbnails[0].length > 1" class="!h-140px"></div>
+						</template>
+					</infoForm>
+					<!-- 缩略图 -->
+					<div class="thumbnail" v-if="thumbnails && thumbnails.length && thumbnails[0].length > 1">
+						<el-carousel :autoplay="false" :pause-on-hover="false" :loop="false" height="120px" class="px-80px py-10px" :arrow="thumbnails?.length > 1 ? 'always' : 'none'">
+							<el-carousel-item v-for="(val, index) in thumbnails" :key="index+'carousel'">
+								<div class="carouselContent">
+									<div
+										v-for="(item, index1) in val"
+										:key="index1 +'carousel'"
+										class="carouselItem"
+										:class="{'carouselItemAct': index*chunkSize + index1 === activeIndex}"
+										@click="handleSwitch(item, index, index1)"
+									>
+										<div>
+											{{ item?.name_zh }}
+											<span v-if="item?.name_en">({{ item?.name_en }})</span>
+										</div>
+										<div>{{ item?.mobile }}</div>
+										<div>{{ item?.hotel_zh }} - {{ item?.title_zh }}</div>
+									</div>
+								</div>
+							</el-carousel-item>
+						</el-carousel>
+					</div>
+				</el-card>
+			</el-col>
+		</el-row>
+
+		<template #footer>
+      <el-button @click="submitForm('keep_main')" type="primary">新记录提交</el-button>
+      <el-button @click="submitForm('ignore')" type="success">忽略(标记为已处理)</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script setup>
+defineOptions({ name: 'TalentMapStoreMerge' })
+import infoForm from './info.vue'
+import { talentDuplicateApi } from '@/api/menduner/system/talentMap/duplicate'
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+const emit = defineEmits(['refresh'])
+
+const mergeLoading = ref(false)
+const dialogVisible = ref(false)
+const list = ref([])
+const talentTags = ref([])
+const infoList = ref([])
+
+function chunkArray(array, chunkSize) {
+  const result = [];
+  for (let i = 0; i < array.length; i += chunkSize) {
+    result.push(array.slice(i, i + chunkSize));
+  }
+  return result;
+}
+
+const chunkSize = 3 // 每组缩略图数量
+const thumbnails = ref([])
+
+const activeIndex = ref(0)
+const activeThumbnail = ref({})
+const currentData = ref({})
+// 切换重合记录
+const handleSwitch = (item, index, index1) => {
+	activeIndex.value = index*chunkSize + index1
+	activeThumbnail.value = item
+}
+
+// 打开弹窗
+const arr = [
+		{
+			"created_at": "2025-05-20 16:56:02",
+			"hotel_en": "DoubleTree by Hilton Shenzhen Bay",
+			"hotel_zh": "深圳湾希尔顿逸林酒店",
+			"id": 1,
+			"mobile": "+86 138 2336 0328,+86 755 8822 9888",
+			"name_en": "Waters Hong",
+			"name_zh": "洪松1",
+			"title_en": "General Manager",
+			"title_zh": "总经理"
+		},
+		{
+			"created_at": "2025-05-20 16:56:02",
+			"hotel_en": "DoubleTree by Hilton Shenzhen Bay",
+			"hotel_zh": "深圳湾希尔顿逸林酒店",
+			"id": 2,
+			"mobile": "+86 138 2336 0328,+86 755 8822 9888",
+			"name_en": "Waters Hong",
+			"name_zh": "洪松2",
+			"title_en": "General Manager",
+			"title_zh": "总经理"
+		},
+		{
+			"created_at": "2025-05-20 16:56:02",
+			"hotel_en": "DoubleTree by Hilton Shenzhen Bay",
+			"hotel_zh": "深圳湾希尔顿逸林酒店",
+			"id": 3,
+			"mobile": "+86 138 2336 0328,+86 755 8822 9888",
+			"name_en": "Waters Hong",
+			"name_zh": "洪松3",
+			"title_en": "General Manager",
+			"title_zh": "总经理"
+		},
+		{
+			"created_at": "2025-05-20 16:56:02",
+			"hotel_en": "DoubleTree by Hilton Shenzhen Bay",
+			"hotel_zh": "深圳湾希尔顿逸林酒店",
+			"id": 4,
+			"mobile": "+86 138 2336 0328,+86 755 8822 9888",
+			"name_en": "Waters Hong",
+			"name_zh": "洪松4",
+			"title_en": "General Manager",
+			"title_zh": "总经理"
+		},
+		{
+			"created_at": "2025-05-20 16:56:02",
+			"hotel_en": "DoubleTree by Hilton Shenzhen Bay",
+			"hotel_zh": "深圳湾希尔顿逸林酒店",
+			"id": 5,
+			"mobile": "+86 138 2336 0328,+86 755 8822 9888",
+			"name_en": "Waters Hong",
+			"name_zh": "洪松5",
+			"title_en": "General Manager",
+			"title_zh": "总经理"
+		},
+		{
+			"created_at": "2025-05-20 16:56:02",
+			"hotel_en": "DoubleTree by Hilton Shenzhen Bay",
+			"hotel_zh": "深圳湾希尔顿逸林酒店",
+			"id": 6,
+			"mobile": "+86 138 2336 0328,+86 755 8822 9888",
+			"name_en": "Waters Hong",
+			"name_zh": "洪松6",
+			"title_en": "General Manager",
+			"title_zh": "总经理"
+		},
+		{
+			"created_at": "2025-05-20 16:56:02",
+			"hotel_en": "DoubleTree by Hilton Shenzhen Bay",
+			"hotel_zh": "深圳湾希尔顿逸林酒店",
+			"id": 7,
+			"mobile": "+86 138 2336 0328,+86 755 8822 9888",
+			"name_en": "Waters Hong",
+			"name_zh": "洪松7",
+			"title_en": "General Manager",
+			"title_zh": "总经理"
+		}
+]
+const open = async (id) => {
+	if (!id) return message.warning('缺少数据ID')
+
+	const data = await talentDuplicateApi.getDuplicateDetail(id)
+
+	currentData.value = data?.main_card // 当前记录
+	activeThumbnail.value = data?.suspected_duplicates[0] // 默认展示第一个缩略图信息
+	thumbnails.value = chunkArray(data?.suspected_duplicates, chunkSize) // 重复数据缩略图
+  dialogVisible.value = true
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+// 处理
+const submitForm = async (action) => {
+	const query = {
+		action
+	}
+	// 选择合并到重复数据中的某条数据时,需要传递选中的id
+	if (action === 'merge_to_suspected') query.selected_duplicate_id = activeThumbnail.value?.id
+
+	try {
+		await talentDuplicateApi.processDuplicateRecord(currentData.value.id, query)
+		message.success('操作成功')
+
+		dialogVisible.value = false
+
+		emit('refresh') // 刷新列表
+	} catch {
+	}
+}
+</script>
+
+<style scoped lang="scss">
+.thumbnail {
+	position: absolute;
+	bottom: 0;
+	left: 0;
+	height: 140px;
+	width: 100%;
+	background-color: #fff;
+	overflow: hidden;
+	border-top: 1px solid #eee;
+	.carouselContent {
+		display: flex;
+		align-items: center;
+		height: 100%;
+		.carouselItem {
+			width: 33.3%;
+			height: 80px;
+			border: 1px solid #80808057;
+			border-radius: 5px;
+			margin: 0 10px;
+			padding: 10px;
+			background-color: #fff;
+			font-size: 12px;
+			overflow: hidden;
+			cursor: pointer;
+		}
+		.carouselItemAct {
+			/* color: #67C23A; */
+			border: 1px solid #62a4e698;
+			background-color: #62a4e623;
+		}
+	}
+}
+</style>

+ 20 - 40
src/views/menduner/system/talentMap/duplicate/index.vue

@@ -34,9 +34,10 @@
   <ContentWrap>
     <el-table v-loading="loading" :data="list" :stripe="true" row-key="id">
       <el-table-column label="姓名" align="center" prop="main_card.name_zh" />
-      <el-table-column label="手机号" align="center" prop="main_card.mobile" />
-      <el-table-column label="职位" align="center" prop="main_card.title_zh" />
       <el-table-column label="酒店" align="center" prop="main_card.hotel_zh" :show-overflow-tooltip="true" />
+      <el-table-column label="职位" align="center" prop="main_card.title_zh" :show-overflow-tooltip="true" />
+      <el-table-column label="手机号" align="center" prop="main_card.mobile" :show-overflow-tooltip="true" />
+      <el-table-column label="原因" align="center" prop="duplicate_reason" :show-overflow-tooltip="true" />
       <el-table-column label="状态" align="center" prop="processing_status">
         <template #default="scope">
           <el-tag :type="statusOptions.find(item => item.value === scope.row.processing_status)?.color">
@@ -44,75 +45,54 @@
           </el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="创建时间" align="center" prop="duplicate_reason" :show-overflow-tooltip="true" />
-      <el-table-column label="创建时间" align="center" prop="created_at" width="180" />
+      <el-table-column label="创建时间" align="center" prop="created_at" width="160" />
       <el-table-column label="操作" align="center">
         <template #default="scope">
-          <el-button link type="primary" @click="openForm(scope.row)">标注</el-button>
+          <el-button
+            v-if="scope.row.processing_status === 'pending'"
+            link
+            type="primary"
+            @click="openForm(scope.row.main_card_id)"
+          >处理</el-button>
         </template>
       </el-table-column>
     </el-table>
   </ContentWrap>
+
+  <MergePage ref="mergePageRef" @refresh="getList" />
 </template>
 
 <script setup lang="ts" name="TalentMapDuplicate">
 import { talentDuplicateApi } from '@/api/menduner/system/talentMap/duplicate'
+import MergePage from '../components/merge.vue'
 
 const loading = ref(false)
-const list = ref([
-  {
-    "id": 1,
-    "main_card_id": 123,
-    "suspected_duplicates": [
-      {
-        "id": 101,
-        "name_zh": "张三",
-        "mobile": "13812345678",
-        "hotel_zh": "北京丽思卡尔顿酒店",
-        "title_zh": "总监",
-        "created_at": "2024-01-15 10:30:00"
-      }
-    ],
-    "duplicate_reason": "姓名相同但手机号码不同:张三,新手机号:13987654321",
-    "processing_status": "pending",
-    "created_at": "2024-01-16 09:15:00",
-    "main_card": {
-      "id": 123,
-      "name_zh": "张三",
-      "mobile": "13987654321",
-      "hotel_zh": "上海丽思卡尔顿酒店",
-      "title_zh": "总经理",
-      "image_path": "abc123def456.jpg"
-    }
-  }
-])
+const list = ref([])
+const mergePageRef = ref()
 const queryParams = reactive({
-	status: 'pending'
+	status: ''
 })
 const queryFormRef = ref() // 搜索的表单
 const statusOptions = [
 	{ label: '待处理', value: 'pending', color: 'warning' },
 	{ label: '已处理', value: 'processed', color: 'success' },
-	{ label: '已忽略', value: 'ignored', color: 'info' }
+	// { label: '已忽略', value: 'ignored', color: 'info' }
 ]
 
-const message = useMessage() // 消息弹窗
-
 /** 查询列表 */
 const getList = async () => {
   loading.value = true
   try {
     const data = await talentDuplicateApi.getDuplicateList(queryParams)
-    list.value = data
+    list.value = data || []
   } finally {
     loading.value = false
   }
 }
 
 /** 添加/修改操作 */
-const formRef = ref()
-const openForm = (data: any) => {
-  formRef.value.open(data)
+const openForm = async (id: number) => {
+  mergePageRef.value.open(id)
 }
 
 /** 搜索按钮操作 */

+ 71 - 40
src/views/menduner/system/talentMap/store/businessCard/index.vue

@@ -23,11 +23,13 @@
 
   <!-- 列表 -->
   <ContentWrap>
-    <el-table v-loading="loading" :data="list" :stripe="true" height="550">
+    <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="phone" />
+      <el-table-column label="固定电话" align="center" prop="mobile" />
       <el-table-column label="创建日期" align="center" prop="created_at" :formatter="dateFormatter" />
       <el-table-column label="状态" align="center" prop="status" width="80">
         <template #default="scope">
@@ -38,20 +40,14 @@
       <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' ? 'danger': 'success'" @click="handleDisable(scope.row)">
             {{ scope.row.status === 'active' ? '禁用' : '启用'}}
           </el-button>
         </template>
       </el-table-column>
     </el-table>
-    <!-- 分页 -->
-    <Pagination
-      v-if="total"
-      :total="total"
-      v-model:page="queryParams.pageNo"
-      v-model:limit="queryParams.pageSize"
-      @pagination="getList"
-    />
+
     <!-- 上传 -->
     <Dialog title="名片解析" v-model="openUploadImg" width="500" @close="handleCancel">
       <UploadImg
@@ -68,6 +64,7 @@
         <el-button @click="handleCancel">取 消</el-button>
       </template>
     </Dialog>
+
     <!-- 解析回显 -->
     <Dialog title="名片解析" v-model="showAnalysisTable" width="80%">
       <div class="analysisInfoBox">
@@ -131,8 +128,8 @@
             </el-row>
             <el-row :gutter="10">
               <el-col :span="24">
-                <el-form-item label="居住地" prop="address">
-                  <el-input v-model="formQuery.address" placeholder="请输入当前居住地址" />
+                <el-form-item label="居住地" prop="residence">
+                  <el-input v-model="formQuery.residence" placeholder="请输入当前居住地址" />
                 </el-form-item>
               </el-col>
             </el-row>
@@ -234,33 +231,38 @@
                 </el-form-item>
               </el-col>
             </el-row>
-            <el-row>
+            <el-row v-if="formType === 'edit'">
               <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 v-if="formType === 'edit'" label="状态">
+               <el-tag v-if="itemData.status" :type="itemData.status === 'active' ? 'success' : 'danger'">
+                {{ 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 label="创建时间" v-if="formType === 'edit'">
+              <el-tag v-if="itemData.created_at" type="primary" effect="light">{{ itemData.created_at }}</el-tag>
             </el-form-item>
-            <el-form-item label="更新时间">
-              <el-tag v-if="itemData.updated_at" type="info" effect="light">{{ itemData.updated_at }}</el-tag>
+            <el-form-item label="更新时间" v-if="formType === 'edit'">
+              <el-tag v-if="itemData.updated_at" type="primary" effect="light">{{ itemData.updated_at }}</el-tag>
             </el-form-item>
           </el-form>
         </div>
       </div>
       <template #footer>
-        <el-button @click="handleSave" type="success" :disabled="analysisLoading">更 新</el-button>
+        <el-button @click="handleSave" type="success" :disabled="analysisLoading">保 存</el-button>
         <el-button @click="showAnalysisTable = false">取 消</el-button>
       </template>
     </Dialog>
   </ContentWrap>
+
+  <MergeForm ref="mergeFormRef" @refresh="getList" />
 </template>
 
 <script setup>
 import { dateFormatter } from '@/utils/formatTime'
 import { talentLabelingApi } from '@/api/menduner/system/talentMap/labeling'
 import { Delete, Plus } from '@element-plus/icons-vue'
+import MergeForm from '../../components/merge.vue'
 
 /** 人才地图 列表 */
 defineOptions({ name: 'TalentMapCard' })
@@ -272,8 +274,6 @@ const loading = ref(false) // 列表的加载中
 const list = ref([]) // 列表的数据
 const total = ref(0) // 列表的总页数
 const queryParams = reactive({
-  pageNo: 1,
-  pageSize: 10,
   name: undefined,
 })
 const queryFormRef = ref() // 搜索的表单
@@ -284,8 +284,7 @@ const getList = async () => {
   try {
     list.value = []
     const data = await talentLabelingApi.getCardList()
-    list.value = data || []
-    // total.value = total || []
+    list.value = data ? data.reverse() : []
   } finally {
     loading.value = false
   }
@@ -293,11 +292,10 @@ const getList = async () => {
 
 /** 搜索按钮操作 */
 const handleQuery = (type) => {
-  if (type === 'search') {
-    message.warning('搜索正在建设中...')
-    return
-  }
-  queryParams.pageNo = 1
+  // if (type === 'search') {
+  //   message.warning('搜索正在建设中...')
+  //   return
+  // }
   getList()
 }
 
@@ -316,9 +314,25 @@ const dealData = (item) => {
   })
 }
 
+/** 删除按钮操作 */
+const handleDelete = async (id) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await talentLabelingApi.deleteBusinessCard(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    setTimeout(async () => {
+      await getList()
+    }, 0)
+  } catch {}
+}
+
 /** 编辑 */
 const { push } = useRouter()
 const handleEdit = async (item) => {
+  formType.value = 'edit'
   dealData(item)
   file.value = null
   filePath.value = null
@@ -359,7 +373,6 @@ const handleDisable = async (item) => {
 }
 
 const formQuery = ref({
-  id: undefined,
   name_zh: undefined,
   name_en: undefined,
   title_zh: undefined,
@@ -379,11 +392,12 @@ const formQuery = ref({
   postal_code_zh: undefined,
   postal_code_en: undefined,
   birthday: undefined,
-  address: undefined,
+  residence: undefined,
 })
 const careerTrajectory = ref([{ company_name: null,  position: null, current_date: null }])
 
 const file = ref(null)
+const uploadFile = ref(null)
 const uploadChange = (raw) => {
   file.value = raw
 }
@@ -400,24 +414,38 @@ const removeCareer = (index) => {
 // 更新
 const showAnalysisTable = ref(false)
 const formLoading = ref(false)
+const formType = ref('')
 const formRef = ref() // 表单 Ref
+const mergeFormRef = ref() // 合并表单 Ref
 const handleSave = async () => {
-  if (!itemData.value.id) {
-    message.warning('ID获取异常')
-    return
-  }
   try {
     formLoading.value = true
     formQuery.value.career_path = careerTrajectory
     Object.assign(itemData.value, formQuery.value)
-    await talentLabelingApi.updateBusinessCard(itemData.value, itemData.value.id)
+
+    let result = {}
+
+    if (formType.value === 'create') {
+      uploadFile.value.append('card_data', JSON.stringify(itemData.value))
+      result = await talentLabelingApi.createBusinessCard(uploadFile.value)
+      message.success('新增成功')
+
+      if (result.code === 202 || result.message.includes('疑似重复')) {
+        if (!result.data?.main_card?.id) return
+        message.notifyWarning('发现与当前名片的疑似数据,请处理')
+        mergeFormRef.value.open(result.data?.main_card?.id)
+      }
+    } else {
+      await talentLabelingApi.updateBusinessCard(itemData.value, itemData.value.id)
+      message.success('更新成功')
+    }
     showAnalysisTable.value = false
-    message.success('更新成功!')
     // 刷新列表
     getList()
   } catch (error) {
     console.log('更新失败', error)
   } finally {
+    uploadFile.value = null
     formLoading.value = false
   }
 }
@@ -441,12 +469,13 @@ const handleAnalysis = async () => {
   try {
     analysisLoading.value = true
     // 开始解析
-    const query = new FormData()
-    query.append('image', file.value)
+    uploadFile.value = new FormData()
+    uploadFile.value.append('image', file.value)
     message.warning('正在解析...')
+
     const index = createAnalysisNum.value
-    const res = await talentLabelingApi.businessCardParse(query)
-    console.log('编号->', index, createAnalysisNum.value)
+    const res = await talentLabelingApi.businessCardParse(uploadFile.value)
+
     if (index !== createAnalysisNum.value || !openUploadImg.value) return // 不是最新的名片解析数据(用户在解析完成前已重新上传)或用户已取消解析
     dealData(res?.data || res)
     openUploadImg.value = false
@@ -454,6 +483,7 @@ const handleAnalysis = async () => {
     message.success('名片解析成功')
   } catch (error) {
     console.log('解析失败', error)
+    uploadFile.value = null
   } finally {
     analysisLoading.value = false
   }
@@ -463,6 +493,7 @@ const handleAnalysis = async () => {
 const openUploadImg = ref(false)
 const createAnalysisNum = ref(0)
 const handleAdd = () => {
+  formType.value = 'create'
   file.value = null
   filePath.value = null
   analysisLoading.value = false

+ 0 - 97
src/views/menduner/system/talentMap/store/components/merge copy.vue

@@ -1,97 +0,0 @@
-<template>
-	<Dialog title="人才对比" v-model="dialogVisible" class="!w-65%">
-		<div class="color-orange-400">
-			提示:查询到当前导入的人才姓名与数据库中的记录重复,请选择处理方式
-			<br />
-			1、在下方数据库中查询出的姓名重复记录选中择一条进行合并
-			<br />
-			2、作为新记录提交
-		</div>
-
-		<el-descriptions class="mt-50px" title="当前导入人才信息" border column="4">
-			<el-descriptions-item label="姓名">张三</el-descriptions-item>
-			<el-descriptions-item label="联系电话">18100000000</el-descriptions-item>
-			<el-descriptions-item label="邮箱">zhangsan@example.com</el-descriptions-item>
-			<el-descriptions-item label="性别">男</el-descriptions-item>
-		</el-descriptions>
-
-		<div class="mt-50px el-descriptions__title">重复记录</div>
-		<el-table :data="list" :stripe="true" class="my-10px">
-      <el-table-column label="头像" align="center" prop="avatar" width="70px" fixed="left">
-        <template #default="scope">
-          <el-image
-						class="h-60px w-60px"
-						:src="scope.row.avatar"
-						lazy
-						preview-teleported
-						:preview-src-list="[scope.row.avatar]"
-						fit="contain"
-					/>
-        </template>
-      </el-table-column>
-      <el-table-column label="姓名" align="center" prop="name" fixed="left" />
-      <el-table-column label="联系电话" align="center" prop="phone" />
-      <el-table-column label="邮箱" align="center" prop="email" />
-      <el-table-column label="性别" align="center" prop="sexStr" />
-      <el-table-column label="操作" align="center" fixed="right" min-width="110">
-        <template #default="scope">
-          <el-button link type="success" @click="handleMerge(scope.row.id)">合并到此记录</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-    <template #footer>
-      <el-button @click="submitForm" type="primary" :disabled="newRecordLoading">新记录提交</el-button>
-      <el-button @click="dialogVisible = false">取 消</el-button>
-    </template>
-  </Dialog>
-</template>
-
-<script setup>
-defineOptions({ name: 'TalentMapStoreMerge' })
-
-const { t } = useI18n() // 国际化
-const message = useMessage() // 消息弹窗
-
-const mergeLoading = ref(false)
-const newRecordLoading = ref(false)
-const dialogVisible = ref(false)
-const list = ref([
-	{
-		id: 1,
-		name: '张三',
-		phone: '13800138000',
-		email: 'zhangsan@example.com',
-		sexStr: '男',
-		avatar: 'https://minio.menduner.com/dev/person/1/img/933de4dc9eb28e2bdf79a1da518a8e1a0e14bf4b78358ea9452fb8e8fdd2f7f6.jpeg'
-	},
-	{
-		id: 2,
-		name: '张三',
-		phone: '13800138001',
-		email: 'lisi@example.com',
-		sexStr: '女',
-		avatar: 'https://minio.menduner.com/dev/person/1/img/933de4dc9eb28e2bdf79a1da518a8e1a0e14bf4b78358ea9452fb8e8fdd2f7f6.jpeg'
-	}
-])
-
-const open = async (data) => {
-  dialogVisible.value = true
-}
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
-
-// 人才合并
-const handleMerge = async (id) => {
-	console.log(id, 'merge')
-}
-
-// 新记录提交
-const submitForm = async () => {
-	// newRecordLoading.value = true
-
-	// try {
-	// 	message.success('新记录提交成功')
-	// } finally {
-	// 	newRecordLoading.value = false
-	// }
-}
-</script>

+ 0 - 241
src/views/menduner/system/talentMap/store/components/merge.vue

@@ -1,241 +0,0 @@
-<template>
-	<Dialog title="人才对比" v-model="dialogVisible" class="!wh-full">
-		<!-- <div class="color-orange-400">
-			提示:查询到当前导入的人才姓名与数据库中的记录重复,请选择处理方式
-			<br />
-			1、在下方数据库中查询出的姓名重复记录选中择一条进行合并
-			<br />
-			2、作为新记录提交
-		</div> -->
-		<el-row :gutter="10">
-			<el-col :span="12">
-				<el-card>
-					<template #header>
-						<CardTitle title="当前记录" />
-					</template>
-					<infoForm :data="currentData" />
-				</el-card>
-			</el-col>
-			<el-col :span="12">
-				<el-card class="position-relative">
-					<template #header>
-						<CardTitle title="重复记录" />
-					</template>
-					<infoForm :data="activeThumbnail">
-						<template #header>
-							<el-button type="primary" size="small" class="mb-10px">点击合并到此记录</el-button>
-						</template>
-						<template #thumbnail>
-							<div class="!h-140px"></div>
-						</template>
-						<!-- 缩略图 -->
-					</infoForm>
-					<div class="thumbnail">
-						<el-carousel :autoplay="false" height="120px" class="px-80px py-10px" :arrow="infoList?.length > 1 ? 'always' : 'none'">
-							<el-carousel-item v-for="(val, index) in thumbnails" :key="index+'carousel'">
-								<div class="carouselContent">
-									<div
-										v-for="(item, index1) in val"
-										:key="index1+'carousel'"
-										class="carouselItem"
-										:class="{'carouselItemAct': index*chunkSize + index1 === activeIndex}"
-										@click="handleSwitch(item, index, index1)"
-									>
-										<div>{{ item?.title }}</div>
-										<div>{{ item?.description }}</div>
-										<div>{{ item?.shortDesc }}</div>
-									</div>
-								</div>
-							</el-carousel-item>
-						</el-carousel>
-					</div>
-				</el-card>
-			</el-col>
-		</el-row>
-
-		<template #footer>
-      <el-button @click="submitForm" type="primary" :disabled="newRecordLoading">新记录提交</el-button>
-      <el-button @click="dialogVisible = false">取 消</el-button>
-    </template>
-  </Dialog>
-</template>
-
-<script setup>
-defineOptions({ name: 'TalentMapStoreMerge' })
-import infoForm from './info.vue'
-
-const { t } = useI18n() // 国际化
-const message = useMessage() // 消息弹窗
-
-const mergeLoading = ref(false)
-const newRecordLoading = ref(false)
-const dialogVisible = ref(false)
-const list = ref([
-	{
-		id: 1,
-		name: '张三',
-		phone: '13800138000',
-		email: 'zhangsan@example.com',
-		sexStr: '男',
-		avatar: 'https://minio.menduner.com/dev/person/1/img/933de4dc9eb28e2bdf79a1da518a8e1a0e14bf4b78358ea9452fb8e8fdd2f7f6.jpeg'
-	},
-	{
-		id: 2,
-		name: '张三',
-		phone: '13800138001',
-		email: 'lisi@example.com',
-		sexStr: '女',
-		avatar: 'https://minio.menduner.com/dev/person/1/img/933de4dc9eb28e2bdf79a1da518a8e1a0e14bf4b78358ea9452fb8e8fdd2f7f6.jpeg'
-	}
-])
-const talentTags = ref([{talent: '1', tag:'新开酒店经验'}, {talent: '2', tag:'总经理'}])
-const infoList = ref([
-	{
-		title: '姓名1',
-		description: '暂无描述',
-		shortDesc: '记录1暂无描述',
-		talentTags: [{talent: '1', tag:'新开酒店经验'}, {talent: '2', tag:'总经理'}]
-	},
-	{
-		title: '姓名2',
-		description: '这是第二条记录的详细描述内容...',
-		shortDesc: '记录2的简短描述'
-	},
-	{
-		title: '姓名3',
-		description: '这是第二条记录的详细描述内容...',
-		shortDesc: '记录2的简短描述'
-	},
-	{
-		title: '姓名4',
-		description: '这是第二条记录的详细描述内容...',
-		shortDesc: '记录2的简短描述'
-	},
-	{
-		title: '姓名5',
-		description: '这是第二条记录的详细描述内容...',
-		shortDesc: '记录2的简短描述'
-	},
-	{
-		title: '姓名2-1',
-		description: '这是第一条记录的详细描述内容...',
-		shortDesc: '记录1的简短描述'
-	},
-	{
-		title: '姓名2-2',
-		description: '这是第二条记录的详细描述内容...',
-		shortDesc: '记录2的简短描述'
-	},
-	{
-		title: '姓名2-3',
-		description: '这是第二条记录的详细描述内容...',
-		shortDesc: '记录2的简短描述'
-	},
-	{
-		title: '姓名2-4',
-		description: '这是第二条记录的详细描述内容...',
-		shortDesc: '记录2的简短描述'
-	},
-	{
-		title: '姓名2-5',
-		description: '这是第二条记录的详细描述内容...',
-		shortDesc: '记录2的简短描述'
-	},
-	{
-		title: '姓名3-1',
-		description: '这是第一条记录的详细描述内容...',
-		shortDesc: '记录1的简短描述'
-	},
-	{
-		title: '姓名3-2',
-		description: '这是第二条记录的详细描述内容...',
-		shortDesc: '记录2的简短描述'
-	},
-	{
-		title: '姓名3-3',
-		description: '这是第二条记录的详细描述内容...',
-		shortDesc: '记录2的简短描述'
-	},
-	{
-		title: '姓名3-4',
-		description: '这是第二条记录的详细描述内容...',
-		shortDesc: '记录2的简短描述'
-	}
-])
-
-function chunkArray(array, chunkSize) {
-  const result = [];
-  for (let i = 0; i < array.length; i += chunkSize) {
-    result.push(array.slice(i, i + chunkSize));
-  }
-  return result;
-}
-
-const chunkSize = 5 // 每组缩略图数量
-const thumbnails = chunkArray(infoList.value, chunkSize)
-
-const open = async (data) => {
-  dialogVisible.value = true
-}
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
-
-const activeIndex = ref(0)
-const activeThumbnail = ref(infoList.value[0])
-const currentData = ref(infoList.value[0])
-// 切换重合记录
-const handleSwitch = (item, index, index1) => {
-	activeIndex.value = index*chunkSize + index1
-	activeThumbnail.value = item
-}
-
-// 人才合并
-const handleMerge = async (id) => {
-	console.log(id, 'merge')
-}
-
-// 新记录提交
-const submitForm = async () => {
-	// newRecordLoading.value = true
-
-	// try {
-	// 	message.success('新记录提交成功')
-	// } finally {
-	// 	newRecordLoading.value = false
-	// }
-}
-</script>
-
-<style scoped>
-.thumbnail {
-	position: absolute;
-	bottom: 0;
-	left: 0;
-	height: 140px;
-	width: 100%;
-	background-color: #fff;
-	overflow: hidden;
-	border-top: 1px solid #eee;
-	.carouselContent {
-		display: flex;
-		align-items: center;
-		height: 100%;
-		.carouselItem {
-			width: 108px;
-			height: 80px;
-			border: 1px solid #80808057;
-			border-radius: 5px;
-			margin: 0 10px;
-			padding: 10px;
-			background-color: #fff;
-			font-size: 12px;
-			overflow: hidden;
-			cursor: pointer;
-		}
-		.carouselItemAct {
-			/* color: #67C23A; */
-			border: 1px solid #62a4e698;
-			background-color: #62a4e623;
-		}
-	}
-}
-</style>

+ 0 - 5
src/views/menduner/system/talentMap/store/resume/index.vue

@@ -41,7 +41,6 @@
         <el-button type="primary" plain @click="handleAdd">
           <Icon icon="ep:plus" class="mr-5px" /> 新增人才
         </el-button>
-        <el-button type="success" plain @click="mergeFormRef.open()">人才合并</el-button>
       </el-form-item>
     </el-form>
   </ContentWrap>
@@ -95,8 +94,6 @@
       @pagination="getList"
     />
   </ContentWrap>
-
-  <MergeForm ref="mergeFormRef" />
 </template>
 
 <script setup>
@@ -104,14 +101,12 @@ import { TalentMap } from '@/api/menduner/system/talentMap'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { getDict } from '@/hooks/web/useDictionaries'
 import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
-import MergeForm from '../components/merge.vue'
 
 /** 人才地图 列表 */
 defineOptions({ name: 'TalentMap' })
 
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
-const mergeFormRef = ref()
 
 const loading = ref(false) // 列表的加载中
 const list = ref([]) // 列表的数据