当前位置: 首页 > news >正文

记录利用Cursor快速实现首页数据大屏

目录

1、大屏主页面

src/views/dashboard/DataScreenView.vue

2、通用面板(40px 标头)

src/components/dashboard/DashboardPanel.vue

3、中间光感联动区

src/components/dashboard/CenterLightVisual.vue

4、实时时钟

src/composables/useDashboardClock.ts

5、路由 /data-screen

src/router/index.ts

6、首页增加入口卡片

src/views/home/HomeView.vue


需求:

首页数据大屏需求:

页面布局:左上角为 logo,上方中间展示一个大的标题(内容大概为保险车险类的),右上角显示时间(时间、星期几、年月日)

中间主要区域显示一辆汽车和一栋大楼,之间光线互相牵连,有流动光感。

左下区域展示两个 Echarts 图表,一个柱状图一个饼状图,

右侧区域上下展示两个数据列表

左下区域和右侧区域都是上下结构布局,上方标头大概60px 高,下方根据屏幕看着给分配高度吧。根据以上图片和需求说明,帮我在此项目中新生成一个页面用于展示和测试。

利用 Cursor 实现起来会很快,我们的角色就跟 产品 / 需求 或者 项目经理似的,只需要在 Cursor Agents 窗口内提任务需求就可以了,它就能够快速理解需求并自动帮你实现,话不多说,先看下成果~


接下来看一下 Cursor 是如何一步一步实现的吧~

1、正在探索项目结构与技术栈,以便创建符合需求的数据大屏页面。

2、正在安装 ECharts 并创建数据大屏页面及相关组件。

$ cd /Users/jimmy/Desktop/PROJECT/StarCollab && npm install echarts


1、大屏主页面

src/views/dashboard/DataScreenView.vue

<template> <div class="data-screen"> <header class="data-screen__header"> <div class="data-screen__logo"> <el-icon :size="36" color="#00d4ff"><Star /></el-icon> <span class="data-screen__logo-text">星盘保险</span> </div> <h1 class="data-screen__title">车险智能运营数据大屏</h1> <div class="data-screen__clock"> <span class="data-screen__time">{{ timeText }}</span> <span class="data-screen__weekday">{{ weekdayText }}</span> <span class="data-screen__date">{{ dateText }}</span> </div> </header> <div class="data-screen__body"> <div class="data-screen__main"> <section class="data-screen__center"> <CenterLightVisual /> </section> <div class="data-screen__charts"> <DashboardPanel title="月度保费与赔付" subtitle="单位:万元"> <div ref="barChartRef" class="chart-box" /> </DashboardPanel> <DashboardPanel title="险种结构占比"> <div ref="pieChartRef" class="chart-box" /> </DashboardPanel> </div> </div> <aside class="data-screen__sidebar"> <DashboardPanel title="今日报案动态"> <ul class="data-list"> <li v-for="item in claimList" :key="item.id" class="data-list__row"> <span class="data-list__plate">{{ item.plate }}</span> <span class="data-list__type">{{ item.type }}</span> <span class="data-list__status" :class="`is-${item.status}`">{{ item.statusLabel }}</span> <span class="data-list__time">{{ item.time }}</span> </li> </ul> </DashboardPanel> <DashboardPanel title="渠道业绩排行"> <ul class="data-list>import { onMounted, onUnmounted, ref } from 'vue' const WEEKDAY_LABELS = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'] /** 数据大屏右上角实时时钟 */ export function useDashboardClock() { const timeText = ref('') const weekdayText = ref('') const dateText = ref('') function pad(n: number): string { return String(n).padStart(2, '0') } function tick(): void { const now = new Date() timeText.value = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}` weekdayText.value = WEEKDAY_LABELS[now.getDay()] dateText.value = `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日` } let timer: ReturnType<typeof setInterval> | undefined onMounted(() => { tick() timer = setInterval(tick, 1000) }) onUnmounted(() => { if (timer) clearInterval(timer) }) return { timeText, weekdayText, dateText } }

2、通用面板(40px 标头)

src/components/dashboard/DashboardPanel.vue

<template> <section class="dashboard-panel"> <header class="dashboard-panel__header"> <span class="dashboard-panel__title">{{ title }}</span> <span v-if="subtitle" class="dashboard-panel__subtitle">{{ subtitle }}</span> </header> <div class="dashboard-panel__body"> <slot /> </div> </section> </template> <script setup lang="ts"> defineProps<{ title: string subtitle?: string }>() </script> <style scoped> .dashboard-panel { display: flex; flex-direction: column; min-height: 0; border: 1px solid rgba(0, 180, 255, 0.25); background: linear-gradient(180deg, rgba(6, 30, 60, 0.85) 0%, rgba(4, 18, 40, 0.92) 100%); box-shadow: inset 0 0 24px rgba(0, 120, 255, 0.08); } .dashboard-panel__header { flex-shrink: 0; height: 60px; display: flex; align-items: center; justify-content: space-between; padding: 0 20px; border-bottom: 1px solid rgba(0, 180, 255, 0.2); background: linear-gradient(90deg, rgba(0, 100, 200, 0.15), transparent 60%, rgba(0, 100, 200, 0.15)); } .dashboard-panel__title { font-size: 18px; font-weight: 600; color: #e8f4ff; letter-spacing: 2px; } .dashboard-panel__subtitle { font-size: 12px; color: rgba(160, 210, 255, 0.65); } .dashboard-panel__body { flex: 1; min-height: 0; padding: 12px; overflow: hidden; } </style>

3、中间光感联动区

src/components/dashboard/CenterLightVisual.vue

<template> <div class="center-visual"> <svg class="center-visual__svg" viewBox="0 0 1000 420" preserveAspectRatio="xMidYMid meet" aria-hidden="true" > <defs> <linearGradient id="carBody" x1="0%" y1="0%" x2="100%" y2="100%"> <stop offset="0%" stop-color="#1a6bff" /> <stop offset="100%" stop-color="#0d3d8c" /> </linearGradient> <linearGradient id="buildingBody" x1="0%" y1="100%" x2="0%" y2="0%"> <stop offset="0%" stop-color="#0a2848" /> <stop offset="100%" stop-color="#1e5a9a" /> </linearGradient> <linearGradient id="beamGrad" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="200" y2="0"> <stop offset="0%" stop-color="transparent" /> <stop offset="35%" stop-color="#00e5ff" /> <stop offset="65%" stop-color="#4d9fff" /> <stop offset="100%" stop-color="transparent" /> <animate attributeName="x1" values="-200;800;-200" dur="3s" repeatCount="indefinite" /> <animate attributeName="x2" values="0;1000;0" dur="3s" repeatCount="indefinite" /> </linearGradient> <filter id="glow"> <feGaussianBlur stdDeviation="3" result="blur" /> <feMerge> <feMergeNode in="blur" /> <feMergeNode in="SourceGraphic" /> </feMerge> </filter> </defs> <!-- 光感连线 --> <g class="light-beams" filter="url(#glow)"> <path class="beam beam--1" d="M 280 200 Q 500 80 720 140" fill="none" stroke="url(#beamGrad)" stroke-width="3" /> <path class="beam beam--2" d="M 300 240 Q 500 320 700 220" fill="none" stroke="url(#beamGrad)" stroke-width="2.5" /> <path class="beam beam--3" d="M 260 260 Q 500 200 740 280" fill="none" stroke="rgba(0, 229, 255, 0.35)" stroke-width="1.5" /> <circle class="pulse pulse--1" r="5" fill="#00e5ff"> <animateMotion dur="4s" repeatCount="indefinite" path="M 280 200 Q 500 80 720 140" /> </circle> <circle class="pulse pulse--2" r="4" fill="#6eb6ff"> <animateMotion dur="3.2s" repeatCount="indefinite" path="M 300 240 Q 500 320 700 220" /> </circle> <circle class="pulse pulse--3" r="3" fill="#00e5ff"> <animateMotion dur="5s" repeatCount="indefinite" path="M 260 260 Q 500 200 740 280" /> </circle> </g> <!-- 汽车 --> <g class="car" transform="translate(80, 150)"> <rect x="20" y="70" width="200" height="50" rx="12" fill="url(#carBody)" stroke="#3d9fff" stroke-width="2" /> <rect x="50" y="35" width="120" height="45" rx="8" fill="#1a4a8a" stroke="#5eb0ff" stroke-width="1.5" /> <circle cx="60" cy="125" r="22" fill="#0a1e3a" stroke="#00d4ff" stroke-width="3" /> <circle cx="180" cy="125" r="22" fill="#0a1e3a" stroke="#00d4ff" stroke-width="3" /> <circle cx="60" cy="125" r="10" fill="#1a3d6e" /> <circle cx="180" cy="125" r="10" fill="#1a3d6e" /> <text x="110" y="105" text-anchor="middle" fill="#a8d8ff" font-size="14" font-weight="600">车险标的</text> </g> <!-- 大楼 --> <g class="building" transform="translate(680, 40)"> <rect x="0" y="0" width="200" height="320" fill="url(#buildingBody)" stroke="#3d8fd9" stroke-width="2" /> <g class="windows"> <rect v-for="(win, i) in windows" :key="i" :x="win.x" :y="win.y" width="28" height="22" :fill="win.on ? '#ffe566' : 'rgba(0, 80, 140, 0.6)'" stroke="rgba(0, 200, 255, 0.3)" /> </g> <text x="100" y="350" text-anchor="middle" fill="#a8d8ff" font-size="14" font-weight="600">承保机构</text> </g> </svg> <p class="center-visual__hint">车辆保障 ⇄ 机构承保 数据实时联动</p> </div> </template> <script setup lang="ts"> /** 大楼窗户亮灭(静态装饰) */ const windows = Array.from({ length: 24 }, (_, i) => { const col = i % 4 const row = Math.floor(i / 4) return { x: 24 + col * 44, y: 30 + row * 58, on: (i + col) % 3 !== 0, } }) </script> <style scoped> .center-visual { position: relative; width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 0; } .center-visual__svg { width: 100%; max-height: calc(100% - 32px); flex: 1; } .center-visual__hint { flex-shrink: 0; margin-top: 8px; font-size: 13px; color: rgba(140, 200, 255, 0.55); letter-spacing: 4px; } .beam { stroke-linecap: round; } .beam--1 { animation: beam-flicker 2.5s ease-in-out infinite; } .beam--2 { animation: beam-flicker 3s ease-in-out infinite 0.4s; } .beam--3 { stroke-dasharray: 8 12; animation: beam-dash 6s linear infinite; } .pulse { filter: drop-shadow(0 0 6px #00e5ff); } @keyframes beam-flicker { 0%, 100% { opacity: 0.75; } 50% { opacity: 1; } } @keyframes beam-dash { to { stroke-dashoffset: -120; } } </style>

4、实时时钟

src/composables/useDashboardClock.ts

import { onMounted, onUnmounted, ref } from 'vue' const WEEKDAY_LABELS = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'] /** 数据大屏右上角实时时钟 */ export function useDashboardClock() { const timeText = ref('') const weekdayText = ref('') const dateText = ref('') function pad(n: number): string { return String(n).padStart(2, '0') } function tick(): void { const now = new Date() timeText.value = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}` weekdayText.value = WEEKDAY_LABELS[now.getDay()] dateText.value = `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日` } let timer: ReturnType<typeof setInterval> | undefined onMounted(() => { tick() timer = setInterval(tick, 1000) }) onUnmounted(() => { if (timer) clearInterval(timer) }) return { timeText, weekdayText, dateText } }

5、路由/data-screen

src/router/index.ts

{ path: '/data-screen', name: 'DataScreen', component: () => import('@/views/dashboard/DataScreenView.vue'), meta: { title: '数据大屏' }, },

6、首页增加入口卡片

src/views/home/HomeView.vue

{ name: '数据大屏', desc: '车险智能运营可视化大屏', path: '/data-screen', icon: DataAnalysis, color: 'linear-gradient(135deg, #00d4ff, #0066cc)', },

成果图展示:

http://www.gsyq.cn/news/1450566.html

相关文章:

  • 高效使用Studio Library:5个提升Maya动画工作效率的实战技巧
  • 【LeetCode刷题日记】77216.回溯算法剪枝优化在组合问题中的应用
  • AnywhereVLA框架:语言驱动的机器人移动操作系统
  • AI时代下,Java程序员还要看源码吗?
  • Transformer模型在表格数据合成中的性能优化与实践
  • LinkSwift:八大网盘直链解析神器,告别限速烦恼
  • 从SVD到RANSAC:点云平面拟合的数学原理与Python代码逐行解析(避坑参数设置)
  • defer性能陷阱:我是如何解决内存逃逸问题的
  • WzComparerR2 终极指南:冒险岛WZ文件提取器的完整使用教程
  • 有哪些真正好用且不贵的 AI 写作软件?100 小时深度体验后我来交作业了
  • 5分钟搞定RabbitMQ!Docker一键安装 + 核心概念图解
  • 全国哪家台球厅设计公司的口碑较好? - myqiye
  • 985计算机水硕,转大模型应用开发的感悟
  • 圆偏振光+磁控溅射AR膜实测:iPhone17 Pro Max强光下反射率≤0.5%,久看不累——观复盾体验
  • 当你的排查助手变成了AI:大模型辅助根因分析在线上故障排查中的应用
  • 虚拟机配置终端连接,出现:因为在此系统上禁止运行脚本。有关详细信息请参阅 https:/go.microsoft.con/fwlink/?LinkID=13517e
  • 微前端架构下实现子应用间虚拟DOM Diff算法原理与沙箱隔离方案
  • 2026年靠谱的空压机代理品牌有哪些 - myqiye
  • 去幼儿园报名,幼儿园需要给小孩面试吗?
  • 自考 / 成人本科论文,性价比高的 AI 写作软件有哪些?真实使用反馈
  • VMware安装虚拟机教程(超详细)
  • 聊聊Java中的of
  • 【系统学AI】论文导读 ③:Building Effective Agents——Anthropic 的 Agent 设计圣经
  • 2026苏州瓷砖空鼓修复哪家靠谱?本地7家免砸砖注浆维修公司推荐 - 苏易修缮
  • 【极验防护挑战】Browser-Use 如何应对具备轨迹检测行为的高级验证码系统?
  • 海关行业知识图谱问答方案
  • 宁波中允业主委员会选举第三方的优势有哪些?怎么收费? - mypinpai
  • 3步打造完美Hackintosh:智能配置工具终极指南
  • 连接世界——远程仓库与 GitHub 协作实战
  • 部署 Waline 评论系统到自己的服务器完全指南 (保姆级教程 2026)