index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. <template>
  2. <div>
  3. <!-- 搜索工作栏 -->
  4. <ContentWrap>
  5. <el-form
  6. class="-mb-15px"
  7. :model="queryParams"
  8. ref="queryFormRef"
  9. :inline="true"
  10. >
  11. <div>
  12. <el-form-item label="企业" prop="enterpriseId">
  13. <el-select
  14. v-model="queryParams.enterpriseId"
  15. filterable
  16. placeholder="请选择企业"
  17. clearable
  18. class="!w-240px"
  19. @change="enterpriseIdSelectChange"
  20. >
  21. <el-option
  22. v-for="dict in enterpriseOption"
  23. :key="dict.value"
  24. :label="dict.name"
  25. :value="dict.id"
  26. />
  27. </el-select>
  28. </el-form-item>
  29. <el-form-item label="职位" prop="jobId">
  30. <el-select
  31. v-model="queryParams.jobId"
  32. placeholder="请选择职位"
  33. :no-data-text="!jobOption.length && queryParams.enterpriseId ? '当前选中企业暂无发布职位' : '请先选择企业'"
  34. clearable
  35. class="!w-240px"
  36. >
  37. <el-option
  38. v-for="dict in jobOption"
  39. :key="dict.value"
  40. :label="dict.name"
  41. :value="dict.id"
  42. />
  43. </el-select>
  44. </el-form-item>
  45. </div>
  46. <el-form-item label="" prop="type">
  47. <el-radio-group v-model="queryParams.type" @change="typeChange">
  48. <el-radio-button label="全部" value="-1" />
  49. <el-radio-button label="24小时内" value="3" />
  50. <el-radio-button label="7天内" value="0" />
  51. <el-radio-button label="30天内" value="4" />
  52. <el-radio-button label="3个月内" value="6" />
  53. <el-radio-button label="1年内" value="5" />
  54. </el-radio-group>
  55. </el-form-item>
  56. <el-form-item label="" prop="time">
  57. <span class="mr-3">自定义日期</span>
  58. <el-date-picker
  59. v-model="queryParams.time"
  60. value-format="YYYY-MM-DD HH:mm:ss"
  61. type="daterange"
  62. start-placeholder="开始日期"
  63. end-placeholder="结束日期"
  64. :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
  65. class="!w-240px"
  66. @change="timeRangeChange"
  67. />
  68. </el-form-item>
  69. <el-form-item>
  70. <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
  71. <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
  72. <el-button type="primary" :loading="exportJobLoading" plain @click="handleExportJob"><Icon icon="ep:download" class="mr-5px" /> 职位列表导出</el-button>
  73. <el-button type="primary" :loading="exportLoading" plain @click="handleExport"><Icon icon="ep:download" class="mr-5px" /> 用户投递情况导出</el-button>
  74. </el-form-item>
  75. </el-form>
  76. </ContentWrap>
  77. <el-row :gutter="16" class="row m-y-20px">
  78. <el-col v-for="item in statisticList" :key="item.name" :md="4" :sm="12" :xs="24" :loading="loading">
  79. <ComparisonCard
  80. :title="item.title"
  81. :value="statistic[item.name]"
  82. style="cursor: pointer;"
  83. @click="openDialog(item)"
  84. />
  85. </el-col>
  86. </el-row>
  87. <Delivery :data="jobCvRelBar" title="企业职位简历投递量统计" />
  88. <!-- <UserJobDelivery class="mt-10px" :query="queryParams" title="用户简历投递量明细" /> -->
  89. <!-- <EnterpriseJob class="my-10px" :query="queryParams" title="企业发布职位明细" /> -->
  90. <!-- 弹窗 -->
  91. <Dialog :title="currentItem.title+'详情'" v-model="showDialog" width="1200" @close="showDialog = false">
  92. <ContentWrap>
  93. <el-table v-loading="dialogLoading" :data="tableData" :stripe="true">
  94. <el-table-column
  95. v-for="item in tableHeaders[currentItem.name]"
  96. :key="item.prop"
  97. :prop="item.prop"
  98. :label="item.name"
  99. :align="item.align || 'center'"
  100. />
  101. </el-table>
  102. <!-- 分页 -->
  103. <Pagination
  104. :total="total"
  105. v-model:page="page.pageNo"
  106. v-model:limit="page.pageSize"
  107. @pagination="paginationChange"
  108. />
  109. </ContentWrap>
  110. </Dialog>
  111. </div>
  112. </template>
  113. <script setup>
  114. /** 招聘会 表单 */
  115. defineOptions({ name: 'JobFairStatistics' })
  116. import { JobFairManageApi } from '@/api/menduner/system/jobFair/manage'
  117. import { statisticAnalysisApi } from '@/api/menduner/system/analysis/statisticAnalysis'
  118. import Delivery from './components/delivery.vue'
  119. import UserJobDelivery from './components/userJobDelivery.vue'
  120. import EnterpriseJob from './components/enterpriseJob.vue'
  121. import ComparisonCard from './components/ComparisonCard.vue'
  122. import { formatName } from '@/utils'
  123. import { timesTampChange } from '@/utils/transform/date'
  124. import { DICT_TYPE, getDictLabel } from '@/utils/dict'
  125. import { JobFairWhiteApi } from '@/api/menduner/system/jobFair/white'
  126. import download from '@/utils/download'
  127. const route = useRoute() // 路由信息
  128. const { id } = route.params
  129. const loading = ref(false)
  130. const dialogLoading = ref(false)
  131. const currentItem = ref({})
  132. const tableData = ref([])
  133. const showDialog = ref(false)
  134. const total = ref(0)
  135. const message = useMessage() // 消息弹窗
  136. const page = reactive({ pageNo: 1, pageSize: 10 })
  137. const queryFormRef = ref()
  138. const queryParams = reactive({
  139. type: '0',
  140. enterpriseId: undefined,
  141. deptId: undefined,
  142. userId: undefined,
  143. jobId: undefined,
  144. jobFairId: id,
  145. time: [],
  146. })
  147. // 统计
  148. const statisticList = [
  149. { title: '企业邀请面试数', name: 'inviteInterviewNum' },
  150. { title: '企业职位数', name: 'jobNum' },
  151. { title: '企业职位投递量', name: 'deliveryNum' },
  152. { title: '用户面试数', name: 'interviewNum' },
  153. { title: '用户简历投递数', name: 'cvDeliveryNum' }
  154. ]
  155. // 统计
  156. const statistic = reactive({
  157. inviteInterviewNum: 0,
  158. jobNum: 0,
  159. deliveryNum: 0,
  160. interviewNum: 0,
  161. cvDeliveryNum: 0
  162. })
  163. const tableHeaders = {
  164. // 企业邀请面试数
  165. inviteInterviewNum: [
  166. { name: '求职者', prop: 'personName' },
  167. { name: '邀请企业', prop: 'enterpriseName' },
  168. { name: '面试岗位', prop: 'jobName' },
  169. { name: '面试类型', prop: 'typeName' },
  170. { name: '面试时间', prop: 'timeName' },
  171. { name: '面试地点', prop: 'addressName' },
  172. ],
  173. // 企业职位情况
  174. jobNum: [
  175. { name: '职位名称', prop: 'name' },
  176. { name: '发布企业', prop: 'enterpriseName' },
  177. { name: '薪酬', prop: 'salaryDisplay' },
  178. { name: '工作地区', prop: 'areaName' },
  179. { name: '工作经验', prop: 'expName' },
  180. { name: '学历要求', prop: 'eduName' },
  181. { name: '发布时间', prop: 'createTime' }
  182. ],
  183. // 企业职位投递量
  184. deliveryNum: [
  185. { name: '投递人', prop: 'personName' },
  186. { name: '职位名称', prop: 'name' },
  187. { name: '发布企业', prop: 'enterpriseName' },
  188. { name: '投递简历名称', prop: 'title' },
  189. { name: '投递时间', prop: 'createTime' },
  190. ],
  191. // 用户面试数
  192. interviewNum: [
  193. { name: '求职者', prop: 'personName' },
  194. { name: '邀请企业', prop: 'enterpriseName' },
  195. { name: '面试岗位', prop: 'jobName' },
  196. { name: '面试类型', prop: 'typeName' },
  197. { name: '面试时间', prop: 'timeName' },
  198. { name: '面试地点', prop: 'addressName' },
  199. ],
  200. // 用户投递数
  201. cvDeliveryNum: [
  202. { name: '投递人', prop: 'personName' },
  203. { name: '投递职位', prop: 'name' },
  204. { name: '发布企业', prop: 'enterpriseName' },
  205. { name: '投递简历名称', prop: 'title' },
  206. { name: '投递时间', prop: 'createTime' },
  207. ]
  208. }
  209. const apiArr = reactive({
  210. inviteInterviewNum: JobFairManageApi.getEnterpriseInterviewInvite,
  211. jobNum: JobFairManageApi.getEnterpriseJob,
  212. deliveryNum: JobFairManageApi.getEnterpriseJobCvRel,
  213. interviewNum: JobFairManageApi.getUserInterviewInvite,
  214. cvDeliveryNum: JobFairManageApi.getUserJobCvRel,
  215. })
  216. const dealTableData = () => {
  217. // 企业邀请面试/用户面试
  218. if (['interviewNum', 'inviteInterviewNum'].includes(currentItem.value.name)) {
  219. tableData.value = tableData.value.map(item => {
  220. item.personName = item.person?.name || item.person?.phone
  221. item.jobName = formatName(item.job.name)
  222. item.enterpriseName = formatName(item.enterpriseName)
  223. item.typeName = item.type === 0 ? '线上面试': '线下面试'
  224. item.timeName = timesTampChange(item.time, 'Y-M-D h:m')
  225. item.addressName = item.job.address
  226. return item
  227. })
  228. }
  229. // 企业职位
  230. if (currentItem.value.name === 'jobNum') {
  231. tableData.value = tableData.value.map(item => {
  232. item.name = formatName(item.name)
  233. item.enterpriseName = formatName(item.enterprise.anotherName || item.enterprise.name)
  234. item.salaryDisplay = item.payFrom && item.payTo ? `${item.payFrom}-${item.payTo}/${getDictLabel(DICT_TYPE.MENDUNER_PAY_UNIT, item.payUnit)}` : '面议'
  235. item.areaName = !item.areaId ? '全国' : item.area.str
  236. item.expName = getDictLabel(DICT_TYPE.MENDUNER_EXP_TYPE, item.expType) || '不限'
  237. item.eduName = getDictLabel(DICT_TYPE.MENDUNER_EDUCATION_TYPE, item.eduType) || '不限'
  238. item.createTime = timesTampChange(item.createTime)
  239. return item
  240. })
  241. }
  242. // 企业职位投递量、用户投递量
  243. if (['cvDeliveryNum', 'deliveryNum'].includes(currentItem.value.name)) {
  244. tableData.value = tableData.value.map(item => {
  245. item.personName = item.person?.name || item.person?.phone
  246. item.name = formatName(item.job.name)
  247. item.enterpriseName = formatName(item.enterpriseName)
  248. item.createTime = timesTampChange(item.createTime)
  249. return item
  250. })
  251. }
  252. }
  253. const exportLoading = ref(false)
  254. /** 导出按钮操作 */
  255. const handleExport = async () => {
  256. try {
  257. // 导出的二次确认
  258. await message.exportConfirm()
  259. // 发起导出
  260. exportLoading.value = true
  261. const data = await JobFairManageApi.exportDelivery({ ...queryParams, pageNo: 1 })
  262. download.excel(data, '用户投递列表.xls')
  263. } catch {
  264. exportLoading.value = false
  265. } finally {
  266. exportLoading.value = false
  267. }
  268. }
  269. const exportJobLoading = ref(false)
  270. /** 导出按钮操作 */
  271. const handleExportJob = async () => {
  272. try {
  273. // 导出的二次确认
  274. await message.exportConfirm()
  275. // 发起导出
  276. exportLoading.value = true
  277. const data = await JobFairWhiteApi.exportJobFairWhiteList(id)
  278. download.excel(data, '招聘会职位列表.xls')
  279. } catch {
  280. } finally {
  281. exportLoading.value = false
  282. }
  283. }
  284. const getList = async (typeName, details = '') => {
  285. loading.value = true
  286. try {
  287. let data
  288. // 使用钻取接口
  289. const params = { ...queryParams, ...page }
  290. data = await apiArr[typeName](params)
  291. if (details) {
  292. tableData.value = data.list || []
  293. total.value = data.total || 0
  294. dealTableData()
  295. } else {
  296. statistic[typeName] = data.total || 0
  297. }
  298. } finally {
  299. loading.value = false
  300. dialogLoading.value = false
  301. }
  302. }
  303. // 打开弹窗
  304. const openDialog = (item) => {
  305. dialogLoading.value = true
  306. currentItem.value = item
  307. page.pageNo = 1
  308. tableData.value = []
  309. getList(item.name, '钻取')
  310. showDialog.value = true
  311. }
  312. const paginationChange = () => {
  313. getList(currentItem.value.name, '钻取')
  314. }
  315. // 企业职位简历投递量统计
  316. const jobCvRelBar = ref({})
  317. const getJobCvRelBar = async () => {
  318. const data = await JobFairManageApi.getEnterpriseJobCvRelBar(queryParams)
  319. jobCvRelBar.value = data || { x: [], y: []}
  320. }
  321. const handleQuery = () => {
  322. if (Object.keys(statistic).length) {
  323. Object.keys(statistic).forEach(name => {
  324. getList(name)
  325. })
  326. }
  327. getJobCvRelBar()
  328. }
  329. handleQuery()
  330. /** 重置按钮操作 */
  331. const resetQuery = () => {
  332. queryFormRef.value.resetFields()
  333. handleQuery()
  334. }
  335. const typeChange = (value) => { //
  336. if (value) {
  337. queryParams.time = []
  338. }
  339. handleQuery()
  340. }
  341. const timeRangeChange = (value) => {
  342. if (value?.length) queryParams.type = '99' // 自定义
  343. else queryParams.type = '0'
  344. }
  345. // 企业
  346. const enterpriseOption = ref([])
  347. const getEnterpriseOption = async () => {
  348. try {
  349. const data = await statisticAnalysisApi.getAnalysisEnterpriseSimpleList({})
  350. enterpriseOption.value = data || []
  351. } catch (error) {
  352. console.log(error)
  353. }
  354. }
  355. getEnterpriseOption()
  356. // 职位
  357. const jobOption = ref([])
  358. const getJobOption = async () => {
  359. try {
  360. const params ={
  361. enterpriseId: queryParams.enterpriseId,
  362. deptId: queryParams.deptId,
  363. status: 0,
  364. jobFairId: id
  365. }
  366. const data = await statisticAnalysisApi.getAnalysisJobAdvertisedList(params)
  367. jobOption.value = data || []
  368. } catch (error) {
  369. console.log(error)
  370. }
  371. }
  372. const enterpriseIdSelectChange = () => {
  373. queryParams.deptId = undefined
  374. queryParams.jobId = undefined
  375. getJobOption()
  376. }
  377. </script>