123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591 |
- <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="500" @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="null">取 消</el-button>
- </template>
- </Dialog>
- <!-- 解析文件上传 -->
- <Dialog :title="radioObject[radioValue]" v-model="dialog_upload" :width="DialogWidth" @close="handleCancel">
- <div>
- <!-- 门墩儿人才库 -->
- <!-- <template v-if="radioValue === 'menduner'">
- </template> -->
- <!-- 简历解析 -->
- <template v-if="radioValue === 'file'">
- <el-upload
- 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'">
- <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 === 'web'"></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" width="80%">
- <div class="analysisInfoBox">
- <div class="analysisFile">
- <!-- 门墩儿人才库 -->
- <template v-if="radioValue === 'menduner'">
- <Search @detail="handleDetail" :detailButTxt="detailButTxt" :searchName="formData?.name_zh || formData?.name_en || ''" />
- </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"/>
- </template>
- </div>
- <FormPage ref="FormPageRef" :analysisType="analysisType" :itemData="formData" />
- </div>
- <template #footer>
- <el-button @click="handleSave" type="success" :disabled="analysisLoading">保 存</el-button>
- <el-button @click="dialog_analysisInfo = false">取 消</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'
- 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 ? data.reverse() : []
- } 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 { 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 dialog_analysisInfo = ref(false)
- const formLoading = ref(false)
- const analysisType = ref('')
- const FormPageRef = ref(null)
- const mergeFormRef = ref() // 合并表单 Ref
- const handleSave = async () => {
- 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(',');
- }
- console.log(params, 'handleSubmit')
- try {
- formLoading.value = true
- 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
- formLoading.value = false
- }
- }
- // 解析中
- 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
- }
- }
- // 简历解析
- const fileUrl = ref('') // https://minio.menduner.com/dev/person/229988673960153088/attachment/ee3eb21f45e13ede3557a03d18585ed80c5b4212ac5634e3436e309afaa8fe6a.pdf
- const uploadRef = ref()
- const fileList = ref([])
- const data = ref({ path: '' })
- // 文件上传
- const handleChange = async (file) => {
- data.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 = ''
- data.value.path = ''
- fileList.value = []
- resumeAnalysisToForm('reset') // 简历解析
- }
- const detailButTxt = '应用'
- // 搜索-查看详情
- const handleDetail = async (userId) => {
- if (!userId) return message.warning('请先选择人才!')
- try {
- const data = await TalentMap.getTalentMapDetail(userId)
- // 去除id
- resumeAnalysisToForm(data) // 简历解析
- message.success(`${detailButTxt}成功`)
- } catch {}
- }
- // 简历解析数据解构赋值
- 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 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') {
- dialog_analysisInfo.value = true
- return
- }
- if (type === 'web') {
- 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: '门墩儿人才库(普通新增)' }
- 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 = () => {
- 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;
- }
- }
- </style>
|