李翔-大数据技术

Big data technology!

数据可视化02-前端

四、Vue前端

安装 Node.js 工具

# 1.安装 node-v20.18.1-x64.msi
默认安装即可

# 2.检查Node.js是否已正确安装。
node -v

Node.js 是一个可以让 JavaScript 脱离浏览器、在服务器上运行的程序。

Node.js 就是让 JavaScript 像 Python 一样,在终端和服务器上直接运行的工具。

创建项目、安装依赖、运行服务器、构建打包,都依赖 Node.js 环境。

Vite 是基于 Node.js 的前端构建工具,用来高效开发 Vue项目。

1. 前端页面编写

Vue项目搭建

(1)创建Vue项目

# 1.以管理员的身份进入CMD窗口,切换到要创建项目的文件夹,输入以下命令创建Vue项目。
# 注意:必须以管理员的身份运行,否则可能会报没有权限的错误
d:
cd /bigdata1

# 2.设置 npm(Node.js 的包管理工具)的默认镜像源为淘宝 NPM 镜像(npmmirror.com),加速下载
npm config set registry https://registry.npmmirror.com

# 3.使用 npm 创建一个基于 Vite 的 Vue 3 项目 analyse-ui 的前端项目。
npm init vite@latest analyse-ui -- --template vue

解释:

部分作用
npmNode 的包管理器,帮你执行创建项目的命令
init vite@latest创建一个基于最新版本的 Vite 项目
analyse-ui新项目的文件夹名字(项目名),创建完会生成 analyse-ui 文件夹
-- --template vue使用 Vue 模板(告诉 Vite:创建一个 Vue 项目的骨架)

image-20250501222739227

(2)添加项目依赖

# 1.进入到项目目录
cd d:/bigdata1/analyse-ui

# 2.安装vite、插件系统等依赖
npm install

# 3.在项目中安装 vue/vue-router/element-plus/axios/echarts 等依赖库
npm install vue vue-router element-plus axios echarts

解释

包名作用备注
vueVue.js 前端框架写前端页面的核心
vue-routerVue 的路由库页面跳转、导航
element-plusUI 组件库提供按钮、表格、对话框等界面组件
axiosHTTP 请求库向后端发送请求(比如 Spring Boot)
echarts数据可视化库绘制图表(柱状图、饼图、折线图等)


(3)启动IDEA打开项目

启动IDEA,点击 File - open 打开刚才创建的项目


配置Vue项目关键文件

(4)查看package.json【不用更改和编辑】

作用:Vue 项目的“说明书”和“依赖清单”。它记录了项目名称、版本号、安装的依赖库(如 Vue、Axios 等)、常用脚本命令(scripts)等内容,方便项目管理和运行。

注意:这个文件是在运行以下命令时自动生成或修改的:

  • npm create vite@latest analyse-ui -- --template vue:创建项目时生成初始版本

  • npm install vue vue-router element-plus axios echarts:安装依赖时更新了 dependencies

{
 "name": "analyse-ui",                     // 项目名称
 "private": true,                          // 私有项目,不允许发布到 npm
 "version": "0.0.0",                      // 项目版本号
 "type": "module",                        // 使用 ES6 模块(import/export)

 "scripts": {                              // 自定义命令
   "dev": "vite",                          // 启动开发服务器(npm run dev)
   "build": "vite build",                  // 打包生产环境代码(npm run build)
   "preview": "vite preview"               // 预览打包结果(npm run preview)
 },

 "dependencies": {                         // 正式环境依赖(项目运行需要)
   "axios": "^1.9.0",                      // HTTP 请求库
   "echarts": "^5.6.0",                    // ECharts 图表库
   "element-plus": "^2.9.9",               // UI 组件库
   "vue": "^3.5.13",                       // Vue 核心库
   "vue-router": "^4.5.1"                  // Vue 路由(页面跳转)
 },

 "devDependencies": {                      // 开发环境依赖(开发、打包用)
   "@vitejs/plugin-vue": "^5.2.2",         // 支持 Vue 单文件组件(.vue 文件)
   "vite": "^6.3.1"                        // Vite 构建工具
 }
}


(5)编辑vite.config.js

在创建的 Vue 项目中,打开并编辑 vite.config.js 文件,在 return 方法中配置以下内容:

  1. 启用 Vue 插件(支持 .vue 文件);

  2. 设置 路径别名(用 @ 表示 src 目录,方便引用组件和模块);

  3. 配置 Vite 开发服务器,包括自动打开浏览器、允许跨域请求(CORS)、并设置 代理 proxy(把前端以 /api 开头的请求转发到后端 http://localhost:8080,解决前后端跨域问题)。

// 引入 Vite 的配置方法(写配置时更方便、有智能提示)
import { defineConfig } from "vite";
// 引入 Vue 插件(让 Vite 能识别 .vue 文件)
import vue from "@vitejs/plugin-vue";
// 引入 Node.js 的路径工具(方便写路径别名)
import path from "path";

// Vite 官方文档: https://vite.dev/config/

// 导出 Vite 配置(默认写法)
export default defineConfig((mode, command) => {
 return {
   // 1. 插件:使用 Vue,让 Vite 支持 .vue 文件
   plugins: [vue()],

   // 2.路径设置
   resolve: {
     // 设置路径别名
     alias: {
       // ~ 代表项目根目录(./ 就是根目录),__dirname:当前配置文件所在的目录(vite.config.js 所在位置)。
       "~": path.resolve(__dirname, "./"),
       // @ 代表 src 目录
       "@": path.resolve(__dirname, "./src"),
     },
     // 导入文件时,这些后缀可以省略
     extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"],
   },

   // 3.开发服务器设置
   server: {
     host: true,     // 允许局域网(别人的电脑)访问你的项目
     open: true,     // 启动项目后自动打开浏览器
     cors: true,     // 允许不同地址的网页来请求项目(解决“跨域”问题)
     // 4.代理(proxy),解决前后端分离的跨域问题
     proxy: {
       // 如果请求路径以 /api 开头,就启用代理
       "/api": {
         // 把请求转发到 Spring Boot 的地址(localhost:8080)
         target: "http://localhost:8080",
         // 把请求头里的来源(Origin)改成后端服务器的地址(必须写,否则后端可能拒绝)
         changeOrigin: true,
         // 把路径里的 /api 去掉(路径重写)
         // 举例:如果前端请求 /api/hive ,实际转发给后端的路径变成 /hive
         rewrite: (p) => p.replace(/^\/api/, ""),
       },
     },
   },
 };
});

(6)创建 routerutilsviews 目录

在src下创建目录结构router , utils, views,分别用于存放路由、工具类和页面


(7)整理资源:将 imagesstyleschina.json 放入 assets

images 文件夹styles文件夹以及china.json文件放入项目中的assets文件夹

index.css:给全站的网页套上统一的“衣服”,定好颜色、大小、边框。在main.js引入

normalize.css:让不同浏览器(Chrome、Firefox、IE、Safari)默认样式一致,开发更省心。

bg_header.png:可视化页面的标题背景图片

china.json:各个省份的形状坐标信息,让 ECharts 知道如何在页面上画出中国地图。


(8)修改 index.html

作用: 提供 Vue 应用的网页壳子(HTML 外框),浏览器最先加载这个页面

目的:

  1. 创建一个 <div id="app">,作为 Vue 应用的挂载点(Vue 后续会把页面内容渲染在这里)

  2. 引入 main.js,启动 Vue 应用

总结: 浏览器 → 打开 index.html → 创建空页面 → 加载 main.js → Vue 渲染页面内容

<!doctype html>
<html lang="en">
 <head>
   <meta charset="UTF-8" />
   <!-- 网站图标 路径:public/vite.svg -->
   <link rel="icon" type="image/svg+xml" href="/vite.svg" />
   <!--
   viewport(视口)设置:让网页适应不同设备(电脑、手机、平板)的屏幕宽度
   作用:
   1. width=device-width    → 页面宽度 = 设备屏幕的宽度(自动适配)
   2. initial-scale=1.0    → 初始缩放比例 = 100%(不放大、不缩小)
   如果不写这行:手机浏览器默认网页宽度会变成 980px,页面内容会很小,需要手动放大
   -->
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <!-- 网页的标题(浏览器标签上显示) -->
   <title> 数据大屏 </title>
 </head>

 <body>
   <!-- Vue 应用的挂载点,Vue 会把 App.vue 的内容渲染到这个<div>里 -->
   <div id="app"></div>
   <!-- ==============================
        加载 main.js(Vue 应用的入口文件)
        1. 启动 Vue 应用
        2. 渲染页面内容
        ============================== -->
   <script type="module" src="/src/main.js"></script>
 </body>
</html>


(9)在utils文件夹内创建request.js文件

封装一个 axios 实例,统一配置请求规则(路径、超时、参数格式)和响应处理,使所有 Vue 组件可以方便、安全地发送 HTTP 请求,并统一管理返回数据和错误。

功能说明
1. 导入 axios使用 axios 库发送 HTTP 请求。
2. 设置请求头默认使用 JSON 格式,避免每次手动设置。
3. 创建 axios 实例统一配置所有请求:• 基础路径 baseURL(自动支持不同环境)• 超时时间 20 秒• 参数序列化(支持数组)
4. 响应拦截器统一处理响应:• 成功:提取 res.data 返回• 失败:返回错误,方便页面显示提示
5. 导出实例页面和组件导入 request,直接发请求,不用重复配置 axios。

内容如下:

// ==============================
// axios 封装(request.js)
// ==============================

// 1. 导入 axios 库,用来向后端发送 HTTP 请求(GET、POST 等)
import axios from "axios";

// 2. 设置默认的请求头
// 告诉后端:
// - 我们发送的数据格式是 JSON
// - 使用 utf-8 编码
axios.defaults.headers["Content-Type"] = "application/json;charset=utf-8";

// 3. 创建一个 axios 实例(以后所有请求都会用这个配置)
const service = axios.create({

    // ------------------------------
    // baseURL:axios 会自动拼接到每个请求前面
    // - 比如 request.get('/hive') 实际请求的是:baseURL + '/hive'
    // - 默认值为 "/api",支持通过 .env 中的 VITE_APP_BASE_API 自定义
    // - 开发环境下若配置了 Vite 代理,会将该前缀请求转发到后端,避免跨域
    // - 例如:请求 /api/hive 时,Vite 会将其转发到后端 http://localhost:8080/hive,从而避免跨域。
    baseURL: import.meta.env.VITE_APP_BASE_API || "/api",

    // 请求超时时间(单位:毫秒)
    // 如果 20 秒内没有响应,自动停止请求
    timeout: 20000,
});

// ==============================
// 4. 响应拦截器(统一处理后端返回的数据)
// ==============================
service.interceptors.response.use(
    // 如果请求成功(状态码是 200)
    (res) => {
        if (res.status === 200) {
            // 把后端返回的数据(res.data)交给调用它的 Vue 页面使用
            return Promise.resolve(res.data);
        } else {
            // 其他状态(比如 404、500)可以根据需要再添加处理逻辑
        }
    },

    // 如果请求失败(比如服务器出错或断网)
    (err) => {
        // 把错误信息返回,方便前端页面处理(比如弹出错误提示)
        return Promise.reject(err);
    }

);

// ==============================
// 5. 导出 axios 实例
// 以后 Vue 的各个组件可以通过这个实例发请求
// ==============================
export default service;


(10)在router文件夹内创建index.js文件

路由入口文件,决定当前网址应该显示哪个页面

作用:

  1. 当用户访问网站的网址 "/"(根目录)时,自动显示 index.vue 页面。

  2. 使用 Vue Router,让用户点击不同的菜单或链接时,切换到不同的页面。

  3. 每次换页面时,自动把滚动条移动到页面最上方。

内容如下:

// ==============================
// Vue Router 配置(router/index.js)
// ==============================

// 从 vue-router 中导入两个方法:
// - createWebHistory:让网址更美观(没有 # 号)
// - createRouter:创建路由对象
import { createWebHistory, createRouter } from "vue-router";

// 1. 定义路由:告诉 Vue 当用户访问某个网址时,应该显示哪个页面组件
// constantRoutes 是一个数组,里面存的是“路径” 和 “页面” 的对应关系
const constantRoutes = [
 {
   // 表示访问网站的根路径 "/",也就是首页
   // 比如用户在浏览器访问:http://localhost:5173/,即访问的是 "/",就执行下面这个组件index.vue
   path: "/",
   // 表示访问这个路径时,加载下面指定的页面组件
   // 这是一个懒加载写法:只有真正访问这个页面时,才去加载这个组件,减少首页加载时间
   // import("@/views/index.vue") 表示引入 src/views/index.vue 页面
   // () => import(...) 是箭头函数写法,意思是“等需要时再执行这段加载代码”
   component: () => import("@/views/index.vue")
 }
];

// 2. 创建路由对象
const router = createRouter({
   history: createWebHistory(), // 使用无 # 的网址路径,比如 http://localhost:5173/about
   routes: constantRoutes, // 使用上面定义的路由数组

   // 控制页面跳转后的滚动位置
   scrollBehavior(to, from, savedPosition) {
       if (savedPosition) {
           // 如果浏览器记录了滚动位置(比如用户按了返回)
           return savedPosition;
       } else {
           // 如果没有记录,滚动到页面顶部
           return { top: 0 };
       }
   },
});

// 3. 导出路由对象,在 main.js 里导入这个 router,把路由挂载到 Vue 应用里
export default router;


(11)修改 main.js

程序入口,启动 Vue 应用

作用:创建 Vue 应用,注册路由、UI 组件,加载全局样式,并把主页面 App.vue 渲染到网页上,让整个 Vue 前端程序运行起来

内容如下

// ========================
// main.js (Vue 3 项目的入口文件)
// ========================

// 1. 导入项目需要用到的库和文件
// 从 Vue 框架导入 createApp 方法,用于创建 Vue 应用
import { createApp } from "vue";
// 导入全局的 CSS 样式(让所有页面有统一的基础样式)
import "./assets/styles/index.css";
// 导入路由配置(router/index.js)
// 作用:告诉 Vue,当网址变化时,应该显示哪个页面
import router from "./router";
// 导入 Element Plus 框架的默认 CSS 样式(让 Element Plus 的组件有样式)
// 规则:如果 import 的路径不是 ./ 或 ../ 开头,默认就去 node_modules 里面找。
import "element-plus/dist/index.css";
// 导入 Element Plus 框架(一个 Vue 的 UI 组件库,包含按钮、表格等现成组件)
import ElementPlus from "element-plus";
// 导入 Element Plus 的中文语言包
import zhCn from "element-plus/es/locale/lang/zh-cn";
// 导入主页面组件 App.vue(相当于网页的“骨架”或“母版”)
import App from "./App.vue";

// 2.创建 Vue 应用,并指定根组件 App.vue 作为页面的起点。
const app = createApp(App);

// 3.启动路由功能(把创建好的路由系统(Vue Router实例)装到 Vue 应用中,让页面支持路由跳转、<router-view />等功能。)
app.use(router);

// 4.注册 Element Plus 组件库 ,并指定语言为中文
app.use(ElementPlus, {
 locale: zhCn,
});

// 5.把 Vue 应用挂(显示)到网页
// 把 Vue 应用挂载到 HTML(index.html) 文件里的 <div id="app"></div>
app.mount("#app");


(12)编写 App.vue

App.vue (整个网站的“框架页面”)

目的:

  1. 定义网站的主页面结构。

  2. <router-view /> 显示不同的子页面。

  3. 可以写全局样式,只对这个页面生效。

文件作用
index.html提供给浏览器的网页框架(Vue 会挂载进去)
App.vueVue 应用的主框架(所有页面都显示在 router-view)

index.html → App.vue → 页面」的简单流程图:

[浏览器加载 index.html]          │          ▼ ┌───────────────────────────────┐ |          index.html                                                        | | ┌───────────────────────────┐     | | |     <div id="app"></div>                       |      |   👈 只提供一个空盒子 | └───────────────────────────┘     | └───────────────────────────────┘          │          ▼ [main.js 创建 Vue 应用,并把 App.vue 放到 #app]          │          ▼ ┌───────────────────────────────┐ |          App.vue                                                             | | ┌───────────────────────────┐     | | |     <router-view />                                   |     |  👈 占位符:根据网址切换页面 | └───────────────────────────┘     | └───────────────────────────────┘          │          ▼ [根据 router/index.js 的路径规则] (如果网址是 /)          │          ▼ ┌───────────────────────────────┐ |          index.vue                                                          | | ┌───────────────────────────┐     | | | 页面排版,加载各种组件                               |      | | | ChartPayType / ChartMap ...                       |      | | └───────────────────────────┘     | └───────────────────────────────┘

总结:

  • index.html 负责告诉浏览器“这里有个盒子”(<div id="app">),

  • App.vue 把整个 Vue 应用放进这个盒子里,并用 <router-view> 准备切换页面,

  • 根据网址,比如 /,Vue Router 加载 index.vue 页面,页面里放各种图表组件。

<!-- ========== 模板部分:页面的结构和内容 ========== -->
<template>
   <!-- <router-view /> 是 Vue Router 官方内置的组件,专门负责“显示当前路径匹配到的页面”。 -->
   <!-- <router-view> 它是一个“页面展示区域”,当前路由匹配到哪个组件,就会自动渲染到这个位置。 -->
   <!-- 根据当前的网址(路径),Vue 会自动【根据Router里index.js配置的路径对应的页面组件】显示在这里 -->
   <!-- 比如访问 /,就会加载并显示 /views/index.vue 页面 -->
   <router-view />
</template>

<!-- ========== 脚本部分(JS逻辑) ========== -->
<script setup>
// 导入 defineOptions 方法(Vue 3 已提供,故无需导入)
// import { defineOptions } from "vue";
// 设置组件名字为 "App",(方便开发者工具中识别)
defineOptions({
 name: "App",
});
</script>

<!-- ========== 样式部分:CSS样式,只对这个组件生效 ========== -->
<style scoped>
/* ============================
这里可以写 App.vue 页面专用的 CSS 样式
scoped 表示这些样式只影响当前页面
============================= */
</style>


(13)编写Index.vue 主页面

任务:views目录中创建Index.vue ,实现把多个数据图表和表格组合在一个页面上,形成完整的可视化数据看板(仪表盘 Dashboard)。

功能:

  • 作为首页/主页面,把所有的图表、表格组件按照合理布局排列展示。

  • 管理页面的排版(用 Element Plus 的栅格系统 el-row / el-col)。

  • 负责加载各个图表和表格的 Vue 组件。

  • 父组件 Index.vue 不再包含复杂逻辑,只做布局。 Index.vue 只负责排版和导入组件。

渲染流程:

浏览器打开首页(http://localhost:5173/)
   ↓
根据路由,加载 Index.vue
   ↓
Index.vue 组织页面布局(3 列,8 个组件)
   ↓
依次加载每个组件(HeaderTitle、ChartPayType、ChartArea ...)
   ↓
每个组件内部:请求数据 → 渲染 ECharts 或表格
   ↓
用户看到完整的数据可视化看板

完整代码:

<!-- ========== 模板部分(页面结构) ========== -->
<template>
 <div class="container">
   <!-- ========== 1. 页面顶部标题组件 ========== -->
   <!-- 在页面中使用自定义组件HeaderTitle -->  
   <HeaderTitle />  
   <!-- ========== 2. 页面主体内容 ========== -->
   <div class="main">
     <!-- 使用 Element Plus 的栅格系统,分成 3 列 -->
     <el-row :gutter="20">  <!-- gutter=20 表示列之间有 20 像素的间距 -->
       <!-- ========== 第一列(宽度 8/24) ========== -->
       <el-col :span="8">
         <ChartPayType />     <!-- 支付方式图表,即“把 ChartPayType.vue 里的内容显示在这个位置”。 -->
         <ChartArea />        <!-- 地区销售图表 -->
         <TableOrderRanking /><!-- 订单销售排行表格 -->
       </el-col>
       <!-- ========== 第二列(宽度 8/24) ========== -->
       <el-col :span="8">
         <ChartMap />             <!-- 城市订单地图 -->
         <TableUserRanking />     <!-- 用户购买排行表格 -->
       </el-col>
       <!-- ========== 第三列(宽度 8/24) ========== -->
       <el-col :span="8">
         <ChartDayOrder />        <!-- 每天订单数量折线图 -->
         <ChartUserCast />        <!-- 用户消费分析 -->
         <ChartProductSale />     <!-- 产品销售额对比图 -->
       </el-col>
     </el-row>
   </div>
 </div>
</template>

<!-- ========== 脚本部分(逻辑代码) ========== -->
<script setup>
// ==================== 1. 导入需要用的组件 ====================

// 页面顶部标题
import HeaderTitle from '@/components/HeaderTitle.vue';

// 第一列的图表和表格
import ChartPayType from '@/components/ChartPayType.vue';
import ChartArea from '@/components/ChartArea.vue';
import TableOrderRanking from '@/components/TableOrderRanking.vue';

// 第二列的图表和表格
import ChartMap from '@/components/ChartMap.vue';
import TableUserRanking from '@/components/TableUserRanking.vue';

// 第三列的图表
import ChartDayOrder from '@/components/ChartDayOrder.vue';
import ChartUserCast from '@/components/ChartUserCast.vue';
import ChartProductSale from '@/components/ChartProductSale.vue';
</script>

<!-- ========== 样式部分 ========== -->
<style scoped>
/* ==================== 页面主体内容的样式 ==================== */
.main {
 padding: 16px 4px;    /* 内容内边距:上下 16px,左右 4px */
 overflow: hidden;     /* 防止内容溢出 */
}
</style>


(14)编写统一样式 chart-style.css

任务:src/assets/styles/ 文件夹中编写 chart-style.css给所有图表和表格组件提供统一的外观样式,避免每个组件重复写 CSS。

功能:

  • 所有图表组件表格组件都引入 chart-style.css。只需在 src/assets/styles//chart-style.css 中维护样式。

  • 统一每个图表和表格的整体布局、标题样式、内容区样式。

  • 保证不同组件的外观风格一致,提高页面的美观性和一致性。

渲染流程:

每个组件加载时
   ↓
组件的 <style> 区域导入 chart-style.css
   ↓
chart-container 控制整个容器的边框和阴影
   ↓
chart-title 控制标题样式
   ↓
chart 控制图表绘制区域
   ↓
el-table 的表头样式被统一修改

完整代码:

/* ==================== 图表整体容器样式 ==================== */
.chart-container {
   position: relative;  /* 方便标题绝对定位 */
   box-shadow:
       -10px 0px 15px #034c6a inset,   /* 左侧内阴影 */
       0px -10px 15px #034c6a inset,   /* 底部内阴影 */
       10px 0px 15px #034c6a inset,    /* 右侧内阴影 */
       0px 10px 15px #034c6a inset;    /* 顶部内阴影 */
   border: 1px solid #034c6a;          /* 蓝色边框 */
   margin-bottom: 24px;                /* 底部间距 */
}

/* ==================== 图表标题样式 ==================== */
.chart-title {
   position: absolute;
   top: -15px;               /* 向上偏移 */
   left: 50%;                /* 水平 50% */
   width: 60%;
   height: 35px;
   line-height: 35px;        /* 垂直居中 */
   text-align: center;
   border-radius: 18px;
   background-color: #034c6a;
   color: #ffffff;
   font-weight: bold;
   transform: translateX(-50%);  /* 左移一半实现居中 */
}

/* ==================== 图表区域样式 ==================== */
.chart {
   width: 100%;
   min-height: 200px; /* 设置最小高度,防止被覆盖 */
}

/*********** 以下是覆盖和美化 Element Plus 的默认样式 ************/
/* ==================== 1.表头样式 ==================== */
:deep(.el-table__header) {
   background-color: #034c6a !important;  /* 表头背景 */
   color: #fff !important;                /* 表头文字白色 */
   font-size: 14px;                       /* 表头字体 */
}

/* ==================== 2.表格整体样式 ==================== */
:deep(.el-table) {
   --el-table-bg-color: transparent !important;        /* 表格主体背景透明 */
   --el-table-header-bg-color: #034c6a !important;     /* 表头背景 */
   --el-table-tr-bg-color: transparent !important;     /* 行背景透明 */
   --el-table-row-hover-bg-color: #02364e !important;  /* 悬停行背景色 */
   color: #fff;                                        /* 字体颜色白色 */
   border-color: #034c6a;                              /* 边框颜色 */
}

/* ==================== 3.表格行底部边框 ==================== */
:deep(.el-table td) {
   border-bottom: 1px solid #034c6a;
}

/* ==================== 4.表格单元格字体 ==================== */
:deep(.el-table .cell) {
   color: #fff;
   font-size: 14px;
   line-height: 20px;
}

/* ==================== 5.表格中悬停行字体颜色保持不变 ==================== */
:deep(.el-table .el-table__body tr:hover td) {
   color: #fff;
}

/* ==================== 6.表格滚动条样式(美化 Element Plus 的滚动区域) ==================== */
/* 设置滚动条宽度(横向或纵向) */
:deep(.el-scrollbar__wrap)::-webkit-scrollbar {
   width: 8px;
}
/* 设置滚动条滑块的颜色和圆角样式 */
:deep(.el-scrollbar__wrap)::-webkit-scrollbar-thumb {
   background-color: #034c6a; /* 滑块颜色:深蓝色 */
   border-radius: 4px;        /* 滑块圆角 */
}


(15)编写 主页面显示文件

以下 vue 文件皆在 components  (组件)文件夹是创建

1. HeaderTitle.vue(标题)

任务:显示网页的头部标题

功能:显示一个居中的页面标题“数据可视化”,带背景图片,用于美观和页面分区,作为可复用的头部组件。

完整代码:

<!-- ========== 模板部分:页面的结构和内容 ========== -->
<template>
 <!-- 整个头部区域的包裹容器 -->
 <div class="header-wrapper">
   <!-- 实际显示标题的区域 -->
   <div class="header">数据可视化</div>
 </div>
</template>

<!-- ========== 脚本部分(JS逻辑) ========== -->
<script setup>
// 当前这个组件不需要逻辑代码,所以这里是空的
</script>

<!-- ========== 样式部分:CSS样式,只对这个组件生效 ========== -->
<style scoped>
/* header-wrapper:整个头部区域的样式 */
.header-wrapper {
 width: 100%;                 /* 占满整个宽度 */
 height: 80px;                /* 高度 80 像素 */
 display: flex;               /* 使用flex布局 */
 justify-content: center;     /* 内容水平居中 */
}

/* header:具体的标题区域 */
.header {
 width: 30%;                  /* 宽度占页面的30% */
 background-image: url("@/assets/images/bg_header.png"); /* 背景图片路径 */
 background-repeat: no-repeat;     /* 背景图片不重复 */
 background-size: 100% 100%;       /* 背景图片拉伸填满整个区域 */
 color: #fff;                 /* 字体颜色白色 */
 text-align: center;          /* 文字居中 */
 font-size: 30px;             /* 字体大小 30 像素 */
 line-height: 60px;           /* 文字的行高,保证垂直居中 */
}
</style>

2. ChartPayType.vue(左上:支付方式柱状图)

任务:显示一个柱状图,统计不同支付方式的销售总额,例如:信用卡、借记卡等。

功能:从后端 /index 接口获取支付方式的销售数据,使用 ECharts 把数据绘制成柱状图,并显示在页面上。

渲染流程:

用户打开网页

页面加载,Vue 执行 onMounted

向后端请求支付方式数据(request.get('/hive'))

拿到数据:xData = 支付方式,yData = 总金额

ECharts 初始化图表

把 xData 和 yData 放入图表

图表渲染在 <div ref="chartRef"> 上

完整代码:

<!-- ========== 模板部分(页面结构) ========== -->
<template>
 <div class="chart-container">
   <!-- 标题:各种支付方式的使用情况 -->
   <div class="chart-title">各种支付方式的使用情况</div>
   <!-- 图表容器(ECharts 将挂载在这个 <div> 上) -->
   <!-- 这是图表要挂载的位置,我们给它取名叫 chartRef,方便后面用 JavaScript 操作它 -->
   <!-- 告诉 Vue 把这个 DOM 元素挂到 JS 里的 chartRef.value 上 -->  
   <div ref="chartRef" class="chart"></div>
 </div>
</template>

<!-- ========== 脚本部分(逻辑代码) ========== -->
<script setup>
// ==================== 1. 导入需要用的功能 ====================    
// 导入 Vue 的 ref(响应式变量) 和 onMounted(生命周期钩子)
import { ref, onMounted } from 'vue';
// 引入请求工具(封装好的 axios 实例)
import request from '@/utils/request';
// 导入 echarts 库
import * as echarts from 'echarts';

// ==================== 2. 定义变量 ====================
// 使用 ref() 创建一个响应式 DOM 引用,用于获取 <div> 元素,供 ECharts 挂载使用
const chartRef = ref();

// 定义图表的字体颜色(白色)
const textStyle = { color: '#fff' };

// ==================== 3. 页面加载完成后执行 ====================    
// 当组件挂载完成后(包括 DOM 渲染),执行初始化逻辑(如获取数据、渲染图表)
// 即:等页面上的内容准备好以后,再执行一些代码(比如画图、请求数据)。    
onMounted(() => {

 // ========== 3.1 请求后端数据 ==========  
 // 通过 axios 向 /hive 接口请求后端数据( /hive 返回支付方式的数据)
 request.get('/hive').then(res => {

   // ========== 3.2 拿到数据并处理 ==========  
   // 获取后端返回的数据,若为空则用空数组兜底,防止 map 报错
   const data = res.payTypeData || [];

   // 准备 x 轴和 y 轴的数据
   const xData = data.map(d => d.payType);  // 数组:支付方式名称
   const yData = data.map(d => d.total);    // 数组:每种支付方式的总金额

   // ========== 3.3 初始化 ECharts 实例 ==========  
   // 在 chartRef 指向的 DOM 上初始化 ECharts 图表实例
   const chart = echarts.init(chartRef.value);

   // ========== 3.4 设置图表配置并渲染 ==========  
   // 设置图表的配置项(option)
   chart.setOption({
     textStyle,  // 设置字体颜色

     // 鼠标悬停时显示数据提示框(tooltip 配置)
     tooltip: {
       trigger: 'axis', // 当鼠标悬停在 x 轴的柱子上触发
       axisPointer: {
         type: 'shadow'  // 使用阴影效果
       },
       // 悬停提示框的内容格式
       formatter: (params) => {
         const item = params[0];  // 获取当前柱子的悬停信息
         return `
           支付方式:${item.name}<br/>
           总金额:${item.value}
         `;
       }
     },

     grid: {
        left: '18%',        // 左边距留足,确保 Y 轴数值不被截断,图表整体不靠左
      //  right: '10%',       // 右边距留一点空白,避免柱子或图形贴边
      //  bottom: '10%',      // 底部留白,用于 X 轴标签或图例显示
      //  top: '15%',         // 顶部留白,防止标题或 tooltip 遮挡图表
      //  containLabel: true  // 自动调整图表区域以包含坐标轴的标签(防止文字溢出)
     },

     // x 轴配置
     xAxis: {
       type: 'category', //  x 轴类型:类目轴。常用于显示文本,比如:支付方式名称
       data: xData       //  x 轴的数据来源:xData(收集的所有支付方式名称)
     },

     // y 轴配置
     yAxis: {
       type: 'value'   // y 轴类型:数值轴。用来展示金额数值
     },

     // 数据系列配置
     series: [
       {
         data: yData, // 每个支付方式的金额,决定柱子的高度(比如信用卡支付了多少金额)
         type: 'bar'  // 柱状图
       }
     ]
   });
 });
});
</script>

<!-- ========== 样式部分:CSS样式,只对这个组件生效 ========== -->
<style scoped>
/* 引入图表通用样式(控制图表宽高、颜色、字体等) */
@import '../assets/styles/chart-style.css';
</style>


注意

不同的接口:

  • /index:支付方式、地域数据。

  • /clickhouse:订单、用户消费、商品销售数据。

  • /hbase:表格数据。

  • /redis:地图数据。



3. ChartArea.vue(左中:地域销售柱状图)

任务:显示一个柱状图,统计不同地域(省份、城市等)的销售总额,例如:北京、上海、广州等。

功能:

  • 从后端 /index 接口获取地域销售数据。

  • 通过 ECharts 把数据绘制成柱状图,展示在页面上。

  • 鼠标悬停时,显示对应地域的销售额。

渲染流程:

csharp复制编辑用户打开网页
   ↓
页面加载,Vue 执行 onMounted
   ↓
向后端请求地域销售数据(request.get('/hive'))
   ↓
拿到数据:xData = 地区名称,yData = 销售总额
   ↓
ECharts 初始化图表
   ↓
把 xData 和 yData 放入图表
   ↓
图表渲染在 <div ref="chartRef"> 上

完整代码:

<!-- ========== 模板部分(页面结构) ========== -->
<template>
 <div class="chart-container">
   <!-- 标题:不同地域的销售情况 -->
   <div class="chart-title">不同地域的销售情况</div>
   <!-- 图表的挂载容器,后面用 ECharts 渲染到这个 div 上 -->
   <!-- 页面上放图表的 <div>,ref="chartRef" 是 Vue 的固定写法,用来让 JavaScript 找到这个 <div> -->
   <div ref="chartRef" class="chart"></div>
 </div>
</template>

<!-- ========== 脚本部分(逻辑代码) ========== -->
<script setup>
// ==================== 1. 导入需要用的功能 ====================    
// 导入 Vue 的 ref(响应式变量) 和 onMounted(生命周期钩子)
import { ref, onMounted } from 'vue';
// 导入封装好的请求方法
import request from '@/utils/request';
// 导入 echarts 库
import * as echarts from 'echarts';

// ==================== 2. 定义变量 ====================
// 使用 Vue 的 ref() 创建一个“响应式引用”,用来获取页面上的 <div>,方便后面 ECharts 在这个 <div> 上绘图
const chartRef = ref();

// 定义图表的字体颜色(白色)
const textStyle = { color: '#fff' };

// ==================== 3. 页面加载完成后执行 ====================    
// 当页面组件挂载(包括 chartRef 对应的 <div>)完成时执行(类似网页加载完成)
// 即:等页面上的内容准备好以后,再执行一些代码(比如画图、请求数据)。    
onMounted(() => {

 // ========== 3.1 请求后端数据 ==========  
 // 通过 axios 向 /index 接口请求后端数据( /index 返回地区销售数据)
 request.get('/hive').then(res => {

   // ========== 3.2 拿到数据并处理 ==========  
   // 获取数据,没有就用空数组(防止报错)
   const data = res.areaData || [];

   // 准备 x 轴和 y 轴 的数据
   const xData = data.map(d => d.statename);  // 地区名称
   const yData = data.map(d => d.total);      // 每个地区的销售总额

   // ========== 3.3 初始化 ECharts 实例 ==========  
   // 初始化 echarts 实例,绑定到 chartRef 所指的 DOM 元素
   const chart = echarts.init(chartRef.value);

   // ========== 3.4 设置图表配置并渲染 ==========  
   // 设置图表的配置项(option)
   chart.setOption({
     textStyle,  // 设置字体颜色

     // ========== 设置鼠标悬停提示框 ==========
     tooltip: {
       trigger: 'axis', // 当鼠标悬停在 x 轴的柱子上触发
       axisPointer: {
         type: 'shadow'  // 使用阴影效果
       },
       // 悬停提示框的内容格式
       formatter: (params) => {
         const item = params[0];  // 获取当前柱子的悬停信息
         return `
           地域:${item.name}<br/>
           销售总额:${item.value}
         `;
       }
     },

     // ========== x 轴配置 ==========
     xAxis: {
       type: 'category', // x 轴类型:类目轴。常用于显示文本,比如:地区名称
       data: xData       // x 轴的数据来源:xData(收集的所有地区名称)
     },

     // ========== y 轴配置 ==========
     yAxis: {
       type: 'value'   // y 轴类型:数值轴。用来展示金额数值
     },

     // ========== 数据系列配置 ==========
     series: [
       {
         data: yData, // 每个地区的销售额,决定柱子的高度
         type: 'bar'  // 柱状图
       }
     ]
   });
 });
});
</script>

<!-- ========== 样式部分 ========== -->
<style scoped>
/* 引入公共样式文件 */
@import '../assets/styles/chart-style.css';
</style>


4. TableOrderRanking.vue(左下:订单销售排行表格)

任务:显示一个表格,列出商品的销售次数排行,例如:哪些商品卖得最多。

功能:从后端 /hbase 接口获取商品销售数据,用 Element Plus 的表格组件 el-table 显示在页面上。

渲染流程:

用户打开网页

页面加载,Vue 执行 onMounted

向后端请求订单排行数据(request.get('/hbase'))

拿到数据,赋值给 tableData

Element Plus 的 el-table 渲染表格

第一列显示:商品名
第二列显示:销售次数

完整代码:

<!-- ========== 模板部分(页面结构) ========== -->
<template>
 <div class="chart-container" style="padding: 40px;">
   <!-- 标题:订单销售排行 -->
   <div class="chart-title">订单销售排行</div>
   <!-- ========== 表格(使用 Element Plus 的 el-table 组件) ========== -->
   <!-- 表格的数据来自 tableData -->
   <el-table :data="tableData" height="200" size="small">
     <!-- 第一列:商品名 -->
     <el-table-column label="商品" prop="key"></el-table-column>
     <!-- 第二列:销售次数 -->
     <el-table-column label="次数" prop="value" width="100"></el-table-column>
   </el-table>
 </div>
</template>

<!-- ========== 脚本部分(逻辑代码) ========== -->
<script setup>
// ==================== 1. 导入需要用的功能 ====================
// 导入 Vue 的 ref(响应式变量) 和 onMounted(生命周期钩子)
import { ref, onMounted } from 'vue';
// 导入封装好的请求方法
import request from '@/utils/request';

// ==================== 2. 定义变量 ====================
// 表格的数据(响应式数组),初始为空
const tableData = ref([]);

// ==================== 3. 页面加载完成后执行 ====================
// 当页面组件挂载完成时执行
onMounted(() => {
 // ========== 3.1 请求后端数据 ==========
 // 向后端的 /hbase 接口请求订单排行数据
 request.get('/hbase').then(res => {
   // ========== 3.2 拿到数据并赋值给表格 ==========
   // 从返回结果中取出 orderData,如果没有数据就用空数组
   tableData.value = res.orderData || [];
 });
});

</script>
<!-- ========== 样式部分 ========== -->
<style scoped>
/* 引入公共样式文件 */
@import '../assets/styles/chart-style.css';
</style>

5. ChartMap.vue(中上:中国地图)

任务: 显示一个中国地图,不同城市的订单数量用不同深浅的颜色标识。

功能: 从后端 /redis 接口获取各城市的订单数量,使用 ECharts 的地图(Map)类型图表,显示在中国地图上,支持自动每 3 秒更新一次数据

渲染流程:

用户打开网页

页面加载,Vue 执行 onMounted

执行 updateMap 函数

向后端请求城市订单数据(request.get('/redis'))

拿到数据,整理为 [{ name: 城市名, value: 订单数量 }]

计算数据里的最大值,设置视觉映射颜色范围

如果第一次加载:创建 ECharts 图表实例
如果已经存在:复用 ECharts 图表

设置地图的配置(option)

ECharts 把数据渲染成中国地图

每隔 3 秒自动再次请求,更新地图数据

完整代码:

<!-- ========== 模板部分(页面结构) ========== -->
<template>
 <div class="chart-container">
   <!-- 标题:城市订单量 -->
   <div class="chart-title">城市订单量</div>
   <!-- 图表的挂载容器,后面用 ECharts 把地图绘制到这个 <div> 上 -->
   <!-- 页面上放图表的 <div>,ref="chartRef" 是 Vue 的固定写法,用来让 JavaScript 找到这个 <div> -->
   <div ref="chartRef" class="chart" style="height: 430px;"></div>
 </div>
</template>

<!-- ========== 脚本部分(逻辑代码) ========== -->
<script setup>
// ==================== 1. 导入需要用的功能 ====================

// 导入 Vue 的 ref(响应式变量) 和 onMounted(生命周期钩子)
import { ref, onMounted } from 'vue';
// 导入封装好的请求方法
import request from '@/utils/request';
// 导入 echarts 库
import * as echarts from 'echarts';
// 导入中国地图数据(GeoJSON 格式)
import chinaJson from '@/assets/china.json';

// ==================== 2. 定义变量 ====================

// 使用 Vue 的 ref() 创建一个“响应式引用”,用来获取页面上的 <div>,方便后面 ECharts 在这个 <div> 上绘图
const chartRef = ref();
// 定义图表的字体颜色(白色)
const textStyle = { color: '#fff' };
// 声明一个变量保存 ECharts 实例,防止3秒更新数据时,重复初始化造成报错或内存泄漏
let chart;

// ==================== 3. 注册地图(只需一次) ====================
// 注册中国地图数据,后面 ECharts 的 series.map 要用这个名称
// 这个步骤只做一次:echarts.registerMap(名字, 数据)
echarts.registerMap('china', chinaJson);

// ==================== 4. 页面加载完成后执行 ====================
// 当页面组件挂载(包括 chartRef 对应的 <div>)完成时执行
onMounted(() => {
 // ========== 4.1 定义更新地图的方法 ==========
 const updateMap = () => {
     // ========== 4.1.1 请求后端数据 ==========  
     // 向后端的 /redis 接口请求城市订单数据
     request.get('/redis').then(res => {
     // ========== 4.1.2 处理数据 ==========  
     const data = res.data || [];
     // 把后端数据转换成 ECharts 需要的格式:[{ name: 城市名, value: 订单数量 }]
     // 即把原res.data的格式[{key: 城市名, value: 订单数量 }]更改为[{ name: 城市名, value: 订单数量 }]
     const formatted = data.map(d => ({
       name: d.key,                   // 把字段key改成 ECharts 识别的字段名name
       value: parseInt(d.value) || 0  // 订单数量转成数字类型
     }));
     // ========== 4.1.3 计算订单数量的最大值 ==========  
     // 计算订单数量的最大值,用于 visualMap 设置颜色映射的上限。
     // 注意:如果所有城市订单量都是 0,map() 得到的是空数组,Math.max 会返回 -Infinity,导致图表颜色失效。
     // 因此加上一个兜底值 10:即使数据全为 0,maxValue 至少也是 10,确保颜色正常显示。    
     const maxValue = Math.max(...formatted.map(item => item.value), 10); // 保底是10,防止全是 0
     // ========== 4.1.4 初始化或复用 ECharts 实例 ==========  
     // 如果 chart 已存在就复用,不存在就创建
     chart = chart || echarts.init(chartRef.value);

     // ========== 4.1.5 设置图表配置并渲染 ==========  
     chart.setOption({    
       textStyle,  // 设置字体颜色
       // ========== 鼠标悬停提示框 ==========
       tooltip: {
         trigger: 'item',  // 悬停在具体“区域”触发(这里是城市)
         formatter: (params) => {
           return `
              城市:${params.name}<br/>
              订单数量:${params.value}`;
         }
       },

       // ========== 视觉映射(颜色深浅) ==========
       visualMap: {
         min: 0,
         max: maxValue,  // 自动设置颜色的最大值
         inRange: {
           color: ['lightskyblue', 'yellow', 'orangered']  // 颜色从浅蓝到橙红
         }
       },

       // ========== 地图配置 ==========
       series: [{
         type: 'map',      // 地图类型
         map: 'china',     // 使用前面注册的 'china' 地图
         data: formatted   // 数据:每个城市的订单数量
       }]
     });

   });
 };

 // ========== 4.2 首次加载地图 ==========  
 updateMap();
 // ========== 4.3 每 3 秒自动更新一次数据 ==========  
 setInterval(updateMap, 3000);
});
</script>

<!-- ========== 样式部分 ========== -->
<style scoped>
/* 引入公共样式文件 */
@import '../assets/styles/chart-style.css';
</style>

maxValue:计算所有城市订单量中的最大值,用于后面 ECharts 的 visualMap 配置中。

下图展示了 ECharts 中 visualMap 颜色映射的原理:数值越大,颜色越深(红色),数值越小,颜色越浅(蓝色),中间值自动渐变。这样用户可以一眼看出哪座城市的订单量最多或最少

已生成图片


6. TableUserRanking.vue(中下:用户购买排行表格)

任务:显示一个表格,列出各用户的总购买销售额排行,例如:哪些用户购买金额最高。

功能:从后端 /hbase 接口获取用户购买销售额数据,用 Element Plus 的表格组件 el-table 显示在页面上,并且把金额格式化为两位小数。

渲染流程:

用户打开网页

页面加载,Vue 执行 onMounted

向后端请求用户购买数据(request.get('/hbase'))

拿到数据,赋值给 tableData

Element Plus 的 el-table 渲染表格

第一列显示:用户(实际代码 prop="key",假设 key 是用户名)
第二列显示:销售额(保留两位小数)

完整代码:

<!-- ========== 模板部分(页面结构) ========== -->
<template>
 <div class="chart-container" style="padding: 40px;">
   <!-- 标题:用户购买排行 -->
   <div class="chart-title">用户购买排行</div>
   <!-- ========== 表格(使用 Element Plus 的 el-table 组件) ========== -->
   <!-- 表格的数据来自 tableData -->
   <el-table :data="tableData" height="200" size="small">
     <!-- 第一列:商品名 -->
     <el-table-column label="商品" prop="key"></el-table-column>
     <!-- 第二列:销售额(格式化为两位小数) -->
     <el-table-column label="销售额" prop="value" width="100">
       <!-- 自定义单元格内容 -->
       <template #default="scope">
         <!-- scope.row.value 是当前这一行的数据 -->
         <!-- ?是条件判断,如果有值,保留两位小数,否则显示 0 -->
         <span>{{ scope.row.value ? parseFloat(scope.row.value).toFixed(2) : 0 }}</span>
       </template>
     </el-table-column>
   </el-table>
 </div>
</template>

<!-- ========== 脚本部分(逻辑代码) ========== -->
<script setup>
// ==================== 1. 导入需要用的功能 ====================
// 导入 Vue 的 ref(响应式变量) 和 onMounted(生命周期钩子)
import { ref, onMounted } from 'vue';
// 导入封装好的请求方法
import request from '@/utils/request';

// ==================== 2. 定义变量 ====================
// 表格的数据(响应式数组),初始为空
const tableData = ref([]);

// ==================== 3. 页面加载完成后执行 ====================
// 当页面组件挂载完成时执行
onMounted(() => {
 // ========== 3.1 请求后端数据 ==========
 // 向后端的 /hbase 接口请求销售额排行数据
 request.get('/hbase').then(res => {
   // ========== 3.2 拿到数据并赋值给表格 ==========
   // 从返回结果中取出 salesData,如果没有数据就用空数组
   tableData.value = res.salesData || [];
 });
});

</script>
<!-- ========== 样式部分 ========== -->
<style scoped>
/* 引入公共样式文件 */
@import '../assets/styles/chart-style.css';
</style>


7. ChartDayOrder.vue(右上:每天订单数量折线图)

任务: 显示一个折线图,统计每天的订单数量。

功能: 从后端 /clickhouse 接口获取每天单数量数据,使用 ECharts 绘制成折线图,并显示在页面上。

渲染流程:

用户打开网页

页面加载,Vue 执行 onMounted

向后端请求每天订单数据(request.get('/clickhouse'))

拿到数据:xData = 日期,yData = 订单数量

ECharts 初始化图表

把 xData 和 yData 放入图表

图表渲染在 <div ref="chartRef"> 上

完整代码:

<!-- ========== 模板部分(页面结构) ========== -->
<template>
 <div class="chart-container">
   <!-- 标题:每天订单数量 -->
   <div class="chart-title">每天订单数量</div>
   <!-- 图表的挂载容器,后面用 ECharts 把图表绘制到这个 <div> 上 -->
   <!-- 页面上放图表的 <div>,ref="chartRef" 是 Vue 的固定写法,用来让 JavaScript 找到这个 <div> -->
   <div ref="chartRef" class="chart"></div>
 </div>
</template>

<!-- ========== 脚本部分(逻辑代码) ========== -->
<script setup>
// ==================== 1. 导入需要用的功能 ====================

// 导入 Vue 的 ref(响应式变量) 和 onMounted(生命周期钩子)
import { ref, onMounted } from 'vue';
// 导入封装好的请求方法
import request from '@/utils/request';
// 导入 echarts 库
import * as echarts from 'echarts';

// ==================== 2. 定义变量 ====================

// 使用 Vue 的 ref() 创建一个“响应式引用”,用来获取页面上的 <div>,方便后面 ECharts 在这个 <div> 上绘图
const chartRef = ref();

// 定义图表的字体颜色(白色)
const textStyle = { color: '#fff' };

// ==================== 3. 页面加载完成后执行 ====================

// 当页面组件挂载(包括 chartRef 对应的 <div>)完成时执行(类似网页加载完成)
// 等页面上的内容准备好以后,再执行一些代码(比如画图、请求数据)
onMounted(() => {

 // ========== 3.1 请求后端数据 ==========
 // 向后端的 /clickhouse 接口请求每天的订单数量数据
 request.get('/clickhouse').then(res => {

   // ========== 3.2 拿到数据并处理 ==========
   // 获取数据,如果没有就用空数组(防止报错)
   const data = res.dayData || [];

   // 提取日期和订单数量
   const xData = data.map(item => item.orderDate);
   const yData = data.map(item => item.orderCount);

   // ========== 3.3 初始化 ECharts 实例 ==========
   // 初始化 echarts 实例,绑定到 chartRef 所指的 DOM 元素
   const chart = echarts.init(chartRef.value);

   // ========== 3.4 设置图表配置并渲染 ==========
   chart.setOption({

     textStyle,  // 设置字体颜色

     // ========== 设置鼠标悬停提示框 ==========
     tooltip: {
       trigger: 'axis', // 当鼠标悬停在 x 轴的点上触发
       axisPointer: {
         type: 'shadow'  // 使用阴影效果
       },
       // 自定义提示框内容
       formatter: (params) => {
         const item = params[0];  // 当前鼠标悬停的数据
         return `
           日期:${item.name}<br/>
           订单数量:${item.value}
         `;
       }
     },

     grid: {
       left: '15%',        // 左边距留足,确保 Y 轴数值不被截断,图表整体不靠左
       //  right: '10%',       // 右边距留一点空白,避免柱子或图形贴边
       //  bottom: '10%',      // 底部留白,用于 X 轴标签或图例显示
       //  top: '15%',         // 顶部留白,防止标题或 tooltip 遮挡图表
       //  containLabel: true  // 自动调整图表区域以包含坐标轴的标签(防止文字溢出)
     },

     // ========== x 轴配置 ==========
     xAxis: {
       type: 'category', // x 轴类型:类目轴。常用于显示文本,比如日期
       data: xData       // x 轴的数据来源:日期数组
     },

     // ========== y 轴配置 ==========
     yAxis: {
       type: 'value'   // y 轴类型:数值轴。用来展示订单数量
     },

     // ========== 添加 dataZoom(缩放滑块) ==========
     dataZoom: [
       {
         type: 'inside', // 鼠标滚轮缩放
         start: 0,       // 默认起始位置
         end: 100        // 默认结束位置(100%)
       },
       {
         start: 0,
         end: 100
         // 不写 type,ECharts 会自动在下方生成滑块
       }
     ],

     // ========== 数据系列配置 ==========
     series: [
       {
         type: 'line', // 折线图
         data: yData   // 每天的订单数量
       }
     ]
   });
 });
});
</script>

<!-- ========== 样式部分 ========== -->
<style scoped>
/* 引入公共样式文件 */
@import '../assets/styles/chart-style.css';
</style>

8. ChartUserCast.vue(右中:用户消费排行榜/销售额占比)

任务: 显示一个双柱状图,对比每个用户的总消费金额平均消费金额

功能: 从后端 /clickhouse 接口获取用户消费数据,使用 ECharts 绘制成双柱状图,展示在页面上。

渲染流程:

用户打开网页

页面加载,Vue 执行 onMounted

向后端请求用户消费数据(request.get('/clickhouse'))

拿到数据:
names = 用户名
total = 总消费金额
avg = 平均消费金额

ECharts 初始化图表

把 names、total、avg 放入图表

图表渲染在 <div ref="chartRef"> 上

完整代码:

<!-- ========== 模板部分(页面结构) ========== -->
<template>
 <div class="chart-container">
   <!-- 标题:用户消费排行榜 -->
   <div class="chart-title">用户消费排行榜</div>
   <!-- 图表的挂载容器,后面用 ECharts 把图表绘制到这个 <div> 上 -->
   <!-- 页面上放图表的 <div>,ref="chartRef" 是 Vue 的固定写法,用来让 JavaScript 找到这个 <div> -->
   <div ref="chartRef" class="chart"></div>
 </div>
</template>

<!-- ========== 脚本部分(逻辑代码) ========== -->
<script setup>
// ==================== 1. 导入需要用的功能 ====================

// 导入 Vue 的 ref(响应式变量) 和 onMounted(生命周期钩子)
import { ref, onMounted } from 'vue';
// 导入封装好的请求方法
import request from '@/utils/request';
// 导入 echarts 库
import * as echarts from 'echarts';

// ==================== 2. 定义变量 ====================

// 使用 Vue 的 ref() 创建一个“响应式引用”,用来获取页面上的 <div>,方便后面 ECharts 在这个 <div> 上绘图
const chartRef = ref();

// 定义图表的字体颜色(白色)
const textStyle = { color: '#fff' };

// ==================== 3. 页面加载完成后执行 ====================

// 当页面组件挂载(包括 chartRef 对应的 <div>)完成时执行(类似网页加载完成)
// 即:等页面上的内容准备好以后,再执行一些代码(比如画图、请求数据)
onMounted(() => {

 // ========== 3.1 请求后端数据 ==========  
 // 向后端的 /clickhouse 接口请求用户消费数据
 request.get('/clickhouse').then(res => {

   // ========== 3.2 拿到数据并处理 ==========  
   // 获取数据,如果没有就用空数组(防止报错)
   const data = res.userCastData || [];

   // 准备 x 轴(用户名)和两个 y 轴的数据(总消费金额、平均消费金额)
   const names = data.map(d => d.userId);              // 用户名
   const total = data.map(d => d.totalSpent);          // 总消费金额
   const avg = data.map(d => d.avgSpentPerOrder);      // 平均消费金额

   // ========== 3.3 初始化 ECharts 实例 ==========  
   // 初始化 echarts 实例,绑定到 chartRef 所指的 DOM 元素
   const chart = echarts.init(chartRef.value);

   // ========== 3.4 设置图表配置并渲染 ==========  
   chart.setOption({

     textStyle,  // 设置字体颜色

     // ========== 设置鼠标悬停提示框 ==========
     tooltip: {
       trigger: 'axis', // 当鼠标悬停在 x 轴的柱子上触发
       axisPointer: {
         type: 'shadow'  // 使用阴影效果
       },
       // 自定义提示框内容
       formatter: (params) => {
         // params 是数组,包含当前鼠标悬停的数据
         const totalData = params.find(p => p.seriesName === '总消费金额');
         const avgData = params.find(p => p.seriesName === '平均消费金额');
         const name = totalData?.name || avgData?.name || '';

         return `
           用户名:${name}<br/>
           总销售金额:${totalData?.value ?? '-'}<br/>
           平均消费金额:${avgData?.value ?? '-'}
         `;
       }
     },

     grid: {
       left: '15%',        // 左边距留足,确保 Y 轴数值不被截断,图表整体不靠左
       //  right: '10%',       // 右边距留一点空白,避免柱子或图形贴边
       //  bottom: '10%',      // 底部留白,用于 X 轴标签或图例显示
       //  top: '15%',         // 顶部留白,防止标题或 tooltip 遮挡图表
       //  containLabel: true  // 自动调整图表区域以包含坐标轴的标签(防止文字溢出)
     },  

     // ========== x 轴配置 ==========
     xAxis: {
       type: 'category', // x 轴类型:类目轴。常用于显示文本,比如用户名
       data: names       // x 轴的数据来源:用户名称数组
     },

     // ========== y 轴配置 ==========
     yAxis: {
       type: 'value'   // y 轴类型:数值轴。用来展示金额数值
     },

     // ========== 数据系列配置 ==========
     series: [
       {
         type: 'bar',             // 柱状图
         name: '总消费金额',        // 系列名称
         data: total              // 总消费金额数据
       },
       {
         type: 'bar',             // 柱状图
         name: '平均消费金额',      // 系列名称
         data: avg                // 平均消费金额数据
       }
     ]
   });
 });
});
</script>

<!-- ========== 样式部分 ========== -->
<style scoped>
/* 引入公共样式文件 */
@import '../assets/styles/chart-style.css';
</style>

9. ChartProductSale.vue(右下:商品销售额排行柱状图)

任务: 显示一个柱状图,对比不同商品类别的销售额排行。

功能:从后端 /clickhouse 接口获取各商品类别的销售额数据,使用 ECharts 绘制柱状图,展示在页面上。

渲染流程:

用户打开网页

页面加载,Vue 执行 onMounted

向后端请求商品销售数据(request.get('/clickhouse'))

拿到数据:
xData = 商品类别
yData = 各类别销售额

ECharts 初始化图表

把 xData 和 yData 放入图表

图表渲染在 <div ref="chartRef"> 上

完整代码:

<template>
 <!-- 外部容器 -->
 <div class="chart-container">
   <!-- 图表标题 -->
   <div class="chart-title">商品销售额排行</div>
   <!-- 图表将挂载到这个 div -->
   <div ref="chartRef" class="chart" style="height: 280px;"></div>
 </div>
</template>

<script setup>
// 1. 引入 vue 的工具
import { ref, onMounted } from 'vue';
// 2. 引入 axios 请求工具
import request from '@/utils/request';
// 3. 引入 echarts 图表库
import * as echarts from 'echarts';

// 4. 创建响应式 DOM 元素引用,供后面挂载图表
const chartRef = ref();

// 5. 统一字体颜色,设置为白色
const textStyle = { color: '#fff' };

// 6. 当页面挂载完成后执行
onMounted(() => {
 // 7. 向后端请求 clickhouse 的销售数据
 request.get('/clickhouse').then(res => {
   // 8. 提取数据,如果没有数据,默认为空数组
   const data = res.saleData || [];

   // 9. 准备 x 轴和 y 轴的数据
   const xData = data.map(d => d.productCategory); // 商品类别
   const yData = data.map(d => d.totalSales);     // 销售额

   // 10. 创建 ECharts 图表实例
   const chart = echarts.init(chartRef.value);

   // 11. 设置图表的配置和数据
   chart.setOption({
     textStyle,  // 设置统一字体颜色

     // 12. 悬停提示框配置
     tooltip: {
       trigger: 'axis', // 在坐标轴上悬停触发
       axisPointer: {
         type: 'shadow'  // 阴影效果
       },
       formatter: (params) => {
         // params 是一个数组,包含当前悬停点的所有系列
         const item = params[0]; // 本图只有一个系列,所以取第一个
         return `
           商品类别:${item.name}<br/>
           销售额:${item.value}
         `;
       }
     },

     grid: {
       left: '15%',        // 左边距留足,确保 Y 轴数值不被截断,图表整体不靠左
       //  right: '10%',       // 右边距留一点空白,避免柱子或图形贴边
       //  bottom: '10%',      // 底部留白,用于 X 轴标签或图例显示
       //  top: '15%',         // 顶部留白,防止标题或 tooltip 遮挡图表
       //  containLabel: true  // 自动调整图表区域以包含坐标轴的标签(防止文字溢出)
     },    

     // 13. x 轴(商品类别)
     xAxis: {
       type: 'category',
       data: xData
     },

     // 14. y 轴(销售额)
     yAxis: {
       type: 'value'
     },

     // 15. 图表系列:一个柱状图
     series: [
       {
         type: 'bar',
         data: yData
       }
     ]
   });
 });
});
</script>

<style scoped>
/* 引用统一的外部样式 */
@import '../assets/styles/chart-style.css';
</style>


(16)完整项目结构

analyse-ui/
├── .idea/、.vscode/     【开发工具配置】(可以忽略,不影响功能)
├── public/
│   └── vite.svg         【网页图标】(浏览器 tab 上的小图标)

├── src/                 【核心代码文件夹】(Vue 默认的开发目录)

│   ├── assets/          【静态资源】
│   │   ├── images/      → 图片目录
|   |     └── bg_header.png  → 主页面标题上使用的背景图片
│   │   ├── styles/      → 样式目录(index.css、normalize.css、chart-style.css)
│   │   ├── china.json   → 地图数据(GeoJSON),ECharts 的中国地图用
│   │   └── vue.svg      → Vue 的小图标
│   │
│   ├── components/      【页面组件】(一个组件=一个图表或表格或标题)
│   │   ├── HeaderTitle.vue         → 页面顶部标题
│   │   ├── ChartPayType.vue        → 支付方式柱状图
│   │   ├── ChartArea.vue           → 地区销售柱状图
│   │   ├── TableOrderRanking.vue   → 商品销售次数排行(表格)
│   │   ├── ChartMap.vue            → 城市订单地图
│   │   ├── TableUserRanking.vue    → 用户购买排行(表格)
│   │   ├── ChartDayOrder.vue       → 每天订单数量折线图
│   │   ├── ChartUserCast.vue       → 用户消费金额柱状图
│   │   └── ChartProductSale.vue    → 商品销售额柱状图

│   ├── router/          【路由配置】(控制网址对应哪个页面)
│   │   └── index.js     → 定义了:网址 "/" 对应 views/Index.vue 页面

│   ├── utils/           【工具类】
│   │   └── request.js   → axios 请求封装(统一管理所有数据请求)

│   ├── views/           【页面视图】(排版页面)
│   │   └── Index.vue    → 首页,排版放置所有图表和表格

│   ├── App.vue          → Vue 应用的根组件,负责渲染 <router-view />
│   ├── main.js          → Vue 的启动入口,挂载 App.vue
│   ├── style.css        → 全局样式(如果有设置)

├── index.html           → 网页模板(Vue 最后会把 Index.vue 渲染到这个页面)
├── package.json         → 项目依赖管理
├── vite.config.js       → Vite 的配置文件


(17)完整项目的运行过程

1.Vite 启动时加载 index.html 作为页面入口。 👉运行 npm run dev,Vite 会启动本地开发服务器(默认 localhost:5173),读取根目录下的 index.html 作为页面入口,加载 main.js 脚本,进而启动整个 Vue 应用。

2. main.js 加载并挂载应用Vue应用,入口是 App.vue 👉 main.js 会创建 Vue 应用实例,把 App.vue 当作整个网页的“母版页”,并挂载到 #app 上。

3. App.vue 显示 router-view(等待路由匹配)。 👉 App.vue 本身不显示具体内容,只准备一个 router-view,等待根据路由换页面。

4. Vue Router 路由 router/index.js 检查当前地址。 👉 比如网址是 /,路由表会查 / 应该显示哪个页面(如 index.vue)。

5. 找到并加载目标页面组件,比如 index.vue 👉 Vue 加载 index.vue,并把它插入 router-view 中展示。

6.index.vue 显示页面内容,并加载多个子组件(如 ChartPayTypeChartArea 等)。 👉 页面加载子图表组件(如 ChartPayType.vueChartArea.vue),每个子组件会发起接口请求。

7.子组件内部直接处理图表绘制(使用 ECharts) 👉 各子组件(如 ChartPayType.vue)中通过 echarts.init() 初始化图表,手动配置 option,并绑定到页面中的 <div ref="chartRef"> 上。

8.ECharts 初始化并渲染图表 👉 每个组件通过 chartRef.value 拿到 DOM 元素,使用 setOption 将配置项传给 ECharts 实例,绘制图形

9. axios 发起接口请求(如 /api/hive)。 👉 因为在request.js中配置了baseURL = /api,那么 request.get('/hive') 会拼接为 /api/hive

10. Vite 代理请求到后端(如 localhost:8080)。 👉 Vite 的代理规则会将 /api/hive 转发为 http://localhost:8080/hive,从 Spring Boot 获取 JSON 数据。

11. 后端返回响应,前端接收数据。 👉 axios 拿到后端响应的数据(如 payTypeData),并传入 ECharts 进行图表更新。

12. 图表更新完成,用户在页面上看到可视化结果。 👉 所有数据渲染到图表后,用户看到完整页面,支持交互(如悬浮提示、缩放等)。


浏览器
  ↓
index.html(由 Vite 启动加载)
  ↓
main.js → App.vue → router → index.vue → 各图表组件(如 ChartPayType) → axios 请求后端 → ECharts 渲染 → 页面显示
  • index.html:Vite 项目启动时的 HTML 页面入口。

  • main.js:程序入口,创建并挂载 Vue 应用。

  • App.vue:母版页,准备好 <router-view> 等待页面加载。

  • router/index.js:根据网址匹配路由,决定加载哪个页面组件。

  • index.vue:具体页面组件,布局多个子图表组件。

  • 图表子组件(如 ChartPayType.vue):组件内部通过 axios 请求数据,使用 ECharts 直接初始化图表并渲染。

  • axios:向后端接口(如 /api/hive)请求数据。

  • Vite 代理:将 /api 请求转发到后端(如 localhost:8080)。

  • ECharts:各组件接收到数据后,通过 echarts.init() 渲染图表到页面中。


【完整流程图】(页面加载 + 数据请求 + 图表渲染过程)

浏览器访问 http://localhost:5173/
   ↓
Vite 作为开发服务器,返回项目根目录下的 index.html
   ↓
index.html 中通过 <script type="module" src="/src/main.js"> 加载 main.js
   ↓
main.js 加载 App.vue,并创建 Vue 应用实例,并引入 Vue Router
   ↓
通过 app.use(router) 安装路由器,并将 Vue 应用挂载到 index.html 中的 <div id="app"></div>
   ↓
Vue 应用启动后,App.vue 被渲染为根组件,其中包含 <router-view /> 占位符
   ↓
Vue Router 根据当前路径 "/" ,查找在 router/index.js 中定义的路由数组进行路由匹配
   ↓
匹配到 path 为 "/" 的路由,加载并渲染 index.vue 组件到 <router-view /> 中
   ↓
用户在页面上看到 index.vue 的内容
   ↓
index.vue 布局页面结构,包含多个子图表组件:
-----------------------------------------------------------
| 第一列                  | 第二列       |      第三列       |
|-------------------|------------------|------------------|
| ChartPayType      | ChartMap         | ChartDayOrder    |
| ChartArea         |                  | ChartUserCast    |
| TableOrderRanking | TableUserRanking | ChartProductSale |
-----------------------------------------------------------
   ↓
index.vue 依次加载这些子组件
   ↓
每个图表组件(如 ChartPayType.vue、ChartArea.vue 等)内部执行以下流程:
   ↓
【步骤1】通过 axios 发送接口请求(例如 /api/hive)
   ↓
【步骤2】Vite 根据 proxy 配置将 /api/hive 转发至后端服务(如 http://localhost:8080/hive)
   ↓
【步骤3】后端返回 JSON 数据,组件接收响应(如 payTypeData)
   ↓
【步骤4】组件将数据转换为 ECharts 所需的 option 配置项
   ↓
【步骤5】通过 echarts.init(chartRef.value) 初始化图表实例
   ↓
【步骤6】调用 chart.setOption(option) 设置配置并渲染图表
   ↓
图表完成绘制,最终在页面中呈现可视化结果


2.数据可视化展示

注:运行本实训前需先启动Hadoop集群、Hbase、Hive、redis、ClickHouse(已启动)以及实时数据分析模块

数据可视化展示步骤:

1.Zookeeper启动命令

# 在三个节点都要启动
zkServer.sh start

2.Hadoop启动命令

start-dfs.sh
start-yarn.sh

3.Hive服务启动命令

nohup hive --service metastore &
nohup hive --service hiveserver2 &

4.HBase服务启动命令

start-hbase.sh

5.redis服务启动命令

cd /opt/apps/redis/src
./redis-server /opt/apps/redis/redis.conf

6.Kafka服务启动命令

# 在三个节点都要启动
kafka-server-start.sh -daemon /opt/apps/kafka/config/server.properties

7.启动实时模拟数据

# 在master上操作
cd /
python3 main.py

8.Flume启动命令

# 新开第2个master窗口运行
flume-ng agent --conf /opt/apps/flume/conf/dataCleanLog --conf-file  \
/opt/apps/flume/conf/dataCleanLog/flume_To_Kafka_dataClean.properties --name a1 \
-Dflume.root.logger=info,console

9.启动kafka消费者

# 新开第3个master窗口运行
kafka-console-consumer.sh --bootstrap-server master:9092 --topic my-topic

10.在IDEA中启动Flink实时任务

# 在IDEA中运行实时任务中的FlinkCityToRedis.scala

11.在IDEA中启动后端analyse

首次运行时,请开启注解处理器

12.在IDEA中启动前端analyse-ui

# 在IDEA的终端[永久切换到Cmd]运行下面的命令【如下图】
# Settings → Tools → Terminal → Shell path
npm run dev

1.在IDEA的终端[永久切换到Cmd]运行下面的命令【如下图】

image-20250522003234606

2.IDEA的终端[切换到Cmd]的方法

image-20250502185023272

在控制台点击URL启动浏览器

image-20250319162253591


最终效果:

image-20250521182133844




发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

Powered By Z-BlogPHP 1.7.3

版权:李翔
备案/许可证编号为:新ICP备2024006115号-1