Xiao_123 пре 5 дана
родитељ
комит
e341fb6e29

+ 38 - 0
src/api/recruit/enterprise/talentMap/labeling.js

@@ -0,0 +1,38 @@
+import request from '@/config/axios'
+
+// 获取所有名片列表
+export const getCardList = async () => {
+	return await request.get({
+		url: '/api/parse/get-business-cards'
+	})
+}
+
+// 根据人才id获取关联的标签列表
+export const getTalentTagById = async (talent_id) => {
+	return await request.get({
+		url: `/api/parse/talent-get-tags/${talent_id}`,
+	})
+}
+
+// 更新人才关联标签
+export const updateTalentTags = async (data) => {
+	return await request.post({
+		url: '/api/parse/talent-update-tags',
+		data
+	})
+}
+
+// 人才启用/禁用
+export const updateTalentStatus = async (talent_id, data) => {
+	return await request.put({
+		url: `/api/parse/update-business-cards/${talent_id}/status`,
+		data
+	})
+}
+
+// 获取人才名片
+export const getTalentCardByImagePath = async (image_path) => {
+	return await request.download({
+		url: `/api/parse/business-cards/image/${image_path}`,
+	})
+}

+ 0 - 31
src/api/recruit/enterprise/talentMap/labeling/index.js

@@ -1,31 +0,0 @@
-import request from '@/config/axios'
-
-// 获取所有名片列表
-export const getCardList = async () => {
-  return await request.get({
-    url: '/api/parse/get-business-cards'
-  })
-}
-
-// 根据条件获取人才列表
-export const getLabelingList = async (data) => {
-  return await request.post({
-    url: '/api/parse/query-kg',
-    data
-	})
-}
-
-// 根据人才id获取关联的标签列表
-export const getTalentTagById = async (talent_id) => {
-  return await request.get({
-    url: `/api/parse/talent-get-tags/${talent_id}`,
-	})
-}
-
-// 更新人才关联标签
-export const updateTalentTags = async (data) => {
-  return await request.post({
-    url: '/api/parse/talent-update-tags',
-    data
-	})
-}

+ 9 - 0
src/api/recruit/enterprise/talentMap/search.js

@@ -0,0 +1,9 @@
+import request from '@/config/axios'
+
+// 根据条件获取人才列表
+export const getTalentList = async (data) => {
+	return await request.post({
+		url: '/api/parse/query-kg',
+		data
+	})
+}

+ 0 - 0
src/api/recruit/enterprise/talentMap/tag/index.js → src/api/recruit/enterprise/talentMap/tag.js


+ 8 - 0
src/router/modules/components/recruit/enterprise.js

@@ -428,6 +428,14 @@ const enterprise = [
         },
         component: () => import('@/views/recruit/enterprise/newTalentMap/newlyAppointed/index.vue')
       },
+      {
+        path: '/recruit/enterprise/talentMap/search',
+        meta: {
+          title: '人才搜索',
+          enName: 'Talent Search'
+        },
+        component: () => import('@/views/recruit/enterprise/newTalentMap/search/index.vue')
+      },
       // {
       //   path: '/recruit/enterprise/talentMap/talentMatching',
       //   meta: {

+ 1 - 0
src/styles/personal/navBar.css

@@ -55,6 +55,7 @@
   position: absolute;
   left: 20px;
   margin-bottom: 1px;
+  cursor: pointer;
 }
 
 .active-route {

+ 1 - 1
src/styles/personal/navBar.min.css

@@ -1 +1 @@
-@media (max-width: 1425px){.menuList{font-size:13px}.menuList>div{width:85px !important}}@media (max-width: 1480px){.menuList{margin-left:120px}}.vipBox .avatar{border:1px solid #b29701}.vipBox .avatar:hover{border:2px solid var(--v-primary-base)}.vipBox .userName{font-family:'MiSans-Bold';background:-webkit-linear-gradient(45deg, #333, #b29701);background:linear-gradient(45deg, #333, #b29701);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.banner{width:100%;height:50px;z-index:var(--zIndex-nav) !important;color:#333;background-color:#fff;border-bottom:1px solid #e8e6e6;padding-left:0px;font-size:15px}.innerBox{position:relative;width:100vw;min-width:1180px;margin:0 auto;align-items:center;height:50px;line-height:50px}.nav-logo{position:absolute;left:20px;margin-bottom:1px}.active-route{color:#00B760;font-weight:700;border-bottom:4px solid #00B760;background-color:#e5f8ef;height:50px}.menuList{display:flex;align-items:center;height:50px}.menuList div{width:110px;text-align:center}.menuList-first-title:hover{color:#00B760;font-weight:700}:deep(.v-toolbar__content){height:50px !important}:deep(.v-chip.v-chip--size-small){height:30px !important}
+@media (max-width: 1425px){.menuList{font-size:13px}.menuList>div{width:85px !important}}@media (max-width: 1480px){.menuList{margin-left:120px}}.vipBox .avatar{border:1px solid #b29701}.vipBox .avatar:hover{border:2px solid var(--v-primary-base)}.vipBox .userName{font-family:'MiSans-Bold';background:-webkit-linear-gradient(45deg, #333, #b29701);background:linear-gradient(45deg, #333, #b29701);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.banner{width:100%;height:50px;z-index:var(--zIndex-nav) !important;color:#333;background-color:#fff;border-bottom:1px solid #e8e6e6;padding-left:0px;font-size:15px}.innerBox{position:relative;width:100vw;min-width:1180px;margin:0 auto;align-items:center;height:50px;line-height:50px}.nav-logo{position:absolute;left:20px;margin-bottom:1px;cursor:pointer}.active-route{color:#00B760;font-weight:700;border-bottom:4px solid #00B760;background-color:#e5f8ef;height:50px}.menuList{display:flex;align-items:center;height:50px}.menuList div{width:110px;text-align:center}.menuList-first-title:hover{color:#00B760;font-weight:700}:deep(.v-toolbar__content){height:50px !important}:deep(.v-chip.v-chip--size-small){height:30px !important}

+ 1 - 0
src/styles/personal/navBar.scss

@@ -54,6 +54,7 @@
   position: absolute;
   left: 20px;
   margin-bottom: 1px;
+  cursor: pointer;
 }
 .active-route {
   color: #00B760;

+ 164 - 130
src/views/recruit/enterprise/newTalentMap/labeling/index.vue

@@ -1,13 +1,6 @@
 <template>
 	<div>
-		<v-card elevation="5" class="pa-10">
-			<TextInput
-        v-model="content"
-        :item="textItem"
-        @enter="handleSearch"
-        @appendInnerClick="handleSearch"
-      ></TextInput>
-		</v-card>
+		<CtFilter :items="filterItems" @reset="handleReset" @search="handleSearch" />
 
 		<v-card elevation="5" class="mt-3">
 			<CtTable
@@ -18,74 +11,73 @@
 				:disable-sort="true"
 				:elevation="0"
 				:isTools="false"
-				:noDataText="!content ? '请先输入您的描述信息定位人才' : '暂无数据'"
 				:items-per-page="-1"
-				height="calc(100vh - 400px)"
+				height="calc(100vh - 150px)"
 				:showFixedLastItem="true"
 				itemKey="id"
 			>
+				<template #status="{ item }">
+					<v-chip size="small" variant="elevated" :color="item.status === 'active' ? 'primary' : 'error'">{{ item.status === 'active' ? '已启用' : '已禁用' }}</v-chip>
+				</template>
 				<template #actions="{ item }">
 					<v-btn variant="text" color="primary" @click.stop="handleAnnotation(item)">标注</v-btn>
-					<v-btn variant="text" color="error" @click.stop="handleDisabled(item)">禁用</v-btn>
-					<v-btn variant="text" color="#008970" @click.stop="handleEnable(item)">启用</v-btn>
-					<v-btn variant="text" color="error" @click.stop="handleDelete(item)">删除</v-btn>
+					<v-btn v-if="item.status === 'active'" variant="text" color="warning" @click.stop="handleAction(item.id, 'inactive')">禁用</v-btn>
+					<v-btn v-if="item.status === 'inactive'" variant="text" color="#007cd6" @click.stop="handleAction(item.id, 'active')">启用</v-btn>
+					<!-- <v-btn variant="text" color="error" @click.stop="handleDelete(item)">删除</v-btn> -->
 				</template>
 			</CtTable>
 		</v-card>
 
 		<!-- 标注 -->
-		<CtDialog :visible="showAnnotationDialog" title="人才标注" :footer="false" widthType="1" @close="showAnnotationDialog = false">
+		<CtDialog :visible="showDialog" title="人才标注" :footer="false" widthType="1" @close="showDialog = false">
 			<v-row>
 				<v-col cols="4">
 					<v-card elevation="3" class="pa-3" style="height: 100%">
-						<p>名片</p>
-						<p>门墩儿新任命</p>
+						<p :class="{'active': previewUrl}" @click="handlePreview">
+							名片
+							<v-icon v-if="previewUrl">mdi-magnify-expand</v-icon>
+						</p>
+						<img v-if="previewUrl" width="100%" :src="previewUrl" />
+						<p class="mb-3" :class="{'mt-3': !previewUrl}">门墩儿新任命</p>
 						<p>门墩儿用户简历</p>
 					</v-card>
 				</v-col>
 				<v-col cols="8">
-					<v-card elevation="3" class="pa-3">
+					<v-card elevation="3" class="pa-3" height="100%">
 						<div class="base-info pa-3">
+							<div class="mb-6">
+								{{ talentItem.name_zh }}
+								<span v-if="talentItem.name_en">({{ talentItem.name_en }})</span>
+							</div>
 							<div class="d-flex align-center">
-								<div v-for="(val, index) in talentInfoKeys" :key="index" class="common-width">
+								<div v-for="(val, index) in talentInfoKeys" :key="index" class="common-width info-item">
 									<p v-ellipse-tooltip>{{ talentItem[val.key] }}</p>
 									<p v-ellipse-tooltip>{{ talentItem[val.value] }}</p>
 								</div>
 							</div>
 							<div class="d-flex align-center my-6">
-								<div class="common-width">{{ talentItem.phone }}</div>
-								<div class="common-width">{{ talentItem.email }}</div>
+								<div class="common-width pr-3" v-ellipse-tooltip>{{ talentItem.mobile }}</div>
+								<div style="flex: 1;">{{ talentItem.email }}</div>
 							</div>
-							<div>{{ talentItem.address }}</div>
+							<div>{{ talentItem.address_zh }}</div>
 						</div>
 						<div class="my-5">
-							<p>标签标注</p>
-							<v-chip
-								v-for="(item, index) in talentItem.automaticDimensionTags" :key="index"
-								class="chip mx-2 mt-4"
-								label color="primary"
-							>
-								{{ item }}
-								<v-icon size="18" color="primary" style="margin-left: 6px;" @click="closeClick(index, 'automaticDimensionTags')">mdi-close-circle</v-icon>
-							</v-chip>
-						</div>
-						<div>
 							<p>人才标签</p>
 							<v-chip
-								v-for="(item, index) in talentItem.manualAnnotationTags" :key="index"
+								v-for="(item, index) in talentSelectedTags" :key="index"
 								class="chip mx-2 mt-4 cursor-pointer"
 								label color="primary"
 							>
 								{{ item.name }}
-								<v-icon size="18" color="primary" style="margin-left: 6px;" @click="closeClick(item, 'manualAnnotationTags')">mdi-close-circle</v-icon>
+								<v-icon size="18" color="primary" style="margin-left: 6px;" @click="closeClick(item)">mdi-close-circle</v-icon>
 							</v-chip>
 
-							<div class="mt-5">
+							<div :class="{'mt-5': talentSelectedTags?.length > 0}">
 								<v-chip
 									v-for="(item, index) in tagList" :key="index"
 									class="chip mx-2 mt-4 cursor-pointer"
-									label color="primary"
-									:disabled="talentItem.manualAnnotationTags.includes(item)"
+									label color="#248dbb"
+									:disabled="talentSelectedTags.includes(item)"
 									@click="handleAdd(item)"
 								>
 									<v-icon icon="mdi-plus" start></v-icon>
@@ -93,10 +85,10 @@
 								</v-chip>
 							</div>
 						</div>
-						<v-divider class="mt-5"></v-divider>
+						<v-divider></v-divider>
 						<div class="d-flex justify-space-evenly my-5">
 							<v-btn width="120" color="warning" elevation="5" @click="handleClose">取 消</v-btn>
-							<v-btn width="120" color="primary" elevation="5" @click="handleSave">保 存</v-btn>
+							<v-btn width="120" color="primary" elevation="5" @click="handleUpdate">更 新</v-btn>
 						</div>
 					</v-card>
 				</v-col>
@@ -105,12 +97,14 @@
 	</div>
 
 	<Loading :visible="annotationLoading"></Loading>
+
+	<PreviewImage v-if="showPreview" :initialIndex="0" :urlList="[previewUrl]" @close="showPreview = !showPreview" />
 </template>
 
 <script setup>
 defineOptions({ name: 'NewTalentMapAnnotation' })
 import { ref } from 'vue'
-import { getLabelingList, getCardList } from '@/api/recruit/enterprise/talentMap/labeling'
+import { getCardList, updateTalentStatus, getTalentCardByImagePath, getTalentTagById, updateTalentTags } from '@/api/recruit/enterprise/talentMap/labeling'
 import { getTalentTagList } from '@/api/recruit/enterprise/talentMap/tag'
 import Snackbar from '@/plugins/snackbar'
 import Confirm from '@/plugins/confirm'
@@ -118,45 +112,54 @@ import { useI18n } from '@/hooks/web/useI18n'
 
 const { t } = useI18n()
 const loading = ref(false)
-const showAnnotationDialog = ref(false)
-const content = ref('查找所有在上海的五星级酒店担任总经理职位的人才')
-const items = ref([
-  {
-    "姓名": "张三",
-    "邮箱": "zhang@example.com",
-    "职位": "总经理",
-    "酒店名称": "上海四季酒店"
-  },
-  {
-    "姓名": "李四",
-    "邮箱": "li@example.com",
-    "职位": "执行总经理",
-    "酒店名称": "上海浦东丽思卡尔顿酒店"
-  }
-])
+const showDialog = ref(false)
+const items = ref([])
 const headers = [
-  { title: '姓名', key: '姓名', sortable: false },
-  { title: '酒店名称', key: '酒店名称', sortable: false },
-  { title: '职位', key: '职位', sortable: false },
-  { title: '邮箱', key: '邮箱', sortable: false },
+  { title: '姓名', key: 'name_zh', sortable: false },
+  // { title: '英文名', key: 'name_en', sortable: false },
+  { title: '职位', key: 'title_zh', sortable: false },
+  { title: '酒店', key: 'hotel_zh', sortable: false },
+  { title: '人才状态', key: 'status', sortable: false },
+  { title: '创建时间', key: 'created_at', sortable: false },
   { title: '操作', key: 'actions', sortable: false, align: 'center' }
 ]
-const textItem = ref({
-  type: 'text',
-  value: '',
-  label: '请输入您的描述信息定位人才',
-  placeholder: '回车开始人才匹配',
-  clearable: true,
-	hideDetails: true,
-  appendInnerIcon: 'mdi-magnify'
+const filterItems = ref({
+  options: [
+    {
+      type: 'text',
+      key: 'title_zh',
+      value: '',
+      label: '职位',
+			clearable: true,
+			hideDetails: true,
+			width: 200
+    },
+		{
+      type: 'text',
+      key: 'hotel_zh',
+      value: '',
+      label: '酒店',
+			clearable: true,
+			hideDetails: true,
+			width: 200
+    },
+		{
+      type: 'text',
+      key: 'name_zh',
+      value: '',
+      label: '中文名',
+			clearable: true,
+			hideDetails: true,
+			width: 200
+    }
+  ]
 })
 
+// 获取名片列表
 const getList = async () => {
 	loading.value = true
 	try {
-		const data = await getLabelingList({ query_requirement: content.value })
-		const card = await getCardList()
-		console.log(data, '列表========', card)
+		const data = await getCardList()
 		items.value = data || []
 	} finally {
 		loading.value = false
@@ -164,35 +167,25 @@ const getList = async () => {
 }
 getList()
 
-
 // 搜索
-const handleSearch = async () => {
-	console.log('搜索', content.value)
-	if (!content.value) return Snackbar.warning('请输入您的描述信息定位人才')
-	getList()
+const handleSearch = async (obj) => {
+	console.log(obj, '搜索')
+	// query.value = obj
+	// getList()
+}
+// 重置
+const handleReset = (obj) => {
+	console.log(obj, '重置')
+	// query.value = obj
+	// getList()
 }
 
 // 标注
-const talentItem = ref({
-	name: '张三',
-	enName: 'Tom',
-	postName: '总经理',
-	enPostName: 'General Manager',
-	inaugurationHotel: '南京洲际酒店',
-	enInaugurationHotel: 'Nanjing InterContinental Hotel',
-	groupName: '洲际集团',
-	enGroupName: 'IHG',
-	phone: '13800138000',
-	email: 'zhangsan@163.com',
-	address: '南京市人民路123号',
-	automaticDimensionTags: ['富强', '民主', '文明', '和谐'],
-	manualAnnotationTags: [],
-})
+const talentItem = ref({})
 const talentInfoKeys = [
-	{ key: 'name', value: 'enName' },
-	{ key: 'postName', value: 'enPostName' },
-	{ key: 'inaugurationHotel', value: 'enInaugurationHotel' },
-	{ key: 'groupName', value: 'enGroupName' },
+	{ key: 'title_zh', value: 'title_en' },
+	{ key: 'hotel_zh', value: 'hotel_en' },
+	{ key: 'brand_zh', value: 'brand_en' },
 ]
 
 // 获取人才标签
@@ -207,59 +200,87 @@ const getTagList = async () => {
 	}
 }
 
+// 名片预览
+const showPreview = ref(false)
+const handlePreview = () => {
+	showPreview.value = true
+}
+
 // 标注
+const talentSelectedTags = ref([])
+const previewUrl = ref(null)
 const annotationLoading = ref(false)
 const handleAnnotation = async (item) => {
+	if (!item || !item.id) return
+	
+	talentItem.value = item
+	// 获取所有标签列表
 	await getTagList()
-	showAnnotationDialog.value = true
-}
 
-// 删除
-const closeClick = (index, dataKey) => {
-	if (dataKey === 'manualAnnotationTags') {
-		const dataIndex = talentItem.value[dataKey].findIndex((i) => i === index)
-		if (dataIndex !== -1) index = dataIndex
+	// 获取名片预览
+	if (item.image_path) {
+		const data = await getTalentCardByImagePath(item.image_path)
+		previewUrl.value = URL.createObjectURL(data)
 	}
-	talentItem.value[dataKey].splice(index, 1)
+
+	// 获取人才标签
+	const tagData = await getTalentTagById(item.id)
+	console.log(tagData, '人才标签')
+
+	showDialog.value = true
 }
 
-// 手动标注添加
-const handleAdd = (item) => {
-	talentItem.value.manualAnnotationTags.push(item)
+// 标签删除
+const closeClick = (item) => {
+	const index = talentSelectedTags.value.findIndex((i) => i === item)
+	if (index !== -1) talentSelectedTags.value.splice(index, 1)
 }
 
-// 禁用
-const handleDisabled = (item) => {
-	console.log(item, '禁用')
-	Confirm(t('common.confirmTitle'), '是否确定禁用此人才数据?').then(async () => {
-    Snackbar.success(t('common.operationSuccessful'))
-  })
+// 标签添加
+const handleAdd = (item) => {
+	talentSelectedTags.value.push(item)
 }
 
-// 启用
-const handleEnable = (item) => {
-	console.log(item, '启用')
-	Confirm(t('common.confirmTitle'), '是否确定启用此人才数据?').then(async () => {
-    Snackbar.success(t('common.operationSuccessful'))
+// 启用、禁用
+const handleAction = (id, status) => {
+	Confirm(t('common.confirmTitle'), `是否确定${status === 'active' ? '启用' : '禁用'}?`).then(async () => {
+		try {
+			await updateTalentStatus(id, { status })
+			Snackbar.success(t('common.operationSuccessful'))
+			getList()
+		} catch {}
   })
 }
 
 // 删除
-const handleDelete = (item) => {
-	console.log(item, '删除')
-	Confirm(t('common.confirmTitle'), '是否确定删除?').then(async () => {
-    // await deleteInvoiceTitle(id)
-    Snackbar.success(t('common.delMsg'))
-    // getList()
-  })
-}
+// const handleDelete = (item) => {
+// 	console.log(item, '删除')
+// 	Confirm(t('common.confirmTitle'), '是否确定删除?').then(async () => {
+//     // await deleteInvoiceTitle(id)
+//     Snackbar.success(t('common.delMsg'))
+//     // getList()
+//   })
+// }
 
+// 更新人才标签
 const handleClose = () => {
-	showAnnotationDialog.value = false
+	showDialog.value = false
+	talentItem.value = {}
+	talentSelectedTags.value = []
+	previewUrl.value = ''
 }
 
-const handleSave = async () => {
-	handleClose()
+const handleUpdate = async () => {
+	const tags = talentSelectedTags.value.map(e => {
+		return { talent: e.id, tag: e.name }
+	})
+
+	try {
+		await updateTalentTags(tags)
+		Snackbar.success('人才标签更新成功')
+		handleClose()
+		getList()
+	} catch {}
 }
 </script>
 
@@ -269,7 +290,20 @@ const handleSave = async () => {
 	border-radius: 6px;
 }
 .common-width {
-	width: 25%;
-	max-width: 25%;
+	width: 33.3%;
+	max-width: 33.3%;
+}
+.info-item {
+	padding-right: 12px;
+	&:nth-child(3n) {
+		padding-right: 0;
+	}
+	p {
+		height: 24px;
+	}
+}
+.active {
+	color: var(--v-primary-base);
+	cursor: pointer;
 }
 </style>

+ 88 - 0
src/views/recruit/enterprise/newTalentMap/search/index.vue

@@ -0,0 +1,88 @@
+<template>
+	<div>
+		<v-card elevation="5" class="pa-10">
+			<TextInput
+        v-model="content"
+        :item="textItem"
+        @enter="handleSearch"
+        @appendInnerClick="handleSearch"
+      ></TextInput>
+		</v-card>
+
+		<v-card elevation="5" class="mt-3">
+			<CtTable
+				:items="items"
+				class="pa-3"
+				:headers="headers"
+				:loading="loading"
+				:disable-sort="true"
+				:elevation="0"
+				:isTools="false"
+				:noDataText="!content ? '请先输入您的描述信息定位人才' : '暂无数据'"
+				:items-per-page="-1"
+				height="calc(100vh - 400px)"
+				:showFixedLastItem="true"
+				itemKey="id"
+			></CtTable>
+		</v-card>
+	</div>
+
+	<Loading :visible="annotationLoading"></Loading>
+</template>
+
+<script setup>
+defineOptions({ name: 'NewTalentMapAnnotation' })
+import { ref } from 'vue'
+import { getTalentList } from '@/api/recruit/enterprise/talentMap/search'
+import Snackbar from '@/plugins/snackbar'
+
+const loading = ref(false)
+const content = ref('')
+const items = ref([])
+const headers = [
+  { title: '姓名', key: 'chinese_name', sortable: false },
+  { title: '酒店名称', key: 'hotel_chinese_name', sortable: false },
+  { title: '职位', key: 'position_chinese', sortable: false },
+  { title: '邮箱', key: 'email', sortable: false },
+  { title: '联系电话', key: 'mobile', sortable: false },
+  { title: '更新时间', key: 'talent_updated', sortable: false },
+]
+const textItem = ref({
+  type: 'text',
+  value: '',
+  label: '请输入您的描述信息定位人才',
+  placeholder: '请输入您的描述信息,例如:查找所有在上海的五星级酒店担任总经理职位的人才',
+  clearable: true,
+	hideDetails: true,
+  appendInnerIcon: 'mdi-magnify'
+})
+
+// 人才列表
+const getList = async () => {
+	loading.value = true
+	try {
+		const data = await getTalentList({ query_requirement: content.value })
+		items.value = data || []
+	} finally {
+		loading.value = false
+	}
+}
+getList()
+
+// 搜索
+const handleSearch = async () => {
+	if (!content.value) return Snackbar.warning('请输入您的描述信息定位人才')
+	getList()
+}
+</script>
+
+<style scoped lang="scss">
+.base-info {
+	background-color: #f7f8fa;
+	border-radius: 6px;
+}
+.common-width {
+	width: 25%;
+	max-width: 25%;
+}
+</style>