增加了分析界面
parent
6a7019ec1a
commit
4f89950fc1
|
@ -10,16 +10,10 @@ declare module '@vue/runtime-core' {
|
|||
Countup: typeof import('./src/components/countup.vue')['default']
|
||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCalendar: typeof import('element-plus/es')['ElCalendar']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCarousel: typeof import('element-plus/es')['ElCarousel']
|
||||
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
|
||||
ElCascader: typeof import('element-plus/es')['ElCascader']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
||||
ElCountdown: typeof import('element-plus/es')['ElCountdown']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
||||
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
|
||||
|
@ -31,7 +25,6 @@ declare module '@vue/runtime-core' {
|
|||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElImage: typeof import('element-plus/es')['ElImage']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||
ElLink: typeof import('element-plus/es')['ElLink']
|
||||
|
@ -40,18 +33,8 @@ declare module '@vue/runtime-core' {
|
|||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElRate: typeof import('element-plus/es')['ElRate']
|
||||
ElResult: typeof import('element-plus/es')['ElResult']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSlider: typeof import('element-plus/es')['ElSlider']
|
||||
ElSpace: typeof import('element-plus/es')['ElSpace']
|
||||
ElStatistic: typeof import('element-plus/es')['ElStatistic']
|
||||
ElStep: typeof import('element-plus/es')['ElStep']
|
||||
ElSteps: typeof import('element-plus/es')['ElSteps']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
|
@ -61,13 +44,8 @@ declare module '@vue/runtime-core' {
|
|||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTimeline: typeof import('element-plus/es')['ElTimeline']
|
||||
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
|
||||
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElTour: typeof import('element-plus/es')['ElTour']
|
||||
ElTourStep: typeof import('element-plus/es')['ElTourStep']
|
||||
ElTransfer: typeof import('element-plus/es')['ElTransfer']
|
||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||
ElWatermark: typeof import('element-plus/es')['ElWatermark']
|
||||
Header: typeof import('./src/components/header.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,6 +12,7 @@
|
|||
<Fold />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div id="analysis-page" @click="handleAnalysisPage">订单分析页面</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="header-user-con">
|
||||
|
@ -94,6 +95,10 @@ const handleCommand = (command: string) => {
|
|||
}
|
||||
};
|
||||
|
||||
const handleAnalysisPage = () => {
|
||||
router.push('/analysis');
|
||||
};
|
||||
|
||||
const setFullScreen = () => {
|
||||
if (document.fullscreenElement) {
|
||||
document.exitFullscreen();
|
||||
|
@ -176,6 +181,15 @@ const setFullScreen = () => {
|
|||
font-size: 20px;
|
||||
}
|
||||
|
||||
#analysis-page {
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#analysis-page:hover {
|
||||
color: rgb(190, 190, 255);
|
||||
}
|
||||
|
||||
.btn-bell-badge {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
|
|
|
@ -1,269 +1,335 @@
|
|||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
||||
import { usePermissStore } from '../store/permiss';
|
||||
import Home from '../views/home.vue';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
|
||||
import { usePermissStore } from "../store/permiss";
|
||||
import Home from "../views/home.vue";
|
||||
import NProgress from "nprogress";
|
||||
import "nprogress/nprogress.css";
|
||||
import { pa } from "element-plus/es/locale";
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/dashboard',
|
||||
path: "/",
|
||||
redirect: "/dashboard",
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
path: "/",
|
||||
name: "Home",
|
||||
component: Home,
|
||||
children: [
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'dashboard',
|
||||
path: "/dashboard",
|
||||
name: "dashboard",
|
||||
meta: {
|
||||
title: '系统首页',
|
||||
title: "系统首页",
|
||||
noAuth: true,
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "dashboard" */ '../views/dashboard.vue'),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "dashboard" */ "../views/dashboard.vue"),
|
||||
},
|
||||
{
|
||||
path: '/system-user',
|
||||
name: 'system-user',
|
||||
path: "/system-user",
|
||||
name: "system-user",
|
||||
meta: {
|
||||
title: '用户管理',
|
||||
permiss: '11',
|
||||
title: "用户管理",
|
||||
permiss: "11",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "system-user" */ '../views/system/user.vue'),
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "system-user" */ "../views/system/user.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/system-role',
|
||||
name: 'system-role',
|
||||
path: "/system-role",
|
||||
name: "system-role",
|
||||
meta: {
|
||||
title: '角色管理',
|
||||
permiss: '12',
|
||||
title: "角色管理",
|
||||
permiss: "12",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "system-role" */ '../views/system/role.vue'),
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "system-role" */ "../views/system/role.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/system-menu',
|
||||
name: 'system-menu',
|
||||
path: "/system-menu",
|
||||
name: "system-menu",
|
||||
meta: {
|
||||
title: '菜单管理',
|
||||
permiss: '13',
|
||||
title: "菜单管理",
|
||||
permiss: "13",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "system-menu" */ '../views/system/menu.vue'),
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "system-menu" */ "../views/system/menu.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/table',
|
||||
name: 'basetable',
|
||||
path: "/table",
|
||||
name: "basetable",
|
||||
meta: {
|
||||
title: '基础表格',
|
||||
permiss: '31',
|
||||
title: "基础表格",
|
||||
permiss: "31",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "table" */ '../views/table/basetable.vue'),
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "table" */ "../views/table/basetable.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/table-editor',
|
||||
name: 'table-editor',
|
||||
path: "/table-editor",
|
||||
name: "table-editor",
|
||||
meta: {
|
||||
title: '可编辑表格',
|
||||
permiss: '32',
|
||||
title: "可编辑表格",
|
||||
permiss: "32",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "table-editor" */ '../views/table/table-editor.vue'),
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "table-editor" */ "../views/table/table-editor.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/schart',
|
||||
name: 'schart',
|
||||
path: "/schart",
|
||||
name: "schart",
|
||||
meta: {
|
||||
title: 'schart图表',
|
||||
permiss: '41',
|
||||
title: "schart图表",
|
||||
permiss: "41",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "schart" */ '../views/chart/schart.vue'),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "schart" */ "../views/chart/schart.vue"),
|
||||
},
|
||||
{
|
||||
path: '/echarts',
|
||||
name: 'echarts',
|
||||
path: "/echarts",
|
||||
name: "echarts",
|
||||
meta: {
|
||||
title: 'echarts图表',
|
||||
permiss: '42',
|
||||
title: "echarts图表",
|
||||
permiss: "42",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "echarts" */ '../views/chart/echarts.vue'),
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "echarts" */ "../views/chart/echarts.vue"
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
path: '/icon',
|
||||
name: 'icon',
|
||||
path: "/icon",
|
||||
name: "icon",
|
||||
meta: {
|
||||
title: '图标',
|
||||
permiss: '5',
|
||||
title: "图标",
|
||||
permiss: "5",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "icon" */ '../views/pages/icon.vue'),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "icon" */ "../views/pages/icon.vue"),
|
||||
},
|
||||
{
|
||||
path: '/ucenter',
|
||||
name: 'ucenter',
|
||||
path: "/ucenter",
|
||||
name: "ucenter",
|
||||
meta: {
|
||||
title: '个人中心',
|
||||
title: "个人中心",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "ucenter" */ '../views/pages/ucenter.vue'),
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "ucenter" */ "../views/pages/ucenter.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/editor',
|
||||
name: 'editor',
|
||||
path: "/editor",
|
||||
name: "editor",
|
||||
meta: {
|
||||
title: '富文本编辑器',
|
||||
permiss: '291',
|
||||
title: "富文本编辑器",
|
||||
permiss: "291",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "editor" */ '../views/pages/editor.vue'),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "editor" */ "../views/pages/editor.vue"),
|
||||
},
|
||||
{
|
||||
path: '/markdown',
|
||||
name: 'markdown',
|
||||
path: "/markdown",
|
||||
name: "markdown",
|
||||
meta: {
|
||||
title: 'markdown编辑器',
|
||||
permiss: '292',
|
||||
title: "markdown编辑器",
|
||||
permiss: "292",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "markdown" */ '../views/pages/markdown.vue'),
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "markdown" */ "../views/pages/markdown.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/export',
|
||||
name: 'export',
|
||||
path: "/export",
|
||||
name: "export",
|
||||
meta: {
|
||||
title: '导出Excel',
|
||||
permiss: '34',
|
||||
title: "导出Excel",
|
||||
permiss: "34",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "export" */ '../views/table/export.vue'),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "export" */ "../views/table/export.vue"),
|
||||
},
|
||||
{
|
||||
path: '/import',
|
||||
name: 'import',
|
||||
path: "/import",
|
||||
name: "import",
|
||||
meta: {
|
||||
title: '导入Excel',
|
||||
permiss: '33',
|
||||
title: "导入Excel",
|
||||
permiss: "33",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "import" */ '../views/table/import.vue'),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "import" */ "../views/table/import.vue"),
|
||||
},
|
||||
{
|
||||
path: '/theme',
|
||||
name: 'theme',
|
||||
path: "/theme",
|
||||
name: "theme",
|
||||
meta: {
|
||||
title: '主题设置',
|
||||
permiss: '7',
|
||||
title: "主题设置",
|
||||
permiss: "7",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "theme" */ '../views/pages/theme.vue'),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "theme" */ "../views/pages/theme.vue"),
|
||||
},
|
||||
{
|
||||
path: '/calendar',
|
||||
name: 'calendar',
|
||||
path: "/calendar",
|
||||
name: "calendar",
|
||||
meta: {
|
||||
title: '日历',
|
||||
permiss: '24',
|
||||
title: "日历",
|
||||
permiss: "24",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "calendar" */ '../views/element/calendar.vue'),
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "calendar" */ "../views/element/calendar.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/watermark',
|
||||
name: 'watermark',
|
||||
path: "/watermark",
|
||||
name: "watermark",
|
||||
meta: {
|
||||
title: '水印',
|
||||
permiss: '25',
|
||||
title: "水印",
|
||||
permiss: "25",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "watermark" */ '../views/element/watermark.vue'),
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "watermark" */ "../views/element/watermark.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/carousel',
|
||||
name: 'carousel',
|
||||
path: "/carousel",
|
||||
name: "carousel",
|
||||
meta: {
|
||||
title: '走马灯',
|
||||
permiss: '23',
|
||||
title: "走马灯",
|
||||
permiss: "23",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "carousel" */ '../views/element/carousel.vue'),
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "carousel" */ "../views/element/carousel.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/tour',
|
||||
name: 'tour',
|
||||
path: "/tour",
|
||||
name: "tour",
|
||||
meta: {
|
||||
title: '分步引导',
|
||||
permiss: '26',
|
||||
title: "分步引导",
|
||||
permiss: "26",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "tour" */ '../views/element/tour.vue'),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "tour" */ "../views/element/tour.vue"),
|
||||
},
|
||||
{
|
||||
path: '/steps',
|
||||
name: 'steps',
|
||||
path: "/steps",
|
||||
name: "steps",
|
||||
meta: {
|
||||
title: '步骤条',
|
||||
permiss: '27',
|
||||
title: "步骤条",
|
||||
permiss: "27",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "steps" */ '../views/element/steps.vue'),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "steps" */ "../views/element/steps.vue"),
|
||||
},
|
||||
{
|
||||
path: '/form',
|
||||
name: 'forms',
|
||||
path: "/form",
|
||||
name: "forms",
|
||||
meta: {
|
||||
title: '表单',
|
||||
permiss: '21',
|
||||
title: "表单",
|
||||
permiss: "21",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "form" */ '../views/element/form.vue'),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "form" */ "../views/element/form.vue"),
|
||||
},
|
||||
{
|
||||
path: '/upload',
|
||||
name: 'upload',
|
||||
path: "/upload",
|
||||
name: "upload",
|
||||
meta: {
|
||||
title: '上传',
|
||||
permiss: '22',
|
||||
title: "上传",
|
||||
permiss: "22",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "upload" */ '../views/element/upload.vue'),
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "upload" */ "../views/element/upload.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/statistic',
|
||||
name: 'statistic',
|
||||
path: "/statistic",
|
||||
name: "statistic",
|
||||
meta: {
|
||||
title: '统计',
|
||||
permiss: '28',
|
||||
title: "统计",
|
||||
permiss: "28",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "statistic" */ '../views/element/statistic.vue'),
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "statistic" */ "../views/element/statistic.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/analysis",
|
||||
name: "analysis",
|
||||
meta: {
|
||||
title: "订单分析",
|
||||
permiss: "1",
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "theme" */ "../views/pages/Analysis.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
path: "/login",
|
||||
meta: {
|
||||
title: '登录',
|
||||
title: "登录",
|
||||
noAuth: true,
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "login" */ '../views/pages/login.vue'),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "login" */ "../views/pages/login.vue"),
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
path: "/register",
|
||||
meta: {
|
||||
title: '注册',
|
||||
title: "注册",
|
||||
noAuth: true,
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "register" */ '../views/pages/register.vue'),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "register" */ "../views/pages/register.vue"),
|
||||
},
|
||||
{
|
||||
path: '/reset-pwd',
|
||||
path: "/reset-pwd",
|
||||
meta: {
|
||||
title: '重置密码',
|
||||
title: "重置密码",
|
||||
noAuth: true,
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "reset-pwd" */ '../views/pages/reset-pwd.vue'),
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "reset-pwd" */ "../views/pages/reset-pwd.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/403',
|
||||
path: "/403",
|
||||
meta: {
|
||||
title: '没有权限',
|
||||
title: "没有权限",
|
||||
noAuth: true,
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "403" */ '../views/pages/403.vue'),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "403" */ "../views/pages/403.vue"),
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
path: "/404",
|
||||
meta: {
|
||||
title: '找不到页面',
|
||||
title: "找不到页面",
|
||||
noAuth: true,
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "404" */ '../views/pages/404.vue'),
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "404" */ "../views/pages/404.vue"),
|
||||
},
|
||||
{ path: '/:path(.*)', redirect: '/404' },
|
||||
{ path: "/:path(.*)", redirect: "/404" },
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
|
@ -273,14 +339,17 @@ const router = createRouter({
|
|||
|
||||
router.beforeEach((to, from, next) => {
|
||||
NProgress.start();
|
||||
const role = localStorage.getItem('vuems_name');
|
||||
const role = localStorage.getItem("vuems_name");
|
||||
const permiss = usePermissStore();
|
||||
|
||||
if (!role && to.meta.noAuth !== true) {
|
||||
next('/login');
|
||||
} else if (typeof to.meta.permiss == 'string' && !permiss.key.includes(to.meta.permiss)) {
|
||||
next("/login");
|
||||
} else if (
|
||||
typeof to.meta.permiss == "string" &&
|
||||
!permiss.key.includes(to.meta.permiss)
|
||||
) {
|
||||
// 如果没有权限,则进入403
|
||||
next('/403');
|
||||
next("/403");
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,515 @@
|
|||
<template>
|
||||
<div class="analysis-container">
|
||||
<!-- 顶部工具栏 -->
|
||||
<div class="toolbar">
|
||||
<h2 class="title">数据分析</h2>
|
||||
<div style="display: flex; gap: 20px;">
|
||||
<button class="edit-btn" @click="showEditDialog = true">
|
||||
编辑数据
|
||||
</button>
|
||||
<button class="edit-btn" @click="() => chartData.push(Math.floor(Math.random() * 100))">
|
||||
插入数据
|
||||
</button>
|
||||
<button class="edit-btn" @click="() => chartData.pop()">
|
||||
删除数据
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计信息卡片 -->
|
||||
<div class="stats-container">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">平均值</div>
|
||||
<div class="stat-value">{{ statistics.mean }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">最大值</div>
|
||||
<div class="stat-value">{{ statistics.max }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">最小值</div>
|
||||
<div class="stat-value">{{ statistics.min }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">总数</div>
|
||||
<div class="stat-value">{{ statistics.sum }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">标准差</div>
|
||||
<div class="stat-value">{{ statistics.stdDev }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图表容器 -->
|
||||
<div class="chart-container">
|
||||
<h2>当前数据</h2>
|
||||
<v-chart class="map-chart" :option="mapOptions" />
|
||||
</div>
|
||||
<br><br><br>
|
||||
<div class="predict-chart-container">
|
||||
<h2>未来趋势</h2>
|
||||
<v-chart class="map-chart" :option="predictOptions" />
|
||||
</div>
|
||||
|
||||
<!-- 编辑数据对话框 -->
|
||||
<div v-if="showEditDialog" class="dialog-overlay" @click="closeDialog">
|
||||
<div class="dialog" @click.stop>
|
||||
<div class="dialog-header">
|
||||
<h3>编辑数据</h3>
|
||||
<button class="close-btn" @click="closeDialog">×</button>
|
||||
</div>
|
||||
<div class="dialog-content">
|
||||
<div class="input-group" v-for="(value, index) in tempChartData" :key="index">
|
||||
<label>数据点 {{ index + 1 }}:</label>
|
||||
<input type="number" v-model.number="tempChartData[index]" class="data-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<button class="cancel-btn" @click="closeDialog">取消</button>
|
||||
<button class="save-btn" @click="saveData">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import VChart from 'vue-echarts';
|
||||
import { use } from 'echarts/core';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { LineChart } from 'echarts/charts';
|
||||
import {
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
GridComponent
|
||||
} from 'echarts/components';
|
||||
import * as echarts from 'echarts/core';
|
||||
|
||||
// 注册必要的组件
|
||||
use([
|
||||
CanvasRenderer,
|
||||
LineChart,
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
GridComponent
|
||||
]);
|
||||
|
||||
const { graphic } = echarts;
|
||||
|
||||
// 响应式数据
|
||||
const chartData = ref([120, 132, 301, 134, 90, 230, 210, 100, 400]);
|
||||
const showEditDialog = ref(false);
|
||||
const tempChartData = ref([...chartData.value]);
|
||||
|
||||
// 计算统计信息
|
||||
const statistics = computed(() => {
|
||||
const data = chartData.value;
|
||||
const sum = data.reduce((acc, val) => acc + val, 0);
|
||||
const mean = Math.round((sum / data.length) * 100) / 100;
|
||||
const max = Math.max(...data);
|
||||
const min = Math.min(...data);
|
||||
|
||||
// 计算方差
|
||||
const variance = Math.round((data.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) / data.length) * 100) / 100;
|
||||
|
||||
// 计算标准差
|
||||
const stdDev = Math.round(Math.sqrt(variance) * 100) / 100;
|
||||
|
||||
return {
|
||||
mean,
|
||||
max,
|
||||
min,
|
||||
sum,
|
||||
stdDev
|
||||
};
|
||||
});
|
||||
|
||||
// 图表配置
|
||||
const mapOptions = computed(() => ({
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: chartData.value.map((_, index) => `${index + 1}`),
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
grid: {
|
||||
top: '5%',
|
||||
left: '3%',
|
||||
right: '3%',
|
||||
bottom: '5%',
|
||||
containLabel: true,
|
||||
},
|
||||
color: ['#1976d2'],
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(0,0,0,0.8)',
|
||||
borderColor: '#1976d2',
|
||||
textStyle: {
|
||||
color: '#fff'
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'line',
|
||||
areaStyle: {
|
||||
color: new graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(25, 118, 210, 0.8)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(25, 118, 210, 0)',
|
||||
},
|
||||
]),
|
||||
},
|
||||
lineStyle: {
|
||||
width: 4,
|
||||
},
|
||||
symbolSize: 8,
|
||||
data: chartData.value,
|
||||
}
|
||||
],
|
||||
}));
|
||||
|
||||
// const predictData = ref([120, 132, 301, 134, 500, 230, 210]);
|
||||
|
||||
const predictData = computed(() => {
|
||||
const data = chartData.value;
|
||||
if (data.length < 2) return [];
|
||||
|
||||
const predictions = [];
|
||||
const windowSize = Math.min(3, data.length); // 使用最近3个点或全部点
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
let predictedValue;
|
||||
|
||||
if (i % 2 === 0) {
|
||||
// 使用线性趋势预测
|
||||
const recentData = [...data, ...predictions];
|
||||
const len = recentData.length;
|
||||
const x1 = len - windowSize, y1 = recentData[len - windowSize];
|
||||
const x2 = len - 1, y2 = recentData[len - 1];
|
||||
|
||||
// 计算斜率
|
||||
const slope = (y2 - y1) / (x2 - x1);
|
||||
predictedValue = y2 + slope;
|
||||
} else {
|
||||
const lastValues = [...data, ...predictions];
|
||||
const sum = lastValues.slice(-windowSize).reduce((acc, val) => acc + val, 0);
|
||||
predictedValue = sum / Math.min(windowSize, lastValues.length);
|
||||
}
|
||||
|
||||
// 添加一些随机波动,使预测更真实
|
||||
const noise = (Math.random() - 0.5) * 20;
|
||||
predictedValue = Math.max(0, Math.round(predictedValue + noise));
|
||||
|
||||
predictions.push(predictedValue);
|
||||
}
|
||||
|
||||
return predictions;
|
||||
});
|
||||
|
||||
const predictOptions = computed(() => ({
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: predictData.value.map((_, index) => `${index + chartData.value.length + 1}`),
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
grid: {
|
||||
top: '5%',
|
||||
left: '3%',
|
||||
right: '3%',
|
||||
bottom: '5%',
|
||||
containLabel: true,
|
||||
},
|
||||
color: ['#dccc13'],
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(0,0,0,0.8)',
|
||||
borderColor: '#dccc13',
|
||||
textStyle: {
|
||||
color: '#fff'
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'line',
|
||||
areaStyle: {
|
||||
color: new graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(255, 246, 143, 0.8)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(255, 246, 143, 0)',
|
||||
},
|
||||
]),
|
||||
},
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
width: 4,
|
||||
},
|
||||
symbolSize: 8,
|
||||
data: predictData.value,
|
||||
}
|
||||
],
|
||||
}));
|
||||
|
||||
// 方法
|
||||
const closeDialog = () => {
|
||||
showEditDialog.value = false;
|
||||
tempChartData.value = [...chartData.value]; // 重置临时数据
|
||||
};
|
||||
|
||||
const saveData = () => {
|
||||
chartData.value = [...tempChartData.value];
|
||||
showEditDialog.value = false;
|
||||
};
|
||||
|
||||
// 监听对话框开启,更新临时数据
|
||||
watch(showEditDialog, (newVal) => {
|
||||
if (newVal) {
|
||||
tempChartData.value = [...chartData.value];
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.analysis-container {
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #f1f8e9 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #1565c0;
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
background: linear-gradient(45deg, #1976d2, #42a5f5);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 25px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 8px rgba(25, 118, 210, 0.3);
|
||||
}
|
||||
|
||||
.edit-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 15px rgba(25, 118, 210, 0.4);
|
||||
background: linear-gradient(45deg, #1565c0, #2196f3);
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 10px rgba(25, 118, 210, 0.1);
|
||||
text-align: center;
|
||||
transition: transform 0.3s ease;
|
||||
border-left: 4px solid #1976d2;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 20px rgba(25, 118, 210, 0.15);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #546e7a;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 10px rgba(25, 118, 210, 0.1);
|
||||
border: 3px solid #1976d2;
|
||||
}
|
||||
|
||||
.predict-chart-container {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 10px rgba(25, 118, 210, 0.1);
|
||||
border: 3px solid #dccc13;
|
||||
}
|
||||
|
||||
.map-chart {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.dialog-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(25, 118, 210, 0.1);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.dialog {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 30px rgba(25, 118, 210, 0.3);
|
||||
border-top: 4px solid #1976d2;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #e3f2fd;
|
||||
background: linear-gradient(135deg, #e3f2fd, #f8f9fa);
|
||||
}
|
||||
|
||||
.dialog-header h3 {
|
||||
margin: 0;
|
||||
color: #1565c0;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
color: #546e7a;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
padding: 20px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
flex: 0 0 120px;
|
||||
color: #546e7a;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.data-input {
|
||||
flex: 1;
|
||||
padding: 8px 12px;
|
||||
border: 2px solid #e3f2fd;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.data-input:focus {
|
||||
outline: none;
|
||||
border-color: #1976d2;
|
||||
box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.1);
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
padding: 20px;
|
||||
border-top: 1px solid #e3f2fd;
|
||||
background: linear-gradient(135deg, #e3f2fd, #f8f9fa);
|
||||
}
|
||||
|
||||
.cancel-btn,
|
||||
.save-btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background: #eceff1;
|
||||
color: #546e7a;
|
||||
border: 1px solid #cfd8dc;
|
||||
}
|
||||
|
||||
.cancel-btn:hover {
|
||||
background: #cfd8dc;
|
||||
color: #37474f;
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
background: linear-gradient(45deg, #1976d2, #42a5f5);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(25, 118, 210, 0.3);
|
||||
}
|
||||
|
||||
.save-btn:hover {
|
||||
background: linear-gradient(45deg, #1565c0, #2196f3);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(25, 118, 210, 0.4);
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue