123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572 |
- <!-- -->
- <template>
- <div>
- <ContentWrap>
- <!-- 搜索工作栏 -->
- <el-form
- class="-mb-15px"
- :model="queryParams"
- ref="queryFormRef"
- :inline="true"
- >
- <div>
- <el-form-item label="企业" prop="enterpriseId">
- <el-select
- v-model="queryParams.enterpriseId"
- filterable
- placeholder="请选择企业"
- clearable
- class="!w-240px"
- @change="enterpriseIdSelectChange"
- >
- <el-option
- v-for="dict in enterpriseOption"
- :key="dict.value"
- :label="dict.name"
- :value="dict.id"
- />
- </el-select>
- </el-form-item>
- <el-form-item label="部门" prop="deptId">
- <el-select
- v-model="queryParams.deptId"
- placeholder="请选择部门"
- clearable
- class="!w-240px"
- >
- <el-option
- v-for="dict in deptOption"
- :key="dict.value"
- :label="dict.label"
- :value="dict.value"
- />
- </el-select>
- </el-form-item>
- <el-form-item label="用户" prop="userId">
- <el-select
- v-model="queryParams.userId"
- placeholder="请选择用户"
- clearable
- class="!w-240px"
- @change="userIdSelectChange"
- >
- <el-option
- v-for="dict in userOption"
- :key="dict.value"
- :label="dict.name"
- :value="dict.userId"
- />
- </el-select>
- </el-form-item>
- <el-form-item label="职位" prop="jobId">
- <el-select
- v-model="queryParams.jobId"
- placeholder="请选择职位"
- :no-data-text="!jobOption.length && queryParams.enterpriseId ? '当前选中企业暂无发布职位' : '请先选择企业'"
- clearable
- class="!w-240px"
- >
- <el-option
- v-for="dict in jobOption"
- :key="dict.value"
- :label="dict.name"
- :value="dict.id"
- />
- </el-select>
- </el-form-item>
- </div>
- <el-form-item label="" prop="type">
- <el-radio-group v-model="queryParams.type" @change="typeChange">
- <el-radio-button label="全部" value="-1" />
- <el-radio-button label="24小时内" value="3" />
- <el-radio-button label="7天内" value="0" />
- <!-- <el-radio-button label="上个月" value="1" /> -->
- <el-radio-button label="30天内" value="4" />
- <el-radio-button label="3个月内" value="6" />
- <!-- <el-radio-button label="上个季度" value="2" /> -->
- <el-radio-button label="1年内" value="5" />
- </el-radio-group>
- </el-form-item>
- <el-form-item label="" prop="time">
- <span class="mr-3">自定义日期</span>
- <el-date-picker
- v-model="queryParams.time"
- value-format="YYYY-MM-DD HH:mm:ss"
- type="daterange"
- start-placeholder="开始日期"
- end-placeholder="结束日期"
- :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
- class="!w-240px"
- @change="timeRangeChange"
- />
- </el-form-item>
- <el-form-item>
- <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
- <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
- </el-form-item>
- </el-form>
- </ContentWrap>
- <div class="flex flex-col">
- <!-- 统计 -->
- <div>
- <CardTitle title="招聘进展" class="m-l-5px"/>
- <div style="text-align: end;">
- <el-button type="success" plain @click="handleExport" :loading="exportLoading">
- <Icon icon="ep:download" class="mr-5px" /> 明细导出
- </el-button>
- </div>
- <el-row :gutter="16" class="row m-t-10px">
- <el-col v-for="item in statisticList" :key="item.name" :md="4" :sm="12" :xs="24" :loading="loading">
- <ComparisonCard
- :title="item.title"
- :value="statistic[item.name]"
- style="cursor: pointer;"
- @click="openDialog(item)"
- />
- </el-col>
- </el-row>
- </div>
- <!-- 职位发布情况 -->
- <JobStatistics :queryParams="queryParams" class="m-b-10px" />
- <!-- 图表 -->
- <div>
- <CardTitle title="应聘简历分析" class="m-l-5px" />
- <el-row :gutter="16" class="row m-t-20px">
- <el-col :md="12">
- <SexDistribution :data="distribution.sexDistributionData" title="性别分布" />
- </el-col>
- <el-col :md="12">
- <ageDistribution :data="distribution.ageDistributionData" title="年龄分布" />
- </el-col>
- </el-row>
- <el-row :gutter="16" class="row">
- <el-col :md="12">
- <workExperience :data="distribution.workExperienceData" title="工作年限分布" />
- </el-col>
- <el-col :md="12">
- <education :data="distribution.educationData" title="学历分布" />
- </el-col>
- </el-row>
- </div>
- </div>
- <!-- 弹窗 -->
- <Dialog :title="currentItem.title+'详情'" v-model="showDialog" width="1200" @close="closeDialog">
- <ContentWrap>
- <el-table v-loading="dialogLoading" :data="tableData" :stripe="true">
- <el-table-column
- v-for="item in tableHeaders[currentItem.name]"
- :key="item.prop"
- :prop="item.prop"
- :label="item.name"
- :align="item.align || 'center'"
- />
- </el-table>
- <!-- 分页 -->
- <Pagination
- :total="total"
- v-model:page="page.pageNo"
- v-model:limit="page.pageSize"
- @pagination="paginationChange"
- />
- </ContentWrap>
- </Dialog>
- </div>
- </template>
- <script setup>
- import ComparisonCard from './components/ComparisonCard.vue'
- import SexDistribution from './components/SexDistribution.vue'
- import ageDistribution from './components/AgeDistribution.vue'
- import workExperience from './components/WorkExperience.vue'
- import education from './components/Education.vue'
- import JobStatistics from './job.vue'
- import { statisticAnalysisApi } from '@/api/menduner/system/analysis/statisticAnalysis'
- defineOptions({name: 'StatisticAnalysis'})
- import download from '@/utils/download'
- const loading = ref(true) // 加载中
- const dialogLoading = ref(false)
- /** 初始化 **/
- onMounted(async () => {
- loading.value = true
- })
- const message = useMessage() // 消息弹窗
- const exportLoading = ref(false)
- const page = reactive({ pageNo: 1, pageSize: 10 })
- const queryParams = reactive({
- type: '0',
- enterpriseId: undefined,
- deptId: undefined,
- userId: undefined,
- jobId: undefined,
- time: [],
- })
- // 招聘进展明细导出
- const handleExport = async () => {
- try {
- // 导出的二次确认
- await message.exportConfirm()
- // 发起导出
- exportLoading.value = true
- const data = await statisticAnalysisApi.analysisExport({ ...queryParams, jobStatus: '0' })
- download.excel(data, '招聘进展明细')
- } catch {
- } finally {
- exportLoading.value = false
- }
- }
- const queryFormRef = ref() // 搜索的表单
- /** 重置按钮操作 */
- const resetQuery = () => {
- queryFormRef.value.resetFields()
- handleQuery()
- }
- const typeChange = (value) => { //
- if (value) {
- queryParams.time = []
- }
- handleQuery()
- }
- const timeRangeChange = (value) => {
- if (value?.length) queryParams.type = '99' // 自定义
- else queryParams.type = '0'
- }
- const apiArr = reactive({
- // 统计
- pageViewsTotal: statisticAnalysisApi.getAnalysisJobBrowseNum, // 职位浏览量-总数据
- pageViews: statisticAnalysisApi.getAnalysisJobBrowseNumPage, // 职位浏览量-钻取
- resumeReceived: statisticAnalysisApi.getAnalysisJobCvNewPage, // 钻取
- pushNum: statisticAnalysisApi.getAnalysisJobNumPage, // 钻取
- resumeViewed: statisticAnalysisApi.getAnalysisJobCvLookPage, // 钻取
- invitedInterviews: statisticAnalysisApi.getAnalysisInterviewWaitPage, // 钻取
- invitedCompleted: statisticAnalysisApi.getAnalysisInterviewCompletePage, // 钻取
- // 分布
- sexDistributionData: statisticAnalysisApi.getAnalysisJobCvSexCount,
- ageDistributionData: statisticAnalysisApi.getAnalysisJobCvAgeCount,
- workExperienceData: statisticAnalysisApi.getAnalysisJobCvExpCount,
- educationData: statisticAnalysisApi.getAnalysisJobCvEduCount,
- })
- // 统计
- const statisticList = [
- { title: '发布职位数量', name: 'pushNum' },
- { title: '职位浏览量', name: 'pageViews' },
- { title: '收到的简历', name: 'resumeReceived' },
- { title: '已查看简历', name: 'resumeViewed' },
- { title: '已邀面试', name: 'invitedInterviews' },
- { title: '面试完成', name: 'invitedCompleted' },
- ]
- // 统计
- const statistic = reactive({
- pageViews: 0,
- pushNum: 0,
- resumeReceived: 0,
- resumeViewed: 0,
- invitedInterviews: 0,
- invitedCompleted: 0,
- })
- // 分布
- const distribution = reactive({
- sexDistributionData: {},
- ageDistributionData: {},
- workExperienceData: {},
- educationData: {},
- })
- // 统计
- const tableData = ref([])
- const total = ref(0)
- const getList = async (typeName, details = '') => {
- loading.value = true
- try {
- let data
- if (!details && typeName === 'pageViews') {
- // 职位浏览量-总数据
- const res = await apiArr.pageViewsTotal(queryParams)
- data = { total: res || 0 }
- } else {
- // 使用钻取接口
- const params = { ...queryParams, ...page }
- if (typeName === 'pushNum') params.jobStatus = 0 // 发布职位数量单独加上jobStatus字段
- data = await apiArr[typeName](params)
- }
- if (details) {
- tableData.value = data.list || []
- total.value = data.total || 0
- dealTableData()
- } else {
- statistic[typeName] = data.total || 0
- }
- } finally {
- loading.value = false
- dialogLoading.value = false
- }
- }
- import { getDictOptions } from '@/utils/transform/getText'
- import { timesTampChange } from '@/utils/transform/date'
- import { DICT_TYPE, getDictLabel } from '@/utils/dict'
- const getText = (value, arr, itemText = 'name', itemValue = 'id') => { // 一维数组
- if (!arr?.length || !(value && value !== 0)) return
- const item = arr.find(formItem => formItem[itemValue] === value)
- if (!item) return
- return item[itemText]
- }
- // 分布
- const getDistributionCount = async (typeName) => {
- try {
- const data = await apiArr[typeName](queryParams)
- distribution[typeName] = data || {}
- } catch (error) {
- console.log(error)
- }
- }
- /** 搜索按钮操作 */
- const handleQuery = () => {
- if (Object.keys(statistic).length) {
- Object.keys(statistic).forEach(name => {
- getList(name)
- })
- }
- //
- if (Object.keys(distribution).length) {
- Object.keys(distribution).forEach(name => {
- getDistributionCount(name)
- })
- }
- }
- handleQuery()
- const showDialog = ref(false)
- const currentItem = ref({})
- // 打开弹窗
- const openDialog = (item) => {
- dialogLoading.value = true
- currentItem.value = item
- page.pageNo = 1
- tableData.value = []
- getList(item.name, '钻取')
- showDialog.value = true
- }
- const closeDialog = () => {
- }
- const paginationChange = () => {
- getList(currentItem.value.name, '钻取')
- }
- const tableHeaders = {
- // 职位浏览量
- pageViews: [
- { name: '浏览量', prop: 'num' },
- { name: '招聘职位', prop: 'name' },
- { name: '发布企业', prop: 'enterpriseName' },
- { name: '薪酬', prop: 'salaryDisplay' },
- { name: '工作地区', prop: 'areaName' },
- { name: '工作经验', prop: 'expName' },
- { name: '学历要求', prop: 'eduName' },
- ],
- // 发布职位数量
- pushNum: [
- { name: '职位名称', prop: 'name' },
- { name: '发布企业', prop: 'enterpriseName' },
- { name: '职位类型', prop: 'positionName' },
- { name: '薪酬', prop: 'salaryDisplay' },
- { name: '工作地区', prop: 'areaName' },
- { name: '工作经验', prop: 'expName' },
- { name: '学历要求', prop: 'eduName' },
- { name: '众聘', prop: 'hire' },
- ],
- // 收到的简历
- resumeReceived: [
- { name: '投递人', prop: 'personName' },
- { name: '求职状态', prop: 'jobStatus' },
- { name: '投递职位', prop: 'job.name' },
- { name: '发布企业', prop: 'enterpriseName' },
- { name: '薪酬', prop: 'salaryDisplay' },
- { name: '工作地区', prop: 'areaName' },
- { name: '工作经验', prop: 'expName' },
- { name: '学历要求', prop: 'eduName' },
- ],
- // 已查看简历
- resumeViewed: [
- { name: '简历标题', prop: 'title' },
- { name: '投递人', prop: 'personName' },
- { name: '投递职位', prop: 'job.name' },
- { name: '发布企业', prop: 'enterpriseName' },
- { name: '投递类型', prop: 'typeName' },
- { name: '推荐人', prop: 'recommendPersonName' },
- ],
- // 已邀面试
- invitedInterviews: [
- { name: '求职者', prop: 'personName' },
- { name: '面试岗位', prop: 'jobName' },
- { name: '面试类型', prop: 'typeName' },
- { name: '面试时间', prop: 'timeName' },
- { name: '面试地点', prop: 'addressName' },
- ],
- // 面试完成
- invitedCompleted: [
- { name: '求职者', prop: 'personName' },
- { name: '面试岗位', prop: 'jobName' },
- { name: '发布企业', prop: 'enterpriseName' },
- { name: '面试类型', prop: 'typeName' },
- { name: '面试时间', prop: 'timeName' },
- { name: '面试地点', prop: 'addressName' },
- { name: '反馈评价', prop: 'evaluate' },
- ],
- }
- const dealTableData = async () => {
- // 职位浏览量
- if (currentItem.value.name === 'pageViews') {
- const areaList = await getDictOptions('areaList')
- tableData.value = tableData.value.map(item => {
- item.salaryDisplay = item.payFrom && item.payTo ? `${item.payFrom}-${item.payTo}/${getDictLabel(DICT_TYPE.MENDUNER_PAY_UNIT, item.payUnit)}` : '面议'
- item.areaName = !item.areaId ? '全国' : getText(item.areaId, areaList)
- item.expName = getDictLabel(DICT_TYPE.MENDUNER_EXP_TYPE, item.expType)
- item.eduName = getDictLabel(DICT_TYPE.MENDUNER_EDUCATION_TYPE, item.eduType)
- return item
- })
- }
- // 发布职位数量
- if (currentItem.value.name === 'pushNum') {
- const areaList = await getDictOptions('areaList')
- const positionData = await getDictOptions('positionData')
- tableData.value = tableData.value.map(item => {
- item.areaName = !item.areaId ? '全国' : getText(item.areaId, areaList)
- item.positionName = getText(item.positionId, positionData, 'nameCn', 'id')
- item.salaryDisplay = item.payFrom && item.payTo ? `${item.payFrom}-${item.payTo}/${getDictLabel(DICT_TYPE.MENDUNER_PAY_UNIT, item.payUnit)}` : '面议'
- item.jobStatus = getDictLabel(DICT_TYPE.MENDUNER_JOB_SEEK_STATUS, item.jobStatus)
- item.expName = getDictLabel(DICT_TYPE.MENDUNER_EXP_TYPE, item.expType)
- item.eduName = getDictLabel(DICT_TYPE.MENDUNER_EDUCATION_TYPE, item.eduType)
- item.hire = item.hire ? '是' : '否'
- return item
- })
- }
- // 收到的简历
- if (currentItem.value.name === 'resumeReceived') {
- const areaList = await getDictOptions('areaList')
- tableData.value = tableData.value.map(item => {
- item.areaName = !item.job.areaId ? '全国' : getText(item.job.areaId, areaList)
- item.salaryDisplay = item.job?.payFrom && item.job.payTo ? `${item.job.payFrom}-${item.job.payTo}/${getDictLabel(DICT_TYPE.MENDUNER_PAY_UNIT, item.job.payUnit)}` : '面议'
- item.jobStatus = getDictLabel(DICT_TYPE.MENDUNER_JOB_SEEK_STATUS, item.person.jobStatus)
- item.expName = getDictLabel(DICT_TYPE.MENDUNER_EXP_TYPE, item.job.expType)
- item.eduName = getDictLabel(DICT_TYPE.MENDUNER_EDUCATION_TYPE, item.job.eduType)
- item.personName = item.person.name
- return item
- })
- }
- // 已查看简历
- if (currentItem.value.name === 'resumeViewed') {
- tableData.value = tableData.value.map(item => {
- item.personName = item.person.name
- item.address = item.job.address
- item.typeName = Number(item.type) === 0 ? '平台投递': '赏金投递'
- item.recommendPersonName = item.recommendPerson?.name || ''
- return item
- })
- }
- // 已邀面试
- if (currentItem.value.name === 'invitedInterviews') {
- tableData.value = tableData.value.map(item => {
- item.personName = item.person.name
- item.jobName = item.job.name
- item.typeName = item.type === 0 ? '线上面试': '线下面试'
- item.timeName = timesTampChange(item.time, 'Y-M-D h:m')
- item.addressName = item.job.address
- return item
- })
- }
- // 面试完成
- if (currentItem.value.name === 'invitedCompleted') {
- tableData.value = tableData.value.map(item => {
- item.personName = item.person.name
- item.jobName = item.job.name
- item.typeName = item.type === 0 ? '线上面试': '线下面试'
- item.timeName = timesTampChange(item.time, 'Y-M-D h:m')
- item.addressName = item.job.address
- return item
- })
- }
- }
- // 企业
- const enterpriseOption = ref([])
- const getEnterpriseOption = async () => {
- try {
- const data = await statisticAnalysisApi.getAnalysisEnterpriseSimpleList()
- enterpriseOption.value = data || []
- } catch (error) {
- console.log(error)
- }
- }
- getEnterpriseOption()
- // 部门
- const deptOption = ref([])
- // 用户
- const userOption = ref([])
- const getUserOption = async () => {
- try {
- const params ={ enterpriseId: queryParams.enterpriseId }
- const data = await statisticAnalysisApi.getAnalysisEnterpriseUserList(params)
- userOption.value = data || []
- } catch (error) {
- console.log(error)
- }
- }
- getUserOption()
- // 职位
- const jobOption = ref([])
- const getJobOption = async () => {
- try {
- const params ={
- enterpriseId: queryParams.enterpriseId,
- deptId: queryParams.deptId,
- status: 0,
- }
- const data = await statisticAnalysisApi.getAnalysisJobAdvertisedList(params)
- jobOption.value = data || []
- } catch (error) {
- console.log(error)
- }
- }
- // getJobOption()
- const enterpriseIdSelectChange = (val) => {
- queryParams.deptId = undefined
- queryParams.jobId = undefined
- getUserOption()
- getJobOption()
- }
- const userIdSelectChange = (val) => {
- queryParams.jobId = undefined
- getJobOption()
- }
- </script>
- <style lang="scss" scoped>
- .row {
- .el-col {
- margin-bottom: 1rem;
- }
- }
- </style>
|