Vue3 + TypeScript + Vite + Echarts + DataV
官网:
1、创建工程
npm create vite@latest
cd datav-app
npm install
npm run dev
2、安装项目依赖模块
npm install @types/node --save-dev
npm install vue-router@4
npm install animate.css --save
npm install gsap --save
npm install fetch --save
npm install axios --save
npm install pinia
npm install less less-loader -D
npm install sass sass-loader --save-dev
npm install scss scss-loader --save-dev
npm install element-plus --save
npm install -D unplugin-vue-components unplugin-auto-import
npm install echarts echarts-wordcloud --save
npm install @kjgl77/datav-vue3
3、配置项目
vite.config.ts
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import {resolve} from "node:path";
const base = 'http://140.143.190.15:8080'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
host: '0.0.0.0',
port: 5173,
open: true,
proxy: {
'/api': {
target: base, //目标代理接口地址
secure: false,
changeOrigin: true, //开启代理,在本地创建一个虚拟服务器
PathRewrite: {
'^/api': '/api'
}
}
}
},
})
3.1 配置路径别名
vite.config.ts
import {resolve} from "node:path"; resolve: { alias: { '@': resolve(__dirname, 'src') }, // 引入文件的时候,可以忽略掉以下文件后缀 // extensions: ['.js', '.mjs', '.vue', '.json', '.less', '.css'] },
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import {resolve} from "node:path";
const base = 'http://140.143.190.15:8080'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
host: '0.0.0.0',
port: 5173,
open: true,
proxy: {
'/api': {
target: base, //目标代理接口地址
secure: false,
changeOrigin: true, //开启代理,在本地创建一个虚拟服务器
PathRewrite: {
'^/api': '/api'
}
}
}
},
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
},
})
ts.config.node.json
/* 路径别名 */ "types": ["node"], "baseUrl": ".", "paths": { "@/*": ["src/*"] }
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
/* 路径别名 */
"types": ["node"],
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["vite.config.ts"]
}
3.2 引入路由
3.2.1 创建路由出口视图并在 app.vue
组件中引入 路由出口视图
DataV.vue
<script setup lang="ts">
</script>
<template>
<router-view/>
</template>
<style scoped>
</style>
app.vue
<script setup lang="ts">
import HelloEcharts from "@/components/DataV.vue";
</script>
<template>
<data-v/>
</template>
<style scoped>
</style>
3.2.2 创建路由文件
router.ts
// 1. 定义路由组件.
// 也可以从其他文件导入
import {createMemoryHistory, createRouter, RouteRecordRaw} from 'vue-router'
// 2. 定义一些路由
const routes: Array<RouteRecordRaw> = [
]
// 3. 创建路由实例并传递 `routes` 配置
// 你可以在这里输入更多的配置,但我们在这里
// 暂时保持简单
const router = createRouter({
// 4. 内部提供了 history 模式的实现。
// memory 模式。createMemoryHistory
// hash 模式。createWebHashHistory
// html5 模式。createWebHistory
history: createMemoryHistory(),
routes,
})
export default router
3.2.3 引入路由配置文件
main.ts
import {createApp} from 'vue';
import './style.css';
import App from './App.vue';
import router from "./routers/router.ts";
const app = createApp(App);
app.use(router);
app.mount('#app');
3.3 引入 animate.css
动画库
main.ts
import 'animate.css'
import {createApp} from 'vue';
import './style.css';
import 'animate.css'
import App from './App.vue';
import router from "./routers/router.ts";
const app = createApp(App);
app.use(router);
app.mount('#app');
3.4 引入 pinia
3.4.1 编写状态管理文件strore.ts
store.ts
import {defineStore} from 'pinia'
import {computed, reactive, ref} from "vue";
export const useStore = defineStore('main', () => {
// ref() 和 reactive() 就是 state 属性
// computed() 就是 getters
// function() 就是 actions
return {}
});
3.4.2 引入 pinia
main.ts
import {createPinia} from "pinia"; // 需要注意的是从pinia中解构出来的createPinia是一个函数,挂载前需要先调用执行 // const pinia = createPinia() // app.use(pinia) app.use(createPinia())
import {createApp} from 'vue';
import './style.css';
import 'animate.css'
import App from './App.vue';
import router from "./routers/router.ts";
import {createPinia} from "pinia";
const app = createApp(App);
// 需要注意的是从pinia中解构出来的createPinia是一个函数,挂载前需要先调用执行
// const pinia = createPinia()
// app.use(pinia)
app.use(createPinia())
app.use(router);
app.mount('#app');
3.5 配置 scss
3.5.1 编写scss
变量存储文件 scss_variable.scss
// 单个图表 宽度和高度
$chart-width: 100%;
$chart-height: 100%;
3.5.2 引入 scss
变量配置文件 scss_variable.scss
vite.config.ts
css: { preprocessorOptions: { scss: { // additionalData: '@import "./src/styles/scss_variable.scss";' additionalData: `@use "./src/styles/scss_variable.scss" as *;`, } } }
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import {resolve} from "node:path";
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
const base = 'http://140.143.190.15:8080'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
host: '0.0.0.0',
port: 5173,
open: true,
proxy: {
'/api': {
target: base, //目标代理接口地址
secure: false,
changeOrigin: true, //开启代理,在本地创建一个虚拟服务器
PathRewrite: {
'^/api': '/api'
}
}
}
},
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
},
css: {
preprocessorOptions: {
scss: {
// additionalData: '@import "./src/styles/scss_variable.scss";',
additionalData: `@use "./src/styles/scss_variable.scss" as *;`,
}
}
}
})
3.6 配置 element plus
完整引入
按需导入
自动导入(本配置使用自动加载)
vite.config.ts
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import {resolve} from "node:path";
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
const base = 'http://140.143.190.15:8080'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
// resolvers: [ElementPlusResolver({importStyle: "sass"})],
resolvers: [ElementPlusResolver()],
}),
],
server: {
host: '0.0.0.0',
port: 5173,
open: true,
proxy: {
'/api': {
target: base, //目标代理接口地址
secure: false,
changeOrigin: true, //开启代理,在本地创建一个虚拟服务器
PathRewrite: {
'^/api': '/api'
}
}
}
},
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
},
css: {
preprocessorOptions: {
scss: {
// additionalData: '@import "./src/styles/scss_variable.scss";',
additionalData: `@use "./src/styles/scss_variable.scss" as *;`,
}
}
}
})
3.7 echarts公共组件
3.7.1 echarts 图表公共组件
ChartLhz.vue
<script setup lang="ts">
import * as echarts from 'echarts';
import 'echarts-wordcloud'
import {onMounted, reactive, ref, useAttrs, useTemplateRef} from "vue";
const chartLhz = ref();
// const chartLhz = useTemplateRef('chartLhz');
// 接受父组件传递的图表的配置项和数据
const props = defineProps(['chart_option'])
const {chart_option} = props;
// 指定图表的配置项和数据
const option = reactive({});
function chartLhzInit() {
// 基于准备好的dom,初始化echarts实例
let LhzChart = echarts.init(chartLhz.value, 'dark');
// 指定图表的配置项和数据
for (const filed in chart_option) {
// 将父组件中传递过来的对象属性赋值给本地的对象
option[filed] = chart_option[filed];
}
// 使用刚指定的配置项和数据显示图表。
LhzChart.setOption(option);
window.addEventListener('resize', () => {
LhzChart.resize();
})
}
onMounted(() => {
// 在挂载阶段 初始化 echarts 实例
chartLhzInit();
});
</script>
<template>
<div id="chartLhz" ref="chartLhz"></div>
</template>
<style scoped lang="scss">
#chartLhz {
width: 100%;
height: 100%;
}
</style>
3.7.2 地图公共组件
ChartMap.vue
<script setup>
import * as echarts from 'echarts';
import {ref, reactive, onMounted, useTemplateRef} from "vue";
import china_province from '@/stores/china_province.json'
import china_geo from '@/stores/china_full.json'
const chartMap = ref()
const convertData = function (data) {
let res = [];
for (let i = 0; i < data.length; i++) {
let geoCoord = china_geo.features[i].properties;
if (geoCoord) {
res.push({
name: data[i].name,
value: geoCoord.center.concat(data[i].value)
});
}
}
return res;
};
// const chartMap = useTemplateRef('chartMap')
function chartMapInit() {
// 基于准备好的dom,初始化echarts实例
const myChart = echarts.init(chartMap.value, 'dark');
// 注入地图数据 GeoJson 注意第一个参数为 china 才会显示 海南岛缩略图 其它名字不会
echarts.registerMap('china', china_geo);
// 指定图表的配置项和数据
const option = reactive({
title: {
text: '',
left: 'center'
},
tooltip: {
trigger: 'item',
formatter: function (params) {
// console.log(params);
return `${params.data.name}:${params.data.value[2]}`
}
},
// 地图配置
geo: {
type: 'map',
// chinaMap 这个参数 为 echarts.registerMap('chinaMap', response); 参数中第一个参数
// 注意参数为 china 才会显示 海南岛缩略图 其它名字不会
map: 'china',
// 是否开启鼠标缩放和平移漫游。默认不开启。如果只想要开启缩放或者平移,可以设置成 'scale' 或者 'move'。设置成 true 为都开启
roam: true,
// 图形上的文本标签,可用于说明图形的一些数据信息,比如值,名称等
label: {
// 是否显示标签
show: true
},
// 默认缩放比例
zoom: 1.1,
// 地图中心点坐标
// 当前视角的中心点,默认使用原始坐标(经纬度)。如果设置了projection则用投影后的坐标表示。
// center: [125.3245, 43.886841]
},
series: [
{
geoIndex: 0,
type: 'effectScatter',
// 配置何时显示特效
// 'render' 绘制完成后显示特效
// 'emphasis' 高亮(hover)的时候显示特效。
showEffectOn: 'render',
// data: [{ name: '北京市', value: [116.405285, 39.904989, 9] }],
data: convertData(china_province),
// 使用地理坐标系,通过 geoIndex 指定相应的地理坐标系组件。
coordinateSystem: 'geo',
symbolSize: function (param) {
// console.log(param[2]);
return param[2] / 2
},
},
],
// 是视觉映射组件,用于进行『视觉编码』,也就是将数据映射到视觉元素(视觉通道
visualMap: {
min: 0,
max: 50,
// 筛选
calculable: true
}
});
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
window.addEventListener('resize', function () {
myChart.resize();
});
}
onMounted(() => {
chartMapInit()
})
</script>
<template>
<div id="chartMap" ref="chartMap"></div>
</template>
<style scoped lang="scss">
#chartMap {
width: 100%;
height: 100%;
}
</style>
3.8 引入 datav
commons.css
html, body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
main.ts
// 引入 DataV import DataVVue3 from '@kjgl77/datav-vue3'
import {createApp} from 'vue'
// import './style.css'
import './assets/css/common.css'
import 'animate.css'
import {createPinia} from "pinia"
import App from './App.vue'
import DataVVue3 from '@kjgl77/datav-vue3'
import router from './routers/router.ts'
// import axios from 'axios'
// axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('token')
// axios.defaults.baseURL = 'http://localhost:8080'
const app = createApp(App)
// 需要注意的是从pinia中解构出来的createPinia是一个函数,挂载前需要先调用执行
// const pinia = createPinia()
// app.use(pinia)
app.use(createPinia())
app.use(DataVVue3)
app.use(router)
app.mount('#app')
4、页面布局
4.1 大屏页面
Layout.vue
<script setup lang="ts">
</script>
<template>
<div id="data-view">
<!-- 全屏容器 -->
<dv-full-screen-container>
<!-- 头部标题组件 -->
<!-- 数据分析容器 -->
<div class="row" style="position: relative; top: -20px">
<div class="col" style="flex: 1">
<div class="row" style="height: 490px">
<!-- 第一列 top 数据分析容器 -->
<div class="col" style="flex: 1">
</div>
</div>
<div class="row" style="height: 490px">
<!-- 第一列 bottom 数据分析容器 -->
<div class="col">
</div>
</div>
</div>
<div class="col">
<div class="row">
<!-- 第二列第一行数据分析容器 -->
<div class="col">
</div>
</div>
<div class="row">
<!-- 第二列第二行数据分析容器 -->
<div class="col">
</div>
</div>
<div class="row">
<div class="col">
<!-- 第二列第三行数据分析容器 -->
</div>
</div>
</div>
<div class="col">
<div class="row" style="height: 490px">
<!-- 第三列第一行数据分析容器 -->
<div class="col">
</div>
</div>
<div class="row" style="height: 490px">
<!-- 第三列第二行数据分析容器 -->
<div class="col">
</div>
</div>
</div>
</div>
</dv-full-screen-container>
</div>
</template>
<style lang="scss" scoped>
#data-view {
width: 100vw;
height: 100vh;
background-color: #030409;
color: #fff;
#dv-full-screen-container {
background-image: url('@/assets/img/bg.png');
background-size: 100% 100%;
box-shadow: 0 0 3px blue;
display: flex;
flex-direction: column;
}
.row, .col {
width: 100%;
height: 100%;
display: flex;
flex: 1;
justify-content: space-between;
}
.row {
flex-direction: row;
}
.col {
flex-direction: column;
}
}
</style>
4.2 配置路由
// 1. 定义路由组件.
// 也可以从其他文件导入
import {createMemoryHistory, createRouter, RouteRecordRaw} from 'vue-router'
import Layout from "../views/Layout.vue";
// 2. 定义一些路由
const routes: Array<RouteRecordRaw> = [
{path: '/', component: Layout},
// 处理 404
{path: '/:pathMatch(.*)*', redirect: "/"},
]
// 3. 创建路由实例并传递 `routes` 配置
// 你可以在这里输入更多的配置,但我们在这里
// 暂时保持简单
const router = createRouter({
// 4. 内部提供了 history 模式的实现。
// memory 模式。createMemoryHistory
// hash 模式。createWebHashHistory
// html5 模式。createWebHistory
history: createMemoryHistory(),
routes,
})
export default router
5、 页面组件
5.1 头部组件
TopHeader.vue
<script setup lang="ts">
</script>
<template>
<div id="top-header">
<!-- 头部左侧 -->
<dv-decoration-8 class="header-left-decoration" />
<!-- 头部标题装饰 -->
<dv-decoration-5 class="header-center-decoration" />"
<!-- 头部右侧 -->
<dv-decoration-8 :reverse="true" class="header-right-decoration" />
<!-- 头部标题 -->
<div class="center-title">计算机就业数据可视化平台</div>
</div>
</template>
<style scoped lang="scss">
#top-header{
width: 100%;
height: 100px;
display: flex;
flex-direction: row;
justify-content: space-between;
.header-center-decoration {
width: 40%;
height: 60px;
margin-top: 30px;
}
.header-left-decoration,.header-right-decoration{
width: 25%;
height: 60px;
}
.center-title{
position: absolute;
font-size: 30px;
font-weight: bold;
left: 50%;
top: 15px;
transform: translateX(-60%);
}
}
</style>
<script setup lang="ts">
import TopHeader from "@/views/TopHeader.vue";
</script>
<template>
<div id="data-view">
<!-- 全屏容器 -->
<dv-full-screen-container>
<!-- 头部标题组件 -->
<top-header/>
<!-- 数据分析容器 -->
<div class="row" style="position: relative; top: -20px">
<div class="col" style="flex: 1">
<div class="row" style="height: 490px">
<!-- 第一列 top 数据分析容器 -->
<div class="col" style="flex: 1">
</div>
</div>
<div class="row" style="height: 490px">
<!-- 第一列 bottom 数据分析容器 -->
<div class="col">
</div>
</div>
</div>
<div class="col">
<div class="row">
<!-- 第二列第一行数据分析容器 -->
<div class="col">
</div>
</div>
<div class="row">
<!-- 第二列第二行数据分析容器 -->
<div class="col">
</div>
</div>
<div class="row">
<div class="col">
<!-- 第二列第三行数据分析容器 -->
</div>
</div>
</div>
<div class="col">
<div class="row" style="height: 490px">
<!-- 第三列第一行数据分析容器 -->
<div class="col">
</div>
</div>
<div class="row" style="height: 490px">
<!-- 第三列第二行数据分析容器 -->
<div class="col">
</div>
</div>
</div>
</div>
</dv-full-screen-container>
</div>
</template>
<style lang="scss" scoped>
#data-view {
width: 100vw;
height: 100vh;
background-color: #030409;
color: #fff;
#dv-full-screen-container {
background-image: url('@/assets/img/bg.png');
background-size: 100% 100%;
box-shadow: 0 0 3px blue;
display: flex;
flex-direction: column;
}
.row, .col {
width: 100%;
height: 100%;
display: flex;
flex: 1;
justify-content: space-between;
}
.row {
flex-direction: row;
}
.col {
flex-direction: column;
}
}
</style>
5.2 全国岗位分布图
CenterMap.vue
<script setup lang="ts">
import axios from "axios";
import * as echarts from 'echarts';
import china_geo from '@/stores/china.json'
import {onMounted, ref, reactive, watch, watchEffect} from "vue";
const chartMap = ref()
function formatCityName(cityName) {
const replacements = {
"上海": "上海市",
"北京": "北京市",
"天津": "天津市",
"广州": "广东省",
"成都": "四川省",
"杭州": "浙江省",
"武汉": "湖北省",
"济南": "山东省",
"深圳": "广东省",
"青岛": "山东省",
// 可以继续添加更多的替换规则
};
return replacements[cityName] || cityName;
}
function initChart(data) {
// 基于准备好的dom,初始化echarts实例
const myChart = echarts.init(chartMap.value);
// 注入地图数据 GeoJson 注意第一个参数为 china 才会显示 海南岛缩略图 其它名字不会
echarts.registerMap('china', china_geo);
// 指定图表的配置项和数据
const option = {
backgroundColor: 'rgba(0,0,0,0)',
// backgroundColor: 'transparent',
title: {
text: '全国岗位分布',
left: 'center',
top: '30px',
textStyle: {
color: '#fff',
fontWeight: 'bold',
fontSize: '30px',
}
},
tooltip: {
trigger: 'item',
formatter: function (params) {
// console.log(params);
return `${params.data.name}:${params.data.value[2]}`
},
backgroundColor: "rgba(0,0,0,.6)",
borderColor: "rgba(147, 235, 248, .8)",
textStyle: {
color: "#FFF",
},
},
// 地图配置
geo: {
type: 'map',
// chinaMap 这个参数 为 echarts.registerMap('chinaMap', response); 参数中第一个参数
// 注意参数为 china 才会显示 海南岛缩略图 其它名字不会
map: 'china',
// 是否开启鼠标缩放和平移漫游。默认不开启。如果只想要开启缩放或者平移,可以设置成 'scale' 或者 'move'。设置成 true 为都开启
roam: true,
// 图形上的文本标签,可用于说明图形的一些数据信息,比如值,名称等
label: {
// 是否显示标签
show: true
},
// 默认缩放比例
zoom: 1.1,
// 地图中心点坐标
// 当前视角的中心点,默认使用原始坐标(经纬度)。如果设置了projection则用投影后的坐标表示。
// center: [125.3245, 43.886841]
},
series: [
{
geoIndex: 0,
type: 'effectScatter',
// 配置何时显示特效
// 'render' 绘制完成后显示特效
// 'emphasis' 高亮(hover)的时候显示特效。
showEffectOn: 'render',
// data: [{ name: '北京市', value: [116.405285, 39.904989, 90] }],
data: data,
// 使用地理坐标系,通过 geoIndex 指定相应的地理坐标系组件。
coordinateSystem: 'geo',
symbolSize: function (param) {
// console.log(param[2]);
return param[2] / 5000
},
},
],
// 是视觉映射组件,用于进行『视觉编码』,也就是将数据映射到视觉元素(视觉通道)
visualMap: {
left: 20,
bottom: 20,
pieces: [
{gte: 50000, label: "10000个以上"}, // 不指定 max,表示 max 为无限大(Infinity)。
{gte: 40000, lte: 49999, label: "40000-49999个"},
{gte: 30000, lte: 39999, label: "30000-39999个"},
{gte: 20000, lte: 29999, label: "20000-29999个"},
{gte: 10000, lte: 19999, label: "10000-19999个"},
{lte: 9999, label: "1-9999个"}, // 不指定 min,表示 min 为无限大(-Infinity)。
],
inRange: {
// 渐变颜色,从小到大
color: [
"#c3d7df",
"#5cb3cc",
"#8abcd1",
"#66a9c9",
"#2f90b9",
"#1781b5",
],
},
textStyle: {
color: "#fff",
},
},
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option, true)
window.addEventListener('resize', function () {
myChart.resize();
});
}
function getData() {
axios.get("/api/ChinaJobCount").then((response) => {
if (response.data.success) {
let province_data = response.data.data.chinaJobCountDtos;
let data = province_data.map((province) => {
let obj = china_geo.features.find(item => item.properties.name === formatCityName(province.name))
let rs = {}
rs.name = obj.properties.name
rs.value = [obj.properties.center[0], obj.properties.center[1], province.value]
return rs
});
initChart(data)
}
})
}
onMounted(() => {
getData();
})
</script>
<template>
<dv-border-box-8 style="text-align: center">
<div ref="chartMap" id="chartMap"/>
</dv-border-box-8>
</template>
<style scoped lang="scss">
#chartMap {
width: 100%;
height: 100%;
}
</style>
<script setup lang="ts">
import TopHeader from "@/views/TopHeader.vue";
import LeftTop from "@/views/LeftTop.vue";
</script>
<template>
<div id="data-view">
<!-- 全屏容器 -->
<dv-full-screen-container>
<!-- 头部标题组件 -->
<top-header/>
<!-- 数据分析容器 -->
<div class="row" style="position: relative; top: -20px">
<div class="col" style="flex: 1">
<div class="row" style="height: 490px">
<!-- 第一列 top 数据分析容器 -->
<div class="col" style="flex: 1">
<center-map/>
</div>
</div>
<div class="row" style="height: 490px">
<!-- 第一列 bottom 数据分析容器 -->
<div class="col">
</div>
</div>
</div>
<div class="col">
<div class="row">
<!-- 第二列第一行数据分析容器 -->
<div class="col">
</div>
</div>
<div class="row">
<!-- 第二列第二行数据分析容器 -->
<div class="col">
</div>
</div>
<div class="row">
<div class="col">
<!-- 第二列第三行数据分析容器 -->
</div>
</div>
</div>
<div class="col">
<div class="row" style="height: 490px">
<!-- 第三列第一行数据分析容器 -->
<div class="col">
</div>
</div>
<div class="row" style="height: 490px">
<!-- 第三列第二行数据分析容器 -->
<div class="col">
</div>
</div>
</div>
</div>
</dv-full-screen-container>
</div>
</template>
<style lang="scss" scoped>
#data-view {
width: 100vw;
height: 100vh;
background-color: #030409;
color: #fff;
#dv-full-screen-container {
background-image: url('@/assets/img/bg.png');
background-size: 100% 100%;
box-shadow: 0 0 3px blue;
display: flex;
flex-direction: column;
}
.row, .col {
width: 100%;
height: 100%;
display: flex;
flex: 1;
justify-content: space-between;
}
.row {
flex-direction: row;
}
.col {
flex-direction: column;
}
}
</style>
5.3 全国岗位需求量占比
CenterPie.vue
<script setup lang="ts">
import axios from "axios";
import {onMounted, reactive} from "vue";
const option = reactive({
title: {
text: '全年岗位需求占比',
style: {
fill: '#ffffff',
fontSize: 20,
}
},
series: [
{
type: 'pie',
data: [],
insideLabel: {
show: true
},
roseType: true
}
]
})
function getData() {
axios.get("/api/job/findJobByCategory").then((response) => {
option.series[0].data = response.data;
})
}
onMounted(() => {
getData();
})
</script>
<template>
<dv-border-box-1 style="text-align: center">
<dv-charts :option="option"/>
</dv-border-box-1>
</template>
<style scoped lang="scss">
</style>
<script setup lang="ts">
import TopHeader from "@/views/TopHeader.vue";
import LeftTop from "@/views/LeftTop.vue";
import CenterPie from "@/views/CenterPie.vue";
</script>
<template>
<div id="data-view">
<!-- 全屏容器 -->
<dv-full-screen-container>
<!-- 头部标题组件 -->
<top-header/>
<!-- 数据分析容器 -->
<div class="row" style="position: relative; top: -20px">
<div class="col" style="flex: 1">
<div class="row" style="height: 490px">
<!-- 第一列 top 数据分析容器 -->
<div class="col" style="flex: 1">
<center-map/>
</div>
</div>
<div class="row" style="height: 490px">
<!-- 第一列 bottom 数据分析容器 -->
<div class="col">
<center-pie/>
</div>
</div>
</div>
<div class="col">
<div class="row">
<!-- 第二列第一行数据分析容器 -->
<div class="col">
</div>
</div>
<div class="row">
<!-- 第二列第二行数据分析容器 -->
<div class="col">
</div>
</div>
<div class="row">
<div class="col">
<!-- 第二列第三行数据分析容器 -->
</div>
</div>
</div>
<div class="col">
<div class="row" style="height: 490px">
<!-- 第三列第一行数据分析容器 -->
<div class="col">
</div>
</div>
<div class="row" style="height: 490px">
<!-- 第三列第二行数据分析容器 -->
<div class="col">
</div>
</div>
</div>
</div>
</dv-full-screen-container>
</div>
</template>
<style lang="scss" scoped>
#data-view {
width: 100vw;
height: 100vh;
background-color: #030409;
color: #fff;
#dv-full-screen-container {
background-image: url('@/assets/img/bg.png');
background-size: 100% 100%;
box-shadow: 0 0 3px blue;
display: flex;
flex-direction: column;
}
.row, .col {
width: 100%;
height: 100%;
display: flex;
flex: 1;
justify-content: space-between;
}
.row {
flex-direction: row;
}
.col {
flex-direction: column;
}
}
</style>
5.4 web方向不同学历岗位薪资
LeftTop.vue
<script setup lang="ts">
import axios from "axios";
import {onMounted, reactive} from "vue";
const config = reactive({
data: [],
showValue: true,
unit: '千元',
labelNum: 0,
})
function getData() {
axios.get("/api/WebDemandSalary").then((response) => {
config.data = response.data.data.webDemandDtos;
config.labelNum = response.data.data.webDemandDtos.length;
})
}
onMounted(() => {
getData();
})
</script>
<template>
<dv-border-box-13 style="text-align: center">
<h1>web岗位平均薪资</h1>
<dv-capsule-chart :config="config"/>
</dv-border-box-13>
</template>
<style scoped lang="scss">
</style>
<script setup lang="ts">
import TopHeader from "@/views/TopHeader.vue";
import LeftTop from "@/views/LeftTop.vue";
import CenterPie from "@/views/CenterPie.vue";
import LeftTop from "@/views/LeftTop.vue";
</script>
<template>
<div id="data-view">
<!-- 全屏容器 -->
<dv-full-screen-container>
<!-- 头部标题组件 -->
<top-header/>
<!-- 数据分析容器 -->
<div class="row" style="position: relative; top: -20px">
<div class="col" style="flex: 1">
<div class="row" style="height: 490px">
<!-- 第一列 top 数据分析容器 -->
<div class="col" style="flex: 1">
<center-map/>
</div>
</div>
<div class="row" style="height: 490px">
<!-- 第一列 bottom 数据分析容器 -->
<div class="col">
<center-pie/>
</div>
</div>
</div>
<div class="col">
<div class="row">
<!-- 第二列第一行数据分析容器 -->
<div class="col">
<left-top/>
</div>
</div>
<div class="row">
<!-- 第二列第二行数据分析容器 -->
<div class="col">
</div>
</div>
<div class="row">
<div class="col">
<!-- 第二列第三行数据分析容器 -->
</div>
</div>
</div>
<div class="col">
<div class="row" style="height: 490px">
<!-- 第三列第一行数据分析容器 -->
<div class="col">
</div>
</div>
<div class="row" style="height: 490px">
<!-- 第三列第二行数据分析容器 -->
<div class="col">
</div>
</div>
</div>
</div>
</dv-full-screen-container>
</div>
</template>
<style lang="scss" scoped>
#data-view {
width: 100vw;
height: 100vh;
background-color: #030409;
color: #fff;
#dv-full-screen-container {
background-image: url('@/assets/img/bg.png');
background-size: 100% 100%;
box-shadow: 0 0 3px blue;
display: flex;
flex-direction: column;
}
.row, .col {
width: 100%;
height: 100%;
display: flex;
flex: 1;
justify-content: space-between;
}
.row {
flex-direction: row;
}
.col {
flex-direction: column;
}
}
</style>
5.5 Java方向不同学历岗位薪资
LeftCenter.vue
<script setup lang="ts">
import axios from "axios";
import {onMounted, reactive} from "vue";
const config = reactive({
data: [],
showValue: true,
unit: '千元',
labelNum: 0,
})
function getData() {
axios.get("/api/JavaDemandSalary").then((response) => {
config.data = response.data.data.javaDemandDtos;
config.labelNum = response.data.data.javaDemandDtos.length;
})
}
onMounted(() => {
getData();
})
</script>
<template>
<dv-border-box-13 style="text-align: center ;padding-top: 20px">
<h1>Java岗位平均薪资</h1>
<dv-capsule-chart :config="config"/>
</dv-border-box-13>
</template>
<style scoped lang="scss">
</style>
<script setup lang="ts">
import TopHeader from "@/views/TopHeader.vue";
import LeftTop from "@/views/LeftTop.vue";
import CenterPie from "@/views/CenterPie.vue";
import LeftTop from "@/views/LeftTop.vue";
import LeftCenter from "@/views/LeftCenter.vue";
</script>
<template>
<div id="data-view">
<!-- 全屏容器 -->
<dv-full-screen-container>
<!-- 头部标题组件 -->
<top-header/>
<!-- 数据分析容器 -->
<div class="row" style="position: relative; top: -20px">
<div class="col" style="flex: 1">
<div class="row" style="height: 490px">
<!-- 第一列 top 数据分析容器 -->
<div class="col" style="flex: 1">
<center-map/>
</div>
</div>
<div class="row" style="height: 490px">
<!-- 第一列 bottom 数据分析容器 -->
<div class="col">
<center-pie/>
</div>
</div>
</div>
<div class="col">
<div class="row">
<!-- 第二列第一行数据分析容器 -->
<div class="col">
<left-top/>
</div>
</div>
<div class="row">
<!-- 第二列第二行数据分析容器 -->
<div class="col">
<left-center/>
</div>
</div>
<div class="row">
<div class="col">
<!-- 第二列第三行数据分析容器 -->
</div>
</div>
</div>
<div class="col">
<div class="row" style="height: 490px">
<!-- 第三列第一行数据分析容器 -->
<div class="col">
</div>
</div>
<div class="row" style="height: 490px">
<!-- 第三列第二行数据分析容器 -->
<div class="col">
</div>
</div>
</div>
</div>
</dv-full-screen-container>
</div>
</template>
<style lang="scss" scoped>
#data-view {
width: 100vw;
height: 100vh;
background-color: #030409;
color: #fff;
#dv-full-screen-container {
background-image: url('@/assets/img/bg.png');
background-size: 100% 100%;
box-shadow: 0 0 3px blue;
display: flex;
flex-direction: column;
}
.row, .col {
width: 100%;
height: 100%;
display: flex;
flex: 1;
justify-content: space-between;
}
.row {
flex-direction: row;
}
.col {
flex-direction: column;
}
}
</style>
5.6 大数据方向不同学历岗位薪资
LeftBottom.vue
<script setup lang="ts">
import axios from "axios";
import {onMounted, reactive} from "vue";
const config = reactive({
data: [],
showValue: true,
unit: '千元',
labelNum: 0,
})
function getData() {
axios.get("/api/BigdataDemandSalary").then((response) => {
config.data = response.data.data.bigdataDemandDtos;
config.labelNum = response.data.data.bigdataDemandDtos.length;
})
}
onMounted(() => {
getData();
})
</script>
<template>
<dv-border-box-13 style="text-align: center ;padding-top: 20px">
<h1>大数据岗位平均薪资</h1>
<dv-capsule-chart :config="config"/>
</dv-border-box-13>
</template>
<style scoped lang="scss">
</style>
<script setup lang="ts">
import TopHeader from "@/views/TopHeader.vue";
import LeftTop from "@/views/LeftTop.vue";
import CenterPie from "@/views/CenterPie.vue";
import LeftTop from "@/views/LeftTop.vue";
import LeftCenter from "@/views/LeftCenter.vue";
import LeftBottom from "@/views/LeftBottom.vue";
</script>
<template>
<div id="data-view">
<!-- 全屏容器 -->
<dv-full-screen-container>
<!-- 头部标题组件 -->
<top-header/>
<!-- 数据分析容器 -->
<div class="row" style="position: relative; top: -20px">
<div class="col" style="flex: 1">
<div class="row" style="height: 490px">
<!-- 第一列 top 数据分析容器 -->
<div class="col" style="flex: 1">
<center-map/>
</div>
</div>
<div class="row" style="height: 490px">
<!-- 第一列 bottom 数据分析容器 -->
<div class="col">
<center-pie/>
</div>
</div>
</div>
<div class="col">
<div class="row">
<!-- 第二列第一行数据分析容器 -->
<div class="col">
<left-top/>
</div>
</div>
<div class="row">
<!-- 第二列第二行数据分析容器 -->
<div class="col">
<left-center/>
</div>
</div>
<div class="row">
<div class="col">
<!-- 第二列第三行数据分析容器 -->
<left-bottom/>
</div>
</div>
</div>
<div class="col">
<div class="row" style="height: 490px">
<!-- 第三列第一行数据分析容器 -->
<div class="col">
</div>
</div>
<div class="row" style="height: 490px">
<!-- 第三列第二行数据分析容器 -->
<div class="col">
</div>
</div>
</div>
</div>
</dv-full-screen-container>
</div>
</template>
<style lang="scss" scoped>
#data-view {
width: 100vw;
height: 100vh;
background-color: #030409;
color: #fff;
#dv-full-screen-container {
background-image: url('@/assets/img/bg.png');
background-size: 100% 100%;
box-shadow: 0 0 3px blue;
display: flex;
flex-direction: column;
}
.row, .col {
width: 100%;
height: 100%;
display: flex;
flex: 1;
justify-content: space-between;
}
.row {
flex-direction: row;
}
.col {
flex-direction: column;
}
}
</style>
5.8 上海平均薪资排行
RightTop.vue
<script setup lang="ts">
import {onMounted, reactive} from "vue";
import st1_img from "@/assets/img/1st.png"
import st2_img from "@/assets/img/2st.png"
import st3_img from "@/assets/img/3st.png"
import st4_img from "@/assets/img/4st.png"
import st5_img from "@/assets/img/5st.png"
import st6_img from "@/assets/img/6st.png"
import st7_img from "@/assets/img/7st.png"
import axios from "axios";
const config = reactive({
showValue: true,
data: [],
img: [st1_img, st1_img, st2_img, st2_img, st3_img, st4_img, st5_img, st7_img, st6_img],
});
function getData() {
axios.get("/api/AvgSalary/shanghai").then(response => {
config.data = response.data.data.salaryRankVos;
});
}
onMounted(() => {
getData();
})
</script>
<template>
<dv-border-box-8 style="text-align: center">
<h1>上海平均薪资排行</h1>
<dv-conical-column-chart :config="config" style=" width: 90%;height:350px; position: relative;left: 30px"/>
</dv-border-box-8>
</template>
<style scoped lang="scss">
</style>
<script setup lang="ts">
import TopHeader from "@/views/TopHeader.vue";
import CenterMap from "@/views/CenterMap.vue";
import CenterPie from "@/views/CenterPie.vue";
import LeftTop from "@/views/LeftTop.vue";
import LeftCenter from "@/views/LeftCenter.vue";
import LeftBottom from "@/views/LeftBottom.vue";
import RightTop from "@/views/RightTop.vue";
</script>
<template>
<div id="data-view">
<!-- 全屏容器 -->
<dv-full-screen-container>
<!-- 头部标题组件 -->
<top-header/>
<!-- 数据分析容器 -->
<div class="row" style="position: relative; top: -20px">
<div class="col" style="flex: 1">
<div class="row" style="height: 490px">
<!-- 第一列 top 数据分析容器 -->
<div class="col" style="flex: 1">
<center-map/>
</div>
</div>
<div class="row" style="height: 490px">
<!-- 第一列 bottom 数据分析容器 -->
<div class="col">
<center-pie/>
</div>
</div>
</div>
<div class="col">
<div class="row">
<!-- 第二列第一行数据分析容器 -->
<div class="col">
<left-top/>
</div>
</div>
<div class="row">
<!-- 第二列第二行数据分析容器 -->
<div class="col">
<left-center/>
</div>
</div>
<div class="row" style="height: 490px">
<div class="col">
<!-- 第二列第三行数据分析容器 -->
<left-bottom/>
</div>
</div>
</div>
<div class="col">
<div class="row">
<!-- 第一行第三列数据分析容器 -->
<div class="col">
<right-top/>
</div>
</div>
<div class="row" style="height: 490px">
<!-- 第二行第三列数据分析容器 -->
<div class="col">
</div>
</div>
</div>
</div>
</dv-full-screen-container>
</div>
</template>
<style lang="scss" scoped>
#data-view {
width: 100vw;
height: 100vh;
background-color: #030409;
color: #fff;
#dv-full-screen-container {
background-image: url('@/assets/img/bg.png');
background-size: 100% 100%;
box-shadow: 0 0 3px blue;
display: flex;
flex-direction: column;
}
.row, .col {
width: 100%;
height: 100%;
display: flex;
flex: 1;
justify-content: space-between;
}
.row {
flex-direction: row;
}
.col {
flex-direction: column;
}
}
</style>
5.9 各地岗位分布
RightBottom.vue
<script setup lang="ts">
import axios from "axios";
import * as echarts from 'echarts';
import 'echarts-wordcloud'
import {computed, onMounted, reactive, ref, toRaw} from "vue";
import ChartLhz from "@/components/ChartLhz.vue";
const data_list = reactive([]);
// 定义一个用于获取数据的函数
function fetchJobCounts() {
// 这里创建三个 API 请求的 Promise
const shanghaiPromise = axios.get("/api/JobCount").then((response) => {
return response.data.data.shanghaiJobCounts; // 返回上海数据
});
const guangzhouPromise = axios.get("/api/JobCount/guangzhou").then((response) => {
return response.data.data.guangJobCounts; // 返回广州数据
});
const chengduPromise = axios.get("/api/JobCount/chengdu").then((response) => {
return response.data.data.chengJobCountDtos; // 返回成都数据
});
// 使用 Promise.all 等待所有请求完成并返回结果
return Promise.all([shanghaiPromise, guangzhouPromise, chengduPromise]);
}
function getData() {
//获取数据
fetchJobCounts().then(([Shanghai_data, Guangzhou_data, Chengdu_data]) => {
// 封装数据格式 为
// [
// ["DBA", "Java", "Python", "web", "其他后端", "其他职业", "大数据", "数据分析", "算法工程师"],
// [99, 17092, 1037, 16200, 583, 4967, 3194, 4403, 3925],
// [47, 15870, 584, 13446, 269, 2771, 930, 1873, 606],
// [25, 16383, 253, 16352, 102, 928, 645, 1118, 1648]
// ]
let tmp = [];
// 遍历 Shanghai_data
for (const item of Shanghai_data) {
tmp.push(item);
}
// 遍历 Guangzhou_data
for (const item of Guangzhou_data) {
tmp.push(item);
}
// 遍历 Chengdu_data
for (const item of Chengdu_data) {
tmp.push(item);
}
// 使用 reduce 来合并数据
const result = tmp.reduce((acc, item) => {
const {name, value} = item;
// 如果标头数组不存在,则初始化标头数组
if (!acc.headers.includes(name)) {
acc.headers.push(name);
}
// 将值添加到相应的 name 索引中
const index = acc.headers.indexOf(name);
if (!acc.values[index]) {
acc.values[index] = [];
}
acc.values[index].push(value);
return acc;
}, {headers: [], values: []});
// 将最终结果格式化为二维数组
const finalResult = [result.headers];
// 确定行数(每个职业的值数量)
const rowsCount = Math.max(...result.values.map(arr => arr.length));
// 将值添加到最终结果中
for (let i = 0; i < rowsCount; i++) {
const row = [];
for (const valueArray of result.values) {
row.push(valueArray[i] || 0); // 如果没有值,则使用 0 填充
}
finalResult.push(row);
}
// 输出结果
data_list.value = finalResult;
}).catch((error) => {
console.error("Error fetching data:", error);
});
}
// 配置项
const chart_option = computed(() => {
let option = {
title: {
text: '主要城市岗位需求数量',
left: 'center',
textStyle: {
color: '#fff',
}
},
legend: {
data: ['上海', '广州', '成都'],
left: 'center',
top: 'bottom',
textStyle: {
color: '#fff',
}
},
radar: {
// shape: 'circle',
indicator: ["DBA","Java","Python","web","其他后端","其他职业","大数据","数据分析","算法工程师"],
// axisName: {
// color: '#fff'
// },
},
series: [
{
name: '主要城市岗位需求数量',
type: 'radar',
data: [],
}
]
};
if (data_list.value) {
option.radar.indicator = [];
console.log(JSON.stringify(data_list.value[0]));
for (const item of data_list.value[0]) {
option.radar.indicator.push({name:item});
}
option.series[0].data[0] = {value: JSON.parse(JSON.stringify(data_list.value[1])), name: '上海'};
option.series[0].data[1] = {value: JSON.parse(JSON.stringify(data_list.value[2])), name: '广州'};
option.series[0].data[2] = {value: JSON.parse(JSON.stringify(data_list.value[3])), name: '成都'};
}
return option;
});
onMounted(() => {
getData();
})
</script>
<template>
<dv-border-box-1 style="text-align: center">
<chart-lhz :chart_option="chart_option" v-if="data_list.value"/>
</dv-border-box-1>
</template>
<style scoped lang="scss">
</style>
<script setup lang="ts">
import TopHeader from "@/views/TopHeader.vue";
import CenterMap from "@/views/CenterMap.vue";
import CenterPie from "@/views/CenterPie.vue";
import LeftTop from "@/views/LeftTop.vue";
import LeftCenter from "@/views/LeftCenter.vue";
import LeftBottom from "@/views/LeftBottom.vue";
import RightTop from "@/views/RightTop.vue";
import RightBottom from "@/views/RightBottom.vue";
</script>
<template>
<div id="data-view">
<!-- 全屏容器 -->
<dv-full-screen-container>
<!-- 头部标题组件 -->
<top-header/>
<!-- 数据分析容器 -->
<div class="row" style="position: relative; top: -20px">
<div class="col" style="flex: 1">
<div class="row" style="height: 490px">
<!-- 第一列 top 数据分析容器 -->
<div class="col" style="flex: 1">
<center-map/>
</div>
</div>
<div class="row" style="height: 490px">
<!-- 第一列 bottom 数据分析容器 -->
<div class="col">
<center-pie/>
</div>
</div>
</div>
<div class="col">
<div class="row">
<!-- 第二列第一行数据分析容器 -->
<div class="col">
<left-top/>
</div>
</div>
<div class="row">
<!-- 第二列第二行数据分析容器 -->
<div class="col">
<left-center/>
</div>
</div>
<div class="row">
<div class="col">
<!-- 第二列第三行数据分析容器 -->
<left-bottom/>
</div>
</div>
</div>
<div class="col">
<div class="row" style="height: 490px">
<!-- 第三列第一行数据分析容器 -->
<div class="col">
<right-top/>
</div>
</div>
<div class="row" style="height: 490px">
<!-- 第三列第二行数据分析容器 -->
<div class="col">
<right-bottom/>
</div>
</div>
</div>
</div>
</dv-full-screen-container>
</div>
</template>
<style lang="scss" scoped>
#data-view {
width: 100vw;
height: 100vh;
background-color: #030409;
color: #fff;
#dv-full-screen-container {
background-image: url('@/assets/img/bg.png');
background-size: 100% 100%;
box-shadow: 0 0 3px blue;
display: flex;
flex-direction: column;
}
.row, .col {
width: 100%;
height: 100%;
display: flex;
flex: 1;
justify-content: space-between;
}
.row {
flex-direction: row;
}
.col {
flex-direction: column;
}
}
</style>
v-full-screen-container>
#dv-full-screen-container {
background-image: url(‘@/assets/img/bg.png’);
background-size: 100% 100%;
box-shadow: 0 0 3px blue;
display: flex;
flex-direction: column;
}
.row, .col {
width: 100%;
height: 100%;
display: flex;
flex: 1;
justify-content: space-between;
}
.row {
flex-direction: row;
}
.col {
flex-direction: column;
}
}