|
@@ -0,0 +1,384 @@
|
|
|
+<template>
|
|
|
+ <Dialog title="人才标注" v-model="dialogVisible" class="!w-90%">
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="9">
|
|
|
+ <el-card>
|
|
|
+ <div>
|
|
|
+ <p :style="{'color': previewUrl ? '#2d8cf0' : ''}">名片</p>
|
|
|
+ <el-image v-if="previewUrl" width="100%" :preview-src-list="[previewUrl]" class="cursor-pointer" :src="previewUrl" />
|
|
|
+ </div>
|
|
|
+ <p>门墩儿用户简历</p>
|
|
|
+ <div>
|
|
|
+ <p :style="{'color': markdown_text ? '#2d8cf0' : ''}">门墩儿新任命</p>
|
|
|
+ <iframe
|
|
|
+ v-if="markdown_text"
|
|
|
+ id="Iframe"
|
|
|
+ class="markdownContent"
|
|
|
+ src=""
|
|
|
+ frameborder="0"
|
|
|
+ ></iframe>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="15">
|
|
|
+ <div class="!h-100% overflow-y-auto">
|
|
|
+ <el-tabs type="border-card">
|
|
|
+ <el-tab-pane label="人才信息">
|
|
|
+ <FormPage ref="baseInfoRef" formType="edit" :itemData="talentItem" />
|
|
|
+ <div class="text-right mt-12px">
|
|
|
+ <el-button @click="handleSave" type="primary" :disabled="saveLoading">保 存</el-button>
|
|
|
+ <el-button @click="dialogVisible = false">取消</el-button>
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+ <el-tab-pane label="人才标签">
|
|
|
+ <el-card shadow="never">
|
|
|
+ <div class="my-5">
|
|
|
+ <div class="mt-4 px-3 pb-3" style="border: 1px dashed #67c23a; border-radius: 4px;">
|
|
|
+ <div v-if="talentSelectedTags?.length">
|
|
|
+ <el-tag
|
|
|
+ v-for="(item, index) in talentSelectedTags"
|
|
|
+ :key="index"
|
|
|
+ closable
|
|
|
+ size="large"
|
|
|
+ type="success"
|
|
|
+ class="mr-14px mt-14px"
|
|
|
+ @close="closeClick(item)"
|
|
|
+ >
|
|
|
+ {{ item.name }}
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ <div v-else style="color: #777; text-align: center; line-height: 50px; margin-top: 12px;">请添加标签</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div :class="{'mt-5': talentSelectedTags?.length > 0}">
|
|
|
+ <el-tag
|
|
|
+ v-for="(item, index) in tagList" :key="index"
|
|
|
+ size="large"
|
|
|
+ type="primary"
|
|
|
+ class="mr-14px mt-14px"
|
|
|
+ :class="{'cursor-pointer': !talentSelectedTags.find(k => k.name === item.name)}"
|
|
|
+ :effect="talentSelectedTags.find(k => k.name === item.name) ? 'info' : 'default'"
|
|
|
+ @click="handleAdd(item)"
|
|
|
+ >
|
|
|
+ + {{ item.name }}
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ <div class="text-right mt-12px">
|
|
|
+ <el-button @click="submitForm" type="primary" :disabled="loading">保 存</el-button>
|
|
|
+ <el-button @click="dialogVisible = false">取消</el-button>
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </Dialog>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { talentLabelingApi } from '@/api/menduner/system/talentMap/labeling'
|
|
|
+import { talentTagApi } from '@/api/menduner/system/talentMap/tag'
|
|
|
+import DefaultData from '@/views/menduner/system/talentMap/details/defaultData'
|
|
|
+import { getDict } from '@/hooks/web/useDictionaries'
|
|
|
+import { cloneDeep } from 'lodash-es'
|
|
|
+import { TalentMap } from '@/api/menduner/system/talentMap'
|
|
|
+import FormPage from '@/views/menduner/system/talentMap/components/FormPage.vue'
|
|
|
+import { marked } from 'marked'
|
|
|
+
|
|
|
+const message = useMessage() // 消息弹窗
|
|
|
+const loading = ref(false)
|
|
|
+const dialogVisible = ref(false) // 弹窗的是否展示
|
|
|
+const talentItem = ref({})
|
|
|
+const previewUrl = ref()
|
|
|
+const talentSelectedTags = ref([])
|
|
|
+const tagList = ref([])
|
|
|
+
|
|
|
+const result = ref(cloneDeep(DefaultData))
|
|
|
+
|
|
|
+// 获取人才标签
|
|
|
+const getTagList = async () => {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const data = await talentTagApi.getTalentTagList()
|
|
|
+ tagList.value = data || []
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 地区树状列表
|
|
|
+const areaTreeData = ref([])
|
|
|
+const getDictData = async () => {
|
|
|
+ const { data } = await getDict('areaTreeData', {}, 'areaTreeData')
|
|
|
+ const obj = data.find(e => e.name === '中国')
|
|
|
+ const list = obj?.children ? obj.children.map(e =>{
|
|
|
+ // 市辖区直接显示区
|
|
|
+ const municipality = e.children && e.children.length && e.children[0].name === '市辖区'
|
|
|
+ if (municipality && e.children[0].children?.length) e.children = e.children[0].children
|
|
|
+ return e
|
|
|
+ }) : []
|
|
|
+ areaTreeData.value = list.length ? list : []
|
|
|
+}
|
|
|
+getDictData()
|
|
|
+
|
|
|
+// 获取人才标签
|
|
|
+const getTalentTagById = async() => {
|
|
|
+ const id = talentItem.value?.id
|
|
|
+ if (!id) {
|
|
|
+ talentSelectedTags.value = []
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const tagData = await talentLabelingApi.getTalentTagById(id)
|
|
|
+ talentSelectedTags.value = tagData ? tagData.map((i) => {
|
|
|
+ return { id: i.talent, name: i.tag }
|
|
|
+ }) : []
|
|
|
+}
|
|
|
+
|
|
|
+// 存储observer引用,用于销毁
|
|
|
+let currentObserver = null
|
|
|
+
|
|
|
+// 查看原网页
|
|
|
+const showPage = (html) => {
|
|
|
+ // 预处理HTML内容
|
|
|
+ html = html.replace(/data-src/g, 'src') // 将 data-src 转化为 src
|
|
|
+ .replace(/https/g, 'http') // 将HTML内容中所有的https替换为http
|
|
|
+
|
|
|
+ // 创建完整的HTML文档,包含强制图片样式的CSS
|
|
|
+ const fullHtml = `
|
|
|
+ <!DOCTYPE html>
|
|
|
+ <html>
|
|
|
+ <head>
|
|
|
+ <meta charset="utf-8">
|
|
|
+ <meta name="referrer" content="never">
|
|
|
+ <style>
|
|
|
+ * {
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+ body {
|
|
|
+ margin: 0;
|
|
|
+ padding: 0;
|
|
|
+ width: 100%;
|
|
|
+ max-width: 100%;
|
|
|
+ overflow-x: hidden;
|
|
|
+ }
|
|
|
+ img {
|
|
|
+ width: 100% !important;
|
|
|
+ max-width: 100% !important;
|
|
|
+ height: auto !important;
|
|
|
+ object-fit: contain !important;
|
|
|
+ display: block !important;
|
|
|
+ }
|
|
|
+ </style>
|
|
|
+ </head>
|
|
|
+ <body>
|
|
|
+ ${html}
|
|
|
+ </body>
|
|
|
+ </html>
|
|
|
+ `
|
|
|
+
|
|
|
+ nextTick(() => {
|
|
|
+ const iframe = document.getElementById('Iframe')
|
|
|
+ if (!iframe) return
|
|
|
+
|
|
|
+ // 销毁之前的observer
|
|
|
+ if (currentObserver) {
|
|
|
+ currentObserver.disconnect()
|
|
|
+ currentObserver = null
|
|
|
+ }
|
|
|
+
|
|
|
+ const doc = iframe.contentDocument || iframe.document
|
|
|
+ doc.open()
|
|
|
+ doc.write(fullHtml)
|
|
|
+ doc.close()
|
|
|
+
|
|
|
+ // 等待内容加载完成后进一步处理
|
|
|
+ setTimeout(() => {
|
|
|
+ // 确保js_content可见
|
|
|
+ const jsContent = doc.getElementById('js_content')
|
|
|
+ if (jsContent) {
|
|
|
+ jsContent.style.visibility = 'visible'
|
|
|
+ jsContent.style.opacity = 1
|
|
|
+ }
|
|
|
+
|
|
|
+ // 强制重新设置所有图片样式
|
|
|
+ const images = doc.querySelectorAll('img')
|
|
|
+ images.forEach(img => {
|
|
|
+ img.style.setProperty('width', '100%', 'important')
|
|
|
+ img.style.setProperty('max-width', '100%', 'important')
|
|
|
+ img.style.setProperty('height', 'auto', 'important')
|
|
|
+ img.style.setProperty('object-fit', 'contain', 'important')
|
|
|
+ img.style.setProperty('display', 'block', 'important')
|
|
|
+ img.removeAttribute('width')
|
|
|
+ img.removeAttribute('height')
|
|
|
+ })
|
|
|
+
|
|
|
+ // 设置所有容器
|
|
|
+ const containers = doc.querySelectorAll('div, section, article, p')
|
|
|
+ containers.forEach(container => {
|
|
|
+ container.style.setProperty('width', '100%', 'important')
|
|
|
+ container.style.setProperty('max-width', '100%', 'important')
|
|
|
+ container.style.setProperty('overflow', 'hidden', 'important')
|
|
|
+ })
|
|
|
+
|
|
|
+ // 创建新的observer并保存引用
|
|
|
+ currentObserver = new MutationObserver((mutations) => {
|
|
|
+ mutations.forEach((mutation) => {
|
|
|
+ mutation.addedNodes.forEach((node) => {
|
|
|
+ if (node.nodeType === 1) {
|
|
|
+ const newImages = node.querySelectorAll ? node.querySelectorAll('img') : []
|
|
|
+ if (node.tagName === 'IMG') newImages.push(node)
|
|
|
+
|
|
|
+ newImages.forEach(img => {
|
|
|
+ img.style.setProperty('width', '100%', 'important')
|
|
|
+ img.style.setProperty('max-width', '100%', 'important')
|
|
|
+ img.style.setProperty('height', 'auto', 'important')
|
|
|
+ img.style.setProperty('object-fit', 'contain', 'important')
|
|
|
+ img.style.setProperty('display', 'block', 'important')
|
|
|
+ img.removeAttribute('width')
|
|
|
+ img.removeAttribute('height')
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ currentObserver.observe(doc.body, {
|
|
|
+ childList: true,
|
|
|
+ subtree: true
|
|
|
+ })
|
|
|
+ }, 200) // 增加延时确保内容完全加载
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+/** 打开弹窗 */
|
|
|
+const markdown_text = ref(null)
|
|
|
+const open = async (data) => {
|
|
|
+ console.log(data, 'open')
|
|
|
+ previewUrl.value = null
|
|
|
+ dialogVisible.value = true
|
|
|
+ talentItem.value = data
|
|
|
+
|
|
|
+ // 获取所有人才标签
|
|
|
+ await getTagList()
|
|
|
+
|
|
|
+ // 获取名片预览
|
|
|
+ // if (data.image_path) {
|
|
|
+ // const res = await talentLabelingApi.getTalentCardByImagePath(data.image_path)
|
|
|
+ // previewUrl.value = URL.createObjectURL(res)
|
|
|
+ // }
|
|
|
+
|
|
|
+ // 网页解析原始markdown内容
|
|
|
+ // if (data.origin_source) {
|
|
|
+ // if (!data.origin_source?.minio_path) return
|
|
|
+ // const result = await talentLabelingApi.getTalentMarkdown(data.origin_source.minio_path)
|
|
|
+ // if (result) {
|
|
|
+ // markdown_text.value = marked(result)
|
|
|
+ // if (markdown_text.value) showPage(markdown_text.value)
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+ // 获取人才标签
|
|
|
+ await getTalentTagById()
|
|
|
+}
|
|
|
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
|
|
+
|
|
|
+// 标签删除
|
|
|
+const closeClick = (item) => {
|
|
|
+ const index = talentSelectedTags.value.findIndex((i) => i === item)
|
|
|
+ if (index !== -1) talentSelectedTags.value.splice(index, 1)
|
|
|
+}
|
|
|
+
|
|
|
+// 标签添加
|
|
|
+const handleAdd = (item) => {
|
|
|
+ talentSelectedTags.value.push(item)
|
|
|
+}
|
|
|
+
|
|
|
+/** 人才标签保存 */
|
|
|
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
|
|
+const submitForm = async () => {
|
|
|
+ if (!talentSelectedTags.value || !talentSelectedTags.value.length) return message.warning('请选择要更新的人才标签')
|
|
|
+
|
|
|
+ loading.value = true
|
|
|
+ const tags = talentSelectedTags.value.map(e => {
|
|
|
+ return { talent: talentItem.value.id, tag: e.name }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 提交请求
|
|
|
+ try {
|
|
|
+ await talentLabelingApi.updateTalentTags(tags)
|
|
|
+ message.success('人才标签更新成功')
|
|
|
+ dialogVisible.value = false
|
|
|
+ // 发送操作成功的事件
|
|
|
+ emit('success')
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 保存人才信息
|
|
|
+const baseInfoRef = ref(null)
|
|
|
+const saveLoading = ref(false)
|
|
|
+const handleSave = async () => {
|
|
|
+ saveLoading.value = true
|
|
|
+ const params = baseInfoRef.value.formQuery
|
|
|
+ if (!params || !Object.keys(params).length) return saveLoading.value = true
|
|
|
+ // 数组转为字符串保存
|
|
|
+ if (Array.isArray(params?.mobile)) {
|
|
|
+ params.mobile = params.mobile.filter(i => Boolean(i)).map(j => String(j).replace(/,|,/g, '')).join(',');
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ await talentLabelingApi.updateBusinessCard({...params, origin_source: talentItem.value?.origin_source}, talentItem.value.id)
|
|
|
+ dialogVisible.value = false
|
|
|
+ message.success('保存成功')
|
|
|
+ emit('success')
|
|
|
+ } finally {
|
|
|
+ saveLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 清理函数,用于销毁observer
|
|
|
+const cleanup = () => {
|
|
|
+ if (currentObserver) {
|
|
|
+ currentObserver.disconnect()
|
|
|
+ currentObserver = null
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 监听弹窗关闭,清理observer
|
|
|
+watch(dialogVisible, (newVal) => {
|
|
|
+ if (!newVal) {
|
|
|
+ cleanup()
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 组件卸载时清理
|
|
|
+onUnmounted(() => {
|
|
|
+ cleanup()
|
|
|
+})
|
|
|
+
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.base-info {
|
|
|
+ background-color: #f7f8fa;
|
|
|
+ border-radius: 6px;
|
|
|
+ padding: 15px;
|
|
|
+}
|
|
|
+:deep {
|
|
|
+ .el-descriptions__content {
|
|
|
+ color: #303133;
|
|
|
+ }
|
|
|
+ .el-descriptions__body {
|
|
|
+ background-color: #f7f8fa;
|
|
|
+ }
|
|
|
+ .markdownContent {
|
|
|
+ width: 100%;
|
|
|
+ max-width: 100%;
|
|
|
+ height: 600px;
|
|
|
+ border: none;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|