瀏覽代碼

统计分析

Xiao_123 5 月之前
父節點
當前提交
aa73ae63c7

+ 19 - 17
package-lock.json

@@ -40,7 +40,6 @@
         "nprogress": "^0.2.0",
         "pinia": "^2.1.7",
         "pinia-plugin-persistedstate": "^3.2.1",
-        "pnpm": "^9.4.0",
         "qrcode": "^1.5.3",
         "qs": "^6.12.0",
         "steady-xml": "^0.1.0",
@@ -51,6 +50,7 @@
         "vue-i18n": "9.10.2",
         "vue-router": "^4.3.0",
         "vue-types": "^5.1.1",
+        "vue3-seamless-scroll": "^2.0.1",
         "vuedraggable": "^4.1.0",
         "web-storage-cache": "^1.1.1",
         "xml-js": "^1.6.11"
@@ -12185,22 +12185,6 @@
         "node": ">=10.13.0"
       }
     },
-    "node_modules/pnpm": {
-      "version": "9.4.0",
-      "resolved": "https://registry.npmmirror.com/pnpm/-/pnpm-9.4.0.tgz",
-      "integrity": "sha512-9Um4pSydK4U2di+ZwHIiBe/Fr5E+d4NdvMw7CwssqefcgCK3gGLBcpHEjoh0nHDOiOtadPH6jEv14Yu0bIvYOg==",
-      "license": "MIT",
-      "bin": {
-        "pnpm": "bin/pnpm.cjs",
-        "pnpx": "bin/pnpx.cjs"
-      },
-      "engines": {
-        "node": ">=18.12"
-      },
-      "funding": {
-        "url": "https://opencollective.com/pnpm"
-      }
-    },
     "node_modules/posix-character-classes": {
       "version": "0.1.1",
       "resolved": "https://registry.npmmirror.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@@ -15064,6 +15048,15 @@
       "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
       "dev": true
     },
+    "node_modules/throttle-debounce": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.0.tgz",
+      "integrity": "sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12.22"
+      }
+    },
     "node_modules/through": {
       "version": "2.3.8",
       "resolved": "https://registry.npmmirror.com/through/-/through-2.3.8.tgz",
@@ -16381,6 +16374,15 @@
       "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.4.21.tgz",
       "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g=="
     },
+    "node_modules/vue3-seamless-scroll": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/vue3-seamless-scroll/-/vue3-seamless-scroll-2.0.1.tgz",
+      "integrity": "sha512-mI3BaDU3pjcPUhVSw3/xNKdfPBDABTi/OdZaZqKysx4cSdNfGRbVvGNDzzptBbJ5S7imv5T55l6x/SqgnxKreg==",
+      "license": "MIT",
+      "dependencies": {
+        "throttle-debounce": "5.0.0"
+      }
+    },
     "node_modules/vuedraggable": {
       "version": "4.1.0",
       "resolved": "https://registry.npmmirror.com/vuedraggable/-/vuedraggable-4.1.0.tgz",

+ 1 - 0
package.json

@@ -67,6 +67,7 @@
     "vue-i18n": "9.10.2",
     "vue-router": "^4.3.0",
     "vue-types": "^5.1.1",
+    "vue3-seamless-scroll": "^2.0.1",
     "vuedraggable": "^4.1.0",
     "web-storage-cache": "^1.1.1",
     "xml-js": "^1.6.11"

+ 15 - 0
src/api/menduner/system/analysis/statisticAnalysis.ts

@@ -76,5 +76,20 @@ export const statisticAnalysisApi = {
   // 招聘进展明细导出
   analysisExport: async (params) => {
     return await request.download({ url: `/menduner/system/analysis/export`, params })
+  },
+
+  // 前N最多人发布的职位类型
+  getJobPositionTop: async (params: any) => {
+    return await request.get({ url: `/menduner/system/analysis/get/job/position/top`, params })
+  },
+
+  // 统计发布职位数量
+  getJobCount: async (params: any) => {
+    return await request.get({ url: `/menduner/system/analysis/get/job/count`, params })
+  },
+
+  // 最新发布的前N个职位
+  getJobDetails: async (params: any) => {
+    return await request.get({ url: `/menduner/system/analysis/get/job/top`, params })
   }
 }

+ 2 - 1
src/views/Home/Index.vue

@@ -59,7 +59,7 @@
   <!-- 图表 -->
    
   <div style="margin-top: 15px;">
-    <CardTitle title="数量统计" style="margin-left: 5px; margin-top: 20px;"/>
+    <CardTitle title="数量统计" style=" margin-top: 20px;margin-left: 5px;"/>
   </div>
   <el-row class="mt-8px" :gutter="10" justify="space-between">
     <el-col :xl="12" :lg="12" :md="24" :sm="24" :xs="24" class="mb-8px" v-for="(val, index) in list" :key="index">
@@ -198,6 +198,7 @@ async function fetchAllData() {
 /** 类型选择操作 */
 const typeChange = (value) => {
   if (value) queryParams.time = []
+  handleQuery()
 }
 
 /** 自定义时间选择操作 */

+ 7 - 7
src/views/menduner/system/analysis/statisticAnalysis/components/ComparisonCard.vue

@@ -20,23 +20,23 @@
 </template>
 <script lang="ts" setup>
 import { propTypes } from '@/utils/propTypes'
-import { toNumber } from 'lodash-es'
-import { calculateRelativeRate } from '@/utils'
+// import { toNumber } from 'lodash-es'
+// import { calculateRelativeRate } from '@/utils'
 
 /** 交易对照卡片 */
 defineOptions({ name: 'ComparisonCard' })
 
-const props = defineProps({
+defineProps({
   title: propTypes.string.def('').isRequired,
   tag: propTypes.string.def(''),
   prefix: propTypes.string.def(''),
   value: propTypes.number.def(0).isRequired,
-  reference: propTypes.number.def(0).isRequired,
+  // reference: propTypes.number.def(0).isRequired,
   decimals: propTypes.number.def(0)
 })
 
 // 计算环比
-const percent = computed(() =>
-  calculateRelativeRate(props.value as number, props.reference as number)
-)
+// const percent = computed(() =>
+//   calculateRelativeRate(props.value as number, props.reference as number)
+// )
 </script>

+ 74 - 0
src/views/menduner/system/analysis/statisticAnalysis/components/jobCount.vue

@@ -0,0 +1,74 @@
+<!--  -->
+<template>
+  <el-skeleton :loading="loading" animated>
+    <el-card shadow="never">
+      <template #header>
+        <CardTitle title="职位发布时间数量统计" />
+      </template>
+      <div>
+        <Echart :height="300" :options="chartOptions" />
+      </div>
+    </el-card>      
+  </el-skeleton>
+</template>
+
+<script lang="ts" setup>
+defineOptions({name: 'JobCount'})
+import { EChartsOption } from 'echarts'
+import { statisticAnalysisApi } from '@/api/menduner/system/analysis/statisticAnalysis'
+
+const props = defineProps({
+  queryParams: Object
+})
+
+const loading = ref(false)
+const chartOptions = reactive<EChartsOption>({
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      type: 'shadow'
+    }
+  },
+  grid: {
+    left: 20,
+    bottom: 10,
+    right: 40,
+    top: 30,
+    containLabel: true
+  },
+  xAxis: {
+    type: 'category',
+    name: '时间',
+    data: []
+  },
+  yAxis: {
+    type: 'value',
+    name: '个'
+  },
+  series: [
+    {
+      data:  [],
+      type: 'line',
+      label: { show: true }
+    }
+  ]
+}) as EChartsOption
+
+const getData = async (queryParams: any) => {
+  loading.value = true
+  try {
+    const data = await statisticAnalysisApi.getJobCount(queryParams)
+    chartOptions.xAxis.data = data.x || []
+    chartOptions.series![0].data = data.y || []
+  } catch (err) {
+    console.log(err);
+    loading.value = false
+  } finally {
+    loading.value = false
+  }
+}
+
+watch(() => props.queryParams, (newVal) => {
+  getData(newVal)
+}, { immediate: true, deep: true })
+</script>

+ 70 - 0
src/views/menduner/system/analysis/statisticAnalysis/components/jobDetails.vue

@@ -0,0 +1,70 @@
+<!--  -->
+<template>
+  <el-skeleton :loading="loading" animated>
+    <el-card shadow="never">
+      <template #header>
+        <CardTitle title="最新发布职位" />
+      </template>
+      <div class="scroll-box">
+        <vue3-seamless-scroll :list="list" style="height: 100%;" :step="0.5">
+          <div v-for="val in list" :key="val.id" class="!h-40px list-item">
+            <div>{{ val.name }}</div>
+            <div>{{ val.enterpriseName }}</div>
+            <div>{{ timesTampChange(val.createTime) }}</div>
+          </div>
+        </vue3-seamless-scroll>
+      </div>
+    </el-card>
+  </el-skeleton>
+</template>
+
+<script lang="ts" setup>
+defineOptions({name: 'JobCount'})
+import { statisticAnalysisApi } from '@/api/menduner/system/analysis/statisticAnalysis'
+import { timesTampChange } from '@/utils/transform/date'
+import { Vue3SeamlessScroll } from "vue3-seamless-scroll"
+
+const props = defineProps({
+  queryParams: Object
+})
+
+const loading = ref(false)
+const list = ref([])
+const getData = async (queryParams: any) => {
+  loading.value = true
+  try {
+    const data = await statisticAnalysisApi.getJobDetails(queryParams)
+    list.value = data || []
+  } catch (err) {
+    console.log(err);
+    loading.value = false
+  } finally {
+    loading.value = false
+  }
+}
+
+watch(() => props.queryParams, (newVal) => {
+  getData(newVal)
+}, { immediate: true, deep: true })
+</script>
+
+<style lang="scss" scoped>
+.scroll-box {
+  height: 400px;
+  overflow: hidden;
+}
+
+.list-item {
+  display: flex;
+  align-items: center;
+  font-size: 13px;
+  color: #333;
+
+  div {
+    width: 33.3%;
+    max-width: 33.3%;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+}
+</style>

+ 75 - 0
src/views/menduner/system/analysis/statisticAnalysis/components/jobTop.vue

@@ -0,0 +1,75 @@
+<!--  -->
+<template>
+  <el-skeleton :loading="loading" animated>
+    <el-card shadow="never">
+      <template #header>
+        <CardTitle title="职位类型发布数量Top10" />
+      </template>
+      <div>
+        <Echart :height="400" :options="chartOptions" />
+      </div>
+    </el-card>      
+  </el-skeleton>
+</template>
+
+<script lang="ts" setup>
+defineOptions({name: 'JobTop10'})
+import { EChartsOption } from 'echarts'
+import { statisticAnalysisApi } from '@/api/menduner/system/analysis/statisticAnalysis'
+
+const props = defineProps({
+  queryParams: Object
+})
+
+const loading = ref(false)
+const chartOptions = reactive<EChartsOption>({
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      type: 'shadow'
+    }
+  },
+  grid: {
+    left: 0,
+    bottom: 10,
+    right: 40,
+    top: 30,
+    containLabel: true
+  },
+  xAxis: {
+    type: 'value',
+    name: '个'
+  },
+  yAxis: {
+    type: 'category',
+    name: '职位类型',
+    data: []
+  },
+  series: [
+    {
+      data:  [],
+      type: 'bar',
+      // barWidth: 40,
+      label: { show: true }
+    }
+  ]
+}) as EChartsOption
+
+const getData = async (queryParams: any) => {
+  loading.value = true
+  try {
+    const data = await statisticAnalysisApi.getJobPositionTop({ ...queryParams, jobStatus: 0 })
+    chartOptions.yAxis.data = data.x || []
+    chartOptions.series![0].data = data.y || []
+  } catch (err) {
+    console.log(err);
+    loading.value = false
+  } finally {
+    loading.value = false
+  }
+}
+
+watch(() => props.queryParams, (newVal) => {
+  getData(newVal)
+}, { immediate: true, deep: true })
+</script>

+ 12 - 6
src/views/menduner/system/analysis/statisticAnalysis/index.vue

@@ -76,9 +76,9 @@
           </el-form-item>
         </div>
         <el-form-item label="" prop="type">
-          <el-radio-group v-model="queryParams.type" @change="typeChange" class="!w-360px">
+          <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="24小时内" value="3" />
           <el-radio-button label="7天内" value="0" />
           <!-- <el-radio-button label="上个月" value="1" /> -->
           <el-radio-button label="30天内" value="4" />
@@ -110,13 +110,13 @@
     <div class="flex flex-col">
       <!-- 统计 -->
       <div>
-        <CardTitle title="招聘进展"  style="margin-left: 5px;"/>
+        <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" style="margin-top: 10px;">
+        <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"
@@ -127,10 +127,14 @@
           </el-col>
         </el-row>
       </div>
+
+      <!-- 职位发布情况 -->
+      <JobStatistics :queryParams="queryParams" class="m-b-10px" /> 
+
       <!-- 图表 -->
       <div>
-        <CardTitle title="应聘简历分析"  style="margin-left: 5px;"/>
-        <el-row :gutter="16" class="row" style="margin-top: 20px;">
+        <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>
@@ -178,6 +182,7 @@ 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'
@@ -228,6 +233,7 @@ const typeChange = (value) => { //
   if (value) {
     queryParams.time = []
   }
+  handleQuery()
 }
 const timeRangeChange = (value) => {
   if (value?.length) queryParams.type = '99' // 自定义

+ 29 - 0
src/views/menduner/system/analysis/statisticAnalysis/job.vue

@@ -0,0 +1,29 @@
+<template>
+  <div>
+    <CardTitle title="职位发布情况" class="m-l-5px" />
+    <el-row :gutter="16" class="row m-t-10px">
+      <el-col :md="12">
+        <JobTop :queryParams="queryParams" />
+      </el-col>
+      <el-col :md="12">
+        <JobDetails :queryParams="queryParams" />
+      </el-col>
+    </el-row>
+    <el-row :gutter="16" class="row m-t-10px">
+      <el-col :md="24">
+        <JobCount :queryParams="queryParams" />
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+import JobTop from './components/jobTop.vue'
+import JobCount from './components/jobCount.vue'
+import JobDetails from './components/jobDetails.vue'
+
+defineProps({
+  queryParams: Object
+})
+</script>
+