新闻详情

新闻详情

首页 / 资讯中心 / 详情

第 3 章:工作台——数据看板

发布时间:2026/9/27 22:48:51来源:尧图网络
第 3 章:工作台——数据看板
本章学习目标学会设计数据看板类页面的布局掌握统计卡片组件的实现数字动效、趋势指标集成 ECharts 实现趋势图、饼图、热力图实现待办队列Tab 切换 虚拟列表了解 Mock 数据与真实接口切换方案3.1 需求分析与布局设计工作台是用户登录后看到的第一个页面承担「数据总览」和「快速入口」两个核心职能。3.1.1 页面结构┌──────────────────────────────────────────────────┐ │ 顶部欢迎语 快捷操作按钮 │ ├──────────┬───────────────────────────────────────┤ │ │ ┌───────────────────────────────┐ │ │ 待办 │ │ 统计卡片组4 个 │ │ │ 队列 │ ├───────────────────────────────┤ │ │(左侧)│ │ 趋势图折线图 │ │ │ │ ├───────────────────────────────┤ │ │ │ │ 分类分布(饼)│ 团队效率(柱)│ │ │ │ └───────────────────────────────┘ │ └──────────┴───────────────────────────────────────┘3.1.2 布局实现使用 Ant Design 的 Row Col 栅格系统// src/pages/workbench/index.tsx import { Row, Col, Card, Typography, Space, Button } from antd; import { PlusOutlined, BellOutlined } from ant-design/icons; import StatCard from components/StatCard; import TodoList from ./components/TodoList; import TrendChart from ./components/TrendChart; import CategoryPie from ./components/CategoryPie; import TeamRanking from ./components/TeamRanking; import { useWorkbenchStore } from ./store; const { Title } Typography; export default function Workbench() { const stats useWorkbenchStore((s) s.stats); const userInfo useUserStore((s) s.userInfo); return ( div style{{ padding: 24 }} {/* 顶部欢迎区 */} Card style{{ marginBottom: 16 }} Space sizelarge style{{ width: 100%, justifyContent: space-between }} div Title level{3} style{{ margin: 0 }} 你好{userInfo?.name || 用户} /Title p style{{ color: #999, margin: 8px 0 0 }} 今天是 {new Date().toLocaleDateString(zh-CN)}祝你工作顺利 /p /div Space Button icon{BellOutlined /}消息中心/Button Button typeprimary icon{PlusOutlined /} 新建任务单 /Button /Space /Space /Card {/* 主体双栏布局 */} Row gutter{16} {/* 左侧待办队列 */} Col span{6} TodoList / /Col {/* 右侧数据看板 */} Col span{18} {/* 统计卡片 */} Row gutter{16} style{{ marginBottom: 16 }} Col span{6} StatCard title待处理 value{stats.pending} trend{12} / /Col Col span{6} StatCard title处理中 value{stats.processing} trend{-5} / /Col Col span{6} StatCard title已完成 value{stats.resolved} trend{23} / /Col Col span{6} StatCard title超时 value{stats.overdue} trend{-8} color#ff4d4f / /Col /Row {/* 趋势图 */} Card title任务趋势 style{{ marginBottom: 16 }} TrendChart / /Card {/* 饼图 柱状图 */} Row gutter{16} Col span{12} Card title分类分布 CategoryPie / /Card /Col Col span{12} Card title团队效率榜 TeamRanking / /Card /Col /Row /Col /Row /div ); }3.2 统计卡片组件StatCard统计卡片是看板最基本的元素需要支持数字动效和趋势指标。3.2.1 组件实现// src/components/StatCard/index.tsx import { useEffect, useState } from react; import { Card, Typography, Space } from antd; import { ArrowUpOutlined, ArrowDownOutlined } from ant-design/icons; const { Text } Typography; interface StatCardProps { title: string; value: number; trend?: number; // 百分比正数上升负数下降 color?: string; prefix?: string; suffix?: string; onClick?: () void; } // 数字滚动动效 function AnimatedNumber({ value, duration 800 }: { value: number; duration?: number }) { const [display, setDisplay] useState(0); useEffect(() { let startTime: number; let animationFrame: number; const animate (timestamp: number) { if (!startTime) startTime timestamp; const progress Math.min((timestamp - startTime) / duration, 1); // 缓动函数easeOutQuart const eased 1 - Math.pow(1 - progress, 4); setDisplay(Math.floor(eased * value)); if (progress 1) { animationFrame requestAnimationFrame(animate); } }; animationFrame requestAnimationFrame(animate); return () cancelAnimationFrame(animationFrame); }, [value, duration]); return {display.toLocaleString()}/; } export default function StatCard({ title, value, trend, color #1677ff, prefix , suffix , onClick, }: StatCardProps) { const isPositive (trend || 0) 0; return ( Card hoverable onClick{onClick} styles{{ body: { padding: 20 } }} Space directionvertical sizemiddle style{{ width: 100% }} Text typesecondary style{{ fontSize: 14 }} {title} /Text div span style{{ fontSize: 28, fontWeight: 600, color }} {prefix} AnimatedNumber value{value} / {suffix} /span /div {trend ! undefined ( Space size{4} {isPositive ? ( ArrowUpOutlined style{{ color: #52c41a }} / ) : ( ArrowDownOutlined style{{ color: #ff4d4f }} / )} Text style{{ color: isPositive ? #52c41a : #ff4d4f, fontSize: 12 }} {Math.abs(trend)}% 较上周 /Text /Space )} /Space /Card ); }3.2.2 设计要点数字动效用requestAnimationFrame实现平滑滚动给用户「数据在变化」的感觉趋势指标箭头 颜色 百分比一目了然hoverable卡片可点击作为快速入口跳转到对应列表可配置颜色、前缀、后缀都通过 props 传入提高复用性3.3 ECharts 图表集成ECharts 是最强大的前端图表库我们用echarts-for-react简化 React 中的使用。3.3.1 安装npminstallecharts echarts-for-react3.3.2 趋势折线图// src/pages/workbench/components/TrendChart.tsx import ReactECharts from echarts-for-react; import { useWorkbenchStore } from ../store; export default function TrendChart() { const trendData useWorkbenchStore((s) s.trendData); const option { tooltip: { trigger: axis }, legend: { data: [新建, 完成], right: 0 }, grid: { left: 40, right: 20, top: 40, bottom: 30 }, xAxis: { type: category, data: trendData.dates, boundaryGap: false, }, yAxis: { type: value }, series: [ { name: 新建, type: line, smooth: true, data: trendData.created, areaStyle: { color: { type: linear, x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: rgba(22, 119, 255, 0.3) }, { offset: 1, color: rgba(22, 119, 255, 0.05) }, ], }, }, lineStyle: { color: #1677ff, width: 2 }, itemStyle: { color: #1677ff }, }, { name: 完成, type: line, smooth: true, data: trendData.resolved, areaStyle: { color: { type: linear, x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: rgba(82, 196, 26, 0.3) }, { offset: 1, color: rgba(82, 196, 26, 0.05) }, ], }, }, lineStyle: { color: #52c41a, width: 2 }, itemStyle: { color: #52c41a }, }, ], }; return ReactECharts option{option} style{{ height: 280 }} /; }3.3.3 分类饼图// src/pages/workbench/components/CategoryPie.tsx import ReactECharts from echarts-for-react; import { useWorkbenchStore } from ../store; export default function CategoryPie() { const categoryData useWorkbenchStore((s) s.categoryData); const option { tooltip: { trigger: item, formatter: {b}: {c} ({d}%) }, legend: { orient: vertical, left: left }, series: [ { type: pie, radius: [40%, 70%], center: [60%, 50%], avoidLabelOverlap: true, itemStyle: { borderRadius: 6, borderColor: #fff, borderWidth: 2 }, label: { show: false }, emphasis: { label: { show: true, fontSize: 14, fontWeight: bold }, }, data: categoryData, }, ], }; return ReactECharts option{option} style{{ height: 280 }} /; }3.3.4 团队效率柱状图// src/pages/workbench/components/TeamRanking.tsx import ReactECharts from echarts-for-react; import { useWorkbenchStore } from ../store; export default function TeamRanking() { const teamData useWorkbenchStore((s) s.teamData); const option { tooltip: { trigger: axis, axisPointer: { type: shadow } }, grid: { left: 80, right: 20, top: 20, bottom: 30 }, xAxis: { type: value }, yAxis: { type: category, data: teamData.map((t) t.name), }, series: [ { type: bar, data: teamData.map((t) t.value), itemStyle: { color: { type: linear, x: 0, y: 0, x2: 1, y2: 0, colorStops: [ { offset: 0, color: #1677ff }, { offset: 1, color: #69c0ff }, ], }, borderRadius: [0, 4, 4, 0], }, barWidth: 16, label: { show: true, position: right }, }, ], }; return ReactECharts option{option} style{{ height: 280 }} /; }3.4 待办队列待办队列是工作台的核心交互区展示当前用户需要处理的任务。3.4.1 Tab 切换 列表// src/pages/workbench/components/TodoList.tsx import { useState } from react; import { Card, Tabs, List, Tag, Avatar, Badge } from antd; import { useNavigate } from react-router-dom; import dayjs from dayjs; import relativeTime from dayjs/plugin/relativeTime; import { useWorkbenchStore } from ../store; dayjs.extend(relativeTime); const tabs [ { key: pending, label: 待处理, badge: 5 }, { key: processing, label: 处理中, badge: 3 }, { key: reminder, label: 提醒, badge: 2 }, ]; export default function TodoList() { const [activeTab, setActiveTab] useState(pending); const navigate useNavigate(); const todoList useWorkbenchStore((s) s.todoList[activeTab as keyof typeof s.todoList]); const priorityColor (p: number) { const colors: Recordnumber, string { 1: red, 2: orange, 3: blue, 4: default }; return colors[p] || default; }; const priorityText (p: number) { const texts: Recordnumber, string { 1: 紧急, 2: 高, 3: 中, 4: 低 }; return texts[p] || 低; }; return ( Card title待办队列 extra{a onClick{() navigate(/ticket)}查看全部/a} styles{{ body: { padding: 0 } }} Tabs activeKey{activeTab} onChange{setActiveTab} items{tabs.map((t) ({ key: t.key, label: ( Badge count{t.badge} sizesmall offset{[4, 0]} {t.label} /Badge ), }))} style{{ padding: 0 16px }} / List dataSource{todoList} renderItem{(item) ( List.Item style{{ padding: 12px 16px, cursor: pointer }} onClick{() navigate(/ticket/${item.id})} List.Item.Meta avatar{Avatar style{{ backgroundColor: #1677ff }}{item.creator?.[0]}/Avatar} title{ div style{{ display: flex, justifyContent: space-between }} span style{{ fontWeight: 500, maxWidth: 180, overflow: hidden, textOverflow: ellipsis, whiteSpace: nowrap }} {item.title} /span Tag color{priorityColor(item.priority)} style{{ marginLeft: 8 }} {priorityText(item.priority)} /Tag /div } description{ div style{{ fontSize: 12, color: #999 }} #{item.id} · {dayjs(item.createdAt).fromNow()} /div } / /List.Item )} style{{ maxHeight: 520, overflow: auto }} / /Card ); }3.5 Mock 数据与真实接口切换在项目早期后端接口还没就绪时我们需要 Mock 数据来推进前端开发。3.5.1 Store 层的数据来源策略// src/pages/workbench/store/index.tsimport{createStore}fromstore/createStore;import{workbenchApi}fromservices/workbench;import{mockStats,mockTrendData,mockCategoryData,mockTeamData,mockTodoList}from../mock;interfaceWorkbenchState{stats:{pending:number;processing:number;resolved:number;overdue:number};trendData:{dates:string[];created:number[];resolved:number[]};categoryData:{name:string;value:number}[];teamData:{name:string;value:number}[];todoList:{pending:any[];processing:any[];reminder:any[];};loading:boolean;}constUSE_MOCKimport.meta.env.DEV;// 开发环境用 mockconstworkbenchStorecreateStoreWorkbenchState({stats:{pending:0,processing:0,resolved:0,overdue:0},trendData:{dates:[],created:[],resolved:[]},categoryData:[],teamData:[],todoList:{pending:[],processing:[],reminder:[]},loading:false,});exportconstuseWorkbenchStoreworkbenchStore.useStore;exportconstsetWorkbenchStateworkbenchStore.setState;exportconstfetchWorkbenchDataasync(){setWorkbenchState({loading:true});try{if(USE_MOCK){// 模拟请求延迟awaitnewPromise((r)setTimeout(r,500));setWorkbenchState({stats:mockStats,trendData:mockTrendData,categoryData:mockCategoryData,teamData:mockTeamData,todoList:mockTodoList,loading:false,});}else{constdataawaitworkbenchApi.getOverview();setWorkbenchState({...data,loading:false});}}catch{setWorkbenchState({loading:false});}};3.5.2 Mock 数据文件// src/pages/workbench/mock/index.tsexportconstmockStats{pending:28,processing:15,resolved:142,overdue:6,};exportconstmockTrendData{dates:[周一,周二,周三,周四,周五,周六,周日],created:[12,19,15,22,18,8,5],resolved:[10,16,18,20,15,6,4],};exportconstmockCategoryData[{name:系统问题,value:35},{name:账号权限,value:28},{name:业务咨询,value:22},{name:功能建议,value:15},{name:其他,value:10},];exportconstmockTeamData[{name:张三,value:32},{name:李四,value:28},{name:王五,value:24},{name:赵六,value:20},{name:钱七,value:16},];exportconstmockTodoList{pending:Array.from({length:8}).map((_,i)({id:TK${1000i},title:待处理任务${i1},priority:(i%4)1,creator:[张三,李四,王五,赵六][i%4],createdAt:newDate(Date.now()-i*3600000).toISOString(),})),processing:Array.from({length:5}).map((_,i)({id:TK${2000i},title:处理中任务${i1},priority:(i%3)1,creator:[张三,李四,王五][i%3],createdAt:newDate(Date.now()-i*7200000).toISOString(),})),reminder:Array.from({length:3}).map((_,i)({id:TK${3000i},title:提醒任务${i1},priority:i1,creator:[张三,李四][i%2],createdAt:newDate(Date.now()-i*1800000).toISOString(),})),};本章小结知识点关键内容布局设计Row Col 栅格系统左侧列表 右侧看板的双栏布局统计卡片requestAnimationFrame实现数字滚动动效趋势指标用颜色箭头EChartsecharts-for-react封装折线图/饼图/柱状图三种常用图表待办队列Tabs 切换、List 列表、点击跳转详情Mock 方案开发环境走 mock 数据生产环境走真实接口通过环境变量切换下一章预告我们进入核心业务模块——任务单管理学习数据模型设计、高级筛选、状态流转等复杂业务场景的实现。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Kata Containers Dragonball vCPU 管理机制解析:配置、状态机与热插拔实现 2026/9/27 23:37:24

Kata Containers Dragonball vCPU 管理机制解析:配置、状态机与热插拔实现

云原生容器运行时 【免费下载链接】kata-containers Kata Containers is an open source project and community working to build a standard implementation of lightweight Virtual Machines (VMs) that feel and perform like containers, but provide the workload isolat…

阅读更多 →
单边傅里叶/拉普拉斯变换 2026/9/27 23:37:06

单边傅里叶/拉普拉斯变换

单边变换和双边变换本质上没有太大的区别,如果信号的频率范围是有限的,实际上单边变换和双边变换的效果是一样的,只是双边变换的频谱幅值是单边变换的两倍而已,但是并不会引起频谱分析的失真。这也就是为什么cos(w0t)的傅里叶变换…

阅读更多 →
拉普拉斯变换的理解 2026/9/27 23:37:06

拉普拉斯变换的理解

1、拉普拉斯变换是信号变换的一种方式,和傅里叶变换类似,傅里叶变换是把信号拆分成e^(-jwt)的形式,傅里叶变换的结果F(jw0)就是指 e^(-jw0t)分量的大小。拉普拉式变换也是类似的,不过拉普拉斯变换是把信号拆分成e^(-σt)*e^(-jwt)…

阅读更多 →
MySQL存储过程与触发器 2026/9/27 23:36:59

MySQL存储过程与触发器

数据库是现代应用程序的核心组件之一,而在日常开发和管理中,自动化、逻辑处理和优化性能尤为重要。MySQL 中的存储过程与触发器提供了强大的工具,帮助开发者在数据库内部实现这些目标。存储过程可以让一组 SQL 语句在数据库中以预编译的方式存储,并在需要时调用。触发器则能…

阅读更多 →
ASL Alphabet图像分类实战:数据预处理、CNN与迁移学习避坑指南 2026/9/27 23:36:59

ASL Alphabet图像分类实战:数据预处理、CNN与迁移学习避坑指南

简介:面向深度学习图像分类与手语识别场景,ASL Alphabet 美国手语字母表数据集内置29类图像样本,涵盖A~Z、space、nothing、del共29个类别,每类超2000张图片,适合作为CNN、ResNet等模型的训练数据。压缩包约311MB&…

阅读更多 →
网站建设mingxinsh避坑指南:看懂建站报价才敢开工 2026/9/27 23:36:59

网站建设mingxinsh避坑指南:看懂建站报价才敢开工

网站建设mingxinsh避坑指南:看懂建站报价才敢开工 网站做好了没人访问,这是很多老板做网站后最头疼的事。你花了大几千甚至几万块钱,网站上线了,每天后台看流量只有个位数,心里直打鼓:这钱是不是打水漂了?别急,这通常不是技术问题,而是你在…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉