index.vue 13 KB

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