数据大屏
|
@ -82,7 +82,11 @@
|
|||
"vxe-table-plugin-antd": "4.0.8",
|
||||
"vxe-pc-ui": "4.6.12",
|
||||
"xe-utils": "3.5.26",
|
||||
"xss": "^1.0.15"
|
||||
"xss": "^1.0.15",
|
||||
"three": "^0.178.0",
|
||||
"jquery": "^3.7.1",
|
||||
"element-plus": "^2.10.4",
|
||||
"@element-plus/icons-vue": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^18.6.1",
|
||||
|
|
14837
pnpm-lock.yaml
After Width: | Height: | Size: 1.0 MiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 61 KiB |
After Width: | Height: | Size: 5.2 KiB |
|
@ -0,0 +1,119 @@
|
|||
|
||||
/*
|
||||
椭圆会使内部失真 transform: rotateX(50deg);
|
||||
*/
|
||||
.rotation3D{
|
||||
position: relative; width: 800px; height: 750px; cursor: move; user-select: none;
|
||||
margin: 0 auto; margin-top: -100px;
|
||||
/*border: 1px solid white; border-radius: 100%;*/
|
||||
}
|
||||
.rotation3D .center{
|
||||
display: none;
|
||||
position: absolute; left: 50%; top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.rotation3D .itemList{ position: absolute; width: 100%; height: 100%; z-index: 20; }
|
||||
.rotation3D .lineList{
|
||||
position: absolute; width: 100%; height: 100%; z-index: 10;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
/*---------------------------点样式---------------------------*/
|
||||
.rotation3D__item{
|
||||
position: absolute; display: block; cursor: pointer; width: 161px; height: 188px;
|
||||
text-align: center; line-height: 30px; font-size: 16px; color: white;
|
||||
/*background: #2292ef; border-radius: 4px;*/
|
||||
}
|
||||
.rotation3D__item .scale{ position: absolute; top: 0; width: 100%; height: 100%; }
|
||||
.rotation3D__item .cont{ position: relative; z-index: 2; }
|
||||
.rotation3D__item .cont .iconfont { font-size: 28px; margin-top: 14px; margin-bottom: 0px; display: block; }
|
||||
.rotation3D__item .cont p{ color: #fff; }
|
||||
|
||||
.rotation3D__item.blue{ color: #01e9fc; }
|
||||
.rotation3D__item.green{ color: #02e943; }
|
||||
.rotation3D__item.yellow{ color: #ffd200; }
|
||||
|
||||
/*底座*/
|
||||
.rotation3D__item .baseImg{ position: absolute; width: 100%; height: 100%; z-index: 1; }
|
||||
.rotation3D__item.blue .baseImg{ background: url("img/blue.png"); }
|
||||
.rotation3D__item.green .baseImg{ background: url("img/green.png"); }
|
||||
.rotation3D__item.yellow .baseImg{ background: url("img/yellow.png"); }
|
||||
|
||||
/*---------------------------
|
||||
线样式
|
||||
线高为总高的一般
|
||||
---------------------------*/
|
||||
.rotation3D__line{
|
||||
position: absolute; left: 50%; top: 50%;
|
||||
display: block; width: 2px; height: 50%;
|
||||
padding-top: 60px; color: #fff; font-size: 50px;
|
||||
/*background: #fff;*/
|
||||
/*原点设置在中间*/
|
||||
transform-origin: 50% 0;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
.rotation3D__line .pos{ position: absolute; top: 0; }
|
||||
.rotation3D__line svg { position: absolute; top: 0; }
|
||||
.rotation3D__line svg path {
|
||||
stroke: #fff; fill: none;
|
||||
stroke-width: 3;
|
||||
animation: path-animation 100s linear 0s infinite normal;
|
||||
}
|
||||
@keyframes path-animation {
|
||||
0% { stroke-dashoffset:500; }
|
||||
100% { stroke-dashoffset:0; }
|
||||
}
|
||||
.rotation3D__line .dot {
|
||||
position: absolute; top: 0; left: 0; text-align: center;
|
||||
/*width: 35px; height: 35px; font-size: 35px; */
|
||||
width: 24px; height: 24px; font-size: 24px;
|
||||
}
|
||||
.rotation3D__line .dot1,.rotation3D__line .dot3,.rotation3D__line .dot4{
|
||||
animation: svg-path-animation 6s ease-in-out 0s infinite normal;
|
||||
}
|
||||
.rotation3D__line .dot1{
|
||||
offset-path: path("M0 400, 0 0"); offset-distance: 0%;
|
||||
}
|
||||
.rotation3D__line .dot2{
|
||||
offset-path: path("M0 200, 0 0"); offset-distance: 0%;
|
||||
background: #ffd200; border-radius: 100%;
|
||||
font-size: 22px; color: #000;
|
||||
}
|
||||
.rotation3D__line .dot3{
|
||||
offset-path: path("M20 400 S 0 200, 20 0"); offset-distance: 0%;
|
||||
}
|
||||
.rotation3D__line .dot4{
|
||||
offset-path: path("M20 0 S 40 200, 20 400"); offset-distance: 0%;
|
||||
}
|
||||
@keyframes svg-path-animation {
|
||||
from {offset-distance: 0%;}
|
||||
to {offset-distance: 100%;}
|
||||
}
|
||||
|
||||
/*颜色*/
|
||||
.rotation3D__line.blue { color: #07b2f9; }
|
||||
.rotation3D__line.green { color: #00ff5b; }
|
||||
.rotation3D__line.yellow { color: #ffd500; }
|
||||
|
||||
.rotation3D__line.blue svg path { stroke: #07b2f9; }
|
||||
.rotation3D__line.green svg path { stroke: #00ff5b; }
|
||||
.rotation3D__line.yellow svg path { stroke: #ffd500; }
|
||||
|
||||
.rotation3D-baseMap{
|
||||
position: absolute; left: 10px; right: 0; top: 126px; margin: auto;
|
||||
width: 900px; height: 516px;
|
||||
background: url("@/assets/img/baseMap.png") no-repeat;
|
||||
background-position:center;
|
||||
}
|
||||
.rotation3D-baseMap::before{
|
||||
position: absolute; left: 0px; right: 0; top: 0px; margin: auto; z-index: 99;
|
||||
width: 342px; height: 318px; display: block; content: '';
|
||||
background: url("@/assets/img/baseLogo.png") no-repeat;
|
||||
animation: 10s bounceUpDown infinite;
|
||||
background-size: 70%;
|
||||
background-position: center ;
|
||||
}
|
||||
.mt-20{
|
||||
margin-top: 40px;
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
/* 全局样式 */
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
background-color: #0a192f;
|
||||
color: #e6f1ff;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(100, 116, 139, 0.1);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(148, 163, 184, 0.5);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(148, 163, 184, 0.8);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 标题样式 */
|
||||
.section-title {
|
||||
position: relative;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.section-title::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 4px;
|
||||
background: linear-gradient(to bottom, #4ce7ff, #165dff);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* 数字动画 */
|
||||
.number-counter {
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 渐变按钮 */
|
||||
.gradient-btn {
|
||||
background: linear-gradient(90deg, #4ce7ff 0%, #165dff 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.gradient-btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 5px 15px rgba(22, 93, 255, 0.3);
|
||||
}
|
||||
|
||||
.gradient-btn:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 5px rgba(22, 93, 255, 0.3);
|
||||
}
|
||||
|
||||
/* 状态标签 */
|
||||
.status-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-tag.up {
|
||||
background-color: rgba(16, 185, 129, 0.1);
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.status-tag.down {
|
||||
background-color: rgba(239, 68, 68, 0.1);
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
/* 骨架屏加载 */
|
||||
.skeleton {
|
||||
background: linear-gradient(90deg, rgba(255, 255, 255, 0.05) 25%, rgba(255, 255, 255, 0.1) 50%, rgba(255, 255, 255, 0.05) 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes skeleton-loading {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
@font-face {
|
||||
font-family: "unidreamLED";
|
||||
src: url('@/assets/fonts/UnidreamLED.ttf');
|
||||
}
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3045003 */
|
||||
src: url('//at.alicdn.com/t/font_3045003_lkxtjaj4m6.woff2?t=1641286939195') format('woff2'),
|
||||
url('//at.alicdn.com/t/font_3045003_lkxtjaj4m6.woff?t=1641286939195') format('woff'),
|
||||
url('//at.alicdn.com/t/font_3045003_lkxtjaj4m6.ttf?t=1641286939195') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-liangchang:before {
|
||||
content: "\e622";
|
||||
}
|
||||
|
||||
.icon-lumianshigong:before {
|
||||
content: "\e623";
|
||||
}
|
||||
|
||||
.icon-shiyanjiance:before {
|
||||
content: "\e624";
|
||||
}
|
||||
|
||||
.icon-renyuanguanli:before {
|
||||
content: "\e625";
|
||||
}
|
||||
|
||||
.icon-tanpuyashifuwu:before {
|
||||
content: "\e626";
|
||||
}
|
||||
|
||||
.icon-shujufuwuzhongxin:before {
|
||||
content: "\e627";
|
||||
}
|
||||
|
||||
.icon-a-lujishigong2x:before {
|
||||
content: "\e620";
|
||||
}
|
||||
|
||||
.icon-GPS:before {
|
||||
content: "\e621";
|
||||
}
|
After Width: | Height: | Size: 71 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
After Width: | Height: | Size: 496 B |
|
@ -64,5 +64,15 @@ export const TokenLoginRoute: AppRouteRecordRaw = {
|
|||
ignoreAuth: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const newScreenRoute: AppRouteRecordRaw = {
|
||||
path: '/screen/screen',
|
||||
name: 'screenIndex',
|
||||
component: () => import('/@/views/screen/screen.vue'),
|
||||
meta: {
|
||||
title: t('routes.basic.login'),
|
||||
},
|
||||
};
|
||||
|
||||
// Basic routing without permission
|
||||
export const basicRoutes = [LoginRoute, RootRoute, ...mainOutRoutes, REDIRECT_ROUTE, PAGE_NOT_FOUND_ROUTE, TokenLoginRoute, Oauth2LoginRoute];
|
||||
export const basicRoutes = [LoginRoute, RootRoute, ...mainOutRoutes, REDIRECT_ROUTE, PAGE_NOT_FOUND_ROUTE, TokenLoginRoute, Oauth2LoginRoute,newScreenRoute];
|
||||
|
|
|
@ -0,0 +1,376 @@
|
|||
import $ from 'jquery'
|
||||
const cancelFrame = window.cancelAnimationFrame || window.cancelRequestAnimationFrame;
|
||||
const requestFrame = window.requestAnimationFrame;
|
||||
export let NumTile = "";
|
||||
const time = !window.performance || !window.performance.now
|
||||
? () => +new Date()
|
||||
: () => performance.now();
|
||||
|
||||
/**
|
||||
* 计算两点距离
|
||||
* @param points
|
||||
* @returns {number}
|
||||
* distance([{x:0,y:0},{x:1,y:1}]);
|
||||
*/
|
||||
const distance = (points) => {
|
||||
const p1 = points[0];
|
||||
const p2 = points[1];
|
||||
const a = p2.x - p1.x;
|
||||
const b = p2.y - p1.y;
|
||||
return Math.sqrt(a * a + b * b);
|
||||
};
|
||||
|
||||
/**
|
||||
* 圆公式
|
||||
* @param rotation 弧度
|
||||
* 计算公式:
|
||||
* Math.PI; //圆周率
|
||||
* Math.sin(); //正弦 x -左 +右
|
||||
* Math.cos; //余弦 y -下 +上
|
||||
*/
|
||||
const circleMath = {
|
||||
/**
|
||||
* 根据弧度计算角度
|
||||
* @param rotation 弧度
|
||||
* rotation, farScale, xs, xr, ys, yr, itemWidth
|
||||
*/
|
||||
parseRotate(rotation, self) {
|
||||
const sin = Math.sin(rotation);
|
||||
const cos = Math.cos(rotation);
|
||||
const sin_cos = sin * cos; //得出偏移正负值,从0°向左依次 +-+-
|
||||
let angle = (180 / Math.PI * rotation) - 180;
|
||||
let lastAngle = angle;
|
||||
|
||||
lastAngle = angle + (self.yr * (sin_cos / (Math.PI + 1)));
|
||||
|
||||
return lastAngle;
|
||||
},
|
||||
/**
|
||||
* 计算scale,x,y
|
||||
* scale 最小尺寸 + ((1 - 最小尺寸) * (sin正弦 + 1) * 0.5)
|
||||
* x x起点 + (尺寸 * cos余弦 * x半径) - 元素宽度一半
|
||||
* y y起点 + (尺寸 * sin正弦 * x半径) - 元素宽度一半
|
||||
* farScale, xs, xr, ys, yr, itemWidth
|
||||
*/
|
||||
parseSXY(rotation, self) {
|
||||
const { farScale, itemWidth, xs, xr, ys, yr } = self;
|
||||
const sin = Math.sin(rotation);
|
||||
const cos = Math.cos(rotation);
|
||||
const scale = farScale + ((1 - farScale) * (sin + 1) * 0.5); //单个尺寸
|
||||
|
||||
// 使用压缩
|
||||
const x = xs + (cos * xr) - (itemWidth * 0.5);
|
||||
const y = ys + (sin * yr) - (itemWidth * 0.5);
|
||||
const distanceNumber = distance([
|
||||
{ x: self.$rotation.width() / 2 - self.$item.width() / 2, y: self.$rotation.height() / 2 - self.$item.height() / 2 },
|
||||
{ x, y }
|
||||
]);
|
||||
|
||||
return { x, y, scale, distanceNumber };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 3D旋转
|
||||
* @param id
|
||||
*/
|
||||
export class Rotation3D {
|
||||
constructor(_opts) {
|
||||
const self = this;
|
||||
this.$rotation = $(_opts.id);
|
||||
this.$lineList = this.$rotation.find('.lineList');
|
||||
this.$item = this.$rotation.find('.rotation3D__item');
|
||||
this.$line = this.$rotation.find('.rotation3D__line');
|
||||
this.itemWidth = this.$item.width();
|
||||
this.itemHeight = this.$item.height();
|
||||
this.length = this.$item.length;
|
||||
|
||||
// 圆计算
|
||||
this.rotation = Math.PI / 2; //圆周率/2
|
||||
this.destRotation = this.rotation;
|
||||
|
||||
const xr = this.$rotation.width() * 0.5;
|
||||
const yr = this.$rotation.height() * 0.5;
|
||||
const xRadius = _opts.xRadius || 0;
|
||||
const yRadius = _opts.yRadius || 0;
|
||||
|
||||
const opts = Object.assign({
|
||||
farScale: 1, // 最小尺寸
|
||||
xs: xr, // x起点
|
||||
ys: yr, // y起点
|
||||
xr: xr - xRadius, // x半径-压缩
|
||||
yr: yr - yRadius, // y半径-压缩
|
||||
// 播放
|
||||
autoPlay: false,
|
||||
autoPlayDelay: 3000,
|
||||
currenIndex: -1,
|
||||
fps: 30,
|
||||
speed: 4,
|
||||
}, _opts);
|
||||
Object.assign(this, opts);
|
||||
|
||||
// 遍历子元素
|
||||
this.$item.each(function (index) {
|
||||
if(NumTile == ''){
|
||||
setTimeout(() => {
|
||||
let num = self.$item[0].dataset.num;
|
||||
let dw = self.$item[0].dataset.dw;
|
||||
$('.rotation3DNum').html(num);
|
||||
$('.dw').html(`(${dw})`);
|
||||
}, 1500);
|
||||
}
|
||||
$(this).click(function () {
|
||||
$(this).addClass('active').siblings().removeClass('active');
|
||||
let num = $(this).attr('data-num');
|
||||
let dw = $(this).attr('data-dw');
|
||||
$('.dw').html(`(${dw})`);
|
||||
$('.rotation3DNum').css('opacity', 0);
|
||||
setTimeout(() => {
|
||||
$('.rotation3DNum').html(num); // 更改数字内容
|
||||
$('.rotation3DNum').css('opacity', 1);
|
||||
}, 500);
|
||||
self.goTo(index);
|
||||
});
|
||||
});
|
||||
|
||||
// 当前控件进入离开
|
||||
this.$rotation.mouseenter(function () {
|
||||
clearInterval(self.autoPlayTimer);
|
||||
});
|
||||
this.$rotation.mouseleave(function () {
|
||||
self.onAutoPlay();
|
||||
});
|
||||
|
||||
this.onAutoPlay();
|
||||
this.onDrag();
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* item样式
|
||||
* x x起点 + (尺寸 * 余弦 * x压缩) - 元素宽度一半
|
||||
* y y起点 + (尺寸 * 正弦 * y压缩) - 元素宽度一半
|
||||
*/
|
||||
itemStyle($item, index, rotation) {
|
||||
const parseSXY = circleMath.parseSXY(rotation, this);
|
||||
const { scale, x, y } = parseSXY;
|
||||
const $line = this.$lineList.find('.rotation3D__line').eq(index);
|
||||
|
||||
//设置当前子菜单的位置(left,top) = (x,y)
|
||||
$item.find('.scale').css({
|
||||
transform: `scale(${scale})`
|
||||
});
|
||||
$item.css({
|
||||
position: 'absolute',
|
||||
display: 'inline-block',
|
||||
'z-index': parseInt(scale * 100),
|
||||
'transform-origin': '0px 0px',
|
||||
transform: `translate(${x}px, ${y}px)`
|
||||
});
|
||||
|
||||
/**
|
||||
* 线样式
|
||||
*/
|
||||
$line.css({
|
||||
height: parseSXY.distanceNumber
|
||||
});
|
||||
$line.find('svg').css({
|
||||
height: parseSXY.distanceNumber
|
||||
});
|
||||
$line.find('.dot1').css({
|
||||
'offset-path': `path("M0 ${parseSXY.distanceNumber}, 0 0")`
|
||||
});
|
||||
$line.find('#path1').attr({
|
||||
d: `M0 ${parseSXY.distanceNumber}, 0 0`
|
||||
});
|
||||
|
||||
$line.find('.dot2').css({
|
||||
'offset-path': `path("M0 ${parseSXY.distanceNumber/2}, 0 0")`
|
||||
});
|
||||
$line.find('#path2').attr({
|
||||
d: `M0 ${parseSXY.distanceNumber}, 0 0`
|
||||
});
|
||||
|
||||
$line.find('.dot3').css({
|
||||
'offset-path': `path("M20 ${parseSXY.distanceNumber} S 0 ${parseSXY.distanceNumber/2}, 20 0")`
|
||||
});
|
||||
$line.find('#path3').attr({
|
||||
d: `M20 ${parseSXY.distanceNumber} S 0 ${parseSXY.distanceNumber/2}, 20 0`
|
||||
});
|
||||
|
||||
$line.find('.dot4').css({
|
||||
'offset-path': `path("M20 0 S 40 ${parseSXY.distanceNumber/2}, 20 ${parseSXY.distanceNumber}")`
|
||||
});
|
||||
$line.find('#path4').attr({
|
||||
d: `M20 0 S 40 ${parseSXY.distanceNumber/2}, 20 ${parseSXY.distanceNumber}`
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* line样式
|
||||
*/
|
||||
lineStyle($line, index, rotation) {
|
||||
const rotate = circleMath.parseRotate(rotation, this);
|
||||
$line.css({
|
||||
transform: `rotate(${rotate}deg)`
|
||||
});
|
||||
this.$lineList.css({
|
||||
// transform: `rotateX(${this.yRadius / 3}deg)`,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 旋转至index
|
||||
*/
|
||||
goTo(index) {
|
||||
const self = this;
|
||||
this.currenIndex = index;
|
||||
|
||||
/**
|
||||
* 1.计算floatIndex,用于控死amdiff
|
||||
*/
|
||||
const itemsRotated = this.length * ((Math.PI / 2) - this.rotation) / (2 * Math.PI);
|
||||
let floatIndex = itemsRotated % this.length;
|
||||
if (floatIndex < 0) { floatIndex = floatIndex + this.length; }
|
||||
|
||||
/**
|
||||
* 2.计算diff,判断方向正反
|
||||
*/
|
||||
let diff = index - (floatIndex % this.length);
|
||||
if (2 * Math.abs(diff) > this.length) {
|
||||
diff -= (diff > 0) ? this.length : -this.length;
|
||||
}
|
||||
// 停止任何正在进行的旋转
|
||||
this.destRotation += (2 * Math.PI / this.length) * -diff;
|
||||
this.scheduleNextFrame();
|
||||
}
|
||||
|
||||
/**
|
||||
* 定时器渐近旋转
|
||||
*/
|
||||
scheduleNextFrame() {
|
||||
const self = this;
|
||||
this.lastTime = time();
|
||||
|
||||
// 暂停
|
||||
const pause = function () {
|
||||
cancelFrame ? cancelFrame(this.timer) : clearTimeout(self.timer);
|
||||
self.timer = 0;
|
||||
};
|
||||
|
||||
// 渐进播放
|
||||
const playFrame = function () {
|
||||
const rem = self.destRotation - self.rotation;
|
||||
const now = time();
|
||||
const dt = (now - self.lastTime) * 0.002;
|
||||
self.lastTime = now;
|
||||
|
||||
if (Math.abs(rem) < 0.003) {
|
||||
self.rotation = self.destRotation;
|
||||
pause();
|
||||
} else {
|
||||
// 渐近地接近目的地
|
||||
self.rotation = self.destRotation - rem / (1 + (self.speed * dt));
|
||||
self.scheduleNextFrame();
|
||||
}
|
||||
self.render();
|
||||
};
|
||||
|
||||
this.timer = cancelFrame
|
||||
? requestFrame(playFrame)
|
||||
: setTimeout(playFrame, 1000 / this.fps);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
*/
|
||||
render() {
|
||||
const self = this;
|
||||
// 图形间隔:弧度
|
||||
const spacing = 2 * Math.PI / this.$item.length;
|
||||
let itemRotation = this.rotation;
|
||||
let lineRotation = this.rotation + (Math.PI / 2);
|
||||
this.$item.each(function (index) {
|
||||
self.itemStyle($(this), index, itemRotation);
|
||||
itemRotation += spacing;
|
||||
});
|
||||
this.$line.each(function (index) {
|
||||
self.lineStyle($(this), index, lineRotation);
|
||||
lineRotation += spacing;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动播放
|
||||
*/
|
||||
onAutoPlay() {
|
||||
const self = this;
|
||||
|
||||
if (this.autoPlay) {
|
||||
this.autoPlayTimer = setInterval(function () {
|
||||
if (self.currenIndex < 0) {
|
||||
self.currenIndex = self.length - 1;
|
||||
}
|
||||
self.goTo(self.currenIndex);
|
||||
self.currenIndex--; //倒叙
|
||||
}, this.autoPlayDelay);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拖拽
|
||||
*/
|
||||
onDrag() {
|
||||
const self = this;
|
||||
let startX, startY, moveX, moveY, endX, endY;
|
||||
|
||||
// 拖拽:三个事件-按下 移动 抬起
|
||||
//按下
|
||||
this.$rotation.mousedown(function (e) {
|
||||
startX = e.pageX;
|
||||
startY = e.pageY;
|
||||
|
||||
// 移动
|
||||
$(document).mousemove(function (e) {
|
||||
endX = e.pageX;
|
||||
endY = e.pageY;
|
||||
moveX = endX - startX;
|
||||
moveY = endY - startY;
|
||||
});
|
||||
|
||||
// 抬起
|
||||
$(document).mouseup(function (e) {
|
||||
endX = e.pageX;
|
||||
endY = e.pageY;
|
||||
moveX = endX - startX;
|
||||
moveY = endY - startY;
|
||||
|
||||
// 每40旋转一步
|
||||
const moveIndex = parseInt(Math.abs(moveX) / 50);
|
||||
if (moveIndex > 0) {
|
||||
if (moveX < 0) { //向左
|
||||
self.currenIndex = self.currenIndex - moveIndex;
|
||||
play(moveIndex);
|
||||
} else { //向右
|
||||
self.currenIndex = self.currenIndex + moveIndex;
|
||||
play(moveIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// 解绑
|
||||
$(document).unbind("mousemove");
|
||||
$(document).unbind("mouseup");
|
||||
});
|
||||
});
|
||||
|
||||
function play() {
|
||||
if (self.currenIndex === 0) {
|
||||
self.currenIndex = self.length - 1;
|
||||
}
|
||||
self.goTo(self.currenIndex % self.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//window.Rotation3D = Rotation3D;
|
||||
//export default Rotation3D
|
|
@ -9,6 +9,7 @@ enum Api {
|
|||
xqtd = '/zh/home/getXqtd',
|
||||
sbtd = '/zh/home/getSbtd',
|
||||
hytd = '/zh/home/getHytd',
|
||||
xqqy = '/zh/home/getXqqy',
|
||||
sstd = '/zh/home/getSstd',
|
||||
}
|
||||
/**
|
||||
|
@ -59,6 +60,12 @@ export const getSbtd = (params) => defHttp.get({ url: Api.sbtd, params }, { isTr
|
|||
*/
|
||||
export const getHytd = (params) => defHttp.get({ url: Api.hytd, params }, { isTransformResponse: false });
|
||||
|
||||
/**
|
||||
* 小区清运
|
||||
* @param params
|
||||
*/
|
||||
export const getXqqy = (params) => defHttp.get({ url: Api.xqqy, params }, { isTransformResponse: false });
|
||||
|
||||
/**
|
||||
* 实时投递
|
||||
* @param params
|
||||
|
|
|
@ -46,7 +46,15 @@
|
|||
</a-row>
|
||||
|
||||
<a-row :gutter="16" style="padding:16px 0px 0px 16px;width: 100%">
|
||||
<a-col :span="6">
|
||||
<a-col :span="4">
|
||||
<a-card>
|
||||
<div @click="openDataBigScreen">
|
||||
<div class="space-between"><span>数据大屏</span></div>
|
||||
<span style="font-size:45px;padding-left: 80px;"><fund-two-tone /></span>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="5">
|
||||
<a-card>
|
||||
<div>
|
||||
<div class="space-between"><span>今日投递重量(T)</span><span>昨日</span></div>
|
||||
|
@ -59,7 +67,7 @@
|
|||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-col :span="5">
|
||||
<a-card>
|
||||
<div class="space-between"><span>今日投递次数(次)</span><span>昨日</span></div>
|
||||
<div class="space-between"><span class="card-title">{{formData.tdcsT}}</span><span>{{formData.tdcsY}}</span></div>
|
||||
|
@ -70,7 +78,7 @@
|
|||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-col :span="5">
|
||||
<a-card>
|
||||
<div class="space-between"><span>今日清运重量(T)</span><span>昨日</span></div>
|
||||
<div class="space-between"><span class="card-title">{{formData.qyzlT}}</span><span>{{formData.qyzlY}}</span></div>
|
||||
|
@ -81,7 +89,7 @@
|
|||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-col :span="5">
|
||||
<a-card>
|
||||
<div class="space-between"><span>今日新增会员(人)</span><span>昨日</span></div>
|
||||
<div class="space-between"><span class="card-title">{{formData.hyrsT}}</span><span>{{formData.hyrsY}}</span></div>
|
||||
|
@ -161,13 +169,15 @@
|
|||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {onMounted, reactive, ref} from 'vue';
|
||||
import { FundTwoTone } from '@ant-design/icons-vue';
|
||||
import { BasicTable, useTable, TableAction } from '/@/components/Table';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import Pie from '/@/components/chart/Pie.vue';
|
||||
import BarAndLine from '/@/components/chart/BarAndLine.vue';
|
||||
import {getTotalInfo, getTodayInfo, getOrderTypeCn, getXqtd, getSbtd, getHytd, getSstd} from './api';
|
||||
import {getTotalInfo, getTodayInfo, getOrderTypeCn, getXqtd, getSbtd, getHytd, getSstd, getXqqy} from './api';
|
||||
import {orderColumns} from './data';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
const router = useRouter();
|
||||
const queryParam = reactive<any>({});
|
||||
//注册table数据
|
||||
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
|
||||
|
@ -350,20 +360,42 @@
|
|||
try {
|
||||
dataSource.value = [];
|
||||
const res = await getSstd({});
|
||||
console.log(res);
|
||||
dataSource.value = res.result;
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 小区清运
|
||||
async function loadXqqy(){
|
||||
try {
|
||||
const res = await getXqqy({});
|
||||
console.log(res);
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadData(){
|
||||
loadTotalData();
|
||||
loadTodayData();
|
||||
loadOrderTypeCn();
|
||||
loadXqtd();
|
||||
loadSbtd();
|
||||
loadHytd();
|
||||
// loadSstd();
|
||||
// loadXqqy();
|
||||
}
|
||||
|
||||
setInterval(function() {
|
||||
reload();
|
||||
loadData();
|
||||
}, 30*60*1000);
|
||||
|
||||
function openDataBigScreen(){
|
||||
const url = router.resolve({ path: '/screen/screen' }).href
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
<template>
|
||||
<div class="rotation3D-baseMap"></div>
|
||||
<div class="muflex">
|
||||
<label class="rotation3DNum"></label><span class="dw"></span>
|
||||
</div>
|
||||
<!--旋转3D-->
|
||||
<div id="rotation3D" class="rotation3D">
|
||||
<div class="itemList">
|
||||
<div class="rotation3D__item" :class="item.type" v-for="item in itemList" :data-num="item.num" :data-dw="item.dw">
|
||||
<div class="scale">
|
||||
<div class="baseImg"></div>
|
||||
<div class="cont">
|
||||
<!-- <i class="iconfont"> {{ item.num }} </i> -->
|
||||
<i class="iconfont" :class="item.icon"></i>
|
||||
<p class="item-name-overflow">{{item.name}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lineList">
|
||||
<div class="rotation3D__line" v-for="item in itemList" :class="item.type">
|
||||
<div class="pos" v-if="item.type == 'blue'">
|
||||
<svg width="10" height="400">
|
||||
<path id="path1" d="M0 400, 0 0" stroke-dasharray="5,10"/>
|
||||
</svg>
|
||||
<div class="dot dot1 el-icon-caret-right"></div>
|
||||
</div>
|
||||
<div class="pos" v-if="item.type == 'yellow'">
|
||||
<svg width="10" height="400">
|
||||
<path id="path2" d="M0 400, 0 0" stroke-dasharray="5,10"/>
|
||||
</svg>
|
||||
<!-- <div class="dot dot2"><i class="el-icon-close"></i></div> -->
|
||||
</div>
|
||||
<div class="pos" style="left: -16px;" v-if="item.type == 'green'">
|
||||
<svg width="50" height="400">
|
||||
<path id="path3" d="M0 400, 0 0" stroke-dasharray="5,10"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {onMounted, ref} from 'vue';
|
||||
import{ Rotation3D } from '@/utiles/index';
|
||||
import { getTotalInfo } from "../../dashboard/Analysis/api";
|
||||
|
||||
const itemList = ref([
|
||||
{ name:'总投递重量(T)', type:'blue', num:'0', icon:'icon-a-lujishigong2x', dw:'T'},
|
||||
{ name:'总清运重量(T)', type:'green', num:'0', icon:'icon-a-lujishigong2x',dw:'T'},
|
||||
{ name:'总会员(人)', type:'yellow', num:'0',icon:'icon-renyuanguanli', dw:'人' },
|
||||
{ name:'总设备(台)', type:'blue', num:'0', icon:'icon-liangchang',dw:'台'},
|
||||
{ name:'在线设备(台)', type:'green', num:'0', icon:'icon-tanpuyashifuwu', dw:'台'},
|
||||
{ name:'离线设备(台)', type:'yellow', num:'0', icon:'icon-lumianshigong',dw:'台'},
|
||||
{ name:'总区域(个)', type:'blue', num:'0', icon:'icon-GPS ',dw:'个' },
|
||||
{ name:'总投递次数(次)', type:'green', num:'0', icon:'icon-shiyanjiance',dw:'次'},
|
||||
]);
|
||||
|
||||
const rotation3D = ref();
|
||||
|
||||
function createRotation3D(){
|
||||
rotation3D.value = new Rotation3D({
|
||||
id: '#rotation3D',
|
||||
farScale: 0.6,
|
||||
// farScale: 1,
|
||||
xRadius: 0, //x半径压缩
|
||||
yRadius: 220, //y半径压缩
|
||||
// yRadius: 0, //y半径压缩
|
||||
//autoPlay:true,
|
||||
//autoPlayDelay:6000,
|
||||
})
|
||||
}
|
||||
|
||||
async function loadTotalData() {
|
||||
const resData = await getTotalInfo({});
|
||||
const res = resData.result;
|
||||
itemList.value = [];
|
||||
itemList.value.push(
|
||||
{
|
||||
name: '总投递重量(T)',
|
||||
type: 'blue',
|
||||
num: res.tdzlA,
|
||||
icon:'icon-a-lujishigong2x',
|
||||
dw:'T'
|
||||
},
|
||||
{
|
||||
name: '总清运重量(T)',
|
||||
type: 'green',
|
||||
num: res.qyzlA,
|
||||
icon:'icon-a-lujishigong2x',
|
||||
dw:'T'
|
||||
},
|
||||
{
|
||||
name: '总会员(人)',
|
||||
type: 'yellow',
|
||||
num: res.hyrsA,
|
||||
icon:'icon-renyuanguanli',
|
||||
dw:'人'
|
||||
},
|
||||
{
|
||||
name: '总设备(台)',
|
||||
type: 'blue',
|
||||
num: res.sbsA,
|
||||
icon:'icon-liangchang',
|
||||
dw:'台'
|
||||
},
|
||||
{
|
||||
name: '在线设备(台)',
|
||||
type: 'green',
|
||||
num: res.zxsbsA,
|
||||
icon:'icon-tanpuyashifuwu',
|
||||
dw:'台'
|
||||
},
|
||||
{
|
||||
name: '离线设备(台)',
|
||||
type: 'yellow',
|
||||
num: res.lxsbsA,
|
||||
icon:'icon-lumianshigong',
|
||||
dw:'台'
|
||||
},
|
||||
{
|
||||
name: '总区域(个)',
|
||||
type: 'blue',
|
||||
num: res.qysA,
|
||||
icon:'icon-GPS',
|
||||
dw:'台'
|
||||
},
|
||||
{
|
||||
name: '总投递次数(次)',
|
||||
type: 'green',
|
||||
num: res.tdcsA,
|
||||
icon:'icon-shiyanjiance',
|
||||
dw:'次'
|
||||
},
|
||||
);
|
||||
// rotation3D.value.render();
|
||||
}
|
||||
|
||||
setInterval(function() {
|
||||
loadTotalData();
|
||||
}, 30*60*1000);
|
||||
|
||||
onMounted(()=>{
|
||||
createRotation3D();
|
||||
loadTotalData();
|
||||
})
|
||||
</script>
|
||||
<style>
|
||||
@import url('@/assets/rotation3D.css');
|
||||
.rotation3DNum{
|
||||
font-size: 48px;
|
||||
color: aqua;
|
||||
font-family: 'unidreamLED';
|
||||
transition: opacity 0.5s ease-out;
|
||||
}
|
||||
.dw{
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: aqua;
|
||||
}
|
||||
.muflex{
|
||||
position: absolute;
|
||||
left: 2%;
|
||||
top: 10px;
|
||||
}
|
||||
.item-name-overflow{
|
||||
word-wrap: break-word; /* 或使用 overflow-wrap: break-word; */
|
||||
width: 60px; /* 设定一个宽度来观察效果 */
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,301 @@
|
|||
<template>
|
||||
<div class="data-card" :id="id"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref, onMounted, reactive} from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import {getXqtd} from "../../dashboard/Analysis/api";
|
||||
// import { Info } from '@element-plus/icons-vue'
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
data:{
|
||||
type:String
|
||||
}
|
||||
});
|
||||
let quLabel = ref([]);
|
||||
let quCn = ref([]);
|
||||
let quWeight = ref([]);
|
||||
let myChart = reactive({});
|
||||
function tid(data){
|
||||
quLabel.value = [];
|
||||
quCn.value = [];
|
||||
quWeight.value = [];
|
||||
data.forEach((item)=>{
|
||||
quLabel.value.push(item.housingestateName);
|
||||
quCn.value.push(item.cn);
|
||||
quWeight.value.push(item.weight)
|
||||
})
|
||||
const chartDom = document.getElementById(props.id);
|
||||
myChart = echarts.init(chartDom);
|
||||
|
||||
// 科技风格配色
|
||||
const colors = {
|
||||
primary: 'rgba(66, 165, 245, 0.9)', // 主色调:科技蓝
|
||||
secondary: 'rgba(129, 199, 132, 0.9)', // 辅助色:科技绿
|
||||
gridLine: 'rgba(100, 150, 255, 0.1)', // 网格线
|
||||
axisLine: 'rgba(100, 150, 255, 0.3)', // 坐标轴
|
||||
textColor: '#a8b2d1', // 文本
|
||||
tooltipBg: 'rgba(10, 25, 47, 0.8)', // 提示框背景
|
||||
};
|
||||
|
||||
// 渐变效果
|
||||
const gradientPrimary = {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{offset: 0, color: 'rgba(66, 165, 245, 0.9)'},
|
||||
{offset: 1, color: 'rgba(66, 165, 245, 0.3)'}
|
||||
]
|
||||
};
|
||||
|
||||
const option = {
|
||||
|
||||
// 图例
|
||||
legend: {
|
||||
data: ['投递重量(T)','投递次数'],
|
||||
textStyle: { color: colors.textColor },
|
||||
icon: 'rect',
|
||||
itemWidth: 14,
|
||||
itemHeight: 8,
|
||||
top: '3%'
|
||||
},
|
||||
|
||||
// 网格(图表边距)
|
||||
grid: {
|
||||
left: '4%',
|
||||
right: '2%',
|
||||
bottom: '15%',
|
||||
containLabel: true,
|
||||
},
|
||||
|
||||
// X轴配置
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: quLabel.value,
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: colors.axisLine }
|
||||
},
|
||||
axisLabel: {
|
||||
color: colors.textColor,
|
||||
interval: 0,
|
||||
rotate: 45
|
||||
}
|
||||
},
|
||||
|
||||
// Y轴配置(左侧:访问量)
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '投递重量(T)',
|
||||
nameTextStyle: { color: colors.primary },
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: colors.primary }
|
||||
},
|
||||
axisLabel: { color: colors.textColor },
|
||||
splitLine: { show: false }
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '投递次数',
|
||||
nameTextStyle: { color: colors.secondary },
|
||||
axisTick: { show: false },
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: colors.secondary }
|
||||
},
|
||||
axisLabel: { color: colors.textColor },
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: { color: colors.gridLine }
|
||||
}
|
||||
},
|
||||
],
|
||||
|
||||
// 系列数据(柱状图+折线图)
|
||||
series: [
|
||||
// 柱状图:访问量(科技蓝渐变)
|
||||
{
|
||||
name: '投递重量(T)',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0,
|
||||
barWidth: '35%',
|
||||
data: quWeight.value,
|
||||
itemStyle: { color: gradientPrimary },
|
||||
emphasis: { itemStyle: { color: colors.primary } },
|
||||
animationDelay: idx => idx * 10
|
||||
},
|
||||
// 折线图:转化率(紫色渐变)
|
||||
{
|
||||
name: '投递次数',
|
||||
type: 'line',
|
||||
yAxisIndex: 1, // 关联右侧Y轴
|
||||
data: quCn.value,
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
itemStyle: { color: colors.secondary },
|
||||
lineStyle: {
|
||||
color: colors.secondary,
|
||||
width: 2,
|
||||
type: 'solid'
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{offset: 0, color: 'rgba(129, 199, 132, 0.4)'},
|
||||
{offset: 1, color: 'rgba(129, 199, 132, 0.05)'}
|
||||
]
|
||||
}
|
||||
},
|
||||
animationDelay: idx => idx * 10 + 100
|
||||
}
|
||||
],
|
||||
|
||||
// tooltip(提示框)样式优化
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
crossStyle: {
|
||||
color: 'rgba(100, 255, 218, 0.7)',
|
||||
width: 1,
|
||||
type: 'dashed'
|
||||
}
|
||||
},
|
||||
backgroundColor: colors.tooltipBg,
|
||||
borderColor: 'rgba(100, 150, 255, 0.3)',
|
||||
borderWidth: 1,
|
||||
textStyle: { color: '#e6f1ff' },
|
||||
},
|
||||
|
||||
// 背景色(与页面背景融合)
|
||||
//backgroundColor: 'transparent'
|
||||
};
|
||||
|
||||
// 设置配置项并渲染
|
||||
myChart.setOption(option);
|
||||
}
|
||||
|
||||
async function loadXqtd() {
|
||||
const resData = await getXqtd({});
|
||||
const res = resData.result;
|
||||
let xqu = [];
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
xqu.push({
|
||||
housingestateName: res[i].housingestateName,
|
||||
cn: res[i].cn,
|
||||
weight: res[i].weight,
|
||||
});
|
||||
}
|
||||
tid(xqu);
|
||||
}
|
||||
|
||||
setInterval(function() {
|
||||
loadXqtd();
|
||||
}, 30*60*1000);
|
||||
|
||||
// 响应窗口大小变化
|
||||
onMounted(()=>{
|
||||
window.addEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
loadXqtd();
|
||||
})
|
||||
// const formattedValue = computed(() => {
|
||||
// if (typeof props.value === 'number') {
|
||||
// if (props.value >= 100000000) {
|
||||
// return (props.value / 100000000).toFixed(2) + '亿'
|
||||
// } else if (props.value >= 10000) {
|
||||
// return (props.value / 10000).toFixed(2) + '万'
|
||||
// } else {
|
||||
// return props.value.toLocaleString()
|
||||
// }
|
||||
// }
|
||||
// return props.value
|
||||
// })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.data-card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/*
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.card-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.number-display {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.number-counter {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.unit {
|
||||
margin-left: 4px;
|
||||
font-size: 14px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.change-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.change-desc {
|
||||
margin-left: 8px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.list-data .el-list-item {
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid rgba(100, 116, 139, 0.1);
|
||||
}
|
||||
|
||||
.list-item-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.item-value {
|
||||
margin: 0 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.item-percentage {
|
||||
min-width: 60px;
|
||||
text-align: right;
|
||||
} */
|
||||
</style>
|
|
@ -0,0 +1,273 @@
|
|||
<template>
|
||||
<div class="data-card" :id="id"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import {getHytd} from "../../dashboard/Analysis/api";
|
||||
// import { Info } from '@element-plus/icons-vue'
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
data: {
|
||||
type: String
|
||||
}
|
||||
});
|
||||
let quLabel = ref([]);
|
||||
let quCn = ref([]);
|
||||
let quWeight = ref([]);
|
||||
|
||||
let myChart = reactive({});
|
||||
function tid(data) {
|
||||
quLabel.value = [];
|
||||
quCn.value = [];
|
||||
quWeight.value = [];
|
||||
data.forEach((item) => {
|
||||
quLabel.value.push(item.phone);
|
||||
quCn.value.push(item.cn);
|
||||
quWeight.value.push(item.weight)
|
||||
})
|
||||
const chartDom = document.getElementById(props.id);
|
||||
myChart = echarts.init(chartDom);
|
||||
// 图表配置
|
||||
const option = {
|
||||
|
||||
// 图例
|
||||
legend: {
|
||||
data: ['投递重量(KG)','投递次数'],
|
||||
top: '3%',
|
||||
textStyle: { color: '#a8b2d1' }, // 图例文字颜色
|
||||
icon: 'circle', // 圆形图例,更简洁
|
||||
itemWidth: 8
|
||||
},
|
||||
|
||||
// 网格(图表边距)
|
||||
grid: {
|
||||
left: '2%',
|
||||
right: '3%',
|
||||
bottom: '15%',
|
||||
containLabel: true,
|
||||
},
|
||||
|
||||
// X轴配置
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: quLabel.value,
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: '#2d3748' } // X轴线颜色(深灰)
|
||||
},
|
||||
axisTick: { show: false }, // 隐藏刻度线
|
||||
axisLabel: {
|
||||
color: '#a0aec0', // 刻度文字颜色
|
||||
rotate: 45,
|
||||
formatter: function (params) {
|
||||
// return params.slice(-4);
|
||||
return params;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Y轴配置(左侧:访问量)
|
||||
yAxis: [
|
||||
// 右侧Y轴(转化率)
|
||||
{
|
||||
type: 'value',
|
||||
name: '投递重量(KG)',
|
||||
nameTextStyle: { color: 'rgba(129, 199, 132)' },
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: 'rgba(129, 199, 132)'
|
||||
}
|
||||
},
|
||||
axisTick: { show: false },
|
||||
splitLine: { show: false }, // 右侧不显示网格线,避免冲突
|
||||
axisLabel: {
|
||||
color: '#a8b2d1',
|
||||
// formatter: '{value}%'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '投递次数',
|
||||
nameTextStyle: { color: '#63b3ed' }, // 轴名称颜色
|
||||
axisLine: { show: true,
|
||||
lineStyle: {
|
||||
color: 'rgba(66, 165, 245)'
|
||||
}
|
||||
},
|
||||
axisTick: { show: false },
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: 'rgba(74, 85, 104, 0.3)', // 网格线半透明
|
||||
type: 'dashed' // 虚线网格,科技感
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#a8b2d1',
|
||||
// formatter: '{value}'
|
||||
}
|
||||
},
|
||||
],
|
||||
|
||||
// 系列数据(柱状图+折线图)
|
||||
series: [
|
||||
// 柱状图:访问量(科技蓝渐变)
|
||||
{
|
||||
name: '投递重量(KG)',
|
||||
type: 'bar',
|
||||
data: quWeight.value,
|
||||
// 柱子宽度
|
||||
barWidth: 24,
|
||||
// 圆角柱子
|
||||
itemStyle: {
|
||||
borderRadius: [6, 6, 0, 0], // 上圆角,下直角
|
||||
// 蓝色渐变(科技感核心)
|
||||
// 蓝色渐变(科技感核心)
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{offset: 0, color: '#00FF9D'},
|
||||
{offset: 1, color: 'rgba(0, 255, 157, 0.4)'}
|
||||
]
|
||||
},
|
||||
},
|
||||
// 柱子边框高亮
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{offset: 0, color: '#00FF9D'}, // 底部颜色
|
||||
{offset: 1, color: 'rgba(0, 255, 157, 0.3)'} // 顶部颜色c
|
||||
]),
|
||||
boxShadow: '0 0 15px rgba(49, 130, 206, 0.6)' // 鼠标悬停发光
|
||||
}
|
||||
},
|
||||
// 数值标签(顶部显示)
|
||||
label: {
|
||||
show: false,
|
||||
verticalAlign: 'middle',
|
||||
align: 'center',
|
||||
color: '#e0e7ff',
|
||||
fontSize: 12
|
||||
}
|
||||
},
|
||||
// 折线图:转化率(紫色渐变)
|
||||
{
|
||||
name: '投递次数',
|
||||
type: 'line',
|
||||
yAxisIndex: 1, // 关联右侧Y轴
|
||||
data: quCn.value,
|
||||
// 线样式
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
// color: '#165DFF', // 主色
|
||||
color: '#16b9ff', // 主色
|
||||
shadowBlur: 10,
|
||||
// shadowColor: 'rgba(22, 93, 255, 0.7)' // 发光效果
|
||||
shadowColor: 'rgba(22,139,255,0.7)' // 发光效果
|
||||
},
|
||||
// 拐点样式
|
||||
symbol: 'circle', // 圆形拐点
|
||||
symbolSize: 8, // 拐点大小
|
||||
itemStyle: {
|
||||
// color: '#805ad5',
|
||||
// borderColor: '#fff',
|
||||
color: '#16b9ff', // 主色
|
||||
borderColor: '#16b9ff', // 主色
|
||||
borderWidth: 2
|
||||
},
|
||||
// 拐点高亮
|
||||
emphasis: {
|
||||
symbolSize: 12,
|
||||
itemStyle: {
|
||||
color: '#d53f8c',
|
||||
boxShadow: '0 0 10px rgba(213, 63, 140, 0.8)'
|
||||
}
|
||||
},
|
||||
// 填充区域渐变
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{offset: 0, color: 'rgba(22, 93, 255, 0.3)'},
|
||||
{offset: 1, color: 'rgba(22, 93, 255, 0.05)'}
|
||||
])
|
||||
},
|
||||
// 数值标签
|
||||
label: {
|
||||
show: false,
|
||||
position: 'bottom',
|
||||
color: '#e9d8fd',
|
||||
// formatter: '{value}%'
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
// tooltip(提示框)样式优化
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
crossStyle: {
|
||||
color: 'rgba(100, 255, 218, 0.7)',
|
||||
width: 1,
|
||||
type: 'dashed'
|
||||
}
|
||||
},
|
||||
backgroundColor: 'rgba(17, 24, 39, 0.9)', // 半透明深色背景
|
||||
borderColor: 'rgba(79, 70, 229, 0.5)',
|
||||
borderWidth: 1,
|
||||
textStyle: { color: '#e0e7ff' },
|
||||
padding: 12,
|
||||
// 提示框内网格线
|
||||
extraCssText: 'box-shadow: 0 0 15px rgba(79, 70, 229, 0.3);',
|
||||
|
||||
},
|
||||
|
||||
// 背景色(与页面背景融合)
|
||||
//backgroundColor: 'transparent'
|
||||
};
|
||||
|
||||
// 设置配置项并渲染
|
||||
myChart.setOption(option);
|
||||
}
|
||||
|
||||
|
||||
async function loadHytd() {
|
||||
const resData = await getHytd({});
|
||||
const res = resData.result;
|
||||
let Hy = [];
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
Hy.push({
|
||||
phone: res[i].phone,
|
||||
cn: res[i].cn,
|
||||
weight: res[i].weight,
|
||||
});
|
||||
}
|
||||
tid(Hy);
|
||||
}
|
||||
|
||||
setInterval(function() {
|
||||
loadHytd();
|
||||
}, 30*60*1000);
|
||||
|
||||
// 响应窗口大小变化
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
loadHytd();
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.data-card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
|
@ -0,0 +1,178 @@
|
|||
<template>
|
||||
<div class="data-card" :id="id"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref, onMounted, reactive} from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import {getXqqy} from "../../dashboard/Analysis/api";
|
||||
// import { Info } from '@element-plus/icons-vue'
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
data: {
|
||||
type: String
|
||||
}
|
||||
});
|
||||
let quCn = ref([
|
||||
]);
|
||||
let quWeight = ref([]);
|
||||
let myChart = reactive({});
|
||||
function tid(data) {
|
||||
quCn.value = [];
|
||||
quWeight.value = [];
|
||||
data.forEach((item, idx) => {
|
||||
quCn.value.push({
|
||||
name: item.housingestateName,
|
||||
value: item.cn,
|
||||
symbolSize: item.cn / 10,
|
||||
count: item.cn,
|
||||
weight: item.weight
|
||||
});
|
||||
|
||||
quWeight.value.push({
|
||||
source: '中心节点',
|
||||
target: item.housingestateName,
|
||||
value: item.weight,
|
||||
lineStyle: {
|
||||
width: item.weight / 20,
|
||||
color: `rgba(100, 255, 218, ${item.weight / 3000 + 0.1})`
|
||||
}
|
||||
});
|
||||
});
|
||||
const chartDom = document.getElementById(props.id);
|
||||
myChart = echarts.init(chartDom);
|
||||
|
||||
// 科技风格配色
|
||||
const colors = {
|
||||
primary: 'rgba(66, 165, 245, 0.9)', // 主色调:科技蓝
|
||||
secondary: 'rgba(100, 255, 218, 0.9)', // 辅助色:科技青
|
||||
accent: 'rgba(255, 102, 102, 0.9)', // 强调色:科技红
|
||||
gridLine: 'rgba(100, 150, 255, 0.1)', // 网格线
|
||||
axisLine: 'rgba(100, 150, 255, 0.3)', // 坐标轴
|
||||
textColor: '#a8b2d1', // 文本
|
||||
tooltipBg: 'rgba(10, 25, 47, 0.8)', // 提示框背景
|
||||
};
|
||||
|
||||
|
||||
// 图表配置
|
||||
// 图表配置
|
||||
const option = {
|
||||
backgroundColor: 'transparent',
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: function (params) {
|
||||
if (params.dataType === 'edge') {
|
||||
return `
|
||||
<div style="color:#64ffda;font-weight:bold">连接</div>
|
||||
<div>投递重量(T): ${params.data.value}</div>
|
||||
<div>${params.data.source} → ${params.data.target}</div>
|
||||
`;
|
||||
}
|
||||
return `
|
||||
<div style="color:#64ffda;font-weight:bold">${params.data.name}</div>
|
||||
<div>投递重量(T): ${params.data.weight}</div>
|
||||
<div>投递次数: ${params.data.count}</div>
|
||||
|
||||
`;
|
||||
},
|
||||
backgroundColor: colors.tooltipBg,
|
||||
borderColor: 'rgba(100, 150, 255, 0.3)',
|
||||
borderWidth: 1,
|
||||
textStyle: { color: '#e6f1ff' }
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'graph',
|
||||
layout: 'force',
|
||||
data: quCn.value,
|
||||
links: quWeight.value,
|
||||
roam: true,
|
||||
focusNodeAdjacency: true,
|
||||
draggable: true,
|
||||
force: {
|
||||
repulsion: 100,
|
||||
edgeLength: [40, 100]
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
color: colors.textColor
|
||||
},
|
||||
lineStyle: {
|
||||
color: 'source',
|
||||
curveness: 0.1
|
||||
},
|
||||
emphasis: {
|
||||
lineStyle: {
|
||||
width: function (edge) {
|
||||
return edge.value / 120;
|
||||
}
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
color: function (params) {
|
||||
if (params.data.category === 0) {
|
||||
return colors.primary;
|
||||
}
|
||||
// 根据次数调整颜色透明度
|
||||
return `rgba(${params.data.count *0.15}, ${params.data.count *0.4}, ${params.data.count * 0.75}, ${params.data.count / 300 + 0.9})`;
|
||||
},
|
||||
shadowBlur: 50,
|
||||
shadowColor: function (params) {
|
||||
if (params.data.category === 0) {
|
||||
return 'rgba(100, 255, 218, 0.7)';
|
||||
}
|
||||
return 'rgba(66, 165, 245, 0.5)';
|
||||
}
|
||||
},
|
||||
animationDuration: 500,
|
||||
animationEasing: 'elasticOut',
|
||||
animationDelay: function (idx) {
|
||||
return idx * 100;
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
// 设置配置项并渲染
|
||||
myChart.setOption(option);
|
||||
}
|
||||
|
||||
// 小区清运
|
||||
async function loadXqqy(){
|
||||
const resData = await getXqqy({});
|
||||
const res = resData.result;
|
||||
let Qy = [];
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
Qy.push({
|
||||
housingestateName: res[i].housingestateName,
|
||||
cn: res[i].cn,
|
||||
weight: res[i].weight,
|
||||
});
|
||||
}
|
||||
tid(Qy);
|
||||
}
|
||||
|
||||
setInterval(function() {
|
||||
loadXqqy();
|
||||
}, 30*60*1000);
|
||||
|
||||
// 响应窗口大小变化
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
loadXqqy();
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.data-card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,267 @@
|
|||
<template>
|
||||
<div class="data-card" :id="id"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref, onMounted, reactive} from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import {getSbtd} from "../../dashboard/Analysis/api";
|
||||
// import { Info } from '@element-plus/icons-vue'
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
data: {
|
||||
type: String
|
||||
}
|
||||
});
|
||||
let quLabel = ref([]);
|
||||
let quCn = ref([]);
|
||||
let quWeight = ref([]);
|
||||
let myChart = reactive({});
|
||||
function tid(data) {
|
||||
quLabel.value = [];
|
||||
quCn.value = [];
|
||||
quWeight.value = [];
|
||||
data.forEach((item) => {
|
||||
quLabel.value.push(`${item.housingestateName}-${item.content}`);
|
||||
//quLabel.value.push(item.content);
|
||||
quCn.value.push(item.cn);
|
||||
quWeight.value.push(item.weight)
|
||||
})
|
||||
const chartDom = document.getElementById(props.id);
|
||||
myChart = echarts.init(chartDom);
|
||||
|
||||
// 图表配置
|
||||
const option = {
|
||||
|
||||
// 图例
|
||||
legend: {
|
||||
data: ['投递重量(T)','投递次数'],
|
||||
top: '3%',
|
||||
textStyle: { color: '#a8b2d1' }, // 图例文字颜色
|
||||
icon: 'circle', // 圆形图例,更简洁
|
||||
itemWidth: 8
|
||||
},
|
||||
|
||||
// 网格(图表边距)
|
||||
grid: {
|
||||
left: '5%',
|
||||
right: '2%',
|
||||
bottom: '10%',
|
||||
containLabel: true,
|
||||
},
|
||||
|
||||
// X轴配置
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: quLabel.value,
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: '#2d3748' } // X轴线颜色(深灰)
|
||||
},
|
||||
axisTick: { show: false }, // 隐藏刻度线
|
||||
axisLabel: {
|
||||
color: '#a0aec0', // 刻度文字颜色
|
||||
rotate: 45,
|
||||
formatter: function (params) {
|
||||
if (typeof params !== 'string') {
|
||||
return ''; // 处理非字符串输入
|
||||
}
|
||||
|
||||
const index = params.lastIndexOf('-');
|
||||
if (index === -1) {
|
||||
return ''; // 没有找到破折号
|
||||
}
|
||||
return params.substring(index + 1).trim();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Y轴配置(左侧:访问量)
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '投递重量(T)',
|
||||
nameTextStyle: { color: '#805ad5' }, // 轴名称颜色
|
||||
axisLine: { show: true,
|
||||
lineStyle: {
|
||||
color: 'rgb(128,90,213)'
|
||||
}
|
||||
},
|
||||
axisTick: { show: false },
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: 'rgba(74, 85, 104, 0.3)', // 网格线半透明
|
||||
type: 'dashed' // 虚线网格,科技感
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#a8b2d1',
|
||||
// formatter: '{value}'
|
||||
}
|
||||
},
|
||||
// 右侧Y轴(转化率)
|
||||
{
|
||||
type: 'value',
|
||||
name: '投递次数',
|
||||
nameTextStyle: { color: '#63b3ed' },
|
||||
axisLine: { show: true,
|
||||
lineStyle: {
|
||||
color: 'rgba(66, 165, 245)'
|
||||
}
|
||||
},
|
||||
axisTick: { show: false },
|
||||
splitLine: { show: false }, // 右侧不显示网格线,避免冲突
|
||||
axisLabel: {
|
||||
color: '#a8b2d1',
|
||||
// formatter: '{value}%'
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
// 系列数据(柱状图+折线图)
|
||||
series: [
|
||||
// 柱状图:访问量(科技蓝渐变)
|
||||
{
|
||||
name: '投递重量(T)',
|
||||
type: 'bar',
|
||||
data: quWeight.value,
|
||||
// 柱子宽度
|
||||
barWidth: 24,
|
||||
// 圆角柱子
|
||||
itemStyle: {
|
||||
borderRadius: [6, 6, 0, 0], // 上圆角,下直角
|
||||
// 蓝色渐变(科技感核心)
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{offset: 0, color: '#9D4EDD'},
|
||||
{offset: 1, color: 'rgba(22, 93, 255, 0.6)'}
|
||||
]
|
||||
},
|
||||
},
|
||||
// 柱子边框高亮
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#63b3ed' }, // 高亮时更亮
|
||||
{ offset: 1, color: '#3182ce' }
|
||||
]),
|
||||
boxShadow: '0 0 15px rgba(49, 130, 206, 0.6)' // 鼠标悬停发光
|
||||
}
|
||||
},
|
||||
// 数值标签(顶部显示)
|
||||
label: {
|
||||
show: false,
|
||||
verticalAlign: 'middle',
|
||||
align: 'center',
|
||||
color: '#e0e7ff',
|
||||
fontSize: 12
|
||||
}
|
||||
},
|
||||
// 折线图:转化率(紫色渐变)
|
||||
{
|
||||
name: '投递次数',
|
||||
type: 'line',
|
||||
yAxisIndex: 1, // 关联右侧Y轴
|
||||
data: quCn.value,
|
||||
// 线样式
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
color: '#00E5FF', // 线条颜色
|
||||
shadowBlur: 10,
|
||||
shadowColor: 'rgba(0, 229, 255, 0.5)' // 发光效果
|
||||
},
|
||||
// 拐点样式
|
||||
symbol: 'circle', // 圆形拐点
|
||||
symbolSize: 8, // 拐点大小
|
||||
itemStyle: {
|
||||
color: '#805ad5',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
// 拐点高亮
|
||||
emphasis: {
|
||||
symbolSize: 12,
|
||||
itemStyle: {
|
||||
color: '#d53f8c',
|
||||
boxShadow: '0 0 10px rgba(213, 63, 140, 0.8)'
|
||||
}
|
||||
},
|
||||
// 填充区域渐变
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(128, 90, 213, 0.3)' },
|
||||
{ offset: 1, color: 'rgba(128, 90, 213, 0)' }
|
||||
])
|
||||
},
|
||||
// 数值标签
|
||||
label: {
|
||||
show: false,
|
||||
position: 'bottom',
|
||||
color: '#e9d8fd',
|
||||
// formatter: '{value}%'
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
// tooltip(提示框)样式优化
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(17, 24, 39, 0.9)', // 半透明深色背景
|
||||
borderColor: 'rgba(79, 70, 229, 0.5)',
|
||||
borderWidth: 1,
|
||||
textStyle: { color: '#e0e7ff' },
|
||||
padding: 12,
|
||||
// 提示框内网格线
|
||||
extraCssText: 'box-shadow: 0 0 15px rgba(79, 70, 229, 0.3);',
|
||||
|
||||
},
|
||||
|
||||
// 背景色(与页面背景融合)
|
||||
//backgroundColor: 'transparent'
|
||||
};
|
||||
|
||||
// 设置配置项并渲染
|
||||
myChart.setOption(option);
|
||||
}
|
||||
|
||||
async function loadSbtd() {
|
||||
const resData = await getSbtd({});
|
||||
const res = resData.result;
|
||||
let Sb = [];
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
Sb.push({
|
||||
housingestateName: res[i].housingestateName,
|
||||
content: res[i].content,
|
||||
cn: res[i].cn,
|
||||
weight: res[i].weight,
|
||||
})
|
||||
}
|
||||
tid(Sb);
|
||||
}
|
||||
|
||||
setInterval(function() {
|
||||
loadSbtd();
|
||||
}, 30*60*1000);
|
||||
|
||||
// 响应窗口大小变化
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
loadSbtd();
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.data-card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<div class="page-header">
|
||||
<div class="header-title">
|
||||
<h1>{{ title }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '吉林资环数据平台'
|
||||
},
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-header {
|
||||
display: flex;
|
||||
height: 60px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-image: url('@/assets/titlebg.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 100% 100%;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.header-title h1 {
|
||||
margin: 0;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 10px;
|
||||
background: linear-gradient(to top, #098fe9, #ffffff);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,277 @@
|
|||
<template>
|
||||
<div class="dashboard-container">
|
||||
<PageHeader />
|
||||
<a-row :gutter="20" class="main-content">
|
||||
<!-- 左侧面板 -->
|
||||
<a-col :span="6">
|
||||
<a-card class="panel-card" :body-style="{'width':'100%','height':'100%','padding':'0', 'margin-top': '-34px'}">
|
||||
<template #title>
|
||||
<div class="card-header">
|
||||
<sliders-two-tone style="margin-right: 5px"/>
|
||||
<span>小区排行</span>
|
||||
</div>
|
||||
</template>
|
||||
<DataCard :id="`echart1`"/>
|
||||
</a-card>
|
||||
|
||||
<a-card class="panel-card mt-20" :body-style="{'width':'100%','height':'100%','padding':'0','margin-top': '-34px'}">
|
||||
<template #title>
|
||||
<div class="card-header">
|
||||
<sliders-two-tone style="margin-right: 5px" />
|
||||
<span>设备排行</span>
|
||||
</div>
|
||||
</template>
|
||||
<DataCardSet :id="`echart2`" :data="Sb" />
|
||||
</a-card>
|
||||
</a-col>
|
||||
<!-- 中间面板 -->
|
||||
<a-col :span="12">
|
||||
<a-card class="mind-card" :body-style="{'padding':'0'}">
|
||||
|
||||
<ChartComponent
|
||||
ref="mainChartRef"
|
||||
:showControls="true"
|
||||
:initialConfig="particleConfig"
|
||||
/>
|
||||
|
||||
<div class="statistics-container">
|
||||
<a-row :gutter="20">
|
||||
<a-col :span="6" v-for="(item, index) in statistics" :key="index">
|
||||
<div class="statistics-item">
|
||||
<div class="title">{{ item.title }}</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="today-data">{{ item.today }}</div>
|
||||
<div class="yesterday">昨日 {{ item.yesterday }}</div>
|
||||
</div>
|
||||
<div :style="{ color: item.rateColor }" class="rate">较昨日 {{ item.rate }}</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="panel-card" :body-style="{'width':'100%','height':'100%','padding':'0', 'margin-top': '-34px'}">
|
||||
<template #title>
|
||||
<div class="card-header">
|
||||
<sliders-two-tone style="margin-right: 5px"/>
|
||||
<span>会员排行</span>
|
||||
</div>
|
||||
</template>
|
||||
<DataCardHy :id="`echart3`" :data="Hy" />
|
||||
</a-card>
|
||||
|
||||
<a-card class="panel-card mt-20" :body-style="{'width':'100%','height':'100%','padding':'0','margin-top': '-34px'}">
|
||||
<template #title>
|
||||
<div class="card-header">
|
||||
<sliders-two-tone style="margin-right: 5px"/>
|
||||
<span>小区清运</span>
|
||||
</div>
|
||||
</template>
|
||||
<DataCardQy :id="`echart4`" :data="Qy"/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref, onMounted, computed, reactive} from 'vue'
|
||||
import {
|
||||
SlidersTwoTone
|
||||
} from '@ant-design/icons-vue';
|
||||
import PageHeader from './components/PageHeader.vue'
|
||||
import DataCard from './components/DataCard.vue'
|
||||
import DataCardSet from './components/DataCardSet.vue'
|
||||
import DataCardHy from './components/DataCardHy.vue'
|
||||
import DataCardQy from './components/DataCardQy.vue'
|
||||
import ChartComponent from './components/ChartComponent.vue'
|
||||
import {getTodayInfo} from "../dashboard/Analysis/api";
|
||||
|
||||
const particleConfig = ref({
|
||||
particleCount: 800,
|
||||
radius: 30,
|
||||
particleSize: 0.4,
|
||||
animationSpeed: 0.015,
|
||||
colorMode: 'group'
|
||||
})
|
||||
|
||||
async function loadTodayData() {
|
||||
statistics.value = [];
|
||||
const resData = await getTodayInfo({});
|
||||
const res = resData.result;
|
||||
statistics.value.push(
|
||||
{
|
||||
title: '今日投递重量(T)',
|
||||
today: res.tdzlT,
|
||||
yesterday: res.tdzlY,
|
||||
rate: res.tdzlR+"%",
|
||||
rateColor: res.tdzlR>=0?'#ff3333':'#00FF9D'
|
||||
},
|
||||
{
|
||||
title: '今日投递次数(次)',
|
||||
today: res.tdcsT,
|
||||
yesterday: res.tdcsY,
|
||||
rate: res.tdcsR+"%",
|
||||
rateColor: res.tdzlR>=0?'#ff3333':'#00FF9D'
|
||||
},
|
||||
{
|
||||
title: '今日清运重量(T)',
|
||||
today: res.qyzlT,
|
||||
yesterday: res.qyzlY,
|
||||
rate: res.qyzlR+"%",
|
||||
rateColor: res.tdzlR>=0?'#ff3333':'#00FF9D'
|
||||
},
|
||||
{
|
||||
title: '今日新增会员(人)',
|
||||
today: res.hyrsT,
|
||||
yesterday: res.hyrsY,
|
||||
rate: res.hyrsR+"%",
|
||||
rateColor: res.tdzlR>=0?'#ff3333':'#00FF9D'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 模拟底部信息数据
|
||||
const statistics = ref([])
|
||||
|
||||
// 图表引用
|
||||
const mainChartRef = ref(null)
|
||||
const budgetChartRef = ref(null)
|
||||
const trendChartRef = ref(null)
|
||||
|
||||
setInterval(function() {
|
||||
loadTodayData();
|
||||
}, 30*60*1000);
|
||||
|
||||
onMounted(() => {
|
||||
loadTodayData();
|
||||
})
|
||||
|
||||
// 窗口大小变化时调整图表
|
||||
const handleResize = () => {
|
||||
if (mainChartRef.value) mainChartRef.value.resize()
|
||||
if (budgetChartRef.value) budgetChartRef.value.resize()
|
||||
if (trendChartRef.value) trendChartRef.value.resize()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
.dashboard-container {
|
||||
background-image: url('@/assets/bg.png');
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
/*margin-top: 20px;*/
|
||||
padding: 15px 15px 0px 15px;
|
||||
}
|
||||
|
||||
.panel-card {
|
||||
background: url('@/assets/borderbg.png') no-repeat center;
|
||||
background-size: 100% 100%;
|
||||
border: 1px solid rgba(64, 158, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
height: 43vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mind-card{
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
height: 30px;
|
||||
padding-left: 20px;
|
||||
|
||||
}
|
||||
::v-deep(.ant-card-head-title){
|
||||
background: url('@/assets/btbg.png') no-repeat center;
|
||||
background-size: 100%;
|
||||
height: 76px;
|
||||
border: none;
|
||||
padding: 5px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
::v-deep(.ant-card-head){
|
||||
margin-left: 0px!important;
|
||||
margin-right: 0px!important;
|
||||
padding: 0px;
|
||||
border: none;
|
||||
}
|
||||
.card-header i {
|
||||
margin-right: 8px;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.small-cards {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.small-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.card-value {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.card-desc {
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.statistics-item {
|
||||
background: #0d0348;
|
||||
border: 1px solid #064f8b;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.title {
|
||||
font-size: 14px;
|
||||
color: #99F2FF;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.today-data {
|
||||
font-size: 36px;
|
||||
color: #00FFFF;
|
||||
font-weight: 700;
|
||||
margin: 10px 0;
|
||||
text-shadow: 0 0 8px rgba(0, 255, 255, 0.4);
|
||||
}
|
||||
.yesterday {
|
||||
color: #B3FFFF;
|
||||
font-size: 12px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.rate {
|
||||
font-size: 12px;
|
||||
}
|
||||
.statistics-container{
|
||||
//display: grid;
|
||||
//grid-template-rows: auto 1fr auto;
|
||||
//.ant-row{
|
||||
// align-self: end;
|
||||
//}
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
width: 49%;
|
||||
}
|
||||
</style>
|