浏览代码

整体分析页面

Xiao_123 10 月之前
父节点
当前提交
2a893b711e

+ 1 - 0
components.d.ts

@@ -23,6 +23,7 @@ declare module 'vue' {
     CtTextField: typeof import('./src/components/CtVuetify/CtTextField/index.vue')['default']
     DatePicker: typeof import('./src/components/DatePicker/index.vue')['default']
     Details: typeof import('./src/components/Enterprise/details.vue')['default']
+    Echarts: typeof import('./src/components/Echarts/index.vue')['default']
     Empty: typeof import('./src/components/Empty/index.vue')['default']
     HeadSearch: typeof import('./src/components/headSearch/index.vue')['default']
     HotPromoted: typeof import('./src/components/Enterprise/hotPromoted.vue')['default']

文件差异内容过多而无法显示
+ 585 - 201
package-lock.json


+ 1 - 0
package.json

@@ -12,6 +12,7 @@
     "@mdi/font": "7.0.96",
     "@vuepic/vue-datepicker": "^8.7.0",
     "axios": "^1.6.8",
+    "echarts": "^5.4.3",
     "js-base64": "^3.7.7",
     "js-cookie": "^3.0.5",
     "lodash": "^4.17.21",

+ 3 - 1
src/components/DatePicker/index.vue

@@ -48,5 +48,7 @@ const time = computed(() => {
 </script>
 
 <style scoped lang="scss">
-
+:deep(.dp__input_icon_pad) {
+  font-size: 14px;
+}
 </style>

+ 60 - 0
src/components/Echarts/index.vue

@@ -0,0 +1,60 @@
+<template>
+  <div>
+    <div :id="domId" :style="{'width':'100%','height': height + 'px','padding-top':'15px','margin':'auto'}"></div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'low-code'})
+import { ref, watch, nextTick, onMounted } from 'vue'
+import * as echarts from 'echarts'
+import { generateUUID } from "@/utils/index" 
+
+const emit = defineEmits(['click'])
+const props = defineProps({
+  height: {
+    type: [Number, String],
+    default: 485
+  },
+  // 附加数据
+  option: {
+    type: [Object, Array],
+    default: () => {}
+  }
+})
+
+const domId = ref(generateUUID())
+const chart = ref({})
+
+const initMap = () => {
+  chart.value = echarts.init(document.getElementById(domId.value), null, { renderer: 'svg' })
+  chart.value.setOption(props.option)
+  chart.value.on('click', (param) => {
+    emit('click', param)
+  })
+  window.addEventListener('resize',()=>{
+    chart.value.resize()
+  })
+}
+
+onMounted(() => {
+  initMap()
+})
+
+watch(
+  () => props.option,
+  () => {
+    if(Object.keys(chart.value).length) {
+      chart.value.dispose()
+    }
+    nextTick(()=>{
+      initMap()
+    })
+  },
+  { deep: true }
+)
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 2 - 0
src/main.js

@@ -8,6 +8,7 @@
 import { registerPlugins } from '@/plugins'
 import '@/styles/index.scss'
 
+// import * as echarts from 'echarts'
 import App from './App.vue'
 import { createApp } from 'vue'
 
@@ -33,6 +34,7 @@ const app = createApp(App)
 app.use(pinia)
 app.use(router)
 
+// app.config.globalProperties.$echarts = echarts
 registerPlugins(app)
 app.component('VueDatePicker', VueDatePicker)
 

+ 1 - 0
src/styles/index.css

@@ -187,4 +187,5 @@
 .card-box {
   width: 100%;
   height: 100%;
+  min-height: 70vh;
 }

+ 1 - 1
src/styles/index.min.css

@@ -1 +1 @@
-:root{--zIndex-dialog:999;--default-bgc:#f2f4f7;--v-primary-base:#00897B;--v-error-base:#fe574a;--v-primary-lighten1:#26A69A;--v-primary-lighten2:#4DB6AC;--v-primary-lighten3:#80CBC4;--v-primary-lighten4:#B2DFDB;--color-222:#222;--color-333:#333;--color-666:#666;--color-777:#777;--color-999:#999;--color-ccc:#ccc;--color-f3:#f3f3f3;--color-f2f4f742:#f2f4f742;--color-f8:#f8f8f8;--color-f2f4f7:#f2f4f7;--color-d5e6e8:#d5e6e8;--zIndex-breadcrumbs:999}.color-222{color:#222}.color-333{color:#333}.color-666{color:#666}.color-777{color:#777}.color-999{color:#999}.color-ccc{color:#ccc}.color-f3f3f3{color:#f3f3f3}.color-f2f4f742{color:#f2f4f742}.color-f8f8f8{color:#f8f8f8}.color-f2f4f7{color:#f2f4f7}.color-d5e6e8{color:#d5e6e8}.color-error{color:#fe574a}.color-primary{color:#00897B}.font-size-12{font-size:12px}.font-size-13{font-size:13px}.font-size-14{font-size:14px}.font-size-15{font-size:15px}.font-size-16{font-size:16px}.font-size-17{font-size:17px}.font-size-18{font-size:18px}.font-size-19{font-size:19px}.font-size-20{font-size:20px}.buttons{height:36px;width:224px}.half-button{height:36px;width:88px}.default-width{width:1184px;min-width:1184px;max-width:1184px;margin:0 auto}.defaultLink{color:#008978;cursor:pointer}.default-active{color:var(--v-primary-base) !important}.border-bottom-dashed{border-bottom:1px dashed var(--color-ccc)}.white-bgc{background-color:#fff}.ellipsis{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.septal-line{display:inline-block;width:1px;height:10px;vertical-align:middle;background-color:#e0e0e0;margin:0 10px}.resume-box{border-radius:5px;padding:20px 30px;background-color:#fff}.resume-header{display:flex;justify-content:space-between;align-items:center;height:36px}.resume-title{font-weight:700;font-size:18px;border-left:5px solid #00897B;padding-left:12px;line-height:17px}.resumeNoDataText{color:var(--color-666);font-size:14px}.card-box{width:100%;height:100%}
+:root{--zIndex-dialog:999;--default-bgc:#f2f4f7;--v-primary-base:#00897B;--v-error-base:#fe574a;--v-primary-lighten1:#26A69A;--v-primary-lighten2:#4DB6AC;--v-primary-lighten3:#80CBC4;--v-primary-lighten4:#B2DFDB;--color-222:#222;--color-333:#333;--color-666:#666;--color-777:#777;--color-999:#999;--color-ccc:#ccc;--color-f3:#f3f3f3;--color-f2f4f742:#f2f4f742;--color-f8:#f8f8f8;--color-f2f4f7:#f2f4f7;--color-d5e6e8:#d5e6e8;--zIndex-breadcrumbs:999}.color-222{color:#222}.color-333{color:#333}.color-666{color:#666}.color-777{color:#777}.color-999{color:#999}.color-ccc{color:#ccc}.color-f3f3f3{color:#f3f3f3}.color-f2f4f742{color:#f2f4f742}.color-f8f8f8{color:#f8f8f8}.color-f2f4f7{color:#f2f4f7}.color-d5e6e8{color:#d5e6e8}.color-error{color:#fe574a}.color-primary{color:#00897B}.font-size-12{font-size:12px}.font-size-13{font-size:13px}.font-size-14{font-size:14px}.font-size-15{font-size:15px}.font-size-16{font-size:16px}.font-size-17{font-size:17px}.font-size-18{font-size:18px}.font-size-19{font-size:19px}.font-size-20{font-size:20px}.buttons{height:36px;width:224px}.half-button{height:36px;width:88px}.default-width{width:1184px;min-width:1184px;max-width:1184px;margin:0 auto}.defaultLink{color:#008978;cursor:pointer}.default-active{color:var(--v-primary-base) !important}.border-bottom-dashed{border-bottom:1px dashed var(--color-ccc)}.white-bgc{background-color:#fff}.ellipsis{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.septal-line{display:inline-block;width:1px;height:10px;vertical-align:middle;background-color:#e0e0e0;margin:0 10px}.resume-box{border-radius:5px;padding:20px 30px;background-color:#fff}.resume-header{display:flex;justify-content:space-between;align-items:center;height:36px}.resume-title{font-weight:700;font-size:18px;border-left:5px solid #00897B;padding-left:12px;line-height:17px}.resumeNoDataText{color:var(--color-666);font-size:14px}.card-box{width:100%;height:100%;min-height:70vh}

+ 1 - 0
src/styles/index.scss

@@ -131,4 +131,5 @@
 .card-box {
   width: 100%;
   height: 100%;
+  min-height: 70vh;
 }

+ 13 - 0
src/utils/index.js

@@ -42,4 +42,17 @@ import { Base64 } from 'js-base64'
 export const previewFile = (url) => {
   const baseUrl = import.meta.env.VITE_PREVIEW_URL
   window.open(`${baseUrl}/onlinePreview?url=${encodeURIComponent(Base64.encode(url))}`)
+}
+
+export const generateUUID = () => {
+  var d = new Date().getTime()
+  if (window.performance && typeof window.performance.now === "function") {
+    d += performance.now(); //use high-precision timer if available
+  }
+  var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+    var r = (d + Math.random() * 16) % 16 | 0
+    d = Math.floor(d / 16)
+    return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16)
+  })
+  return uuid.replace(/-/g, "")
 }

+ 53 - 0
src/views/recruit/enterprise/statistics/components/daily.vue

@@ -0,0 +1,53 @@
+<template>
+  <v-data-table
+    :items="items"
+    :headers="headers"
+  > 
+    <template #bottom>
+      <CtPagination
+        :total="total"
+        :page="query.pageNo"
+        :limit="query.pageSize"
+        @handleChange="handleChangePage"
+      ></CtPagination>
+    </template>
+  </v-data-table>
+</template>
+
+<script setup>
+defineOptions({ name: 'daily-page'})
+import { ref } from 'vue'
+
+const total = ref(8)
+const query = ref({
+  pageSize: 10,
+  pageNo: 1
+})
+const headers = [
+  { title: '日期', key: 'date', sortable: false },
+  { title: '有效职位', key: 'effectivePositions', sortable: false },
+  { title: '刷新次数', key: 'refreshTimes', sortable: false },
+  { title: '职位浏览', key: 'jobBrowse', sortable: false },
+  { title: '简历投递', key: 'resumeDelivery', sortable: false},
+  { title: '简历处理', key: 'resumeDeal', sortable: false },
+  { title: '邀请面试', key: 'invite', sortable: false }
+]
+const items = ref([
+  { date: '2024-07-01', effectivePositions: 0, refreshTimes: 0, jobBrowse: 0, resumeDelivery: 0, resumeDeal: 0, invite: 0 },
+  { date: '2024-07-02', effectivePositions: 0, refreshTimes: 0, jobBrowse: 0, resumeDelivery: 0, resumeDeal: 0, invite: 0 },
+  { date: '2024-07-03', effectivePositions: 0, refreshTimes: 0, jobBrowse: 0, resumeDelivery: 0, resumeDeal: 0, invite: 0 },
+  { date: '2024-07-04', effectivePositions: 0, refreshTimes: 0, jobBrowse: 0, resumeDelivery: 0, resumeDeal: 0, invite: 0 },
+  { date: '2024-07-05', effectivePositions: 0, refreshTimes: 0, jobBrowse: 0, resumeDelivery: 0, resumeDeal: 0, invite: 0 },
+  { date: '2024-07-06', effectivePositions: 0, refreshTimes: 0, jobBrowse: 0, resumeDelivery: 0, resumeDeal: 0, invite: 0 },
+  { date: '2024-07-07', effectivePositions: 0, refreshTimes: 0, jobBrowse: 0, resumeDelivery: 0, resumeDeal: 0, invite: 0 },
+  { date: '2024-07-08', effectivePositions: 0, refreshTimes: 0, jobBrowse: 0, resumeDelivery: 0, resumeDeal: 0, invite: 0 }
+])
+
+const handleChangePage = (e) => {
+  query.value.pageNo = e
+}
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 171 - 0
src/views/recruit/enterprise/statistics/components/data.js

@@ -0,0 +1,171 @@
+export const list = [
+  {
+    col: 6,
+    option: {
+      title: {
+        text: '性别比例'
+      },
+      tooltip: {
+        trigger: 'item'
+      },
+      legend: {
+        top: '5%',
+        left: 'center'
+      },
+      series: [
+        {
+          name: 'Access From',
+          type: 'pie',
+          radius: ['40%', '70%'],
+          avoidLabelOverlap: false,
+          itemStyle: {
+            borderRadius: 10,
+            borderColor: '#fff',
+            borderWidth: 2
+          },
+          label: {
+            show: true,
+            formatter: e => {
+              return e.name + ': ' + e.value + '%'
+            }
+          },
+          labelLine: {
+            show: true
+          },
+          data: [
+            { value: 65, name: '男' },
+            { value: 35, name: '女' }
+          ]
+        }
+      ]
+    }
+  },
+  {
+    col: 6,
+    option: {
+      title: {
+        text: '年龄分布'
+      },
+      xAxis: {
+        type: 'category',
+        data: ['18-22岁', '22-30岁', '30-39岁', '40-49岁', '50-59岁']
+      },
+      yAxis: {
+        type: 'value'
+      },
+      grid: {
+        left: '0',
+        top: '50',
+        right: '0',
+        bottom: 0,
+        containLabel: true
+      },
+      series: [
+        {
+          data: [120, 200, 150, 80, 70],
+          type: 'bar',
+          barWidth: 40,
+          label: {
+            show: true
+          }
+        }
+      ]
+    }
+  },
+  {
+    col: 6,
+    option: {
+      title: {
+        text: '工作年限分布'
+      },
+      xAxis: {
+        type: 'category',
+        data: ['应届毕业生', '1年以上', '2年以上', '3年以上', '5年以上', '8年以上', '10年以上']
+      },
+      yAxis: {
+        type: 'value'
+      },
+      grid: {
+        left: '0',
+        top: '50',
+        right: '0',
+        bottom: 0,
+        containLabel: true
+      },
+      series: [
+        {
+          data: [120, 200, 150, 80, 70, 110, 130],
+          type: 'bar',
+          barWidth: 40,
+          label: {
+            show: true
+          }
+        }
+      ]
+    }
+  },
+  {
+    col: 6,
+    option: {
+      title: {
+        text: '学历分布'
+      },
+      xAxis: {
+        type: 'category',
+        data: ['本科以上', '大专', '中专', '中技', '高中', '初中']
+      },
+      yAxis: {
+        type: 'value'
+      },
+      grid: {
+        left: '0',
+        top: '50',
+        right: '0',
+        bottom: 0,
+        containLabel: true
+      },
+      series: [
+        {
+          data: [120, 200, 150, 80, 70, 110],
+          type: 'bar',
+          barWidth: 40,
+          label: {
+            show: true
+          }
+        }
+      ]
+    }
+  },
+  {
+    col: 12,
+    option: {
+      title: {
+        text: '期望月薪'
+      },
+      xAxis: {
+        type: 'category',
+        data: ['3-5k', '5-8k', '8-12k', '12-15k', '15-20k', '20-30k', '面议']
+      },
+      yAxis: {
+        type: 'value'
+      },
+      grid: {
+        left: '0',
+        top: '50',
+        right: '0',
+        bottom: 0,
+        containLabel: true
+      },
+      series: [
+        {
+          data: [120, 200, 150, 80, 70, 110, 130],
+          type: 'bar',
+          barWidth: 40,
+          label: {
+            show: true
+          }
+        }
+      ]
+    }
+  }
+]

+ 152 - 0
src/views/recruit/enterprise/statistics/components/overview.vue

@@ -0,0 +1,152 @@
+<template>
+  <div class="overview my-5">
+    <div class="overview-item pa-5 color-666" v-for="(val, i) in overview" :key="i">
+      <div>{{ val.title }}</div>
+      <div class="overview-item-value my-3">{{ val.value }}</div>
+      <div class="font-size-14">
+        环比
+        <span class="color-error">0% ↑</span>
+      </div>
+    </div>
+  </div>
+  <div id="myChart" style="width: 100%; height: 500px;background-color: #f7f8fa;border-radius: 8px;" class="pa-3"></div>
+</template>
+
+<script setup>
+defineOptions({ name: 'overview-page'})
+import { ref, onMounted } from 'vue'
+import * as echarts from 'echarts'
+
+// 数据概况
+const overview = ref([
+  { title: '我看过', value: 86, desc: '' },
+  { title: '看过我', value: 12, desc: '' },
+  { title: '我打招呼', value: 0, desc: '' },
+  { title: '牛人新招呼', value: 4, desc: '' },
+  { title: '我沟通', value: 0, desc: '' },
+  { title: '收获简历', value: 5, desc: '' },
+  { title: '交换电话微信', value: 2, desc: '' },
+  { title: '接受面试', value: 0, desc: '' }
+])
+
+onMounted(() => {
+  var chartDom = document.getElementById('myChart')
+  var myChart = echarts.init(chartDom)
+  var option
+
+  option = {
+    title: {
+      text: '历史数据走势图'
+    },
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'cross',
+        label: {
+          backgroundColor: '#6a7985'
+        }
+      }
+    },
+    legend: {
+      data: ['职位数', '刷新次数', '职位浏览', '简历投递数', '简历处理']
+    },
+    toolbox: {
+      feature: {
+        saveAsImage: {}
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: [
+      {
+        type: 'category',
+        boundaryGap: false,
+        data: ['2024-07-10', '2024-07-11', '2024-07-12']
+      }
+    ],
+    yAxis: [
+      {
+        type: 'value'
+      }
+    ],
+    series: [
+      {
+        name: '职位数',
+        type: 'line',
+        emphasis: {
+          focus: 'series'
+        },
+        data: [120, 132, 101, 134, 90, 230, 210]
+      },
+      {
+        name: '刷新次数',
+        type: 'line',
+        emphasis: {
+          focus: 'series'
+        },
+        data: [220, 182, 191, 234, 290, 330, 310]
+      },
+      {
+        name: '职位浏览',
+        type: 'line',
+        emphasis: {
+          focus: 'series'
+        },
+        data: [150, 232, 201, 154, 190, 330, 410]
+      },
+      {
+        name: '简历投递数',
+        type: 'line',
+        emphasis: {
+          focus: 'series'
+        },
+        data: [320, 332, 301, 334, 390, 330, 320]
+      },
+      {
+        name: '简历处理',
+        type: 'line',
+        label: {
+          show: true,
+          position: 'top'
+        },
+        emphasis: {
+          focus: 'series'
+        },
+        data: [820, 932, 901, 934, 1290, 1330, 1320]
+      }
+    ]
+  }
+  option && myChart.setOption(option)
+})
+</script>
+
+<style scoped lang="scss">
+.overview {
+  display: flex;
+  flex-wrap: wrap;
+  width: 100%;
+}
+.overview-item {
+  width: calc((100% - 84px) / 8);
+  min-width: calc((100% - 84px) / 8);
+  max-width: calc((100% - 84px) / 8);
+  margin: 0 12px 12px 0;
+  height: 175px;
+  border-radius: 12px;
+  overflow: hidden;
+  transition: all .2s linear;
+  background-color: #f7f8fa;
+  &:nth-child(8n) {
+    margin-right: 0;
+  }
+}
+.overview-item-value {
+  color: var(--v-primary-base);
+  font-weight: 700;
+  font-size: 44px;
+}
+</style>

+ 22 - 0
src/views/recruit/enterprise/statistics/components/resume.vue

@@ -0,0 +1,22 @@
+<template>
+  <v-container>
+    <v-row>
+      <v-col class="bgc" v-for="(val, i) in list" :key="i" :md="val.col">
+        <Echarts :height="400" :option="val.option"></Echarts>
+      </v-col>
+    </v-row>
+  </v-container>
+</template>
+
+<script setup>
+defineOptions({ name: 'resume-analysis'})
+import { list } from './data.js'
+
+</script>
+
+<style scoped lang="scss">
+.bgc {
+  background-color: #f7f8fa;
+  border-radius: 8px;
+}
+</style>

+ 66 - 2
src/views/recruit/enterprise/statistics/overallAnalysis.vue

@@ -1,11 +1,75 @@
 <template>
-  <div>整体分析</div>
+  <v-card class="card-box pa-5">
+    <div class="d-flex color-666 font-size-14">
+      <div class="d-flex align-center">
+        <span>选择时间</span>
+        <div class="ml-5 after">
+          <span v-for="(k, i) in list" :key="i" :class="['item', { 'active': current === (i + 1) }]" @click="current = i + 1 ">{{ k }}</span>
+        </div>
+      </div>
+      <div class="d-flex align-center ml-15">
+        <span>自定义日期</span>
+        <div class="ml-5">
+          <date-picker v-model="date" :options="{ range: true, clearable: true, placeholder: '请选择要查看的时间范围' }"></date-picker>
+        </div>
+      </div>
+    </div>
+    <div class="mt-10">
+      <v-tabs v-model="tab" align-tabs="start" color="primary" bg-color="#f7f8fa">
+        <v-tab :value="1">数据概况</v-tab>
+      </v-tabs>
+      <Overview class="mt-5"></Overview>
+    </div>
+    <div class="my-10">
+      <v-tabs v-model="tab" align-tabs="start" color="primary" bg-color="#f7f8fa">
+        <v-tab :value="1">应聘简历分析</v-tab>
+      </v-tabs>
+      <ResumeAnalysis class="mt-5"></ResumeAnalysis>
+    </div>
+    <div>
+      <v-tabs v-model="tab" align-tabs="start" color="primary" bg-color="#f7f8fa">
+        <v-tab :value="1">每日统计</v-tab>
+      </v-tabs>
+      <DailyPage class="mt-5"></DailyPage>
+    </div>
+  </v-card>
 </template>
 
 <script setup>
 defineOptions({ name: 'overallAnalysis'})
+import { ref } from 'vue'
+import Overview from './components/overview.vue'
+import DailyPage from './components/daily.vue'
+import ResumeAnalysis from './components/resume.vue'
+
+const tab = ref(1)
+const date = ref(null)
+const current = ref(1)
+const list = ['最近7天', '上个月', '上季度']
+
 </script>
 
 <style scoped lang="scss">
-
+.after {
+  height: 36px;
+  background-color: #f2f3f5;
+  border-radius: 4px;
+  padding: 1.5px;
+}
+.item {
+  line-height: 31px;
+  padding: 0 12px;
+  margin: 1.5px;
+  cursor: pointer;
+}
+.active {
+  position: relative;
+  color: var(--v-primary-base);
+  background-color: #fff;
+  display: inline-block;
+  margin: 1.5px;
+  height: 30px;
+  border-radius: 4px;
+  transition: all .1s linear;
+}
 </style>

+ 2 - 2
src/views/recruit/personal/PersonalCenter/dynamic/left.vue

@@ -73,7 +73,7 @@ import communication from '../components/communication.vue'
 import delivery from '../components/delivery.vue'
 import interview from '../components/interview/index.vue'
 import interested from '../components/interested.vue'
-import interestedMe from '../components/interestedMe.vue'
+// import interestedMe from '../components/interestedMe.vue'
 import seenMe from '../components/seenMe.vue'
 
 const { t } = useI18n()
@@ -82,7 +82,7 @@ const list = [
   { title: t('position.delivered'), path: delivery },
   { title: t('position.interview'), path: interview },
   { title: t('position.interested'), path: interested },
-  { title: t('position.interestedInMe'), path: interestedMe },
+  // { title: t('position.interestedInMe'), path: interestedMe },
   { title: t('position.haveSeenMe'), path: seenMe }
 ]
 const tab = ref(1)

部分文件因为文件数量过多而无法显示