feat: 数据分析-基础骨架

This commit is contained in:
bunny 2025-05-11 20:48:41 +08:00
parent 68ebd4b72f
commit 8ca95304c1
16 changed files with 498 additions and 141 deletions

2
.env
View File

@ -14,7 +14,7 @@ VITE_APP_URL=http://localhost:8801
VITE_STRICT_PORT=false
# 是否启用屏幕转vw适配可以选择 postcss-px-to-viewport-8-plugin || autofit
VITE_POST_CSS_PX_TO_VIEWPORT8_PLUGIN="autofit"
; VITE_POST_CSS_PX_TO_VIEWPORT8_PLUGIN="postcss-px-to-viewport-8-plugin"
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false
VITE_CDN=false

View File

@ -20,8 +20,8 @@ VITE_MOCK_DEV_SERVER=true
VITE_STRICT_PORT=false
# 是否启用屏幕转vw适配可以选择 postcss-px-to-viewport-8-plugin || autofit
VITE_POST_CSS_PX_TO_VIEWPORT8_PLUGIN="autofit"
#VITE_POST_CSS_PX_TO_VIEWPORT8_PLUGIN="postcss-px-to-viewport-8-plugin"
# VITE_POST_CSS_PX_TO_VIEWPORT8_PLUGIN="autofit"
VITE_POST_CSS_PX_TO_VIEWPORT8_PLUGIN="postcss-px-to-viewport-8-plugin"
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false
VITE_CDN=false

24
src/utils/copy.ts Normal file
View File

@ -0,0 +1,24 @@
/**
*
* @param text
*/
export const copy = (text: string) => {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
document.body.appendChild(textarea);
textarea.select();
try {
const success = document.execCommand('copy');
if (success) {
(window as any as any).$message.success('复制成功!');
} else {
(window as any).$message.success('复制失败!');
}
} catch (err: any) {
(window as any).$message.success('复制失败!请手动复制');
} finally {
document.body.removeChild(textarea);
}
};

45
src/utils/file.ts Normal file
View File

@ -0,0 +1,45 @@
export function downloadTextAsFile(text: string, filename: string) {
// 直接创建 File 对象(比 Blob 更高级)
const file = new File([text], filename, { type: 'text/plain' });
// 创建下载链接
const url = URL.createObjectURL(file);
const a = document.createElement('a');
a.href = url;
a.download = filename;
// 触发下载
document.body.appendChild(a);
a.click();
// 清理
requestIdleCallback(() => {
document.body.removeChild(a);
URL.revokeObjectURL(a.href);
});
}
export const downloadBlob = (response: any) => {
try {
// 从响应头获取文件名
const contentDisposition = response.headers['content-disposition'];
let fileName = 'download.zip';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename="?(.+)"/);
if (fileNameMatch && fileNameMatch[1]) {
fileName = fileNameMatch[1];
}
}
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
} catch (error) {
console.error(error);
}
};

View File

@ -1,11 +1,12 @@
<script lang="ts" setup>
import { onMounted, onUnmounted, ref } from 'vue';
import { useIntervalFn } from '@vueuse/core';
import { onMounted, ref } from 'vue';
import { renderEcharts, updateChart } from '@/views/big-data/charts/left-bottom';
const chart = ref<HTMLDivElement>();
const timer = ref(null);
/* 随机数据 */
const randomData = () => {
function random() {
return Array(12)
@ -21,18 +22,18 @@ const randomData = () => {
updateChart(data);
};
/* 模拟数据 */
const mockRandomData = () => {
useIntervalFn(() => {
randomData();
}, 1000);
};
onMounted(() => {
renderEcharts(chart);
randomData();
timer.value = setInterval(() => {
randomData();
}, 6000);
});
onUnmounted(() => {
clearInterval(timer.value);
timer.value = null;
mockRandomData();
});
</script>

View File

@ -1,10 +1,10 @@
<script lang="tsx" setup>
import { onMounted, onUnmounted, ref } from 'vue';
import { useIntervalFn } from '@vueuse/core';
import { onMounted, ref } from 'vue';
import { formatter } from '@/utils/chart';
import { ChartProgress } from '@/views/big-data/charts/left-middle';
const timer = ref(null);
const randomNumber = (range: number = 100) => {
return parseInt((Math.random() * range).toFixed(0));
};
@ -34,8 +34,8 @@ const renderItem = () => {
);
};
onMounted(() => {
timer.value = setInterval(() => {
const mockList = () => {
useIntervalFn(() => {
list.value = [
{ title: '经营总收入', amount: randomNumber(9999999), percent: randomNumber() },
{ title: '经营总收入', amount: randomNumber(9999999), percent: randomNumber() },
@ -43,11 +43,10 @@ onMounted(() => {
{ title: '经营总收入', amount: randomNumber(9999999), percent: randomNumber() },
];
}, 1000);
});
};
onUnmounted(() => {
clearInterval(timer.value);
timer.value = null;
onMounted(() => {
mockList();
});
</script>

View File

@ -11,5 +11,3 @@ import RightTop from '@/views/big-data/components/big-data-right/components/righ
<right-bottom />
</div>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,65 @@
import 'echarts/lib/component/dataZoom';
import type { Ref } from 'vue';
import echarts from '@/plugins/echarts';
import { debounceChart } from '@/utils/chart';
let myChart = null;
const option = {
tooltip: { trigger: 'item' },
legend: {
top: 'bottom',
data: ['品牌A', '品牌B', '品牌C', '品牌D', '品牌E', '品牌F'],
},
series: [
{
name: '品牌占比',
type: 'pie',
radius: [25, 100],
center: ['50%', '45%'],
roseType: 'area',
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
label: {
formatter: '{b}: {c}%',
},
data: [
{ value: 10.89, name: '品牌A' },
{ value: 30.89, name: '品牌B' },
{ value: 38.38, name: '品牌C' },
{ value: 27.47, name: '品牌D' },
{ value: 29.79, name: '品牌E' },
{ value: 50, name: '品牌F' },
{ value: 50, name: '品牌G' },
{ value: 39.72, name: '品牌H' },
],
},
],
};
/** 渲染图表 */
export const renderEcharts = (element: Ref<HTMLDivElement>) => {
myChart = echarts.init(element.value, null, {
renderer: 'canvas',
devicePixelRatio: window.devicePixelRatio,
});
debounceChart(myChart);
myChart.setOption(option);
};
/** 更新图表数据 */
export const updateChart = (option: Array<Array<number>>) => {
const series = myChart.getOption().series;
// series[0].data = option[0];
// series[1].data = option[1];
myChart.setOption({ series });
};

View File

@ -0,0 +1,97 @@
import type { Ref } from 'vue';
import echarts from '@/plugins/echarts';
import { debounceChart } from '@/utils/chart';
let myChart = null;
const myData = [
{
value: 137,
name: '数学',
},
{
value: 103,
name: '语文',
},
{
value: 124,
name: '英语',
},
{
value: 180,
name: '理综',
},
];
const option = {
color: ['#1aa3ff', '#04f9fa', '#16bd87', '#c961ff', '#7cfc12'],
tooltip: {
trigger: 'item',
formatter: '{b}{c} ({d}%)',
},
legend: {
bottom: '20%',
itemWidth: 10,
itemHeight: 10,
textStyle: {
color: '#fff',
fontSize: '14',
},
},
series: [
// 内圈
{
type: 'pie',
center: ['50%', '40%'],
radius: ['33.5%', '35.5%'],
silent: true, //取消高亮
label: { show: false, position: 'center' },
data: myData,
},
// 外圈
{
type: 'pie',
center: ['50%', '40%'],
radius: ['40%', '50%'],
label: { show: false, position: 'center' },
emphasis: {
label: {
show: true,
fontSize: 33,
lineHeight: 45,
formatter: (params) => {
return '{name|' + params.name + '}\n{value|' + params.value + '}';
},
rich: {
name: {
color: '#fff',
},
value: {
color: '#04F9FA',
},
},
},
},
data: myData,
},
],
};
/** 渲染图表 */
export const renderEcharts = (element: Ref<HTMLDivElement>) => {
myChart = echarts.init(element.value, null, {
renderer: 'canvas',
devicePixelRatio: window.devicePixelRatio,
});
debounceChart(myChart);
myChart.setOption(option);
};
/** 更新图表数据 */
export const updateChart = (option: Array<Array<number>>) => {
const series = myChart.getOption().series;
// series[0].data = option[0];
// series[1].data = option[1];
myChart.setOption({ series });
};

View File

@ -1,7 +1,21 @@
<script lang="ts" setup></script>
<script lang="ts" setup>
const titleList = [170582, 586220, 168902];
</script>
<template>
<div class="data-analyse-content">data-analyse-content</div>
<div class="data-analyse-content">
<ul class="data-analyse-content__header">
<li v-for="(item, index) in titleList" :key="index">
<h3>数据展示</h3>
<strong>{{ item }}</strong>
<span></span>
</li>
</ul>
<main class="data-analyse-content__body">body</main>
<footer class="data-analyse-content__footer">footer</footer>
</div>
</template>
<style lang="scss" scoped>
@ -10,6 +24,38 @@
width: 803px;
height: 970px;
background: darkgrey;
&__header {
display: flex;
justify-content: space-between;
margin: 15px 0 0 0;
h3 {
color: #94ddff;
font-size: 18px;
}
strong {
color: #fff;
font-size: 36px;
}
span {
margin: 0 0 0 8px;
color: #fff;
font-size: 16px;
}
}
&__body {
margin: 14px 0 0 0;
width: 803px;
height: 567px;
}
&__footer {
margin: 6px 0 0 0;
width: 860px;
height: 284px;
}
}
</style>

View File

@ -1,60 +0,0 @@
<script lang="ts" setup>
import PanelTitle1 from '@/components/PanelItem/PanelTitle/PanelTitle1.vue';
import Progress1 from '@/components/PanelItem/Progress/Progress1.vue';
const list = [
{ name: '科技有限公司', amount: 18888, percent: '50' },
{ name: '科技有限公司', amount: 18888, percent: '50' },
{ name: '科技有限公司', amount: 18888, percent: '50' },
{ name: '科技有限公司', amount: 18888, percent: '50' },
{ name: '科技有限公司', amount: 18888, percent: '50' },
];
</script>
<template>
<div class="data-analyse-left__center">
<PanelTitle1 title="销售公司销售设备数量占比" />
<ul>
<li v-for="(item, index) in list" :key="index">
<div class="info">
<span>{{ item.name }}</span>
<div class="info-left">
<span class="mr-[14px]">{{ item.amount }}</span>
<span class="mr-[9px] c-primary-secondary">环比 {{ item.percent }} %</span>
<i class="i-mdi-trending-up">s</i>
</div>
</div>
<Progress1 :progress="50" />
</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
.data-analyse-left__center {
li {
margin: 0 0 27px 0;
&:nth-child(1) {
margin: 35px 0 27px 0;
}
}
}
.info {
display: flex;
justify-content: space-between;
.info-left {
display: flex;
justify-content: space-between;
align-items: center;
i {
fonst-size: 15px;
color: var(--color-primary-secondary);
}
}
}
</style>

View File

@ -1,37 +0,0 @@
<script lang="ts" setup></script>
<template>
<div class="data-analyse-left__top">
<div>
<h1>销售设备总量</h1>
<span>
环比去年增长 52%
<i class="i-mdi-trending-up">s</i>
</span>
</div>
</div>
</template>
<style lang="scss" scoped>
.data-analyse-left__top {
width: 248px;
height: 53px;
h1 {
font-size: 20px;
}
span {
font-size: 15px;
color: var(--color-primary-secondary);
}
i {
position: absolute;
top: 18px;
left: 23px;
font-size: 24px;
color: var(--color-primary-secondary);
}
}
</style>

View File

@ -1,12 +1,68 @@
<script lang="ts" setup>
import LeftCenter from '@/views/data-analyse/components/data-analyse-left/components/left-center.vue';
import LeftTop from '@/views/data-analyse/components/data-analyse-left/components/left-top.vue';
import { onMounted, ref } from 'vue';
import PanelTitle1 from '@/components/PanelItem/PanelTitle/PanelTitle1.vue';
import Progress1 from '@/components/PanelItem/Progress/Progress1.vue';
import { renderEcharts } from '@/views/data-analyse/charts/left-brand';
const brandChartRef = ref();
const deviceTotal = ref('1010');
const companyList = [
{ name: '科技有限公司', amount: 18888, percent: '40' },
{ name: '科技有限公司', amount: 18888, percent: '50' },
{ name: '科技有限公司', amount: 18888, percent: '10' },
{ name: '科技有限公司', amount: 18888, percent: '80' },
{ name: '科技有限公司', amount: 18888, percent: '50' },
];
onMounted(() => {
renderEcharts(brandChartRef);
});
</script>
<template>
<!--顶部-->
<div class="data-analyse-left">
<left-top />
<left-center />
<div class="data-analyse-left__top">
<div>
<h1 class="font-size-[21px]">销售设备总量()</h1>
<div class="data-analyse-left__top-percent">
<span>环比去年增长 52%</span>
<i class="i-mdi-trending-up">s</i>
</div>
</div>
<ul class="flex">
<li v-for="(num, index) in deviceTotal" :key="index">{{ num }}</li>
</ul>
</div>
<!--中心区域-->
<div class="data-analyse-left__center">
<PanelTitle1 title="销售公司销售设备数量占比" />
<ul>
<li v-for="(item, index) in companyList" :key="index">
<div class="data-analyse-left__center-info">
<span>{{ item.name }}</span>
<div class="info-left">
<span class="mr-[14px]">{{ item.amount }}</span>
<span class="mr-[9px] c-primary-secondary">环比 {{ item.percent }} %</span>
<i class="i-mdi-trending-up" />
</div>
</div>
<Progress1 :progress="parseInt(item.percent)" />
</li>
</ul>
</div>
<!--底部区域-->
<div class="data-analyse-left__bottom">
<PanelTitle1 title="品牌占有率" />
<div ref="brandChartRef" class="data-analyse-left__bottom-chart" />
</div>
</div>
</template>
@ -15,5 +71,82 @@ import LeftTop from '@/views/data-analyse/components/data-analyse-left/component
width: 496px;
height: 970px;
color: white;
/* 顶部 */
&__top {
display: flex;
justify-content: space-between;
align-items: center;
width: 496px;
height: 100px;
&-percent {
display: flex;
justify-content: center;
align-items: center;
color: var(--color-primary-secondary);
font-size: 16px;
span {
font-size: 18px;
}
i {
font-size: 21px;
}
}
li {
margin: 0 4px;
width: 56px;
height: 53px;
line-height: 53px;
text-align: center;
font-size: 47px;
border: 1px solid var(--color-primary-secondary);
background-color: rgba(39, 63, 86, 0.34);
}
}
/* 中心区域 */
&__center {
margin: 47px 0 0 0;
li {
margin: 0 0 27px 0;
&:nth-child(1) {
margin: 35px 0 27px 0;
}
}
&-info {
display: flex;
justify-content: space-between;
.info-left {
display: flex;
justify-content: space-between;
align-items: center;
i {
color: var(--color-primary-secondary);
}
}
}
}
/* 底部区域 */
&__bottom {
margin: 25px 0 0 0;
width: 468px;
height: 387px;
&-chart {
margin: 0 0 25px 0;
width: 100%;
height: 100%;
}
}
}
</style>

View File

@ -1,13 +1,67 @@
<script lang="ts" setup></script>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import PanelTitle1 from '@/components/PanelItem/PanelTitle/PanelTitle1.vue';
import { renderEcharts } from '@/views/data-analyse/charts/right-header';
const footerChartRef = ref();
const renderFooterChart = () => {
renderEcharts(footerChartRef);
};
onMounted(() => {
renderFooterChart();
});
</script>
<template>
<div class="data-analyse-right">data-analyse-right</div>
<div class="data-analyse-right">
<header class="data-analyse-right__header">
<PanelTitle1 title="数据占有率" />
</header>
<main class="data-analyse-right__body">
<PanelTitle1 title="数据分析展示" />
</main>
<footer class="data-analyse-right__footer">
<PanelTitle1 title="数据展示统计" />
<div ref="footerChartRef" class="data-analyse-right__footer-chart" />
</footer>
</div>
</template>
<style lang="scss" scoped>
.data-analyse-right {
width: 496px;
height: 970px;
background: palegoldenrod;
h1 {
color: #fff;
}
&__header {
margin: 10px 0 0 0;
width: 100%;
height: 305px;
}
&__body {
margin: 10px 0 0 0;
width: 100%;
height: 350px;
}
&__footer {
margin: 10px 0 0 0;
width: 100%;
height: 257px;
&-chart {
width: 100%;
height: 100%;
}
}
}
</style>

View File

@ -20,7 +20,5 @@ import DataAnalyseRight from '@/views/data-analyse/components/data-analyse-right
padding: 0 20px;
width: 100%;
height: 100%;
background: cadetblue;
}
</style>

View File

@ -1,12 +1,11 @@
<script lang="ts" setup>
import { onMounted, onUnmounted, ref } from 'vue';
import { useIntervalFn } from '@vueuse/core';
import { onMounted, ref } from 'vue';
import { renderEcharts, updateChart } from '@/views/smart-park/charts/right-sidebar';
const weekDataChart = ref<HTMLDivElement>();
const timer = ref(null);
/** 随机数据 */
const randomData = () => {
function random() {
@ -18,7 +17,7 @@ const randomData = () => {
});
}
timer.value = setInterval(() => {
useIntervalFn(() => {
updateChart({ data1: random(), data2: random() });
}, 1000);
};
@ -27,11 +26,6 @@ onMounted(() => {
renderEcharts(weekDataChart);
randomData();
});
onUnmounted(() => {
clearInterval(timer.value);
timer.value = null;
});
</script>
<template>