|
@@ -107,39 +107,53 @@
|
|
|
<div class="flex flex-col">
|
|
|
<!-- 数据对照 -->
|
|
|
<el-row :gutter="16" class="row">
|
|
|
- <el-col :md="4" :sm="12" :xs="24" :loading="loading">
|
|
|
- <ComparisonCard title="职位浏览量" :value="pageViews.total" />
|
|
|
- </el-col>
|
|
|
- <el-col :md="4" :sm="12" :xs="24" :loading="loading">
|
|
|
- <ComparisonCard title="收到的简历" :value="resumeReceived.total" />
|
|
|
- </el-col>
|
|
|
- <el-col :md="4" :sm="12" :xs="24" :loading="loading">
|
|
|
- <ComparisonCard title="已查看简历" :value="resumeViewed.total" />
|
|
|
- </el-col>
|
|
|
- <el-col :md="4" :sm="12" :xs="24" :loading="loading">
|
|
|
- <ComparisonCard title="已邀面试" :value="invitedInterviews.total" />
|
|
|
- </el-col>
|
|
|
- <el-col :md="4" :sm="12" :xs="24" :loading="loading">
|
|
|
- <ComparisonCard title="面试完成" :value="invitedCompleted.total" />
|
|
|
+ <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>
|
|
|
<el-row :gutter="16" class="row">
|
|
|
<el-col :md="12">
|
|
|
- <SexDistribution :data="sexDistributionData" title="性别分布" />
|
|
|
+ <SexDistribution :data="distribution.sexDistributionData" title="性别分布" />
|
|
|
</el-col>
|
|
|
<el-col :md="12">
|
|
|
- <ageDistribution :data="ageDistributionData" title="年龄分布" />
|
|
|
+ <ageDistribution :data="distribution.ageDistributionData" title="年龄分布" />
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
<el-row :gutter="16" class="row">
|
|
|
<el-col :md="12">
|
|
|
- <workExperience :data="workExperienceData" title="工作年限分布" />
|
|
|
+ <workExperience :data="distribution.workExperienceData" title="工作年限分布" />
|
|
|
</el-col>
|
|
|
<el-col :md="12">
|
|
|
- <education :data="educationData" title="学历分布" />
|
|
|
+ <education :data="distribution.educationData" title="学历分布" />
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
</div>
|
|
|
+ <!-- 弹窗 -->
|
|
|
+ <Dialog :title="currentItem.title+'详情'" v-model="showDialog" width="1200" @close="closeDialog">
|
|
|
+ <ContentWrap>
|
|
|
+ <el-table v-loading="dialogLoading" :data="tableData" :stripe="true" :show-overflow-tooltip="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>
|
|
|
|
|
@@ -150,20 +164,17 @@ import ageDistribution from './components/AgeDistribution.vue'
|
|
|
import workExperience from './components/WorkExperience.vue'
|
|
|
import education from './components/Education.vue'
|
|
|
import { statisticAnalysisApi } from '@/api/menduner/system/analysis/statisticAnalysis'
|
|
|
+// import { dealDictArrayData, dealDictObjData } from '@/utils/position'
|
|
|
defineOptions({name: 'StatisticAnalysis'})
|
|
|
|
|
|
const loading = ref(true) // 加载中
|
|
|
|
|
|
/** 初始化 **/
|
|
|
onMounted(async () => {
|
|
|
- // loading.value = true
|
|
|
- loading.value = false
|
|
|
+ loading.value = true
|
|
|
})
|
|
|
|
|
|
-// const pageParams = {
|
|
|
-// pageNo: 1,
|
|
|
-// pageSize: 10,
|
|
|
-// }
|
|
|
+const page = reactive({ pageNo: 1, pageSize: 10 })
|
|
|
const queryParams = reactive({
|
|
|
pageNo: 1,
|
|
|
pageSize: 10,
|
|
@@ -194,132 +205,209 @@ const timeRangeChange = (value) => {
|
|
|
handleQuery()
|
|
|
}
|
|
|
|
|
|
-// 职位浏览量
|
|
|
-const pageViews = reactive({ list: [], total: 0 })
|
|
|
-const getPageViewsList = async () => {
|
|
|
- loading.value = true
|
|
|
- try {
|
|
|
- const data = await statisticAnalysisApi.getAnalysisJobBrowseNumPage(queryParams)
|
|
|
- pageViews.list = data.list
|
|
|
- pageViews.total = data.total
|
|
|
- } finally {
|
|
|
- loading.value = false
|
|
|
- }
|
|
|
-}
|
|
|
+const apiArr = reactive({
|
|
|
+ // 统计
|
|
|
+ pageViews: statisticAnalysisApi.getAnalysisJobBrowseNumPage,
|
|
|
+ resumeReceived: statisticAnalysisApi.getAnalysisJobCvNewPage,
|
|
|
+ resumeViewed: statisticAnalysisApi.getAnalysisJobCvLookPage,
|
|
|
+ invitedInterviews: statisticAnalysisApi.getAnalysisInterviewWaitPage,
|
|
|
+ invitedCompleted: statisticAnalysisApi.getAnalysisInterviewCompletePage,
|
|
|
+ // 分布
|
|
|
+ sexDistributionData: statisticAnalysisApi.getAnalysisJobCvSexCount,
|
|
|
+ ageDistributionData: statisticAnalysisApi.getAnalysisJobCvAgeCount,
|
|
|
+ workExperienceData: statisticAnalysisApi.getAnalysisJobCvExpCount,
|
|
|
+ educationData: statisticAnalysisApi.getAnalysisJobCvEduCount,
|
|
|
+})
|
|
|
|
|
|
-// 收到的简历
|
|
|
-const resumeReceived = reactive({ list: [], total: 0 })
|
|
|
-const getResumeReceivedList = async () => {
|
|
|
- loading.value = true
|
|
|
- try {
|
|
|
- const data = await statisticAnalysisApi.getAnalysisJobCvNewPage(queryParams)
|
|
|
- resumeReceived.list = data.list
|
|
|
- resumeReceived.total = data.total
|
|
|
- } finally {
|
|
|
- loading.value = false
|
|
|
- }
|
|
|
-}
|
|
|
+// 统计
|
|
|
+const statisticList = [
|
|
|
+ { title: '职位浏览量', name: 'pageViews' },
|
|
|
+ { title: '收到的简历', name: 'resumeReceived' },
|
|
|
+ { title: '已查看简历', name: 'resumeViewed' },
|
|
|
+ { title: '已邀面试', name: 'invitedInterviews' },
|
|
|
+ { title: '面试完成', name: 'invitedCompleted' },
|
|
|
+]
|
|
|
+// 统计
|
|
|
+const statistic = reactive({
|
|
|
+ pageViews: 0,
|
|
|
+ resumeReceived: 0,
|
|
|
+ resumeViewed: 0,
|
|
|
+ invitedInterviews: 0,
|
|
|
+ invitedCompleted: 0,
|
|
|
+})
|
|
|
|
|
|
-// 已查看简历
|
|
|
-const resumeViewed = reactive({ list: [], total: 0 })
|
|
|
-const getResumeViewedList = async () => {
|
|
|
- loading.value = true
|
|
|
- try {
|
|
|
- const data = await statisticAnalysisApi.getAnalysisJobCvLookPage(queryParams)
|
|
|
- resumeViewed.list = data.list
|
|
|
- resumeViewed.total = data.total
|
|
|
- } finally {
|
|
|
- loading.value = false
|
|
|
- }
|
|
|
-}
|
|
|
+// 分布
|
|
|
+const distribution = reactive({
|
|
|
+ sexDistributionData: {},
|
|
|
+ ageDistributionData: {},
|
|
|
+ workExperienceData: {},
|
|
|
+ educationData: {},
|
|
|
+})
|
|
|
|
|
|
-// 已邀面试
|
|
|
-const invitedInterviews = reactive({ list: [], total: 0 })
|
|
|
-const getInvitedInterviewsList = async () => {
|
|
|
+// 统计
|
|
|
+const getList = async (typeName, deal = '') => {
|
|
|
loading.value = true
|
|
|
try {
|
|
|
- const data = await statisticAnalysisApi.getAnalysisInterviewWaitPage(queryParams)
|
|
|
- invitedInterviews.list = data.list
|
|
|
- invitedInterviews.total = data.total
|
|
|
+ const data = await apiArr[typeName]({ ...queryParams, ...customTimeObj })
|
|
|
+ tableData.value = data.list || []
|
|
|
+ statistic[typeName] = data.total
|
|
|
+ if (deal) dealTableData()
|
|
|
} finally {
|
|
|
loading.value = false
|
|
|
+ dialogLoading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// 面试完成
|
|
|
-const invitedCompleted = reactive({ list: [], total: 0 })
|
|
|
-const getInvitedCompletedList = async () => {
|
|
|
- loading.value = true
|
|
|
- try {
|
|
|
- const data = await statisticAnalysisApi.getAnalysisInterviewCompletePage(queryParams)
|
|
|
- invitedCompleted.list = data.list
|
|
|
- invitedCompleted.total = data.total
|
|
|
- } finally {
|
|
|
- loading.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 sexDistributionData = ref(null)
|
|
|
-const getAnalysisJobCvSexCount = async () => {
|
|
|
- try {
|
|
|
- const data = await statisticAnalysisApi.getAnalysisJobCvSexCount(queryParams)
|
|
|
- if (data) sexDistributionData.value = data
|
|
|
- console.log('性别分布', data)
|
|
|
- } catch (error) {
|
|
|
- console.log(error)
|
|
|
+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}/${getDictLabel(DICT_TYPE.MENDUNER_PAY_UNIT, item.payUnit)}`
|
|
|
+ item.areaName = 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
|
|
|
+ })
|
|
|
}
|
|
|
-}
|
|
|
-// 年龄分布
|
|
|
-const ageDistributionData = ref(null)
|
|
|
-const getAnalysisJobCvAgeCount = async () => {
|
|
|
- try {
|
|
|
- const data = await statisticAnalysisApi.getAnalysisJobCvAgeCount(queryParams)
|
|
|
- if (data) ageDistributionData.value = data
|
|
|
- } catch (error) {
|
|
|
- console.log(error)
|
|
|
+ if (currentItem.value.name === 'resumeReceived') {
|
|
|
+ const areaList = await getDictOptions('areaList')
|
|
|
+ tableData.value = tableData.value.map(item => {
|
|
|
+ item.areaName = getText(item.job.areaId, areaList)
|
|
|
+ item.salaryDisplay = `${item.job.payFrom}-${item.job.payTo}/${getDictLabel(DICT_TYPE.MENDUNER_PAY_UNIT, item.job.payUnit)}`
|
|
|
+ item.jobStatus = getDictLabel(DICT_TYPE.MENDUNER_JOB_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
|
|
|
+ })
|
|
|
}
|
|
|
-}
|
|
|
-// 工作年限分布
|
|
|
-const workExperienceData = ref(null)
|
|
|
-const getAnalysisJobCvExpCount = async () => {
|
|
|
- try {
|
|
|
- const data = await statisticAnalysisApi.getAnalysisJobCvExpCount(queryParams)
|
|
|
- if (data) workExperienceData.value = data
|
|
|
- } catch (error) {
|
|
|
- console.log(error)
|
|
|
+ if (currentItem.value.name === 'resumeViewed') {
|
|
|
+ tableData.value = tableData.value.map(item => {
|
|
|
+ item.personName = item.person.name
|
|
|
+ item.address = item.job.address
|
|
|
+ item.typeName = 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 educationData = ref(null)
|
|
|
-const getAnalysisJobCvEduCount = async () => {
|
|
|
+
|
|
|
+// 分布
|
|
|
+const getDistributionCount = async (typeName) => {
|
|
|
try {
|
|
|
- const data = await statisticAnalysisApi.getAnalysisJobCvEduCount(queryParams)
|
|
|
- if (data) educationData.value = data
|
|
|
+ const data = await apiArr[typeName]({ ...queryParams, ...customTimeObj })
|
|
|
+ distribution[typeName] = data || {}
|
|
|
} catch (error) {
|
|
|
console.log(error)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-
|
|
|
/** 搜索按钮操作 */
|
|
|
let customTimeObj
|
|
|
const handleQuery = () => {
|
|
|
customTimeObj = queryParams.type === '99' && queryParams.dateRange?.length === 2 ? customTimeObj = { 'time[0]': queryParams.dateRange.date[0] + ' 00:00:00', 'time[1]': queryParams.dateRange.date[1] + ' 23:59:59' } : {}
|
|
|
- queryParams.value = { ...queryParams, ...customTimeObj }
|
|
|
- queryParams.pageNo = 1
|
|
|
- getPageViewsList()
|
|
|
- getResumeReceivedList()
|
|
|
- getResumeViewedList()
|
|
|
- getInvitedInterviewsList()
|
|
|
- getInvitedCompletedList()
|
|
|
- getAnalysisJobCvSexCount()
|
|
|
- getAnalysisJobCvAgeCount()
|
|
|
- getAnalysisJobCvExpCount()
|
|
|
- getAnalysisJobCvEduCount()
|
|
|
+ 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 dialogLoading = ref(false)
|
|
|
+const tableData = ref([])
|
|
|
+const currentItem = ref({})
|
|
|
+// 打开弹窗
|
|
|
+const openDialog = (item) => {
|
|
|
+ dialogLoading.value = true
|
|
|
+ queryParams.pageNo = 1
|
|
|
+ currentItem.value = item
|
|
|
+ tableData.value = []
|
|
|
+ getList(item.name, '钻取')
|
|
|
+ showDialog.value = true
|
|
|
+}
|
|
|
+const closeDialog = () => {
|
|
|
+}
|
|
|
+
|
|
|
+const tableHeaders = {
|
|
|
+ pageViews: [
|
|
|
+ { name: '招聘职位', prop: 'name' },
|
|
|
+ { name: '薪酬', prop: 'salaryDisplay' },
|
|
|
+ { name: '工作地区', prop: 'areaName' },
|
|
|
+ { name: '工作经验', prop: 'expName' },
|
|
|
+ { name: '学历要求', prop: 'eduName' },
|
|
|
+ { name: '浏览量', prop: 'num' }
|
|
|
+ ],
|
|
|
+ resumeReceived: [
|
|
|
+ { name: '投递人', prop: 'personName' },
|
|
|
+ { name: '求职状态', prop: 'jobStatus' },
|
|
|
+ { name: '薪酬', prop: 'salaryDisplay' },
|
|
|
+ { name: '工作地区', prop: 'areaName' },
|
|
|
+ { name: '工作经验', prop: 'expName' },
|
|
|
+ { name: '学历要求', prop: 'eduName' },
|
|
|
+ ],
|
|
|
+ resumeViewed: [
|
|
|
+ { name: '简历标题', prop: 'title' },
|
|
|
+ { name: '投递人', prop: 'personName' },
|
|
|
+ { 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: 'typeName' },
|
|
|
+ { name: '面试时间', prop: 'timeName' },
|
|
|
+ { name: '面试地点', prop: 'addressName' },
|
|
|
+ { name: '反馈评价', prop: 'evaluate' },
|
|
|
+ ],
|
|
|
+}
|
|
|
+
|
|
|
const enterpriseOption = [
|
|
|
{ label: '企业 1', value: '1' },
|
|
|
{ label: '企业 2', value: '2' },
|