index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. <template>
  2. <!-- 搜索工作栏 -->
  3. <ContentWrap>
  4. <el-form
  5. class="-mb-15px"
  6. :model="queryParams"
  7. ref="queryFormRef"
  8. :inline="true"
  9. label-width="68px"
  10. >
  11. <el-form-item label="任务类型" prop="task_type">
  12. <el-select
  13. v-model="queryParams.task_type"
  14. placeholder="请选择类型"
  15. clearable
  16. class="!w-200px"
  17. @change="handleQuery"
  18. >
  19. <el-option v-for="(val, index) in taskType" :label="val.label" :value="val.value" :key="index" />
  20. </el-select>
  21. </el-form-item>
  22. <el-form-item label="任务状态" prop="task_status">
  23. <el-select
  24. v-model="queryParams.task_status"
  25. placeholder="请选择状态"
  26. clearable
  27. class="!w-200px"
  28. @change="handleQuery"
  29. >
  30. <el-option v-for="(val, index) in taskStatus" :label="val" :value="val" :key="index" />
  31. </el-select>
  32. </el-form-item>
  33. <el-form-item>
  34. <el-button @click="handleQuery"><Icon icon="ep:search" /> 搜索</el-button>
  35. <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
  36. <el-button type="primary" plain @click="handleAdd">
  37. <Icon icon="ep:plus" class="mr-5px" /> 新增人才
  38. </el-button>
  39. </el-form-item>
  40. </el-form>
  41. </ContentWrap>
  42. <!-- 列表 -->
  43. <ContentWrap>
  44. <el-table v-loading="loading" :data="list" :stripe="true">
  45. <el-table-column label="任务名称" align="center" prop="task_name" />
  46. <el-table-column label="任务类型" align="center" prop="task_type" />
  47. <el-table-column label="任务状态" align="center" prop="task_status" />
  48. <el-table-column label="创建时间" align="center" prop="created_at" :formatter="dateFormatter" />
  49. <el-table-column label="更新时间" align="center" prop="updated_at" :formatter="dateFormatter" />
  50. <el-table-column label="操作" align="center" fixed="right" min-width="110">
  51. <template #default="scope">
  52. <el-button
  53. v-if="scope.row.task_status === '待解析'"
  54. link
  55. type="success"
  56. @click="handleAnalysis(scope.row)"
  57. >解析</el-button>
  58. <el-button
  59. v-if="scope.row.task_status === '成功' && scope.row.parse_result"
  60. link
  61. type="primary"
  62. @click="handleStore(scope.row)"
  63. >入库</el-button>
  64. </template>
  65. </el-table-column>
  66. </el-table>
  67. <!-- 分页 -->
  68. <Pagination
  69. :total="total"
  70. v-model:page="queryParams.page"
  71. v-model:limit="queryParams.per_page"
  72. @pagination="getList"
  73. />
  74. </ContentWrap>
  75. <!-- 选择来源 -->
  76. <Dialog title="新增" v-model="openSelect" width="550" @close="openSelect = false">
  77. <el-radio-group v-model="radioValue" size="large" class="radioBox">
  78. <el-radio
  79. v-for="item in radioList"
  80. :key="item.value"
  81. :value="item.value"
  82. >
  83. {{ item.label }}
  84. </el-radio>
  85. </el-radio-group>
  86. <template #footer>
  87. <el-button type="primary" @click="handleSelect">确 认</el-button>
  88. <el-button @click="openSelect = false">取 消</el-button>
  89. </template>
  90. </Dialog>
  91. <!-- 解析文件上传 -->
  92. <Dialog :title="radioObject[radioValue]" v-model="dialog_upload" :modalClose="false" :width="DialogWidth" @close="handleCancel">
  93. <div>
  94. <!-- 简历解析 -->
  95. <template v-if="radioValue === 'file'">
  96. <el-upload
  97. ref="uploadRef"
  98. v-model:file-list="fileList"
  99. :action="uploadUrl"
  100. :auto-upload="false"
  101. :data="fileData"
  102. :on-change="handleChange"
  103. :on-error="submitFormError"
  104. :on-exceed="handleExceed"
  105. :http-request="httpRequest"
  106. accept=".pdf"
  107. drag
  108. :limit="5"
  109. multiple
  110. class="flex-1"
  111. >
  112. <i class="el-icon-upload"></i>
  113. <div class="el-upload__text">仅允许导入PDF格式文件! 将文件拖到此处,或 <em>点击上传</em></div>
  114. <template #tip>
  115. <div class="el-upload__tip color-red">
  116. 提示:最多可上传5份简历({{ fileList.length }}/5)
  117. </div>
  118. </template>
  119. </el-upload>
  120. </template>
  121. <!-- 名片解析 -->
  122. <template v-if="radioValue === 'card'">
  123. <UploadImgs
  124. v-model="cardImgUrl"
  125. :uploadSuccessTip="false"
  126. :limit="10"
  127. @handle-change="cardUploadChange"
  128. height="150px" width="150px" style="margin: 20px auto; text-align: center;"
  129. >
  130. <template #tip>{{ cardImgUrl ? `${cardImgUrl.length}/10` : '请上传名片(最多可上传10张)' }}</template>
  131. </UploadImgs>
  132. </template>
  133. <!-- 杂项 -->
  134. <template v-if="radioValue === 'picture'">
  135. <div class="flex align-center">
  136. <UploadImg
  137. v-model="cardImgUrl"
  138. :uploadSuccessTip="false"
  139. @handle-change="val => cardUploadChange(val, '杂项')"
  140. height="150px" width="150px" style="margin: 20px auto; text-align: center;"
  141. >
  142. <template #tip>{{ cardImgUrl ? '' : '请上传图片' }}</template>
  143. </UploadImg>
  144. </div>
  145. </template>
  146. <!-- 网页解析 -->
  147. <template v-if="radioValue === 'web'">
  148. <webAnalysis
  149. @analysis="handleWebAnalysis"
  150. @reset="webOriginList = []"
  151. />
  152. </template>
  153. <!-- 网页解析 -->
  154. <template v-if="radioValue === 'menduner'">
  155. <Search ref="SearchRef" />
  156. </template>
  157. </div>
  158. <template #footer>
  159. <el-button @click="handleSubmit" type="success" :disabled="analysisLoading" :loading="analysisLoading">提 交</el-button>
  160. <el-button @click="handleCancel">取 消</el-button>
  161. </template>
  162. </Dialog>
  163. <StorePage ref="StorePageRef" @refresh="getList" />
  164. </template>
  165. <script setup>
  166. defineOptions({ name: 'TalentMapStoreIndex' })
  167. import { dateFormatter } from '@/utils/formatTime'
  168. import { talentLabelingApi } from '@/api/menduner/system/talentMap/labeling'
  169. import { TalentMap } from '@/api/menduner/system/talentMap'
  170. import Search from './components/search.vue'
  171. import webAnalysis from './components/webAnalysis.vue'
  172. import { useUpload } from '@/components/UploadFile/src/useUpload'
  173. import { commonApi } from '@/api/menduner/common'
  174. import { Base64 } from 'js-base64'
  175. import Info from '@/views/menduner/system/person/details/components/info.vue'
  176. import expExtend from '@/views/menduner/system/person/details/components/expExtend.vue'
  177. import Attachment from '@/views/menduner/system/person/details/components/attachment.vue'
  178. import { talentWebParsingApi } from '@/api/menduner/system/talentMap/webParsing'
  179. import { ElLoading } from 'element-plus'
  180. import StorePage from '@/views/menduner/system/talentMap/maintenance/gather/components/store.vue'
  181. import { talentGatherApi } from '@/api/menduner/system/talentMap/gather'
  182. const { uploadUrl, httpRequest } = useUpload()
  183. const message = useMessage() // 消息弹窗
  184. const { t } = useI18n() // 国际化
  185. const loading = ref(false) // 列表的加载中
  186. const list = ref([]) // 列表的数据
  187. const total = ref(0) // 列表的总页数
  188. const queryParams = reactive({
  189. page: 1,
  190. per_page: 10,
  191. task_type: undefined, // 任务类型
  192. task_status: undefined // 任务状态
  193. })
  194. const queryFormRef = ref() // 搜索的表单
  195. const dialog_upload = ref(false)
  196. const taskType = [
  197. { label: '名片', value: '名片' },
  198. { label: '简历', value: '简历' },
  199. { label: '门墩儿新任命', value: '新任命' },
  200. { label: '门墩儿招聘', value: '招聘' },
  201. { label: '杂项', value: '杂项' }
  202. ]
  203. const taskStatus = ['待解析', '成功']
  204. const itemData = ref({})
  205. const SearchRef = ref(null)
  206. /** 查询列表 */
  207. const getList = async () => {
  208. loading.value = true
  209. try {
  210. list.value = []
  211. const data = await talentGatherApi.getTaskList(queryParams)
  212. list.value = data.tasks ?? []
  213. total.value = data.pagination.total ?? 0
  214. } finally {
  215. loading.value = false
  216. }
  217. }
  218. /** 搜索按钮操作 */
  219. const handleQuery = () => {
  220. queryParams.page = 1
  221. getList()
  222. }
  223. /** 重置按钮操作 */
  224. const resetQuery = () => {
  225. queryFormRef.value.resetFields()
  226. handleQuery()
  227. }
  228. // 网页解析-信息提取
  229. const webOriginList = ref([])
  230. const handleWebAnalysis = (list) => {
  231. webOriginList.value = list ?? []
  232. }
  233. // 入库
  234. const StorePageRef = ref(null)
  235. const handleStore = async (row) => {
  236. const { task_type, task_name, parse_result } = row
  237. // const { results } = parse_result
  238. // if (!results || !results.length) return message.warning('该任务无解析数据,无法入库')
  239. // StorePageRef.value.open(task_type, results, { summary, processed_time })
  240. // if (!row?.task_name) return message.warning('该任务无解析数据,无法入库')
  241. StorePageRef.value.open(task_type, parse_result)
  242. }
  243. // 任务解析
  244. const handleAnalysis = async ({ task_type, id, task_source, task_name }) => {
  245. if (!id) return
  246. await message.confirm('是否解析当前任务?')
  247. const loading = ElLoading.service({
  248. lock: true,
  249. text: '任务解析中...',
  250. background: 'rgba(0, 0, 0, 0.7)',
  251. })
  252. const params = {
  253. task_type,
  254. id,
  255. data: task_source.minio_paths_json
  256. }
  257. if (task_type === '杂项') params.process_type = 'table'
  258. if (task_type === '新任命') params.publish_time = '2025-07-23'
  259. // 招聘数据未返回,params.data设置为测试数据
  260. if (task_type === '招聘' && !params.data?.length) params.data = [{"id":"1843906600113389570","userId":"598525227328212992","name":"尹世丹","name_zh":"尹世丹"},{"id":"1942125259693166593","userId":"1942124499676893185","name":"陈梅","name_zh":"陈梅"}]
  261. try {
  262. const { results } = await talentGatherApi.taskAnalysis(params)
  263. message.success('解析成功')
  264. getList()
  265. StorePageRef.value.open(task_type, { results })
  266. } finally {
  267. loading.close()
  268. }
  269. }
  270. // 关闭上传弹窗
  271. const handleCancel = () => {
  272. dialog_upload.value = false
  273. analysisLoading.value = false
  274. cardUploadRow.value = []
  275. fileList.value = []
  276. }
  277. // 创建任务
  278. const handleCreateTask = async (query) => {
  279. try {
  280. await talentGatherApi.createTask(query)
  281. message.success('任务创建成功')
  282. getList()
  283. } finally {
  284. analysisLoading.value = false
  285. webOriginList.value = []
  286. handleCancel()
  287. }
  288. }
  289. // 新任命
  290. const handleCreateWebTask = async () => {
  291. if (!webOriginList.value || !webOriginList.value?.length) return message.warning('请输入新任命链接查看后再进行提交')
  292. analysisLoading.value = true
  293. const formData = new FormData()
  294. formData.append('task_type', '新任命')
  295. webOriginList.value.forEach(e => {
  296. formData.append('files', e.file)
  297. formData.append('publish_time', e.publish_time)
  298. })
  299. handleCreateTask(formData)
  300. }
  301. // 任务创建
  302. const analysisLoading = ref(false)
  303. const handleSubmit = async () => {
  304. const type = radioValue.value
  305. // 新任命
  306. if (type === 'web') return handleCreateWebTask()
  307. // 杂项、名片、简历
  308. if (['card', 'file', 'picture'].includes(type)) {
  309. const currentList = type === 'file' ? fileList.value : cardUploadRow.value
  310. if (!currentList || !currentList?.length) {
  311. message.warning(`请上传${type === 'file' ? '简历' : type === 'card' ? '名片' : '杂项'}`)
  312. return
  313. }
  314. analysisLoading.value = true
  315. const formData = new FormData()
  316. formData.append('task_type', type === 'file' ? '简历' : type === 'card' ? '名片' : '杂项')
  317. currentList.forEach(e => {
  318. formData.append('files', e.raw)
  319. })
  320. handleCreateTask(formData)
  321. }
  322. // 门墩儿招聘
  323. if (type === 'menduner') {
  324. const formData = new FormData()
  325. const list = SearchRef.value?.addList?.length ? SearchRef.value.addList : null
  326. if (!list) return message.warning('请先选择人员 !')
  327. formData.append('task_type', '招聘')
  328. formData.append('data', JSON.stringify(list))
  329. handleCreateTask(formData)
  330. }
  331. }
  332. // 简历解析
  333. const uploadRef = ref()
  334. const fileList = ref([])
  335. const fileData = ref({ path: '' })
  336. // 文件上传
  337. const handleChange = async (file) => {
  338. fileData.value.path = file.name
  339. unref(uploadRef)?.submit()
  340. }
  341. const submitFormError = () => {
  342. message.error('上传失败,请您重新上传!')
  343. }
  344. const handleExceed = () => {
  345. message.error('最多只能上传一个文件!')
  346. }
  347. // 名片解析
  348. const cardUploadRow = ref(null)
  349. const cardImgUrl = ref(null)
  350. const cardUploadChange = (file, type) => {
  351. cardUploadRow.value = type ? [{ raw: file }] : file
  352. }
  353. // 选择解析方式
  354. const DialogWidth = ref('500')
  355. const handleSelect = () => {
  356. openSelect.value = false
  357. itemData.value = {}
  358. DialogWidth.value = '500'
  359. const type = radioValue.value
  360. if (['card', 'web'].includes(type)) {
  361. DialogWidth.value = '800'
  362. }
  363. if (type === 'menduner') {
  364. DialogWidth.value = '1200'
  365. }
  366. dialog_upload.value = true
  367. }
  368. const openSelect = ref(false)
  369. const radioObject = { card: '名片', file: '简历', web: '门墩儿新任命', menduner: '门墩儿招聘', picture: '杂项' }
  370. const radioList = ref(Object.keys(radioObject).map(key => ({ value: key, label: radioObject[key]}) ))
  371. const defaultValue = radioList.value[0].value // 默认选中
  372. const radioValue = ref(defaultValue)
  373. // 新增解析
  374. const handleAdd = () => {
  375. webOriginList.value = []
  376. cardUploadRow.value = null
  377. cardImgUrl.value = null
  378. analysisLoading.value = false
  379. radioValue.value = defaultValue // 重置解析类型
  380. openSelect.value = true
  381. }
  382. /** 初始化 **/
  383. onMounted(() => {
  384. getList()
  385. })
  386. </script>
  387. <style lang="scss" scoped>
  388. .radioBox {
  389. margin: 40px 0;
  390. }
  391. :deep {
  392. .el-tag__content {
  393. display: flex;
  394. align-items: center;
  395. }
  396. }
  397. </style>