feat: 搭建可视化数据监控平台并更新相关资源
- 替换站点图标为logo.jpg,删除旧的logo.png
- 新增近20张静态资源图片,覆盖首页、路由项、图表背景等场景
- 重构axios请求工具,提取基础URL和超时配置为常量
- 新增手机号脱敏、字符串脱敏、时间格式化工具函数
- 实现统计数据大盘、路由项轮播页面,封装通用标题组件TitleBox
- 集成多款ECharts图表实现数据可视化展示
- 新增API请求服务层统一管理后端接口
- 更新路由配置,将默认首页替换为统计大盘页面
- 删除无用的旧空首页组件,调整App.vue大屏适配配置
2个文件已删除
1 文件已重命名
28个文件已添加
5个文件已修改
| | |
| | | <meta http-equiv="X-UA-Compatible" content="IE=edge"> |
| | | <meta name="viewport" content="width=device-width,initial-scale=1.0"> |
| | | <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width" /> |
| | | <script src="https://cache.amap.com/lbs/static/es5.min.js"></script> |
| | | <script type="text/javascript"> |
| | | window._AMapSecurityConfig = { |
| | | securityJsCode: "c75eccf0742998b2ea43823e08cacccd", |
| | | }; |
| | | </script> |
| | | <script src="https://webapi.amap.com/loader.js"></script> |
| | | <link rel="icon" href="./logo.png"> |
| | | <title>放心行</title> |
| | | <link rel="icon" href="./logo.jpg"> |
| | | <title>放心行</title> |
| | | </head> |
| | | |
| | | <body> |
| | |
| | | dw: 1920, |
| | | el: '#app', |
| | | resize: true, |
| | | ignore: ['#container'] |
| | | }) |
| | | }, |
| | | created () {} |
| New file |
| | |
| | | <template> |
| | | <div class="title-box"> |
| | | <div class="left-slot"> |
| | | <slot name="left"></slot> |
| | | </div> |
| | | <div class="right-slot"> |
| | | <slot name="right"></slot> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | name: 'TitleBox' |
| | | } |
| | | </script> |
| | | |
| | | <style lang="less" scoped> |
| | | .title-box { |
| | | width: 100%; |
| | | height: 50px; |
| | | background: url('~@/assets/img/home/home-title-bg.png') no-repeat center/100% 100%; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .left-slot { |
| | | margin-top: 6px; |
| | | margin-left: 20px; |
| | | font-family: PangMenZhengDao; |
| | | font-size: 30px; |
| | | font-weight: normal; |
| | | line-height: normal; |
| | | letter-spacing: 0em; |
| | | background: linear-gradient(180deg, #ffffff 0%, #2bbaf8 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | text-fill-color: transparent; |
| | | } |
| | | |
| | | .right-slot { |
| | | margin-right: 18px; |
| | | flex-shrink: 0; |
| | | } |
| | | </style> |
| | |
| | | Vue.use(Router) |
| | | |
| | | export const constantRoutes = [ |
| | | // { |
| | | // path: '/', |
| | | // component: () => import('../view/route-item/index.vue'), |
| | | // meta: { |
| | | // title: '放心出行智慧平台' |
| | | // } |
| | | // }, |
| | | { |
| | | // path: '/statistical-table', |
| | | path: '/', |
| | | component: () => import('../view/home/index.vue'), |
| | | component: () => import('../view/statistical-table/index.vue'), |
| | | meta: { |
| | | title: '首页' |
| | | title: '放心行可视化数据平台' |
| | | } |
| | | } |
| | | }, |
| | | ] |
| | | |
| | | const createRouter = () => new Router({ |
| | |
| | | import axios from 'axios' |
| | | import { Message } from 'element-ui' |
| | | |
| | | /** |
| | | * 基础请求 URL |
| | | * @type {string} |
| | | */ |
| | | const BASE_URL = process.env.VUE_APP_BASE_URL |
| | | |
| | | /** |
| | | * 默认请求超时时间(毫秒) |
| | | * @type {number} |
| | | */ |
| | | const TIMEOUT = 30000 |
| | | |
| | | const service = axios.create({ |
| | | baseURL: process.env.VUE_APP_BASE_URL, |
| | | baseURL: BASE_URL, |
| | | withCredentials: false, |
| | | timeout: 30000 |
| | | timeout: TIMEOUT |
| | | }) |
| | | |
| | | // 请求拦截 |
| New file |
| | |
| | | /** |
| | | * 格式化当前时间,返回包含日期、星期、时间的结构化对象 |
| | | * @returns {Object} 包含以下属性的对象: |
| | | * - currentDate {string} 当前日期,格式为 YYYY-MM-DD |
| | | * - currentDay {string} 当前星期,例如 '星期一' |
| | | * - currentTime {string} 当前时间,格式为 HH:mm:ss |
| | | */ |
| | | export function formatTime() { |
| | | const now = new Date(); |
| | | const year = now.getFullYear(); |
| | | const month = String(now.getMonth() + 1).padStart(2, '0'); |
| | | const date = String(now.getDate()).padStart(2, '0'); |
| | | |
| | | const days = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']; |
| | | const currentDay = days[now.getDay()]; |
| | | |
| | | const hours = String(now.getHours()).padStart(2, '0'); |
| | | const minutes = String(now.getMinutes()).padStart(2, '0'); |
| | | const seconds = String(now.getSeconds()).padStart(2, '0'); |
| | | |
| | | return { |
| | | currentDate: `${year}-${month}-${date}`, |
| | | currentDay, |
| | | currentTime: `${hours}:${minutes}:${seconds}` |
| | | }; |
| | | } |
| | |
| | | /** |
| | | * 手机号脱敏处理(隐藏中间四位) |
| | | * @param {string} phoneNumber - 需要脱敏的手机号码 |
| | | * @returns {string} 脱敏后的手机号码,例如 '138****1234' |
| | | */ |
| | | export const maskPhoneNumber = (phoneNumber) => { |
| | | if (!phoneNumber || phoneNumber === '-') return '-' |
| | | let str = '' |
| | |
| | | return str |
| | | } |
| | | |
| | | /** |
| | | * 字符串脱敏处理(仅保留第一位,后面用星号代替) |
| | | * @param {string} strName - 需要脱敏的字符串 |
| | | * @returns {string} 脱敏后的字符串,例如 '张***' |
| | | */ |
| | | export const maskString = (strName) => { |
| | | if (!strName || strName === '-' || strName.length === 0) return '-' |
| | | let str = '' |
| New file |
| | |
| | | .page-container { |
| | | width: 1920px; |
| | | height: 1080px; |
| | | background: url('~@/assets/img/route-item/route-bg.png') no-repeat center/cover; |
| | | position: relative; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .top-bar { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | width: 100%; |
| | | height: 97px; |
| | | background: url('~@/assets/img/route-item/route-item-top-bg.png') no-repeat center/100% 100%; |
| | | } |
| | | |
| | | .top-title { |
| | | position: absolute; |
| | | top: 6px; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | font-family: PangMenZhengDao; |
| | | line-height: normal; |
| | | font-size: 48px; |
| | | letter-spacing: 0.14em; |
| | | color: #FFFFFF; |
| | | text-shadow: 0px 4px 4px rgba(17, 143, 215, 0.49); |
| | | } |
| | | |
| | | .top-right { |
| | | position: absolute; |
| | | top: 4px; |
| | | right: 30px; |
| | | display: flex; |
| | | gap: 30px; |
| | | |
| | | span { |
| | | font-family: ABeeZee; |
| | | font-size: 16px; |
| | | line-height: 24px; |
| | | font-variation-settings: "opsz" auto; |
| | | background: linear-gradient(180deg, #ffffff 35%, #008bfd 100%), #45A2FF; |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | text-fill-color: transparent; |
| | | } |
| | | } |
| | | |
| | | .main-content { |
| | | position: absolute; |
| | | bottom: 336px; |
| | | left: 0; |
| | | width: 100%; |
| | | height: 456px; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | } |
| | | |
| | | .arrow-box { |
| | | width: 300px; |
| | | height: 456px; |
| | | position: absolute; |
| | | top: 0; |
| | | display: flex; |
| | | align-items: center; |
| | | z-index: 10; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .left-arrow-box { |
| | | left: 0; |
| | | background: linear-gradient(90deg, #000f29 34%, #000f2900 99%); |
| | | justify-content: flex-start; |
| | | } |
| | | |
| | | .right-arrow-box { |
| | | right: 0; |
| | | background: linear-gradient(270deg, #000f29 34%, #000f2900 99%); |
| | | justify-content: flex-end; |
| | | } |
| | | |
| | | .arrow-icon { |
| | | width: 100px; |
| | | height: 100px; |
| | | } |
| | | |
| | | .items-container { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 100%; |
| | | z-index: 5; |
| | | } |
| | | |
| | | .item { |
| | | position: absolute; |
| | | top: 50%; |
| | | background: url('~@/assets/img/route-item/no-active-item.png') no-repeat center/100% 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | cursor: pointer; |
| | | transition: all 0.5s ease; |
| | | |
| | | &.active { |
| | | background: url('~@/assets/img/route-item/active-item.png') no-repeat center/100% 100%; |
| | | } |
| | | |
| | | &.no-transition { |
| | | transition: none !important; |
| | | } |
| | | } |
| | | |
| | | .item-icons { |
| | | position: relative; |
| | | width: 100%; |
| | | } |
| | | |
| | | .icon-1 { |
| | | position: absolute; |
| | | width: 106px; |
| | | height: 107px; |
| | | top: 106px; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | z-index: 2; |
| | | transition: all 0.5s ease; |
| | | } |
| | | |
| | | .icon-2 { |
| | | position: absolute; |
| | | width: 248px; |
| | | height: 248px; |
| | | top: 35px; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | z-index: 1; |
| | | transition: all 0.5s ease; |
| | | } |
| | | |
| | | |
| | | .item-name-container { |
| | | position: absolute; |
| | | bottom: 78px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | transition: all 0.5s ease; |
| | | |
| | | .item-name { |
| | | font-family: Alibaba-PuHuiTi-R; |
| | | font-size: 36px; |
| | | font-weight: 800; |
| | | line-height: normal; |
| | | text-align: center; |
| | | letter-spacing: 0px; |
| | | color: #FFFFFF; |
| | | } |
| | | |
| | | .icon-3{ |
| | | margin-top: 10px; |
| | | height: 6px; |
| | | width: 141px; |
| | | transition: all 0.5s ease; |
| | | } |
| | | } |
| New file |
| | |
| | | <template> |
| | | <div class="page-container"> |
| | | <!-- 导航栏 --> |
| | | <div class="top-bar"> |
| | | <div class="top-title">放心出行智慧平台</div> |
| | | <div class="top-right"> |
| | | <span>{{ currentDate }}</span> |
| | | <span>{{ currentDay }}</span> |
| | | <span>{{ currentTime }}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 主内容 --> |
| | | <div class="main-content"> |
| | | <!-- 左箭头盒子 --> |
| | | <div class="arrow-box left-arrow-box" @click="prev"> |
| | | <img src="~@/assets/img/route-item/left-change-icon.png" class="arrow-icon" /> |
| | | </div> |
| | | |
| | | <!-- 轮播图容器 --> |
| | | <div class="items-container"> |
| | | <div |
| | | v-for="item in carouselItems" |
| | | :key="item.id" |
| | | :class="['item', { active: item.diff === 0, 'no-transition': wrappingIndices.includes(item.index) }]" |
| | | :style="item.style" |
| | | @click="selectItem(item.index)" |
| | | > |
| | | <div class="item-icons"> |
| | | <img src="~@/assets/img/route-item/item-top-bg-icon-2.png" class="icon-2" :style="item.icon2Style" /> |
| | | <img src="~@/assets/img/route-item/item-top-bg-icon-1.png" class="icon-1" :style="item.icon1Style" /> |
| | | </div> |
| | | <div class="item-name-container" :style="item.nameContainerStyle"> |
| | | <div class="item-name" :style="item.nameStyle">{{ item.name }}</div> |
| | | <img src="~@/assets/img/route-item/item-bottom-bg-icon.png" class="icon-3" :style="item.icon3Style" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 右箭头盒子 --> |
| | | <div class="arrow-box right-arrow-box" @click="next"> |
| | | <img src="~@/assets/img/route-item/right-change-icon.png" class="arrow-icon" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import { formatTime } from '@/utils/time'; |
| | | |
| | | export default { |
| | | name: "RouteItem", |
| | | data() { |
| | | return { |
| | | items: [ |
| | | { id: 1, name: '导航名称' }, |
| | | { id: 2, name: '导航名称' }, |
| | | { id: 3, name: '导航名称' }, |
| | | { id: 4, name: '导航名称' }, |
| | | { id: 5, name: '导航名称' }, |
| | | ], |
| | | activeIndex: 2, // 默认中间一项为选中态 (0, 1, 2, 3, 4) |
| | | wrappingIndices: [], // 记录正在飞越边界的元素,取消过渡动画 |
| | | currentDate: '', |
| | | currentDay: '', |
| | | currentTime: '', |
| | | timer: null |
| | | }; |
| | | }, |
| | | computed: { |
| | | carouselItems() { |
| | | return this.items.map((item, index) => { |
| | | let diff = index - this.activeIndex; |
| | | if (diff < -2) diff += 5; |
| | | if (diff > 2) diff -= 5; |
| | | |
| | | let left, width, height, zIndex, opacity = 1; |
| | | |
| | | // 基础尺寸(中心项) |
| | | const baseWidth = 348; |
| | | const baseHeight = 450; |
| | | |
| | | let scale = 1; |
| | | if (diff === 0) { |
| | | left = '50%'; zIndex = 3; |
| | | scale = 1; |
| | | } else if (diff === -1 || diff === 1) { |
| | | left = diff === -1 ? 'calc(50% - 380px)' : 'calc(50% + 380px)'; |
| | | zIndex = 2; |
| | | scale = 332 / baseWidth; // 约 0.954 |
| | | } else if (diff === -2 || diff === 2) { |
| | | left = diff === -2 ? 'calc(50% - 733px)' : 'calc(50% + 733px)'; |
| | | zIndex = 1; |
| | | scale = 294 / baseWidth; // 约 0.8448 |
| | | } |
| | | |
| | | // 根据比例计算所有内部元素的尺寸和位置 |
| | | width = baseWidth * scale; |
| | | height = baseHeight * scale; |
| | | |
| | | // icon-2 原尺寸 249 |
| | | const icon2Size = 249 * scale; |
| | | // icon-1 原尺寸 110 x 107 |
| | | const icon1Width = 110 * scale; |
| | | const icon1Height = 107 * scale; |
| | | |
| | | // 如果想让非中心项的 icon 有微小的上移(模拟视觉重心变化) |
| | | // 假设中间时 icon-1 top 为 106px,icon-2 top 为 35px,两边依次上移 |
| | | const offsetTop = (1 - scale) * 40; // 这个可调系数用来控制往两边递减时的上移幅度 |
| | | |
| | | // 分别计算两个 icon 的动态 top |
| | | const icon1Top = `${106 * scale - offsetTop}px`; |
| | | const icon2Top = `${35 * scale - offsetTop}px`; |
| | | |
| | | // item-name-container 距离底部 |
| | | const nameBottom = 78 * scale; |
| | | |
| | | // 名字字体大小 |
| | | const nameFontSize = 36 * scale; |
| | | |
| | | // 底部 icon-3 的尺寸,原图大小大概可以缩放 |
| | | const icon3Width = 141 * scale; |
| | | |
| | | // 当元素正在“瞬移”到另一侧准备进场时,将其设为透明,避免视觉上突然出现 |
| | | if (this.wrappingIndices.includes(index)) { |
| | | opacity = 0; |
| | | } |
| | | |
| | | return { |
| | | ...item, |
| | | index, |
| | | diff, |
| | | style: { |
| | | left, |
| | | width: `${width}px`, |
| | | height: `${height}px`, |
| | | zIndex, |
| | | opacity, |
| | | transform: 'translate(-50%, -50%)', |
| | | }, |
| | | icon1Style: { |
| | | width: `${icon1Width}px`, |
| | | height: `${icon1Height}px`, |
| | | top: icon1Top |
| | | }, |
| | | icon2Style: { |
| | | width: `${icon2Size}px`, |
| | | height: `${icon2Size}px`, |
| | | top: icon2Top |
| | | }, |
| | | nameContainerStyle: { |
| | | bottom: `${nameBottom}px` |
| | | }, |
| | | nameStyle: { |
| | | fontSize: `${nameFontSize}px` |
| | | }, |
| | | icon3Style: { |
| | | width: `${icon3Width}px` |
| | | } |
| | | }; |
| | | }); |
| | | } |
| | | }, |
| | | created() { |
| | | this.updateTime(); |
| | | this.timer = setInterval(this.updateTime, 1000); |
| | | }, |
| | | beforeDestroy() { |
| | | if (this.timer) { |
| | | clearInterval(this.timer); |
| | | } |
| | | }, |
| | | methods: { |
| | | updateTime() { |
| | | const timeData = formatTime(); |
| | | this.currentDate = timeData.currentDate; |
| | | this.currentDay = timeData.currentDay; |
| | | this.currentTime = timeData.currentTime; |
| | | }, |
| | | prev() { |
| | | let target = this.activeIndex - 1; |
| | | if (target < 0) target = 4; |
| | | this.selectItem(target); |
| | | }, |
| | | next() { |
| | | let target = this.activeIndex + 1; |
| | | if (target > 4) target = 0; |
| | | this.selectItem(target); |
| | | }, |
| | | selectItem(targetIndex) { |
| | | if (targetIndex === this.activeIndex) return; |
| | | |
| | | let wrappers = []; |
| | | this.items.forEach((item, index) => { |
| | | let oldDiff = index - this.activeIndex; |
| | | if (oldDiff < -2) oldDiff += 5; |
| | | if (oldDiff > 2) oldDiff -= 5; |
| | | |
| | | let newDiff = index - targetIndex; |
| | | if (newDiff < -2) newDiff += 5; |
| | | if (newDiff > 2) newDiff -= 5; |
| | | |
| | | // 判断是否跨越了边界(即从一端飞到另一端) |
| | | if (Math.abs(oldDiff - newDiff) > 2) { |
| | | wrappers.push(index); |
| | | } |
| | | }); |
| | | |
| | | this.wrappingIndices = wrappers; |
| | | this.activeIndex = targetIndex; |
| | | |
| | | // 稍微加长等待时间,确保浏览器绘制透明的瞬移状态,再恢复过渡动画和透明度 |
| | | setTimeout(() => { |
| | | this.wrappingIndices = []; |
| | | }, 50); |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="less" scoped src="./index.less"></style> |
| New file |
| | |
| | | import * as echarts from "echarts"; |
| | | |
| | | /** |
| | | * 初始化当日各时段订单情况折线图 |
| | | * @param {HTMLElement} chartDom - 图表容器 DOM 元素 |
| | | * @param {Array<string>} xData - X 轴类目数据(例如:小时列表) |
| | | * @param {Array<number>} seriesData - 折线图对应的数值数据 |
| | | * @returns {echarts.ECharts} ECharts 实例 |
| | | */ |
| | | export function initDayOrderVolumeChart(chartDom, xData, seriesData) { |
| | | if (!chartDom) return null; |
| | | const myChart = echarts.init(chartDom); |
| | | |
| | | const option = { |
| | | grid: { |
| | | top: "40px", |
| | | left: "20px", |
| | | right: "20px", |
| | | bottom: "20px", |
| | | containLabel: true, |
| | | }, |
| | | title: { |
| | | text: "订单量", |
| | | textStyle: { |
| | | fontSize: 13, |
| | | color: "#B4C0CC", |
| | | fontFamily: "Alibaba PuHuiTi 2.0" |
| | | }, |
| | | left: "15px", |
| | | top: "10px", |
| | | }, |
| | | tooltip: { |
| | | trigger: "axis", |
| | | axisPointer: { |
| | | type: "line", |
| | | lineStyle: { |
| | | color: "rgba(255, 218, 107, 0.5)", |
| | | type: "dashed" |
| | | } |
| | | }, |
| | | backgroundColor: "#0b1e53", |
| | | borderColor: "rgba(173, 198, 255, 0.502)", |
| | | textStyle: { |
| | | color: "#E7F9FF", |
| | | }, |
| | | formatter: function (params) { |
| | | const data = params[0]; |
| | | return ` |
| | | <div> |
| | | ${data.name}时 |
| | | <div style="display:flex;align-items:center;"> |
| | | <span style="display:inline-block;margin-right:4px;border-radius:10px;width:10px;height:10px;background-color:#FFDA6B;"></span> |
| | | <p style="color:#fff;margin-top:4px;">订单量</p> |
| | | </div> |
| | | <span style="font-size: 18px; font-weight: bold; margin-top: 4px; display: block;color:rgba(193, 234, 248, 1);">${data.value}</span> |
| | | </div> |
| | | `; |
| | | } |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: xData, |
| | | boundaryGap: false, |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: "rgba(180, 192, 204, 0.2)", |
| | | }, |
| | | }, |
| | | axisTick: { |
| | | show: false, |
| | | }, |
| | | axisLabel: { |
| | | color: "#B4C0CC", |
| | | fontFamily: "Alibaba PuHuiTi 2.0", |
| | | fontSize: 12, |
| | | margin: 15 |
| | | }, |
| | | splitLine: { |
| | | show: false |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | min: 0, |
| | | max: 250, |
| | | interval: 50, |
| | | splitLine: { |
| | | show: false |
| | | }, |
| | | axisLine: { |
| | | show: false, |
| | | }, |
| | | axisTick: { |
| | | show: false, |
| | | }, |
| | | axisLabel: { |
| | | color: "#B4C0CC", |
| | | fontFamily: "Alibaba PuHuiTi 2.0", |
| | | fontSize: 12, |
| | | margin: 15 |
| | | }, |
| | | }, |
| | | series: [ |
| | | { |
| | | name: "订单量", |
| | | type: "line", |
| | | smooth: true, |
| | | symbol: "circle", |
| | | symbolSize: 8, |
| | | showSymbol: true, |
| | | itemStyle: { |
| | | color: "#FFDA6B", |
| | | borderColor: "#0b1e53", |
| | | borderWidth: 2, |
| | | }, |
| | | lineStyle: { |
| | | width: 3, |
| | | color: "#FFDA6B", |
| | | }, |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: "rgba(255, 218, 107, 0.2)" }, |
| | | { offset: 1, color: "rgba(255, 218, 107, 0)" } |
| | | ]) |
| | | }, |
| | | emphasis: { |
| | | focus: "series", |
| | | itemStyle: { |
| | | color: "#FFDA6B", |
| | | borderColor: "#0b1e53", |
| | | borderWidth: 3, |
| | | }, |
| | | }, |
| | | data: seriesData, |
| | | } |
| | | ], |
| | | }; |
| | | |
| | | myChart.setOption(option); |
| | | return myChart; |
| | | } |
| New file |
| | |
| | | import * as echarts from "echarts"; |
| | | |
| | | /** |
| | | * 初始化新增乘客柱状图 |
| | | * @param {HTMLElement} chartDom - 图表容器 DOM 元素 |
| | | * @param {Array<string>} xData - X 轴类目数据(例如:月份或日期列表) |
| | | * @param {Array<number>} seriesData - 柱状图对应的数值数据 |
| | | * @returns {echarts.ECharts} ECharts 实例 |
| | | */ |
| | | export function initNewPassengerChart(chartDom, xData, seriesData) { |
| | | if (!chartDom) return null; |
| | | const myChart = echarts.init(chartDom); |
| | | |
| | | // 辅助背景柱的数据,始终等于 Y 轴的 max (50) |
| | | const bgData = new Array(seriesData.length).fill(50); |
| | | |
| | | const option = { |
| | | grid: { |
| | | top: "30px", |
| | | left: "20px", |
| | | right: "20px", |
| | | bottom: "20px", |
| | | containLabel: true, |
| | | }, |
| | | tooltip: { |
| | | trigger: "axis", |
| | | axisPointer: { |
| | | type: "shadow", |
| | | }, |
| | | backgroundColor: "#0b1e53", |
| | | borderColor: "rgba(173, 198, 255, 0.502)", |
| | | textStyle: { |
| | | color: "#E7F9FF", |
| | | }, |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: xData, |
| | | axisLine: { |
| | | show: false, |
| | | }, |
| | | axisTick: { |
| | | show: false, |
| | | }, |
| | | axisLabel: { |
| | | color: "#B4C0CC", |
| | | fontFamily: "Alibaba-PuHuiTi-R", |
| | | fontSize: 12 |
| | | }, |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | min: 0, |
| | | max: 50, |
| | | interval: 10, |
| | | splitLine: { |
| | | show: false, |
| | | }, |
| | | axisLine: { |
| | | show: false, |
| | | }, |
| | | axisTick: { |
| | | show: false, |
| | | }, |
| | | axisLabel: { |
| | | color: "#B4C0CC", |
| | | fontFamily: "Alibaba-PuHuiTi-R", |
| | | fontSize: 12 |
| | | }, |
| | | }, |
| | | series: [ |
| | | { |
| | | name: "新增乘客", |
| | | type: "bar", |
| | | barWidth: 10, |
| | | // 渐变色:从浅蓝到深蓝 (注意 echarts 的 LinearGradient 坐标系:0,0,0,1 表示从上到下) |
| | | // 设计要求:background: linear-gradient(180deg, #0055ff00 1%, #0783fa 100%); |
| | | itemStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: "rgba(0, 85, 255, 0)" }, // #0055ff00 顶部全透明 |
| | | { offset: 1, color: "#0783fa" } // 底部蓝色 |
| | | ]) |
| | | }, |
| | | // 防止鼠标悬浮时颜色变化或者其他默认高亮行为影响视觉 |
| | | emphasis: { |
| | | itemStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: "rgba(0, 85, 255, 0)" }, |
| | | { offset: 1, color: "#0783fa" } |
| | | ]) |
| | | } |
| | | }, |
| | | // 顶部的小横线模拟:可以通过一个独立的 scatter 或者配置顶部的 markPoint/markLine,这里为了简单完美,直接利用 bar 本身和背景 bar 来组合,或者在上面盖一层 |
| | | data: seriesData, |
| | | }, |
| | | // 这个用于渲染顶部的横线 |
| | | { |
| | | name: "顶部横线", |
| | | type: "pictorialBar", |
| | | symbol: "rect", |
| | | symbolSize: [10, 4], // 宽10,与主柱子 barWidth 一致,高2 |
| | | symbolPosition: "end", // 定位到柱子的顶端 |
| | | symbolOffset: [-2, -2], // 强制向上偏移2个像素(等于横线高度),使其彻底贴在柱子最上方的边缘外侧 |
| | | itemStyle: { |
| | | color: "#15b1ff", |
| | | }, |
| | | // 防止鼠标悬浮时图形变大 |
| | | emphasis: { |
| | | scale: false, |
| | | itemStyle: { |
| | | color: "#15b1ff", |
| | | } |
| | | }, |
| | | tooltip: { |
| | | show: false // 禁用顶部横线在 tooltip 中的显示 |
| | | }, |
| | | data: seriesData, |
| | | z: 10 |
| | | }, |
| | | // 这个用于渲染底部的灰色柱子背景(整列阴影) |
| | | { |
| | | name: "背景阴影", |
| | | type: "bar", |
| | | barWidth: 20, |
| | | barGap: "-150%", // 让背景阴影和主柱状图重叠并稍微宽一点 |
| | | itemStyle: { |
| | | color: "rgba(255, 255, 255, 0.05)", |
| | | }, |
| | | data: bgData, |
| | | z: -1, |
| | | tooltip: { |
| | | show: false |
| | | } |
| | | } |
| | | ], |
| | | }; |
| | | |
| | | myChart.setOption(option); |
| | | return myChart; |
| | | } |
| New file |
| | |
| | | import * as echarts from "echarts"; |
| | | |
| | | /** |
| | | * 初始化订单量柱状图(分段渐变样式) |
| | | * @param {HTMLElement} chartDom - 图表容器 DOM 元素 |
| | | * @param {Array<string>} xData - X 轴类目数据(例如:月份或日期列表) |
| | | * @param {Array<number>} seriesData - 柱状图对应的数值数据 |
| | | * @returns {echarts.ECharts} ECharts 实例 |
| | | */ |
| | | export function initOrderVolumeChart(chartDom, xData, seriesData) { |
| | | if (!chartDom) return null; |
| | | const myChart = echarts.init(chartDom); |
| | | |
| | | const option = { |
| | | grid: { |
| | | top: "40px", |
| | | left: "20px", |
| | | right: "20px", |
| | | bottom: "20px", |
| | | containLabel: true, |
| | | }, |
| | | title: { |
| | | text: "订单量", |
| | | textStyle: { |
| | | fontSize: 13, |
| | | color: "#B4C0CC", |
| | | fontFamily: "Alibaba-PuHuiTi-R" |
| | | }, |
| | | left: "15px", |
| | | top: "10px", |
| | | }, |
| | | tooltip: { |
| | | trigger: "axis", |
| | | axisPointer: { |
| | | type: "shadow", |
| | | }, |
| | | backgroundColor: "#0b1e53", |
| | | borderColor: "rgba(173, 198, 255, 0.502)", |
| | | textStyle: { |
| | | color: "#E7F9FF", |
| | | }, |
| | | formatter: function (params) { |
| | | const data = params[0]; |
| | | return ` |
| | | <div> |
| | | ${data.name} |
| | | <div style="display:flex;align-items:center;"> |
| | | <span style="display:inline-block;margin-right:4px;border-radius:10px;width:10px;height:10px;background-color:#15b1ff;"></span> |
| | | <p style="color:#fff;margin-top:4px;">订单量</p> |
| | | </div> |
| | | <span style="font-size: 18px; font-weight: bold; margin-top: 4px; display: block;color:rgba(193, 234, 248, 1);">${data.value}</span> |
| | | </div> |
| | | `; |
| | | } |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: xData, |
| | | axisLine: { |
| | | show: false |
| | | }, |
| | | axisTick: { |
| | | show: false, |
| | | }, |
| | | axisLabel: { |
| | | color: "#B4C0CC", |
| | | fontFamily: "Alibaba-PuHuiTi-R", |
| | | fontSize: 12, |
| | | margin: 15 |
| | | }, |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | min: 0, |
| | | max: 250, |
| | | interval: 50, |
| | | splitLine: { |
| | | show: false // 设计图中Y轴没有横向网格线 |
| | | }, |
| | | axisLine: { |
| | | show: false, |
| | | }, |
| | | axisTick: { |
| | | show: false, |
| | | }, |
| | | axisLabel: { |
| | | color: "#B4C0CC", |
| | | fontFamily: "Alibaba-PuHuiTi-R", |
| | | fontSize: 12, |
| | | margin: 15 |
| | | }, |
| | | }, |
| | | series: [ |
| | | { |
| | | name: "订单量", |
| | | type: "pictorialBar", |
| | | symbol: "rect", |
| | | symbolRepeat: true, |
| | | symbolSize: [16, 4], // 宽度16,高度4,形成长条分段 |
| | | symbolMargin: 2, // 分段之间的间隔 |
| | | itemStyle: { |
| | | // 渐变色:从浅蓝到深蓝 (注意 echarts 的 LinearGradient 坐标系:0,0,0,1 表示从上到下) |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: "#00FFFF" }, |
| | | { offset: 1, color: "#0055FF" } |
| | | ]) |
| | | }, |
| | | emphasis: { |
| | | itemStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: "#00FFFF" }, |
| | | { offset: 1, color: "#0055FF" } |
| | | ]) |
| | | } |
| | | }, |
| | | data: seriesData, |
| | | z: 10 |
| | | } |
| | | ], |
| | | }; |
| | | |
| | | myChart.setOption(option); |
| | | return myChart; |
| | | } |
| New file |
| | |
| | | import * as echarts from "echarts"; |
| | | |
| | | /** |
| | | * 初始化总体订单统计饼图 |
| | | * @param {HTMLElement} chartDom - 图表容器 DOM 元素 |
| | | * @param {Array} orderStats - 订单统计数据源 |
| | | * @returns {echarts.ECharts} ECharts 实例 |
| | | */ |
| | | export function initTotalOrderChart(chartDom, orderStats) { |
| | | if (!chartDom) return null; |
| | | |
| | | const myChart = echarts.init(chartDom); |
| | | |
| | | const option = { |
| | | // 通过配置默认的 legend 来配合 dispatchAction 控制扇区隐藏显示 |
| | | legend: { |
| | | show: false, // 隐藏原生 legend |
| | | selected: orderStats.reduce((acc, cur) => { |
| | | acc[cur.name] = cur.selected !== false; |
| | | return acc; |
| | | }, {}) |
| | | }, |
| | | series: [ |
| | | { |
| | | name: "订单统计", |
| | | type: "pie", |
| | | radius: ["35%", "85%%"], |
| | | center: ["50%", "50%"], |
| | | avoidLabelOverlap: false, |
| | | label: { |
| | | show: false, |
| | | position: 'center' |
| | | }, |
| | | emphasis: { |
| | | label: { |
| | | show: false |
| | | } |
| | | }, |
| | | labelLine: { |
| | | show: false |
| | | }, |
| | | data: orderStats.map(item => ({ |
| | | value: item.value, |
| | | name: item.name, |
| | | itemStyle: { color: item.color } |
| | | })) |
| | | } |
| | | ] |
| | | }; |
| | | |
| | | myChart.setOption(option); |
| | | return myChart; |
| | | } |
| New file |
| | |
| | | import * as echarts from "echarts"; |
| | | |
| | | /** |
| | | * 初始化年度趋势图表 |
| | | * @param {HTMLElement} chartDom - 图表容器 DOM 元素 |
| | | * @returns {echarts.ECharts} ECharts 实例 |
| | | */ |
| | | export function initYearTrendChart(chartDom) { |
| | | if (!chartDom) return null; |
| | | const myChart = echarts.init(chartDom); |
| | | |
| | | const option = { |
| | | grid: { |
| | | top: "55px", |
| | | left: "20px", |
| | | right: "20px", |
| | | bottom: "20px", |
| | | containLabel: true, |
| | | }, |
| | | title: { |
| | | text: "成功订单(万)", |
| | | textStyle: { |
| | | fontSize: 13, |
| | | color: "#B4C0CC", |
| | | }, |
| | | left: "15px", |
| | | top: "15px", |
| | | }, |
| | | tooltip: { |
| | | trigger: "axis", |
| | | backgroundColor: "#0b1e53", |
| | | borderColor: "rgba(173, 198, 255, 0.502)", |
| | | padding: [12, 12, 7, 12], |
| | | textStyle: { |
| | | fontSize: 16, |
| | | color: "#E7F9FF", |
| | | }, |
| | | formatter: function (params) { |
| | | const data = params[0]; |
| | | return ` |
| | | <div> |
| | | 2026年${data.name}月 |
| | | <div style="display:flex;align-items:center;"> |
| | | <span style="display:inline-block;margin-right:4px;border-radius:10px;width:10px;height:10px;background-color:#66FFFF;"></span> |
| | | <p style="color:#fff;margin-top:4px;">成功订单</p> |
| | | </div> |
| | | <span style="font-size: 18px; font-weight: bold; margin-top: 4px; display: block;color:rgba(193, 234, 248, 1);">${data.value}万</span> |
| | | </div> |
| | | `; |
| | | }, |
| | | axisPointer: { |
| | | type: "none", |
| | | }, |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"], |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: "#242D34", |
| | | }, |
| | | }, |
| | | axisTick: { |
| | | show: false, |
| | | }, |
| | | axisLabel: { |
| | | color: "#B4C0CC", |
| | | }, |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | min: 0, |
| | | max: 5000, |
| | | interval: 1000, |
| | | splitLine: { |
| | | show: true, |
| | | lineStyle: { |
| | | type: "dashed", |
| | | color: "rgba(180, 192, 204, 0.2)", |
| | | }, |
| | | }, |
| | | axisLine: { |
| | | show: false, |
| | | }, |
| | | axisTick: { |
| | | show: false, |
| | | }, |
| | | axisLabel: { |
| | | color: "#B4C0CC", |
| | | formatter: "{value}", |
| | | }, |
| | | nameGap: 15, |
| | | }, |
| | | series: [ |
| | | { |
| | | name: "成功订单", |
| | | type: "line", |
| | | smooth: true, |
| | | symbol: "emptyCircle", |
| | | symbolSize: 6, |
| | | showSymbol: false, |
| | | itemStyle: { |
| | | color: "#66FFFF", |
| | | borderColor: "#66FFFF", |
| | | borderWidth: 2, |
| | | }, |
| | | lineStyle: { |
| | | width: 4, |
| | | color: "#66FFFF", |
| | | cap: "round", |
| | | join: "round", |
| | | }, |
| | | emphasis: { |
| | | focus: "series", |
| | | itemStyle: { |
| | | color: "#0D274D", |
| | | borderColor: "#66FFFF", |
| | | borderWidth: 2, |
| | | }, |
| | | }, |
| | | data: [2500, 3500, 2300, 3000, 2500, 3300, 1600, 2000, 1600, 3000, 1600, 2500], |
| | | }, |
| | | ], |
| | | }; |
| | | |
| | | myChart.setOption(option); |
| | | return myChart; |
| | | } |
| New file |
| | |
| | | <template> |
| | | <div class="page-container"> |
| | | <!-- 顶部导航栏 --> |
| | | <div class="top-bar"> |
| | | <!-- 左侧图标 --> |
| | | <div class="top-left"> |
| | | <div class="icon-circle"> |
| | | <img src="~@/assets/img/home/home-icon.png" class="home-icon" /> |
| | | </div> |
| | | <div class="icon-circle"> |
| | | <img src="~@/assets/img/home/exit-icon.png" class="exit-icon" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 中间标题 --> |
| | | <div class="top-title">放心行可视化数据平台</div> |
| | | |
| | | <!-- 右侧时间 --> |
| | | <div class="top-right"> |
| | | <span>{{ currentDate }}</span> |
| | | <span>{{ currentDay }}</span> |
| | | <span>{{ currentTime }}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 内容区 --> |
| | | <div class="content-container"> |
| | | <!-- 左侧 --> |
| | | <div class="left-content"> |
| | | <!-- 年度趋势 --> |
| | | <div class="year-trend"> |
| | | <TitleBox> |
| | | <template #left>年度趋势</template> |
| | | <template #right> |
| | | <div class="date-picker-box"> |
| | | <img src="~@/assets/img/home/date-icon.png" class="date-icon" /> |
| | | <span>2026</span> |
| | | </div> |
| | | </template> |
| | | </TitleBox> |
| | | <div class="chart-container" ref="yearTrendChart"></div> |
| | | </div> |
| | | |
| | | <!-- 总体订单统计 --> |
| | | <div class="total-order"> |
| | | <TitleBox> |
| | | <template #left>总体订单统计</template> |
| | | <template #right> |
| | | <div class="date-range-picker-box"> |
| | | <div class="date-item"> |
| | | <img |
| | | src="~@/assets/img/home/date-icon.png" |
| | | class="date-icon" |
| | | /> |
| | | <span>2026-01-30</span> |
| | | </div> |
| | | <div class="date-item"> |
| | | <img |
| | | src="~@/assets/img/home/date-icon.png" |
| | | class="date-icon" |
| | | /> |
| | | <span>2026-05-30</span> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </TitleBox> |
| | | |
| | | <div class="pie-chart-wrapper"> |
| | | <div class="pie-chart-bg"> |
| | | <div class="pie-chart-container" ref="totalOrderChart"></div> |
| | | <div class="chart-title"> |
| | | <div class="title">12345</div> |
| | | <div class="subtitle">总订单</div> |
| | | </div> |
| | | </div> |
| | | <div class="legend-container"> |
| | | <div |
| | | class="legend-item" |
| | | v-for="(item, index) in orderStats" |
| | | :key="index" |
| | | @click="toggleLegend(item)" |
| | | :style="{ opacity: item.selected === false ? 0.5 : 1 }" |
| | | > |
| | | <div class="legend-title-wrap"> |
| | | <span |
| | | class="dot" |
| | | :style="{ |
| | | backgroundColor: |
| | | item.selected === false ? '#ccc' : item.color, |
| | | }" |
| | | ></span> |
| | | <span class="name">{{ item.name }}</span> |
| | | </div> |
| | | <div class="legend-value-wrap"> |
| | | <span>{{ item.value }}</span> |
| | | <span class="percent">({{ item.percent }})</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 新增乘客 --> |
| | | <div class="new-passenger"> |
| | | <TitleBox> |
| | | <template #left>新增乘客</template> |
| | | <template #right> |
| | | <div class="filter-btn-group"> |
| | | <div |
| | | class="filter-btn" |
| | | :class="{ active: newPassengerFilter === 'day' }" |
| | | @click="newPassengerFilter = 'day'" |
| | | > |
| | | 日新增 |
| | | </div> |
| | | <div |
| | | class="filter-btn" |
| | | :class="{ active: newPassengerFilter === 'month' }" |
| | | @click="newPassengerFilter = 'month'" |
| | | > |
| | | 月新增 |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </TitleBox> |
| | | <div class="chart-container" ref="newPassengerChart"></div> |
| | | </div> |
| | | </div> |
| | | <!-- 中间内容 --> |
| | | <div class="middle-content"> |
| | | <!-- 订单量 --> |
| | | <div class="order-volume"> |
| | | <TitleBox> |
| | | <template #left>订单量</template> |
| | | <template #right> |
| | | <div class="filter-btn-group"> |
| | | <div |
| | | class="filter-btn" |
| | | :class="{ active: orderVolumeFilter === 'day' }" |
| | | @click="orderVolumeFilter = 'day'" |
| | | > |
| | | 日订单量 |
| | | </div> |
| | | <div |
| | | class="filter-btn" |
| | | :class="{ active: orderVolumeFilter === 'month' }" |
| | | @click="orderVolumeFilter = 'month'" |
| | | > |
| | | 月订单量 |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </TitleBox> |
| | | <div class="chart-container" ref="orderVolumeChart"></div> |
| | | </div> |
| | | <!-- 当日各时段订单情况 --> |
| | | <div class="day-order-volume"> |
| | | <TitleBox> |
| | | <template #left>当日各时段订单情况</template> |
| | | <template #right> |
| | | <div class="time-picker-box"> |
| | | <img src="~@/assets/img/home/history-icon.png" class="time-icon" /> |
| | | <span>16~24时</span> |
| | | </div> |
| | | </template> |
| | | </TitleBox> |
| | | <div class="chart-container" ref="dayOrderVolumeChart"></div> |
| | | </div> |
| | | </div> |
| | | <!-- 右侧 --> |
| | | <div class="right-content"> |
| | | <!-- 司机接单排名 --> |
| | | <div class="driver-order-rank"></div> |
| | | <!-- 乘客排名 --> |
| | | <!-- 乘客排名 --> |
| | | <div class="passenger-rank"> |
| | | <TitleBox> |
| | | <template #left>乘客排名</template> |
| | | <template #right> |
| | | <div class="filter-btn-group"> |
| | | <div |
| | | class="filter-btn" |
| | | :class="{ active: passengerRankFilter === 'day' }" |
| | | @click="passengerRankFilter = 'day'" |
| | | > |
| | | 今日 |
| | | </div> |
| | | <div |
| | | class="filter-btn" |
| | | :class="{ active: passengerRankFilter === 'week' }" |
| | | @click="passengerRankFilter = 'week'" |
| | | > |
| | | 本周 |
| | | </div> |
| | | <div |
| | | class="filter-btn" |
| | | :class="{ active: passengerRankFilter === 'month' }" |
| | | @click="passengerRankFilter = 'month'" |
| | | > |
| | | 本月 |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </TitleBox> |
| | | |
| | | <div class="rank-list-container"> |
| | | <div class="rank-header"> |
| | | <span class="header-index"></span> |
| | | <span class="header-phone">乘客电话</span> |
| | | <span class="header-count">叫车次数</span> |
| | | <span class="header-orders">完成订单数</span> |
| | | </div> |
| | | <div class="rank-list"> |
| | | <div |
| | | class="rank-item" |
| | | v-for="(item, index) in passengerRankList" |
| | | :key="index" |
| | | :class="'bg-' + (index < 3 ? index + 1 : 4)" |
| | | > |
| | | <div class="rank-index-wrapper"> |
| | | TOP <span class="rank-number" :class="'rank-' + (index < 3 ? index + 1 : 4)">{{ index + 1 }}</span> |
| | | </div> |
| | | <div class="item-phone">{{ item.phone }}</div> |
| | | <div class="item-count">{{ item.count }}</div> |
| | | <div class="item-orders">{{ item.orders }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import { formatTime } from "@/utils/time"; |
| | | import TitleBox from "@/components/TitleBox.vue"; |
| | | import { initYearTrendChart } from "./charts/yearTrend"; |
| | | import { initTotalOrderChart } from "./charts/totalOrder"; |
| | | import { initNewPassengerChart } from "./charts/newPassenger"; |
| | | import { initOrderVolumeChart } from "./charts/orderVolume"; |
| | | import { initDayOrderVolumeChart } from "./charts/dayOrderVolume"; |
| | | |
| | | export default { |
| | | name: "StatisticalTable", |
| | | components: { |
| | | TitleBox, |
| | | }, |
| | | props: {}, |
| | | data() { |
| | | return { |
| | | /** @type {string} 当前日期 */ |
| | | currentDate: "", |
| | | /** @type {string} 当前星期 */ |
| | | currentDay: "", |
| | | /** @type {string} 当前时间 */ |
| | | currentTime: "", |
| | | /** @type {number|null} 时间更新定时器 ID */ |
| | | timer: null, |
| | | /** @type {Array} 总体订单统计图例数据源 */ |
| | | orderStats: [ |
| | | { |
| | | name: "乘客取消", |
| | | value: 748, |
| | | percent: "23%", |
| | | color: "#00FFFF", |
| | | selected: true, |
| | | }, |
| | | { |
| | | name: "完成订单", |
| | | value: 680, |
| | | percent: "13%", |
| | | color: "#1B82FF", |
| | | selected: true, |
| | | }, |
| | | { |
| | | name: "司机取消", |
| | | value: 548, |
| | | percent: "11%", |
| | | color: "#FFD159", |
| | | selected: true, |
| | | }, |
| | | { |
| | | name: "超时订单", |
| | | value: 548, |
| | | percent: "11%", |
| | | color: "#FF7F59", |
| | | selected: true, |
| | | }, |
| | | { |
| | | name: "无效订单", |
| | | value: 548, |
| | | percent: "11%", |
| | | color: "#7359FF", |
| | | selected: true, |
| | | }, |
| | | { |
| | | name: "未处理订单", |
| | | value: 548, |
| | | percent: "11%", |
| | | color: "#59FF59", |
| | | selected: true, |
| | | }, |
| | | ], |
| | | /** @type {string} 新增乘客筛选类型: 'day' 日新增 | 'month' 月新增 */ |
| | | newPassengerFilter: "month", |
| | | /** @type {string} 订单量筛选类型: 'day' 日订单量 | 'month' 月订单量 */ |
| | | orderVolumeFilter: "month", |
| | | /** @type {string} 乘客排名筛选类型: 'day' | 'week' | 'month' */ |
| | | passengerRankFilter: "day", |
| | | /** @type {Array} 乘客排名数据 */ |
| | | passengerRankList: [], |
| | | /** @type {Object|null} 年度趋势图表实例 */ |
| | | yearTrendChartInstance: null, |
| | | /** @type {Object|null} 总体订单图表实例 */ |
| | | totalOrderChartInstance: null, |
| | | /** @type {Object|null} 新增乘客图表实例 */ |
| | | newPassengerChartInstance: null, |
| | | /** @type {Object|null} 订单量图表实例 */ |
| | | orderVolumeChartInstance: null, |
| | | /** @type {Object|null} 当日各时段订单情况图表实例 */ |
| | | dayOrderVolumeChartInstance: null, |
| | | }; |
| | | }, |
| | | computed: {}, |
| | | watch: { |
| | | /** |
| | | * 监听新增乘客的筛选条件,改变时重新渲染图表 |
| | | */ |
| | | newPassengerFilter() { |
| | | this.initNewPassengerChart(); |
| | | }, |
| | | /** |
| | | * 监听订单量的筛选条件,改变时重新渲染图表 |
| | | */ |
| | | orderVolumeFilter() { |
| | | this.initOrderVolumeChart(); |
| | | }, |
| | | /** |
| | | * 监听乘客排名的筛选条件,改变时更新数据 |
| | | */ |
| | | passengerRankFilter() { |
| | | this.fetchPassengerRankData(); |
| | | } |
| | | }, |
| | | created() { |
| | | this.updateTime(); |
| | | this.timer = setInterval(this.updateTime, 1000); |
| | | }, |
| | | beforeDestroy() { |
| | | if (this.timer) { |
| | | clearInterval(this.timer); |
| | | } |
| | | if (this.yearTrendChartInstance) { |
| | | this.yearTrendChartInstance.dispose(); |
| | | } |
| | | if (this.totalOrderChartInstance) { |
| | | this.totalOrderChartInstance.dispose(); |
| | | } |
| | | if (this.newPassengerChartInstance) { |
| | | this.newPassengerChartInstance.dispose(); |
| | | } |
| | | if (this.orderVolumeChartInstance) { |
| | | this.orderVolumeChartInstance.dispose(); |
| | | } |
| | | if (this.dayOrderVolumeChartInstance) { |
| | | this.dayOrderVolumeChartInstance.dispose(); |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.initChart(); |
| | | this.initTotalOrderChart(); |
| | | this.initNewPassengerChart(); |
| | | this.initOrderVolumeChart(); |
| | | this.initDayOrderVolumeChart(); |
| | | this.fetchPassengerRankData(); |
| | | |
| | | window.addEventListener("resize", this.handleResize); |
| | | }, |
| | | destroyed() { |
| | | window.removeEventListener("resize", this.handleResize); |
| | | }, |
| | | methods: { |
| | | /** |
| | | * 更新顶部时间显示 |
| | | */ |
| | | updateTime() { |
| | | const timeData = formatTime(); |
| | | this.currentDate = timeData.currentDate; |
| | | this.currentDay = timeData.currentDay; |
| | | this.currentTime = timeData.currentTime; |
| | | }, |
| | | /** |
| | | * 处理窗口改变大小事件,重置所有图表大小 |
| | | */ |
| | | handleResize() { |
| | | if (this.yearTrendChartInstance) { |
| | | this.yearTrendChartInstance.resize(); |
| | | } |
| | | if (this.totalOrderChartInstance) { |
| | | this.totalOrderChartInstance.resize(); |
| | | } |
| | | if (this.newPassengerChartInstance) { |
| | | this.newPassengerChartInstance.resize(); |
| | | } |
| | | if (this.orderVolumeChartInstance) { |
| | | this.orderVolumeChartInstance.resize(); |
| | | } |
| | | if (this.dayOrderVolumeChartInstance) { |
| | | this.dayOrderVolumeChartInstance.resize(); |
| | | } |
| | | }, |
| | | /** |
| | | * 初始化年度趋势图表 |
| | | */ |
| | | initChart() { |
| | | if (this.yearTrendChartInstance) { |
| | | this.yearTrendChartInstance.dispose(); |
| | | } |
| | | this.yearTrendChartInstance = initYearTrendChart( |
| | | this.$refs.yearTrendChart |
| | | ); |
| | | }, |
| | | /** |
| | | * 初始化总体订单图表 |
| | | */ |
| | | initTotalOrderChart() { |
| | | if (this.totalOrderChartInstance) { |
| | | this.totalOrderChartInstance.dispose(); |
| | | } |
| | | this.totalOrderChartInstance = initTotalOrderChart( |
| | | this.$refs.totalOrderChart, |
| | | this.orderStats |
| | | ); |
| | | }, |
| | | /** |
| | | * 切换图例显示/隐藏 |
| | | * @param {Object} item - 被点击的图例对象 |
| | | */ |
| | | toggleLegend(item) { |
| | | if (item.selected === undefined) { |
| | | this.$set(item, "selected", false); |
| | | } else { |
| | | item.selected = !item.selected; |
| | | } |
| | | |
| | | // 触发 echarts 内部的 action 隐藏或显示对应扇区 |
| | | if (this.totalOrderChartInstance) { |
| | | this.totalOrderChartInstance.dispatchAction({ |
| | | type: "legendToggleSelect", |
| | | name: item.name, |
| | | }); |
| | | } |
| | | }, |
| | | /** |
| | | * 初始化/更新新增乘客图表数据 |
| | | */ |
| | | initNewPassengerChart() { |
| | | if (this.newPassengerChartInstance) { |
| | | this.newPassengerChartInstance.dispose(); |
| | | } |
| | | |
| | | let xData = []; |
| | | let seriesData = []; |
| | | |
| | | if (this.newPassengerFilter === "month") { |
| | | // 月新增:生成当前年 1-12 月 |
| | | xData = Array.from({ length: 12 }, (_, i) => `${i + 1}月`); |
| | | // 模拟 12 个月的随机数据 |
| | | seriesData = Array.from( |
| | | { length: 12 }, |
| | | () => Math.floor(Math.random() * 40) + 10 |
| | | ); |
| | | } else { |
| | | // 日新增:生成当前月的所有日 |
| | | const now = new Date(); |
| | | const year = now.getFullYear(); |
| | | const month = now.getMonth() + 1; |
| | | const daysInMonth = new Date(year, month, 0).getDate(); // 获取当月天数 |
| | | |
| | | xData = Array.from({ length: daysInMonth }, (_, i) => `${i + 1}日`); |
| | | // 模拟每天的随机数据 |
| | | seriesData = Array.from( |
| | | { length: daysInMonth }, |
| | | () => Math.floor(Math.random() * 40) + 10 |
| | | ); |
| | | } |
| | | |
| | | this.newPassengerChartInstance = initNewPassengerChart( |
| | | this.$refs.newPassengerChart, |
| | | xData, |
| | | seriesData |
| | | ); |
| | | }, |
| | | /** |
| | | * 初始化/更新订单量图表数据 |
| | | */ |
| | | initOrderVolumeChart() { |
| | | if (this.orderVolumeChartInstance) { |
| | | this.orderVolumeChartInstance.dispose(); |
| | | } |
| | | |
| | | let xData = []; |
| | | let seriesData = []; |
| | | |
| | | if (this.orderVolumeFilter === "month") { |
| | | // 月新增:生成当前年 1-12 月 |
| | | xData = Array.from( |
| | | { length: 12 }, |
| | | (_, i) => `${String(i + 1).padStart(2, "0")}` |
| | | ); |
| | | // 模拟数据 |
| | | seriesData = [ |
| | | 178, 145, 160, 115, 225, 145, 105, 245, 178, 105, 150, 75, |
| | | ]; |
| | | } else { |
| | | // 日新增:生成当前月的所有日 |
| | | const now = new Date(); |
| | | const year = now.getFullYear(); |
| | | const month = now.getMonth() + 1; |
| | | const daysInMonth = new Date(year, month, 0).getDate(); |
| | | |
| | | xData = Array.from( |
| | | { length: daysInMonth }, |
| | | (_, i) => `${String(i + 1).padStart(2, "0")}` |
| | | ); |
| | | seriesData = Array.from( |
| | | { length: daysInMonth }, |
| | | () => Math.floor(Math.random() * 200) + 50 |
| | | ); |
| | | } |
| | | |
| | | this.orderVolumeChartInstance = initOrderVolumeChart( |
| | | this.$refs.orderVolumeChart, |
| | | xData, |
| | | seriesData |
| | | ); |
| | | }, |
| | | /** |
| | | * 初始化当日各时段订单情况图表数据 |
| | | */ |
| | | initDayOrderVolumeChart() { |
| | | if (this.dayOrderVolumeChartInstance) { |
| | | this.dayOrderVolumeChartInstance.dispose(); |
| | | } |
| | | |
| | | const xData = [ |
| | | "01", |
| | | "02", |
| | | "03", |
| | | "04", |
| | | "05", |
| | | "06", |
| | | "07", |
| | | "08", |
| | | "09", |
| | | "10", |
| | | "11", |
| | | "12", |
| | | ]; |
| | | const seriesData = [ |
| | | 85, 130, 185, 145, 120, 180, 170, 160, 215, 120, 85, 85, |
| | | ]; |
| | | |
| | | this.dayOrderVolumeChartInstance = initDayOrderVolumeChart( |
| | | this.$refs.dayOrderVolumeChart, |
| | | xData, |
| | | seriesData |
| | | ); |
| | | }, |
| | | /** |
| | | * 获取乘客排名数据 |
| | | */ |
| | | fetchPassengerRankData() { |
| | | // 模拟接口请求数据 |
| | | let multiplier = this.passengerRankFilter === 'month' ? 30 : this.passengerRankFilter === 'week' ? 7 : 1; |
| | | this.passengerRankList = Array.from({ length: 8 }, (_, i) => ({ |
| | | phone: '15215268013', |
| | | count: Math.floor((88 - i * 5) * multiplier), |
| | | orders: Math.floor((335 - i * 15) * multiplier) |
| | | })); |
| | | } |
| | | }, |
| | | }; |
| | | </script> |
| | | <style lang='less' scoped> |
| | | @import "./styles/layout.less"; |
| | | @import "./styles/year-trend.less"; |
| | | @import "./styles/total-order.less"; |
| | | @import "./styles/new-passenger.less"; |
| | | @import "./styles/order-volume.less"; |
| | | @import "./styles/day-order-volume.less"; |
| | | @import "./styles/passenger-rank.less"; |
| | | </style> |
| New file |
| | |
| | | .day-order-volume { |
| | | width: 838px; |
| | | height: 465px; |
| | | margin-top: 20px; |
| | | background: linear-gradient(142deg, #0066ff1a -1%, #0066ff1a 38%, #009bff00 130%); |
| | | box-sizing: border-box; |
| | | border: 1px solid; |
| | | border-image: linear-gradient(4deg, #007be178 2%, #00000000 101%) 1; |
| | | position: relative; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .time-picker-box { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | padding: 4px 12px; |
| | | box-sizing: border-box; |
| | | cursor: pointer; |
| | | position: relative; |
| | | border-radius: 4px; |
| | | border: 1px solid transparent; |
| | | background-clip: padding-box, border-box; |
| | | background-origin: padding-box, border-box; |
| | | background-image: linear-gradient(#0B1E53, #0B1E53), linear-gradient(0deg, #498dc4 0%, #cbdeff 50%, #498dc4 100%); |
| | | |
| | | .time-icon { |
| | | width: 14px; |
| | | height: 14px; |
| | | } |
| | | |
| | | span { |
| | | color: rgba(95, 178, 255, 1); |
| | | font-size: 14px; |
| | | font-family: "Alibaba PuHuiTi 2.0"; |
| | | } |
| | | } |
| | | |
| | | .chart-container { |
| | | flex: 1; |
| | | width: 100%; |
| | | } |
| New file |
| | |
| | | .page-container { |
| | | width: 100%; |
| | | height: 100%; |
| | | background: url('~@/assets/img/home/home-bg.png') no-repeat center/cover; |
| | | position: relative; |
| | | overflow: hidden; |
| | | background-color: #03122d; |
| | | } |
| | | |
| | | .top-bar { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | width: 100%; |
| | | height: 100px; |
| | | background: url('~@/assets/img/home/home-top-bg.png') no-repeat center/100% 100%; |
| | | } |
| | | |
| | | .top-left { |
| | | position: absolute; |
| | | top: 24px; |
| | | left: 30px; |
| | | display: flex; |
| | | gap: 24px; |
| | | } |
| | | |
| | | .icon-circle { |
| | | width: 24px; |
| | | height: 24px; |
| | | border-radius: 50%; |
| | | background: linear-gradient(90deg, #3572ed 0%, #1b2a6f 51%, #223b8b 100%); |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | cursor: pointer; |
| | | |
| | | img { |
| | | width: 12px; |
| | | height: 12px; |
| | | object-fit: contain; |
| | | } |
| | | } |
| | | |
| | | .top-title { |
| | | position: absolute; |
| | | top: 6px; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | font-family: PangMenZhengDao; |
| | | line-height: normal; |
| | | font-size: 48px; |
| | | letter-spacing: 0.14em; |
| | | color: #FFFFFF; |
| | | text-shadow: 0px 4px 4px rgba(17, 143, 215, 0.49); |
| | | } |
| | | |
| | | .top-right { |
| | | position: absolute; |
| | | top: 20px; |
| | | right: 30px; |
| | | display: flex; |
| | | gap: 30px; |
| | | |
| | | span { |
| | | font-family: ABeeZee; |
| | | font-size: 16px; |
| | | line-height: 24px; |
| | | font-variation-settings: "opsz" auto; |
| | | background: linear-gradient(180deg, #ffffff 35%, #008bfd 100%), #45A2FF; |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | text-fill-color: transparent; |
| | | } |
| | | } |
| | | |
| | | .content-container { |
| | | width: 100%; |
| | | height: calc(100% - 80px); |
| | | position: absolute; |
| | | top: 80px; |
| | | left: 0; |
| | | padding: 20px 30px; |
| | | box-sizing: border-box; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .left-content { |
| | | width: 491px; |
| | | } |
| New file |
| | |
| | | .new-passenger { |
| | | width: 491px; |
| | | height: 290px; |
| | | margin-top: 20px; |
| | | background: linear-gradient(142deg, #0066ff1a -1%, #0066ff1a 38%, #009bff00 130%); |
| | | box-sizing: border-box; |
| | | border: 1px solid; |
| | | border-image: linear-gradient(4deg, #007be178 2%, #00000000 101%) 1; |
| | | position: relative; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .filter-btn-group { |
| | | display: flex; |
| | | gap: 8px; |
| | | |
| | | .filter-btn { |
| | | flex-shrink: 0; |
| | | padding: 4px 8px; |
| | | background: #04325B; |
| | | border-radius: 4px; |
| | | border: 1px solid #04325B; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | font-family: Alibaba-PuHuiTi-R; |
| | | font-size: 14px; |
| | | color: rgba(255, 255, 255, .8); |
| | | cursor: pointer; |
| | | transition: all 0.3s; |
| | | |
| | | &:hover { |
| | | color: #FFFFFF; |
| | | border-radius: 4px; |
| | | border: 1px solid transparent; |
| | | background-clip: padding-box, border-box; |
| | | background-origin: padding-box, border-box; |
| | | background-image: linear-gradient(#0259A2, #0259A2), linear-gradient(0deg, #498dc4 0%, #cbdeff 50%, #498dc4 100%); |
| | | } |
| | | |
| | | &.active { |
| | | color: #FFFFFF; |
| | | border-radius: 4px; |
| | | border: 1px solid transparent; |
| | | background-clip: padding-box, border-box; |
| | | background-origin: padding-box, border-box; |
| | | background-image: linear-gradient(#0259A2, #0259A2), linear-gradient(0deg, #498dc4 0%, #cbdeff 50%, #498dc4 100%); |
| | | } |
| | | } |
| | | } |
| | | |
| | | .chart-container { |
| | | flex: 1; |
| | | width: 100%; |
| | | } |
| New file |
| | |
| | | .order-volume { |
| | | width: 838px; |
| | | height: 465px; |
| | | background: linear-gradient(142deg, #0066ff1a -1%, #0066ff1a 38%, #009bff00 130%); |
| | | box-sizing: border-box; |
| | | border: 1px solid; |
| | | border-image: linear-gradient(4deg, #007be178 2%, #00000000 101%) 1; |
| | | position: relative; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .chart-container { |
| | | flex: 1; |
| | | width: 100%; |
| | | } |
| New file |
| | |
| | | .passenger-rank { |
| | | width: 491px; |
| | | height: 465px; |
| | | margin-top: 20px; |
| | | background: linear-gradient(142deg, #0066ff1a -1%, #0066ff1a 38%, #009bff00 130%); |
| | | box-sizing: border-box; |
| | | border: 1px solid; |
| | | border-image: linear-gradient(4deg, #007be178 2%, #00000000 101%) 1; |
| | | position: relative; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .rank-list-container { |
| | | flex: 1; |
| | | width: 100%; |
| | | padding: 20px 0px; |
| | | box-sizing: border-box; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .rank-header { |
| | | display: flex; |
| | | border-bottom: 1px solid rgba(255, 255, 255, 0.1); |
| | | font-family: Alibaba PuHuiTi 2.0; |
| | | font-size: 14px; |
| | | color: #B4C0CC; |
| | | |
| | | .header-index { |
| | | width: 105px; |
| | | } |
| | | |
| | | .header-phone{ |
| | | width: 93px; |
| | | text-align: center; |
| | | } |
| | | |
| | | .header-count{ |
| | | margin-left: 55px; |
| | | margin-right: 63px; |
| | | } |
| | | } |
| | | |
| | | .rank-list { |
| | | flex: 1; |
| | | overflow-y: auto; |
| | | margin-top: 10px; |
| | | |
| | | /* 自定义滚动条样式 */ |
| | | &::-webkit-scrollbar { |
| | | width: 4px; |
| | | } |
| | | |
| | | &::-webkit-scrollbar-thumb { |
| | | background: rgba(0, 102, 255, 0.5); |
| | | border-radius: 2px; |
| | | } |
| | | |
| | | .rank-item { |
| | | display: flex; |
| | | align-items: center; |
| | | height: 36px; |
| | | margin-bottom: 8px; |
| | | padding-left: 29px; |
| | | box-sizing: border-box; |
| | | |
| | | /* 控制整行的背景图 */ |
| | | background: url('~@/assets/img/home/passenger-rank-4.png') no-repeat center/100% 100%; |
| | | |
| | | &.bg-1 { |
| | | background: url('~@/assets/img/home/passenger-rank-1.png') no-repeat center/100% 100%; |
| | | } |
| | | |
| | | &.bg-2 { |
| | | background: url('~@/assets/img/home/passenger-rank-2.png') no-repeat center/100% 100%; |
| | | } |
| | | |
| | | &.bg-3 { |
| | | background: url('~@/assets/img/home/passenger-rank-3.png') no-repeat center/100% 100%; |
| | | } |
| | | |
| | | &.bg-4 { |
| | | background: url('~@/assets/img/home/passenger-rank-4.png') no-repeat center/100% 100%; |
| | | } |
| | | |
| | | .rank-index-wrapper { |
| | | width: 76px; |
| | | display: flex; |
| | | align-items: center; |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | gap: 10px; |
| | | |
| | | .rank-number { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | width: 24px; |
| | | height: 24px; |
| | | font-size: 18px; |
| | | box-sizing: border-box; |
| | | color: #FFFFFF; |
| | | |
| | | &.rank-1 { |
| | | background: rgba(233, 183, 1, 0.1); |
| | | border: 1.68px solid; |
| | | border-image: linear-gradient(180deg, #e9b701 0%, #e9b70100 10%, #e9b70100 50%, #e9b70100 89%, #e9b701 100%) 1.68; |
| | | box-shadow: -3px 0px 7px 0px rgba(110, 88, 9, 0.5), inset 0px 0px 10px 0px rgba(233, 183, 1, 0.5); |
| | | } |
| | | |
| | | &.rank-2 { |
| | | background: rgba(14, 209, 163, 0.1); |
| | | border: 1.68px solid; |
| | | border-image: linear-gradient(180deg, #0ed1a3 0%, #0ed1a300 9%, #0ed1a300 50%, #0ed1a300 90%, #0ed1a3 100%) 1.68; |
| | | box-shadow: -3px 0px 7px 0px rgba(5, 121, 93, 0.5), inset 0px 0px 10px 0px rgba(14, 209, 163, 0.5); |
| | | } |
| | | |
| | | &.rank-3 { |
| | | background: rgba(0, 137, 255, 0.1); |
| | | border: 1.68px solid; |
| | | border-image: linear-gradient(180deg, #0089ff 0%, #0089ff00 9%, #0089ff00 52%, #0089ff00 90%, #0089ff 100%) 1.68; |
| | | box-shadow: -3px 0px 7px 0px rgba(3, 47, 73, 0.5), inset 0px 0px 10px 0px rgba(0, 137, 255, 0.5); |
| | | } |
| | | |
| | | &.rank-4 { |
| | | background: rgba(14, 191, 218, 0.1); |
| | | border: 1.68px solid; |
| | | border-image: linear-gradient(180deg, #0ebfda 0%, #0ebfda00 9%, #0ebfda00 52%, #0ebfda00 90%, #0ebfda 100%) 1.68; |
| | | box-shadow: -3px 0px 7px 0px rgba(0, 51, 63, 0.5), inset 0px 0px 10px 0px rgba(14, 191, 218, 0.5); |
| | | } |
| | | } |
| | | } |
| | | |
| | | .item-phone { |
| | | flex: 1.5; |
| | | font-family: Alibaba PuHuiTi 2.0; |
| | | font-size: 16px; |
| | | color: #FFFFFF; |
| | | } |
| | | |
| | | .item-count { |
| | | flex: 1; |
| | | font-family: Alibaba PuHuiTi 2.0; |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | color: #FFFFFF; |
| | | } |
| | | |
| | | .item-orders { |
| | | flex: 1; |
| | | font-family: Alibaba PuHuiTi 2.0; |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | color: #FFFFFF; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | .total-order { |
| | | width: 491px; |
| | | height: 290px; |
| | | margin-top: 20px; |
| | | background: linear-gradient(142deg, #0066ff1a -1%, #0066ff1a 38%, #009bff00 130%); |
| | | box-sizing: border-box; |
| | | border: 1px solid; |
| | | border-image: linear-gradient(4deg, #007be178 2%, #00000000 101%) 1; |
| | | position: relative; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .date-range-picker-box { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 16px; |
| | | padding: 4px 12px; |
| | | box-sizing: border-box; |
| | | cursor: pointer; |
| | | border-radius: 4px; |
| | | border: 1px solid transparent; |
| | | background-clip: padding-box, border-box; |
| | | background-origin: padding-box, border-box; |
| | | background-image: linear-gradient(#0B1E53, #0B1E53), linear-gradient(0deg, #498dc4 0%, #cbdeff 50%, #498dc4 100%); |
| | | |
| | | .date-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | |
| | | .date-icon { |
| | | width: 14px; |
| | | height: 14px; |
| | | } |
| | | |
| | | span { |
| | | color: rgba(95, 178, 255, 1); |
| | | font-size: 14px; |
| | | font-family: "Alibaba-PuHuiTi-R"; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .pie-chart-wrapper { |
| | | flex: 1; |
| | | width: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | padding-top: 10px; |
| | | |
| | | .pie-chart-bg { |
| | | width: 200px; |
| | | height: 200px; |
| | | margin-left: 20px; |
| | | background: url('~@/assets/img/home/totalOrderChart.png') no-repeat center/contain; |
| | | position: relative; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | } |
| | | |
| | | .pie-chart-container { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | .chart-title { |
| | | position: absolute; |
| | | top: 50%; |
| | | left: 50%; |
| | | width: 104px; |
| | | height: 104px; |
| | | transform: translate(-50%, -50%); |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background: rgba(0, 19, 66, .8); |
| | | border-radius: 50%; |
| | | |
| | | .title { |
| | | color: #fff; |
| | | font-size: 24px; |
| | | font-family: PangMenZhengDao; |
| | | } |
| | | |
| | | .subtitle { |
| | | line-height: 20px; |
| | | color: #B4C0CC; |
| | | font-size: 12px; |
| | | font-family: Alibaba-PuHuiTi-R; |
| | | } |
| | | } |
| | | |
| | | .legend-container { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | align-content: center; |
| | | padding-left: 35px; |
| | | gap: 20px 0; |
| | | |
| | | .legend-item { |
| | | width: 50%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | cursor: pointer; |
| | | transition: opacity 0.3s; |
| | | |
| | | .legend-title-wrap { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | |
| | | .dot { |
| | | width: 6px; |
| | | height: 6px; |
| | | border-radius: 50%; |
| | | transition: background-color 0.3s; |
| | | } |
| | | |
| | | .name { |
| | | font-family: Alibaba-PuHuiTi-R; |
| | | font-size: 14px; |
| | | color: #B4C0CC; |
| | | } |
| | | } |
| | | |
| | | .legend-value-wrap { |
| | | margin-top: 4px; |
| | | padding-left: 12px; |
| | | font-family: Alibaba-PuHuiTi-R; |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | color: #FFFFFF; |
| | | |
| | | .percent { |
| | | margin-left: 4px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | .year-trend { |
| | | width: 491px; |
| | | height: 335px; |
| | | background: linear-gradient(142deg, #0066ff1a -1%, #0066ff1a 38%, #009bff00 130%); |
| | | box-sizing: border-box; |
| | | border: 1px solid; |
| | | border-image: linear-gradient(4deg, #007be178 2%, #00000000 101%) 1; |
| | | position: relative; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .date-picker-box { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | padding: 4px 12px; |
| | | background: #0B1E53; |
| | | box-sizing: border-box; |
| | | cursor: pointer; |
| | | border-radius: 4px; |
| | | border: 1px solid transparent; |
| | | background-clip: padding-box, border-box; |
| | | background-origin: padding-box, border-box; |
| | | background-image: linear-gradient(#0B1E53, #0B1E53), linear-gradient(0deg, #498dc4 0%, #cbdeff 50%, #498dc4 100%); |
| | | |
| | | .date-icon { |
| | | width: 14px; |
| | | height: 14px; |
| | | } |
| | | |
| | | span { |
| | | color: #5FB2FF; |
| | | font-size: 14px; |
| | | font-family: "Alibaba-PuHuiTi-R"; |
| | | } |
| | | } |
| | | |
| | | .chart-container { |
| | | flex: 1; |
| | | width: 100%; |
| | | } |